opencode-magi 0.0.0-dev-20260630052048 → 0.0.0-dev-20260630070412
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/magi.js +1 -1
- package/dist/prompts/merge/edit/output-contract.md +5 -3
- package/dist/prompts/merge/edit/validate.json +6 -3
- package/dist/prompts/review/close-reconsideration/output-contract.md +2 -1
- package/dist/prompts/review/close-reconsideration/validate.json +11 -5
- package/dist/prompts/review/rereview/output-contract.md +4 -3
- package/dist/prompts/review/rereview/validate.json +13 -6
- package/dist/prompts/review/review/output-contract.md +3 -2
- package/dist/prompts/review/review/validate.json +15 -6
- package/dist/prompts/triage/create/output-contract.md +4 -3
- package/dist/prompts/triage/create/validate.json +8 -2
- package/dist/prompts/triage/signal/output-contract.md +1 -1
- package/dist/prompts/triage/signal/validate.json +0 -1
- package/dist/tools/merge/action.js +2 -2
- package/dist/tools/merge/editor.js +11 -7
- package/dist/tools/merge/merge.js +1 -1
- package/dist/tools/review/action.js +4 -4
- package/dist/tools/review/check.js +6 -6
- package/dist/tools/review/review.js +5 -1
- package/dist/tools/review/reviewer.js +9 -9
- package/dist/utils/function.js +1 -1
- package/package.json +1 -1
package/dist/magi.js
CHANGED
|
@@ -200,7 +200,7 @@ export class Magi {
|
|
|
200
200
|
const prev = await this.getState(dir);
|
|
201
201
|
const state = merge(prev, next);
|
|
202
202
|
const values = [this.createStateFile(state)];
|
|
203
|
-
if (next.text)
|
|
203
|
+
if (!state.sync && next.text)
|
|
204
204
|
values.push(this.notify(prev.sessionId, next.text));
|
|
205
205
|
await Promise.all(values);
|
|
206
206
|
return state;
|
|
@@ -19,6 +19,8 @@ Rules:
|
|
|
19
19
|
- `"ASK"` means you need clarification and did not edit.
|
|
20
20
|
- Do not make changes just because a reviewer requested them; edit only when you understand and agree.
|
|
21
21
|
- Do not push. The orchestrator pushes after validating this envelope.
|
|
22
|
-
- `filesTouched` must include every final changed file.
|
|
23
|
-
- `responses` must include a reply for each thread you addressed.
|
|
24
|
-
- `"
|
|
22
|
+
- `filesTouched` is required for `"EDITED"` and must include every final changed file.
|
|
23
|
+
- `responses` is required and must include a reply for each thread you addressed.
|
|
24
|
+
- `"EDITED"` requires at least one response.
|
|
25
|
+
- `"REPLIED"` may omit `filesTouched`. If present, it must be empty.
|
|
26
|
+
- `"REPLIED"` requires at least one `"DISAGREE"` or `"ASK"` response.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
|
-
"required": ["mode", "
|
|
3
|
+
"required": ["mode", "responses"],
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"mode": { "enum": ["EDITED", "REPLIED"] },
|
|
@@ -28,8 +28,11 @@
|
|
|
28
28
|
{
|
|
29
29
|
"if": { "properties": { "mode": { "const": "EDITED" } } },
|
|
30
30
|
"then": {
|
|
31
|
-
"required": ["commitSha", "commitMessage"],
|
|
32
|
-
"properties": {
|
|
31
|
+
"required": ["commitSha", "commitMessage", "filesTouched"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"filesTouched": { "type": "array", "minItems": 1 },
|
|
34
|
+
"responses": { "type": "array", "minItems": 1 }
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
37
|
},
|
|
35
38
|
{
|
|
@@ -11,7 +11,8 @@ Return exactly one JSON object and nothing else. Do not wrap it in markdown.
|
|
|
11
11
|
Rules:
|
|
12
12
|
|
|
13
13
|
- `"verdict"` must be `"APPROVED"` or `"CHANGES_REQUESTED"`.
|
|
14
|
-
- `"APPROVED"`
|
|
14
|
+
- `"APPROVED"` may omit `"findings"`. If present, it must be empty.
|
|
15
|
+
- `"APPROVED"` may omit `"comment"`. If present, it must be an empty string.
|
|
15
16
|
- `"CHANGES_REQUESTED"` requires `"comment"` and at least one `"finding"`.
|
|
16
17
|
- `"comment"` for `"CHANGES_REQUESTED"` must be a concise prose review summary, not a bullet list of findings.
|
|
17
18
|
- `"CLOSED"` is not allowed in this reconsideration step.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
|
-
"required": ["verdict"
|
|
3
|
+
"required": ["verdict"],
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"verdict": { "enum": ["APPROVED", "CHANGES_REQUESTED"] },
|
|
@@ -18,20 +18,26 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"comment": { "type": "string"
|
|
21
|
+
"comment": { "type": "string" }
|
|
22
22
|
},
|
|
23
23
|
"allOf": [
|
|
24
24
|
{
|
|
25
25
|
"if": { "properties": { "verdict": { "const": "APPROVED" } } },
|
|
26
26
|
"then": {
|
|
27
|
-
"properties": {
|
|
27
|
+
"properties": {
|
|
28
|
+
"findings": { "type": "array", "maxItems": 0 },
|
|
29
|
+
"comment": { "const": "" }
|
|
30
|
+
}
|
|
28
31
|
}
|
|
29
32
|
},
|
|
30
33
|
{
|
|
31
34
|
"if": { "properties": { "verdict": { "const": "CHANGES_REQUESTED" } } },
|
|
32
35
|
"then": {
|
|
33
|
-
"required": ["comment"],
|
|
34
|
-
"properties": {
|
|
36
|
+
"required": ["comment", "findings"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"findings": { "type": "array", "minItems": 1 },
|
|
39
|
+
"comment": { "type": "string", "minLength": 1 }
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
]
|
|
@@ -15,11 +15,12 @@ Rules:
|
|
|
15
15
|
- `"verdict"` must be `"APPROVED"`, `"CHANGES_REQUESTED"`, or `"CLOSED"`.
|
|
16
16
|
- `"resolves"` contains threads that should be resolved because the issue is fixed or the user's explanation is acceptable.
|
|
17
17
|
- Each `"resolves"` item must use the exact `"commentId"` and `"threadId"` from `<unresolved_threads>`.
|
|
18
|
-
-
|
|
19
|
-
- `"APPROVED"`
|
|
18
|
+
- Omit `"resolves"` when no thread should be resolved.
|
|
19
|
+
- `"APPROVED"` may omit `"followUps"` and `"newFindings"`. If present, they must be empty.
|
|
20
|
+
- `"APPROVED"` may omit `"comment"`. If present, it must be an empty string.
|
|
20
21
|
- `"CHANGES_REQUESTED"` requires `"comment"` and at least one `"followUp"` or `"newFinding"`.
|
|
21
22
|
- `"comment"` for `"CHANGES_REQUESTED"` must be a concise prose review summary, not a bullet list of findings.
|
|
22
|
-
- `"CLOSED"` requires `"comment"` and
|
|
23
|
+
- `"CLOSED"` requires `"comment"` and may omit `"followUps"` and `"newFindings"`. If present, they must be empty.
|
|
23
24
|
- `"line"` is required and must target a valid right-side line inside the latest PR diff hunk.
|
|
24
25
|
- `"startLine"` is optional and must also target a valid right-side line inside the same latest PR diff hunk range.
|
|
25
26
|
- Omit `"startLine"` for single-line findings.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
|
-
"required": ["verdict"
|
|
3
|
+
"required": ["verdict"],
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"verdict": { "enum": ["APPROVED", "CHANGES_REQUESTED", "CLOSED"] },
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
-
"comment": { "type": "string"
|
|
45
|
+
"comment": { "type": "string" }
|
|
46
46
|
},
|
|
47
47
|
"allOf": [
|
|
48
48
|
{
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"then": {
|
|
51
51
|
"properties": {
|
|
52
52
|
"followUps": { "type": "array", "maxItems": 0 },
|
|
53
|
-
"newFindings": { "type": "array", "maxItems": 0 }
|
|
53
|
+
"newFindings": { "type": "array", "maxItems": 0 },
|
|
54
|
+
"comment": { "const": "" }
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
},
|
|
@@ -59,11 +60,16 @@
|
|
|
59
60
|
"then": {
|
|
60
61
|
"required": ["comment"],
|
|
61
62
|
"anyOf": [
|
|
62
|
-
{ "properties": { "followUps": { "type": "array", "minItems": 1 } } },
|
|
63
63
|
{
|
|
64
|
+
"required": ["followUps"],
|
|
65
|
+
"properties": { "followUps": { "type": "array", "minItems": 1 } }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"required": ["newFindings"],
|
|
64
69
|
"properties": { "newFindings": { "type": "array", "minItems": 1 } }
|
|
65
70
|
}
|
|
66
|
-
]
|
|
71
|
+
],
|
|
72
|
+
"properties": { "comment": { "type": "string", "minLength": 1 } }
|
|
67
73
|
}
|
|
68
74
|
},
|
|
69
75
|
{
|
|
@@ -72,7 +78,8 @@
|
|
|
72
78
|
"required": ["comment"],
|
|
73
79
|
"properties": {
|
|
74
80
|
"followUps": { "type": "array", "maxItems": 0 },
|
|
75
|
-
"newFindings": { "type": "array", "maxItems": 0 }
|
|
81
|
+
"newFindings": { "type": "array", "maxItems": 0 },
|
|
82
|
+
"comment": { "type": "string", "minLength": 1 }
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
}
|
|
@@ -11,10 +11,11 @@ Return exactly one JSON object and nothing else. Do not wrap it in markdown.
|
|
|
11
11
|
Rules:
|
|
12
12
|
|
|
13
13
|
- `"verdict"` must be `"APPROVED"`, `"CHANGES_REQUESTED"`, or `"CLOSED"`.
|
|
14
|
-
- `"APPROVED"`
|
|
14
|
+
- `"APPROVED"` may omit `"findings"`. If present, it must be empty.
|
|
15
|
+
- `"APPROVED"` may omit `"comment"`. If present, it must be an empty string.
|
|
15
16
|
- `"CHANGES_REQUESTED"` requires `"comment"` and at least one `"finding"`.
|
|
16
17
|
- `"comment"` for `"CHANGES_REQUESTED"` must be a concise prose review summary, not a bullet list of findings.
|
|
17
|
-
- `"CLOSED"` requires `"comment"` and
|
|
18
|
+
- `"CLOSED"` requires `"comment"` and may omit `"findings"`. If present, `"findings"` must be empty.
|
|
18
19
|
- `"path"` must be repository-relative.
|
|
19
20
|
- `"line"` is required and must target a valid right-side line inside the PR diff hunk.
|
|
20
21
|
- `"startLine"` is optional and must also target a valid right-side line inside the same PR diff hunk range.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
|
-
"required": ["verdict"
|
|
3
|
+
"required": ["verdict"],
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"verdict": { "enum": ["APPROVED", "CHANGES_REQUESTED", "CLOSED"] },
|
|
@@ -18,27 +18,36 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"comment": { "type": "string"
|
|
21
|
+
"comment": { "type": "string" }
|
|
22
22
|
},
|
|
23
23
|
"allOf": [
|
|
24
24
|
{
|
|
25
25
|
"if": { "properties": { "verdict": { "const": "APPROVED" } } },
|
|
26
26
|
"then": {
|
|
27
|
-
"properties": {
|
|
27
|
+
"properties": {
|
|
28
|
+
"findings": { "type": "array", "maxItems": 0 },
|
|
29
|
+
"comment": { "const": "" }
|
|
30
|
+
}
|
|
28
31
|
}
|
|
29
32
|
},
|
|
30
33
|
{
|
|
31
34
|
"if": { "properties": { "verdict": { "const": "CHANGES_REQUESTED" } } },
|
|
32
35
|
"then": {
|
|
33
|
-
"required": ["comment"],
|
|
34
|
-
"properties": {
|
|
36
|
+
"required": ["comment", "findings"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"findings": { "type": "array", "minItems": 1 },
|
|
39
|
+
"comment": { "type": "string", "minLength": 1 }
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
},
|
|
37
43
|
{
|
|
38
44
|
"if": { "properties": { "verdict": { "const": "CLOSED" } } },
|
|
39
45
|
"then": {
|
|
40
46
|
"required": ["comment"],
|
|
41
|
-
"properties": {
|
|
47
|
+
"properties": {
|
|
48
|
+
"findings": { "type": "array", "maxItems": 0 },
|
|
49
|
+
"comment": { "type": "string", "minLength": 1 }
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
]
|
|
@@ -22,6 +22,7 @@ Rules:
|
|
|
22
22
|
- Use REPLIED when you only replied without code changes.
|
|
23
23
|
- For EDITED, pullRequest.title and pullRequest.body must be non-empty and follow the repository's PR conventions.
|
|
24
24
|
- Do not push or create the PR. The orchestrator pushes and creates the PR using pullRequest exactly as provided.
|
|
25
|
-
- filesTouched must include every final changed file.
|
|
26
|
-
- responses may be
|
|
27
|
-
- REPLIED
|
|
25
|
+
- filesTouched is required for EDITED and must include every final changed file.
|
|
26
|
+
- responses may be omitted when no review threads were addressed.
|
|
27
|
+
- REPLIED may omit filesTouched. If present, it must be empty.
|
|
28
|
+
- REPLIED requires at least one DISAGREE or ASK response.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
|
-
"required": ["mode"
|
|
3
|
+
"required": ["mode"],
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"mode": { "enum": ["EDITED", "REPLIED"] },
|
|
@@ -37,13 +37,19 @@
|
|
|
37
37
|
{
|
|
38
38
|
"if": { "properties": { "mode": { "const": "EDITED" } } },
|
|
39
39
|
"then": {
|
|
40
|
-
"required": [
|
|
40
|
+
"required": [
|
|
41
|
+
"commitSha",
|
|
42
|
+
"commitMessage",
|
|
43
|
+
"filesTouched",
|
|
44
|
+
"pullRequest"
|
|
45
|
+
],
|
|
41
46
|
"properties": { "filesTouched": { "type": "array", "minItems": 1 } }
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
49
|
{
|
|
45
50
|
"if": { "properties": { "mode": { "const": "REPLIED" } } },
|
|
46
51
|
"then": {
|
|
52
|
+
"required": ["responses"],
|
|
47
53
|
"not": {
|
|
48
54
|
"anyOf": [
|
|
49
55
|
{ "required": ["commitSha"] },
|
|
@@ -9,10 +9,10 @@ export async function editCycles(callback) {
|
|
|
9
9
|
throw error;
|
|
10
10
|
return verdict;
|
|
11
11
|
}, {
|
|
12
|
-
error: (e, count) => {
|
|
12
|
+
error: async (e, count) => {
|
|
13
13
|
if (e !== error)
|
|
14
14
|
throw e;
|
|
15
|
-
this.
|
|
15
|
+
await this.notify(`Attempt ${count} failed to edit cycles for ${this.getLink()}. Retrying...`);
|
|
16
16
|
},
|
|
17
17
|
retries: this.config.merge.maxThreadResolutionCycles,
|
|
18
18
|
});
|
|
@@ -30,17 +30,21 @@ export async function edit() {
|
|
|
30
30
|
const parsed = prompt.parse(raw);
|
|
31
31
|
if (!prompt.validate(parsed))
|
|
32
32
|
throw new Error("Invalid output for editor.");
|
|
33
|
-
|
|
33
|
+
const output = {
|
|
34
|
+
...parsed,
|
|
35
|
+
filesTouched: parsed.filesTouched ?? [],
|
|
36
|
+
};
|
|
37
|
+
if (output.mode === "EDITED") {
|
|
34
38
|
const head = await this.exec(command("git", "rev-parse", "HEAD"), {
|
|
35
39
|
cwd: this.state.worktree.path,
|
|
36
40
|
signal: this.context.abort,
|
|
37
41
|
});
|
|
38
|
-
if (head !==
|
|
39
|
-
throw new Error(`Editor reported commit ${
|
|
42
|
+
if (head !== output.commitSha)
|
|
43
|
+
throw new Error(`Editor reported commit ${output.commitSha}, but worktree HEAD is ${head}.`);
|
|
40
44
|
}
|
|
41
|
-
return
|
|
45
|
+
return output;
|
|
42
46
|
}, {
|
|
43
|
-
error: (_, count) => this.
|
|
47
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to edit ${this.getLink()}. Retrying...`),
|
|
44
48
|
retries: this.config.output.repairAttempts,
|
|
45
49
|
});
|
|
46
50
|
if (!output)
|
|
@@ -49,7 +53,7 @@ export async function edit() {
|
|
|
49
53
|
editor: { outputs: [...(this.state.editor.outputs ?? []), output] },
|
|
50
54
|
text: `Finished editing ${this.getLink()}.`,
|
|
51
55
|
});
|
|
52
|
-
await this.
|
|
56
|
+
await this.notify(filterEmpty([
|
|
53
57
|
`Editor ${output.mode === "EDITED" ? "edited" : "replied to"} ${this.getLink()}.`,
|
|
54
58
|
output.commitSha
|
|
55
59
|
? `Commit: ${output.commitSha}${output.commitMessage ? ` ${output.commitMessage}` : ""}`
|
|
@@ -135,7 +139,7 @@ export async function resolveConflict() {
|
|
|
135
139
|
responses: [],
|
|
136
140
|
};
|
|
137
141
|
}, {
|
|
138
|
-
error: (_, count) => this.
|
|
142
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to resolve conflicts for ${this.getLink()}. Retrying...`),
|
|
139
143
|
retries: this.config.output.repairAttempts,
|
|
140
144
|
});
|
|
141
145
|
if (!output)
|
|
@@ -27,8 +27,8 @@ export class Merge extends Review {
|
|
|
27
27
|
permissions: config.merge.editor.permissions,
|
|
28
28
|
};
|
|
29
29
|
const state = await magi.createState(join(config.review.output, number.toString()), {
|
|
30
|
+
...options,
|
|
30
31
|
command: "merge",
|
|
31
|
-
dryRun: options.dryRun,
|
|
32
32
|
editor,
|
|
33
33
|
operator,
|
|
34
34
|
pr: { number, url },
|
|
@@ -45,7 +45,7 @@ export async function postReviews() {
|
|
|
45
45
|
.map((finding) => ({ ...finding, id }));
|
|
46
46
|
});
|
|
47
47
|
if (!findings.length) {
|
|
48
|
-
this.
|
|
48
|
+
await this.notify(`Finished posting reviews for ${this.getLink()}.`);
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
params.comments = findings.map(({ body, id, line, path, startLine }) => ({
|
|
@@ -63,7 +63,7 @@ export async function postReviews() {
|
|
|
63
63
|
if (this.state.pr.verdict !== "APPROVED") {
|
|
64
64
|
if (!this.state.operator?.sessionId)
|
|
65
65
|
throw new MagiError("blocked", "Reporter session ID not found.");
|
|
66
|
-
await this.
|
|
66
|
+
await this.notify(`Generating comment for ${this.getLink()} by operator.`);
|
|
67
67
|
const contents = JSON.stringify(reviewers.flatMap(([, { outputs }]) => {
|
|
68
68
|
const output = outputs?.at(-1);
|
|
69
69
|
if (!output || output.verdict === "APPROVED")
|
|
@@ -88,12 +88,12 @@ export async function postReviews() {
|
|
|
88
88
|
throw new Error("Invalid output for operator.");
|
|
89
89
|
return parsed.comment;
|
|
90
90
|
}, {
|
|
91
|
-
error: (_, count) => this.
|
|
91
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to post comment for ${this.getLink()} by operator. Retrying...`),
|
|
92
92
|
retries: this.config.output.repairAttempts,
|
|
93
93
|
});
|
|
94
94
|
if (!output)
|
|
95
95
|
throw new MagiError("blocked", "Invalid output for operator.");
|
|
96
|
-
await this.
|
|
96
|
+
await this.notify(`Generated comment for ${this.getLink()} by operator.`);
|
|
97
97
|
params.body = output;
|
|
98
98
|
}
|
|
99
99
|
params.body = [
|
|
@@ -115,7 +115,7 @@ export async function classifyChecks() {
|
|
|
115
115
|
await Promise.all(Object.entries(this.state.reviewers ?? {}).map(([id, { sessionId }]) => worker.run(async () => {
|
|
116
116
|
if (!sessionId)
|
|
117
117
|
throw new Error(`No session ID found for reviewer ${id}.`);
|
|
118
|
-
await this.
|
|
118
|
+
await this.notify(`Classifying CI checks for ${this.getLink()} with reviewer ${id}.`);
|
|
119
119
|
const output = await retry(async (count) => {
|
|
120
120
|
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : repairMessage);
|
|
121
121
|
const parsed = prompt.parse(raw);
|
|
@@ -123,7 +123,7 @@ export async function classifyChecks() {
|
|
|
123
123
|
throw new Error(`Invalid output for reviewer ${id}.`);
|
|
124
124
|
return parsed;
|
|
125
125
|
}, {
|
|
126
|
-
error: (_, count) => this.
|
|
126
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to classify CI checks for ${this.getLink()} with reviewer ${id}. Retrying...`),
|
|
127
127
|
retries: this.config.output.repairAttempts,
|
|
128
128
|
});
|
|
129
129
|
if (!output)
|
|
@@ -151,7 +151,7 @@ export async function classifyChecks() {
|
|
|
151
151
|
.filter(([, { scope }]) => !scope)
|
|
152
152
|
.map(([id, { comment }]) => `- ${id}: ${comment}`),
|
|
153
153
|
};
|
|
154
|
-
await this.
|
|
154
|
+
await this.notify(filterEmpty([
|
|
155
155
|
scope
|
|
156
156
|
? `Check ${name} for ${this.getLink()} was classified as in scope by majority vote.`
|
|
157
157
|
: `Check ${name} for ${this.getLink()} was classified as out of scope by majority vote. Rerunning it.`,
|
|
@@ -192,7 +192,7 @@ export async function rerunChecks() {
|
|
|
192
192
|
}
|
|
193
193
|
else {
|
|
194
194
|
await retry(async () => {
|
|
195
|
-
await this.
|
|
195
|
+
await this.notify(`Rerunning checks ${label} for ${this.getLink()}.`);
|
|
196
196
|
await Promise.all(checks.failed
|
|
197
197
|
.filter(({ scope }) => !scope)
|
|
198
198
|
.map(async ({ id }) => rerunCheck.call(this, id)));
|
|
@@ -212,7 +212,7 @@ export async function rerunChecks() {
|
|
|
212
212
|
label = failedChecks.map(({ name }) => name).join(", ");
|
|
213
213
|
throw new Error(`Checks ${label} for ${this.getLink()} still failed.`);
|
|
214
214
|
}, {
|
|
215
|
-
error: (_, count) => this.
|
|
215
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to rerun checks ${label} for ${this.getLink()}. Retrying...`),
|
|
216
216
|
retries: this.config.review.checks.retryFailedJobs,
|
|
217
217
|
});
|
|
218
218
|
}
|
|
@@ -227,7 +227,7 @@ export async function rerunChecks() {
|
|
|
227
227
|
: undefined,
|
|
228
228
|
]).join("\n");
|
|
229
229
|
if (message)
|
|
230
|
-
await this.
|
|
230
|
+
await this.notify(message);
|
|
231
231
|
this.state = await this.magi.updateState(this.state.output, {
|
|
232
232
|
pr: { checks },
|
|
233
233
|
text: `Finished rerunning checks for ${this.getLink()}.`,
|
|
@@ -40,8 +40,8 @@ export class Review {
|
|
|
40
40
|
? config.review.reviewers.find(({ id }) => id === config.review.operator)
|
|
41
41
|
: config.review.reviewers[Math.abs(number) % config.review.reviewers.length];
|
|
42
42
|
const state = await magi.createState(join(config.review.output, number.toString()), {
|
|
43
|
+
...options,
|
|
43
44
|
command: "review",
|
|
44
|
-
dryRun: options.dryRun,
|
|
45
45
|
operator,
|
|
46
46
|
pr: { number, url },
|
|
47
47
|
repo: quote(`${config.github.owner}/${config.github.repo}`),
|
|
@@ -64,6 +64,10 @@ export class Review {
|
|
|
64
64
|
postReviews = postReviews;
|
|
65
65
|
automate = automate;
|
|
66
66
|
createReport = createReport;
|
|
67
|
+
async notify(text) {
|
|
68
|
+
if (!this.state.sync)
|
|
69
|
+
await this.magi.notify(this.state.sessionId, text);
|
|
70
|
+
}
|
|
67
71
|
async cleanup() {
|
|
68
72
|
if (this.state.worktree?.path)
|
|
69
73
|
await this.magi.deleteWorktree(this.state.worktree.path);
|
|
@@ -22,7 +22,7 @@ export async function review() {
|
|
|
22
22
|
throw new MagiError("blocked", "Reviewers not found.");
|
|
23
23
|
const { outputs, review, sessionId, status } = this.state.reviewers[id];
|
|
24
24
|
if (status === "skip") {
|
|
25
|
-
this.
|
|
25
|
+
await this.notify(`Skipping review for ${this.getLink()} with reviewer ${id}.`);
|
|
26
26
|
return [id, {}];
|
|
27
27
|
}
|
|
28
28
|
else {
|
|
@@ -32,7 +32,7 @@ export async function review() {
|
|
|
32
32
|
throw new MagiError("blocked", `Missing previous review commit for reviewer ${id}.`);
|
|
33
33
|
const rereview = status !== "initial";
|
|
34
34
|
const label = rereview ? "rereview" : "review";
|
|
35
|
-
await this.
|
|
35
|
+
await this.notify(`Running ${label} for ${this.getLink()} with reviewer ${id}.`);
|
|
36
36
|
const sha = rereview
|
|
37
37
|
? (review?.commit_id ?? this.state.pr.metadata.base.sha)
|
|
38
38
|
: this.state.pr.metadata.base.sha;
|
|
@@ -104,7 +104,7 @@ export async function review() {
|
|
|
104
104
|
validateInlineCommentTargets(status, parsed, inlineCommentTargets);
|
|
105
105
|
return parsed;
|
|
106
106
|
}, {
|
|
107
|
-
error: (_, count) => this.
|
|
107
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to ${label} for ${this.getLink()} with reviewer ${id}. Retrying...`),
|
|
108
108
|
retries: this.config.output.repairAttempts,
|
|
109
109
|
});
|
|
110
110
|
if (!output)
|
|
@@ -184,7 +184,7 @@ export async function reconsiderClose() {
|
|
|
184
184
|
repo: this.config.github.repo,
|
|
185
185
|
});
|
|
186
186
|
const repairMessage = await prompt.repair();
|
|
187
|
-
await this.
|
|
187
|
+
await this.notify(`Reconsidering close verdict for ${this.getLink()} with reviewer ${id}.`);
|
|
188
188
|
const output = await retry(async (count) => {
|
|
189
189
|
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : repairMessage);
|
|
190
190
|
const parsed = prompt.parse(raw);
|
|
@@ -193,7 +193,7 @@ export async function reconsiderClose() {
|
|
|
193
193
|
validateInlineCommentTargets("initial", parsed, inlineCommentTargets);
|
|
194
194
|
return parsed;
|
|
195
195
|
}, {
|
|
196
|
-
error: (_, count) => this.
|
|
196
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to reconsider close verdict for ${this.getLink()} with reviewer ${id}. Retrying...`),
|
|
197
197
|
retries: this.config.output.repairAttempts,
|
|
198
198
|
});
|
|
199
199
|
if (!output)
|
|
@@ -240,7 +240,7 @@ async function collectAcceptedFindings(findings) {
|
|
|
240
240
|
const { sessionId } = this.state.reviewers[id];
|
|
241
241
|
if (!sessionId)
|
|
242
242
|
throw new MagiError("blocked", `No session ID found for reviewer ${id}.`);
|
|
243
|
-
await this.
|
|
243
|
+
await this.notify(`Validating review findings for ${this.getLink()} with reviewer ${id}.`);
|
|
244
244
|
const targetFindings = findings.filter((target) => target.reviewer !== id);
|
|
245
245
|
const expectedKeys = new Set(targetFindings.map(({ index, reviewer }) => `${reviewer}:${index}`));
|
|
246
246
|
const taskMessage = await prompt.create(this.config.review.prompts?.findingValidation, [
|
|
@@ -273,7 +273,7 @@ async function collectAcceptedFindings(findings) {
|
|
|
273
273
|
throw new Error(`Missing finding vote: ${key}.`);
|
|
274
274
|
return parsed;
|
|
275
275
|
}, {
|
|
276
|
-
error: (_, count) => this.
|
|
276
|
+
error: (_, count) => this.notify(`Attempt ${count} failed to validate review findings for ${this.getLink()} with reviewer ${id}. Retrying...`),
|
|
277
277
|
retries: this.config.output.repairAttempts,
|
|
278
278
|
});
|
|
279
279
|
if (!output)
|
|
@@ -298,7 +298,7 @@ async function collectAcceptedFindings(findings) {
|
|
|
298
298
|
.map(({ validator, vote }) => `- ${validator}: ${vote.comment}`);
|
|
299
299
|
if (agrees >= threshold)
|
|
300
300
|
accepted.add(key);
|
|
301
|
-
await this.
|
|
301
|
+
await this.notify(filterEmpty([
|
|
302
302
|
`Finding ${reviewer} #${index + 1} for ${this.getLink()} was ${agrees >= threshold ? "accepted" : "rejected"} by majority vote.`,
|
|
303
303
|
`Finding: ${finding.path}:${finding.line}\n${finding.body}`,
|
|
304
304
|
acceptedComments.length
|
|
@@ -338,7 +338,7 @@ async function notifyVerdictChanges(prev, next, reason) {
|
|
|
338
338
|
const nextVerdict = reviewer.outputs?.at(-1)?.verdict;
|
|
339
339
|
if (!prevVerdict || !nextVerdict || prevVerdict === nextVerdict)
|
|
340
340
|
return;
|
|
341
|
-
await this.
|
|
341
|
+
await this.notify(`Reviewer ${id} verdict changed from ${prevVerdict} to ${nextVerdict} for ${this.getLink()} ${reason}.`);
|
|
342
342
|
}));
|
|
343
343
|
}
|
|
344
344
|
function validateInlineCommentTargets(status, output, inlineCommentTargets) {
|
package/dist/utils/function.js
CHANGED
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-20260630070412",
|
|
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>",
|