release-skill 0.2.3 → 0.2.5
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +266 -913
- package/README.zh-CN.md +224 -535
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/claude/schemas/release-plan.schema.json +228 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +155 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/codex/schemas/release-plan.schema.json +228 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +155 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/kimi/schemas/release-plan.schema.json +228 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +155 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/workbuddy/schemas/release-plan.schema.json +228 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +155 -2
- package/bin/release-skill.bundle.mjs +19805 -17556
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +228 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +155 -2
- package/scripts/sync-public-files.mjs +20 -9
- package/src/adapters/plugin-marketplace.mjs +1237 -403
- package/src/commands/prepare.mjs +454 -40
- package/src/commands/publish.mjs +169 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +454 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +158 -6
- package/src/core/skill-resource-closure.mjs +425 -0
- package/src/platforms/codebuddy.mjs +206 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +191 -119
- package/src/platforms/registry.mjs +24 -6
package/README.md
CHANGED
|
@@ -2,34 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](README.zh-CN.md) · Installation: [English](INSTALL.md) / [简体中文](INSTALL.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
<!-- release-skill:release-version: 0.2.
|
|
6
|
-
Release preparation for Claude Code, Codex, and Kimi Code, with human-edited files kept intact.
|
|
5
|
+
<!-- release-skill:release-version: 0.2.5 -->
|
|
6
|
+
Release preparation for Claude Code, CodeBuddy, WorkBuddy, Codex, and Kimi Code, with human-edited files kept intact.
|
|
7
7
|
|
|
8
8
|
release-skill helps a maintainer answer three questions: what will be released,
|
|
9
|
-
which checks still fail, and which exact bytes will reach users.
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
which checks still fail, and which exact bytes will reach users. release-skill
|
|
10
|
+
does not regenerate or rewrite project source files. `prepare` copies each
|
|
11
|
+
configured public file into an isolated snapshot and verifies the copied bytes —
|
|
12
|
+
it freezes the reviewed artifacts first and publishes those same artifacts later.
|
|
13
|
+
Setup surfaces only the deterministic `compactSummary` review view; the full
|
|
14
|
+
report stays in a temporary session directory.
|
|
12
15
|
|
|
13
16
|
<!-- release-skill:managed:start id=latest-release -->
|
|
14
|
-
**0.2.
|
|
17
|
+
**0.2.5** (2026-07-28)
|
|
15
18
|
|
|
16
|
-
v0.2.
|
|
19
|
+
v0.2.5 adds a built-in skill resource-closure release gate. Release Skill now validates the actual frozen and installed plugin projections instead of treating repository-level file presence as proof that every skill can resolve its resources.
|
|
20
|
+
|
|
21
|
+
**Added**
|
|
22
|
+
|
|
23
|
+
- **Skill resource-closure gate**: recursively discovers nested skills and validates skill-local references, assets, schemas, examples, and private scripts from each skill root.
|
|
24
|
+
- **Frozen and installed receipts**: prepare records closure receipts for every distribution surface, publish rechecks the frozen artifact before external writes, and verify requires matching receipts from real npm and plugin installation roots.
|
|
25
|
+
- **Adversarial path checks**: rejects current-working-directory fallbacks, source-tree backjumps, absolute machine paths, path escape, symbolic links, and non-regular resource targets.
|
|
17
26
|
|
|
18
27
|
**Changed**
|
|
19
28
|
|
|
20
|
-
- **
|
|
29
|
+
- **Shared runtime resolution is explicit**: plugin-level scripts and cross-skill resources must resolve from a validated plugin root; bounded source-only build tools remain auditable exceptions.
|
|
30
|
+
- **Kimi Code and CodeBuddy verification tightened**: consumer verification plans now bind to actual isolated or managed plugin installation paths.
|
|
31
|
+
- **Legacy plan compatibility preserved**: existing frozen plans remain readable, while newly prepared plans require resource-closure evidence.
|
|
21
32
|
|
|
22
33
|
**Fixed**
|
|
23
34
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **CodeBuddy attestation baseline exclusion**: `.release-skill/codebuddy-attestations/` is now properly excluded from workspace baseline digest calculation, preventing self-drift when codebuddy attestation files are written during the release lifecycle.
|
|
27
|
-
- **CodeBuddy manifest skills field accepts array**: `normalizeCodeBuddySkillsRel()` now accepts both string and single-element array forms for the `skills` field, matching the real CodeBuddy validator's expected shape. The root `.codebuddy-plugin/plugin.json` now uses array form.
|
|
28
|
-
- **CLI channel attestation path validation**: `validateCodeBuddyAttestation()` now validates that CLI channel `installPath` ends with the well-known `.codebuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>` segment tail, closing a path-escape gap.
|
|
35
|
+
- **Nested skill blind spot removed**: author, review, repair, and other nested skills are no longer skipped by resource validation.
|
|
36
|
+
- **Public baseline tests synchronized**: migration invariants now track the configured v0.2.4 public baseline commit.
|
|
29
37
|
<!-- release-skill:managed:end id=latest-release -->
|
|
30
38
|
|
|
31
39
|
<!-- release-skill:capability:external-write-boundary -->
|
|
32
|
-
> **Current boundary:** v0.2.
|
|
40
|
+
> **Current boundary:** v0.2.5 is the current release (v0.2.2 previously held
|
|
33
41
|
> published status before the platform verification convergence fix was added).
|
|
34
42
|
> v0.1.1 completed a real production release to GitHub and npm — the first
|
|
35
43
|
> production-verified milestone — followed by
|
|
@@ -47,7 +55,7 @@ v0.2.3 is a fix-forward release that addresses platform verification, public art
|
|
|
47
55
|
> publish global preflight.
|
|
48
56
|
|
|
49
57
|
<!-- release-skill:capability:safe-first-command -->
|
|
50
|
-
> **Production path verified since the v0.1.1 milestone; v0.
|
|
58
|
+
> **Production path verified since the v0.1.1 milestone; v0.2.5 is the current
|
|
51
59
|
> release.** The npm-installed CLI is the supported user entry. Source checkout
|
|
52
60
|
> is the development/contributor fallback.
|
|
53
61
|
>
|
|
@@ -63,89 +71,26 @@ v0.2.3 is a fix-forward release that addresses platform verification, public art
|
|
|
63
71
|
> specifically requires `prepare --online --production`. Without digest confirmation,
|
|
64
72
|
> no remote preflight or write starts.
|
|
65
73
|
|
|
66
|
-
##
|
|
74
|
+
## Table of contents
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
exception states: NEEDS_INPUT / BLOCKED / PARTIAL
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Each CLI command maps to one transition:
|
|
78
|
-
|
|
79
|
-
- `help` checks the environment; `setup` discovers the project and, after digest
|
|
80
|
-
confirmation, creates the config once.
|
|
81
|
-
- `assess` performs a read-only readiness evaluation (`DISCOVERED -> ASSESSED`).
|
|
82
|
-
- `prepare` runs the verification gates, freezes an immutable release plan, and
|
|
83
|
-
copies the configured public files into an isolated snapshot
|
|
84
|
-
(`ASSESSED -> PREPARED`). It writes only under `.release-skill/` and never
|
|
85
|
-
touches remote services.
|
|
86
|
-
- `approve` records human approval bound to the plan digest with a 24-hour expiry
|
|
87
|
-
(`PREPARED -> APPROVED`). A changed plan invalidates the approval automatically.
|
|
88
|
-
- `publish` executes the external write checkpoints in order
|
|
89
|
-
(`APPROVED -> PUBLISHING -> PUBLISHED`).
|
|
90
|
-
- `reconcile` recovers from `PARTIAL`; `verify` performs a fresh consumer-install
|
|
91
|
-
check in an isolated environment (`PUBLISHED -> VERIFIED`).
|
|
92
|
-
|
|
93
|
-
`PUBLISHED` is **not** the terminal state. Only a fresh `verify` run that confirms
|
|
94
|
-
remote state and exact consumer installs match the frozen plan reaches `VERIFIED`.
|
|
95
|
-
|
|
96
|
-
**Publishing checkpoint order.** `publish` runs a read-only global preflight over
|
|
97
|
-
all actions, then executes and observes in this fixed order: public snapshot
|
|
98
|
-
branch -> signed/tracked tag -> npm publish -> GitHub Release -> configured
|
|
99
|
-
Claude/Codex plugin-marketplace install -> run record. Any failure stops subsequent
|
|
100
|
-
checkpoints and the run lands in `PARTIAL`. The system never auto-deletes remote
|
|
101
|
-
tags, unpublishes packages, or restarts from scratch; `reconcile` queries the actual
|
|
102
|
-
remote state, skips already-consistent steps, and retries only safe, incomplete
|
|
103
|
-
actions, while remote conflicts require a human decision.
|
|
104
|
-
|
|
105
|
-
## Why this is safe for a hand-edited README
|
|
106
|
-
|
|
107
|
-
release-skill does not regenerate or rewrite project source files. `prepare` copies
|
|
108
|
-
each configured public file from the current workspace into an isolated local
|
|
109
|
-
snapshot and verifies the copied bytes. That includes the complete README:
|
|
110
|
-
slogans, examples, prose, formatting, and later human edits.
|
|
111
|
-
|
|
112
|
-
- A later prepare reads the current file again; it does not rebuild it from a template.
|
|
113
|
-
- The snapshot must match the source bytes exactly.
|
|
114
|
-
- A changed plan gets a new digest, so an old approval cannot authorize it.
|
|
115
|
-
- A source edit after prepare makes publish stop before remote writes. Preserve
|
|
116
|
-
the edit by preparing, reviewing, and approving a new plan.
|
|
117
|
-
- Tampering with a frozen snapshot, Git object, or tarball fails its digest gate.
|
|
118
|
-
- Existing remote branches, tags, releases, or npm versions require human
|
|
119
|
-
intervention; the tool does not force or overwrite them.
|
|
120
|
-
- Only files listed in `publicFiles` are copied. Add translated READMEs, images,
|
|
121
|
-
demos, and linked documents explicitly when they belong in the release.
|
|
122
|
-
- A release freezes only the current truth: `prepare` never refreshes or
|
|
123
|
-
rewrites human docs. Maintainers update README, INSTALL, and CHANGELOG first
|
|
124
|
-
— including the machine-readable `release-skill:release-version` markers,
|
|
125
|
-
which must equal the `package.json` version, and the formal CHANGELOG
|
|
126
|
-
heading for the current version — then prepare, review, and approve. A
|
|
127
|
-
pre-release gate fails closed when any doc version marker or the CHANGELOG
|
|
128
|
-
current-version entry drifts.
|
|
129
|
-
|
|
130
|
-
This is the preservation contract: **copy current truth, freeze reviewed
|
|
131
|
-
truth, and never rewrite human truth.**
|
|
76
|
+
- [Quick start](#quick-start)
|
|
77
|
+
- [Release workflow](#release-workflow)
|
|
78
|
+
- [Documentation](#documentation)
|
|
79
|
+
- [Skills](#skills)
|
|
80
|
+
- [Platform distribution](#platform-distribution)
|
|
81
|
+
- [License](#license)
|
|
132
82
|
|
|
133
83
|
## Quick start
|
|
134
84
|
|
|
135
|
-
|
|
136
|
-
and surfaces only the deterministic `compactSummary` review view; the summary
|
|
137
|
-
is a review aid, never a substitute for the bound digest authorization.
|
|
138
|
-
|
|
139
|
-
### Install / requirements
|
|
85
|
+
### Install
|
|
140
86
|
|
|
141
|
-
- Node.js 22
|
|
142
|
-
- Git 2.30+
|
|
143
|
-
- A target Git repository with at least one commit
|
|
87
|
+
- Node.js 22+, Git 2.30+, a target Git repository with at least one commit.
|
|
144
88
|
|
|
145
|
-
**
|
|
89
|
+
**npm (recommended):**
|
|
146
90
|
|
|
147
91
|
```bash
|
|
148
92
|
npm install -g release-skill
|
|
93
|
+
release-skill help
|
|
149
94
|
```
|
|
150
95
|
|
|
151
96
|
Or run directly without installing:
|
|
@@ -154,251 +99,200 @@ Or run directly without installing:
|
|
|
154
99
|
npx release-skill help
|
|
155
100
|
```
|
|
156
101
|
|
|
157
|
-
**
|
|
102
|
+
**Plugin (Claude Code / CodeBuddy / WorkBuddy / Codex):**
|
|
158
103
|
|
|
159
|
-
|
|
160
|
-
release-skill
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**Install as a plugin (Claude Code / CodeBuddy / WorkBuddy / Codex / Kimi Code):**
|
|
104
|
+
Claude Code, CodeBuddy, WorkBuddy, and Codex install from the bundled-family
|
|
105
|
+
marketplace `ifoohoo/release-skill`:
|
|
164
106
|
|
|
165
|
-
All four plugin hosts install from the unified marketplace
|
|
166
|
-
`ifoohoo/artifact-skill-set` — for example, in a Claude Code session:
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
/plugin marketplace add ifoohoo/artifact-skill-set
|
|
170
|
-
/plugin install release-skill@artifact-skill-set
|
|
171
107
|
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
plugin repository carries only the plugin manifest, while the marketplace index
|
|
175
|
-
is centralized in the external marketplace repository. When a release unit's
|
|
176
|
-
plugin distribution declares `marketplaceRepo`, `prepare --online --production`
|
|
177
|
-
freezes the external marketplace HEAD (Codex pins the commit sha — strong
|
|
178
|
-
freeze; Claude pins the default branch name — weak freeze) and verifies the
|
|
179
|
-
installed payload against the unit's own frozen snapshot whole-tree. **Release
|
|
180
|
-
ordering:** publish the external marketplace index first — its entry version
|
|
181
|
-
must equal the target release version — before `prepare` can freeze a
|
|
182
|
-
marketplace sha containing that entry. See [INSTALL.md](INSTALL.md) for the
|
|
183
|
-
per-platform commands and `references/06-adapter-contract.md` §2.3/§2.4 for the
|
|
184
|
-
contract and the advanced direct-repository options.
|
|
185
|
-
|
|
186
|
-
**Development install (contributor fallback, from source checkout):**
|
|
187
|
-
|
|
188
|
-
Set the checkout location and install dependencies:
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
export RELEASE_SKILL_HOME=/absolute/path/to/release-skill
|
|
192
|
-
cd "$RELEASE_SKILL_HOME"
|
|
193
|
-
npm exec --yes pnpm@10.17.1 -- install --frozen-lockfile
|
|
108
|
+
/plugin marketplace add ifoohoo/release-skill
|
|
109
|
+
/plugin install release-skill@release-skill
|
|
194
110
|
```
|
|
195
111
|
|
|
196
|
-
|
|
112
|
+
> **Prerequisite: GitHub access.** The `owner/repo` shorthand makes Claude Code
|
|
113
|
+
> clone via SSH. If you do not use SSH, pass the full HTTPS URL —
|
|
114
|
+
> `/plugin marketplace add https://github.com/ifoohoo/release-skill` — or set
|
|
115
|
+
> `CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1`.
|
|
197
116
|
|
|
198
|
-
|
|
117
|
+
**Kimi Code:** Kimi Code has no marketplace install API. Install manually from a
|
|
118
|
+
pinned release tag — see [INSTALL.md](INSTALL.md#install-as-a-kimi-code-plugin).
|
|
199
119
|
|
|
200
|
-
|
|
201
|
-
.release-skill/*
|
|
202
|
-
!.release-skill/project.yaml
|
|
203
|
-
```
|
|
120
|
+
See [INSTALL.md](INSTALL.md) for CodeBuddy, Codex, and Kimi Code commands.
|
|
204
121
|
|
|
205
|
-
###
|
|
122
|
+
### Main workflow
|
|
206
123
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
view. The summary does not replace authorization: `setupDigest` still binds the
|
|
210
|
-
complete facts, candidates, and answers.
|
|
124
|
+
Run these steps in order. Steps 1-4 are safe (read-only or local-only);
|
|
125
|
+
steps 5-9 require explicit human gates.
|
|
211
126
|
|
|
212
127
|
```bash
|
|
128
|
+
CLI=(release-skill) # or: CLI=(node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs")
|
|
213
129
|
PROJECT=/absolute/path/to/my-project
|
|
214
|
-
|
|
215
|
-
REPORT="$SETUP_SESSION/discovery.json"
|
|
216
|
-
ANSWERS="$SETUP_SESSION/answers.json"
|
|
217
|
-
BOUND_REPORT="$SETUP_SESSION/bound.json"
|
|
218
|
-
printf 'SETUP_SESSION=%s\nPROJECT=%s\n' "$SETUP_SESSION" "$PROJECT"
|
|
219
|
-
|
|
220
|
-
release-skill setup --root "$PROJECT" --json > "$REPORT" || test "$?" -eq 2
|
|
221
|
-
node -e 'const fs=require("node:fs");const r=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(!r.compactSummary){console.error("compactSummary missing");process.exit(2)}process.stdout.write(JSON.stringify(r.compactSummary,null,2)+"\n")' "$REPORT"
|
|
130
|
+
ACTOR=your-name
|
|
222
131
|
```
|
|
223
132
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
133
|
+
1. **help** — check the environment:
|
|
134
|
+
```bash
|
|
135
|
+
"${CLI[@]}" help
|
|
136
|
+
```
|
|
137
|
+
2. **setup** — first-use only (read-only discovery, then create-once config):
|
|
138
|
+
```bash
|
|
139
|
+
SETUP_SESSION="$(mktemp -d "${TMPDIR:-/tmp}/release-setup.XXXXXX")"
|
|
140
|
+
REPORT="$SETUP_SESSION/discovery.json"
|
|
141
|
+
ANSWERS="$SETUP_SESSION/answers.json"
|
|
142
|
+
printf 'SETUP_SESSION=%s\nPROJECT=%s\n' "$SETUP_SESSION" "$PROJECT"
|
|
143
|
+
"${CLI[@]}" setup --root "$PROJECT" --json > "$REPORT" || test "$?" -eq 2
|
|
144
|
+
```
|
|
145
|
+
If `proposalConflicts` is non-empty, stop and let a human correct the
|
|
146
|
+
conflicting repository or mapping authority. With no conflicts, extract
|
|
147
|
+
`recommendedAnswers` mechanically (never hand-write complete answers):
|
|
148
|
+
```bash
|
|
149
|
+
SETUP_SESSION='<session-directory-absolute-path-printed-above>'
|
|
150
|
+
node -e 'const fs=require("node:fs");const r=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if((r.proposalConflicts??[]).length){console.error("proposal conflicts require human resolution");process.exit(2)}if(!r.recommendedAnswers){console.error("recommendedAnswers missing");process.exit(2)}fs.writeFileSync(process.argv[2],JSON.stringify(r.recommendedAnswers,null,2)+"\n",{flag:"wx",mode:0o600})' "$REPORT" "$ANSWERS"
|
|
151
|
+
```
|
|
152
|
+
Confirm the bound `setupDigest` once, then create the config:
|
|
153
|
+
```bash
|
|
154
|
+
SETUP_SESSION='<session-directory-absolute-path-printed-above>'
|
|
155
|
+
PROJECT='<project-absolute-path-printed-above>'
|
|
156
|
+
ANSWERS="$SETUP_SESSION/answers.json"
|
|
157
|
+
CREATED_REPORT="$SETUP_SESSION/created.json"
|
|
158
|
+
POST_REPORT="$SETUP_SESSION/post-setup.json"
|
|
159
|
+
ASSESS_REPORT="$SETUP_SESSION/assess.json"
|
|
160
|
+
"${CLI[@]}" setup --root "$PROJECT" --answers "$ANSWERS" \
|
|
161
|
+
--write --confirm-setup <confirmed-setupDigest> --json > "$CREATED_REPORT"
|
|
162
|
+
"${CLI[@]}" setup --root "$PROJECT" --json > "$POST_REPORT"
|
|
163
|
+
set +e
|
|
164
|
+
"${CLI[@]}" assess --root "$PROJECT" --offline --json > "$ASSESS_REPORT"
|
|
165
|
+
ASSESS_EXIT=$?
|
|
166
|
+
set -e
|
|
167
|
+
[ "$ASSESS_EXIT" -eq 0 ] || [ "$ASSESS_EXIT" -eq 1 ] || exit "$ASSESS_EXIT"
|
|
168
|
+
node -e 'const fs=require("node:fs");const [c,p,a]=process.argv.slice(1).map(x=>JSON.parse(fs.readFileSync(x,"utf8")));if(c.status!=="CONFIG_CREATED"||p.status!=="ALREADY_CONFIGURED"||!["ASSESSED","NEEDS_INPUT","BLOCKED"].includes(a.status)){process.exit(2)}' "$CREATED_REPORT" "$POST_REPORT" "$ASSESS_REPORT"
|
|
169
|
+
node -e 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:false})' "$SETUP_SESSION"
|
|
170
|
+
```
|
|
171
|
+
The write must return `CONFIG_CREATED`; the next setup must return
|
|
172
|
+
`ALREADY_CONFIGURED`. Existing configuration is never regenerated — make only
|
|
173
|
+
reviewed incremental edits. Discovered scripts are `SIDE_EFFECTS_UNPROVEN`.
|
|
174
|
+
Add a project-specific hook or gate only after human review: edit
|
|
175
|
+
`projectConfig.hooks`, or edit `verificationGates` and add the same id to
|
|
176
|
+
`selectedGateIds`, then rerun the bound dry-run.
|
|
177
|
+
See [INSTALL.md](INSTALL.md#first-use-setup) for the full multi-step flow.
|
|
178
|
+
3. **assess** — read-only readiness:
|
|
179
|
+
```bash
|
|
180
|
+
"${CLI[@]}" assess --root "$PROJECT" --offline --json
|
|
181
|
+
```
|
|
182
|
+
4. **prepare** — local snapshot and plan freeze:
|
|
183
|
+
```bash
|
|
184
|
+
"${CLI[@]}" prepare --root "$PROJECT" --offline \
|
|
185
|
+
--acknowledge-hook-side-effects \
|
|
186
|
+
--acknowledge-gate-side-effects --json
|
|
187
|
+
```
|
|
188
|
+
Omit an acknowledgement only when that project config has no corresponding
|
|
189
|
+
hook or snapshot gate. Never grant either acknowledgement before reviewing
|
|
190
|
+
the configured executable, arguments, working directory, and side effects.
|
|
191
|
+
5. **Human review:** inspect `planPath`, `externalActions`, `targetVersion`, and `planDigest`.
|
|
192
|
+
6. **prepare --production** — freeze the production plan:
|
|
193
|
+
```bash
|
|
194
|
+
PLAN_JSON=$("${CLI[@]}" prepare --root "$PROJECT" --online --production \
|
|
195
|
+
--acknowledge-hook-side-effects \
|
|
196
|
+
--acknowledge-gate-side-effects --json)
|
|
197
|
+
PLAN_PATH=$(printf '%s\n' "$PLAN_JSON" | jq -r '.planPath')
|
|
198
|
+
PLAN_DIGEST=$(printf '%s\n' "$PLAN_JSON" | jq -r '.planDigest')
|
|
199
|
+
```
|
|
200
|
+
7. **approve** — human approval bound to the plan digest (24-hour expiry):
|
|
201
|
+
```bash
|
|
202
|
+
APPROVAL_JSON=$("${CLI[@]}" approve --plan "$PLAN_PATH" \
|
|
203
|
+
--digest "$PLAN_DIGEST" --actor "$ACTOR" --json)
|
|
204
|
+
APPROVAL_PATH=$(printf '%s\n' "$APPROVAL_JSON" | jq -r '.approvalPath')
|
|
205
|
+
```
|
|
206
|
+
8. **publish** — remote writes start here:
|
|
207
|
+
```bash
|
|
208
|
+
PUBLISH_JSON=$("${CLI[@]}" publish --root "$PROJECT" \
|
|
209
|
+
--plan "$PLAN_PATH" --approval "$APPROVAL_PATH" \
|
|
210
|
+
--confirm-production "$PLAN_DIGEST" --json)
|
|
211
|
+
PUBLISH_RUN_PATH=$(printf '%s\n' "$PUBLISH_JSON" | jq -r '.runPath')
|
|
212
|
+
```
|
|
213
|
+
`PUBLISHED` is **not** the terminal state.
|
|
214
|
+
9. **verify** — consumer install check:
|
|
215
|
+
```bash
|
|
216
|
+
"${CLI[@]}" verify --root "$PROJECT" \
|
|
217
|
+
--plan "$PLAN_PATH" --run "$PUBLISH_RUN_PATH" \
|
|
218
|
+
--acknowledge-gate-side-effects --json
|
|
219
|
+
```
|
|
228
220
|
|
|
229
|
-
|
|
230
|
-
|
|
221
|
+
The handoff example requires `jq`. Without it, copy the returned JSON fields
|
|
222
|
+
exactly; do not pass angle-bracket labels as shell syntax.
|
|
231
223
|
|
|
232
|
-
|
|
233
|
-
SETUP_SESSION='/session-directory-absolute-path-printed-above'
|
|
234
|
-
PROJECT='/project-absolute-path-printed-above'
|
|
235
|
-
REPORT="$SETUP_SESSION/discovery.json"
|
|
236
|
-
ANSWERS="$SETUP_SESSION/answers.json"
|
|
237
|
-
BOUND_REPORT="$SETUP_SESSION/bound.json"
|
|
238
|
-
node -e 'const fs=require("node:fs");const r=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if((r.proposalConflicts??[]).length){console.error("proposal conflicts require human resolution");process.exit(2)}if(!r.recommendedAnswers){console.error("recommendedAnswers missing");process.exit(2)}fs.writeFileSync(process.argv[2],JSON.stringify(r.recommendedAnswers,null,2)+"\n",{flag:"wx",mode:0o600})' "$REPORT" "$ANSWERS"
|
|
239
|
-
|
|
240
|
-
release-skill setup --root "$PROJECT" --answers "$ANSWERS" --json > "$BOUND_REPORT"
|
|
241
|
-
node -e 'const fs=require("node:fs");const r=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(!r.compactSummary||!r.setupDigest){console.error("bound setup report incomplete");process.exit(2)}process.stdout.write(JSON.stringify({compactSummary:r.compactSummary,setupDigest:r.setupDigest},null,2)+"\n")' "$BOUND_REPORT"
|
|
242
|
-
printf 'SETUP_SESSION=%s\nPROJECT=%s\n' "$SETUP_SESSION" "$PROJECT"
|
|
243
|
-
```
|
|
224
|
+
### PARTIAL recovery and reconcile
|
|
244
225
|
|
|
245
|
-
|
|
246
|
-
|
|
226
|
+
When `publish` succeeds at some checkpoints but fails at others, the run enters
|
|
227
|
+
`PARTIAL` status. **Do not restart from scratch and do not delete remote state.**
|
|
247
228
|
|
|
248
229
|
```bash
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
--
|
|
257
|
-
|
|
258
|
-
set +e
|
|
259
|
-
release-skill assess --root "$PROJECT" --offline --json > "$ASSESS_REPORT"
|
|
260
|
-
ASSESS_EXIT=$?
|
|
261
|
-
set -e
|
|
262
|
-
[ "$ASSESS_EXIT" -eq 0 ] || [ "$ASSESS_EXIT" -eq 1 ] || exit "$ASSESS_EXIT"
|
|
263
|
-
node -e 'const fs=require("node:fs");const [c,p,a]=process.argv.slice(1).map(x=>JSON.parse(fs.readFileSync(x,"utf8")));if(c.status!=="CONFIG_CREATED"||p.status!=="ALREADY_CONFIGURED"||!["ASSESSED","NEEDS_INPUT","BLOCKED"].includes(a.status)){process.exit(2)}process.stdout.write(JSON.stringify({created:c.status,postSetup:p.status,assessment:{status:a.status,summary:a.summary,gapCount:(a.gaps??[]).length,blockingCodes:(a.gaps??[]).filter(g=>g.severity==="error").map(g=>g.code)}},null,2)+"\n")' "$CREATED_REPORT" "$POST_REPORT" "$ASSESS_REPORT"
|
|
264
|
-
node -e 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:false})' "$SETUP_SESSION"
|
|
230
|
+
RECONCILE_JSON=$("${CLI[@]}" reconcile --root "$PROJECT" \
|
|
231
|
+
--run "$PUBLISH_RUN_PATH" \
|
|
232
|
+
--plan "$PLAN_PATH" \
|
|
233
|
+
--approval "$APPROVAL_PATH" \
|
|
234
|
+
--confirm-production "$PLAN_DIGEST" --json)
|
|
235
|
+
RECONCILE_RUN_PATH=$(printf '%s\n' "$RECONCILE_JSON" | jq -r '.runPath')
|
|
236
|
+
"${CLI[@]}" verify --root "$PROJECT" \
|
|
237
|
+
--plan "$PLAN_PATH" --run "$RECONCILE_RUN_PATH" \
|
|
238
|
+
--acknowledge-gate-side-effects --json
|
|
265
239
|
```
|
|
266
240
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
`SIDE_EFFECTS_UNPROVEN` and are not selected automatically. Add a project-specific
|
|
271
|
-
hook or gate only after human review: edit `projectConfig.hooks`, or edit
|
|
272
|
-
`verificationGates` and add the same id to `selectedGateIds`, then rerun the
|
|
273
|
-
bound dry-run. Keep human files at `mode: preserve`, and
|
|
274
|
-
use `sourceScope: workspace` only for explicit cross-unit shared sources.
|
|
241
|
+
`reconcile` queries the actual remote state, skips already-consistent steps,
|
|
242
|
+
and retries only safe incomplete actions. Remote conflicts require human
|
|
243
|
+
decision. Successful reconcile returns `PUBLISHED`, not `VERIFIED`.
|
|
275
244
|
|
|
276
|
-
|
|
245
|
+
## Release workflow
|
|
277
246
|
|
|
278
|
-
|
|
279
|
-
|
|
247
|
+
release-skill models the release lifecycle as a strict state machine
|
|
248
|
+
(normative definition: `references/01-state-machine.md`):
|
|
280
249
|
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"apiVersion": "release-skill/v1",
|
|
285
|
-
"kind": "ReleaseProject",
|
|
286
|
-
"project": { "name": "my-project", "defaultBranch": "main" },
|
|
287
|
-
"releaseUnits": [{
|
|
288
|
-
"id": "my-project",
|
|
289
|
-
"source": ".",
|
|
290
|
-
"publicRepo": "owner/my-project",
|
|
291
|
-
"version": { "source": "package.json", "tagTemplate": "v{version}" },
|
|
292
|
-
"distributions": [{
|
|
293
|
-
"type": "npm",
|
|
294
|
-
"package": "my-project",
|
|
295
|
-
"access": "public",
|
|
296
|
-
"provenance": false,
|
|
297
|
-
"tag": "latest",
|
|
298
|
-
"registry": "https://registry.npmjs.org",
|
|
299
|
-
"publisher": "my-npm-username"
|
|
300
|
-
}],
|
|
301
|
-
"publicFiles": [
|
|
302
|
-
{ "from": "README.md", "to": "README.md", "mode": "preserve" },
|
|
303
|
-
{ "from": "package.json", "to": "package.json", "mode": "preserve" }
|
|
304
|
-
],
|
|
305
|
-
"requiredPublicFiles": ["README.md", "package.json"],
|
|
306
|
-
"previousPublicBaseline": { "mode": "none" },
|
|
307
|
-
"production": {
|
|
308
|
-
"branchTemplate": "release/{tag}",
|
|
309
|
-
"branchStrategy": "create-release-branch"
|
|
310
|
-
}
|
|
311
|
-
}]
|
|
312
|
-
},
|
|
313
|
-
"selectedGateIds": []
|
|
314
|
-
}
|
|
250
|
+
```text
|
|
251
|
+
DISCOVERED -> ASSESSED -> PREPARED -> APPROVED -> PUBLISHING -> PUBLISHED -> VERIFIED
|
|
252
|
+
exception states: NEEDS_INPUT / BLOCKED / PARTIAL
|
|
315
253
|
```
|
|
316
254
|
|
|
317
|
-
|
|
318
|
-
|
|
255
|
+
Each CLI command maps to one transition. `PUBLISHED` is **not** the terminal
|
|
256
|
+
state — only a fresh `verify` that confirms remote state and consumer installs
|
|
257
|
+
match the frozen plan reaches `VERIFIED`.
|
|
319
258
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
259
|
+
**Preservation contract:** release-skill does not regenerate or rewrite project
|
|
260
|
+
source files. `prepare` copies each configured public file into an isolated
|
|
261
|
+
snapshot and verifies the copied bytes. A later prepare reads the current file
|
|
262
|
+
again; it never rebuilds from a template. Only files listed in `publicFiles` are
|
|
263
|
+
copied. `prepare` never refreshes or rewrites human docs — maintainers update
|
|
264
|
+
README, INSTALL, and CHANGELOG first, then prepare, review, and approve.
|
|
323
265
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"kind": "ReleaseProject",
|
|
329
|
-
"project": { "name": "my-project", "defaultBranch": "main" },
|
|
330
|
-
"releaseUnits": [{
|
|
331
|
-
"id": "my-project",
|
|
332
|
-
"source": ".",
|
|
333
|
-
"publicRepo": "owner/my-project",
|
|
334
|
-
"version": { "source": "package.json", "tagTemplate": "v{version}" },
|
|
335
|
-
"distributions": [{
|
|
336
|
-
"type": "npm",
|
|
337
|
-
"package": "my-project",
|
|
338
|
-
"access": "public",
|
|
339
|
-
"provenance": false,
|
|
340
|
-
"tag": "latest",
|
|
341
|
-
"registry": "https://registry.npmjs.org",
|
|
342
|
-
"publisher": "my-npm-username"
|
|
343
|
-
}],
|
|
344
|
-
"publicFiles": [
|
|
345
|
-
{ "from": "package.json", "to": "package.json", "mode": "preserve" }
|
|
346
|
-
],
|
|
347
|
-
"requiredPublicFiles": ["package.json"],
|
|
348
|
-
"previousPublicBaseline": { "mode": "none" },
|
|
349
|
-
"production": {
|
|
350
|
-
"branchTemplate": "release/{tag}",
|
|
351
|
-
"branchStrategy": "create-release-branch"
|
|
352
|
-
}
|
|
353
|
-
}],
|
|
354
|
-
"verificationGates": [{
|
|
355
|
-
"id": "my-project-script-test",
|
|
356
|
-
"phase": "snapshot-verify",
|
|
357
|
-
"scope": { "unit": "my-project" },
|
|
358
|
-
"command": ["node", "-e", "const p=require('./package.json');if(!p.name)process.exit(1)"],
|
|
359
|
-
"cwd": ".",
|
|
360
|
-
"timeoutMs": 30000,
|
|
361
|
-
"envAllowlist": []
|
|
362
|
-
}]
|
|
363
|
-
},
|
|
364
|
-
"selectedGateIds": ["my-project-script-test"]
|
|
365
|
-
}
|
|
366
|
-
```
|
|
266
|
+
**Write safety:** `setup` is read-only by default (create-once after digest
|
|
267
|
+
confirmation). `prepare` writes only under `.release-skill/`. `publish` is the
|
|
268
|
+
production write entry, requiring both approval and the current plan digest.
|
|
269
|
+
Project hooks and gates are acknowledged local processes without an OS sandbox.
|
|
367
270
|
|
|
368
|
-
|
|
369
|
-
The example command is self-contained in the public snapshot. A project script
|
|
370
|
-
is valid only when the script and every dependency it needs are included in
|
|
371
|
-
`publicFiles`; a snapshot gate cannot see the parent workspace's tests,
|
|
372
|
-
development dependencies, or `node_modules` unless they are explicitly public.
|
|
271
|
+
## Documentation
|
|
373
272
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
273
|
+
| Document | Description |
|
|
274
|
+
|---|---|
|
|
275
|
+
| [INSTALL.md](INSTALL.md) / [INSTALL.zh-CN.md](INSTALL.zh-CN.md) | Full installation guide: npm, plugin, source checkout, setup flow, branch strategies |
|
|
276
|
+
| [CHANGELOG.md](CHANGELOG.md) | Release history |
|
|
277
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute (includes generated-artifact rules) |
|
|
278
|
+
| [SECURITY.md](SECURITY.md) | Security policy |
|
|
279
|
+
| `references/01-state-machine.md` | Normative state machine definition |
|
|
280
|
+
| `references/02-project-config.md` | Project configuration schema reference |
|
|
281
|
+
| `references/05-evidence-and-errors.md` | Evidence format and error codes |
|
|
282
|
+
| `references/06-adapter-contract.md` | Adapter and marketplace contract details |
|
|
283
|
+
| [GitHub Issues](https://github.com/ifoohoo/release-skill/issues) | Bug reports and feature requests |
|
|
381
284
|
|
|
382
|
-
|
|
383
|
-
That create-once step uses the digest-registered `darwin-arm64` native
|
|
384
|
-
prebuild shipped in v0.1.3; unsupported platforms fail closed with
|
|
385
|
-
`SAFE_WRITE_UNAVAILABLE` instead of falling back to path-based writes.
|
|
386
|
-
`ALREADY_CONFIGURED`/`CONFIG_EXISTS` means the existing file remains
|
|
387
|
-
human-owned and must be edited incrementally. README, slogans, CHANGELOG, and
|
|
388
|
-
business scripts are never generated or overwritten. A project with no remote
|
|
389
|
-
channel reports `LOCAL_ONLY_DETECTED` instead of inventing production support.
|
|
285
|
+
## Configuration
|
|
390
286
|
|
|
391
|
-
|
|
392
|
-
|
|
287
|
+
A minimal human-authored configuration (see [INSTALL.md](INSTALL.md) for the
|
|
288
|
+
full schema and setup flow):
|
|
393
289
|
|
|
394
290
|
```yaml
|
|
395
291
|
apiVersion: release-skill/v1
|
|
396
292
|
kind: ReleaseProject
|
|
397
|
-
|
|
398
293
|
project:
|
|
399
294
|
name: my-project
|
|
400
295
|
defaultBranch: main
|
|
401
|
-
|
|
402
296
|
releaseUnits:
|
|
403
297
|
- id: my-project
|
|
404
298
|
source: .
|
|
@@ -413,371 +307,25 @@ releaseUnits:
|
|
|
413
307
|
- from: package.json
|
|
414
308
|
to: package.json
|
|
415
309
|
mode: preserve
|
|
416
|
-
|
|
417
|
-
to: LICENSE
|
|
418
|
-
mode: preserve
|
|
419
|
-
requiredPublicFiles: [README.md, LICENSE, package.json]
|
|
310
|
+
requiredPublicFiles: [README.md, package.json]
|
|
420
311
|
previousPublicBaseline:
|
|
421
|
-
mode: none
|
|
312
|
+
mode: none
|
|
422
313
|
distributions:
|
|
423
314
|
- type: npm
|
|
424
315
|
package: my-project
|
|
425
|
-
access: public
|
|
426
|
-
provenance: false
|
|
316
|
+
access: public
|
|
317
|
+
provenance: false
|
|
427
318
|
tag: latest
|
|
428
319
|
registry: https://registry.npmjs.org
|
|
429
320
|
publisher: my-npm-username
|
|
430
|
-
# Optional: CLI smoke verification. When smokeBin is set, verify
|
|
431
|
-
# installs the package in an isolated directory and runs the named
|
|
432
|
-
# binary. Without smokeBin, verify only confirms install + name/version.
|
|
433
|
-
# smokeBin: my-project
|
|
434
|
-
# smokeArgs: [help, --json]
|
|
435
|
-
# smokeExpectedJson:
|
|
436
|
-
# command: help
|
|
437
|
-
# status: READY
|
|
438
321
|
production:
|
|
439
322
|
branchTemplate: release/{tag}
|
|
440
323
|
branchStrategy: create-release-branch
|
|
441
|
-
releaseTitleTemplate: "{unit} {version}"
|
|
442
|
-
releaseNotes: "Human-maintained release notes"
|
|
443
324
|
```
|
|
444
325
|
|
|
445
|
-
Every release unit must declare its previous public baseline. Use `mode: none`
|
|
446
|
-
only when you have verified that no earlier public version exists. For an
|
|
447
|
-
existing public repository, bind the exact immutable ref and commit instead:
|
|
448
|
-
|
|
449
|
-
```yaml
|
|
450
|
-
previousPublicBaseline:
|
|
451
|
-
mode: bound
|
|
452
|
-
repo: owner/my-project
|
|
453
|
-
ref: release/v0.9.0
|
|
454
|
-
commit: 0123456789abcdef0123456789abcdef01234567
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
`none` is not a conflict-check bypass: publish still checks target branch,
|
|
458
|
-
tag, GitHub Release, and npm version uniqueness before any write. A bound
|
|
459
|
-
production prepare must run online so the ref-to-commit mapping can be observed.
|
|
460
|
-
The default observer does not download remote file contents, so it reports a
|
|
461
|
-
mapping diff and marks content diff unavailable. On drift, stop and choose
|
|
462
|
-
`merge`, `adopt`, or `reject` manually. First obtain and review the actual remote
|
|
463
|
-
commit; the tool does not download or merge its files. `merge` keeps both local
|
|
464
|
-
and remote edits in the human-owned source. `adopt` copies the reviewed remote
|
|
465
|
-
bytes into that source. `reject` stops the release while the remote/ref is
|
|
466
|
-
investigated or corrected; never switch to `mode: none` to bypass the drift.
|
|
467
|
-
After `merge` or `adopt`, rebind `previousPublicBaseline` to the accepted
|
|
468
|
-
immutable `repo`/`ref`/`commit`, then run a new `prepare --online --production`,
|
|
469
|
-
review, and approval.
|
|
470
|
-
|
|
471
|
-
Choose a branch strategy that matches the real repository:
|
|
472
|
-
|
|
473
|
-
- `create-release-branch` creates an absent immutable release branch and stops
|
|
474
|
-
if the name already exists.
|
|
475
|
-
- `advance-existing-branch` creates a single-parent commit on the exact
|
|
476
|
-
`previousPublicBaseline` commit and permits only an ordinary fast-forward
|
|
477
|
-
push; concurrent drift requires human intervention.
|
|
478
|
-
- `initialize-default-branch` creates an absent standard branch under control.
|
|
479
|
-
Only explicit `setAsDefaultBranch` and `expectedCurrentDefaultBranch` values
|
|
480
|
-
add a separately approved, observed, and reconcilable default-branch action.
|
|
481
|
-
|
|
482
|
-
Minimal configurations for the three strategies are:
|
|
483
|
-
|
|
484
|
-
```yaml
|
|
485
|
-
# New immutable release branch; the target must not exist.
|
|
486
|
-
previousPublicBaseline: { mode: none } # only for a true first public release
|
|
487
|
-
production:
|
|
488
|
-
branchTemplate: release/{tag}
|
|
489
|
-
branchStrategy: create-release-branch
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
```yaml
|
|
493
|
-
# Advance main; the bound ref must be exactly the target branch.
|
|
494
|
-
previousPublicBaseline:
|
|
495
|
-
mode: bound
|
|
496
|
-
repo: owner/my-project
|
|
497
|
-
ref: refs/heads/main
|
|
498
|
-
commit: 0123456789abcdef0123456789abcdef01234567
|
|
499
|
-
production:
|
|
500
|
-
branchTemplate: main
|
|
501
|
-
branchStrategy: advance-existing-branch
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
```yaml
|
|
505
|
-
# One-time creation of an absent main and an explicit default-branch switch.
|
|
506
|
-
previousPublicBaseline:
|
|
507
|
-
mode: bound
|
|
508
|
-
repo: owner/my-project
|
|
509
|
-
ref: refs/heads/old-public-branch
|
|
510
|
-
commit: 0123456789abcdef0123456789abcdef01234567
|
|
511
|
-
production:
|
|
512
|
-
branchTemplate: main
|
|
513
|
-
branchStrategy: initialize-default-branch
|
|
514
|
-
setAsDefaultBranch: true
|
|
515
|
-
expectedCurrentDefaultBranch: old-public-branch
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
The latter two require `prepare --online --production`. If the observed branch,
|
|
519
|
-
commit, target absence, or current default branch differs, stop and update the
|
|
520
|
-
human-owned source/config only after reviewing the real remote state; never
|
|
521
|
-
force-push or weaken the baseline.
|
|
522
|
-
|
|
523
|
-
This is a mechanics-only local example, not a complete npm publication map.
|
|
524
|
-
Before a real release, enumerate every public runtime file, executable, type
|
|
525
|
-
declaration, image, and linked document. In a monorepo, set `source` to a path
|
|
526
|
-
such as `packages/my-plugin`, and keep each `from` path relative to the workspace
|
|
527
|
-
root, for example `packages/my-plugin/README.md`.
|
|
528
|
-
|
|
529
|
-
Before the first prepare, preferably commit `.gitignore`, `.release-skill/project.yaml`,
|
|
530
|
-
the README, version files, and all intended release content so the Git baseline
|
|
531
|
-
is easy to reproduce. Uncommitted edits that already exist at prepare time and
|
|
532
|
-
remain unchanged are included in the snapshot/baseline; only a later change
|
|
533
|
-
causes baseline validation to stop.
|
|
534
|
-
|
|
535
|
-
### Main workflow
|
|
536
|
-
|
|
537
|
-
Run these steps in order. Steps 1–4 are safe default (read-only or local-only);
|
|
538
|
-
steps 5–9 are production publishing with explicit human gates.
|
|
539
|
-
|
|
540
|
-
```bash
|
|
541
|
-
# npm-installed CLI (recommended):
|
|
542
|
-
CLI=(release-skill)
|
|
543
|
-
PROJECT=/absolute/path/to/my-project
|
|
544
|
-
ACTOR=your-name
|
|
545
|
-
# Development fallback (source checkout):
|
|
546
|
-
# CLI=(node "$RELEASE_SKILL_HOME/packages/release-skill/bin/release-skill.mjs")
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
The npm-installed CLI is the supported user entry after v0.1.1 production
|
|
550
|
-
publication. The source checkout remains the development/contributor fallback.
|
|
551
|
-
|
|
552
|
-
1. **Environment check:**
|
|
553
|
-
```bash
|
|
554
|
-
"${CLI[@]}" help
|
|
555
|
-
```
|
|
556
|
-
2. **First-use setup (only when config is absent; read-only):**
|
|
557
|
-
```bash
|
|
558
|
-
"${CLI[@]}" setup --root "$PROJECT" --json
|
|
559
|
-
```
|
|
560
|
-
Follow the mechanical `compactSummary` and `recommendedAnswers` path above;
|
|
561
|
-
confirm the bound `setupDigest` once, and skip this step when configuration exists.
|
|
562
|
-
3. **Readiness assessment (read-only):**
|
|
563
|
-
```bash
|
|
564
|
-
"${CLI[@]}" assess --root "$PROJECT" --offline --json
|
|
565
|
-
```
|
|
566
|
-
4. **Local snapshot and plan freeze:**
|
|
567
|
-
```bash
|
|
568
|
-
"${CLI[@]}" prepare --root "$PROJECT" --offline \
|
|
569
|
-
--acknowledge-hook-side-effects \
|
|
570
|
-
--acknowledge-gate-side-effects --json
|
|
571
|
-
```
|
|
572
|
-
Omit an acknowledgement only when that project config has no corresponding
|
|
573
|
-
hook or snapshot gate. Never grant either acknowledgement before reviewing
|
|
574
|
-
the configured executable, arguments, working directory, and side effects.
|
|
575
|
-
5. **Human review:** inspect the returned `planPath`, `externalActions`,
|
|
576
|
-
`units[].targetVersion`, and `planDigest`. Each unit's snapshot is under
|
|
577
|
-
`<evidenceDir>/snapshots/<unit-id>/`. The release-skill pipeline writes its
|
|
578
|
-
own data under `.release-skill/`; acknowledged project hooks and gates are
|
|
579
|
-
arbitrary project processes without an operating-system sandbox and may
|
|
580
|
-
write elsewhere, access the network, and read any credentials, tokens,
|
|
581
|
-
keys, and environment variables accessible to the current account.
|
|
582
|
-
6. **Production plan freeze:**
|
|
583
|
-
```bash
|
|
584
|
-
PRODUCTION_JSON=$("${CLI[@]}" prepare --root "$PROJECT" --online --production \
|
|
585
|
-
--acknowledge-hook-side-effects \
|
|
586
|
-
--acknowledge-gate-side-effects --json)
|
|
587
|
-
printf '%s\n' "$PRODUCTION_JSON" | jq .
|
|
588
|
-
PLAN_PATH=$(printf '%s\n' "$PRODUCTION_JSON" | jq -r '.planPath')
|
|
589
|
-
PLAN_DIGEST=$(printf '%s\n' "$PRODUCTION_JSON" | jq -r '.planDigest')
|
|
590
|
-
```
|
|
591
|
-
As above, omit only acknowledgements that are not required by the project
|
|
592
|
-
config, and review every configured process before granting them.
|
|
593
|
-
Review the new plan's externalActions, npm policy, branch/tag, and frozen
|
|
594
|
-
digests. `prepare --json` returns the immutable production authority as
|
|
595
|
-
`<project>/.release-skill/plans/<planDigest>.json`; always carry that returned
|
|
596
|
-
`planPath` forward. `.release-skill/release-plan.json` is only a mutable
|
|
597
|
-
convenience alias and must not be passed to production approve/publish/reconcile.
|
|
598
|
-
7. **Approval:**
|
|
599
|
-
```bash
|
|
600
|
-
APPROVAL_JSON=$("${CLI[@]}" approve --plan "$PLAN_PATH" \
|
|
601
|
-
--digest "$PLAN_DIGEST" --actor "$ACTOR" --json)
|
|
602
|
-
printf '%s\n' "$APPROVAL_JSON" | jq .
|
|
603
|
-
APPROVAL_PATH=$(printf '%s\n' "$APPROVAL_JSON" | jq -r '.approvalPath')
|
|
604
|
-
```
|
|
605
|
-
Returns the immutable production authority as `approvalPath` at
|
|
606
|
-
`<project>/.release-skill/approvals/<planDigest>/<approvalDigest>.json`.
|
|
607
|
-
`latestApprovalPath` points to `.release-skill/approval-record.json`, which is
|
|
608
|
-
only a mutable convenience alias and must not be passed to production
|
|
609
|
-
publish/reconcile. Approval expires after 24
|
|
610
|
-
hours; a PARTIAL recovery may create a new approval for the same plan while
|
|
611
|
-
preserving every earlier approval byte-for-byte. Use the returned
|
|
612
|
-
`approvalPath` and `expiresAt` as authority. `--actor` is only an
|
|
613
|
-
unauthenticated local audit label: release-skill performs no identity
|
|
614
|
-
authentication and provides no digital signature, so it cannot prove that
|
|
615
|
-
a real human actually approved — it only records the identity the operator
|
|
616
|
-
self-reports.
|
|
617
|
-
8. **Publish (remote writes start here):**
|
|
618
|
-
```bash
|
|
619
|
-
PUBLISH_JSON=$("${CLI[@]}" publish --root "$PROJECT" \
|
|
620
|
-
--plan "$PLAN_PATH" --approval "$APPROVAL_PATH" \
|
|
621
|
-
--confirm-production "$PLAN_DIGEST" --json)
|
|
622
|
-
printf '%s\n' "$PUBLISH_JSON" | jq .
|
|
623
|
-
PUBLISH_RUN_PATH=$(printf '%s\n' "$PUBLISH_JSON" | jq -r '.runPath')
|
|
624
|
-
```
|
|
625
|
-
Save the returned `runPath`. `PUBLISHED` is **not** the terminal state.
|
|
626
|
-
9. **Verify (consumer install check):**
|
|
627
|
-
```bash
|
|
628
|
-
"${CLI[@]}" verify --root "$PROJECT" \
|
|
629
|
-
--plan "$PLAN_PATH" --run "$PUBLISH_RUN_PATH" \
|
|
630
|
-
--acknowledge-gate-side-effects --json
|
|
631
|
-
```
|
|
632
|
-
Omit the acknowledgement only when the plan has neither consumer gates nor
|
|
633
|
-
a configured npm `smokeBin`. Both execute installed project code without an
|
|
634
|
-
OS or network sandbox.
|
|
635
|
-
|
|
636
|
-
The handoff example requires `jq`. Without it, copy the same four returned JSON
|
|
637
|
-
fields exactly; do not pass the angle-bracket labels shown elsewhere as shell
|
|
638
|
-
syntax.
|
|
639
|
-
|
|
640
|
-
Production prepare seals a standalone Git commit/tree for every public snapshot
|
|
641
|
-
and creates a fixed tarball for every npm unit. Publish globally preflights all
|
|
642
|
-
actions, then executes and observes public branch, tag, npm, GitHub Release,
|
|
643
|
-
and configured Claude/Codex marketplace installation checkpoints. Kimi Code has
|
|
644
|
-
no scriptable install API, so its checkpoint **fails closed** and `publish`
|
|
645
|
-
lands in `PARTIAL` after the automated writes, emitting a version-pinned manual
|
|
646
|
-
install requirement. The operator then launches Kimi Code with the requirement's
|
|
647
|
-
isolated `KIMI_CODE_HOME`, runs the pinned `/plugins install <release-tag URL>`,
|
|
648
|
-
writes a trusted attestation (binding the frozen **plan** digest and the
|
|
649
|
-
snapshot **payload** digest) into the plan-digest-keyed directory
|
|
650
|
-
`.release-skill/kimi-attestations/<planDigest>/<plugin>/`, and re-runs
|
|
651
|
-
`reconcile` (→ `PUBLISHED`) and `verify` (→ `VERIFIED`); both read the
|
|
652
|
-
attestation from that same stable location. An install into the ordinary
|
|
653
|
-
`~/.kimi-code` is not accepted. See `INSTALL.md` for the full procedure and the
|
|
654
|
-
attestation JSON fields. `verify`
|
|
655
|
-
installs every exact npm `package@version` in an isolated directory; when
|
|
656
|
-
`smokeBin` is configured it also runs the CLI and validates output. Only when
|
|
657
|
-
all evidence matches does the run reach `VERIFIED`.
|
|
658
|
-
Before a real release run `gh auth login`, `gh auth setup-git`, and
|
|
659
|
-
`npm login`, and confirm Git HTTPS credentials can access the target repository.
|
|
660
|
-
Version branches default to `release/<tag>` and can be configured per unit with
|
|
661
|
-
`production.branchTemplate`; any existing remote object stops for human review.
|
|
662
|
-
|
|
663
|
-
### Release-document refresh (optional)
|
|
664
|
-
|
|
665
|
-
A release unit can declare `releaseDocuments` so one structured, bilingual
|
|
666
|
-
notes source deterministically refreshes the managed README regions and the
|
|
667
|
-
current CHANGELOG entry. The core CLI runs entirely offline: it does not use
|
|
668
|
-
the network, does not call any large language model, and does not
|
|
669
|
-
auto-translate. It only rewrites the declared managed regions, the unique
|
|
670
|
-
version marker's machine value, and the current CHANGELOG managed entry;
|
|
671
|
-
every byte outside those regions is preserved verbatim. `prepare` only
|
|
672
|
-
checks freshness and never writes the working tree.
|
|
673
|
-
|
|
674
|
-
```yaml
|
|
675
|
-
# .release-skill/project.yaml (release unit fragment)
|
|
676
|
-
releaseUnits:
|
|
677
|
-
- id: my-project
|
|
678
|
-
source: .
|
|
679
|
-
releaseDocuments:
|
|
680
|
-
notesSource: release-notes/{version}.yaml
|
|
681
|
-
locales: [en, zh-CN]
|
|
682
|
-
changelogs:
|
|
683
|
-
- path: CHANGELOG.md
|
|
684
|
-
locale: en
|
|
685
|
-
readmes:
|
|
686
|
-
- path: README.md
|
|
687
|
-
locale: en
|
|
688
|
-
regions: [latest-release]
|
|
689
|
-
versionMarkers:
|
|
690
|
-
- id: current-version
|
|
691
|
-
pattern: '<!-- release-skill:version -->v{version}<!-- /release-skill:version -->'
|
|
692
|
-
- path: README.zh-CN.md
|
|
693
|
-
locale: zh-CN
|
|
694
|
-
regions: [latest-release]
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
`notesSource` and every target path are relative to the release unit root.
|
|
698
|
-
`versionMarkers[].pattern` must match the README's existing unique version
|
|
699
|
-
marker exactly, with `{version}` standing in for the machine value; the
|
|
700
|
-
refresh replaces only that value (zero or multiple matches fail closed).
|
|
701
|
-
|
|
702
|
-
```yaml
|
|
703
|
-
# release-notes/0.1.6.yaml (structured notes source)
|
|
704
|
-
version: 0.1.6
|
|
705
|
-
date: 2026-07-21
|
|
706
|
-
locales:
|
|
707
|
-
en:
|
|
708
|
-
summary: Deterministic multilingual release-document refresh.
|
|
709
|
-
changes:
|
|
710
|
-
added:
|
|
711
|
-
- Refresh managed README regions and changelogs from one source.
|
|
712
|
-
upgradeNotes: Review and commit refreshed documents before prepare.
|
|
713
|
-
zh-CN:
|
|
714
|
-
summary: 从同一说明源确定性刷新多语种发布文档。
|
|
715
|
-
changes:
|
|
716
|
-
added:
|
|
717
|
-
- 自动刷新 README 受管区域和 CHANGELOG。
|
|
718
|
-
upgradeNotes: prepare 前审阅并提交刷新结果。
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
`version` must exactly equal the resolved unit version; every configured
|
|
722
|
-
locale appears exactly once with a non-empty `summary` and at least one
|
|
723
|
-
change under `security`, `breaking`, `added`, `changed`, `deprecated`,
|
|
724
|
-
`removed`, or `fixed`. YAML aliases, duplicate keys, unknown fields, and
|
|
725
|
-
locale fallback all fail closed.
|
|
726
|
-
|
|
727
|
-
1. **Read-only drill:**
|
|
728
|
-
```bash
|
|
729
|
-
"${CLI[@]}" docs refresh --root "$PROJECT" --unit my-project --json
|
|
730
|
-
```
|
|
731
|
-
Prints `status` (`changes` or `clean`), per-file relative `path`,
|
|
732
|
-
`locale`, `kind`, old/new digests, the unit `version`, `locales`,
|
|
733
|
-
`inputDigest`, and `refreshDigest` — a binding over the protocol
|
|
734
|
-
version, the unit, the canonical notes object, the configuration
|
|
735
|
-
projection, and the sorted per-file old/new digests. It never binds
|
|
736
|
-
time, absolute paths, or display text. `nextCommand.argv` carries the
|
|
737
|
-
exact write command.
|
|
738
|
-
2. **Digest-confirmed local write (only after explicit human authorization
|
|
739
|
-
of the local release-document write):**
|
|
740
|
-
```bash
|
|
741
|
-
"${CLI[@]}" docs refresh --root "$PROJECT" --unit my-project \
|
|
742
|
-
--write --confirm-refresh <refreshDigest> \
|
|
743
|
-
--ack-local-document-write --json
|
|
744
|
-
```
|
|
745
|
-
All three bindings are required; a mismatched digest fails closed with
|
|
746
|
-
`RELEASE_DOCS_REFRESH_STALE` and writes nothing. When the candidate is
|
|
747
|
-
unchanged the drill reports `clean` and the write performs zero writes.
|
|
748
|
-
All targets commit as one transaction; a successful write is followed
|
|
749
|
-
by a re-drill that must return `clean`.
|
|
750
|
-
|
|
751
|
-
This authorization covers only the declared local document targets. It is
|
|
752
|
-
not authorization for hooks, Git commits, pushes, publishes, or installs:
|
|
753
|
-
a maintainer must review the refreshed documents, commit them, and rerun
|
|
754
|
-
`prepare` — the new bytes change the snapshot, workspace digest, and plan
|
|
755
|
-
digest, so an earlier approval cannot authorize the refreshed plan.
|
|
756
|
-
|
|
757
|
-
When configured documents drift, `prepare` fails closed with
|
|
758
|
-
`RELEASE_DOCS_STALE` before hooks, baseline, snapshot, remote checks, and
|
|
759
|
-
plan freeze. Recovery: run the drill, review the shown files/locales/
|
|
760
|
-
version/digest, authorize and perform the local write, review and commit
|
|
761
|
-
the result, then rerun `prepare`. `RELEASE_DOCS_INVALID` (bad
|
|
762
|
-
configuration or notes data), `RELEASE_DOCS_TRANSLATION_MISSING` (a
|
|
763
|
-
configured locale absent), and `RELEASE_DOCS_CONFLICT` (unmanaged
|
|
764
|
-
same-version content or marker damage) each require fixing the source or
|
|
765
|
-
target first; never widen the write scope to resolve them.
|
|
766
|
-
|
|
767
|
-
### Parent workspace with npm + plugin sub-units
|
|
768
|
-
|
|
769
|
-
When a monorepo produces both an npm package and a Claude/Codex/Kimi Code
|
|
770
|
-
plugin from different directories, define separate release units. Only add a plugin
|
|
771
|
-
distribution when the unit actually ships a plugin with manifest, marketplace,
|
|
772
|
-
and entry Skill:
|
|
773
|
-
|
|
774
|
-
Here `project` is the parent workspace's orchestration container, not a public
|
|
775
|
-
release unit. If the workspace root also publishes its own repository or
|
|
776
|
-
package, add another release unit with `source: .`.
|
|
777
326
|
`version.source` is resolved relative to that release unit's `source` directory
|
|
778
|
-
(`version.source` 相对于该发布单元的 `source` 目录解析)
|
|
779
|
-
|
|
780
|
-
`packages/app/package.json`.
|
|
327
|
+
(`version.source` 相对于该发布单元的 `source` 目录解析). A monorepo with
|
|
328
|
+
separate npm and plugin units defines multiple release units:
|
|
781
329
|
|
|
782
330
|
```yaml
|
|
783
331
|
apiVersion: release-skill/v1
|
|
@@ -785,7 +333,6 @@ kind: ReleaseProject
|
|
|
785
333
|
project:
|
|
786
334
|
name: my-workspace
|
|
787
335
|
defaultBranch: main
|
|
788
|
-
|
|
789
336
|
releaseUnits:
|
|
790
337
|
- id: my-app
|
|
791
338
|
source: packages/app
|
|
@@ -801,28 +348,16 @@ releaseUnits:
|
|
|
801
348
|
tag: latest
|
|
802
349
|
registry: https://registry.npmjs.org
|
|
803
350
|
publisher: my-npm-username
|
|
804
|
-
smokeBin: my-app
|
|
805
|
-
smokeArgs: [help, --json]
|
|
806
|
-
smokeExpectedJson:
|
|
807
|
-
command: help
|
|
808
|
-
status: READY
|
|
809
351
|
publicFiles:
|
|
810
|
-
- from: packages/app/README.md
|
|
811
|
-
to: README.md
|
|
812
|
-
mode: preserve
|
|
813
352
|
- from: packages/app/package.json
|
|
814
353
|
to: package.json
|
|
815
354
|
mode: preserve
|
|
816
|
-
|
|
817
|
-
to: LICENSE
|
|
818
|
-
mode: preserve
|
|
819
|
-
requiredPublicFiles: [README.md, package.json, LICENSE]
|
|
355
|
+
requiredPublicFiles: [package.json]
|
|
820
356
|
previousPublicBaseline:
|
|
821
357
|
mode: none
|
|
822
358
|
production:
|
|
823
359
|
branchTemplate: release/{tag}
|
|
824
|
-
|
|
825
|
-
|
|
360
|
+
branchStrategy: create-release-branch
|
|
826
361
|
- id: my-plugin
|
|
827
362
|
source: packages/plugin
|
|
828
363
|
publicRepo: owner/my-plugin
|
|
@@ -830,166 +365,66 @@ releaseUnits:
|
|
|
830
365
|
source: package.json
|
|
831
366
|
tagTemplate: my-plugin-v{version}
|
|
832
367
|
distributions:
|
|
833
|
-
# Declare plugin consumers only when the unit ships a plugin.
|
|
834
|
-
# The CLI smoke is independent; only declare smokeBin when the plugin
|
|
835
|
-
# package also exposes a CLI binary.
|
|
836
368
|
- type: claude-plugin
|
|
837
369
|
plugin: my-plugin
|
|
838
370
|
marketplace: my-plugin
|
|
839
371
|
entrySkill: my-plugin-help
|
|
840
|
-
|
|
841
|
-
- type: codex-plugin
|
|
842
|
-
plugin: my-plugin
|
|
843
|
-
marketplace: my-plugin
|
|
844
|
-
entrySkill: my-plugin-help
|
|
845
|
-
timeoutMs: 300000 # optional; range 30000-900000; default 300000
|
|
846
|
-
- type: kimi-plugin
|
|
847
|
-
plugin: my-plugin
|
|
848
|
-
entrySkill: my-plugin-help
|
|
849
|
-
timeoutMs: 300000 # optional; range 30000-900000; default 300000 (Kimi has no install command; bounds read-only verification)
|
|
372
|
+
marketplaceSourceType: bundled-family
|
|
850
373
|
publicFiles:
|
|
851
|
-
- from: packages/plugin/.claude-plugin/plugin.json
|
|
852
|
-
to: .claude-plugin/plugin.json
|
|
853
|
-
mode: preserve
|
|
854
|
-
- from: packages/plugin/.claude-plugin/marketplace.json
|
|
855
|
-
to: .claude-plugin/marketplace.json
|
|
856
|
-
mode: preserve
|
|
857
|
-
- from: packages/plugin/.codex-plugin/plugin.json
|
|
858
|
-
to: .codex-plugin/plugin.json
|
|
859
|
-
mode: preserve
|
|
860
|
-
- from: packages/plugin/.kimi-plugin/plugin.json
|
|
861
|
-
to: .kimi-plugin/plugin.json
|
|
862
|
-
mode: preserve
|
|
863
|
-
- from: packages/plugin/.agents/plugins/marketplace.json
|
|
864
|
-
to: .agents/plugins/marketplace.json
|
|
865
|
-
mode: preserve
|
|
866
|
-
- from: packages/plugin/skills/my-plugin-help/SKILL.md
|
|
867
|
-
to: skills/my-plugin-help/SKILL.md
|
|
868
|
-
mode: preserve
|
|
869
|
-
- from: packages/plugin/README.md
|
|
870
|
-
to: README.md
|
|
871
|
-
mode: preserve
|
|
872
374
|
- from: packages/plugin/package.json
|
|
873
375
|
to: package.json
|
|
874
376
|
mode: preserve
|
|
875
|
-
|
|
876
|
-
to: LICENSE
|
|
877
|
-
mode: preserve
|
|
878
|
-
requiredPublicFiles:
|
|
879
|
-
- .claude-plugin/plugin.json
|
|
880
|
-
- .claude-plugin/marketplace.json
|
|
881
|
-
- .codex-plugin/plugin.json
|
|
882
|
-
- .kimi-plugin/plugin.json
|
|
883
|
-
- .agents/plugins/marketplace.json
|
|
884
|
-
- skills/my-plugin-help/SKILL.md
|
|
885
|
-
- README.md
|
|
886
|
-
- package.json
|
|
887
|
-
- LICENSE
|
|
377
|
+
requiredPublicFiles: [package.json]
|
|
888
378
|
previousPublicBaseline:
|
|
889
379
|
mode: none
|
|
890
380
|
production:
|
|
891
381
|
branchTemplate: release/{tag}
|
|
892
|
-
|
|
382
|
+
branchStrategy: create-release-branch
|
|
893
383
|
```
|
|
894
384
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
the entry Skill, and all required public files. A CLI smoke (`smokeBin`) is
|
|
898
|
-
optional for plugin units and only applies when the published npm package
|
|
899
|
-
exposes a CLI binary.
|
|
900
|
-
|
|
901
|
-
Plugin distributions may declare `timeoutMs` (range 30,000--900,000 ms; default
|
|
902
|
-
300,000 ms). This sets the subprocess timeout for the marketplace add, plugin
|
|
903
|
-
install, and plugin list commands. On real networks these commands can take
|
|
904
|
-
40--105 seconds; the default 300-second timeout avoids false `PARTIAL` failures.
|
|
905
|
-
The resolved value is frozen into the plan and approved along with all other
|
|
906
|
-
action parameters. Old plans without `timeoutMs` default to 300,000 ms at
|
|
907
|
-
execution time for backward compatibility.
|
|
908
|
-
|
|
909
|
-
### PARTIAL recovery and reconcile
|
|
910
|
-
|
|
911
|
-
When `publish` succeeds at some checkpoints but fails at others, the run enters
|
|
912
|
-
`PARTIAL` status. **Do not restart from scratch and do not delete remote state**
|
|
913
|
-
(e.g., do not delete a tag that was already pushed, or unpublish a package).
|
|
385
|
+
Add a gate to the extracted `recommendedAnswers` by editing `verificationGates`
|
|
386
|
+
and binding the same id in `selectedGateIds`:
|
|
914
387
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
"
|
|
928
|
-
|
|
929
|
-
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"projectConfig": {
|
|
391
|
+
"apiVersion": "release-skill/v1",
|
|
392
|
+
"kind": "ReleaseProject",
|
|
393
|
+
"project": { "name": "my-project", "defaultBranch": "main" },
|
|
394
|
+
"releaseUnits": [{
|
|
395
|
+
"id": "my-project",
|
|
396
|
+
"source": ".",
|
|
397
|
+
"publicRepo": "owner/my-project",
|
|
398
|
+
"version": { "source": "package.json", "tagTemplate": "v{version}" },
|
|
399
|
+
"distributions": [{
|
|
400
|
+
"type": "npm", "package": "my-project", "access": "public",
|
|
401
|
+
"provenance": false, "tag": "latest",
|
|
402
|
+
"registry": "https://registry.npmjs.org", "publisher": "my-npm-username"
|
|
403
|
+
}],
|
|
404
|
+
"publicFiles": [{ "from": "package.json", "to": "package.json", "mode": "preserve" }],
|
|
405
|
+
"requiredPublicFiles": ["package.json"],
|
|
406
|
+
"previousPublicBaseline": { "mode": "none" },
|
|
407
|
+
"production": { "branchTemplate": "release/{tag}", "branchStrategy": "create-release-branch" }
|
|
408
|
+
}],
|
|
409
|
+
"verificationGates": [{
|
|
410
|
+
"id": "my-project-script-test",
|
|
411
|
+
"phase": "snapshot-verify",
|
|
412
|
+
"scope": { "unit": "my-project" },
|
|
413
|
+
"command": ["node", "-e", "const p=require('./package.json');if(!p.name)process.exit(1)"],
|
|
414
|
+
"cwd": ".",
|
|
415
|
+
"timeoutMs": 30000,
|
|
416
|
+
"envAllowlist": []
|
|
417
|
+
}]
|
|
418
|
+
},
|
|
419
|
+
"selectedGateIds": ["my-project-script-test"]
|
|
420
|
+
}
|
|
930
421
|
```
|
|
931
422
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
`reconcile` queries the actual remote state (Git refs, npm version, GitHub
|
|
938
|
-
Release, marketplace install), skips any step whose evidence already matches
|
|
939
|
-
the frozen plan, and retries only safe and incomplete steps. Remote conflicts
|
|
940
|
-
(e.g., an unexpected tag or npm version) require human decision and cannot be
|
|
941
|
-
auto-resolved.
|
|
942
|
-
Successful reconcile returns `PUBLISHED`, not `VERIFIED`; only the fresh
|
|
943
|
-
`verify` run may produce the terminal `VERIFIED` state.
|
|
944
|
-
|
|
945
|
-
## Accepted capabilities
|
|
946
|
-
|
|
947
|
-
- validates project configuration and release units;
|
|
948
|
-
- reports readiness without changing the project during `assess`;
|
|
949
|
-
- copies configured public files into an isolated snapshot;
|
|
950
|
-
- discovers first-use candidates read-only and creates a config only once after
|
|
951
|
-
exact `setupDigest` confirmation;
|
|
952
|
-
- runs human-selected project gates in frozen-snapshot copies and exact
|
|
953
|
-
consumer installation roots;
|
|
954
|
-
- checks required files, path safety, exact bytes/modes, and obvious leaks;
|
|
955
|
-
- records Git/workspace identity and freezes a digest-bound release plan;
|
|
956
|
-
- binds approval to the plan digest, expiry, and explicit action allowlist;
|
|
957
|
-
- publishes only frozen Git objects and npm tarballs, then checks remote
|
|
958
|
-
commit/tree/tag/integrity;
|
|
959
|
-
- installs configured Claude/Codex plugins from the frozen Git ref and proves
|
|
960
|
-
the entry Skill and payload digest in fresh isolated consumer homes; for Kimi
|
|
961
|
-
Code (no scriptable install API) it emits a version-pinned manual install
|
|
962
|
-
requirement and proves the entry Skill and payload digest only from a trusted
|
|
963
|
-
attestation bound to the frozen plan digest;
|
|
964
|
-
- supports an external independent marketplace for Claude/Codex distributions
|
|
965
|
-
(`marketplaceRepo`): `prepare --online --production` freezes the external
|
|
966
|
-
marketplace HEAD (Codex commit sha / Claude default branch name), validates the
|
|
967
|
-
marketplace index entry at that sha, and verifies the installed payload against
|
|
968
|
-
the unit's own frozen snapshot whole-tree (`external-marketplace-v1`), while the
|
|
969
|
-
install-side CLI list observation fails closed on version drift;
|
|
970
|
-
- ships a generated, self-contained CodeBuddy/WorkBuddy adapter
|
|
971
|
-
(`adapters/workbuddy/`, manifest `.codebuddy-plugin/plugin.json`, skills
|
|
972
|
-
rendered with `${CODEBUDDY_PLUGIN_ROOT}`) alongside the Claude/Codex/Kimi
|
|
973
|
-
adapters; because the codebuddy CLI cannot pin a frozen ref there is no
|
|
974
|
-
automated marketplace install checkpoint, so it emits a manual install
|
|
975
|
-
requirement and proves the entry Skill and payload digest only from a trusted
|
|
976
|
-
attestation bound to the frozen plan digest;
|
|
977
|
-
- distinguishes `PUBLISHED` (writes completed) from `VERIFIED` (remote and
|
|
978
|
-
consumer installation evidence completed);
|
|
979
|
-
- stops subsequent checkpoints on failure and writes a separate run record
|
|
980
|
-
without mutating the frozen plan or undoing successful remote actions.
|
|
981
|
-
|
|
982
|
-
## Project-specific verification: hooks and gates
|
|
983
|
-
|
|
984
|
-
`hooks.docs/build/test/typecheck/lint` run before the snapshot is frozen. Use
|
|
985
|
-
them only for work that genuinely needs the parent workspace or generates
|
|
986
|
-
source files. They can modify files or access the network, so prepare requires
|
|
987
|
-
`--acknowledge-hook-side-effects`.
|
|
988
|
-
|
|
989
|
-
Each hook is an object, never a bare command list.
|
|
990
|
-
`command` is an executable/argument array, not a shell string
|
|
991
|
-
(`command` 是可执行文件/参数数组,不是 shell 字符串). Each hook also declares
|
|
992
|
-
`cwd`, `timeoutMs`, and `envAllowlist`:
|
|
423
|
+
### Hooks and gates
|
|
424
|
+
|
|
425
|
+
`hooks.docs/build/test/typecheck/lint` run before the snapshot is frozen. Each
|
|
426
|
+
hook is an object. `command` is an executable/argument array, not a shell string
|
|
427
|
+
(`command` 是可执行文件/参数数组,不是 shell 字符串):
|
|
993
428
|
|
|
994
429
|
```yaml
|
|
995
430
|
hooks:
|
|
@@ -1005,118 +440,24 @@ hooks:
|
|
|
1005
440
|
envAllowlist: []
|
|
1006
441
|
```
|
|
1007
442
|
|
|
1008
|
-
Hooks
|
|
1009
|
-
|
|
1010
|
-
`
|
|
1011
|
-
|
|
1012
|
-
`verificationGates` are the controlled extension point for release calibration:
|
|
1013
|
-
|
|
1014
|
-
```yaml
|
|
1015
|
-
verificationGates:
|
|
1016
|
-
- id: package-contract
|
|
1017
|
-
phase: snapshot-verify
|
|
1018
|
-
scope: { unit: my-project }
|
|
1019
|
-
command:
|
|
1020
|
-
- node
|
|
1021
|
-
- -e
|
|
1022
|
-
- "const p=require('./package.json'); if (!p.name) process.exit(1)"
|
|
1023
|
-
cwd: .
|
|
1024
|
-
timeoutMs: 120000
|
|
1025
|
-
envAllowlist: [CI]
|
|
1026
|
-
- id: installed-help
|
|
1027
|
-
phase: consumer-verify
|
|
1028
|
-
scope: { unit: my-project, distribution: npm }
|
|
1029
|
-
command: [node, scripts/check-installed-help.mjs]
|
|
1030
|
-
cwd: .
|
|
1031
|
-
timeoutMs: 30000
|
|
1032
|
-
envAllowlist: []
|
|
1033
|
-
expectedJson: { status: READY }
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
The snapshot example is deliberately self-contained and reads only a mapped
|
|
1037
|
-
public file. Any replacement script and every dependency it needs must exist
|
|
1038
|
-
in the frozen public snapshot. The consumer script must likewise be present in
|
|
1039
|
-
the exact installed distribution; gates cannot borrow tests, development
|
|
1040
|
-
dependencies, or `node_modules` from the parent workspace.
|
|
1041
|
-
|
|
1042
|
-
`snapshot-verify` runs in a disposable writable copy of the frozen public
|
|
1043
|
-
snapshot. `consumer-verify` runs from an exact isolated npm/Claude/Codex/Kimi
|
|
1044
|
-
Code install root. Both use executable arrays instead of shell strings; definitions and
|
|
1045
|
-
results enter digest-bound evidence, and prepare/verify require
|
|
1046
|
-
`--acknowledge-gate-side-effects`. Gates are still project processes without a
|
|
1047
|
-
network sandbox, so release-skill cannot promise that they will not write files
|
|
1048
|
-
or access the network. Push, tag, default-branch changes, GitHub Releases, and
|
|
1049
|
-
npm publish may never be hooks/gates; they remain controlled plan actions.
|
|
1050
|
-
|
|
1051
|
-
## What it does not do yet
|
|
1052
|
-
|
|
1053
|
-
<!-- release-skill:capability:unsupported-scope -->
|
|
1054
|
-
- no automatic README generation or source-file overwrite;
|
|
1055
|
-
- no automatic conflict merge or rollback workflow;
|
|
1056
|
-
- no claim that a real production canary has run for marketplace verification;
|
|
1057
|
-
- `prepare --online` observes previous public baselines (bound mode) and defers
|
|
1058
|
-
remote uniqueness checks to publish global preflight;
|
|
1059
|
-
- no overwrite of branches/tags/releases or npm unpublish; create-only refs use
|
|
1060
|
-
`--force-with-lease=<ref>:` solely as an atomic compare-and-set assertion that
|
|
1061
|
-
the ref is absent, while existing branches use an ordinary non-force push;
|
|
1062
|
-
- no automated CodeBuddy/WorkBuddy marketplace install checkpoint — the
|
|
1063
|
-
codebuddy CLI cannot pin a frozen ref, so installation is a manual step proven
|
|
1064
|
-
by the same attestation closed loop as Kimi Code;
|
|
1065
|
-
- no promise of Windows or broad multi-platform native write support;
|
|
1066
|
-
- no hidden commit, push, tag, release, or package publication.
|
|
1067
|
-
|
|
1068
|
-
### Write Safety
|
|
1069
|
-
|
|
1070
|
-
`setup` is read-only by default and may create a config only once after exact
|
|
1071
|
-
digest confirmation. `assess` is read-only unless an explicit report output is requested. `prepare`
|
|
1072
|
-
writes local files under `.release-skill/`; it does not write project source
|
|
1073
|
-
files or remote services. If hooks are configured, they are arbitrary local
|
|
1074
|
-
processes and require `--acknowledge-hook-side-effects`; hooks may have their
|
|
1075
|
-
own filesystem or network side effects. Gates are also project processes and
|
|
1076
|
-
require `--acknowledge-gate-side-effects`; they may have the same side effects.
|
|
1077
|
-
`publish` is the production write entry
|
|
1078
|
-
and requires both approval and the current plan digest. Omit hooks and use local
|
|
1079
|
-
sandbox targets for the smallest safe rehearsal.
|
|
1080
|
-
|
|
1081
|
-
### If something fails
|
|
1082
|
-
|
|
1083
|
-
| Result | What to do |
|
|
1084
|
-
|---|---|
|
|
1085
|
-
| `NEEDS_INPUT` | Complete setup's repository, tag, channel, baseline, and gate decisions. |
|
|
1086
|
-
| `LOCAL_ONLY_DETECTED` | Establish a remote channel or keep only a local configuration design; do not claim production readiness. |
|
|
1087
|
-
| `SETUP_DIGEST_MISMATCH` | Facts or answers changed; rerun dry-run, review, and confirm the new digest. |
|
|
1088
|
-
| `CONFIG_EXISTS` | Setup never overwrites the existing config; assess it and edit incrementally. |
|
|
1089
|
-
| `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. |
|
|
1090
|
-
| `CONFIG_INVALID` | Correct `.release-skill/project.yaml`, then rerun `assess`. |
|
|
1091
|
-
| `PUBLIC_FILE_MISSING` | Add or correct the configured public file. |
|
|
1092
|
-
| `FORBIDDEN_CONTENT_DETECTED` | Remove the leaked/private content, then prepare again. |
|
|
1093
|
-
| `SNAPSHOT_FIDELITY_FAILED` | Inspect the source/snapshot path and rerun `prepare`. |
|
|
1094
|
-
| `BASELINE_CHANGED` | Keep the human edit, then prepare, review, and approve again. |
|
|
1095
|
-
| `GATE_FAILED` during `prepare` | Fix the snapshot gate or frozen public artifact, then run a new `prepare`; the failed plan cannot be approved. |
|
|
1096
|
-
| `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. |
|
|
1097
|
-
| `PARTIAL` | Do not restart or delete remote state; review the returned `runPath` and run `reconcile` (see above). |
|
|
1098
|
-
| `PUBLISHED` | Run `verify --plan <planPath> --run <publishRunPath>`; this is not terminal success. |
|
|
1099
|
-
| `VERIFIED` | Remote state, exact npm install, and configured plugin consumer installs all matched the frozen plan. |
|
|
443
|
+
Hooks run only after human review and with
|
|
444
|
+
`prepare --acknowledge-hook-side-effects`. Gates are the controlled extension
|
|
445
|
+
point for release calibration (see `references/02-project-config.md`).
|
|
1100
446
|
|
|
1101
447
|
## Skills
|
|
1102
448
|
|
|
1103
449
|
- `release-help`: environment check and next-step guidance.
|
|
1104
|
-
- `release-setup`: read-only discovery, human calibration, and create-once
|
|
450
|
+
- `release-setup`: read-only discovery, human calibration, and create-once configuration.
|
|
1105
451
|
- `release-assess`: read-only release readiness report.
|
|
1106
452
|
- `release-prepare`: local snapshot and reviewable release plan.
|
|
1107
453
|
- `release-publish`: approved, digest-confirmed frozen GitHub+npm publishing.
|
|
1108
454
|
- `release-reconcile`: evidence-based PARTIAL recovery with human intervention on conflicts.
|
|
1109
455
|
- `release-verify`: post-publish verification; only `VERIFIED` is the happy end.
|
|
1110
456
|
|
|
1111
|
-
Conflicts still default to human intervention. The npm-installed `release-skill`
|
|
1112
|
-
CLI is the supported user entry after v0.1.1 production publication; source
|
|
1113
|
-
checkout remains the development/contributor fallback.
|
|
1114
|
-
|
|
1115
457
|
## Platform distribution
|
|
1116
458
|
|
|
1117
459
|
One deterministic core engine ships to several targets through build-only adapter
|
|
1118
|
-
closures. A release unit declares what reaches users via `distributions
|
|
1119
|
-
distribution type maps to a concrete artifact:
|
|
460
|
+
closures. A release unit declares what reaches users via `distributions`:
|
|
1120
461
|
|
|
1121
462
|
| `distributions` type | Physical artifact | Install |
|
|
1122
463
|
|---|---|---|
|
|
@@ -1124,17 +465,29 @@ distribution type maps to a concrete artifact:
|
|
|
1124
465
|
| `claude-plugin` | self-contained closure under `adapters/claude/` | automated marketplace checkpoint |
|
|
1125
466
|
| `codex-plugin` | self-contained closure under `adapters/codex/` | automated marketplace checkpoint |
|
|
1126
467
|
| `kimi-plugin` | self-contained closure (no scriptable install API) | manual, attestation-bound |
|
|
1127
|
-
| `codebuddy-plugin` | generated `adapters/workbuddy/` with `.codebuddy-plugin/plugin.json`
|
|
1128
|
-
|
|
1129
|
-
Each adapter closure bundles its own
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
requirement; the CodeBuddy/WorkBuddy checkpoint likewise fails closed — because
|
|
1134
|
-
the codebuddy CLI cannot pin a frozen ref there is no automated install
|
|
1135
|
-
checkpoint — and produces a manual install requirement proven by a trusted
|
|
468
|
+
| `codebuddy-plugin` | generated `adapters/workbuddy/` with `.codebuddy-plugin/plugin.json` | manual, attestation-bound |
|
|
469
|
+
|
|
470
|
+
Each adapter closure bundles its own CLI, skills, and schemas for zero external
|
|
471
|
+
dependency after installation. `publish` only publishes frozen Git objects and
|
|
472
|
+
npm tarballs, then checks remote commit/tree/tag integrity. Claude/Codex
|
|
473
|
+
verification is automated; Kimi Code and CodeBuddy/WorkBuddy require a trusted
|
|
1136
474
|
attestation bound to the frozen plan digest.
|
|
1137
475
|
|
|
476
|
+
<!-- release-skill:capability:unsupported-scope -->
|
|
477
|
+
- no automatic README generation or source-file overwrite;
|
|
478
|
+
- no automatic conflict merge or rollback workflow;
|
|
479
|
+
- no claim that a real production canary has run for marketplace verification;
|
|
480
|
+
- `prepare --online` observes previous public baselines (bound mode) and defers
|
|
481
|
+
remote uniqueness checks to publish global preflight;
|
|
482
|
+
- no overwrite of branches/tags/releases or npm unpublish; create-only refs use
|
|
483
|
+
`--force-with-lease=<ref>:` solely as an atomic compare-and-set assertion that
|
|
484
|
+
the ref is absent, while existing branches use an ordinary non-force push;
|
|
485
|
+
- no automated CodeBuddy/WorkBuddy marketplace install checkpoint — the
|
|
486
|
+
codebuddy CLI cannot pin a frozen ref, so installation is a manual step proven
|
|
487
|
+
by the same attestation closed loop as Kimi Code;
|
|
488
|
+
- no promise of Windows or broad multi-platform native write support;
|
|
489
|
+
- no hidden commit, push, tag, release, or package publication.
|
|
490
|
+
|
|
1138
491
|
## License
|
|
1139
492
|
|
|
1140
493
|
MIT. See [LICENSE](LICENSE).
|