From 90a1845c134236ed5b2025cdb371950c2404729a Mon Sep 17 00:00:00 2001 From: ericnoam Date: Thu, 2 Apr 2026 20:34:23 +0200 Subject: [PATCH] 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. --- .../scripts/create-release-packages.ps1 | 42 ++++++++-- .../scripts/create-release-packages.sh | 10 ++- scripts/bash/update-agent-context.sh | 3 +- scripts/powershell/update-agent-context.ps1 | 77 +++++++++++++------ 4 files changed, 102 insertions(+), 30 deletions(-) diff --git a/.github/workflows/scripts/create-release-packages.ps1 b/.github/workflows/scripts/create-release-packages.ps1 index 932f54f80..0e18c9ddd 100644 --- a/.github/workflows/scripts/create-release-packages.ps1 +++ b/.github/workflows/scripts/create-release-packages.ps1 @@ -491,17 +491,49 @@ function Build-Variant { '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 - $regex = [regex]'(?m)^---$' - $content = $regex.Replace($content, "---`nname: $cmdName", 1) - + if (-not $hasNameInFrontmatter) { + $regex = [regex]'(?m)^---$' + $content = $regex.Replace($content, "---`nname: $cmdName", 1) + } + Set-Content -Path $cmdFile.FullName -Value $content -NoNewline } } diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index b6a5e6234..482ba7619 100755 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -339,7 +339,15 @@ build_variant() { [[ -f "$_cmd_file" ]] || continue _cmd_name=$(basename "$_cmd_file" .md) _tmp_file="${_cmd_file}.tmp" - awk -v name="$_cmd_name" 'NR==1 && /^---$/ { print; print "name: "name; next } { print }' "$_cmd_file" > "$_tmp_file" + # Only inject name if frontmatter doesn't already have one + awk -v name="$_cmd_name" ' + BEGIN { in_frontmatter=0; has_name=0; first_dash_seen=0 } + NR==1 && /^---$/ { in_frontmatter=1; first_dash_seen=1; print; next } + in_frontmatter && /^---$/ { in_frontmatter=0 } + in_frontmatter && /^[ \t]*name[ \t]*:/ { has_name=1 } + first_dash_seen && !has_name && NR==2 && in_frontmatter { print "name: "name } + { print } + ' "$_cmd_file" > "$_tmp_file" mv "$_tmp_file" "$_cmd_file" done ;; generic) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 7ea54b274..a818c68bd 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -74,7 +74,7 @@ AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" QODER_FILE="$REPO_ROOT/QODER.md" -# Amp, Kiro CLI, IBM Bob, and Pi all share AGENTS.md — use AGENTS_FILE to avoid +# Amp, Kiro CLI, IBM Bob, Pi, and Forge all share AGENTS.md — use AGENTS_FILE to avoid # updating the same file multiple times. AMP_FILE="$AGENTS_FILE" SHAI_FILE="$REPO_ROOT/SHAI.md" @@ -86,6 +86,7 @@ VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md" KIMI_FILE="$REPO_ROOT/KIMI.md" TRAE_FILE="$REPO_ROOT/.trae/rules/AGENTS.md" IFLOW_FILE="$REPO_ROOT/IFLOW.md" +FORGE_FILE="$AGENTS_FILE" # Template file TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md" diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 6ba93e183..ee9109417 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -425,29 +425,60 @@ function Update-SpecificAgent { function Update-AllExistingAgents { $found = $false $ok = $true - if (Test-Path $CLAUDE_FILE) { if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }; $found = $true } - if (Test-Path $GEMINI_FILE) { if (-not (Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }; $found = $true } - if (Test-Path $COPILOT_FILE) { if (-not (Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }; $found = $true } - if (Test-Path $CURSOR_FILE) { if (-not (Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }; $found = $true } - if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true } - if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true } - if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true } - if (Test-Path $JUNIE_FILE) { if (-not (Update-AgentFile -TargetFile $JUNIE_FILE -AgentName 'Junie')) { $ok = $false }; $found = $true } - if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $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 $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true } - if (Test-Path $QODER_FILE) { if (-not (Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder 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 $TABNINE_FILE) { if (-not (Update-AgentFile -TargetFile $TABNINE_FILE -AgentName 'Tabnine CLI')) { $ok = $false }; $found = $true } - if (Test-Path $KIRO_FILE) { if (-not (Update-AgentFile -TargetFile $KIRO_FILE -AgentName 'Kiro CLI')) { $ok = $false }; $found = $true } - if (Test-Path $AGY_FILE) { if (-not (Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity')) { $ok = $false }; $found = $true } - if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true } - if (Test-Path $VIBE_FILE) { if (-not (Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe')) { $ok = $false }; $found = $true } - if (Test-Path $KIMI_FILE) { if (-not (Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false }; $found = $true } - if (Test-Path $TRAE_FILE) { if (-not (Update-AgentFile -TargetFile $TRAE_FILE -AgentName 'Trae')) { $ok = $false }; $found = $true } - if (Test-Path $IFLOW_FILE) { if (-not (Update-AgentFile -TargetFile $IFLOW_FILE -AgentName 'iFlow CLI')) { $ok = $false }; $found = $true } - if (Test-Path $FORGE_FILE) { if (-not (Update-AgentFile -TargetFile $FORGE_FILE -AgentName 'Forge')) { $ok = $false }; $found = $true } + $updatedPaths = @() + + # Helper function to update only if file exists and hasn't been updated yet + function Update-IfNew { + param( + [Parameter(Mandatory=$true)] + [string]$FilePath, + [Parameter(Mandatory=$true)] + [string]$AgentName + ) + + if (-not (Test-Path $FilePath)) { return $true } + + # Get the real path to detect duplicates (e.g., AMP_FILE, KIRO_FILE, BOB_FILE all point to AGENTS.md) + $realPath = (Get-Item -LiteralPath $FilePath).FullName + + # Check if we've already updated this file + if ($updatedPaths -contains $realPath) { + return $true + } + + # Record the file as seen before attempting the update + $script:updatedPaths += $realPath + $script:found = $true + + # Perform the update + return (Update-AgentFile -TargetFile $FilePath -AgentName $AgentName) + } + + if (-not (Update-IfNew -FilePath $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false } + if (-not (Update-IfNew -FilePath $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false } + if (-not (Update-IfNew -FilePath $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false } + if (-not (Update-IfNew -FilePath $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false } + if (-not (Update-IfNew -FilePath $AGENTS_FILE -AgentName 'Codex/opencode/Amp/Kiro/Bob/Pi/Forge')) { $ok = $false } + if (-not (Update-IfNew -FilePath $AMP_FILE -AgentName 'Amp')) { $ok = $false } + if (-not (Update-IfNew -FilePath $KIRO_FILE -AgentName 'Kiro CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false } + if (-not (Update-IfNew -FilePath $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false } + if (-not (Update-IfNew -FilePath $JUNIE_FILE -AgentName 'Junie')) { $ok = $false } + if (-not (Update-IfNew -FilePath $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false } + if (-not (Update-IfNew -FilePath $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $ROO_FILE -AgentName 'Roo Code')) { $ok = $false } + if (-not (Update-IfNew -FilePath $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $QODER_FILE -AgentName 'Qoder CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $SHAI_FILE -AgentName 'SHAI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $TABNINE_FILE -AgentName 'Tabnine CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $AGY_FILE -AgentName 'Antigravity')) { $ok = $false } + if (-not (Update-IfNew -FilePath $VIBE_FILE -AgentName 'Mistral Vibe')) { $ok = $false } + if (-not (Update-IfNew -FilePath $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false } + if (-not (Update-IfNew -FilePath $TRAE_FILE -AgentName 'Trae')) { $ok = $false } + if (-not (Update-IfNew -FilePath $IFLOW_FILE -AgentName 'iFlow CLI')) { $ok = $false } + if (-not (Update-IfNew -FilePath $FORGE_FILE -AgentName 'Forge')) { $ok = $false } + if (-not $found) { Write-Info 'No existing agent files found, creating default Claude file...' if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }