diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fa20c809..5b37ed0b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,6 @@ name: Lint +permissions: + contents: read on: push: diff --git a/.github/workflows/scripts/create-github-release.sh b/.github/workflows/scripts/create-github-release.sh index 7490dfdf..09d60b6d 100644 --- a/.github/workflows/scripts/create-github-release.sh +++ b/.github/workflows/scripts/create-github-release.sh @@ -42,6 +42,8 @@ gh release create "$VERSION" \ .genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \ .genreleases/spec-kit-template-amp-sh-"$VERSION".zip \ .genreleases/spec-kit-template-amp-ps-"$VERSION".zip \ + .genreleases/spec-kit-template-shai-sh-"$VERSION".zip \ + .genreleases/spec-kit-template-shai-ps-"$VERSION".zip \ .genreleases/spec-kit-template-q-sh-"$VERSION".zip \ .genreleases/spec-kit-template-q-ps-"$VERSION".zip \ .genreleases/spec-kit-template-bob-sh-"$VERSION".zip \ diff --git a/.github/workflows/scripts/create-release-packages.ps1 b/.github/workflows/scripts/create-release-packages.ps1 new file mode 100644 index 00000000..f935dbe8 --- /dev/null +++ b/.github/workflows/scripts/create-release-packages.ps1 @@ -0,0 +1,416 @@ +#!/usr/bin/env pwsh +#requires -Version 7.0 + +<# +.SYNOPSIS + Build Spec Kit template release archives for each supported AI assistant and script type. + +.DESCRIPTION + create-release-packages.ps1 (workflow-local) + Build Spec Kit template release archives for each supported AI assistant and script type. + +.PARAMETER Version + Version string with leading 'v' (e.g., v0.2.0) + +.PARAMETER Agents + Comma or space separated subset of agents to build (default: all) + Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, q + +.PARAMETER Scripts + Comma or space separated subset of script types to build (default: both) + Valid scripts: sh, ps + +.EXAMPLE + .\create-release-packages.ps1 -Version v0.2.0 + +.EXAMPLE + .\create-release-packages.ps1 -Version v0.2.0 -Agents claude,copilot -Scripts sh + +.EXAMPLE + .\create-release-packages.ps1 -Version v0.2.0 -Agents claude -Scripts ps +#> + +param( + [Parameter(Mandatory=$true, Position=0)] + [string]$Version, + + [Parameter(Mandatory=$false)] + [string]$Agents = "", + + [Parameter(Mandatory=$false)] + [string]$Scripts = "" +) + +$ErrorActionPreference = "Stop" + +# Validate version format +if ($Version -notmatch '^v\d+\.\d+\.\d+$') { + Write-Error "Version must look like v0.0.0" + exit 1 +} + +Write-Host "Building release packages for $Version" + +# Create and use .genreleases directory for all build artifacts +$GenReleasesDir = ".genreleases" +if (Test-Path $GenReleasesDir) { + Remove-Item -Path $GenReleasesDir -Recurse -Force -ErrorAction SilentlyContinue +} +New-Item -ItemType Directory -Path $GenReleasesDir -Force | Out-Null + +function Rewrite-Paths { + param([string]$Content) + + $Content = $Content -replace '(/?)\bmemory/', '.specify/memory/' + $Content = $Content -replace '(/?)\bscripts/', '.specify/scripts/' + $Content = $Content -replace '(/?)\btemplates/', '.specify/templates/' + return $Content +} + +function Generate-Commands { + param( + [string]$Agent, + [string]$Extension, + [string]$ArgFormat, + [string]$OutputDir, + [string]$ScriptVariant + ) + + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null + + $templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue + + foreach ($template in $templates) { + $name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name) + + # Read file content and normalize line endings + $fileContent = (Get-Content -Path $template.FullName -Raw) -replace "`r`n", "`n" + + # Extract description from YAML frontmatter + $description = "" + if ($fileContent -match '(?m)^description:\s*(.+)$') { + $description = $matches[1] + } + + # Extract script command from YAML frontmatter + $scriptCommand = "" + if ($fileContent -match "(?m)^\s*${ScriptVariant}:\s*(.+)$") { + $scriptCommand = $matches[1] + } + + if ([string]::IsNullOrEmpty($scriptCommand)) { + Write-Warning "No script command found for $ScriptVariant in $($template.Name)" + $scriptCommand = "(Missing script command for $ScriptVariant)" + } + + # Extract agent_script command from YAML frontmatter if present + $agentScriptCommand = "" + if ($fileContent -match "(?ms)agent_scripts:.*?^\s*${ScriptVariant}:\s*(.+?)$") { + $agentScriptCommand = $matches[1].Trim() + } + + # Replace {SCRIPT} placeholder with the script command + $body = $fileContent -replace '\{SCRIPT\}', $scriptCommand + + # Replace {AGENT_SCRIPT} placeholder with the agent script command if found + if (-not [string]::IsNullOrEmpty($agentScriptCommand)) { + $body = $body -replace '\{AGENT_SCRIPT\}', $agentScriptCommand + } + + # Remove the scripts: and agent_scripts: sections from frontmatter + $lines = $body -split "`n" + $outputLines = @() + $inFrontmatter = $false + $skipScripts = $false + $dashCount = 0 + + foreach ($line in $lines) { + if ($line -match '^---$') { + $outputLines += $line + $dashCount++ + if ($dashCount -eq 1) { + $inFrontmatter = $true + } else { + $inFrontmatter = $false + } + continue + } + + if ($inFrontmatter) { + if ($line -match '^(scripts|agent_scripts):$') { + $skipScripts = $true + continue + } + if ($line -match '^[a-zA-Z].*:' -and $skipScripts) { + $skipScripts = $false + } + if ($skipScripts -and $line -match '^\s+') { + continue + } + } + + $outputLines += $line + } + + $body = $outputLines -join "`n" + + # Apply other substitutions + $body = $body -replace '\{ARGS\}', $ArgFormat + $body = $body -replace '__AGENT__', $Agent + $body = Rewrite-Paths -Content $body + + # Generate output file based on extension + $outputFile = Join-Path $OutputDir "speckit.$name.$Extension" + + switch ($Extension) { + 'toml' { + $body = $body -replace '\\', '\\' + $output = "description = `"$description`"`n`nprompt = `"`"`"`n$body`n`"`"`"" + Set-Content -Path $outputFile -Value $output -NoNewline + } + 'md' { + Set-Content -Path $outputFile -Value $body -NoNewline + } + 'agent.md' { + Set-Content -Path $outputFile -Value $body -NoNewline + } + } + } +} + +function Generate-CopilotPrompts { + param( + [string]$AgentsDir, + [string]$PromptsDir + ) + + New-Item -ItemType Directory -Path $PromptsDir -Force | Out-Null + + $agentFiles = Get-ChildItem -Path "$AgentsDir/speckit.*.agent.md" -File -ErrorAction SilentlyContinue + + foreach ($agentFile in $agentFiles) { + $basename = $agentFile.Name -replace '\.agent\.md$', '' + $promptFile = Join-Path $PromptsDir "$basename.prompt.md" + + $content = @" +--- +agent: $basename +--- +"@ + Set-Content -Path $promptFile -Value $content + } +} + +function Build-Variant { + param( + [string]$Agent, + [string]$Script + ) + + $baseDir = Join-Path $GenReleasesDir "sdd-${Agent}-package-${Script}" + Write-Host "Building $Agent ($Script) package..." + New-Item -ItemType Directory -Path $baseDir -Force | Out-Null + + # Copy base structure but filter scripts by variant + $specDir = Join-Path $baseDir ".specify" + New-Item -ItemType Directory -Path $specDir -Force | Out-Null + + # Copy memory directory + if (Test-Path "memory") { + Copy-Item -Path "memory" -Destination $specDir -Recurse -Force + Write-Host "Copied memory -> .specify" + } + + # Only copy the relevant script variant directory + if (Test-Path "scripts") { + $scriptsDestDir = Join-Path $specDir "scripts" + New-Item -ItemType Directory -Path $scriptsDestDir -Force | Out-Null + + switch ($Script) { + 'sh' { + if (Test-Path "scripts/bash") { + Copy-Item -Path "scripts/bash" -Destination $scriptsDestDir -Recurse -Force + Write-Host "Copied scripts/bash -> .specify/scripts" + } + } + 'ps' { + if (Test-Path "scripts/powershell") { + Copy-Item -Path "scripts/powershell" -Destination $scriptsDestDir -Recurse -Force + Write-Host "Copied scripts/powershell -> .specify/scripts" + } + } + } + + # Copy any script files that aren't in variant-specific directories + Get-ChildItem -Path "scripts" -File -ErrorAction SilentlyContinue | ForEach-Object { + Copy-Item -Path $_.FullName -Destination $scriptsDestDir -Force + } + } + + # Copy templates (excluding commands directory and vscode-settings.json) + if (Test-Path "templates") { + $templatesDestDir = Join-Path $specDir "templates" + New-Item -ItemType Directory -Path $templatesDestDir -Force | Out-Null + + Get-ChildItem -Path "templates" -Recurse -File | Where-Object { + $_.FullName -notmatch 'templates[/\\]commands[/\\]' -and $_.Name -ne 'vscode-settings.json' + } | ForEach-Object { + $relativePath = $_.FullName.Substring((Resolve-Path "templates").Path.Length + 1) + $destFile = Join-Path $templatesDestDir $relativePath + $destFileDir = Split-Path $destFile -Parent + New-Item -ItemType Directory -Path $destFileDir -Force | Out-Null + Copy-Item -Path $_.FullName -Destination $destFile -Force + } + Write-Host "Copied templates -> .specify/templates" + } + + # Generate agent-specific command files + switch ($Agent) { + 'claude' { + $cmdDir = Join-Path $baseDir ".claude/commands" + Generate-Commands -Agent 'claude' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'gemini' { + $cmdDir = Join-Path $baseDir ".gemini/commands" + Generate-Commands -Agent 'gemini' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script + if (Test-Path "agent_templates/gemini/GEMINI.md") { + Copy-Item -Path "agent_templates/gemini/GEMINI.md" -Destination (Join-Path $baseDir "GEMINI.md") + } + } + 'copilot' { + $agentsDir = Join-Path $baseDir ".github/agents" + Generate-Commands -Agent 'copilot' -Extension 'agent.md' -ArgFormat '$ARGUMENTS' -OutputDir $agentsDir -ScriptVariant $Script + + # Generate companion prompt files + $promptsDir = Join-Path $baseDir ".github/prompts" + Generate-CopilotPrompts -AgentsDir $agentsDir -PromptsDir $promptsDir + + # Create VS Code workspace settings + $vscodeDir = Join-Path $baseDir ".vscode" + New-Item -ItemType Directory -Path $vscodeDir -Force | Out-Null + if (Test-Path "templates/vscode-settings.json") { + Copy-Item -Path "templates/vscode-settings.json" -Destination (Join-Path $vscodeDir "settings.json") + } + } + 'cursor-agent' { + $cmdDir = Join-Path $baseDir ".cursor/commands" + Generate-Commands -Agent 'cursor-agent' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'qwen' { + $cmdDir = Join-Path $baseDir ".qwen/commands" + Generate-Commands -Agent 'qwen' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script + if (Test-Path "agent_templates/qwen/QWEN.md") { + Copy-Item -Path "agent_templates/qwen/QWEN.md" -Destination (Join-Path $baseDir "QWEN.md") + } + } + 'opencode' { + $cmdDir = Join-Path $baseDir ".opencode/command" + Generate-Commands -Agent 'opencode' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'windsurf' { + $cmdDir = Join-Path $baseDir ".windsurf/workflows" + Generate-Commands -Agent 'windsurf' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'codex' { + $cmdDir = Join-Path $baseDir ".codex/prompts" + Generate-Commands -Agent 'codex' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'kilocode' { + $cmdDir = Join-Path $baseDir ".kilocode/workflows" + Generate-Commands -Agent 'kilocode' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'auggie' { + $cmdDir = Join-Path $baseDir ".augment/commands" + Generate-Commands -Agent 'auggie' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'roo' { + $cmdDir = Join-Path $baseDir ".roo/commands" + Generate-Commands -Agent 'roo' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'codebuddy' { + $cmdDir = Join-Path $baseDir ".codebuddy/commands" + Generate-Commands -Agent 'codebuddy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'amp' { + $cmdDir = Join-Path $baseDir ".agents/commands" + Generate-Commands -Agent 'amp' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + 'q' { + $cmdDir = Join-Path $baseDir ".amazonq/prompts" + Generate-Commands -Agent 'q' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script + } + } + + # Create zip archive + $zipFile = Join-Path $GenReleasesDir "spec-kit-template-${Agent}-${Script}-${Version}.zip" + Compress-Archive -Path "$baseDir/*" -DestinationPath $zipFile -Force + Write-Host "Created $zipFile" +} + +# Define all agents and scripts +$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'q') +$AllScripts = @('sh', 'ps') + +function Normalize-List { + param([string]$Input) + + if ([string]::IsNullOrEmpty($Input)) { + return @() + } + + # Split by comma or space and remove duplicates while preserving order + $items = $Input -split '[,\s]+' | Where-Object { $_ } | Select-Object -Unique + return $items +} + +function Validate-Subset { + param( + [string]$Type, + [string[]]$Allowed, + [string[]]$Items + ) + + $ok = $true + foreach ($item in $Items) { + if ($item -notin $Allowed) { + Write-Error "Unknown $Type '$item' (allowed: $($Allowed -join ', '))" + $ok = $false + } + } + return $ok +} + +# Determine agent list +if (-not [string]::IsNullOrEmpty($Agents)) { + $AgentList = Normalize-List -Input $Agents + if (-not (Validate-Subset -Type 'agent' -Allowed $AllAgents -Items $AgentList)) { + exit 1 + } +} else { + $AgentList = $AllAgents +} + +# Determine script list +if (-not [string]::IsNullOrEmpty($Scripts)) { + $ScriptList = Normalize-List -Input $Scripts + if (-not (Validate-Subset -Type 'script' -Allowed $AllScripts -Items $ScriptList)) { + exit 1 + } +} else { + $ScriptList = $AllScripts +} + +Write-Host "Agents: $($AgentList -join ', ')" +Write-Host "Scripts: $($ScriptList -join ', ')" + +# Build all variants +foreach ($agent in $AgentList) { + foreach ($script in $ScriptList) { + Build-Variant -Agent $agent -Script $script + } +} + +Write-Host "`nArchives in ${GenReleasesDir}:" +Get-ChildItem -Path $GenReleasesDir -Filter "spec-kit-template-*-${Version}.zip" | ForEach-Object { + Write-Host " $($_.Name)" +} diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh old mode 100644 new mode 100755 index bd341ea5..901f793f --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -6,7 +6,7 @@ set -euo pipefail # Usage: .github/workflows/scripts/create-release-packages.sh # Version argument should include leading 'v'. # Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built. -# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp (default: all) +# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp shai (default: all) # SCRIPTS : space or comma separated subset of: sh ps (default: both) # Examples: # AGENTS=claude SCRIPTS=sh $0 v0.2.0 @@ -95,12 +95,32 @@ generate_commands() { { echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;; md) echo "$body" > "$output_dir/speckit.$name.$ext" ;; - prompt.md) + agent.md) echo "$body" > "$output_dir/speckit.$name.$ext" ;; esac done } +generate_copilot_prompts() { + local agents_dir=$1 prompts_dir=$2 + mkdir -p "$prompts_dir" + + # Generate a .prompt.md file for each .agent.md file + for agent_file in "$agents_dir"/speckit.*.agent.md; do + [[ -f "$agent_file" ]] || continue + + local basename=$(basename "$agent_file" .agent.md) + local prompt_file="$prompts_dir/${basename}.prompt.md" + + # Create prompt file with agent frontmatter + cat > "$prompt_file" < space separated unique while preserving order of first occurrence - tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i)}}}END{printf("\n")}' + # convert comma+space separated -> line separated unique while preserving order of first occurrence + tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}' } validate_subset() { local type=$1; shift; local -n allowed=$1; shift; local items=("$@") - local ok=1 + local invalid=0 for it in "${items[@]}"; do local found=0 for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done if [[ $found -eq 0 ]]; then echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2 - ok=0 + invalid=1 fi done - return $ok + return $invalid } if [[ -n ${AGENTS:-} ]]; then diff --git a/.gitignore b/.gitignore index 94bed859..1ed57362 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ env/ *.swp *.swo .DS_Store +*.tmp # Project specific *.log diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index a96fd3e6..d6db0277 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -20,5 +20,8 @@ "MD050": { "style": "asterisk" } - } + }, + "ignores": [ + ".genreleases/" + ] } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 1c827497..2015bc8a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ Specify supports multiple AI agents by generating agent-specific command files a |-------|-----------|---------|----------|-------------| | **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 | +| **GitHub Copilot** | `.github/agents/` | 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 | @@ -45,6 +45,7 @@ Specify supports multiple AI agents by generating agent-specific command files a | **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI | | **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI | | **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI | +| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI | | **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE | ### Step-by-Step Integration Guide @@ -312,6 +313,7 @@ Require a command-line tool to be installed: - **Amazon Q Developer CLI**: `q` CLI - **CodeBuddy CLI**: `codebuddy` CLI - **Amp**: `amp` CLI +- **SHAI**: `shai` CLI ### IDE-Based Agents @@ -325,7 +327,9 @@ Work within integrated development environments: ### Markdown Format -Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp, IBM Bob +Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp, SHAI, IBM Bob + +**Standard format:** ```markdown --- @@ -335,6 +339,17 @@ description: "Command description" Command content with {SCRIPT} and $ARGUMENTS placeholders. ``` +**GitHub Copilot Chat Mode format:** + +```markdown +--- +description: "Command description" +mode: speckit.command-name +--- + +Command content with {SCRIPT} and $ARGUMENTS placeholders. +``` + ### TOML Format Used by: Gemini, Qwen @@ -351,7 +366,7 @@ Command content with {SCRIPT} and {{args}} placeholders. - **CLI agents**: Usually `./commands/` - **IDE agents**: Follow IDE-specific patterns: - - Copilot: `.github/prompts/` + - Copilot: `.github/agents/` - Cursor: `.cursor/commands/` - Windsurf: `.windsurf/workflows/` diff --git a/CHANGELOG.md b/CHANGELOG.md index ea672914..7e2ac369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ All notable changes to the Specify CLI and templates are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.22] - 2025-11-07 + +- Support for VS Code/Copilot agents, and moving away from prompts to proper agents with hand-offs. +- Move to use `AGENTS.md` for Copilot workloads, since it's already supported out-of-the-box. +- Adds support for the version command. ([#486](https://github.com/github/spec-kit/issues/486)) +- Fixes potential bug with the `create-new-feature.ps1` script that ignores existing feature branches when determining next feature number ([#975](https://github.com/github/spec-kit/issues/975)) +- Add graceful fallback and logging for GitHub API rate-limiting during template fetch ([#970](https://github.com/github/spec-kit/issues/970)) + +## [0.0.21] - 2025-10-21 + +- Fixes [#975](https://github.com/github/spec-kit/issues/975) (thank you [@fgalarraga](https://github.com/fgalarraga)). +- Adds support for Amp CLI. +- Adds support for VS Code hand-offs and moves prompts to be full-fledged chat modes. +- Adds support for `version` command (addresses [#811](https://github.com/github/spec-kit/issues/811) and [#486](https://github.com/github/spec-kit/issues/486), thank you [@mcasalaina](https://github.com/mcasalaina) and [@dentity007](https://github.com/dentity007)). +- Adds support for rendering the rate limit errors from the CLI when encountered ([#970](https://github.com/github/spec-kit/issues/970), thank you [@psmman](https://github.com/psmman)). + ## [0.0.20] - 2025-10-14 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5897b38..c413dd01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,6 +70,7 @@ To test your templates, commands, and other changes locally, follow these steps: 1. **Create release packages** Run the following command to generate the local packages: + ``` ./.github/workflows/scripts/create-release-packages.sh v1.0.0 ``` diff --git a/README.md b/README.md index 87f31d16..e2d49daf 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ specify init specify check ``` -To upgrade specify run: +To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed instructions. Quick upgrade: ```bash uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git @@ -150,6 +150,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c | [Codex CLI](https://github.com/openai/codex) | ✅ | | | [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/) | ⚠️ | Amazon Q Developer CLI [does not support](https://github.com/aws/amazon-q-developer-cli/issues/3064) custom arguments for slash commands. | | [Amp](https://ampcode.com/) | ✅ | | +| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | | | [IBM Bob](https://www.ibm.com/products/bob) | ✅ | IDE-based agent with slash command support | ## 🔧 Specify CLI Reference @@ -161,14 +162,14 @@ The `specify` command supports the following options: | Command | Description | |-------------|----------------------------------------------------------------| | `init` | Initialize a new Specify project from the latest template | -| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`) | +| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`) | ### `specify init` Arguments & Options | Argument/Option | Type | Description | |------------------------|----------|------------------------------------------------------------------------------| | `` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) | -| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `q`, or `bob` | +| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `q`, or `bob` | | `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) | | `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code | | `--no-git` | Flag | Skip git repository initialization | @@ -196,6 +197,9 @@ specify init my-project --ai windsurf # Initialize with Amp support specify init my-project --ai amp +# Initialize with SHAI support +specify init my-project --ai shai + # Initialize with IBM Bob support specify init my-project --ai bob @@ -209,7 +213,7 @@ specify init --here --ai copilot # Force merge into current (non-empty) directory without confirmation specify init . --force --ai copilot -# or +# or specify init --here --force --ai copilot # Skip git initialization diff --git a/docs/index.md b/docs/index.md index 38a6de34..a56fcc17 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,6 +12,7 @@ Spec-Driven Development **flips the script** on traditional software development - [Installation Guide](installation.md) - [Quick Start Guide](quickstart.md) +- [Upgrade Guide](upgrade.md) - [Local Development](local-development.md) ## Core Philosophy diff --git a/docs/toc.yml b/docs/toc.yml index 082bb8c8..18650cb5 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -9,10 +9,11 @@ href: installation.md - name: Quick Start href: quickstart.md + - name: Upgrade + href: upgrade.md # Development workflows - name: Development items: - name: Local Development href: local-development.md - diff --git a/docs/upgrade.md b/docs/upgrade.md new file mode 100644 index 00000000..035706eb --- /dev/null +++ b/docs/upgrade.md @@ -0,0 +1,436 @@ +# Upgrade Guide + +> You have Spec Kit installed and want to upgrade to the latest version to get new features, bug fixes, or updated slash commands. This guide covers both upgrading the CLI tool and updating your project files. + +--- + +## Quick Reference + +| What to Upgrade | Command | When to Use | +|----------------|---------|-------------| +| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git` | Get latest CLI features without touching project files | +| **Project Files** | `specify init --here --force --ai ` | Update slash commands, templates, and scripts in your project | +| **Both** | Run CLI upgrade, then project update | Recommended for major version updates | + +--- + +## Part 1: Upgrade the CLI Tool + +The CLI tool (`specify`) is separate from your project files. Upgrade it to get the latest features and bug fixes. + +### If you installed with `uv tool install` + +```bash +uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git +``` + +### If you use one-shot `uvx` commands + +No upgrade needed—`uvx` always fetches the latest version. Just run your commands as normal: + +```bash +uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai copilot +``` + +### Verify the upgrade + +```bash +specify check +``` + +This shows installed tools and confirms the CLI is working. + +--- + +## Part 2: Updating Project Files + +When Spec Kit releases new features (like new slash commands or updated templates), you need to refresh your project's Spec Kit files. + +### What gets updated? + +Running `specify init --here --force` will update: + +- ✅ **Slash command files** (`.claude/commands/`, `.github/prompts/`, etc.) +- ✅ **Script files** (`.specify/scripts/`) +- ✅ **Template files** (`.specify/templates/`) +- ✅ **Shared memory files** (`.specify/memory/`) - **⚠️ See warnings below** + +### What stays safe? + +These files are **never touched** by the upgrade—the template packages don't even contain them: + +- ✅ **Your specifications** (`specs/001-my-feature/spec.md`, etc.) - **CONFIRMED SAFE** +- ✅ **Your implementation plans** (`specs/001-my-feature/plan.md`, `tasks.md`, etc.) - **CONFIRMED SAFE** +- ✅ **Your source code** - **CONFIRMED SAFE** +- ✅ **Your git history** - **CONFIRMED SAFE** + +The `specs/` directory is completely excluded from template packages and will never be modified during upgrades. + +### Update command + +Run this inside your project directory: + +```bash +specify init --here --force --ai +``` + +Replace `` with your AI assistant. Refer to this list of [Supported AI Agents](../README.md#-supported-ai-agents) + +**Example:** + +```bash +specify init --here --force --ai copilot +``` + +### Understanding the `--force` flag + +Without `--force`, the CLI warns you and asks for confirmation: + +``` +Warning: Current directory is not empty (25 items) +Template files will be merged with existing content and may overwrite existing files +Proceed? [y/N] +``` + +With `--force`, it skips the confirmation and proceeds immediately. + +**Important: Your `specs/` directory is always safe.** The `--force` flag only affects template files (commands, scripts, templates, memory). Your feature specifications, plans, and tasks in `specs/` are never included in upgrade packages and cannot be overwritten. + +--- + +## ⚠️ Important Warnings + +### 1. Constitution file will be overwritten + +**Known issue:** `specify init --here --force` currently overwrites `.specify/memory/constitution.md` with the default template, erasing any customizations you made. + +**Workaround:** + +```bash +# 1. Back up your constitution before upgrading +cp .specify/memory/constitution.md .specify/memory/constitution-backup.md + +# 2. Run the upgrade +specify init --here --force --ai copilot + +# 3. Restore your customized constitution +mv .specify/memory/constitution-backup.md .specify/memory/constitution.md +``` + +Or use git to restore it: + +```bash +# After upgrade, restore from git history +git restore .specify/memory/constitution.md +``` + +### 2. Custom template modifications + +If you customized any templates in `.specify/templates/`, the upgrade will overwrite them. Back them up first: + +```bash +# Back up custom templates +cp -r .specify/templates .specify/templates-backup + +# After upgrade, merge your changes back manually +``` + +### 3. Duplicate slash commands (IDE-based agents) + +Some IDE-based agents (like Kilo Code, Windsurf) may show **duplicate slash commands** after upgrading—both old and new versions appear. + +**Solution:** Manually delete the old command files from your agent's folder. + +**Example for Kilo Code:** + +```bash +# Navigate to the agent's commands folder +cd .kilocode/rules/ + +# List files and identify duplicates +ls -la + +# Delete old versions (example filenames - yours may differ) +rm speckit.specify-old.md +rm speckit.plan-v1.md +``` + +Restart your IDE to refresh the command list. + +--- + +## Common Scenarios + +### Scenario 1: "I just want new slash commands" + +```bash +# Upgrade CLI (if using persistent install) +uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git + +# Update project files to get new commands +specify init --here --force --ai copilot + +# Restore your constitution if customized +git restore .specify/memory/constitution.md +``` + +### Scenario 2: "I customized templates and constitution" + +```bash +# 1. Back up customizations +cp .specify/memory/constitution.md /tmp/constitution-backup.md +cp -r .specify/templates /tmp/templates-backup + +# 2. Upgrade CLI +uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git + +# 3. Update project +specify init --here --force --ai copilot + +# 4. Restore customizations +mv /tmp/constitution-backup.md .specify/memory/constitution.md +# Manually merge template changes if needed +``` + +### Scenario 3: "I see duplicate slash commands in my IDE" + +This happens with IDE-based agents (Kilo Code, Windsurf, Roo Code, etc.). + +```bash +# Find the agent folder (example: .kilocode/rules/) +cd .kilocode/rules/ + +# List all files +ls -la + +# Delete old command files +rm speckit.old-command-name.md + +# Restart your IDE +``` + +### Scenario 4: "I'm working on a project without Git" + +If you initialized your project with `--no-git`, you can still upgrade: + +```bash +# Manually back up files you customized +cp .specify/memory/constitution.md /tmp/constitution-backup.md + +# Run upgrade +specify init --here --force --ai copilot --no-git + +# Restore customizations +mv /tmp/constitution-backup.md .specify/memory/constitution.md +``` + +The `--no-git` flag skips git initialization but doesn't affect file updates. + +--- + +## Using `--no-git` Flag + +The `--no-git` flag tells Spec Kit to **skip git repository initialization**. This is useful when: + +- You manage version control differently (Mercurial, SVN, etc.) +- Your project is part of a larger monorepo with existing git setup +- You're experimenting and don't want version control yet + +**During initial setup:** + +```bash +specify init my-project --ai copilot --no-git +``` + +**During upgrade:** + +```bash +specify init --here --force --ai copilot --no-git +``` + +### What `--no-git` does NOT do + +❌ Does NOT prevent file updates +❌ Does NOT skip slash command installation +❌ Does NOT affect template merging + +It **only** skips running `git init` and creating the initial commit. + +### Working without Git + +If you use `--no-git`, you'll need to manage feature directories manually: + +**Set the `SPECIFY_FEATURE` environment variable** before using planning commands: + +```bash +# Bash/Zsh +export SPECIFY_FEATURE="001-my-feature" + +# PowerShell +$env:SPECIFY_FEATURE = "001-my-feature" +``` + +This tells Spec Kit which feature directory to use when creating specs, plans, and tasks. + +**Why this matters:** Without git, Spec Kit can't detect your current branch name to determine the active feature. The environment variable provides that context manually. + +--- + +## Troubleshooting + +### "Slash commands not showing up after upgrade" + +**Cause:** Agent didn't reload the command files. + +**Fix:** + +1. **Restart your IDE/editor** completely (not just reload window) +2. **For CLI-based agents**, verify files exist: + ```bash + ls -la .claude/commands/ # Claude Code + ls -la .gemini/commands/ # Gemini + ls -la .cursor/commands/ # Cursor + ``` +3. **Check agent-specific setup:** + - Codex requires `CODEX_HOME` environment variable + - Some agents need workspace restart or cache clearing + +### "I lost my constitution customizations" + +**Fix:** Restore from git or backup: + +```bash +# If you committed before upgrading +git restore .specify/memory/constitution.md + +# If you backed up manually +cp /tmp/constitution-backup.md .specify/memory/constitution.md +``` + +**Prevention:** Always commit or back up `constitution.md` before upgrading. + +### "Warning: Current directory is not empty" + +**Full warning message:** +``` +Warning: Current directory is not empty (25 items) +Template files will be merged with existing content and may overwrite existing files +Do you want to continue? [y/N] +``` + +**What this means:** + +This warning appears when you run `specify init --here` (or `specify init .`) in a directory that already has files. It's telling you: + +1. **The directory has existing content** - In the example, 25 files/folders +2. **Files will be merged** - New template files will be added alongside your existing files +3. **Some files may be overwritten** - If you already have Spec Kit files (`.claude/`, `.specify/`, etc.), they'll be replaced with the new versions + +**What gets overwritten:** + +Only Spec Kit infrastructure files: +- Agent command files (`.claude/commands/`, `.github/prompts/`, etc.) +- Scripts in `.specify/scripts/` +- Templates in `.specify/templates/` +- Memory files in `.specify/memory/` (including constitution) + +**What stays untouched:** + +- Your `specs/` directory (specifications, plans, tasks) +- Your source code files +- Your `.git/` directory and git history +- Any other files not part of Spec Kit templates + +**How to respond:** + +- **Type `y` and press Enter** - Proceed with the merge (recommended if upgrading) +- **Type `n` and press Enter** - Cancel the operation +- **Use `--force` flag** - Skip this confirmation entirely: + ```bash + specify init --here --force --ai copilot + ``` + +**When you see this warning:** + +- ✅ **Expected** when upgrading an existing Spec Kit project +- ✅ **Expected** when adding Spec Kit to an existing codebase +- ⚠️ **Unexpected** if you thought you were creating a new project in an empty directory + +**Prevention tip:** Before upgrading, commit or back up your `.specify/memory/constitution.md` if you customized it. + +### "CLI upgrade doesn't seem to work" + +Verify the installation: + +```bash +# Check installed tools +uv tool list + +# Should show specify-cli + +# Verify path +which specify + +# Should point to the uv tool installation directory +``` + +If not found, reinstall: + +```bash +uv tool uninstall specify-cli +uv tool install specify-cli --from git+https://github.com/github/spec-kit.git +``` + +### "Do I need to run specify every time I open my project?" + +**Short answer:** No, you only run `specify init` once per project (or when upgrading). + +**Explanation:** + +The `specify` CLI tool is used for: +- **Initial setup:** `specify init` to bootstrap Spec Kit in your project +- **Upgrades:** `specify init --here --force` to update templates and commands +- **Diagnostics:** `specify check` to verify tool installation + +Once you've run `specify init`, the slash commands (like `/speckit.specify`, `/speckit.plan`, etc.) are **permanently installed** in your project's agent folder (`.claude/`, `.github/prompts/`, etc.). Your AI assistant reads these command files directly—no need to run `specify` again. + +**If your agent isn't recognizing slash commands:** + +1. **Verify command files exist:** + ```bash + # For GitHub Copilot + ls -la .github/prompts/ + + # For Claude + ls -la .claude/commands/ + ``` + +2. **Restart your IDE/editor completely** (not just reload window) + +3. **Check you're in the correct directory** where you ran `specify init` + +4. **For some agents**, you may need to reload the workspace or clear cache + +**Related issue:** If Copilot can't open local files or uses PowerShell commands unexpectedly, this is typically an IDE context issue, not related to `specify`. Try: +- Restarting VS Code +- Checking file permissions +- Ensuring the workspace folder is properly opened + +--- + +## Version Compatibility + +Spec Kit follows semantic versioning for major releases. The CLI and project files are designed to be compatible within the same major version. + +**Best practice:** Keep both CLI and project files in sync by upgrading both together during major version changes. + +--- + +## Next Steps + +After upgrading: + +- **Test new slash commands:** Run `/speckit.constitution` or another command to verify everything works +- **Review release notes:** Check [GitHub Releases](https://github.com/github/spec-kit/releases) for new features and breaking changes +- **Update workflows:** If new commands were added, update your team's development workflows +- **Check documentation:** Visit [github.io/spec-kit](https://github.github.io/spec-kit/) for updated guides diff --git a/pyproject.toml b/pyproject.toml index 567d48cd..fb972adc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.0.20" +version = "0.0.22" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh index 54f32ec3..98e387c2 100644 --- a/scripts/bash/check-prerequisites.sh +++ b/scripts/bash/check-prerequisites.sh @@ -75,7 +75,7 @@ EOF done # Source common functions -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Get feature paths and validate branch diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 6931eccc..2c3165e4 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -7,7 +7,7 @@ get_repo_root() { git rev-parse --show-toplevel else # Fall back to script location for non-git repos - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" (cd "$script_dir/../../.." && pwd) fi } diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 86d9ecf8..592dab2e 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -80,9 +80,56 @@ find_repo_root() { return 1 } +# Function to get highest number from specs directory +get_highest_from_specs() { + local specs_dir="$1" + local highest=0 + + if [ -d "$specs_dir" ]; then + for dir in "$specs_dir"/*; do + [ -d "$dir" ] || continue + dirname=$(basename "$dir") + number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + done + fi + + echo "$highest" +} + +# Function to get highest number from git branches +get_highest_from_branches() { + local highest=0 + + # Get all branches (local and remote) + branches=$(git branch -a 2>/dev/null || echo "") + + if [ -n "$branches" ]; then + while IFS= read -r branch; do + # Clean branch name: remove leading markers and remote prefixes + clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') + + # Extract feature number if branch matches pattern ###-* + if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then + number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done <<< "$branches" + fi + + echo "$highest" +} + # Function to check existing branches (local and remote) and return next available number check_existing_branches() { local short_name="$1" + local specs_dir="$2" # Fetch all remotes to get latest branch info (suppress errors if no remotes) git fetch --all --prune 2>/dev/null || true @@ -95,8 +142,8 @@ check_existing_branches() { # Check specs directory as well local spec_dirs="" - if [ -d "$SPECS_DIR" ]; then - spec_dirs=$(find "$SPECS_DIR" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) + if [ -d "$specs_dir" ]; then + spec_dirs=$(find "$specs_dir" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) fi # Combine all sources and get the highest number @@ -111,10 +158,16 @@ check_existing_branches() { echo $((max_num + 1)) } +# Function to clean and format a branch name +clean_branch_name() { + local name="$1" + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' +} + # Resolve repository root. Prefer git information when available, but fall back # to searching for repository markers so the workflow still functions in repositories that # were initialised with --no-git. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if git rev-parse --show-toplevel >/dev/null 2>&1; then REPO_ROOT=$(git rev-parse --show-toplevel) @@ -176,14 +229,15 @@ generate_branch_name() { echo "$result" else # Fallback to original logic if no meaningful words found - echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + local cleaned=$(clean_branch_name "$description") + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' fi } # Generate branch name if [ -n "$SHORT_NAME" ]; then # Use provided short name, just clean it up - BRANCH_SUFFIX=$(echo "$SHORT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//') + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") else # Generate from description with smart filtering BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") @@ -193,19 +247,10 @@ fi if [ -z "$BRANCH_NUMBER" ]; then if [ "$HAS_GIT" = true ]; then # Check existing branches on remotes - BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX") + BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX" "$SPECS_DIR") else # Fall back to local directory check - HIGHEST=0 - if [ -d "$SPECS_DIR" ]; then - for dir in "$SPECS_DIR"/*; do - [ -d "$dir" ] || continue - dirname=$(basename "$dir") - number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") - number=$((10#$number)) - if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi - done - fi + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") BRANCH_NUMBER=$((HIGHEST + 1)) fi fi diff --git a/scripts/bash/setup-plan.sh b/scripts/bash/setup-plan.sh index 740a1438..d01c6d6c 100644 --- a/scripts/bash/setup-plan.sh +++ b/scripts/bash/setup-plan.sh @@ -24,7 +24,7 @@ for arg in "$@"; do done # Get script directory and load common functions -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Get all paths and variables from common functions diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 8c685b8c..1e552b18 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -30,12 +30,12 @@ # # 5. Multi-Agent Support # - Handles agent-specific file paths and naming conventions -# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Amp, or Amazon Q Developer CLI +# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Amp, SHAI, or Amazon Q Developer CLI # - Can update single agents or all existing agent files # - Creates default Claude file if no agent files exist # # Usage: ./update-agent-context.sh [agent_type] -# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|q +# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q # Leave empty to update all existing agent files set -e @@ -49,7 +49,7 @@ set -o pipefail #============================================================================== # Get script directory and load common functions -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Get all paths and variables from common functions @@ -61,7 +61,7 @@ AGENT_TYPE="${1:-}" # Agent-specific file paths CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" -COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" +COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" QWEN_FILE="$REPO_ROOT/QWEN.md" AGENTS_FILE="$REPO_ROOT/AGENTS.md" @@ -71,6 +71,7 @@ AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" AMP_FILE="$REPO_ROOT/AGENTS.md" +SHAI_FILE="$REPO_ROOT/SHAI.md" Q_FILE="$REPO_ROOT/AGENTS.md" BOB_FILE="$REPO_ROOT/AGENTS.md" @@ -619,6 +620,9 @@ update_specific_agent() { amp) update_agent_file "$AMP_FILE" "Amp" ;; + shai) + update_agent_file "$SHAI_FILE" "SHAI" + ;; q) update_agent_file "$Q_FILE" "Amazon Q Developer CLI" ;; @@ -627,7 +631,7 @@ update_specific_agent() { ;; *) log_error "Unknown agent type '$agent_type'" - log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|q|bob" + log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|bob" exit 1 ;; esac @@ -692,6 +696,11 @@ update_all_existing_agents() { found_agent=true fi + if [[ -f "$SHAI_FILE" ]]; then + update_agent_file "$SHAI_FILE" "SHAI" + found_agent=true + fi + if [[ -f "$Q_FILE" ]]; then update_agent_file "$Q_FILE" "Amazon Q Developer CLI" found_agent=true @@ -726,7 +735,7 @@ print_summary() { echo - log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q|bob]" + log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|bob]" } #============================================================================== diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 4daa6d2c..351f4e9e 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -59,6 +59,46 @@ function Find-RepositoryRoot { } } +function Get-HighestNumberFromSpecs { + param([string]$SpecsDir) + + $highest = 0 + if (Test-Path $SpecsDir) { + Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object { + if ($_.Name -match '^(\d+)') { + $num = [int]$matches[1] + if ($num -gt $highest) { $highest = $num } + } + } + } + return $highest +} + +function Get-HighestNumberFromBranches { + param() + + $highest = 0 + try { + $branches = git branch -a 2>$null + if ($LASTEXITCODE -eq 0) { + foreach ($branch in $branches) { + # Clean branch name: remove leading markers and remote prefixes + $cleanBranch = $branch.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', '' + + # Extract feature number if branch matches pattern ###-* + if ($cleanBranch -match '^(\d+)-') { + $num = [int]$matches[1] + if ($num -gt $highest) { $highest = $num } + } + } + } + } catch { + # If git command fails, return 0 + Write-Verbose "Could not check Git branches: $_" + } + return $highest +} + function Get-NextBranchNumber { param( [string]$ShortName, @@ -127,6 +167,12 @@ function Get-NextBranchNumber { # Return next number return $maxNum + 1 } + +function ConvertTo-CleanBranchName { + param([string]$Name) + + return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' +} $fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot) if (-not $fallbackRoot) { Write-Error "Error: Could not determine repository root. Please run this script from within the repository." @@ -189,7 +235,7 @@ function Get-BranchName { return $result } else { # Fallback to original logic if no meaningful words found - $result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' + $result = ConvertTo-CleanBranchName -Name $Description $fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3 return [string]::Join('-', $fallbackWords) } @@ -198,7 +244,7 @@ function Get-BranchName { # Generate branch name if ($ShortName) { # Use provided short name, just clean it up - $branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' + $branchSuffix = ConvertTo-CleanBranchName -Name $ShortName } else { # Generate from description with smart filtering $branchSuffix = Get-BranchName -Description $featureDesc @@ -211,16 +257,7 @@ if ($Number -eq 0) { $Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir } else { # Fall back to local directory check - $highest = 0 - if (Test-Path $specsDir) { - Get-ChildItem -Path $specsDir -Directory | ForEach-Object { - if ($_.Name -match '^(\d{3})') { - $num = [int]$matches[1] - if ($num -gt $highest) { $highest = $num } - } - } - } - $Number = $highest + 1 + $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 } } diff --git a/scripts/powershell/setup-plan.ps1 b/scripts/powershell/setup-plan.ps1 index db6e9f2d..d0ed582f 100644 --- a/scripts/powershell/setup-plan.ps1 +++ b/scripts/powershell/setup-plan.ps1 @@ -59,4 +59,3 @@ if ($Json) { Write-Output "BRANCH: $($paths.CURRENT_BRANCH)" Write-Output "HAS_GIT: $($paths.HAS_GIT)" } - diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 7307285e..c4525c02 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh: 2. Plan Data Extraction 3. Agent File Management (create from template or update existing) 4. Content Generation (technology stack, recent changes, timestamp) - 5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, amp, q) + 5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, codebuddy, amp, shai, q) .PARAMETER AgentType Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist). @@ -25,7 +25,7 @@ Relies on common helper functions in common.ps1 #> param( [Parameter(Position=0)] - [ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','q','bob')] + [ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q','bob')] [string]$AgentType ) @@ -46,7 +46,7 @@ $NEW_PLAN = $IMPL_PLAN # Agent file paths $CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md' $GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md' -$COPILOT_FILE = Join-Path $REPO_ROOT '.github/copilot-instructions.md' +$COPILOT_FILE = Join-Path $REPO_ROOT '.github/agents/copilot-instructions.md' $CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc' $QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md' $AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md' @@ -56,6 +56,7 @@ $AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md' $ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md' $CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md' $AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.md' +$SHAI_FILE = Join-Path $REPO_ROOT 'SHAI.md' $Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md' $BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md' @@ -382,9 +383,10 @@ function Update-SpecificAgent { 'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' } 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' } 'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' } + 'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' } 'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' } 'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' } - default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|q|bob'; return $false } + default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|bob'; return $false } } } @@ -402,6 +404,7 @@ function Update-AllExistingAgents { if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true } if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true } if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true } + if (Test-Path $SHAI_FILE) { if (-not (Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI')) { $ok = $false }; $found = $true } if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true } if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true } if (-not $found) { @@ -418,7 +421,7 @@ function Print-Summary { if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" } if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" } Write-Host '' - Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|q|bob]' + Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|bob]' } function Main { diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 0637b1ee..33e76c5d 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -51,6 +51,7 @@ from typer.core import TyperGroup import readchar import ssl import truststore +from datetime import datetime, timezone ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) client = httpx.Client(verify=ssl_context) @@ -64,6 +65,63 @@ def _github_auth_headers(cli_token: str | None = None) -> dict: token = _github_token(cli_token) return {"Authorization": f"Bearer {token}"} if token else {} +def _parse_rate_limit_headers(headers: httpx.Headers) -> dict: + """Extract and parse GitHub rate-limit headers.""" + info = {} + + # Standard GitHub rate-limit headers + if "X-RateLimit-Limit" in headers: + info["limit"] = headers.get("X-RateLimit-Limit") + if "X-RateLimit-Remaining" in headers: + info["remaining"] = headers.get("X-RateLimit-Remaining") + if "X-RateLimit-Reset" in headers: + reset_epoch = int(headers.get("X-RateLimit-Reset", "0")) + if reset_epoch: + reset_time = datetime.fromtimestamp(reset_epoch, tz=timezone.utc) + info["reset_epoch"] = reset_epoch + info["reset_time"] = reset_time + info["reset_local"] = reset_time.astimezone() + + # Retry-After header (seconds or HTTP-date) + if "Retry-After" in headers: + retry_after = headers.get("Retry-After") + try: + info["retry_after_seconds"] = int(retry_after) + except ValueError: + # HTTP-date format - not implemented, just store as string + info["retry_after"] = retry_after + + return info + +def _format_rate_limit_error(status_code: int, headers: httpx.Headers, url: str) -> str: + """Format a user-friendly error message with rate-limit information.""" + rate_info = _parse_rate_limit_headers(headers) + + lines = [f"GitHub API returned status {status_code} for {url}"] + lines.append("") + + if rate_info: + lines.append("[bold]Rate Limit Information:[/bold]") + if "limit" in rate_info: + lines.append(f" • Rate Limit: {rate_info['limit']} requests/hour") + if "remaining" in rate_info: + lines.append(f" • Remaining: {rate_info['remaining']}") + if "reset_local" in rate_info: + reset_str = rate_info["reset_local"].strftime("%Y-%m-%d %H:%M:%S %Z") + lines.append(f" • Resets at: {reset_str}") + if "retry_after_seconds" in rate_info: + lines.append(f" • Retry after: {rate_info['retry_after_seconds']} seconds") + lines.append("") + + # Add troubleshooting guidance + lines.append("[bold]Troubleshooting Tips:[/bold]") + lines.append(" • If you're on a shared CI or corporate environment, you may be rate-limited.") + lines.append(" • Consider using a GitHub token via --github-token or the GH_TOKEN/GITHUB_TOKEN") + lines.append(" environment variable to increase rate limits.") + lines.append(" • Authenticated requests have a limit of 5,000/hour vs 60/hour for unauthenticated.") + + return "\n".join(lines) + # Agent configuration with name, folder, install URL, and CLI tool requirement AGENT_CONFIG = { "copilot": { @@ -150,6 +208,12 @@ AGENT_CONFIG = { "install_url": "https://ampcode.com/manual#install", "requires_cli": True, }, + "shai": { + "name": "SHAI", + "folder": ".shai/", + "install_url": "https://github.com/ovh/shai", + "requires_cli": True, + }, "bob": { "name": "IBM Bob", "folder": ".bob/", @@ -583,10 +647,11 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri ) status = response.status_code if status != 200: - msg = f"GitHub API returned {status} for {api_url}" + # Format detailed error message with rate-limit info + error_msg = _format_rate_limit_error(status, response.headers, api_url) if debug: - msg += f"\nResponse headers: {response.headers}\nBody (truncated 500): {response.text[:500]}" - raise RuntimeError(msg) + error_msg += f"\n\n[dim]Response body (truncated 500):[/dim]\n{response.text[:500]}" + raise RuntimeError(error_msg) try: release_data = response.json() except ValueError as je: @@ -633,8 +698,11 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri headers=_github_auth_headers(github_token), ) as response: if response.status_code != 200: - body_sample = response.text[:400] - raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}") + # Handle rate-limiting on download as well + error_msg = _format_rate_limit_error(response.status_code, response.headers, download_url) + if debug: + error_msg += f"\n\n[dim]Response body (truncated 400):[/dim]\n{response.text[:400]}" + raise RuntimeError(error_msg) total_size = int(response.headers.get('content-length', 0)) with open(zip_path, 'wb') as f: if total_size == 0: @@ -871,7 +939,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, or use '.' for current directory)"), - ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, amp, q, or bob"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, amp, shai, q, or bob"), 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"), @@ -1208,6 +1276,85 @@ def check(): if not any(agent_results.values()): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") +@app.command() +def version(): + """Display version and system information.""" + import platform + import importlib.metadata + + show_banner() + + # Get CLI version from package metadata + cli_version = "unknown" + try: + cli_version = importlib.metadata.version("specify-cli") + except Exception: + # Fallback: try reading from pyproject.toml if running from source + try: + import tomllib + pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml" + if pyproject_path.exists(): + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + cli_version = data.get("project", {}).get("version", "unknown") + except Exception: + pass + + # Fetch latest template release version + repo_owner = "github" + repo_name = "spec-kit" + api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" + + template_version = "unknown" + release_date = "unknown" + + try: + response = client.get( + api_url, + timeout=10, + follow_redirects=True, + headers=_github_auth_headers(), + ) + if response.status_code == 200: + release_data = response.json() + template_version = release_data.get("tag_name", "unknown") + # Remove 'v' prefix if present + if template_version.startswith("v"): + template_version = template_version[1:] + release_date = release_data.get("published_at", "unknown") + if release_date != "unknown": + # Format the date nicely + try: + dt = datetime.fromisoformat(release_date.replace('Z', '+00:00')) + release_date = dt.strftime("%Y-%m-%d") + except Exception: + pass + except Exception: + pass + + info_table = Table(show_header=False, box=None, padding=(0, 2)) + info_table.add_column("Key", style="cyan", justify="right") + info_table.add_column("Value", style="white") + + info_table.add_row("CLI Version", cli_version) + info_table.add_row("Template Version", template_version) + info_table.add_row("Released", release_date) + info_table.add_row("", "") + info_table.add_row("Python", platform.python_version()) + info_table.add_row("Platform", platform.system()) + info_table.add_row("Architecture", platform.machine()) + info_table.add_row("OS Version", platform.version()) + + panel = Panel( + info_table, + title="[bold cyan]Specify CLI Information[/bold cyan]", + border_style="cyan", + padding=(1, 2) + ) + + console.print(panel) + console.print() + def main(): app() diff --git a/templates/commands/clarify.md b/templates/commands/clarify.md index 9a62dfa7..4de842aa 100644 --- a/templates/commands/clarify.md +++ b/templates/commands/clarify.md @@ -1,5 +1,9 @@ --- description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec. +handoffs: + - label: Build Technical Plan + agent: speckit.plan + prompt: Create a plan for the spec. I am building with... scripts: sh: scripts/bash/check-prerequisites.sh --json --paths-only ps: scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly diff --git a/templates/commands/constitution.md b/templates/commands/constitution.md index f557dd17..cf81f08c 100644 --- a/templates/commands/constitution.md +++ b/templates/commands/constitution.md @@ -1,5 +1,9 @@ --- -description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync +description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync. +handoffs: + - label: Build Specification + agent: speckit.specify + prompt: Implement the feature specification based on the updated constitution. I want to build... --- ## User Input diff --git a/templates/commands/implement.md b/templates/commands/implement.md index 38601920..39abb1e6 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -67,7 +67,8 @@ You **MUST** consider the user input before proceeding (if not empty). ``` - Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore - - Check if .eslintrc*or eslint.config.* exists → create/verify .eslintignore + - Check if .eslintrc* exists → create/verify .eslintignore + - Check if eslint.config.* exists → ensure the config's `ignores` entries cover required patterns - Check if .prettierrc* exists → create/verify .prettierignore - Check if .npmrc or package.json exists → create/verify .npmignore (if publishing) - Check if terraform files (*.tf) exist → create/verify .terraformignore @@ -135,4 +136,3 @@ You **MUST** consider the user input before proceeding (if not empty). - Report final status with summary of completed work Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit.tasks` first to regenerate the task list. - diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 7dfe63a2..147da0af 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -1,5 +1,13 @@ --- description: Execute the implementation planning workflow using the plan template to generate design artifacts. +handoffs: + - label: Create Tasks + agent: speckit.tasks + prompt: Break the plan into tasks + send: true + - label: Create Checklist + agent: speckit.checklist + prompt: Create a checklist for the following domain... scripts: sh: scripts/bash/setup-plan.sh --json ps: scripts/powershell/setup-plan.ps1 -Json diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 69258a8b..3f0ddb78 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -1,5 +1,13 @@ --- description: Create or update the feature specification from a natural language feature description. +handoffs: + - label: Build Technical Plan + agent: speckit.plan + prompt: Create a plan for the spec. I am building with... + - label: Clarify Spec Requirements + agent: speckit.clarify + prompt: Clarify specification requirements + send: true scripts: sh: scripts/bash/create-new-feature.sh --json "{ARGS}" ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 4663c1b6..86a76682 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -1,5 +1,14 @@ --- description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. +handoffs: + - label: Analyze For Consistency + agent: speckit.analyze + prompt: Run a project analysis for consistency + send: true + - label: Implement Project + agent: speckit.implement + prompt: Start the implementation in phases + send: true scripts: sh: scripts/bash/check-prerequisites.sh --json ps: scripts/powershell/check-prerequisites.ps1 -Json diff --git a/templates/commands/taskstoissues.md b/templates/commands/taskstoissues.md new file mode 100644 index 00000000..fb6acc17 --- /dev/null +++ b/templates/commands/taskstoissues.md @@ -0,0 +1,31 @@ +--- +description: Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts. +tools: ['github/github-mcp-server/issue_write'] +scripts: + sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks + ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Outline + +1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). +1. From the executed script, extract the path to **tasks**. +1. Get the Git remote by running: + +```bash +git config --get remote.origin.url +``` + +**ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL** + +1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. + +**UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL**