opencode-magi 0.1.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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/commands.js +18 -0
- package/dist/config/load.js +62 -0
- package/dist/config/output.js +16 -0
- package/dist/config/resolve.js +113 -0
- package/dist/config/validate.js +567 -0
- package/dist/github/commands.js +398 -0
- package/dist/github/retry.js +44 -0
- package/dist/index.js +536 -0
- package/dist/orchestrator/abort.js +9 -0
- package/dist/orchestrator/ci.js +568 -0
- package/dist/orchestrator/findings.js +66 -0
- package/dist/orchestrator/majority.js +48 -0
- package/dist/orchestrator/merge.js +836 -0
- package/dist/orchestrator/model.js +202 -0
- package/dist/orchestrator/pool.js +15 -0
- package/dist/orchestrator/report.js +168 -0
- package/dist/orchestrator/review.js +790 -0
- package/dist/orchestrator/run-manager.js +1663 -0
- package/dist/orchestrator/safety.js +44 -0
- package/dist/permissions/common.json +24 -0
- package/dist/permissions/editor.json +7 -0
- package/dist/prompts/compose.js +298 -0
- package/dist/prompts/contracts.js +189 -0
- package/dist/prompts/output.js +260 -0
- package/dist/prompts/templates/ci-classification-after-edit.md +16 -0
- package/dist/prompts/templates/ci-classification.md +9 -0
- package/dist/prompts/templates/close-reconsideration.md +6 -0
- package/dist/prompts/templates/edit.md +9 -0
- package/dist/prompts/templates/finding-validation.md +7 -0
- package/dist/prompts/templates/rereview-close-reconsideration.md +6 -0
- package/dist/prompts/templates/rereview.md +16 -0
- package/dist/prompts/templates/review.md +7 -0
- package/dist/types.js +1 -0
- package/package.json +69 -0
- package/schema.json +200 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
function extractJson(text) {
|
|
2
|
+
const trimmed = text.trim();
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(trimmed);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let escaped = false;
|
|
9
|
+
let inString = false;
|
|
10
|
+
let start = -1;
|
|
11
|
+
const candidates = [];
|
|
12
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
13
|
+
const char = trimmed[index];
|
|
14
|
+
if (escaped) {
|
|
15
|
+
escaped = false;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (char === "\\" && inString) {
|
|
19
|
+
escaped = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (char === '"') {
|
|
23
|
+
inString = !inString;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (inString)
|
|
27
|
+
continue;
|
|
28
|
+
if (char === "{") {
|
|
29
|
+
if (depth === 0)
|
|
30
|
+
start = index;
|
|
31
|
+
depth += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (char !== "}" || depth === 0)
|
|
35
|
+
continue;
|
|
36
|
+
depth -= 1;
|
|
37
|
+
if (depth === 0 && start !== -1) {
|
|
38
|
+
candidates.push(trimmed.slice(start, index + 1));
|
|
39
|
+
start = -1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const candidate of candidates.reverse()) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(candidate);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Keep scanning older JSON-looking blocks; prompts may include examples.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error("output does not contain a valid JSON object");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function isVerdict(value) {
|
|
54
|
+
return value === "MERGE" || value === "CHANGES_REQUESTED" || value === "CLOSE";
|
|
55
|
+
}
|
|
56
|
+
function isEditResponseAction(value) {
|
|
57
|
+
return value === "FIXED" || value === "DISAGREE" || value === "ASK";
|
|
58
|
+
}
|
|
59
|
+
function requireArray(value, path) {
|
|
60
|
+
if (!Array.isArray(value))
|
|
61
|
+
throw new Error(`${path} must be an array`);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function requireString(value, path) {
|
|
65
|
+
if (typeof value !== "string" || !value)
|
|
66
|
+
throw new Error(`${path} must be a non-empty string`);
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function requireNumber(value, path) {
|
|
70
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
71
|
+
throw new Error(`${path} must be an integer`);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
export function parseReviewOutput(text) {
|
|
75
|
+
const data = extractJson(text);
|
|
76
|
+
if (!data || typeof data !== "object")
|
|
77
|
+
throw new Error("review output must be an object");
|
|
78
|
+
if (!isVerdict(data.verdict))
|
|
79
|
+
throw new Error("verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
|
|
80
|
+
const findings = requireArray(data.findings, "findings").map((finding, index) => {
|
|
81
|
+
const item = finding;
|
|
82
|
+
return {
|
|
83
|
+
fix: requireString(item.fix, `findings[${index}].fix`),
|
|
84
|
+
issue: requireString(item.issue, `findings[${index}].issue`),
|
|
85
|
+
line: requireNumber(item.line, `findings[${index}].line`),
|
|
86
|
+
path: requireString(item.path, `findings[${index}].path`),
|
|
87
|
+
perspective: item.perspective == null
|
|
88
|
+
? undefined
|
|
89
|
+
: requireString(item.perspective, `findings[${index}].perspective`),
|
|
90
|
+
startLine: item.startLine == null
|
|
91
|
+
? undefined
|
|
92
|
+
: requireNumber(item.startLine, `findings[${index}].startLine`),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
if (data.verdict === "MERGE" && findings.length)
|
|
96
|
+
throw new Error("MERGE requires no findings");
|
|
97
|
+
if (data.verdict === "CHANGES_REQUESTED" && !findings.length)
|
|
98
|
+
throw new Error("CHANGES_REQUESTED requires findings");
|
|
99
|
+
if (data.verdict === "CLOSE" && findings.length)
|
|
100
|
+
throw new Error("CLOSE requires no findings");
|
|
101
|
+
const reason = typeof data.reason === "string" && data.reason.trim()
|
|
102
|
+
? data.reason
|
|
103
|
+
: undefined;
|
|
104
|
+
if (data.verdict === "CLOSE" && !reason)
|
|
105
|
+
throw new Error("CLOSE requires reason");
|
|
106
|
+
return {
|
|
107
|
+
findings,
|
|
108
|
+
reason,
|
|
109
|
+
verdict: data.verdict,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function parseRereviewOutput(text) {
|
|
113
|
+
const data = extractJson(text);
|
|
114
|
+
if (!isVerdict(data.verdict))
|
|
115
|
+
throw new Error("rereview verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
|
|
116
|
+
const resolve = requireArray(data.resolve, "resolve").map((item, index) => {
|
|
117
|
+
const value = item;
|
|
118
|
+
return {
|
|
119
|
+
commentId: requireNumber(value.commentId, `resolve[${index}].commentId`),
|
|
120
|
+
threadId: requireString(value.threadId, `resolve[${index}].threadId`),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
const followUps = requireArray(data.followUps, "followUps").map((item, index) => {
|
|
124
|
+
const value = item;
|
|
125
|
+
return {
|
|
126
|
+
body: requireString(value.body, `followUps[${index}].body`),
|
|
127
|
+
commentId: requireNumber(value.commentId, `followUps[${index}].commentId`),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const newFindings = requireArray(data.newFindings, "newFindings").map((item, index) => {
|
|
131
|
+
const value = item;
|
|
132
|
+
return {
|
|
133
|
+
body: requireString(value.body, `newFindings[${index}].body`),
|
|
134
|
+
line: requireNumber(value.line, `newFindings[${index}].line`),
|
|
135
|
+
path: requireString(value.path, `newFindings[${index}].path`),
|
|
136
|
+
startLine: value.startLine == null
|
|
137
|
+
? undefined
|
|
138
|
+
: requireNumber(value.startLine, `newFindings[${index}].startLine`),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
if (data.verdict === "MERGE" && (followUps.length || newFindings.length)) {
|
|
142
|
+
throw new Error("MERGE requires no followUps or newFindings");
|
|
143
|
+
}
|
|
144
|
+
if (data.verdict === "CLOSE" && (followUps.length || newFindings.length)) {
|
|
145
|
+
throw new Error("CLOSE requires no followUps or newFindings");
|
|
146
|
+
}
|
|
147
|
+
if (data.verdict === "CLOSE" && !data.reason) {
|
|
148
|
+
throw new Error("CLOSE requires reason");
|
|
149
|
+
}
|
|
150
|
+
if (data.verdict === "CHANGES_REQUESTED" &&
|
|
151
|
+
!followUps.length &&
|
|
152
|
+
!newFindings.length) {
|
|
153
|
+
throw new Error("CHANGES_REQUESTED requires followUps or newFindings");
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
followUps,
|
|
157
|
+
newFindings,
|
|
158
|
+
reason: data.reason == null ? undefined : requireString(data.reason, "reason"),
|
|
159
|
+
resolve,
|
|
160
|
+
verdict: data.verdict,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function parseFindingValidationOutput(text) {
|
|
164
|
+
const data = extractJson(text);
|
|
165
|
+
if (!data || typeof data !== "object")
|
|
166
|
+
throw new Error("finding validation output must be an object");
|
|
167
|
+
return {
|
|
168
|
+
votes: requireArray(data.votes, "votes").map((item, index) => {
|
|
169
|
+
const value = item;
|
|
170
|
+
const vote = requireString(value.vote, `votes[${index}].vote`);
|
|
171
|
+
if (vote !== "AGREE" && vote !== "DISAGREE")
|
|
172
|
+
throw new Error(`votes[${index}].vote must be AGREE or DISAGREE`);
|
|
173
|
+
return {
|
|
174
|
+
findingIndex: requireNumber(value.findingIndex, `votes[${index}].findingIndex`),
|
|
175
|
+
reason: value.reason == null
|
|
176
|
+
? undefined
|
|
177
|
+
: requireString(value.reason, `votes[${index}].reason`),
|
|
178
|
+
reviewer: requireString(value.reviewer, `votes[${index}].reviewer`),
|
|
179
|
+
vote,
|
|
180
|
+
};
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export function parseCloseReconsiderationOutput(text) {
|
|
185
|
+
const output = parseReviewOutput(text);
|
|
186
|
+
if (output.verdict === "CLOSE")
|
|
187
|
+
throw new Error("close reconsideration must be MERGE or CHANGES_REQUESTED");
|
|
188
|
+
return output;
|
|
189
|
+
}
|
|
190
|
+
export function parseRereviewCloseReconsiderationOutput(text) {
|
|
191
|
+
const output = parseRereviewOutput(text);
|
|
192
|
+
if (output.verdict === "CLOSE")
|
|
193
|
+
throw new Error("rereview close reconsideration must be MERGE or CHANGES_REQUESTED");
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
export function parseCiClassificationOutput(text) {
|
|
197
|
+
const data = extractJson(text);
|
|
198
|
+
if (!data || typeof data !== "object")
|
|
199
|
+
throw new Error("CI classification output must be an object");
|
|
200
|
+
return {
|
|
201
|
+
checks: requireArray(data.checks, "checks").map((item, index) => {
|
|
202
|
+
const value = item;
|
|
203
|
+
const classification = requireString(value.classification, `checks[${index}].classification`);
|
|
204
|
+
if (classification !== "SCOPE_IN" && classification !== "SCOPE_OUT") {
|
|
205
|
+
throw new Error(`checks[${index}].classification must be SCOPE_IN or SCOPE_OUT`);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
classification,
|
|
209
|
+
name: requireString(value.name, `checks[${index}].name`),
|
|
210
|
+
reason: requireString(value.reason, `checks[${index}].reason`),
|
|
211
|
+
};
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function parseEditOutput(text) {
|
|
216
|
+
const data = extractJson(text);
|
|
217
|
+
if (!data || typeof data !== "object")
|
|
218
|
+
throw new Error("edit output must be an object");
|
|
219
|
+
if (data.mode !== "EDITED" && data.mode !== "REPLIED")
|
|
220
|
+
throw new Error("mode must be EDITED or REPLIED");
|
|
221
|
+
const filesTouched = requireArray(data.filesTouched, "filesTouched").map((file, index) => requireString(file, `filesTouched[${index}]`));
|
|
222
|
+
const responses = requireArray(data.responses, "responses").map((item, index) => {
|
|
223
|
+
const value = item;
|
|
224
|
+
if (!isEditResponseAction(value.action)) {
|
|
225
|
+
throw new Error(`responses[${index}].action must be FIXED, DISAGREE, or ASK`);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
action: value.action,
|
|
229
|
+
body: requireString(value.body, `responses[${index}].body`),
|
|
230
|
+
commentId: requireNumber(value.commentId, `responses[${index}].commentId`),
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
if (!responses.length)
|
|
234
|
+
throw new Error("responses must not be empty");
|
|
235
|
+
if (data.mode === "EDITED") {
|
|
236
|
+
if (!filesTouched.length)
|
|
237
|
+
throw new Error("EDITED requires filesTouched");
|
|
238
|
+
return {
|
|
239
|
+
commitMessage: requireString(data.commitMessage, "commitMessage"),
|
|
240
|
+
commitSha: requireString(data.commitSha, "commitSha"),
|
|
241
|
+
filesTouched,
|
|
242
|
+
mode: data.mode,
|
|
243
|
+
responses,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (data.commitMessage != null)
|
|
247
|
+
throw new Error("REPLIED must omit commitMessage");
|
|
248
|
+
if (data.commitSha != null)
|
|
249
|
+
throw new Error("REPLIED must omit commitSha");
|
|
250
|
+
if (filesTouched.length)
|
|
251
|
+
throw new Error("REPLIED requires empty filesTouched");
|
|
252
|
+
if (responses.some((response) => response.action === "FIXED")) {
|
|
253
|
+
throw new Error("REPLIED cannot include FIXED responses");
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
filesTouched,
|
|
257
|
+
mode: data.mode,
|
|
258
|
+
responses,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Classify failed CI jobs after editor changes for pull request #{pr} in {owner}/{repo}.
|
|
2
|
+
This is edit cycle {cycle}.
|
|
3
|
+
|
|
4
|
+
The editor changed the PR from {previousHeadSha} to {headSha}.
|
|
5
|
+
The PR worktree is {worktreePath}.
|
|
6
|
+
Use this diff for context: git -C {jsonEncodedWorktreePath} diff {previousHeadSha}...{headSha}
|
|
7
|
+
|
|
8
|
+
Decide whether each failure is caused by the PR/editor changes or is likely flaky, external, or infrastructure-related.
|
|
9
|
+
Treat failures that appeared after the editor changes as SCOPE_IN unless there is strong evidence they are unrelated.
|
|
10
|
+
If uncertain, choose SCOPE_IN.
|
|
11
|
+
|
|
12
|
+
Failed checks with structured failure evidence:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{failedChecks}
|
|
16
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Classify failed CI jobs for pull request #{pr} in {owner}/{repo}.
|
|
2
|
+
Decide whether each failure is caused by the PR changes or is likely flaky, external, or infrastructure-related.
|
|
3
|
+
If uncertain, choose SCOPE_IN.
|
|
4
|
+
|
|
5
|
+
Failed checks with structured failure evidence:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{failedChecks}
|
|
9
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
You requested CLOSE for pull request #{pr} in {owner}/{repo}, but the other reviewers did not.
|
|
2
|
+
Reconsider your decision and choose MERGE or CHANGES_REQUESTED instead.
|
|
3
|
+
Use the existing review session context.
|
|
4
|
+
Original close reason:
|
|
5
|
+
{closeReason}
|
|
6
|
+
Do not edit files or perform write operations.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Fix pull request #{pr} for {owner}/{repo}.
|
|
2
|
+
The PR worktree is {worktreePath}.
|
|
3
|
+
Act as the PR author and resolve every unresolved review thread listed below.
|
|
4
|
+
{unresolvedThreads}
|
|
5
|
+
For each thread, decide whether you agree with the reviewer.
|
|
6
|
+
If you understand and agree with the requested change, edit the code, stage changes, commit, and reply with action FIXED.
|
|
7
|
+
If the requested change is incorrect or unnecessary and you have a clear reason, do not edit for that thread; reply with action DISAGREE and explain why.
|
|
8
|
+
If you cannot determine whether the request is correct or what change is expected, do not blindly edit; reply with action ASK and ask a concrete question.
|
|
9
|
+
Do not make changes just because a reviewer requested them. Do not push.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Validate review findings for pull request #{pr} in {owner}/{repo}.
|
|
2
|
+
Use the existing review session context.
|
|
3
|
+
Vote on whether each listed finding is a valid reason to request changes.
|
|
4
|
+
Do not edit files or perform write operations.
|
|
5
|
+
|
|
6
|
+
Findings to validate:
|
|
7
|
+
{findings}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
You requested CLOSE while re-reviewing pull request #{pr} in {owner}/{repo}, but the other reviewers did not.
|
|
2
|
+
Reconsider your decision and choose MERGE or CHANGES_REQUESTED instead.
|
|
3
|
+
Use the existing re-review session context.
|
|
4
|
+
Original close reason:
|
|
5
|
+
{closeReason}
|
|
6
|
+
Do not edit files or perform write operations.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Re-review pull request #{pr} for {owner}/{repo}.
|
|
2
|
+
The PR worktree is {worktreePath}.
|
|
3
|
+
Validate whether your unresolved comments are fixed in the diff from {previousHeadSha} to {headSha} and in the review thread conversation.
|
|
4
|
+
Use: git -C {jsonEncodedWorktreePath} diff {previousHeadSha}...{headSha}
|
|
5
|
+
Your unresolved threads are provided as JSON below.
|
|
6
|
+
{unresolvedThreads}
|
|
7
|
+
|
|
8
|
+
If there is no new commit, still reconsider the thread when a user replied after your latest comment.
|
|
9
|
+
If you agree with the user's explanation or the code is fixed, resolve the thread.
|
|
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
|
+
Do not duplicate an existing unresolved thread as a newFinding. Use newFindings only for separate new issues.
|
|
12
|
+
|
|
13
|
+
{ciFailureContextBlock}
|
|
14
|
+
Do not edit files or perform write operations.
|
|
15
|
+
|
|
16
|
+
{previousReviewBlock}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Review pull request #{pr} for {owner}/{repo}.
|
|
2
|
+
The PR worktree is {worktreePath}.
|
|
3
|
+
Review only the diff from {baseSha} to {headSha}.
|
|
4
|
+
Use: git -C {jsonEncodedWorktreePath} diff {baseSha}...{headSha}
|
|
5
|
+
Do not edit files or perform write operations.
|
|
6
|
+
|
|
7
|
+
{ciFailureContextBlock}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-magi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/hirotomoyamada/opencode-magi"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/hirotomoyamada/opencode-magi/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opencode",
|
|
16
|
+
"plugin",
|
|
17
|
+
"github",
|
|
18
|
+
"pull-request",
|
|
19
|
+
"review"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=24.14"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"schema.json"
|
|
29
|
+
],
|
|
30
|
+
"packageManager": "pnpm@10.33.0",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prebuild": "node scripts/copy-data.ts",
|
|
33
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
34
|
+
"clean": "rimraf node_modules dist coverage",
|
|
35
|
+
"format:check": "oxfmt --check .",
|
|
36
|
+
"format:write": "oxfmt --write .",
|
|
37
|
+
"lint:check": "oxlint . --max-warnings=0",
|
|
38
|
+
"lint:fix": "oxlint . --max-warnings=0 --fix",
|
|
39
|
+
"prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true",
|
|
40
|
+
"quality": "pnpm format:check && pnpm lint:check && pnpm typecheck && pnpm test",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"typecheck": "tsgo --noEmit",
|
|
43
|
+
"release": "changeset publish",
|
|
44
|
+
"release:version": "changeset version"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@opencode-ai/plugin": "latest",
|
|
48
|
+
"ajv": "^8.20.0",
|
|
49
|
+
"picomatch": "^4.0.4",
|
|
50
|
+
"valibot": "^1.4.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@changesets/changelog-github": "^0.7.0",
|
|
54
|
+
"@changesets/cli": "^2.30.0",
|
|
55
|
+
"@commitlint/cli": "^21.0.1",
|
|
56
|
+
"@commitlint/config-conventional": "^21.0.1",
|
|
57
|
+
"@types/node": "^25.7.0",
|
|
58
|
+
"@types/picomatch": "^4.0.3",
|
|
59
|
+
"@typescript/native-preview": "7.0.0-dev.20260513.1",
|
|
60
|
+
"eslint-plugin-perfectionist": "^5.9.0",
|
|
61
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
62
|
+
"lefthook": "^2.1.6",
|
|
63
|
+
"oxfmt": "^0.49.0",
|
|
64
|
+
"oxlint": "^1.64.0",
|
|
65
|
+
"oxlint-tsgolint": "^0.22.1",
|
|
66
|
+
"rimraf": "^6.1.3",
|
|
67
|
+
"vitest": "^4.1.5"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://opencode-magi.local/schema.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"$schema": { "type": "string" },
|
|
8
|
+
"agents": { "$ref": "#/$defs/agents" },
|
|
9
|
+
"automation": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"merge": { "type": "boolean", "default": true },
|
|
14
|
+
"close": { "type": "boolean", "default": true }
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"clear": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"properties": {
|
|
21
|
+
"output": { "type": "boolean", "default": true },
|
|
22
|
+
"worktree": { "type": "boolean", "default": true },
|
|
23
|
+
"session": { "type": "boolean", "default": true },
|
|
24
|
+
"branch": { "type": "boolean", "default": true }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"checks": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"properties": {
|
|
31
|
+
"exclude": { "type": "array", "items": { "type": "string" } },
|
|
32
|
+
"waitAfterEdit": { "type": "boolean" },
|
|
33
|
+
"waitBeforeReview": { "type": "boolean" },
|
|
34
|
+
"retryFailedJobs": { "type": "integer", "minimum": 0, "default": 3 }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"concurrency": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"runs": { "type": "integer", "minimum": 1, "default": 3 },
|
|
42
|
+
"reviewers": { "type": "integer", "minimum": 1, "default": 3 }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"github": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"required": ["owner", "repo"],
|
|
48
|
+
"additionalProperties": false,
|
|
49
|
+
"properties": {
|
|
50
|
+
"host": { "type": "string", "default": "github.com" },
|
|
51
|
+
"apiRetryAttempts": {
|
|
52
|
+
"type": "integer",
|
|
53
|
+
"minimum": 0,
|
|
54
|
+
"default": 3,
|
|
55
|
+
"description": "Number of retry attempts for GitHub CLI API calls that fail with rate limit errors."
|
|
56
|
+
},
|
|
57
|
+
"owner": { "type": "string", "minLength": 1 },
|
|
58
|
+
"repo": { "type": "string", "minLength": 1 }
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"language": { "type": "string" },
|
|
62
|
+
"merge": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"properties": {
|
|
66
|
+
"approvalPolicy": {
|
|
67
|
+
"enum": ["majority", "unanimous"],
|
|
68
|
+
"default": "majority"
|
|
69
|
+
},
|
|
70
|
+
"method": { "enum": ["merge", "squash", "rebase"] },
|
|
71
|
+
"auto": { "type": "boolean" },
|
|
72
|
+
"deleteBranch": { "type": "boolean" },
|
|
73
|
+
"mergeQueue": { "type": "boolean", "default": false },
|
|
74
|
+
"maxThreadResolutionCycles": {
|
|
75
|
+
"type": "integer",
|
|
76
|
+
"minimum": 0,
|
|
77
|
+
"default": 5,
|
|
78
|
+
"description": "Maximum resolution attempts per unresolved review thread. Set 0 for unlimited attempts."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"output": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"additionalProperties": false,
|
|
85
|
+
"properties": {
|
|
86
|
+
"dirs": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"pr": { "type": "string" }
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"repairAttempts": { "type": "integer", "minimum": 0, "default": 3 }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"prompts": { "$ref": "#/$defs/prompts" },
|
|
97
|
+
"safety": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"additionalProperties": false,
|
|
100
|
+
"properties": {
|
|
101
|
+
"allowAuthors": { "type": "array", "items": { "type": "string" } },
|
|
102
|
+
"blockedPaths": { "type": "array", "items": { "type": "string" } },
|
|
103
|
+
"maxChangedFiles": { "type": "integer", "minimum": 0 },
|
|
104
|
+
"requiredLabels": { "type": "array", "items": { "type": "string" } }
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"worktree": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"additionalProperties": false,
|
|
110
|
+
"properties": {
|
|
111
|
+
"dir": { "type": "string" }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"$defs": {
|
|
116
|
+
"reviewer": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"required": ["model", "account"],
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"properties": {
|
|
121
|
+
"id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
|
122
|
+
"model": { "type": "string", "minLength": 1 },
|
|
123
|
+
"options": { "type": "object", "additionalProperties": true },
|
|
124
|
+
"account": { "type": "string", "minLength": 1 },
|
|
125
|
+
"permission": { "$ref": "#/$defs/permission" },
|
|
126
|
+
"persona": { "type": "string" }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"editor": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": ["model", "account", "author"],
|
|
132
|
+
"additionalProperties": false,
|
|
133
|
+
"properties": {
|
|
134
|
+
"model": { "type": "string", "minLength": 1 },
|
|
135
|
+
"options": { "type": "object", "additionalProperties": true },
|
|
136
|
+
"account": { "type": "string", "minLength": 1 },
|
|
137
|
+
"author": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"required": ["name", "email"],
|
|
140
|
+
"additionalProperties": false,
|
|
141
|
+
"properties": {
|
|
142
|
+
"name": { "type": "string", "minLength": 1 },
|
|
143
|
+
"email": { "type": "string", "minLength": 1 }
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"permission": { "$ref": "#/$defs/permission" },
|
|
147
|
+
"persona": { "type": "string" }
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"agents": {
|
|
151
|
+
"type": "object",
|
|
152
|
+
"additionalProperties": false,
|
|
153
|
+
"properties": {
|
|
154
|
+
"permissions": { "$ref": "#/$defs/permission" },
|
|
155
|
+
"reviewers": {
|
|
156
|
+
"type": "array",
|
|
157
|
+
"minItems": 3,
|
|
158
|
+
"items": { "$ref": "#/$defs/reviewer" }
|
|
159
|
+
},
|
|
160
|
+
"editor": { "$ref": "#/$defs/editor" }
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"permissionAction": { "enum": ["allow", "ask", "deny"] },
|
|
164
|
+
"permissionRule": {
|
|
165
|
+
"oneOf": [
|
|
166
|
+
{ "$ref": "#/$defs/permissionAction" },
|
|
167
|
+
{
|
|
168
|
+
"type": "object",
|
|
169
|
+
"additionalProperties": { "$ref": "#/$defs/permissionAction" }
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
"permission": {
|
|
174
|
+
"oneOf": [
|
|
175
|
+
{ "$ref": "#/$defs/permissionAction" },
|
|
176
|
+
{
|
|
177
|
+
"type": "object",
|
|
178
|
+
"additionalProperties": { "$ref": "#/$defs/permissionRule" }
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
"prompts": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": false,
|
|
185
|
+
"properties": {
|
|
186
|
+
"review": { "type": "string" },
|
|
187
|
+
"rereview": { "type": "string" },
|
|
188
|
+
"edit": { "type": "string" },
|
|
189
|
+
"editGuidelines": { "type": "string" },
|
|
190
|
+
"findingValidation": { "type": "string" },
|
|
191
|
+
"closeReconsideration": { "type": "string" },
|
|
192
|
+
"rereviewCloseReconsideration": { "type": "string" },
|
|
193
|
+
"ciClassification": { "type": "string" },
|
|
194
|
+
"ciClassificationAfterEdit": { "type": "string" },
|
|
195
|
+
"report": { "type": "string" },
|
|
196
|
+
"reviewGuidelines": { "type": "string" }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|