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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/CHANGELOG.md +60 -0
- package/INSTALL.md +179 -5
- package/INSTALL.zh-CN.md +320 -0
- package/README.md +347 -67
- package/README.zh-CN.md +318 -59
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/skills/release-help/SKILL.md +7 -4
- package/adapters/claude/skills/release-prepare/SKILL.md +11 -1
- package/adapters/claude/skills/release-publish/SKILL.md +6 -3
- package/adapters/claude/skills/release-reconcile/SKILL.md +1 -1
- package/adapters/claude/skills/release-setup/SKILL.md +111 -0
- package/adapters/claude/skills/release-verify/SKILL.md +5 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/skills/release-help/SKILL.md +7 -4
- package/adapters/codex/skills/release-prepare/SKILL.md +11 -1
- package/adapters/codex/skills/release-publish/SKILL.md +6 -3
- package/adapters/codex/skills/release-reconcile/SKILL.md +1 -1
- package/adapters/codex/skills/release-setup/SKILL.md +111 -0
- package/adapters/codex/skills/release-verify/SKILL.md +5 -2
- package/bin/release-skill.mjs +65 -9
- package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
- package/native/safe-write/prebuilds.json +22 -2
- package/native/safe-write/src/safe_write.cc +11 -2
- package/package.json +3 -1
- package/references/02-project-config.md +54 -3
- package/references/05-evidence-and-errors.md +6 -2
- package/schemas/release-plan.schema.json +550 -65
- package/schemas/release-project.schema.json +398 -29
- package/schemas/release-run.schema.json +165 -18
- package/skills/release-help/SKILL.md +7 -4
- package/skills/release-prepare/SKILL.md +11 -1
- package/skills/release-publish/SKILL.md +6 -3
- package/skills/release-reconcile/SKILL.md +1 -1
- package/skills/release-setup/SKILL.md +111 -0
- package/skills/release-verify/SKILL.md +5 -2
- package/skills-src/release-help/SKILL.md +7 -4
- package/skills-src/release-prepare/SKILL.md +11 -1
- package/skills-src/release-publish/SKILL.md +6 -3
- package/skills-src/release-reconcile/SKILL.md +1 -1
- package/skills-src/release-setup/SKILL.md +111 -0
- package/skills-src/release-verify/SKILL.md +5 -2
- package/src/adapters/contract.mjs +3 -0
- package/src/adapters/git-github.mjs +84 -2
- package/src/adapters/plugin-marketplace.mjs +65 -21
- package/src/adapters/push-snapshot.mjs +84 -17
- package/src/commands/prepare.mjs +223 -20
- package/src/commands/publish.mjs +45 -0
- package/src/commands/reconcile.mjs +152 -0
- package/src/commands/setup.mjs +886 -0
- package/src/commands/verify.mjs +122 -26
- package/src/core/config.mjs +34 -0
- package/src/core/errors.mjs +4 -0
- package/src/core/plan.mjs +123 -0
- package/src/core/previous-public-baseline.mjs +21 -1
- package/src/core/verification-gates.mjs +451 -0
- package/src/snapshot/frozen.mjs +89 -5
package/bin/release-skill.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { parseNodeMajor, meetsMinimum, computeReadinessStatus } from '../src/cor
|
|
|
7
7
|
|
|
8
8
|
const execFile = promisify(execFileCb);
|
|
9
9
|
|
|
10
|
-
const COMMANDS = new Set(['help', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'artifacts']);
|
|
10
|
+
const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'artifacts']);
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Check if a command is available and get its version.
|
|
@@ -113,6 +113,11 @@ async function performEnvironmentChecks() {
|
|
|
113
113
|
*/
|
|
114
114
|
function getCapabilityMaturity() {
|
|
115
115
|
return {
|
|
116
|
+
setup: {
|
|
117
|
+
available: true,
|
|
118
|
+
mode: 'read-only dry-run / confirmed create-once',
|
|
119
|
+
description: 'Discover first-use facts and create an absent config only after exact setupDigest confirmation',
|
|
120
|
+
},
|
|
116
121
|
assess: {
|
|
117
122
|
available: true,
|
|
118
123
|
mode: 'read-only',
|
|
@@ -125,17 +130,17 @@ function getCapabilityMaturity() {
|
|
|
125
130
|
},
|
|
126
131
|
publish: {
|
|
127
132
|
available: true,
|
|
128
|
-
mode: 'controlled production (sandbox
|
|
133
|
+
mode: 'controlled production (protocol-tested; no OS/network sandbox)',
|
|
129
134
|
description: 'Publishes frozen GitHub/npm artifacts and runs configured Claude/Codex consumer checkpoints with approval and exact digest confirmation',
|
|
130
135
|
},
|
|
131
136
|
reconcile: {
|
|
132
137
|
available: true,
|
|
133
|
-
mode: 'evidence-based recovery (sandbox
|
|
138
|
+
mode: 'evidence-based recovery (protocol-tested; no OS/network sandbox)',
|
|
134
139
|
description: 'Reconcile PARTIAL runs, retry safe missing checkpoints, and stop for human decisions on conflicts',
|
|
135
140
|
},
|
|
136
141
|
verify: {
|
|
137
142
|
available: true,
|
|
138
|
-
mode: 'fresh consumer verification (sandbox
|
|
143
|
+
mode: 'fresh consumer verification (protocol-tested; no OS/network sandbox)',
|
|
139
144
|
description: 'Recheck remote state, exact npm installation, CLI help, and configured Claude/Codex installs before VERIFIED',
|
|
140
145
|
},
|
|
141
146
|
};
|
|
@@ -149,6 +154,7 @@ Usage:
|
|
|
149
154
|
|
|
150
155
|
Commands:
|
|
151
156
|
help Show this help message and exit
|
|
157
|
+
setup Discover first-use configuration and gate candidates (dry-run by default)
|
|
152
158
|
assess Read-only assessment of project release readiness
|
|
153
159
|
prepare Freeze a release plan (release-skill output to .release-skill/; hooks may do remote ops)
|
|
154
160
|
approve Record local approval for a frozen release plan
|
|
@@ -166,12 +172,17 @@ Options:
|
|
|
166
172
|
--confirm-production <digest> Confirm the exact production plan digest
|
|
167
173
|
--output <path> Override prepare/approve output path (non-production only)
|
|
168
174
|
--run-dir <path> Override prepare run directory; production requires one direct child of .release-skill/runs
|
|
175
|
+
--answers <path> Human-reviewed setup answers JSON
|
|
176
|
+
--write Create an absent project.yaml during setup; never overwrites
|
|
177
|
+
--confirm-setup <digest> Confirm exact setup facts and answers before create
|
|
178
|
+
--acknowledge-hook-side-effects Acknowledge unsandboxed legacy hook execution
|
|
179
|
+
--acknowledge-gate-side-effects Acknowledge unsandboxed local verification gate execution
|
|
169
180
|
--json Output results as JSON
|
|
170
181
|
--version Show version and exit
|
|
171
182
|
-h, --help Show this help message and exit
|
|
172
183
|
|
|
173
184
|
Safety:
|
|
174
|
-
Safe default: help -> assess -> prepare --offline -> human review.
|
|
185
|
+
Safe default: help -> setup (when config is absent) -> assess -> prepare --offline -> human review.
|
|
175
186
|
Production happy end: prepare --production -> approve -> publish -> verify.
|
|
176
187
|
prepare copies current public files into a local snapshot; it does not rewrite source files.
|
|
177
188
|
- Default mode is offline (release-skill pipeline does no remote writes)
|
|
@@ -185,6 +196,7 @@ Safety:
|
|
|
185
196
|
|
|
186
197
|
First safe command:
|
|
187
198
|
release-skill help --json # Environment check (read-only)
|
|
199
|
+
release-skill setup --root <path> --json # First-use discovery (read-only)
|
|
188
200
|
release-skill assess --root <path> --offline --json # Project assessment`);
|
|
189
201
|
}
|
|
190
202
|
|
|
@@ -245,12 +257,13 @@ if (!command || command === 'help') {
|
|
|
245
257
|
checks,
|
|
246
258
|
capabilities,
|
|
247
259
|
maturity: {
|
|
260
|
+
setup: 'read-only by default; create-once requires answers plus exact setupDigest confirmation',
|
|
248
261
|
assess: 'read-only (default); --output writes local report',
|
|
249
|
-
prepare: 'offline local writes;
|
|
262
|
+
prepare: 'offline local writes; configured hooks/gates require their explicit side-effect acknowledgements',
|
|
250
263
|
onlinePrepare: 'previous-public-baseline observation available; production mode freezes publish artifacts and fails closed on drift or unknown state',
|
|
251
|
-
publish: 'GitHub/npm plus configured Claude/Codex consumer checkpoints are sandbox
|
|
252
|
-
reconcile: 'PARTIAL recovery sandbox
|
|
253
|
-
verify: 'fresh exact npm and Claude/Codex consumer installation checks are sandbox
|
|
264
|
+
publish: 'GitHub/npm plus configured Claude/Codex consumer checkpoints are protocol-tested without an OS/network sandbox; approval and exact digest confirmation required',
|
|
265
|
+
reconcile: 'PARTIAL recovery is protocol-tested without an OS/network sandbox; remote conflicts require human intervention',
|
|
266
|
+
verify: 'fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; configured consumer processes require explicit acknowledgement; success reaches VERIFIED',
|
|
254
267
|
},
|
|
255
268
|
recommendations: [],
|
|
256
269
|
};
|
|
@@ -309,6 +322,45 @@ if (!COMMANDS.has(command)) {
|
|
|
309
322
|
process.exit(2);
|
|
310
323
|
}
|
|
311
324
|
|
|
325
|
+
// --- Setup command routing ---
|
|
326
|
+
if (command === 'setup') {
|
|
327
|
+
const { setupProject } = await import('../src/commands/setup.mjs');
|
|
328
|
+
const rootIdx = args.indexOf('--root');
|
|
329
|
+
const rawRoot = rootIdx !== -1 && args[rootIdx + 1] ? args[rootIdx + 1] : process.cwd();
|
|
330
|
+
const root = resolve(rawRoot);
|
|
331
|
+
const answersIdx = args.indexOf('--answers');
|
|
332
|
+
const answersPath = answersIdx !== -1 && args[answersIdx + 1] ? args[answersIdx + 1] : undefined;
|
|
333
|
+
const confirmationIdx = args.indexOf('--confirm-setup');
|
|
334
|
+
const confirmSetup = confirmationIdx !== -1 && args[confirmationIdx + 1]
|
|
335
|
+
? args[confirmationIdx + 1]
|
|
336
|
+
: undefined;
|
|
337
|
+
const write = args.includes('--write');
|
|
338
|
+
try {
|
|
339
|
+
const report = await setupProject({ root, answersPath, write, confirmSetup });
|
|
340
|
+
if (hasJson) {
|
|
341
|
+
console.log(JSON.stringify(report, null, 2));
|
|
342
|
+
} else {
|
|
343
|
+
console.log(`Setup status: ${report.status}`);
|
|
344
|
+
if (report.setupDigest) console.log(`Setup digest: ${report.setupDigest}`);
|
|
345
|
+
if (report.configPath) console.log(`Config: ${report.configPath}`);
|
|
346
|
+
if (report.next) console.log(`Next: ${report.next}`);
|
|
347
|
+
}
|
|
348
|
+
process.exit(['READY_TO_WRITE', 'CONFIG_CREATED', 'ALREADY_CONFIGURED'].includes(report.status) ? 0 : 2);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
if (hasJson) {
|
|
351
|
+
console.log(JSON.stringify({
|
|
352
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
353
|
+
message: err.message,
|
|
354
|
+
details: err.details ?? {},
|
|
355
|
+
exitCode: err.exitCode ?? 1,
|
|
356
|
+
}));
|
|
357
|
+
} else {
|
|
358
|
+
console.error(`Error: ${err.message}`);
|
|
359
|
+
}
|
|
360
|
+
process.exit(err.exitCode ?? 1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
312
364
|
// --- Assess command routing ---
|
|
313
365
|
if (command === 'assess') {
|
|
314
366
|
const { assessProject } = await import('../src/commands/assess.mjs');
|
|
@@ -366,6 +418,7 @@ if (command === 'prepare') {
|
|
|
366
418
|
}
|
|
367
419
|
|
|
368
420
|
const hooksAuthorized = args.includes('--acknowledge-hook-side-effects');
|
|
421
|
+
const verificationGatesAuthorized = args.includes('--acknowledge-gate-side-effects');
|
|
369
422
|
const production = args.includes('--production');
|
|
370
423
|
const outputIdx = args.indexOf('--output');
|
|
371
424
|
const output = outputIdx !== -1 && args[outputIdx + 1] ? resolve(args[outputIdx + 1]) : undefined;
|
|
@@ -378,6 +431,7 @@ if (command === 'prepare') {
|
|
|
378
431
|
version: targetVersion,
|
|
379
432
|
offline,
|
|
380
433
|
hooksAuthorized,
|
|
434
|
+
verificationGatesAuthorized,
|
|
381
435
|
production,
|
|
382
436
|
output,
|
|
383
437
|
runDir,
|
|
@@ -563,6 +617,7 @@ if (command === 'verify') {
|
|
|
563
617
|
const planPath = planIdx !== -1 && args[planIdx + 1] ? resolve(args[planIdx + 1]) : undefined;
|
|
564
618
|
const runIdx = args.indexOf('--run');
|
|
565
619
|
const runPath = runIdx !== -1 && args[runIdx + 1] ? resolve(args[runIdx + 1]) : undefined;
|
|
620
|
+
const verificationGatesAuthorized = args.includes('--acknowledge-gate-side-effects');
|
|
566
621
|
|
|
567
622
|
if (!planPath || !runPath) {
|
|
568
623
|
const msg = 'verify requires --plan <path> and --run <path>';
|
|
@@ -587,6 +642,7 @@ if (command === 'verify') {
|
|
|
587
642
|
sourceRunPath: runPath,
|
|
588
643
|
adapterRegistry: registry,
|
|
589
644
|
root,
|
|
645
|
+
verificationGatesAuthorized,
|
|
590
646
|
});
|
|
591
647
|
|
|
592
648
|
if (hasJson) {
|
|
Binary file
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"comment": "
|
|
3
|
-
"prebuilds": {
|
|
2
|
+
"comment": "Registered Node-API prebuilds. Each entry is loaded only after path, file identity, exports, and SHA-256 verification.",
|
|
3
|
+
"prebuilds": {
|
|
4
|
+
"darwin-arm64": {
|
|
5
|
+
"path": "prebuilds/darwin-arm64/safe_write.node",
|
|
6
|
+
"sha256": "a0e39732c359f3faa23715e6d17d8bc5fec5c5b86e0390220e1beb9f5def2d2e",
|
|
7
|
+
"exports": [
|
|
8
|
+
"openRoot",
|
|
9
|
+
"openDir",
|
|
10
|
+
"readEntry",
|
|
11
|
+
"readFile",
|
|
12
|
+
"createTemp",
|
|
13
|
+
"rename",
|
|
14
|
+
"abortTemp",
|
|
15
|
+
"mkdir",
|
|
16
|
+
"rmdir",
|
|
17
|
+
"unlink",
|
|
18
|
+
"chmod",
|
|
19
|
+
"fsync",
|
|
20
|
+
"close"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
4
24
|
}
|
|
@@ -637,7 +637,7 @@ static napi_value OpenDir(napi_env env, napi_callback_info info) {
|
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
// ---------------------------------------------------------------------------
|
|
640
|
-
// readEntry(handle, name) -> { type, size, mode } | null
|
|
640
|
+
// readEntry(handle, name) -> { type, size, mode, dev, ino, nlink } | null
|
|
641
641
|
// ---------------------------------------------------------------------------
|
|
642
642
|
|
|
643
643
|
static napi_value ReadEntry(napi_env env, napi_callback_info info) {
|
|
@@ -683,17 +683,26 @@ static napi_value ReadEntry(napi_env env, napi_callback_info info) {
|
|
|
683
683
|
else if (S_ISCHR(st.st_mode)) type = "char-device";
|
|
684
684
|
else if (S_ISSOCK(st.st_mode)) type = "socket";
|
|
685
685
|
|
|
686
|
-
napi_value type_val, size_val, mode_val;
|
|
686
|
+
napi_value type_val, size_val, mode_val, dev_val, ino_val, nlink_val;
|
|
687
687
|
NAPI_CHECK(napi_create_string_utf8(env, type, NAPI_AUTO_LENGTH, &type_val));
|
|
688
688
|
NAPI_CHECK(
|
|
689
689
|
napi_create_int64(env, static_cast<int64_t>(st.st_size), &size_val));
|
|
690
690
|
NAPI_CHECK(
|
|
691
691
|
napi_create_int32(env, static_cast<int32_t>(st.st_mode & 07777),
|
|
692
692
|
&mode_val));
|
|
693
|
+
NAPI_CHECK(
|
|
694
|
+
napi_create_int64(env, static_cast<int64_t>(st.st_dev), &dev_val));
|
|
695
|
+
NAPI_CHECK(
|
|
696
|
+
napi_create_int64(env, static_cast<int64_t>(st.st_ino), &ino_val));
|
|
697
|
+
NAPI_CHECK(
|
|
698
|
+
napi_create_int64(env, static_cast<int64_t>(st.st_nlink), &nlink_val));
|
|
693
699
|
|
|
694
700
|
NAPI_CHECK(napi_set_named_property(env, result, "type", type_val));
|
|
695
701
|
NAPI_CHECK(napi_set_named_property(env, result, "size", size_val));
|
|
696
702
|
NAPI_CHECK(napi_set_named_property(env, result, "mode", mode_val));
|
|
703
|
+
NAPI_CHECK(napi_set_named_property(env, result, "dev", dev_val));
|
|
704
|
+
NAPI_CHECK(napi_set_named_property(env, result, "ino", ino_val));
|
|
705
|
+
NAPI_CHECK(napi_set_named_property(env, result, "nlink", nlink_val));
|
|
697
706
|
return result;
|
|
698
707
|
}
|
|
699
708
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"CODE_OF_CONDUCT.md",
|
|
34
34
|
"CONTRIBUTING.md",
|
|
35
35
|
"INSTALL.md",
|
|
36
|
+
"INSTALL.zh-CN.md",
|
|
36
37
|
"LICENSE",
|
|
37
38
|
"NOTICE",
|
|
38
39
|
"SECURITY.md"
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"build:adapters": "node scripts/build-adapters.mjs",
|
|
48
49
|
"build:adapters:check": "node scripts/build-adapters.mjs --check",
|
|
49
50
|
"native:build": "node-gyp rebuild --directory native/safe-write",
|
|
51
|
+
"native:package-current": "node scripts/package-native-prebuild.mjs",
|
|
50
52
|
"native:build:test": "GYP_DEFINES='test_seam=1' node-gyp rebuild --directory native/safe-write",
|
|
51
53
|
"native:test": "npm run native:build:test && node --expose-gc --test test/artifacts-safe-fs-native-seam.test.mjs && npm run native:build && RELEASE_SKILL_NATIVE_DEV=1 node --test test/artifacts-safe-fs.test.mjs test/artifacts-transaction-native.test.mjs && node --input-type=module -e \"import {createRequire} from 'node:module';const a=createRequire(import.meta.url)('./native/safe-write/build/Release/safe_write.node');const k=Object.keys(a).sort();const e=['abortTemp','chmod','close','createTemp','fsync','mkdir','openDir','openRoot','readEntry','readFile','rename','rmdir','unlink'];if(k.length!==e.length||k.some((v,i)=>v!==e[i])){console.error('Production exports mismatch:',k);process.exit(1)}console.log('Production exports verified:',k.length,'functions')\""
|
|
52
54
|
},
|
|
@@ -30,6 +30,10 @@ releaseUnits: # 发布单元数组,至少 1 个
|
|
|
30
30
|
- id: <string> # 唯一标识
|
|
31
31
|
source: <string> # 源码相对路径(相对项目根目录)
|
|
32
32
|
publicRepo: <string> # 公开仓库 owner/repo
|
|
33
|
+
production:
|
|
34
|
+
githubHost: github.com
|
|
35
|
+
branchTemplate: <string>
|
|
36
|
+
branchStrategy: create-release-branch # 或 advance-existing-branch / initialize-default-branch
|
|
33
37
|
version:
|
|
34
38
|
source: <string> # 版本来源,如 "package.json"
|
|
35
39
|
tagTemplate: <string> # tag 模板,如 "artifact-graph-v{version}"
|
|
@@ -46,6 +50,17 @@ releaseUnits: # 发布单元数组,至少 1 个
|
|
|
46
50
|
mode: none # none | bound
|
|
47
51
|
# bound 时还必须提供 repo/ref/commit,可选 tree/manifestDigest
|
|
48
52
|
|
|
53
|
+
verificationGates: # 可选;定义会进入冻结计划摘要
|
|
54
|
+
- id: <stable-id>
|
|
55
|
+
phase: snapshot-verify # 或 consumer-verify
|
|
56
|
+
scope:
|
|
57
|
+
unit: <release-unit-id>
|
|
58
|
+
# consumer-verify 还必须填写 distribution
|
|
59
|
+
command: [<executable>, <arg>]
|
|
60
|
+
cwd: .
|
|
61
|
+
timeoutMs: 30000
|
|
62
|
+
envAllowlist: []
|
|
63
|
+
|
|
49
64
|
hooks: # 可选,命令 hooks
|
|
50
65
|
docs: <hook>
|
|
51
66
|
build: <hook>
|
|
@@ -90,6 +105,14 @@ human-owned 权威源,然后重新 prepare、审阅并 approve;后续环节
|
|
|
90
105
|
|
|
91
106
|
旧的 `policy.requiredPublicFiles` 已不支持。加载器必须返回 `CONFIG_INVALID`,并引导迁移到各自的 `releaseUnits[].requiredPublicFiles`。
|
|
92
107
|
|
|
108
|
+
### 3.2 公开分支策略
|
|
109
|
+
|
|
110
|
+
- `create-release-branch`:目标分支必须不存在;创建独立 release 分支,冻结提交没有父提交。
|
|
111
|
+
- `advance-existing-branch`:目标分支必须与 `previousPublicBaseline` 的精确提交一致;冻结提交以它为唯一父提交,publish 仅允许普通 fast-forward push。并发漂移或非快进时失败关闭,不使用 force。
|
|
112
|
+
- `initialize-default-branch`:目标标准分支必须不存在,冻结提交以当前公开基线为父提交;只有同时声明 `setAsDefaultBranch: true` 和 `expectedCurrentDefaultBranch` 时,计划才会增加显式 `set-default-branch` action。切换失败进入 PARTIAL,可由 reconcile 依据旧/新/第三方状态恢复或转人工。
|
|
113
|
+
|
|
114
|
+
已有公开仓不得使用 `mode: none` 伪装成首次发布。示例中的 commit 只是校准时点证据;每轮 production prepare 都必须在线重观测。
|
|
115
|
+
|
|
93
116
|
---
|
|
94
117
|
|
|
95
118
|
## 4. Hooks 参数约束
|
|
@@ -121,6 +144,32 @@ hooks:
|
|
|
121
144
|
envAllowlist: [CI]
|
|
122
145
|
```
|
|
123
146
|
|
|
147
|
+
### 4.1 分阶段验证 gate
|
|
148
|
+
|
|
149
|
+
`verificationGates` 是项目个性化校验的受控扩展点,不是另一套发布流程:
|
|
150
|
+
|
|
151
|
+
| 字段 | 约束 |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `id` | 全局唯一、稳定,匹配 `^[a-z0-9][a-z0-9._-]*$` |
|
|
154
|
+
| `phase` | `snapshot-verify` 或 `consumer-verify` |
|
|
155
|
+
| `scope.unit` | 必须引用现有发布单元 |
|
|
156
|
+
| `scope.distribution` | consumer 阶段必填,且必须是该单元真实声明的渠道 |
|
|
157
|
+
| `command` | `execFile` 风格字符串数组;禁止 shell 字符串 |
|
|
158
|
+
| `cwd` | 相对执行根,不允许路径逃逸或符号链接分量 |
|
|
159
|
+
| `timeoutMs` | 1000 至 7200000 |
|
|
160
|
+
| `envAllowlist` | 仅显式变量;其他凭证和环境变量不转发 |
|
|
161
|
+
| `expectedJson` | 可选;stdout 必须为 JSON 且递归包含这些字段 |
|
|
162
|
+
|
|
163
|
+
`snapshot-verify` 获得冻结公开快照的一次性可写副本;副本销毁后重新摘要原快照,失败时还没有远端写入。`consumer-verify` 只在精确 npm 或 Claude/Codex 隔离安装成功后,从安装根执行;结果摘要进入 verify run,失败不得达到 VERIFIED。
|
|
164
|
+
|
|
165
|
+
gate 与 legacy hook 都是无网络沙箱的本地进程,必须显式确认副作用。release-skill 会限制环境、路径、超时和证据输出,但不能替项目保证其命令不会写文件或访问网络。Git push、tag、默认分支、GitHub Release 和 npm publish 只能是冻结计划里的 adapter action,不能注册为 hook/gate。
|
|
166
|
+
|
|
167
|
+
### 4.2 首次接入 setup
|
|
168
|
+
|
|
169
|
+
`release-skill setup --root <path> --json` 默认只读,扫描工作区、包与插件 manifest、Git remote、旧版 `public-release.json` 和脚本,输出候选、逐单元前序公开基线状态与 `setupDigest`。旧配置只作为不可信迁移事实,其中的 `snapshotCommands` 只成为未授权 gate 候选;它不会把脚本存在解释成用户选择,也不会读取 README 中的自然语言作为命令。
|
|
170
|
+
|
|
171
|
+
写入要求人工提供完整 answers JSON,再次 dry-run,并使用同一摘要执行 `--write --confirm-setup <setupDigest>`。写入只允许首次原子创建 `.release-skill/project.yaml`;已有配置返回 `ALREADY_CONFIGURED`/`CONFIG_EXISTS`,由人工增量编辑。事实漂移返回 `SETUP_DIGEST_MISMATCH`。没有远端渠道的项目返回 `LOCAL_ONLY_DETECTED`,不得宣称 production-ready。
|
|
172
|
+
|
|
124
173
|
---
|
|
125
174
|
|
|
126
175
|
## 5. Profile(规划中)
|
|
@@ -201,9 +250,11 @@ profile 是预设的默认配置模板,提供常见发布拓扑的开箱配置
|
|
|
201
250
|
5. `releaseUnits` 至少包含 1 个元素。
|
|
202
251
|
6. 每个 `releaseUnit.id` 全局唯一。
|
|
203
252
|
7. 每个 `releaseUnit.source` 路径在项目中存在。
|
|
204
|
-
8. 所有 hook 的 `command` 为数组且第一个元素非空。
|
|
205
|
-
9. 所有 hook 的 `cwd` 不包含绝对路径前缀和 `..`。
|
|
206
|
-
10.
|
|
253
|
+
8. 所有 hook/gate 的 `command` 为数组且第一个元素非空。
|
|
254
|
+
9. 所有 hook/gate 的 `cwd` 不包含绝对路径前缀和 `..`。
|
|
255
|
+
10. gate id 唯一,unit/distribution scope 引用真实配置。
|
|
256
|
+
11. 分支推进策略使用 bound 基线;默认分支初始化具备显式旧/新分支断言。
|
|
257
|
+
12. 所有路径字段不包含 `<project-root>/` 前缀。
|
|
207
258
|
|
|
208
259
|
验证失败返回 `CONFIG_INVALID` 错误码,附带具体失败原因。
|
|
209
260
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# 05 -- 证据与错误
|
|
2
2
|
|
|
3
|
-
本文档定义 release-skill
|
|
3
|
+
本文档定义 release-skill 的稳定错误码、JSON/JSONL 事件格式、脱敏规则和证据目录结构。状态机见 `01-state-machine.md`,配置约束见 `02-project-config.md`。
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## 1. 稳定错误码
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
以下列出用户最常遇到的稳定错误码。实现中的完整列表由 `src/core/errors.mjs` 维护;已发布错误码不得重命名或删除。
|
|
10
10
|
|
|
11
11
|
| 错误码 | 含义 | 触发场景 | 典型恢复 |
|
|
12
12
|
|---|---|---|---|
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
| `HOOK_TIMEOUT` | 项目 hook 执行超时 | hook 运行时间超过 `timeoutMs` 配置 | 增加超时值或优化 hook 执行效率 |
|
|
20
20
|
| `PARTIAL_RELEASE` | 发布部分成功 | 至少一个外部检查点成功但后续检查点失败 | 使用 `reconcile` 从检查点恢复 |
|
|
21
21
|
| `POST_PUBLISH_VERIFY_FAILED` | 发布后验证未通过 | 安装测试失败、泄漏审计未通过、provenance 验证失败 | 检查失败原因,可能需要人工干预 |
|
|
22
|
+
| `SETUP_DIGEST_MISMATCH` | setup 事实或答案已漂移 | dry-run 后 README/package/manifest/remote/answers 发生变化,或确认摘要错误 | 重新运行 setup dry-run、审阅并确认新摘要 |
|
|
23
|
+
| `CONFIG_EXISTS` | setup 目标配置已经存在 | 写入模式试图创建已有 `.release-skill/project.yaml` | 不覆盖;运行 assess 并人工增量编辑 |
|
|
22
24
|
|
|
23
25
|
---
|
|
24
26
|
|
|
@@ -86,6 +88,8 @@
|
|
|
86
88
|
| `stdout` | 标准输出引用(脱敏后存储,见第 4 节) |
|
|
87
89
|
| `stderr` | 标准错误引用(脱敏后存储,见第 4 节) |
|
|
88
90
|
|
|
91
|
+
验证 gate 不保存原始 stdout/stderr;证据仅记录命令数组、相对 cwd、时间、exit code、字节数、SHA-256 摘要和结构化裁决,避免把项目命令输出中的凭证复制进证据。
|
|
92
|
+
|
|
89
93
|
---
|
|
90
94
|
|
|
91
95
|
## 4. 脱敏规则
|