diff --git a/scripts/install.sh b/scripts/install.sh index 1ff20c47..a3c13dc6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -176,6 +176,19 @@ division_files() { # division_count — number of agents in a division. division_count() { division_files "$1" | grep -c . ; } +# agent_slug_exists — verify a requested agent against the source roster. +# Selection filters should fail before installation when they name nothing that +# can be installed; otherwise dry-run counts and completion messages lie. +agent_slug_exists() { + local target="$1" div f + for div in "${ALL_DIVISIONS[@]}"; do + while IFS= read -r f; do + [[ "$(agent_slug "$f")" == "$target" ]] && return 0 + done < <(division_files "$div") + done + return 1 +} + # build_selection — compute the allowed slug set from --division/--agent/--agents-file. # With no filter flags, SELECTION_ACTIVE stays false (install everything). build_selection() { @@ -184,20 +197,32 @@ build_selection() { return fi SELECTION_ACTIVE=true - local slugs="" div f s line + local slugs="" div f s line requested for div in ${FILTER_DIVISIONS[@]+"${FILTER_DIVISIONS[@]}"}; do while IFS= read -r f; do s="$(agent_slug "$f")"; [[ -n "$s" ]] && slugs+="$s"$'\n' done < <(division_files "$div") done - for s in ${FILTER_AGENTS[@]+"${FILTER_AGENTS[@]}"}; do slugs+="$(slugify "$s")"$'\n'; done + for s in ${FILTER_AGENTS[@]+"${FILTER_AGENTS[@]}"}; do + requested="$(slugify "$s")" + if ! agent_slug_exists "$requested"; then + err "Unknown agent '$s'. Use --list agents to see the available roster." + exit 1 + fi + slugs+="$requested"$'\n' + done if [[ -n "$AGENTS_FILE" ]]; then [[ -f "$AGENTS_FILE" ]] || { err "agents-file not found: $AGENTS_FILE"; exit 1; } while IFS= read -r line || [[ -n "$line" ]]; do line="${line%%#*}" # strip trailing comment line="$(printf '%s' "$line" | xargs 2>/dev/null)" # trim [[ -z "$line" ]] && continue - slugs+="$(slugify "$line")"$'\n' + requested="$(slugify "$line")" + if ! agent_slug_exists "$requested"; then + err "Unknown agent '$line' in agents-file '$AGENTS_FILE'." + exit 1 + fi + slugs+="$requested"$'\n' done < "$AGENTS_FILE" fi _ALLOWED_SLUGS="$(printf '%s' "$slugs" | sort -u | sed '/^$/d')" diff --git a/scripts/test-agent-selection.sh b/scripts/test-agent-selection.sh new file mode 100755 index 00000000..5ce0561c --- /dev/null +++ b/scripts/test-agent-selection.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Regression coverage for install.sh agent-selection validation. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALLER="$SCRIPT_DIR/install.sh" +AGENTS_FILE="$(mktemp "${TMPDIR:-/tmp}/agency-agent-selection.XXXXXX")" +trap 'rm -f "$AGENTS_FILE"' EXIT + +set +e +output="$($INSTALLER --tool claude-code --agent definitely-not-an-agent --dry-run 2>&1)" +status=$? +set -e +[[ "$status" -ne 0 ]] || { + printf 'Unknown --agent selection unexpectedly succeeded:\n%s\n' "$output" >&2 + exit 1 +} +[[ "$output" == *"Unknown agent"* ]] || { + printf 'Unknown --agent selection did not explain the error:\n%s\n' "$output" >&2 + exit 1 +} + +printf '%s\n' 'definitely-not-an-agent' > "$AGENTS_FILE" +set +e +output="$($INSTALLER --tool claude-code --agents-file "$AGENTS_FILE" --dry-run 2>&1)" +status=$? +set -e +[[ "$status" -ne 0 ]] || { + printf 'Unknown agents-file entry unexpectedly succeeded:\n%s\n' "$output" >&2 + exit 1 +} +[[ "$output" == *"in agents-file"* ]] || { + printf 'Unknown agents-file entry did not identify its source:\n%s\n' "$output" >&2 + exit 1 +} + +output="$($INSTALLER --tool claude-code --agent 'Developer Tooling Engineer' --dry-run 2>&1)" +[[ "$output" == *"Agents: 1"* ]] || { + printf 'Valid display-name selection did not resolve to one agent:\n%s\n' "$output" >&2 + exit 1 +} + +echo "PASS: install.sh rejects unknown agent selections and accepts display names"