opencode-magi 0.0.0-dev-20260520180659 → 0.0.0-dev-20260520183328

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.
@@ -784,7 +784,7 @@ async function createImplementationPr(input) {
784
784
  permission: creator.permission,
785
785
  prompt,
786
786
  repairAttempts: 3,
787
- schemaName: "edit",
787
+ schemaName: "triage create PR",
788
788
  signal: input.input.signal,
789
789
  title: `Magi triage create PR #${input.issue.number}`,
790
790
  });
@@ -795,15 +795,18 @@ async function createImplementationPr(input) {
795
795
  await writeJson(join(input.outputDir, "create-pr.json"), result.value);
796
796
  if (result.value.mode !== "EDITED")
797
797
  return undefined;
798
+ const pullRequest = result.value.pullRequest;
799
+ if (!pullRequest)
800
+ throw new Error("EDITED requires pullRequest");
798
801
  await pushHead(input.input.exec, input.input.repository, worktreePath, creator.account, {
799
802
  owner: input.input.repository.github.owner,
800
803
  ref: branch,
801
804
  repo: input.input.repository.github.repo,
802
805
  });
803
806
  return createPullRequest(input.input.exec, input.input.repository, creator.account, {
804
- body: `Closes #${input.issue.number}`,
807
+ body: pullRequest.body,
805
808
  head: branch,
806
- title: `fix: address issue #${input.issue.number}`,
809
+ title: pullRequest.title,
807
810
  });
808
811
  }
809
812
  finally {
@@ -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, triageCommentClassificationOutputContract, triageActionOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
4
+ import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageActionOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
5
5
  async function readOptionalPrompt(directory, path, values = {}) {
6
6
  if (!path)
7
7
  return "";
@@ -387,7 +387,7 @@ export async function composeTriageCreatePrPrompt(input) {
387
387
  task,
388
388
  languageBlock(input.repository.language),
389
389
  personaBlock(persona),
390
- editOutputContract,
390
+ triageCreatePrOutputContract,
391
391
  ]
392
392
  .filter(Boolean)
393
393
  .join("\n\n");
@@ -148,6 +148,32 @@ Rules:
148
148
  - responses must include a reply for each thread you addressed.
149
149
  - REPLIED requires filesTouched to be empty and at least one DISAGREE or ASK response.
150
150
  </output_contract>`.trim();
151
+ export const triageCreatePrOutputContract = `
152
+ <output_contract>
153
+ Return exactly one JSON object and nothing else. Do not wrap it in markdown.
154
+
155
+ The object must match this shape:
156
+ {
157
+ "mode": "EDITED" | "REPLIED",
158
+ "commitSha": "full sha, required only when mode is EDITED; omit when mode is REPLIED",
159
+ "commitMessage": "fix(scope): short description, required only when mode is EDITED; omit when mode is REPLIED",
160
+ "filesTouched": ["relative/path.ext"],
161
+ "pullRequest": {
162
+ "title": "PR title, required only when mode is EDITED; omit when mode is REPLIED",
163
+ "body": "PR body, required only when mode is EDITED; omit when mode is REPLIED"
164
+ },
165
+ "responses": [{ "commentId": 123, "action": "FIXED" | "DISAGREE" | "ASK", "body": "Fixed." }]
166
+ }
167
+
168
+ Rules:
169
+ - Use EDITED only when you edited files, staged changes, and committed.
170
+ - Use REPLIED when you only replied without code changes.
171
+ - For EDITED, pullRequest.title and pullRequest.body must be non-empty and follow the repository's PR conventions.
172
+ - Do not push or create the PR. The orchestrator pushes and creates the PR using pullRequest exactly as provided.
173
+ - filesTouched must include every final changed file.
174
+ - responses may be empty when no review threads were addressed.
175
+ - REPLIED requires filesTouched to be empty and at least one DISAGREE or ASK response.
176
+ </output_contract>`.trim();
151
177
  export const ciClassificationOutputContract = `
152
178
  <output_contract>
153
179
  Return exactly one JSON object and nothing else. Do not wrap it in markdown.
@@ -255,6 +281,7 @@ const outputContractsBySchemaName = {
255
281
  "triage action": triageActionOutputContract,
256
282
  "triage acceptance": triageVoteOutputContract('"YES" | "NO" | "ASK"'),
257
283
  "triage category": triageVoteOutputContract('"ASK" or one of the configured category IDs'),
284
+ "triage create PR": triageCreatePrOutputContract,
258
285
  "triage comment classification": triageCommentClassificationOutputContract,
259
286
  "triage duplicate": triageDuplicateOutputContract,
260
287
  "triage existing PR": triageVoteOutputContract('"RELATED_PR_HANDLES_ISSUE" | "RELATED_PR_DOES_NOT_HANDLE_ISSUE"'),
@@ -317,6 +317,20 @@ export function parseCiClassificationOutput(text) {
317
317
  }),
318
318
  };
319
319
  }
320
+ function parsePullRequest(value, options) {
321
+ if (value == null) {
322
+ if (options.required)
323
+ throw new Error("pullRequest is required");
324
+ return undefined;
325
+ }
326
+ if (typeof value !== "object")
327
+ throw new Error("pullRequest must be an object");
328
+ const pullRequest = value;
329
+ return {
330
+ body: requireString(pullRequest.body, "pullRequest.body"),
331
+ title: requireString(pullRequest.title, "pullRequest.title"),
332
+ };
333
+ }
320
334
  function parseEditOutputWithOptions(text, options) {
321
335
  const data = extractJson(text);
322
336
  if (!data || typeof data !== "object")
@@ -340,11 +354,15 @@ function parseEditOutputWithOptions(text, options) {
340
354
  if (data.mode === "EDITED") {
341
355
  if (!filesTouched.length)
342
356
  throw new Error("EDITED requires filesTouched");
357
+ const pullRequest = parsePullRequest(data.pullRequest, {
358
+ required: options.requirePullRequest,
359
+ });
343
360
  return {
344
361
  commitMessage: requireString(data.commitMessage, "commitMessage"),
345
362
  commitSha: requireString(data.commitSha, "commitSha"),
346
363
  filesTouched,
347
364
  mode: data.mode,
365
+ ...(pullRequest ? { pullRequest } : {}),
348
366
  responses,
349
367
  };
350
368
  }
@@ -364,8 +382,14 @@ function parseEditOutputWithOptions(text, options) {
364
382
  };
365
383
  }
366
384
  export function parseEditOutput(text) {
367
- return parseEditOutputWithOptions(text, { requireResponses: true });
385
+ return parseEditOutputWithOptions(text, {
386
+ requirePullRequest: false,
387
+ requireResponses: true,
388
+ });
368
389
  }
369
390
  export function parseTriageCreatePrOutput(text) {
370
- return parseEditOutputWithOptions(text, { requireResponses: false });
391
+ return parseEditOutputWithOptions(text, {
392
+ requirePullRequest: true,
393
+ requireResponses: false,
394
+ });
371
395
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260520180659",
3
+ "version": "0.0.0-dev-20260520183328",
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>",