review-loop

Cross-LLM iterative code review loop. Spawns a peer reviewer (Codex or Gemini CLI) to review code changes, then iterates until both agents agree on the final code state. Code gets modified during the loop — the final output is improved code + consensus report. Use when: "review loop", "peer review", "cross review", "review with codex", "review with gemini", "让 codex review", "交叉 review", "peer review 这段代码", "code review loop", "iterative review"

Review Loop — Cross-LLM Iterative Code Review

Spawns a peer reviewer (Codex or Gemini) to independently review your code changes. Claude evaluates findings, implements accepted fixes, and re-submits for peer re-review. Iterates until both agents agree on the final code state.

Key: You (the human) do NOT need to participate. Watch progress via .review-loop/<session>/rounds.json and summary.md.

Prerequisites

  • Required: git CLI
  • Peer (one of): codex CLI (codex --version) or gemini CLI (gemini --version)
  • Optional: gh CLI (for PR scope detection)

Configuration

Defaults

SettingDefaultOptions
peer_reviewercodexcodex, gemini
max_rounds51–10
timeout_per_round600seconds
scope_preferenceautoauto, diff, branch, pr

The peer reviewer always runs with full local repository access and the loop always implements accepted fixes. There is no read-only mode.

Override via project config

Create .review-loop/config.json in the project root to override defaults:

{
  "peer_reviewer": "gemini",
  "max_rounds": 8
}

Override via invocation

User can specify at invocation time: "review loop with gemini, max 3 rounds". Invocation overrides take highest precedence.


Phase 0 + 1: Preflight & Context Collection (Single Execution)

IMPORTANT: Run preflight.sh in a SINGLE bash call. This eliminates ~15 sequential tool calls.

# Locate the skill's scripts directory (works for plugin installs and local clones)
SKILL_DIR=""
for candidate in \
  "$(find ~/.claude/plugins/cache -path "*/review-loop/scripts/preflight.sh" 2>/dev/null | head -1 | xargs dirname 2>/dev/null | xargs dirname 2>/dev/null)" \
  "$(find ~/.claude/skills -path "*/review-loop/scripts/preflight.sh" 2>/dev/null | head -1 | xargs dirname 2>/dev/null | xargs dirname 2>/dev/null)"; do
  [[ -d "$candidate" ]] && SKILL_DIR="$candidate" && break
done
if [[ -z "$SKILL_DIR" ]]; then
  echo "Error: review-loop skill directory not found. Ensure the plugin is installed." >&2
  exit 1
fi
PREFLIGHT_OUTPUT="$($SKILL_DIR/scripts/preflight.sh \
  --peer {peer_reviewer} \
  --max-rounds {max_rounds} \
  --timeout {timeout_per_round} \
  --scope {scope_preference})"
# For a specific commit: add --commit-sha <SHA>

Pass user invocation-time overrides as CLI args — they take highest precedence.

Precedence: built-in defaults < .review-loop/config.json < CLI args

preflight.sh does ALL of the following in one shot:

  1. Reads .review-loop/config.json (merges over defaults)
  2. Applies CLI args (highest precedence — invocation overrides)
  3. Checks peer CLI availability (falls back to alternative)
  4. Detects base branch and repo root
  5. Auto-detects review scope (local-diff → branch-commits → PR), or uses --commit-sha for a specific commit
  6. Creates session directory with timestamp
  7. Auto-adds .review-loop/ to .gitignore
  8. Initializes rounds.json
  9. Collects the priority file list for the peer to inspect locally
  10. Creates checkpoint commit
  11. Collects project context (CLAUDE.md / package.json / README)

Parse preflight output

The script outputs key-value pairs. Extract:

  • SESSION_DIR, SESSION_ID, PEER, SCOPE, BASE_BRANCH, REPO_ROOT, etc.
  • TARGET_FILES_B64_START...TARGET_FILES_B64_END — base64-encoded newline-separated file list
  • PROJECT_B64_START...PROJECT_B64_END — base64-encoded project context

Decode with: echo "$TARGET_FILES_B64" | base64 --decode

If the script exits non-zero, stop and report the error.

Log and proceed

Print to user:

Review Loop starting: {scope} ({detail}) → peer: {peer}, max: {max_rounds} rounds

Build the initial review prompt

Use Template 1 from prompt-templates.md as a stable prompt contract. Do NOT rewrite the prompt body each run. Only fill the small runtime fields:

  • repo_root
  • scope_type / scope_detail
  • target_files
  • a short project_description
  • a compact project_context snippet when needed

Do NOT embed the full diff. Do NOT paste large sections of CLAUDE.md or README into the prompt. Round 1 should be a static template plus a lightweight runtime brief so prompt assembly stays cheap and consistent. Write the completed prompt to:

PROMPT_FILE="$SESSION_DIR/peer-output/round-1-prompt.md"

Step 1.3: Invoke peer reviewer

Determine the path to peer-invoke.sh. It is located relative to the skill's installed directory, NOT the project being reviewed:

# The skill's base directory is provided by Claude Code at invocation time.
# Look for it in the plugin cache or fall back to common install paths.
PEER_SCRIPT=""
for candidate in \
  "$SKILL_BASE_DIR/scripts/peer-invoke.sh" \
  "$(find ~/.claude/plugins/cache -path "*/review-loop/scripts/peer-invoke.sh" 2>/dev/null | head -1)" \
  "$(find ~/.claude/skills -path "*/review-loop/scripts/peer-invoke.sh" 2>/dev/null | head -1)"; do
  [[ -x "$candidate" ]] && PEER_SCRIPT="$candidate" && break
done

if [[ -z "$PEER_SCRIPT" ]]; then
  echo "Error: peer-invoke.sh not found. Ensure the review-loop skill is properly installed." >&2
  exit 1
fi

Note: $SKILL_BASE_DIR is set by Claude Code from the skill's metadata. The fallback searches the plugin cache and skills directories.

peer-invoke.sh runs Codex in the current repository directory with full access so it can read local files directly. For Codex, it also launches against an isolated temporary CODEX_HOME with no MCP servers, strips inherited CODEX_API_KEY by default, and records the peer session id for reuse in later rounds.

Invoke:

$PEER_SCRIPT \
  --peer {peer_reviewer} \
  --prompt-file "$PROMPT_FILE" \
  --output-file "$SESSION_DIR/peer-output/round-1-raw.txt" \
  --session-id-file "$SESSION_DIR/peer-output/peer-session-id.txt" \
  --timeout {timeout_per_round}

Step 1.4: Parse peer output

Read round-1-raw.txt. Parse for:

  • FINDING: fN blocks → extract into structured findings
  • NO_FINDINGS: → immediate consensus (skip to Phase 3)

Step 1.5: Update rounds.json

Add Round 1 data with all peer_findings. Set claude_actions to empty (not yet evaluated).


Phase 2: Code Evolution Loop

For each round N (starting from Round 1's findings):

Step 2.1: Evaluate findings

For each peer finding, apply the evaluation criteria from synthesis-protocol.md:

  • ACCEPT: The finding is valid and actionable
  • REJECT: The finding is a false positive or conflicts with project conventions

Record each decision with reasoning in claude_actions.

Step 2.2: Implement accepted fixes

For each accepted finding:

  1. Read the relevant file
  2. Make the minimal code change to address the finding
  3. Record the change in claude_actions[].code_changes

Step 2.3: Checkpoint commit

git add -A && git commit -m "review-loop: changes from round {N}" --allow-empty

The --allow-empty flag ensures rounds where Claude only rejects findings (no code changes) don't fail.

Step 2.4: Update rounds.json

Update the current round's claude_actions with all decisions and changes.

Step 2.5: Convergence check

Check if all findings are resolved:

  • All findings ACCEPTED and fixed → peer needs to confirm fixes are correct
  • Some findings REJECTED → peer needs to evaluate rejections
  • If this is a re-review round and peer said CONSENSUS: → go to Phase 3

If round >= max_rounds:

  • Mark remaining unresolved findings as escalated
  • Go to Phase 3 with status max_rounds

Step 2.6: Build re-review prompt

Use Template 2 from prompt-templates.md:

  • Keep the template body fixed
  • Include only the files Claude changed this round via git diff --name-only HEAD~1 HEAD
  • Include rejected findings with Claude's reasoning
  • Include a short summary of accepted/fixed findings

Do NOT paste the diff body into the prompt. Re-review should happen against the current local repository state.

Write to $SESSION_DIR/peer-output/round-{N+1}-prompt.md.

Step 2.7: Invoke peer for re-review

if [[ -f "$SESSION_DIR/peer-output/peer-session-id.txt" ]]; then
  PEER_RESUME_ARGS=(--resume-session "$(cat "$SESSION_DIR/peer-output/peer-session-id.txt")")
else
  PEER_RESUME_ARGS=()
fi

$PEER_SCRIPT \
  --peer {peer_reviewer} \
  "${PEER_RESUME_ARGS[@]}" \
  --prompt-file "$SESSION_DIR/peer-output/round-{N+1}-prompt.md" \
  --output-file "$SESSION_DIR/peer-output/round-{N+1}-raw.txt" \
  --session-id-file "$SESSION_DIR/peer-output/peer-session-id.txt" \
  --timeout {timeout_per_round}

Reuse the same Codex session for re-review rounds when available. This avoids repeated cold starts, preserves the peer's review context, and materially reduces round-trip latency. Do NOT reuse that session for the final approval pass in Phase 3.

Step 2.8: Parse re-review output

Look for:

  • CONSENSUS: → all resolved, go to Phase 3
  • ACCEPTED_REJECTION: fN → finding resolved, mark in rounds.json
  • INSIST: fN → peer insists, Claude must re-evaluate
  • New FINDING: fN → new issues found in Claude's changes

Step 2.9: Handle INSIST findings

For each INSIST:

  1. Count how many rounds this finding has been debated
  2. If debated < 2 rounds → Claude re-evaluates with peer's stronger argument
  3. If debated >= 2 rounds → Mark as ESCALATED

Then loop back to Step 2.1 with the updated findings list.


Phase 3: Final Consensus + Report

Step 3.1: Build final consensus prompt

Use Template 3 from prompt-templates.md. Keep the template body fixed and fill only:

  • repo_root
  • final_target_files
  • resolution_table_rows

This is the quality gate. Always run it in a fresh peer session, even if earlier re-review rounds reused the same Codex session.

Write to $SESSION_DIR/peer-output/final-consensus-prompt.md.

Step 3.2: Invoke peer for final consensus in a fresh session

$PEER_SCRIPT \
  --peer {peer_reviewer} \
  --prompt-file "$SESSION_DIR/peer-output/final-consensus-prompt.md" \
  --output-file "$SESSION_DIR/peer-output/final-consensus-raw.txt" \
  --session-id-file "$SESSION_DIR/peer-output/final-peer-session-id.txt" \
  --timeout {timeout_per_round}

Important:

  • Do NOT pass --resume-session
  • This final pass must be independent from the iterative repair conversation

Step 3.3: Parse final consensus output

Look for:

  • CONSENSUS: → final approval confirmed, continue to report generation
  • New FINDING: fN blocks → treat them as real blocking findings

If the fresh final pass reports new findings:

  • If total rounds < max_rounds, append the findings as a new round and return to Phase 2.1
  • If total rounds >= max_rounds, mark them as escalated and continue with status max_rounds

Step 3.4: Complete rounds.json

Update session metadata:

  • completed_at: current ISO timestamp
  • status: consensus or max_rounds
  • total_rounds: actual count
  • summary: compute totals from all rounds

Step 3.5: Generate summary.md

Write $SESSION_DIR/summary.md in this format:

# Review Loop Summary

**Session**: {session_id}
**Peer**: {peer_reviewer} CLI
**Scope**: {scope} ({scope_detail})
**Rounds**: {total_rounds} | **Status**: {status_emoji} {status}

## Changes Made

{for each modified file: bullet with file path and description of change}

## Findings Resolution

| # | Finding | Severity | Action | Resolution |
|---|---------|----------|--------|------------|
{for each finding: row with id, title, severity, accept/reject, final status}

## Consensus

{if consensus: "Both Claude Code and {peer} agree the code is in good shape after {N} rounds."}
{if max_rounds: "Review stopped after {N} rounds. {M} items remain unresolved."}

{if escalated items exist:}
## Escalated Items (Needs Human Decision)

{for each escalated finding: peer's argument, Claude's argument, recommendation}

Step 3.6: Terminal output

Print a concise summary to the user:

Review Loop complete.
  Session:    {session_id}
  Peer:       {peer_reviewer}
  Rounds:     {total_rounds}
  Status:     {status}
  Accepted:   {N} findings fixed
  Rejected:   {N} (resolved by peer)
  Escalated:  {N} (needs human decision)

  Details: .review-loop/{session_id}/summary.md

Step 3.7: Update latest symlink

ln -sfn "{session_id}" .review-loop/latest

Error Handling

ErrorAction
Peer CLI not foundInform user, suggest installation command, offer alternative peer
Peer times out (exit 124)Log timeout, mark round as failed, ask user whether to retry or stop
Peer output unparseableSave raw output, inform user, attempt to extract any findings manually
Max rounds reachedStop loop, generate report with max_rounds status, list unresolved items
Git operations failStop loop, inform user, preserve current state
User cancelsMark session as aborted, generate partial report

Examples

Example 1: Basic local diff review

User: "review loop"
→ Detects local diff (5 files changed)
→ Logs startup config and proceeds
→ Round 1: Codex finds 3 issues
→ Claude accepts 2, rejects 1
→ Claude fixes accepted issues, commits
→ Round 2: Codex reviews fixes, accepts rejection reasoning
→ CONSENSUS after 2 rounds

Example 2: PR review with Gemini

User: "review loop with gemini for PR 42"
→ Scope: PR #42
→ Peer: Gemini (override)
→ Round 1: Gemini finds 5 issues
→ Claude accepts 4, rejects 1
→ Round 2: Gemini insists on rejected finding
→ Claude re-evaluates, accepts
→ Round 3: Gemini confirms all fixes
→ CONSENSUS after 3 rounds

Example 3: Max rounds reached

User: "review loop, max 3 rounds"
→ Round 1: Peer finds 8 issues
→ Round 2: 5 resolved, 3 debated
→ Round 3: 2 more resolved, 1 still debated
→ Status: max_rounds, 1 escalated finding
→ Summary shows escalated item for human decision