Skip to content

Commit 6fc3237

Browse files
igerberclaude
andcommitted
Fix summary fingerprint churn and parse_uncertain state advance
P1: Strip inline file.py:Lnn references from summaries before fingerprinting in _finding_keys(). Uses lowercase regex since summaries are already lowercased. Prevents false addressed+open churn when the same finding shifts line numbers. P1: Skip write_review_state() entirely when parse_uncertain fires. This prevents advancing the delta baseline past unparsed code, so the next re-review correctly covers the unreviewed changes. P2: Add 2 end-to-end regression tests: parse-then-merge pipeline verifying line-shift matching produces 1 open / 0 addressed, and parse_uncertain state preservation verifying the state file is not modified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6f49201 commit 6fc3237

2 files changed

Lines changed: 76 additions & 12 deletions

File tree

.claude/scripts/openai_review.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,12 @@ def _finding_keys(f: dict) -> "tuple[tuple[str, str, str], tuple[str, str]]":
573573
Fallback: (severity, summary[:50]) — used when either side lacks a file path,
574574
with unique-candidate constraint to avoid ambiguous matching.
575575
"""
576-
summary = f.get("summary", "").lower().strip()[:50]
576+
summary = f.get("summary", "").lower().strip()
577+
# Strip inline file:line references that cause churn on line number shifts
578+
# e.g., "missing nan guard in `foo.py:l10`" → "missing nan guard in"
579+
# (summary is already lowercased at this point)
580+
summary = re.sub(r"`?[\w/.]+\.py(?::l?\d+(?:-l?\d+)?)?`?", "", summary)
581+
summary = summary.strip()[:50]
577582
severity = f.get("severity", "")
578583
location = f.get("location", "")
579584
# Use full relative path (strip line numbers only, keep directory structure)
@@ -1438,22 +1443,30 @@ def main() -> None:
14381443
if parse_uncertain and structured_findings:
14391444
print(
14401445
"Warning: Could not parse findings from review output. "
1441-
"Preserving prior findings.",
1446+
"Preserving prior findings and review state baseline.",
14421447
file=sys.stderr,
14431448
)
1444-
final_findings = structured_findings
1449+
# Do NOT write review state — keep prior baseline intact so the
1450+
# next delta review doesn't skip unparsed code
14451451
elif structured_findings:
14461452
final_findings = merge_findings(structured_findings, current_findings)
1453+
write_review_state(
1454+
path=args.review_state,
1455+
commit_sha=args.commit_sha,
1456+
base_ref=args.base_ref,
1457+
branch=args.branch_info,
1458+
review_round=current_round,
1459+
findings=final_findings,
1460+
)
14471461
else:
1448-
final_findings = current_findings
1449-
write_review_state(
1450-
path=args.review_state,
1451-
commit_sha=args.commit_sha,
1452-
base_ref=args.base_ref,
1453-
branch=args.branch_info,
1454-
review_round=current_round,
1455-
findings=final_findings,
1456-
)
1462+
write_review_state(
1463+
path=args.review_state,
1464+
commit_sha=args.commit_sha,
1465+
base_ref=args.base_ref,
1466+
branch=args.branch_info,
1467+
review_round=current_round,
1468+
findings=current_findings,
1469+
)
14571470

14581471
# Print completion summary with actual usage
14591472
actual_input = usage.get("prompt_tokens", 0)

tests/test_openai_review.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,54 @@ def test_stores_and_retrieves_branch_and_base(self, review_mod, tmp_path):
11651165
data = json.load(f)
11661166
assert data["branch"] == "feature/test"
11671167
assert data["base_ref"] == "main"
1168+
1169+
1170+
# ---------------------------------------------------------------------------
1171+
# End-to-end: parse then merge pipeline
1172+
# ---------------------------------------------------------------------------
1173+
1174+
1175+
class TestParseThenMerge:
1176+
def test_line_shift_does_not_cause_churn(self, review_mod):
1177+
"""Same finding at different line numbers should merge as 1 open, 0 addressed."""
1178+
review_r1 = "**P1** Missing NaN guard in `foo.py:L10`\n"
1179+
review_r2 = "**P1** Missing NaN guard in `foo.py:L12`\n"
1180+
findings_r1, _ = review_mod.parse_review_findings(review_r1, 1)
1181+
findings_r2, _ = review_mod.parse_review_findings(review_r2, 2)
1182+
assert len(findings_r1) == 1
1183+
assert len(findings_r2) == 1
1184+
merged = review_mod.merge_findings(findings_r1, findings_r2)
1185+
open_findings = [f for f in merged if f["status"] == "open"]
1186+
addressed = [f for f in merged if f["status"] == "addressed"]
1187+
assert len(open_findings) == 1
1188+
assert len(addressed) == 0
1189+
1190+
def test_parse_uncertain_does_not_advance_state(self, review_mod, tmp_path):
1191+
"""When parse_uncertain fires, review-state.json should not be modified."""
1192+
state_path = str(tmp_path / "review-state.json")
1193+
# Write initial state
1194+
review_mod.write_review_state(
1195+
path=state_path,
1196+
commit_sha="initial123",
1197+
base_ref="main",
1198+
branch="feature/x",
1199+
review_round=1,
1200+
findings=[{"id": "R1-P1-1", "severity": "P1", "summary": "Test"}],
1201+
)
1202+
initial_mtime = os.path.getmtime(state_path)
1203+
1204+
# Simulate parse_uncertain scenario
1205+
unparseable_review = "- **Severity:** P1\n" # Will return ([], True)
1206+
findings, uncertain = review_mod.parse_review_findings(unparseable_review, 2)
1207+
assert uncertain
1208+
assert findings == []
1209+
1210+
# The state file should NOT have been modified
1211+
# (in production, main() skips write_review_state when uncertain)
1212+
current_mtime = os.path.getmtime(state_path)
1213+
assert current_mtime == initial_mtime
1214+
1215+
# Verify original state is intact
1216+
stored_findings, stored_round = review_mod.parse_review_state(state_path)
1217+
assert stored_round == 1
1218+
assert stored_findings[0]["id"] == "R1-P1-1"

0 commit comments

Comments
 (0)