import type { PullRequestCheck, PullRequestComment, PullRequestDetailView, PullRequestReviewThread,} from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { buildAskAboutLinesHandoff, buildAskAboutPullRequestHandoff, buildExplainPullRequestHandoff, buildFixFindingHandoff, buildFixFindingsHandoff, groupPullRequestTimelineConversations, handoffPrompt, handoffReviewComments, orderPullRequestComments, pullRequestFindingKey, readableFailure, buildPullRequestTimeline, describePullRequestState,} from "./pullRequestDetail.logic";import type { ReviewCommentContext } from "~/reviewCommentContext"; const TIMELINE_SOURCE: Pick< PullRequestDetailView, "createdAt" | "author" | "commits" | "comments" | "mergedAt" | "closedAt"> = { createdAt: "2026-07-01T00:00:00Z", author: { login: "octocat", name: null, avatarUrl: null }, commits: [ { oid: "1baf7bdcafe", messageHeadline: "add the page", committedDate: "2026-07-02T00:00:00Z" }, ], comments: [ { id: "c1", kind: "issue-comment", author: { login: "bilal", name: null, avatarUrl: null }, body: "looks good", createdAt: "2026-07-03T00:00:00Z", url: null, path: null, reviewState: null, }, ], mergedAt: null, closedAt: null,}; describe("pull request state description", () => { it("keeps draft and conflicts orthogonal to the terminal states", () => { expect(describePullRequestState("open", true)).toBe("Draft"); expect(describePullRequestState("open", false)).toBe("Ready for review"); expect(describePullRequestState("merged", true)).toBe("Merged"); expect(describePullRequestState("closed", false)).toBe("Closed"); });}); describe("ordering comments", () => { it("reverses the chronological list for newest first, and leaves oldest first alone", () => { const comments = [{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]; expect(orderPullRequestComments(comments, "newest")).toEqual([ { createdAt: "c" }, { createdAt: "b" }, { createdAt: "a" }, ]); expect(orderPullRequestComments(comments, "oldest")).toEqual(comments); // The source array is chronological input, not a mutation target. expect(comments).toEqual([{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]); });}); describe("pull request timeline", () => { it("orders creation, commits and comments newest first", () => { // What happened last is what the reader opening the tab is asking about. expect(buildPullRequestTimeline(TIMELINE_SOURCE).map((event) => event.id)).toEqual([ "c1", "1baf7bdcafe", "created", ]); }); it("carries the comment url, and leaves the events the host cannot address without one", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [{ ...TIMELINE_SOURCE.comments[0]!, url: "https://example.test/pull/1#c1" }], }); expect(events.map((event) => event.url)).toEqual([ "https://example.test/pull/1#c1", null, null, ]); }); it("carries actors and review context into the presentation model", () => { const commitAuthors = [ { login: "octocat", name: null, avatarUrl: "https://example.test/octocat.png" }, { login: "pair", name: "Pair Author", avatarUrl: null }, ]; const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, authors: commitAuthors }], comments: [ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", path: "src/app.ts", reviewState: "APPROVED", }, ], }); expect(events.find((event) => event.kind === "commit")?.commitAuthors).toEqual(commitAuthors); expect(events.find((event) => event.kind === "review")).toMatchObject({ actor: TIMELINE_SOURCE.comments[0]?.author, path: "src/app.ts", reviewState: "APPROVED", }); }); it("carries each commit's line counts into its timeline event", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, additions: 12, deletions: 4 }], }); expect(events.find((event) => event.kind === "commit")).toMatchObject({ additions: 12, deletions: 4, }); }); it("drops a body that is nothing but a bot's HTML comment, and keeps one that says more", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, body: "<!-- MURMUR_IGNORE -->" }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", body: "<!-- summarize by coderabbit.ai -->\nNeeds a test.", createdAt: "2026-07-04T00:00:00Z", }, ], }); expect(events.find((event) => event.id === "c1")?.body).toBeNull(); // Kept whole: the renderer drops the marker itself, and stripping it here would also // strip an HTML comment a reviewer quoted inside a code fence. expect(events.find((event) => event.id === "c2")?.body).toBe( "<!-- summarize by coderabbit.ai -->\nNeeds a test.", ); }); it("calls a comment markdown and a commit headline plain text", () => { const events = buildPullRequestTimeline(TIMELINE_SOURCE); // A headline reading `fix: drop *legacy* path` is not asking for emphasis. expect(events.map((event) => [event.title.startsWith("Commit"), event.markdown])).toEqual( expect.arrayContaining([[true, false]]), ); expect(events.find((event) => event.id === "c1")?.markdown).toBe(true); }); it("reports a merge rather than the close GitHub records alongside it", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, mergedAt: "2026-07-04T00:00:00Z", closedAt: "2026-07-04T00:00:00Z", }); // Newest first, so the terminal event opens the list rather than ending it. expect(events[0]?.id).toBe("merged"); expect(events.some((event) => event.id === "closed")).toBe(false); }); it("groups conversation sections without crossing commits or PR updates", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-1", createdAt: "2026-07-04T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-2", createdAt: "2026-07-03T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "old-comment-1", createdAt: "2026-07-01T12:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "old-comment-2", createdAt: "2026-07-01T06:00:00Z", }, ], mergedAt: "2026-07-05T00:00:00Z", }); const rows = groupPullRequestTimelineConversations(events); expect( rows.map((row) => row.kind === "comments" ? [row.kind, ...row.events.map((event) => event.id)] : [row.kind, row.event.id], ), ).toEqual([ ["event", "merged"], ["comments", "new-comment-1", "new-comment-2"], ["event", "1baf7bdcafe"], ["comments", "old-comment-1", "old-comment-2"], ["event", "created"], ]); });}); describe("fix findings handoff", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", comments: [] as ReadonlyArray<PullRequestComment>, commentsTruncated: false, }; function thread( body: string, overrides: Partial<PullRequestReviewThread> = {}, ): PullRequestReviewThread { return { id: "t1", path: "apps/web/src/page.tsx", line: 12, side: "right", isResolved: false, isOutdated: false, comments: [ { id: "tc1", author: { login: "reviewer", name: null, avatarUrl: null }, body, createdAt: "2026-07-03T00:00:00Z", url: null, }, ], ...overrides, }; } const failingCheck: PullRequestCheck = { name: "typecheck", status: "failure", description: "2 errors", url: null, }; it("attaches a review thread as an annotation instead of quoting it in the prompt", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [thread("rename the helper")], checks: [], }); expect(handoff.reviewComments).toEqual([ expect.objectContaining({ filePath: "apps/web/src/page.tsx", rangeLabel: "L12", startIndex: 11, endIndex: 11, text: "reviewer: rename the helper", }), ]); expect(handoff.prompt).not.toContain("rename the helper"); expect(handoff.prompt).toContain("untrusted data"); }); it("names the pre-change side, and a thread the host pinned to the file rather than a line", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ thread("was this deleted on purpose?", { side: "left" }), thread("wrong module", { id: "t2", line: null }), ], checks: [], }); expect(handoff.reviewComments.map((comment) => comment.rangeLabel)).toEqual([ "L12 (before)", "file", ]); }); it("keeps failing checks in the prompt, having no line to attach them to", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [], checks: [failingCheck], }); expect(handoff.prompt).toContain("> typecheck — 2 errors"); expect(handoff.reviewComments).toEqual([]); }); it("leaves out a resolved conversation, and one nobody wrote in", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ thread("already handled", { isResolved: true }), thread(" ", { id: "t2" }), thread("still open", { id: "t3" }), ], checks: [], }); expect(handoff.reviewComments.map((comment) => comment.text)).toEqual(["reviewer: still open"]); }); it("says so plainly when there is nothing actionable", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [], checks: [] }); expect(handoff.prompt).toContain("No unresolved review findings were returned"); expect(handoff.reviewComments).toEqual([]); }); it("bounds a hostile review body instead of attaching it whole", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [thread("x".repeat(5_000))], checks: [], }); expect(handoff.reviewComments[0]?.text).toHaveLength(1_000); expect(handoff.reviewComments[0]?.text.endsWith("...")).toBe(true); }); it("keeps the newest threads and the failing checks when it has to cut", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: Array.from({ length: 25 }, (_, index) => thread(`finding ${index}`, { id: `t${index}` }), ), checks: [failingCheck], }); // Oldest threads are dropped rather than the current failure and the recent feedback. const texts = handoff.reviewComments.map((comment) => comment.text); expect(texts).toHaveLength(19); expect(texts.at(-1)).toBe("reviewer: finding 24"); expect(texts).not.toContain("reviewer: finding 0"); expect(handoff.prompt).toContain("typecheck"); expect(handoff.prompt).toContain("6 further findings were omitted"); });}); describe("findings that cannot be attached", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", reviewThreads: [] as ReadonlyArray<PullRequestReviewThread>, checks: [] as ReadonlyArray<PullRequestCheck>, commentsTruncated: false, }; const review: PullRequestComment = { id: "r1", kind: "review", author: { login: "julius", name: null, avatarUrl: null }, body: "This breaks SSO auth, revert the middleware change.", createdAt: "2026-07-01T00:00:00Z", url: null, path: null, reviewState: "CHANGES_REQUESTED", }; it("carries a review submitted with words but no line, which has nothing to attach to", () => { const handoff = buildFixFindingsHandoff({ ...base, comments: [review] }); // It has no file and no line, so it travels the way a failing check does rather than // being dropped for lacking somewhere to point. expect(handoff.reviewComments).toEqual([]); expect(handoff.prompt).toContain("revert the middleware change"); expect(handoff.prompt).not.toContain("No unresolved review findings"); }); it("carries a host's line comments when it reports no threads at all", () => { // Azure DevOps has no diff to pin a conversation to, so every remark arrives this way. const handoff = buildFixFindingsHandoff({ ...base, comments: [{ ...review, id: "a1", kind: "review-comment", path: "src/app.ts" }], }); expect(handoff.prompt).toContain("src/app.ts"); expect(handoff.prompt).toContain("revert the middleware change"); }); it("does not repeat a remark that was already attached as a thread", () => { const attachedId = "t1c1"; const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ { id: "t1", path: "src/app.ts", line: 12, side: "right", isResolved: false, isOutdated: false, comments: [ { id: attachedId, author: { login: "julius", name: null, avatarUrl: null }, body: "rename the helper", createdAt: "2026-07-01T00:00:00Z", url: null, }, ], }, ], comments: [{ ...review, id: attachedId, kind: "review-comment", body: "rename the helper" }], }); expect(handoff.reviewComments).toHaveLength(1); expect(handoff.prompt).not.toContain("rename the helper"); });}); describe("one finding handed over on its own", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", }; const reviewThread: PullRequestReviewThread = { id: "t1", path: "apps/web/src/page.tsx", line: 12, side: "right", isResolved: true, isOutdated: false, comments: [ { id: "tc1", author: { login: "reviewer", name: null, avatarUrl: null }, body: "rename the helper", createdAt: "2026-07-03T00:00:00Z", url: null, }, ], }; it("attaches a thread as its own annotation, resolved or not", () => { // The bulk handoff skips resolved threads as finished work. Pressing the button on one is // an explicit request for that thread, so it is not second-guessed. const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "thread", thread: reviewThread }, }); expect(handoff.reviewComments).toEqual([ expect.objectContaining({ filePath: "apps/web/src/page.tsx", rangeLabel: "L12" }), ]); expect(handoff.prompt).toContain("attached to this message"); expect(handoff.prompt).not.toContain("rename the helper"); }); it("quotes a review remark, which has no line to attach it to", () => { const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "comment", comment: { id: "c1", kind: "review", author: { login: "julius", name: null, avatarUrl: null }, body: "this breaks SSO auth", createdAt: "2026-07-01T00:00:00Z", url: null, path: "apps/server/src/auth.ts", reviewState: "CHANGES_REQUESTED", }, }, }); expect(handoff.reviewComments).toEqual([]); expect(handoff.prompt).toContain("> julius on `apps/server/src/auth.ts`: this breaks SSO auth"); }); it("quotes a failing check with what the host reported about it", () => { const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "check", check: { name: "typecheck", status: "failure", description: "2 errors", url: null }, }, }); expect(handoff.prompt).toContain("> typecheck — 2 errors"); expect(handoff.prompt).toContain("Reproduce it locally first"); });Lines 1–500 of 789. Load more
import type { PullRequestCheck, PullRequestComment, PullRequestDetailView, PullRequestReviewThread, } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { buildAskAboutLinesHandoff, buildAskAboutPullRequestHandoff, buildExplainPullRequestHandoff, buildFixFindingHandoff, buildFixFindingsHandoff, groupPullRequestTimelineConversations, handoffPrompt, handoffReviewComments, orderPullRequestComments, pullRequestFindingKey, readableFailure, buildPullRequestTimeline, describePullRequestState, } from "./pullRequestDetail.logic"; import type { ReviewCommentContext } from "~/reviewCommentContext"; const TIMELINE_SOURCE: Pick< PullRequestDetailView, "createdAt" | "author" | "commits" | "comments" | "mergedAt" | "closedAt" > = { createdAt: "2026-07-01T00:00:00Z", author: { login: "octocat", name: null, avatarUrl: null }, commits: [ { oid: "1baf7bdcafe", messageHeadline: "add the page", committedDate: "2026-07-02T00:00:00Z" }, ], comments: [ { id: "c1", kind: "issue-comment", author: { login: "bilal", name: null, avatarUrl: null }, body: "looks good", createdAt: "2026-07-03T00:00:00Z", url: null, path: null, reviewState: null, }, ], mergedAt: null, closedAt: null, }; describe("pull request state description", () => { it("keeps draft and conflicts orthogonal to the terminal states", () => { expect(describePullRequestState("open", true)).toBe("Draft"); expect(describePullRequestState("open", false)).toBe("Ready for review"); expect(describePullRequestState("merged", true)).toBe("Merged"); expect(describePullRequestState("closed", false)).toBe("Closed"); }); }); describe("ordering comments", () => { it("reverses the chronological list for newest first, and leaves oldest first alone", () => { const comments = [{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]; expect(orderPullRequestComments(comments, "newest")).toEqual([ { createdAt: "c" }, { createdAt: "b" }, { createdAt: "a" }, ]); expect(orderPullRequestComments(comments, "oldest")).toEqual(comments); // The source array is chronological input, not a mutation target. expect(comments).toEqual([{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }]); }); }); describe("pull request timeline", () => { it("orders creation, commits and comments newest first", () => { // What happened last is what the reader opening the tab is asking about. expect(buildPullRequestTimeline(TIMELINE_SOURCE).map((event) => event.id)).toEqual([ "c1", "1baf7bdcafe", "created", ]); }); it("carries the comment url, and leaves the events the host cannot address without one", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [{ ...TIMELINE_SOURCE.comments[0]!, url: "https://example.test/pull/1#c1" }], }); expect(events.map((event) => event.url)).toEqual([ "https://example.test/pull/1#c1", null, null, ]); }); it("carries actors and review context into the presentation model", () => { const commitAuthors = [ { login: "octocat", name: null, avatarUrl: "https://example.test/octocat.png" }, { login: "pair", name: "Pair Author", avatarUrl: null }, ]; const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, authors: commitAuthors }], comments: [ { ...TIMELINE_SOURCE.comments[0]!, kind: "review", path: "src/app.ts", reviewState: "APPROVED", }, ], }); expect(events.find((event) => event.kind === "commit")?.commitAuthors).toEqual(commitAuthors); expect(events.find((event) => event.kind === "review")).toMatchObject({ actor: TIMELINE_SOURCE.comments[0]?.author, path: "src/app.ts", reviewState: "APPROVED", }); }); it("carries each commit's line counts into its timeline event", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, commits: [{ ...TIMELINE_SOURCE.commits[0]!, additions: 12, deletions: 4 }], }); expect(events.find((event) => event.kind === "commit")).toMatchObject({ additions: 12, deletions: 4, }); }); it("drops a body that is nothing but a bot's HTML comment, and keeps one that says more", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, body: "<!-- MURMUR_IGNORE -->" }, { ...TIMELINE_SOURCE.comments[0]!, id: "c2", body: "<!-- summarize by coderabbit.ai -->\nNeeds a test.", createdAt: "2026-07-04T00:00:00Z", }, ], }); expect(events.find((event) => event.id === "c1")?.body).toBeNull(); // Kept whole: the renderer drops the marker itself, and stripping it here would also // strip an HTML comment a reviewer quoted inside a code fence. expect(events.find((event) => event.id === "c2")?.body).toBe( "<!-- summarize by coderabbit.ai -->\nNeeds a test.", ); }); it("calls a comment markdown and a commit headline plain text", () => { const events = buildPullRequestTimeline(TIMELINE_SOURCE); // A headline reading `fix: drop *legacy* path` is not asking for emphasis. expect(events.map((event) => [event.title.startsWith("Commit"), event.markdown])).toEqual( expect.arrayContaining([[true, false]]), ); expect(events.find((event) => event.id === "c1")?.markdown).toBe(true); }); it("reports a merge rather than the close GitHub records alongside it", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, mergedAt: "2026-07-04T00:00:00Z", closedAt: "2026-07-04T00:00:00Z", }); // Newest first, so the terminal event opens the list rather than ending it. expect(events[0]?.id).toBe("merged"); expect(events.some((event) => event.id === "closed")).toBe(false); }); it("groups conversation sections without crossing commits or PR updates", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, comments: [ { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-1", createdAt: "2026-07-04T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "new-comment-2", createdAt: "2026-07-03T00:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "old-comment-1", createdAt: "2026-07-01T12:00:00Z", }, { ...TIMELINE_SOURCE.comments[0]!, id: "old-comment-2", createdAt: "2026-07-01T06:00:00Z", }, ], mergedAt: "2026-07-05T00:00:00Z", }); const rows = groupPullRequestTimelineConversations(events); expect( rows.map((row) => row.kind === "comments" ? [row.kind, ...row.events.map((event) => event.id)] : [row.kind, row.event.id], ), ).toEqual([ ["event", "merged"], ["comments", "new-comment-1", "new-comment-2"], ["event", "1baf7bdcafe"], ["comments", "old-comment-1", "old-comment-2"], ["event", "created"], ]); }); }); describe("fix findings handoff", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", comments: [] as ReadonlyArray<PullRequestComment>, commentsTruncated: false, }; function thread( body: string, overrides: Partial<PullRequestReviewThread> = {}, ): PullRequestReviewThread { return { id: "t1", path: "apps/web/src/page.tsx", line: 12, side: "right", isResolved: false, isOutdated: false, comments: [ { id: "tc1", author: { login: "reviewer", name: null, avatarUrl: null }, body, createdAt: "2026-07-03T00:00:00Z", url: null, }, ], ...overrides, }; } const failingCheck: PullRequestCheck = { name: "typecheck", status: "failure", description: "2 errors", url: null, }; it("attaches a review thread as an annotation instead of quoting it in the prompt", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [thread("rename the helper")], checks: [], }); expect(handoff.reviewComments).toEqual([ expect.objectContaining({ filePath: "apps/web/src/page.tsx", rangeLabel: "L12", startIndex: 11, endIndex: 11, text: "reviewer: rename the helper", }), ]); expect(handoff.prompt).not.toContain("rename the helper"); expect(handoff.prompt).toContain("untrusted data"); }); it("names the pre-change side, and a thread the host pinned to the file rather than a line", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ thread("was this deleted on purpose?", { side: "left" }), thread("wrong module", { id: "t2", line: null }), ], checks: [], }); expect(handoff.reviewComments.map((comment) => comment.rangeLabel)).toEqual([ "L12 (before)", "file", ]); }); it("keeps failing checks in the prompt, having no line to attach them to", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [], checks: [failingCheck], }); expect(handoff.prompt).toContain("> typecheck — 2 errors"); expect(handoff.reviewComments).toEqual([]); }); it("leaves out a resolved conversation, and one nobody wrote in", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ thread("already handled", { isResolved: true }), thread(" ", { id: "t2" }), thread("still open", { id: "t3" }), ], checks: [], }); expect(handoff.reviewComments.map((comment) => comment.text)).toEqual(["reviewer: still open"]); }); it("says so plainly when there is nothing actionable", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [], checks: [] }); expect(handoff.prompt).toContain("No unresolved review findings were returned"); expect(handoff.reviewComments).toEqual([]); }); it("bounds a hostile review body instead of attaching it whole", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [thread("x".repeat(5_000))], checks: [], }); expect(handoff.reviewComments[0]?.text).toHaveLength(1_000); expect(handoff.reviewComments[0]?.text.endsWith("...")).toBe(true); }); it("keeps the newest threads and the failing checks when it has to cut", () => { const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: Array.from({ length: 25 }, (_, index) => thread(`finding ${index}`, { id: `t${index}` }), ), checks: [failingCheck], }); // Oldest threads are dropped rather than the current failure and the recent feedback. const texts = handoff.reviewComments.map((comment) => comment.text); expect(texts).toHaveLength(19); expect(texts.at(-1)).toBe("reviewer: finding 24"); expect(texts).not.toContain("reviewer: finding 0"); expect(handoff.prompt).toContain("typecheck"); expect(handoff.prompt).toContain("6 further findings were omitted"); }); }); describe("findings that cannot be attached", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", reviewThreads: [] as ReadonlyArray<PullRequestReviewThread>, checks: [] as ReadonlyArray<PullRequestCheck>, commentsTruncated: false, }; const review: PullRequestComment = { id: "r1", kind: "review", author: { login: "julius", name: null, avatarUrl: null }, body: "This breaks SSO auth, revert the middleware change.", createdAt: "2026-07-01T00:00:00Z", url: null, path: null, reviewState: "CHANGES_REQUESTED", }; it("carries a review submitted with words but no line, which has nothing to attach to", () => { const handoff = buildFixFindingsHandoff({ ...base, comments: [review] }); // It has no file and no line, so it travels the way a failing check does rather than // being dropped for lacking somewhere to point. expect(handoff.reviewComments).toEqual([]); expect(handoff.prompt).toContain("revert the middleware change"); expect(handoff.prompt).not.toContain("No unresolved review findings"); }); it("carries a host's line comments when it reports no threads at all", () => { // Azure DevOps has no diff to pin a conversation to, so every remark arrives this way. const handoff = buildFixFindingsHandoff({ ...base, comments: [{ ...review, id: "a1", kind: "review-comment", path: "src/app.ts" }], }); expect(handoff.prompt).toContain("src/app.ts"); expect(handoff.prompt).toContain("revert the middleware change"); }); it("does not repeat a remark that was already attached as a thread", () => { const attachedId = "t1c1"; const handoff = buildFixFindingsHandoff({ ...base, reviewThreads: [ { id: "t1", path: "src/app.ts", line: 12, side: "right", isResolved: false, isOutdated: false, comments: [ { id: attachedId, author: { login: "julius", name: null, avatarUrl: null }, body: "rename the helper", createdAt: "2026-07-01T00:00:00Z", url: null, }, ], }, ], comments: [{ ...review, id: attachedId, kind: "review-comment", body: "rename the helper" }], }); expect(handoff.reviewComments).toHaveLength(1); expect(handoff.prompt).not.toContain("rename the helper"); }); }); describe("one finding handed over on its own", () => { const base = { number: 42, title: "Add the pull requests page", url: "https://github.com/pingdotgg/t3code/pull/42", headBranch: "feat/page", baseBranch: "main", }; const reviewThread: PullRequestReviewThread = { id: "t1", path: "apps/web/src/page.tsx", line: 12, side: "right", isResolved: true, isOutdated: false, comments: [ { id: "tc1", author: { login: "reviewer", name: null, avatarUrl: null }, body: "rename the helper", createdAt: "2026-07-03T00:00:00Z", url: null, }, ], }; it("attaches a thread as its own annotation, resolved or not", () => { // The bulk handoff skips resolved threads as finished work. Pressing the button on one is // an explicit request for that thread, so it is not second-guessed. const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "thread", thread: reviewThread }, }); expect(handoff.reviewComments).toEqual([ expect.objectContaining({ filePath: "apps/web/src/page.tsx", rangeLabel: "L12" }), ]); expect(handoff.prompt).toContain("attached to this message"); expect(handoff.prompt).not.toContain("rename the helper"); }); it("quotes a review remark, which has no line to attach it to", () => { const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "comment", comment: { id: "c1", kind: "review", author: { login: "julius", name: null, avatarUrl: null }, body: "this breaks SSO auth", createdAt: "2026-07-01T00:00:00Z", url: null, path: "apps/server/src/auth.ts", reviewState: "CHANGES_REQUESTED", }, }, }); expect(handoff.reviewComments).toEqual([]); expect(handoff.prompt).toContain("> julius on `apps/server/src/auth.ts`: this breaks SSO auth"); }); it("quotes a failing check with what the host reported about it", () => { const handoff = buildFixFindingHandoff({ ...base, finding: { kind: "check", check: { name: "typecheck", status: "failure", description: "2 errors", url: null }, }, }); expect(handoff.prompt).toContain("> typecheck — 2 errors"); expect(handoff.prompt).toContain("Reproduce it locally first"); });