opencode-ship 0.10.0-rc.19 → 0.10.0-rc.28

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
@@ -4,22 +4,68 @@ All notable changes to `opencode-ship` are recorded here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
- - The `release/1.0-completion` branch contains the complete
8
- production runtime for the planned `0.10.0` release. No
9
- `0.10.0` or `1.0.0` has been published; consumers should keep
10
- using `opencode-ship@0.9.0`. The local `v0.10.0` and `v1.0.0`
11
- tags are placeholders only and must not be pushed.
12
- - See `RELEASING.md` for the operational runbook and
13
- `.git/opencode-ship/plans/opencode-ship-1.0-completion/execution-state.json`
14
- for the authoritative task state.
15
-
16
- ### Shipped source on `release/1.0-completion` (not yet released)
17
-
18
- These changes are present in source on the
19
- `release/1.0-completion` branch but are not production-ready
20
- until the formal registry dogfood on the npm-published `0.10.0`
21
- succeeds. Items will move to a dated release header only after
22
- that step completes.
7
+ - The `release/0.10.0` branch is the live release branch carrying
8
+ the complete production runtime for the planned `0.10.0` and
9
+ `1.0.0` releases. Release candidates are published under the
10
+ `next` npm dist-tag. No `0.10.0` or `1.0.0` has been published;
11
+ consumers should keep using `opencode-ship@0.9.0`. The local
12
+ `v0.10.0` and `v1.0.0` tags are placeholders only and must not
13
+ be pushed.
14
+ - See `docs/release/1.0.0-execution-plan.md` for the authoritative
15
+ execution plan and issue #37 for the bounded evidence ledger.
16
+
17
+ ### Release-qualification gaps closed (S1)
18
+
19
+ These changes close the S1 release-qualification gaps on the
20
+ `release/0.10.0` branch:
21
+
22
+ - **Real Node compatibility lanes.** The `node-compat` job's
23
+ `setup-node` step now drives from `${{ matrix.node }}` for each
24
+ matrix row (`22.6.0`, current `22`, `24`); the trusted-publishing
25
+ `publish` job remains the only place that pins `22.14.0`. The
26
+ per-row observed `node --version` output is uploaded and
27
+ aggregated into the qualification report by
28
+ `scripts/compose-node-versions.mjs`.
29
+ - **Real OpenCode startup and discovery smoke.** A new
30
+ `tests/release/opencode-discovery.test.mjs` boots a real
31
+ `opencode serve` instance against a packed-tarball fixture
32
+ with both `core` and `engineering` profiles, polls
33
+ `/global/health`, reads `/experimental/tool/ids`, and asserts
34
+ the canonical 24-tool set exported from
35
+ `tests/plugin/expected-tools.mjs`. The canonical set is the
36
+ single source of truth shared by the in-process plugin-load
37
+ test and the live-server smoke.
38
+ - **Correct prerelease metadata.** The publish job now resolves a
39
+ `prerelease` flag from a dedicated step that delegates to
40
+ `scripts/is-prerelease.mjs`. SemVer prereleases (`-rc.N`,
41
+ `-alpha.N`, `-beta.N`) become `prerelease: true`; stable
42
+ versions become `prerelease: false`.
43
+ - **Version-independent runtimeSourceSha256.** A new
44
+ `scripts/runtime-source-sha.mjs` computes a deterministic
45
+ digest over `src/**`, `assets/**`, `schema/**`, `vendor/**`,
46
+ `scripts/build.mjs`, `scripts/prepack.mjs`, and a normalised
47
+ `package.json` with the top-level `version` field removed.
48
+ The qualification report carries `runtimeSourceSha256`, and the
49
+ release-policy job refuses any `1.0.x` tag whose digest does
50
+ not match the accepted `0.10.0` qualification artifact
51
+ (via `scripts/promote-1.0-policy.mjs`).
52
+ - **Truthful documentation.** README, CHANGELOG, and this file
53
+ no longer claim `release/1.0-completion` is the live branch,
54
+ no longer assert a stale test-count baseline, and no longer
55
+ promise a promotion rule that depends on equal source SHAs
56
+ between `0.10.0` and `1.0.0`.
57
+ - **Stronger neutral-consumer assertions.**
58
+ `tests/package/neutral-consumer.test.mjs` now requires explicit
59
+ model IDs before any engineering install writes, asserts the
60
+ engineering install's exit code is `0` before doctor runs, and
61
+ covers the engineering-init-without-models failure path.
62
+
63
+ ### Shipped source on `release/0.10.0` (not yet released)
64
+
65
+ These changes are present in source on the `release/0.10.0`
66
+ branch but are not production-ready until the formal registry
67
+ dogfood on the npm-published `0.10.0` succeeds. Items will move
68
+ to a dated release header only after that step completes.
23
69
 
24
70
  - Crash-safe Git-common storage: link()-based atomic publication
25
71
  with rename() fallback, SHA-256 hashed resource locks, explicit
@@ -76,7 +122,7 @@ that step completes.
76
122
 
77
123
  ### Planned (not yet shipped)
78
124
 
79
- These changes are present in source on the `release/1.0-completion` branch but are not production-ready and have not been published. Items will move to a dated release header only after the relevant plan task is verified end-to-end and the formal registry dogfood passes.
125
+ These changes are present in source on the `release/0.10.0` branch but are not production-ready and have not been published. Items will move to a dated release header only after the relevant plan task is verified end-to-end and the formal registry dogfood passes.
80
126
 
81
127
  - Lock schema v3 with a `scope` field per root pointer record (core | engineering) and byte-stable hash identity.
82
128
  - `src/state/git-common-dir.js` and `src/state/durable-store.js` for the shared crash-safe storage under the resolved Git common directory.
package/README.md CHANGED
@@ -2,9 +2,9 @@
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:** v0.9.0 published. The `release/1.0-completion` branch holds the complete production runtime for `0.10.0` and `1.0.0`: 24 typed tools (9 existing delivery tools + 7 control-plane tools + 8 workflow tools), the deterministic controller, the contract-version-2 envelope, the GitHub operation store, the immutable plan + run + commit ledgers, the same-HEAD gate, the bounded compaction block, the per-run resume lock, the fail-closed engineering profile, and the ten-job release qualification pipeline. The local `v0.10.0` and `v1.0.0` tags are placeholders only; they MUST NOT be pushed until the formal fourteen-step dogfood on the npm-published `0.10.0` succeeds. See `RELEASING.md` for the full operational runbook and `.git/opencode-ship/plans/opencode-ship-1.0-completion/execution-state.json` for the authoritative task state.
5
+ > **Status:** v0.9.0 is the current `npm latest`. The `release/0.10.0` branch is the live release branch carrying the complete production runtime for the upcoming `0.10.0` and `1.0.0` releases: 24 typed tools (9 existing delivery tools + 7 control-plane tools + 8 workflow tools), the deterministic controller, the contract-version-2 envelope, the GitHub operation store, the immutable plan + run + commit ledgers, the same-HEAD gate, the bounded compaction block, the per-run resume lock, the fail-closed engineering profile, the version-independent `runtimeSourceSha256` digest, the ten-job release qualification pipeline with a real OpenCode discovery smoke, and the prerelease-aware GitHub release metadata. Release candidates are published under `npm dist-tag next`; stable `0.10.0` and `1.0.0` remain unpublished until their gates pass. The previously-mentioned `release/1.0-completion` branch is not the live release branch and is not referenced by this README or `RELEASING.md`. See `docs/release/1.0.0-execution-plan.md` for the authoritative execution plan and issue #37 for the bounded evidence ledger.
6
6
  >
7
- > No `0.10.0` or `1.0.0` has been published yet. Consumers should keep using `opencode-ship@0.9.0`.
7
+ > Consumers should keep using `opencode-ship@0.9.0` until `1.0.0` is promoted to `npm dist-tag latest`.
8
8
 
9
9
  ---
10
10
 
@@ -113,7 +113,7 @@ Existing consumers of `opencode-delivery@0.1.x` (commit-pinned shim) can run `pn
113
113
 
114
114
  ## Development
115
115
 
116
- `npm run verify` runs `format:check`, `lint`, `typecheck`, `build`, and the auto-discovered test suite. The tests cover the installer CLI, the lock and root-config planners, the catalog validator, the schema validator, agents, the packed-artifact smoke check, the transaction-recovery contract, the profile-resolution precedence chain, the catalog profile filter, the order-preserving root-config merge, the engineering↔core transition, the durable plan artifact, the Plan Mode permission integration, the M3 task loop contract (run store + task brief + Spec/Quality verdicts + 3-round breaker + commit binding + compaction context), the Ready gate (parallel GPT Standards/Spec + verifier + CI on one HEAD; Build cannot self-record), and the transition matrix smoke (core omits engineering, engineering adds engineering, lock tracks the active profile). v0.9 ships with 320 tests across 34 suites on the approved plan hash `f85bae931d9eed7763e2f6f4dc68e5fad71bdd38c8a667fc9ffe78b5290200be`.
116
+ `npm run verify` runs `format:check`, `lint`, `typecheck`, `build`, and the auto-discovered test suite. The tests cover the installer CLI, the lock and root-config planners, the catalog validator, the schema validator, agents, the packed-artifact smoke check, the transaction-recovery contract, the profile-resolution precedence chain, the catalog profile filter, the order-preserving root-config merge, the engineering↔core transition, the durable plan artifact, the Plan Mode permission integration, the M3 task loop contract (run store + task brief + Spec/Quality verdicts + 3-round breaker + commit binding + compaction context), the Ready gate (parallel GPT Standards/Spec + verifier + CI on one HEAD; Build cannot self-record), the transition matrix smoke (core omits engineering, engineering adds engineering, lock tracks the active profile), the static release-policy tests (Node matrix wiring, prerelease metadata, runtime-source digest wiring, 1.0 promotion policy), the runtime-source digest algorithm tests (deterministic across clean checkouts; ignores version and release-metadata changes; tracks every runtime-source modification), and the OpenCode live-server discovery smoke (24-tool canonical set exposed via `/experimental/tool/ids` on a real `opencode serve` instance for both `core` and `engineering` profiles). The suite count and per-test count are derived from the test runner at qualification time; this README intentionally does not lock in a specific test-count baseline.
117
117
 
118
118
  ```
119
119
  npm ci
@@ -1,6 +1,6 @@
1
1
  # Third-Party Notices
2
2
 
3
- `opencode-ship@0.10.0-rc.19` ships the complete Matt Pocock and Superpowers
3
+ `opencode-ship@0.10.0-rc.28` 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
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // opencode-ship CLI v0.10.0-rc.19
2
+ // opencode-ship CLI v0.10.0-rc.28
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __esm = (fn, res) => function __init() {
@@ -689,7 +689,7 @@ function resolvePackageRoot(startUrl) {
689
689
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
690
690
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
691
691
  import { fileURLToPath as fileURLToPath2 } from "node:url";
692
- var PACKAGE_VERSION = "0.10.0-rc.19";
692
+ var PACKAGE_VERSION = "0.10.0-rc.28";
693
693
  var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
694
694
 
695
695
  // src/installer/catalog.js
@@ -2960,7 +2960,7 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
2960
2960
  manager: {
2961
2961
  schemaVersion: CURRENT_LOCK_SCHEMA,
2962
2962
  name: "opencode-ship",
2963
- version: "0.10.0-rc.19",
2963
+ version: "0.10.0-rc.28",
2964
2964
  templateSet: TEMPLATE_SET_ID,
2965
2965
  // Newly written locks always carry the resolved profile so
2966
2966
  // future CLI invocations without --profile still resolve to
@@ -3535,7 +3535,8 @@ async function runDiff(options) {
3535
3535
  profile: options.profile ?? null,
3536
3536
  replaceManaged: false,
3537
3537
  forceConfig: false,
3538
- forceRootConfig: false
3538
+ forceRootConfig: false,
3539
+ models: options.models ?? null
3539
3540
  });
3540
3541
  if (!preview.ok) {
3541
3542
  const exitCode = preview.error?.kind === "unsupported-lock-schema" ? 5 : preview.error?.kind === "lock-invalid" ? 3 : 2;
@@ -3610,7 +3611,8 @@ async function runUpdate(options) {
3610
3611
  profile: options.profile ?? null,
3611
3612
  replaceManaged: options.replaceManaged,
3612
3613
  forceConfig: options.forceConfig,
3613
- forceRootConfig: options.forceRootConfig
3614
+ forceRootConfig: options.forceRootConfig,
3615
+ models: options.models ?? null
3614
3616
  });
3615
3617
  if (!preview.ok) {
3616
3618
  if (preview.error?.kind === "unsupported-lock-schema") {
@@ -3791,7 +3793,7 @@ ${helpText()}`);
3791
3793
  });
3792
3794
  return;
3793
3795
  case "diff":
3794
- await runDiff({ json: !!opts.json, rootPath: opts.rootPath, profile });
3796
+ await runDiff({ json: !!opts.json, rootPath: opts.rootPath, profile, models: hasModels ? models : null });
3795
3797
  return;
3796
3798
  case "update":
3797
3799
  await runUpdate({
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship/core v0.10.0-rc.19
1
+ // opencode-ship/core v0.10.0-rc.28
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 v0.10.0-rc.19
1
+ // opencode-ship v0.10.0-rc.28
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __esm = (fn, res) => function __init() {
@@ -16793,7 +16793,7 @@ function flattenShipConfig(ship) {
16793
16793
  import { readFileSync as readFileSync2, existsSync as existsSync12 } from "node:fs";
16794
16794
  import { dirname as dirname7, resolve as resolve13 } from "node:path";
16795
16795
  import { fileURLToPath } from "node:url";
16796
- var PACKAGE_VERSION = "0.10.0-rc.19";
16796
+ var PACKAGE_VERSION = "0.10.0-rc.28";
16797
16797
  var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
16798
16798
 
16799
16799
  // src/plugin.js
@@ -0,0 +1,972 @@
1
+ # opencode-ship 1.0.0 execution plan
2
+
3
+ > Status: Approved for execution
4
+ > Owner: Maintainer
5
+ > Last reviewed: 2026-08-07
6
+ > Tracker: https://github.com/Viktorxyz/opencode-ship/issues/37
7
+ > Related: [RELEASING.md](../../RELEASING.md),
8
+ > [README.md](../../README.md), [CHANGELOG.md](../../CHANGELOG.md)
9
+
10
+ ## Purpose
11
+
12
+ Finish `opencode-ship` as a registry-installed OpenCode engineering workflow,
13
+ prove it in a neutral repository with real OpenCode, GitHub, and model
14
+ dispatch, publish a byte-equivalent `1.0.0`, move npm `latest` to `1.0.0`, and
15
+ then install it in a real consumer project.
16
+
17
+ This plan is deliberately explicit so a low-cost Build model can execute
18
+ reversible steps without making architecture or release-policy decisions.
19
+
20
+ ## Fixed decisions
21
+
22
+ | Decision | Value |
23
+ |---|---|
24
+ | Final npm target | `opencode-ship@1.0.0` on `latest` |
25
+ | Consumer profile | `engineering` |
26
+ | Planner model | `openai/gpt-5.6-sol` |
27
+ | Builder model | `minimax/MiniMax-M3` |
28
+ | Final reviewer model | `openai/gpt-5.6-sol` |
29
+ | First real qualification consumer | `Viktorxyz/opencode-ship-dogfood` |
30
+ | Package source | npm registry only for external dogfood |
31
+ | Merge policy | Explicit user request only |
32
+ | Release policy | Immutable versions; a failed published RC is replaced by a new RC |
33
+
34
+ Do not revisit these decisions during execution unless the user explicitly
35
+ changes them.
36
+
37
+ ## Current factual baseline
38
+
39
+ Re-check every value before starting because registry and branch state can
40
+ change between sessions.
41
+
42
+ | Item | Value at plan approval |
43
+ |---|---|
44
+ | Upstream repository | `/home/viktorcordas/repos/opencode-ship` |
45
+ | Active release branch | `release/0.10.0` |
46
+ | Active release HEAD | `9bc178c692cd197982c64c109440ece9205c460b` |
47
+ | Published candidate | `0.10.0-rc.19` |
48
+ | npm `next` | `0.10.0-rc.19` |
49
+ | npm `latest` | `0.9.0` |
50
+ | RC19 npm integrity | `sha512-rpD5sgCokj2GNBnXmnbRhnQh5FpHel2GtzspApLTMcQaZCuLEkt1iJcRqhwtzqY8ikC+nFHLWPkuvksNQFeXlw==` |
51
+ | RC19 tarball SHA-256 | `410ef29ed3189fb041c44c51006b685b46b6d1d2b8da45128d1bc4ea7b3ca8d7` |
52
+ | Neutral dogfood repository | Does not exist yet |
53
+ | Obsolete Leo migration | `Viktorxyz/leo#221` |
54
+
55
+ `Viktorxyz/leo#221` is not a migration base. It targets the abandoned
56
+ `practices`/v0.4 proposal, is conflicting, and was produced by copying an
57
+ unpublished artifact. It must eventually be closed as superseded by a fresh
58
+ registry-generated migration.
59
+
60
+ ## Scope
61
+
62
+ The critical path includes:
63
+
64
+ 1. Persisting the release tracker and evidence ledger.
65
+ 2. Closing known release-qualification gaps.
66
+ 3. Publishing and qualifying one final RC.
67
+ 4. Running registry-sourced neutral install acceptance.
68
+ 5. Publishing stable `0.10.0`.
69
+ 6. Running the real 14-step engineering dogfood.
70
+ 7. Promoting equivalent runtime source to `1.0.0` and npm `latest`.
71
+ 8. Installing `1.0.0` into a real project.
72
+ 9. Replacing Leo PR #221 with a clean migration.
73
+
74
+ ## Non-goals
75
+
76
+ - Do not change Leo product, design-lab, API, dashboard, web, or database code.
77
+ - Do not touch, clean, reset, stash, stage, or commit the dirty Leo main
78
+ worktree.
79
+ - Do not add new workflow features after the final RC is selected.
80
+ - Do not manually copy plugin, agent, command, or skill files into a consumer.
81
+ - Do not treat `scripts/dogfood-local.mjs` or a fake harness as formal
82
+ external dogfood.
83
+ - Do not publish `1.0.0` merely because unit and package tests pass.
84
+
85
+ ## Definition of done
86
+
87
+ The release is complete only when all rows are proven with linked evidence.
88
+
89
+ - [ ] `npm view opencode-ship dist-tags --json` returns `latest: 1.0.0`.
90
+ - [ ] `pnpm dlx opencode-ship@latest --version` prints `1.0.0`.
91
+ - [ ] The accepted final RC has a green qualification workflow and public
92
+ qualification artifact.
93
+ - [ ] npm and pnpm install both `core` and `engineering` from the registry.
94
+ - [ ] A real OpenCode CLI starts in the neutral consumer and discovers the
95
+ exact expected tool IDs.
96
+ - [ ] `diff` is read-only and same-version init/update are idempotent.
97
+ - [ ] A changed managed file causes a conflict without being overwritten.
98
+ - [ ] `0.9.0` upgrades to the final candidate or stable package.
99
+ - [ ] `engineering -> core -> engineering` transitions converge safely.
100
+ - [ ] Uninstall restores pre-install root config bytes.
101
+ - [ ] The real 14-step dogfood completes with real GitHub and real models.
102
+ - [ ] Final Standards review, Spec review, verifier, CI, PR, and Ready records
103
+ bind to one HEAD.
104
+ - [ ] Merge happens only after a separate explicit user request.
105
+ - [ ] Cleanup succeeds and leaves no unpublished worktree commits.
106
+ - [ ] `1.0.0` has the same version-independent runtime-source digest as the
107
+ accepted `0.10.0`.
108
+ - [ ] A user-selected real project installs the `engineering` profile and
109
+ `doctor --json` exits 0.
110
+ - [ ] Leo PR #221 is superseded by a fresh registry-generated migration.
111
+ - [ ] README, CHANGELOG, RELEASING, GitHub release metadata, and npm dist-tags
112
+ all describe the same shipped state.
113
+
114
+ ## Safety and authorization gates
115
+
116
+ The executing agent may autonomously inspect, edit, test, commit, push a normal
117
+ feature branch, and open a draft PR when the user starts a Build session with
118
+ this plan.
119
+
120
+ The agent must stop and request explicit user authorization before each of the
121
+ following operations:
122
+
123
+ | Gate | Irreversible or externally visible operation |
124
+ |---|---|
125
+ | A | Create `Viktorxyz/opencode-ship-dogfood`; the user chooses visibility, default private |
126
+ | B | Merge a qualification or release PR |
127
+ | C | Push any release tag |
128
+ | D | Trigger an npm stable/candidate publication if tag push is not itself the trigger |
129
+ | E | Merge the real dogfood PR |
130
+ | F | Run `npm dist-tag add ... latest` |
131
+ | G | Merge the Leo migration PR |
132
+
133
+ Never use force-push, hard reset, stash, tag mutation, version overwrite,
134
+ manual npm publish, raw Git ref plumbing, or a bypass around a failed gate.
135
+
136
+ ## Evidence ledger
137
+
138
+ The parent issue is the authoritative progress tracker. Add one bounded comment
139
+ after every slice using this schema:
140
+
141
+ ```text
142
+ Status: pass | fail | blocked | partial
143
+ Phase: S0 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8
144
+ Source SHA: <40 hex>
145
+ Package version: <semver or n/a>
146
+ Summary:
147
+ - <at most three bullets>
148
+ Evidence:
149
+ - issue/PR/workflow/release URLs
150
+ - npm integrity and tarball SHA-256 when applicable
151
+ - artifact URL when applicable
152
+ Verification:
153
+ - <command> -> exit <code>
154
+ Risks:
155
+ - <unresolved concern or none>
156
+ Next authorization: <exact gate or none>
157
+ ```
158
+
159
+ Never place tokens, credentials, raw model prompts containing secrets, or
160
+ unsanitized environment dumps in the issue or repository.
161
+
162
+ ## Work breakdown
163
+
164
+ | Slice | Deliverable | Type | Blocked by |
165
+ |---|---|---|---|
166
+ | S0 | Durable plan and parent tracker | AFK | None |
167
+ | S1 | Release qualification gaps closed | AFK | S0 |
168
+ | S2 | Final RC published and qualified | HITL release | S1 |
169
+ | S3 | Neutral registry acceptance green | HITL repo creation | S2 |
170
+ | S4 | Stable `0.10.0` published | HITL release | S3 |
171
+ | S5 | Real 14-step dogfood green | HITL merge | S4 |
172
+ | S6 | Equivalent `1.0.0` promoted to `latest` | HITL release | S5 |
173
+ | S7 | Real-project and Leo consumer migrations | HITL merge | S6 |
174
+ | S8 | Evidence closure and truthful docs | AFK | S7 |
175
+
176
+ ## S0: Persist the plan and tracker
177
+
178
+ ### Goal
179
+
180
+ Create durable coordination surfaces before changing release code.
181
+
182
+ ### Steps
183
+
184
+ 1. Re-read this plan, `README.md`, `CHANGELOG.md`, `RELEASING.md`,
185
+ `schema/ship-config.schema.json`, `.github/workflows/release.yml`, and
186
+ `tests/package/neutral-consumer.test.mjs`.
187
+ 2. Confirm the upstream worktree is clean and record branch, HEAD, remote main,
188
+ npm dist-tags, and the highest published RC.
189
+ 3. Create or reuse the parent GitHub issue named at the top of this file.
190
+ 4. Add the issue URL to this document's `Tracker` header.
191
+ 5. Add the S0 evidence comment to the issue.
192
+ 6. Commit this plan before any release or runtime changes.
193
+
194
+ ### Verification
195
+
196
+ ```bash
197
+ git diff --check
198
+ git status --short
199
+ npm view opencode-ship dist-tags --json
200
+ ```
201
+
202
+ ### Acceptance
203
+
204
+ - [ ] The parent issue exists.
205
+ - [ ] The issue and this file contain identical fixed decisions.
206
+ - [ ] Current branch, SHA, and npm tags are recorded.
207
+ - [ ] No runtime or release workflow code changed in the plan commit.
208
+
209
+ ## S1: Close release-qualification gaps
210
+
211
+ ### Goal
212
+
213
+ Make the release pipeline prove the claims it records before publishing another
214
+ immutable candidate.
215
+
216
+ ### Branching
217
+
218
+ Create a normal feature branch from the current `release/0.10.0` head. Open a
219
+ PR back to `release/0.10.0`. Do not work directly on the release branch.
220
+
221
+ ### Required changes
222
+
223
+ #### 1. Real Node compatibility lanes
224
+
225
+ The release workflow currently declares Node `22.6.0`, current 22, and 24 but
226
+ sets up Node `22.14.0` in every lane. Change the compatibility job so each
227
+ matrix row runs the declared runtime. Keep Node 22.14/npm 11.5.2 only in the
228
+ trusted-publishing job where npm OIDC requires it.
229
+
230
+ Acceptance:
231
+
232
+ - [ ] Workflow source proves `matrix.node` drives setup-node.
233
+ - [ ] Tests or static policy checks fail if the matrix is ignored.
234
+ - [ ] Qualification reports the actual Node versions observed.
235
+
236
+ #### 2. Real OpenCode startup and discovery smoke
237
+
238
+ Package-level plugin import tests are insufficient. Add a deterministic test or
239
+ release job that:
240
+
241
+ 1. Creates a neutral temporary Git repository.
242
+ 2. Installs the packed package into that repository.
243
+ 3. Initializes `core` and `engineering` in separate fixtures.
244
+ 4. Installs a pinned real OpenCode CLI.
245
+ 5. Starts `opencode serve` with an isolated HOME and the fixture as cwd.
246
+ 6. Polls `/global/health` until healthy or a bounded timeout expires.
247
+ 7. Reads `/experimental/tool/ids`.
248
+ 8. Compares the exact set with the canonical expected set from
249
+ `tests/plugin/plugin-load.test.mjs`.
250
+ 9. Terminates the server and proves cleanup.
251
+
252
+ Exercise OpenCode `1.15.5` and `1.18.10`. Do not make a model call.
253
+
254
+ Acceptance:
255
+
256
+ - [ ] Both supported OpenCode versions boot successfully.
257
+ - [ ] The engineering fixture exposes exactly the expected 24 custom tools.
258
+ - [ ] Missing or extra tools fail the release.
259
+ - [ ] Timeout and process cleanup are deterministic.
260
+
261
+ #### 3. Correct prerelease metadata
262
+
263
+ GitHub release `0.10.0-rc.19` was marked as a normal release. Configure the
264
+ release step so SemVer prereleases are marked prerelease and stable versions
265
+ are not.
266
+
267
+ Acceptance:
268
+
269
+ - [ ] RC metadata test expects `prerelease=true`.
270
+ - [ ] Stable metadata test expects `prerelease=false`.
271
+
272
+ #### 4. Version-independent runtime-source digest
273
+
274
+ The existing runbook asks `1.0.0` to match the `0.10.0` source SHA, which is
275
+ impossible after a version/docs commit. Replace that rule with a reproducible
276
+ `runtimeSourceSha256`.
277
+
278
+ The canonical digest must include the version-independent inputs that can
279
+ change behavior:
280
+
281
+ ```text
282
+ src/**
283
+ assets/**
284
+ schema/**
285
+ vendor/**
286
+ scripts/build.mjs
287
+ scripts/prepack.mjs
288
+ package.json with the top-level `version` field removed before canonicalization
289
+ ```
290
+
291
+ Sort normalized relative paths, include path separators and bytes in the hash,
292
+ and exclude generated output, lockfile version, README, CHANGELOG, and release
293
+ metadata. The normalized package manifest remains in scope because `bin`,
294
+ `exports`, `files`, `engines`, and peer ranges affect consumer behavior even
295
+ when runtime source bytes do not change. Document the exact algorithm and test
296
+ it with an independent implementation or golden fixture.
297
+
298
+ Add `runtimeSourceSha256` to the qualification report. For `1.0.x`, the release
299
+ policy must fetch the accepted `0.10.0` qualification artifact and refuse the
300
+ release unless the digest matches.
301
+
302
+ Acceptance:
303
+
304
+ - [ ] Digest is deterministic across clean checkouts.
305
+ - [ ] Runtime source modification changes the digest.
306
+ - [ ] Version/docs-only changes do not change the digest.
307
+ - [ ] `1.0.0` promotion fails when the accepted `0.10.0` digest is absent or
308
+ different.
309
+
310
+ #### 5. Truthful documentation and branch names
311
+
312
+ Update README, CHANGELOG, and RELEASING to state the actual current status:
313
+
314
+ - RCs are published under npm `next`.
315
+ - `release/0.10.0` is the live release branch.
316
+ - `release/1.0-completion` does not exist and must not be referenced as live.
317
+ - Stable `0.10.0` and `1.0.0` remain unpublished until their gates pass.
318
+ - Promotion compares `runtimeSourceSha256`, not impossible source-SHA equality.
319
+ - The release job count and matrix must match the actual workflow.
320
+
321
+ #### 6. Stronger neutral-consumer assertions
322
+
323
+ `tests/package/neutral-consumer.test.mjs` must not run `doctor` after an
324
+ unchecked engineering init. Require explicit model IDs, assert init exit 0,
325
+ then assert doctor exit 0 and profile footprint.
326
+
327
+ Add explicit coverage that engineering init without model IDs fails before any
328
+ write.
329
+
330
+ ### Required verification
331
+
332
+ ```bash
333
+ npm ci
334
+ npm run verify
335
+ npm pack --json
336
+ git diff --check
337
+ git status --short
338
+ ```
339
+
340
+ Run an independent code review on the final PR HEAD. Resolve every Critical or
341
+ Important finding before merge.
342
+
343
+ ### Acceptance
344
+
345
+ - [ ] Every required change above has an automated regression test.
346
+ - [ ] `npm run verify` exits 0.
347
+ - [ ] Packed artifact smoke exits 0.
348
+ - [ ] Independent review passes on the final HEAD.
349
+ - [ ] The PR is merged into `release/0.10.0` only after Gate B authorization.
350
+
351
+ ## S2: Publish and qualify the final RC
352
+
353
+ ### Goal
354
+
355
+ Publish one immutable candidate containing the corrected qualification policy.
356
+
357
+ ### Version selection
358
+
359
+ Resolve the next unused RC at execution time. The expected version is
360
+ `0.10.0-rc.20`, but if that version exists, choose the next unused integer.
361
+
362
+ ```bash
363
+ npm view opencode-ship versions --json
364
+ ```
365
+
366
+ Set and record:
367
+
368
+ ```bash
369
+ export RC=<next-unused-0.10.0-rc.N>
370
+ ```
371
+
372
+ ### Steps
373
+
374
+ 1. Start from the reviewed and merged `release/0.10.0` head.
375
+ 2. Bump `package.json` and `package-lock.json` to `$RC` without creating a tag.
376
+ 3. Update release-status docs only where the candidate number must appear.
377
+ 4. Run the full local verification.
378
+ 5. Commit and push the release version commit.
379
+ 6. Confirm the release branch HEAD, package version, lock version, and proposed
380
+ tag all match.
381
+ 7. Stop at Gate C and request permission to push the signed release tag.
382
+ 8. After authorization, create and push the canonical no-leading-v signed tag.
383
+ 9. Monitor all release qualification jobs.
384
+ 10. If any job fails, do not reuse the version. Fix source, choose the next RC,
385
+ and repeat S1/S2 verification.
386
+ 11. On success, verify npm registry metadata, provenance, integrity,
387
+ post-publish install smoke, GitHub prerelease metadata, SBOM, tarball hash,
388
+ and qualification artifact.
389
+ 12. Record all evidence in the parent issue.
390
+
391
+ ### Acceptance
392
+
393
+ - [ ] Every qualification job is green on one source tag.
394
+ - [ ] npm `next` points to `$RC`.
395
+ - [ ] Registry integrity equals the qualification report.
396
+ - [ ] GitHub asset digest equals the tarball SHA-256.
397
+ - [ ] GitHub marks the release prerelease.
398
+ - [ ] `runtimeSourceSha256` exists in the qualification artifact.
399
+ - [ ] Registry-sourced `--version` prints `$RC`.
400
+
401
+ ## S3: Neutral registry acceptance
402
+
403
+ ### Goal
404
+
405
+ Prove that a non-Leo project can safely install and operate the published RC.
406
+
407
+ ### Repository fixture
408
+
409
+ The neutral repository is `Viktorxyz/opencode-ship-dogfood`. Re-check whether
410
+ it exists. If absent, stop at Gate A before creating it. Ask the user to choose
411
+ visibility; use private unless the user explicitly requests public evidence.
412
+
413
+ The repository must contain only:
414
+
415
+ - a Git repository on `main` with a GitHub origin;
416
+ - a private, single-dependency Node package;
417
+ - `node --test` as its verification command;
418
+ - a required GitHub status check named `verify`;
419
+ - two ordered implementation tasks used later in S5;
420
+ - an `opencode.json` with `$schema`, `share: "disabled"`, and unrelated sentinel
421
+ fields that must survive install/uninstall;
422
+ - no Leo code, no local opencode-ship link, and no manually copied assets.
423
+
424
+ Use these deterministic dogfood tasks:
425
+
426
+ | Task | User-visible behavior | Test seam |
427
+ |---|---|---|
428
+ | A | Add `formatReleaseLabel(version)` that returns a canonical `v<semver>` label and rejects malformed input | `node:test` unit cases |
429
+ | B | Add a small CLI that prints the canonical label and exits nonzero for malformed input | spawned CLI integration cases |
430
+
431
+ For Task B round 1, the dogfood fixture deliberately omits the nonzero invalid
432
+ input path so the reviewer has one real blocking finding. Round 2 fixes it.
433
+
434
+ ### Preflight
435
+
436
+ Record, without secrets:
437
+
438
+ ```bash
439
+ node --version
440
+ git --version
441
+ gh --version
442
+ gh auth status
443
+ opencode --version
444
+ pnpm --version
445
+ npm --version
446
+ ```
447
+
448
+ Requirements:
449
+
450
+ - Node `>=22.6.0`;
451
+ - OpenCode `>=1.15.5`;
452
+ - authenticated GitHub CLI with repository access;
453
+ - npm and pnpm able to reach the public registry.
454
+
455
+ ### Command wrapper
456
+
457
+ Use the exact accepted RC version, never `latest` or an unpinned local path:
458
+
459
+ ```bash
460
+ ship() {
461
+ pnpm dlx --package="opencode-ship@$RC" opencode-ship "$@"
462
+ }
463
+ ```
464
+
465
+ ### Acceptance matrix
466
+
467
+ #### A1: Root conflict refusal
468
+
469
+ Create a disposable conflicting Build permission in a fixture clone. Run init.
470
+
471
+ Expected: exit 3, conflict reported, existing bytes unchanged, no partial lock
472
+ or managed files.
473
+
474
+ #### A2: Read-only preview
475
+
476
+ Digest the fixture tree, run:
477
+
478
+ ```bash
479
+ ship diff \
480
+ --profile engineering \
481
+ --planner-model openai/gpt-5.6-sol \
482
+ --builder-model minimax/MiniMax-M3 \
483
+ --final-reviewer-model openai/gpt-5.6-sol \
484
+ --json
485
+ ```
486
+
487
+ Expected: exit 1 with `status: changed`; tree digest is unchanged.
488
+
489
+ #### A3: Clean engineering initialization
490
+
491
+ ```bash
492
+ ship init \
493
+ --profile engineering \
494
+ --planner-model openai/gpt-5.6-sol \
495
+ --builder-model minimax/MiniMax-M3 \
496
+ --final-reviewer-model openai/gpt-5.6-sol \
497
+ --force-config \
498
+ --strict-doctor \
499
+ --json
500
+ ```
501
+
502
+ Use `--force-root-config` only in a fixture that has no root OpenCode config.
503
+
504
+ Expected: exit 0; engineering lock/profile, model configuration, managed files,
505
+ root pointers, hashes, and sentinel fields are correct.
506
+
507
+ #### A4: Doctor
508
+
509
+ ```bash
510
+ ship doctor --profile engineering --json
511
+ ```
512
+
513
+ Expected: exit 0 and every required row `noop`.
514
+
515
+ #### A5: Real OpenCode discovery
516
+
517
+ Quit any existing OpenCode session because project config is not hot-reloaded.
518
+ Start a fresh real OpenCode server with the neutral repo as cwd and an isolated
519
+ HOME. Poll `/global/health`; read `/experimental/tool/ids`; assert the exact
520
+ expected custom tool set from the accepted package.
521
+
522
+ Expected: health 200, no config error, exact tool IDs, clean server shutdown.
523
+
524
+ #### A6: Idempotency
525
+
526
+ Run `diff`, `init`, and `update` again at the same version.
527
+
528
+ Expected: diff exit 0/noop; init/update exit 0; no creates, updates, deletes, or
529
+ conflicts; managed bytes and sentinel root config are unchanged.
530
+
531
+ #### A7: Managed-file conflict
532
+
533
+ Modify one managed agent in a disposable fixture and run update.
534
+
535
+ Expected: exit 3; edited bytes and prior lock remain unchanged. Restore the
536
+ file; update then exits 0.
537
+
538
+ #### A8: Upgrade from stable 0.9.0
539
+
540
+ Initialize a fresh fixture using exactly `0.9.0`, then run accepted RC diff,
541
+ update, doctor, and OpenCode discovery.
542
+
543
+ Expected: update exit 0, lock manager version changes to `$RC`, final diff is
544
+ noop, doctor and discovery pass.
545
+
546
+ #### A9: Profile transitions
547
+
548
+ Run `core -> engineering -> core -> engineering`, checking managed files,
549
+ Plan Mode root pointers, lock scope, and root sentinel bytes after every step.
550
+
551
+ Expected: profile-specific files/pointers are added and removed exactly once,
552
+ with no residue or user-field loss.
553
+
554
+ #### A10: Uninstall and restoration
555
+
556
+ ```bash
557
+ ship uninstall --purge-config --json
558
+ ```
559
+
560
+ Expected: exit 0; managed files, lock, and generated ship config are absent;
561
+ user files remain; original root OpenCode config bytes are restored.
562
+
563
+ #### A11: npm invoker
564
+
565
+ Repeat clean core and engineering installs with:
566
+
567
+ ```bash
568
+ npm exec --yes --package="opencode-ship@$RC" -- opencode-ship <command>
569
+ ```
570
+
571
+ Expected: results match pnpm invocation.
572
+
573
+ ### Acceptance
574
+
575
+ - [ ] A1-A11 pass from registry bytes.
576
+ - [ ] No test uses source checkout, local link, or local tarball.
577
+ - [ ] Sanitized evidence is linked in the parent issue.
578
+ - [ ] No consumer-owned root field changed unexpectedly.
579
+
580
+ ## S4: Publish stable 0.10.0
581
+
582
+ ### Goal
583
+
584
+ Publish the accepted candidate runtime as stable `0.10.0` under npm `next`.
585
+
586
+ ### Invariants
587
+
588
+ - No runtime-source file may change after S3 acceptance.
589
+ - The candidate and stable `runtimeSourceSha256` must match.
590
+ - Only package/lock version, release docs, and release metadata may differ.
591
+
592
+ ### Steps
593
+
594
+ 1. Start from the accepted RC source.
595
+ 2. Bump `package.json` and `package-lock.json` to `0.10.0` without tagging.
596
+ 3. Update README, CHANGELOG, and RELEASING status truthfully.
597
+ 4. Run full local verification and compute the runtime-source digest.
598
+ 5. Open the stable release PR into `main` so the eventual tag is reachable
599
+ from `origin/main`.
600
+ 6. Require independent review and all branch CI.
601
+ 7. Stop at Gate B before merging.
602
+ 8. After explicit authorization, merge and fetch the final main SHA.
603
+ 9. Verify final main has version `0.10.0`, expected digest, and a clean tree.
604
+ 10. Stop at Gate C before creating/pushing signed tag `0.10.0`.
605
+ 11. After authorization, push the tag and monitor the full release workflow.
606
+ 12. Verify npm `next`, registry integrity, provenance, GitHub assets, SBOM,
607
+ qualification report, and post-publish registry smoke.
608
+ 13. Record the accepted `0.10.0` runtime-source digest and artifact URLs.
609
+
610
+ ### Acceptance
611
+
612
+ - [ ] `opencode-ship@0.10.0` is publicly installable.
613
+ - [ ] npm `next` points to `0.10.0`.
614
+ - [ ] Stable release metadata is not marked prerelease.
615
+ - [ ] Stable digest equals accepted RC digest.
616
+ - [ ] Full qualification report is public and green.
617
+
618
+ ## S5: Formal real 14-step dogfood
619
+
620
+ ### Goal
621
+
622
+ Prove the complete engineering lifecycle using the npm-published stable
623
+ `0.10.0`, real OpenCode, real GitHub, and the approved real model roles.
624
+
625
+ ### Hard rule
626
+
627
+ Do not substitute `scripts/dogfood-local.mjs`, fake GitHub, fake models, direct
628
+ source imports, or a packed local tarball. Those remain useful pre-release
629
+ tests but do not satisfy S5.
630
+
631
+ ### Environment
632
+
633
+ ```bash
634
+ export DOGFOOD_REPO=/path/to/opencode-ship-dogfood
635
+ export DOGFOOD_PLANNER=openai/gpt-5.6-sol
636
+ export DOGFOOD_BUILDER=minimax/MiniMax-M3
637
+ export DOGFOOD_REVIEWER=openai/gpt-5.6-sol
638
+ ```
639
+
640
+ ### Required sequence
641
+
642
+ 1. Install `opencode-ship@0.10.0` core from npm.
643
+ 2. Upgrade the same repo to engineering with explicit model IDs.
644
+ 3. Restart OpenCode and have the real strong planner generate PlanV2 for dogfood
645
+ Tasks A and B.
646
+ 4. Obtain explicit user approval and persist the immutable plan seal.
647
+ 5. Use typed tools to create the issue, worktree, branch, and draft PR linked
648
+ with `Closes #N`.
649
+ 6. Dispatch Task A to the MiniMax builder; run Spec and Quality task reviews;
650
+ require pass; commit only the reviewed range.
651
+ 7. Trigger native OpenCode compaction after Task A and capture the bounded
652
+ opencode-ship resume context.
653
+ 8. Dispatch Task B round 1 with the controlled missing invalid-input path;
654
+ require the reviewer to return one blocking finding.
655
+ 9. Trigger native compaction during Task B fix work.
656
+ 10. Invoke `ship_resume`; verify the durable ledger resumes the same task,
657
+ revision, review round, and immutable plan hash.
658
+ 11. Test issue-mirror restoration. Before removing local plan storage, stop for
659
+ explicit user confirmation and record the exact disposable path. Remove
660
+ only the dogfood plan copy, then invoke `ship_resume` and prove restoration
661
+ from the verified issue mirror.
662
+ 12. Finish Task B round 2. On one final HEAD, run parallel strong Standards and
663
+ Spec reviews, independent verification, and required `verify` CI. Every
664
+ record must reference the same HEAD and package integrity.
665
+ 13. Invoke `delivery_ready`. Capture evidence that the PR is Ready and unmerged.
666
+ 14. Stop at Gate E. Only after the user says `merge it`, invoke the typed merge,
667
+ re-check fresh same-HEAD gates, squash merge, clean the worktree, downgrade
668
+ to core, then uninstall and prove root-config restoration.
669
+
670
+ ### Required evidence
671
+
672
+ - Issue and PR URLs.
673
+ - npm package version and integrity.
674
+ - Plan revision, plan hash, approval record, and mirror URL.
675
+ - Sanitized model-routing records for planner, builder, and final reviewers.
676
+ - Task A and Task B review records.
677
+ - Compaction snapshots and resume counters.
678
+ - Mirror restoration evidence.
679
+ - Final Standards, Spec, verifier, CI, PR, and Ready common HEAD.
680
+ - Ready-before-merge snapshot.
681
+ - Explicit merge authorization and merge SHA.
682
+ - Cleanup result.
683
+ - Core downgrade and uninstall byte-restoration hashes.
684
+
685
+ ### Acceptance
686
+
687
+ - [ ] All 14 steps complete in order.
688
+ - [ ] No step is simulated.
689
+ - [ ] No context loss changes task identity, plan hash, or review round.
690
+ - [ ] Ready does not merge.
691
+ - [ ] Merge requires a separate explicit user request.
692
+ - [ ] Cleanup and uninstall restoration pass.
693
+
694
+ If any runtime defect appears, stop. Publish a new immutable 0.10 patch/RC as
695
+ appropriate and repeat the complete dogfood. Never promote known-defective
696
+ runtime bytes.
697
+
698
+ ## S6: Promote equivalent 1.0.0 to npm latest
699
+
700
+ ### Goal
701
+
702
+ Publish `1.0.0` with the exact dogfooded runtime-source inputs and make it the
703
+ default npm install.
704
+
705
+ ### Allowed changes from accepted 0.10.0
706
+
707
+ - `package.json` version;
708
+ - `package-lock.json` version;
709
+ - README release status;
710
+ - CHANGELOG release status;
711
+ - RELEASING release status;
712
+ - release metadata that does not affect runtime behavior.
713
+
714
+ No other change is allowed. Any runtime-source change invalidates S5 and
715
+ requires another 0.10 dogfood.
716
+
717
+ ### Steps
718
+
719
+ 1. Create a release branch from the accepted `0.10.0` tag/main state.
720
+ 2. Bump package and lock to `1.0.0` without tagging.
721
+ 3. Update release documentation.
722
+ 4. Run full verification and compute `runtimeSourceSha256`.
723
+ 5. Compare it to the accepted `0.10.0` qualification artifact.
724
+ 6. Open the 1.0 promotion PR into `main`.
725
+ 7. Require independent review and CI.
726
+ 8. Stop at Gate B before merge.
727
+ 9. After authorization, merge and verify final main state.
728
+ 10. Stop at Gate C before creating/pushing signed tag `1.0.0`.
729
+ 11. After authorization, push the tag and monitor all qualification jobs.
730
+ 12. Verify publication under npm `candidate`, provenance, integrity, assets,
731
+ qualification artifact, and registry smoke.
732
+ 13. Re-run clean neutral `init`, `doctor`, real OpenCode discovery, and
733
+ uninstall using exactly `opencode-ship@1.0.0`.
734
+ 14. Stop at Gate F before changing npm `latest`.
735
+ 15. After authorization, run:
736
+
737
+ ```bash
738
+ npm dist-tag add opencode-ship@1.0.0 latest
739
+ ```
740
+
741
+ 16. Verify registry propagation from a clean npm cache and fresh directory.
742
+
743
+ ### Acceptance
744
+
745
+ - [ ] `1.0.0` qualification is green.
746
+ - [ ] `1.0.0` runtime-source digest equals accepted `0.10.0`.
747
+ - [ ] npm provenance and GitHub asset digest match.
748
+ - [ ] Clean registry install and real OpenCode discovery pass.
749
+ - [ ] `npm view opencode-ship dist-tags --json` reports `latest: 1.0.0`.
750
+ - [ ] `pnpm dlx opencode-ship@latest --version` prints `1.0.0`.
751
+
752
+ ## S7: Install real consumers
753
+
754
+ ### Part A: User-selected project
755
+
756
+ Ask the user for the target repository path. Inspect its package manager,
757
+ OpenCode config, Git remote, default branch, verification command, required CI,
758
+ and existing agents/skills before writing.
759
+
760
+ Run read-only preview first:
761
+
762
+ ```bash
763
+ pnpm dlx opencode-ship@latest diff \
764
+ --profile engineering \
765
+ --planner-model openai/gpt-5.6-sol \
766
+ --builder-model minimax/MiniMax-M3 \
767
+ --final-reviewer-model openai/gpt-5.6-sol \
768
+ --json
769
+ ```
770
+
771
+ Resolve every ownership conflict explicitly. Then initialize:
772
+
773
+ ```bash
774
+ pnpm dlx opencode-ship@latest init \
775
+ --profile engineering \
776
+ --planner-model openai/gpt-5.6-sol \
777
+ --builder-model minimax/MiniMax-M3 \
778
+ --final-reviewer-model openai/gpt-5.6-sol \
779
+ --force-config \
780
+ --strict-doctor \
781
+ --json
782
+ ```
783
+
784
+ Use `--force-root-config` only when the target has no root OpenCode config and
785
+ the user approves creating one.
786
+
787
+ Quit and restart OpenCode after installation. Verify doctor, startup, exact
788
+ tool/agent/skill discovery, unchanged user-owned root fields, and one small
789
+ real issue through worktree, PR, review, verification, CI, and Ready. Stop
790
+ before merge unless the user explicitly authorizes it.
791
+
792
+ ### Part B: Leo replacement migration
793
+
794
+ The Leo main worktree contains unrelated design-lab changes. Never touch it.
795
+
796
+ 1. Use a fresh isolated worktree from current remote `main`.
797
+ 2. Create a new Leo GitHub issue for the `opencode-ship@1.0.0` registry
798
+ migration.
799
+ 3. Run registry `diff` and `init --profile engineering` with the approved model
800
+ IDs.
801
+ 4. Never copy managed artifacts manually.
802
+ 5. Reconcile `opencode.json`, `.opencode/ship.config.json`, generated lock,
803
+ package lock, agent task permissions, AGENTS skill names, workflow docs,
804
+ plugin contract tests, and `docs/delivery/STATUS.md`.
805
+ 6. Ensure every installed companion file is present and every managed hash is
806
+ final, not `pending`.
807
+ 7. Remove legacy `opencode-delivery` paths only through supported migration
808
+ behavior or explicit reviewed cleanup.
809
+ 8. Run frozen install from a clean checkout/worktree.
810
+ 9. Run `pnpm verify:workspace`.
811
+ 10. Run canonical delivery reviewer, verifier, and required CI on one final
812
+ HEAD.
813
+ 11. Open the replacement PR and mark Ready only when gates are fresh.
814
+ 12. Close Leo PR #221 as superseded only after the replacement URL exists.
815
+ 13. Stop at Gate G before merging the replacement.
816
+
817
+ ### Acceptance
818
+
819
+ - [ ] User-selected project works with `opencode-ship@latest` engineering.
820
+ - [ ] Leo replacement uses registry-generated `1.0.0` artifacts.
821
+ - [ ] Leo frozen install and workspace verification pass.
822
+ - [ ] PR #221 links to the replacement before closure.
823
+ - [ ] Unrelated design-lab files remain untouched.
824
+
825
+ ## S8: Close release evidence
826
+
827
+ ### Steps
828
+
829
+ 1. Update README install commands to use `opencode-ship@latest` and document
830
+ the engineering model flags/config requirement.
831
+ 2. Move final CHANGELOG items from Unreleased to `1.0.0` with the release date.
832
+ 3. Update RELEASING current-state tables and preserve the historical process.
833
+ 4. Verify GitHub releases, npm tags, registry metadata, and docs agree.
834
+ 5. Add a final bounded evidence comment to the parent issue.
835
+ 6. Close the parent issue only after every Definition of Done checkbox is
836
+ linked to evidence.
837
+ 7. Record any non-blocking follow-up as a new issue, not an unchecked note in
838
+ this plan.
839
+
840
+ ## Failure and recovery rules
841
+
842
+ | Failure | Required response |
843
+ |---|---|
844
+ | Local verification fails | Diagnose and fix before any tag |
845
+ | Release qualification fails before publish | Fix and rerun same unconsumed tag only if no immutable publication occurred; otherwise choose a new RC |
846
+ | npm version exists with different integrity | Stop immediately; never overwrite |
847
+ | Registry propagation times out | Preserve workflow evidence, retry read-only checks, do not republish |
848
+ | Neutral install conflict | Preserve consumer bytes and resolve ownership explicitly |
849
+ | Managed hash conflict | Do not use `--replace-managed` without user review and approval |
850
+ | Real dogfood runtime defect | Publish a corrected 0.10 patch/candidate and repeat S3-S5 |
851
+ | Plan/mirror restoration fails | Stop promotion; preserve issue and local evidence |
852
+ | Same-HEAD gate mismatch | Re-run every invalidated final gate on the new HEAD |
853
+ | Cleanup refuses | Preserve worktree and manifest; diagnose without deleting it manually |
854
+ | 1.0 digest mismatch | Refuse promotion and identify the unauthorized runtime-source change |
855
+
856
+ ## Per-session operating contract
857
+
858
+ At the start of every continuation session:
859
+
860
+ 1. Read this file and the parent issue.
861
+ 2. Inspect current git branch, HEAD, worktree status, npm dist-tags, open PRs,
862
+ release workflows, and the last evidence comment.
863
+ 3. Identify exactly one active slice and mark it in progress in the issue.
864
+ 4. Do not repeat completed irreversible operations.
865
+ 5. Use current registry/remote facts over stale values in this document.
866
+ 6. Execute until the next approval gate, a real blocker after one retry, or the
867
+ slice acceptance criteria are complete.
868
+ 7. Post the evidence envelope before ending the session.
869
+
870
+ ## Suggested skills
871
+
872
+ Load strong matches before acting:
873
+
874
+ - `customize-opencode` for config, plugin, agent, permission, and skill work;
875
+ - `verification-before-completion` before every success claim;
876
+ - `requesting-code-review` before release/release-policy merges;
877
+ - `systematic-debugging` for qualification or registry failures;
878
+ - `test-driven-development` for release-policy and installer regressions;
879
+ - `delivery-workflow` where the consumer has the typed delivery lifecycle;
880
+ - `git-commit` only when the user asks the session to commit;
881
+ - `handoff` when stopping at an approval gate.
882
+
883
+ ## Final report format
884
+
885
+ The executor's final response must include:
886
+
887
+ ```text
888
+ Status: pass | fail | blocked | partial
889
+ Completed through: S<N>
890
+ Source SHA: <40 hex>
891
+ Published versions: <list>
892
+ npm dist-tags: <json>
893
+ Evidence URLs: <issue, PRs, workflows, releases, qualification artifacts>
894
+ Verification: <commands and exit codes>
895
+ Definition of Done: <passed>/<total>
896
+ Remaining risks: <list or none>
897
+ Next authorization: <exact gate or none>
898
+ ```
899
+
900
+ Do not report `complete` until every Definition of Done checkbox is proven.
901
+
902
+ ## Copyable handoff prompt
903
+
904
+ Use this prompt to start or resume execution with a lower-cost Build model:
905
+
906
+ ```text
907
+ Work in /home/viktorcordas/repos/opencode-ship.
908
+
909
+ Goal: Execute the approved opencode-ship 1.0 release plan through the next
910
+ authorization gate, preserving evidence so another session can resume without
911
+ reconstructing context.
912
+
913
+ Mandatory source of truth:
914
+ docs/release/1.0.0-execution-plan.md
915
+
916
+ Before acting:
917
+ 1. Load every strong matching skill, especially customize-opencode,
918
+ verification-before-completion, requesting-code-review, and systematic
919
+ debugging or test-driven-development when applicable.
920
+ 2. Read the entire plan, README.md, CHANGELOG.md, RELEASING.md,
921
+ schema/ship-config.schema.json, .github/workflows/release.yml,
922
+ src/installer/cli-args.js, and tests/package/neutral-consumer.test.mjs.
923
+ 3. Inspect git status/branch/HEAD, npm dist-tags, published versions, open
924
+ issues/PRs, release workflows, and the parent issue's latest evidence.
925
+ 4. If the parent issue does not exist, create or reuse the exact issue required
926
+ by S0 and add its URL to the plan header before release-code changes.
927
+ 5. Identify exactly one active slice. Do not skip ahead or repeat an already
928
+ completed irreversible operation.
929
+
930
+ Fixed decisions:
931
+ - Final target: opencode-ship@1.0.0 on npm latest.
932
+ - Profile: engineering.
933
+ - Planner: openai/gpt-5.6-sol.
934
+ - Builder: minimax/MiniMax-M3.
935
+ - Final reviewer: openai/gpt-5.6-sol.
936
+ - Registry-sourced real dogfood is mandatory.
937
+
938
+ Constraints:
939
+ - Never edit, reset, stash, clean, stage, or commit the dirty Leo main
940
+ worktree. Leo is out of scope until S7 and must use a fresh worktree.
941
+ - Never copy managed plugin/agent/skill files manually into a consumer.
942
+ - Never overwrite, retag, unpublish, or mutate a published npm version.
943
+ - Never use force-push, hard reset, raw Git ref plumbing, or bypass a failed
944
+ review/verification/CI gate.
945
+ - Never expose tokens or credentials in logs, issues, commits, or artifacts.
946
+ - Stop for every authorization gate in the plan. In particular, do not create
947
+ the dogfood repository, merge a PR, push a release tag, publish/move an npm
948
+ dist-tag, or merge dogfood/Leo without explicit user authorization.
949
+ - Restart OpenCode after project config, plugin, agent, command, or skill files
950
+ change.
951
+ - Do not claim completion from unit tests alone.
952
+
953
+ Execution behavior:
954
+ - Work autonomously through reversible steps in the active slice.
955
+ - Verify every change using the commands required by that slice.
956
+ - Run independent review before release-policy or release merges.
957
+ - Update the parent issue with the bounded evidence envelope before stopping.
958
+ - Stop only at an authorization gate, a real blocker after one retry, or when
959
+ the active slice acceptance criteria are complete.
960
+
961
+ Return exactly:
962
+ Status: pass | fail | blocked | partial
963
+ Completed through: S<N>
964
+ Source SHA: <40 hex>
965
+ Published versions: <list>
966
+ npm dist-tags: <json>
967
+ Evidence URLs: <issue, PRs, workflows, releases, qualification artifacts>
968
+ Verification: <commands and exit codes>
969
+ Definition of Done: <passed>/<total>
970
+ Remaining risks: <list or none>
971
+ Next authorization: <exact gate or none>
972
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ship",
3
- "version": "0.10.0-rc.19",
3
+ "version": "0.10.0-rc.28",
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",
@@ -49,6 +49,7 @@
49
49
  "docs",
50
50
  "vendor",
51
51
  "tests/plugin/plugin-load.test.mjs",
52
+ "tests/plugin/expected-tools.mjs",
52
53
  "THIRD_PARTY_NOTICES.md",
53
54
  "README.md",
54
55
  "CHANGELOG.md",
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Canonical opencode-ship tool set.
3
+ *
4
+ * The bundled plugin registers exactly 24 typed tools (9
5
+ * existing delivery + 7 control-plane + 8 workflow). This module
6
+ * is the single source of truth for the expected set so:
7
+ *
8
+ * - the in-process plugin-load test asserts the contract at the
9
+ * plugin boundary;
10
+ * - the opencode-discovery smoke test asserts the contract at
11
+ * the runtime boundary by polling a real opencode server;
12
+ * - the release workflow can fail fast if a future change adds
13
+ * or removes a tool without updating both consumers.
14
+ *
15
+ * Changing this set is a contract change. Both the in-process
16
+ * test and the live-server test must continue to agree.
17
+ */
18
+
19
+ export const EXPECTED_OPENCODE_SHIP_TOOLS = Object.freeze([
20
+ // 9 existing delivery tools
21
+ "delivery_cleanup",
22
+ "delivery_github_read",
23
+ "delivery_inspect",
24
+ "delivery_issue",
25
+ "delivery_issue_close",
26
+ "delivery_issue_comment",
27
+ "delivery_issue_labels",
28
+ "delivery_issue_link",
29
+ "delivery_merge",
30
+ "delivery_pr",
31
+ "delivery_publish",
32
+ "delivery_ready",
33
+ "delivery_review",
34
+ "delivery_sync",
35
+ "delivery_verify",
36
+ "delivery_worktree",
37
+ // 7 control-plane + 8 workflow tools
38
+ "ship_plan_approve",
39
+ "ship_plan_start",
40
+ "ship_plan_submit",
41
+ "ship_resume",
42
+ "ship_run_start",
43
+ "ship_status",
44
+ "ship_task_report",
45
+ "ship_task_review",
46
+ ]);
47
+
48
+ /**
49
+ * Frozen set, sorted lexicographically. The plugin-load and
50
+ * opencode-discovery tests both compare against this exact set.
51
+ */
52
+ export const EXPECTED_OPENCODE_SHIP_TOOL_IDS = Object.freeze(
53
+ [...EXPECTED_OPENCODE_SHIP_TOOLS].sort(),
54
+ );
55
+
56
+ export const OPENCODE_SHIP_TOOL_COUNT = EXPECTED_OPENCODE_SHIP_TOOLS.length;
@@ -7,41 +7,21 @@
7
7
  * - calling it returns an object with a `tool` key
8
8
  * - the `tool` object exposes exactly the 24 named tool
9
9
  * definitions (9 delivery + 7 control-plane + 8 workflow)
10
+ *
11
+ * The canonical 24-tool set is imported from
12
+ * `tests/plugin/expected-tools.mjs`, the single source of truth
13
+ * shared with the opencode-discovery smoke test.
10
14
  */
11
15
 
12
16
  import test from "node:test";
13
17
  import assert from "node:assert/strict";
14
18
  import { pathToFileURL } from "node:url";
15
19
  import { resolve } from "node:path";
20
+ import { EXPECTED_OPENCODE_SHIP_TOOL_IDS, OPENCODE_SHIP_TOOL_COUNT } from "./expected-tools.mjs";
16
21
 
17
22
  const pluginPath = pathToFileURL(resolve("dist/plugin.js")).href;
18
23
 
19
- const EXPECTED_TOOLS = [
20
- "delivery_cleanup",
21
- "delivery_github_read",
22
- "delivery_inspect",
23
- "delivery_issue",
24
- "delivery_issue_close",
25
- "delivery_issue_comment",
26
- "delivery_issue_labels",
27
- "delivery_issue_link",
28
- "delivery_merge",
29
- "delivery_pr",
30
- "delivery_publish",
31
- "delivery_ready",
32
- "delivery_review",
33
- "delivery_sync",
34
- "delivery_verify",
35
- "delivery_worktree",
36
- "ship_plan_approve",
37
- "ship_plan_start",
38
- "ship_plan_submit",
39
- "ship_resume",
40
- "ship_run_start",
41
- "ship_status",
42
- "ship_task_report",
43
- "ship_task_review",
44
- ];
24
+ const EXPECTED_TOOLS = EXPECTED_OPENCODE_SHIP_TOOL_IDS;
45
25
 
46
26
  test("plugin: default export is a function", async () => {
47
27
  const mod = await import(pluginPath);
@@ -60,7 +40,7 @@ test("plugin: registers exactly 24 tools", async () => {
60
40
  const result = await mod.default(fakeCtx);
61
41
  assert.ok(result.tool, "result.tool should exist");
62
42
  const ids = Object.keys(result.tool).sort();
63
- assert.deepEqual(ids, EXPECTED_TOOLS, `expected 24 tools, got ${ids.length}: ${ids.join(", ")}`);
43
+ assert.deepEqual(ids, EXPECTED_TOOLS, `expected ${OPENCODE_SHIP_TOOL_COUNT} tools, got ${ids.length}: ${ids.join(", ")}`);
64
44
  for (const id of ids) {
65
45
  assert.equal(typeof result.tool[id].execute, "function", `${id} should expose an execute function`);
66
46
  assert.equal(typeof result.tool[id].description, "string", `${id} should expose a description`);