opencode-magi 0.7.0 → 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/dist/config/resolve.js +28 -1
- package/dist/config/validate.js +97 -3
- package/dist/github/commands.js +32 -0
- package/dist/orchestrator/merge.js +310 -10
- package/dist/orchestrator/report.js +1 -1
- package/dist/orchestrator/review.js +97 -1
- package/dist/orchestrator/triage.js +249 -64
- 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 +57 -4
package/dist/prompts/compose.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { isAbsolute, join } from "node:path";
|
|
4
|
-
import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
|
|
4
|
+
import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageSignalOutputContract, triageVoteOutputContract, } from "./contracts";
|
|
5
5
|
async function readOptionalPrompt(directory, path, values = {}) {
|
|
6
6
|
if (!path)
|
|
7
7
|
return "";
|
|
@@ -35,6 +35,7 @@ function repositoryValues(repository) {
|
|
|
35
35
|
}
|
|
36
36
|
function reviewValues(input) {
|
|
37
37
|
const ciFailureContext = input.ciFailureContext?.trim() ?? "";
|
|
38
|
+
const mergeConflictContext = input.mergeConflictContext?.trim() ?? "";
|
|
38
39
|
return {
|
|
39
40
|
...repositoryValues(input.repository),
|
|
40
41
|
baseSha: input.baseSha,
|
|
@@ -44,6 +45,10 @@ function reviewValues(input) {
|
|
|
44
45
|
: "",
|
|
45
46
|
headSha: input.headSha,
|
|
46
47
|
jsonEncodedWorktreePath: JSON.stringify(input.worktreePath),
|
|
48
|
+
mergeConflictContext,
|
|
49
|
+
mergeConflictContextBlock: mergeConflictContext
|
|
50
|
+
? `<merge_conflict_context>\n${mergeConflictContext}\n</merge_conflict_context>`
|
|
51
|
+
: "",
|
|
47
52
|
pr: String(input.pr),
|
|
48
53
|
reviewContext: input.reviewContext ?? "",
|
|
49
54
|
worktreePath: input.worktreePath,
|
|
@@ -67,6 +72,17 @@ function editValues(input) {
|
|
|
67
72
|
worktreePath: input.worktreePath,
|
|
68
73
|
};
|
|
69
74
|
}
|
|
75
|
+
function mergeConflictValues(input) {
|
|
76
|
+
return {
|
|
77
|
+
...repositoryValues(input.repository),
|
|
78
|
+
baseBranch: input.baseBranch,
|
|
79
|
+
baseSha: input.baseSha,
|
|
80
|
+
conflictedFiles: input.conflictedFiles,
|
|
81
|
+
headSha: input.headSha,
|
|
82
|
+
pr: String(input.pr),
|
|
83
|
+
worktreePath: input.worktreePath,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
70
86
|
function triageValues(input) {
|
|
71
87
|
const categories = input.repository.triage?.categories ?? [];
|
|
72
88
|
const categoryOptions = categories
|
|
@@ -74,12 +90,16 @@ function triageValues(input) {
|
|
|
74
90
|
? `- ${category.id}: ${category.description}`
|
|
75
91
|
: `- ${category.id}`)
|
|
76
92
|
.join("\n");
|
|
93
|
+
const signalOptions = (input.repository.triage?.signals ?? [])
|
|
94
|
+
.map((signal) => `- ${signal.id}: ${signal.description}`)
|
|
95
|
+
.join("\n");
|
|
77
96
|
return {
|
|
78
97
|
...repositoryValues(input.repository),
|
|
79
98
|
author: input.author ?? "",
|
|
80
99
|
categoryOptions,
|
|
81
100
|
context: input.context,
|
|
82
101
|
issue: String(input.issue),
|
|
102
|
+
signalOptions,
|
|
83
103
|
worktreePath: input.worktreePath ?? "",
|
|
84
104
|
};
|
|
85
105
|
}
|
|
@@ -97,6 +117,12 @@ function previousReviewBlock(previousReview) {
|
|
|
97
117
|
function reviewContextBlock(reviewContext) {
|
|
98
118
|
return reviewContext?.trim() ? reviewContext.trim() : "";
|
|
99
119
|
}
|
|
120
|
+
function mergeConflictContextBlock(mergeConflictContext) {
|
|
121
|
+
const body = mergeConflictContext?.trim();
|
|
122
|
+
return body
|
|
123
|
+
? `<merge_conflict_context>\n${body}\n</merge_conflict_context>`
|
|
124
|
+
: "";
|
|
125
|
+
}
|
|
100
126
|
async function reviewGuidelinesBlock(input) {
|
|
101
127
|
const body = (await readOptionalPrompt(input.directory, input.path, input.values)).trim();
|
|
102
128
|
return body ? `<review_guidelines>\n${body}\n</review_guidelines>` : "";
|
|
@@ -136,6 +162,7 @@ export async function composeReviewPrompt(input) {
|
|
|
136
162
|
return [
|
|
137
163
|
task,
|
|
138
164
|
reviewContextBlock(input.reviewContext),
|
|
165
|
+
mergeConflictContextBlock(input.mergeConflictContext),
|
|
139
166
|
languageBlock(input.repository.language),
|
|
140
167
|
personaBlock(input.reviewer.persona),
|
|
141
168
|
await reviewGuidelinesBlock({
|
|
@@ -159,6 +186,7 @@ export async function composeRereviewPrompt(input) {
|
|
|
159
186
|
return [
|
|
160
187
|
task,
|
|
161
188
|
reviewContextBlock(input.reviewContext),
|
|
189
|
+
mergeConflictContextBlock(input.mergeConflictContext),
|
|
162
190
|
input.includeSessionContext === false
|
|
163
191
|
? ""
|
|
164
192
|
: languageBlock(input.repository.language),
|
|
@@ -200,6 +228,28 @@ export async function composeEditPrompt(input) {
|
|
|
200
228
|
.filter(Boolean)
|
|
201
229
|
.join("\n\n");
|
|
202
230
|
}
|
|
231
|
+
export async function composeMergeConflictPrompt(input) {
|
|
232
|
+
const values = mergeConflictValues(input);
|
|
233
|
+
const task = await taskBlock({
|
|
234
|
+
builtin: "merge/conflict",
|
|
235
|
+
directory: input.directory,
|
|
236
|
+
values,
|
|
237
|
+
});
|
|
238
|
+
const persona = input.repository.agents.editor?.persona;
|
|
239
|
+
return [
|
|
240
|
+
task,
|
|
241
|
+
languageBlock(input.repository.language),
|
|
242
|
+
personaBlock(persona),
|
|
243
|
+
await editGuidelinesBlock({
|
|
244
|
+
directory: input.directory,
|
|
245
|
+
path: input.repository.prompts.editGuidelines,
|
|
246
|
+
values,
|
|
247
|
+
}),
|
|
248
|
+
editOutputContract,
|
|
249
|
+
]
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.join("\n\n");
|
|
252
|
+
}
|
|
203
253
|
export async function composeFindingValidationPrompt(input) {
|
|
204
254
|
const values = { ...reviewValues(input), findings: input.findings };
|
|
205
255
|
const task = await taskBlock({
|
|
@@ -395,7 +445,14 @@ export async function composeTriageAcceptancePrompt(input) {
|
|
|
395
445
|
...input,
|
|
396
446
|
builtin: "acceptance",
|
|
397
447
|
customPath: input.repository.triage?.prompts.acceptance,
|
|
398
|
-
outputContract: triageVoteOutputContract('"YES" | "NO" | "ASK"'),
|
|
448
|
+
outputContract: triageVoteOutputContract('"YES" | "NO" | "INVALID" | "ASK"'),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
export async function composeTriageSignalPrompt(input) {
|
|
452
|
+
return composeTriageVotePrompt({
|
|
453
|
+
...input,
|
|
454
|
+
builtin: "signal",
|
|
455
|
+
outputContract: triageSignalOutputContract,
|
|
399
456
|
});
|
|
400
457
|
}
|
|
401
458
|
export async function composeTriageCommentClassificationPrompt(input) {
|
|
@@ -256,6 +256,24 @@ The object must match this shape:
|
|
|
256
256
|
]
|
|
257
257
|
}
|
|
258
258
|
</output_contract>`.trim();
|
|
259
|
+
export const triageSignalOutputContract = `
|
|
260
|
+
<output_contract>
|
|
261
|
+
Return exactly one JSON object and nothing else. Do not wrap it in markdown.
|
|
262
|
+
|
|
263
|
+
The object must match this shape:
|
|
264
|
+
{
|
|
265
|
+
"signals": [
|
|
266
|
+
{
|
|
267
|
+
"id": "configured_signal_id",
|
|
268
|
+
"reason": "Short rationale."
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
Rules:
|
|
274
|
+
- Return only configured signal IDs that apply.
|
|
275
|
+
- Return an empty signals array when none apply.
|
|
276
|
+
</output_contract>`.trim();
|
|
259
277
|
const outputContractsBySchemaName = {
|
|
260
278
|
"CI classification": ciClassificationOutputContract,
|
|
261
279
|
"close reconsideration": closeReconsiderationOutputContract,
|
|
@@ -264,13 +282,14 @@ const outputContractsBySchemaName = {
|
|
|
264
282
|
rereview: rereviewOutputContract,
|
|
265
283
|
"rereview close reconsideration": rereviewCloseReconsiderationOutputContract,
|
|
266
284
|
review: reviewOutputContract,
|
|
267
|
-
"triage acceptance": triageVoteOutputContract('"YES" | "NO" | "ASK"'),
|
|
285
|
+
"triage acceptance": triageVoteOutputContract('"YES" | "NO" | "INVALID" | "ASK"'),
|
|
268
286
|
"triage category": triageVoteOutputContract('"ASK" or one of the configured category IDs'),
|
|
269
287
|
"triage create PR": triageCreatePrOutputContract,
|
|
270
288
|
"triage comment classification": triageCommentClassificationOutputContract,
|
|
271
289
|
"triage duplicate": triageDuplicateOutputContract,
|
|
272
290
|
"triage existing PR": triageVoteOutputContract('"RELATED_PR_HANDLES_ISSUE" | "RELATED_PR_DOES_NOT_HANDLE_ISSUE"'),
|
|
273
291
|
"triage reconsider": triageVoteOutputContract('"YES" | "NO" | "ASK"'),
|
|
292
|
+
"triage signal": triageSignalOutputContract,
|
|
274
293
|
};
|
|
275
294
|
export function repairPrompt(schemaName) {
|
|
276
295
|
const outputContract = outputContractsBySchemaName[schemaName];
|
package/dist/prompts/output.js
CHANGED
|
@@ -112,7 +112,25 @@ export function parseTriageCategoryOutput(text, categories) {
|
|
|
112
112
|
return parseTriageVote(text, ["ASK", ...categories]);
|
|
113
113
|
}
|
|
114
114
|
export function parseTriageBinaryOutput(text) {
|
|
115
|
-
return parseTriageVote(text, ["ASK", "NO", "YES"]);
|
|
115
|
+
return parseTriageVote(text, ["ASK", "INVALID", "NO", "YES"]);
|
|
116
|
+
}
|
|
117
|
+
export function parseTriageSignalOutput(text, signalIds) {
|
|
118
|
+
const data = extractJson(text);
|
|
119
|
+
if (!data || typeof data !== "object")
|
|
120
|
+
throw new Error("triage signal output must be an object");
|
|
121
|
+
const ids = new Set(signalIds);
|
|
122
|
+
return {
|
|
123
|
+
signals: requireArray(data.signals, "signals").map((item, index) => {
|
|
124
|
+
const value = item;
|
|
125
|
+
const id = requireString(value.id, `signals[${index}].id`);
|
|
126
|
+
if (!ids.has(id))
|
|
127
|
+
throw new Error(`signals[${index}].id is not configured`);
|
|
128
|
+
return {
|
|
129
|
+
id,
|
|
130
|
+
reason: requireString(value.reason, `signals[${index}].reason`),
|
|
131
|
+
};
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
116
134
|
}
|
|
117
135
|
export function parseTriageDuplicateOutput(text) {
|
|
118
136
|
const data = extractJson(text);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Resolve merge conflicts for pull request #{pr} in {owner}/{repo}.
|
|
2
|
+
The PR worktree is {worktreePath}.
|
|
3
|
+
|
|
4
|
+
The latest base branch is {baseBranch} at {baseSha}.
|
|
5
|
+
The PR head before conflict recovery was {headSha}.
|
|
6
|
+
|
|
7
|
+
Conflicted files:
|
|
8
|
+
{conflictedFiles}
|
|
9
|
+
|
|
10
|
+
Resolve every merge conflict in the worktree. Preserve the intended PR behavior while incorporating the latest base branch changes. Stage all resolved files and create a commit. Do not push.
|
|
@@ -13,6 +13,8 @@ Every newFinding must target a valid right-side line in the PR diff.
|
|
|
13
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
14
|
Do not omit line. Do not create file-level or body-only newFindings.
|
|
15
15
|
|
|
16
|
+
If `<merge_conflict_context>` is present, treat unresolved merge conflicts as review findings. Request changes when a conflict makes the PR unsafe or impossible to merge, and prefer the provided `suggestedLine` when it is present.
|
|
17
|
+
|
|
16
18
|
{ciFailureContextBlock}
|
|
17
19
|
Do not edit files or perform write operations.
|
|
18
20
|
|
|
@@ -14,4 +14,6 @@ Every finding must target a valid right-side line in the PR diff.
|
|
|
14
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
15
|
Do not omit line. Do not create file-level or body-only findings.
|
|
16
16
|
|
|
17
|
+
If `<merge_conflict_context>` is present, treat unresolved merge conflicts as review findings. Request changes when a conflict makes the PR unsafe or impossible to merge, and prefer the provided `suggestedLine` when it is present.
|
|
18
|
+
|
|
17
19
|
{ciFailureContextBlock}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Evaluate issue #{issue} in {owner}/{repo} for the selected category.
|
|
2
2
|
|
|
3
|
-
Choose YES when the issue should be accepted for the project. Choose NO when it should be
|
|
3
|
+
Choose YES when the issue should be accepted for the project. Choose NO when it should not be worked on for normal wontfix reasons. Choose INVALID when the report is unreproducible, contradictory, based on an impossible premise, or otherwise not actionable as an issue. Choose ASK when specific missing information is required before deciding.
|
|
4
4
|
|
|
5
5
|
<context>
|
|
6
6
|
{context}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Evaluate optional triage signals for issue #{issue} in {owner}/{repo}.
|
|
2
|
+
|
|
3
|
+
Configured signals:
|
|
4
|
+
{signalOptions}
|
|
5
|
+
|
|
6
|
+
Return every configured signal that applies to the final triage result and issue context. Do not invent signal IDs.
|
|
7
|
+
|
|
8
|
+
<context>
|
|
9
|
+
{context}
|
|
10
|
+
</context>
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -179,6 +179,15 @@
|
|
|
179
179
|
"close": { "type": "boolean" }
|
|
180
180
|
}
|
|
181
181
|
},
|
|
182
|
+
"mergeAutomation": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": false,
|
|
185
|
+
"properties": {
|
|
186
|
+
"merge": { "type": "boolean", "default": true },
|
|
187
|
+
"close": { "type": "boolean", "default": false },
|
|
188
|
+
"conflict": { "type": "boolean", "default": false }
|
|
189
|
+
}
|
|
190
|
+
},
|
|
182
191
|
"reviewChecks": {
|
|
183
192
|
"type": "object",
|
|
184
193
|
"additionalProperties": false,
|
|
@@ -270,6 +279,47 @@
|
|
|
270
279
|
"description": { "type": "string" }
|
|
271
280
|
}
|
|
272
281
|
},
|
|
282
|
+
"triageSignal": {
|
|
283
|
+
"type": "object",
|
|
284
|
+
"additionalProperties": false,
|
|
285
|
+
"required": ["id", "description"],
|
|
286
|
+
"properties": {
|
|
287
|
+
"id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
|
288
|
+
"description": { "type": "string", "minLength": 1 }
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"triageLabelRuleCondition": {
|
|
292
|
+
"type": "object",
|
|
293
|
+
"additionalProperties": false,
|
|
294
|
+
"minProperties": 1,
|
|
295
|
+
"properties": {
|
|
296
|
+
"disposition": {
|
|
297
|
+
"enum": [
|
|
298
|
+
"accepted",
|
|
299
|
+
"rejected",
|
|
300
|
+
"invalid",
|
|
301
|
+
"duplicate",
|
|
302
|
+
"already_handled",
|
|
303
|
+
"needs_category",
|
|
304
|
+
"needs_acceptance",
|
|
305
|
+
"blocked",
|
|
306
|
+
"failed"
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
"category": { "type": "string" },
|
|
310
|
+
"signals": { "type": "array", "items": { "type": "string" } }
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"triageLabelRule": {
|
|
314
|
+
"type": "object",
|
|
315
|
+
"additionalProperties": false,
|
|
316
|
+
"required": ["when"],
|
|
317
|
+
"properties": {
|
|
318
|
+
"when": { "$ref": "#/$defs/triageLabelRuleCondition" },
|
|
319
|
+
"add": { "type": "array", "items": { "type": "string" } },
|
|
320
|
+
"remove": { "type": "array", "items": { "type": "string" } }
|
|
321
|
+
}
|
|
322
|
+
},
|
|
273
323
|
"triageAutomation": {
|
|
274
324
|
"type": "object",
|
|
275
325
|
"additionalProperties": false,
|
|
@@ -278,10 +328,9 @@
|
|
|
278
328
|
"create": { "type": "boolean", "default": false },
|
|
279
329
|
"review": { "type": "boolean", "default": false },
|
|
280
330
|
"merge": { "type": "boolean", "default": false },
|
|
281
|
-
"
|
|
331
|
+
"label": {
|
|
282
332
|
"type": "array",
|
|
283
|
-
"items": { "
|
|
284
|
-
"default": ["triage"]
|
|
333
|
+
"items": { "$ref": "#/$defs/triageLabelRule" }
|
|
285
334
|
}
|
|
286
335
|
}
|
|
287
336
|
},
|
|
@@ -339,7 +388,7 @@
|
|
|
339
388
|
"editor": { "$ref": "#/$defs/editor" },
|
|
340
389
|
"checks": { "$ref": "#/$defs/mergeChecks" },
|
|
341
390
|
"prompts": { "$ref": "#/$defs/mergePrompts" },
|
|
342
|
-
"automation": { "$ref": "#/$defs/
|
|
391
|
+
"automation": { "$ref": "#/$defs/mergeAutomation" },
|
|
343
392
|
"maxThreadResolutionCycles": {
|
|
344
393
|
"type": "integer",
|
|
345
394
|
"minimum": 0,
|
|
@@ -362,6 +411,10 @@
|
|
|
362
411
|
"type": "array",
|
|
363
412
|
"items": { "$ref": "#/$defs/triageCategory" }
|
|
364
413
|
},
|
|
414
|
+
"signals": {
|
|
415
|
+
"type": "array",
|
|
416
|
+
"items": { "$ref": "#/$defs/triageSignal" }
|
|
417
|
+
},
|
|
365
418
|
"automation": { "$ref": "#/$defs/triageAutomation" },
|
|
366
419
|
"safety": { "$ref": "#/$defs/triageSafety" },
|
|
367
420
|
"concurrency": { "$ref": "#/$defs/triageConcurrency" },
|