Skip to content

Feat/review accuracy and repo context#2465

Merged
atoomic merged 17 commits into
mainfrom
feat/review-accuracy-and-repo-context
Jul 22, 2026
Merged

Feat/review accuracy and repo context#2465
atoomic merged 17 commits into
mainfrom
feat/review-accuracy-and-repo-context

Conversation

@atoomic

@atoomic atoomic commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Overhauls /review accuracy and consistency so repeat reviews stop drifting, the yellow "Important"
tier stops over-blocking, and humans can close the loop. Two bodies of work on this branch:

Review accuracy groundwork (4605879, pre-existing on branch)

  • Snippet validation — reconcile each finding's model-authored code_snippet against authoritative
    content at the reviewed SHA (resync stale quotes, drop invalid anchors) so a review never quotes
    pre-fix source under a HEAD permalink.
  • Prior-finding reconciliation — suppress re-raised, already-fixed findings; add a "Resolved since
    last review" section.
  • Repo-convention ingestion — feed the reviewed repo's own AGENTS.md/CLAUDE.md/docs/ bundle into the
    prompt so the reviewer stops raising convention-based false positives. Config-gated, fail-open, on by
    default.

Consistency-critical logic lives in focused, deterministic, unit-tested modules (review_identity,
review_reuse, review_reconcile, review_triage); qualitative judgment stays in shared prompt partials.
No review output-schema change. New config (all fail-open, backward-compatible): review_consistency,
review_discovery, review_dispositions (+ the pre-existing
review_snippet_validation/review_reconcile/review_convention_docs).

Testing

  • make lint (ruff): clean project-wide.
  • Full suite: 19,354 passed, 10 skipped, 1 xfailed, 0 failed — new unit tests for identity, reuse,
    reconcile/freeze, triage (pre-existing + deferred), config getters, and prompt-injection gating; no
    regressions.
  • Offline eval scorer green (9 golden cases, dataset valid), incl. new borderline_not_blocking,
    pre_existing_downgrade, recall_all cases.
  • ⚠️ Live eval not yet run — run KOAN_EVAL_LIVE=1 python -m app.skill_evals review --live (needs
    provider) before merge to confirm no recall/precision regression (SC-006/007).
  • Deferred to a follow-up harness extension: run-twice repeat-stability (SC-007), recall-gain
    (SC-009), and comment-injection for a live disposition eval — the single-diff harness can't express
    these; they're covered by deterministic unit tests for now.

Declarations

  • Architectural change — modifies the durable skill contract specs/skills/review.md. Rationale:
    adds the review consistency/reuse/freeze guarantee, the yellow-tier bar, [Pre-Existing Issue]
    labeling, the human-disposition authority (+ attribution/injection guardrails), and the opt-in
    comprehensive-discovery mode — landed contract-first (spec updated before/with the code).

@github-actions
github-actions Bot requested a review from Koan-Bot July 22, 2026 16:41
atoomic and others added 10 commits July 22, 2026 10:45
…o conventions

Fixes /review re-flagging already-fixed findings and quoting pre-fix source, and
gives the reviewer the reviewed repo's own conventions so it stops raising
convention-based false positives.

- Snippet validation gate: reconcile each finding's model-authored code_snippet
  against authoritative content at the reviewed SHA (git show -> contents API,
  read-only, fail-open). Resync stale quotes (default) or drop; drop invalid
  anchors. Kills the pre-fix quote under a HEAD permalink.
- Prior-finding reconciliation: read the existing review sidecar, detect prior
  findings resolved at HEAD, and suppress re-raised copies (conservative: only
  when the current quote also fails its live check). Adds a "Resolved since last
  review" section. Runs before resync so it isn't masked.
- Native convention-docs ingestion: read AGENTS.md/CLAUDE.md/CONTRIBUTING.md +
  an auto-detected OKF docs/ bundle into a fenced, verdict-guardrailed
  {REPO_CONVENTIONS} prompt slot. Config-gated, capped, on by default (no-op
  when absent).

New config keys: review_snippet_validation, review_reconcile,
review_convention_docs (all backward-compatible, fail-open). Tests added for
every helper/config path; existing review_runner/config/prompts suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ational)

Foundational substrate for spec 010 (review consistency, yellow-tier triage &
comprehensive discovery). Setup + Foundational phases:

- review_identity.py: deterministic finding-identity key (file + tolerant region
  bucket + semantic category) + same_finding() matcher (FR-002, research D1).
  No model call -> consistency-critical paths stay deterministic and testable.
- _write_review_findings_sidecar stamps each persisted finding with its
  identity_key + schema_version=1, copying (not mutating) caller findings
  (research D6). Reader stays backward-compatible and fail-open.
- instance.example/config.yaml: documented, default-preserving blocks for
  review_consistency, review_severity, review_discovery, review_dispositions
  (review_severity is used instead of extending review_triage, which is already
  the trivial-file pre-filter).
- Full spec/plan/tasks/contracts for feature 010.

Tests: test_review_identity.py + TestReviewSidecarIdentity; review_runner suite
green (491). Lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reeze

Delivers the MVP anti-whiplash core (spec 010 User Story 1):

- review_reuse.py: request_signature() + should_reuse() — reproduce the prior
  review verbatim when the PR head AND base (merge-base) SHA are unchanged and
  the request is equivalent (FR-001). Fail-safe by construction.
- review_reconcile.py: compute_freeze() — on a re-review, suppress first-time
  non-critical findings on files unchanged since the prior review (the "finds
  new issues on code I didn't touch" whiplash), keeping a critical labelled
  [Pre-Existing Issue] (FR-003). File-level + fail-open.
- run_review wiring: reuse short-circuit after the existing no-new-commits skip;
  freeze inside _apply_review_accuracy_gate; fail-open _compare_changed_files /
  _merge_base_sha (GitHub compare); persist base_sha + request_signature in the
  sidecar; get_review_consistency_config (both switches default on).
- review_identity: _norm_path -> public norm_path (shared with reconcile).

Cross-run consistency is verified by deterministic unit tests (identity, reuse,
reconcile) + integration tests (TestReviewFreezeWiring, TestReviewSidecarReuseFields);
the single-diff eval harness can't express run-twice-compare, so a harness-level
repeat-stability metric (FR-022/SC-007) is deferred to Polish. All fail-open, so
the default path is unchanged when data is absent.

Tests: +40 (reuse/reconcile/identity/config) + review_runner suite green (491).
Lint clean project-wide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raise the bar for the blocking 🟡 Important tier so an otherwise merge-ready PR
is not blocked by borderline nits (spec 010, User Story 2):

- New shared partial review-severity-rubric.md: reserve `warning` for issues that
  clearly block merge or risk real harm; demote borderline "should-fix" to
  `suggestion` (green, non-blocking); drop vague/speculative/cosmetic noise. Plus
  the Verdict Contract (lgtm follows post-triage severity). "When unsure, demote."
- Extracted review.md's inline Severity Calibration + Verdict Contract into the
  partial and {@include}-d it; also added it to review-with-plan.md, which
  previously had NO explicit severity/verdict guidance (DRY + fixes a gap).
- review-architecture.md outputs markdown (🔴/🟡/🟢, no lgtm JSON), so it gets a
  markdown-appropriate calibration Rules bullet instead of the JSON verdict rubric.
- Eval case borderline_not_blocking.json (borderline improvement -> suggestion,
  lgtm true). No new Python: the verdict-follows-severity invariant + noise-drop
  reflection already enforce the deterministic side; the bar is prompt-driven.

Include resolution verified; eval dataset-validity + offline suites green (127).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-critical issues that predate the PR's changeset become non-blocking green
recommendations prefixed [Pre-Existing Issue]; criticals keep severity but carry
the label (spec 010, User Story 6):

- review_triage.py: enforce_pre_existing() + derive_lgtm(). Detection is the
  reviewer's semantic call (FR-029) — a finding tagged [Pre-Existing Issue] in
  its title. Enforcement is deterministic: non-critical -> forced `suggestion`
  (FR-027); critical -> severity kept, label retained (FR-028); prefix normalized
  to one leading occurrence; lgtm re-derived on demotion (FR-012).
- review-severity-rubric.md: the [Pre-Existing Issue] rule (predates-changeset
  assessment; never for code the PR adds/modifies).
- Wired in the accuracy gate AFTER the freeze so FR-030 "freeze wins" holds.
  Reuses PRE_EXISTING_PREFIX from review_reconcile (US1 already labels the
  critical-on-unchanged exception with it — T025).
- Eval case pre_existing_downgrade.json.

Tests: test_review_triage.py (14); review_runner suite green (497); eval
dataset-validity green (127). Lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the default review aim to surface all issues in one parse, so a later review
has little to add — attacking incompleteness at the source (which the FR-003
freeze otherwise leaves hidden) (spec 010, User Story 5):

- review-severity-rubric.md gains a "Coverage — find all the issues" directive:
  keep searching after finding several; no finding cap; higher recall must not
  inflate the blocking set (the severity bar keeps extras in the right tier).
  Inherited by review.md + review-with-plan.md via the shared include.
- review-architecture.md (markdown) gets an equivalent "be exhaustive" Rules bullet.
- Eval case recall_all.json: three independent issues (hardcoded secret,
  unsanitized SQL, bare except) in one diff -> a single pass must find all three.

Prompt-only; no Python. Include resolution verified; eval dataset-validity green (127).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a re-review, honor reviewer decisions left as PR comments — "the agent
proposes, the human decides" (spec 010, User Story 7):

- New system-prompt review-dispositions.md: honor DISMISS ("ignore"/"not a
  problem" -> don't re-raise as a blocker) and DEFER ("fix later" -> non-blocking
  [Deferred] recommendation). Any non-bot commenter, all severities including a
  human-dismissed critical. Attribution mandatory (name + quote). Injection
  guardrail: a comment disposes of ONE finding, never rewrites the rubric/verdict.
- Injected via a config-gated {DISPOSITIONS} placeholder in review.md +
  review-with-plan.md (mirrors {REPO_CONVENTIONS}); get_review_dispositions_config
  gives a real `enabled` kill-switch for the open "any commenter" posture.
- review_triage.enforce_deferred(): deterministically force a [Deferred] finding
  to a non-blocking suggestion + re-derive lgtm (mirrors enforce_pre_existing).
  Wired in the accuracy gate.

Design notes: detection is prompt-driven (NL); stickiness/retraction come free
from re-reading the live PR thread each review (no sidecar persistence needed —
comments are the store). honor_critical/min_role are prompt-fixed at the chosen
posture; only `enabled` is runtime config.

Tests: enforce_deferred + dispositions config + config-gated prompt-injection.
review_runner+config suites green (807); eval validity green (127). Lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opt-in mode (default OFF) that reviews from a fixed set of perspectives and
merges findings into one set, catching more in a single pass so later reviews
have less to add (spec 010, User Story 3):

- New system-prompt review-comprehensive-discovery.md: fixed perspectives
  (correctness, security, architecture, silent-failure, test-coverage; sub-agents
  where supported), merge-into-one-set with same-issue dedup keeping the highest
  severity, same severity bar/verdict, best-effort + bounded, partial-coverage honored.
- Config-gated {COMPREHENSIVE_DISCOVERY} injection in review.md + review-with-plan.md
  (mirrors {REPO_CONVENTIONS}/{DISPOSITIONS}); get_review_discovery_config default
  OFF -> the default review carries zero discovery content (SC-008).
- discovery_enabled wired into the reuse request signature (FR-016), so a
  single-pass review is never reused as a comprehensive one.

Dedup stays prompt-driven (the model merges) — a deterministic coarse finding_key
dedup would risk merging two distinct issues in the same region/category, so it's
intentionally omitted per the "prompt partial, not orchestration" clarification.

Tests: discovery config + config-gated prompt injection + signature. review_runner
+config green (813); eval validity green (127). Lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface what triage filtered so the aggressive freeze/demote is auditable
(spec 010, User Story 4, FR-024):

- _format_triage_accounting(): a single collapsed "ℹ️ Triage summary" <details>
  line built from the stashed _freeze_summary / _pre_existing_summary /
  _deferred_summary — N suppressed (freeze), M demoted to recommendation,
  K deferred. Only present categories are shown; "" when nothing was filtered
  (so a no-filter review is byte-identical). Appended at the end of the review
  body (parsimony: secondary + collapsed, never competing with the findings).

Tests: TestTriageAccounting (5); review_runner suite green (507). Lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes out spec 010 with the durable-contract update and docs (Polish phase):

- specs/skills/review.md (DURABLE CONTRACT, architectural change — must be
  declared in the PR): new "Consistency, triage & human dispositions (spec 010)"
  invariants — finding identity, reuse on unchanged head+base, re-review freeze
  (+ critical exception), yellow-tier bar, exhaustive single-pass, pre-existing
  labeling (+ freeze-coexist), human dispositions (+ attribution/injection
  guardrail), opt-in comprehensive discovery. `updated:` bumped.
- docs/users/skills.md: user-facing behavior (consistent reviews, trustworthy
  yellow tier, [Pre-Existing Issue], honored comments, opt-in comprehensive mode).
- docs/design/review-consistency.md: the why + the deterministic-vs-prompt split
  + the "human decides" posture and its injection tradeoff (cross-linked to the
  threat model / prompt-guard).
- tasks.md: Polish phase status; live eval (T048) + /brain sync (T045) + PR
  architectural-change declaration (T049) flagged for the human.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@atoomic
atoomic force-pushed the feat/review-accuracy-and-repo-context branch from 5657a9e to 467122e Compare July 22, 2026 16:47
Fix the wiki-sync CI gaps for the spec-010 docs:
- docs/design/review-consistency.md: add required `type: doc` frontmatter field.
- wiki/index.md: add the design/review-consistency.md page entry and the
  010-review-consistency-triage status entry (shipped, 33/33 tasks).
- docs/design/index.md: regenerated via scripts/okf_backfill.py indexes.

Verified: scripts/wiki_check.py and scripts/wiki_sync_ci.py --base-ref
upstream/main both report no gaps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Important issues found.

  • Freeze can silently suppress real new findings when GitHub's compare files array is truncated

@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Important issues found.

  • _compare_changed_files can flip the freeze from fail-open to fail-toward-suppression

atoomic and others added 2 commits July 22, 2026 13:42
…cated diffs

`_compare_changed_files` returned an empty `set()` on a blank/error compare
response and had no guard for GitHub's 300-file, non-paginated cap on the
`compare` endpoint. Either case under-populates the changed-files set, flipping
the re-review freeze from fail-open to fail-toward-suppression: it silently
drops first-time non-critical findings on genuinely-changed code — the inverse
of FR-003's intent, and invisible.

Return `None` (skip the freeze) on a blank response and when the file list is at
the 300-entry cap, matching the documented FR-003 fail-open contract. A genuine
zero-file diff ("[]") still returns an empty set so the freeze legitimately
applies. Adds direct unit tests for both bug paths plus the boundary cases.

Addresses the "Important" finding in the PR #2465 automated review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@atoomic
atoomic force-pushed the feat/review-accuracy-and-repo-context branch from 3864f0a to 50c1f8f Compare July 22, 2026 19:58
@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Important issues found.

  • End-to-end gate test leaves run_gh unmocked — freeze path fires a real gh api compare with retry sleeps

`test_gate_end_to_end_resync_and_resolved_render` sets a prior head, so the
accuracy gate runs the re-review freeze, whose `_compare_changed_files` call
shells out to the real `gh` CLI — host-dependent, and with `retry_with_backoff`
sleeps before it fail-opens. The test only exercises reconcile + snippet-resync
+ resolved render, not the freeze.

Patch `_compare_changed_files` (fail-open `None` = freeze inert), mirroring
`TestReviewFreezeWiring._gate`, so the test no longer touches the external
boundary. Verified: with `run_gh` guarded to raise on any call, the test still
completes.

Addresses the "Important" finding in the PR #2465 automated review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

atoomic and others added 2 commits July 22, 2026 14:49
`_resolve_rebase_conflicts` labeled a failed conflict round as a timeout only
when the error text contained the literal "timed out". `run_claude` reports a
hard timeout as "Timeout (<n>s)" (and "Timeout (idle ...)" / "Timeout (max
duration ...)"), so real timeouts were mislabeled as generic failures in the
rebase failure summary — contradicting the documented behavior that the summary
identifies an agent timeout as the cause.

Reuse the existing timeout detector (renamed `_is_feedback_timeout_error` ->
`_is_agent_timeout_error`, since it now serves both the feedback and conflict
legs) which already matches both "timeout (" and "timed out", and make the error
lookup None-safe. The regression test now stubs `run_claude`'s real timeout
string instead of a fabricated one, and a new case pins the non-timeout label.

Addresses the medium finding from the HEAD-commit engineering review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

… + hygiene)

Addresses the remaining suggestion-level findings from the PR #2465 automated
review:

- W1: correct `get_review_snippet_validation_config`'s docstring. Out-of-range
  findings are dropped only when `on_mismatch != "off"`, and a file that no
  longer resolves at HEAD is kept (fail-open). The old "always dropped
  regardless of this setting" was inaccurate on both clauses. Docstring only;
  behavior unchanged.
- W2: `_read_file_at_sha`'s GitHub-contents fallback now logs on failure (still
  fail-open, still broad-catch like its sibling enrichment helpers). A
  systematic fetch/auth/decode failure previously disabled snippet validation
  and prior-finding reconciliation for the whole review with no trace.
- W3: `read_repo_convention_docs` logs a topic-index glob `OSError` before
  falling back to `[]`, matching `_read_or_empty`.
- W4: delete the dead `REPRODUCTION_NOTE` constant (never imported); the reuse
  short-circuit builds its own inline notify string.

Test-first coverage added for W2 and W3. Simplifications S1-S4 from the review
are deferred to a follow-up PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Koan-Bot

Koan-Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code Review

⚠️ Partial review — 4 file(s) omitted due to diff size and NOT reviewed: specs/010-review-consistency-triage/spec.md, instance.example/config.yaml (partial), koan/skills/core/review/evals/cases/recall_all.json, specs/010-review-consistency-triage/contracts/review-sidecar.schema.json

PR Review — Feat/review accuracy and repo context

Solid, well-factored consistency work — merge-ready with non-blocking nits.

Strengths worth calling out:

  • Consistency-critical logic (identity/reuse/reconcile/triage) lives in small, pure, genuinely behavior-tested sibling modules rather than growing the ~4k-line runner; the accuracy-gate order (reconcile → snippet-resync → freeze → pre-existing → deferred) is deliberate and documented, and the freeze/label precedence ("freeze wins") is coherent.
  • Prior blocking issues are resolved: _compare_changed_files fails open toward skipping the freeze (None on blank response and at the 300-file cap), and the e2e gate test mocks _compare_changed_files so it no longer shells out to real gh.
  • Two carried-over concerns are now fixed in this revision: _read_file_at_sha's API fallback logs on failure, and the stale REPRODUCTION_NOTE dead constant is gone.
  • Untrusted repo-convention and PR-comment content is fenced with explicit injection guardrails; the durable-contract change to specs/skills/review.md is declared architectural and the docs are in sync.

Remaining items are all suggestions:

  • _has_skipped_disposition keys the push/no-push decision off one exact output format — ordinary model-output variance can reject a legitimate rebase (fail-safe, but annoying).
  • on_mismatch="off" keeps out-of-range findings, contradicting the config docstring; no test pins that combination.
  • The run_review reuse short-circuit (Step 1g) is new branching logic with no integration test driving the early return.
  • Prompt-block builders catch bare Exception, so a coding error could silently strip the {DISPOSITIONS} injection guardrail (they do log, so not fully silent).

🟢 Suggestions

1. feedback_no_disposition gate hinges on a brittle exact-format regex
koan/app/rebase_pr.py:2195-2196

_has_skipped_disposition requires the model's no-change output to match ^SKIPPED:\n- <text> exactly (multiline, SKIPPED: at line start, a newline, then a dash bullet). When the feedback leg runs, the agent makes no commit, and it phrases the no-op differently, _run_rebase_impl returns [feedback_no_disposition] and does not push the otherwise-successful rebase.

Why it matters: the gate is a good addition (it stops a /fix on a PR from silently reporting success when feedback wasn't applied), but the push/no-push decision now depends on one exact output shape. A valid no-op phrased SKIPPED: already addressed (inline reason), **SKIPPED:**, or as prose will fail the regex and discard a good rebase, forcing a manual retry.

It fails safe (never pushes bad code, always recoverable), so this is non-blocking — but consider loosening the matcher to accept SKIPPED: with an inline or bulleted reason, case-insensitively, so ordinary model-output variance doesn't reject a legitimate rebase.

def _has_skipped_disposition(summary: str) -> bool:
    return bool(re.search(r"(?m)^SKIPPED:\s*\n\s*-\s+\S", summary))
2. on_mismatch="off" keeps out-of-range findings, contradicting the config docstring
koan/app/review_runner.py:1419-1421

get_review_snippet_validation_config's docstring states "A finding whose anchor line is past end-of-file at HEAD is dropped unless on_mismatch is 'off'" — but read against the review-runner comments elsewhere, the intent is that a location that no longer exists should not be posted under a permalink. With on_mismatch: "off", _validate_finding_snippets skips the out-of-range drop entirely, so a finding can still be posted with a file:line permalink pointing past EOF at HEAD — the exact stale-anchor case the gate exists to prevent.

Non-default and fail-safe in direction, but the code and its contract should agree, and no test pins the off + out-of-range combination. Either drop out-of-range findings unconditionally, or state the off carve-out explicitly in the config docstring so the behavior is intentional and covered.

        if line_start > line_count:
            if on_mismatch != "off":
                drop_indices.add(idx)
            continue
3. Reuse short-circuit (Step 1g) has no test driving run_review into the early return
koan/app/review_runner.py:3985-3992

The new reuse block assembles _focus_flags, computes _merge_base_sha, builds _request_signature, calls should_reuse(...), and on a match returns early from run_review (return True, msg, None), skipping the provider call entirely.

The deterministic helpers are well covered (test_review_reuse.py, TestReviewSidecarReuseFields, TestDiscoverySignature), but no test appears to exercise run_review actually taking this branch — the focus-flag assembly, the if _base_sha: guard, the 3-tuple early return, and the notify_fn call are untested. This is new branching logic on a path that can bypass the entire review; a regression that wrongly reused (or wrongly skipped) a review wouldn't be caught.

Add an integration test that mocks the prior record + SHAs so should_reuse returns True, then asserts run_review returns the reproduction tuple without invoking the provider.

            if should_reuse(_prior_record, _head_sha, _base_sha, _request_signature):
                msg = (...)
                log("review", msg)
                notify_fn(msg)
                return True, msg, None
4. Prompt-block builders catch bare Exception, so a coding error silently strips the injection guardrail
koan/app/review_runner.py:725-727

_build_dispositions_block, _build_repo_conventions_block, and _build_discovery_block each wrap their body in except Exception and return "" (with a log line). Fail-open is the right posture for a prompt-enrichment slot, and — to their credit — these do log, so it is not fully silent.

But catching bare Exception means a genuine programming error (renamed config key, missing {PLACEHOLDER}, a load_prompt typo) would be indistinguishable from a benign "feature off / no docs" and would drop the whole {DISPOSITIONS} block — including the FR-038 prompt-injection guardrail — from every review, leaving only a log line no one is watching.

Consider narrowing the caught types to the expected I/O/config errors (OSError, KeyError, ValueError) so a real defect surfaces loudly instead of quietly removing a guardrail block.

    except Exception as e:
        log("review", f"dispositions guidance injection skipped: {e}")
        return ""

Checklist

  • New deterministic helpers have behavior tests (identity/reuse/reconcile/triage)
  • New tests avoid real external boundaries (gh/provider); mock at run_gh
  • New branching logic has accompanying tests — suggestion #1, suggestion #3
  • Config behavior matches its documented contract — suggestion #2
  • Boundary failures are observable (logged), not silently swallowed — suggestion #4
  • Untrusted external content (repo docs, PR comments) is fenced/injection-guarded
  • Durable-contract spec change declared as architectural

Silent Failure Analysis

🟡 **MEDIUM** — swallowed exception (silent local fallback)
koan/app/review_runner.py:1282-1291

Risk: The local git show branch suppresses all errors (including subprocess.TimeoutExpired, a SubprocessError) with no log, so a systematically broken or slow local clone silently falls through to a second network call each time — the exact 'silently disables/degrades' failure the sibling API branch's comment says to guard against, yet the git branch has no breadcrumb.

if project_path:
    with contextlib.suppress(OSError, ValueError, subprocess.SubprocessError):
        r = subprocess.run(
            ["git", "show", f"{sha}:{path}"],
            cwd=project_path, ..., timeout=timeout,
        )

Fix: Log at debug/info when the git show path raises (distinguishing an expected missing-object from an unexpected OSError/timeout) so a systematic local-repo failure is observable, mirroring the logging already added to the API fallback.


Automated review by Skuggi HEAD=39f7941 3 min 53s

@Koan-Bot Koan-Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@Koan-Bot

Copy link
Copy Markdown
Contributor

💡 PR Explanation

Explaining the PR: "Review accuracy and repo context"

1. The Problem (What was wrong?)

/review behaved like a slot machine. The findings came straight from the model, so nothing pinned them down between runs.

Concrete example of the pain:

Those new issues were always there. They either mattered (and should have been caught the first time) or they didn't (and shouldn't be raised at all). Surfacing them after a fix-and-push is pure whiplash — it feels like the reviewer moved the goalposts.

Three more concrete failure modes:

  • Stale code quotes. A finding quotes a code_snippet, but the quote is model-authored. After a fix, a re-review could show the old, pre-fix source under a permalink that actually points at the already-fixed code. Confusing and wrong.
  • Over-blocking yellow tier. warning ("🟡 Important") is a blocking severity. Borderline "should-fix" nits landed there, so merge-ready PRs got turned into request-changes over trivia.
  • Convention false positives. The reviewer didn't know the reviewed repo's own rules (its CLAUDE.md, AGENTS.md, docs), so it flagged things like a documented link style as "broken."

There was also no way for a human to say "this finding isn't a problem" and have the reviewer honor it — which violates the project's core principle: the agent proposes, the human decides.


2. The Solution (How does this PR fix it?)

The strategy is: move the consistency-critical decisions out of the model and into deterministic Python, while leaving qualitative judgment (what is a bug) in the prompt.

Four new focused, unit-tested modules do the mechanical work:

  • review_identity.py — computes a stable identity for each finding, so the same issue is recognized across runs (needed for dedup and reconciliation).
  • review_reuse.py — the reuse decision. If PR head and base are unchanged and you asked for the same kind of review, reproduce the prior review instead of re-analyzing. No drift because there's no re-analysis.
  • review_reconcile.py — the re-review "freeze." When code did change, re-derive findings but suppress first-time non-critical findings on files that didn't change since the last review. This is the direct fix for fix-and-push whiplash. A genuine critical still breaks through, labeled [Pre-Existing Issue].
  • review_triage.py — enforces the mechanical rules: verdict follows severity, and [Deferred] / [Pre-Existing Issue] findings get demoted to non-blocking. enforce_deferred is real Python, not a prompt hope.

Supporting changes:

  • Snippet validation — each finding's quoted code is reconciled against the actual file content at the reviewed SHA. Stale quotes get resynced; quotes for lines that no longer exist get dropped. No more pre-fix source under a HEAD permalink.
  • Repo-convention ingestion (review-repo-conventions.md, review_convention_docs config) — feeds the reviewed repo's own AGENTS.md/CLAUDE.md/CONTRIBUTING.md and (if present) its docs/ OKF bundle into the review prompt. Auto-detecting, degrades to a no-op for repos that ship none of these — safe to leave on.
  • Prompt-fixed yellow bar — the "Important" bar and the [Pre-Existing Issue] label live in the review-severity-rubric prompt partial. They're constants, not config knobs, because the Python label must match the prompt exactly.
  • Human dispositions (review-dispositions.md, review_dispositions config) — a human PR comment can dismiss a finding ("not a problem" → not re-raised as a blocker) or defer it ("fix later" → non-blocking [Deferred]). Every such change is attributed to the commenter with their quoted rationale.
  • Comprehensive discovery mode (review-comprehensive-discovery.md, review_discovery, default off) — an opt-in multi-perspective pass for high-stakes PRs.

Before / after:

  • Before: fix 3 issues, push, re-review → 2 new complaints on untouched files.
  • After: fix 3 issues, push, re-review → clean, unless a real critical exists on pre-existing code (which is shown, but labeled [Pre-Existing Issue] and doesn't block for code you didn't write).

Every new switch is fail-open and backward-compatible — if a piece breaks or is disabled, /review behaves as it did before.


3. How It Works (The mechanism)

End-to-end scenario — re-running /review on PR #42 after a fix-and-push:

  1. Reuse check (review_reuse): Compare current head SHA + base merge-base SHA + request type against the prior review's recorded values. If all unchanged → reproduce the prior review verbatim. Done. No model call for fresh findings.
  2. Changed → re-derive. Head moved, so the model re-analyzes. The prompt now also carries the repo's own conventions and any human dispositions.
  3. Snippet validation. Each finding's quote is checked against live source at the reviewed SHA. Stale → resynced; gone → dropped.
  4. Reconcile + freeze (review_reconcile): Prior findings already fixed at this SHA are detected via the review sidecar and listed under "Resolved since last review." A current finding that merely re-raises a resolved prior — and whose own quote no longer matches live source — is suppressed (conservative: still-broken code is never hidden). First-time non-critical findings on unchanged files are frozen out; a critical survives, labeled [Pre-Existing Issue].
  5. Triage (review_triage): Severities are enforced, [Deferred] findings demoted to non-blocking, verdict computed from the surviving severities.

Edge cases handled:

  • Human dismissed a critical — honored (the posture is deliberately open: any non-bot commenter, all severities), but always attributed and quoted so it's auditable.
  • Injection guardrail (FR-038) — a comment can only dispose of one specific finding; it can't rewrite the severity rubric or verdict rules. Instruction-like comment text is treated as untrusted.
  • Kill switches everywherereview_consistency.reuse_enabled, freeze_enabled, review_dispositions.enabled, etc., all fail-open.

There's also a smaller, semi-independent change riding along: /rebase conflict resolution now has a per-round 600s budget (rebase_conflict_timeout) before falling back to recreate, and the failure summary names the agent timeout when that was the cause (claude_step.py now surfaces detail over a bare exception string).


4. Could It Be Simpler? (Critical analysis)

The split of concerns is the right call, and it's the codebase's established pattern — see the memory note about "Determinism where it counts." Consistency guarantees (identity, reuse, freeze, verdict-follows-severity) genuinely cannot live in the model, because the model is the source of the drift. Putting them in four small, unit-tested modules is the honest way to make the guarantee real. I wouldn't collapse those.

Where it's worth questioning:

  • Four separate modules vs. fewer. review_identity, review_reuse, review_reconcile, review_triage could arguably be one review_consistency.py. But they have distinct responsibilities and each is independently tested — the separation buys testability, which is worth more than a smaller file count here.

  • The disposition posture is a real, acknowledged tradeoff, not a simplification miss. Accepting dispositions from any non-bot commenter on all severities (including a dismissed critical) widens a prompt-injection surface. The PR mitigates rather than eliminates it (attribution + injection guardrail + kill-switch). A stricter default (bot-author only, non-critical only) would be simpler to defend but weaker on the "human decides" principle. The choice is deliberate and documented in docs/design/review-consistency.md — reasonable, but reviewers should weigh it consciously.

  • The reviewer bot flagged genuine simplicity/safety gaps that should land before merge:

    • _compare_changed_files can flip the freeze from fail-open to fail-toward-suppression when GitHub's compare files array is truncated — the freeze could then silently hide real new findings. That's the most important open issue; it undercuts the whole "never suppress still-broken code" promise.
    • The prompt-block builders (_build_dispositions_block etc.) catch bare Exception and return "". A real coding bug (renamed config key, typo) would be indistinguishable from "feature off" and would silently strip the injection guardrail from every review. Narrowing to OSError/KeyError/ValueError would make a real defect fail loudly.
    • The local git show snippet-fetch branch swallows all errors with no log, so a broken local clone silently degrades to an extra network call each time — mirror the logging the API branch already has.

Net: the architecture is close to the simplest that can actually deliver the guarantee. The remaining work is not "make it simpler" — it's closing the fail-open holes (truncated compare, bare-Exception guardrail strip) so the deterministic promises hold under adverse inputs. Those are exactly what the open CHANGES_REQUESTED review is pointing at.

One thing unclear from the truncated diff: 49 files are omitted, including the core review_runner.py and all the new modules' bodies — I'm reasoning about their behavior from the config comments, docs, spec, and review commentary, not the implementation itself. The freeze's exact fail-open logic (the flagged truncation bug) can't be verified from what's shown.

@atoomic
atoomic merged commit 41bcd96 into main Jul 22, 2026
7 checks passed
@atoomic
atoomic deleted the feat/review-accuracy-and-repo-context branch July 22, 2026 23:31
@Koan-Bot

Copy link
Copy Markdown
Contributor

Simple rebase

Branch feat/review-accuracy-and-repo-context was rebased onto main — no additional changes were needed.

Stats

56 files changed, 5326 insertions(+), 83 deletions(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No commit on main implements spec 010's review consistency/triage/disposition features (snippet vali)
  • Rebased feat/review-accuracy-and-repo-context onto upstream/main
  • Pre-push CI check: previous run passed
  • Force-pushed feat/review-accuracy-and-repo-context to upstream
  • Private review gate passed
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Skuggi

@sukria

sukria commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

@sukria-koan0 explain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants