opencode-magi 0.10.0 → 0.12.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 +31 -26
  3. package/dist/commands.js +11 -9
  4. package/dist/config/index.js +2 -0
  5. package/dist/config/resolve.js +120 -246
  6. package/dist/config/validate.js +98 -1110
  7. package/dist/constant.js +131 -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 +116 -0
  62. package/dist/tools/merge/editor.js +256 -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 +459 -0
  68. package/dist/tools/review/check.js +335 -0
  69. package/dist/tools/review/context.js +254 -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 +146 -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 +46 -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 +36 -23
  90. package/schema.json +116 -20
  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 -1480
  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
@@ -0,0 +1,21 @@
1
+ import { isObject } from "./assertion";
2
+ export function merge(base, override) {
3
+ const merged = { ...base };
4
+ for (const [key, value] of Object.entries(override)) {
5
+ const existing = merged[key];
6
+ merged[key] =
7
+ isObject(existing) && isObject(value) ? merge(existing, value) : value;
8
+ }
9
+ return merged;
10
+ }
11
+ export function filterObject(obj, func) {
12
+ const result = {};
13
+ Object.entries(obj).forEach(([key, value]) => {
14
+ if (func(key, value, obj))
15
+ result[key] = value;
16
+ });
17
+ return result;
18
+ }
19
+ export function omitNullish(obj) {
20
+ return filterObject(obj, (_, val) => val != null);
21
+ }
@@ -0,0 +1,25 @@
1
+ import { isArray } from "./assertion";
2
+ import { createExec } from "./exec";
3
+ export async function getModels(input) {
4
+ const providers = await input.client.config
5
+ .providers({ directory: input.directory })
6
+ .catch(() => input.client.provider.list({ directory: input.directory }));
7
+ const data = "data" in providers ? providers.data : undefined;
8
+ const all = data && "providers" in data ? data.providers : data?.all;
9
+ const models = isArray(all)
10
+ ? all.flatMap((provider) => {
11
+ const models = Object.keys(provider.models);
12
+ return provider.id ? models.map((id) => `${provider.id}/${id}`) : models;
13
+ })
14
+ : [];
15
+ if (models.length)
16
+ return models;
17
+ else
18
+ try {
19
+ const output = await createExec(input.directory)("opencode models");
20
+ return output.split("\n").filter((model) => model.includes("/"));
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ }
@@ -0,0 +1,38 @@
1
+ import { filterEmpty } from "./array";
2
+ export function quote(value) {
3
+ return `'${value.replaceAll("'", "'\\''")}'`;
4
+ }
5
+ export function split(value) {
6
+ return filterEmpty(value.split(/[\s,]+/));
7
+ }
8
+ export function toTitleCase(value) {
9
+ return value
10
+ .replace(/([A-Z])/g, " $1")
11
+ .replace(/[_-](.)/g, (_, val) => ` ${val.toUpperCase()}`)
12
+ .replace(/^./, (str) => str.toUpperCase())
13
+ .trim();
14
+ }
15
+ export function command(...values) {
16
+ return filterEmpty(values).join(" ");
17
+ }
18
+ export const marker = {
19
+ parse(body) {
20
+ const matchAll = body.matchAll(/<!--\s*opencode-magi\s+([^>]*)-->/g);
21
+ return [...matchAll].map((match) => Object.fromEntries(match[1]
22
+ ?.trim()
23
+ .split(/\s+/)
24
+ .flatMap((part) => {
25
+ const [key, ...rest] = part.split("=");
26
+ if (!key || !rest.length)
27
+ return [];
28
+ return [[key, rest.join("=")]];
29
+ }) ?? []));
30
+ },
31
+ stringify(...values) {
32
+ return values
33
+ .map((value) => `<!-- opencode-magi ${Object.entries(value)
34
+ .map(([key, value]) => `${key}=${value}`)
35
+ .join(" ")} -->`)
36
+ .join("\n");
37
+ },
38
+ };
@@ -0,0 +1,30 @@
1
+ export class Worker {
2
+ active = 0;
3
+ tasks = [];
4
+ limit;
5
+ constructor(limit) {
6
+ this.limit = Math.max(1, Math.floor(limit));
7
+ }
8
+ async run(run) {
9
+ const task = new Promise((resolve, reject) => {
10
+ this.tasks.push({ reject, resolve, run });
11
+ });
12
+ this.drain();
13
+ return task;
14
+ }
15
+ drain() {
16
+ while (this.active < this.limit) {
17
+ const task = this.tasks.shift();
18
+ if (!task)
19
+ return;
20
+ this.active += 1;
21
+ void task
22
+ .run()
23
+ .then(task.resolve, task.reject)
24
+ .finally(() => {
25
+ this.active -= 1;
26
+ this.drain();
27
+ });
28
+ }
29
+ }
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
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>",
@@ -28,42 +28,55 @@
28
28
  "schema.json"
29
29
  ],
30
30
  "dependencies": {
31
- "@opencode-ai/plugin": "^1.15.10",
31
+ "@opencode-ai/plugin": "^1.18.4",
32
+ "@opencode-ai/sdk": "^1.18.4",
32
33
  "ajv": "^8.20.0",
33
- "picomatch": "^4.0.4",
34
- "valibot": "^1.4.1"
34
+ "graphql": "^17.0.2",
35
+ "graphql-tag": "^2.12.7",
36
+ "octokit": "^5.0.5",
37
+ "picomatch": "^4.0.5",
38
+ "valibot": "^1.4.2"
35
39
  },
36
40
  "devDependencies": {
37
41
  "@changesets/changelog-github": "^0.7.0",
38
- "@changesets/cli": "^2.30.0",
39
- "@commitlint/cli": "^21.0.1",
40
- "@commitlint/config-conventional": "^21.0.1",
41
- "@types/node": "^25.9.1",
42
+ "@changesets/cli": "^2.31.1",
43
+ "@commitlint/cli": "^21.2.1",
44
+ "@commitlint/config-conventional": "^21.2.0",
45
+ "@graphql-codegen/cli": "^7.2.0",
46
+ "@graphql-codegen/typescript-generic-sdk": "^5.0.1",
47
+ "@graphql-codegen/typescript-operations": "^6.1.1",
48
+ "@limegrass/eslint-plugin-import-alias": "^1.6.1",
49
+ "@octokit/graphql-schema": "^15.26.1",
50
+ "@octokit/plugin-throttling": "^11.0.3",
51
+ "@stylistic/eslint-plugin": "^5.10.0",
52
+ "@types/node": "^26.1.1",
42
53
  "@types/picomatch": "^4.0.3",
43
- "@typescript/native-preview": "7.0.0-dev.20260525.1",
44
- "@vitest/coverage-v8": "^4.1.7",
45
- "@vitest/ui": "^4.1.7",
46
- "eslint-plugin-perfectionist": "^5.9.0",
54
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
55
+ "@vitest/coverage-v8": "^4.1.10",
56
+ "@vitest/ui": "^4.1.10",
57
+ "eslint-plugin-perfectionist": "^5.10.0",
47
58
  "eslint-plugin-unused-imports": "^4.4.1",
48
- "lefthook": "^2.1.8",
49
- "oxfmt": "^0.51.0",
50
- "oxlint": "^1.66.0",
51
- "oxlint-tsgolint": "^0.23.0",
59
+ "lefthook": "^2.1.10",
60
+ "oxfmt": "^0.60.0",
61
+ "oxlint": "^1.75.0",
62
+ "oxlint-tsgolint": "^7.0.2001",
52
63
  "rimraf": "^6.1.3",
53
- "vitest": "^4.1.7"
64
+ "vitest": "^4.1.10"
54
65
  },
55
66
  "scripts": {
56
- "prebuild": "node scripts/copy-data.ts",
57
- "build": "tsgo -p tsconfig.build.json",
58
67
  "clean": "rimraf node_modules dist coverage",
68
+ "postbuild": "node scripts/copy-data.ts",
69
+ "build": "tsgo -p tsconfig.build.json",
70
+ "graphql:codegen": "graphql-codegen --config codegen.json",
59
71
  "format:check": "oxfmt --check .",
60
72
  "format:write": "oxfmt --write .",
61
73
  "lint:check": "oxlint . --max-warnings=0",
62
74
  "lint:fix": "oxlint . --max-warnings=0 --fix",
63
- "quality": "pnpm format:check && pnpm lint:check && pnpm typecheck && pnpm test",
64
- "test": "vitest run",
65
- "test:dev": "vitest --watch --ui",
66
- "typecheck": "tsgo --noEmit",
75
+ "type:check": "tsgo --noEmit",
76
+ "test": "vitest run --passWithNoTests",
77
+ "test:coverage": "vitest run --coverage --coverage.reporter=html && open coverage/index.html",
78
+ "test:dev": "vitest --watch --ui --passWithNoTests",
79
+ "quality": "pnpm format:check && pnpm lint:check && pnpm type:check && pnpm test",
67
80
  "release": "changeset publish",
68
81
  "release:dev": "changeset version --snapshot dev && changeset publish --tag dev"
69
82
  }
package/schema.json CHANGED
@@ -33,14 +33,15 @@
33
33
  "additionalProperties": false,
34
34
  "properties": {
35
35
  "host": { "type": "string", "default": "github.com" },
36
- "apiRetryAttempts": {
36
+ "retryApiAttempts": {
37
37
  "type": "integer",
38
38
  "minimum": 0,
39
39
  "default": 3,
40
40
  "description": "Number of retry attempts for GitHub CLI API calls that fail with rate limit errors."
41
41
  },
42
42
  "owner": { "type": "string", "minLength": 1 },
43
- "repo": { "type": "string", "minLength": 1 }
43
+ "repo": { "type": "string", "minLength": 1 },
44
+ "url": { "type": "string" }
44
45
  }
45
46
  },
46
47
  "language": { "type": "string" },
@@ -170,24 +171,128 @@
170
171
  "additionalProperties": false,
171
172
  "properties": {
172
173
  "id": { "type": "string", "minLength": 1 },
173
- "options": { "type": "object", "additionalProperties": true }
174
+ "variant": { "type": "string" }
175
+ }
176
+ },
177
+ "safetyFilter": {
178
+ "type": "object",
179
+ "additionalProperties": false,
180
+ "minProperties": 1,
181
+ "properties": {
182
+ "exclude": { "type": "array", "items": { "type": "string" } },
183
+ "include": { "type": "array", "items": { "type": "string" } }
184
+ }
185
+ },
186
+ "safetyBranchFilter": {
187
+ "type": "object",
188
+ "additionalProperties": false,
189
+ "minProperties": 1,
190
+ "properties": {
191
+ "base": { "$ref": "#/$defs/safetyFilter" },
192
+ "head": { "$ref": "#/$defs/safetyFilter" }
193
+ }
194
+ },
195
+ "safety": {
196
+ "type": "object",
197
+ "additionalProperties": false,
198
+ "properties": {
199
+ "authors": { "$ref": "#/$defs/safetyFilter" },
200
+ "branches": { "$ref": "#/$defs/safetyBranchFilter" },
201
+ "labels": { "$ref": "#/$defs/safetyFilter" },
202
+ "maxChangedFiles": { "type": "integer", "minimum": 0 },
203
+ "paths": { "$ref": "#/$defs/safetyFilter" }
204
+ }
205
+ },
206
+ "conditions": {
207
+ "oneOf": [
208
+ { "type": "boolean" },
209
+ {
210
+ "type": "array",
211
+ "items": {
212
+ "type": "array",
213
+ "minItems": 2,
214
+ "maxItems": 2,
215
+ "prefixItems": [{ "type": "boolean" }, { "$ref": "#/$defs/safety" }]
216
+ }
217
+ }
218
+ ]
219
+ },
220
+ "automationSafety": {
221
+ "type": "object",
222
+ "additionalProperties": false,
223
+ "properties": {
224
+ "authors": { "$ref": "#/$defs/safetyFilter" },
225
+ "branches": { "$ref": "#/$defs/safetyBranchFilter" },
226
+ "labels": { "$ref": "#/$defs/safetyFilter" },
227
+ "paths": { "$ref": "#/$defs/safetyFilter" }
228
+ }
229
+ },
230
+ "automationConditions": {
231
+ "oneOf": [
232
+ { "type": "boolean" },
233
+ {
234
+ "type": "array",
235
+ "items": {
236
+ "type": "array",
237
+ "minItems": 2,
238
+ "maxItems": 2,
239
+ "prefixItems": [
240
+ { "type": "boolean" },
241
+ { "$ref": "#/$defs/automationSafety" }
242
+ ]
243
+ }
244
+ }
245
+ ]
246
+ },
247
+ "mergeAutomationConditions": {
248
+ "oneOf": [
249
+ { "type": "boolean" },
250
+ {
251
+ "type": "array",
252
+ "items": {
253
+ "type": "array",
254
+ "minItems": 2,
255
+ "maxItems": 2,
256
+ "prefixItems": [
257
+ { "type": "boolean" },
258
+ { "$ref": "#/$defs/mergeAutomationSafety" }
259
+ ]
260
+ }
261
+ }
262
+ ]
263
+ },
264
+ "mergeAutomationSafety": {
265
+ "type": "object",
266
+ "additionalProperties": false,
267
+ "properties": {
268
+ "authors": { "$ref": "#/$defs/safetyFilter" },
269
+ "branches": { "$ref": "#/$defs/safetyBranchFilter" },
270
+ "edited": { "type": "boolean" },
271
+ "labels": { "$ref": "#/$defs/safetyFilter" },
272
+ "paths": { "$ref": "#/$defs/safetyFilter" }
174
273
  }
175
274
  },
176
275
  "automation": {
177
276
  "type": "object",
178
277
  "additionalProperties": false,
179
278
  "properties": {
180
- "merge": { "type": "boolean", "default": true },
181
- "close": { "type": "boolean" }
279
+ "merge": { "$ref": "#/$defs/automationConditions", "default": true },
280
+ "close": { "$ref": "#/$defs/automationConditions", "default": false }
182
281
  }
183
282
  },
184
283
  "mergeAutomation": {
185
284
  "type": "object",
186
285
  "additionalProperties": false,
187
286
  "properties": {
188
- "merge": { "type": "boolean", "default": true },
189
- "close": { "type": "boolean", "default": false },
190
- "conflict": { "type": "boolean", "default": false }
287
+ "merge": {
288
+ "$ref": "#/$defs/mergeAutomationConditions",
289
+ "default": true
290
+ },
291
+ "close": {
292
+ "$ref": "#/$defs/mergeAutomationConditions",
293
+ "default": false
294
+ },
295
+ "conflict": { "$ref": "#/$defs/automationConditions", "default": false }
191
296
  }
192
297
  },
193
298
  "reviewChecks": {
@@ -221,16 +326,6 @@
221
326
  "runs": { "type": "integer", "minimum": 1, "default": 3 }
222
327
  }
223
328
  },
224
- "safety": {
225
- "type": "object",
226
- "additionalProperties": false,
227
- "properties": {
228
- "allowAuthors": { "type": "array", "items": { "type": "string" } },
229
- "blockedPaths": { "type": "array", "items": { "type": "string" } },
230
- "maxChangedFiles": { "type": "integer", "minimum": 0 },
231
- "requiredLabels": { "type": "array", "items": { "type": "string" } }
232
- }
233
- },
234
329
  "reviewPrompts": {
235
330
  "type": "object",
236
331
  "additionalProperties": false,
@@ -375,10 +470,11 @@
375
470
  },
376
471
  "prompts": { "$ref": "#/$defs/reviewPrompts" },
377
472
  "checks": { "$ref": "#/$defs/reviewChecks" },
378
- "safety": { "$ref": "#/$defs/safety" },
473
+ "safety": { "$ref": "#/$defs/conditions" },
379
474
  "automation": { "$ref": "#/$defs/automation" },
380
475
  "concurrency": { "$ref": "#/$defs/concurrency" },
381
476
  "merge": { "$ref": "#/$defs/reviewMerge" },
477
+ "operator": { "type": "string", "minLength": 1 },
382
478
  "output": { "type": "string" },
383
479
  "worktree": { "type": "string" }
384
480
  }
@@ -421,7 +517,7 @@
421
517
  "safety": { "$ref": "#/$defs/triageSafety" },
422
518
  "concurrency": { "$ref": "#/$defs/triageConcurrency" },
423
519
  "prompts": { "$ref": "#/$defs/triagePrompts" },
424
- "reporter": { "type": "string", "minLength": 1 },
520
+ "operator": { "type": "string", "minLength": 1 },
425
521
  "output": { "type": "string" },
426
522
  "worktree": { "type": "string" }
427
523
  }
@@ -1,62 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { homedir } from "node:os";
3
- import { isAbsolute, join } from "node:path";
4
- const GLOBAL_CONFIG = join(homedir(), ".config", "opencode", "magi.json");
5
- const PROJECT_CONFIG = join(".opencode", "magi.json");
6
- function isPlainObject(value) {
7
- return (!!value &&
8
- typeof value === "object" &&
9
- !Array.isArray(value) &&
10
- Object.getPrototypeOf(value) === Object.prototype);
11
- }
12
- export function mergeMagiConfig(base, override) {
13
- const merged = { ...base };
14
- for (const [key, value] of Object.entries(override)) {
15
- const existing = merged[key];
16
- merged[key] =
17
- isPlainObject(existing) && isPlainObject(value)
18
- ? mergeMagiConfig(existing, value)
19
- : value;
20
- }
21
- return merged;
22
- }
23
- async function readConfig(path) {
24
- try {
25
- return JSON.parse(await readFile(path, "utf8"));
26
- }
27
- catch (error) {
28
- const code = error.code;
29
- if (code === "ENOENT")
30
- return null;
31
- throw error;
32
- }
33
- }
34
- export async function loadConfig(directory, configPath) {
35
- if (configPath) {
36
- const path = isAbsolute(configPath)
37
- ? configPath
38
- : join(directory, configPath);
39
- const config = await readConfig(path);
40
- if (!config)
41
- throw new Error(`Magi config not found: ${path}`);
42
- return { config: config, path };
43
- }
44
- const projectPath = join(directory, PROJECT_CONFIG);
45
- const configs = await Promise.all([
46
- readConfig(GLOBAL_CONFIG),
47
- readConfig(projectPath),
48
- ]);
49
- const loaded = configs
50
- .map((config, index) => ({
51
- config,
52
- path: index === 0 ? GLOBAL_CONFIG : projectPath,
53
- }))
54
- .filter((item) => Boolean(item.config));
55
- if (!loaded.length)
56
- throw new Error(`Magi config not found. Tried: ${GLOBAL_CONFIG}, ${projectPath}`);
57
- const config = loaded.reduce((merged, item) => mergeMagiConfig(merged, item.config), {});
58
- return {
59
- config: config,
60
- path: loaded.map((item) => item.path).join(", "),
61
- };
62
- }
@@ -1,25 +0,0 @@
1
- import { isAbsolute, join } from "node:path";
2
- const DEFAULT_OUTPUT_DIRS = {
3
- issue: ".magi/runs/issue",
4
- pr: ".magi/runs/pr",
5
- };
6
- function resolvePath(directory, path) {
7
- return isAbsolute(path) ? path : join(directory, path);
8
- }
9
- export function outputBaseDir(directory, config, kind) {
10
- return resolvePath(directory, kind === "issue"
11
- ? (config.triage?.output ?? DEFAULT_OUTPUT_DIRS[kind])
12
- : (config.review?.output ?? DEFAULT_OUTPUT_DIRS[kind]));
13
- }
14
- export function outputBaseDirs(directory, config) {
15
- return [
16
- outputBaseDir(directory, config, "pr"),
17
- outputBaseDir(directory, config, "issue"),
18
- ];
19
- }
20
- export function prRunOutputDir(input) {
21
- return join(outputBaseDir(input.directory, input.config, "pr"), String(input.pr), ...(input.runId ? [input.runId] : []));
22
- }
23
- export function issueRunOutputDir(input) {
24
- return join(outputBaseDir(input.directory, input.config, "issue"), String(input.issue), ...(input.runId ? [input.runId] : []));
25
- }
@@ -1,25 +0,0 @@
1
- import { isAbsolute, join } from "node:path";
2
- const DEFAULT_WORKTREE_DIRS = {
3
- issue: ".magi/worktrees/issue",
4
- pr: ".magi/worktrees/pr",
5
- };
6
- function resolvePath(directory, path) {
7
- return isAbsolute(path) ? path : join(directory, path);
8
- }
9
- export function worktreeBaseDir(directory, config, kind) {
10
- return resolvePath(directory, kind === "issue"
11
- ? (config.triage?.worktree ?? DEFAULT_WORKTREE_DIRS[kind])
12
- : (config.review?.worktree ?? DEFAULT_WORKTREE_DIRS[kind]));
13
- }
14
- export function worktreeBaseDirs(directory, config = {}) {
15
- return [
16
- worktreeBaseDir(directory, config, "pr"),
17
- worktreeBaseDir(directory, config, "issue"),
18
- ];
19
- }
20
- export function prRunWorktreeDir(input) {
21
- return join(worktreeBaseDir(input.directory, input.config, "pr"), String(input.pr), input.runId);
22
- }
23
- export function issueRunWorktreeDir(input) {
24
- return join(worktreeBaseDir(input.directory, input.config, "issue"), String(input.issue), input.runId);
25
- }