opencode-ship 1.1.2 → 1.1.3

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 CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to `opencode-ship` are recorded here.
4
4
 
5
+ ## 1.1.3 — Tool permission regression + setup-ship-workflow loader
6
+
7
+ > Branch: `fix/1.1.3-tool-permissions`. Hotfix on top of `1.1.2`.
8
+
9
+ - **Drop the `*` wildcard from the Build / ship-controller tool
10
+ permission map.** `1.1.2` emitted a `permission: { "*": "deny" }`
11
+ leaf at the agent root, and OpenCode's last-match-wins semantics
12
+ masked the consumer-owned built-ins (`read`, `edit`, `bash`,
13
+ `glob`, `grep`, `list`, `skill`, `webfetch`, …). Symptom: the
14
+ Build agent kept only the ship/delivery tool subset, the
15
+ `setup-ship-workflow` skill was never auto-loaded because the
16
+ agent could not read its own `SKILL.md`, and the user was
17
+ prompted with `Skill install blocked: … untrusted-owner` because
18
+ the model tried to reinstall the locally-installed skill via
19
+ `ship_skill_install`. The fix is to deny only the explicit
20
+ `PUBLIC_TOOL_ID` set (plus `allow` / `ask`) and let consumer
21
+ built-ins stay consumer-owned.
22
+ - **Drop leftover matrix leaves on install / update.** The
23
+ reconciler now diffs `previousRecords` against the current
24
+ descriptor set and removes pointers that the matrix no longer
25
+ emits (for example the dropped `/agent/build/permission/*`),
26
+ restoring the preinstall `previous` value or the leaf when no
27
+ previous existed. The byte output is updated for both JSON and
28
+ JSONC paths so `opencode-ship update` from `1.1.2` to `1.1.3`
29
+ cleans the orphan pointer on the first run.
30
+ - **`/setup-ship-workflow` command is self-loading.** The command
31
+ wrapper now spells out the procedure: read the skill body from
32
+ `.opencode/skills/setup-ship-workflow/SKILL.md` and follow it.
33
+ Do not call `ship_skill_install` for this skill — it is
34
+ installed by `init` and the install path requires a trusted
35
+ npm-owner allowlist that `opencode-ship` does not satisfy.
36
+
5
37
  ## 1.1.2 — Stable: Plan Mode write access
6
38
 
7
39
  > Branch: `release/1.1.2`. Promoted from `1.1.2-rc.4` after dogfood.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > npm-distributed OpenCode installer and delivery plugin: a single command materialises the lifecycle plugin, reviewer/verifier agents, and skills into any consumer repository, with a recoverable lock and never silently overwrites managed files.
4
4
  >
5
- > **Status:** `1.1.2` is on `npm dist-tag latest` and is the first fully self-hosting release. It is the accepted successor of `1.1.1` (now deprecated). The `1.1.2 stabilization` line carried three rejected RCs (`1.1.2-rc.1`, `1.1.2-rc.2`, `1.1.2-rc.3`) before the stable candidate `1.1.2-rc.4` was promoted; see `docs/release/1.1.2-correction-plan.md` for the authoritative plan and issue #56 for the bounded evidence ledger. The `1.1.0` tag is immutable. Consumers of `opencode-delivery@0.1.x` should run `pnpm dlx opencode-ship@latest init` from the same checkout; migration recognises the legacy artifacts and adopts them when their bytes match.
5
+ > **Status:** `1.1.3` is on `npm dist-tag latest` and supersedes the `1.1.2 stabilization` line. `1.1.2` published a `permission: { "*": "deny" }` leaf that masked the consumer-owned built-ins and broke `/setup-ship-workflow`; `1.1.3` drops the wildcard, adds a stale-pointer removal path so `update` cleans the orphan key on the first run, and makes the `/setup-ship-workflow` command self-loading. The `1.1.0` tag is immutable. Consumers of `opencode-delivery@0.1.x` should run `pnpm dlx opencode-ship@latest init` from the same checkout; migration recognises the legacy artifacts and adopts them when their bytes match.
6
6
  >
7
7
  > Consumers should use `opencode-ship@1.1.0` (or `@latest`) until `1.1.1` ships. The previous `1.0.0` is on `next`; consumers who pinned to `1.0.x` will continue to receive updates on that channel.
8
8
 
@@ -1,6 +1,6 @@
1
1
  # Third-Party Notices
2
2
 
3
- `opencode-ship@1.1.2` ships the complete Matt Pocock and Superpowers
3
+ `opencode-ship@1.1.3` ships the complete Matt Pocock and Superpowers
4
4
  methodology under the MIT license, plus the Ship-owned installer,
5
5
  plugin, agents, and skills. The complete immutable pin list is in
6
6
  `vendor/sources.json`; the upstream snapshots are under
@@ -5,11 +5,30 @@ description: Configure the opencode-ship workflow for this repo: GitHub tracker,
5
5
  # Setup Ship Workflow
6
6
 
7
7
  This command is a thin wrapper around the canonical
8
- `setup-ship-workflow` skill. OpenCode will load the skill
9
- and follow its procedure.
10
-
11
- To run the skill from this command, the user types
12
- `/setup-ship-workflow` in OpenCode. The CLI's
13
- `setup-complete` command is the transactional gate that
14
- commits the lock flip and clears the marker once the skill
15
- has produced every artifact.
8
+ `setup-ship-workflow` skill. The skill file is written to disk by
9
+ `opencode-ship init` at `.opencode/skills/setup-ship-workflow/SKILL.md`
10
+ in the consumer repo and in every issue worktree.
11
+
12
+ ## When the user types `/setup-ship-workflow`
13
+
14
+ 1. **Read** the skill body from
15
+ `.opencode/skills/setup-ship-workflow/SKILL.md` in the current
16
+ worktree (or the consumer repo root if no worktree is open).
17
+ Do NOT call `ship_skill_install` for this skill: it is already
18
+ on disk from `init` and the install path requires a trusted
19
+ npm-owner allowlist that the `opencode-ship` publisher does
20
+ not satisfy.
21
+ 2. **Follow** the procedure in the skill body. The skill writes
22
+ the four required artifacts (`docs/agents/issue-tracker.md`,
23
+ `docs/agents/triage-labels.md`, `docs/agents/domain.md`,
24
+ `AGENTS.md`), populates the `workflow.models` block in
25
+ `.opencode/ship.config.json`, and runs the
26
+ `opencode-ship setup-complete` CLI to flip
27
+ `lock.manager.setupComplete` to `true`.
28
+
29
+ If the skill file is missing (for example the consumer skipped
30
+ `init`), fall back to running `opencode-ship init --root "$PWD"`
31
+ from a `bash` block first, then read the freshly written skill and
32
+ continue. Do not silently invent the setup procedure; the CLI is
33
+ the single source of truth for the artifact list and the model
34
+ names that the controller will dispatch.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // opencode-ship CLI v1.1.2
2
+ // opencode-ship CLI v1.1.3
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __esm = (fn, res) => function __init() {
@@ -110,7 +110,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
110
110
  var PACKAGE_VERSION, TEMPLATE_SET;
111
111
  var init_version = __esm({
112
112
  "src/version.js"() {
113
- PACKAGE_VERSION = "1.1.2";
113
+ PACKAGE_VERSION = "1.1.3";
114
114
  TEMPLATE_SET = `v${PACKAGE_VERSION}`;
115
115
  }
116
116
  });
@@ -1871,7 +1871,7 @@ function denyMap(globs) {
1871
1871
  return out;
1872
1872
  }
1873
1873
  function toolPermissionMap(allow, ask) {
1874
- const out = { "*": DENY };
1874
+ const out = {};
1875
1875
  for (const id of PUBLIC_TOOL_IDS) out[id] = DENY;
1876
1876
  for (const id of allow) out[id] = ALLOW;
1877
1877
  for (const id of ask) out[id] = ASK;
@@ -3538,6 +3538,10 @@ function reconcileRecordsAfterTransition(descriptors, previousRecords) {
3538
3538
  }
3539
3539
  return out;
3540
3540
  }
3541
+ function staleRecordsToRemove(previousRecords, descriptors) {
3542
+ const desired = new Set(descriptors.map((d) => d.pointer));
3543
+ return previousRecords.filter((r) => r.scope === "engineering" && !desired.has(r.pointer));
3544
+ }
3541
3545
  function planInstallRoot({ doc, target, relPath, descriptors, previousRecords }) {
3542
3546
  const promotion = promotePlanEditIfString(doc.value);
3543
3547
  if (promotion.record && promotion.record.conflict) {
@@ -3553,12 +3557,24 @@ function planInstallRoot({ doc, target, relPath, descriptors, previousRecords })
3553
3557
  document: doc.value
3554
3558
  };
3555
3559
  }
3556
- const sourceDoc = promotion.doc;
3560
+ const stale = staleRecordsToRemove(previousRecords, descriptors);
3561
+ let sourceDoc = promotion.doc;
3562
+ const staleEdits = [];
3563
+ for (const r of stale) {
3564
+ if (r.previous && r.previous.existed) {
3565
+ sourceDoc = setPointer(sourceDoc, r.pointer, r.previous.value);
3566
+ staleEdits.push({ kind: "restore", pointer: r.pointer, value: r.previous.value });
3567
+ } else {
3568
+ sourceDoc = removePointer(sourceDoc, r.pointer);
3569
+ staleEdits.push({ kind: "remove", pointer: r.pointer });
3570
+ }
3571
+ }
3572
+ const livePreviousRecords = previousRecords.filter((r) => !stale.some((s) => s.pointer === r.pointer));
3557
3573
  const result = applyOwnedPointers(sourceDoc, {
3558
3574
  pointerEntries: descriptors.map((d) => ({ pointer: d.pointer, strategy: d.strategy, value: d.value })),
3559
3575
  allowEqualValues: true
3560
3576
  });
3561
- const edits = [];
3577
+ const edits = [...staleEdits];
3562
3578
  for (const a of result.applied) edits.push({ kind: "create", pointer: a.pointer, value: a.value });
3563
3579
  for (const s of result.skipped) {
3564
3580
  if (s.reason === "already equal") continue;
@@ -3592,11 +3608,31 @@ function planInstallRoot({ doc, target, relPath, descriptors, previousRecords })
3592
3608
  "set"
3593
3609
  ) });
3594
3610
  }
3611
+ for (const e of staleEdits) {
3612
+ if (e.kind === "restore") {
3613
+ pointerEdits.push({ pointer: e.pointer, op: (
3614
+ /** @type {"set"} */
3615
+ "set"
3616
+ ), value: e.value });
3617
+ } else {
3618
+ pointerEdits.push({ pointer: e.pointer, op: (
3619
+ /** @type {"delete"} */
3620
+ "delete"
3621
+ ) });
3622
+ }
3623
+ }
3595
3624
  bytes = applyJsoncEdits(doc.raw ?? "", pointerEdits);
3596
3625
  } else {
3597
3626
  const { value: sourceValue } = parseRootConfigPreservingOrder(doc.raw ?? "");
3598
3627
  if (sourceValue && typeof sourceValue === "object") {
3599
3628
  docForWrite = sourceValue;
3629
+ for (const e of staleEdits) {
3630
+ if (e.kind === "restore") {
3631
+ docForWrite = setPointer(docForWrite, e.pointer, e.value);
3632
+ } else {
3633
+ docForWrite = removePointer(docForWrite, e.pointer);
3634
+ }
3635
+ }
3600
3636
  if (promotion.record) {
3601
3637
  docForWrite = setPointer(docForWrite, promotion.record.pointer, { "*": promotion.record.previous.value });
3602
3638
  }
@@ -3612,7 +3648,7 @@ function planInstallRoot({ doc, target, relPath, descriptors, previousRecords })
3612
3648
  } catch {
3613
3649
  bytes = Buffer.from(formatRootConfig(result.doc), "utf8");
3614
3650
  }
3615
- const records = mergePointerRecords(descriptors, previousRecords, result, doc.before);
3651
+ const records = mergePointerRecords(descriptors, livePreviousRecords, result, doc.before);
3616
3652
  if (promotion.record) {
3617
3653
  records.push({
3618
3654
  pointer: promotion.record.pointer,
@@ -5210,7 +5246,7 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
5210
5246
  manager: {
5211
5247
  schemaVersion: CURRENT_LOCK_SCHEMA,
5212
5248
  name: "opencode-ship",
5213
- version: "1.1.2",
5249
+ version: "1.1.3",
5214
5250
  templateSet: TEMPLATE_SET_ID,
5215
5251
  profile: resolvedProfile,
5216
5252
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship/core v1.1.2
1
+ // opencode-ship/core v1.1.3
2
2
 
3
3
  // src/adapter.js
4
4
  import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship v1.1.2
1
+ // opencode-ship v1.1.3
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __esm = (fn, res) => function __init() {
@@ -19328,7 +19328,7 @@ function selectRuntimeAdapter({ config: config2, shipAdapter, legacyAdapter }) {
19328
19328
  import { readFileSync as readFileSync4, existsSync as existsSync27 } from "node:fs";
19329
19329
  import { dirname as dirname12, resolve as resolve21 } from "node:path";
19330
19330
  import { fileURLToPath } from "node:url";
19331
- var PACKAGE_VERSION = "1.1.2";
19331
+ var PACKAGE_VERSION = "1.1.3";
19332
19332
  var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
19333
19333
 
19334
19334
  // src/plugin.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ship",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "private": false,
5
5
  "description": "npm-distributed OpenCode installer that materializes the delivery plugin, reviewer/verifier agents, skills, ship config and lock into any consumer repository.",
6
6
  "license": "MIT",