Releases: OrenAshkenazy/gh-review-loop
Release list
v0.2.39: feat: restore duplicate-block mirror sweep
Summary by Sourcery
Introduce mirror-based duplicate-block sweeps that can operate from a single multi-line finding, alongside existing token-based sibling sweeps.
New Features:
- Support ranged sites (PATH:START-END) and exact normalized multi-line block matching, reported as mirror candidates distinct from token-intersection hits.
- Allow single ranged findings to trigger duplicate-block sweeps without requiring multiple sites, while preserving safety constraints around identifier-like tokens and changed-file boundaries.
Bug Fixes:
- Ensure token sweeps ignore flagged lines within multi-line ranges and never read sources outside the declared changed files.
- Prevent duplicate reporting when mirror ranges overlap flagged spans or when token hits fall inside mirror blocks.
- Refine status and reporting logic so mirror-only sweeps can return ok from a single ranged site, while token sweeps still enforce the two-site minimum.
Enhancements:
- Add language-aware comment stripping, whitespace normalization, and indentation preservation for robust block fingerprinting.
- Label candidates by class (mirror vs token) in both JSON and human-readable reports, and centralize candidate sorting/truncation.
- Extend clustering to preserve multi-line anchored ranges and use synthetic corpora to exercise span clustering and mirror sweeps.
- Clarify README and skill documentation around mirror vs token sweeps, including CLI usage for ranged sites.
Tests:
- Add comprehensive tests for block normalization, mirror candidate discovery, interaction with token sweeps, and range parsing.
- Replace prior external PR195-based clustering and sweep fixtures with small, repository-owned synthetic corpora for spans and mirrors.
- Update request rereview tests to use a neutral example repository name instead of a real upstream repo.
v0.2.38: Revert "feat: add duplicate-block mirror sweep"
Reverts #80
Summary by Sourcery
Remove single-site duplicate-block mirror sweep support and revert range-based sibling sweeping while adding new clustering fixtures and tests for PR195 patterns.
Enhancements:
- Simplify sibling sweep to require two or more single-line flagged sites and report only token-based candidates.
- Update cluster finding site formatting to no longer encode multi-line ranges and streamline sweep report output.
- Adjust provider query construction and keyword policy fixtures used by clustering tests for PR195 to align with current search behavior.
Documentation:
- Update gh-review-loop skill documentation and README to remove references to mirror duplicate-block sweeps and single-range sweep usage examples.
Tests:
- Remove mirror sweep and range-based sweep tests while adding new PR195 clustering and sweep regression fixtures and test cases.
- Update request rereview tests to reflect the current target repository naming.
v0.2.37: feat: add duplicate-block mirror sweep
Summary
- preserve flagged ranges in cluster sites and accept
path:start-endsweep inputs - normalize and fingerprint a single flagged multi-line block, then search only changed files for exact copies
- report exact hits as
mirrorcandidates while retaining the existingtokencandidate path - exclude flagged and overlapping ranges from both mirror and token results
- cover range clustering and mirror detection with small repository-owned synthetic corpora
Validation
/opt/homebrew/bin/pytest -q— 838 passed/opt/homebrew/bin/ruff check plugins/gh-review-loop/skills/gh-review-loop/scripts testsgit diff --check- repository-wide relationship scan — clean
- recorded replay output remains byte-stable
Depends on the merged #79 range metadata support.
Closes #77.
Summary by Sourcery
Add exact duplicate-block mirror sweeps for multi-line findings and distinguish them from existing token-based sibling detection.
New Features:
- Support parsing and preserving flagged multi-line ranges in sweep inputs and cluster sites.
- Introduce mirror candidate detection by normalizing, fingerprinting, and matching multi-line code blocks across changed files.
- Label sweep candidates by class (
mirrorvstoken) and surface this in reports and CLI usage.
Bug Fixes:
- Exclude flagged and overlapping ranges from both mirror and token sweep results.
- Prevent sweeps from reading files or sites outside the declared changed-file set.
Enhancements:
- Refine sweep status handling so a successful mirror match can be reported even when token-based patterns are too thin or have too few sites.
- Share candidate truncation and sorting logic across mirror and token sweeps.
- Improve report output to omit empty invariant-token sections and to explain truncation or fallback reasons more clearly.
Documentation:
- Document ranged-site usage, mirror sweeps, and their interaction with token-based sweeps in the review-loop skill guide.
Tests:
- Use repository-owned synthetic fixtures for mirror detection, span clustering, punctuation significance, range parsing, comment stripping, overlap exclusion, and report labelling.
v0.2.36: fix: make shape clustering span-aware
Summary
- fetch review range starts at thread and comment level, with single-line fallback
- cluster on the union of code tokens across each anchored span
- require shared identifier-like tokens at both shape and sweep safety floors
- add a captured 16-finding SignalScout #195 corpus with matching historical source snapshots
Validation
- ........................................................................ [ 8%]
........................................................................ [ 17%]
........................................................................ [ 26%]
........................................................................ [ 35%]
........................................................................ [ 43%]
........................................................................ [ 52%]
........................................................................ [ 61%]
........................................................................ [ 70%]
........................................................................ [ 78%]
........................................................................ [ 87%]
........................................................................ [ 96%]
.............................. [100%]
822 passed in 6.38s — 822 passed - .......... [100%]
10 passed in 0.36s — 10 passed; recorded replay output remains byte-stable - All checks passed!
Closes #74.
Closes #75.
Closes #76.
Summary by Sourcery
Make review-shape clustering span-aware and tighten token-based safety floors for clustering and sibling sweeps.
New Features:
- Support span-anchored clustering by reading all code lines in a review range instead of a single line.
- Include GitHub review range start fields in thread fetching and propagate them into captured findings.
- Add a captured SignalScout PR195 corpus with anchors and matching historical source snapshots for real-world clustering coverage.
Enhancements:
- Cluster shapes and sweep siblings only when shared tokens include identifier-like signal rather than punctuation-only intersections.
Documentation:
- Update the GH review loop skill documentation to explain span-based shape clustering, single-line fallback, and the requirement for letter-bearing shared tokens in sweep patterns.
Tests:
- Extend clustering, sweep sibling, and GitHub thread fetching tests with PR195 corpus cases and new assertions around span unioning, significance-filtered tokens, and GraphQL query fields.
v0.2.35: fix: flag degenerate review clustering
Warn explicitly when three or more review findings each collapse into singleton clusters, so classifier failure cannot masquerade as an ordinary zero-sweep result.
Fixed
- Emit a receipt advisory that identifies likely prose-hash fallback signatures and requires a manual sweep before fixing.
- Keep the advisory informational; loop control and the re-review cap are unchanged.
- Document the advisory beside the existing
Convergence:contract.
Validation
/opt/homebrew/bin/pytest -q— 796 passedruff check plugins/ tests/— passed- Two-axis local review against issue #73 and repository standards — clean after strengthening the multi-site negative test
Closes #73
Summary by Sourcery
Add an explicit advisory to receipts when review clustering degenerates into singleton patterns, and document the new advisory alongside existing convergence guidance.
Bug Fixes:
- Flag degenerate review clustering by emitting a warning when three or more findings all become singleton patterns, distinguishing classifier failures from normal zero-sweep results.
Enhancements:
- Extend receipt output to include a clustering advisory line and clarify its advisory-only role in the skill documentation.
Documentation:
- Document the new
Clustering:advisory behavior and its relationship to the existingConvergence:line in the review loop skill guide.
Tests:
- Add tests covering when the degenerate clustering advisory should be emitted or suppressed based on cluster composition.
v0.2.34: fix: report swept patterns per run, and add a deterministic sweep replay
Two changes: a bug in a number that was about to be published, and the artifact that publishes it.
1. The swept-pattern accumulator leaked across runs
A cycle receipt on #67 printed Swept 2 patterns. on a run where no --swept-pattern was passed at all.
The convergence block unioned the live accumulator with the swept signatures of every prior recorded run of the PR, then wrote that union back into the new record. --record-run did clear the live accumulator — but pattern_history_for_pr read the count straight back out of runs.jsonl, and each new record re-persisted the union. One run that swept two patterns made every later run of that PR report Swept 2 patterns forever, and the number could never fall again.
Reproduced before fixing:
--- cycle 1: agent passes --swept-pattern twice ---
receipt line : Convergence: 1 distinct patterns this cycle, 0 recurred. Swept 2 patterns.
--record-run: appended record, cleared live accumulator
--- cycle 2: fresh run, NO --swept-pattern passed ---
live accumulator : [] <- correctly empty
swept_sigs used : ['64250ca5', 'exception-wrap'] <- leaked back in from runs.jsonl
receipt line : Convergence: 1 distinct patterns this cycle, 0 recurred. Swept 2 patterns.
The two sets answer different questions and are now kept apart in swept_pattern_sets():
| meaning | drives | |
|---|---|---|
this_run |
what --swept-pattern supplied on this run |
the receipt count, and what gets persisted |
ever |
plus signatures from prior runs of the PR | recurrence detection only |
Persisting this_run rather than ever is what stops the compounding — pattern_history_for_pr already unions across records, so writing the union back into each record made it permanent.
Recurrence detection is unaffected: a pattern swept in an earlier cycle that reappears now is exactly what the advisory is for, and there is a test pinning that it still fires.
The lifecycle decision used to be inline in main(), reachable only through a network fetch — which is why nothing caught it. It is now a named helper with three regression tests, verified red before the fix and green after:
=== RED without fix ===
FAILED tests/test_fetch_gemini_threads.py::TestSweptPatternLifecycle::test_a_run_that_swept_nothing_reports_zero
FAILED tests/test_fetch_gemini_threads.py::TestSweptPatternLifecycle::test_history_still_drives_recurrence_detection
FAILED tests/test_fetch_gemini_threads.py::TestSweptPatternLifecycle::test_persisting_the_union_would_compound_across_records
3 failed
=== fix restored ===
3 passed
2. evals/replay — deterministic sweep evidence
A live-reviewer demo is not reliable evidence. Sourcery returned two findings against b391162 and one finding against 67551bd, which is the same file contents re-pushed after a rebase. Retrying until a run happens to show the sweep would mean publishing the luckiest sample.
The sweep is deterministic, so this proves it deterministically over both captured payloads, and states the reviewer variance plainly rather than hiding it:
- run 1 — two findings worded differently → two prose clusters → one shape-merged cluster of two sites → sweep reports 3 unflagged siblings
- run 2 — one finding → one cluster of one site → correctly no sweep
$ python3 evals/replay/replay.py
-- clustered by finding prose alone --
Patterns (2):
[unknown] exception-wrap — 1 site (sig: exception-wrap)
loaders/profiles.py:17
[unknown] **suggestion (bug_risk):** add type and error checks around … — 1 site (sig: 64250ca5)
loaders/bundle.py:13
-- clustered by prose and code shape (shipping behaviour) --
Patterns (1):
[unknown] **suggestion:** defensively handle malformed or non-object j… — 2 sites (sig: shape:802659ea)
loaders/profiles.py:17, loaders/bundle.py:13
-- sweep --
[sweep] **suggestion:** defensively handle malformed or non-object j…
flagged: loaders/profiles.py:17, loaders/bundle.py:13
shared: data get {})
siblings: 3 unflagged site(s) match the same shape
+ loaders/bundle.py:25 return data.get("overrides", {})
+ loaders/profiles.py:23 return data.get("reviewers", {})
+ loaders/profiles.py:29 return data.get("defaults", {})
Fixtures are real captured data — bodies, paths, line anchors and author login copied verbatim from the recorded comment IDs (3721865840, 3721865850, 3731481473), with the reviewed loaders/ source vendored beside them so the replay does not depend on the demo branch surviving. replay.py takes no arguments; verified zero non-stdlib imports across the whole chain and no network or subprocess surface in it. Output is pinned by a committed golden file.
Verification
pytest -q→ 789 passed (782 + 7 replay)ruff check plugins/ tests/ evals/→ clean
🤖 Generated with Claude Code
https://claude.ai/code/session_014f8SDmEgdjSHo5iw4a3ykm
Summary by Sourcery
Ensure swept-pattern reporting reflects only the current run while adding a deterministic replay for sibling-sweep behaviour over captured reviewer payloads.
Bug Fixes:
- Prevent swept-pattern counts from accumulating across runs of the same PR so that receipts report only patterns swept in the current run.
Enhancements:
- Introduce a helper to separate current-run swept pattern signatures from historical ones and use it to drive recurrence detection and persistence correctly.
- Add a deterministic replay script that runs the real clustering and sibling-sweep logic over captured reviewer payloads and vendored source without external dependencies.
Documentation:
- Document the deterministic sweep replay fixtures, behaviour, and rationale in a new README under evals/replay/.
Tests:
- Add regression tests covering the swept-pattern lifecycle to ensure counts reset per run while still leveraging history for recurrence detection.
- Add tests that pin the deterministic replay output to a golden file, enforce determinism and portability, and verify fixture provenance and vendored source contents.
v0.2.33: fix: keep the test suite hermetic
The re-review cap check in request_rereview.py calls gh_login() and
count_agent_pings(), which shell out to the real gh. Fourteen tests reach
that path without stubbing it.
Where gh is absent (CI) the lookup fails, the cap degrades to permissive, and
the suite is green — so the leak is invisible. Where gh is authenticated the
suite made live API calls against a real repo, and on a PR that had already used
its cap the write was refused, failing test_custom_phrase_is_preserved_exactly.
It also read and wrote ~/.config/gh-gemini-review-loop/.
Changes
- Two autouse
conftest.pyfixtures: one raises if any test invokes the real
gh, one pointsGGRL_STATE_DIRat a tmp dir. gh_login/count_agent_pings/post_rereviewresolve the subprocess
runner at call time (runner or subprocess.run) instead of binding
subprocess.runas a default argument, so monkeypatching works.gh_loginstubbed in three test modules.- The sibling sweep no longer matches comment-only lines.
Verification
A normal run cannot show this, because the leak is silent when gh is missing
and masked when the developer's own config lifts the cap. Both halves were
verified against the pre-patch tree under identical conditions.
1. Fake gh first on PATH — echoes a login for api user, and five prior
pings for any comments call (the local preferences.json sets
max_rereview_requests: 5, so five is what trips the cap):
| tree | result | real gh invocations |
|---|---|---|
| pre-patch | 1 failed, 755 passed — test_custom_phrase_is_preserved_exactly, KeyError: 'phrase' (write refused, post_rereview never reached) |
2+ |
| this branch | 769 passed | 0 |
The zero is the load-bearing number: the guard fixture means no test reaches
gh at all, whatever it would have returned.
2. HOME set to an empty tmp dir:
| tree | files left in HOME |
|---|---|
| pre-patch | 1 — .config/gh-gemini-review-loop/preferences.json |
| this branch | 0 (not even a directory) |
Gates: pytest -q → 769 passed. ruff check plugins/ tests/ evals/ → clean.
Summary by Sourcery
Enforce hermetic behavior for the gh-review-loop test suite and tighten sibling sweep behavior to ignore comment-only lines.
New Features:
- Classify code versus comment lines in the sibling sweeper and expose a helper for this classification.
Bug Fixes:
- Prevent comment-only lines from being proposed as sibling candidates or used as flagged source when sweeping.
- Ensure the re-review cap check is inert in tests that are not explicitly about cap enforcement so they do not hit live gh APIs.
Enhancements:
- Add global test fixtures to forbid real gh subprocess calls and to isolate per-user state under a temporary directory.
- Resolve subprocess runners for gh-related helpers at call time to allow effective monkeypatching in tests.
Tests:
- Add focused tests verifying comment-line classification and sweep behavior when flagged sites point at comments.
- Add a self-check test that asserts the hermetic guard intercepts attempts to shell out to gh.
- Extend request re-review, JSON stdout discipline, and acceptance cycle tests with fixtures that neutralize the cap check by default.
v0.2.32: sweep implementation
- fix: retire the sunset Gemini default and make every PR write previewable
- docs: rebuild the README around what is actually differentiated
- fix: enforce the re-review cap at the write, and correct four overstated claims
- feat: implement the sibling sweep as a script, and correct the Gemini tier claim
Summary by Sourcery
Switch the default reviewer integration from Gemini to Codex, tighten safety guarantees around re-review requests, and add a sibling-pattern sweep tool with accompanying documentation updates.
Enhancements:
- Document the tool around differentiated capabilities like sibling pattern sweeping, verification gating, and bounded re-review cycles, and refresh installation and usage guidance for Codex and other bots.
- Reposition Gemini Code Assist as a fully supported but non-default reviewer with clear tier caveats, and centralize reviewer-vendor metadata in a dedicated helper.
- Introduce a sibling sweep script that deterministically finds unflagged instances of flagged patterns within changed files and integrates it into the documented loop behavior.
- Enforce a hard, agent-scoped re-review cap at the point of posting re-review comments, including dry-run preview support and robust handling of corrupted preference state.
- Improve loop safety and observability by updating metrics, stats output, gates, and skill documentation to use reviewer-generic language and expose clearer next-step guidance and wait-state messaging.
Tests:
- Add comprehensive tests for the new sibling sweep script, including safety-focused cases that ensure it remains conservative when patterns are under-specified.
- Extend re-review helper tests to cover dry-run behavior, cap enforcement logic, tier handling for reviewers, and regex matching of mentions.
- Update existing tests to reflect Codex as the default reviewer, new wait/settling messages, and revised metrics and stats strings.
v0.2.31: launch readiness
- fix: retire the sunset Gemini default and make every PR write previewable
- docs: rebuild the README around what is actually differentiated
- fix: enforce the re-review cap at the write, and correct four overstated claims
Summary by Sourcery
Retire Gemini as the default reviewer in favor of Codex, enforce a hard re-review cap at the re-review request script, and update documentation to describe the multi-bot AI review loop and verification gate more accurately.
Enhancements:
- Add cap enforcement, agent-authored ping counting, and dry-run support to the re-review request helper to prevent PR spam and preview writes.
- Generalize reviewer terminology and flows from Gemini-specific to generic AI reviewers, including Codex as the new default vendor and clarifying severity parsing and shutdown behavior for Gemini.
- Tighten verification profiles, metrics, and loop gating behavior to ensure tests gate pushes, stats remain local-only, and summaries are emitted before pushes.
Tests:
- Extend and adjust tests to cover cap enforcement, dry-run behavior, Codex as default reviewer, and updated messaging across scripts and metrics.
v0.2.30: feat: ask the user how to proceed when reviewer quota is exhausted
Problem
A reviewer refusal was terminal. The loop stopped, recorded a human outcome, and the run was over.
But the two refusal causes aren't the same:
- Service withdrawn (Gemini sunset, "no longer supported") — gone for good, nothing the user can do.
- Usage quota spent (
You have reached your Codex usage limits for code reviews) — a billing limit the user can lift in a minute.
Ending the run on a quota cap throws away a cycle the user would happily have paid to keep. Real case: OrenAshkenazy/SignalScout#168 (comment)
Change
REVIEWER_REFUSAL_RESpatterns are now labeledREFUSAL_QUOTA/REFUSAL_WITHDRAWN;reviewer_refusal()returns the label askind, which flows throughrun_wait_chunkand the JSON wait payload unchanged.print_reviewer_refusal()on a quota refusal prints an ask-the-user block (stop + record, or upgrade/add credits and re-run the same wait) instead of the record-and-stop instruction. Withdrawn keeps the existing terminal wording.SKILL.mdsplits therefusedstatus bykind. On quota the loop prompts immediately via the runtime's choice mechanism and does not record a terminal outcome or re-request the review — the re-review was already delivered and refused, so re-asking spends a cycle for nothing.
Verification
pytest— 701 passed, including 4 new tests: kind labeling for both refusal families, quota stop-block wording, and withdrawn offering no retry path.- Ran the classifier against the live Codex comment body from SignalScout#168: classifies
quota_exhaustedand prints the ask block with the billing dashboard link intact.
🤖 Generated with Claude Code
https://claude.ai/code/session_018FBzqdm2VrefT9QDJeYQdL
Summary by Sourcery
Distinguish recoverable quota exhaustion from permanent service withdrawal in reviewer refusals and update loop behavior and docs to prompt users appropriately instead of always terminating.
New Features:
- Label reviewer refusals as either quota_exhausted or withdrawn and surface this kind in the wait JSON payload and CLI output.
- Prompt operators with explicit choices when a reviewer refusal is due to quota exhaustion, allowing them to either stop the loop or upgrade/add credits and retry the same wait.
Enhancements:
- Refine reviewer refusal detection patterns to classify usage-limit and service-sunset messages into separate categories.
- Update SKILL documentation to describe the new refusal kinds and the corresponding runtime behavior for each.
Tests:
- Add tests covering refusal kind labeling, JSON wait payload contents, and the different stop-block messages for quota exhaustion versus service withdrawal.