opencode-ship 0.9.0 → 0.10.0-rc.17
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.
- package/CHANGELOG.md +96 -0
- package/README.md +3 -1
- package/THIRD_PARTY_NOTICES.md +77 -19
- package/assets/agents/ship-controller.md +122 -0
- package/assets/agents/ship-final-spec-reviewer.md +87 -0
- package/assets/agents/ship-final-standards-reviewer.md +83 -0
- package/assets/agents/ship-planner.md +78 -0
- package/assets/agents/ship-task-builder.md +91 -0
- package/assets/agents/ship-task-reviewer.md +78 -0
- package/assets/commands/ship-deliver.md +30 -0
- package/assets/commands/ship-resume.md +28 -0
- package/assets/commands/ship-status.md +25 -0
- package/assets/skills/brainstorming/SKILL.md +160 -0
- package/assets/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/assets/skills/brainstorming/visual-companion.md +298 -0
- package/assets/skills/code-review/SKILL.md +98 -0
- package/assets/skills/codebase-design/DEEPENING.md +37 -0
- package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/assets/skills/codebase-design/SKILL.md +123 -0
- package/assets/skills/dispatching-parallel-agents/SKILL.md +176 -0
- package/assets/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/assets/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/assets/skills/domain-modeling/SKILL.md +83 -0
- package/assets/skills/engineering-workflow/SKILL.md +125 -0
- package/assets/skills/engineering-workflow/domain.md +51 -0
- package/assets/skills/engineering-workflow/issue-tracker-github.md +45 -0
- package/assets/skills/engineering-workflow/issue-tracker-gitlab.md +46 -0
- package/assets/skills/engineering-workflow/issue-tracker-local.md +30 -0
- package/assets/skills/engineering-workflow/triage-labels.md +15 -0
- package/assets/skills/executing-plans/SKILL.md +73 -0
- package/assets/skills/grill-with-docs/SKILL.md +10 -14
- package/assets/skills/grilling/SKILL.md +21 -0
- package/assets/skills/handoff/SKILL.md +25 -0
- package/assets/skills/prototype/LOGIC.md +79 -0
- package/assets/skills/prototype/SKILL.md +35 -0
- package/assets/skills/prototype/UI.md +112 -0
- package/assets/skills/receiving-code-review/SKILL.md +214 -0
- package/assets/skills/requesting-code-review/SKILL.md +104 -0
- package/assets/skills/requesting-code-review/code-reviewer.md +172 -0
- package/assets/skills/research/SKILL.md +21 -0
- package/assets/skills/setup-engineering-workflow/SKILL.md +125 -0
- package/assets/skills/setup-engineering-workflow/domain.md +51 -0
- package/assets/skills/setup-engineering-workflow/issue-tracker-github.md +45 -0
- package/assets/skills/setup-engineering-workflow/issue-tracker-gitlab.md +46 -0
- package/assets/skills/setup-engineering-workflow/issue-tracker-local.md +30 -0
- package/assets/skills/setup-engineering-workflow/triage-labels.md +15 -0
- package/assets/skills/subagent-driven-development/SKILL.md +512 -0
- package/assets/skills/subagent-driven-development/implementer-prompt.md +142 -0
- package/assets/skills/subagent-driven-development/re-review-prompt.md +106 -0
- package/assets/skills/subagent-driven-development/task-reviewer-prompt.md +185 -0
- package/assets/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/assets/skills/systematic-debugging/SKILL.md +292 -0
- package/assets/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/assets/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/assets/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/assets/skills/systematic-debugging/find-polluter.sh +72 -0
- package/assets/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/assets/skills/systematic-debugging/test-academic.md +14 -0
- package/assets/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/assets/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/assets/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/assets/skills/test-driven-development/SKILL.md +329 -0
- package/assets/skills/test-driven-development/writing-good-tests.md +198 -0
- package/assets/skills/to-spec/SKILL.md +84 -0
- package/assets/skills/to-tickets/SKILL.md +114 -0
- package/assets/skills/triage/AGENT-BRIEF.md +207 -0
- package/assets/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/assets/skills/triage/SKILL.md +114 -15
- package/assets/skills/verification-before-completion/SKILL.md +129 -0
- package/assets/skills/wayfinder/SKILL.md +137 -0
- package/assets/skills/writing-plans/SKILL.md +177 -0
- package/assets/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/dist/cli.js +1146 -435
- package/dist/core.js +210 -61
- package/dist/plugin.js +2368 -230
- package/package.json +3 -1
- package/schema/ship-config.schema.json +63 -2
- package/schema/ship-lock.schema.json +5 -3
- package/tests/plugin/plugin-load.test.mjs +85 -0
- package/vendor/mattpocock/LICENSE +30 -0
- package/vendor/obra/LICENSE +30 -0
- package/vendor/sources.json +976 -0
- package/vendor/superpowers/LICENSE +30 -0
- package/vendor/upstreams/mattpocock/skills/engineering/code-review/SKILL.md +89 -0
- package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/DEEPENING.md +37 -0
- package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/vendor/upstreams/mattpocock/skills/engineering/codebase-design/SKILL.md +114 -0
- package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/ADR-FORMAT.md +47 -0
- package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/vendor/upstreams/mattpocock/skills/engineering/domain-modeling/SKILL.md +74 -0
- package/vendor/upstreams/mattpocock/skills/engineering/grill-with-docs/SKILL.md +7 -0
- package/vendor/upstreams/mattpocock/skills/engineering/prototype/LOGIC.md +79 -0
- package/vendor/upstreams/mattpocock/skills/engineering/prototype/SKILL.md +26 -0
- package/vendor/upstreams/mattpocock/skills/engineering/prototype/UI.md +112 -0
- package/vendor/upstreams/mattpocock/skills/engineering/research/SKILL.md +12 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/SKILL.md +116 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/domain.md +51 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-github.md +45 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-gitlab.md +46 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/issue-tracker-local.md +30 -0
- package/vendor/upstreams/mattpocock/skills/engineering/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/vendor/upstreams/mattpocock/skills/engineering/to-spec/SKILL.md +75 -0
- package/vendor/upstreams/mattpocock/skills/engineering/to-tickets/SKILL.md +105 -0
- package/vendor/upstreams/mattpocock/skills/engineering/triage/AGENT-BRIEF.md +207 -0
- package/vendor/upstreams/mattpocock/skills/engineering/triage/OUT-OF-SCOPE.md +105 -0
- package/vendor/upstreams/mattpocock/skills/engineering/triage/SKILL.md +112 -0
- package/vendor/upstreams/mattpocock/skills/engineering/wayfinder/SKILL.md +128 -0
- package/vendor/upstreams/mattpocock/skills/productivity/grilling/SKILL.md +12 -0
- package/vendor/upstreams/mattpocock/skills/productivity/handoff/SKILL.md +16 -0
- package/vendor/upstreams/obra/skills/brainstorming/SKILL.md +151 -0
- package/vendor/upstreams/obra/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/vendor/upstreams/obra/skills/brainstorming/visual-companion.md +298 -0
- package/vendor/upstreams/obra/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/vendor/upstreams/obra/skills/executing-plans/SKILL.md +64 -0
- package/vendor/upstreams/obra/skills/receiving-code-review/SKILL.md +205 -0
- package/vendor/upstreams/obra/skills/requesting-code-review/SKILL.md +95 -0
- package/vendor/upstreams/obra/skills/requesting-code-review/code-reviewer.md +172 -0
- package/vendor/upstreams/obra/skills/subagent-driven-development/SKILL.md +503 -0
- package/vendor/upstreams/obra/skills/subagent-driven-development/implementer-prompt.md +142 -0
- package/vendor/upstreams/obra/skills/subagent-driven-development/re-review-prompt.md +106 -0
- package/vendor/upstreams/obra/skills/subagent-driven-development/task-reviewer-prompt.md +185 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/SKILL.md +283 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/find-polluter.sh +72 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/test-academic.md +14 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/vendor/upstreams/obra/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/vendor/upstreams/obra/skills/test-driven-development/SKILL.md +320 -0
- package/vendor/upstreams/obra/skills/test-driven-development/writing-good-tests.md +198 -0
- package/vendor/upstreams/obra/skills/verification-before-completion/SKILL.md +120 -0
- package/vendor/upstreams/obra/skills/writing-plans/SKILL.md +168 -0
- 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.
|
|
1
|
+
// opencode-ship/core v0.10.0-rc.17
|
|
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
|
|
329
|
-
import {
|
|
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
|
-
|
|
332
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
340
|
-
proc.on("
|
|
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
|
-
|
|
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
|
-
|
|
362
|
+
reject(new Error("git rev-parse --git-common-dir returned an empty path"));
|
|
349
363
|
return;
|
|
350
364
|
}
|
|
351
|
-
|
|
365
|
+
resolveP(resolve2(repoRoot, trimmed));
|
|
352
366
|
});
|
|
353
367
|
});
|
|
354
368
|
}
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
await
|
|
365
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
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
|
|
379
|
-
const
|
|
380
|
-
|
|
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
|
-
|
|
388
|
-
|
|
460
|
+
const seen = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const dir of [canonicalDir, legacyDir]) {
|
|
462
|
+
let names;
|
|
389
463
|
try {
|
|
390
|
-
|
|
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
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
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(
|
|
464
|
-
const applyExists = apply.status === 0 && safeExists(
|
|
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((
|
|
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) =>
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
1125
|
-
const normalized =
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
1690
|
-
const mainCwd =
|
|
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" };
|