opencode-magi 0.0.0-dev-20260520165753 → 0.0.0-dev-20260520173258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/resolve.js +29 -10
- package/dist/config/validate.js +42 -17
- package/dist/github/commands.js +98 -6
- package/dist/index.js +133 -24
- package/dist/orchestrator/findings.js +4 -3
- package/dist/orchestrator/report.js +8 -1
- package/dist/orchestrator/review-context.js +309 -0
- package/dist/orchestrator/review.js +26 -5
- package/dist/orchestrator/run-manager.js +5 -3
- package/dist/orchestrator/triage.js +151 -88
- package/dist/prompts/compose.js +28 -16
- package/dist/prompts/contracts.js +28 -16
- package/dist/prompts/output.js +36 -24
- package/dist/prompts/templates/review/review.md +6 -0
- package/dist/prompts/templates/triage/acceptance.md +7 -0
- package/dist/prompts/templates/triage/category.md +10 -0
- package/package.json +1 -1
- package/schema.json +16 -15
- package/dist/prompts/templates/triage/bug.md +0 -7
- package/dist/prompts/templates/triage/feature.md +0 -7
- package/dist/prompts/templates/triage/kind.md +0 -7
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { fetchIssue, fetchIssueCommentPage, fetchPullRequestClosingIssues, fetchPullRequestCommentPage, fetchPullRequestReviewThreadPage, fetchPullRequestSafetyMeta, } from "../github/commands";
|
|
2
|
+
const LIMITS = {
|
|
3
|
+
closingIssueComments: 20,
|
|
4
|
+
commentBody: 4000,
|
|
5
|
+
prComments: 20,
|
|
6
|
+
referencedIssueComments: 10,
|
|
7
|
+
reviewThreadComments: 20,
|
|
8
|
+
reviewThreads: 50,
|
|
9
|
+
};
|
|
10
|
+
function escapeRegExp(value) {
|
|
11
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12
|
+
}
|
|
13
|
+
function truncateBody(body) {
|
|
14
|
+
if (body.length <= LIMITS.commentBody)
|
|
15
|
+
return { body };
|
|
16
|
+
return {
|
|
17
|
+
body: `${body.slice(0, LIMITS.commentBody)}\n[truncated after ${LIMITS.commentBody} characters]`,
|
|
18
|
+
truncated: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function boundedComments(comments, limit) {
|
|
22
|
+
return [...comments]
|
|
23
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
24
|
+
.slice(-limit)
|
|
25
|
+
.map((comment) => ({
|
|
26
|
+
author: comment.author,
|
|
27
|
+
createdAt: comment.createdAt,
|
|
28
|
+
id: comment.id,
|
|
29
|
+
url: comment.url,
|
|
30
|
+
...truncateBody(comment.body),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function omittedCommentCount(input) {
|
|
34
|
+
return input.omitted + Math.max(0, input.comments.length - input.limit);
|
|
35
|
+
}
|
|
36
|
+
function quoteEvidence(value) {
|
|
37
|
+
const compact = value.replaceAll(/\s+/g, " ").trim();
|
|
38
|
+
return compact.length > 120 ? `${compact.slice(0, 117)}...` : compact;
|
|
39
|
+
}
|
|
40
|
+
function issueReferencePattern(repository) {
|
|
41
|
+
const host = escapeRegExp(repository.github.host || "github.com");
|
|
42
|
+
const owner = escapeRegExp(repository.github.owner);
|
|
43
|
+
const repo = escapeRegExp(repository.github.repo);
|
|
44
|
+
return new RegExp(`(?:https?://${host}/${owner}/${repo}/issues/(\\d+)|#(\\d+))`, "gi");
|
|
45
|
+
}
|
|
46
|
+
function issueNumberFromMatch(match) {
|
|
47
|
+
return Number(match[1] ?? match[2]);
|
|
48
|
+
}
|
|
49
|
+
function addRelationship(relationships, number, relationship, source) {
|
|
50
|
+
const current = relationships.get(number);
|
|
51
|
+
const nextRelationship = current?.relationship === "closing" || relationship === "closing"
|
|
52
|
+
? "closing"
|
|
53
|
+
: "referenced";
|
|
54
|
+
const sources = current?.sources ?? [];
|
|
55
|
+
relationships.set(number, {
|
|
56
|
+
number,
|
|
57
|
+
relationship: nextRelationship,
|
|
58
|
+
sources: sources.includes(source) ? sources : [...sources, source],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function scanRelationshipText(input) {
|
|
62
|
+
const referencePattern = issueReferencePattern(input.repository);
|
|
63
|
+
const closingPattern = new RegExp(`\\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\\b[\\s\\S]{0,80}?${referencePattern.source}`, "gi");
|
|
64
|
+
for (const match of input.text.matchAll(referencePattern)) {
|
|
65
|
+
const number = issueNumberFromMatch(match);
|
|
66
|
+
if (number === input.currentPr)
|
|
67
|
+
continue;
|
|
68
|
+
addRelationship(input.relationships, number, "referenced", `${input.label} "${quoteEvidence(match[0])}"`);
|
|
69
|
+
}
|
|
70
|
+
for (const match of input.text.matchAll(closingPattern)) {
|
|
71
|
+
const number = Number(match[1] ?? match[2]);
|
|
72
|
+
if (!number || number === input.currentPr)
|
|
73
|
+
continue;
|
|
74
|
+
addRelationship(input.relationships, number, "closing", `${input.label} "${quoteEvidence(match[0])}"`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function collectIssueRelationships(input) {
|
|
78
|
+
const relationships = new Map();
|
|
79
|
+
for (const issue of input.closingIssues) {
|
|
80
|
+
if (issue.number === input.pr.number)
|
|
81
|
+
continue;
|
|
82
|
+
addRelationship(relationships, issue.number, "closing", "GitHub closingIssuesReferences");
|
|
83
|
+
}
|
|
84
|
+
scanRelationshipText({
|
|
85
|
+
currentPr: input.pr.number,
|
|
86
|
+
label: "PR title",
|
|
87
|
+
relationships,
|
|
88
|
+
repository: input.repository,
|
|
89
|
+
text: input.pr.title,
|
|
90
|
+
});
|
|
91
|
+
scanRelationshipText({
|
|
92
|
+
currentPr: input.pr.number,
|
|
93
|
+
label: "PR body",
|
|
94
|
+
relationships,
|
|
95
|
+
repository: input.repository,
|
|
96
|
+
text: input.pr.body ?? "",
|
|
97
|
+
});
|
|
98
|
+
for (const comment of input.prComments) {
|
|
99
|
+
scanRelationshipText({
|
|
100
|
+
currentPr: input.pr.number,
|
|
101
|
+
label: `PR comment ${comment.id}`,
|
|
102
|
+
relationships,
|
|
103
|
+
repository: input.repository,
|
|
104
|
+
text: comment.body,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
for (const thread of input.reviewThreads) {
|
|
108
|
+
for (const comment of thread.comments) {
|
|
109
|
+
scanRelationshipText({
|
|
110
|
+
currentPr: input.pr.number,
|
|
111
|
+
label: `review thread ${thread.threadId} comment ${comment.commentId}`,
|
|
112
|
+
relationships,
|
|
113
|
+
repository: input.repository,
|
|
114
|
+
text: comment.body,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return [...relationships.values()].sort((a, b) => a.number - b.number);
|
|
119
|
+
}
|
|
120
|
+
async function contextIssue(input) {
|
|
121
|
+
const issue = input.issue ??
|
|
122
|
+
(await fetchIssue(input.exec, input.repository, input.relationship.number));
|
|
123
|
+
const commentPage = await fetchIssueCommentPage(input.exec, input.repository, issue.number, input.limit);
|
|
124
|
+
return {
|
|
125
|
+
author: issue.author,
|
|
126
|
+
body: issue.body,
|
|
127
|
+
comments: boundedComments(commentPage.comments, input.limit),
|
|
128
|
+
commentsOmitted: omittedCommentCount({
|
|
129
|
+
comments: commentPage.comments,
|
|
130
|
+
limit: input.limit,
|
|
131
|
+
omitted: commentPage.omitted,
|
|
132
|
+
}),
|
|
133
|
+
number: issue.number,
|
|
134
|
+
relationship: input.relationship.relationship,
|
|
135
|
+
source: input.relationship.sources.join("; "),
|
|
136
|
+
state: issue.state,
|
|
137
|
+
title: issue.title,
|
|
138
|
+
url: issue.url,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function orderReviewThreads(threads) {
|
|
142
|
+
return [...threads]
|
|
143
|
+
.sort((a, b) => {
|
|
144
|
+
if (a.isResolved !== b.isResolved)
|
|
145
|
+
return a.isResolved ? 1 : -1;
|
|
146
|
+
const aLatest = a.comments.at(-1)?.createdAt ?? "";
|
|
147
|
+
const bLatest = b.comments.at(-1)?.createdAt ?? "";
|
|
148
|
+
return bLatest.localeCompare(aLatest);
|
|
149
|
+
})
|
|
150
|
+
.slice(0, LIMITS.reviewThreads)
|
|
151
|
+
.map((thread) => ({
|
|
152
|
+
...thread,
|
|
153
|
+
comments: thread.comments
|
|
154
|
+
.slice(-LIMITS.reviewThreadComments)
|
|
155
|
+
.map((comment) => ({
|
|
156
|
+
...comment,
|
|
157
|
+
...truncateBody(comment.body),
|
|
158
|
+
})),
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
export async function buildReviewContextSnapshot(input) {
|
|
162
|
+
const [prCommentPage, reviewThreadPage, safetyMeta, closingIssues] = await Promise.all([
|
|
163
|
+
fetchPullRequestCommentPage(input.exec, input.repository, input.pr.number, LIMITS.prComments),
|
|
164
|
+
fetchPullRequestReviewThreadPage(input.exec, input.repository, input.pr.number, LIMITS.reviewThreads, LIMITS.reviewThreadComments),
|
|
165
|
+
fetchPullRequestSafetyMeta(input.exec, input.repository, input.pr.number),
|
|
166
|
+
fetchPullRequestClosingIssues(input.exec, input.repository, input.pr.number).catch(() => []),
|
|
167
|
+
]);
|
|
168
|
+
const prComments = prCommentPage.comments;
|
|
169
|
+
const orderedReviewThreads = orderReviewThreads(reviewThreadPage.threads);
|
|
170
|
+
const prCommentsOmitted = omittedCommentCount({
|
|
171
|
+
comments: prComments,
|
|
172
|
+
limit: LIMITS.prComments,
|
|
173
|
+
omitted: prCommentPage.omitted,
|
|
174
|
+
});
|
|
175
|
+
const relationships = collectIssueRelationships({
|
|
176
|
+
closingIssues,
|
|
177
|
+
pr: input.pr,
|
|
178
|
+
prComments,
|
|
179
|
+
repository: input.repository,
|
|
180
|
+
reviewThreads: orderedReviewThreads,
|
|
181
|
+
});
|
|
182
|
+
const closingIssueMap = new Map(closingIssues.map((issue) => [issue.number, issue]));
|
|
183
|
+
const closingRelationships = relationships.filter((relationship) => relationship.relationship === "closing");
|
|
184
|
+
const referencedRelationships = relationships.filter((relationship) => relationship.relationship === "referenced");
|
|
185
|
+
return {
|
|
186
|
+
closingIssues: await Promise.all(closingRelationships.map((relationship) => contextIssue({
|
|
187
|
+
exec: input.exec,
|
|
188
|
+
issue: closingIssueMap.get(relationship.number),
|
|
189
|
+
limit: LIMITS.closingIssueComments,
|
|
190
|
+
relationship,
|
|
191
|
+
repository: input.repository,
|
|
192
|
+
}))),
|
|
193
|
+
pullRequest: {
|
|
194
|
+
author: input.pr.author?.login ?? safetyMeta.author,
|
|
195
|
+
baseRef: input.pr.baseRefName,
|
|
196
|
+
baseSha: input.pr.baseRefOid,
|
|
197
|
+
body: input.pr.body ?? "",
|
|
198
|
+
changedFiles: safetyMeta.files,
|
|
199
|
+
comments: boundedComments(prComments, LIMITS.prComments),
|
|
200
|
+
commentsOmitted: prCommentsOmitted,
|
|
201
|
+
headRef: input.pr.headRefName,
|
|
202
|
+
headSha: input.pr.headRefOid,
|
|
203
|
+
number: input.pr.number,
|
|
204
|
+
relationship: "target",
|
|
205
|
+
source: "/magi:review input",
|
|
206
|
+
state: input.pr.state ?? "",
|
|
207
|
+
title: input.pr.title,
|
|
208
|
+
url: input.pr.url,
|
|
209
|
+
},
|
|
210
|
+
referencedIssues: await Promise.all(referencedRelationships.map((relationship) => contextIssue({
|
|
211
|
+
exec: input.exec,
|
|
212
|
+
limit: LIMITS.referencedIssueComments,
|
|
213
|
+
relationship,
|
|
214
|
+
repository: input.repository,
|
|
215
|
+
}))),
|
|
216
|
+
reviewDiscussion: {
|
|
217
|
+
prComments: boundedComments(prComments, LIMITS.prComments),
|
|
218
|
+
prCommentsOmitted,
|
|
219
|
+
reviewThreads: orderedReviewThreads,
|
|
220
|
+
reviewThreadsOmitted: reviewThreadPage.omitted,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function indented(value) {
|
|
225
|
+
return value.trim() ? value : "(empty)";
|
|
226
|
+
}
|
|
227
|
+
function renderOmissionNote(omitted, label, limit) {
|
|
228
|
+
return omitted > 0
|
|
229
|
+
? `\n[omitted ${omitted} older ${label} due to limit ${limit}]`
|
|
230
|
+
: "";
|
|
231
|
+
}
|
|
232
|
+
function renderComments(comments, omitted = 0, limit = comments.length) {
|
|
233
|
+
if (!comments.length)
|
|
234
|
+
return `(none)${renderOmissionNote(omitted, "comments", limit)}`;
|
|
235
|
+
return (comments
|
|
236
|
+
.map((comment) => {
|
|
237
|
+
const suffix = comment.truncated ? " [truncated]" : "";
|
|
238
|
+
return `- ${comment.createdAt} @${comment.author} (${comment.id})${suffix}\n${indented(comment.body)}`;
|
|
239
|
+
})
|
|
240
|
+
.join("\n") + renderOmissionNote(omitted, "comments", limit));
|
|
241
|
+
}
|
|
242
|
+
function renderIssue(issue) {
|
|
243
|
+
return `<issue>
|
|
244
|
+
number: ${issue.number}
|
|
245
|
+
title: ${issue.title}
|
|
246
|
+
url: ${issue.url}
|
|
247
|
+
state: ${issue.state}
|
|
248
|
+
author: ${issue.author}
|
|
249
|
+
relationship: ${issue.relationship}
|
|
250
|
+
source: ${issue.source}
|
|
251
|
+
body:
|
|
252
|
+
${indented(issue.body)}
|
|
253
|
+
comments:
|
|
254
|
+
${renderComments(issue.comments, issue.commentsOmitted, issue.relationship === "closing" ? LIMITS.closingIssueComments : LIMITS.referencedIssueComments)}
|
|
255
|
+
</issue>`;
|
|
256
|
+
}
|
|
257
|
+
function renderThreads(threads, omitted = 0) {
|
|
258
|
+
if (!threads.length) {
|
|
259
|
+
return `(none)${renderOmissionNote(omitted, "review threads", LIMITS.reviewThreads)}`;
|
|
260
|
+
}
|
|
261
|
+
return (threads
|
|
262
|
+
.map((thread) => {
|
|
263
|
+
const comments = thread.comments
|
|
264
|
+
.map((comment) => {
|
|
265
|
+
const suffix = comment.truncated ? " [truncated]" : "";
|
|
266
|
+
return ` - ${comment.createdAt} @${comment.author} (${comment.commentId})${suffix}\n${indented(comment.body)}`;
|
|
267
|
+
})
|
|
268
|
+
.join("\n") +
|
|
269
|
+
renderOmissionNote(thread.omittedComments ?? 0, "thread comments", LIMITS.reviewThreadComments);
|
|
270
|
+
return `- threadId: ${thread.threadId}\n resolved: ${Boolean(thread.isResolved)}\n path: ${thread.path}:${thread.line}\n comments:\n${comments}`;
|
|
271
|
+
})
|
|
272
|
+
.join("\n") +
|
|
273
|
+
renderOmissionNote(omitted, "review threads", LIMITS.reviewThreads));
|
|
274
|
+
}
|
|
275
|
+
export function renderReviewContext(snapshot) {
|
|
276
|
+
return [
|
|
277
|
+
`<pull_request_context>
|
|
278
|
+
number: ${snapshot.pullRequest.number}
|
|
279
|
+
title: ${snapshot.pullRequest.title}
|
|
280
|
+
url: ${snapshot.pullRequest.url}
|
|
281
|
+
state: ${snapshot.pullRequest.state}
|
|
282
|
+
author: ${snapshot.pullRequest.author}
|
|
283
|
+
relationship: ${snapshot.pullRequest.relationship}
|
|
284
|
+
source: ${snapshot.pullRequest.source}
|
|
285
|
+
baseRef: ${snapshot.pullRequest.baseRef}
|
|
286
|
+
headRef: ${snapshot.pullRequest.headRef}
|
|
287
|
+
baseSha: ${snapshot.pullRequest.baseSha}
|
|
288
|
+
headSha: ${snapshot.pullRequest.headSha}
|
|
289
|
+
body:
|
|
290
|
+
${indented(snapshot.pullRequest.body)}
|
|
291
|
+
comments:
|
|
292
|
+
${renderComments(snapshot.pullRequest.comments, snapshot.pullRequest.commentsOmitted, LIMITS.prComments)}
|
|
293
|
+
changedFiles:
|
|
294
|
+
${snapshot.pullRequest.changedFiles.length ? snapshot.pullRequest.changedFiles.map((file) => `- ${file}`).join("\n") : "(none)"}
|
|
295
|
+
</pull_request_context>`,
|
|
296
|
+
`<closing_issues>
|
|
297
|
+
${snapshot.closingIssues.length ? snapshot.closingIssues.map(renderIssue).join("\n") : "(none)"}
|
|
298
|
+
</closing_issues>`,
|
|
299
|
+
`<referenced_issues>
|
|
300
|
+
${snapshot.referencedIssues.length ? snapshot.referencedIssues.map(renderIssue).join("\n") : "(none)"}
|
|
301
|
+
</referenced_issues>`,
|
|
302
|
+
`<review_discussion>
|
|
303
|
+
prComments:
|
|
304
|
+
${renderComments(snapshot.reviewDiscussion.prComments, snapshot.reviewDiscussion.prCommentsOmitted, LIMITS.prComments)}
|
|
305
|
+
reviewThreads:
|
|
306
|
+
${renderThreads(snapshot.reviewDiscussion.reviewThreads, snapshot.reviewDiscussion.reviewThreadsOmitted)}
|
|
307
|
+
</review_discussion>`,
|
|
308
|
+
].join("\n\n");
|
|
309
|
+
}
|
|
@@ -13,6 +13,7 @@ import { closeMinorityReviewers, mergeVerdictForPolicy, } from "./majority";
|
|
|
13
13
|
import { runModelWithRepair } from "./model";
|
|
14
14
|
import { mapPool } from "./pool";
|
|
15
15
|
import { formatReviewReport } from "./report";
|
|
16
|
+
import { buildReviewContextSnapshot, renderReviewContext, } from "./review-context";
|
|
16
17
|
import { checkSafetyGate, hasSafetyGate } from "./safety";
|
|
17
18
|
function errorMessage(error) {
|
|
18
19
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -38,7 +39,7 @@ async function postReviewOutput(input, reviewerKey, output) {
|
|
|
38
39
|
return postApproval(input.exec, input.repository, input.pr, reviewer.account);
|
|
39
40
|
if (output.verdict === "CLOSE")
|
|
40
41
|
return postCloseComment(input.exec, input.repository, input.pr, reviewer.account, output.reason ?? "Close requested.");
|
|
41
|
-
return postChangesRequested(input.exec, input.repository, input.pr, reviewer.account, output.findings);
|
|
42
|
+
return postChangesRequested(input.exec, input.repository, input.pr, reviewer.account, output.findings, output.requirementFindings);
|
|
42
43
|
}
|
|
43
44
|
function dryRunReviewPost(key, output) {
|
|
44
45
|
if (output.verdict === "MERGE")
|
|
@@ -137,8 +138,13 @@ function parseRereviewOutputWithInlineTargets(text, targets) {
|
|
|
137
138
|
function reviewOutputFromState(review) {
|
|
138
139
|
const verdict = reviewStateToVerdict(review.state);
|
|
139
140
|
return verdict === "CLOSE"
|
|
140
|
-
? {
|
|
141
|
-
|
|
141
|
+
? {
|
|
142
|
+
findings: [],
|
|
143
|
+
reason: review.body || "Close requested.",
|
|
144
|
+
requirementFindings: [],
|
|
145
|
+
verdict,
|
|
146
|
+
}
|
|
147
|
+
: { findings: [], requirementFindings: [], verdict };
|
|
142
148
|
}
|
|
143
149
|
export function hasPendingThreadReply(threads, reviewerAccount) {
|
|
144
150
|
return threads.some((thread) => {
|
|
@@ -162,7 +168,7 @@ async function postRereviewOutput(input, reviewerKey, output) {
|
|
|
162
168
|
return postApproval(input.exec, input.repository, input.pr, reviewer.account);
|
|
163
169
|
if (output.verdict === "CLOSE")
|
|
164
170
|
return postCloseComment(input.exec, input.repository, input.pr, reviewer.account, output.reason ?? "Close requested.");
|
|
165
|
-
if (!output.newFindings.length)
|
|
171
|
+
if (!output.newFindings.length && !output.requirementFindings.length)
|
|
166
172
|
return "";
|
|
167
173
|
return postChangesRequested(input.exec, input.repository, input.pr, reviewer.account, output.newFindings.map((finding) => ({
|
|
168
174
|
fix: "Please address this before merging.",
|
|
@@ -170,7 +176,7 @@ async function postRereviewOutput(input, reviewerKey, output) {
|
|
|
170
176
|
line: finding.line,
|
|
171
177
|
path: finding.path,
|
|
172
178
|
startLine: finding.startLine,
|
|
173
|
-
})));
|
|
179
|
+
})), output.requirementFindings);
|
|
174
180
|
}
|
|
175
181
|
function isReviewOutput(output) {
|
|
176
182
|
return "findings" in output;
|
|
@@ -202,6 +208,7 @@ async function runFindingValidation(input) {
|
|
|
202
208
|
includeSessionContext: !hasReviewerSession,
|
|
203
209
|
pr: input.reviewInput.pr,
|
|
204
210
|
repository: input.reviewInput.repository,
|
|
211
|
+
reviewContext: input.reviewContext,
|
|
205
212
|
reviewer,
|
|
206
213
|
worktreePath: input.worktreePath,
|
|
207
214
|
});
|
|
@@ -314,6 +321,7 @@ async function runCloseReconsideration(input) {
|
|
|
314
321
|
includeSessionContext: !hasReviewerSession,
|
|
315
322
|
pr: input.reviewInput.pr,
|
|
316
323
|
repository: input.reviewInput.repository,
|
|
324
|
+
reviewContext: input.reviewContext,
|
|
317
325
|
reviewer,
|
|
318
326
|
worktreePath: input.worktreePath,
|
|
319
327
|
});
|
|
@@ -464,6 +472,15 @@ export async function runReview(input) {
|
|
|
464
472
|
pr: input.pr,
|
|
465
473
|
}), ...(input.runId ? [input.runId] : []));
|
|
466
474
|
await mkdir(outputDir, { recursive: true });
|
|
475
|
+
await input.onProgress?.({ phase: "fetching review context", type: "phase" });
|
|
476
|
+
const reviewContextSnapshot = await buildReviewContextSnapshot({
|
|
477
|
+
exec,
|
|
478
|
+
pr: meta,
|
|
479
|
+
repository: input.repository,
|
|
480
|
+
});
|
|
481
|
+
const reviewContext = renderReviewContext(reviewContextSnapshot);
|
|
482
|
+
await writeFile(join(outputDir, "review-context.json"), JSON.stringify(reviewContextSnapshot, null, 2));
|
|
483
|
+
await writeFile(join(outputDir, "review-context.md"), `${reviewContext}\n`);
|
|
467
484
|
await input.onProgress?.({ phase: "waiting for checks", type: "phase" });
|
|
468
485
|
const checkResult = await waitForChecksWithClassification({
|
|
469
486
|
client: input.client,
|
|
@@ -565,6 +582,7 @@ export async function runReview(input) {
|
|
|
565
582
|
previousReview: previousReviewText(previous),
|
|
566
583
|
previousHeadSha: previous.commit.oid,
|
|
567
584
|
repository: input.repository,
|
|
585
|
+
reviewContext,
|
|
568
586
|
reviewer,
|
|
569
587
|
unresolvedThreads: JSON.stringify(unresolved, null, 2),
|
|
570
588
|
worktreePath,
|
|
@@ -631,6 +649,7 @@ export async function runReview(input) {
|
|
|
631
649
|
headSha: meta.headRefOid,
|
|
632
650
|
pr: input.pr,
|
|
633
651
|
repository: input.repository,
|
|
652
|
+
reviewContext,
|
|
634
653
|
reviewer,
|
|
635
654
|
worktreePath,
|
|
636
655
|
});
|
|
@@ -727,6 +746,7 @@ export async function runReview(input) {
|
|
|
727
746
|
inlineCommentTargets,
|
|
728
747
|
meta,
|
|
729
748
|
outputDir,
|
|
749
|
+
reviewContext,
|
|
730
750
|
reviewInput: { ...input, exec },
|
|
731
751
|
sessionIds,
|
|
732
752
|
targets: closeTargets,
|
|
@@ -736,6 +756,7 @@ export async function runReview(input) {
|
|
|
736
756
|
entries,
|
|
737
757
|
meta,
|
|
738
758
|
outputDir,
|
|
759
|
+
reviewContext,
|
|
739
760
|
reviewInput: { ...input, exec },
|
|
740
761
|
sessionIds,
|
|
741
762
|
worktreePath,
|
|
@@ -1273,10 +1273,12 @@ export class MagiRunManager {
|
|
|
1273
1273
|
const completed = this.active.get(input.runId);
|
|
1274
1274
|
if (!completed || completed.status === "cancelled")
|
|
1275
1275
|
return;
|
|
1276
|
-
|
|
1277
|
-
completed.
|
|
1276
|
+
const triageResult = JSON.stringify(result.result);
|
|
1277
|
+
completed.status =
|
|
1278
|
+
result.result.disposition === "failed" ? "failed" : "completed";
|
|
1279
|
+
completed.phase = triageResult;
|
|
1278
1280
|
completed.completedAt = now();
|
|
1279
|
-
completed.verdict =
|
|
1281
|
+
completed.verdict = triageResult;
|
|
1280
1282
|
completed.reportPath = join(completed.outputDir, "report.md");
|
|
1281
1283
|
for (const agent of Object.values(completed.reviewers)) {
|
|
1282
1284
|
if (agent.status === "pending")
|