opencode-ship 0.9.0 → 0.10.0-rc.18

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 (137) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +3 -1
  3. package/THIRD_PARTY_NOTICES.md +77 -19
  4. package/assets/agents/ship-controller.md +122 -0
  5. package/assets/agents/ship-final-spec-reviewer.md +87 -0
  6. package/assets/agents/ship-final-standards-reviewer.md +83 -0
  7. package/assets/agents/ship-planner.md +78 -0
  8. package/assets/agents/ship-task-builder.md +91 -0
  9. package/assets/agents/ship-task-reviewer.md +78 -0
  10. package/assets/commands/ship-deliver.md +30 -0
  11. package/assets/commands/ship-resume.md +28 -0
  12. package/assets/commands/ship-status.md +25 -0
  13. package/assets/skills/brainstorming/SKILL.md +160 -0
  14. package/assets/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  15. package/assets/skills/brainstorming/visual-companion.md +298 -0
  16. package/assets/skills/code-review/SKILL.md +98 -0
  17. package/assets/skills/codebase-design/DEEPENING.md +37 -0
  18. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  19. package/assets/skills/codebase-design/SKILL.md +123 -0
  20. package/assets/skills/dispatching-parallel-agents/SKILL.md +176 -0
  21. package/assets/skills/domain-modeling/ADR-FORMAT.md +47 -0
  22. package/assets/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  23. package/assets/skills/domain-modeling/SKILL.md +83 -0
  24. package/assets/skills/engineering-workflow/SKILL.md +125 -0
  25. package/assets/skills/engineering-workflow/domain.md +51 -0
  26. package/assets/skills/engineering-workflow/issue-tracker-github.md +45 -0
  27. package/assets/skills/engineering-workflow/issue-tracker-gitlab.md +46 -0
  28. package/assets/skills/engineering-workflow/issue-tracker-local.md +30 -0
  29. package/assets/skills/engineering-workflow/triage-labels.md +15 -0
  30. package/assets/skills/executing-plans/SKILL.md +73 -0
  31. package/assets/skills/grill-with-docs/SKILL.md +10 -14
  32. package/assets/skills/grilling/SKILL.md +21 -0
  33. package/assets/skills/handoff/SKILL.md +25 -0
  34. package/assets/skills/prototype/LOGIC.md +79 -0
  35. package/assets/skills/prototype/SKILL.md +35 -0
  36. package/assets/skills/prototype/UI.md +112 -0
  37. package/assets/skills/receiving-code-review/SKILL.md +214 -0
  38. package/assets/skills/requesting-code-review/SKILL.md +104 -0
  39. package/assets/skills/requesting-code-review/code-reviewer.md +172 -0
  40. package/assets/skills/research/SKILL.md +21 -0
  41. package/assets/skills/setup-engineering-workflow/SKILL.md +125 -0
  42. package/assets/skills/setup-engineering-workflow/domain.md +51 -0
  43. package/assets/skills/setup-engineering-workflow/issue-tracker-github.md +45 -0
  44. package/assets/skills/setup-engineering-workflow/issue-tracker-gitlab.md +46 -0
  45. package/assets/skills/setup-engineering-workflow/issue-tracker-local.md +30 -0
  46. package/assets/skills/setup-engineering-workflow/triage-labels.md +15 -0
  47. package/assets/skills/subagent-driven-development/SKILL.md +512 -0
  48. package/assets/skills/subagent-driven-development/implementer-prompt.md +142 -0
  49. package/assets/skills/subagent-driven-development/re-review-prompt.md +106 -0
  50. package/assets/skills/subagent-driven-development/task-reviewer-prompt.md +185 -0
  51. package/assets/skills/systematic-debugging/CREATION-LOG.md +119 -0
  52. package/assets/skills/systematic-debugging/SKILL.md +292 -0
  53. package/assets/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  54. package/assets/skills/systematic-debugging/condition-based-waiting.md +115 -0
  55. package/assets/skills/systematic-debugging/defense-in-depth.md +122 -0
  56. package/assets/skills/systematic-debugging/find-polluter.sh +72 -0
  57. package/assets/skills/systematic-debugging/root-cause-tracing.md +169 -0
  58. package/assets/skills/systematic-debugging/test-academic.md +14 -0
  59. package/assets/skills/systematic-debugging/test-pressure-1.md +58 -0
  60. package/assets/skills/systematic-debugging/test-pressure-2.md +68 -0
  61. package/assets/skills/systematic-debugging/test-pressure-3.md +69 -0
  62. package/assets/skills/test-driven-development/SKILL.md +329 -0
  63. package/assets/skills/test-driven-development/writing-good-tests.md +198 -0
  64. package/assets/skills/to-spec/SKILL.md +84 -0
  65. package/assets/skills/to-tickets/SKILL.md +114 -0
  66. package/assets/skills/triage/AGENT-BRIEF.md +207 -0
  67. package/assets/skills/triage/OUT-OF-SCOPE.md +105 -0
  68. package/assets/skills/triage/SKILL.md +114 -15
  69. package/assets/skills/verification-before-completion/SKILL.md +129 -0
  70. package/assets/skills/wayfinder/SKILL.md +137 -0
  71. package/assets/skills/writing-plans/SKILL.md +177 -0
  72. package/assets/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  73. package/dist/cli.js +1146 -435
  74. package/dist/core.js +210 -61
  75. package/dist/plugin.js +2368 -230
  76. package/package.json +3 -1
  77. package/schema/ship-config.schema.json +63 -2
  78. package/schema/ship-lock.schema.json +5 -3
  79. package/tests/plugin/plugin-load.test.mjs +85 -0
  80. package/vendor/mattpocock/LICENSE +30 -0
  81. package/vendor/obra/LICENSE +30 -0
  82. package/vendor/sources.json +976 -0
  83. package/vendor/superpowers/LICENSE +30 -0
  84. package/vendor/upstreams/mattpocock/skills/engineering/code-review/SKILL.md +89 -0
  85. package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/DEEPENING.md +37 -0
  86. package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/DESIGN-IT-TWICE.md +44 -0
  87. package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/SKILL.md +114 -0
  88. package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/ADR-FORMAT.md +47 -0
  89. package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/CONTEXT-FORMAT.md +60 -0
  90. package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/SKILL.md +74 -0
  91. package/vendor/upstreams/mattpocock/skills/engineering/grill-with-docs/SKILL.md +7 -0
  92. package/vendor/upstreams/mattpocock/skills/engineering/prototype/LOGIC.md +79 -0
  93. package/vendor/upstreams/mattpocock/skills/engineering/prototype/SKILL.md +26 -0
  94. package/vendor/upstreams/mattpocock/skills/engineering/prototype/UI.md +112 -0
  95. package/vendor/upstreams/mattpocock/skills/engineering/research/SKILL.md +12 -0
  96. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/SKILL.md +116 -0
  97. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/domain.md +51 -0
  98. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-github.md +45 -0
  99. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-gitlab.md +46 -0
  100. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-local.md +30 -0
  101. package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/triage-labels.md +15 -0
  102. package/vendor/upstreams/mattpocock/skills/engineering/to-spec/SKILL.md +75 -0
  103. package/vendor/upstreams/mattpocock/skills/engineering/to-tickets/SKILL.md +105 -0
  104. package/vendor/upstreams/mattpocock/skills/engineering/triage/AGENT-BRIEF.md +207 -0
  105. package/vendor/upstreams/mattpocock/skills/engineering/triage/OUT-OF-SCOPE.md +105 -0
  106. package/vendor/upstreams/mattpocock/skills/engineering/triage/SKILL.md +112 -0
  107. package/vendor/upstreams/mattpocock/skills/engineering/wayfinder/SKILL.md +128 -0
  108. package/vendor/upstreams/mattpocock/skills/productivity/grilling/SKILL.md +12 -0
  109. package/vendor/upstreams/mattpocock/skills/productivity/handoff/SKILL.md +16 -0
  110. package/vendor/upstreams/obra/skills/brainstorming/SKILL.md +151 -0
  111. package/vendor/upstreams/obra/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  112. package/vendor/upstreams/obra/skills/brainstorming/visual-companion.md +298 -0
  113. package/vendor/upstreams/obra/skills/dispatching-parallel-agents/SKILL.md +167 -0
  114. package/vendor/upstreams/obra/skills/executing-plans/SKILL.md +64 -0
  115. package/vendor/upstreams/obra/skills/receiving-code-review/SKILL.md +205 -0
  116. package/vendor/upstreams/obra/skills/requesting-code-review/SKILL.md +95 -0
  117. package/vendor/upstreams/obra/skills/requesting-code-review/code-reviewer.md +172 -0
  118. package/vendor/upstreams/obra/skills/subagent-driven-development/SKILL.md +503 -0
  119. package/vendor/upstreams/obra/skills/subagent-driven-development/implementer-prompt.md +142 -0
  120. package/vendor/upstreams/obra/skills/subagent-driven-development/re-review-prompt.md +106 -0
  121. package/vendor/upstreams/obra/skills/subagent-driven-development/task-reviewer-prompt.md +185 -0
  122. package/vendor/upstreams/obra/skills/systematic-debugging/CREATION-LOG.md +119 -0
  123. package/vendor/upstreams/obra/skills/systematic-debugging/SKILL.md +283 -0
  124. package/vendor/upstreams/obra/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  125. package/vendor/upstreams/obra/skills/systematic-debugging/condition-based-waiting.md +115 -0
  126. package/vendor/upstreams/obra/skills/systematic-debugging/defense-in-depth.md +122 -0
  127. package/vendor/upstreams/obra/skills/systematic-debugging/find-polluter.sh +72 -0
  128. package/vendor/upstreams/obra/skills/systematic-debugging/root-cause-tracing.md +169 -0
  129. package/vendor/upstreams/obra/skills/systematic-debugging/test-academic.md +14 -0
  130. package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-1.md +58 -0
  131. package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-2.md +68 -0
  132. package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-3.md +69 -0
  133. package/vendor/upstreams/obra/skills/test-driven-development/SKILL.md +320 -0
  134. package/vendor/upstreams/obra/skills/test-driven-development/writing-good-tests.md +198 -0
  135. package/vendor/upstreams/obra/skills/verification-before-completion/SKILL.md +120 -0
  136. package/vendor/upstreams/obra/skills/writing-plans/SKILL.md +168 -0
  137. package/vendor/upstreams/obra/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship/core v0.9.0
1
+ // opencode-ship/core v0.10.0-rc.18
2
2
 
3
3
  // src/adapter.js
4
4
  import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
@@ -325,87 +325,169 @@ function mustRerunVerifier(previousSha, currentSha) {
325
325
  }
326
326
 
327
327
  // src/state/manifest-store.js
328
- import { readFile as readFile2, writeFile as writeFile2, rename as rename2, mkdir as mkdir2, readdir, unlink } from "node:fs/promises";
329
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
328
+ import { readFile as readFile3, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
329
+ import { join as join4, resolve as resolve4 } from "node:path";
330
+
331
+ // src/state/git-common-dir.js
330
332
  import { spawn } from "node:child_process";
331
- async function runGitCommonDir(repoRoot) {
332
- return new Promise((res, rej) => {
333
+ import { resolve as resolve2, join as join2 } from "node:path";
334
+ async function resolveGitCommonDir(repoRoot) {
335
+ if (typeof repoRoot !== "string" || repoRoot.length === 0) {
336
+ throw new Error("resolveGitCommonDir: repoRoot must be a non-empty string");
337
+ }
338
+ return new Promise((resolveP, reject) => {
333
339
  const proc = spawn(
334
340
  "git",
335
- ["rev-parse", "--path-format=absolute", "--git-common-dir"],
336
- { cwd: repoRoot, stdio: ["ignore", "pipe", "pipe"], shell: false }
341
+ ["-C", repoRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"],
342
+ { stdio: ["ignore", "pipe", "pipe"], shell: false }
337
343
  );
338
344
  let out = "";
339
- proc.stdout.on("data", (d) => out += d.toString());
340
- proc.on("error", rej);
345
+ let err = "";
346
+ proc.stdout.on("data", (d) => {
347
+ out += d.toString("utf8");
348
+ });
349
+ proc.stderr.on("data", (d) => {
350
+ err += d.toString("utf8");
351
+ });
352
+ proc.on("error", reject);
341
353
  proc.on("close", (code) => {
342
354
  if (code !== 0) {
343
- rej(new Error(`git rev-parse --git-common-dir failed with ${code}`));
355
+ reject(new Error(
356
+ `git rev-parse --git-common-dir failed (exit ${code}): ${err.trim() || "unknown error"}`
357
+ ));
344
358
  return;
345
359
  }
346
360
  const trimmed = out.trim();
347
361
  if (!trimmed) {
348
- rej(new Error("git rev-parse returned an empty path"));
362
+ reject(new Error("git rev-parse --git-common-dir returned an empty path"));
349
363
  return;
350
364
  }
351
- res(resolve2(repoRoot, trimmed));
365
+ resolveP(resolve2(repoRoot, trimmed));
352
366
  });
353
367
  });
354
368
  }
355
- function manifestPath(commonDir, taskId) {
356
- return join2(commonDir, "opencode-delivery", "manifests", `${taskId}.json`);
369
+
370
+ // src/state/durable-store.js
371
+ import {
372
+ open as fsOpen,
373
+ writeFile as writeFile2,
374
+ readFile as readFile2,
375
+ rename as rename2,
376
+ link,
377
+ mkdir as mkdir2,
378
+ readdir,
379
+ unlink,
380
+ stat
381
+ } from "node:fs/promises";
382
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
383
+ import { createHash as createHash2, randomBytes } from "node:crypto";
384
+ var STALE_LOCK_MS = 120 * 1e3;
385
+ function randomToken() {
386
+ return randomBytes(8).toString("hex");
387
+ }
388
+ function ensureString(value) {
389
+ return JSON.stringify(value, null, 2) + "\n";
390
+ }
391
+ async function fsyncDir(path) {
392
+ try {
393
+ const handle = await fsOpen(path, "r");
394
+ try {
395
+ await handle.sync();
396
+ } finally {
397
+ await handle.close();
398
+ }
399
+ } catch {
400
+ }
357
401
  }
358
- async function writeManifest(repoRoot, manifest) {
359
- const commonDir = await runGitCommonDir(repoRoot);
360
- const path = manifestPath(commonDir, manifest.taskId);
361
- await mkdir2(dirname2(path), { recursive: true });
362
- const tmp = `${path}.tmp`;
363
- await writeFile2(tmp, JSON.stringify(manifest, null, 2) + "\n", "utf8");
364
- await rename2(tmp, path);
365
- return resolve2(path);
402
+ async function atomicReplaceJson(path, value) {
403
+ if (typeof path !== "string" || path.length === 0) {
404
+ throw new Error("atomicReplaceJson: path must be a non-empty string");
405
+ }
406
+ const target = resolve3(path);
407
+ const parent = dirname2(target);
408
+ await mkdir2(parent, { recursive: true });
409
+ const tmp = `${target}.${randomToken()}.tmp`;
410
+ const handle = await fsOpen(tmp, "w", 384);
411
+ try {
412
+ await handle.writeFile(ensureString(value), "utf8");
413
+ await handle.sync();
414
+ } finally {
415
+ await handle.close();
416
+ }
417
+ await rename2(tmp, target);
418
+ await fsyncDir(parent);
366
419
  }
367
- async function readManifest(repoRoot, taskId) {
368
- const commonDir = await runGitCommonDir(repoRoot);
369
- const path = manifestPath(commonDir, taskId);
420
+
421
+ // src/state/manifest-store.js
422
+ var SHIP_DIRNAME = "opencode-ship";
423
+ var LEGACY_DIRNAME = "opencode-delivery";
424
+ function canonicalManifestPath(commonDir, taskId) {
425
+ return join4(commonDir, SHIP_DIRNAME, "delivery", "manifests", `${taskId}.json`);
426
+ }
427
+ function legacyManifestPath(commonDir, taskId) {
428
+ return join4(commonDir, LEGACY_DIRNAME, "manifests", `${taskId}.json`);
429
+ }
430
+ async function commonDirFromRepoRoot(repoRoot) {
431
+ return resolveGitCommonDir(repoRoot);
432
+ }
433
+ async function readJsonOrNull(path) {
370
434
  try {
371
- const raw = await readFile2(path, "utf8");
435
+ const raw = await readFile3(path, "utf8");
372
436
  return JSON.parse(raw);
373
437
  } catch {
374
438
  return null;
375
439
  }
376
440
  }
441
+ async function writeManifest(repoRoot, manifest) {
442
+ const commonDir = await commonDirFromRepoRoot(repoRoot);
443
+ const path = canonicalManifestPath(commonDir, manifest.taskId);
444
+ await atomicReplaceJson(path, manifest);
445
+ return resolve4(path);
446
+ }
447
+ async function readManifest(repoRoot, taskId) {
448
+ const commonDir = await commonDirFromRepoRoot(repoRoot);
449
+ const canonical = await readJsonOrNull(canonicalManifestPath(commonDir, taskId));
450
+ if (canonical !== null) return canonical;
451
+ const legacy = await readJsonOrNull(legacyManifestPath(commonDir, taskId));
452
+ if (legacy !== null) return legacy;
453
+ return null;
454
+ }
377
455
  async function listManifests(repoRoot) {
378
- const commonDir = await runGitCommonDir(repoRoot);
379
- const dir = join2(commonDir, "opencode-delivery", "manifests");
380
- let names;
381
- try {
382
- names = await readdir(dir);
383
- } catch {
384
- return [];
385
- }
456
+ const commonDir = await commonDirFromRepoRoot(repoRoot);
457
+ const canonicalDir = join4(commonDir, SHIP_DIRNAME, "delivery", "manifests");
458
+ const legacyDir = join4(commonDir, LEGACY_DIRNAME, "manifests");
386
459
  const out = [];
387
- for (const name of names) {
388
- if (!name.endsWith(".json")) continue;
460
+ const seen = /* @__PURE__ */ new Set();
461
+ for (const dir of [canonicalDir, legacyDir]) {
462
+ let names;
389
463
  try {
390
- const raw = await readFile2(join2(dir, name), "utf8");
391
- out.push(JSON.parse(raw));
464
+ names = await readdir2(dir);
392
465
  } catch {
466
+ continue;
467
+ }
468
+ for (const name of names) {
469
+ if (!name.endsWith(".json")) continue;
470
+ if (seen.has(name)) continue;
471
+ const parsed = await readJsonOrNull(join4(dir, name));
472
+ if (parsed !== null) {
473
+ seen.add(name);
474
+ out.push(parsed);
475
+ }
393
476
  }
394
477
  }
395
478
  return out;
396
479
  }
397
480
  async function deleteManifest(repoRoot, taskId) {
398
- const commonDir = await runGitCommonDir(repoRoot);
399
- const path = manifestPath(commonDir, taskId);
400
- try {
401
- await unlink(path);
402
- } catch {
403
- }
481
+ const commonDir = await commonDirFromRepoRoot(repoRoot);
482
+ const canonical = canonicalManifestPath(commonDir, taskId);
483
+ const legacy = legacyManifestPath(commonDir, taskId);
484
+ await unlink2(canonical).catch(() => null);
485
+ await unlink2(legacy).catch(() => null);
404
486
  }
405
487
 
406
488
  // src/drivers/git.js
407
489
  import { spawnSync } from "node:child_process";
408
- import { resolve as resolve3 } from "node:path";
490
+ import { resolve as resolve5 } from "node:path";
409
491
  import { existsSync as existsSync2 } from "node:fs";
410
492
  var WorktreeRecord = {
411
493
  path: "",
@@ -460,8 +542,8 @@ function isWorktreeClean(cwd) {
460
542
  function isRebaseInProgress(cwd) {
461
543
  const merge = runGit(["rev-parse", "--git-path", "rebase-merge"], cwd);
462
544
  const apply = runGit(["rev-parse", "--git-path", "rebase-apply"], cwd);
463
- const mergeExists = merge.status === 0 && safeExists(resolve3(cwd, merge.stdout.trim()));
464
- const applyExists = apply.status === 0 && safeExists(resolve3(cwd, apply.stdout.trim()));
545
+ const mergeExists = merge.status === 0 && safeExists(resolve5(cwd, merge.stdout.trim()));
546
+ const applyExists = apply.status === 0 && safeExists(resolve5(cwd, apply.stdout.trim()));
465
547
  return mergeExists || applyExists;
466
548
  }
467
549
  function safeExists(p) {
@@ -551,9 +633,62 @@ function parseRepoSlug(slug) {
551
633
  return { owner: slug.slice(0, slash), name: slug.slice(slash + 1) };
552
634
  }
553
635
 
636
+ // src/drivers/github-command-policy.js
637
+ var ALLOWED_VERBS = /* @__PURE__ */ new Set([
638
+ "issue list",
639
+ "issue view",
640
+ "issue create",
641
+ "issue comment",
642
+ "issue edit",
643
+ "issue close",
644
+ "pr list",
645
+ "pr view",
646
+ "pr create",
647
+ "pr edit",
648
+ "pr checks",
649
+ "pr ready",
650
+ "pr merge"
651
+ ]);
652
+ var FORBIDDEN_FLAGS = /* @__PURE__ */ new Set([
653
+ "--web",
654
+ "--body-file",
655
+ "--template"
656
+ ]);
657
+ function validateGhArgv(argv) {
658
+ if (!Array.isArray(argv) || argv.length < 2) {
659
+ return { ok: false, reason: "argv must be a non-empty array starting with the binary" };
660
+ }
661
+ const [bin, ...rest] = argv;
662
+ if (bin !== "gh") {
663
+ return { ok: false, reason: `expected 'gh' binary, got ${JSON.stringify(bin)}` };
664
+ }
665
+ if (rest.length === 0) {
666
+ return { ok: false, reason: "no gh subcommand" };
667
+ }
668
+ const verb = rest.slice(0, 2).join(" ");
669
+ if (verb.includes("api")) {
670
+ return { ok: false, reason: "gh api is not allowed (use typed Ship tools instead)" };
671
+ }
672
+ if (!ALLOWED_VERBS.has(verb)) {
673
+ return { ok: false, reason: `gh subcommand not in the allowlist: ${verb}` };
674
+ }
675
+ for (const arg of rest.slice(2)) {
676
+ if (FORBIDDEN_FLAGS.has(arg)) {
677
+ return { ok: false, reason: `gh flag ${arg} is forbidden (use Ship's typed body argument instead)` };
678
+ }
679
+ if (typeof arg !== "string" || arg.length === 0) {
680
+ return { ok: false, reason: "gh argv must contain only non-empty strings" };
681
+ }
682
+ }
683
+ return { ok: true, verb: (
684
+ /** @type {any} */
685
+ verb
686
+ ) };
687
+ }
688
+
554
689
  // src/drivers/gh-cli.js
555
690
  function defaultRunner(cwd, env) {
556
- return (args) => new Promise((resolve7, reject) => {
691
+ return (args) => new Promise((resolve9, reject) => {
557
692
  const proc = spawn2("gh", args, {
558
693
  cwd,
559
694
  env,
@@ -565,7 +700,7 @@ function defaultRunner(cwd, env) {
565
700
  proc.stdout.on("data", (d) => stdout += d.toString());
566
701
  proc.stderr.on("data", (d) => stderr += d.toString());
567
702
  proc.on("error", reject);
568
- proc.on("close", (status) => resolve7({ status: status ?? -1, stdout, stderr }));
703
+ proc.on("close", (status) => resolve9({ status: status ?? -1, stdout, stderr }));
569
704
  });
570
705
  }
571
706
  function viewFields() {
@@ -801,6 +936,20 @@ Closes #${issueNumber}`;
801
936
  "headRefOid"
802
937
  ]);
803
938
  return fields.headRefOid;
939
+ },
940
+ async runCommand(argv) {
941
+ if (!Array.isArray(argv) || argv.length === 0) {
942
+ throw new Error("runCommand: argv must be a non-empty array");
943
+ }
944
+ if (typeof argv[0] !== "string" || argv[0].length === 0) {
945
+ throw new Error("runCommand: argv[0] must be a non-empty string");
946
+ }
947
+ const policy = validateGhArgv(argv);
948
+ if (!policy.ok) {
949
+ throw new Error(`runCommand: rejected by policy: ${policy.reason}`);
950
+ }
951
+ const r = await run(argv);
952
+ return r;
804
953
  }
805
954
  };
806
955
  }
@@ -864,7 +1013,7 @@ function recoverManifestAfterCrash(manifest) {
864
1013
 
865
1014
  // src/doctor.js
866
1015
  import { spawnSync as spawnSync2 } from "node:child_process";
867
- import { resolve as resolve4 } from "node:path";
1016
+ import { resolve as resolve6 } from "node:path";
868
1017
  function runVersion(argv) {
869
1018
  const r = spawnSync2(argv[0], argv.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
870
1019
  if (r.status !== 0) return null;
@@ -912,7 +1061,7 @@ async function doctor(repoRoot, packageVersion) {
912
1061
  contractVersion: 1,
913
1062
  adapterPath: adapter.ok ? adapter.path : null,
914
1063
  adapterSha256: adapter.ok ? adapter.sha256 : null,
915
- lockPath: lock ? resolve4(repoRoot, ".opencode", "delivery.lock.json") : null,
1064
+ lockPath: lock ? resolve6(repoRoot, ".opencode", "delivery.lock.json") : null,
916
1065
  lockSha256: lock ? lock.adapterSha256 : null,
917
1066
  packageVersion,
918
1067
  nodeVersion: process.version,
@@ -1098,7 +1247,7 @@ function createIssueTool(deps) {
1098
1247
  }
1099
1248
 
1100
1249
  // src/tools/delivery-worktree.js
1101
- import { resolve as resolve5 } from "node:path";
1250
+ import { resolve as resolve7 } from "node:path";
1102
1251
  import { spawn as spawn3 } from "node:child_process";
1103
1252
  function runBootstrap(args, cwd) {
1104
1253
  return new Promise((resolveP, rejectP) => {
@@ -1121,8 +1270,8 @@ function runBootstrap(args, cwd) {
1121
1270
  });
1122
1271
  }
1123
1272
  function isPathContained(repoRoot, worktreeRoot, candidatePath) {
1124
- const rootAbs = resolve5(repoRoot, worktreeRoot);
1125
- const normalized = resolve5(candidatePath);
1273
+ const rootAbs = resolve7(repoRoot, worktreeRoot);
1274
+ const normalized = resolve7(candidatePath);
1126
1275
  if (normalized !== rootAbs && !normalized.startsWith(rootAbs + "/")) {
1127
1276
  return false;
1128
1277
  }
@@ -1158,12 +1307,12 @@ function createWorktreeTool(deps) {
1158
1307
  return { kind: "missing-input", field: "worktreeRelativePath" };
1159
1308
  }
1160
1309
  const worktreeRoot = deps.adapter?.worktree?.root ?? ".worktrees";
1161
- const worktreePath = resolve5(deps.repoRoot, input.worktreeRelativePath);
1310
+ const worktreePath = resolve7(deps.repoRoot, input.worktreeRelativePath);
1162
1311
  if (!isPathContained(deps.repoRoot, worktreeRoot, worktreePath)) {
1163
1312
  return {
1164
1313
  kind: "path-escape",
1165
1314
  resolvedPath: worktreePath,
1166
- expectedRoot: resolve5(deps.repoRoot, worktreeRoot)
1315
+ expectedRoot: resolve7(deps.repoRoot, worktreeRoot)
1167
1316
  };
1168
1317
  }
1169
1318
  const remote = deps.remote ?? "origin";
@@ -1334,7 +1483,7 @@ function createVerifyTool(deps) {
1334
1483
  ],
1335
1484
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1336
1485
  };
1337
- const manifestPath2 = await writeManifest(deps.repoRoot, next);
1486
+ const manifestPath = await writeManifest(deps.repoRoot, next);
1338
1487
  return {
1339
1488
  contractVersion: 1,
1340
1489
  commandId: cmd.id,
@@ -1342,7 +1491,7 @@ function createVerifyTool(deps) {
1342
1491
  stdoutTail,
1343
1492
  stderrTail,
1344
1493
  headSha: head,
1345
- manifestPath: manifestPath2
1494
+ manifestPath
1346
1495
  };
1347
1496
  };
1348
1497
  }
@@ -1625,7 +1774,7 @@ function createMergeTool(deps) {
1625
1774
  }
1626
1775
 
1627
1776
  // src/tools/delivery-cleanup.js
1628
- import { resolve as resolve6 } from "node:path";
1777
+ import { resolve as resolve8 } from "node:path";
1629
1778
  import { spawnSync as spawnSync3 } from "node:child_process";
1630
1779
  function safeRemoveWorktree(repoRoot, path) {
1631
1780
  const r = spawnSync3("git", ["worktree", "remove", path], {
@@ -1686,8 +1835,8 @@ function createCleanupTool(deps) {
1686
1835
  return { kind: "manifest-state", state: m.state };
1687
1836
  }
1688
1837
  if (!m.worktreePath) return { kind: "missing-worktree-path" };
1689
- const wtPath = resolve6(m.worktreePath);
1690
- const mainCwd = resolve6(deps.repoRoot);
1838
+ const wtPath = resolve8(m.worktreePath);
1839
+ const mainCwd = resolve8(deps.repoRoot);
1691
1840
  if (wtPath === mainCwd) return { kind: "current-checkout", worktreePath: wtPath };
1692
1841
  if (!isWorktreeClean(wtPath)) return { kind: "dirty-worktree" };
1693
1842
  if (isRebaseInProgress(wtPath)) return { kind: "rebase-in-progress" };