release-skill 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/CHANGELOG.md +60 -0
  5. package/INSTALL.md +179 -5
  6. package/INSTALL.zh-CN.md +320 -0
  7. package/README.md +347 -67
  8. package/README.zh-CN.md +318 -59
  9. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/skills/release-help/SKILL.md +7 -4
  12. package/adapters/claude/skills/release-prepare/SKILL.md +11 -1
  13. package/adapters/claude/skills/release-publish/SKILL.md +6 -3
  14. package/adapters/claude/skills/release-reconcile/SKILL.md +1 -1
  15. package/adapters/claude/skills/release-setup/SKILL.md +111 -0
  16. package/adapters/claude/skills/release-verify/SKILL.md +5 -2
  17. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  18. package/adapters/codex/skills/release-help/SKILL.md +7 -4
  19. package/adapters/codex/skills/release-prepare/SKILL.md +11 -1
  20. package/adapters/codex/skills/release-publish/SKILL.md +6 -3
  21. package/adapters/codex/skills/release-reconcile/SKILL.md +1 -1
  22. package/adapters/codex/skills/release-setup/SKILL.md +111 -0
  23. package/adapters/codex/skills/release-verify/SKILL.md +5 -2
  24. package/bin/release-skill.mjs +65 -9
  25. package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  26. package/native/safe-write/prebuilds.json +22 -2
  27. package/native/safe-write/src/safe_write.cc +11 -2
  28. package/package.json +3 -1
  29. package/references/02-project-config.md +54 -3
  30. package/references/05-evidence-and-errors.md +6 -2
  31. package/schemas/release-plan.schema.json +550 -65
  32. package/schemas/release-project.schema.json +398 -29
  33. package/schemas/release-run.schema.json +165 -18
  34. package/skills/release-help/SKILL.md +7 -4
  35. package/skills/release-prepare/SKILL.md +11 -1
  36. package/skills/release-publish/SKILL.md +6 -3
  37. package/skills/release-reconcile/SKILL.md +1 -1
  38. package/skills/release-setup/SKILL.md +111 -0
  39. package/skills/release-verify/SKILL.md +5 -2
  40. package/skills-src/release-help/SKILL.md +7 -4
  41. package/skills-src/release-prepare/SKILL.md +11 -1
  42. package/skills-src/release-publish/SKILL.md +6 -3
  43. package/skills-src/release-reconcile/SKILL.md +1 -1
  44. package/skills-src/release-setup/SKILL.md +111 -0
  45. package/skills-src/release-verify/SKILL.md +5 -2
  46. package/src/adapters/contract.mjs +3 -0
  47. package/src/adapters/git-github.mjs +84 -2
  48. package/src/adapters/plugin-marketplace.mjs +65 -21
  49. package/src/adapters/push-snapshot.mjs +84 -17
  50. package/src/commands/prepare.mjs +223 -20
  51. package/src/commands/publish.mjs +45 -0
  52. package/src/commands/reconcile.mjs +152 -0
  53. package/src/commands/setup.mjs +886 -0
  54. package/src/commands/verify.mjs +122 -26
  55. package/src/core/config.mjs +34 -0
  56. package/src/core/errors.mjs +4 -0
  57. package/src/core/plan.mjs +123 -0
  58. package/src/core/previous-public-baseline.mjs +21 -1
  59. package/src/core/verification-gates.mjs +451 -0
  60. package/src/snapshot/frozen.mjs +89 -5
@@ -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-verified)',
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-verified)',
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-verified)',
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; requires --acknowledge-hook-side-effects when hooks are configured',
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-verified; approval and exact digest confirmation required',
252
- reconcile: 'PARTIAL recovery sandbox-verified; remote conflicts require human intervention',
253
- verify: 'fresh exact npm and Claude/Codex consumer installation checks are sandbox-verified; success reaches VERIFIED',
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) {
@@ -1,4 +1,24 @@
1
1
  {
2
- "comment": "Prebuilt addon manifest. Entries are added when platform-specific build evidence is available. SHA-256 hashes are required for loading prebuilt addons; source-build users must use RELEASE_SKILL_NATIVE_DEV=1.",
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.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. 所有路径字段不包含 `<project-root>/` 前缀。
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 的 9 类稳定错误码、JSON/JSONL 事件格式、脱敏规则和证据目录结构。状态机见 `01-state-machine.md`,配置约束见 `02-project-config.md`。
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 个错误码覆盖 release-skill 全部可恢复和不可恢复错误场景。错误码在所有版本间保持稳定,不得重命名或删除。
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. 脱敏规则