fix: harden bash scripts against shell injection and improve robustness (#1809)

- Replace eval of unquoted get_feature_paths output with safe pattern:
  capture into variable, check return code, then eval quoted result
- Use printf '%q' in get_feature_paths to safely emit shell assignments,
  preventing injection via paths containing quotes or metacharacters
- Add json_escape() helper for printf JSON fallback paths, handling
  backslash, double-quote, and control characters when jq is unavailable
- Use jq -cn for safe JSON construction with proper escaping when
  available, with printf + json_escape() fallback
- Replace declare -A (bash 4+) with indexed array for bash 3.2
  compatibility (macOS default)
- Use inline command -v jq check in create-new-feature.sh since it
  does not source common.sh
- Guard trap cleanup against re-entrant invocation by disarming traps
  at entry
- Use printf '%q' for shell-escaped branch names in user-facing output
- Return failure instead of silently returning wrong path on ambiguous
  spec directory matches
- Deduplicate agent file updates via realpath to prevent multiple writes
  to the same file (e.g. AGENTS.md aliased by multiple variables)
This commit is contained in:
Pierluigi Lenoci
2026-03-13 16:47:17 +01:00
committed by GitHub
parent 017e1c4c2f
commit 46bc65b1ce
5 changed files with 178 additions and 149 deletions

View File

@@ -79,15 +79,28 @@ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh" source "$SCRIPT_DIR/common.sh"
# Get feature paths and validate branch # Get feature paths and validate branch
eval $(get_feature_paths) _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
eval "$_paths_output"
unset _paths_output
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
# If paths-only mode, output paths and exit (support JSON + paths-only combined) # If paths-only mode, output paths and exit (support JSON + paths-only combined)
if $PATHS_ONLY; then if $PATHS_ONLY; then
if $JSON_MODE; then if $JSON_MODE; then
# Minimal JSON paths payload (no validation performed) # Minimal JSON paths payload (no validation performed)
if has_jq; then
jq -cn \
--arg repo_root "$REPO_ROOT" \
--arg branch "$CURRENT_BRANCH" \
--arg feature_dir "$FEATURE_DIR" \
--arg feature_spec "$FEATURE_SPEC" \
--arg impl_plan "$IMPL_PLAN" \
--arg tasks "$TASKS" \
'{REPO_ROOT:$repo_root,BRANCH:$branch,FEATURE_DIR:$feature_dir,FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,TASKS:$tasks}'
else
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
"$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" "$(json_escape "$REPO_ROOT")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$TASKS")"
fi
else else
echo "REPO_ROOT: $REPO_ROOT" echo "REPO_ROOT: $REPO_ROOT"
echo "BRANCH: $CURRENT_BRANCH" echo "BRANCH: $CURRENT_BRANCH"
@@ -141,14 +154,25 @@ fi
# Output results # Output results
if $JSON_MODE; then if $JSON_MODE; then
# Build JSON array of documents # Build JSON array of documents
if has_jq; then
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
fi
jq -cn \
--arg feature_dir "$FEATURE_DIR" \
--argjson docs "$json_docs" \
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs}'
else
if [[ ${#docs[@]} -eq 0 ]]; then if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]" json_docs="[]"
else else
json_docs=$(printf '"%s",' "${docs[@]}") json_docs=$(printf '"%s",' "${docs[@]}")
json_docs="[${json_docs%,}]" json_docs="[${json_docs%,}]"
fi fi
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs"
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" fi
else else
# Text output # Text output
echo "FEATURE_DIR:$FEATURE_DIR" echo "FEATURE_DIR:$FEATURE_DIR"

View File

@@ -120,7 +120,7 @@ find_feature_dir_by_prefix() {
# Multiple matches - this shouldn't happen with proper naming convention # Multiple matches - this shouldn't happen with proper naming convention
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2 echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
echo "Please ensure only one spec directory exists per numeric prefix." >&2 echo "Please ensure only one spec directory exists per numeric prefix." >&2
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script return 1
fi fi
} }
@@ -134,21 +134,42 @@ get_feature_paths() {
fi fi
# Use prefix-based lookup to support multiple branches per spec # Use prefix-based lookup to support multiple branches per spec
local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch") local feature_dir
if ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then
echo "ERROR: Failed to resolve feature directory" >&2
return 1
fi
cat <<EOF # Use printf '%q' to safely quote values, preventing shell injection
REPO_ROOT='$repo_root' # via crafted branch names or paths containing special characters
CURRENT_BRANCH='$current_branch' printf 'REPO_ROOT=%q\n' "$repo_root"
HAS_GIT='$has_git_repo' printf 'CURRENT_BRANCH=%q\n' "$current_branch"
FEATURE_DIR='$feature_dir' printf 'HAS_GIT=%q\n' "$has_git_repo"
FEATURE_SPEC='$feature_dir/spec.md' printf 'FEATURE_DIR=%q\n' "$feature_dir"
IMPL_PLAN='$feature_dir/plan.md' printf 'FEATURE_SPEC=%q\n' "$feature_dir/spec.md"
TASKS='$feature_dir/tasks.md' printf 'IMPL_PLAN=%q\n' "$feature_dir/plan.md"
RESEARCH='$feature_dir/research.md' printf 'TASKS=%q\n' "$feature_dir/tasks.md"
DATA_MODEL='$feature_dir/data-model.md' printf 'RESEARCH=%q\n' "$feature_dir/research.md"
QUICKSTART='$feature_dir/quickstart.md' printf 'DATA_MODEL=%q\n' "$feature_dir/data-model.md"
CONTRACTS_DIR='$feature_dir/contracts' printf 'QUICKSTART=%q\n' "$feature_dir/quickstart.md"
EOF printf 'CONTRACTS_DIR=%q\n' "$feature_dir/contracts"
}
# Check if jq is available for safe JSON construction
has_jq() {
command -v jq >/dev/null 2>&1
}
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
# Handles backslash, double-quote, and control characters (newline, tab, carriage return).
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/\\r}"
printf '%s' "$s"
} }
check_file() { [[ -f "$1" ]] && echo "$2" || echo "$2"; } check_file() { [[ -f "$1" ]] && echo "$2" || echo "$2"; }

View File

@@ -162,6 +162,17 @@ clean_branch_name() {
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
} }
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/\\r}"
printf '%s' "$s"
}
# Resolve repository root. Prefer git information when available, but fall back # Resolve repository root. Prefer git information when available, but fall back
# to searching for repository markers so the workflow still functions in repositories that # to searching for repository markers so the workflow still functions in repositories that
# were initialised with --no-git. # were initialised with --no-git.
@@ -300,14 +311,22 @@ TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
SPEC_FILE="$FEATURE_DIR/spec.md" SPEC_FILE="$FEATURE_DIR/spec.md"
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
# Set the SPECIFY_FEATURE environment variable for the current session # Inform the user how to persist the feature variable in their own shell
export SPECIFY_FEATURE="$BRANCH_NAME" printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
if $JSON_MODE; then if $JSON_MODE; then
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" if command -v jq >/dev/null 2>&1; then
jq -cn \
--arg branch_name "$BRANCH_NAME" \
--arg spec_file "$SPEC_FILE" \
--arg feature_num "$FEATURE_NUM" \
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
else
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
fi
else else
echo "BRANCH_NAME: $BRANCH_NAME" echo "BRANCH_NAME: $BRANCH_NAME"
echo "SPEC_FILE: $SPEC_FILE" echo "SPEC_FILE: $SPEC_FILE"
echo "FEATURE_NUM: $FEATURE_NUM" echo "FEATURE_NUM: $FEATURE_NUM"
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME"
fi fi

View File

@@ -28,7 +28,9 @@ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh" source "$SCRIPT_DIR/common.sh"
# Get all paths and variables from common functions # Get all paths and variables from common functions
eval $(get_feature_paths) _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
eval "$_paths_output"
unset _paths_output
# Check if we're on a proper feature branch (only for git repos) # Check if we're on a proper feature branch (only for git repos)
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
@@ -49,8 +51,18 @@ fi
# Output results # Output results
if $JSON_MODE; then if $JSON_MODE; then
if has_jq; then
jq -cn \
--arg feature_spec "$FEATURE_SPEC" \
--arg impl_plan "$IMPL_PLAN" \
--arg specs_dir "$FEATURE_DIR" \
--arg branch "$CURRENT_BRANCH" \
--arg has_git "$HAS_GIT" \
'{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch,HAS_GIT:$has_git}'
else
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$HAS_GIT")"
fi
else else
echo "FEATURE_SPEC: $FEATURE_SPEC" echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN" echo "IMPL_PLAN: $IMPL_PLAN"

View File

@@ -53,7 +53,9 @@ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh" source "$SCRIPT_DIR/common.sh"
# Get all paths and variables from common functions # Get all paths and variables from common functions
eval $(get_feature_paths) _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
eval "$_paths_output"
unset _paths_output
NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code
AGENT_TYPE="${1:-}" AGENT_TYPE="${1:-}"
@@ -71,12 +73,14 @@ AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
QODER_FILE="$REPO_ROOT/QODER.md" QODER_FILE="$REPO_ROOT/QODER.md"
AMP_FILE="$REPO_ROOT/AGENTS.md" # AMP, Kiro CLI, and IBM Bob 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" SHAI_FILE="$REPO_ROOT/SHAI.md"
TABNINE_FILE="$REPO_ROOT/TABNINE.md" TABNINE_FILE="$REPO_ROOT/TABNINE.md"
KIRO_FILE="$REPO_ROOT/AGENTS.md" KIRO_FILE="$AGENTS_FILE"
AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md" AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
BOB_FILE="$REPO_ROOT/AGENTS.md" BOB_FILE="$AGENTS_FILE"
VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md" VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md"
KIMI_FILE="$REPO_ROOT/KIMI.md" KIMI_FILE="$REPO_ROOT/KIMI.md"
@@ -112,6 +116,8 @@ log_warning() {
# Cleanup function for temporary files # Cleanup function for temporary files
cleanup() { cleanup() {
local exit_code=$? local exit_code=$?
# Disarm traps to prevent re-entrant loop
trap - EXIT INT TERM
rm -f /tmp/agent_update_*_$$ rm -f /tmp/agent_update_*_$$
rm -f /tmp/manual_additions_$$ rm -f /tmp/manual_additions_$$
exit $exit_code exit $exit_code
@@ -607,67 +613,67 @@ update_specific_agent() {
case "$agent_type" in case "$agent_type" in
claude) claude)
update_agent_file "$CLAUDE_FILE" "Claude Code" update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
;; ;;
gemini) gemini)
update_agent_file "$GEMINI_FILE" "Gemini CLI" update_agent_file "$GEMINI_FILE" "Gemini CLI" || return 1
;; ;;
copilot) copilot)
update_agent_file "$COPILOT_FILE" "GitHub Copilot" update_agent_file "$COPILOT_FILE" "GitHub Copilot" || return 1
;; ;;
cursor-agent) cursor-agent)
update_agent_file "$CURSOR_FILE" "Cursor IDE" update_agent_file "$CURSOR_FILE" "Cursor IDE" || return 1
;; ;;
qwen) qwen)
update_agent_file "$QWEN_FILE" "Qwen Code" update_agent_file "$QWEN_FILE" "Qwen Code" || return 1
;; ;;
opencode) opencode)
update_agent_file "$AGENTS_FILE" "opencode" update_agent_file "$AGENTS_FILE" "opencode" || return 1
;; ;;
codex) codex)
update_agent_file "$AGENTS_FILE" "Codex CLI" update_agent_file "$AGENTS_FILE" "Codex CLI" || return 1
;; ;;
windsurf) windsurf)
update_agent_file "$WINDSURF_FILE" "Windsurf" update_agent_file "$WINDSURF_FILE" "Windsurf" || return 1
;; ;;
kilocode) kilocode)
update_agent_file "$KILOCODE_FILE" "Kilo Code" update_agent_file "$KILOCODE_FILE" "Kilo Code" || return 1
;; ;;
auggie) auggie)
update_agent_file "$AUGGIE_FILE" "Auggie CLI" update_agent_file "$AUGGIE_FILE" "Auggie CLI" || return 1
;; ;;
roo) roo)
update_agent_file "$ROO_FILE" "Roo Code" update_agent_file "$ROO_FILE" "Roo Code" || return 1
;; ;;
codebuddy) codebuddy)
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" || return 1
;; ;;
qodercli) qodercli)
update_agent_file "$QODER_FILE" "Qoder CLI" update_agent_file "$QODER_FILE" "Qoder CLI" || return 1
;; ;;
amp) amp)
update_agent_file "$AMP_FILE" "Amp" update_agent_file "$AMP_FILE" "Amp" || return 1
;; ;;
shai) shai)
update_agent_file "$SHAI_FILE" "SHAI" update_agent_file "$SHAI_FILE" "SHAI" || return 1
;; ;;
tabnine) tabnine)
update_agent_file "$TABNINE_FILE" "Tabnine CLI" update_agent_file "$TABNINE_FILE" "Tabnine CLI" || return 1
;; ;;
kiro-cli) kiro-cli)
update_agent_file "$KIRO_FILE" "Kiro CLI" update_agent_file "$KIRO_FILE" "Kiro CLI" || return 1
;; ;;
agy) agy)
update_agent_file "$AGY_FILE" "Antigravity" update_agent_file "$AGY_FILE" "Antigravity" || return 1
;; ;;
bob) bob)
update_agent_file "$BOB_FILE" "IBM Bob" update_agent_file "$BOB_FILE" "IBM Bob" || return 1
;; ;;
vibe) vibe)
update_agent_file "$VIBE_FILE" "Mistral Vibe" update_agent_file "$VIBE_FILE" "Mistral Vibe" || return 1
;; ;;
kimi) kimi)
update_agent_file "$KIMI_FILE" "Kimi Code" update_agent_file "$KIMI_FILE" "Kimi Code" || return 1
;; ;;
generic) generic)
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent." log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
@@ -682,106 +688,53 @@ update_specific_agent() {
update_all_existing_agents() { update_all_existing_agents() {
local found_agent=false local found_agent=false
local _updated_paths=()
# Check each possible agent file and update if it exists # Helper: skip non-existent files and files already updated (dedup by
if [[ -f "$CLAUDE_FILE" ]]; then # realpath so that variables pointing to the same file — e.g. AMP_FILE,
update_agent_file "$CLAUDE_FILE" "Claude Code" # KIRO_FILE, BOB_FILE all resolving to AGENTS_FILE — are only written once).
found_agent=true # Uses a linear array instead of associative array for bash 3.2 compatibility.
update_if_new() {
local file="$1" name="$2"
[[ -f "$file" ]] || return 0
local real_path
real_path=$(realpath "$file" 2>/dev/null || echo "$file")
local p
if [[ ${#_updated_paths[@]} -gt 0 ]]; then
for p in "${_updated_paths[@]}"; do
[[ "$p" == "$real_path" ]] && return 0
done
fi fi
update_agent_file "$file" "$name" || return 1
_updated_paths+=("$real_path")
found_agent=true
}
if [[ -f "$GEMINI_FILE" ]]; then update_if_new "$CLAUDE_FILE" "Claude Code"
update_agent_file "$GEMINI_FILE" "Gemini CLI" update_if_new "$GEMINI_FILE" "Gemini CLI"
found_agent=true update_if_new "$COPILOT_FILE" "GitHub Copilot"
fi update_if_new "$CURSOR_FILE" "Cursor IDE"
update_if_new "$QWEN_FILE" "Qwen Code"
if [[ -f "$COPILOT_FILE" ]]; then update_if_new "$AGENTS_FILE" "Codex/opencode"
update_agent_file "$COPILOT_FILE" "GitHub Copilot" update_if_new "$AMP_FILE" "Amp"
found_agent=true update_if_new "$KIRO_FILE" "Kiro CLI"
fi update_if_new "$BOB_FILE" "IBM Bob"
update_if_new "$WINDSURF_FILE" "Windsurf"
if [[ -f "$CURSOR_FILE" ]]; then update_if_new "$KILOCODE_FILE" "Kilo Code"
update_agent_file "$CURSOR_FILE" "Cursor IDE" update_if_new "$AUGGIE_FILE" "Auggie CLI"
found_agent=true update_if_new "$ROO_FILE" "Roo Code"
fi update_if_new "$CODEBUDDY_FILE" "CodeBuddy CLI"
update_if_new "$SHAI_FILE" "SHAI"
if [[ -f "$QWEN_FILE" ]]; then update_if_new "$TABNINE_FILE" "Tabnine CLI"
update_agent_file "$QWEN_FILE" "Qwen Code" update_if_new "$QODER_FILE" "Qoder CLI"
found_agent=true update_if_new "$AGY_FILE" "Antigravity"
fi update_if_new "$VIBE_FILE" "Mistral Vibe"
update_if_new "$KIMI_FILE" "Kimi Code"
if [[ -f "$AGENTS_FILE" ]]; then
update_agent_file "$AGENTS_FILE" "Codex/opencode"
found_agent=true
fi
if [[ -f "$WINDSURF_FILE" ]]; then
update_agent_file "$WINDSURF_FILE" "Windsurf"
found_agent=true
fi
if [[ -f "$KILOCODE_FILE" ]]; then
update_agent_file "$KILOCODE_FILE" "Kilo Code"
found_agent=true
fi
if [[ -f "$AUGGIE_FILE" ]]; then
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
found_agent=true
fi
if [[ -f "$ROO_FILE" ]]; then
update_agent_file "$ROO_FILE" "Roo Code"
found_agent=true
fi
if [[ -f "$CODEBUDDY_FILE" ]]; then
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
found_agent=true
fi
if [[ -f "$SHAI_FILE" ]]; then
update_agent_file "$SHAI_FILE" "SHAI"
found_agent=true
fi
if [[ -f "$TABNINE_FILE" ]]; then
update_agent_file "$TABNINE_FILE" "Tabnine CLI"
found_agent=true
fi
if [[ -f "$QODER_FILE" ]]; then
update_agent_file "$QODER_FILE" "Qoder CLI"
found_agent=true
fi
if [[ -f "$KIRO_FILE" ]]; then
update_agent_file "$KIRO_FILE" "Kiro CLI"
found_agent=true
fi
if [[ -f "$AGY_FILE" ]]; then
update_agent_file "$AGY_FILE" "Antigravity"
found_agent=true
fi
if [[ -f "$BOB_FILE" ]]; then
update_agent_file "$BOB_FILE" "IBM Bob"
found_agent=true
fi
if [[ -f "$VIBE_FILE" ]]; then
update_agent_file "$VIBE_FILE" "Mistral Vibe"
found_agent=true
fi
if [[ -f "$KIMI_FILE" ]]; then
update_agent_file "$KIMI_FILE" "Kimi Code"
found_agent=true
fi
# If no agent files exist, create a default Claude file # If no agent files exist, create a default Claude file
if [[ "$found_agent" == false ]]; then if [[ "$found_agent" == false ]]; then
log_info "No existing agent files found, creating default Claude file..." log_info "No existing agent files found, creating default Claude file..."
update_agent_file "$CLAUDE_FILE" "Claude Code" update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
fi fi
} }
print_summary() { print_summary() {