Archived
feat: add conversational agent reviews
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import type { Job } from "../../../../adapters/database/store.js";
|
||||
import {
|
||||
parseUnifiedDiff,
|
||||
renderPullReviewComments,
|
||||
validateStructuredFindings,
|
||||
} from "../../../../adapters/git/diff.js";
|
||||
import { workspaceDiff } from "../../../../adapters/git/repository/changes.js";
|
||||
import { headSha } from "../../../../adapters/git/repository/checkout.js";
|
||||
import {
|
||||
marker,
|
||||
parseMarker,
|
||||
protocolVersion,
|
||||
sha256,
|
||||
} from "../../../../core/contracts.js";
|
||||
import {
|
||||
type PublicationContext,
|
||||
type PublicationOutcome,
|
||||
upsertJobStatus,
|
||||
} from "../../status.js";
|
||||
|
||||
export async function publishPullReview(
|
||||
context: PublicationContext,
|
||||
job: Job,
|
||||
): Promise<PublicationOutcome> {
|
||||
const review = job.result?.review;
|
||||
const workspace = job.workspace;
|
||||
if (review?.kind !== "pull" || !workspace)
|
||||
throw new Error("Pull review result is incomplete");
|
||||
const pull = await context.client.getPullRequest(job.targetNumber);
|
||||
if (
|
||||
pull.state !== "open" ||
|
||||
pull.number !== review.pullRequestNumber ||
|
||||
pull.head.sha !== review.headSha ||
|
||||
pull.merge_base !== review.mergeBaseSha
|
||||
)
|
||||
throw new Error(
|
||||
"Pull request changed while its review was being published",
|
||||
);
|
||||
const options = context.signal ? { signal: context.signal } : {};
|
||||
if ((await headSha(workspace, options)) !== review.headSha)
|
||||
throw new Error(
|
||||
"Review workspace does not match the pull request head",
|
||||
);
|
||||
const diff = await workspaceDiff(workspace, review.mergeBaseSha, options);
|
||||
if (sha256(diff) !== review.diffDigest)
|
||||
throw new Error("Pull-request diff changed after review");
|
||||
const parsed = parseUnifiedDiff(diff);
|
||||
const findings = validateStructuredFindings(review.findings, parsed);
|
||||
const reviewMarker = {
|
||||
v: protocolVersion,
|
||||
kind: "review" as const,
|
||||
issue: job.issueNumber,
|
||||
mode: "implement" as const,
|
||||
request: job.id,
|
||||
pullRequest: job.targetNumber,
|
||||
};
|
||||
const reviews = await context.client.listPullReviews(job.targetNumber);
|
||||
const existing = reviews.find((candidate) => {
|
||||
if (
|
||||
candidate.user.login.toLowerCase() !==
|
||||
context.botLogin.toLowerCase()
|
||||
)
|
||||
return false;
|
||||
const found = parseMarker(candidate.body, "review");
|
||||
return (
|
||||
found?.request === job.id && found.pullRequest === job.targetNumber
|
||||
);
|
||||
});
|
||||
const pending = reviews.filter(
|
||||
(candidate) =>
|
||||
candidate.user.login.toLowerCase() ===
|
||||
context.botLogin.toLowerCase() &&
|
||||
candidate.state.toUpperCase() === "PENDING" &&
|
||||
(!candidate.commit_id || candidate.commit_id === review.headSha),
|
||||
);
|
||||
for (const candidate of pending)
|
||||
await context.client.deletePullReview(job.targetNumber, candidate.id);
|
||||
if (existing && existing.state.toUpperCase() !== "PENDING") {
|
||||
await publishStatus(context, job, findings.length);
|
||||
return { terminal: "succeeded", reviewId: existing.id };
|
||||
}
|
||||
const general = review.generalFindings.length
|
||||
? `\n\n## General findings\n\n${review.generalFindings.map((item) => `- ${item}`).join("\n")}`
|
||||
: "";
|
||||
const body = `${marker(reviewMarker)}\n${review.summary}${general}`;
|
||||
const published = await context.client.createPullReview(job.targetNumber, {
|
||||
event: "COMMENT",
|
||||
body,
|
||||
commit_id: review.headSha,
|
||||
comments: renderPullReviewComments(findings),
|
||||
});
|
||||
await publishStatus(context, job, findings.length);
|
||||
return { terminal: "succeeded", reviewId: published.id };
|
||||
}
|
||||
|
||||
async function publishStatus(
|
||||
context: PublicationContext,
|
||||
job: Job,
|
||||
findingCount: number,
|
||||
): Promise<void> {
|
||||
await upsertJobStatus(
|
||||
context.client,
|
||||
context.botLogin,
|
||||
job,
|
||||
"Agent pull request review completed",
|
||||
findingCount
|
||||
? `${findingCount} inline finding(s) were published.`
|
||||
: "No blocking inline findings were reported.",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user