feat: add forge to PowerShell script and fix test whitespace

1. PowerShell script (create-release-packages.ps1):
   - Added forge agent support for Windows users
   - Enables `specify init --ai forge --offline` on Windows
   - Enhanced Generate-Commands with ExtraStripKey parameter
   - Added frontmatter stripping for handoffs key
   - Added $ARGUMENTS replacement for {{parameters}}
   - Implemented forge case with name field injection
   - Complete parity with bash script

2. Test file (test_core_pack_scaffold.py):
   - Removed trailing whitespace from blank lines
   - Cleaner diffs and no linter warnings

Addresses Copilot PR feedback on both issues.
This commit is contained in:
ericnoam
2026-03-31 20:56:18 +02:00
parent 635790d66b
commit 506b7ef1c7
2 changed files with 34 additions and 7 deletions

View File

@@ -14,7 +14,7 @@
.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, generic
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)
@@ -73,7 +73,8 @@ function Generate-Commands {
[string]$Extension,
[string]$ArgFormat,
[string]$OutputDir,
[string]$ScriptVariant
[string]$ScriptVariant,
[string]$ExtraStripKey = ""
)
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
@@ -137,7 +138,16 @@ function Generate-Commands {
}
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
}
@@ -156,6 +166,7 @@ function Generate-Commands {
# Apply other substitutions
$body = $body -replace '\{ARGS\}', $ArgFormat
$body = $body -replace '\$ARGUMENTS', $ArgFormat
$body = $body -replace '__AGENT__', $Agent
$body = Rewrite-Paths -Content $body
@@ -477,6 +488,22 @@ function Build-Variant {
$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 ---
$content = $content -replace '(?m)^---$', "---`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
@@ -493,7 +520,7 @@ function Build-Variant {
}
# 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', 'generic')
$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 {

View File

@@ -466,12 +466,12 @@ def test_markdown_has_frontmatter(agent, scaffolded_sh):
def test_forge_name_field_in_frontmatter(scaffolded_sh):
"""Forge: every command file must have a 'name' field in frontmatter that matches the filename.
Forge requires both 'name' and 'description' fields in command frontmatter.
This test ensures the release script's name injection is working correctly.
"""
project = scaffolded_sh("forge")
cmd_dir = _expected_cmd_dir(project, "forge")
for f in _list_command_files(cmd_dir, "forge"):
content = f.read_text(encoding="utf-8")
@@ -482,13 +482,13 @@ def test_forge_name_field_in_frontmatter(scaffolded_sh):
assert len(parts) >= 3, f"Incomplete frontmatter in '{f.name}'"
fm = yaml.safe_load(parts[1])
assert fm is not None, f"Empty frontmatter in '{f.name}'"
# Check that 'name' field exists
assert "name" in fm, (
f"'name' key missing from frontmatter in '{f.name}' - "
f"Forge requires both 'name' and 'description' fields"
)
# Check that name matches the filename (without extension)
expected_name = f.name.removesuffix(".md")
actual_name = fm["name"]