mirror of
https://github.com/github/spec-kit.git
synced 2026-02-01 21:43:37 +00:00
Compare commits
22 Commits
copilot/st
...
v0.0.83
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf8b67e187 | ||
|
|
7c4294b163 | ||
|
|
2f58f4b9f0 | ||
|
|
7777e145d9 | ||
|
|
7e568c1201 | ||
|
|
f4fcd82920 | ||
|
|
3dcbb6e3a9 | ||
|
|
392f8b7dfa | ||
|
|
24b6d31471 | ||
|
|
960e4c0a32 | ||
|
|
df15b8e2a2 | ||
|
|
161a415274 | ||
|
|
e20d79e78b | ||
|
|
a26ee578ae | ||
|
|
bcd3f8468a | ||
|
|
e976080cbf | ||
|
|
e6d6f3cdee | ||
|
|
598148ca67 | ||
|
|
b40b41cf50 | ||
|
|
1f3d9b5fdd | ||
|
|
f7fe48bd6a | ||
|
|
d6136cb22f |
@@ -42,6 +42,8 @@ gh release create "$VERSION" \
|
|||||||
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-amp-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-amp-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-amp-ps-"$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-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \
|
||||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||||
|
|||||||
416
.github/workflows/scripts/create-release-packages.ps1
vendored
Normal file
416
.github/workflows/scripts/create-release-packages.ps1
vendored
Normal file
@@ -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)"
|
||||||
|
}
|
||||||
35
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file → Executable file
35
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file → Executable file
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||||
# Version argument should include leading 'v'.
|
# Version argument should include leading 'v'.
|
||||||
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
# 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)
|
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
||||||
# Examples:
|
# Examples:
|
||||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||||
@@ -95,21 +95,21 @@ generate_commands() {
|
|||||||
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;;
|
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;;
|
||||||
md)
|
md)
|
||||||
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
||||||
chatmode.md)
|
agent.md)
|
||||||
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_copilot_prompts() {
|
generate_copilot_prompts() {
|
||||||
local chatmodes_dir=$1 prompts_dir=$2
|
local agents_dir=$1 prompts_dir=$2
|
||||||
mkdir -p "$prompts_dir"
|
mkdir -p "$prompts_dir"
|
||||||
|
|
||||||
# Generate a .prompt.md file for each .chatmode.md file
|
# Generate a .prompt.md file for each .agent.md file
|
||||||
for chatmode_file in "$chatmodes_dir"/speckit.*.chatmode.md; do
|
for agent_file in "$agents_dir"/speckit.*.agent.md; do
|
||||||
[[ -f "$chatmode_file" ]] || continue
|
[[ -f "$agent_file" ]] || continue
|
||||||
|
|
||||||
local basename=$(basename "$chatmode_file" .chatmode.md)
|
local basename=$(basename "$agent_file" .agent.md)
|
||||||
local prompt_file="$prompts_dir/${basename}.prompt.md"
|
local prompt_file="$prompts_dir/${basename}.prompt.md"
|
||||||
|
|
||||||
# Create prompt file with agent frontmatter
|
# Create prompt file with agent frontmatter
|
||||||
@@ -166,10 +166,10 @@ build_variant() {
|
|||||||
generate_commands gemini toml "{{args}}" "$base_dir/.gemini/commands" "$script"
|
generate_commands gemini toml "{{args}}" "$base_dir/.gemini/commands" "$script"
|
||||||
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md "$base_dir/GEMINI.md" ;;
|
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md "$base_dir/GEMINI.md" ;;
|
||||||
copilot)
|
copilot)
|
||||||
mkdir -p "$base_dir/.github/chatmodes"
|
mkdir -p "$base_dir/.github/agents"
|
||||||
generate_commands copilot chatmode.md "\$ARGUMENTS" "$base_dir/.github/chatmodes" "$script"
|
generate_commands copilot agent.md "\$ARGUMENTS" "$base_dir/.github/agents" "$script"
|
||||||
# Generate companion prompt files
|
# Generate companion prompt files
|
||||||
generate_copilot_prompts "$base_dir/.github/chatmodes" "$base_dir/.github/prompts"
|
generate_copilot_prompts "$base_dir/.github/agents" "$base_dir/.github/prompts"
|
||||||
# Create VS Code workspace settings
|
# Create VS Code workspace settings
|
||||||
mkdir -p "$base_dir/.vscode"
|
mkdir -p "$base_dir/.vscode"
|
||||||
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
|
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
|
||||||
@@ -205,6 +205,9 @@ build_variant() {
|
|||||||
amp)
|
amp)
|
||||||
mkdir -p "$base_dir/.agents/commands"
|
mkdir -p "$base_dir/.agents/commands"
|
||||||
generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;;
|
generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;;
|
||||||
|
shai)
|
||||||
|
mkdir -p "$base_dir/.shai/commands"
|
||||||
|
generate_commands shai md "\$ARGUMENTS" "$base_dir/.shai/commands" "$script" ;;
|
||||||
q)
|
q)
|
||||||
mkdir -p "$base_dir/.amazonq/prompts"
|
mkdir -p "$base_dir/.amazonq/prompts"
|
||||||
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
|
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
|
||||||
@@ -214,26 +217,26 @@ build_variant() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Determine agent list
|
# Determine agent list
|
||||||
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp q)
|
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q)
|
||||||
ALL_SCRIPTS=(sh ps)
|
ALL_SCRIPTS=(sh ps)
|
||||||
|
|
||||||
norm_list() {
|
norm_list() {
|
||||||
# convert comma+space separated -> space separated unique while preserving order of first occurrence
|
# 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?" ":"") $i)}}}END{printf("\n")}'
|
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_subset() {
|
validate_subset() {
|
||||||
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
|
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
|
||||||
local ok=1
|
local invalid=0
|
||||||
for it in "${items[@]}"; do
|
for it in "${items[@]}"; do
|
||||||
local found=0
|
local found=0
|
||||||
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
|
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
|
||||||
if [[ $found -eq 0 ]]; then
|
if [[ $found -eq 0 ]]; then
|
||||||
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
||||||
ok=0
|
invalid=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return $ok
|
return $invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ -n ${AGENTS:-} ]]; then
|
if [[ -n ${AGENTS:-} ]]; then
|
||||||
|
|||||||
@@ -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 |
|
| **Claude Code** | `.claude/commands/` | Markdown | `claude` | Anthropic's Claude Code CLI |
|
||||||
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
|
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
|
||||||
| **GitHub Copilot** | `.github/chatmodes/` | 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 |
|
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
|
||||||
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
|
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
|
||||||
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode 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 |
|
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
||||||
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
|
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
|
||||||
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
|
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
|
||||||
|
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
||||||
|
|
||||||
### Step-by-Step Integration Guide
|
### Step-by-Step Integration Guide
|
||||||
|
|
||||||
@@ -311,6 +312,7 @@ Require a command-line tool to be installed:
|
|||||||
- **Amazon Q Developer CLI**: `q` CLI
|
- **Amazon Q Developer CLI**: `q` CLI
|
||||||
- **CodeBuddy CLI**: `codebuddy` CLI
|
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||||
- **Amp**: `amp` CLI
|
- **Amp**: `amp` CLI
|
||||||
|
- **SHAI**: `shai` CLI
|
||||||
|
|
||||||
### IDE-Based Agents
|
### IDE-Based Agents
|
||||||
|
|
||||||
@@ -323,7 +325,7 @@ Work within integrated development environments:
|
|||||||
|
|
||||||
### Markdown Format
|
### Markdown Format
|
||||||
|
|
||||||
Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp
|
Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp, SHAI
|
||||||
|
|
||||||
**Standard format:**
|
**Standard format:**
|
||||||
|
|
||||||
@@ -362,7 +364,7 @@ Command content with {SCRIPT} and {{args}} placeholders.
|
|||||||
|
|
||||||
- **CLI agents**: Usually `.<agent-name>/commands/`
|
- **CLI agents**: Usually `.<agent-name>/commands/`
|
||||||
- **IDE agents**: Follow IDE-specific patterns:
|
- **IDE agents**: Follow IDE-specific patterns:
|
||||||
- Copilot: `.github/chatmodes/`
|
- Copilot: `.github/agents/`
|
||||||
- Cursor: `.cursor/commands/`
|
- Cursor: `.cursor/commands/`
|
||||||
- Windsurf: `.windsurf/workflows/`
|
- Windsurf: `.windsurf/workflows/`
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ 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/),
|
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).
|
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
|
## [0.0.21] - 2025-10-21
|
||||||
|
|
||||||
- Fixes [#975](https://github.com/github/spec-kit/issues/975) (thank you [@fgalarraga](https://github.com/fgalarraga)).
|
- Fixes [#975](https://github.com/github/spec-kit/issues/975) (thank you [@fgalarraga](https://github.com/fgalarraga)).
|
||||||
|
|||||||
@@ -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) | ✅ | |
|
| [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. |
|
| [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/) | ✅ | |
|
| [Amp](https://ampcode.com/) | ✅ | |
|
||||||
|
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
|
||||||
|
|
||||||
## 🔧 Specify CLI Reference
|
## 🔧 Specify CLI Reference
|
||||||
|
|
||||||
@@ -160,14 +161,14 @@ The `specify` command supports the following options:
|
|||||||
| Command | Description |
|
| Command | Description |
|
||||||
|-------------|----------------------------------------------------------------|
|
|-------------|----------------------------------------------------------------|
|
||||||
| `init` | Initialize a new Specify project from the latest template |
|
| `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
|
### `specify init` Arguments & Options
|
||||||
|
|
||||||
| Argument/Option | Type | Description |
|
| 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: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, or `q` |
|
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, or `q` |
|
||||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
| `--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 |
|
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||||
| `--no-git` | Flag | Skip git repository initialization |
|
| `--no-git` | Flag | Skip git repository initialization |
|
||||||
@@ -195,6 +196,9 @@ specify init my-project --ai windsurf
|
|||||||
# Initialize with Amp support
|
# Initialize with Amp support
|
||||||
specify init my-project --ai amp
|
specify init my-project --ai amp
|
||||||
|
|
||||||
|
# Initialize with SHAI support
|
||||||
|
specify init my-project --ai shai
|
||||||
|
|
||||||
# Initialize with PowerShell scripts (Windows/cross-platform)
|
# Initialize with PowerShell scripts (Windows/cross-platform)
|
||||||
specify init my-project --ai copilot --script ps
|
specify init my-project --ai copilot --script ps
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ set -e
|
|||||||
|
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
SHORT_NAME=""
|
SHORT_NAME=""
|
||||||
|
BRANCH_NUMBER=""
|
||||||
ARGS=()
|
ARGS=()
|
||||||
i=1
|
i=1
|
||||||
while [ $i -le $# ]; do
|
while [ $i -le $# ]; do
|
||||||
@@ -26,17 +27,31 @@ while [ $i -le $# ]; do
|
|||||||
fi
|
fi
|
||||||
SHORT_NAME="$next_arg"
|
SHORT_NAME="$next_arg"
|
||||||
;;
|
;;
|
||||||
|
--number)
|
||||||
|
if [ $((i + 1)) -gt $# ]; then
|
||||||
|
echo 'Error: --number requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
i=$((i + 1))
|
||||||
|
next_arg="${!i}"
|
||||||
|
if [[ "$next_arg" == --* ]]; then
|
||||||
|
echo 'Error: --number requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
BRANCH_NUMBER="$next_arg"
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>"
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --json Output in JSON format"
|
echo " --json Output in JSON format"
|
||||||
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
echo " --number N Specify branch number manually (overrides auto-detection)"
|
||||||
echo " --help, -h Show this help message"
|
echo " --help, -h Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||||
echo " $0 'Implement OAuth2 integration for API'"
|
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -48,7 +63,7 @@ done
|
|||||||
|
|
||||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>" >&2
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -65,6 +80,90 @@ find_repo_root() {
|
|||||||
return 1
|
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
|
||||||
|
|
||||||
|
# Find all branches matching the pattern using git ls-remote (more reliable)
|
||||||
|
local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n)
|
||||||
|
|
||||||
|
# Also check local branches
|
||||||
|
local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Combine all sources and get the highest number
|
||||||
|
local max_num=0
|
||||||
|
for num in $remote_branches $local_branches $spec_dirs; do
|
||||||
|
if [ "$num" -gt "$max_num" ]; then
|
||||||
|
max_num=$num
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Return next number
|
||||||
|
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
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
# to searching for repository markers so the workflow still functions in repositories that
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
# were initialised with --no-git.
|
# were initialised with --no-git.
|
||||||
@@ -87,48 +186,6 @@ cd "$REPO_ROOT"
|
|||||||
SPECS_DIR="$REPO_ROOT/specs"
|
SPECS_DIR="$REPO_ROOT/specs"
|
||||||
mkdir -p "$SPECS_DIR"
|
mkdir -p "$SPECS_DIR"
|
||||||
|
|
||||||
# Get highest number from specs directory
|
|
||||||
HIGHEST_FROM_SPECS=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_FROM_SPECS" ]; then HIGHEST_FROM_SPECS=$number; fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get highest number from branch names (both local and remote)
|
|
||||||
HIGHEST_FROM_BRANCHES=0
|
|
||||||
if [ "$HAS_GIT" = true ]; then
|
|
||||||
# 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_FROM_BRANCHES" ]; then HIGHEST_FROM_BRANCHES=$number; fi
|
|
||||||
fi
|
|
||||||
done <<< "$branches"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use the highest number from either source
|
|
||||||
HIGHEST=$HIGHEST_FROM_SPECS
|
|
||||||
if [ "$HIGHEST_FROM_BRANCHES" -gt "$HIGHEST" ]; then
|
|
||||||
HIGHEST=$HIGHEST_FROM_BRANCHES
|
|
||||||
fi
|
|
||||||
|
|
||||||
NEXT=$((HIGHEST + 1))
|
|
||||||
FEATURE_NUM=$(printf "%03d" "$NEXT")
|
|
||||||
|
|
||||||
# Function to generate branch name with stop word filtering and length filtering
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
generate_branch_name() {
|
generate_branch_name() {
|
||||||
local description="$1"
|
local description="$1"
|
||||||
@@ -172,19 +229,33 @@ generate_branch_name() {
|
|||||||
echo "$result"
|
echo "$result"
|
||||||
else
|
else
|
||||||
# Fallback to original logic if no meaningful words found
|
# 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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generate branch name
|
# Generate branch name
|
||||||
if [ -n "$SHORT_NAME" ]; then
|
if [ -n "$SHORT_NAME" ]; then
|
||||||
# Use provided short name, just clean it up
|
# 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
|
else
|
||||||
# Generate from description with smart filtering
|
# Generate from description with smart filtering
|
||||||
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Determine branch number
|
||||||
|
if [ -z "$BRANCH_NUMBER" ]; then
|
||||||
|
if [ "$HAS_GIT" = true ]; then
|
||||||
|
# Check existing branches on remotes
|
||||||
|
BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX" "$SPECS_DIR")
|
||||||
|
else
|
||||||
|
# Fall back to local directory check
|
||||||
|
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||||
|
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER")
|
||||||
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
|
||||||
# GitHub enforces a 244-byte limit on branch names
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
#
|
#
|
||||||
# 5. Multi-Agent Support
|
# 5. Multi-Agent Support
|
||||||
# - Handles agent-specific file paths and naming conventions
|
# - 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
|
# - Can update single agents or all existing agent files
|
||||||
# - Creates default Claude file if no agent files exist
|
# - Creates default Claude file if no agent files exist
|
||||||
#
|
#
|
||||||
# Usage: ./update-agent-context.sh [agent_type]
|
# 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
|
# Leave empty to update all existing agent files
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -61,7 +61,7 @@ AGENT_TYPE="${1:-}"
|
|||||||
# Agent-specific file paths
|
# Agent-specific file paths
|
||||||
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
|
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
|
||||||
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
|
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
|
||||||
COPILOT_FILE="$REPO_ROOT/.github/chatmodes/copilot-instructions.md"
|
COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md"
|
||||||
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
||||||
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
||||||
AGENTS_FILE="$REPO_ROOT/AGENTS.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"
|
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
|
||||||
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
||||||
AMP_FILE="$REPO_ROOT/AGENTS.md"
|
AMP_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
|
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
||||||
Q_FILE="$REPO_ROOT/AGENTS.md"
|
Q_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
|
|
||||||
# Template file
|
# Template file
|
||||||
@@ -618,12 +619,15 @@ update_specific_agent() {
|
|||||||
amp)
|
amp)
|
||||||
update_agent_file "$AMP_FILE" "Amp"
|
update_agent_file "$AMP_FILE" "Amp"
|
||||||
;;
|
;;
|
||||||
|
shai)
|
||||||
|
update_agent_file "$SHAI_FILE" "SHAI"
|
||||||
|
;;
|
||||||
q)
|
q)
|
||||||
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown agent type '$agent_type'"
|
log_error "Unknown agent type '$agent_type'"
|
||||||
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|q"
|
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -688,6 +692,11 @@ update_all_existing_agents() {
|
|||||||
found_agent=true
|
found_agent=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$SHAI_FILE" ]]; then
|
||||||
|
update_agent_file "$SHAI_FILE" "SHAI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -f "$Q_FILE" ]]; then
|
if [[ -f "$Q_FILE" ]]; then
|
||||||
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||||
found_agent=true
|
found_agent=true
|
||||||
@@ -717,7 +726,7 @@ print_summary() {
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q]"
|
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q]"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
param(
|
param(
|
||||||
[switch]$Json,
|
[switch]$Json,
|
||||||
[string]$ShortName,
|
[string]$ShortName,
|
||||||
|
[int]$Number = 0,
|
||||||
[switch]$Help,
|
[switch]$Help,
|
||||||
[Parameter(ValueFromRemainingArguments = $true)]
|
[Parameter(ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$FeatureDescription
|
[string[]]$FeatureDescription
|
||||||
@@ -12,11 +13,12 @@ $ErrorActionPreference = 'Stop'
|
|||||||
|
|
||||||
# Show help if requested
|
# Show help if requested
|
||||||
if ($Help) {
|
if ($Help) {
|
||||||
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
|
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] [-Number N] <feature description>"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Options:"
|
Write-Host "Options:"
|
||||||
Write-Host " -Json Output in JSON format"
|
Write-Host " -Json Output in JSON format"
|
||||||
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
|
||||||
Write-Host " -Help Show this help message"
|
Write-Host " -Help Show this help message"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Examples:"
|
Write-Host "Examples:"
|
||||||
@@ -56,6 +58,121 @@ function Find-RepositoryRoot {
|
|||||||
$current = $parent
|
$current = $parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
[string]$SpecsDir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
|
||||||
|
try {
|
||||||
|
git fetch --all --prune 2>$null | Out-Null
|
||||||
|
} catch {
|
||||||
|
# Ignore fetch errors
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find remote branches matching the pattern using git ls-remote
|
||||||
|
$remoteBranches = @()
|
||||||
|
try {
|
||||||
|
$remoteRefs = git ls-remote --heads origin 2>$null
|
||||||
|
if ($remoteRefs) {
|
||||||
|
$remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
|
||||||
|
if ($_ -match "refs/heads/(\d+)-") {
|
||||||
|
[int]$matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check local branches
|
||||||
|
$localBranches = @()
|
||||||
|
try {
|
||||||
|
$allBranches = git branch 2>$null
|
||||||
|
if ($allBranches) {
|
||||||
|
$localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
|
||||||
|
if ($_ -match "(\d+)-") {
|
||||||
|
[int]$matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check specs directory
|
||||||
|
$specDirs = @()
|
||||||
|
if (Test-Path $SpecsDir) {
|
||||||
|
try {
|
||||||
|
$specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
|
||||||
|
if ($_.Name -match "^(\d+)-") {
|
||||||
|
[int]$matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Combine all sources and get the highest number
|
||||||
|
$maxNum = 0
|
||||||
|
foreach ($num in ($remoteBranches + $localBranches + $specDirs)) {
|
||||||
|
if ($num -gt $maxNum) {
|
||||||
|
$maxNum = $num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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)
|
$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
|
||||||
if (-not $fallbackRoot) {
|
if (-not $fallbackRoot) {
|
||||||
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
|
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
|
||||||
@@ -79,43 +196,6 @@ Set-Location $repoRoot
|
|||||||
$specsDir = Join-Path $repoRoot 'specs'
|
$specsDir = Join-Path $repoRoot 'specs'
|
||||||
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
||||||
|
|
||||||
# Get highest number from specs directory
|
|
||||||
$highestFromSpecs = 0
|
|
||||||
if (Test-Path $specsDir) {
|
|
||||||
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
|
||||||
if ($_.Name -match '^(\d{3})') {
|
|
||||||
$num = [int]$matches[1]
|
|
||||||
if ($num -gt $highestFromSpecs) { $highestFromSpecs = $num }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get highest number from branch names (both local and remote)
|
|
||||||
$highestFromBranches = 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{3})-') {
|
|
||||||
$num = [int]$matches[1]
|
|
||||||
if ($num -gt $highestFromBranches) { $highestFromBranches = $num }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
# If git command fails, just continue with specs-only check
|
|
||||||
Write-Verbose "Could not check Git branches: $_"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the highest number from either source
|
|
||||||
$highest = [Math]::Max($highestFromSpecs, $highestFromBranches)
|
|
||||||
$next = $highest + 1
|
|
||||||
$featureNum = ('{0:000}' -f $next)
|
|
||||||
|
|
||||||
# Function to generate branch name with stop word filtering and length filtering
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
function Get-BranchName {
|
function Get-BranchName {
|
||||||
param([string]$Description)
|
param([string]$Description)
|
||||||
@@ -155,7 +235,7 @@ function Get-BranchName {
|
|||||||
return $result
|
return $result
|
||||||
} else {
|
} else {
|
||||||
# Fallback to original logic if no meaningful words found
|
# 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
|
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||||
return [string]::Join('-', $fallbackWords)
|
return [string]::Join('-', $fallbackWords)
|
||||||
}
|
}
|
||||||
@@ -164,12 +244,24 @@ function Get-BranchName {
|
|||||||
# Generate branch name
|
# Generate branch name
|
||||||
if ($ShortName) {
|
if ($ShortName) {
|
||||||
# Use provided short name, just clean it up
|
# 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 {
|
} else {
|
||||||
# Generate from description with smart filtering
|
# Generate from description with smart filtering
|
||||||
$branchSuffix = Get-BranchName -Description $featureDesc
|
$branchSuffix = Get-BranchName -Description $featureDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Determine branch number
|
||||||
|
if ($Number -eq 0) {
|
||||||
|
if ($hasGit) {
|
||||||
|
# Check existing branches on remotes
|
||||||
|
$Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir
|
||||||
|
} else {
|
||||||
|
# Fall back to local directory check
|
||||||
|
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$featureNum = ('{0:000}' -f $Number)
|
||||||
$branchName = "$featureNum-$branchSuffix"
|
$branchName = "$featureNum-$branchSuffix"
|
||||||
|
|
||||||
# GitHub enforces a 244-byte limit on branch names
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
|||||||
@@ -59,4 +59,3 @@ if ($Json) {
|
|||||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||||
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
|||||||
2. Plan Data Extraction
|
2. Plan Data Extraction
|
||||||
3. Agent File Management (create from template or update existing)
|
3. Agent File Management (create from template or update existing)
|
||||||
4. Content Generation (technology stack, recent changes, timestamp)
|
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
|
.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).
|
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(
|
param(
|
||||||
[Parameter(Position=0)]
|
[Parameter(Position=0)]
|
||||||
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','q')]
|
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q')]
|
||||||
[string]$AgentType
|
[string]$AgentType
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ $NEW_PLAN = $IMPL_PLAN
|
|||||||
# Agent file paths
|
# Agent file paths
|
||||||
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
|
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
|
||||||
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
|
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
|
||||||
$COPILOT_FILE = Join-Path $REPO_ROOT '.github/chatmodes/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'
|
$CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
|
||||||
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
||||||
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.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'
|
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
|
||||||
$CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md'
|
$CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md'
|
||||||
$AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.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'
|
$Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
|
||||||
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||||
@@ -381,8 +382,9 @@ function Update-SpecificAgent {
|
|||||||
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
||||||
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
|
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
|
||||||
'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' }
|
'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' }
|
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
|
||||||
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'; 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'; return $false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +402,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 $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 $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 $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 $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true }
|
||||||
if (-not $found) {
|
if (-not $found) {
|
||||||
Write-Info 'No existing agent files found, creating default Claude file...'
|
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||||
@@ -415,7 +418,7 @@ function Print-Summary {
|
|||||||
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||||
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||||
Write-Host ''
|
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]'
|
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q]'
|
||||||
}
|
}
|
||||||
|
|
||||||
function Main {
|
function Main {
|
||||||
|
|||||||
@@ -208,6 +208,12 @@ AGENT_CONFIG = {
|
|||||||
"install_url": "https://ampcode.com/manual#install",
|
"install_url": "https://ampcode.com/manual#install",
|
||||||
"requires_cli": True,
|
"requires_cli": True,
|
||||||
},
|
},
|
||||||
|
"shai": {
|
||||||
|
"name": "SHAI",
|
||||||
|
"folder": ".shai/",
|
||||||
|
"install_url": "https://github.com/ovh/shai",
|
||||||
|
"requires_cli": True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||||
@@ -927,7 +933,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
|
|||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
|
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, or q"),
|
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, or q"),
|
||||||
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
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"),
|
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"),
|
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
||||||
@@ -1328,7 +1334,7 @@ def version():
|
|||||||
info_table.add_row("Template Version", template_version)
|
info_table.add_row("Template Version", template_version)
|
||||||
info_table.add_row("Released", release_date)
|
info_table.add_row("Released", release_date)
|
||||||
info_table.add_row("", "")
|
info_table.add_row("", "")
|
||||||
info_table.add_row("Python", f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
|
info_table.add_row("Python", platform.python_version())
|
||||||
info_table.add_row("Platform", platform.system())
|
info_table.add_row("Platform", platform.system())
|
||||||
info_table.add_row("Architecture", platform.machine())
|
info_table.add_row("Architecture", platform.machine())
|
||||||
info_table.add_row("OS Version", platform.version())
|
info_table.add_row("OS Version", platform.version())
|
||||||
|
|||||||
@@ -39,16 +39,36 @@ Given that feature description, do this:
|
|||||||
- "Create a dashboard for analytics" → "analytics-dashboard"
|
- "Create a dashboard for analytics" → "analytics-dashboard"
|
||||||
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
||||||
|
|
||||||
2. Run the script `{SCRIPT}` from repo root **with the short-name argument** and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
|
2. **Check for existing branches before creating new one**:
|
||||||
|
|
||||||
|
a. First, fetch all remote branches to ensure we have the latest information:
|
||||||
|
```bash
|
||||||
|
git fetch --all --prune
|
||||||
|
```
|
||||||
|
|
||||||
|
b. Find the highest feature number across all sources for the short-name:
|
||||||
|
- Remote branches: `git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$'`
|
||||||
|
- Local branches: `git branch | grep -E '^[* ]*[0-9]+-<short-name>$'`
|
||||||
|
- Specs directories: Check for directories matching `specs/[0-9]+-<short-name>`
|
||||||
|
|
||||||
|
c. Determine the next available number:
|
||||||
|
- Extract all numbers from all three sources
|
||||||
|
- Find the highest number N
|
||||||
|
- Use N+1 for the new branch number
|
||||||
|
|
||||||
|
d. Run the script `{SCRIPT}` with the calculated number and short-name:
|
||||||
|
- Pass `--number N+1` and `--short-name "your-short-name"` along with the feature description
|
||||||
|
- Bash example: `{SCRIPT} --json --number 5 --short-name "user-auth" "Add user authentication"`
|
||||||
|
- PowerShell example: `{SCRIPT} -Json -Number 5 -ShortName "user-auth" "Add user authentication"`
|
||||||
|
|
||||||
**IMPORTANT**:
|
**IMPORTANT**:
|
||||||
|
- Check all three sources (remote branches, local branches, specs directories) to find the highest number
|
||||||
- Append the short-name argument to the `{SCRIPT}` command with the 2-4 word short name you created in step 1. Keep the feature description as the final argument.
|
- Only match branches/directories with the exact short-name pattern
|
||||||
- Bash example: `--short-name "your-generated-short-name" "Feature description here"`
|
- If no existing branches/directories found with this short-name, start with number 1
|
||||||
- PowerShell example: `-ShortName "your-generated-short-name" "Feature description here"`
|
- You must only ever run this script once per feature
|
||||||
- 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")
|
|
||||||
- You must only ever run this script once
|
|
||||||
- The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for
|
- The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for
|
||||||
|
- The JSON output will contain BRANCH_NAME and SPEC_FILE paths
|
||||||
|
- 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")
|
||||||
|
|
||||||
3. Load `templates/spec-template.md` to understand required sections.
|
3. Load `templates/spec-template.md` to understand required sections.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
|
description: Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts.
|
||||||
tools: ['github/github-mcp-server/create_issue']
|
tools: ['github/github-mcp-server/issue_write']
|
||||||
scripts:
|
scripts:
|
||||||
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
|
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
||||||
|
|||||||
Reference in New Issue
Block a user