opencode-magi 0.0.0-dev-20260521013020 → 0.0.0-dev-20260521140727

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.
@@ -157,6 +157,56 @@ function ghHostOption(config) {
157
157
  function isPlainObject(value) {
158
158
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
159
159
  }
160
+ function expandAgentRefUse(value, path, refs, refsInvalid, errors) {
161
+ if (!isPlainObject(value) || !Object.hasOwn(value, "ref"))
162
+ return value;
163
+ const use = { ...value };
164
+ const ref = use.ref;
165
+ delete use.ref;
166
+ if (typeof ref !== "string") {
167
+ errors.push(`${path}.ref must be a string`);
168
+ return use;
169
+ }
170
+ if (refsInvalid) {
171
+ errors.push(`agents.refs must be an object to resolve ${path}.ref`);
172
+ return use;
173
+ }
174
+ const preset = refs?.[ref];
175
+ if (preset == null) {
176
+ errors.push(`${path}.ref references unknown agents.refs preset: ${ref}`);
177
+ return use;
178
+ }
179
+ if (!isPlainObject(preset)) {
180
+ errors.push(`agents.refs.${ref} must be an object when referenced by ${path}.ref`);
181
+ return use;
182
+ }
183
+ const presetFields = { ...preset };
184
+ delete presetFields.ref;
185
+ return { ...presetFields, ...use };
186
+ }
187
+ function expandAgentRefs(config, errors) {
188
+ if (!config || typeof config !== "object")
189
+ return;
190
+ const magiConfig = config;
191
+ const agents = magiConfig.agents;
192
+ const refsValue = isPlainObject(agents) ? agents.refs : undefined;
193
+ const refsInvalid = refsValue != null && !isPlainObject(refsValue);
194
+ const refs = isPlainObject(refsValue) ? refsValue : undefined;
195
+ if (Array.isArray(magiConfig.review?.agents)) {
196
+ magiConfig.review.agents = magiConfig.review.agents.map((agent, index) => expandAgentRefUse(agent, `review.agents[${index}]`, refs, refsInvalid, errors));
197
+ }
198
+ if (isPlainObject(magiConfig.merge?.editor)) {
199
+ magiConfig.merge.editor = expandAgentRefUse(magiConfig.merge.editor, "merge.editor", refs, refsInvalid, errors);
200
+ }
201
+ if (Array.isArray(magiConfig.triage?.agents)) {
202
+ magiConfig.triage.agents = magiConfig.triage.agents.map((agent, index) => expandAgentRefUse(agent, `triage.agents[${index}]`, refs, refsInvalid, errors));
203
+ }
204
+ if (isPlainObject(magiConfig.triage?.creator)) {
205
+ magiConfig.triage.creator = expandAgentRefUse(magiConfig.triage.creator, "triage.creator", refs, refsInvalid, errors);
206
+ }
207
+ if (isPlainObject(magiConfig.agents))
208
+ delete magiConfig.agents.refs;
209
+ }
160
210
  function validateKnownKeys(value, path, keys, errors) {
161
211
  if (!isPlainObject(value))
162
212
  return;
@@ -792,6 +842,7 @@ export async function validateConfig(config, options = {}) {
792
842
  const warnings = [];
793
843
  if (!config || typeof config !== "object")
794
844
  errors.push("config must be an object");
845
+ expandAgentRefs(config, errors);
795
846
  if (config && typeof config === "object")
796
847
  validateJsonSchema(config, errors);
797
848
  validateKnownKeys(config, "config", CONFIG_KEYS, errors);
@@ -246,6 +246,12 @@ function duplicateReferences(text) {
246
246
  refs.add(Number(match[1]));
247
247
  return [...refs];
248
248
  }
249
+ function issueTitleSearchQuery(title, fallback) {
250
+ return (title
251
+ .replaceAll(/[^\p{L}\p{N}_]+/gu, " ")
252
+ .replaceAll(/\s+/g, " ")
253
+ .trim() || fallback);
254
+ }
249
255
  async function fetchIssueCandidate(exec, repository, number, whyCandidate) {
250
256
  const raw = await exec(`gh issue view ${number} --repo ${shellQuote(repoSpecifier(repository))} --json number,title,url,state,body,createdAt`).catch(() => undefined);
251
257
  if (!raw)
@@ -254,7 +260,7 @@ async function fetchIssueCandidate(exec, repository, number, whyCandidate) {
254
260
  return { ...data, whyCandidate };
255
261
  }
256
262
  export async function searchDuplicateIssues(exec, repository, issue, limit = 5) {
257
- const query = issue.title;
263
+ const query = issueTitleSearchQuery(issue.title, String(issue.number));
258
264
  const explicitCandidates = await Promise.all(duplicateReferences(issue.body)
259
265
  .filter((number) => number !== issue.number)
260
266
  .map((number) => fetchIssueCandidate(exec, repository, number, "Issue body explicitly references a duplicate target.")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260521013020",
3
+ "version": "0.0.0-dev-20260521140727",
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
@@ -9,7 +9,11 @@
9
9
  "type": "object",
10
10
  "additionalProperties": false,
11
11
  "properties": {
12
- "permissions": { "$ref": "#/$defs/permissions" }
12
+ "permissions": { "$ref": "#/$defs/permissions" },
13
+ "refs": {
14
+ "type": "object",
15
+ "additionalProperties": { "$ref": "#/$defs/agentRef" }
16
+ }
13
17
  }
14
18
  },
15
19
  "clear": {
@@ -51,11 +55,33 @@
51
55
  "triage": { "$ref": "#/$defs/triage" }
52
56
  },
53
57
  "$defs": {
58
+ "agentRef": {
59
+ "type": "object",
60
+ "additionalProperties": false,
61
+ "properties": {
62
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
63
+ "model": { "type": "string", "minLength": 1 },
64
+ "options": { "type": "object", "additionalProperties": true },
65
+ "account": { "type": "string", "minLength": 1 },
66
+ "author": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "properties": {
70
+ "name": { "type": "string", "minLength": 1 },
71
+ "email": { "type": "string", "minLength": 1 }
72
+ }
73
+ },
74
+ "permissions": { "$ref": "#/$defs/permissions" },
75
+ "persona": { "type": "string" }
76
+ }
77
+ },
54
78
  "reviewer": {
55
79
  "type": "object",
56
- "required": ["model", "account"],
80
+ "if": { "not": { "required": ["ref"] } },
81
+ "then": { "required": ["model", "account"] },
57
82
  "additionalProperties": false,
58
83
  "properties": {
84
+ "ref": { "type": "string", "minLength": 1 },
59
85
  "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
60
86
  "model": { "type": "string", "minLength": 1 },
61
87
  "options": { "type": "object", "additionalProperties": true },
@@ -66,9 +92,11 @@
66
92
  },
67
93
  "editor": {
68
94
  "type": "object",
69
- "required": ["model", "account", "author"],
95
+ "if": { "not": { "required": ["ref"] } },
96
+ "then": { "required": ["model", "account", "author"] },
70
97
  "additionalProperties": false,
71
98
  "properties": {
99
+ "ref": { "type": "string", "minLength": 1 },
72
100
  "model": { "type": "string", "minLength": 1 },
73
101
  "options": { "type": "object", "additionalProperties": true },
74
102
  "account": { "type": "string", "minLength": 1 },
@@ -87,9 +115,11 @@
87
115
  },
88
116
  "triageAgent": {
89
117
  "type": "object",
90
- "required": ["model"],
118
+ "if": { "not": { "required": ["ref"] } },
119
+ "then": { "required": ["model"] },
91
120
  "additionalProperties": false,
92
121
  "properties": {
122
+ "ref": { "type": "string", "minLength": 1 },
93
123
  "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
94
124
  "model": { "type": "string", "minLength": 1 },
95
125
  "options": { "type": "object", "additionalProperties": true },
@@ -99,9 +129,11 @@
99
129
  },
100
130
  "triageCreator": {
101
131
  "type": "object",
102
- "required": ["model", "author"],
132
+ "if": { "not": { "required": ["ref"] } },
133
+ "then": { "required": ["model", "author"] },
103
134
  "additionalProperties": false,
104
135
  "properties": {
136
+ "ref": { "type": "string", "minLength": 1 },
105
137
  "account": { "type": "string", "minLength": 1 },
106
138
  "model": { "type": "string", "minLength": 1 },
107
139
  "options": { "type": "object", "additionalProperties": true },