diff --git a/commands/santa-loop.md b/commands/santa-loop.md index 111087966..fcc749bdd 100644 --- a/commands/santa-loop.md +++ b/commands/santa-loop.md @@ -70,38 +70,71 @@ Launch an Agent (subagent_type: `code-reviewer`, model: `opus`) with the full ru - "You are an independent quality reviewer. You have NOT seen any other review. Your job is to find problems, not to approve." - Return the structured JSON verdict above -#### Reviewer B: External Model (Claude fallback only if no external CLI installed) +#### Reviewer B: External Model (Claude fallback if no external reviewer is ready) -First, detect which CLIs are available: -```bash -command -v codex >/dev/null 2>&1 && echo "codex" || true -command -v gemini >/dev/null 2>&1 && echo "gemini" || true -``` +Antigravity is optional and requires the external `ccg-workflow` runtime; it is not included in the base ECC install. An existing installation must provide both an executable `~/.claude/bin/codeagent-wrapper` and a readable regular reviewer role file at `~/.claude/.ccg/prompts/antigravity/reviewer.md`. Otherwise, retain the Claude fallback. Use the wrapper's maintained model-selection contract; do not add an Antigravity model pin. + +Run detection, prompt creation, and the selected external review together in this single Bash subshell. Replace the prompt placeholder with the same rubric, file contents, and review-only instructions given to Reviewer A. The order remains Codex, Gemini, Antigravity, then Claude. The Claude fallback creates no prompt file. -Build the reviewer prompt (identical rubric + instructions as Reviewer A) and write it to a unique temp file: ```bash -PROMPT_FILE=$(mktemp /tmp/santa-reviewer-b-XXXXXX.txt) -cat > "$PROMPT_FILE" << 'EOF' +( + set -e + set -o pipefail + + REVIEWER_WRAPPER="$HOME/.claude/bin/codeagent-wrapper" + REVIEWER_ROLE="$HOME/.claude/.ccg/prompts/antigravity/reviewer.md" + if command -v codex >/dev/null 2>&1; then + REVIEWER_BACKEND=codex + elif command -v gemini >/dev/null 2>&1; then + REVIEWER_BACKEND=gemini + elif [ -x "$REVIEWER_WRAPPER" ] && [ -f "$REVIEWER_ROLE" ] && [ -r "$REVIEWER_ROLE" ]; then + REVIEWER_BACKEND=antigravity + else + printf '%s\n' 'CLAUDE_FALLBACK' + exit 0 + fi + + cleanup_reviewer_prompt() { + reviewer_status=$? + trap - EXIT + if ! rm -f -- "$PROMPT_FILE"; then + printf '%s\n' 'Could not remove reviewer prompt; remove the private temp file before continuing.' >&2 + if [ "$reviewer_status" -eq 0 ]; then reviewer_status=1; fi + fi + exit "$reviewer_status" + } + + umask 077 + PROMPT_FILE=$(mktemp "${TMPDIR:-/tmp}/santa-reviewer-b.XXXXXX") + trap cleanup_reviewer_prompt EXIT + trap 'exit 129' HUP + trap 'exit 130' INT + trap 'exit 143' TERM + cat > "$PROMPT_FILE" << 'EOF' ... full rubric + file contents + reviewer instructions ... EOF + + case "$REVIEWER_BACKEND" in + codex) + codex exec --sandbox read-only -m gpt-5.4 -C "$(pwd)" - < "$PROMPT_FILE" + ;; + gemini) + REVIEWER_PROMPT=$(cat "$PROMPT_FILE") + gemini -p "$REVIEWER_PROMPT" -m gemini-2.5-pro + ;; + antigravity) + { + printf 'ROLE_FILE: %s\n' "$REVIEWER_ROLE" + cat "$PROMPT_FILE" + } | "$REVIEWER_WRAPPER" --backend antigravity - "$PWD" + ;; + esac +) ``` -Use the first available CLI: +The subshell removes its prompt on success, backend or prompt-write failure, and handled HUP/INT/TERM signals, preserving the original failure status. A cleanup-only failure also returns nonzero. Forced termination such as SIGKILL cannot run cleanup; inspect private temp files after an interrupted process. A failed external invocation is not an approval and must not silently become a fallback or proceed to the verdict gate. -**Codex CLI** (if installed) -```bash -codex exec --sandbox read-only -m gpt-5.4 -C "$(pwd)" - < "$PROMPT_FILE" -rm -f "$PROMPT_FILE" -``` - -**Gemini CLI** (if installed and codex is not) -```bash -gemini -p "$(cat "$PROMPT_FILE")" -m gemini-2.5-pro -rm -f "$PROMPT_FILE" -``` - -**Claude Agent fallback** (only if neither `codex` nor `gemini` is installed) -Launch a second Claude Agent (subagent_type: `code-reviewer`, model: `opus`). Log a warning that both reviewers share the same model family — true model diversity was not achieved but context isolation is still enforced. +**Claude Agent fallback:** If the subshell prints `CLAUDE_FALLBACK`, launch a second Claude Agent (subagent_type: `code-reviewer`, model: `opus`) with the rubric and file contents directly. The marker is a dispatch instruction, not a review verdict. Log that both reviewers share the same model family; keep their contexts separate. In all cases, the reviewer must return the same structured JSON verdict as Reviewer A. @@ -166,9 +199,9 @@ Result: [PUSHED / ESCALATED TO USER] ## Notes - Reviewer A (Claude Opus) always runs — guarantees at least one strong reviewer regardless of tooling. -- Model diversity is the goal for Reviewer B. GPT-5.4 or Gemini 2.5 Pro gives true independence — different training data, different biases, different blind spots. The Claude-only fallback still provides value via context isolation but loses model diversity. -- Strongest available models are used: Opus for Reviewer A, GPT-5.4 or Gemini 2.5 Pro for Reviewer B. -- External reviewers run with `--sandbox read-only` (Codex) to prevent repo mutation during review. +- Model diversity is the goal for Reviewer B. Record the actual model/provider reported by the selected backend; the Antigravity wrapper name alone does not establish a different model family. The Claude-only fallback provides context isolation but loses model diversity. +- Use each backend's maintained model-selection contract. Do not pin a transient Antigravity model ID in this workflow. +- Request review-only output from every reviewer. Codex uses `--sandbox read-only`; Gemini and Antigravity permissions depend on their installed runtime configuration. The CCG wrapper invocation alone does not guarantee a read-only sandbox. - Fresh reviewers each round prevents anchoring bias from prior findings. - The rubric is the most important input. Tighten it if reviewers rubber-stamp or flag subjective style issues. - Commits happen on NAUGHTY rounds so fixes are preserved even if the loop is interrupted.