Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/review/unified-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ export interface ExtractedReviewSummary {
consensusBlocker: boolean;
}

/** Case-insensitive de-dup of concern lines (two reviewers often raise the same point). Preserves first wording. */
/** Case-insensitive de-dup of concern lines (two reviewers often raise the same point). Preserves first wording.
* Uncapped, mirroring dedupeLines (#10010): callers pass the FULL deduped set to truncateFindingsForDisplay,
* which is the disclosed-truncation stage -- capping here instead silently dropped items 21+ from the
* AI-context block and understated the "+N more" footer. */
function dedupeConcerns(items: string[]): string[] {
const seen = new Set<string>();
const out: string[] = [];
Expand All @@ -129,7 +132,7 @@ function dedupeConcerns(items: string[]): string[] {
seen.add(key);
out.push(t);
}
return out.slice(0, 20);
return out;
}

function extractReviewSummary(reviews: DualReviewNote[]): ExtractedReviewSummary {
Expand Down
61 changes: 61 additions & 0 deletions test/unit/unified-comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,67 @@ describe("review.max_findings display caps (#2049)", () => {
});
});

describe("dedupeConcerns un-capped (#10010)", () => {
// dedupeConcerns (the extraction-layer dedupe one layer above dedupeLines) used to hard-cap at 20 with
// `slice(0, 20)` -- an UNDISCLOSED cap, unlike the disclosed truncation at render time. That silently dropped
// blockers/nits 21+ before they ever reached truncateFindingsForDisplay, so the "+N more" footer, the AI-context
// block, and the Nits sub-label all understated the real count. #9670 fixed the same defect one layer down in
// dedupeLines; this closes it here too.
const thirtyBlockers = Array.from({ length: 30 }, (_, i) => `blocker ${i + 1}`);
const thirtyNits = Array.from({ length: 30 }, (_, i) => `nit ${i + 1}`);

it("buildUnifiedReviewInput carries all 30 distinct blockers and all 30 distinct nits, not capped at 20", () => {
const input = buildUnifiedReviewInput({
changedFiles: 1,
reviews: [reviewNote("close", { blockers: thirtyBlockers, nits: thirtyNits })],
});
expect(input.blockers).toHaveLength(30);
expect(input.nits).toHaveLength(30);
});

it("renders exactly 12 blocker bullets with the true '+18 more' footer for 30 distinct blockers", () => {
const input = buildUnifiedReviewInput({ changedFiles: 1, reviews: [reviewNote("close", { blockers: thirtyBlockers })] });
const md = renderUnifiedReviewComment(input);
const humanSection = md.split("📋 Copy for AI agents")[0]!;
expect(humanSection.match(/- blocker \d+/g)).toHaveLength(12);
expect(md).toContain("- blocker 12");
expect(humanSection).not.toContain("- blocker 13");
expect(md).toContain("_+18 more_");
expect(md).not.toContain("_+8 more_"); // the old, wrongly-capped-at-20 hidden count
});

it("the copy-for-AI-agents block carries every one of the 30 blockers, not just the display-capped 12", () => {
const input = buildUnifiedReviewInput({ changedFiles: 1, reviews: [reviewNote("close", { blockers: thirtyBlockers })] });
const md = renderUnifiedReviewComment(input);
const aiSection = md.split("📋 Copy for AI agents")[1]!;
expect(aiSection).toContain("1. blocker 1");
expect(aiSection).toContain("30. blocker 30");
});

it("the Nits collapsible sub-label reports the true deduped count of 30, not 20", () => {
const input = buildUnifiedReviewInput({ changedFiles: 1, reviews: [reviewNote("merge", { nits: thirtyNits })] });
const md = renderUnifiedReviewComment(input);
expect(md).toContain("30 non-blocking");
expect(md).not.toContain("20 non-blocking");
});

it("does not silently drop concerns past 20 before the disclosed truncation stage (#9670 follow-up)", () => {
const twentyFiveBlockers = Array.from({ length: 25 }, (_, i) => `blocker ${i + 1}`);
// A display cap equal to the item count (not null -- null is byte-identical to "unset", i.e. the 12-item
// default, per the maxFindingsCaps doc comment) puts truncateFindingsForDisplay on its `items.length <= cap`
// arm: display truncation never kicks in, so a missing bullet here can only be dedupeConcerns' old cap.
const input = buildUnifiedReviewInput({
changedFiles: 1,
reviews: [reviewNote("close", { blockers: twentyFiveBlockers })],
maxFindingsCaps: { blockers: 25, nits: null },
});
const md = renderUnifiedReviewComment(input);
expect(input.blockers).toHaveLength(25);
for (const line of twentyFiveBlockers) expect(md).toContain(`- ${line}`);
expect(md).not.toMatch(/_\+\d+ more_/);
});
});

describe("review.comment_verbosity (#2047)", () => {
const input: UnifiedReviewInput = {
...base,
Expand Down