1import type { PullRequestActivity, PullRequestDetail } from "@t3tools/contracts";
2import type { ReviewGuideGenerationInput } from "@t3tools/provider-core/server/textGeneration";
3
4/** Longest a settled remark is quoted. The guide needs what was raised, not the argument. */
5const SETTLED_REMARK_CHARS = 200;
6
7function oneLine(text: string): string {
8 const flat = text
9 .replace(/<!--[\s\S]*?-->/g, "")
10 .replace(/\s+/g, " ")
11 .trim();
12 return flat.length > SETTLED_REMARK_CHARS ? `${flat.slice(0, SETTLED_REMARK_CHARS)}…` : flat;
13}
14
15/**
16 * What review already settled, one line per conversation: resolved threads, and threads whose
17 * line a later push removed. These are the review loop's finished work; the guide is told about
18 * them so it does not raise them again.
19 */
20export function settledReviewSummary(activity: PullRequestActivity): string {
21 return activity.reviewThreads
22 .filter((thread) => thread.isResolved || thread.isOutdated)
23 .flatMap((thread) => {
24 const opener = thread.comments[0];
25 if (opener === undefined) return [];
26 const who = opener.author?.login ?? "ghost";
27 const state = thread.isResolved ? "resolved" : "outdated";
28 return [`- ${thread.path} (${state}, ${who}): ${oneLine(opener.body)}`];
29 })
30 .join("\n");
31}
32
33/** The prompt input for a guide, minus the checkout and model the caller provides. */
34export function reviewGuideInputFromPullRequest(
35 detail: PullRequestDetail,
36 activity: PullRequestActivity,
37 baseCommit: string,
38): Omit<ReviewGuideGenerationInput, "cwd" | "modelSelection"> {
39 return {
40 number: detail.number,
41 title: detail.title,
42 description: detail.body,
43 baseBranch: detail.baseBranch,
44 baseCommit,
45 settledReview: settledReviewSummary(activity),
46 };
47}