opencode-magi 0.10.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 +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 +103 -1110
  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 +6 -4
  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.11.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,8 +28,12 @@
28
28
  "schema.json"
29
29
  ],
30
30
  "dependencies": {
31
- "@opencode-ai/plugin": "^1.15.10",
31
+ "@opencode-ai/plugin": "^1.18.1",
32
+ "@opencode-ai/sdk": "^1.18.1",
32
33
  "ajv": "^8.20.0",
34
+ "graphql": "^16.14.0",
35
+ "graphql-tag": "^2.12.6",
36
+ "octokit": "^5.0.5",
33
37
  "picomatch": "^4.0.4",
34
38
  "valibot": "^1.4.1"
35
39
  },
@@ -38,6 +42,13 @@
38
42
  "@changesets/cli": "^2.30.0",
39
43
  "@commitlint/cli": "^21.0.1",
40
44
  "@commitlint/config-conventional": "^21.0.1",
45
+ "@graphql-codegen/cli": "^7.1.1",
46
+ "@graphql-codegen/typescript-generic-sdk": "^5.0.1",
47
+ "@graphql-codegen/typescript-operations": "^6.0.3",
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",
41
52
  "@types/node": "^25.9.1",
42
53
  "@types/picomatch": "^4.0.3",
43
54
  "@typescript/native-preview": "7.0.0-dev.20260525.1",
@@ -53,17 +64,18 @@
53
64
  "vitest": "^4.1.7"
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:dev": "vitest --watch --ui --passWithNoTests",
78
+ "quality": "pnpm format:check && pnpm lint:check && pnpm type:check && pnpm test",
67
79
  "release": "changeset publish",
68
80
  "release:dev": "changeset version --snapshot dev && changeset publish --tag dev"
69
81
  }
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,7 +171,7 @@
170
171
  "additionalProperties": false,
171
172
  "properties": {
172
173
  "id": { "type": "string", "minLength": 1 },
173
- "options": { "type": "object", "additionalProperties": true }
174
+ "variant": { "type": "string" }
174
175
  }
175
176
  },
176
177
  "automation": {
@@ -379,6 +380,7 @@
379
380
  "automation": { "$ref": "#/$defs/automation" },
380
381
  "concurrency": { "$ref": "#/$defs/concurrency" },
381
382
  "merge": { "$ref": "#/$defs/reviewMerge" },
383
+ "operator": { "type": "string", "minLength": 1 },
382
384
  "output": { "type": "string" },
383
385
  "worktree": { "type": "string" }
384
386
  }
@@ -421,7 +423,7 @@
421
423
  "safety": { "$ref": "#/$defs/triageSafety" },
422
424
  "concurrency": { "$ref": "#/$defs/triageConcurrency" },
423
425
  "prompts": { "$ref": "#/$defs/triagePrompts" },
424
- "reporter": { "type": "string", "minLength": 1 },
426
+ "operator": { "type": "string", "minLength": 1 },
425
427
  "output": { "type": "string" },
426
428
  "worktree": { "type": "string" }
427
429
  }
@@ -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
- }