opencode-magi 0.0.0-dev-20260525164734 → 0.0.0-dev-20260525205150
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 +14 -13
- package/dist/config/resolve.js +3 -5
- package/dist/config/validate.js +14 -14
- package/dist/orchestrator/merge.js +3 -3
- package/dist/orchestrator/review.js +13 -11
- package/package.json +1 -1
- package/schema.json +2 -2
package/README.md
CHANGED
|
@@ -64,20 +64,18 @@ Add the following content to the configuration file.
|
|
|
64
64
|
"agents": {
|
|
65
65
|
"refs": {
|
|
66
66
|
"account-1": {
|
|
67
|
-
"model": "openai/gpt-5.5"
|
|
68
|
-
"account": "account-1"
|
|
67
|
+
"model": "openai/gpt-5.5"
|
|
69
68
|
},
|
|
70
69
|
"account-2": {
|
|
71
|
-
"model": "anthropic/claude-opus-4-7"
|
|
72
|
-
"account": "account-2"
|
|
70
|
+
"model": "anthropic/claude-opus-4-7"
|
|
73
71
|
},
|
|
74
72
|
"account-3": {
|
|
75
|
-
"model": "opencode/kimi-k2-6"
|
|
76
|
-
"account": "account-3"
|
|
73
|
+
"model": "opencode/kimi-k2-6"
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
},
|
|
80
77
|
"review": {
|
|
78
|
+
"account": "your-account",
|
|
81
79
|
"reviewers": [
|
|
82
80
|
{ "ref": "account-1" },
|
|
83
81
|
{ "ref": "account-2" },
|
|
@@ -87,19 +85,22 @@ Add the following content to the configuration file.
|
|
|
87
85
|
}
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
By default, `review.mode` is `"single"`. Magi uses one `review.account` to post reviewer-originated GitHub mutations while still running multiple logical reviewer agents and preserving majority voting, finding validation, and close reconsideration. The account must be authenticated with `gh auth token --user <account>`.
|
|
91
89
|
|
|
92
|
-
For
|
|
90
|
+
For team setups that need separate GitHub review identities, set `review.mode: "multi"` and configure a unique account for each reviewer.
|
|
93
91
|
|
|
94
92
|
```json
|
|
95
93
|
{
|
|
96
94
|
"review": {
|
|
97
|
-
"mode": "
|
|
98
|
-
"account": "your-account",
|
|
95
|
+
"mode": "multi",
|
|
99
96
|
"reviewers": [
|
|
100
|
-
{ "id": "general", "model": "openai/gpt-5.5" },
|
|
101
|
-
{
|
|
102
|
-
|
|
97
|
+
{ "id": "general", "model": "openai/gpt-5.5", "account": "account-1" },
|
|
98
|
+
{
|
|
99
|
+
"id": "security",
|
|
100
|
+
"model": "anthropic/claude-opus-4-7",
|
|
101
|
+
"account": "account-2"
|
|
102
|
+
},
|
|
103
|
+
{ "id": "compat", "model": "opencode/kimi-k2-6", "account": "account-3" }
|
|
103
104
|
]
|
|
104
105
|
}
|
|
105
106
|
}
|
package/dist/config/resolve.js
CHANGED
|
@@ -113,7 +113,7 @@ export function resolveAgents(config) {
|
|
|
113
113
|
const agents = config.agents ?? {};
|
|
114
114
|
const editor = config.merge?.editor;
|
|
115
115
|
const creator = config.triage?.creator;
|
|
116
|
-
const singleReviewAccount = config.review
|
|
116
|
+
const singleReviewAccount = config.review && config.mode !== "multi" ? config.account : undefined;
|
|
117
117
|
return {
|
|
118
118
|
editor: editor
|
|
119
119
|
? {
|
|
@@ -186,6 +186,8 @@ export function resolveRepository(config) {
|
|
|
186
186
|
repo: config.github.repo,
|
|
187
187
|
},
|
|
188
188
|
language: config.language,
|
|
189
|
+
account: config.account,
|
|
190
|
+
mode: config.mode ?? "single",
|
|
189
191
|
merge: {
|
|
190
192
|
approvalPolicy: config.review?.merge?.approvalPolicy ?? "majority",
|
|
191
193
|
method: config.review?.merge?.method ?? "squash",
|
|
@@ -206,10 +208,6 @@ export function resolveRepository(config) {
|
|
|
206
208
|
review: config.review?.prompts?.review,
|
|
207
209
|
reviewGuidelines: config.review?.prompts?.reviewGuidelines,
|
|
208
210
|
},
|
|
209
|
-
review: {
|
|
210
|
-
account: config.review?.account,
|
|
211
|
-
mode: config.review?.mode ?? "multi",
|
|
212
|
-
},
|
|
213
211
|
reviewAutomation: {
|
|
214
212
|
close: config.review?.automation?.close ?? false,
|
|
215
213
|
merge: config.review?.automation?.merge ?? true,
|
package/dist/config/validate.js
CHANGED
|
@@ -13,11 +13,13 @@ const TRIAGE_CATEGORY_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
|
13
13
|
const RESERVED_TRIAGE_CATEGORY_IDS = new Set(["ASK", "none"]);
|
|
14
14
|
const CONFIG_KEYS = new Set([
|
|
15
15
|
"$schema",
|
|
16
|
+
"account",
|
|
16
17
|
"agents",
|
|
17
18
|
"clear",
|
|
18
19
|
"github",
|
|
19
20
|
"language",
|
|
20
21
|
"merge",
|
|
22
|
+
"mode",
|
|
21
23
|
"output",
|
|
22
24
|
"review",
|
|
23
25
|
"triage",
|
|
@@ -54,12 +56,10 @@ const TRIAGE_CREATOR_KEYS = new Set([
|
|
|
54
56
|
const AUTHOR_KEYS = new Set(["email", "name"]);
|
|
55
57
|
const GITHUB_KEYS = new Set(["apiRetryAttempts", "host", "owner", "repo"]);
|
|
56
58
|
const REVIEW_KEYS = new Set([
|
|
57
|
-
"account",
|
|
58
59
|
"automation",
|
|
59
60
|
"checks",
|
|
60
61
|
"concurrency",
|
|
61
62
|
"merge",
|
|
62
|
-
"mode",
|
|
63
63
|
"output",
|
|
64
64
|
"prompts",
|
|
65
65
|
"reviewers",
|
|
@@ -386,7 +386,7 @@ function validateAndNormalizeModel(target, path, errors, catalog) {
|
|
|
386
386
|
}
|
|
387
387
|
errors.push(`${path} must contain at least one usable OpenCode model candidate${candidateErrors.length ? ` (${candidateErrors.join("; ")})` : ""}`);
|
|
388
388
|
}
|
|
389
|
-
function validateReviewerList(reviewers, path, errors, catalog, mode = "
|
|
389
|
+
function validateReviewerList(reviewers, path, errors, catalog, mode = "single") {
|
|
390
390
|
if (reviewers == null)
|
|
391
391
|
return;
|
|
392
392
|
if (!Array.isArray(reviewers)) {
|
|
@@ -456,7 +456,7 @@ function validateTriageAgentList(voters, path, errors, catalog) {
|
|
|
456
456
|
}
|
|
457
457
|
});
|
|
458
458
|
}
|
|
459
|
-
function validateResolvedReviewers(reviewers, path, errors, mode = "
|
|
459
|
+
function validateResolvedReviewers(reviewers, path, errors, mode = "single") {
|
|
460
460
|
const keys = new Set();
|
|
461
461
|
const accounts = new Set();
|
|
462
462
|
for (const reviewer of reviewers) {
|
|
@@ -469,16 +469,15 @@ function validateResolvedReviewers(reviewers, path, errors, mode = "multi") {
|
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
function reviewMode(config) {
|
|
472
|
-
return config.
|
|
472
|
+
return config.mode === "multi" ? "multi" : "single";
|
|
473
473
|
}
|
|
474
474
|
function validateReviewIdentity(config, errors) {
|
|
475
|
-
const mode = config.
|
|
475
|
+
const mode = config.mode;
|
|
476
476
|
if (mode != null && mode !== "multi" && mode !== "single") {
|
|
477
|
-
errors.push("
|
|
477
|
+
errors.push("mode must be multi or single");
|
|
478
478
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
errors.push("review.account is required when review.mode is single");
|
|
479
|
+
if ((mode == null || mode === "single") && !config.account) {
|
|
480
|
+
errors.push("account is required when mode is single");
|
|
482
481
|
}
|
|
483
482
|
}
|
|
484
483
|
function validateResolvedTriageAgents(agents, path, errors) {
|
|
@@ -924,8 +923,8 @@ async function validateAuth(config, exec, errors) {
|
|
|
924
923
|
const accounts = new Set();
|
|
925
924
|
const agents = resolveAgents(config);
|
|
926
925
|
if (reviewMode(config) === "single") {
|
|
927
|
-
if (config.
|
|
928
|
-
accounts.add(config.
|
|
926
|
+
if (config.account)
|
|
927
|
+
accounts.add(config.account);
|
|
929
928
|
}
|
|
930
929
|
else {
|
|
931
930
|
for (const reviewer of agents.reviewers)
|
|
@@ -978,8 +977,8 @@ async function validateRepositoryPermissions(config, exec, errors, warnings) {
|
|
|
978
977
|
return;
|
|
979
978
|
const agents = resolveAgents(config);
|
|
980
979
|
const reviewAccounts = reviewMode(config) === "single"
|
|
981
|
-
? config.
|
|
982
|
-
? [config.
|
|
980
|
+
? config.account
|
|
981
|
+
? [config.account]
|
|
983
982
|
: []
|
|
984
983
|
: agents.reviewers.map((reviewer) => reviewer.account);
|
|
985
984
|
await Promise.all(reviewAccounts.map(async (account) => {
|
|
@@ -1040,6 +1039,7 @@ export async function validateConfig(config, options = {}) {
|
|
|
1040
1039
|
validateJsonSchema(config, errors);
|
|
1041
1040
|
validateKnownKeys(config, "config", CONFIG_KEYS, errors);
|
|
1042
1041
|
validateString(config.$schema, "$schema", errors);
|
|
1042
|
+
validateString(config.account, "account", errors);
|
|
1043
1043
|
validateString(config.language, "language", errors);
|
|
1044
1044
|
if (config.agents != null && !isPlainObject(config.agents)) {
|
|
1045
1045
|
errors.push("agents must be an object");
|
|
@@ -213,7 +213,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
213
213
|
worktreePath,
|
|
214
214
|
});
|
|
215
215
|
const artifactDir = outputDir(input);
|
|
216
|
-
const singleReviewMode = input.repository.
|
|
216
|
+
const singleReviewMode = input.repository.mode !== "multi";
|
|
217
217
|
const reviewerKeys = input.repository.agents.reviewers.map((reviewer) => reviewer.key);
|
|
218
218
|
const singleModeThreads = singleReviewMode
|
|
219
219
|
? assignThreadsByReviewFindingMarker({
|
|
@@ -221,7 +221,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
221
221
|
pr: input.pr,
|
|
222
222
|
reviewerKeys,
|
|
223
223
|
threads: options.dryRunThreads == null
|
|
224
|
-
? await fetchUnresolvedThreads(input.exec, input.repository, input.pr, input.repository.
|
|
224
|
+
? await fetchUnresolvedThreads(input.exec, input.repository, input.pr, input.repository.account ?? "")
|
|
225
225
|
: Object.values(options.dryRunThreads).flat(),
|
|
226
226
|
})
|
|
227
227
|
: undefined;
|
|
@@ -412,7 +412,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
412
412
|
? { consensus: `dry-run:would-post-single-review:${verdict}` }
|
|
413
413
|
: {
|
|
414
414
|
consensus: await (async () => {
|
|
415
|
-
const account = input.repository.
|
|
415
|
+
const account = input.repository.account ?? "";
|
|
416
416
|
await Promise.all(entries.flatMap((entry) => entry.output.resolve.map((item) => resolveThread(input.exec, input.repository, account, item.threadId))));
|
|
417
417
|
await Promise.all(entries.flatMap((entry) => entry.output.followUps.map((item) => postReply(input.exec, input.repository, input.pr, account, item.commentId, [
|
|
418
418
|
`**Reviewer:** ${entry.reviewer}`,
|
|
@@ -16,12 +16,14 @@ import { formatReviewReport } from "./report";
|
|
|
16
16
|
import { buildReviewContextSnapshot, renderReviewContext, } from "./review-context";
|
|
17
17
|
import { checkSafetyGate, hasSafetyGate } from "./safety";
|
|
18
18
|
function resolvedReviewMode(repository) {
|
|
19
|
-
return repository.
|
|
19
|
+
return repository.mode === "multi" ? "multi" : "single";
|
|
20
20
|
}
|
|
21
21
|
export function reviewPostingAccount(repository, reviewer) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (resolvedReviewMode(repository) !== "single")
|
|
23
|
+
return reviewer.account;
|
|
24
|
+
if (repository.account)
|
|
25
|
+
return repository.account;
|
|
26
|
+
throw new Error("account is required for single review mode");
|
|
25
27
|
}
|
|
26
28
|
function reviewAssignmentKey(repository, reviewer) {
|
|
27
29
|
return resolvedReviewMode(repository) === "single"
|
|
@@ -579,9 +581,9 @@ function singleFindingBody(input) {
|
|
|
579
581
|
].join("\n");
|
|
580
582
|
}
|
|
581
583
|
export async function postSingleConsensusReview(input) {
|
|
582
|
-
const account = input.repository.
|
|
584
|
+
const account = input.repository.account;
|
|
583
585
|
if (!account)
|
|
584
|
-
throw new Error("
|
|
586
|
+
throw new Error("account is required for single review mode");
|
|
585
587
|
const body = singleReviewBody(input);
|
|
586
588
|
if (input.verdict === "MERGE") {
|
|
587
589
|
return postApproval(input.exec, input.repository, input.pr, account, body);
|
|
@@ -927,7 +929,7 @@ export async function runReview(input) {
|
|
|
927
929
|
const reviewerAccounts = input.repository.agents.reviewers.map((reviewer) => reviewer.account);
|
|
928
930
|
const preliminaryMode = singleReviewMode
|
|
929
931
|
? resolveSingleAccountReviewMode({
|
|
930
|
-
account: input.repository.
|
|
932
|
+
account: input.repository.account ?? "",
|
|
931
933
|
current: freshnessTarget,
|
|
932
934
|
pr: input.pr,
|
|
933
935
|
reviewerKeys,
|
|
@@ -942,7 +944,7 @@ export async function runReview(input) {
|
|
|
942
944
|
return (preliminaryMode.assignments.get(reviewAssignmentKey(input.repository, reviewer))?.type === "skip");
|
|
943
945
|
});
|
|
944
946
|
if (singleReviewMode) {
|
|
945
|
-
const account = input.repository.
|
|
947
|
+
const account = input.repository.account ?? "";
|
|
946
948
|
const threads = await fetchUnresolvedThreads(exec, input.repository, input.pr, account);
|
|
947
949
|
const assigned = assignThreadsByReviewFindingMarker({
|
|
948
950
|
fallbackReviewerKeys: reviewerKeys,
|
|
@@ -973,7 +975,7 @@ export async function runReview(input) {
|
|
|
973
975
|
const mode = singleReviewMode
|
|
974
976
|
? pendingThreadReplyReviewers.size
|
|
975
977
|
? resolveSingleAccountReviewMode({
|
|
976
|
-
account: input.repository.
|
|
978
|
+
account: input.repository.account ?? "",
|
|
977
979
|
current: freshnessTarget,
|
|
978
980
|
pendingReviewers: pendingThreadReplyReviewers,
|
|
979
981
|
pr: input.pr,
|
|
@@ -1368,7 +1370,7 @@ export async function runReview(input) {
|
|
|
1368
1370
|
consensus: input.dryRun
|
|
1369
1371
|
? `dry-run:would-post-single-review:${verdict}`
|
|
1370
1372
|
: await (async () => {
|
|
1371
|
-
const account = input.repository.
|
|
1373
|
+
const account = input.repository.account ?? "";
|
|
1372
1374
|
await Promise.all(Object.values(activeOutputs).flatMap((output) => {
|
|
1373
1375
|
if (!("resolve" in output))
|
|
1374
1376
|
return [];
|
|
@@ -1414,7 +1416,7 @@ export async function runReview(input) {
|
|
|
1414
1416
|
]))),
|
|
1415
1417
|
};
|
|
1416
1418
|
const automationAccount = singleReviewMode
|
|
1417
|
-
? input.repository.
|
|
1419
|
+
? input.repository.account
|
|
1418
1420
|
: input.repository.agents.reviewers[0]?.account;
|
|
1419
1421
|
const enableReviewAutomation = input.enableReviewAutomation ?? true;
|
|
1420
1422
|
if (enableReviewAutomation &&
|
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-20260525205150",
|
|
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>",
|
package/schema.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"additionalProperties": false,
|
|
6
6
|
"properties": {
|
|
7
7
|
"$schema": { "type": "string" },
|
|
8
|
+
"account": { "type": "string", "minLength": 1 },
|
|
8
9
|
"agents": {
|
|
9
10
|
"type": "object",
|
|
10
11
|
"additionalProperties": false,
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
},
|
|
45
46
|
"language": { "type": "string" },
|
|
46
47
|
"merge": { "$ref": "#/$defs/merge" },
|
|
48
|
+
"mode": { "enum": ["multi", "single"], "default": "single" },
|
|
47
49
|
"output": {
|
|
48
50
|
"type": "object",
|
|
49
51
|
"additionalProperties": false,
|
|
@@ -366,8 +368,6 @@
|
|
|
366
368
|
"type": "object",
|
|
367
369
|
"additionalProperties": false,
|
|
368
370
|
"properties": {
|
|
369
|
-
"mode": { "enum": ["multi", "single"], "default": "multi" },
|
|
370
|
-
"account": { "type": "string", "minLength": 1 },
|
|
371
371
|
"reviewers": {
|
|
372
372
|
"type": "array",
|
|
373
373
|
"minItems": 3,
|