opencode-magi 0.1.0 → 0.3.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 (43) hide show
  1. package/README.md +33 -10
  2. package/dist/commands.js +4 -0
  3. package/dist/config/output.js +11 -2
  4. package/dist/config/resolve.js +124 -26
  5. package/dist/config/validate.js +486 -191
  6. package/dist/config/worktree.js +19 -0
  7. package/dist/github/commands.js +349 -17
  8. package/dist/index.js +257 -27
  9. package/dist/orchestrator/ci.js +1 -1
  10. package/dist/orchestrator/findings.js +4 -3
  11. package/dist/orchestrator/inline-comments.js +73 -0
  12. package/dist/orchestrator/majority.js +14 -0
  13. package/dist/orchestrator/merge.js +24 -4
  14. package/dist/orchestrator/report.js +15 -1
  15. package/dist/orchestrator/review-context.js +309 -0
  16. package/dist/orchestrator/review.js +78 -10
  17. package/dist/orchestrator/run-manager.js +418 -20
  18. package/dist/orchestrator/triage.js +1119 -0
  19. package/dist/permissions/editor.json +8 -1
  20. package/dist/prompts/compose.js +172 -15
  21. package/dist/prompts/contracts.js +119 -12
  22. package/dist/prompts/output.js +149 -14
  23. package/dist/prompts/templates/{close-reconsideration.md → review/close-reconsideration.md} +1 -2
  24. package/dist/prompts/templates/review/review.md +13 -0
  25. package/dist/prompts/templates/triage/acceptance.md +7 -0
  26. package/dist/prompts/templates/triage/action.md +5 -0
  27. package/dist/prompts/templates/triage/category.md +10 -0
  28. package/dist/prompts/templates/triage/comment-classification.md +7 -0
  29. package/dist/prompts/templates/triage/comment.md +5 -0
  30. package/dist/prompts/templates/triage/create.md +7 -0
  31. package/dist/prompts/templates/triage/duplicate.md +7 -0
  32. package/dist/prompts/templates/triage/existing-pr.md +7 -0
  33. package/dist/prompts/templates/triage/question.md +5 -0
  34. package/dist/prompts/templates/triage/reconsider.md +5 -0
  35. package/package.json +28 -27
  36. package/schema.json +234 -90
  37. package/dist/prompts/templates/rereview-close-reconsideration.md +0 -6
  38. package/dist/prompts/templates/review.md +0 -7
  39. /package/dist/prompts/templates/{ci-classification-after-edit.md → merge/ci-classification.md} +0 -0
  40. /package/dist/prompts/templates/{edit.md → merge/edit.md} +0 -0
  41. /package/dist/prompts/templates/{ci-classification.md → review/ci-classification.md} +0 -0
  42. /package/dist/prompts/templates/{finding-validation.md → review/finding-validation.md} +0 -0
  43. /package/dist/prompts/templates/{rereview.md → review/rereview.md} +0 -0
package/README.md CHANGED
@@ -36,6 +36,8 @@ Add the plugin to `opencode.json`.
36
36
  }
37
37
  ```
38
38
 
39
+ Restart OpenCode. Done.
40
+
39
41
  ### Configure
40
42
 
41
43
  Configure global defaults in `~/.config/opencode/magi.json` and project overrides in `<project>/.opencode/magi.json`.
@@ -58,9 +60,9 @@ Add the following content to the configuration file.
58
60
 
59
61
  ```json
60
62
  {
61
- "$schema": "https://raw.githubusercontent.com/hirotomoyamada/opencode-magi/main/schema.json",
62
- "agents": {
63
- "reviewers": [
63
+ "$schema": "https://raw.githubusercontent.com/magi-ai/opencode-magi/main/schema.json",
64
+ "review": {
65
+ "agents": [
64
66
  {
65
67
  "account": "your-account-1",
66
68
  "model": "openai/gpt-5.5"
@@ -78,7 +80,7 @@ Add the following content to the configuration file.
78
80
  }
79
81
  ```
80
82
 
81
- `agents.reviewers[].account` is the GitHub account used to post reviews and approvals. Must be authenticated with `gh auth token --user <account>` and must be unique.
83
+ `review.agents[].account` is the GitHub account used to post reviews and approvals. Must be authenticated with `gh auth token --user <account>` and must be unique.
82
84
 
83
85
  #### Set project config
84
86
 
@@ -94,13 +96,13 @@ Add the following content to the configuration file.
94
96
 
95
97
  ```json
96
98
  {
97
- "$schema": "https://raw.githubusercontent.com/hirotomoyamada/opencode-magi/main/schema.json",
99
+ "$schema": "https://raw.githubusercontent.com/magi-ai/opencode-magi/main/schema.json",
98
100
  "github": {
99
101
  "owner": "your-owner",
100
102
  "repo": "your-repo"
101
103
  },
102
- "agents": {
103
- "reviewers": [
104
+ "review": {
105
+ "agents": [
104
106
  {
105
107
  "account": "your-account-1",
106
108
  "model": "openai/gpt-5.5"
@@ -113,7 +115,9 @@ Add the following content to the configuration file.
113
115
  "account": "your-account-3",
114
116
  "model": "opencode/kimi-k2-6"
115
117
  }
116
- ],
118
+ ]
119
+ },
120
+ "merge": {
117
121
  "editor": {
118
122
  "account": "your-editor-account",
119
123
  "model": "openai/gpt-5.5",
@@ -122,11 +126,28 @@ Add the following content to the configuration file.
122
126
  "email": "your-email@example.com"
123
127
  }
124
128
  }
129
+ },
130
+ "triage": {
131
+ "account": "your-triage-account",
132
+ "agents": [
133
+ {
134
+ "id": "general",
135
+ "model": "openai/gpt-5.5"
136
+ },
137
+ {
138
+ "id": "maintenance",
139
+ "model": "anthropic/claude-opus-4-7"
140
+ },
141
+ {
142
+ "id": "product",
143
+ "model": "opencode/kimi-k2-6"
144
+ }
145
+ ]
125
146
  }
126
147
  }
127
148
  ```
128
149
 
129
- `agents.reviewers[].account` is the GitHub account used to post reviews and approvals. Must be authenticated with `gh auth token --user <account>` and must be unique.
150
+ `review.agents[].account` is the GitHub account used to post reviews and approvals. Must be authenticated with `gh auth token --user <account>` and must be unique. `merge.editor.account` is used by `/magi:merge` to push fixes, close PRs, and merge PRs.
130
151
 
131
152
  #### Validate config
132
153
 
@@ -145,6 +166,8 @@ Run commands from OpenCode.
145
166
  /magi:review --dry-run 123
146
167
  /magi:merge 123
147
168
  /magi:merge --dry-run 123
169
+ /magi:triage 47 48
170
+ /magi:triage --dry-run 47
148
171
  /magi:clear
149
172
  ```
150
173
 
@@ -152,7 +175,7 @@ Run commands from OpenCode.
152
175
 
153
176
  - [Commands](docs/commands/index.md)
154
177
  - [Config](docs/config.md)
155
- - [Prompts](docs/prompts.md)
178
+ - [Prompts](docs/prompts/index.md)
156
179
 
157
180
  ## Contributing
158
181
 
package/dist/commands.js CHANGED
@@ -7,6 +7,10 @@ export const MAGI_COMMANDS = {
7
7
  description: "Review and merge pull requests with Magi",
8
8
  template: [`Call the \`magi_merge\` tool.`, "PR: $ARGUMENTS"].join("\n"),
9
9
  },
10
+ "magi:triage": {
11
+ description: "Triage GitHub issues with Magi",
12
+ template: [`Call the \`magi_triage\` tool.`, "Issue: $ARGUMENTS"].join("\n"),
13
+ },
10
14
  "magi:review": {
11
15
  description: "Review pull requests with Magi",
12
16
  template: [`Call the \`magi_review\` tool.`, "PR: $ARGUMENTS"].join("\n"),
@@ -1,16 +1,25 @@
1
1
  import { isAbsolute, join } from "node:path";
2
2
  const DEFAULT_OUTPUT_DIRS = {
3
+ issue: ".magi/runs/issue",
3
4
  pr: ".magi/runs/pr",
4
5
  };
5
6
  function resolvePath(directory, path) {
6
7
  return isAbsolute(path) ? path : join(directory, path);
7
8
  }
8
9
  export function outputBaseDir(directory, config, kind) {
9
- return resolvePath(directory, config.output?.dirs?.[kind] ?? DEFAULT_OUTPUT_DIRS[kind]);
10
+ return resolvePath(directory, kind === "issue"
11
+ ? (config.triage?.output ?? DEFAULT_OUTPUT_DIRS[kind])
12
+ : (config.review?.output ?? DEFAULT_OUTPUT_DIRS[kind]));
10
13
  }
11
14
  export function outputBaseDirs(directory, config) {
12
- return [outputBaseDir(directory, config, "pr")];
15
+ return [
16
+ outputBaseDir(directory, config, "pr"),
17
+ outputBaseDir(directory, config, "issue"),
18
+ ];
13
19
  }
14
20
  export function prRunOutputDir(input) {
15
21
  return join(outputBaseDir(input.directory, input.config, "pr"), String(input.pr), ...(input.runId ? [input.runId] : []));
16
22
  }
23
+ export function issueRunOutputDir(input) {
24
+ return join(outputBaseDir(input.directory, input.config, "issue"), String(input.issue), ...(input.runId ? [input.runId] : []));
25
+ }
@@ -4,9 +4,32 @@ const ID_PATTERN = /^[A-Za-z0-9_-]+$/;
4
4
  const DEFAULT_COMMON_PERMISSION = commonPermission;
5
5
  const DEFAULT_REVIEWER_PERMISSION = DEFAULT_COMMON_PERMISSION;
6
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
+ ];
7
27
  export function reviewerKey(reviewer, index) {
8
28
  return reviewer.id ?? `reviewer-${index + 1}`;
9
29
  }
30
+ export function triageAgentKey(agent, index) {
31
+ return agent.id ?? `triage-${index + 1}`;
32
+ }
10
33
  export function validateReviewerId(id) {
11
34
  return ID_PATTERN.test(id);
12
35
  }
@@ -44,27 +67,57 @@ export function mergePermissions(base, override) {
44
67
  return merged;
45
68
  }
46
69
  export function resolveReviewerPermission(agents, reviewer) {
47
- return mergePermissions(mergePermissions(DEFAULT_REVIEWER_PERMISSION, agents.permissions), reviewer.permission);
70
+ return mergePermissions(mergePermissions(DEFAULT_REVIEWER_PERMISSION, agents.permissions), reviewer.permissions);
48
71
  }
49
72
  export function resolveEditorPermission(agents, editor) {
50
- return mergePermissions(mergePermissions(DEFAULT_EDITOR_PERMISSION, agents.permissions), editor.permission);
73
+ return mergePermissions(mergePermissions(DEFAULT_EDITOR_PERMISSION, agents.permissions), editor.permissions);
74
+ }
75
+ export function resolveTriageAgentPermission(agents, agent) {
76
+ return mergePermissions(mergePermissions(DEFAULT_REVIEWER_PERMISSION, agents.permissions), agent.permissions);
77
+ }
78
+ export function resolveTriageCreatorPermission(agents, creator) {
79
+ return mergePermissions(mergePermissions(DEFAULT_EDITOR_PERMISSION, agents.permissions), creator.permissions);
51
80
  }
52
- export function resolveAgents(agents) {
81
+ export function resolveAgents(config) {
82
+ const agents = config.agents ?? {};
83
+ const editor = config.merge?.editor;
84
+ const creator = config.triage?.creator;
53
85
  return {
54
- editor: agents.editor
86
+ editor: editor
55
87
  ? {
56
- ...agents.editor,
57
- permission: resolveEditorPermission(agents, agents.editor),
88
+ ...editor,
89
+ permission: resolveEditorPermission(agents, editor),
58
90
  }
59
91
  : undefined,
60
- reviewers: (agents.reviewers ?? []).map((reviewer, index) => ({
92
+ reviewers: (config.review?.agents ?? []).map((reviewer, index) => ({
61
93
  ...reviewer,
62
94
  key: reviewerKey(reviewer, index),
63
95
  index,
64
96
  permission: resolveReviewerPermission(agents, reviewer),
65
97
  })),
98
+ triage: (config.triage?.agents ?? []).map((agent, index) => ({
99
+ ...agent,
100
+ key: triageAgentKey(agent, index),
101
+ index,
102
+ permission: resolveTriageAgentPermission(agents, agent),
103
+ })),
104
+ triageCreator: creator
105
+ ? {
106
+ ...creator,
107
+ account: creator.account ?? config.triage?.account ?? "",
108
+ permission: resolveTriageCreatorPermission(agents, creator),
109
+ }
110
+ : undefined,
66
111
  };
67
112
  }
113
+ function resolveTriageCategories(config) {
114
+ return (config.triage?.categories ?? DEFAULT_TRIAGE_CATEGORIES).map((category) => ({
115
+ description: category.description,
116
+ id: category.id ?? "",
117
+ labels: category.labels ?? [],
118
+ types: category.types ?? [],
119
+ }));
120
+ }
68
121
  export function resolveRepository(config) {
69
122
  if (!config.github?.owner)
70
123
  throw new Error("github.owner is required");
@@ -72,20 +125,21 @@ export function resolveRepository(config) {
72
125
  throw new Error("github.repo is required");
73
126
  return {
74
127
  alias: config.github.repo,
75
- agents: resolveAgents(config.agents),
128
+ agents: resolveAgents(config),
76
129
  automation: {
77
- close: config.automation?.close ?? true,
78
- merge: config.automation?.merge ?? true,
130
+ close: config.merge?.automation?.close ?? false,
131
+ merge: config.merge?.automation?.merge ?? true,
79
132
  },
80
133
  checks: {
81
- exclude: config.checks?.exclude ?? [],
82
- waitAfterEdit: config.checks?.waitAfterEdit ?? true,
83
- waitBeforeReview: config.checks?.waitBeforeReview ?? true,
84
- retryFailedJobs: config.checks?.retryFailedJobs ?? 3,
134
+ exclude: config.review?.checks?.exclude ?? [],
135
+ retryFailedJobs: config.review?.checks?.retryFailedJobs ?? 3,
136
+ wait: config.review?.checks?.wait ?? true,
137
+ waitAfterEdit: config.merge?.checks?.wait ?? true,
138
+ waitBeforeReview: config.review?.checks?.wait ?? true,
85
139
  },
86
140
  concurrency: {
87
- runs: config.concurrency?.runs ?? 3,
88
- reviewers: config.concurrency?.reviewers ?? 3,
141
+ runs: config.review?.concurrency?.runs ?? 3,
142
+ reviewers: config.review?.concurrency?.reviewers ?? 3,
89
143
  },
90
144
  github: {
91
145
  apiRetryAttempts: config.github.apiRetryAttempts ?? 3,
@@ -95,19 +149,63 @@ export function resolveRepository(config) {
95
149
  },
96
150
  language: config.language,
97
151
  merge: {
98
- approvalPolicy: config.merge?.approvalPolicy ?? "majority",
99
- method: config.merge?.method ?? "squash",
100
- auto: config.merge?.auto ?? true,
101
- deleteBranch: config.merge?.deleteBranch ?? true,
102
- mergeQueue: config.merge?.mergeQueue ?? false,
152
+ approvalPolicy: config.review?.merge?.approvalPolicy ?? "majority",
153
+ method: config.review?.merge?.method ?? "squash",
154
+ auto: config.review?.merge?.auto ?? true,
155
+ deleteBranch: config.review?.merge?.deleteBranch ?? true,
156
+ queue: config.review?.merge?.queue ?? false,
157
+ mergeQueue: config.review?.merge?.queue ?? false,
103
158
  maxThreadResolutionCycles: config.merge?.maxThreadResolutionCycles ?? 5,
104
159
  },
105
- prompts: config.prompts ?? {},
160
+ prompts: {
161
+ ciClassification: config.review?.prompts?.ciClassification,
162
+ ciClassificationAfterEdit: config.merge?.prompts?.ciClassification,
163
+ closeReconsideration: config.review?.prompts?.closeReconsideration,
164
+ edit: config.merge?.prompts?.edit,
165
+ editGuidelines: config.merge?.prompts?.editGuidelines,
166
+ findingValidation: config.review?.prompts?.findingValidation,
167
+ rereview: config.review?.prompts?.rereview,
168
+ review: config.review?.prompts?.review,
169
+ reviewGuidelines: config.review?.prompts?.reviewGuidelines,
170
+ },
171
+ reviewAutomation: {
172
+ close: config.review?.automation?.close ?? false,
173
+ merge: config.review?.automation?.merge ?? true,
174
+ },
106
175
  safety: {
107
- allowAuthors: config.safety?.allowAuthors ?? [],
108
- blockedPaths: config.safety?.blockedPaths ?? [],
109
- maxChangedFiles: config.safety?.maxChangedFiles,
110
- requiredLabels: config.safety?.requiredLabels ?? [],
176
+ allowAuthors: config.review?.safety?.allowAuthors ?? [],
177
+ blockedPaths: config.review?.safety?.blockedPaths ?? [],
178
+ maxChangedFiles: config.review?.safety?.maxChangedFiles,
179
+ requiredLabels: config.review?.safety?.requiredLabels ?? [],
180
+ },
181
+ triage: {
182
+ account: config.triage?.account,
183
+ automation: {
184
+ clear: config.triage?.automation?.clear ?? ["triage"],
185
+ close: config.triage?.automation?.close ?? false,
186
+ create: config.triage?.automation?.create ?? false,
187
+ merge: config.triage?.automation?.merge ?? false,
188
+ review: config.triage?.automation?.review ?? false,
189
+ },
190
+ categories: resolveTriageCategories(config),
191
+ concurrency: {
192
+ runs: config.triage?.concurrency?.runs ?? 3,
193
+ },
194
+ output: config.triage?.output,
195
+ prompts: config.triage?.prompts ?? {},
196
+ safety: {
197
+ allowAuthors: config.triage?.safety?.allowAuthors ?? [],
198
+ allowMentionActors: config.triage?.safety?.allowMentionActors ?? [],
199
+ allowMentionRoles: config.triage?.safety?.allowMentionRoles ?? [
200
+ "AUTHOR",
201
+ "OWNER",
202
+ "MEMBER",
203
+ "COLLABORATOR",
204
+ ],
205
+ blockedLabels: config.triage?.safety?.blockedLabels ?? [],
206
+ requiredLabels: config.triage?.safety?.requiredLabels ?? ["triage"],
207
+ },
208
+ worktree: config.triage?.worktree,
111
209
  },
112
210
  };
113
211
  }