* Rewrite AGENTS.md for integration subpackage architecture Replaces the old AGENT_CONFIG dict-based 7-step process with documentation reflecting the integration subpackage architecture shipped in #1924. Removed: Supported Agents table, old step-by-step guide referencing AGENT_CONFIG/release scripts/case statements, Agent Categories lists, Directory Conventions section, Important Design Decisions section. Kept: About Spec Kit and Specify, Command File Formats, Argument Patterns, Devcontainer section. Added: Architecture overview, decision tree for base class selection, configure/register/scripts/test/override steps with real code examples from existing integrations (Windsurf, Gemini, Codex, Copilot). Agent-Logs-Url: https://github.com/github/spec-kit/sessions/71b25c53-7d0c-492a-9503-f40a437d5ece Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * Fix JSONC comment syntax in devcontainer example Agent-Logs-Url: https://github.com/github/spec-kit/sessions/71b25c53-7d0c-492a-9503-f40a437d5ece Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * docs(AGENTS.md): address Copilot PR review comments - Clarify that integrations are registered by _register_builtins() in __init__.py, not self-registered at import time - Scope the key-must-match-executable rule to CLI-based integrations (requires_cli: True); IDE-based integrations use canonical identifiers - Replace <commands_dir> placeholder in test snippet with a concrete example path (.windsurf/workflows/) - Document that hyphens in keys become underscores in test filenames (e.g. cursor-agent -> test_integration_cursor_agent.py) - Note that the argument placeholder is integration-specific (registrar_config["args"]); add Forge's {{parameters}} as an example - Apply consistency fixes to Required fields table, Key design rule callout, and Common Pitfalls #1 * docs(AGENTS.md): clarify scripts path uses Python-safe package_dir not key The scripts step previously referenced src/specify_cli/integrations/<key>/scripts/ but for hyphenated keys the actual directory is underscored (e.g. kiro-cli -> kiro_cli/). Rename the placeholder to <package_dir> and add a note explaining: - <package_dir> matches <key> for non-hyphenated keys - <package_dir> uses underscores for hyphenated keys (e.g. kiro-cli -> kiro_cli/) - IntegrationBase.key always retains the original hyphenated value Addresses: https://github.com/github/spec-kit/pull/2119#discussion_r3054946896 * docs(AGENTS.md): use <key_with_underscores> in pytest example command The pytest command previously used <key> as a placeholder, but test filenames always use underscores even for hyphenated keys. This was internally inconsistent since the preceding sentence already explained the hyphen→underscore mapping. Switch to <key_with_underscores> to match the actual filename on disk. Addresses: https://github.com/github/spec-kit/pull/2119#discussion_r3054962863 * docs(AGENTS.md): use <package_dir> in step 2 subpackage path The path src/specify_cli/integrations/<key>/__init__.py was inaccurate for hyphenated keys (e.g. kiro-cli lives in kiro_cli/, not kiro-cli/). Rename the placeholder to <package_dir>, define it inline (hyphens become underscores), and note that IntegrationBase.key always retains the original hyphenated value. Addresses: https://github.com/github/spec-kit/pull/2119#discussion_r3058050583 * docs(AGENTS.md): qualify 'single source of truth' to Python metadata only The registry is only authoritative for Python integration metadata. Context-update dispatcher scripts (bash + PowerShell) still require explicit per-agent cases and maintain their own supported-agent lists until they are migrated to registry-based dispatch. Tighten the claim to avoid misleading contributors into skipping the script updates. Addresses: https://github.com/github/spec-kit/pull/2119#pullrequestreview-4083090261 * docs(AGENTS.md): mention ValidateSet update in PowerShell dispatcher step The update-agent-context.ps1 script has a [ValidateSet(...)] on the AgentType parameter. Without adding the new key to that list, the script rejects the argument before reaching Update-SpecificAgent. Add this as an explicit step alongside the switch case and Update-AllExistingAgents. Addresses: https://github.com/github/spec-kit/pull/2119#pullrequestreview-4083217694 * fix(integrations): sort codebuddy before codex in _register_builtins() Both the import list and the _register() call list had codex before codebuddy, violating the alphabetical ordering that AGENTS.md documents. Swap them so the file matches the documented convention. Addresses: https://github.com/github/spec-kit/pull/2119#pullrequestreview-4083341590 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
14 KiB
AGENTS.md
About Spec Kit and Specify
GitHub Spec Kit is a comprehensive toolkit for implementing Spec-Driven Development (SDD) - a methodology that emphasizes creating clear specifications before implementation. The toolkit includes templates, scripts, and workflows that guide development teams through a structured approach to building software.
Specify CLI is the command-line interface that bootstraps projects with the Spec Kit framework. It sets up the necessary directory structures, templates, and AI agent integrations to support the Spec-Driven Development workflow.
The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices.
Integration Architecture
Each AI agent is a self-contained integration subpackage under src/specify_cli/integrations/<key>/. 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().
src/specify_cli/integrations/
├── __init__.py # INTEGRATION_REGISTRY + _register_builtins()
├── base.py # IntegrationBase, MarkdownIntegration, TomlIntegration, SkillsIntegration
├── manifest.py # IntegrationManifest (file tracking)
├── claude/ # Example: SkillsIntegration subclass
│ ├── __init__.py # ClaudeIntegration class
│ └── scripts/ # Thin wrapper scripts
│ ├── update-context.sh
│ └── update-context.ps1
├── gemini/ # Example: TomlIntegration subclass
│ ├── __init__.py
│ └── scripts/
├── windsurf/ # Example: MarkdownIntegration subclass
│ ├── __init__.py
│ └── scripts/
├── copilot/ # Example: IntegrationBase subclass (custom setup)
│ ├── __init__.py
│ └── scripts/
└── ... # One subpackage per supported agent
The registry is the single source of truth for Python integration metadata. Supported agents, their directories, formats, and capabilities are derived from the integration classes for the Python integration layer. However, context-update behavior still requires explicit cases in the shared dispatcher scripts (scripts/bash/update-agent-context.sh and scripts/powershell/update-agent-context.ps1), which currently maintain their own supported-agent lists and agent-key→context-file mappings until they are migrated to registry-based dispatch.
Adding a New Integration
1. Choose a base class
| Your agent needs… | Subclass |
|---|---|
Standard markdown commands (.md) |
MarkdownIntegration |
TOML-format commands (.toml) |
TomlIntegration |
Skill directories (speckit-<name>/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/<package_dir>/__init__.py, where <package_dir> is the Python-safe directory name derived from <key>: 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 (Windsurf):
"""Windsurf IDE integration."""
from ..base import MarkdownIntegration
class WindsurfIntegration(MarkdownIntegration):
key = "windsurf"
config = {
"name": "Windsurf",
"folder": ".windsurf/",
"commands_subdir": "workflows",
"install_url": None,
"requires_cli": False,
}
registrar_config = {
"dir": ".windsurf/workflows",
"format": "markdown",
"args": "$ARGUMENTS",
"extension": ".md",
}
context_file = ".windsurf/rules/specify-rules.md"
TOML agent (Gemini):
"""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",
}
context_file = "GEMINI.md"
Skills agent (Codex):
"""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",
}
context_file = "AGENTS.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 |
context_file |
Class attribute (str or None) | Path to agent context/instructions file (e.g., "CLAUDE.md", ".github/copilot-instructions.md") |
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., "windsurf", "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:
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. Add scripts
Create two thin wrapper scripts in src/specify_cli/integrations/<package_dir>/scripts/ that delegate to the shared context-update scripts. Each is ~25 lines of boilerplate.
Note on
<package_dir>vs<key>:<package_dir>is the Python-safe directory name for your integration — it matches<key>exactly when the key contains no hyphens (e.g., key"gemini"→gemini/), but uses underscores when it does (e.g., key"kiro-cli"→kiro_cli/). TheIntegrationBase.keyclass attribute always retains the original hyphenated value (e.g.,key = "kiro-cli"), since that is what the CLI and registry use.
update-context.sh:
#!/usr/bin/env bash
# update-context.sh — <Agent Name> integration: create/update <context_file>
set -euo pipefail
_script_dir="$(cd "$(dirname "$0")" && pwd)"
_root="$_script_dir"
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
if [ -z "${REPO_ROOT:-}" ]; then
if [ -d "$_root/.specify" ]; then
REPO_ROOT="$_root"
else
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
REPO_ROOT="$git_root"
else
REPO_ROOT="$_root"
fi
fi
fi
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" <key>
update-context.ps1:
# update-context.ps1 — <Agent Name> integration: create/update <context_file>
$ErrorActionPreference = 'Stop'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
$repoRoot = $scriptDir
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
$repoRoot = Split-Path -Parent $repoRoot
}
}
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType <key>
Replace <key> with your integration key and <Agent Name> / <context_file> with the appropriate values.
You must also add the agent to the shared context-update scripts so the shared dispatcher recognises the new key:
scripts/bash/update-agent-context.sh— add a file-path variable and a case inupdate_specific_agent().scripts/powershell/update-agent-context.ps1— add a file-path variable, add the new key to theAgentTypeparameter's[ValidateSet(...)], add a switch case inUpdate-SpecificAgent, and add an entry inUpdate-AllExistingAgents.
5. Test it
# Install into a test project
specify init my-project --integration <key>
# Verify files were created in the commands directory configured by
# config["folder"] + config["commands_subdir"] (for example, .windsurf/workflows/)
ls -R my-project/.windsurf/workflows/
# Uninstall cleanly
cd my-project && specify integration uninstall <key>
Each integration also has a dedicated test file at tests/integrations/test_integration_<key>.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:
pytest tests/integrations/test_integration_<key_with_underscores>.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 |
setup() |
Custom install logic (companion files, settings merge) | Copilot → .agent.md + .prompt.md + .vscode/settings.json |
teardown() |
Custom uninstall logic | Rarely needed; base handles manifest-tracked files |
Example — Copilot (fully custom setup):
Copilot extends IntegrationBase directly because it 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:
{
"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:
#!/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
Markdown Format
Standard format:
---
description: "Command description"
---
Command content with {SCRIPT} and $ARGUMENTS placeholders.
GitHub Copilot Chat Mode format:
---
description: "Command description"
mode: speckit.command-name
---
Command content with {SCRIPT} and $ARGUMENTS placeholders.
TOML Format
description = "Command description"
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) - Custom: some agents override the default (e.g., Forge uses
{{parameters}}) - Script placeholders:
{SCRIPT}(replaced with actual script path) - Agent placeholders:
__AGENT__(replaced with agent name)
Common Pitfalls
- Using shorthand keys for CLI-based integrations: For CLI-based integrations (
requires_cli: True), thekeymust 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. - Forgetting update scripts: Both bash and PowerShell thin wrappers and the shared context-update scripts must be updated.
- Incorrect
requires_clivalue: Set toTrueonly for agents that have a CLI tool; set toFalsefor IDE-based agents. - Wrong argument format: Use
$ARGUMENTSfor Markdown agents,{{args}}for TOML agents. - Skipping registration: The import and
_register()call in_register_builtins()must both be added.
This documentation should be updated whenever new integrations are added to maintain accuracy and completeness.