import assert from "node:assert/strict"; import { test } from "node:test"; import { dismissReviewFindings, reconcileReviewReport, reviewReportToPresentation, validateReviewFindingLocations, type ReviewReportInput, } from "./review-report.js"; function report(line: number): ReviewReportInput { return { mode: "deep", summary: "Found one correctness issue.", findings: [ { identity: "retry-loop:exhaustion-drops-final-error", title: "Retry loop drops the final error", claim: "The exhausted retry path returns undefined instead of throwing the last error.", impact: "Callers may treat a failed operation as successful.", evidence: "The loop exits after attempt === maxAttempts without a throw or return.", severity: "high", category: "correctness", confidence: 0.96, location: { file: "src/retry.ts", line, side: "new" }, }, ], checks: [], coverage: { reviewedFiles: ["src/retry.ts"], skipped: [] }, }; } test("reconcileReviewReport keeps finding identity when its line moves", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const second = reconcileReviewReport(report(24), first.findings, "2026-07-26T11:00:00.000Z"); assert.equal(second.findings.length, 1); assert.equal(second.findings[0]!.id, first.findings[0]!.id); assert.equal(second.findings[0]!.fingerprint, first.findings[0]!.fingerprint); assert.equal(second.findings[0]!.location.line, 24); assert.equal(second.findings[0]!.firstSeenAt, "2026-07-26T10:00:00.000Z"); assert.equal(second.findings[0]!.lastSeenAt, "2026-07-26T11:00:00.000Z"); }); test("reconcileReviewReport keeps finding identity across minor reviewer rewording", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const rephrased = report(23); rephrased.findings[0] = { ...rephrased.findings[0]!, title: "Final retry failure is discarded", claim: "After retry exhaustion, the function returns undefined rather than throwing the final error.", }; const second = reconcileReviewReport(rephrased, first.findings, "2026-07-26T11:00:00.000Z"); assert.equal(second.findings[0]!.id, first.findings[0]!.id); assert.equal(second.findings[0]!.fingerprint, first.findings[0]!.fingerprint); }); test("deep review marks previously open findings fixed when they are no longer reported", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const clean: ReviewReportInput = { ...report(20), summary: "The prior issue is fixed.", findings: [], }; const second = reconcileReviewReport(clean, first.findings, "2026-07-26T11:00:00.000Z"); assert.equal(second.findings.length, 1); assert.equal(second.findings[0]!.status, "fixed"); assert.equal(second.findings[0]!.lastSeenAt, "2026-07-26T10:00:00.000Z"); }); test("deep review preserves omitted findings in files it explicitly skipped", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const partial: ReviewReportInput = { ...report(20), summary: "Deep review skipped generated retry code.", findings: [], coverage: { reviewedFiles: ["src/other.ts"], skipped: [{ file: "src/retry.ts", reason: "generated" }], }, }; const second = reconcileReviewReport(partial, first.findings, "2026-07-26T11:00:00.000Z"); assert.equal(second.findings[0]!.status, "open"); }); test("quick review preserves previously open findings outside its reported coverage", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const quick: ReviewReportInput = { ...report(20), mode: "quick", summary: "Quick scan found nothing new.", findings: [], coverage: { reviewedFiles: [], skipped: [{ file: "src/retry.ts", reason: "outside quick scope" }] }, }; const second = reconcileReviewReport(quick, first.findings, "2026-07-26T11:00:00.000Z"); assert.equal(second.findings.length, 1); assert.equal(second.findings[0]!.status, "open"); }); test("reconcileReviewReport rejects findings without admissible evidence", () => { const invalid = report(20); invalid.findings[0] = { ...invalid.findings[0]!, evidence: " ", confidence: 1.2, }; assert.throws( () => reconcileReviewReport(invalid, []), /error.*finding 1.*evidence.*confidence/si, ); }); test("reconcileReviewReport rejects duplicate stable finding identities", () => { const duplicate = report(20); duplicate.findings.push({ ...duplicate.findings[0]!, title: "Same issue phrased twice", location: { ...duplicate.findings[0]!.location, line: 21 }, }); assert.throws( () => reconcileReviewReport(duplicate, []), /duplicate finding identity/i, ); }); test("reviewReportToPresentation exposes active findings and review coverage", () => { const current = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const clean = reconcileReviewReport( { ...report(20), findings: [], summary: "Fixed", mode: "deep" }, current.findings, "2026-07-26T11:00:00.000Z", ); const next = reconcileReviewReport( { ...report(20), mode: "quick", summary: "New scan", findings: [ { ...report(20).findings[0]!, title: "Another issue", claim: "A second issue remains.", }, ], checks: [{ command: "npm test", status: "passed", summary: "27 tests passed" }], coverage: { reviewedFiles: ["src/retry.ts"], skipped: [{ file: "docs/a.md", reason: "docs-only" }] }, }, clean.findings, "2026-07-26T12:00:00.000Z", ); const presentation = reviewReportToPresentation(next, [ { sha: null, label: "working", subject: "Uncommitted changes" }, ]); assert.equal(presentation.comments.length, 1); assert.equal(presentation.comments[0]!.title, "Another issue"); assert.match(presentation.comments[0]!.body, /second issue/); assert.equal(presentation.comments[0]!.impact, "Callers may treat a failed operation as successful."); assert.equal(presentation.summaries[0]!.title, "Quick review"); assert.match(presentation.summaries.map((item) => item.detail).join("\n"), /npm test.*27 tests passed/s); assert.match(presentation.summaries.map((item) => item.detail).join("\n"), /docs\/a\.md.*docs-only/s); }); test("dismissed findings stay dismissed when the reviewer reports them again", () => { const first = reconcileReviewReport(report(20), [], "2026-07-26T10:00:00.000Z"); const dismissed = dismissReviewFindings(first, [first.findings[0]!.id]); const repeated = reconcileReviewReport(report(21), dismissed.findings, "2026-07-26T11:00:00.000Z"); assert.equal(dismissed.findings[0]!.status, "dismissed"); assert.equal(repeated.findings[0]!.status, "dismissed"); assert.equal(reviewReportToPresentation(repeated, []).comments.length, 0); }); test("validateReviewFindingLocations rejects anchors that are not in the diff", async () => { const diff = [ "diff --git a/src/retry.ts b/src/retry.ts", "--- a/src/retry.ts", "+++ b/src/retry.ts", "@@ -19,2 +19,2 @@", "-return undefined;", "+throw lastError;", ].join("\n"); const valid = reconcileReviewReport(report(19), [], "2026-07-26T10:00:00.000Z"); const invalid = { ...valid, findings: [{ ...valid.findings[0]!, location: { ...valid.findings[0]!.location, line: 99 } }] }; const items = [{ sha: null, label: "working", subject: "Uncommitted changes" }]; assert.deepEqual(await validateReviewFindingLocations(valid, items, async () => diff), []); assert.match( (await validateReviewFindingLocations(invalid, items, async () => diff)).join("\n"), /src\/retry\.ts:99.*not present in the diff/, ); });