mirror of
https://github.com/github/spec-kit.git
synced 2026-07-05 05:21:48 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3f0edd142 |
598
AGENTS.md
598
AGENTS.md
@@ -10,281 +10,277 @@ The toolkit supports multiple AI coding assistants, allowing teams to use their
|
||||
|
||||
---
|
||||
|
||||
## Integration Architecture
|
||||
## Adding New Agent Support
|
||||
|
||||
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()`.
|
||||
This section explains how to add support for new AI agents/assistants to the Specify CLI. Use this guide as a reference when integrating new AI tools into the Spec-Driven Development workflow.
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
### Overview
|
||||
|
||||
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.
|
||||
Specify supports multiple AI agents by generating agent-specific command files and directory structures when initializing projects. Each agent has its own conventions for:
|
||||
|
||||
---
|
||||
- **Command file formats** (Markdown, TOML, etc.)
|
||||
- **Directory structures** (`.claude/commands/`, `.windsurf/workflows/`, etc.)
|
||||
- **Command invocation patterns** (slash commands, CLI tools, etc.)
|
||||
- **Argument passing conventions** (`$ARGUMENTS`, `{{args}}`, etc.)
|
||||
|
||||
## Adding a New Integration
|
||||
### Current Supported Agents
|
||||
|
||||
### 1. Choose a base class
|
||||
| Agent | Directory | Format | CLI Tool | Description |
|
||||
| -------------------------- | ---------------------- | -------- | --------------- | --------------------------- |
|
||||
| **Claude Code** | `.claude/commands/` | Markdown | `claude` | Anthropic's Claude Code CLI |
|
||||
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
|
||||
| **GitHub Copilot** | `.github/agents/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
|
||||
| **Cursor** | `.cursor/commands/` | Markdown | N/A (IDE-based) | Cursor IDE (`--ai cursor-agent`) |
|
||||
| **Qwen Code** | `.qwen/commands/` | Markdown | `qwen` | Alibaba's Qwen Code CLI |
|
||||
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
||||
| **Codex CLI** | `.agents/skills/` | Markdown | `codex` | Codex CLI (`--ai codex --ai-skills`) |
|
||||
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
||||
| **Junie** | `.junie/commands/` | Markdown | `junie` | Junie by JetBrains |
|
||||
| **Kilo Code** | `.kilocode/workflows/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
||||
| **Auggie CLI** | `.augment/commands/` | Markdown | `auggie` | Auggie CLI |
|
||||
| **Roo Code** | `.roo/commands/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
||||
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
||||
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qodercli` | Qoder CLI |
|
||||
| **Kiro CLI** | `.kiro/prompts/` | Markdown | `kiro-cli` | Kiro CLI |
|
||||
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
|
||||
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
||||
| **Tabnine CLI** | `.tabnine/agent/commands/` | TOML | `tabnine` | Tabnine CLI |
|
||||
| **Kimi Code** | `.kimi/skills/` | Markdown | `kimi` | Kimi Code CLI (Moonshot AI) |
|
||||
| **Pi Coding Agent** | `.pi/prompts/` | Markdown | `pi` | Pi terminal coding agent |
|
||||
| **iFlow CLI** | `.iflow/commands/` | Markdown | `iflow` | iFlow CLI (iflow-ai) |
|
||||
| **Forge** | `.forge/commands/` | Markdown | `forge` | Forge CLI (forgecode.dev) |
|
||||
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
|
||||
| **Trae** | `.trae/rules/` | Markdown | N/A (IDE-based) | Trae IDE |
|
||||
| **Antigravity** | `.agent/commands/` | Markdown | N/A (IDE-based) | Antigravity IDE (`--ai agy --ai-skills`) |
|
||||
| **Mistral Vibe** | `.vibe/prompts/` | Markdown | `vibe` | Mistral Vibe CLI |
|
||||
| **Generic** | User-specified via `--ai-commands-dir` | Markdown | N/A | Bring your own agent |
|
||||
|
||||
| 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 |
|
||||
### Step-by-Step Integration Guide
|
||||
|
||||
Most agents only need `MarkdownIntegration` — a minimal subclass with zero method overrides.
|
||||
Follow these steps to add a new agent (using a hypothetical new agent as an example):
|
||||
|
||||
### 2. Create the subpackage
|
||||
#### 1. Add to AGENT_CONFIG
|
||||
|
||||
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.
|
||||
**IMPORTANT**: Use the actual CLI tool name as the key, not a shortened version.
|
||||
|
||||
**Minimal example — Markdown agent (Windsurf):**
|
||||
Add the new agent to the `AGENT_CONFIG` dictionary in `src/specify_cli/__init__.py`. This is the **single source of truth** for all agent metadata:
|
||||
|
||||
```python
|
||||
"""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"
|
||||
AGENT_CONFIG = {
|
||||
# ... existing agents ...
|
||||
"new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal)
|
||||
"name": "New Agent Display Name",
|
||||
"folder": ".newagent/", # Directory for agent files
|
||||
"commands_subdir": "commands", # Subdirectory name for command files (default: "commands")
|
||||
"install_url": "https://example.com/install", # URL for installation docs (or None if IDE-based)
|
||||
"requires_cli": True, # True if CLI tool required, False for IDE-based agents
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**TOML agent (Gemini):**
|
||||
**Key Design Principle**: The dictionary key should match the actual executable name that users install. For example:
|
||||
|
||||
- ✅ Use `"cursor-agent"` because the CLI tool is literally called `cursor-agent`
|
||||
- ❌ Don't use `"cursor"` as a shortcut if the tool is `cursor-agent`
|
||||
|
||||
This eliminates the need for special-case mappings throughout the codebase.
|
||||
|
||||
**Field Explanations**:
|
||||
|
||||
- `name`: Human-readable display name shown to users
|
||||
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
||||
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
|
||||
- Most agents use `"commands"` (e.g., `.claude/commands/`)
|
||||
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode), `"prompts"` (codex, kiro-cli, pi), `"command"` (opencode - singular)
|
||||
- This field enables `--ai-skills` to locate command templates correctly for skill generation
|
||||
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
||||
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
||||
|
||||
#### 2. Update CLI Help Text
|
||||
|
||||
Update the `--ai` parameter help text in the `init()` command to include the new agent:
|
||||
|
||||
```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",
|
||||
}
|
||||
context_file = "GEMINI.md"
|
||||
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, new-agent-cli, or kiro-cli"),
|
||||
```
|
||||
|
||||
**Skills agent (Codex):**
|
||||
Also update any function docstrings, examples, and error messages that list available agents.
|
||||
|
||||
```python
|
||||
"""Codex CLI integration — skills-based agent."""
|
||||
#### 3. Update README Documentation
|
||||
|
||||
from __future__ import annotations
|
||||
Update the **Supported AI Agents** section in `README.md` to include the new agent:
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
- Add the new agent to the table with appropriate support level (Full/Partial)
|
||||
- Include the agent's official website link
|
||||
- Add any relevant notes about the agent's implementation
|
||||
- Ensure the table formatting remains aligned and consistent
|
||||
|
||||
#### 4. Update Release Package Script
|
||||
|
||||
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"
|
||||
Modify `.github/workflows/scripts/create-release-packages.sh`:
|
||||
|
||||
@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:
|
||||
|
||||
```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. 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/`). The `IntegrationBase.key` class attribute always retains the original hyphenated value (e.g., `key = "kiro-cli"`), since that is what the CLI and registry use.
|
||||
|
||||
**`update-context.sh`:**
|
||||
##### Add to ALL_AGENTS array
|
||||
|
||||
```bash
|
||||
#!/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>
|
||||
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf kiro-cli)
|
||||
```
|
||||
|
||||
**`update-context.ps1`:**
|
||||
##### Add case statement for directory structure
|
||||
|
||||
```bash
|
||||
case $agent in
|
||||
# ... existing cases ...
|
||||
windsurf)
|
||||
mkdir -p "$base_dir/.windsurf/workflows"
|
||||
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||
esac
|
||||
```
|
||||
|
||||
#### 4. Update GitHub Release Script
|
||||
|
||||
Modify `.github/workflows/scripts/create-github-release.sh` to include the new agent's packages:
|
||||
|
||||
```bash
|
||||
gh release create "$VERSION" \
|
||||
# ... existing packages ...
|
||||
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||
# Add new agent packages here
|
||||
```
|
||||
|
||||
#### 5. Update Agent Context Scripts
|
||||
|
||||
##### Bash script (`scripts/bash/update-agent-context.sh`)
|
||||
|
||||
Add file variable:
|
||||
|
||||
```bash
|
||||
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||
```
|
||||
|
||||
Add to case statement:
|
||||
|
||||
```bash
|
||||
case "$AGENT_TYPE" in
|
||||
# ... existing cases ...
|
||||
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
|
||||
"")
|
||||
# ... existing checks ...
|
||||
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf";
|
||||
# Update default creation condition
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
##### PowerShell script (`scripts/powershell/update-agent-context.ps1`)
|
||||
|
||||
Add file variable:
|
||||
|
||||
```powershell
|
||||
# update-context.ps1 — <Agent Name> integration: create/update <context_file>
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
|
||||
```
|
||||
|
||||
$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
|
||||
Add to switch statement:
|
||||
|
||||
```powershell
|
||||
switch ($AgentType) {
|
||||
# ... existing cases ...
|
||||
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
|
||||
'' {
|
||||
foreach ($pair in @(
|
||||
# ... existing pairs ...
|
||||
@{file=$windsurfFile; name='Windsurf'}
|
||||
)) {
|
||||
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
||||
}
|
||||
# Update default creation condition
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. Update CLI Tool Checks (Optional)
|
||||
|
||||
For agents that require CLI tools, add checks in the `check()` command and agent validation:
|
||||
|
||||
```python
|
||||
# In check() command
|
||||
tracker.add("windsurf", "Windsurf IDE (optional)")
|
||||
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
|
||||
|
||||
# In init validation (only if CLI tool required)
|
||||
elif selected_ai == "windsurf":
|
||||
if not check_tool("windsurf", "Install from: https://windsurf.com/"):
|
||||
console.print("[red]Error:[/red] Windsurf CLI is required for Windsurf projects")
|
||||
agent_tool_missing = True
|
||||
```
|
||||
|
||||
**Note**: CLI tool checks are now handled automatically based on the `requires_cli` field in AGENT_CONFIG. No additional code changes needed in the `check()` or `init()` commands - they automatically loop through AGENT_CONFIG and check tools as needed.
|
||||
|
||||
## Important Design Decisions
|
||||
|
||||
### Using Actual CLI Tool Names as Keys
|
||||
|
||||
**CRITICAL**: When adding a new agent to AGENT_CONFIG, always use the **actual executable name** as the dictionary key, not a shortened or convenient version.
|
||||
|
||||
**Why this matters:**
|
||||
|
||||
- The `check_tool()` function uses `shutil.which(tool)` to find executables in the system PATH
|
||||
- If the key doesn't match the actual CLI tool name, you'll need special-case mappings throughout the codebase
|
||||
- This creates unnecessary complexity and maintenance burden
|
||||
|
||||
**Example - The Cursor Lesson:**
|
||||
|
||||
❌ **Wrong approach** (requires special-case mapping):
|
||||
|
||||
```python
|
||||
AGENT_CONFIG = {
|
||||
"cursor": { # Shorthand that doesn't match the actual tool
|
||||
"name": "Cursor",
|
||||
# ...
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType <key>
|
||||
# Then you need special cases everywhere:
|
||||
cli_tool = agent_key
|
||||
if agent_key == "cursor":
|
||||
cli_tool = "cursor-agent" # Map to the real tool name
|
||||
```
|
||||
|
||||
Replace `<key>` with your integration key and `<Agent Name>` / `<context_file>` with the appropriate values.
|
||||
✅ **Correct approach** (no mapping needed):
|
||||
|
||||
You must also add the agent to the shared context-update scripts so the shared dispatcher recognises the new key:
|
||||
```python
|
||||
AGENT_CONFIG = {
|
||||
"cursor-agent": { # Matches the actual executable name
|
||||
"name": "Cursor",
|
||||
# ...
|
||||
}
|
||||
}
|
||||
|
||||
- **`scripts/bash/update-agent-context.sh`** — add a file-path variable and a case in `update_specific_agent()`.
|
||||
- **`scripts/powershell/update-agent-context.ps1`** — add a file-path variable, add the new key to the `AgentType` parameter's `[ValidateSet(...)]`, add a switch case in `Update-SpecificAgent`, and add an entry in `Update-AllExistingAgents`.
|
||||
|
||||
### 5. Test it
|
||||
|
||||
```bash
|
||||
# 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>
|
||||
# No special cases needed - just use agent_key directly!
|
||||
```
|
||||
|
||||
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:
|
||||
**Benefits of this approach:**
|
||||
|
||||
```bash
|
||||
pytest tests/integrations/test_integration_<key_with_underscores>.py -v
|
||||
```
|
||||
- Eliminates special-case logic scattered throughout the codebase
|
||||
- Makes the code more maintainable and easier to understand
|
||||
- Reduces the chance of bugs when adding new agents
|
||||
- Tool checking "just works" without additional mappings
|
||||
|
||||
### 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)
|
||||
#### 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
|
||||
##### VS Code Extension-based Agents
|
||||
|
||||
For agents available as VS Code extensions, add them to `.devcontainer/devcontainer.json`:
|
||||
|
||||
```jsonc
|
||||
```json
|
||||
{
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
// ... existing extensions ...
|
||||
// [New Agent Name]
|
||||
"[New Agent Extension ID]"
|
||||
]
|
||||
}
|
||||
@@ -292,7 +288,7 @@ For agents available as VS Code extensions, add them to `.devcontainer/devcontai
|
||||
}
|
||||
```
|
||||
|
||||
#### CLI-based Agents
|
||||
##### CLI-based Agents
|
||||
|
||||
For agents that require CLI tools, add installation commands to `.devcontainer/post-create.sh`:
|
||||
|
||||
@@ -302,16 +298,63 @@ For agents that require CLI tools, add installation commands to `.devcontainer/p
|
||||
# Existing installations...
|
||||
|
||||
echo -e "\n🤖 Installing [New Agent Name] CLI..."
|
||||
# run_command "npm install -g [agent-cli-package]@latest"
|
||||
# run_command "npm install -g [agent-cli-package]@latest" # Example for node-based CLI
|
||||
# or other installation instructions (must be non-interactive and compatible with Linux Debian "Trixie" or later)...
|
||||
echo "✅ Done"
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
**Quick Tips:**
|
||||
|
||||
- **Extension-based agents**: Add to the `extensions` array in `devcontainer.json`
|
||||
- **CLI-based agents**: Add installation scripts to `post-create.sh`
|
||||
- **Hybrid agents**: May require both extension and CLI installation
|
||||
- **Test thoroughly**: Ensure installations work in the devcontainer environment
|
||||
|
||||
## Agent Categories
|
||||
|
||||
### CLI-Based Agents
|
||||
|
||||
Require a command-line tool to be installed:
|
||||
|
||||
- **Claude Code**: `claude` CLI
|
||||
- **Gemini CLI**: `gemini` CLI
|
||||
- **Qwen Code**: `qwen` CLI
|
||||
- **opencode**: `opencode` CLI
|
||||
- **Codex CLI**: `codex` CLI (requires `--ai-skills`)
|
||||
- **Junie**: `junie` CLI
|
||||
- **Auggie CLI**: `auggie` CLI
|
||||
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||
- **Qoder CLI**: `qodercli` CLI
|
||||
- **Kiro CLI**: `kiro-cli` CLI
|
||||
- **Amp**: `amp` CLI
|
||||
- **SHAI**: `shai` CLI
|
||||
- **Tabnine CLI**: `tabnine` CLI
|
||||
- **Kimi Code**: `kimi` CLI
|
||||
- **Mistral Vibe**: `vibe` CLI
|
||||
- **Pi Coding Agent**: `pi` CLI
|
||||
- **iFlow CLI**: `iflow` CLI
|
||||
- **Forge**: `forge` CLI
|
||||
|
||||
### IDE-Based Agents
|
||||
|
||||
Work within integrated development environments:
|
||||
|
||||
- **GitHub Copilot**: Built into VS Code/compatible editors
|
||||
- **Cursor**: Built into Cursor IDE (`--ai cursor-agent`)
|
||||
- **Windsurf**: Built into Windsurf IDE
|
||||
- **Kilo Code**: Built into Kilo Code IDE
|
||||
- **Roo Code**: Built into Roo Code IDE
|
||||
- **IBM Bob**: Built into IBM Bob IDE
|
||||
- **Trae**: Built into Trae IDE
|
||||
- **Antigravity**: Built into Antigravity IDE (`--ai agy --ai-skills`)
|
||||
|
||||
## Command File Formats
|
||||
|
||||
### Markdown Format
|
||||
|
||||
Used by: Claude, Cursor, GitHub Copilot, opencode, Windsurf, Junie, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen, Pi, Codex, Auggie, CodeBuddy, Qoder, Roo Code, Kilo Code, Trae, Antigravity, Mistral Vibe, iFlow, Forge
|
||||
|
||||
**Standard format:**
|
||||
|
||||
```markdown
|
||||
@@ -335,6 +378,8 @@ Command content with {SCRIPT} and $ARGUMENTS placeholders.
|
||||
|
||||
### TOML Format
|
||||
|
||||
Used by: Gemini, Tabnine
|
||||
|
||||
```toml
|
||||
description = "Command description"
|
||||
|
||||
@@ -343,24 +388,109 @@ Command content with {SCRIPT} and {{args}} placeholders.
|
||||
"""
|
||||
```
|
||||
|
||||
## Directory Conventions
|
||||
|
||||
- **CLI agents**: Usually `.<agent-name>/commands/`
|
||||
- **Singular command exception**:
|
||||
- opencode: `.opencode/command/` (singular `command`, not `commands`)
|
||||
- **Nested path exception**:
|
||||
- Tabnine: `.tabnine/agent/commands/` (extra `agent/` segment)
|
||||
- **Shared `.agents/` folder**:
|
||||
- Amp: `.agents/commands/` (shared folder, not `.amp/`)
|
||||
- Codex: `.agents/skills/` (shared folder; requires `--ai-skills`; invoked as `$speckit-<command>`)
|
||||
- **Skills-based exceptions**:
|
||||
- Kimi Code: `.kimi/skills/` (skills, invoked as `/skill:speckit-<command>`)
|
||||
- **Prompt-based exceptions**:
|
||||
- Kiro CLI: `.kiro/prompts/`
|
||||
- Pi: `.pi/prompts/`
|
||||
- Mistral Vibe: `.vibe/prompts/`
|
||||
- **Rules-based exceptions**:
|
||||
- Trae: `.trae/rules/`
|
||||
- **IDE agents**: Follow IDE-specific patterns:
|
||||
- Copilot: `.github/agents/`
|
||||
- Cursor: `.cursor/commands/`
|
||||
- Windsurf: `.windsurf/workflows/`
|
||||
- Kilo Code: `.kilocode/workflows/`
|
||||
- Roo Code: `.roo/commands/`
|
||||
- IBM Bob: `.bob/commands/`
|
||||
- Antigravity: `.agent/skills/` (`--ai-skills` required; `.agent/commands/` is deprecated)
|
||||
|
||||
## 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:
|
||||
Different agents use different argument placeholders:
|
||||
|
||||
- **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}}`)
|
||||
- **Markdown/prompt-based**: `$ARGUMENTS`
|
||||
- **TOML-based**: `{{args}}`
|
||||
- **Forge-specific**: `{{parameters}}` (uses custom parameter syntax)
|
||||
- **Script placeholders**: `{SCRIPT}` (replaced with actual script path)
|
||||
- **Agent placeholders**: `__AGENT__` (replaced with agent name)
|
||||
|
||||
## Special Processing Requirements
|
||||
|
||||
Some agents require custom processing beyond the standard template transformations:
|
||||
|
||||
### Copilot Integration
|
||||
|
||||
GitHub Copilot has unique requirements:
|
||||
- Commands use `.agent.md` extension (not `.md`)
|
||||
- Each command gets a companion `.prompt.md` file in `.github/prompts/`
|
||||
- Installs `.vscode/settings.json` with prompt file recommendations
|
||||
- Context file lives at `.github/copilot-instructions.md`
|
||||
|
||||
Implementation: Extends `IntegrationBase` with custom `setup()` method that:
|
||||
1. Processes templates with `process_template()`
|
||||
2. Generates companion `.prompt.md` files
|
||||
3. Merges VS Code settings
|
||||
|
||||
### 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
|
||||
6. Ensures the shared `update-agent-context.*` scripts include a `forge` case that maps context updates to `AGENTS.md` (similar to `opencode`/`codex`/`pi`) and lists `forge` in their usage/help text
|
||||
|
||||
### Standard Markdown Agents
|
||||
|
||||
Most agents (Bob, Claude, Windsurf, etc.) use `MarkdownIntegration`:
|
||||
- Simple subclass with just `key`, `config`, `registrar_config` set
|
||||
- Inherits standard processing from `MarkdownIntegration.setup()`
|
||||
- No custom processing needed
|
||||
|
||||
## Testing New Agent Integration
|
||||
|
||||
1. **Build test**: Run package creation script locally
|
||||
2. **CLI test**: Test `specify init --ai <agent>` command
|
||||
3. **File generation**: Verify correct directory structure and files
|
||||
4. **Command validation**: Ensure generated commands work with the agent
|
||||
5. **Context update**: Test agent context update scripts
|
||||
|
||||
## 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. **Forgetting update scripts**: Both bash and PowerShell thin wrappers and the shared context-update scripts must be updated.
|
||||
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.
|
||||
1. **Using shorthand keys instead of actual CLI tool names**: Always use the actual executable name as the AGENT_CONFIG key (e.g., `"cursor-agent"` not `"cursor"`). This prevents the need for special-case mappings throughout the codebase.
|
||||
2. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated when adding new agents.
|
||||
3. **Incorrect `requires_cli` value**: Set to `True` only for agents that actually have CLI tools to check; set to `False` for IDE-based agents.
|
||||
4. **Wrong argument format**: Use correct placeholder format for each agent type (`$ARGUMENTS` for Markdown, `{{args}}` for TOML).
|
||||
5. **Directory naming**: Follow agent-specific conventions exactly (check existing agents for patterns).
|
||||
6. **Help text inconsistency**: Update all user-facing text consistently (help strings, docstrings, README, error messages).
|
||||
|
||||
## Future Considerations
|
||||
|
||||
When adding new agents:
|
||||
|
||||
- Consider the agent's native command/workflow patterns
|
||||
- Ensure compatibility with the Spec-Driven Development process
|
||||
- Document any special requirements or limitations
|
||||
- Update this guide with lessons learned
|
||||
- Verify the actual CLI tool name before adding to AGENT_CONFIG
|
||||
|
||||
---
|
||||
|
||||
*This documentation should be updated whenever new integrations are added to maintain accuracy and completeness.*
|
||||
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -2,35 +2,6 @@
|
||||
|
||||
<!-- insert new changelog below this comment -->
|
||||
|
||||
## [0.6.1] - 2026-04-10
|
||||
|
||||
### Changed
|
||||
|
||||
- feat: add bundled lean preset with minimal workflow commands (#2161)
|
||||
- Add Brownfield Bootstrap extension to community catalog (#2145)
|
||||
- Add CI Guard extension to community catalog (#2157)
|
||||
- Add SpecTest extension to community catalog (#2159)
|
||||
- fix: bundled extensions should not have download URLs (#2155)
|
||||
- Add PR Bridge extension to community catalog (#2148)
|
||||
- feat(cursor-agent): migrate from .cursor/commands to .cursor/skills (#2156)
|
||||
- Add TinySpec extension to community catalog (#2147)
|
||||
- chore: bump spec-kit-verify to 1.0.3 and spec-kit-review to 1.0.1 (#2146)
|
||||
- Add Status Report extension to community catalog (#2123)
|
||||
- chore: release 0.6.0, begin 0.6.1.dev0 development (#2144)
|
||||
|
||||
## [0.6.0] - 2026-04-09
|
||||
|
||||
### Changed
|
||||
|
||||
- Add Bugfix Workflow community extension to catalog and README (#2135)
|
||||
- Add Worktree Isolation extension to community catalog (#2143)
|
||||
- Add multi-repo-branching preset to community catalog (#2139)
|
||||
- Readme clarity (#2013)
|
||||
- Rewrite AGENTS.md for integration architecture (#2119)
|
||||
- docs: add SpecKit Companion to Community Friends section (#2140)
|
||||
- feat: add memorylint extension to community catalog (#2138)
|
||||
- chore: release 0.5.1, begin 0.5.2.dev0 development (#2137)
|
||||
|
||||
## [0.5.1] - 2026-04-08
|
||||
|
||||
### Changed
|
||||
|
||||
125
README.md
125
README.md
@@ -186,10 +186,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||
| Azure DevOps Integration | Sync user stories and tasks to Azure DevOps work items using OAuth authentication | `integration` | Read+Write | [spec-kit-azure-devops](https://github.com/pragya247/spec-kit-azure-devops) |
|
||||
| Branch Convention | Configurable branch and folder naming conventions for /specify with presets and custom patterns | `process` | Read+Write | [spec-kit-branch-convention](https://github.com/Quratulain-bilal/spec-kit-branch-convention) |
|
||||
| Brownfield Bootstrap | Bootstrap spec-kit for existing codebases — auto-discover architecture and adopt SDD incrementally | `process` | Read+Write | [spec-kit-brownfield](https://github.com/Quratulain-bilal/spec-kit-brownfield) |
|
||||
| Bugfix Workflow | Structured bugfix workflow — capture bugs, trace to spec artifacts, and patch specs surgically | `process` | Read+Write | [spec-kit-bugfix](https://github.com/Quratulain-bilal/spec-kit-bugfix) |
|
||||
| Canon | Adds canon-driven (baseline-driven) workflows: spec-first, code-first, spec-drift. Requires Canon Core preset installation. | `process` | Read+Write | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon/tree/master/extension) |
|
||||
| CI Guard | Spec compliance gates for CI/CD — verify specs exist, check drift, and block merges on gaps | `process` | Read-only | [spec-kit-ci-guard](https://github.com/Quratulain-bilal/spec-kit-ci-guard) |
|
||||
| Checkpoint Extension | Commit the changes made during the middle of the implementation, so you don't end up with just one very large commit at the end | `code` | Read+Write | [spec-kit-checkpoint](https://github.com/aaronrsun/spec-kit-checkpoint) |
|
||||
| Cleanup Extension | Post-implementation quality gate that reviews changes, fixes small issues (scout rule), creates tasks for medium issues, and generates analysis for large issues | `code` | Read+Write | [spec-kit-cleanup](https://github.com/dsrednicki/spec-kit-cleanup) |
|
||||
| Conduct Extension | Orchestrates spec-kit phases via sub-agent delegation to reduce context pollution. | `process` | Read+Write | [spec-kit-conduct-ext](https://github.com/twbrandon7/spec-kit-conduct-ext) |
|
||||
@@ -209,11 +206,9 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| MAQA Jira Integration | Jira integration for MAQA — syncs Stories and Subtasks as features progress through the board | `integration` | Read+Write | [spec-kit-maqa-jira](https://github.com/GenieRobot/spec-kit-maqa-jira) |
|
||||
| MAQA Linear Integration | Linear integration for MAQA — syncs issues and sub-issues across workflow states as features progress | `integration` | Read+Write | [spec-kit-maqa-linear](https://github.com/GenieRobot/spec-kit-maqa-linear) |
|
||||
| MAQA Trello Integration | Trello board integration for MAQA — populates board from specs, moves cards, real-time checklist ticking | `integration` | Read+Write | [spec-kit-maqa-trello](https://github.com/GenieRobot/spec-kit-maqa-trello) |
|
||||
| MemoryLint | Agent memory governance tool: Automatically audits and fixes boundary conflicts between AGENTS.md and the constitution. | `process` | Read+Write | [memorylint](https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/memorylint) |
|
||||
| Onboard | Contextual onboarding and progressive growth for developers new to spec-kit projects. Explains specs, maps dependencies, validates understanding, and guides the next step | `process` | Read+Write | [spec-kit-onboard](https://github.com/dmux/spec-kit-onboard) |
|
||||
| Optimize | Audit and optimize AI governance for context efficiency — token budgets, rule health, interpretability, compression, coherence, and echo detection | `process` | Read+Write | [spec-kit-optimize](https://github.com/sakitA/spec-kit-optimize) |
|
||||
| Plan Review Gate | Require spec.md and plan.md to be merged via MR/PR before allowing task generation | `process` | Read-only | [spec-kit-plan-review-gate](https://github.com/luno/spec-kit-plan-review-gate) |
|
||||
| PR Bridge | Auto-generate pull request descriptions, checklists, and summaries from spec artifacts | `process` | Read-only | [spec-kit-pr-bridge-](https://github.com/Quratulain-bilal/spec-kit-pr-bridge-) |
|
||||
| Presetify | Create and validate presets and preset catalogs | `process` | Read+Write | [presetify](https://github.com/mnriem/spec-kit-extensions/tree/main/presetify) |
|
||||
| Product Forge | Full product lifecycle: research → product spec → SpecKit → implement → verify → test | `process` | Read+Write | [speckit-product-forge](https://github.com/VaiYav/speckit-product-forge) |
|
||||
| Project Health Check | Diagnose a Spec Kit project and report health issues across structure, agents, features, scripts, extensions, and git | `visibility` | Read-only | [spec-kit-doctor](https://github.com/KhawarHabibKhan/spec-kit-doctor) |
|
||||
@@ -232,15 +227,11 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Spec Diagram | Auto-generate Mermaid diagrams of SDD workflow state, feature progress, and task dependencies | `visibility` | Read-only | [spec-kit-diagram-](https://github.com/Quratulain-bilal/spec-kit-diagram-) |
|
||||
| Spec Refine | Update specs in-place, propagate changes to plan and tasks, and diff impact across artifacts | `process` | Read+Write | [spec-kit-refine](https://github.com/Quratulain-bilal/spec-kit-refine) |
|
||||
| Spec Sync | Detect and resolve drift between specs and implementation. AI-assisted resolution with human approval | `docs` | Read+Write | [spec-kit-sync](https://github.com/bgervin/spec-kit-sync) |
|
||||
| SpecTest | Auto-generate test scaffolds from spec criteria, map coverage, and find untested requirements | `code` | Read+Write | [spec-kit-spectest](https://github.com/Quratulain-bilal/spec-kit-spectest) |
|
||||
| Staff Review Extension | Staff-engineer-level code review that validates implementation against spec, checks security, performance, and test coverage | `code` | Read-only | [spec-kit-staff-review](https://github.com/arunt14/spec-kit-staff-review) |
|
||||
| Status Report | Project status, feature progress, and next-action recommendations for spec-driven workflows | `visibility` | Read-only | [Open-Agent-Tools/spec-kit-status](https://github.com/Open-Agent-Tools/spec-kit-status) |
|
||||
| Superpowers Bridge | Orchestrates obra/superpowers skills within the spec-kit SDD workflow across the full lifecycle (clarification, TDD, review, verification, critique, debugging, branch completion) | `process` | Read+Write | [superpowers-bridge](https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/superpowers-bridge) |
|
||||
| TinySpec | Lightweight single-file workflow for small tasks — skip the heavy multi-step SDD process | `process` | Read+Write | [spec-kit-tinyspec](https://github.com/Quratulain-bilal/spec-kit-tinyspec) |
|
||||
| V-Model Extension Pack | Enforces V-Model paired generation of development specs and test specs with full traceability | `docs` | Read+Write | [spec-kit-v-model](https://github.com/leocamello/spec-kit-v-model) |
|
||||
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | `code` | Read-only | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |
|
||||
| Verify Tasks Extension | Detect phantom completions: tasks marked [X] in tasks.md with no real implementation | `code` | Read-only | [spec-kit-verify-tasks](https://github.com/datastone-inc/spec-kit-verify-tasks) |
|
||||
| Worktree Isolation | Spawn isolated git worktrees for parallel feature development without checkout switching | `process` | Read+Write | [spec-kit-worktree](https://github.com/Quratulain-bilal/spec-kit-worktree) |
|
||||
|
||||
To submit your own extension, see the [Extension Publishing Guide](extensions/EXTENSION-PUBLISHING-GUIDE.md).
|
||||
|
||||
@@ -256,7 +247,6 @@ The following community-contributed presets customize how Spec Kit behaves — o
|
||||
| AIDE In-Place Migration | Adapts the AIDE extension workflow for in-place technology migrations (X → Y pattern) — adds migration objectives, verification gates, knowledge documents, and behavioral equivalence criteria | 2 templates, 8 commands | AIDE extension | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Canon Core | Adapts original Spec Kit workflow to work together with Canon extension | 2 templates, 8 commands | — | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon) |
|
||||
| Explicit Task Dependencies | Adds explicit `(depends on T###)` dependency declarations and an Execution Wave DAG to tasks.md for parallel scheduling | 1 template, 1 command | — | [spec-kit-preset-explicit-task-dependencies](https://github.com/Quratulain-bilal/spec-kit-preset-explicit-task-dependencies) |
|
||||
| Multi-Repo Branching | Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases | 2 commands | — | [spec-kit-preset-multi-repo-branching](https://github.com/sakitA/spec-kit-preset-multi-repo-branching) |
|
||||
| Pirate Speak (Full) | Transforms all Spec Kit output into pirate speak — specs become "Voyage Manifests", plans become "Battle Plans", tasks become "Crew Assignments" | 6 templates, 9 commands | — | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Table of Contents Navigation | Adds a navigable Table of Contents to generated spec.md, plan.md, and tasks.md documents | 3 templates, 3 commands | — | [spec-kit-preset-toc-navigation](https://github.com/Quratulain-bilal/spec-kit-preset-toc-navigation) |
|
||||
| VS Code Ask Questions | Enhances the clarify command to use `vscode/askQuestions` for batched interactive questioning. | 1 command | — | [spec-kit-presets](https://github.com/fdcastel/spec-kit-presets) |
|
||||
@@ -295,9 +285,8 @@ Community projects that extend, visualize, or build on Spec Kit:
|
||||
|
||||
- **[Spec Kit Assistant](https://marketplace.visualstudio.com/items?itemName=rfsales.speckit-assistant)** — A VS Code extension that provides a visual orchestrator for the full SDD workflow (constitution → specification → planning → tasks → implementation) with phase status visualization, an interactive task checklist, DAG visualization, and support for Claude, Gemini, GitHub Copilot, and OpenAI backends. Requires the `specify` CLI in your PATH.
|
||||
|
||||
- **[SpecKit Companion](https://marketplace.visualstudio.com/items?itemName=alfredoperez.speckit-companion)** — A VS Code extension that brings a visual GUI to Spec Kit. Browse specs in a rich markdown viewer with clickable file references, create specifications with image attachments, comment and refine each step inline (GitHub-style review), track your progress through the SDD workflow with a visual phase stepper, and manage steering documents like constitutions and templates.
|
||||
|
||||
## 🤖 Supported AI Agents
|
||||
|
||||
| Agent | Support | Notes |
|
||||
| ------------------------------------------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Qoder CLI](https://qoder.com/cli) | ✅ | |
|
||||
@@ -329,63 +318,22 @@ Community projects that extend, visualize, or build on Spec Kit:
|
||||
| [Trae](https://www.trae.ai/) | ✅ | |
|
||||
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
||||
|
||||
## Available Slash Commands
|
||||
|
||||
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. If you pass `--ai <agent> --ai-skills`, Spec Kit installs agent skills instead of slash-command prompt files; `--ai-skills` requires `--ai`.
|
||||
|
||||
#### Core Commands
|
||||
|
||||
Essential commands for the Spec-Driven Development workflow:
|
||||
|
||||
| Command | Agent Skill | Description |
|
||||
| ------------------------ | ---------------------- | -------------------------------------------------------------------------- |
|
||||
| `/speckit.constitution` | `speckit-constitution` | Create or update project governing principles and development guidelines |
|
||||
| `/speckit.specify` | `speckit-specify` | Define what you want to build (requirements and user stories) |
|
||||
| `/speckit.plan` | `speckit-plan` | Create technical implementation plans with your chosen tech stack |
|
||||
| `/speckit.tasks` | `speckit-tasks` | Generate actionable task lists for implementation |
|
||||
| `/speckit.taskstoissues` | `speckit-taskstoissues`| Convert generated task lists into GitHub issues for tracking and execution |
|
||||
| `/speckit.implement` | `speckit-implement` | Execute all tasks to build the feature according to the plan |
|
||||
|
||||
#### Optional Commands
|
||||
|
||||
Additional commands for enhanced quality and validation:
|
||||
|
||||
| Command | Agent Skill | Description |
|
||||
| -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `/speckit.clarify` | `speckit-clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) |
|
||||
| `/speckit.analyze` | `speckit-analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) |
|
||||
| `/speckit.checklist` | `speckit-checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") |
|
||||
|
||||
## 🔧 Specify CLI Reference
|
||||
|
||||
The `specify` tool is invoked as
|
||||
|
||||
```text
|
||||
specify <COMMAND> [SUBCOMMAND] [OPTIONS]
|
||||
```
|
||||
|
||||
and supports the following commands:
|
||||
The `specify` command supports the following options:
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `init` | Initialize a new Specify project from the latest template. |
|
||||
| `check` | Check for installed tools: `git` plus all CLI-based agents configured in `AGENT_CONFIG` (for example: `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `junie`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, `forge`, etc.) |
|
||||
| `version` | Show the currently installed Spec Kit version. |
|
||||
| `extension` | Manage extensions |
|
||||
| `preset` | Manage presets |
|
||||
| `integration` | Manage integrations |
|
||||
| Command | Description |
|
||||
| ------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `init` | Initialize a new Specify project from the latest template |
|
||||
| `check` | Check for installed tools: `git` plus all CLI-based agents configured in `AGENT_CONFIG` (for example: `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `junie`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, `forge`, etc.) |
|
||||
|
||||
### `specify init` Arguments & Options
|
||||
|
||||
```bash
|
||||
specify init [PROJECT_NAME] <OPTIONS>
|
||||
```
|
||||
|
||||
| Argument/Option | Type | Description |
|
||||
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `<PROJECT_NAME>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
|
||||
| ---------------------- | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
|
||||
| `--ai` | Option | AI assistant to use (see `AGENT_CONFIG` for the full, up-to-date list). Common options include: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `junie`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `kiro-cli` (`kiro` alias), `agy`, `bob`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, `forge`, or `generic` (requires `--ai-commands-dir`) |
|
||||
| `--ai-commands-dir` | Option | Directory for agent command files (required with `--ai generic`, e.g. `.myagent/commands/`) |
|
||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||
@@ -482,6 +430,38 @@ specify init my-project --ai claude --branch-numbering timestamp
|
||||
specify check
|
||||
```
|
||||
|
||||
### Available Slash Commands
|
||||
|
||||
After running `specify init`, your AI coding agent will have access to these structured development commands.
|
||||
|
||||
Most agents expose the traditional dotted slash commands shown below, like `/speckit.plan`.
|
||||
|
||||
Claude Code installs spec-kit as skills and invokes them as `/speckit-constitution`, `/speckit-specify`, `/speckit-plan`, `/speckit-tasks`, and `/speckit-implement`.
|
||||
|
||||
For Codex CLI, `--ai-skills` installs spec-kit as agent skills instead of slash-command prompt files. In Codex skills mode, invoke spec-kit as `$speckit-constitution`, `$speckit-specify`, `$speckit-plan`, `$speckit-tasks`, and `$speckit-implement`.
|
||||
|
||||
#### Core Commands
|
||||
|
||||
Essential commands for the Spec-Driven Development workflow:
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------ |
|
||||
| `/speckit.constitution` | Create or update project governing principles and development guidelines |
|
||||
| `/speckit.specify` | Define what you want to build (requirements and user stories) |
|
||||
| `/speckit.plan` | Create technical implementation plans with your chosen tech stack |
|
||||
| `/speckit.tasks` | Generate actionable task lists for implementation |
|
||||
| `/speckit.implement` | Execute all tasks to build the feature according to the plan |
|
||||
|
||||
#### Optional Commands
|
||||
|
||||
Additional commands for enhanced quality and validation:
|
||||
|
||||
| Command | Description |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `/speckit.clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) |
|
||||
| `/speckit.analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) |
|
||||
| `/speckit.checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
@@ -492,18 +472,21 @@ specify check
|
||||
|
||||
Spec Kit can be tailored to your needs through two complementary systems — **extensions** and **presets** — plus project-local overrides for one-off adjustments:
|
||||
|
||||
| Priority | Component Type | Location |
|
||||
| -------: | ------------------------------------------------- | -------------------------------- |
|
||||
| ⬆ 1 | Project-Local Overrides | `.specify/templates/overrides/` |
|
||||
| 2 | Presets — Customize core & extensions | `.specify/presets/templates/` |
|
||||
| 3 | Extensions — Add new capabilities | `.specify/extensions/templates/` |
|
||||
| ⬇ 4 | Spec Kit Core — Built-in SDD commands & templates | `.specify/templates/` |
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 1
|
||||
overrides["⬆ Highest priority\nProject-Local Overrides\n.specify/templates/overrides/"]
|
||||
presets["Presets — Customize core & extensions\n.specify/presets/<preset-id>/templates/"]
|
||||
extensions["Extensions — Add new capabilities\n.specify/extensions/<ext-id>/templates/"]
|
||||
core["Spec Kit Core — Built-in SDD commands & templates\n.specify/templates/\n⬇ Lowest priority"]
|
||||
|
||||
- **Templates** are resolved at **runtime** — Spec Kit walks the stack top-down and uses the first match.
|
||||
- Project-local overrides (`.specify/templates/overrides/`) let you make one-off adjustments for a single project without creating a full preset.
|
||||
- **Extension/preset commands** are applied at **install time** — when you run `specify extension add` or `specify preset add`, command files are written into agent directories (e.g., `.claude/commands/`).
|
||||
- If multiple presets or extensions provide the same command, the highest-priority version wins. On removal, the next-highest-priority version is restored automatically.
|
||||
- If no overrides or customizations exist, Spec Kit uses its core defaults.
|
||||
style overrides fill:transparent,stroke:#999
|
||||
style presets fill:transparent,stroke:#4a9eda
|
||||
style extensions fill:transparent,stroke:#4a9e4a
|
||||
style core fill:transparent,stroke:#e6a817
|
||||
```
|
||||
|
||||
**Templates** are resolved at **runtime** — Spec Kit walks the stack top-down and uses the first match. Project-local overrides (`.specify/templates/overrides/`) let you make one-off adjustments for a single project without creating a full preset. **Commands** are applied at **install time** — when you run `specify extension add` or `specify preset add`, command files are written into agent directories (e.g., `.claude/commands/`). If multiple presets or extensions provide the same command, the highest-priority version wins. On removal, the next-highest-priority version is restored automatically. If no overrides or customizations exist, Spec Kit uses its core defaults.
|
||||
|
||||
### Extensions — Add New Capabilities
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ This tells Spec Kit which feature directory to use when creating specs, plans, a
|
||||
```bash
|
||||
ls -la .claude/commands/ # Claude Code
|
||||
ls -la .gemini/commands/ # Gemini
|
||||
ls -la .cursor/skills/ # Cursor
|
||||
ls -la .cursor/commands/ # Cursor
|
||||
ls -la .pi/prompts/ # Pi Coding Agent
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-10T17:00:00Z",
|
||||
"updated_at": "2026-04-08T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||
"extensions": {
|
||||
"aide": {
|
||||
@@ -138,70 +138,6 @@
|
||||
"created_at": "2026-04-08T00:00:00Z",
|
||||
"updated_at": "2026-04-08T00:00:00Z"
|
||||
},
|
||||
"brownfield": {
|
||||
"name": "Brownfield Bootstrap",
|
||||
"id": "brownfield",
|
||||
"description": "Bootstrap spec-kit for existing codebases — auto-discover architecture and adopt SDD incrementally.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-brownfield/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-brownfield",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-brownfield",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-brownfield/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-brownfield/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"brownfield",
|
||||
"bootstrap",
|
||||
"existing-project",
|
||||
"migration",
|
||||
"onboarding"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
},
|
||||
"bugfix": {
|
||||
"name": "Bugfix Workflow",
|
||||
"id": "bugfix",
|
||||
"description": "Structured bugfix workflow — capture bugs, trace to spec artifacts, and patch specs surgically.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-bugfix/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-bugfix",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-bugfix",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-bugfix/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-bugfix/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"bugfix",
|
||||
"debugging",
|
||||
"workflow",
|
||||
"traceability",
|
||||
"maintenance"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-09T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
},
|
||||
"canon": {
|
||||
"name": "Canon",
|
||||
"id": "canon",
|
||||
@@ -237,39 +173,6 @@
|
||||
"created_at": "2026-03-29T00:00:00Z",
|
||||
"updated_at": "2026-03-29T00:00:00Z"
|
||||
},
|
||||
"ci-guard": {
|
||||
"name": "CI Guard",
|
||||
"id": "ci-guard",
|
||||
"description": "Spec compliance gates for CI/CD — verify specs exist, check drift, and block merges on gaps.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-ci-guard",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-ci-guard",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 5,
|
||||
"hooks": 2
|
||||
},
|
||||
"tags": [
|
||||
"ci-cd",
|
||||
"compliance",
|
||||
"governance",
|
||||
"quality-gate",
|
||||
"drift-detection",
|
||||
"automation"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-10T17:00:00Z",
|
||||
"updated_at": "2026-04-10T17:00:00Z"
|
||||
},
|
||||
"checkpoint": {
|
||||
"name": "Checkpoint Extension",
|
||||
"id": "checkpoint",
|
||||
@@ -967,38 +870,6 @@
|
||||
"created_at": "2026-03-26T00:00:00Z",
|
||||
"updated_at": "2026-03-26T00:00:00Z"
|
||||
},
|
||||
"memorylint": {
|
||||
"name": "MemoryLint",
|
||||
"id": "memorylint",
|
||||
"description": "Agent memory governance tool: Automatically audits and fixes boundary conflicts between AGENTS.md and the constitution.",
|
||||
"author": "RbBtSn0w",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/RbBtSn0w/spec-kit-extensions/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/RbBtSn0w/spec-kit-extensions",
|
||||
"homepage": "https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/memorylint",
|
||||
"documentation": "https://github.com/RbBtSn0w/spec-kit-extensions/blob/main/memorylint/README.md",
|
||||
"changelog": "https://github.com/RbBtSn0w/spec-kit-extensions/blob/main/memorylint/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.5.1"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"memory",
|
||||
"governance",
|
||||
"constitution",
|
||||
"agents-md",
|
||||
"process"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-09T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
},
|
||||
"onboard": {
|
||||
"name": "Onboard",
|
||||
"id": "onboard",
|
||||
@@ -1095,38 +966,6 @@
|
||||
"created_at": "2026-03-27T08:22:30Z",
|
||||
"updated_at": "2026-03-27T08:22:30Z"
|
||||
},
|
||||
"pr-bridge": {
|
||||
"name": "PR Bridge",
|
||||
"id": "pr-bridge",
|
||||
"description": "Auto-generate pull request descriptions, checklists, and summaries from spec artifacts.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"pull-request",
|
||||
"automation",
|
||||
"traceability",
|
||||
"workflow",
|
||||
"review"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
},
|
||||
"presetify": {
|
||||
"name": "Presetify",
|
||||
"id": "presetify",
|
||||
@@ -1428,8 +1267,8 @@
|
||||
"id": "review",
|
||||
"description": "Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification.",
|
||||
"author": "ismaelJimenez",
|
||||
"version": "1.0.1",
|
||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-review/archive/refs/tags/v1.0.1.zip",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-review/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/ismaelJimenez/spec-kit-review",
|
||||
"homepage": "https://github.com/ismaelJimenez/spec-kit-review",
|
||||
"documentation": "https://github.com/ismaelJimenez/spec-kit-review/blob/main/README.md",
|
||||
@@ -1455,7 +1294,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-06T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
"updated_at": "2026-03-06T00:00:00Z"
|
||||
},
|
||||
"security-review": {
|
||||
"name": "Security Review",
|
||||
@@ -1551,39 +1390,6 @@
|
||||
"created_at": "2026-03-18T00:00:00Z",
|
||||
"updated_at": "2026-03-18T00:00:00Z"
|
||||
},
|
||||
"spectest": {
|
||||
"name": "SpecTest",
|
||||
"id": "spectest",
|
||||
"description": "Auto-generate test scaffolds from spec criteria, map coverage, and find untested requirements.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-spectest/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-spectest",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-spectest",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-spectest/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-spectest/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"testing",
|
||||
"test-generation",
|
||||
"coverage",
|
||||
"quality",
|
||||
"automation",
|
||||
"traceability"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-10T16:00:00Z",
|
||||
"updated_at": "2026-04-10T16:00:00Z"
|
||||
},
|
||||
"staff-review": {
|
||||
"name": "Staff Review Extension",
|
||||
"id": "staff-review",
|
||||
@@ -1646,36 +1452,6 @@
|
||||
"created_at": "2026-03-16T00:00:00Z",
|
||||
"updated_at": "2026-03-16T00:00:00Z"
|
||||
},
|
||||
"status-report": {
|
||||
"name": "Status Report",
|
||||
"id": "status-report",
|
||||
"description": "Project status, feature progress, and next-action recommendations for spec-driven workflows.",
|
||||
"author": "Open-Agent-Tools",
|
||||
"version": "1.2.5",
|
||||
"download_url": "https://github.com/Open-Agent-Tools/spec-kit-status/archive/refs/tags/v1.2.5.zip",
|
||||
"repository": "https://github.com/Open-Agent-Tools/spec-kit-status",
|
||||
"homepage": "https://github.com/Open-Agent-Tools/spec-kit-status",
|
||||
"documentation": "https://github.com/Open-Agent-Tools/spec-kit-status/blob/main/README.md",
|
||||
"changelog": "https://github.com/Open-Agent-Tools/spec-kit-status/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"workflow",
|
||||
"project-management",
|
||||
"status"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-08T15:05:14Z",
|
||||
"updated_at": "2026-04-08T15:05:14Z"
|
||||
},
|
||||
"superb": {
|
||||
"name": "Superpowers Bridge",
|
||||
"id": "superb",
|
||||
@@ -1751,38 +1527,6 @@
|
||||
"created_at": "2026-03-02T00:00:00Z",
|
||||
"updated_at": "2026-03-02T00:00:00Z"
|
||||
},
|
||||
"tinyspec": {
|
||||
"name": "TinySpec",
|
||||
"id": "tinyspec",
|
||||
"description": "Lightweight single-file workflow for small tasks — skip the heavy multi-step SDD process.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-tinyspec",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-tinyspec",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"lightweight",
|
||||
"small-tasks",
|
||||
"workflow",
|
||||
"productivity",
|
||||
"efficiency"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
},
|
||||
"v-model": {
|
||||
"name": "V-Model Extension Pack",
|
||||
"id": "v-model",
|
||||
@@ -1820,8 +1564,8 @@
|
||||
"id": "verify",
|
||||
"description": "Post-implementation quality gate that validates implemented code against specification artifacts.",
|
||||
"author": "ismaelJimenez",
|
||||
"version": "1.0.3",
|
||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-verify/archive/refs/tags/v1.0.3.zip",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-verify/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/ismaelJimenez/spec-kit-verify",
|
||||
"homepage": "https://github.com/ismaelJimenez/spec-kit-verify",
|
||||
"documentation": "https://github.com/ismaelJimenez/spec-kit-verify/blob/main/README.md",
|
||||
@@ -1845,7 +1589,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-03T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
"updated_at": "2026-03-03T00:00:00Z"
|
||||
},
|
||||
"verify-tasks": {
|
||||
"name": "Verify Tasks Extension",
|
||||
@@ -1877,38 +1621,6 @@
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-16T00:00:00Z",
|
||||
"updated_at": "2026-03-16T00:00:00Z"
|
||||
},
|
||||
"worktree": {
|
||||
"name": "Worktree Isolation",
|
||||
"id": "worktree",
|
||||
"description": "Spawn isolated git worktrees for parallel feature development without checkout switching.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-worktree/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-worktree",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-worktree",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-worktree/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-worktree/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"worktree",
|
||||
"git",
|
||||
"parallel",
|
||||
"isolation",
|
||||
"workflow"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-09T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-06T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
|
||||
"extensions": {
|
||||
"git": {
|
||||
@@ -10,13 +10,27 @@
|
||||
"description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection",
|
||||
"author": "spec-kit-core",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"bundled": true,
|
||||
"download_url": "https://github.com/github/spec-kit/releases/download/ext-git-v1.0.0/git.zip",
|
||||
"tags": [
|
||||
"git",
|
||||
"branching",
|
||||
"workflow",
|
||||
"core"
|
||||
]
|
||||
},
|
||||
"selftest": {
|
||||
"name": "Spec Kit Self-Test Utility",
|
||||
"id": "selftest",
|
||||
"version": "1.0.0",
|
||||
"description": "Verifies catalog extensions by programmatically walking through the discovery, installation, and registration lifecycle.",
|
||||
"author": "spec-kit-core",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"download_url": "https://github.com/github/spec-kit/releases/download/selftest-v1.0.0/selftest.zip",
|
||||
"tags": [
|
||||
"testing",
|
||||
"core",
|
||||
"utility"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-09T08:00:00Z",
|
||||
"updated_at": "2026-04-06T06:30:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
|
||||
"presets": {
|
||||
"aide-in-place": {
|
||||
@@ -78,33 +78,6 @@
|
||||
"wave-dag"
|
||||
]
|
||||
},
|
||||
"multi-repo-branching": {
|
||||
"name": "Multi-Repo Branching",
|
||||
"id": "multi-repo-branching",
|
||||
"version": "1.0.0",
|
||||
"description": "Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases.",
|
||||
"author": "sakitA",
|
||||
"repository": "https://github.com/sakitA/spec-kit-preset-multi-repo-branching",
|
||||
"download_url": "https://github.com/sakitA/spec-kit-preset-multi-repo-branching/archive/refs/tags/v1.0.0.zip",
|
||||
"homepage": "https://github.com/sakitA/spec-kit-preset-multi-repo-branching",
|
||||
"documentation": "https://github.com/sakitA/spec-kit-preset-multi-repo-branching/blob/master/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 0,
|
||||
"commands": 2
|
||||
},
|
||||
"tags": [
|
||||
"multi-repo-branching",
|
||||
"multi-module",
|
||||
"submodules",
|
||||
"monorepo"
|
||||
],
|
||||
"created_at": "2026-04-09T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
},
|
||||
"pirate": {
|
||||
"name": "Pirate Speak (Full)",
|
||||
"id": "pirate",
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-03-10T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.json",
|
||||
"presets": {
|
||||
"lean": {
|
||||
"name": "Lean Workflow",
|
||||
"id": "lean",
|
||||
"version": "1.0.0",
|
||||
"description": "Minimal core workflow commands - just the prompt, just the artifact",
|
||||
"author": "github",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"bundled": true,
|
||||
"tags": [
|
||||
"lean",
|
||||
"minimal",
|
||||
"workflow",
|
||||
"core"
|
||||
]
|
||||
}
|
||||
}
|
||||
"presets": {}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
description: Create or update the project constitution.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
1. Create or update the project constitution and store it in `.specify/memory/constitution.md`.
|
||||
- Project name, guiding principles, non-negotiable rules
|
||||
- Derive from user input and existing repo context (README, docs)
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
description: Execute the implementation plan by processing all tasks in tasks.md.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
1. Read `.specify/feature.json` to get the feature directory path.
|
||||
|
||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md` and `<feature_directory>/plan.md` and `<feature_directory>/tasks.md`.
|
||||
|
||||
3. **Execute tasks** in order:
|
||||
- Complete each task before moving to the next
|
||||
- Mark completed tasks by changing `- [ ]` to `- [x]` in `<feature_directory>/tasks.md`
|
||||
- Halt on failure and report the issue
|
||||
|
||||
4. **Validate**: Verify all tasks are completed and the implementation matches the spec.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
description: Create a plan and store it in plan.md.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
1. Read `.specify/feature.json` to get the feature directory path.
|
||||
|
||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md`.
|
||||
|
||||
3. Create an implementation plan and store it in `<feature_directory>/plan.md`.
|
||||
- Technical context: tech stack, dependencies, project structure
|
||||
- Design decisions, architecture, file structure
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
description: Create a specification and store it in spec.md.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Ask the user** for the feature directory path (e.g., `specs/my-feature`). Do not proceed until provided.
|
||||
|
||||
2. Create the directory and write `.specify/feature.json`:
|
||||
```json
|
||||
{ "feature_directory": "<feature_directory>" }
|
||||
```
|
||||
|
||||
3. Create a specification from the user input and store it in `<feature_directory>/spec.md`.
|
||||
- Overview, functional requirements, user scenarios, success criteria
|
||||
- Every requirement must be testable
|
||||
- Make informed defaults for unspecified details
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
description: Create the tasks needed for implementation and store them in tasks.md.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
1. Read `.specify/feature.json` to get the feature directory path.
|
||||
|
||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md` and `<feature_directory>/plan.md`.
|
||||
|
||||
3. Create dependency-ordered implementation tasks and store them in `<feature_directory>/tasks.md`.
|
||||
- Every task uses checklist format: `- [ ] [TaskID] Description with file path`
|
||||
- Organized by phase: setup, foundational, user stories in priority order, polish
|
||||
@@ -1,50 +0,0 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
preset:
|
||||
id: "lean"
|
||||
name: "Lean Workflow"
|
||||
version: "1.0.0"
|
||||
description: "Minimal core workflow commands - just the prompt, just the artifact"
|
||||
author: "github"
|
||||
repository: "https://github.com/github/spec-kit"
|
||||
license: "MIT"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.6.0"
|
||||
|
||||
provides:
|
||||
templates:
|
||||
- type: "command"
|
||||
name: "speckit.specify"
|
||||
file: "commands/speckit.specify.md"
|
||||
description: "Lean specify - create spec.md from a feature description"
|
||||
replaces: "speckit.specify"
|
||||
|
||||
- type: "command"
|
||||
name: "speckit.plan"
|
||||
file: "commands/speckit.plan.md"
|
||||
description: "Lean plan - create plan.md from the spec"
|
||||
replaces: "speckit.plan"
|
||||
|
||||
- type: "command"
|
||||
name: "speckit.tasks"
|
||||
file: "commands/speckit.tasks.md"
|
||||
description: "Lean tasks - create tasks.md from plan and spec"
|
||||
replaces: "speckit.tasks"
|
||||
|
||||
- type: "command"
|
||||
name: "speckit.implement"
|
||||
file: "commands/speckit.implement.md"
|
||||
description: "Lean implement - execute tasks from tasks.md"
|
||||
replaces: "speckit.implement"
|
||||
|
||||
- type: "command"
|
||||
name: "speckit.constitution"
|
||||
file: "commands/speckit.constitution.md"
|
||||
description: "Lean constitution - create or update project constitution"
|
||||
replaces: "speckit.constitution"
|
||||
|
||||
tags:
|
||||
- "lean"
|
||||
- "minimal"
|
||||
- "workflow"
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.6.1"
|
||||
version = "0.5.1"
|
||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
@@ -41,8 +41,6 @@ packages = ["src/specify_cli"]
|
||||
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
||||
# Bundled extensions (installable via `specify extension add <name>`)
|
||||
"extensions/git" = "specify_cli/core_pack/extensions/git"
|
||||
# Bundled presets (installable via `specify preset add <name>` or `specify init --preset <name>`)
|
||||
"presets/lean" = "specify_cli/core_pack/presets/lean"
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
|
||||
@@ -621,31 +621,6 @@ def _locate_bundled_extension(extension_id: str) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def _locate_bundled_preset(preset_id: str) -> Path | None:
|
||||
"""Return the path to a bundled preset, or None.
|
||||
|
||||
Checks the wheel's core_pack first, then falls back to the
|
||||
source-checkout ``presets/<id>/`` directory.
|
||||
"""
|
||||
import re as _re
|
||||
if not _re.match(r'^[a-z0-9-]+$', preset_id):
|
||||
return None
|
||||
|
||||
core = _locate_core_pack()
|
||||
if core is not None:
|
||||
candidate = core / "presets" / preset_id
|
||||
if (candidate / "preset.yml").is_file():
|
||||
return candidate
|
||||
|
||||
# Source-checkout / editable install: look relative to repo root
|
||||
repo_root = Path(__file__).parent.parent.parent
|
||||
candidate = repo_root / "presets" / preset_id
|
||||
if (candidate / "preset.yml").is_file():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _install_shared_infra(
|
||||
project_path: Path,
|
||||
script_type: str,
|
||||
@@ -1291,44 +1266,27 @@ def init(
|
||||
preset_manager = PresetManager(project_path)
|
||||
speckit_ver = get_speckit_version()
|
||||
|
||||
# Try local directory first, then bundled, then catalog
|
||||
# Try local directory first, then catalog
|
||||
local_path = Path(preset).resolve()
|
||||
if local_path.is_dir() and (local_path / "preset.yml").exists():
|
||||
preset_manager.install_from_directory(local_path, speckit_ver)
|
||||
else:
|
||||
bundled_path = _locate_bundled_preset(preset)
|
||||
if bundled_path:
|
||||
preset_manager.install_from_directory(bundled_path, speckit_ver)
|
||||
preset_catalog = PresetCatalog(project_path)
|
||||
pack_info = preset_catalog.get_pack_info(preset)
|
||||
if not pack_info:
|
||||
console.print(f"[yellow]Warning:[/yellow] Preset '{preset}' not found in catalog. Skipping.")
|
||||
else:
|
||||
preset_catalog = PresetCatalog(project_path)
|
||||
pack_info = preset_catalog.get_pack_info(preset)
|
||||
if not pack_info:
|
||||
console.print(f"[yellow]Warning:[/yellow] Preset '{preset}' not found in catalog. Skipping.")
|
||||
elif pack_info.get("bundled") and not pack_info.get("download_url"):
|
||||
from .extensions import REINSTALL_COMMAND
|
||||
console.print(
|
||||
f"[yellow]Warning:[/yellow] Preset '{preset}' is bundled with spec-kit "
|
||||
f"but could not be found in the installed package."
|
||||
)
|
||||
console.print(
|
||||
"This usually means the spec-kit installation is incomplete or corrupted."
|
||||
)
|
||||
console.print(f"Try reinstalling: {REINSTALL_COMMAND}")
|
||||
else:
|
||||
zip_path = None
|
||||
try:
|
||||
zip_path = preset_catalog.download_pack(preset)
|
||||
preset_manager.install_from_zip(zip_path, speckit_ver)
|
||||
# Clean up downloaded ZIP to avoid cache accumulation
|
||||
try:
|
||||
zip_path = preset_catalog.download_pack(preset)
|
||||
preset_manager.install_from_zip(zip_path, speckit_ver)
|
||||
except PresetError as preset_err:
|
||||
console.print(f"[yellow]Warning:[/yellow] Failed to install preset '{preset}': {preset_err}")
|
||||
finally:
|
||||
if zip_path is not None:
|
||||
# Clean up downloaded ZIP to avoid cache accumulation
|
||||
try:
|
||||
zip_path.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
# Best-effort cleanup; failure to delete is non-fatal
|
||||
pass
|
||||
zip_path.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
# Best-effort cleanup; failure to delete is non-fatal
|
||||
pass
|
||||
except PresetError as preset_err:
|
||||
console.print(f"[yellow]Warning:[/yellow] Failed to install preset '{preset}': {preset_err}")
|
||||
except Exception as preset_err:
|
||||
console.print(f"[yellow]Warning:[/yellow] Failed to install preset: {preset_err}")
|
||||
|
||||
@@ -1380,7 +1338,7 @@ def init(
|
||||
step_num = 2
|
||||
|
||||
# Determine skill display mode for the next-steps panel.
|
||||
# Skills integrations (codex, kimi, agy, trae, cursor-agent) should show skill invocation syntax.
|
||||
# Skills integrations (codex, kimi, agy, trae) should show skill invocation syntax.
|
||||
from .integrations.base import SkillsIntegration as _SkillsInt
|
||||
_is_skills_integration = isinstance(resolved_integration, _SkillsInt)
|
||||
|
||||
@@ -1389,8 +1347,7 @@ def init(
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
|
||||
trae_skill_mode = selected_ai == "trae"
|
||||
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
|
||||
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode or cursor_agent_skill_mode
|
||||
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode
|
||||
|
||||
if codex_skill_mode and not ai_skills:
|
||||
# Integration path installed skills; show the helpful notice
|
||||
@@ -1399,9 +1356,6 @@ def init(
|
||||
if claude_skill_mode and not ai_skills:
|
||||
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
|
||||
step_num += 1
|
||||
if cursor_agent_skill_mode and not ai_skills:
|
||||
steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]")
|
||||
step_num += 1
|
||||
usage_label = "skills" if native_skill_mode else "slash commands"
|
||||
|
||||
def _display_cmd(name: str) -> str:
|
||||
@@ -1411,8 +1365,6 @@ def init(
|
||||
return f"/speckit-{name}"
|
||||
if kimi_skill_mode:
|
||||
return f"/skill:speckit-{name}"
|
||||
if cursor_agent_skill_mode:
|
||||
return f"/speckit-{name}"
|
||||
return f"/speckit.{name}"
|
||||
|
||||
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")
|
||||
@@ -2182,50 +2134,28 @@ def preset_add(
|
||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
||||
|
||||
elif pack_id:
|
||||
# Try bundled preset first, then catalog
|
||||
bundled_path = _locate_bundled_preset(pack_id)
|
||||
if bundled_path:
|
||||
console.print(f"Installing bundled preset [cyan]{pack_id}[/cyan]...")
|
||||
manifest = manager.install_from_directory(bundled_path, speckit_version, priority)
|
||||
catalog = PresetCatalog(project_root)
|
||||
pack_info = catalog.get_pack_info(pack_id)
|
||||
|
||||
if not pack_info:
|
||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' not found in catalog")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if not pack_info.get("_install_allowed", True):
|
||||
catalog_name = pack_info.get("_catalog_name", "unknown")
|
||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' is from the '{catalog_name}' catalog which is discovery-only (install not allowed).")
|
||||
console.print("Add the catalog with --install-allowed or install from the preset's repository directly with --from.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"Installing preset [cyan]{pack_info.get('name', pack_id)}[/cyan]...")
|
||||
|
||||
try:
|
||||
zip_path = catalog.download_pack(pack_id)
|
||||
manifest = manager.install_from_zip(zip_path, speckit_version, priority)
|
||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
||||
else:
|
||||
catalog = PresetCatalog(project_root)
|
||||
pack_info = catalog.get_pack_info(pack_id)
|
||||
|
||||
if not pack_info:
|
||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' not found in catalog")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Bundled presets should have been caught above; if we reach
|
||||
# here the bundled files are missing from the installation.
|
||||
if pack_info.get("bundled") and not pack_info.get("download_url"):
|
||||
from .extensions import REINSTALL_COMMAND
|
||||
console.print(
|
||||
f"[red]Error:[/red] Preset '{pack_id}' is bundled with spec-kit "
|
||||
f"but could not be found in the installed package."
|
||||
)
|
||||
console.print(
|
||||
"\nThis usually means the spec-kit installation is incomplete or corrupted."
|
||||
)
|
||||
console.print("Try reinstalling spec-kit:")
|
||||
console.print(f" {REINSTALL_COMMAND}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if not pack_info.get("_install_allowed", True):
|
||||
catalog_name = pack_info.get("_catalog_name", "unknown")
|
||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' is from the '{catalog_name}' catalog which is discovery-only (install not allowed).")
|
||||
console.print("Add the catalog with --install-allowed or install from the preset's repository directly with --from.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"Installing preset [cyan]{pack_info.get('name', pack_id)}[/cyan]...")
|
||||
|
||||
try:
|
||||
zip_path = catalog.download_pack(pack_id)
|
||||
manifest = manager.install_from_zip(zip_path, speckit_version, priority)
|
||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
||||
finally:
|
||||
if 'zip_path' in locals() and zip_path.exists():
|
||||
zip_path.unlink(missing_ok=True)
|
||||
finally:
|
||||
if 'zip_path' in locals() and zip_path.exists():
|
||||
zip_path.unlink(missing_ok=True)
|
||||
else:
|
||||
console.print("[red]Error:[/red] Specify a preset ID, --from URL, or --dev path")
|
||||
raise typer.Exit(1)
|
||||
@@ -3071,7 +3001,7 @@ def extension_add(
|
||||
priority: int = typer.Option(10, "--priority", help="Resolution priority (lower = higher precedence, default 10)"),
|
||||
):
|
||||
"""Install an extension."""
|
||||
from .extensions import ExtensionManager, ExtensionCatalog, ExtensionError, ValidationError, CompatibilityError, REINSTALL_COMMAND
|
||||
from .extensions import ExtensionManager, ExtensionCatalog, ExtensionError, ValidationError, CompatibilityError
|
||||
|
||||
project_root = Path.cwd()
|
||||
|
||||
@@ -3173,19 +3103,6 @@ def extension_add(
|
||||
manifest = manager.install_from_directory(bundled_path, speckit_version, priority=priority)
|
||||
|
||||
if bundled_path is None:
|
||||
# Bundled extensions without a download URL must come from the local package
|
||||
if ext_info.get("bundled") and not ext_info.get("download_url"):
|
||||
console.print(
|
||||
f"[red]Error:[/red] Extension '{ext_info['id']}' is bundled with spec-kit "
|
||||
f"but could not be found in the installed package."
|
||||
)
|
||||
console.print(
|
||||
"\nThis usually means the spec-kit installation is incomplete or corrupted."
|
||||
)
|
||||
console.print("Try reinstalling spec-kit:")
|
||||
console.print(f" {REINSTALL_COMMAND}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Enforce install_allowed policy
|
||||
if not ext_info.get("_install_allowed", True):
|
||||
catalog_name = ext_info.get("_catalog_name", "community")
|
||||
|
||||
@@ -38,8 +38,6 @@ _FALLBACK_CORE_COMMAND_NAMES = frozenset({
|
||||
})
|
||||
EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$")
|
||||
|
||||
REINSTALL_COMMAND = "uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git"
|
||||
|
||||
|
||||
def _load_core_command_names() -> frozenset[str]:
|
||||
"""Discover bundled core command names from the packaged templates.
|
||||
@@ -1872,14 +1870,6 @@ class ExtensionCatalog:
|
||||
if not ext_info:
|
||||
raise ExtensionError(f"Extension '{extension_id}' not found in catalog")
|
||||
|
||||
# Bundled extensions without a download URL must be installed locally
|
||||
if ext_info.get("bundled") and not ext_info.get("download_url"):
|
||||
raise ExtensionError(
|
||||
f"Extension '{extension_id}' is bundled with spec-kit and has no download URL. "
|
||||
f"It should be installed from the local package. "
|
||||
f"Try reinstalling: {REINSTALL_COMMAND}"
|
||||
)
|
||||
|
||||
download_url = ext_info.get("download_url")
|
||||
if not download_url:
|
||||
raise ExtensionError(f"Extension '{extension_id}' has no download URL")
|
||||
@@ -2180,7 +2170,6 @@ class HookExecutor:
|
||||
codex_skill_mode = selected_ai == "codex" and bool(init_options.get("ai_skills"))
|
||||
claude_skill_mode = selected_ai == "claude" and bool(init_options.get("ai_skills"))
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
cursor_skill_mode = selected_ai == "cursor-agent" and bool(init_options.get("ai_skills"))
|
||||
|
||||
skill_name = self._skill_name_from_command(command_id)
|
||||
if codex_skill_mode and skill_name:
|
||||
@@ -2189,8 +2178,6 @@ class HookExecutor:
|
||||
return f"/{skill_name}"
|
||||
if kimi_skill_mode and skill_name:
|
||||
return f"/skill:{skill_name}"
|
||||
if cursor_skill_mode and skill_name:
|
||||
return f"/{skill_name}"
|
||||
|
||||
return f"/{command_id}"
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ def _register_builtins() -> None:
|
||||
from .auggie import AuggieIntegration
|
||||
from .bob import BobIntegration
|
||||
from .claude import ClaudeIntegration
|
||||
from .codebuddy import CodebuddyIntegration
|
||||
from .codex import CodexIntegration
|
||||
from .codebuddy import CodebuddyIntegration
|
||||
from .copilot import CopilotIntegration
|
||||
from .cursor_agent import CursorAgentIntegration
|
||||
from .forge import ForgeIntegration
|
||||
@@ -80,8 +80,8 @@ def _register_builtins() -> None:
|
||||
_register(AuggieIntegration())
|
||||
_register(BobIntegration())
|
||||
_register(ClaudeIntegration())
|
||||
_register(CodebuddyIntegration())
|
||||
_register(CodexIntegration())
|
||||
_register(CodebuddyIntegration())
|
||||
_register(CopilotIntegration())
|
||||
_register(CursorAgentIntegration())
|
||||
_register(ForgeIntegration())
|
||||
|
||||
@@ -1,39 +1,21 @@
|
||||
"""Cursor IDE integration.
|
||||
"""Cursor IDE integration."""
|
||||
|
||||
Cursor Agent uses the ``.cursor/skills/speckit-<name>/SKILL.md`` layout.
|
||||
Commands are deprecated; ``--skills`` defaults to ``True``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class CursorAgentIntegration(SkillsIntegration):
|
||||
class CursorAgentIntegration(MarkdownIntegration):
|
||||
key = "cursor-agent"
|
||||
config = {
|
||||
"name": "Cursor",
|
||||
"folder": ".cursor/",
|
||||
"commands_subdir": "skills",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".cursor/skills",
|
||||
"dir": ".cursor/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
"extension": ".md",
|
||||
}
|
||||
|
||||
context_file = ".cursor/rules/specify-rules.mdc"
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Install as agent skills (recommended for Cursor)",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1587,16 +1587,6 @@ class PresetCatalog:
|
||||
f"Preset '{pack_id}' not found in catalog"
|
||||
)
|
||||
|
||||
# Bundled presets without a download URL must be installed locally
|
||||
if pack_info.get("bundled") and not pack_info.get("download_url"):
|
||||
from .extensions import REINSTALL_COMMAND
|
||||
raise PresetError(
|
||||
f"Preset '{pack_id}' is bundled with spec-kit and has no download URL. "
|
||||
f"It should be installed from the local package. "
|
||||
f"Use 'specify preset add {pack_id}' to install from the bundled package, "
|
||||
f"or reinstall spec-kit if the bundled files are missing: {REINSTALL_COMMAND}"
|
||||
)
|
||||
|
||||
if not pack_info.get("_install_allowed", True):
|
||||
catalog_name = pack_info.get("_catalog_name", "unknown")
|
||||
raise PresetError(
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
"""Tests for CursorAgentIntegration."""
|
||||
|
||||
from .test_integration_base_skills import SkillsIntegrationTests
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestCursorAgentIntegration(SkillsIntegrationTests):
|
||||
class TestCursorAgentIntegration(MarkdownIntegrationTests):
|
||||
KEY = "cursor-agent"
|
||||
FOLDER = ".cursor/"
|
||||
COMMANDS_SUBDIR = "skills"
|
||||
REGISTRAR_DIR = ".cursor/skills"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".cursor/commands"
|
||||
CONTEXT_FILE = ".cursor/rules/specify-rules.mdc"
|
||||
|
||||
|
||||
class TestCursorAgentAutoPromote:
|
||||
"""--ai cursor-agent auto-promotes to integration path."""
|
||||
|
||||
def test_ai_cursor_agent_without_ai_skills_auto_promotes(self, tmp_path):
|
||||
"""--ai cursor-agent should work the same as --integration cursor-agent."""
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
target = tmp_path / "test-proj"
|
||||
result = runner.invoke(app, ["init", str(target), "--ai", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"])
|
||||
|
||||
assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}"
|
||||
assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists()
|
||||
|
||||
|
||||
@@ -2995,122 +2995,6 @@ class TestExtensionAddCLI:
|
||||
f"but was called with '{download_called_with[0]}'"
|
||||
)
|
||||
|
||||
def test_add_bundled_extension_not_found_gives_clear_error(self, tmp_path):
|
||||
"""extension add should give a clear error when a bundled extension is not found locally."""
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch, MagicMock
|
||||
from specify_cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
# Create project structure
|
||||
project_dir = tmp_path / "test-project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / ".specify").mkdir()
|
||||
(project_dir / ".specify" / "extensions").mkdir(parents=True)
|
||||
|
||||
# Mock catalog that returns a bundled extension without download_url
|
||||
mock_catalog = MagicMock()
|
||||
mock_catalog.get_extension_info.return_value = {
|
||||
"id": "git",
|
||||
"name": "Git Branching Workflow",
|
||||
"version": "1.0.0",
|
||||
"description": "Git branching extension",
|
||||
"bundled": True,
|
||||
"_install_allowed": True,
|
||||
}
|
||||
mock_catalog.search.return_value = []
|
||||
|
||||
with patch("specify_cli.extensions.ExtensionCatalog", return_value=mock_catalog), \
|
||||
patch("specify_cli._locate_bundled_extension", return_value=None), \
|
||||
patch.object(Path, "cwd", return_value=project_dir):
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["extension", "add", "git"],
|
||||
catch_exceptions=True,
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "bundled with spec-kit" in result.output
|
||||
assert "reinstall" in result.output.lower()
|
||||
|
||||
|
||||
class TestDownloadExtensionBundled:
|
||||
"""Tests for download_extension handling of bundled extensions."""
|
||||
|
||||
def test_download_extension_raises_for_bundled(self, temp_dir):
|
||||
"""download_extension should raise a clear error for bundled extensions without a URL."""
|
||||
from unittest.mock import patch
|
||||
|
||||
project_dir = temp_dir / "project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / ".specify").mkdir()
|
||||
|
||||
catalog = ExtensionCatalog(project_dir)
|
||||
|
||||
bundled_ext_info = {
|
||||
"name": "Git Branching Workflow",
|
||||
"id": "git",
|
||||
"version": "1.0.0",
|
||||
"description": "Git workflow",
|
||||
"bundled": True,
|
||||
}
|
||||
|
||||
with patch.object(catalog, "get_extension_info", return_value=bundled_ext_info):
|
||||
with pytest.raises(ExtensionError, match="bundled with spec-kit"):
|
||||
catalog.download_extension("git")
|
||||
|
||||
def test_download_extension_allows_bundled_with_url(self, temp_dir):
|
||||
"""download_extension should allow bundled extensions that have a download_url (newer version)."""
|
||||
from unittest.mock import patch, MagicMock
|
||||
import urllib.request
|
||||
|
||||
project_dir = temp_dir / "project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / ".specify").mkdir()
|
||||
|
||||
catalog = ExtensionCatalog(project_dir)
|
||||
|
||||
bundled_with_url = {
|
||||
"name": "Git Branching Workflow",
|
||||
"id": "git",
|
||||
"version": "2.0.0",
|
||||
"description": "Git workflow",
|
||||
"bundled": True,
|
||||
"download_url": "https://example.com/git-2.0.0.zip",
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = b"fake zip data"
|
||||
mock_response.__enter__ = lambda s: s
|
||||
mock_response.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
with patch.object(catalog, "get_extension_info", return_value=bundled_with_url), \
|
||||
patch.object(urllib.request, "urlopen", return_value=mock_response):
|
||||
result = catalog.download_extension("git")
|
||||
assert result.name == "git-2.0.0.zip"
|
||||
|
||||
def test_download_extension_raises_no_url_for_non_bundled(self, temp_dir):
|
||||
"""download_extension should raise 'no download URL' for non-bundled extensions without URL."""
|
||||
from unittest.mock import patch
|
||||
|
||||
project_dir = temp_dir / "project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / ".specify").mkdir()
|
||||
|
||||
catalog = ExtensionCatalog(project_dir)
|
||||
|
||||
non_bundled_ext_info = {
|
||||
"name": "Some Extension",
|
||||
"id": "some-ext",
|
||||
"version": "1.0.0",
|
||||
"description": "Test",
|
||||
}
|
||||
|
||||
with patch.object(catalog, "get_extension_info", return_value=non_bundled_ext_info):
|
||||
with pytest.raises(ExtensionError, match="has no download URL"):
|
||||
catalog.download_extension("some-ext")
|
||||
|
||||
|
||||
class TestExtensionUpdateCLI:
|
||||
"""CLI integration tests for extension update command."""
|
||||
|
||||
@@ -2865,182 +2865,3 @@ class TestPresetEnableDisable:
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "corrupted state" in result.output.lower()
|
||||
|
||||
|
||||
# ===== Lean Preset Tests =====
|
||||
|
||||
|
||||
LEAN_PRESET_DIR = Path(__file__).parent.parent / "presets" / "lean"
|
||||
|
||||
LEAN_COMMAND_NAMES = [
|
||||
"speckit.specify",
|
||||
"speckit.plan",
|
||||
"speckit.tasks",
|
||||
"speckit.implement",
|
||||
"speckit.constitution",
|
||||
]
|
||||
|
||||
|
||||
class TestLeanPreset:
|
||||
"""Tests for the lean preset that ships with the repo."""
|
||||
|
||||
def test_lean_preset_exists(self):
|
||||
"""Verify the lean preset directory and manifest exist."""
|
||||
assert LEAN_PRESET_DIR.exists()
|
||||
assert (LEAN_PRESET_DIR / "preset.yml").exists()
|
||||
|
||||
def test_lean_manifest_valid(self):
|
||||
"""Verify the lean preset manifest is valid."""
|
||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
||||
assert manifest.id == "lean"
|
||||
assert manifest.name == "Lean Workflow"
|
||||
assert manifest.version == "1.0.0"
|
||||
assert len(manifest.templates) == 5 # 5 commands
|
||||
|
||||
def test_lean_provides_core_workflow_commands(self):
|
||||
"""Verify the lean preset provides overrides for core workflow commands."""
|
||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
||||
provided_names = {t["name"] for t in manifest.templates}
|
||||
for name in LEAN_COMMAND_NAMES:
|
||||
assert name in provided_names, f"Lean preset missing command: {name}"
|
||||
|
||||
def test_lean_command_files_exist(self):
|
||||
"""Verify that all declared command files actually exist on disk."""
|
||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
||||
for tmpl in manifest.templates:
|
||||
tmpl_path = LEAN_PRESET_DIR / tmpl["file"]
|
||||
assert tmpl_path.exists(), f"Missing command file: {tmpl['file']}"
|
||||
|
||||
def test_lean_commands_have_no_scripts(self):
|
||||
"""Verify lean commands have no scripts or agent_scripts in frontmatter."""
|
||||
from specify_cli.agents import CommandRegistrar
|
||||
|
||||
for name in LEAN_COMMAND_NAMES:
|
||||
cmd_path = LEAN_PRESET_DIR / "commands" / f"speckit.{name.split('.')[-1]}.md"
|
||||
content = cmd_path.read_text()
|
||||
frontmatter, _ = CommandRegistrar.parse_frontmatter(content)
|
||||
assert "scripts" not in frontmatter, f"{name} should not have scripts in frontmatter"
|
||||
assert "agent_scripts" not in frontmatter, f"{name} should not have agent_scripts in frontmatter"
|
||||
|
||||
def test_lean_commands_have_no_hooks(self):
|
||||
"""Verify lean commands do not contain extension hook boilerplate."""
|
||||
for name in LEAN_COMMAND_NAMES:
|
||||
cmd_path = LEAN_PRESET_DIR / "commands" / f"speckit.{name.split('.')[-1]}.md"
|
||||
content = cmd_path.read_text()
|
||||
assert "hooks." not in content, f"{name} should not reference extension hooks"
|
||||
assert "extensions.yml" not in content, f"{name} should not reference extensions.yml"
|
||||
|
||||
def test_install_lean_preset(self, project_dir):
|
||||
"""Test installing the lean preset from its directory."""
|
||||
manager = PresetManager(project_dir)
|
||||
manifest = manager.install_from_directory(LEAN_PRESET_DIR, "0.6.0")
|
||||
assert manifest.id == "lean"
|
||||
assert manager.registry.is_installed("lean")
|
||||
|
||||
def test_lean_overrides_commands(self, project_dir):
|
||||
"""Test that lean preset overrides are resolved correctly."""
|
||||
manager = PresetManager(project_dir)
|
||||
manager.install_from_directory(LEAN_PRESET_DIR, "0.6.0")
|
||||
|
||||
resolver = PresetResolver(project_dir)
|
||||
for name in LEAN_COMMAND_NAMES:
|
||||
result = resolver.resolve(name, template_type="command")
|
||||
assert result is not None, f"Lean override for {name} not resolved"
|
||||
|
||||
|
||||
# ===== Bundled Preset Locator Tests =====
|
||||
|
||||
|
||||
class TestBundledPresetLocator:
|
||||
"""Tests for _locate_bundled_preset discovery function."""
|
||||
|
||||
def test_locate_bundled_lean_preset(self):
|
||||
"""_locate_bundled_preset finds the lean preset."""
|
||||
from specify_cli import _locate_bundled_preset
|
||||
|
||||
path = _locate_bundled_preset("lean")
|
||||
assert path is not None
|
||||
assert (path / "preset.yml").is_file()
|
||||
|
||||
def test_locate_bundled_preset_not_found(self):
|
||||
"""_locate_bundled_preset returns None for nonexistent preset."""
|
||||
from specify_cli import _locate_bundled_preset
|
||||
|
||||
path = _locate_bundled_preset("nonexistent-preset")
|
||||
assert path is None
|
||||
|
||||
def test_locate_bundled_preset_rejects_invalid_id(self):
|
||||
"""_locate_bundled_preset rejects IDs with invalid characters."""
|
||||
from specify_cli import _locate_bundled_preset
|
||||
|
||||
assert _locate_bundled_preset("../escape") is None
|
||||
assert _locate_bundled_preset("UPPERCASE") is None
|
||||
assert _locate_bundled_preset("has spaces") is None
|
||||
|
||||
def test_bundled_preset_add_via_cli(self, project_dir):
|
||||
"""Test that 'specify preset add lean' installs the bundled preset."""
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch
|
||||
from specify_cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
with patch.object(Path, "cwd", return_value=project_dir), \
|
||||
patch("specify_cli.get_speckit_version", return_value="0.6.0"):
|
||||
result = runner.invoke(app, ["preset", "add", "lean"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "Lean Workflow" in result.output
|
||||
assert "installed" in result.output.lower()
|
||||
|
||||
def test_bundled_preset_in_catalog(self):
|
||||
"""Verify the lean preset is listed in catalog.json with bundled marker."""
|
||||
catalog_path = Path(__file__).parent.parent / "presets" / "catalog.json"
|
||||
catalog = json.loads(catalog_path.read_text())
|
||||
assert "lean" in catalog["presets"]
|
||||
assert catalog["presets"]["lean"]["bundled"] is True
|
||||
assert "download_url" not in catalog["presets"]["lean"]
|
||||
|
||||
def test_bundled_preset_download_raises_error(self, project_dir):
|
||||
"""download_pack raises PresetError for bundled presets without download_url."""
|
||||
catalog = PresetCatalog(project_dir)
|
||||
|
||||
catalog_data = {
|
||||
"test-bundled": {
|
||||
"name": "Test Bundled",
|
||||
"version": "1.0.0",
|
||||
"bundled": True,
|
||||
}
|
||||
}
|
||||
from unittest.mock import patch
|
||||
with patch.object(catalog, "_get_merged_packs", return_value=catalog_data):
|
||||
with pytest.raises(PresetError, match="bundled with spec-kit"):
|
||||
catalog.download_pack("test-bundled")
|
||||
|
||||
def test_bundled_preset_missing_locally_cli_error(self, project_dir):
|
||||
"""CLI shows clear error when bundled preset cannot be found locally."""
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch
|
||||
from specify_cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
# Patch _locate_bundled_preset to return None (simulating missing files)
|
||||
# and mock the catalog to return a bundled entry for "lean"
|
||||
fake_pack_info = {
|
||||
"id": "lean",
|
||||
"name": "Lean Workflow",
|
||||
"version": "1.0.0",
|
||||
"bundled": True,
|
||||
"_install_allowed": True,
|
||||
}
|
||||
with patch.object(Path, "cwd", return_value=project_dir), \
|
||||
patch("specify_cli._locate_bundled_preset", return_value=None), \
|
||||
patch("specify_cli.presets.PresetCatalog") as MockCatalog:
|
||||
MockCatalog.return_value.get_pack_info.return_value = fake_pack_info
|
||||
result = runner.invoke(app, ["preset", "add", "lean"])
|
||||
|
||||
# Should fail with a helpful error explaining this is a bundled preset
|
||||
# and suggesting how to recover.
|
||||
assert result.exit_code == 1
|
||||
output = strip_ansi(result.output).lower()
|
||||
assert "bundled" in output, result.output
|
||||
assert "reinstall" in output, result.output
|
||||
|
||||
Reference in New Issue
Block a user