← GH Review LoopCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to GH Review Loop
Snapshot Sep 30, 2026 · 23:16 UTC · version 0.4.2
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use after a GitHub PR is opened or to handle AI reviewer feedback (Codex, CodeRabbit, Copilot, or any reviewer bot) - fix reviewer comments, sweep sibling instances, verify, push, request re-review. Needs an open PR and authenticated gh; asks which bot if none is configured; stops at the cap.",
"included_files": [
{
"relative_path": "references/judge-eval.md",
"size_in_bytes": 4342
},
{
"relative_path": "references/receipts-and-metrics.md",
"size_in_bytes": 4299
},
{
"relative_path": "references/resume-and-recovery.md",
"size_in_bytes": 3330
},
{
"relative_path": "references/reviewer-selection.md",
"size_in_bytes": 2531
},
{
"relative_path": "references/script-usage.md",
"size_in_bytes": 6138
},
{
"relative_path": "references/sweep-internals.md",
"size_in_bytes": 6530
},
{
"relative_path": "references/terminal-report.md",
"size_in_bytes": 3039
},
{
"relative_path": "references/variations.md",
"size_in_bytes": 4373
},
{
"relative_path": "references/verification-profile.md",
"size_in_bytes": 2659
},
{
"relative_path": "references/write-safety.md",
"size_in_bytes": 2668
},
{
"relative_path": "scripts/cluster_findings.py",
"size_in_bytes": 16263
},
{
"relative_path": "scripts/detect_profile.py",
"size_in_bytes": 17026
},
{
"relative_path": "scripts/fetch_gemini_threads.py",
"size_in_bytes": 170484
},
{
"relative_path": "scripts/hook_runtime.py",
"size_in_bytes": 1807
},
{
"relative_path": "scripts/judge.py",
"size_in_bytes": 25320
},
{
"relative_path": "scripts/judge_doctor.py",
"size_in_bytes": 11666
},
{
"relative_path": "scripts/key_resolver.py",
"size_in_bytes": 12866
},
{
"relative_path": "scripts/loop_color.py",
"size_in_bytes": 686
},
{
"relative_path": "scripts/loop_profile_gate.py",
"size_in_bytes": 3433
},
{
"relative_path": "scripts/loop_state.py",
"size_in_bytes": 14170
},
{
"relative_path": "scripts/loop_summary_gate.py",
"size_in_bytes": 4301
},
{
"relative_path": "scripts/loop_summary_hook.py",
"size_in_bytes": 4157
},
{
"relative_path": "scripts/metrics.py",
"size_in_bytes": 39971
},
{
"relative_path": "scripts/request_rereview.py",
"size_in_bytes": 20323
},
{
"relative_path": "scripts/review_vendors.py",
"size_in_bytes": 4205
},
{
"relative_path": "scripts/reviewer_resolver.py",
"size_in_bytes": 4434
},
{
"relative_path": "scripts/run_profile.py",
"size_in_bytes": 7732
},
{
"relative_path": "scripts/sweep_siblings.py",
"size_in_bytes": 44474
}
],
"name": "gh-review-loop",
"skill_md_contents": "---\nname: gh-review-loop\ndescription: Use after a GitHub PR is opened or to handle AI reviewer feedback (Codex, CodeRabbit, Copilot, or any reviewer bot) - fix reviewer comments, sweep sibling instances, verify, push, request re-review. Needs an open PR and authenticated gh; asks which bot if none is configured; stops at the cap.\n---\n\n# AI Reviewer PR Review Loop\n\nRun the full GitHub PR loop: wait for the configured AI reviewer, fetch unresolved actionable review threads, acknowledge, fix, verify against the repo's own checks, push, request re-review — capped. Prefer thread-aware review data over flat PR comments (threads preserve `isResolved`, `isOutdated`, paths, line anchors, diff hunks).\n\n## Precedence and preconditions\n\nExplicit user instructions override every default here and in the references (cap, severity filter, auto-resolve, reviewer choice); \"never X without an explicit request\" means an explicit request unlocks X. Say in one line when you override a default. This file wins over any reference file — don't stop to reconcile them, follow this file.\n\nRequires `python3` (3.10+) and an authenticated `gh` CLI with GitHub access. If either is missing the script prints `error: ...` — relay it and stop. Never substitute raw API calls, unauthenticated requests, or guesses about review state.\n\n## Reference files (load on demand)\n\nThis file holds the every-cycle happy path. Read the referenced file **at the point of need**, not up front:\n\n| File | Load when |\n|---|---|\n| `references/reviewer-selection.md` | No persisted reviewer; discovery returns `none_configured`/`partial`/`confirmation_required`; reviewer switch/reset |\n| `references/verification-profile.md` | First run for a repo (edit gate blocks until a profile decision); customize/un-skip/re-detect |\n| `references/sweep-internals.md` | Receipt shows a multi-site pattern (`count >= 2`), a multi-line ranged finding, or a `Clustering:` advisory |\n| `references/write-safety.md` | Resolving beyond the auto-resolve set; write constraints; is a push safe; what the bundled hooks enforce |\n| `references/judge-eval.md` | `judge_mode` ≠ `off`, the user mentions judge/eval, or the one-time tip is due |\n| `references/receipts-and-metrics.md` | `--post-receipt`/`--sticky-receipt`, `--semantic-risk`, `--stats`, the `--record-run` flag catalog |\n| `references/terminal-report.md` | Terminal `[loop] Summary` has `remaining_actionable > 0` |\n| `references/resume-and-recovery.md` | Re-invocation at the cap; new pushes after the loop stopped; resumed/compacted session |\n| `references/variations.md` | Phrasing isn't the plain default loop (severity filters, audit-only, cap changes, judge modes, stats) |\n| `references/script-usage.md` | Any invocation not shown inline (wait catalog + outcome handling, discovery, read-only, dry-run, JSON, history) |\n\n## Reviewer Selection\n\nOne configured reviewer bot per PR; returning runs reuse the persisted selection silently. Anything else — no persisted reviewer, `none_configured`, `partial: true`, `confirmation_required: true`, or a switch/reset — load `references/reviewer-selection.md` and run its prompt flow before any edit or re-review request. Never wait on a reviewer the user did not choose; never hand-write a trigger mention for an unknown bot.\n\n## Thread States\n\n- **`RESOLVED`** / **`OUTDATED`** — skip (outdated is auto-resolved by the script).\n- **`ADDRESSED_BY_REPLY`** — a maintainer's substantive reply deferred it: do not fix again; auto-resolved next pass (details: `references/write-safety.md`).\n- **`UNRESOLVED`** — actionable; drives the next fix attempt. Ordered `critical → high → medium → low → unknown`, parsed from the reviewer's severity badges.\n\n## Cycle Counting\n\nA **cycle** is one agent-posted re-review request after the reviewer's first review (cycle 0, free). Cap = `max_rereview_requests` (prefs file, default 3); after cycle N, hard stop. Replies and pushes without a re-review request do not count — only the agent's own requests do (login auto-detected; `--agent-login`/`--no-agent-filter` to override). The cap blocks new fix cycles and re-review requests, not stale-thread cleanup, metrics, terminal classification, or recording terminal state.\n\n## Pattern → Sweep → Converge\n\nBot reviewers are LLMs: fixing only the flagged sites of a pattern teaches the bot to flag more next cycle. Collapse that into one cycle. The receipt's `Patterns (N):` section clusters findings — reason about patterns, not a flat list. For each multi-site pattern (`count >= 2`) and each multi-line ranged finding: load `references/sweep-internals.md`, run `sweep_siblings.py` over the PR's **changed files only**, print the sweep report first (report-then-go — never edit unflagged code silently), then fix the cluster plus reported siblings this cycle. Honor the script's `status` (`too_few_sites`/`pattern_too_thin`/`no_source` → do not sweep; `truncated: true` → show the report, ask first). Mark swept patterns with `--swept-pattern <sig>` alongside `--fixed-finding`.\n\n## Verification Profile\n\nEach repo can have a code-derived verification profile — the checks the verify step runs. **First run for a repo** (a `PreToolUse` hook blocks edits until a profile decision is saved): load `references/verification-profile.md` and run its detect → menu → save flow before any edit. **Subsequent runs**: no prompt — the fetch output ends with the profile intro and planned-verification blocks; relay the intro from there, then for `confirmed`/`customized` run `run_profile.py <owner/repo> <repo_root>` — never call the test runner directly — feeding its `verification` field into `--verification` and its JSON into `--verification-details`. Verify fails iff any `required` check fails or times out. On `skipped`/unknown, relay the fallback intro and use ad-hoc narrowest-meaningful checks.\n\n## Receipts: per-cycle and terminal\n\n- **`--cycle-summary`** — read-only mid-loop receipt from accumulated run state. Safe every cycle.\n- **`--record-run`** — terminal: appends one record to local `runs.jsonl` and clears the run accumulator. Call exactly once, at loop end (flag catalog: `references/receipts-and-metrics.md`).\n\n**Emit a receipt at the end of every cycle.** Non-terminal → `--cycle-summary` right after verify, REQUIRED even when fixes were small. Terminal → `--record-run` only (never both on one cycle; never twice).\n\n**Single-channel delivery.** Both deliver the FULL receipt to the sticky PR comment (one per PR, edited in place) and print a one-line `[loop]` pointer to stdout. Relay the pointer verbatim; do NOT reprint the full receipt in chat. On comment-write failure (or `--dry-run`) the script prints the full receipt to stdout — relay that fallback verbatim instead.\n\n**Script-owned human blocks** — relay verbatim, never paraphrase unless the script fails: profile intro, planned verification, judge table/skip line, receipt pointer (or fallback receipt), semantic-risk note, `Next options:`, wait heartbeat. Before the reviewer confirms a re-review use the script's fixed-pending wording, never `Remaining valid actionable`. `--json`/`--format json` stdout is machine JSON — parse, don't relay.\n\n## Progress Narration\n\n<HARD-GATE>\nBefore `git push` on a non-terminal cycle: run `--cycle-summary` and relay its printed `[loop]` pointer line. For a terminal cycle **that follows a final push**: request re-review, capture `REREVIEW_AT`, wait with `--wait --after \"$REREVIEW_AT\"`, and set the terminal reviewer-confirmation flag from that wait result before `--record-run`. Terminal paths that publish nothing (already clean, cap reached, human decision, regression, no progress) record directly — no push, re-review, or wait. Enforced mechanically on hook runtimes: `loop_summary_gate.py` blocks `git push` while the summary is stale (exit 2 names the exact fix); `--record-run` is exempt. Violating the letter violates the spirit.\n</HARD-GATE>\n\nEmit one-line status updates at each phase transition. **N** = session cycle: copy it from the script (`[loop] session cycle N`, `loopStatus.sessionCycle`, or the receipt header) — never count it yourself. It exists only after a fetch, so the pre-fetch line omits it. **M/K** = cap consumed.\n\n| Phase | Narration line |\n|---|---|\n| Before fetch | `[loop] fetching threads from PR #<num>...` (no ordinal yet — the fetch is what reports it) |\n| After fetch | `[loop] session cycle N — re-review cap: M/K consumed. <J> actionable thread(s) (severity: <breakdown>). Fixing.` + judge tip/block if due |\n| After fixes | `[loop] session cycle N — fixes applied. Verifying via profile runner.` |\n| After verify | `[loop] session cycle N — verified (<test summary>).` |\n| Before push | `--cycle-summary` + relay pointer (HARD GATE), then `[loop] session cycle N — committing and pushing <sha>...` |\n| After push | `[loop] session cycle N — pushed. Requesting reviewer re-review. Cap now M/K.` |\n| Reviewer wait | Background task (primary) or chunked heartbeats (fallback) — Workflow step 8 |\n| Stop | `[loop] STOP — <stop-condition>: <one-line explanation>.` |\n| Done | `[loop] DONE — 0 actionable threads remaining. Re-review requests used: M/K.` (cap consumption, not N — a clean PR ends at `0/K`) + relay the `--record-run` pointer; `remaining_actionable > 0` → `references/terminal-report.md` |\n\nSkip narration only in pure non-interactive batch mode. User stepping away → pair with `--sticky-receipt`.\n\n## Optional Judge Eval\n\nAn opt-in, read-only OpenAI judge can classify findings. **Off by default; nothing is sent to OpenAI unless the user opts in.** When the saved mode ≠ `off`, the user mentions judge eval, or the one-time tip is due, load `references/judge-eval.md`.\n\n## Stopping Conditions\n\nStop and report instead of pushing or re-asking when any is true: **1. Cap reached.** **2. All clean** — no `UNRESOLVED` actionable threads after cleanup. **3. Human decision required** — remaining threads are informational, duplicate, contradictory, or need a human call (includes `ADDRESSED_BY_REPLY`). **4. Test regression** — failure not clearly caused by the finding-addressing change. **5. No progress** — the script prints `[loop] no_progress: …` (unchanged actionable fingerprint): stop immediately with `--record-run --outcome no_progress`; do not push or re-request.\n\nAt the cap still run cleanup, terminal classification, metrics recording, and the final summary. Re-invocation at the cap is usually a resume signal — load `references/resume-and-recovery.md` before declaring a hard stop.\n\n## Workflow\n\n1. **Trigger by default after PR creation.** \"Create the PR\", \"ship this\", \"run the review loop\" all authorize the full loop.\n2. **Resolve the PR** — given URL/number, else `gh pr view --json number,url,headRefName,baseRefName`. No PR → report the blocker.\n3. **Select the reviewer** (persisted → continue silently; else see Reviewer Selection).\n4. **Wait for the first review.** `reviewerSelection.auto_reviews: false` (Codex) and no review activity → post the trigger via `request_rereview.py` first, then wait with `--after`; waiting without pinging burns the whole timeout. Activity already present → skip the wait and fetch. Timeout → say so; never invent feedback.\n5. **Check loop status** — count prior agent re-review requests; at or above the cap → Stopping Conditions.\n6. **Fetch, acknowledge, classify.** Default fetch (below). Summarize actionable findings grouped by file/behavior; none → report clean and stop. First run for the repo → profile decision NOW, before any edit (see Verification Profile), then relay the profile intro. Explanation requests get a reply draft, not a forced edit; conflicts or regression risk → stop and surface the tradeoff.\n7. **Fix + verify.** Scoped to feedback; read before editing; each change traceable to a feedback cluster; sweep per Pattern → Sweep → Converge. Verify via the profile runner; checks can't run → report why.\n8. **Commit, push, re-review, wait, record.** Commit; `--cycle-summary` + relay pointer; push. Within the cap, post the re-review via `request_rereview.py --repo OWNER/REPO --pr N --json` (never hand-write the trigger — it also stamps the cycle boundary, so a hand-written ping leaves the ordinal stuck; `status: no_safe_trigger`, `capped`, `uncountable_trigger`, or `uncountable_count` → nothing was posted; stop and relay exactly), capturing `created_at` as `REREVIEW_AT`. Then wait — primary (runtimes with background-task completion notifications, e.g. Claude Code): a **background** Bash task, one turn per wait:\n ```bash\n python3 \"$GGRL_PLUGIN_ROOT/skills/gh-review-loop/scripts/fetch_gemini_threads.py\" \\\n --wait --after \"$REREVIEW_AT\" --timeout 1800\n ```\n Do not poll it; never invent its result; relay its final output once. Fallback (no completion notifications, e.g. Codex): chunked waits — commands, statuses, refused/timed-out handling in `references/script-usage.md`. After the final wait, `--record-run` exactly once and relay its `[loop] Summary` pointer line.\n\n## Script Usage\n\nResolve the runtime-neutral plugin root once, as its own Bash call:\n\n```bash\nGGRL_PLUGIN_ROOT=\"${GGRL_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}\"\nif [ -z \"$GGRL_PLUGIN_ROOT\" ]; then\n GGRL_PLUGIN_ROOT=$(\n find ~/.codex/plugins ~/.codex/plugins/cache ~/.claude/plugins/cache \\\n -type d -path \"*/skills/gh-review-loop\" 2>/dev/null \\\n | sort -rV | head -1 | sed 's|/skills/gh-review-loop$||'\n )\nfi\nif [ -z \"$GGRL_PLUGIN_ROOT\" ] && [ -d \"$(git rev-parse --show-toplevel 2>/dev/null)/plugins/gh-review-loop\" ]; then\n GGRL_PLUGIN_ROOT=\"$(git rev-parse --show-toplevel)/plugins/gh-review-loop\"\nfi\nexport GGRL_PLUGIN_ROOT\n```\n\nDefault fetch (resolves stale threads, prints current feedback, ends with the profile intro + planned-verification blocks):\n\n```bash\npython3 \"$GGRL_PLUGIN_ROOT/skills/gh-review-loop/scripts/fetch_gemini_threads.py\" [--pr <URL>]\n```\n\n**Delta mode.** Threads unchanged since the previous cycle collapse to one line (anchor, severity, URL) — not missing data; any change renders full automatically. On a resumed session or after context compaction, run one fetch with `--full` to re-establish the baseline. Full option catalog: `references/script-usage.md`. The script warns on stderr when a GraphQL page limit is hit.\n\n## GitHub Write Safety\n\nInvariants: **never resolve an `UNRESOLVED` thread without an explicit user request; never submit approve/request-changes reviews unless explicitly asked.** Stale threads (outdated, addressed-by-reply) auto-resolve by default; uncertain run → `--dry-run` first. Full policy, publish-stop conditions, and the bundled-hooks table: `references/write-safety.md`.\n"
}SHA-256 of public snapshot: d23c634ed34b867205d6871fc5225d1bd6842bb6d0c5893d447239b941e6b997