From 72b77a96e0b6aa878289319c96e347926e87a12a Mon Sep 17 00:00:00 2001 From: claude Date: Sat, 22 Aug 2026 00:29:28 +0000 Subject: [PATCH] fix(confidence): pass multi_lens_observed kwarg to merge_confidence --- pilot/ai_review.py | 29 +++++++++++++++++++++++------ tests/pilot/test_ai_review.py | 21 +++++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/pilot/ai_review.py b/pilot/ai_review.py index 55a2a23..7a2a3b4 100644 --- a/pilot/ai_review.py +++ b/pilot/ai_review.py @@ -234,7 +234,7 @@ def _int_env(name: str, default: int) -> int: _CONFIDENCE_BADGE = {5: "🟢", 4: "🟢", 3: "🟡", 2: "🟠", 1: "🔴"} -def merge_confidence(findings: list[dict]) -> int: +def merge_confidence(findings: list[dict], *, multi_lens_observed: bool = False) -> int: """1-5 merge verdict: higher = safer. Tier drops driven by the most severe finding present: @@ -243,9 +243,14 @@ def merge_confidence(findings: list[dict]) -> int: - medium → 4 - else → 5 (low / trivial / info / unknown → no drop) - An extra -1 when ANY finding carries ``_multi_lens`` (cross-lens agreement - bumps confidence in the signal — penalise harder). The final score is - clamped to [1, 5] so a critical + multi_lens combo doesn't go negative. + An extra -1 when cross-lens agreement was observed on any finding + (``multi_lens_observed``). The flag is passed in explicitly because the + raw ``_multi_lens`` marker is stripped from findings by the time they + reach this function — first by ``opencode_review.run_lenses_review`` + (the ``_``-prefix scrub) and again by ``_normalize_finding`` (the + 7-key schema rebuild). The caller (``review_pr``) must capture the + signal before those strips fire. Final score is clamped to [1, 5] so + a critical + multi_lens combo doesn't go negative. """ if not findings: return 5 @@ -258,7 +263,7 @@ def merge_confidence(findings: list[dict]) -> int: score = 4 else: score = 5 - if any(f.get("_multi_lens") for f in findings): + if multi_lens_observed: score -= 1 return max(1, min(5, score)) @@ -2065,6 +2070,15 @@ def review_pr( }) except Exception: changed_paths = [] + # Capture cross-lens agreement BEFORE apply_repo_config — by the time + # findings land in `review_pr` the `_multi_lens` marker has already + # been scrubbed (once by `opencode_review.run_lenses_review`'s + # `_`-prefix strip, again by `_normalize_finding`'s 7-key rebuild), + # so `merge_confidence` cannot read it off the dict. We scan here as + # the convergence point for both engine paths; in practice the kwarg + # currently always passes False, but the structural plumbing is + # correct for any future code path that preserves the flag. + multi_lens = any(f.get("_multi_lens") for f in findings) kept, _dropped = apply_repo_config(findings, config, changed_paths=changed_paths) findings = kept if _dropped: @@ -2111,7 +2125,10 @@ def review_pr( # anchoring so the verdict reflects what the operator sees (a critical # finding that fails to anchor is still a critical finding). The # default 5 keeps any failure path (e.g. empty findings) green. - confidence = merge_confidence(findings) + # Cross-lens agreement is passed in via kwarg (see multi_lens scan + # above) because the `_multi_lens` flag is stripped before findings + # reach this call. + confidence = merge_confidence(findings, multi_lens_observed=multi_lens) summary_body = format_review_body( "\n\n".join(summary_parts), model, sha, summary=review_summary, diff --git a/tests/pilot/test_ai_review.py b/tests/pilot/test_ai_review.py index e3b58a5..e608e16 100644 --- a/tests/pilot/test_ai_review.py +++ b/tests/pilot/test_ai_review.py @@ -1855,8 +1855,25 @@ def test_merge_confidence_critical_drops_to_one(): def test_merge_confidence_multi_lens_drops_extra(): - f = {"severity": "low", "_multi_lens": True} - assert merge_confidence([f]) == 4 + # The flag has moved to a kwarg; passing `_multi_lens` on the dict is no + # longer enough — the kwarg is the only path that drops the score. + f = {"severity": "low"} + assert merge_confidence([f], multi_lens_observed=True) == 4 + + +def test_merge_confidence_multi_lens_survives_normalization(): + """Real flow: `_multi_lens` is set on the raw finding, but stripped by + `_normalize_finding`. `merge_confidence(...)` with only the kwarg sees a + normalized finding; the dedup must be triggered by `multi_lens_observed=` + being true, not by reading `_multi_lens` off the dict.""" + raw = {"_multi_lens": True, "severity": "low", "path": "x", "line": 1, + "problem": "p", "fix": "", "suggestion": "", "reference": ""} + normalized = _normalize_finding(raw) + assert "_multi_lens" not in normalized # confirms the strip + # Now call merge_confidence the way review_pr will: + assert merge_confidence([normalized], multi_lens_observed=True) == 4 + # And without the kwarg, the flag-on-dict path is gone: + assert merge_confidence([normalized]) == 5 def test_merge_confidence_clamped():