mirror of
https://github.com/github/spec-kit.git
synced 2026-07-07 06:35:06 +08:00
Addresses Copilot feedback: PowerShell's -replace operator does not support a third argument for replacement count. Using it causes an error or mis-parsing that would break forge package generation on Windows. Changed from: $content -replace '(?m)^---$', "---`nname: $cmdName", 1 To: $regex = [regex]'(?m)^---$' $content = $regex.Replace($content, "---`nname: $cmdName", 1) The .NET Regex.Replace() method properly supports the count parameter, ensuring the name field is injected only after the first frontmatter delimiter (not the closing one). This fix is critical for Windows users running: specify init --ai forge --offline
589 lines
22 KiB
PowerShell
589 lines
22 KiB
PowerShell
#!/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, junie, codex, kilocode, auggie, roo, codebuddy, amp, kiro-cli, bob, qodercli, shai, tabnine, agy, vibe, kimi, trae, pi, iflow, forge, generic
|
|
|
|
.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,
|
|
[string]$ExtraStripKey = ""
|
|
)
|
|
|
|
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) {
|
|
# Check for scripts/agent_scripts or extra strip key
|
|
$shouldSkip = $false
|
|
if ($line -match '^(scripts|agent_scripts):$') {
|
|
$shouldSkip = $true
|
|
}
|
|
if (-not [string]::IsNullOrEmpty($ExtraStripKey) -and $line -match "^${ExtraStripKey}:") {
|
|
$shouldSkip = $true
|
|
}
|
|
|
|
if ($shouldSkip) {
|
|
$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 '\$ARGUMENTS', $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
|
|
}
|
|
}
|
|
|
|
# Create skills in <skills_dir>\<name>\SKILL.md format.
|
|
# Skills use hyphenated names (e.g. speckit-plan).
|
|
#
|
|
# Technical debt note:
|
|
# Keep SKILL.md frontmatter aligned with `install_ai_skills()` and extension
|
|
# overrides (at minimum: name/description/compatibility/metadata.{author,source}).
|
|
function New-Skills {
|
|
param(
|
|
[string]$SkillsDir,
|
|
[string]$ScriptVariant,
|
|
[string]$AgentName,
|
|
[string]$Separator = '-'
|
|
)
|
|
|
|
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
|
|
|
|
foreach ($template in $templates) {
|
|
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
|
|
$skillName = "speckit${Separator}$name"
|
|
$skillDir = Join-Path $SkillsDir $skillName
|
|
New-Item -ItemType Directory -Force -Path $skillDir | Out-Null
|
|
|
|
$fileContent = (Get-Content -Path $template.FullName -Raw) -replace "`r`n", "`n"
|
|
|
|
# Extract description
|
|
$description = "Spec Kit: $name workflow"
|
|
if ($fileContent -match '(?m)^description:\s*(.+)$') {
|
|
$description = $matches[1]
|
|
}
|
|
|
|
# Extract script command
|
|
$scriptCommand = "(Missing script command for $ScriptVariant)"
|
|
if ($fileContent -match "(?m)^\s*${ScriptVariant}:\s*(.+)$") {
|
|
$scriptCommand = $matches[1]
|
|
}
|
|
|
|
# Extract agent_script command from frontmatter if present
|
|
$agentScriptCommand = ""
|
|
if ($fileContent -match "(?ms)agent_scripts:.*?^\s*${ScriptVariant}:\s*(.+?)$") {
|
|
$agentScriptCommand = $matches[1].Trim()
|
|
}
|
|
|
|
# Replace {SCRIPT}, strip scripts sections, rewrite paths
|
|
$body = $fileContent -replace '\{SCRIPT\}', $scriptCommand
|
|
if (-not [string]::IsNullOrEmpty($agentScriptCommand)) {
|
|
$body = $body -replace '\{AGENT_SCRIPT\}', $agentScriptCommand
|
|
}
|
|
|
|
$lines = $body -split "`n"
|
|
$outputLines = @()
|
|
$inFrontmatter = $false
|
|
$skipScripts = $false
|
|
$dashCount = 0
|
|
|
|
foreach ($line in $lines) {
|
|
if ($line -match '^---$') {
|
|
$outputLines += $line
|
|
$dashCount++
|
|
$inFrontmatter = ($dashCount -eq 1)
|
|
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"
|
|
$body = $body -replace '\{ARGS\}', '$ARGUMENTS'
|
|
$body = $body -replace '__AGENT__', $AgentName
|
|
$body = Rewrite-Paths -Content $body
|
|
|
|
# Strip existing frontmatter, keep only body
|
|
$templateBody = ""
|
|
$fmCount = 0
|
|
$inBody = $false
|
|
foreach ($line in ($body -split "`n")) {
|
|
if ($line -match '^---$') {
|
|
$fmCount++
|
|
if ($fmCount -eq 2) { $inBody = $true }
|
|
continue
|
|
}
|
|
if ($inBody) { $templateBody += "$line`n" }
|
|
}
|
|
|
|
$skillContent = "---`nname: `"$skillName`"`ndescription: `"$description`"`ncompatibility: `"Requires spec-kit project structure with .specify/ directory`"`nmetadata:`n author: `"github-spec-kit`"`n source: `"templates/commands/$name.md`"`n---`n`n$templateBody"
|
|
Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value $skillContent -NoNewline
|
|
}
|
|
}
|
|
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
$promptsDir = Join-Path $baseDir ".github/prompts"
|
|
Generate-CopilotPrompts -AgentsDir $agentsDir -PromptsDir $promptsDir
|
|
|
|
$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 'md' -ArgFormat '$ARGUMENTS' -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
|
|
}
|
|
'junie' {
|
|
$cmdDir = Join-Path $baseDir ".junie/commands"
|
|
Generate-Commands -Agent 'junie' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'codex' {
|
|
$skillsDir = Join-Path $baseDir ".agents/skills"
|
|
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
|
|
New-Skills -SkillsDir $skillsDir -ScriptVariant $Script -AgentName 'codex' -Separator '-'
|
|
}
|
|
'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
|
|
}
|
|
'kiro-cli' {
|
|
$cmdDir = Join-Path $baseDir ".kiro/prompts"
|
|
Generate-Commands -Agent 'kiro-cli' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'bob' {
|
|
$cmdDir = Join-Path $baseDir ".bob/commands"
|
|
Generate-Commands -Agent 'bob' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'qodercli' {
|
|
$cmdDir = Join-Path $baseDir ".qoder/commands"
|
|
Generate-Commands -Agent 'qodercli' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'shai' {
|
|
$cmdDir = Join-Path $baseDir ".shai/commands"
|
|
Generate-Commands -Agent 'shai' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'tabnine' {
|
|
$cmdDir = Join-Path $baseDir ".tabnine/agent/commands"
|
|
Generate-Commands -Agent 'tabnine' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
|
|
$tabnineTemplate = Join-Path 'agent_templates' 'tabnine/TABNINE.md'
|
|
if (Test-Path $tabnineTemplate) { Copy-Item $tabnineTemplate (Join-Path $baseDir 'TABNINE.md') }
|
|
}
|
|
'agy' {
|
|
$cmdDir = Join-Path $baseDir ".agent/commands"
|
|
Generate-Commands -Agent 'agy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'vibe' {
|
|
$cmdDir = Join-Path $baseDir ".vibe/prompts"
|
|
Generate-Commands -Agent 'vibe' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'kimi' {
|
|
$skillsDir = Join-Path $baseDir ".kimi/skills"
|
|
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
|
|
New-Skills -SkillsDir $skillsDir -ScriptVariant $Script -AgentName 'kimi'
|
|
}
|
|
'trae' {
|
|
$rulesDir = Join-Path $baseDir ".trae/rules"
|
|
New-Item -ItemType Directory -Force -Path $rulesDir | Out-Null
|
|
Generate-Commands -Agent 'trae' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $rulesDir -ScriptVariant $Script
|
|
}
|
|
'pi' {
|
|
$cmdDir = Join-Path $baseDir ".pi/prompts"
|
|
Generate-Commands -Agent 'pi' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'iflow' {
|
|
$cmdDir = Join-Path $baseDir ".iflow/commands"
|
|
Generate-Commands -Agent 'iflow' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
'forge' {
|
|
$cmdDir = Join-Path $baseDir ".forge/commands"
|
|
Generate-Commands -Agent 'forge' -Extension 'md' -ArgFormat '{{parameters}}' -OutputDir $cmdDir -ScriptVariant $Script -ExtraStripKey 'handoffs'
|
|
|
|
# Inject name field into frontmatter (forge requires name + description)
|
|
$cmdFiles = Get-ChildItem -Path "$cmdDir/*.md" -File -ErrorAction SilentlyContinue
|
|
foreach ($cmdFile in $cmdFiles) {
|
|
$cmdName = [System.IO.Path]::GetFileNameWithoutExtension($cmdFile.Name)
|
|
$content = Get-Content -Path $cmdFile.FullName -Raw
|
|
|
|
# Inject name field after first --- using .NET Regex.Replace with count limit
|
|
$regex = [regex]'(?m)^---$'
|
|
$content = $regex.Replace($content, "---`nname: $cmdName", 1)
|
|
|
|
Set-Content -Path $cmdFile.FullName -Value $content -NoNewline
|
|
}
|
|
}
|
|
'generic' {
|
|
$cmdDir = Join-Path $baseDir ".speckit/commands"
|
|
Generate-Commands -Agent 'generic' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
|
}
|
|
default {
|
|
throw "Unsupported agent '$Agent'."
|
|
}
|
|
}
|
|
|
|
# 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', 'junie', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'kiro-cli', 'bob', 'qodercli', 'shai', 'tabnine', 'agy', 'vibe', 'kimi', 'trae', 'pi', 'iflow', 'forge', 'generic')
|
|
$AllScripts = @('sh', 'ps')
|
|
|
|
function Normalize-List {
|
|
param([string]$Value)
|
|
|
|
if ([string]::IsNullOrEmpty($Value)) {
|
|
return @()
|
|
}
|
|
|
|
$items = $Value -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 -Value $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 -Value $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)"
|
|
}
|