| name | review-loop |
| version | 1.4.0 |
| description | Cross-LLM iterative code review loop. Spawns a peer reviewer (Codex, Claude, 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 claude", "review with gemini", "让 codex review", "让 claude review",
"交叉 review", "peer review 这段代码", "code review loop", "iterative review"
|
Review Loop — Cross-LLM Iterative Code Review
Spawns a peer reviewer (Codex, Claude, or Gemini) to independently review your code changes.
The host agent 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.
Compatibility note: rounds.json keeps the historical field name claude_actions for backward compatibility. In Codex-hosted runs, that field still stores the host agent's decisions and code changes.
Prerequisites
- Required:
git CLI
- Peer (one of):
codex CLI (codex --version), claude CLI (claude --version), or gemini CLI (gemini --version)
- Optional:
gh CLI (for PR scope detection)
Configuration
Defaults
| Setting | Default | Options |
|---|
peer_reviewer | codex | codex, claude, gemini |
max_rounds | 5 | 1–10 |
timeout_per_round | 600 | seconds |
scope_preference | auto | auto, diff, branch, pr |
read_only | false | true = report-only, no code changes |
The peer reviewer always runs with local repository access.
Read-only mode (read_only: true): Peer reviews code and the host agent evaluates findings, but NO code changes are made. Output is a findings report only — no fix commits, no code evolution loop. Useful when review-loop is used as a sensor by other skills (e.g., the bundled harness skill's Evaluator Tier 2). In read-only mode, Phase 2 (Code Evolution Loop) is skipped entirely — after Round 1 findings are evaluated, the loop goes directly to Phase 3 report generation with all findings classified as reported (not accepted/rejected).
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.
SKILL_DIR="${SKILL_BASE_DIR:-}"
if [[ -z "$SKILL_DIR" || ! -x "$SKILL_DIR/scripts/preflight.sh" ]]; then
for candidate in \
"$(find ~/.claude/plugins/cache -path "*/review-loop" -type d 2>/dev/null | head -1)" \
"$(find ~/.claude/skills -path "*/review-loop" -type d 2>/dev/null | head -1)" \
"$(find ~/.codex/skills -path "*/review-loop" -type d 2>/dev/null | head -1)"; do
[[ -x "$candidate/scripts/preflight.sh" ]] && SKILL_DIR="$candidate" && break
done
fi
if [[ -z "$SKILL_DIR" ]]; then
echo "Error: review-loop skill directory not found" >&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})"
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:
- Reads
.review-loop/config.json (merges over defaults)
- Applies CLI args (highest precedence — invocation overrides)
- Checks peer CLI availability (falls back to alternative)
- Detects base branch and repo root
- Auto-detects review scope (local-diff → branch-commits → PR), or uses
--commit-sha for a specific commit
- Creates session directory with timestamp
- Auto-adds
.review-loop/ to .gitignore
- Initializes
rounds.json
- Collects the priority file list for the peer to inspect locally
- Creates checkpoint commit
- 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:
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)" \
"$(find ~/.codex/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 the selected peer in the current repository directory 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. For Claude, it uses JSON output mode and records the Claude session id for reuse.
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 (historical field name; stores host-side actions).
Phase 2: Code Evolution Loop
If read_only: true: Skip this entire phase. Go directly to Phase 3 with all Round 1 findings classified as reported (not accepted/rejected). No code changes, no checkpoint commits, no re-review rounds.
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 — MUST attach a
Verification: block per protocol-quick-ref.md §verification-block. Form B (verification-impossible) automatically downgrades to deferred for verification.
Record each decision with reasoning AND (for rejections) the Verification: block in claude_actions[].verification.
Step 2.2: Implement accepted fixes
For each accepted finding:
- Read the relevant file
- Make the minimal code change to address the finding
- Record the change in
claude_actions[].code_changes
Step 2.3: Checkpoint commit
git commit -am "review-loop: changes from round {N}" --allow-empty
Scoped, not broad (issue #36): git commit -am stages
tracked-modified files only. Do NOT use git add -A — it sweeps every
untracked file in the workspace (including .harness/ scratch from
prior or parallel tasks) onto the feature branch, poisoning the diff
the peer reviews. If your round's accepted-fix work creates a genuinely
new file that belongs on the branch, git add <path> it explicitly
before this checkpoint commit.
The --allow-empty flag ensures rounds where the host agent 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 AND the verbatim
Verification: block from claude_actions[].verification (so the peer can audit the evidence, not just the 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, the host agent must re-evaluate
- New
FINDING: fN → new issues found in Claude's changes
Step 2.9: Handle INSIST findings
For each INSIST:
- Count how many rounds this finding has been debated
- If debated < 2 rounds → Claude re-evaluates with peer's stronger argument
- If debated >= 2 rounds → Mark as ESCALATED
Then loop back to Step 2.1 with the updated findings list.
Phase 3: Final Consensus + Report
Documentation / Protocol Scope Rule (load-bearing invariant)
When the review scope targets documentation or protocol files — defined as
any .md file under a skill's references/, agents/, or a repo-level
dotfiles/ directory — fresh-final consensus (Step 3.2) is load-bearing
and non-optional. Historical evidence: on PR #42 the codex-mode.md ↔
planning-protocol.md escalation-rule contradiction (rFinal.f1) was
caught only by fresh-final; resumed-session rounds had converged on
CONSENSUS while the bug was still shipping.
Operational implications:
- Step 3.2 MUST run in a fresh peer session even if earlier rounds
converged cleanly. Any future optimization that would skip fresh-final
under "all rounds resolved quickly" heuristics MUST exclude this scope.
- If fresh-final reports new findings on docs/protocol scope with
read_only: false, treat them as a new iteration round per Step 3.3 —
Consensus is reached only when a fresh session returns zero findings.
- In
read_only: true mode, fresh-final findings on docs/protocol scope
MUST be surfaced in summary.md with the explicit note that these would
have been blockers in normal mode — do not let them disappear silently
into the generic reported bucket.
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
read_only: true: Record findings as reported in rounds.json. Do NOT return to Phase 2. Continue to report generation with status read_only_complete.
- If
read_only: false and total rounds < max_rounds, append the findings as a new round and return to Phase 2.1
- If
read_only: false and 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, max_rounds, or read_only_complete
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}
## Round Breakdown
| Source | Real issues found |
|--------|-------------------|
| Resumed-session rounds (r1–rN) | {resumed_real_issue_count} |
| Fresh-final consensus pass | {fresh_final_real_issue_count} |
Fresh-final contributed {fresh_final_real_issue_count}/{total_real_issue_count} real issues this session.
Count only findings that survived host evaluation (action=accept, or action=reject with Form B deferral). Exclude findings where the host's Form A verification produced empirical contradiction — those were peer false positives, not "real issues" for this purpose. The point of this breakdown is to track how much signal fresh-final finds that resumed rounds miss, so the load-bearing invariant in Phase 3 Documentation / Protocol Scope Rule stays visibly justified across sessions.
## 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 summary.deferred_for_verification > 0:}
## Deferred for Verification
{for each finding with action == "deferred for verification": bullet with
- finding id and title
- peer's authority-only argument (the original finding description)
- host's Form B `reason` (why verification was not possible)
- note: "auto-downgraded per synthesis-protocol.md — peer is NOT required to re-evaluate; surfaced here for human follow-up."}
{if escalated items exist:}
## Escalated Items (Needs Human Decision)
{for each escalated finding: peer's argument, Claude's argument, recommendation}
The Deferred for Verification section is conditional: omit it entirely when summary.deferred_for_verification == 0. This section surfaces rejections that were auto-downgraded because they relied on authority (spec/design/conventions) without empirical proof per protocol-quick-ref.md §verification-block Form B and synthesis-protocol.md §Rejection Requirements.
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
| Error | Action |
|---|
| Peer CLI not found | Inform 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 unparseable | Save raw output, inform user, attempt to extract any findings manually |
| Max rounds reached | Stop loop, generate report with max_rounds status, list unresolved items |
| Git operations fail | Stop loop, inform user, preserve current state |
| User cancels | Mark 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