From 8f97d7e7355b30661345065cf8e34c9bbb7ede1a Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Sat, 4 Apr 2026 19:00:53 +1100 Subject: [PATCH 1/7] fix(bash): sed replacement escaping, BSD portability, dead cleanup code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs in update-agent-context.sh: 1. **sed escaping targets wrong side** (line 318-320): The escaping function escapes regex pattern characters (`[`, `.`, `*`, `^`, `$`, `+`, `{`, `}`, `|`) but these variables are used as sed *replacement* strings, not patterns. Only `&` (insert matched text), `\` (escape char), and `|` (our sed delimiter) are special in the replacement context. Also adds escaping for `project_name` which was used unescaped. 2. **BSD sed newline insertion fails on macOS** (line 364-366): Uses bash variable expansion to insert a literal newline into a sed replacement string. This works on GNU sed (Linux) but fails silently on BSD sed (macOS). Replaced with portable awk approach that works on both platforms. 3. **cleanup() removes non-existent files** (line 125-126): The cleanup trap attempts `rm -f /tmp/agent_update_*_$$` and `rm -f /tmp/manual_additions_$$` but the script never creates files matching these patterns — all temp files use `mktemp`. The wildcard with `$$` (PID) in /tmp could theoretically match unrelated files. Fixes #154 (macOS sed failure) Fixes #293 (sed expression errors) Related: #338 (shellcheck findings) --- scripts/bash/update-agent-context.sh | 62 ++++++++++++++++------------ 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index da06ed469..c2dea72a0 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -30,12 +30,12 @@ # # 5. Multi-Agent Support # - Handles agent-specific file paths and naming conventions -# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Junie, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Pi Coding Agent, iFlow CLI, Forge, Antigravity or Generic +# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Junie, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Pi Coding Agent, iFlow CLI, Antigravity or Generic # - Can update single agents or all existing agent files # - Creates default Claude file if no agent files exist # # Usage: ./update-agent-context.sh [agent_type] -# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic +# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic # Leave empty to update all existing agent files set -e @@ -63,7 +63,7 @@ AGENT_TYPE="${1:-}" # Agent-specific file paths CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" -COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" +COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" QWEN_FILE="$REPO_ROOT/QWEN.md" AGENTS_FILE="$REPO_ROOT/AGENTS.md" @@ -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, Pi, and Forge all share AGENTS.md — use AGENTS_FILE to avoid +# Amp, Kiro CLI, IBM Bob, and Pi 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,7 +86,6 @@ 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" @@ -117,13 +116,17 @@ log_warning() { echo "WARNING: $1" >&2 } +# Track temporary files for cleanup on interrupt +_CLEANUP_FILES=() + # Cleanup function for temporary files cleanup() { local exit_code=$? # Disarm traps to prevent re-entrant loop trap - EXIT INT TERM - rm -f /tmp/agent_update_*_$$ - rm -f /tmp/manual_additions_$$ + for f in "${_CLEANUP_FILES[@]+"${_CLEANUP_FILES[@]}"}"; do + rm -f "$f" "$f.bak" "$f.tmp" + done exit $exit_code } @@ -284,7 +287,8 @@ get_language_conventions() { create_new_agent_file() { local target_file="$1" local temp_file="$2" - local project_name="$3" + local project_name + project_name=$(printf '%s\n' "$3" | sed 's/[\\&|]/\\&/g') local current_date="$4" if [[ ! -f "$TEMPLATE_FILE" ]]; then @@ -307,18 +311,21 @@ create_new_agent_file() { # Replace template placeholders local project_structure project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") + project_structure=$(printf '%s\n' "$project_structure" | sed 's/[\\&|]/\\&/g') local commands commands=$(get_commands_for_language "$NEW_LANG") - + local language_conventions language_conventions=$(get_language_conventions "$NEW_LANG") - - # Perform substitutions with error checking using safer approach - # Escape special characters for sed by using a different delimiter or escaping - local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g') - local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g') - local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g') + + # Escape special characters for sed replacement strings (right side of s|pattern|replacement|) + # & and \ are replacement-side specials; | must also be escaped because it's our sed delimiter + local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\\&|]/\\&/g') + local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\\&|]/\\&/g') + commands=$(printf '%s\n' "$commands" | sed 's/[\\&|]/\\&/g') + language_conventions=$(printf '%s\n' "$language_conventions" | sed 's/[\\&|]/\\&/g') + local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\\&|]/\\&/g') # Build technology stack and recent change strings conditionally local tech_stack @@ -361,17 +368,17 @@ create_new_agent_file() { fi done - # Convert \n sequences to actual newlines - newline=$(printf '\n') - sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" + # Convert literal \n sequences to actual newlines (portable — works on BSD + GNU) + awk '{gsub(/\\n/,"\n")}1' "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" - # Clean up backup files - rm -f "$temp_file.bak" "$temp_file.bak2" + # Clean up backup files from sed -i.bak + rm -f "$temp_file.bak" # Prepend Cursor frontmatter for .mdc files so rules are auto-included if [[ "$target_file" == *.mdc ]]; then local frontmatter_file frontmatter_file=$(mktemp) || return 1 + _CLEANUP_FILES+=("$frontmatter_file") printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file" cat "$temp_file" >> "$frontmatter_file" mv "$frontmatter_file" "$temp_file" @@ -395,6 +402,7 @@ update_existing_agent_file() { log_error "Failed to create temporary file" return 1 } + _CLEANUP_FILES+=("$temp_file") # Process the file in one pass local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") @@ -519,6 +527,7 @@ update_existing_agent_file() { if ! head -1 "$temp_file" | grep -q '^---'; then local frontmatter_file frontmatter_file=$(mktemp) || { rm -f "$temp_file"; return 1; } + _CLEANUP_FILES+=("$frontmatter_file") printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file" cat "$temp_file" >> "$frontmatter_file" mv "$frontmatter_file" "$temp_file" @@ -571,6 +580,7 @@ update_agent_file() { log_error "Failed to create temporary file" return 1 } + _CLEANUP_FILES+=("$temp_file") if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then if mv "$temp_file" "$target_file"; then @@ -691,15 +701,12 @@ update_specific_agent() { iflow) update_agent_file "$IFLOW_FILE" "iFlow CLI" || return 1 ;; - forge) - update_agent_file "$AGENTS_FILE" "Forge" || return 1 - ;; generic) log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent." ;; *) log_error "Unknown agent type '$agent_type'" - log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic" + log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic" exit 1 ;; esac @@ -743,7 +750,10 @@ update_all_existing_agents() { _update_if_new "$COPILOT_FILE" "GitHub Copilot" || _all_ok=false _update_if_new "$CURSOR_FILE" "Cursor IDE" || _all_ok=false _update_if_new "$QWEN_FILE" "Qwen Code" || _all_ok=false - _update_if_new "$AGENTS_FILE" "Codex/opencode/Amp/Kiro/Bob/Pi/Forge" || _all_ok=false + _update_if_new "$AGENTS_FILE" "Codex/opencode" || _all_ok=false + _update_if_new "$AMP_FILE" "Amp" || _all_ok=false + _update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false + _update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false _update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false _update_if_new "$JUNIE_FILE" "Junie" || _all_ok=false _update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false @@ -784,7 +794,7 @@ print_summary() { fi echo - log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic]" + log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic]" } #============================================================================== From 1661ddb37217114129a7a6679b99caddd54b60ed Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 12:34:50 +1000 Subject: [PATCH 2/7] fix: restore forge case and revert copilot path change Address PR review feedback: - Restore forge) case in update_specific_agent since src/specify_cli/integrations/forge/__init__.py still exists - Revert COPILOT_FILE path from .github/agents/ back to .github/ to stay consistent with Python integration and tests - Restore FORGE_FILE variable, comments, and usage strings Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index c2dea72a0..6921f67fb 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -30,12 +30,12 @@ # # 5. Multi-Agent Support # - Handles agent-specific file paths and naming conventions -# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Junie, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Pi Coding Agent, iFlow CLI, Antigravity or Generic +# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Junie, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Pi Coding Agent, iFlow CLI, Forge, Antigravity or Generic # - Can update single agents or all existing agent files # - Creates default Claude file if no agent files exist # # Usage: ./update-agent-context.sh [agent_type] -# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic +# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic # Leave empty to update all existing agent files set -e @@ -63,7 +63,7 @@ AGENT_TYPE="${1:-}" # Agent-specific file paths CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" -COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" +COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" QWEN_FILE="$REPO_ROOT/QWEN.md" AGENTS_FILE="$REPO_ROOT/AGENTS.md" @@ -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" @@ -701,12 +702,15 @@ update_specific_agent() { iflow) update_agent_file "$IFLOW_FILE" "iFlow CLI" || return 1 ;; + forge) + update_agent_file "$AGENTS_FILE" "Forge" || return 1 + ;; generic) log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent." ;; *) log_error "Unknown agent type '$agent_type'" - log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic" + log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic" exit 1 ;; esac @@ -754,6 +758,7 @@ update_all_existing_agents() { _update_if_new "$AMP_FILE" "Amp" || _all_ok=false _update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false _update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false + _update_if_new "$FORGE_FILE" "Forge" || _all_ok=false _update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false _update_if_new "$JUNIE_FILE" "Junie" || _all_ok=false _update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false @@ -794,7 +799,7 @@ print_summary() { fi echo - log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic]" + log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|generic]" } #============================================================================== From 3280cd756d6e5ec01ae19fbb7080508940d97f2f Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 12:57:18 +1000 Subject: [PATCH 3/7] refactor: extract repeated sed escaping into _esc_sed helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address Gemini review feedback — the inline sed escaping pattern appeared 7 times in create_new_agent_file(). Extract to a single helper function for maintainability and readability. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 6921f67fb..7b9d49500 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -285,11 +285,15 @@ get_language_conventions() { echo "$lang: Follow standard conventions" } +# Escape sed replacement-side specials for | delimiter. +# & and \ are replacement-side specials; | is our sed delimiter. +_esc_sed() { printf '%s\n' "$1" | sed 's/[\\&|]/\\&/g'; } + create_new_agent_file() { local target_file="$1" local temp_file="$2" local project_name - project_name=$(printf '%s\n' "$3" | sed 's/[\\&|]/\\&/g') + project_name=$(_esc_sed "$3") local current_date="$4" if [[ ! -f "$TEMPLATE_FILE" ]]; then @@ -312,7 +316,7 @@ create_new_agent_file() { # Replace template placeholders local project_structure project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") - project_structure=$(printf '%s\n' "$project_structure" | sed 's/[\\&|]/\\&/g') + project_structure=$(_esc_sed "$project_structure") local commands commands=$(get_commands_for_language "$NEW_LANG") @@ -320,13 +324,11 @@ create_new_agent_file() { local language_conventions language_conventions=$(get_language_conventions "$NEW_LANG") - # Escape special characters for sed replacement strings (right side of s|pattern|replacement|) - # & and \ are replacement-side specials; | must also be escaped because it's our sed delimiter - local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\\&|]/\\&/g') - local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\\&|]/\\&/g') - commands=$(printf '%s\n' "$commands" | sed 's/[\\&|]/\\&/g') - language_conventions=$(printf '%s\n' "$language_conventions" | sed 's/[\\&|]/\\&/g') - local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\\&|]/\\&/g') + local escaped_lang=$(_esc_sed "$NEW_LANG") + local escaped_framework=$(_esc_sed "$NEW_FRAMEWORK") + commands=$(_esc_sed "$commands") + language_conventions=$(_esc_sed "$language_conventions") + local escaped_branch=$(_esc_sed "$CURRENT_BRANCH") # Build technology stack and recent change strings conditionally local tech_stack From e4933f967000cfd9e74d39b07b0697dc7e90d20b Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 13:03:10 +1000 Subject: [PATCH 4/7] fix: restore combined AGENTS_FILE label in update_all_existing_agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini correctly identified that splitting AGENTS_FILE updates into individual calls is redundant — _update_if_new deduplicates by realpath, so only the first call logs. Restore the combined label and add back missing Pi reference. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 7b9d49500..a629f1215 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -756,11 +756,7 @@ update_all_existing_agents() { _update_if_new "$COPILOT_FILE" "GitHub Copilot" || _all_ok=false _update_if_new "$CURSOR_FILE" "Cursor IDE" || _all_ok=false _update_if_new "$QWEN_FILE" "Qwen Code" || _all_ok=false - _update_if_new "$AGENTS_FILE" "Codex/opencode" || _all_ok=false - _update_if_new "$AMP_FILE" "Amp" || _all_ok=false - _update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false - _update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false - _update_if_new "$FORGE_FILE" "Forge" || _all_ok=false + _update_if_new "$AGENTS_FILE" "Codex/opencode/Amp/Kiro/Bob/Pi/Forge" || _all_ok=false _update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false _update_if_new "$JUNIE_FILE" "Junie" || _all_ok=false _update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false From e26a1e60730a045dfdb2b99432fb89c376e983ce Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 13:27:28 +1000 Subject: [PATCH 5/7] fix: remove pre-escaped && in JS/TS commands now that _esc_sed handles it The old code manually pre-escaped & as \& in get_commands_for_language because the broken escaping function didn't handle &. Now that _esc_sed properly escapes replacement-side specials, the pre-escaping causes double-escaping: && becomes \&\& in generated files. Found by blind audit. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index a629f1215..141818c70 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -272,7 +272,7 @@ get_commands_for_language() { echo "cargo test && cargo clippy" ;; *"JavaScript"*|*"TypeScript"*) - echo "npm test \\&\\& npm run lint" + echo "npm test && npm run lint" ;; *) echo "# Add commands for $lang" From 414d01692f35fec313e861bce7349083459e2dca Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 13:42:19 +1000 Subject: [PATCH 6/7] fix: split awk && mv to let set -e catch awk failures Under set -e, the left side of && does not trigger errexit on failure. Split into two statements so awk failures are fatal instead of silent. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 141818c70..6347d459e 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -372,7 +372,8 @@ create_new_agent_file() { done # Convert literal \n sequences to actual newlines (portable — works on BSD + GNU) - awk '{gsub(/\\n/,"\n")}1' "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" + awk '{gsub(/\\n/,"\n")}1' "$temp_file" > "$temp_file.tmp" + mv "$temp_file.tmp" "$temp_file" # Clean up backup files from sed -i.bak rm -f "$temp_file.bak" From aac7864734beea7dfe92c2408bd4ca825a1a4356 Mon Sep 17 00:00:00 2001 From: Bo Bobson Date: Tue, 7 Apr 2026 13:49:05 +1000 Subject: [PATCH 7/7] fix: guard empty _CLEANUP_FILES array for Bash 3.2 compatibility On Bash 3.2, the ${arr[@]+"${arr[@]}"} pattern expands to a single empty string when the array is empty, causing rm to target .bak and .tmp in the current directory. Use explicit length check instead, which also avoids the word-splitting risk of unquoted expansion. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/bash/update-agent-context.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 6347d459e..fef487660 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -125,9 +125,11 @@ cleanup() { local exit_code=$? # Disarm traps to prevent re-entrant loop trap - EXIT INT TERM - for f in "${_CLEANUP_FILES[@]+"${_CLEANUP_FILES[@]}"}"; do - rm -f "$f" "$f.bak" "$f.tmp" - done + if [ ${#_CLEANUP_FILES[@]} -gt 0 ]; then + for f in "${_CLEANUP_FILES[@]}"; do + rm -f "$f" "$f.bak" "$f.tmp" + done + fi exit $exit_code }