release-skill 0.1.1 → 0.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.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/CHANGELOG.md +60 -0
  5. package/INSTALL.md +179 -5
  6. package/INSTALL.zh-CN.md +320 -0
  7. package/README.md +347 -67
  8. package/README.zh-CN.md +318 -59
  9. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/skills/release-help/SKILL.md +7 -4
  12. package/adapters/claude/skills/release-prepare/SKILL.md +11 -1
  13. package/adapters/claude/skills/release-publish/SKILL.md +6 -3
  14. package/adapters/claude/skills/release-reconcile/SKILL.md +1 -1
  15. package/adapters/claude/skills/release-setup/SKILL.md +111 -0
  16. package/adapters/claude/skills/release-verify/SKILL.md +5 -2
  17. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  18. package/adapters/codex/skills/release-help/SKILL.md +7 -4
  19. package/adapters/codex/skills/release-prepare/SKILL.md +11 -1
  20. package/adapters/codex/skills/release-publish/SKILL.md +6 -3
  21. package/adapters/codex/skills/release-reconcile/SKILL.md +1 -1
  22. package/adapters/codex/skills/release-setup/SKILL.md +111 -0
  23. package/adapters/codex/skills/release-verify/SKILL.md +5 -2
  24. package/bin/release-skill.mjs +65 -9
  25. package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  26. package/native/safe-write/prebuilds.json +22 -2
  27. package/native/safe-write/src/safe_write.cc +11 -2
  28. package/package.json +3 -1
  29. package/references/02-project-config.md +54 -3
  30. package/references/05-evidence-and-errors.md +6 -2
  31. package/schemas/release-plan.schema.json +550 -65
  32. package/schemas/release-project.schema.json +398 -29
  33. package/schemas/release-run.schema.json +165 -18
  34. package/skills/release-help/SKILL.md +7 -4
  35. package/skills/release-prepare/SKILL.md +11 -1
  36. package/skills/release-publish/SKILL.md +6 -3
  37. package/skills/release-reconcile/SKILL.md +1 -1
  38. package/skills/release-setup/SKILL.md +111 -0
  39. package/skills/release-verify/SKILL.md +5 -2
  40. package/skills-src/release-help/SKILL.md +7 -4
  41. package/skills-src/release-prepare/SKILL.md +11 -1
  42. package/skills-src/release-publish/SKILL.md +6 -3
  43. package/skills-src/release-reconcile/SKILL.md +1 -1
  44. package/skills-src/release-setup/SKILL.md +111 -0
  45. package/skills-src/release-verify/SKILL.md +5 -2
  46. package/src/adapters/contract.mjs +3 -0
  47. package/src/adapters/git-github.mjs +84 -2
  48. package/src/adapters/plugin-marketplace.mjs +65 -21
  49. package/src/adapters/push-snapshot.mjs +84 -17
  50. package/src/commands/prepare.mjs +223 -20
  51. package/src/commands/publish.mjs +45 -0
  52. package/src/commands/reconcile.mjs +152 -0
  53. package/src/commands/setup.mjs +886 -0
  54. package/src/commands/verify.mjs +122 -26
  55. package/src/core/config.mjs +34 -0
  56. package/src/core/errors.mjs +4 -0
  57. package/src/core/plan.mjs +123 -0
  58. package/src/core/previous-public-baseline.mjs +21 -1
  59. package/src/core/verification-gates.mjs +451 -0
  60. package/src/snapshot/frozen.mjs +89 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # release-skill
2
2
 
3
- [简体中文](README.zh-CN.md)
3
+ [简体中文](README.zh-CN.md) · Installation: [English](INSTALL.md) / [简体中文](INSTALL.zh-CN.md)
4
4
 
5
5
  Release preparation for Claude Code and Codex, with human-edited files kept intact.
6
6
 
@@ -10,28 +10,25 @@ reviewed artifacts first and publishes those same artifacts later; it does not
10
10
  regenerate a README or re-pack the live workspace at the last step.
11
11
 
12
12
  <!-- release-skill:capability:external-write-boundary -->
13
- > **Current boundary:** read-only `assess`, offline `prepare`, frozen Git
14
- > branch/tag, GitHub Release, npm tarball, and Claude/Codex marketplace consumer
15
- > installation verification have passed a local production-equivalent protocol
16
- > sandbox using the real release-skill CLI and frozen artifacts, local bare Git
17
- > remotes, and protocol fakes for `gh`, `npm`, Claude, and Codex. Separate
18
- > isolated local probes have exercised the installed Claude/Codex CLIs; no real
19
- > marketplace or production API was contacted. The tests do not provide
20
- > OS-level network isolation. We
21
- > have not run a real production canary for you; treat the first real release as
22
- > a monitored canary. Real APIs, auth, permissions, rate limits, and eventual
23
- > consistency are outside this sandbox claim. `prepare --online` observes previous
24
- > public baselines (bound mode) and fails closed on drift; remote uniqueness checks
25
- > are deferred to publish global preflight.
13
+ > **Current boundary:** v0.1.1 completed a real production release to GitHub and
14
+ > npm, followed by exact npm installation and Claude/Codex consumer installation
15
+ > verification from the frozen Git ref. The same workflow also has a local
16
+ > production-equivalent protocol suite using the real release-skill CLI and
17
+ > frozen artifacts, local bare Git remotes, and protocol fakes for `gh`, `npm`,
18
+ > Claude, and Codex. The suite does not provide OS-level network isolation, and
19
+ > it does not prove that another project's credentials, permissions, rate limits,
20
+ > or eventual-consistency behavior will match this release. Treat each project's
21
+ > first production run as a monitored canary. `prepare --online` observes bound
22
+ > previous-public baselines and fails closed on drift; remote uniqueness checks
23
+ > run during publish global preflight.
26
24
 
27
25
  <!-- release-skill:capability:safe-first-command -->
28
- > **v0.1.1 release candidate:** until `npm view release-skill version` returns
29
- > `0.1.1`, use the source-checkout command below; do not assume the npm command
30
- > already exists.
26
+ > **v0.1.1 production verified.** The npm-installed CLI is the supported user
27
+ > entry. Source checkout is the development/contributor fallback.
31
28
  >
32
29
  > **Start here:**
33
- > - published package: `release-skill help`
34
- > - current release candidate: `node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs" help`
30
+ > - npm install: `npm install -g release-skill` → `release-skill help`
31
+ > - source checkout: `node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs" help`
35
32
 
36
33
  <!-- release-skill:maturity:v0.1-boundary -->
37
34
  <!-- release-skill:maturity:boundary -->
@@ -70,12 +67,7 @@ truth, and never rewrite human truth.**
70
67
  - Git 2.30+
71
68
  - A target Git repository with at least one commit
72
69
 
73
- > **v0.1.1 release candidate:** before the production release completes, the
74
- > npm registry may return 404. Use the source-checkout path below for review.
75
- > After `npm view release-skill version` returns `0.1.1`, the npm-installed CLI
76
- > is the supported user entry and the source checkout is a development fallback.
77
-
78
- **Install from npm (supported after v0.1.1 publication):**
70
+ **Install from npm (recommended):**
79
71
 
80
72
  ```bash
81
73
  npm install -g release-skill
@@ -93,7 +85,7 @@ npx release-skill help
93
85
  release-skill help
94
86
  ```
95
87
 
96
- **Development install (from source checkout):**
88
+ **Development install (contributor fallback, from source checkout):**
97
89
 
98
90
  Set the checkout location and install dependencies:
99
91
 
@@ -105,8 +97,6 @@ npm exec --yes pnpm@10.17.1 -- install --frozen-lockfile
105
97
 
106
98
  Then use the CLI via `node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs"`.
107
99
 
108
- Create `.release-skill/project.yaml` in the target project:
109
-
110
100
  First keep local plans, approvals, and frozen artifacts out of Git:
111
101
 
112
102
  ```gitignore
@@ -114,7 +104,142 @@ First keep local plans, approvals, and frozen artifacts out of Git:
114
104
  !.release-skill/project.yaml
115
105
  ```
116
106
 
117
- Then create the configuration. npm visibility must be explicit:
107
+ ### First use: discover, then let a human finalize
108
+
109
+ When the project has no configuration, start with read-only setup. It scans
110
+ packages, plugin manifests, Git remotes, legacy `public-release.json`, and
111
+ quality scripts. It reports release-unit, tag, branch, previous-baseline, and
112
+ verification-gate candidates, but never treats README prose as instructions or
113
+ automatically selects or executes a script:
114
+
115
+ ```bash
116
+ release-skill setup --root /absolute/path/to/my-project --json
117
+ ```
118
+
119
+ `NEEDS_INPUT` and `LOCAL_ONLY_DETECTED` intentionally exit with code 2. They
120
+ are unresolved decision states, not a crash. Automation should inspect the JSON
121
+ `status` instead of treating every nonzero setup result as an internal failure.
122
+
123
+ After review, create an answers JSON containing the complete `projectConfig`.
124
+ Its `selectedGateIds` must exactly match `projectConfig.verificationGates[].id`;
125
+ use an explicit empty array when no project-specific gate is selected. Dry-run
126
+ again with those answers, review the new `setupDigest`, then create the config:
127
+
128
+ ```json
129
+ {
130
+ "projectConfig": {
131
+ "apiVersion": "release-skill/v1",
132
+ "kind": "ReleaseProject",
133
+ "project": { "name": "my-project", "defaultBranch": "main" },
134
+ "releaseUnits": [{
135
+ "id": "my-project",
136
+ "source": ".",
137
+ "publicRepo": "owner/my-project",
138
+ "version": { "source": "package.json", "tagTemplate": "v{version}" },
139
+ "distributions": [{
140
+ "type": "npm",
141
+ "package": "my-project",
142
+ "access": "public",
143
+ "provenance": false,
144
+ "tag": "latest",
145
+ "registry": "https://registry.npmjs.org",
146
+ "publisher": "my-npm-username"
147
+ }],
148
+ "publicFiles": [
149
+ { "from": "README.md", "to": "README.md", "mode": "preserve" },
150
+ { "from": "package.json", "to": "package.json", "mode": "preserve" }
151
+ ],
152
+ "requiredPublicFiles": ["README.md", "package.json"],
153
+ "previousPublicBaseline": { "mode": "none" },
154
+ "production": {
155
+ "branchTemplate": "release/{tag}",
156
+ "branchStrategy": "create-release-branch"
157
+ }
158
+ }]
159
+ },
160
+ "selectedGateIds": []
161
+ }
162
+ ```
163
+
164
+ This is a complete schema-shaped wrapper, not authoritative project data.
165
+ Replace every repository, channel, baseline, and public-file decision with the
166
+ reviewed facts from your project; use `mode: none` only when no public version
167
+ exists.
168
+
169
+ To select one reported gate, keep the same complete `projectConfig`, add a
170
+ top-level `verificationGates` array inside it, and make the wrapper's
171
+ `selectedGateIds` identical. For example, when dry-run reported
172
+ `my-project-script-test`:
173
+
174
+ ```json
175
+ {
176
+ "projectConfig": {
177
+ "apiVersion": "release-skill/v1",
178
+ "kind": "ReleaseProject",
179
+ "project": { "name": "my-project", "defaultBranch": "main" },
180
+ "releaseUnits": [{
181
+ "id": "my-project",
182
+ "source": ".",
183
+ "publicRepo": "owner/my-project",
184
+ "version": { "source": "package.json", "tagTemplate": "v{version}" },
185
+ "distributions": [{
186
+ "type": "npm",
187
+ "package": "my-project",
188
+ "access": "public",
189
+ "provenance": false,
190
+ "tag": "latest",
191
+ "registry": "https://registry.npmjs.org",
192
+ "publisher": "my-npm-username"
193
+ }],
194
+ "publicFiles": [
195
+ { "from": "package.json", "to": "package.json", "mode": "preserve" }
196
+ ],
197
+ "requiredPublicFiles": ["package.json"],
198
+ "previousPublicBaseline": { "mode": "none" },
199
+ "production": {
200
+ "branchTemplate": "release/{tag}",
201
+ "branchStrategy": "create-release-branch"
202
+ }
203
+ }],
204
+ "verificationGates": [{
205
+ "id": "my-project-script-test",
206
+ "phase": "snapshot-verify",
207
+ "scope": { "unit": "my-project" },
208
+ "command": ["node", "-e", "const p=require('./package.json');if(!p.name)process.exit(1)"],
209
+ "cwd": ".",
210
+ "timeoutMs": 30000,
211
+ "envAllowlist": []
212
+ }]
213
+ },
214
+ "selectedGateIds": ["my-project-script-test"]
215
+ }
216
+ ```
217
+
218
+ The id must be copied from the current `gateCandidates`; do not invent one.
219
+ The example command is self-contained in the public snapshot. A project script
220
+ is valid only when the script and every dependency it needs are included in
221
+ `publicFiles`; a snapshot gate cannot see the parent workspace's tests,
222
+ development dependencies, or `node_modules` unless they are explicitly public.
223
+
224
+ ```bash
225
+ release-skill setup --root /absolute/path/to/my-project \
226
+ --answers /absolute/path/to/setup-answers.json --json
227
+ release-skill setup --root /absolute/path/to/my-project \
228
+ --answers /absolute/path/to/setup-answers.json \
229
+ --write --confirm-setup <setupDigest> --json
230
+ ```
231
+
232
+ Setup atomically creates only an absent `.release-skill/project.yaml`.
233
+ That create-once step uses the digest-registered `darwin-arm64` native
234
+ prebuild shipped in v0.1.3; unsupported platforms fail closed with
235
+ `SAFE_WRITE_UNAVAILABLE` instead of falling back to path-based writes.
236
+ `ALREADY_CONFIGURED`/`CONFIG_EXISTS` means the existing file remains
237
+ human-owned and must be edited incrementally. README, slogans, CHANGELOG, and
238
+ business scripts are never generated or overwritten. A project with no remote
239
+ channel reports `LOCAL_ONLY_DETECTED` instead of inventing production support.
240
+
241
+ The following is a minimal human-authored configuration. npm visibility,
242
+ public-file boundaries, and remote targets must be explicit:
118
243
 
119
244
  ```yaml
120
245
  apiVersion: release-skill/v1
@@ -162,6 +287,7 @@ releaseUnits:
162
287
  # status: READY
163
288
  production:
164
289
  branchTemplate: release/{tag}
290
+ branchStrategy: create-release-branch
165
291
  releaseTitleTemplate: "{unit} {version}"
166
292
  releaseNotes: "Human-maintained release notes"
167
293
  ```
@@ -192,6 +318,58 @@ After `merge` or `adopt`, rebind `previousPublicBaseline` to the accepted
192
318
  immutable `repo`/`ref`/`commit`, then run a new `prepare --online --production`,
193
319
  review, and approval.
194
320
 
321
+ Choose a branch strategy that matches the real repository:
322
+
323
+ - `create-release-branch` creates an absent immutable release branch and stops
324
+ if the name already exists.
325
+ - `advance-existing-branch` creates a single-parent commit on the exact
326
+ `previousPublicBaseline` commit and permits only an ordinary fast-forward
327
+ push; concurrent drift requires human intervention.
328
+ - `initialize-default-branch` creates an absent standard branch under control.
329
+ Only explicit `setAsDefaultBranch` and `expectedCurrentDefaultBranch` values
330
+ add a separately approved, observed, and reconcilable default-branch action.
331
+
332
+ Minimal configurations for the three strategies are:
333
+
334
+ ```yaml
335
+ # New immutable release branch; the target must not exist.
336
+ previousPublicBaseline: { mode: none } # only for a true first public release
337
+ production:
338
+ branchTemplate: release/{tag}
339
+ branchStrategy: create-release-branch
340
+ ```
341
+
342
+ ```yaml
343
+ # Advance main; the bound ref must be exactly the target branch.
344
+ previousPublicBaseline:
345
+ mode: bound
346
+ repo: owner/my-project
347
+ ref: refs/heads/main
348
+ commit: 0123456789abcdef0123456789abcdef01234567
349
+ production:
350
+ branchTemplate: main
351
+ branchStrategy: advance-existing-branch
352
+ ```
353
+
354
+ ```yaml
355
+ # One-time creation of an absent main and an explicit default-branch switch.
356
+ previousPublicBaseline:
357
+ mode: bound
358
+ repo: owner/my-project
359
+ ref: refs/heads/old-public-branch
360
+ commit: 0123456789abcdef0123456789abcdef01234567
361
+ production:
362
+ branchTemplate: main
363
+ branchStrategy: initialize-default-branch
364
+ setAsDefaultBranch: true
365
+ expectedCurrentDefaultBranch: old-public-branch
366
+ ```
367
+
368
+ The latter two require `prepare --online --production`. If the observed branch,
369
+ commit, target absence, or current default branch differs, stop and update the
370
+ human-owned source/config only after reviewing the real remote state; never
371
+ force-push or weaken the baseline.
372
+
195
373
  This is a mechanics-only local example, not a complete npm publication map.
196
374
  Before a real release, enumerate every public runtime file, executable, type
197
375
  declaration, image, and linked document. In a monorepo, set `source` to a path
@@ -206,48 +384,71 @@ causes baseline validation to stop.
206
384
 
207
385
  ### Main workflow
208
386
 
209
- Run these steps in order. Steps 1–3 are safe default (read-only or local-only);
210
- steps 48 are production publishing with explicit human gates.
387
+ Run these steps in order. Steps 1–4 are safe default (read-only or local-only);
388
+ steps 59 are production publishing with explicit human gates.
211
389
 
212
390
  ```bash
213
- # Current v0.1.1 release candidate:
214
- CLI=(node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs")
391
+ # npm-installed CLI (recommended):
392
+ CLI=(release-skill)
215
393
  PROJECT=/absolute/path/to/my-project
216
- # After npm view reports 0.1.1 and the package is installed:
217
- # CLI=(release-skill)
394
+ ACTOR=your-name
395
+ # Development fallback (source checkout):
396
+ # CLI=(node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs")
218
397
  ```
219
398
 
220
- The source-checkout entry remains the candidate default until npm publication is
221
- verified; after that, the installed npm entry is the supported user default.
399
+ The npm-installed CLI is the supported user entry after v0.1.1 production
400
+ publication. The source checkout remains the development/contributor fallback.
222
401
 
223
402
  1. **Environment check:**
224
403
  ```bash
225
404
  "${CLI[@]}" help
226
405
  ```
227
- 2. **Readiness assessment (read-only):**
406
+ 2. **First-use setup (only when config is absent; read-only):**
407
+ ```bash
408
+ "${CLI[@]}" setup --root "$PROJECT" --json
409
+ ```
410
+ Complete the answers and exact `setupDigest` confirmation described above;
411
+ skip this step when configuration already exists.
412
+ 3. **Readiness assessment (read-only):**
228
413
  ```bash
229
414
  "${CLI[@]}" assess --root "$PROJECT" --offline --json
230
415
  ```
231
- 3. **Local snapshot and plan freeze:**
416
+ 4. **Local snapshot and plan freeze:**
232
417
  ```bash
233
- "${CLI[@]}" prepare --root "$PROJECT" --offline --json
418
+ "${CLI[@]}" prepare --root "$PROJECT" --offline \
419
+ --acknowledge-hook-side-effects \
420
+ --acknowledge-gate-side-effects --json
234
421
  ```
235
- 4. **Human review:** inspect the returned `planPath`, `externalActions`,
422
+ Omit an acknowledgement only when that project config has no corresponding
423
+ hook or snapshot gate. Never grant either acknowledgement before reviewing
424
+ the configured executable, arguments, working directory, and side effects.
425
+ 5. **Human review:** inspect the returned `planPath`, `externalActions`,
236
426
  `units[].targetVersion`, and `planDigest`. Each unit's snapshot is under
237
- `<evidenceDir>/snapshots/<unit-id>/`. The command writes only local release
238
- data under `.release-skill/`.
239
- 5. **Production plan freeze:**
427
+ `<evidenceDir>/snapshots/<unit-id>/`. The release-skill pipeline writes its
428
+ own data under `.release-skill/`; acknowledged project hooks and gates are
429
+ unsandboxed processes and may write elsewhere or access the network.
430
+ 6. **Production plan freeze:**
240
431
  ```bash
241
- "${CLI[@]}" prepare --root "$PROJECT" --online --production --json
432
+ PRODUCTION_JSON=$("${CLI[@]}" prepare --root "$PROJECT" --online --production \
433
+ --acknowledge-hook-side-effects \
434
+ --acknowledge-gate-side-effects --json)
435
+ printf '%s\n' "$PRODUCTION_JSON" | jq .
436
+ PLAN_PATH=$(printf '%s\n' "$PRODUCTION_JSON" | jq -r '.planPath')
437
+ PLAN_DIGEST=$(printf '%s\n' "$PRODUCTION_JSON" | jq -r '.planDigest')
242
438
  ```
439
+ As above, omit only acknowledgements that are not required by the project
440
+ config, and review every configured process before granting them.
243
441
  Review the new plan's externalActions, npm policy, branch/tag, and frozen
244
442
  digests. `prepare --json` returns the immutable production authority as
245
443
  `<project>/.release-skill/plans/<planDigest>.json`; always carry that returned
246
444
  `planPath` forward. `.release-skill/release-plan.json` is only a mutable
247
445
  convenience alias and must not be passed to production approve/publish/reconcile.
248
- 6. **Approval:**
446
+ 7. **Approval:**
249
447
  ```bash
250
- "${CLI[@]}" approve --plan <planPath> --digest <planDigest> --actor <name> --json
448
+ APPROVAL_JSON=$("${CLI[@]}" approve --plan "$PLAN_PATH" \
449
+ --digest "$PLAN_DIGEST" --actor "$ACTOR" --json)
450
+ printf '%s\n' "$APPROVAL_JSON" | jq .
451
+ APPROVAL_PATH=$(printf '%s\n' "$APPROVAL_JSON" | jq -r '.approvalPath')
251
452
  ```
252
453
  Returns the immutable production authority as `approvalPath` at
253
454
  `<project>/.release-skill/approvals/<planDigest>/<approvalDigest>.json`.
@@ -257,18 +458,28 @@ verified; after that, the installed npm entry is the supported user default.
257
458
  hours; a PARTIAL recovery may create a new approval for the same plan while
258
459
  preserving every earlier approval byte-for-byte. Use the returned
259
460
  `approvalPath` and `expiresAt` as authority.
260
- 7. **Publish (remote writes start here):**
461
+ 8. **Publish (remote writes start here):**
261
462
  ```bash
262
- "${CLI[@]}" publish --root "$PROJECT" \
263
- --plan <planPath> --approval <approvalPath> \
264
- --confirm-production <planDigest> --json
463
+ PUBLISH_JSON=$("${CLI[@]}" publish --root "$PROJECT" \
464
+ --plan "$PLAN_PATH" --approval "$APPROVAL_PATH" \
465
+ --confirm-production "$PLAN_DIGEST" --json)
466
+ printf '%s\n' "$PUBLISH_JSON" | jq .
467
+ PUBLISH_RUN_PATH=$(printf '%s\n' "$PUBLISH_JSON" | jq -r '.runPath')
265
468
  ```
266
469
  Save the returned `runPath`. `PUBLISHED` is **not** the terminal state.
267
- 8. **Verify (consumer install check):**
470
+ 9. **Verify (consumer install check):**
268
471
  ```bash
269
472
  "${CLI[@]}" verify --root "$PROJECT" \
270
- --plan <planPath> --run <publishRunPath> --json
473
+ --plan "$PLAN_PATH" --run "$PUBLISH_RUN_PATH" \
474
+ --acknowledge-gate-side-effects --json
271
475
  ```
476
+ Omit the acknowledgement only when the plan has neither consumer gates nor
477
+ a configured npm `smokeBin`. Both execute installed project code without an
478
+ OS or network sandbox.
479
+
480
+ The handoff example requires `jq`. Without it, copy the same four returned JSON
481
+ fields exactly; do not pass the angle-bracket labels shown elsewhere as shell
482
+ syntax.
272
483
 
273
484
  Production prepare seals a standalone Git commit/tree for every public snapshot
274
485
  and creates a fixed tarball for every npm unit. Publish globally preflights all
@@ -411,17 +622,24 @@ Instead, use `reconcile` to inspect actual remote state, skip already-consistent
411
622
  steps, and safely retry incomplete actions:
412
623
 
413
624
  ```bash
414
- "${CLI[@]}" reconcile --root "$PROJECT" \
415
- --run <publishRunPath> \
416
- --plan <planPath> \
417
- --approval <approvalPath> \
418
- --confirm-production <planDigest> \
419
- --json
625
+ RECONCILE_JSON=$("${CLI[@]}" reconcile --root "$PROJECT" \
626
+ --run "$PUBLISH_RUN_PATH" \
627
+ --plan "$PLAN_PATH" \
628
+ --approval "$APPROVAL_PATH" \
629
+ --confirm-production "$PLAN_DIGEST" --json)
630
+ printf '%s\n' "$RECONCILE_JSON" | jq .
631
+ RECONCILE_RUN_PATH=$(printf '%s\n' "$RECONCILE_JSON" | jq -r '.runPath')
420
632
  # Save reconcile's new runPath, then perform the fresh install verification.
421
633
  "${CLI[@]}" verify --root "$PROJECT" \
422
- --plan <planPath> --run <reconcileRunPath> --json
634
+ --plan "$PLAN_PATH" --run "$RECONCILE_RUN_PATH" \
635
+ --acknowledge-gate-side-effects --json
423
636
  ```
424
637
 
638
+ Omit the verify acknowledgement only when the frozen plan has neither consumer
639
+ gates nor an npm `smokeBin`. The variables above are the exact values captured
640
+ by the main flow; if approval expired during recovery, create a fresh approval
641
+ for the same immutable plan and replace `APPROVAL_PATH` before reconcile.
642
+
425
643
  `reconcile` queries the actual remote state (Git refs, npm version, GitHub
426
644
  Release, marketplace install), skips any step whose evidence already matches
427
645
  the frozen plan, and retries only safe and incomplete steps. Remote conflicts
@@ -435,6 +653,10 @@ Successful reconcile returns `PUBLISHED`, not `VERIFIED`; only the fresh
435
653
  - validates project configuration and release units;
436
654
  - reports readiness without changing the project during `assess`;
437
655
  - copies configured public files into an isolated snapshot;
656
+ - discovers first-use candidates read-only and creates a config only once after
657
+ exact `setupDigest` confirmation;
658
+ - runs human-selected project gates in frozen-snapshot copies and exact
659
+ consumer installation roots;
438
660
  - checks required files, path safety, exact bytes/modes, and obvious leaks;
439
661
  - records Git/workspace identity and freezes a digest-bound release plan;
440
662
  - binds approval to the plan digest, expiry, and explicit action allowlist;
@@ -447,6 +669,52 @@ Successful reconcile returns `PUBLISHED`, not `VERIFIED`; only the fresh
447
669
  - stops subsequent checkpoints on failure and writes a separate run record
448
670
  without mutating the frozen plan or undoing successful remote actions.
449
671
 
672
+ ## Project-specific verification: hooks and gates
673
+
674
+ `hooks.docs/build/test/typecheck/lint` run before the snapshot is frozen. Use
675
+ them only for work that genuinely needs the parent workspace or generates
676
+ source files. They can modify files or access the network, so prepare requires
677
+ `--acknowledge-hook-side-effects`.
678
+
679
+ `verificationGates` are the controlled extension point for release calibration:
680
+
681
+ ```yaml
682
+ verificationGates:
683
+ - id: package-contract
684
+ phase: snapshot-verify
685
+ scope: { unit: my-project }
686
+ command:
687
+ - node
688
+ - -e
689
+ - "const p=require('./package.json'); if (!p.name) process.exit(1)"
690
+ cwd: .
691
+ timeoutMs: 120000
692
+ envAllowlist: [CI]
693
+ - id: installed-help
694
+ phase: consumer-verify
695
+ scope: { unit: my-project, distribution: npm }
696
+ command: [node, scripts/check-installed-help.mjs]
697
+ cwd: .
698
+ timeoutMs: 30000
699
+ envAllowlist: []
700
+ expectedJson: { status: READY }
701
+ ```
702
+
703
+ The snapshot example is deliberately self-contained and reads only a mapped
704
+ public file. Any replacement script and every dependency it needs must exist
705
+ in the frozen public snapshot. The consumer script must likewise be present in
706
+ the exact installed distribution; gates cannot borrow tests, development
707
+ dependencies, or `node_modules` from the parent workspace.
708
+
709
+ `snapshot-verify` runs in a disposable writable copy of the frozen public
710
+ snapshot. `consumer-verify` runs from an exact isolated npm/Claude/Codex install
711
+ root. Both use executable arrays instead of shell strings; definitions and
712
+ results enter digest-bound evidence, and prepare/verify require
713
+ `--acknowledge-gate-side-effects`. Gates are still project processes without a
714
+ network sandbox, so release-skill cannot promise that they will not write files
715
+ or access the network. Push, tag, default-branch changes, GitHub Releases, and
716
+ npm publish may never be hooks/gates; they remain controlled plan actions.
717
+
450
718
  ## What it does not do yet
451
719
 
452
720
  <!-- release-skill:capability:unsupported-scope -->
@@ -455,17 +723,22 @@ Successful reconcile returns `PUBLISHED`, not `VERIFIED`; only the fresh
455
723
  - no claim that a real production canary has run for marketplace verification;
456
724
  - `prepare --online` observes previous public baselines (bound mode) and defers
457
725
  remote uniqueness checks to publish global preflight;
458
- - no force push, overwrite of branches/tags/releases, or npm unpublish;
726
+ - no overwrite of branches/tags/releases or npm unpublish; create-only refs use
727
+ `--force-with-lease=<ref>:` solely as an atomic compare-and-set assertion that
728
+ the ref is absent, while existing branches use an ordinary non-force push;
459
729
  - no promise of Windows or broad multi-platform native write support;
460
730
  - no hidden commit, push, tag, release, or package publication.
461
731
 
462
732
  ### Write Safety
463
733
 
464
- `assess` is read-only unless an explicit report output is requested. `prepare`
734
+ `setup` is read-only by default and may create a config only once after exact
735
+ digest confirmation. `assess` is read-only unless an explicit report output is requested. `prepare`
465
736
  writes local files under `.release-skill/`; it does not write project source
466
737
  files or remote services. If hooks are configured, they are arbitrary local
467
738
  processes and require `--acknowledge-hook-side-effects`; hooks may have their
468
- own filesystem or network side effects. `publish` is the production write entry
739
+ own filesystem or network side effects. Gates are also project processes and
740
+ require `--acknowledge-gate-side-effects`; they may have the same side effects.
741
+ `publish` is the production write entry
469
742
  and requires both approval and the current plan digest. Omit hooks and use local
470
743
  sandbox targets for the smallest safe rehearsal.
471
744
 
@@ -473,12 +746,18 @@ sandbox targets for the smallest safe rehearsal.
473
746
 
474
747
  | Result | What to do |
475
748
  |---|---|
749
+ | `NEEDS_INPUT` | Complete setup's repository, tag, channel, baseline, and gate decisions. |
750
+ | `LOCAL_ONLY_DETECTED` | Establish a remote channel or keep only a local configuration design; do not claim production readiness. |
751
+ | `SETUP_DIGEST_MISMATCH` | Facts or answers changed; rerun dry-run, review, and confirm the new digest. |
752
+ | `CONFIG_EXISTS` | Setup never overwrites the existing config; assess it and edit incrementally. |
753
+ | `SAFE_WRITE_UNAVAILABLE` | Automatic create-once setup is unsupported on this platform; keep the dry-run report and create the reviewed config manually without overwriting an existing file. |
476
754
  | `CONFIG_INVALID` | Correct `.release-skill/project.yaml`, then rerun `assess`. |
477
755
  | `PUBLIC_FILE_MISSING` | Add or correct the configured public file. |
478
756
  | `FORBIDDEN_CONTENT_DETECTED` | Remove the leaked/private content, then prepare again. |
479
757
  | `SNAPSHOT_FIDELITY_FAILED` | Inspect the source/snapshot path and rerun `prepare`. |
480
758
  | `BASELINE_CHANGED` | Keep the human edit, then prepare, review, and approve again. |
481
- | `GATE_FAILED` | Inspect frozen artifacts, auth, remote uniqueness, and digest confirmation. |
759
+ | `GATE_FAILED` during `prepare` | Fix the snapshot gate or frozen public artifact, then run a new `prepare`; the failed plan cannot be approved. |
760
+ | `GATE_FAILED` during `verify` | If the consumer environment failed, repair it and rerun `verify` from the same `PUBLISHED` run. If the published artifact is defective, release a new patch version; never overwrite it. |
482
761
  | `PARTIAL` | Do not restart or delete remote state; review the returned `runPath` and run `reconcile` (see above). |
483
762
  | `PUBLISHED` | Run `verify --plan <planPath> --run <publishRunPath>`; this is not terminal success. |
484
763
  | `VERIFIED` | Remote state, exact npm install, and configured plugin consumer installs all matched the frozen plan. |
@@ -486,15 +765,16 @@ sandbox targets for the smallest safe rehearsal.
486
765
  ## Skills
487
766
 
488
767
  - `release-help`: environment check and next-step guidance.
768
+ - `release-setup`: read-only discovery, human calibration, and create-once first-use configuration.
489
769
  - `release-assess`: read-only release readiness report.
490
770
  - `release-prepare`: local snapshot and reviewable release plan.
491
771
  - `release-publish`: approved, digest-confirmed frozen GitHub+npm publishing.
492
772
  - `release-reconcile`: evidence-based PARTIAL recovery with human intervention on conflicts.
493
773
  - `release-verify`: post-publish verification; only `VERIFIED` is the happy end.
494
774
 
495
- Conflicts still default to human intervention. Before v0.1.1 is published, use
496
- the source CLI shown above; after registry verification, the supported user
497
- entry is the npm-installed `release-skill` CLI.
775
+ Conflicts still default to human intervention. The npm-installed `release-skill`
776
+ CLI is the supported user entry after v0.1.1 production publication; source
777
+ checkout remains the development/contributor fallback.
498
778
 
499
779
  ## License
500
780