opencode-magi 0.0.0-dev-20260521221222 → 0.0.0-dev-20260521222649
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/github/commands.js +7 -46
- package/dist/orchestrator/findings.js +3 -4
- package/dist/orchestrator/inline-comments.js +0 -6
- package/dist/orchestrator/merge.js +16 -38
- package/dist/orchestrator/report.js +7 -18
- package/dist/orchestrator/review.js +11 -23
- package/dist/prompts/contracts.js +30 -37
- package/dist/prompts/output.js +22 -40
- package/dist/prompts/templates/merge/edit.md +1 -2
- package/dist/prompts/templates/review/close-reconsideration.md +1 -0
- package/dist/prompts/templates/review/rereview.md +3 -0
- package/dist/prompts/templates/review/review.md +4 -0
- package/package.json +1 -1
package/dist/github/commands.js
CHANGED
|
@@ -515,9 +515,6 @@ export async function postCloseComment(exec, repository, pr, account, body) {
|
|
|
515
515
|
await rm(payloadPath, { force: true });
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
|
-
function isInlineFinding(finding) {
|
|
519
|
-
return finding.line != null;
|
|
520
|
-
}
|
|
521
518
|
function findingComment(finding) {
|
|
522
519
|
const comment = {
|
|
523
520
|
body: `**Issue:** ${finding.issue}\n\n**Fix:** ${finding.fix}`,
|
|
@@ -531,54 +528,18 @@ function findingComment(finding) {
|
|
|
531
528
|
}
|
|
532
529
|
return comment;
|
|
533
530
|
}
|
|
534
|
-
function
|
|
535
|
-
return
|
|
536
|
-
|
|
537
|
-
`
|
|
538
|
-
` Fix: ${finding.fix}`,
|
|
539
|
-
].join("\n");
|
|
540
|
-
}
|
|
541
|
-
function findingLocation(finding) {
|
|
542
|
-
if (finding.line == null)
|
|
543
|
-
return finding.path;
|
|
544
|
-
if (finding.startLine == null)
|
|
545
|
-
return `${finding.path}:${finding.line}`;
|
|
546
|
-
return `${finding.path}:${finding.startLine}-${finding.line}`;
|
|
547
|
-
}
|
|
548
|
-
function findingSummary(finding) {
|
|
549
|
-
return [
|
|
550
|
-
`- ${findingLocation(finding)}: ${finding.issue}`,
|
|
551
|
-
` Fix: ${finding.fix}`,
|
|
552
|
-
]
|
|
553
|
-
.filter(Boolean)
|
|
554
|
-
.join("\n");
|
|
555
|
-
}
|
|
556
|
-
function changesRequestedBody(findings, requirementFindings) {
|
|
557
|
-
const inlineFindings = findings.filter(isInlineFinding);
|
|
558
|
-
const fileLevelFindings = findings.filter((finding) => !isInlineFinding(finding));
|
|
559
|
-
const sections = [];
|
|
560
|
-
if (inlineFindings.length) {
|
|
561
|
-
sections.push(["Inline findings:", ...inlineFindings.map(findingSummary)].join("\n"));
|
|
562
|
-
}
|
|
563
|
-
if (fileLevelFindings.length) {
|
|
564
|
-
sections.push(["File-level findings:", ...fileLevelFindings.map(findingSummary)].join("\n"));
|
|
565
|
-
}
|
|
566
|
-
if (requirementFindings.length) {
|
|
567
|
-
sections.push([
|
|
568
|
-
"Requirement findings:",
|
|
569
|
-
...requirementFindings.map(requirementFindingSummary),
|
|
570
|
-
].join("\n"));
|
|
571
|
-
}
|
|
572
|
-
return sections.join("\n\n");
|
|
531
|
+
function changesRequestedBody(findings) {
|
|
532
|
+
return findings.length === 1
|
|
533
|
+
? "Changes requested: 1 inline comment."
|
|
534
|
+
: `Changes requested: ${findings.length} inline comments.`;
|
|
573
535
|
}
|
|
574
|
-
export async function postChangesRequested(exec, repository, pr, account, findings
|
|
536
|
+
export async function postChangesRequested(exec, repository, pr, account, findings) {
|
|
575
537
|
const token = await ghToken(exec, repository, account);
|
|
576
538
|
const payloadPath = join(tmpdir(), `magi-review-${process.pid}-${Date.now()}.json`);
|
|
577
|
-
const
|
|
578
|
-
const body = changesRequestedBody(findings, requirementFindings);
|
|
539
|
+
const body = changesRequestedBody(findings);
|
|
579
540
|
await writeFile(payloadPath, JSON.stringify({
|
|
580
541
|
body,
|
|
581
|
-
comments:
|
|
542
|
+
comments: findings.map(findingComment),
|
|
582
543
|
event: "REQUEST_CHANGES",
|
|
583
544
|
}));
|
|
584
545
|
try {
|
|
@@ -58,10 +58,9 @@ export function applyFindingValidation(input) {
|
|
|
58
58
|
discarded.push(target);
|
|
59
59
|
return false;
|
|
60
60
|
});
|
|
61
|
-
next[reviewer] =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: { findings: [], requirementFindings: [], verdict: "MERGE" };
|
|
61
|
+
next[reviewer] = findings.length
|
|
62
|
+
? { ...output, findings }
|
|
63
|
+
: { findings: [], verdict: "MERGE" };
|
|
65
64
|
}
|
|
66
65
|
return { outputs: next, summary: { discarded, kept } };
|
|
67
66
|
}
|
|
@@ -52,12 +52,6 @@ function assertPositiveInteger(value, name) {
|
|
|
52
52
|
export function validateInlineCommentTargets(findings, targets, label = "findings") {
|
|
53
53
|
for (const [index, finding] of findings.entries()) {
|
|
54
54
|
const name = `${label}[${index}]`;
|
|
55
|
-
if (finding.line == null) {
|
|
56
|
-
if (finding.startLine != null) {
|
|
57
|
-
throw new Error(`${name}.startLine requires line`);
|
|
58
|
-
}
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
55
|
assertPositiveInteger(finding.line, `${name}.line`);
|
|
62
56
|
if (finding.startLine != null) {
|
|
63
57
|
assertPositiveInteger(finding.startLine, `${name}.startLine`);
|
|
@@ -146,14 +146,14 @@ async function postRereviewOutput(input, reviewerKey, output) {
|
|
|
146
146
|
if (output.verdict === "CLOSE") {
|
|
147
147
|
return postCloseComment(input.exec, input.repository, input.pr, reviewer.account, output.reason ?? "Close requested.");
|
|
148
148
|
}
|
|
149
|
-
if (output.newFindings.length
|
|
149
|
+
if (output.newFindings.length) {
|
|
150
150
|
return postChangesRequested(input.exec, input.repository, input.pr, reviewer.account, output.newFindings.map((finding) => ({
|
|
151
151
|
fix: "Please address this before merging.",
|
|
152
152
|
issue: finding.body,
|
|
153
153
|
path: finding.path,
|
|
154
|
-
|
|
154
|
+
line: finding.line,
|
|
155
155
|
startLine: finding.startLine,
|
|
156
|
-
}))
|
|
156
|
+
})));
|
|
157
157
|
}
|
|
158
158
|
return replies[0] ?? "";
|
|
159
159
|
}
|
|
@@ -166,45 +166,29 @@ function newFindingToEditorFinding(reviewer, finding) {
|
|
|
166
166
|
return {
|
|
167
167
|
body: finding.body,
|
|
168
168
|
fix: "Please address this before merging.",
|
|
169
|
+
line: finding.line,
|
|
169
170
|
path: finding.path,
|
|
170
171
|
reviewer,
|
|
171
|
-
...(finding.line == null ? {} : { line: finding.line }),
|
|
172
172
|
...(finding.startLine == null ? {} : { startLine: finding.startLine }),
|
|
173
|
-
type:
|
|
173
|
+
type: "inline",
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
export function blockingReviewFindings(outputs) {
|
|
177
177
|
return Object.entries(outputs).flatMap(([reviewer, output]) => {
|
|
178
178
|
if (output.verdict !== "CHANGES_REQUESTED")
|
|
179
179
|
return [];
|
|
180
|
-
const requirementFindings = output.requirementFindings.map((finding) => ({
|
|
181
|
-
evidence: finding.evidence,
|
|
182
|
-
fix: finding.fix,
|
|
183
|
-
issueNumber: finding.issueNumber,
|
|
184
|
-
requirement: finding.requirement,
|
|
185
|
-
reviewer,
|
|
186
|
-
type: "requirement",
|
|
187
|
-
}));
|
|
188
180
|
if ("findings" in output) {
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
: { startLine: finding.startLine }),
|
|
199
|
-
type: finding.line == null ? "file" : "inline",
|
|
200
|
-
})),
|
|
201
|
-
...requirementFindings,
|
|
202
|
-
];
|
|
181
|
+
return output.findings.map((finding) => ({
|
|
182
|
+
fix: finding.fix,
|
|
183
|
+
issue: finding.issue,
|
|
184
|
+
line: finding.line,
|
|
185
|
+
path: finding.path,
|
|
186
|
+
reviewer,
|
|
187
|
+
...(finding.startLine == null ? {} : { startLine: finding.startLine }),
|
|
188
|
+
type: "inline",
|
|
189
|
+
}));
|
|
203
190
|
}
|
|
204
|
-
return
|
|
205
|
-
...output.newFindings.map((finding) => newFindingToEditorFinding(reviewer, finding)),
|
|
206
|
-
...requirementFindings,
|
|
207
|
-
];
|
|
191
|
+
return output.newFindings.map((finding) => newFindingToEditorFinding(reviewer, finding));
|
|
208
192
|
});
|
|
209
193
|
}
|
|
210
194
|
async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionIds, ciFailureContext, options = {}) {
|
|
@@ -507,8 +491,6 @@ function syntheticReviewThreads(outputs) {
|
|
|
507
491
|
for (const [reviewer, output] of Object.entries(outputs)) {
|
|
508
492
|
if ("findings" in output) {
|
|
509
493
|
threads[reviewer] = output.findings.flatMap((finding) => {
|
|
510
|
-
if (finding.line == null)
|
|
511
|
-
return [];
|
|
512
494
|
const commentId = nextCommentId--;
|
|
513
495
|
return [
|
|
514
496
|
{
|
|
@@ -531,8 +513,6 @@ function syntheticReviewThreads(outputs) {
|
|
|
531
513
|
continue;
|
|
532
514
|
}
|
|
533
515
|
threads[reviewer] = output.newFindings.flatMap((finding) => {
|
|
534
|
-
if (finding.line == null)
|
|
535
|
-
return [];
|
|
536
516
|
const commentId = nextCommentId--;
|
|
537
517
|
return [
|
|
538
518
|
{
|
|
@@ -721,9 +701,7 @@ export async function runMerge(input) {
|
|
|
721
701
|
threads: unresolvedThreads,
|
|
722
702
|
});
|
|
723
703
|
const editorFindings = blockingReviewFindings(reportOutputs);
|
|
724
|
-
const editableFindings = editableThreads.length
|
|
725
|
-
? editorFindings
|
|
726
|
-
: editorFindings.filter((finding) => finding.type !== "inline");
|
|
704
|
+
const editableFindings = editableThreads.length ? editorFindings : [];
|
|
727
705
|
const findingAttemptsExhausted = input.repository.merge.maxThreadResolutionCycles !== 0 &&
|
|
728
706
|
cycle > input.repository.merge.maxThreadResolutionCycles;
|
|
729
707
|
if (!editableThreads.length &&
|
|
@@ -16,24 +16,17 @@ function pullRequestLine(input) {
|
|
|
16
16
|
return `- **Pull Request**: [#${input.pr}](${url})`;
|
|
17
17
|
}
|
|
18
18
|
function formatFinding(finding) {
|
|
19
|
-
const line = finding.
|
|
20
|
-
? finding.path
|
|
21
|
-
: finding.startLine
|
|
22
|
-
? `${finding.path}:${finding.line}`
|
|
23
|
-
: `${finding.path}:${finding.startLine}-${finding.line}`;
|
|
19
|
+
const line = finding.startLine == null
|
|
20
|
+
? `${finding.path}:${finding.line}`
|
|
21
|
+
: `${finding.path}:${finding.startLine}-${finding.line}`;
|
|
24
22
|
return `\`${line}\`: ${finding.issue}`;
|
|
25
23
|
}
|
|
26
24
|
function formatRereviewFinding(finding) {
|
|
27
|
-
const line = finding.
|
|
28
|
-
? finding.path
|
|
29
|
-
: finding.startLine
|
|
30
|
-
? `${finding.path}:${finding.line}`
|
|
31
|
-
: `${finding.path}:${finding.startLine}-${finding.line}`;
|
|
25
|
+
const line = finding.startLine == null
|
|
26
|
+
? `${finding.path}:${finding.line}`
|
|
27
|
+
: `${finding.path}:${finding.startLine}-${finding.line}`;
|
|
32
28
|
return `\`${line}\`: ${finding.body}`;
|
|
33
29
|
}
|
|
34
|
-
function formatRequirementFinding(finding) {
|
|
35
|
-
return `Issue #${finding.issueNumber}: ${finding.requirement}`;
|
|
36
|
-
}
|
|
37
30
|
function isReviewOutput(output) {
|
|
38
31
|
return "findings" in output;
|
|
39
32
|
}
|
|
@@ -85,10 +78,7 @@ function reviewerDetailLines(output) {
|
|
|
85
78
|
return output.reason ? [output.reason] : [];
|
|
86
79
|
if (output.verdict !== "CHANGES_REQUESTED")
|
|
87
80
|
return [];
|
|
88
|
-
return
|
|
89
|
-
...output.findings.map(formatFinding),
|
|
90
|
-
...output.requirementFindings.map(formatRequirementFinding),
|
|
91
|
-
];
|
|
81
|
+
return output.findings.map(formatFinding);
|
|
92
82
|
}
|
|
93
83
|
if (output.verdict === "CLOSE")
|
|
94
84
|
return output.reason ? [output.reason] : [];
|
|
@@ -96,7 +86,6 @@ function reviewerDetailLines(output) {
|
|
|
96
86
|
return [];
|
|
97
87
|
return [
|
|
98
88
|
...output.newFindings.map(formatRereviewFinding),
|
|
99
|
-
...output.requirementFindings.map(formatRequirementFinding),
|
|
100
89
|
...output.followUps.map((item) => `Comment #${item.commentId}: ${item.body}`),
|
|
101
90
|
];
|
|
102
91
|
}
|
|
@@ -39,7 +39,7 @@ async function postReviewOutput(input, reviewerKey, output) {
|
|
|
39
39
|
return postApproval(input.exec, input.repository, input.pr, reviewer.account);
|
|
40
40
|
if (output.verdict === "CLOSE")
|
|
41
41
|
return postCloseComment(input.exec, input.repository, input.pr, reviewer.account, output.reason ?? "Close requested.");
|
|
42
|
-
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);
|
|
43
43
|
}
|
|
44
44
|
function dryRunReviewPost(key, output) {
|
|
45
45
|
if (output.verdict === "MERGE")
|
|
@@ -147,11 +147,10 @@ function parsePostedFindingLocation(location) {
|
|
|
147
147
|
const line = /^(.*):(\d+)$/.exec(location);
|
|
148
148
|
if (line)
|
|
149
149
|
return { line: Number(line[2]), path: line[1] ?? location };
|
|
150
|
-
return
|
|
150
|
+
return undefined;
|
|
151
151
|
}
|
|
152
152
|
function reviewFindingsFromBody(body) {
|
|
153
153
|
const findings = [];
|
|
154
|
-
const requirementFindings = [];
|
|
155
154
|
const lines = (body ?? "").split(/\r?\n/);
|
|
156
155
|
let section;
|
|
157
156
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -164,26 +163,16 @@ function reviewFindingsFromBody(body) {
|
|
|
164
163
|
section = undefined;
|
|
165
164
|
continue;
|
|
166
165
|
}
|
|
167
|
-
const requirementMatch = /^- Missing issue #(\d+) requirement: (.+)$/.exec(line ?? "");
|
|
168
|
-
const evidence = /^\s+Evidence: (.+)$/.exec(lines[index + 1] ?? "");
|
|
169
|
-
const requirementFix = /^\s+Fix: (.+)$/.exec(lines[index + 2] ?? "");
|
|
170
|
-
if (requirementMatch && evidence && requirementFix) {
|
|
171
|
-
requirementFindings.push({
|
|
172
|
-
evidence: evidence[1] ?? "See review body.",
|
|
173
|
-
fix: requirementFix[1] ?? "Please address this before merging.",
|
|
174
|
-
issueNumber: Number(requirementMatch[1]),
|
|
175
|
-
requirement: requirementMatch[2] ?? "Review requirement.",
|
|
176
|
-
});
|
|
177
|
-
index += 2;
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
166
|
if (section === "finding") {
|
|
181
167
|
const match = /^- (.*): (.+)$/.exec(line ?? "");
|
|
182
168
|
const fix = /^\s+Fix: (.+)$/.exec(lines[index + 1] ?? "");
|
|
183
169
|
if (!match || !fix)
|
|
184
170
|
continue;
|
|
171
|
+
const location = parsePostedFindingLocation(match[1] ?? "");
|
|
172
|
+
if (!location)
|
|
173
|
+
continue;
|
|
185
174
|
findings.push({
|
|
186
|
-
...
|
|
175
|
+
...location,
|
|
187
176
|
fix: fix[1] ?? "Please address this before merging.",
|
|
188
177
|
issue: match[2] ?? "Review finding.",
|
|
189
178
|
});
|
|
@@ -191,7 +180,7 @@ function reviewFindingsFromBody(body) {
|
|
|
191
180
|
continue;
|
|
192
181
|
}
|
|
193
182
|
}
|
|
194
|
-
return { findings
|
|
183
|
+
return { findings };
|
|
195
184
|
}
|
|
196
185
|
export function reviewOutputFromState(review) {
|
|
197
186
|
const verdict = reviewStateToVerdict(review.state);
|
|
@@ -201,10 +190,9 @@ export function reviewOutputFromState(review) {
|
|
|
201
190
|
? {
|
|
202
191
|
findings: [],
|
|
203
192
|
reason: review.body || "Close requested.",
|
|
204
|
-
requirementFindings: [],
|
|
205
193
|
verdict,
|
|
206
194
|
}
|
|
207
|
-
: { findings: [],
|
|
195
|
+
: { findings: [], verdict };
|
|
208
196
|
}
|
|
209
197
|
export function hasPendingThreadReply(threads, reviewerAccount) {
|
|
210
198
|
return threads.some((thread) => {
|
|
@@ -228,15 +216,15 @@ async function postRereviewOutput(input, reviewerKey, output) {
|
|
|
228
216
|
return postApproval(input.exec, input.repository, input.pr, reviewer.account);
|
|
229
217
|
if (output.verdict === "CLOSE")
|
|
230
218
|
return postCloseComment(input.exec, input.repository, input.pr, reviewer.account, output.reason ?? "Close requested.");
|
|
231
|
-
if (!output.newFindings.length
|
|
219
|
+
if (!output.newFindings.length)
|
|
232
220
|
return "";
|
|
233
221
|
return postChangesRequested(input.exec, input.repository, input.pr, reviewer.account, output.newFindings.map((finding) => ({
|
|
234
222
|
fix: "Please address this before merging.",
|
|
235
223
|
issue: finding.body,
|
|
236
224
|
path: finding.path,
|
|
237
|
-
|
|
225
|
+
line: finding.line,
|
|
238
226
|
startLine: finding.startLine,
|
|
239
|
-
}))
|
|
227
|
+
})));
|
|
240
228
|
}
|
|
241
229
|
function isReviewOutput(output) {
|
|
242
230
|
return "findings" in output;
|
|
@@ -15,26 +15,20 @@ The object must match this shape:
|
|
|
15
15
|
"perspective": "Optional review perspective."
|
|
16
16
|
}
|
|
17
17
|
],
|
|
18
|
-
"requirementFindings": [
|
|
19
|
-
{
|
|
20
|
-
"issueNumber": 47,
|
|
21
|
-
"requirement": "Required closing-issue behavior that is missing.",
|
|
22
|
-
"evidence": "Why the PR does not satisfy the requirement.",
|
|
23
|
-
"fix": "How to satisfy the requirement."
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
18
|
"reason": "Required only for CLOSE."
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
Rules:
|
|
30
|
-
- MERGE requires empty findings
|
|
31
|
-
- CHANGES_REQUESTED requires at least one finding
|
|
32
|
-
- CLOSE requires a reason and empty findings
|
|
22
|
+
- MERGE requires an empty findings array.
|
|
23
|
+
- CHANGES_REQUESTED requires at least one finding.
|
|
24
|
+
- CLOSE requires a reason and an empty findings array.
|
|
33
25
|
- path must be repository-relative.
|
|
34
|
-
- line is
|
|
35
|
-
- startLine is
|
|
36
|
-
- Omit startLine for single-line findings
|
|
37
|
-
-
|
|
26
|
+
- line is required and must target a valid right-side line inside the PR diff hunk.
|
|
27
|
+
- startLine is optional and must also target a valid right-side line inside the same PR diff hunk range.
|
|
28
|
+
- Omit startLine for single-line findings.
|
|
29
|
+
- Do not omit line. Do not create file-level or body-only findings.
|
|
30
|
+
- Missing closing-issue requirements must be normal findings anchored to the nearest responsible changed line.
|
|
31
|
+
- If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
|
|
38
32
|
</output_contract>`.trim();
|
|
39
33
|
export const rereviewOutputContract = `
|
|
40
34
|
<output_contract>
|
|
@@ -46,18 +40,19 @@ The object must match this shape:
|
|
|
46
40
|
"resolve": [{ "commentId": 123, "threadId": "..." }],
|
|
47
41
|
"followUps": [{ "commentId": 123, "body": "..." }],
|
|
48
42
|
"newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }],
|
|
49
|
-
"requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }],
|
|
50
43
|
"reason": "Required only for CLOSE."
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
Rules:
|
|
54
|
-
- MERGE requires empty followUps
|
|
55
|
-
- CHANGES_REQUESTED requires at least one followUp
|
|
56
|
-
- CLOSE requires a reason and empty followUps
|
|
57
|
-
- line is
|
|
58
|
-
- startLine is
|
|
59
|
-
- Omit startLine for single-line findings
|
|
60
|
-
-
|
|
47
|
+
- MERGE requires empty followUps and newFindings arrays.
|
|
48
|
+
- CHANGES_REQUESTED requires at least one followUp or newFinding.
|
|
49
|
+
- CLOSE requires a reason and empty followUps and newFindings arrays.
|
|
50
|
+
- line is required and must target a valid right-side line inside the latest PR diff hunk.
|
|
51
|
+
- startLine is optional and must also target a valid right-side line inside the same latest PR diff hunk range.
|
|
52
|
+
- Omit startLine for single-line findings.
|
|
53
|
+
- Do not omit line. Do not create file-level or body-only findings.
|
|
54
|
+
- Missing closing-issue requirements must be normal newFindings anchored to the nearest responsible changed line.
|
|
55
|
+
- If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
|
|
61
56
|
</output_contract>`.trim();
|
|
62
57
|
export const findingValidationOutputContract = `
|
|
63
58
|
<output_contract>
|
|
@@ -96,17 +91,16 @@ The object must match this shape:
|
|
|
96
91
|
"issue": "What is wrong.",
|
|
97
92
|
"fix": "How to fix it."
|
|
98
93
|
}
|
|
99
|
-
]
|
|
100
|
-
"requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }]
|
|
94
|
+
]
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
Rules:
|
|
104
|
-
- MERGE requires empty findings
|
|
105
|
-
- CHANGES_REQUESTED requires at least one finding
|
|
98
|
+
- MERGE requires an empty findings array.
|
|
99
|
+
- CHANGES_REQUESTED requires at least one finding.
|
|
106
100
|
- CLOSE is not allowed in this reconsideration step.
|
|
107
|
-
- line is
|
|
108
|
-
- startLine is
|
|
109
|
-
-
|
|
101
|
+
- line is required and must target a valid right-side line inside the PR diff hunk.
|
|
102
|
+
- startLine is optional and must also target a valid right-side line inside the same PR diff hunk range.
|
|
103
|
+
- Do not omit line. Do not create file-level or body-only findings.
|
|
110
104
|
</output_contract>`.trim();
|
|
111
105
|
export const rereviewCloseReconsiderationOutputContract = `
|
|
112
106
|
<output_contract>
|
|
@@ -117,17 +111,16 @@ The object must match this shape:
|
|
|
117
111
|
"verdict": "MERGE" | "CHANGES_REQUESTED",
|
|
118
112
|
"resolve": [{ "commentId": 123, "threadId": "..." }],
|
|
119
113
|
"followUps": [{ "commentId": 123, "body": "..." }],
|
|
120
|
-
"newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }]
|
|
121
|
-
"requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }]
|
|
114
|
+
"newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }]
|
|
122
115
|
}
|
|
123
116
|
|
|
124
117
|
Rules:
|
|
125
|
-
- MERGE requires empty followUps
|
|
126
|
-
- CHANGES_REQUESTED requires at least one followUp
|
|
118
|
+
- MERGE requires empty followUps and newFindings arrays.
|
|
119
|
+
- CHANGES_REQUESTED requires at least one followUp or newFinding.
|
|
127
120
|
- CLOSE is not allowed in this reconsideration step.
|
|
128
|
-
- line is
|
|
129
|
-
- startLine is
|
|
130
|
-
-
|
|
121
|
+
- line is required and must target a valid right-side line inside the latest PR diff hunk.
|
|
122
|
+
- startLine is optional and must also target a valid right-side line inside the same latest PR diff hunk range.
|
|
123
|
+
- Do not omit line. Do not create file-level or body-only findings.
|
|
131
124
|
</output_contract>`.trim();
|
|
132
125
|
export const editOutputContract = `
|
|
133
126
|
<output_contract>
|
package/dist/prompts/output.js
CHANGED
|
@@ -71,27 +71,16 @@ function requireNumber(value, path) {
|
|
|
71
71
|
throw new Error(`${path} must be an integer`);
|
|
72
72
|
return value;
|
|
73
73
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
74
|
+
function requireLine(value, path) {
|
|
75
|
+
if (value == null)
|
|
76
|
+
throw new Error(`${path} is required`);
|
|
77
|
+
return requireNumber(value, path);
|
|
76
78
|
}
|
|
77
79
|
function optionalStartLine(input) {
|
|
78
80
|
if (input.value == null)
|
|
79
81
|
return undefined;
|
|
80
|
-
if (input.line == null)
|
|
81
|
-
throw new Error(`${input.path} requires line`);
|
|
82
82
|
return requireNumber(input.value, input.path);
|
|
83
83
|
}
|
|
84
|
-
function parseRequirementFindings(value) {
|
|
85
|
-
return (value == null ? [] : requireArray(value, "requirementFindings")).map((finding, index) => {
|
|
86
|
-
const item = finding;
|
|
87
|
-
return {
|
|
88
|
-
evidence: requireString(item.evidence, `requirementFindings[${index}].evidence`),
|
|
89
|
-
fix: requireString(item.fix, `requirementFindings[${index}].fix`),
|
|
90
|
-
issueNumber: requireNumber(item.issueNumber, `requirementFindings[${index}].issueNumber`),
|
|
91
|
-
requirement: requireString(item.requirement, `requirementFindings[${index}].requirement`),
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
84
|
function requireOneOf(value, path, values) {
|
|
96
85
|
const text = requireString(value, path);
|
|
97
86
|
if (!values.includes(text)) {
|
|
@@ -179,11 +168,13 @@ export function parseReviewOutput(text) {
|
|
|
179
168
|
const data = extractJson(text);
|
|
180
169
|
if (!data || typeof data !== "object")
|
|
181
170
|
throw new Error("review output must be an object");
|
|
171
|
+
if (data.requirementFindings != null)
|
|
172
|
+
throw new Error("requirementFindings is not accepted");
|
|
182
173
|
if (!isVerdict(data.verdict))
|
|
183
174
|
throw new Error("verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
|
|
184
175
|
const findings = requireArray(data.findings, "findings").map((finding, index) => {
|
|
185
176
|
const item = finding;
|
|
186
|
-
const line =
|
|
177
|
+
const line = requireLine(item.line, `findings[${index}].line`);
|
|
187
178
|
return {
|
|
188
179
|
fix: requireString(item.fix, `findings[${index}].fix`),
|
|
189
180
|
issue: requireString(item.issue, `findings[${index}].issue`),
|
|
@@ -199,17 +190,12 @@ export function parseReviewOutput(text) {
|
|
|
199
190
|
}),
|
|
200
191
|
};
|
|
201
192
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
throw new Error("
|
|
206
|
-
if (data.verdict === "
|
|
207
|
-
|
|
208
|
-
!requirementFindings.length)
|
|
209
|
-
throw new Error("CHANGES_REQUESTED requires findings or requirementFindings");
|
|
210
|
-
if (data.verdict === "CLOSE" &&
|
|
211
|
-
(findings.length || requirementFindings.length))
|
|
212
|
-
throw new Error("CLOSE requires no findings or requirementFindings");
|
|
193
|
+
if (data.verdict === "MERGE" && findings.length)
|
|
194
|
+
throw new Error("MERGE requires no findings");
|
|
195
|
+
if (data.verdict === "CHANGES_REQUESTED" && !findings.length)
|
|
196
|
+
throw new Error("CHANGES_REQUESTED requires findings");
|
|
197
|
+
if (data.verdict === "CLOSE" && findings.length)
|
|
198
|
+
throw new Error("CLOSE requires no findings");
|
|
213
199
|
const reason = typeof data.reason === "string" && data.reason.trim()
|
|
214
200
|
? data.reason
|
|
215
201
|
: undefined;
|
|
@@ -218,7 +204,6 @@ export function parseReviewOutput(text) {
|
|
|
218
204
|
return {
|
|
219
205
|
findings,
|
|
220
206
|
reason,
|
|
221
|
-
requirementFindings,
|
|
222
207
|
verdict: data.verdict,
|
|
223
208
|
};
|
|
224
209
|
}
|
|
@@ -226,6 +211,8 @@ export function parseRereviewOutput(text) {
|
|
|
226
211
|
const data = extractJson(text);
|
|
227
212
|
if (!isVerdict(data.verdict))
|
|
228
213
|
throw new Error("rereview verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
|
|
214
|
+
if (data.requirementFindings != null)
|
|
215
|
+
throw new Error("requirementFindings is not accepted");
|
|
229
216
|
const resolve = requireArray(data.resolve, "resolve").map((item, index) => {
|
|
230
217
|
const value = item;
|
|
231
218
|
return {
|
|
@@ -242,7 +229,7 @@ export function parseRereviewOutput(text) {
|
|
|
242
229
|
});
|
|
243
230
|
const newFindings = requireArray(data.newFindings, "newFindings").map((item, index) => {
|
|
244
231
|
const value = item;
|
|
245
|
-
const line =
|
|
232
|
+
const line = requireLine(value.line, `newFindings[${index}].line`);
|
|
246
233
|
return {
|
|
247
234
|
body: requireString(value.body, `newFindings[${index}].body`),
|
|
248
235
|
line,
|
|
@@ -254,29 +241,24 @@ export function parseRereviewOutput(text) {
|
|
|
254
241
|
}),
|
|
255
242
|
};
|
|
256
243
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
(followUps.length || newFindings.length || requirementFindings.length)) {
|
|
260
|
-
throw new Error("MERGE requires no followUps, newFindings, or requirementFindings");
|
|
244
|
+
if (data.verdict === "MERGE" && (followUps.length || newFindings.length)) {
|
|
245
|
+
throw new Error("MERGE requires no followUps or newFindings");
|
|
261
246
|
}
|
|
262
|
-
if (data.verdict === "CLOSE" &&
|
|
263
|
-
(followUps
|
|
264
|
-
throw new Error("CLOSE requires no followUps, newFindings, or requirementFindings");
|
|
247
|
+
if (data.verdict === "CLOSE" && (followUps.length || newFindings.length)) {
|
|
248
|
+
throw new Error("CLOSE requires no followUps or newFindings");
|
|
265
249
|
}
|
|
266
250
|
if (data.verdict === "CLOSE" && !data.reason) {
|
|
267
251
|
throw new Error("CLOSE requires reason");
|
|
268
252
|
}
|
|
269
253
|
if (data.verdict === "CHANGES_REQUESTED" &&
|
|
270
254
|
!followUps.length &&
|
|
271
|
-
!newFindings.length
|
|
272
|
-
|
|
273
|
-
throw new Error("CHANGES_REQUESTED requires followUps, newFindings, or requirementFindings");
|
|
255
|
+
!newFindings.length) {
|
|
256
|
+
throw new Error("CHANGES_REQUESTED requires followUps or newFindings");
|
|
274
257
|
}
|
|
275
258
|
return {
|
|
276
259
|
followUps,
|
|
277
260
|
newFindings,
|
|
278
261
|
reason: data.reason == null ? undefined : requireString(data.reason, "reason"),
|
|
279
|
-
requirementFindings,
|
|
280
262
|
resolve,
|
|
281
263
|
verdict: data.verdict,
|
|
282
264
|
};
|
|
@@ -2,7 +2,7 @@ Fix pull request #{pr} for {owner}/{repo}.
|
|
|
2
2
|
The PR worktree is {worktreePath}.
|
|
3
3
|
|
|
4
4
|
Act as the PR author and address every blocking review finding listed below.
|
|
5
|
-
Review findings are the complete set of requested changes.
|
|
5
|
+
Review findings are the complete set of requested changes. Each finding targets a PR diff line and should have a corresponding GitHub review thread unless it comes from legacy read-side state.
|
|
6
6
|
{reviewFindings}
|
|
7
7
|
|
|
8
8
|
Unresolved GitHub review threads are conversations that may need replies or resolution.
|
|
@@ -12,5 +12,4 @@ For each review finding and thread, decide whether you agree with the reviewer.
|
|
|
12
12
|
If you understand and agree with the requested change, edit the code, stage changes, commit, and reply with action FIXED for each related thread.
|
|
13
13
|
If a requested change in a thread is incorrect or unnecessary and you have a clear reason, do not edit for that thread; reply with action DISAGREE and explain why.
|
|
14
14
|
If you cannot determine whether a threaded request is correct or what change is expected, do not blindly edit; reply with action ASK and ask a concrete question.
|
|
15
|
-
File-level and requirement findings may not have a thread to reply to, but they are still blocking and must be addressed.
|
|
16
15
|
Do not make changes just because a reviewer requested them. Do not push.
|
|
@@ -2,4 +2,5 @@ You requested CLOSE for pull request #{pr} in {owner}/{repo}, but the other revi
|
|
|
2
2
|
Reconsider your decision using the existing session context and choose MERGE or CHANGES_REQUESTED instead.
|
|
3
3
|
Original close reason:
|
|
4
4
|
{closeReason}
|
|
5
|
+
Every finding must target a valid right-side line in the PR diff. If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior.
|
|
5
6
|
Do not edit files or perform write operations.
|
|
@@ -9,6 +9,9 @@ If there is no new commit, still reconsider the thread when a user replied after
|
|
|
9
9
|
If you agree with the user's explanation or the code is fixed, resolve the thread.
|
|
10
10
|
If you do not agree, reply in the same thread with a followUp explaining why the issue still needs changes and keep CHANGES_REQUESTED.
|
|
11
11
|
Do not duplicate an existing unresolved thread as a newFinding. Use newFindings only for separate new issues.
|
|
12
|
+
Every newFinding must target a valid right-side line in the PR diff.
|
|
13
|
+
If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
|
|
14
|
+
Do not omit line. Do not create file-level or body-only newFindings.
|
|
12
15
|
|
|
13
16
|
{ciFailureContextBlock}
|
|
14
17
|
Do not edit files or perform write operations.
|
|
@@ -10,4 +10,8 @@ Request changes if a closing issue requirement is missing, only documented, only
|
|
|
10
10
|
Do not approve solely because the PR improves the codebase if it claims to close an issue that remains incomplete.
|
|
11
11
|
For referenced non-closing issues, use them as context only unless the PR body explicitly claims to complete them.
|
|
12
12
|
|
|
13
|
+
Every finding must target a valid right-side line in the PR diff.
|
|
14
|
+
If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
|
|
15
|
+
Do not omit line. Do not create file-level or body-only findings.
|
|
16
|
+
|
|
13
17
|
{ciFailureContextBlock}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-magi",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260521222649",
|
|
4
4
|
"description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",
|