diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b90568c..c12f0ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,7 +81,7 @@ jobs: cat > release_notes.md << EOF Template release ${{ steps.get_tag.outputs.new_version }} - Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, and opencode. + Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, and Windsurf. Now includes per-script variants for POSIX shell (sh) and PowerShell (ps). @@ -98,6 +98,8 @@ jobs: - spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip EOF echo "Generated release notes:" @@ -122,6 +124,8 @@ jobs: spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \ --title "Spec Kit Templates - $VERSION_NO_V" \ --notes-file release_notes.md env: diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index d45f33e..8d7eaf2 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -154,13 +154,16 @@ build_variant() { opencode) mkdir -p "$base_dir/.opencode/command" generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;; + windsurf) + mkdir -p "$base_dir/.windsurf/workflows" + generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;; esac ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" } # Determine agent list -ALL_AGENTS=(claude gemini copilot cursor qwen opencode) +ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf) ALL_SCRIPTS=(sh ps) diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c7fb657 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,230 @@ +# AGENTS.md + +## About Spec Kit and Specify + +**GitHub Spec Kit** is a comprehensive toolkit for implementing Spec-Driven Development (SDD) - a methodology that emphasizes creating clear specifications before implementation. The toolkit includes templates, scripts, and workflows that guide development teams through a structured approach to building software. + +**Specify CLI** is the command-line interface that bootstraps projects with the Spec Kit framework. It sets up the necessary directory structures, templates, and AI agent integrations to support the Spec-Driven Development workflow. + +The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices. + +--- + +## Adding New Agent Support + +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. + +### Overview + +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.) + +### Current Supported Agents + +| 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/prompts/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code | +| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI | +| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI | +| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI | +| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows | + +### Step-by-Step Integration Guide + +Follow these steps to add a new agent (using Windsurf as an example): + +#### 1. Update AI_CHOICES Constant + +Add the new agent to the `AI_CHOICES` dictionary in `src/specify_cli/__init__.py`: + +```python +AI_CHOICES = { + "copilot": "GitHub Copilot", + "claude": "Claude Code", + "gemini": "Gemini CLI", + "cursor": "Cursor", + "qwen": "Qwen Code", + "opencode": "opencode", + "windsurf": "Windsurf" # Add new agent here +} +``` + +#### 2. Update CLI Help Text + +Update all help text and examples to include the new agent: + +- Command option help: `--ai` parameter description +- Function docstrings and examples +- Error messages with agent lists + +#### 3. Update Release Package Script + +Modify `.github/workflows/scripts/create-release-packages.sh`: + +##### Add to ALL_AGENTS array: +```bash +ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf) +``` + +##### 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 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 +$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md' +``` + +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 + } +} +``` + +#### 5. 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**: Skip CLI checks for IDE-based agents (Copilot, Windsurf). + +## Agent Categories + +### CLI-Based Agents +Require a command-line tool to be installed: +- **Claude Code**: `claude` CLI +- **Gemini CLI**: `gemini` CLI +- **Cursor**: `cursor-agent` CLI +- **Qwen Code**: `qwen` CLI +- **opencode**: `opencode` CLI + +### IDE-Based Agents +Work within integrated development environments: +- **GitHub Copilot**: Built into VS Code/compatible editors +- **Windsurf**: Built into Windsurf IDE + +## Command File Formats + +### Markdown Format +Used by: Claude, Cursor, opencode, Windsurf + +```markdown +--- +description: "Command description" +--- + +Command content with {SCRIPT} and $ARGUMENTS placeholders. +``` + +### TOML Format +Used by: Gemini, Qwen + +```toml +description = "Command description" + +prompt = """ +Command content with {SCRIPT} and {{args}} placeholders. +""" +``` + +## Directory Conventions + +- **CLI agents**: Usually `./commands/` +- **IDE agents**: Follow IDE-specific patterns: + - Copilot: `.github/prompts/` + - Cursor: `.cursor/commands/` + - Windsurf: `.windsurf/workflows/` + +## Argument Patterns + +Different agents use different argument placeholders: +- **Markdown/prompt-based**: `$ARGUMENTS` +- **TOML-based**: `{{args}}` +- **Script placeholders**: `{SCRIPT}` (replaced with actual script path) +- **Agent placeholders**: `__AGENT__` (replaced with agent name) + +## Testing New Agent Integration + +1. **Build test**: Run package creation script locally +2. **CLI test**: Test `specify init --ai ` 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. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated +2. **Missing CLI checks**: Only add for agents that actually have CLI tools +3. **Wrong argument format**: Use correct placeholder format for each agent type +4. **Directory naming**: Follow agent-specific conventions exactly +5. **Help text inconsistency**: Update all user-facing text consistently + +## 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 + +--- + +*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.* \ No newline at end of file diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index a79edd9..ba3ab33 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -4,7 +4,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel) CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH" NEW_PLAN="$FEATURE_DIR/plan.md" -CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md" +CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"; WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md" AGENT_TYPE="$1" [ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; } echo "=== Updating agent context files for feature $CURRENT_BRANCH ===" @@ -54,13 +54,15 @@ case "$AGENT_TYPE" in cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;; qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;; opencode) update_agent_file "$AGENTS_FILE" "opencode" ;; + windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;; "") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; \ [ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \ [ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \ [ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \ [ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \ [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \ - if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; - *) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;; + [ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf"; \ + if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ] && [ ! -f "$WINDSURF_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; + *) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode|windsurf)"; exit 1 ;; esac -echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode]" +echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]" diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index e9f3455..0a12728 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -15,6 +15,7 @@ $copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md' $cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc' $qwenFile = Join-Path $repoRoot 'QWEN.md' $agentsFile = Join-Path $repoRoot 'AGENTS.md' +$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md' Write-Output "=== Updating agent context files for feature $currentBranch ===" @@ -75,6 +76,7 @@ switch ($AgentType) { 'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' } 'qwen' { Update-AgentFile $qwenFile 'Qwen Code' } 'opencode' { Update-AgentFile $agentsFile 'opencode' } + 'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' } '' { foreach ($pair in @( @{file=$claudeFile; name='Claude Code'}, @@ -82,16 +84,17 @@ switch ($AgentType) { @{file=$copilotFile; name='GitHub Copilot'}, @{file=$cursorFile; name='Cursor IDE'}, @{file=$qwenFile; name='Qwen Code'}, - @{file=$agentsFile; name='opencode'} + @{file=$agentsFile; name='opencode'}, + @{file=$windsurfFile; name='Windsurf'} )) { if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name } } - if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile)) { + if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile) -and -not (Test-Path $windsurfFile)) { Write-Output 'No agent context files found. Creating Claude Code context file by default.' Update-AgentFile $claudeFile 'Claude Code' } } - Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode or leave empty for all."; exit 1 } + Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf or leave empty for all."; exit 1 } } Write-Output '' @@ -101,4 +104,4 @@ if ($newFramework) { Write-Output "- Added framework: $newFramework" } if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" } Write-Output '' -Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode]' +Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]' diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index d6c4940..2c1c3c3 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -72,7 +72,8 @@ AI_CHOICES = { "gemini": "Gemini CLI", "cursor": "Cursor", "qwen": "Qwen Code", - "opencode": "opencode" + "opencode": "opencode", + "windsurf": "Windsurf" } # Add script type choices SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} @@ -750,7 +751,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), - ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen or opencode"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode or windsurf"), script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), @@ -764,7 +765,7 @@ def init( This command will: 1. Check that required tools are installed (git is optional) - 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code or opencode) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode or Windsurf) 3. Download the appropriate template from GitHub 4. Extract the template to a new project directory or current directory 5. Initialize a fresh git repository (if not --no-git and no existing repo) @@ -777,7 +778,7 @@ def init( specify init my-project --ai copilot --no-git specify init my-project --ai cursor specify init my-project --ai qwen - specify init my-project --ai opencode + specify init my-project --ai windsurf specify init --ignore-agent-tools my-project specify init --here --ai claude specify init --here @@ -1002,6 +1003,7 @@ def check(): tracker.add("qwen", "Qwen Code CLI") tracker.add("code", "VS Code (for GitHub Copilot)") tracker.add("cursor-agent", "Cursor IDE agent (optional)") + tracker.add("windsurf", "Windsurf IDE (optional)") tracker.add("opencode", "opencode") git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker) @@ -1012,6 +1014,7 @@ def check(): if not code_ok: code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker) cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker) + windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker) opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker) console.print(tracker.render()) @@ -1020,7 +1023,7 @@ def check(): if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok): + if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or opencode_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")