opencode-magi 0.4.0 → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, join } from "node:path";
4
- import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageActionOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
4
+ import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
5
5
  async function readOptionalPrompt(directory, path, values = {}) {
6
6
  if (!path)
7
7
  return "";
@@ -333,20 +333,12 @@ async function composeTriageVotePrompt(input) {
333
333
  return [
334
334
  task,
335
335
  languageBlock(input.repository.language),
336
- personaBlock(input.reviewer.persona),
336
+ personaBlock(input.voter.persona),
337
337
  input.outputContract,
338
338
  ]
339
339
  .filter(Boolean)
340
340
  .join("\n\n");
341
341
  }
342
- export async function composeTriageActionPrompt(input) {
343
- return composeTriageVotePrompt({
344
- ...input,
345
- builtin: "action",
346
- customPath: input.repository.triage?.prompts.action,
347
- outputContract: triageActionOutputContract,
348
- });
349
- }
350
342
  export async function composeTriageCommentPrompt(input) {
351
343
  const values = triageValues(input);
352
344
  const task = await taskBlock({
@@ -15,26 +15,20 @@ The object must match this shape:
15
15
  "perspective": "Optional review perspective."
16
16
  }
17
17
  ],
18
- "requirementFindings": [
19
- {
20
- "issueNumber": 47,
21
- "requirement": "Required closing-issue behavior that is missing.",
22
- "evidence": "Why the PR does not satisfy the requirement.",
23
- "fix": "How to satisfy the requirement."
24
- }
25
- ],
26
18
  "reason": "Required only for CLOSE."
27
19
  }
28
20
 
29
21
  Rules:
30
- - MERGE requires empty findings and requirementFindings arrays.
31
- - CHANGES_REQUESTED requires at least one finding or requirementFinding.
32
- - CLOSE requires a reason and empty findings and requirementFindings arrays.
22
+ - MERGE requires an empty findings array.
23
+ - CHANGES_REQUESTED requires at least one finding.
24
+ - CLOSE requires a reason and an empty findings array.
33
25
  - path must be repository-relative.
34
- - line is optional. Include line only when the finding targets a valid line inside the PR diff hunk.
35
- - startLine is allowed only when line is present and must also refer to a line inside the PR diff hunk.
36
- - Omit startLine for single-line findings and omit line for file-level or body-only findings.
37
- - Use requirementFindings only for missing closing-issue requirements; use findings for ordinary file-level issues that do not map cleanly to a diff line.
26
+ - line is required and must target a valid right-side line inside the PR diff hunk.
27
+ - startLine is optional and must also target a valid right-side line inside the same PR diff hunk range.
28
+ - Omit startLine for single-line findings.
29
+ - Do not omit line. Do not create file-level or body-only findings.
30
+ - Missing closing-issue requirements must be normal findings anchored to the nearest responsible changed line.
31
+ - If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
38
32
  </output_contract>`.trim();
39
33
  export const rereviewOutputContract = `
40
34
  <output_contract>
@@ -46,18 +40,19 @@ The object must match this shape:
46
40
  "resolve": [{ "commentId": 123, "threadId": "..." }],
47
41
  "followUps": [{ "commentId": 123, "body": "..." }],
48
42
  "newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }],
49
- "requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }],
50
43
  "reason": "Required only for CLOSE."
51
44
  }
52
45
 
53
46
  Rules:
54
- - MERGE requires empty followUps, newFindings, and requirementFindings arrays.
55
- - CHANGES_REQUESTED requires at least one followUp, newFinding, or requirementFinding.
56
- - CLOSE requires a reason and empty followUps, newFindings, and requirementFindings arrays.
57
- - line is optional. Include line only when the newFinding targets a valid line inside the latest PR diff hunk.
58
- - startLine is allowed only when line is present and must also refer to a line inside the latest PR diff hunk.
59
- - Omit startLine for single-line findings and omit line for file-level or body-only findings.
60
- - Use requirementFindings only for missing closing-issue requirements; use newFindings for ordinary file-level issues that do not map cleanly to a diff line.
47
+ - MERGE requires empty followUps and newFindings arrays.
48
+ - CHANGES_REQUESTED requires at least one followUp or newFinding.
49
+ - CLOSE requires a reason and empty followUps and newFindings arrays.
50
+ - line is required and must target a valid right-side line inside the latest PR diff hunk.
51
+ - startLine is optional and must also target a valid right-side line inside the same latest PR diff hunk range.
52
+ - Omit startLine for single-line findings.
53
+ - Do not omit line. Do not create file-level or body-only findings.
54
+ - Missing closing-issue requirements must be normal newFindings anchored to the nearest responsible changed line.
55
+ - If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
61
56
  </output_contract>`.trim();
62
57
  export const findingValidationOutputContract = `
63
58
  <output_contract>
@@ -96,17 +91,16 @@ The object must match this shape:
96
91
  "issue": "What is wrong.",
97
92
  "fix": "How to fix it."
98
93
  }
99
- ],
100
- "requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }]
94
+ ]
101
95
  }
102
96
 
103
97
  Rules:
104
- - MERGE requires empty findings and requirementFindings arrays.
105
- - CHANGES_REQUESTED requires at least one finding or requirementFinding.
98
+ - MERGE requires an empty findings array.
99
+ - CHANGES_REQUESTED requires at least one finding.
106
100
  - CLOSE is not allowed in this reconsideration step.
107
- - line is optional. Include line only when the finding targets a valid line inside the PR diff hunk.
108
- - startLine is allowed only when line is present.
109
- - Omit startLine for single-line findings and omit line for file-level or body-only findings.
101
+ - line is required and must target a valid right-side line inside the PR diff hunk.
102
+ - startLine is optional and must also target a valid right-side line inside the same PR diff hunk range.
103
+ - Do not omit line. Do not create file-level or body-only findings.
110
104
  </output_contract>`.trim();
111
105
  export const rereviewCloseReconsiderationOutputContract = `
112
106
  <output_contract>
@@ -117,17 +111,16 @@ The object must match this shape:
117
111
  "verdict": "MERGE" | "CHANGES_REQUESTED",
118
112
  "resolve": [{ "commentId": 123, "threadId": "..." }],
119
113
  "followUps": [{ "commentId": 123, "body": "..." }],
120
- "newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }],
121
- "requirementFindings": [{ "issueNumber": 47, "requirement": "Missing requirement.", "evidence": "Why it is missing.", "fix": "How to fix it." }]
114
+ "newFindings": [{ "path": "relative/path.ext", "line": 123, "startLine": 120, "body": "..." }]
122
115
  }
123
116
 
124
117
  Rules:
125
- - MERGE requires empty followUps, newFindings, and requirementFindings arrays.
126
- - CHANGES_REQUESTED requires at least one followUp, newFinding, or requirementFinding.
118
+ - MERGE requires empty followUps and newFindings arrays.
119
+ - CHANGES_REQUESTED requires at least one followUp or newFinding.
127
120
  - CLOSE is not allowed in this reconsideration step.
128
- - line is optional. Include line only when the newFinding targets a valid line inside the latest PR diff hunk.
129
- - startLine is allowed only when line is present.
130
- - Omit startLine for single-line findings and omit line for file-level or body-only findings.
121
+ - line is required and must target a valid right-side line inside the latest PR diff hunk.
122
+ - startLine is optional and must also target a valid right-side line inside the same latest PR diff hunk range.
123
+ - Do not omit line. Do not create file-level or body-only findings.
131
124
  </output_contract>`.trim();
132
125
  export const editOutputContract = `
133
126
  <output_contract>
@@ -224,8 +217,13 @@ Return exactly one JSON object and nothing else. Do not wrap it in markdown.
224
217
  The object must match this shape:
225
218
  {
226
219
  "vote": ${votes},
227
- "reason": "Short rationale."
220
+ "reason": "Short rationale.",
221
+ "body": "Required only when vote is ASK. Public issue comment body asking for the missing information."
228
222
  }
223
+
224
+ Rules:
225
+ - body is required when vote is ASK and must be written for the issue author.
226
+ - Omit body when vote is not ASK.
229
227
  </output_contract>`.trim();
230
228
  }
231
229
  export const triageDuplicateOutputContract = `
@@ -258,24 +256,6 @@ The object must match this shape:
258
256
  ]
259
257
  }
260
258
  </output_contract>`.trim();
261
- export const triageActionOutputContract = `
262
- <output_contract>
263
- Return exactly one JSON object and nothing else. Do not wrap it in markdown.
264
-
265
- The object must match this shape:
266
- {
267
- "action": "ASK" | "COMMENT" | "CLOSE" | "PR" | "CLEAR_ONLY",
268
- "reason": "Short rationale."
269
- }
270
-
271
- Rules:
272
- - Choose only an action listed as allowed in the task context.
273
- - ASK means post an author-mentioned question and do not close, create a PR, or clear labels.
274
- - COMMENT means post a decision comment only.
275
- - CLOSE means post a decision comment and close the issue.
276
- - PR means post a decision comment and create an implementation PR.
277
- - CLEAR_ONLY means clear labels without posting a comment.
278
- </output_contract>`.trim();
279
259
  const outputContractsBySchemaName = {
280
260
  "CI classification": ciClassificationOutputContract,
281
261
  "close reconsideration": closeReconsiderationOutputContract,
@@ -284,7 +264,6 @@ const outputContractsBySchemaName = {
284
264
  rereview: rereviewOutputContract,
285
265
  "rereview close reconsideration": rereviewCloseReconsiderationOutputContract,
286
266
  review: reviewOutputContract,
287
- "triage action": triageActionOutputContract,
288
267
  "triage acceptance": triageVoteOutputContract('"YES" | "NO" | "ASK"'),
289
268
  "triage category": triageVoteOutputContract('"ASK" or one of the configured category IDs'),
290
269
  "triage create PR": triageCreatePrOutputContract,
@@ -71,27 +71,16 @@ function requireNumber(value, path) {
71
71
  throw new Error(`${path} must be an integer`);
72
72
  return value;
73
73
  }
74
- function optionalLine(value, path) {
75
- return value == null ? undefined : requireNumber(value, path);
74
+ function requireLine(value, path) {
75
+ if (value == null)
76
+ throw new Error(`${path} is required`);
77
+ return requireNumber(value, path);
76
78
  }
77
79
  function optionalStartLine(input) {
78
80
  if (input.value == null)
79
81
  return undefined;
80
- if (input.line == null)
81
- throw new Error(`${input.path} requires line`);
82
82
  return requireNumber(input.value, input.path);
83
83
  }
84
- function parseRequirementFindings(value) {
85
- return (value == null ? [] : requireArray(value, "requirementFindings")).map((finding, index) => {
86
- const item = finding;
87
- return {
88
- evidence: requireString(item.evidence, `requirementFindings[${index}].evidence`),
89
- fix: requireString(item.fix, `requirementFindings[${index}].fix`),
90
- issueNumber: requireNumber(item.issueNumber, `requirementFindings[${index}].issueNumber`),
91
- requirement: requireString(item.requirement, `requirementFindings[${index}].requirement`),
92
- };
93
- });
94
- }
95
84
  function requireOneOf(value, path, values) {
96
85
  const text = requireString(value, path);
97
86
  if (!values.includes(text)) {
@@ -103,9 +92,14 @@ function parseTriageVote(text, votes) {
103
92
  const data = extractJson(text);
104
93
  if (!data || typeof data !== "object")
105
94
  throw new Error("triage vote output must be an object");
95
+ const vote = requireOneOf(data.vote, "vote", votes);
96
+ const body = data.body == null ? undefined : requireString(data.body, "body");
97
+ if (vote === "ASK" && !body?.trim())
98
+ throw new Error("ASK requires body");
106
99
  return {
100
+ body,
107
101
  reason: requireString(data.reason, "reason"),
108
- vote: requireOneOf(data.vote, "vote", votes),
102
+ vote,
109
103
  };
110
104
  }
111
105
  export function parseTriageExistingPrOutput(text) {
@@ -160,30 +154,17 @@ export function parseTriageCommentClassificationOutput(text) {
160
154
  }),
161
155
  };
162
156
  }
163
- export function parseTriageActionOutput(text) {
164
- const data = extractJson(text);
165
- if (!data || typeof data !== "object")
166
- throw new Error("triage action output must be an object");
167
- return {
168
- action: requireOneOf(data.action, "action", [
169
- "ASK",
170
- "CLEAR_ONLY",
171
- "CLOSE",
172
- "COMMENT",
173
- "PR",
174
- ]),
175
- reason: requireString(data.reason, "reason"),
176
- };
177
- }
178
157
  export function parseReviewOutput(text) {
179
158
  const data = extractJson(text);
180
159
  if (!data || typeof data !== "object")
181
160
  throw new Error("review output must be an object");
161
+ if (data.requirementFindings != null)
162
+ throw new Error("requirementFindings is not accepted");
182
163
  if (!isVerdict(data.verdict))
183
164
  throw new Error("verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
184
165
  const findings = requireArray(data.findings, "findings").map((finding, index) => {
185
166
  const item = finding;
186
- const line = optionalLine(item.line, `findings[${index}].line`);
167
+ const line = requireLine(item.line, `findings[${index}].line`);
187
168
  return {
188
169
  fix: requireString(item.fix, `findings[${index}].fix`),
189
170
  issue: requireString(item.issue, `findings[${index}].issue`),
@@ -199,17 +180,12 @@ export function parseReviewOutput(text) {
199
180
  }),
200
181
  };
201
182
  });
202
- const requirementFindings = parseRequirementFindings(data.requirementFindings);
203
- if (data.verdict === "MERGE" &&
204
- (findings.length || requirementFindings.length))
205
- throw new Error("MERGE requires no findings or requirementFindings");
206
- if (data.verdict === "CHANGES_REQUESTED" &&
207
- !findings.length &&
208
- !requirementFindings.length)
209
- throw new Error("CHANGES_REQUESTED requires findings or requirementFindings");
210
- if (data.verdict === "CLOSE" &&
211
- (findings.length || requirementFindings.length))
212
- throw new Error("CLOSE requires no findings or requirementFindings");
183
+ if (data.verdict === "MERGE" && findings.length)
184
+ throw new Error("MERGE requires no findings");
185
+ if (data.verdict === "CHANGES_REQUESTED" && !findings.length)
186
+ throw new Error("CHANGES_REQUESTED requires findings");
187
+ if (data.verdict === "CLOSE" && findings.length)
188
+ throw new Error("CLOSE requires no findings");
213
189
  const reason = typeof data.reason === "string" && data.reason.trim()
214
190
  ? data.reason
215
191
  : undefined;
@@ -218,7 +194,6 @@ export function parseReviewOutput(text) {
218
194
  return {
219
195
  findings,
220
196
  reason,
221
- requirementFindings,
222
197
  verdict: data.verdict,
223
198
  };
224
199
  }
@@ -226,6 +201,8 @@ export function parseRereviewOutput(text) {
226
201
  const data = extractJson(text);
227
202
  if (!isVerdict(data.verdict))
228
203
  throw new Error("rereview verdict must be MERGE, CHANGES_REQUESTED, or CLOSE");
204
+ if (data.requirementFindings != null)
205
+ throw new Error("requirementFindings is not accepted");
229
206
  const resolve = requireArray(data.resolve, "resolve").map((item, index) => {
230
207
  const value = item;
231
208
  return {
@@ -242,7 +219,7 @@ export function parseRereviewOutput(text) {
242
219
  });
243
220
  const newFindings = requireArray(data.newFindings, "newFindings").map((item, index) => {
244
221
  const value = item;
245
- const line = optionalLine(value.line, `newFindings[${index}].line`);
222
+ const line = requireLine(value.line, `newFindings[${index}].line`);
246
223
  return {
247
224
  body: requireString(value.body, `newFindings[${index}].body`),
248
225
  line,
@@ -254,29 +231,24 @@ export function parseRereviewOutput(text) {
254
231
  }),
255
232
  };
256
233
  });
257
- const requirementFindings = parseRequirementFindings(data.requirementFindings);
258
- if (data.verdict === "MERGE" &&
259
- (followUps.length || newFindings.length || requirementFindings.length)) {
260
- throw new Error("MERGE requires no followUps, newFindings, or requirementFindings");
234
+ if (data.verdict === "MERGE" && (followUps.length || newFindings.length)) {
235
+ throw new Error("MERGE requires no followUps or newFindings");
261
236
  }
262
- if (data.verdict === "CLOSE" &&
263
- (followUps.length || newFindings.length || requirementFindings.length)) {
264
- throw new Error("CLOSE requires no followUps, newFindings, or requirementFindings");
237
+ if (data.verdict === "CLOSE" && (followUps.length || newFindings.length)) {
238
+ throw new Error("CLOSE requires no followUps or newFindings");
265
239
  }
266
240
  if (data.verdict === "CLOSE" && !data.reason) {
267
241
  throw new Error("CLOSE requires reason");
268
242
  }
269
243
  if (data.verdict === "CHANGES_REQUESTED" &&
270
244
  !followUps.length &&
271
- !newFindings.length &&
272
- !requirementFindings.length) {
273
- throw new Error("CHANGES_REQUESTED requires followUps, newFindings, or requirementFindings");
245
+ !newFindings.length) {
246
+ throw new Error("CHANGES_REQUESTED requires followUps or newFindings");
274
247
  }
275
248
  return {
276
249
  followUps,
277
250
  newFindings,
278
251
  reason: data.reason == null ? undefined : requireString(data.reason, "reason"),
279
- requirementFindings,
280
252
  resolve,
281
253
  verdict: data.verdict,
282
254
  };
@@ -2,7 +2,7 @@ Fix pull request #{pr} for {owner}/{repo}.
2
2
  The PR worktree is {worktreePath}.
3
3
 
4
4
  Act as the PR author and address every blocking review finding listed below.
5
- Review findings are the complete set of requested changes. Inline findings target a PR diff line; file-level findings may not have a GitHub thread; requirement findings describe missing closing-issue requirements.
5
+ Review findings are the complete set of requested changes. Each finding targets a PR diff line and should have a corresponding GitHub review thread unless it comes from legacy read-side state.
6
6
  {reviewFindings}
7
7
 
8
8
  Unresolved GitHub review threads are conversations that may need replies or resolution.
@@ -12,5 +12,4 @@ For each review finding and thread, decide whether you agree with the reviewer.
12
12
  If you understand and agree with the requested change, edit the code, stage changes, commit, and reply with action FIXED for each related thread.
13
13
  If a requested change in a thread is incorrect or unnecessary and you have a clear reason, do not edit for that thread; reply with action DISAGREE and explain why.
14
14
  If you cannot determine whether a threaded request is correct or what change is expected, do not blindly edit; reply with action ASK and ask a concrete question.
15
- File-level and requirement findings may not have a thread to reply to, but they are still blocking and must be addressed.
16
15
  Do not make changes just because a reviewer requested them. Do not push.
@@ -2,4 +2,5 @@ You requested CLOSE for pull request #{pr} in {owner}/{repo}, but the other revi
2
2
  Reconsider your decision using the existing session context and choose MERGE or CHANGES_REQUESTED instead.
3
3
  Original close reason:
4
4
  {closeReason}
5
+ Every finding must target a valid right-side line in the PR diff. If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior.
5
6
  Do not edit files or perform write operations.
@@ -9,6 +9,9 @@ If there is no new commit, still reconsider the thread when a user replied after
9
9
  If you agree with the user's explanation or the code is fixed, resolve the thread.
10
10
  If you do not agree, reply in the same thread with a followUp explaining why the issue still needs changes and keep CHANGES_REQUESTED.
11
11
  Do not duplicate an existing unresolved thread as a newFinding. Use newFindings only for separate new issues.
12
+ Every newFinding must target a valid right-side line in the PR diff.
13
+ If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
14
+ Do not omit line. Do not create file-level or body-only newFindings.
12
15
 
13
16
  {ciFailureContextBlock}
14
17
  Do not edit files or perform write operations.
@@ -10,4 +10,8 @@ Request changes if a closing issue requirement is missing, only documented, only
10
10
  Do not approve solely because the PR improves the codebase if it claims to close an issue that remains incomplete.
11
11
  For referenced non-closing issues, use them as context only unless the PR body explicitly claims to complete them.
12
12
 
13
+ Every finding must target a valid right-side line in the PR diff.
14
+ If the problem itself does not have an exact changed line, choose the nearest changed line that represents the cause, responsibility, missing implementation, or affected behavior. This includes but is not limited to missing validation, missing wiring, missing requirements, missing tests, missing documentation, affected configuration, or relevant call sites.
15
+ Do not omit line. Do not create file-level or body-only findings.
16
+
13
17
  {ciFailureContextBlock}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.4.0",
3
+ "version": "0.6.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>",
package/schema.json CHANGED
@@ -116,12 +116,13 @@
116
116
  "triageAgent": {
117
117
  "type": "object",
118
118
  "if": { "not": { "required": ["ref"] } },
119
- "then": { "required": ["model"] },
119
+ "then": { "required": ["model", "account"] },
120
120
  "additionalProperties": false,
121
121
  "properties": {
122
122
  "ref": { "type": "string", "minLength": 1 },
123
123
  "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
124
124
  "model": { "type": "string", "minLength": 1 },
125
+ "account": { "type": "string", "minLength": 1 },
125
126
  "options": { "type": "object", "additionalProperties": true },
126
127
  "permissions": { "$ref": "#/$defs/permissions" },
127
128
  "persona": { "type": "string" }
@@ -228,7 +229,6 @@
228
229
  "duplicate": { "type": "string" },
229
230
  "category": { "type": "string" },
230
231
  "acceptance": { "type": "string" },
231
- "action": { "type": "string" },
232
232
  "question": { "type": "string" },
233
233
  "comment": { "type": "string" },
234
234
  "commentClassification": { "type": "string" },
@@ -334,7 +334,6 @@
334
334
  "type": "object",
335
335
  "additionalProperties": false,
336
336
  "properties": {
337
- "account": { "type": "string", "minLength": 1 },
338
337
  "agents": {
339
338
  "type": "array",
340
339
  "minItems": 3,
@@ -349,6 +348,7 @@
349
348
  "safety": { "$ref": "#/$defs/triageSafety" },
350
349
  "concurrency": { "$ref": "#/$defs/triageConcurrency" },
351
350
  "prompts": { "$ref": "#/$defs/triagePrompts" },
351
+ "reporter": { "type": "string", "minLength": 1 },
352
352
  "output": { "type": "string" },
353
353
  "worktree": { "type": "string" }
354
354
  }
@@ -1,5 +0,0 @@
1
- Decide the next action for issue #{issue} in {owner}/{repo} from the provided triage result and allowed actions.
2
-
3
- <context>
4
- {context}
5
- </context>