Files
github-spec-kit/.github/workflows/scripts/create-release-packages.ps1
ericnoam 90a1845c13 fix: resolve unbound variable and duplicate file update issues
- Fix undefined FORGE_FILE variable in bash update-agent-context.sh
  - Add missing FORGE_FILE definition pointing to AGENTS.md
  - Update comment to include Forge in list of agents sharing AGENTS.md
  - Prevents crash with 'set -u' when running without explicit agent type

- Add deduplication logic to PowerShell update-agent-context.ps1
  - Implement Update-IfNew helper to track processed files by real path
  - Prevents AGENTS.md from being rewritten multiple times
  - Matches existing deduplication behavior in bash script

- Prevent duplicate YAML keys in Forge frontmatter injection
  - Check for existing 'name:' field before injection in both scripts
  - PowerShell: Parse frontmatter to detect existing name field
  - Bash: Enhanced awk script to check frontmatter state
  - Future-proofs against template changes that add name fields

All scripts now have consistent behavior and proper error handling.
2026-04-02 20:34:23 +02:00

621 lines
24 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
# Determine whether the first frontmatter block already contains a name field
$hasNameInFrontmatter = $false
$lines = $content -split "`n"
$frontmatterStart = $null
$frontmatterEnd = $null
for ($i = 0; $i -lt $lines.Length; $i++) {
if ($lines[$i] -match '^\s*---\s*$') {
if ($null -eq $frontmatterStart) {
$frontmatterStart = $i
} elseif ($null -eq $frontmatterEnd) {
$frontmatterEnd = $i
break
}
}
}
if ($null -ne $frontmatterStart) {
if ($null -eq $frontmatterEnd) {
$frontmatterEnd = $lines.Length
}
for ($j = $frontmatterStart + 1; $j -lt $frontmatterEnd; $j++) {
if ($lines[$j] -match '^[ \t]*name\s*:') {
$hasNameInFrontmatter = $true
break
}
}
}
# Inject name field after first --- using .NET Regex.Replace with count limit
if (-not $hasNameInFrontmatter) {
$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)"
}