opencode-magi 0.9.0 → 0.11.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.
Files changed (126) hide show
  1. package/README.ja.md +254 -0
  2. package/README.md +37 -34
  3. package/dist/commands.js +11 -9
  4. package/dist/config/index.js +2 -0
  5. package/dist/config/resolve.js +120 -248
  6. package/dist/config/validate.js +103 -1100
  7. package/dist/constant.js +135 -0
  8. package/dist/graphql/index.generated.js +125 -0
  9. package/dist/graphql/index.js +49 -0
  10. package/dist/index.js +12 -800
  11. package/dist/magi.js +357 -0
  12. package/dist/permissions/common.json +22 -22
  13. package/dist/permissions/editor.json +12 -12
  14. package/dist/prompts/index.js +98 -0
  15. package/dist/prompts/merge/ci-classification/output-contract.md +22 -0
  16. package/dist/prompts/merge/ci-classification/task.md +10 -0
  17. package/dist/prompts/merge/ci-classification/validate.json +20 -0
  18. package/dist/prompts/merge/conflict/output-contract.md +12 -0
  19. package/dist/prompts/merge/conflict/task.md +9 -0
  20. package/dist/prompts/merge/conflict/validate.json +4 -0
  21. package/dist/prompts/merge/edit/output-contract.md +26 -0
  22. package/dist/prompts/merge/edit/task.md +10 -0
  23. package/dist/prompts/merge/edit/validate.json +60 -0
  24. package/dist/prompts/review/ci-classification/output-contract.md +21 -0
  25. package/dist/prompts/review/ci-classification/task.md +7 -0
  26. package/dist/prompts/review/ci-classification/validate.json +20 -0
  27. package/dist/prompts/review/close-reconsideration/output-contract.md +21 -0
  28. package/dist/prompts/review/close-reconsideration/task.md +6 -0
  29. package/dist/prompts/review/close-reconsideration/validate.json +44 -0
  30. package/dist/prompts/review/comment/output-contract.md +11 -0
  31. package/dist/prompts/review/comment/task.md +3 -0
  32. package/dist/prompts/review/comment/validate.json +8 -0
  33. package/dist/prompts/review/finding-validation/output-contract.md +25 -0
  34. package/dist/prompts/{templates/review/finding-validation.md → review/finding-validation/task.md} +2 -3
  35. package/dist/prompts/review/finding-validation/validate.json +21 -0
  36. package/dist/prompts/review/rereview/output-contract.md +30 -0
  37. package/dist/prompts/{templates/review/rereview.md → review/rereview/task.md} +8 -11
  38. package/dist/prompts/review/rereview/validate.json +87 -0
  39. package/dist/prompts/review/review/output-contract.md +25 -0
  40. package/dist/prompts/{templates/review/review.md → review/review/task.md} +2 -5
  41. package/dist/prompts/review/review/validate.json +54 -0
  42. package/dist/prompts/triage/acceptance/output-contract.md +16 -0
  43. package/dist/prompts/triage/acceptance/validate.json +23 -0
  44. package/dist/prompts/triage/category/output-contract.md +16 -0
  45. package/dist/prompts/triage/category/validate.json +23 -0
  46. package/dist/prompts/triage/comment-classification/output-contract.md +15 -0
  47. package/dist/prompts/triage/comment-classification/validate.json +28 -0
  48. package/dist/prompts/triage/create/output-contract.md +28 -0
  49. package/dist/prompts/triage/create/validate.json +73 -0
  50. package/dist/prompts/triage/deplicate/output-contract.md +16 -0
  51. package/dist/prompts/triage/deplicate/validate.json +20 -0
  52. package/dist/prompts/triage/existing/output-contract.md +16 -0
  53. package/dist/prompts/triage/existing/validate.json +13 -0
  54. package/dist/prompts/triage/reconsider/output-contract.md +16 -0
  55. package/dist/prompts/triage/reconsider/validate.json +23 -0
  56. package/dist/prompts/triage/signal/output-contract.md +19 -0
  57. package/dist/prompts/triage/signal/validate.json +18 -0
  58. package/dist/tools/clear/index.js +21 -0
  59. package/dist/tools/index.js +12 -0
  60. package/dist/tools/merge/action.js +47 -0
  61. package/dist/tools/merge/context.js +118 -0
  62. package/dist/tools/merge/editor.js +264 -0
  63. package/dist/tools/merge/index.js +151 -0
  64. package/dist/tools/merge/index.type.js +1 -0
  65. package/dist/tools/merge/merge.js +42 -0
  66. package/dist/tools/merge/report.js +73 -0
  67. package/dist/tools/review/action.js +429 -0
  68. package/dist/tools/review/check.js +295 -0
  69. package/dist/tools/review/context.js +258 -0
  70. package/dist/tools/review/index.js +104 -0
  71. package/dist/tools/review/index.type.js +1 -0
  72. package/dist/tools/review/report.js +143 -0
  73. package/dist/tools/review/review.js +145 -0
  74. package/dist/tools/review/reviewer.js +409 -0
  75. package/dist/tools/triage/index.js +16 -0
  76. package/dist/tools/validate/index.js +28 -0
  77. package/dist/utils/array.js +6 -0
  78. package/dist/utils/assertion.js +38 -0
  79. package/dist/utils/exec.js +14 -0
  80. package/dist/utils/fs.js +24 -0
  81. package/dist/utils/function.js +37 -0
  82. package/dist/utils/github.js +63 -0
  83. package/dist/utils/index.js +10 -0
  84. package/dist/utils/index.type.js +1 -0
  85. package/dist/utils/object.js +21 -0
  86. package/dist/utils/opencode.js +25 -0
  87. package/dist/utils/string.js +38 -0
  88. package/dist/utils/worker.js +30 -0
  89. package/package.json +20 -8
  90. package/schema.json +9 -7
  91. package/dist/config/load.js +0 -62
  92. package/dist/config/output.js +0 -25
  93. package/dist/config/worktree.js +0 -25
  94. package/dist/github/commands.js +0 -905
  95. package/dist/github/retry.js +0 -44
  96. package/dist/orchestrator/abort.js +0 -9
  97. package/dist/orchestrator/ci.js +0 -579
  98. package/dist/orchestrator/findings.js +0 -88
  99. package/dist/orchestrator/inline-comments.js +0 -73
  100. package/dist/orchestrator/majority.js +0 -62
  101. package/dist/orchestrator/merge.js +0 -1260
  102. package/dist/orchestrator/model.js +0 -216
  103. package/dist/orchestrator/pool.js +0 -15
  104. package/dist/orchestrator/report.js +0 -175
  105. package/dist/orchestrator/review-context.js +0 -342
  106. package/dist/orchestrator/review.js +0 -1478
  107. package/dist/orchestrator/run-manager.js +0 -2132
  108. package/dist/orchestrator/safety.js +0 -44
  109. package/dist/orchestrator/triage.js +0 -1370
  110. package/dist/prompts/compose.js +0 -473
  111. package/dist/prompts/contracts.js +0 -300
  112. package/dist/prompts/output.js +0 -401
  113. package/dist/prompts/templates/merge/ci-classification.md +0 -16
  114. package/dist/prompts/templates/merge/conflict.md +0 -10
  115. package/dist/prompts/templates/merge/edit.md +0 -15
  116. package/dist/prompts/templates/review/ci-classification.md +0 -9
  117. package/dist/prompts/templates/review/close-reconsideration.md +0 -6
  118. /package/dist/{types.js → config/index.type.js} +0 -0
  119. /package/dist/prompts/{templates/triage/acceptance.md → triage/acceptance/task.md} +0 -0
  120. /package/dist/prompts/{templates/triage/category.md → triage/category/task.md} +0 -0
  121. /package/dist/prompts/{templates/triage/comment-classification.md → triage/comment-classification/task.md} +0 -0
  122. /package/dist/prompts/{templates/triage/create.md → triage/create/task.md} +0 -0
  123. /package/dist/prompts/{templates/triage/duplicate.md → triage/deplicate/task.md} +0 -0
  124. /package/dist/prompts/{templates/triage/existing-pr.md → triage/existing/task.md} +0 -0
  125. /package/dist/prompts/{templates/triage/reconsider.md → triage/reconsider/task.md} +0 -0
  126. /package/dist/prompts/{templates/triage/signal.md → triage/signal/task.md} +0 -0
@@ -1,256 +1,128 @@
1
- import editorPermission from "../permissions/editor.json" with { type: "json" };
2
- import commonPermission from "../permissions/common.json" with { type: "json" };
3
- const ID_PATTERN = /^[A-Za-z0-9_-]+$/;
4
- const DEFAULT_COMMON_PERMISSION = commonPermission;
5
- const DEFAULT_REVIEWER_PERMISSION = DEFAULT_COMMON_PERMISSION;
6
- const DEFAULT_EDITOR_PERMISSION = mergePermissions(DEFAULT_COMMON_PERMISSION, editorPermission);
7
- const DEFAULT_TRIAGE_CATEGORIES = [
8
- {
9
- description: "Something is broken or behaves incorrectly.",
10
- id: "bug",
11
- labels: ["bug"],
12
- types: ["Bug"],
13
- },
14
- {
15
- description: "Maintenance, refactoring, chores, or planned work.",
16
- id: "task",
17
- labels: ["task"],
18
- types: ["Task"],
19
- },
20
- {
21
- description: "New or improved user-facing capability.",
22
- id: "feature",
23
- labels: ["enhancement"],
24
- types: ["Feature"],
25
- },
26
- ];
27
- export const DEFAULT_TRIAGE_LABEL_RULES = [
28
- { remove: ["triage"], when: { disposition: "accepted" } },
29
- {
30
- add: ["duplicate"],
31
- remove: ["triage"],
32
- when: { disposition: "duplicate" },
33
- },
34
- {
35
- add: ["duplicate"],
36
- remove: ["triage"],
37
- when: { disposition: "already_handled" },
38
- },
39
- {
40
- add: ["wontfix"],
41
- remove: ["triage"],
42
- when: { disposition: "rejected" },
43
- },
44
- {
45
- add: ["invalid"],
46
- remove: ["triage"],
47
- when: { disposition: "invalid" },
48
- },
49
- { add: ["question"], when: { disposition: "needs_category" } },
50
- { add: ["question"], when: { disposition: "needs_acceptance" } },
51
- ];
52
- export function reviewerKey(reviewer, index) {
53
- return reviewer.id ?? `reviewer-${index + 1}`;
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { CONFIG_PATH, DEFAULT_CONFIG } from "@/constant";
4
+ import editPermissions from "@/permissions/editor.json" with { type: "json" };
5
+ import { filterEmpty, getModels, isArray, isObject, isString, merge, } from "@/utils";
6
+ export function resolvePermissions(permissions) {
7
+ if (!permissions)
8
+ return undefined;
9
+ if (isString(permissions))
10
+ return [
11
+ "read",
12
+ "edit",
13
+ "glob",
14
+ "grep",
15
+ "bash",
16
+ "task",
17
+ "skill",
18
+ "lsp",
19
+ "webfetch",
20
+ "websearch",
21
+ "external_directory",
22
+ "doom_loop",
23
+ ].map((permission) => ({
24
+ action: permissions,
25
+ pattern: "*",
26
+ permission,
27
+ }));
28
+ else
29
+ return Object.entries(permissions).flatMap(([permission, rule]) => {
30
+ if (isString(rule))
31
+ return [{ action: rule, pattern: "*", permission }];
32
+ else
33
+ return Object.entries(rule).map(([pattern, action]) => ({
34
+ action,
35
+ pattern,
36
+ permission,
37
+ }));
38
+ });
54
39
  }
55
- export function triageAgentKey(agent, index) {
56
- return agent.id ?? `voter-${index + 1}`;
57
- }
58
- export function validateReviewerId(id) {
59
- return ID_PATTERN.test(id);
40
+ function mergePermissions(base, override) {
41
+ if (!override)
42
+ return base;
43
+ else if (isString(override) || isString(base))
44
+ return override;
45
+ else
46
+ return merge(base, override);
60
47
  }
61
- function normalizedModel(model) {
62
- if (typeof model !== "string") {
63
- throw new Error("model must be normalized before resolving agents");
64
- }
65
- return model;
48
+ function mergeAgent(base, agent) {
49
+ if (base.agents.refs && agent.ref)
50
+ agent = { ...base.agents.refs[agent.ref], ...agent };
51
+ agent.permissions = mergePermissions(base.agents.permissions, agent.permissions);
52
+ if (base.account)
53
+ agent.account ??= base.account;
54
+ return agent;
66
55
  }
67
- function clonePermissionValue(value) {
68
- return typeof value === "string" ? value : { ...value };
56
+ function resolveModel(models, model) {
57
+ if (isArray(model))
58
+ model = model.find((model) => isString(model) ? models.includes(model) : models.includes(model.id));
59
+ if (!model)
60
+ return undefined;
61
+ if (isString(model))
62
+ return models.includes(model) ? { id: model } : undefined;
63
+ else
64
+ return models.includes(model.id) ? model : undefined;
69
65
  }
70
- export function mergePermissions(base, override) {
71
- if (!override) {
72
- return typeof base === "string"
73
- ? base
74
- : Object.fromEntries(Object.entries(base).map(([key, value]) => [
75
- key,
76
- clonePermissionValue(value),
77
- ]));
66
+ async function readConfig(path) {
67
+ try {
68
+ const config = JSON.parse(await readFile(path, "utf8"));
69
+ if (!isObject(config))
70
+ throw new Error(`Config must be a JSON object: ${path}`);
71
+ return config;
78
72
  }
79
- if (typeof override === "string")
80
- return override;
81
- if (typeof base === "string") {
82
- return Object.fromEntries(Object.entries(override).map(([key, value]) => [
83
- key,
84
- clonePermissionValue(value),
85
- ]));
73
+ catch (e) {
74
+ if (e.code === "ENOENT")
75
+ return undefined;
76
+ throw e;
86
77
  }
87
- const merged = Object.fromEntries(Object.entries(base).map(([key, value]) => [
88
- key,
89
- clonePermissionValue(value),
90
- ]));
91
- for (const [permission, value] of Object.entries(override)) {
92
- const existing = merged[permission];
93
- merged[permission] =
94
- existing && typeof existing !== "string" && typeof value !== "string"
95
- ? { ...existing, ...value }
96
- : clonePermissionValue(value);
97
- }
98
- return merged;
99
- }
100
- export function resolveReviewerPermission(agents, reviewer) {
101
- return mergePermissions(mergePermissions(DEFAULT_REVIEWER_PERMISSION, agents.permissions), reviewer.permissions);
102
- }
103
- export function resolveEditorPermission(agents, editor) {
104
- return mergePermissions(mergePermissions(DEFAULT_EDITOR_PERMISSION, agents.permissions), editor.permissions);
105
78
  }
106
- export function resolveTriageAgentPermission(agents, agent) {
107
- return mergePermissions(mergePermissions(DEFAULT_REVIEWER_PERMISSION, agents.permissions), agent.permissions);
108
- }
109
- export function resolveTriageCreatorPermission(agents, creator) {
110
- return mergePermissions(mergePermissions(DEFAULT_EDITOR_PERMISSION, agents.permissions), creator.permissions);
111
- }
112
- export function resolveAgents(config) {
113
- const agents = config.agents ?? {};
114
- const editor = config.merge?.editor;
115
- const creator = config.triage?.creator;
116
- const singleReviewAccount = config.review && config.review.mode !== "multi"
117
- ? config.review.account
118
- : undefined;
119
- return {
120
- editor: editor
121
- ? {
122
- ...editor,
123
- model: normalizedModel(editor.model),
124
- permission: resolveEditorPermission(agents, editor),
125
- }
126
- : undefined,
127
- reviewers: (config.review?.reviewers ?? []).map((reviewer, index) => ({
128
- ...reviewer,
129
- account: singleReviewAccount ?? reviewer.account ?? "",
130
- key: reviewerKey(reviewer, index),
131
- index,
132
- model: normalizedModel(reviewer.model),
133
- permission: resolveReviewerPermission(agents, reviewer),
134
- })),
135
- triage: (config.triage?.voters ?? []).map((agent, index) => ({
136
- ...agent,
137
- key: triageAgentKey(agent, index),
138
- index,
139
- model: normalizedModel(agent.model),
140
- permission: resolveTriageAgentPermission(agents, agent),
141
- })),
142
- triageCreator: creator
143
- ? {
144
- ...creator,
145
- account: creator.account ?? "",
146
- model: normalizedModel(creator.model),
147
- permission: resolveTriageCreatorPermission(agents, creator),
148
- }
149
- : undefined,
150
- };
151
- }
152
- function resolveTriageCategories(config) {
153
- return (config.triage?.categories ?? DEFAULT_TRIAGE_CATEGORIES).map((category) => ({
154
- description: category.description,
155
- id: category.id ?? "",
156
- labels: category.labels ?? [],
157
- types: category.types ?? [],
158
- }));
159
- }
160
- export function resolveRepository(config) {
161
- if (!config.github?.owner)
162
- throw new Error("github.owner is required");
163
- if (!config.github?.repo)
164
- throw new Error("github.repo is required");
165
- return {
166
- alias: config.github.repo,
167
- agents: resolveAgents(config),
168
- automation: {
169
- close: config.merge?.automation?.close ?? false,
170
- conflict: config.merge?.automation?.conflict ?? false,
171
- merge: config.merge?.automation?.merge ?? true,
172
- },
173
- checks: {
174
- exclude: config.review?.checks?.exclude ?? [],
175
- retryFailedJobs: config.review?.checks?.retryFailedJobs ?? 3,
176
- wait: config.review?.checks?.wait ?? true,
177
- waitAfterEdit: config.merge?.checks?.wait ?? true,
178
- waitBeforeReview: config.review?.checks?.wait ?? true,
179
- },
180
- concurrency: {
181
- runs: config.review?.concurrency?.runs ?? 3,
182
- reviewers: config.review?.concurrency?.reviewers ?? 3,
183
- },
184
- github: {
185
- apiRetryAttempts: config.github.apiRetryAttempts ?? 3,
186
- host: config.github.host ?? "github.com",
187
- owner: config.github.owner,
188
- repo: config.github.repo,
189
- },
190
- language: config.language,
191
- merge: {
192
- approvalPolicy: config.review?.merge?.approvalPolicy ?? "majority",
193
- method: config.review?.merge?.method ?? "squash",
194
- auto: config.review?.merge?.auto ?? true,
195
- deleteBranch: config.review?.merge?.deleteBranch ?? true,
196
- queue: config.review?.merge?.queue ?? false,
197
- mergeQueue: config.review?.merge?.queue ?? false,
198
- maxThreadResolutionCycles: config.merge?.maxThreadResolutionCycles ?? 5,
199
- },
200
- prompts: {
201
- ciClassification: config.review?.prompts?.ciClassification,
202
- ciClassificationAfterEdit: config.merge?.prompts?.ciClassification,
203
- closeReconsideration: config.review?.prompts?.closeReconsideration,
204
- edit: config.merge?.prompts?.edit,
205
- editGuidelines: config.merge?.prompts?.editGuidelines,
206
- findingValidation: config.review?.prompts?.findingValidation,
207
- rereview: config.review?.prompts?.rereview,
208
- review: config.review?.prompts?.review,
209
- reviewGuidelines: config.review?.prompts?.reviewGuidelines,
210
- },
211
- review: {
212
- account: config.review?.account,
213
- mode: config.review?.mode ?? "single",
214
- },
215
- reviewAutomation: {
216
- close: config.review?.automation?.close ?? false,
217
- merge: config.review?.automation?.merge ?? true,
218
- },
219
- safety: {
220
- allowAuthors: config.review?.safety?.allowAuthors ?? [],
221
- blockedPaths: config.review?.safety?.blockedPaths ?? [],
222
- maxChangedFiles: config.review?.safety?.maxChangedFiles,
223
- requiredLabels: config.review?.safety?.requiredLabels ?? [],
224
- },
225
- triage: {
226
- automation: {
227
- close: config.triage?.automation?.close ?? false,
228
- create: config.triage?.automation?.create ?? false,
229
- label: config.triage?.automation?.label ?? DEFAULT_TRIAGE_LABEL_RULES,
230
- merge: config.triage?.automation?.merge ?? false,
231
- review: config.triage?.automation?.review ?? false,
232
- },
233
- categories: resolveTriageCategories(config),
234
- concurrency: {
235
- runs: config.triage?.concurrency?.runs ?? 3,
236
- },
237
- output: config.triage?.output,
238
- prompts: config.triage?.prompts ?? {},
239
- reporter: config.triage?.reporter,
240
- safety: {
241
- allowAuthors: config.triage?.safety?.allowAuthors ?? [],
242
- allowMentionActors: config.triage?.safety?.allowMentionActors ?? [],
243
- allowMentionRoles: config.triage?.safety?.allowMentionRoles ?? [
244
- "AUTHOR",
245
- "OWNER",
246
- "MEMBER",
247
- "COLLABORATOR",
248
- ],
249
- blockedLabels: config.triage?.safety?.blockedLabels ?? [],
250
- requiredLabels: config.triage?.safety?.requiredLabels ?? ["triage"],
251
- },
252
- signals: config.triage?.signals ?? [],
253
- worktree: config.triage?.worktree,
254
- },
255
- };
79
+ export async function getConfig(input) {
80
+ const projectPath = join(input.directory, CONFIG_PATH.PROJECT);
81
+ const [models, ...data] = await Promise.all([
82
+ getModels(input),
83
+ readConfig(CONFIG_PATH.GLOBAL),
84
+ readConfig(projectPath),
85
+ ]);
86
+ const results = filterEmpty(data);
87
+ if (!models.length)
88
+ throw new Error("No OpenCode models found.");
89
+ if (!results.length)
90
+ throw new Error(`No Magi config found. Expected ${CONFIG_PATH.GLOBAL} or ${projectPath}.`);
91
+ const config = results.reduce((prev, current) => merge(prev, current), DEFAULT_CONFIG);
92
+ config.github.url = `https://${config.github.host}/${config.github.owner}/${config.github.repo}`;
93
+ if (config.review.reviewers)
94
+ config.review.reviewers = config.review.reviewers.map((agent, index) => {
95
+ const { id, model, ...rest } = mergeAgent(config, agent);
96
+ return {
97
+ ...rest,
98
+ id: id || `reviewer-${index + 1}`,
99
+ model: resolveModel(models, model),
100
+ };
101
+ });
102
+ if (config.merge.editor) {
103
+ if (config.merge.editor.permissions)
104
+ config.merge.editor.permissions = mergePermissions(editPermissions, config.merge.editor.permissions);
105
+ else
106
+ config.merge.editor.permissions = editPermissions;
107
+ config.merge.editor = mergeAgent(config, config.merge.editor);
108
+ config.merge.editor.model = resolveModel(models, config.merge.editor.model);
109
+ }
110
+ if (config.triage.voters)
111
+ config.triage.voters = config.triage.voters.map((agent, index) => {
112
+ const { id, model, ...rest } = mergeAgent(config, agent);
113
+ return {
114
+ ...rest,
115
+ id: id || `voter-${index + 1}`,
116
+ model: resolveModel(models, model),
117
+ };
118
+ });
119
+ if (config.triage.creator) {
120
+ if (config.triage.creator.permissions)
121
+ config.triage.creator.permissions = mergePermissions(editPermissions, config.triage.creator.permissions);
122
+ else
123
+ config.triage.creator.permissions = editPermissions;
124
+ config.triage.creator = mergeAgent(config, config.triage.creator);
125
+ config.triage.creator.model = resolveModel(models, config.triage.creator.model);
126
+ }
127
+ return config;
256
128
  }