diff --git a/AGENTS.md b/AGENTS.md index 18c80c80a8..2daaa2ea59 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,604 +8,51 @@ The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices. -## Repository Design References +## Adding or Updating CLI Commands -Before adding or reorganizing Specify CLI commands, read +Before adding, updating, or reorganizing Specify CLI commands, read [Specify CLI Command Architecture](design/cli.md). It defines command-module naming, private command phases, nested command groups, registration ownership, mirrored tests, and the rationale for making the CLI structure predictable for both humans and coding agents. ---- +## Adding or Updating Agent Integrations -## Quickstart — Add a New Integration in 5 Steps +Before adding or changing AI agent integrations, read +[Agent Integration Design](design/integration.md). It covers +delivery routes, output formats, registration, and install/uninstall ownership. -If you are new to the codebase and want to add support for a new AI agent, here is the shortest path from zero to a working integration: +## Adding or Updating Workflow Steps -1. **Choose a base class** — most agents only need `MarkdownIntegration`. See [Choose a base class](#1-choose-a-base-class). -2. **Create a subpackage** — add `src/specify_cli/integrations//__init__.py` with the required `key`, `config`, and `registrar_config` fields. -3. **Register it** — add one import and one `_register()` call in `src/specify_cli/integrations/__init__.py` (both alphabetical). -4. **Write a test file** — create `tests/integrations/test_integration_.py` (hyphens in the key become underscores in the filename). -5. **Run and verify** — use `specify init --integration ` to exercise the full install/uninstall cycle. +Before adding or changing workflow step types, read +[Workflow Step Design](design/workflow-step.md). It covers registration, +validation, execution, resume, and installed step packages. -Each step is expanded under [Adding a New Integration](#adding-a-new-integration). Note that agent **context files** (`CLAUDE.md`, `AGENTS.md`, …) are **not** handled by the integration — that is owned by the opt-in `agent-context` extension; see [Context file behavior](#4-context-file-behavior). +## Testing Executable Behavior ---- +Before changing code or configuration that runs or controls execution without +an LLM, read +[Testing deterministic behavior](CONTRIBUTING.md#testing-deterministic-behavior). +Behavioral changes need positive and negative coverage; bug fixes need +before-and-after regression evidence. -## Integration Architecture +## Branches and Agent Contributions -Each AI agent is a self-contained **integration subpackage** under `src/specify_cli/integrations//`. The subpackage exposes a single class that declares all metadata and inherits setup/teardown logic from a base class. Built-in integrations are then instantiated and added to the global `INTEGRATION_REGISTRY` by `src/specify_cli/integrations/__init__.py` via `_register_builtins()`. +When creating a branch, follow [Branch naming](CONTRIBUTING.md#branch-naming). +Before authoring commits, opening PRs, or posting review comments, read +[Agent-authored Git and review activity](CONTRIBUTING.md#agent-authored-git-and-review-activity). +Agent-authored commits and AI-generated PRs and comments each require their +own disclosure; a PR-body disclosure alone does not cover later activity. -```text -src/specify_cli/integrations/ -├── __init__.py # INTEGRATION_REGISTRY + _register_builtins() -├── base.py # IntegrationBase, MarkdownIntegration, TomlIntegration, YamlIntegration, SkillsIntegration -├── manifest.py # IntegrationManifest (file tracking) -├── claude/ # Example: SkillsIntegration subclass -│ └── __init__.py # ClaudeIntegration class -├── gemini/ # Example: TomlIntegration subclass -│ └── __init__.py -├── kilocode/ # Example: MarkdownIntegration subclass -│ └── __init__.py -├── copilot/ # Example: IntegrationBase subclass (custom setup) -│ └── __init__.py -├── docker_agent/ # Example: Docker Agent SkillsIntegration subclass -│ └── __init__.py -└── ... # One subpackage per supported agent -``` +## Other Contribution Guidance -The registry is the **single source of truth for Python integration metadata**. Supported agents, their directories, formats, capabilities, and context files are derived from the integration classes for the Python integration layer. - ---- - -## IntegrationManifest — File Tracking - -`manifest.py` provides the `IntegrationManifest` class, which records every file an integration installs. This record is what makes uninstall reliable and safe. - -### How it works - -`setup()` receives an `IntegrationManifest` and writes files through it rather than touching the filesystem directly: - -```python -# Produce a new file and record its hash for later verification. -manifest.record_file("commands/speckit.plan.md", processed_content) - -# Adopt a pre-existing file the integration is now responsible for. -manifest.record_existing(".vscode/settings.json") -``` - -The manifest is persisted at `.specify/integrations/.manifest.json` (one per integration, keyed by `key`) and stores a SHA-256 hash per file. When the user runs `specify integration uninstall `, `teardown()` delegates to `manifest.uninstall()`, which removes only files whose current hash still matches the recorded value — so files the user later edited by hand are skipped, not clobbered (use `specify integration uninstall --force` to remove modified tracked files anyway). - -### Why this matters - -Without hash-tracked manifests, uninstall would either remove files it should not (destructive) or leave orphans behind (messy). If you write a custom `setup()`, route **every** file you create through `manifest.record_file(...)` (or `record_existing(...)` for files you adopt) so uninstall can reason about them. - ---- - -## Adding a New Integration - -### 1. Choose a base class - -| Your agent needs… | Subclass | -|---|---| -| Standard markdown commands (`.md`) | `MarkdownIntegration` | -| TOML-format commands (`.toml`) | `TomlIntegration` | -| YAML recipe files (`.yaml`) | `YamlIntegration` | -| Skill directories (`speckit-/SKILL.md`) | `SkillsIntegration` | -| Fully custom output (companion files, settings merge, etc.) | `IntegrationBase` directly | - -Most agents only need `MarkdownIntegration` — a minimal subclass with zero method overrides. - -### 2. Create the subpackage - -Create `src/specify_cli/integrations//__init__.py`, where `` is the Python-safe directory name derived from ``: use the key as-is when it contains no hyphens (e.g., key `"gemini"` → `gemini/`), or replace hyphens with underscores when it does (e.g., key `"kiro-cli"` → `kiro_cli/`). The `IntegrationBase.key` class attribute always retains the original hyphenated value, since that is what the CLI and registry use. For CLI-based integrations (`requires_cli: True`), the `key` should match the actual CLI tool name (the executable users install and run) so CLI checks can resolve it correctly. For IDE-based integrations (`requires_cli: False`), use the canonical integration identifier instead. - -**Minimal example — Markdown agent (Kilo Code):** - -```python -"""Kilo Code IDE integration.""" - -from ..base import MarkdownIntegration - - -class KilocodeIntegration(MarkdownIntegration): - key = "kilocode" - config = { - "name": "Kilo Code", - "folder": ".kilo/", - "commands_subdir": "commands", - "install_url": None, - "requires_cli": False, - } - registrar_config = { - "dir": ".kilo/commands", - "legacy_dir": ".kilocode/workflows", - "format": "markdown", - "args": "$ARGUMENTS", - "extension": ".md", - } -``` - -**TOML agent (Gemini):** - -```python -"""Gemini CLI integration.""" - -from ..base import TomlIntegration - - -class GeminiIntegration(TomlIntegration): - key = "gemini" - config = { - "name": "Gemini CLI", - "folder": ".gemini/", - "commands_subdir": "commands", - "install_url": "https://github.com/google-gemini/gemini-cli", - "requires_cli": True, - } - registrar_config = { - "dir": ".gemini/commands", - "format": "toml", - "args": "{{args}}", - "extension": ".toml", - } -``` - -**Skills agent (Codex):** - -```python -"""Codex CLI integration — skills-based agent.""" - -from __future__ import annotations - -from ..base import IntegrationOption, SkillsIntegration - - -class CodexIntegration(SkillsIntegration): - key = "codex" - config = { - "name": "Codex CLI", - "folder": ".agents/", - "commands_subdir": "skills", - "install_url": "https://github.com/openai/codex", - "requires_cli": True, - } - registrar_config = { - "dir": ".agents/skills", - "format": "markdown", - "args": "$ARGUMENTS", - "extension": "/SKILL.md", - } - - @classmethod - def options(cls) -> list[IntegrationOption]: - return [ - IntegrationOption( - "--skills", - is_flag=True, - default=True, - help="Install as agent skills (default for Codex)", - ), - ] -``` - -#### Required fields - -| Field | Location | Purpose | -|---|---|---| -| `key` | Class attribute | Unique identifier; for CLI-based integrations (`requires_cli: True`), must match the CLI executable name | -| `config` | Class attribute (dict) | Agent metadata: `name`, `folder`, `commands_subdir`, `install_url`, `requires_cli` | -| `registrar_config` | Class attribute (dict) | Command output config: `dir`, `format`, `args` placeholder, file `extension` | - -**Key design rule:** For CLI-based integrations (`requires_cli: True`), `key` must be the actual executable name (e.g., `"cursor-agent"` not `"cursor"`). This ensures `shutil.which(key)` works for CLI-tool checks without special-case mappings. IDE-based integrations (`requires_cli: False`) should use their canonical identifier (e.g., `"kilocode"`, `"copilot"`). - -### 3. Register it - -In `src/specify_cli/integrations/__init__.py`, add one import and one `_register()` call inside `_register_builtins()`. Both lists are alphabetical: - -```python -def _register_builtins() -> None: - # -- Imports (alphabetical) ------------------------------------------- - from .claude import ClaudeIntegration - # ... - from .newagent import NewAgentIntegration # ← add import - # ... - - # -- Registration (alphabetical) -------------------------------------- - _register(ClaudeIntegration()) - # ... - _register(NewAgentIntegration()) # ← add registration - # ... -``` - -### 4. Context file behavior - -The Specify CLI carries **no agent-context state whatsoever**. Integration classes do **not** declare a `context_file`, and the CLI never creates, updates, removes, resolves, or migrates a context/instruction file (`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`, …). New integrations add nothing for context handling. - -Managing the "Spec Kit" section in the context file is fully owned by the bundled `agent-context` extension (`extensions/agent-context/`), which is a **full opt-in**: `specify init` does not install it. A user adds/enables it through the standard extension verbs, after which the extension's own bundled scripts maintain the context section. When the extension is absent or disabled, nothing in Spec Kit touches the context file. - -The extension reads its own config file at `.specify/extensions/agent-context/agent-context-config.yml`: - -```yaml -# Path to the coding agent context file managed by this extension -context_file: CLAUDE.md - -# Delimiters for the managed Spec Kit section -context_markers: - start: "" - end: "" -``` - -- The Specify CLI does **not** write this config. When `context_file` is empty, the extension's bundled scripts self-seed it by looking up the active integration's key in the extension's own `agent-context-defaults.json` map (`extensions/agent-context/scripts/bash/update-agent-context.sh`, `.ps1`, and `extensions/agent-context/scripts/python/update_agent_context.py`). The CLI registry is never consulted — all agent→context-file knowledge lives inside the extension. -- `context_markers.{start,end}` are read solely by the extension's scripts; they default to the Spec Kit markers shown above and can be customized by editing `agent-context-config.yml` directly. - -Existing projects created by older Spec Kit versions keep working: any previously written managed section or extension config is left intact and is only ever updated by the extension when run. - -Only add custom setup logic when the agent needs non-standard behavior. Integrations no longer require per-agent thin wrapper scripts or shared context-update dispatcher scripts — the `agent-context` extension is fully generic. - -### 5. Test it - -```bash -# Install into a test project -specify init my-project --integration - -# Verify files were created in the commands directory configured by -# config["folder"] + config["commands_subdir"] (for example, .kilo/commands/) -ls -R my-project/.kilo/commands/ - -# Uninstall cleanly -cd my-project && specify integration uninstall -``` - -Each integration also has a dedicated test file at `tests/integrations/test_integration_.py`. Note that hyphens in the key are replaced with underscores in the filename (e.g., key `cursor-agent` → `test_integration_cursor_agent.py`, key `kiro-cli` → `test_integration_kiro_cli.py`). Run it with: - -```bash -pytest tests/integrations/test_integration_.py -v -``` - -### 6. Optional overrides - -The base classes handle most work automatically. Override only when the agent deviates from standard patterns: - -| Override | When to use | Example | -|---|---|---| -| `command_filename(template_name)` | Custom file naming or extension | Copilot → `speckit.{name}.agent.md` | -| `options()` | Integration-specific CLI flags via `--integration-options` | Codex → `--skills` flag, Copilot → `--commands` flag | -| `setup()` | Custom install logic (companion files, settings merge) | Copilot → `speckit-/SKILL.md` (default) or `.agent.md` + `.prompt.md` + `.vscode/settings.json` (`--commands`) | -| `teardown()` | Custom uninstall logic | Rarely needed; base handles manifest-tracked files | - -**Example — Copilot (fully custom `setup`):** - -Copilot extends `IntegrationBase` directly because it supports two layouts. It scaffolds `speckit-/SKILL.md` under `.github/skills/` by default using composition with an internal `_CopilotSkillsHelper`. Its `--commands` mode creates `.agent.md` commands, companion `.prompt.md` files, and merges `.vscode/settings.json`. See `src/specify_cli/integrations/copilot/__init__.py` for the full implementation. - -### 7. Update Devcontainer files (Optional) - -For agents that have VS Code extensions or require CLI installation, update the devcontainer configuration files: - -#### VS Code Extension-based Agents - -For agents available as VS Code extensions, add them to `.devcontainer/devcontainer.json`: - -```jsonc -{ - "customizations": { - "vscode": { - "extensions": [ - // ... existing extensions ... - "[New Agent Extension ID]" - ] - } - } -} -``` - -#### CLI-based Agents - -For agents that require CLI tools, add installation commands to `.devcontainer/post-create.sh`: - -```bash -#!/bin/bash - -# Existing installations... - -echo -e "\n🤖 Installing [New Agent Name] CLI..." -# run_command "npm install -g [agent-cli-package]@latest" -echo "✅ Done" -``` - ---- - -## Command File Formats - -### Script References (`scripts:` frontmatter) - -Core command templates (`templates/commands/*.md`) that invoke a helper script declare it in a `scripts:` frontmatter block with one line per supported script type. The `{SCRIPT}` placeholder in the command body is replaced at install time with the entry matching the project's selected script type (`--script sh|ps|py`): - -```yaml -scripts: - sh: scripts/bash/setup-plan.sh --json - ps: scripts/powershell/setup-plan.ps1 -Json - py: scripts/python/setup_plan.py --json -``` - -| Key | Script type | Location | -| ---- | ---------------------- | -------------------------- | -| `sh` | POSIX shell (bash/zsh) | `scripts/bash/*.sh` | -| `ps` | PowerShell | `scripts/powershell/*.ps1` | -| `py` | Python | `scripts/python/*.py` | - -All three entries must be present and behaviorally equivalent — agents parse the same stdout contract (`FEATURE_DIR:…`, `AVAILABLE_DOCS:…`, `--json` shapes) regardless of which one runs. (The bundled `agent-context` and `git` extension command templates also invoke helpers but do not yet use `scripts:` frontmatter — see [Script Types and Migration](#script-types-and-migration).) - -### Markdown Format - -**Standard format:** - -```markdown ---- -description: "Command description" ---- - -Command content with {SCRIPT} and $ARGUMENTS placeholders. -``` - -**GitHub Copilot Chat Mode format:** - -```markdown ---- -description: "Command description" -mode: speckit.command-name ---- - -Command content with {SCRIPT} and $ARGUMENTS placeholders. -``` - -### TOML Format - -```toml -description = "Command description" - -prompt = """ -Command content with {SCRIPT} and {{args}} placeholders. -""" -``` - -### YAML Format - -Used by: Goose - -```yaml -version: 1.0.0 -title: "Command Title" -description: "Command description" -author: - contact: spec-kit -extensions: - - type: builtin - name: developer -activities: - - Spec-Driven Development -prompt: | - Command content with {SCRIPT} and {{args}} placeholders. -``` - -## Argument Patterns - -Different agents use different argument placeholders. The placeholder used in command files is always taken from `registrar_config["args"]` for each integration — check there first when in doubt: - -- **Markdown/prompt-based**: `$ARGUMENTS` (default for most markdown agents) -- **TOML-based**: `{{args}}` (e.g., Gemini) -- **YAML-based**: `{{args}}` (e.g., Goose) -- **Custom**: some agents override the default (e.g., Forge uses `{{parameters}}`) -- **Script placeholders**: `{SCRIPT}` (replaced with the resolved command from the template's `scripts:` frontmatter, per the project's `--script sh|ps|py` selection) -- **Agent placeholders**: `__AGENT__` (replaced with agent name) - -## Script Types and Migration - -Spec Kit ships every core workflow script in three interchangeable variants — POSIX shell (`sh`), PowerShell (`ps`), and Python (`py`) — selected per project with `specify init --script sh|ps|py`. Each core command template that invokes a helper script carries all three in its `scripts:` frontmatter (templates that don't call a script, e.g. `constitution`/`specify`, have no `scripts:` block); see [Script References](#script-references-scripts-frontmatter). - -### Why Python is recommended - -- **No extra runtime.** The `specify` CLI is already Python, so the interpreter is guaranteed present — `py` adds no new dependency. -- **Path toward a single source of truth.** The shell variants require paired `.sh` + `.ps1` maintenance and diverge on JSON handling (`jq` vs manual parsing). The Python variant avoids `jq` and is intended to eventually replace that dual-maintenance — but that consolidation has not happened yet: all three variants are still maintained in parallel (see the parity rule below). -- **Parity-tested.** The Python ports are covered by tests — output-parity tests against the shell scripts where the contract is stdout-based, and direct unit tests elsewhere — so the stdout contract agents rely on stays stable. - -### Defaults and availability - -- `py` is available today for the core command templates (via their `scripts:` frontmatter). The bundled extensions (`agent-context`, `git`) ship Python script variants on disk, but their command templates still hard-code the Bash/PowerShell invocations, so `--script py` does not yet route those extension commands to Python — wiring `py` into the extension command templates is tracked separately. -- Selection is per project: interactive `specify init` prompts for the script type, while non-interactive runs default to a shell variant by OS (`sh` on Linux/macOS, `ps` on Windows). `py` is chosen at the prompt or via `--script py`. -- `sh` and `ps` remain fully supported. Nothing is removed, and `py` is not yet the default. - -### Parity rule for contributors - -All three script types are first-class: any change to a workflow script must update `sh`, `ps`, and `py` together and keep their tests (parity and unit) green. Making `py` the default and eventually retiring `sh`/`ps` is future work gated on adoption, tracked under the script-unification epic ([#3277](https://github.com/github/spec-kit/issues/3277)) — not something to act on from this doc. - -## Special Processing Requirements - -Some agents require custom processing beyond the standard template transformations: - -### Copilot Integration - -GitHub Copilot uses skills by default, scaffolded as -`speckit-/SKILL.md` under `.github/skills/`. - -**Commands mode (`--commands`):** Copilot also supports a commands-based layout -via `--integration-options="--commands"`. When enabled: - -- Commands use `.agent.md` extension under `.github/agents/` -- Each command gets a companion `.prompt.md` file in `.github/prompts/` -- `.vscode/settings.json` is merged with prompt file recommendations -- `build_command_invocation()` returns bare args for `--agent` dispatch - -In the default skills mode, no companion prompts or VS Code settings merge are -created, and `build_command_invocation()` returns `/speckit-`. - -The two modes are mutually exclusive — a project uses one or the other: - -```bash -# Default skills mode: speckit-/SKILL.md under .github/skills/ -specify init my-project --integration copilot - -# Commands mode: .agent.md agents + .prompt.md companions + settings merge -specify init my-project --integration copilot --integration-options="--commands" -``` - -### Forge Integration - -Forge has special frontmatter and argument requirements: - -- Uses `{{parameters}}` instead of `$ARGUMENTS` -- Strips `handoffs` frontmatter key (Forge-specific collaboration feature) -- Injects `name` field into frontmatter when missing - -Implementation: Extends `MarkdownIntegration` with custom `setup()` method that: - -1. Inherits standard template processing from `MarkdownIntegration` -2. Adds extra `$ARGUMENTS` → `{{parameters}}` replacement after template processing -3. Applies Forge-specific transformations via `_apply_forge_transformations()` -4. Strips `handoffs` frontmatter key -5. Injects missing `name` fields - -### Goose Integration - -Goose is a YAML-format agent using Block's recipe system: - -- Uses `.goose/recipes/` directory for YAML recipe files -- Uses `{{args}}` argument placeholder -- Produces YAML with `prompt: |` block scalar for command content - -Implementation: Extends `YamlIntegration` (parallel to `TomlIntegration`): - -1. Processes templates through the standard placeholder pipeline -2. Extracts title and description from frontmatter -3. Renders output as Goose recipe YAML (version, title, description, author, extensions, activities, prompt) -4. Uses `yaml.safe_dump()` for header fields to ensure proper escaping - -## Branch Naming Convention - -Branches follow one of two patterns depending on whether an issue exists: - -```text -/- # when an issue is created first -/ # when no issue exists (PR-only changes) -``` - -When an issue exists, include its number immediately after the prefix — this is what makes branches traceable. For small or self-contained changes that go straight to a PR without a tracking issue, omit the number. - -| Prefix | When to use | Example | -|---|---|---| -| `feat/` | New features | `feat/2342-workflow-cli-alignment` | -| `fix/` | Bug fixes | `fix/2653-paths-only-validation` | -| `docs/` | Documentation changes | `docs/2677-branch-naming-convention`, `docs/update-landing-stats` | -| `community/` | Community catalog additions | `community/2492-add-mde-extension` | -| `chore/` | Maintenance, tooling, CI | `chore/2366-editorconfig` | - -**Rules:** - -1. Include the issue number when one exists — this is what makes branches traceable -2. Use kebab-case for the slug -3. Keep the slug short — enough to identify the work without looking up the issue - ---- - -## Agent Disclosure for PRs, Comments, and Commits - -Disclosure is **continuous**, not a one-time event. A single AI-disclosure paragraph in the PR body does **not** cover the commits and replies you add during review rounds. Each of the following must independently attest to agent authorship. - -### Opening pull requests - -- Before opening a pull request, check whether the account that will file it already has three open pull requests in this repository. -- If so, alert the user that additional submissions may receive lower review priority and ask for explicit permission to proceed. Do not assume consent. If the user is unavailable to provide that permission, including during autonomous or non-interactive operation, do not open the pull request. Preserve the work on a branch and report that confirmation is required. -- Repository-owned `gh-aw` maintenance workflows are exempt from this open-PR count check and confirmation requirement. - -### Commits - -- **Every commit you author must carry an `Assisted-by:` trailer** identifying the agent and whether it acted autonomously or under direct human supervision, for example: - - ``` - Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) - ``` - - Name the actual model you are running as — the same hard requirement contributors are held to in [CONTRIBUTING.md](./CONTRIBUTING.md#ai-contributions-in-spec-kit). Only in the rare case where an agent genuinely cannot determine its own model may it write `model: unknown` instead. - Use `supervised` instead of `autonomous` only when a human actually authored or line-by-line reviewed the change before it was committed. -- **Never push solo-authored commits that hide agent authorship behind the operator's git identity.** If an agent generated the change, the trailer must say so even when the commit is attributed to a human account. -- Preserve any tool-generated `Co-authored-by:` trailers (e.g. Copilot Autofix) — do not strip them to make a commit look hand-written. - -### Comments - -- If you are an agent working on behalf of a human, **disclose your identity in your PR comment** — name the agent, its model, the settings/mode (e.g. autonomous vs. human-supervised), the extent of AI involvement in the comment, and the human you are acting for (e.g., "Posted on behalf of @user by GitHub Copilot (model: Claude Opus 4.8, autonomous); comment fully AI-drafted."). This matches the disclosure the public [CONTRIBUTING.md](./CONTRIBUTING.md#ai-contributions-in-spec-kit) policy requires for AI-generated PR responses and comments. -- **Re-state agent identity in each review-round summary comment.** A prior PR-body disclosure does not cover later comments or commits. -- Post **one** top-level summary comment per review round listing what changed and the commit SHA. Do not reply on every individual comment. -- Reply inline only when context is needed (disagreement, deferral, non-obvious fix). Keep it to a sentence or two. -- **Never click "Resolve conversation"** — that belongs to the reviewer or PR author. -- No emoji, no celebratory framing, no checklist mirroring the reviewer's items, no restating what the reviewer wrote. -- Re-request review once per round (when all feedback is addressed), not after every intermediate push. - -### Anti-patterns (do not do these) - -- **Do not** reply "Done" or push a "fix" within seconds/minutes of a review event without disclosing that the response or commit was agent-generated. Speed of turnaround is not a substitute for attestation — a near-instant tested code change is itself a signal of automation and must be disclosed as such. -- **Do not** claim "reviewed, tested, and understood by me" for commits that were authored and pushed automatically in response to a review trigger. If the loop is automated, disclose it as automated. - ---- +For contribution or repository-workflow questions not covered above, or when +the applicable guidance is unclear, read [CONTRIBUTING.md](CONTRIBUTING.md) +before acting. ## Common Pitfalls -1. **Using shorthand keys for CLI-based integrations**: For CLI-based integrations (`requires_cli: True`), the `key` must match the executable name (e.g., `"cursor-agent"` not `"cursor"`). `shutil.which(key)` is used for CLI tool checks — mismatches require special-case mappings. IDE-based integrations (`requires_cli: False`) are not subject to this constraint. -2. **Reintroducing context handling into the CLI**: The opt-in `agent-context` extension owns everything about context files — including the per-agent default mapping in `agent-context-defaults.json`. Integration classes must **not** declare a `context_file`, and no CLI code should read, write, resolve, or migrate context files. All context-file logic lives in `.specify/extensions/agent-context/` and its bundled scripts. -3. **Incorrect `requires_cli` value**: Set to `True` only for agents that have a CLI tool; set to `False` for IDE-based agents. -4. **Wrong argument format**: Use `$ARGUMENTS` for Markdown agents, `{{args}}` for TOML agents. -5. **Skipping registration**: The import and `_register()` call in `_register_builtins()` must both be added. -6. **Running tests against the wrong environment**: Always run the suite inside this working tree's own virtualenv (`uv sync --extra test` then `.venv/bin/python -m pytest`, or activate the venv first). A bare `uv run pytest` can resolve to an ambient/global interpreter whose editable `.pth` points at a *different* worktree. The failure is sneaky: test collection still imports `specify_cli` successfully, but newly-added subpackages (e.g. a fresh `specify_cli/bundler/`) resolve as a stale namespace package and raise `ModuleNotFoundError`. If a brand-new subpackage imports under `python -c` but not under pytest, suspect environment contamination, not your code. - ---- - -## Error Handling and Debugging - -### Common Errors and Fixes - -| Symptom | Likely Cause | Fix | -|---|---|---| -| `Integration '' not found` | Missing `_register()` call | Add `_register(Integration())` inside `_register_builtins()` | -| `NameError: name 'Integration' is not defined` at startup | Missing import | Add `from . import Integration` inside `_register_builtins()` | -| CLI check fails for a `requires_cli: True` agent | `key` does not match the executable name | Set `key` to the exact name `shutil.which(key)` must resolve (e.g. `"cursor-agent"`, not `"cursor"`) | -| Command files have the wrong argument syntax | Wrong `args` value in `registrar_config` | Use `$ARGUMENTS` for Markdown agents, `{{args}}` for TOML/YAML agents, or the agent's custom placeholder | -| `ModuleNotFoundError` on a brand-new subpackage under pytest only | Ambient interpreter with a stale editable `.pth` | Run inside this tree's own venv (see Common Pitfall 6) | -| Uninstall leaves files behind, or skips files you expected removed | Files not recorded via the manifest, or their hash changed after install | Route every created file through `manifest.record_file(...)`; user-edited files are intentionally skipped unless `force=True` | -| Context file (`CLAUDE.md`, etc.) not updated | Expecting the CLI to manage it | Context files are owned by the opt-in `agent-context` extension, not the integration — see [Context file behavior](#4-context-file-behavior) | - -### Debugging Tips - -**Inspect the manifest** to see what an installed integration tracks: - -```bash -cat .specify/integrations/.manifest.json -``` - -**Verify a CLI tool is detected** before debugging a `requires_cli` agent: - -```bash -which # Should print the executable path if installed -``` - -**Verify the installed output structure** after `specify init`: - -```bash -find my-project/ -type f -``` - ---- - -## Contribution Checklist - -Before opening or merging an integration PR, confirm the following: - -- [ ] Added the integration subpackage under `src/specify_cli/integrations//`. -- [ ] Registered it (import **and** `_register()`) in `src/specify_cli/integrations/__init__.py`, both alphabetical. -- [ ] Added or updated tests in `tests/integrations/test_integration_.py`. -- [ ] Verified the install/uninstall flow with `specify init --integration `. -- [ ] Did **not** add `context_file` handling to the CLI (that belongs to the `agent-context` extension). -- [ ] Updated devcontainer files if the agent needs a VS Code extension or CLI install step. -- [ ] Updated this guide or other relevant docs if the integration has special setup or limitations. - ---- - -*This documentation should be updated whenever new integrations are added to maintain accuracy and completeness.* +- **Running tests against the wrong environment:** Run the suite inside this + worktree's own virtualenv (`uv sync --extra test` then + `.venv/bin/python -m pytest`). A bare `uv run pytest` can pick up an + editable install from another worktree and fail to import new subpackages. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d5ac2947e..e45ef52228 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ On [GitHub Codespaces](https://github.com/features/codespaces) it's even simpler 1. Fork and clone the repository 1. Configure and install the dependencies: `uv sync --extra test` 1. Make sure the CLI works on your machine: `uv run specify --help` -1. Create a new branch: `git checkout -b /-` (see [Branch naming](#branch-naming) below) +1. Create a new branch (see [Branch naming](#branch-naming) below) 1. Make your change, add tests, and make sure everything still works 1. Test the CLI functionality with a sample project if relevant 1. Push to your fork and submit a pull request @@ -55,7 +55,7 @@ Here are a few things you can do that will increase the likelihood of your pull - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). - Test your changes with the Spec-Driven Development workflow to ensure compatibility. -Accounts with three open pull requests may continue submitting changes, but additional submissions may be placed behind contributions from other authors in the review queue. Coding agents should disclose this possibility and obtain the filer's confirmation before opening another pull request. Repository-owned `gh-aw` maintenance workflows do not require this confirmation. +Accounts with three open pull requests may continue submitting changes, but additional submissions may be placed behind contributions from other authors in the review queue. Coding agents must follow the [agent-authored PR rules](#agent-authored-git-and-review-activity) before opening another pull request. ### Evidence gate @@ -137,7 +137,9 @@ fix to a broken entry is still an update and needs the same validation. Always p ### Branch naming -We recommend naming branches as `/-`, where `` is the issue or PR number (whichever comes first) and `` is one of: +When an issue exists, name the branch `/-`. +For PR-only changes without an issue, use `/` instead. +Choose the prefix from: | Prefix | When to use | Example | |---|---|---| @@ -147,7 +149,9 @@ We recommend naming branches as `/-`, where `` | `community/` | Community catalog additions | `community/2492-add-mde-extension` | | `chore/` | Maintenance, tooling, CI | `chore/2366-editorconfig` | -Including the issue or PR number makes branches traceable — especially useful since the project uses squash merges and `git branch --merged` won't detect merged branches. If you start with a PR (no issue), use the PR number once it's assigned. +Put an existing issue number immediately after the prefix to make the branch +traceable; do not add a PR number later for PR-only changes. Keep the slug +short, descriptive, and kebab-case. ## Development workflow @@ -179,6 +183,22 @@ When working on spec-kit: 3. Test script functionality in the `scripts/` directory 4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made +### Testing deterministic behavior + +Behavioral changes to repository-maintained code or configuration that runs or +controls execution without an LLM require tests for both intended behavior +(positive cases) and relevant invalid inputs, failures, or prevented behavior +(negative cases). This includes Python, shell, and PowerShell code in the CLI, +core scripts, extensions, presets, and tooling; workflow step types; and +execution wiring such as script selection, workflow shell steps, and CI `run:` +steps. Existing tests count when they cover the changed behavior. + +Bug fixes also need a regression test that fails before the fix and passes +afterward. If a baseline or an execution path cannot be tested automatically, +explain the limitation and provide reproducible evidence for the affected +success and failure paths. Prompt-only and other non-behavioral edits do not +require invented tests. + ### Recommended validation flow For the smoothest review experience, validate changes in this order: @@ -346,6 +366,8 @@ That being said, if you are using any kind of AI assistance (e.g., agents, ChatG If your PR responses or comments are being generated by an AI, disclose that as well, naming the same agent, model(s), settings/mode, and extent. As an exception, trivial spacing or typo fixes don't need to be disclosed, so long as the changes are limited to small parts of the code or short phrases. The catalog-submission issue forms (extension, preset, and bundle submissions) feed content-neutral validation automation rather than code review, so they carry no disclosure field and are exempt from this requirement. Spec Kit's own bundled agentic workflows (the automated bug-fix and community-catalog prompts under `.github/workflows/`) are likewise exempt: they run as a fixed, known agent and open draft PRs for maintainer review, so their agent identity is inherent to the workflow rather than self-declared per contribution. +The trivial-fix exception does not waive the disclosures below for agents acting +on behalf of a contributor; the bundled workflows retain their exception. An example disclosure: @@ -365,6 +387,39 @@ what extent, which in turn helps us judge whether our [`AGENTS.md`](./AGENTS.md) In a perfect world, AI assistance would produce equal or higher quality work than any human. That isn't the world we live in today, and in most cases where human supervision or expertise is not in the loop, it's generating code that cannot be reasonably maintained or evolved. +### Agent-authored Git and review activity + +For an agent acting on behalf of a contributor, disclosure is continuous: the +PR body, each agent-authored commit, and each AI-generated PR comment or reply +must independently identify the agent's involvement. A PR-body disclosure does +not cover later commits or review rounds. + +- **Before opening a PR:** Check whether the filing account already has three + open PRs in this repository. If so, explain that another submission may + receive lower review priority and obtain the filer's explicit permission. + Without it, including during autonomous operation, leave the work on a branch + and report that confirmation is required. Repository-owned `gh-aw` maintenance + workflows are exempt from this count check. +- **For every agent-authored commit:** Include an `Assisted-by:` trailer naming + the actual agent and model and whether it acted `autonomous` or `supervised`, + for example, `Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)`. + Use `model: unknown` only if the model genuinely cannot be determined. Use + `supervised` only if a human authored or line-by-line reviewed the change + before commit. Do not hide agent authorship behind the operator's git + identity or remove tool-generated `Co-authored-by:` trailers. +- **For each AI-generated PR comment:** Identify the contributor on whose + behalf it is posted, the agent, model, settings/mode, and extent of AI + involvement. Restate this in every review-round summary, not just the PR + body. Post one top-level summary per round with the changes and commit SHA; + reply inline only for disagreement, deferral, or a non-obvious fix, and keep + those replies brief. Do not resolve conversations on a reviewer's behalf. + Avoid emoji, celebratory framing, checklist mirroring, and restating the + reviewer's comments. Re-request review once, after all feedback is addressed. + +Fast responses do not replace disclosure: do not post an unexplained "Done" or +push an undisclosed fix moments after review. Do not claim personal human review +or testing for an automated change; describe the automation truthfully. + ### What we're looking for When submitting AI-assisted contributions, please ensure they include: diff --git a/design/integration.md b/design/integration.md new file mode 100644 index 0000000000..2bcb516022 --- /dev/null +++ b/design/integration.md @@ -0,0 +1,108 @@ +# Agent Integration Design + +Integrations adapt the shared Spec Kit workflows to an AI coding agent. Their +**availability** (built-in, generic, or catalog-only) is separate from their +**output format** (commands, recipes, skills, or a custom layout). The Python +integration registry owns installation behavior; catalogs provide discovery, +not executable integration implementations. + +## Availability + +| Route | What ships | How users get it | +|---|---|---| +| Built-in | A registered class under `src/specify_cli/integrations/` and, for discovery, an entry in `integrations/catalog.json` | `specify init my-project --integration copilot` or, in an initialized project, `specify integration install copilot` | +| Generic | The registered `generic` integration, with a user-supplied `--commands-dir` and optional `--skills` | `specify init my-project --integration generic --integration-options="--commands-dir .agent/commands"` | +| Community | Metadata in `integrations/catalog.community.json` pointing to an external project | Discover with `specify integration list --catalog` or `search`; obtain and vet it from its source | + +The default community catalog is discovery-only. A catalog entry (including +one in a custom catalog) does **not** register a Python integration or make +`specify integration install ` work: installation resolves keys through +`INTEGRATION_REGISTRY`. See [catalog contribution guidance](../integrations/CONTRIBUTING.md) +for descriptor and submission details. + +## Built-in contract + +Each built-in agent has one Python-safe subpackage: `copilot` lives in +`src/specify_cli/integrations/copilot/` and exposes `CopilotIntegration`. +Hyphenated keys use underscores in package names. Its class declares: + +- `key`: unique user-facing identifier. CLI-backed integrations normally use + the executable name: tool checks use the key and runtime dispatch defaults + to it. Agents with a different executable must handle both paths explicitly; + IDE-only agents use their canonical identifier. +- `config`: agent name, folder, commands subdirectory, install URL, and + `requires_cli`. +- `registrar_config`: output directory, format, argument placeholder, and + file extension. + +Import and `_register()` the class in `src/specify_cli/integrations/__init__.py` +(both lists alphabetically). This registry is the source of built-in Python +integration behavior. If the agent supports non-interactive workflows, implement +`build_exec_args()` with the full base signature; `options()` declares +install-time `--integration-options`, not per-workflow runtime options. +Agent-specific native events can be declared on the integration. Set +`multi_install_safe = True` only for a static, non-overlapping agent root and +command directory; shared dynamic paths are not safe by default. + +## Output flavors + +Choose the smallest base class that matches the agent's native format. The +format bases render shared `templates/commands/*.md`; `IntegrationBase.setup()` +copies templates raw unless overridden. Paths below are relative to each +agent's configured root. + +| Flavor | Base class | Typical output | Arguments | +|---|---|---|---| +| Markdown commands | `MarkdownIntegration` | `commands/speckit.plan.md` | `$ARGUMENTS` | +| TOML commands | `TomlIntegration` | `commands/speckit.plan.toml` | `{{args}}` | +| YAML recipes | `YamlIntegration` | `recipes/speckit.plan.yaml` | `{{args}}` | +| Agent skills | `SkillsIntegration` | `skills/speckit-plan/SKILL.md` | `$ARGUMENTS` | +| Nonstandard or dual-mode | `IntegrationBase` or a targeted override of a format base | Agent-specific files, companions, or settings | Agent-specific | + +`registrar_config["args"]` selects the installed argument syntax; +`command_filename()` and `setup()` are override points when the native layout +demands them. Keep mode selection, invocation spelling, and registration in +sync. Agent-specific layouts and options belong in the integration code and +the [supported-integrations reference](../docs/reference/integrations.md). + +Core templates that call scripts declare `sh`, `ps`, and `py` commands in +`scripts:` frontmatter; template processing replaces `{SCRIPT}` with the +selected variant. `py` is opt-in; non-interactive init defaults to `sh` on +POSIX or `ps` on Windows. Maintain equivalent stdout behavior across all three. +Bundled extension commands do not yet use this core-template script routing. +`__AGENT__` and command references are resolved during rendering, not by +adding per-agent wrapper scripts. + +## Ownership and lifecycle + +An installation records its files and SHA-256 hashes in +`.specify/integrations/.manifest.json`. Custom `setup()` code must track +files it creates via the manifest (`record_file()` or the base class's +write-and-record helpers). Do not track a pre-existing user file merely because +you merged settings into it: unchanged tracked files are deleted on uninstall. +`teardown()` preserves modified tracked files by default; `--force` can remove +them. Keep agent-specific settings and events consistent with that lifecycle. + +The integration does **not** own agent context files such as `AGENTS.md` or +`CLAUDE.md`. The opt-in `extensions/agent-context/` owns their defaults, +configuration, and managed sections; do not add `context_file` fields or +context-file handling to the CLI. `specify init` does not enable the extension +implicitly. Extensions and presets register command or skill overrides for the +current default integration, not every installed integration. + +## Adding an agent + +1. Run `specify integration scaffold my-agent --type markdown` from this + repository (`toml`, `yaml`, and `skills` are also supported), or start with + a custom class only when necessary. +2. Review the generated `config` and `registrar_config`; register the class + alphabetically and add a matching entry to `integrations/catalog.json`. +3. Add focused coverage in `tests/integrations/test_integration_.py` + for metadata, generated output, installation, and uninstall (including + preservation of edited files where applicable). +4. Exercise `specify init my-project --integration ` and the install/uninstall + lifecycle; update the [supported agents](../docs/reference/integrations.md) + and devcontainer setup if the agent needs additional tooling. + +The scaffold creates a package and test skeleton, **not** registry or catalog +entries. Prefer existing bases and shared processing over copied setup loops. diff --git a/design/workflow-step.md b/design/workflow-step.md new file mode 100644 index 0000000000..e9ff094298 --- /dev/null +++ b/design/workflow-step.md @@ -0,0 +1,64 @@ +# Workflow Step Design + +A workflow step type defines what a `type:` in workflow YAML does. This is an +extension point of the workflow engine, distinct from an agent integration: +integrations dispatch work to an agent; steps validate and execute workflow +behavior. For the overall engine, see +[Workflow System Architecture](../workflows/ARCHITECTURE.md); for YAML usage, +see [Workflows](../docs/reference/workflows.md). + +## Delivery and registration + +| Route | Source | Registration | +|---|---|---| +| Built-in | `src/specify_cli/workflows/steps//__init__.py` | Explicit import and `_register_step()` in `src/specify_cli/workflows/__init__.py` | +| Installed | Package under `.specify/workflows/steps//`, containing `step.yml` and `__init__.py` | `load_custom_steps(project_root)` loads a matching `StepBase` subclass at workflow run/resume | + +`STEP_REGISTRY` maps each `type_key` to a single step instance. Built-in keys +are snapshotted in `BUILTIN_STEP_TYPES` before project steps are loaded; do not +infer built-in status from the mutable registry. Installed packages can be +found through step catalogs and added with `specify workflow step add `. +The default community catalog is discovery-only, not an install or trust +endorsement. Review external step code before installing it: loading a package +imports and executes its Python module. + +## Step contract + +Subclass `StepBase` from `src/specify_cli/workflows/base.py`, set `type_key` +to the workflow YAML `type`, and implement +`execute(config: dict[str, Any], context: StepContext) -> StepResult`. +Override `validate(config)` for step-specific errors; the base validator +requires an `id`. Omitting `type` in YAML selects the built-in `command` step. + +`StepContext` supplies inputs, previous step results, workflow defaults, run +and project paths, and iteration/resume context. Return a `StepResult` with a +`StepStatus`, an output mapping, and an error message on failure. The engine +records the result for later `{{ steps..output.* }}` expressions and +persists run state. `PAUSED` stops for resume; `FAILED` normally stops the run +unless `continue_on_error: true` is set (an explicit abort always stops). +`next_steps` supplies nested steps for control flow. + +The engine calls `validate()` during workflow validation but does not +automatically validate a definition passed to `execute()`. Guard invalid +configurations in `execute()` too, returning a failed result rather than a +successful default or an unhandled exception. Resume restarts the current +top-level step; a pause inside nested steps re-runs their parent and nested +body. Design side effects accordingly. + +The registry holds one shared instance per type. Concurrent `fan-out` can +invoke that instance from multiple threads: keep execution stateless and +thread-safe, with per-run data in `config` and `context`, not on `self`. + +## Adding a step type + +1. Implement the step in its own `steps//` subpackage and register + it explicitly with a unique `type_key`. For an external package, provide + `step.yml` with a matching `step.type_key` and a matching subclass in + `__init__.py`; catalog discovery alone does not register the code. +2. Test valid and invalid configurations, status/output/error behavior, and + resume or nested/concurrent execution when relevant. Registry and engine + coverage lives in `tests/test_workflows.py`; focused step suites may live + under `tests/workflows/`. +3. Update the [workflow reference](../docs/reference/workflows.md) when a + built-in step changes the YAML users can write. Keep agent-specific CLI + dispatch in integrations rather than duplicating it in step types. diff --git a/integrations/CONTRIBUTING.md b/integrations/CONTRIBUTING.md index 77a50d4d98..3009450344 100644 --- a/integrations/CONTRIBUTING.md +++ b/integrations/CONTRIBUTING.md @@ -10,11 +10,11 @@ Built-in integrations are maintained by the Spec Kit core team and ship with the 1. **Create the integration subpackage** under `src/specify_cli/integrations//` — `` matches the integration key when it contains no hyphens (e.g., `gemini`), or replaces hyphens with underscores when it does (e.g., key `cursor-agent` → directory `cursor_agent/`, key `kiro-cli` → directory `kiro_cli/`). Python package names cannot use hyphens. -2. **Implement the integration class** extending `MarkdownIntegration`, `TomlIntegration`, or `SkillsIntegration` +2. **Implement the integration class** using the appropriate base class from [integration design](../design/integration.md) 3. **Register the integration** in `src/specify_cli/integrations/__init__.py` 4. **Add tests** under `tests/integrations/test_integration_.py` 5. **Add a catalog entry** in `integrations/catalog.json` -6. **Update documentation** in `AGENTS.md` and `README.md` +6. **Update documentation** in [integration design](../design/integration.md), [supported integrations](../docs/reference/integrations.md), and this catalog's README as needed ### Catalog Entry Format @@ -43,7 +43,7 @@ Community integrations are contributed by external developers and listed in `int ### Prerequisites -1. **Working integration** — tested with `specify integration install` +1. **Working external integration** — distributed from its own repository; a community catalog listing alone does not make it installable through `specify integration install` 2. **Public repository** — hosted on GitHub or similar 3. **`integration.yml` descriptor** — valid descriptor file (see below) 4. **Documentation** — README with usage instructions