opencode-magi 0.6.1 → 0.8.0
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/README.md +16 -6
- package/dist/config/resolve.js +40 -3
- package/dist/config/validate.js +231 -66
- package/dist/github/commands.js +32 -0
- package/dist/index.js +21 -50
- package/dist/orchestrator/merge.js +310 -10
- package/dist/orchestrator/report.js +1 -1
- package/dist/orchestrator/review.js +97 -1
- package/dist/orchestrator/run-manager.js +4 -4
- package/dist/orchestrator/triage.js +312 -103
- package/dist/prompts/compose.js +59 -2
- package/dist/prompts/contracts.js +20 -1
- package/dist/prompts/output.js +19 -1
- package/dist/prompts/templates/merge/conflict.md +10 -0
- package/dist/prompts/templates/review/rereview.md +2 -0
- package/dist/prompts/templates/review/review.md +2 -0
- package/dist/prompts/templates/triage/acceptance.md +1 -1
- package/dist/prompts/templates/triage/signal.md +10 -0
- package/package.json +1 -1
- package/schema.json +89 -16
|
@@ -163,6 +163,90 @@ export async function inlineCommentTargetsForDiff(input) {
|
|
|
163
163
|
cwd: input.worktreePath,
|
|
164
164
|
}));
|
|
165
165
|
}
|
|
166
|
+
function firstTargetLine(targets, path) {
|
|
167
|
+
const lines = targets.get(path);
|
|
168
|
+
if (!lines?.size)
|
|
169
|
+
return undefined;
|
|
170
|
+
return [...lines].sort((a, b) => a - b)[0];
|
|
171
|
+
}
|
|
172
|
+
function mergeInlineCommentTargets(left, right) {
|
|
173
|
+
const merged = new Map();
|
|
174
|
+
for (const [path, lines] of [...left, ...right]) {
|
|
175
|
+
const targetLines = merged.get(path) ?? new Set();
|
|
176
|
+
for (const line of lines)
|
|
177
|
+
targetLines.add(line);
|
|
178
|
+
merged.set(path, targetLines);
|
|
179
|
+
}
|
|
180
|
+
return merged;
|
|
181
|
+
}
|
|
182
|
+
function targetLineSummary(targets, path) {
|
|
183
|
+
const lines = targets.get(path);
|
|
184
|
+
if (!lines?.size)
|
|
185
|
+
return "(none)";
|
|
186
|
+
const sorted = [...lines].sort((a, b) => a - b);
|
|
187
|
+
const shown = sorted.slice(0, 12).join(", ");
|
|
188
|
+
return sorted.length > 12 ? `${shown}, ...` : shown;
|
|
189
|
+
}
|
|
190
|
+
function indentedExcerpt(lines) {
|
|
191
|
+
return lines
|
|
192
|
+
.slice(0, 24)
|
|
193
|
+
.map((line) => ` ${line}`)
|
|
194
|
+
.join("\n");
|
|
195
|
+
}
|
|
196
|
+
function parseMergeConflictSections(output) {
|
|
197
|
+
const conflictHeaders = new Set([
|
|
198
|
+
"added in both",
|
|
199
|
+
"changed in both",
|
|
200
|
+
"removed in local",
|
|
201
|
+
"removed in remote",
|
|
202
|
+
]);
|
|
203
|
+
const sections = [];
|
|
204
|
+
let current;
|
|
205
|
+
for (const line of output.split("\n")) {
|
|
206
|
+
if (!line.trim())
|
|
207
|
+
continue;
|
|
208
|
+
if (!line.startsWith(" ") &&
|
|
209
|
+
!line.startsWith("+") &&
|
|
210
|
+
!line.startsWith("-") &&
|
|
211
|
+
!line.startsWith("@")) {
|
|
212
|
+
current = conflictHeaders.has(line)
|
|
213
|
+
? { lines: [line], paths: new Set() }
|
|
214
|
+
: undefined;
|
|
215
|
+
if (current)
|
|
216
|
+
sections.push(current);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!current)
|
|
220
|
+
continue;
|
|
221
|
+
current.lines.push(line);
|
|
222
|
+
const path = /^ (?:base|our|their)\s+\d+\s+[0-9a-f]+\s+(.+)$/.exec(line)?.[1];
|
|
223
|
+
if (path)
|
|
224
|
+
current.paths.add(path);
|
|
225
|
+
}
|
|
226
|
+
return sections.flatMap((section) => [...section.paths].map((path) => ({
|
|
227
|
+
excerpt: indentedExcerpt(section.lines),
|
|
228
|
+
path,
|
|
229
|
+
})));
|
|
230
|
+
}
|
|
231
|
+
export async function mergeConflictContextForDiff(input) {
|
|
232
|
+
const mergeBase = (await input.exec(`git merge-base ${shellQuote(input.baseSha)} ${shellQuote(input.headSha)}`, { cwd: input.worktreePath })).trim();
|
|
233
|
+
const output = await input.exec(`git merge-tree ${shellQuote(mergeBase)} ${shellQuote(input.headSha)} ${shellQuote(input.baseSha)}`, { cwd: input.worktreePath });
|
|
234
|
+
const conflicts = parseMergeConflictSections(output);
|
|
235
|
+
if (!conflicts.length)
|
|
236
|
+
return "";
|
|
237
|
+
return [
|
|
238
|
+
"The PR currently has unresolved merge conflicts with the base branch.",
|
|
239
|
+
"Treat unresolved conflicts as review findings and request changes when they make the PR unsafe or impossible to merge.",
|
|
240
|
+
"Use suggestedLine when it is present; it is a valid right-side PR diff line for an inline finding.",
|
|
241
|
+
...conflicts.map((conflict) => {
|
|
242
|
+
const suggestedLine = firstTargetLine(input.inlineCommentTargets, conflict.path);
|
|
243
|
+
const suggestedLineText = suggestedLine
|
|
244
|
+
? `suggestedLine: ${suggestedLine}`
|
|
245
|
+
: "suggestedLine: (no right-side PR diff line found)";
|
|
246
|
+
return `<conflict_file>\npath: ${conflict.path}\n${suggestedLineText}\nrightSideDiffLines: ${targetLineSummary(input.inlineCommentTargets, conflict.path)}\nmergeTreeExcerpt:\n${conflict.excerpt}\n</conflict_file>`;
|
|
247
|
+
}),
|
|
248
|
+
].join("\n");
|
|
249
|
+
}
|
|
166
250
|
function parsePostedFindingLocation(location) {
|
|
167
251
|
const range = /^(.*):(\d+)-(\d+)$/.exec(location);
|
|
168
252
|
if (range) {
|
|
@@ -709,6 +793,13 @@ export async function runReview(input) {
|
|
|
709
793
|
toSha: meta.headRefOid,
|
|
710
794
|
worktreePath,
|
|
711
795
|
});
|
|
796
|
+
const mergeConflictContext = await mergeConflictContextForDiff({
|
|
797
|
+
baseSha: meta.baseRefOid,
|
|
798
|
+
exec,
|
|
799
|
+
headSha: meta.headRefOid,
|
|
800
|
+
inlineCommentTargets: initialInlineCommentTargets,
|
|
801
|
+
worktreePath,
|
|
802
|
+
});
|
|
712
803
|
for (const reviewer of input.repository.agents.reviewers) {
|
|
713
804
|
const assignment = mode.assignments.get(reviewer.account);
|
|
714
805
|
if (assignment?.type !== "skip")
|
|
@@ -740,6 +831,9 @@ export async function runReview(input) {
|
|
|
740
831
|
toSha: meta.headRefOid,
|
|
741
832
|
worktreePath,
|
|
742
833
|
});
|
|
834
|
+
const rereviewInlineCommentTargets = mergeConflictContext
|
|
835
|
+
? mergeInlineCommentTargets(inlineCommentTargets, initialInlineCommentTargets)
|
|
836
|
+
: inlineCommentTargets;
|
|
743
837
|
const unresolved = unresolvedThreadsByAccount.get(reviewer.account) ??
|
|
744
838
|
(await fetchUnresolvedThreads(exec, input.repository, input.pr, reviewer.account));
|
|
745
839
|
const prompt = await composeRereviewPrompt({
|
|
@@ -747,6 +841,7 @@ export async function runReview(input) {
|
|
|
747
841
|
ciFailureContext,
|
|
748
842
|
directory: input.directory,
|
|
749
843
|
headSha: meta.headRefOid,
|
|
844
|
+
mergeConflictContext,
|
|
750
845
|
pr: input.pr,
|
|
751
846
|
previousReview: previousReviewText(previous),
|
|
752
847
|
previousHeadSha: previous.commit.oid,
|
|
@@ -787,7 +882,7 @@ export async function runReview(input) {
|
|
|
787
882
|
},
|
|
788
883
|
options: reviewer.options,
|
|
789
884
|
parentSessionId: input.parentSessionId,
|
|
790
|
-
parse: (text) => parseRereviewOutputWithInlineTargets(text,
|
|
885
|
+
parse: (text) => parseRereviewOutputWithInlineTargets(text, rereviewInlineCommentTargets),
|
|
791
886
|
permission: reviewer.permission,
|
|
792
887
|
prompt,
|
|
793
888
|
repairAttempts: input.config.output?.repairAttempts ?? 3,
|
|
@@ -819,6 +914,7 @@ export async function runReview(input) {
|
|
|
819
914
|
ciFailureContext,
|
|
820
915
|
directory: input.directory,
|
|
821
916
|
headSha: meta.headRefOid,
|
|
917
|
+
mergeConflictContext,
|
|
822
918
|
pr: input.pr,
|
|
823
919
|
repository: input.repository,
|
|
824
920
|
reviewContext,
|
|
@@ -1616,16 +1616,16 @@ export class MagiRunManager {
|
|
|
1616
1616
|
}));
|
|
1617
1617
|
}
|
|
1618
1618
|
if (progress.type === "triage_agent_started") {
|
|
1619
|
-
await this.notify(state, `**Triage
|
|
1619
|
+
await this.notify(state, `**Triage voter ${progress.voter}** started ${progress.phase} for ${issue}.`);
|
|
1620
1620
|
}
|
|
1621
1621
|
if (progress.type === "triage_agent_repair") {
|
|
1622
|
-
await this.notify(state, `**Triage
|
|
1622
|
+
await this.notify(state, `**Triage voter ${progress.voter}** started JSON regeneration for ${issue}.`);
|
|
1623
1623
|
}
|
|
1624
1624
|
if (progress.type === "triage_agent_completed") {
|
|
1625
|
-
await this.notify(state, `**Triage
|
|
1625
|
+
await this.notify(state, `**Triage voter ${progress.voter}** completed ${progress.phase} for ${issue}: ${progress.vote}.`);
|
|
1626
1626
|
}
|
|
1627
1627
|
if (progress.type === "triage_agent_failed") {
|
|
1628
|
-
await this.notify(state, `**Triage
|
|
1628
|
+
await this.notify(state, `**Triage voter ${progress.voter}** failed ${progress.phase} for ${issue}: ${redactSecrets(progress.error)}`);
|
|
1629
1629
|
}
|
|
1630
1630
|
if (progress.type === "comment_posting") {
|
|
1631
1631
|
await this.notify(state, `Posting triage comment for ${issue}.`);
|