release-skill 0.6.2 → 0.6.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 (73) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +23 -0
  7. package/CONTRIBUTING.md +1 -1
  8. package/INSTALL.md +47 -2
  9. package/INSTALL.zh-CN.md +29 -2
  10. package/README.md +126 -9
  11. package/README.zh-CN.md +108 -9
  12. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  13. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  14. package/adapters/claude/bin/release-skill.bundle.mjs +6408 -1654
  15. package/adapters/claude/schemas/.render-manifest.json +10 -6
  16. package/adapters/claude/schemas/postpublish-approval-record.schema.json +47 -0
  17. package/adapters/claude/schemas/release-plan.schema.json +65 -1
  18. package/adapters/claude/schemas/release-project.schema.json +73 -1
  19. package/adapters/claude/schemas/release-run.schema.json +11 -6
  20. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  21. package/adapters/codex/bin/release-skill.bundle.mjs +6408 -1654
  22. package/adapters/codex/schemas/.render-manifest.json +10 -6
  23. package/adapters/codex/schemas/postpublish-approval-record.schema.json +47 -0
  24. package/adapters/codex/schemas/release-plan.schema.json +65 -1
  25. package/adapters/codex/schemas/release-project.schema.json +73 -1
  26. package/adapters/codex/schemas/release-run.schema.json +11 -6
  27. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  28. package/adapters/kimi/bin/release-skill.bundle.mjs +6408 -1654
  29. package/adapters/kimi/schemas/.render-manifest.json +10 -6
  30. package/adapters/kimi/schemas/postpublish-approval-record.schema.json +47 -0
  31. package/adapters/kimi/schemas/release-plan.schema.json +65 -1
  32. package/adapters/kimi/schemas/release-project.schema.json +73 -1
  33. package/adapters/kimi/schemas/release-run.schema.json +11 -6
  34. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  35. package/adapters/workbuddy/bin/release-skill.bundle.mjs +6408 -1654
  36. package/adapters/workbuddy/schemas/.render-manifest.json +10 -6
  37. package/adapters/workbuddy/schemas/postpublish-approval-record.schema.json +47 -0
  38. package/adapters/workbuddy/schemas/release-plan.schema.json +65 -1
  39. package/adapters/workbuddy/schemas/release-project.schema.json +73 -1
  40. package/adapters/workbuddy/schemas/release-run.schema.json +11 -6
  41. package/bin/release-skill-cli.mjs +181 -3
  42. package/bin/release-skill.bundle.mjs +6408 -1654
  43. package/package.json +2 -1
  44. package/platform-manifest.json +4 -4
  45. package/references/.render-manifest.json +5 -5
  46. package/references/01-state-machine.md +22 -2
  47. package/schemas/.render-manifest.json +10 -6
  48. package/schemas/postpublish-approval-record.schema.json +47 -0
  49. package/schemas/release-plan.schema.json +65 -1
  50. package/schemas/release-project.schema.json +73 -1
  51. package/schemas/release-run.schema.json +11 -6
  52. package/src/commands/approve.mjs +167 -1
  53. package/src/commands/distribute.mjs +411 -33
  54. package/src/commands/postverify.mjs +734 -0
  55. package/src/commands/prepare.mjs +280 -42
  56. package/src/commands/setup.mjs +715 -0
  57. package/src/commands/ship.mjs +152 -5
  58. package/src/commands/verify.mjs +92 -15
  59. package/src/core/approval.mjs +93 -68
  60. package/src/core/bounded-output.mjs +46 -0
  61. package/src/core/derived-artifact-gates.mjs +258 -0
  62. package/src/core/docs-refresh-preset.mjs +167 -0
  63. package/src/core/errors.mjs +4 -0
  64. package/src/core/hooks.mjs +28 -0
  65. package/src/core/marketplace-registry-entry.mjs +174 -0
  66. package/src/core/notify-handoff.mjs +76 -0
  67. package/src/core/postpublish-approval.mjs +110 -0
  68. package/src/core/postpublish.mjs +424 -7
  69. package/src/core/preset-executor.mjs +156 -0
  70. package/src/core/preset-gitwrite.mjs +463 -0
  71. package/src/core/presets.mjs +706 -0
  72. package/src/core/proposal-inbox.mjs +630 -0
  73. package/src/core/run.mjs +91 -6
@@ -21,17 +21,21 @@
21
21
  "digest": "b7e14522a5aba818cab191545f23d2323be0e4c80566e9d3e4da576103a00490",
22
22
  "bytes": 2390
23
23
  },
24
+ "postpublish-approval-record.schema.json": {
25
+ "digest": "e365c9e9f5aa189ca6788e68dbedfa718aa120f2c197534417c21e019b9a29a4",
26
+ "bytes": 1744
27
+ },
24
28
  "release-plan.schema.json": {
25
- "digest": "5e988a5301b89b3d3260154ffae12669af0e4d6ec7f9db8c44df21037ea0eaf6",
26
- "bytes": 46948
29
+ "digest": "517ac9b1d92d2bf348497ee0e79c9e474a4c60e6ed8ba77830948716d143dfa2",
30
+ "bytes": 49369
27
31
  },
28
32
  "release-project.schema.json": {
29
- "digest": "546e05845cd83a3b1db0a54036f9555081420337e9df961d783d2d2630800ef7",
30
- "bytes": 41898
33
+ "digest": "d38649d1dc9b1dbc643f4e2c98fd18d111090e9ce221c9c61fa50245f6c4c232",
34
+ "bytes": 45715
31
35
  },
32
36
  "release-run.schema.json": {
33
- "digest": "6dd239c59f7045df11d13d6ba2027610d3c55c27a6af5bc87676b0eaad9600fd",
34
- "bytes": 17663
37
+ "digest": "a605db801d6994d657049431ad453233974bd10610b7b447b196136bb0692866",
38
+ "bytes": 17847
35
39
  }
36
40
  }
37
41
  }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://release-skill.dev/schemas/postpublish-approval-record/v1",
4
+ "title": "PostPublish Hook Approval Record",
5
+ "description": "Schema for checkpoint-level approval of a requiresApproval postPublish hook (v0.6.3 R1, design §2.7 ruling 2). Binds the approval to (planDigest, hookId); the plan-level approval-record schema is untouched. runId is audit-only and never participates in binding or validation semantics.",
6
+ "type": "object",
7
+ "required": [
8
+ "planDigest",
9
+ "hookId",
10
+ "actor",
11
+ "approvedAt",
12
+ "expiresAt"
13
+ ],
14
+ "additionalProperties": false,
15
+ "properties": {
16
+ "planDigest": {
17
+ "type": "string",
18
+ "pattern": "^[a-f0-9]{64}$",
19
+ "description": "SHA-256 digest of the frozen release plan whose postPublish hooks block this approval binds to"
20
+ },
21
+ "hookId": {
22
+ "type": "string",
23
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
24
+ "description": "id of the requiresApproval postPublish hook being approved (must be declared in the frozen plan)"
25
+ },
26
+ "actor": {
27
+ "type": "string",
28
+ "minLength": 1,
29
+ "description": "Identity of the approver"
30
+ },
31
+ "approvedAt": {
32
+ "type": "string",
33
+ "format": "date-time",
34
+ "description": "ISO 8601 timestamp when approval was granted"
35
+ },
36
+ "expiresAt": {
37
+ "type": "string",
38
+ "format": "date-time",
39
+ "description": "ISO 8601 timestamp when approval expires (max 24h after approvedAt; 5-minute clock skew tolerated at validation)"
40
+ },
41
+ "runId": {
42
+ "type": "string",
43
+ "minLength": 1,
44
+ "description": "Audit-only identifier of the distribute run present when approval was granted; never a binding"
45
+ }
46
+ }
47
+ }
@@ -1307,7 +1307,6 @@
1307
1307
  "description": "冻结的发布后分发块:声明本身 + tag/tagCommit/unitId/payloadSource 绑定。整体参与 plan digest(planVersion 2 记录层剥离不包含该块),任何配置变更都会改变 planDigest。tagCommit 仅在 prepare 能冻结 create-tag 预期 commit 时存在(production 资产);缺省时 distribute fail-closed。",
1308
1308
  "required": [
1309
1309
  "materialize",
1310
- "targets",
1311
1310
  "commitIdentity",
1312
1311
  "tag",
1313
1312
  "unitId",
@@ -1325,6 +1324,13 @@
1325
1324
  "$ref": "#/definitions/planPostPublishTarget"
1326
1325
  }
1327
1326
  },
1327
+ "hooks": {
1328
+ "type": "array",
1329
+ "description": "冻结的发布后钩子声明(v0.6.3 R1);随整块参与 plan digest",
1330
+ "items": {
1331
+ "$ref": "#/definitions/planPostPublishHook"
1332
+ }
1333
+ },
1328
1334
  "commitIdentity": {
1329
1335
  "type": "object",
1330
1336
  "required": [
@@ -1373,6 +1379,64 @@
1373
1379
  }
1374
1380
  }
1375
1381
  },
1382
+ "planPostPublishHook": {
1383
+ "type": "object",
1384
+ "description": "冻结的发布后钩子声明项(v0.6.3 R1):preset 与 command 运行时互斥;phase 缺省 distribute;blocksVerified 缺省 true 且仅 preset 可声明 false",
1385
+ "required": [
1386
+ "id"
1387
+ ],
1388
+ "additionalProperties": false,
1389
+ "properties": {
1390
+ "id": {
1391
+ "type": "string",
1392
+ "pattern": "^[a-z0-9][a-z0-9._-]*$"
1393
+ },
1394
+ "preset": {
1395
+ "type": "string",
1396
+ "pattern": "^[a-z0-9][a-z0-9._-]*$"
1397
+ },
1398
+ "command": {
1399
+ "type": "array",
1400
+ "minItems": 1,
1401
+ "items": {
1402
+ "type": "string"
1403
+ }
1404
+ },
1405
+ "phase": {
1406
+ "type": "string",
1407
+ "enum": [
1408
+ "distribute",
1409
+ "postVerify"
1410
+ ]
1411
+ },
1412
+ "config": {
1413
+ "type": "object"
1414
+ },
1415
+ "cwd": {
1416
+ "type": "string",
1417
+ "pattern": "^(?:\\.$|(?!\\/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(?:\\/|$))(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/\\/$)(?!.*\\/$).*)$"
1418
+ },
1419
+ "timeoutMs": {
1420
+ "type": "integer",
1421
+ "minimum": 1000,
1422
+ "maximum": 7200000
1423
+ },
1424
+ "envAllowlist": {
1425
+ "type": "array",
1426
+ "items": {
1427
+ "type": "string",
1428
+ "pattern": "^(?!.*(?:TOKEN|SECRET|PASSWORD|PASSPHRASE|API_KEY|CREDENTIAL))[A-Z_][A-Z0-9_]*$"
1429
+ },
1430
+ "uniqueItems": true
1431
+ },
1432
+ "requiresApproval": {
1433
+ "type": "boolean"
1434
+ },
1435
+ "blocksVerified": {
1436
+ "type": "boolean"
1437
+ }
1438
+ }
1439
+ },
1376
1440
  "planPostPublishMaterialize": {
1377
1441
  "type": "object",
1378
1442
  "required": [
@@ -889,7 +889,6 @@
889
889
  "description": "发布后分发声明:materialize 钩子从冻结 tag 工作树物化载荷,targets 将载荷镜像或渲染市场索引推送到独立 git 远端。分发在 publish 之后由 distribute 命令执行,时序契约禁止读取工作区状态。",
890
890
  "required": [
891
891
  "materialize",
892
- "targets",
893
892
  "commitIdentity"
894
893
  ],
895
894
  "additionalProperties": false,
@@ -904,6 +903,13 @@
904
903
  "$ref": "#/definitions/postPublishTarget"
905
904
  }
906
905
  },
906
+ "hooks": {
907
+ "type": "array",
908
+ "description": "发布后钩子声明(v0.6.3 R1):每项为命名 preset(+config)或自定义 command 钩子(executable+参数数组,禁止 shell 字符串)。schema 层只校验形状;preset 与 command 互斥、preset 注册表归属等语义在运行时 fail-closed 校验(POSTPUBLISH_HOOK_INVALID)",
909
+ "items": {
910
+ "$ref": "#/definitions/postPublishHook"
911
+ }
912
+ },
907
913
  "commitIdentity": {
908
914
  "type": "object",
909
915
  "description": "分发仓库提交者身份(bot 身份),通过 git -c user.name/-c user.email 注入,绝不读取或修改全局 git 配置",
@@ -936,6 +942,72 @@
936
942
  }
937
943
  }
938
944
  },
945
+ "postPublishHook": {
946
+ "type": "object",
947
+ "description": "发布后钩子声明项:preset(命名预设 + config)与 command(自定义命令)在运行时互斥校验;phase 缺省为 distribute(VERIFIED 之前执行),postVerify 在 verify 通过后作为独立 run 执行(R3);blocksVerified 缺省 true,仅 preset 可声明 false",
948
+ "required": [
949
+ "id"
950
+ ],
951
+ "additionalProperties": false,
952
+ "properties": {
953
+ "id": {
954
+ "type": "string",
955
+ "pattern": "^[a-z0-9][a-z0-9._-]*$"
956
+ },
957
+ "preset": {
958
+ "type": "string",
959
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
960
+ "description": "预设注册表中的预设名(R2 起);R1 无注册表,任何 preset 引用在运行时 fail-closed"
961
+ },
962
+ "command": {
963
+ "type": "array",
964
+ "minItems": 1,
965
+ "items": {
966
+ "type": "string"
967
+ },
968
+ "description": "executable+参数数组(禁止 shell 字符串);在冻结 tag 工作树中执行"
969
+ },
970
+ "phase": {
971
+ "type": "string",
972
+ "enum": [
973
+ "distribute",
974
+ "postVerify"
975
+ ],
976
+ "description": "执行阶段;缺省 distribute"
977
+ },
978
+ "config": {
979
+ "type": "object",
980
+ "description": "preset 钩子的配置对象;语义由预设注册表校验(R2 起)"
981
+ },
982
+ "cwd": {
983
+ "type": "string",
984
+ "pattern": "^(?:\\.$|(?!\\/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(?:\\/|$))(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/\\/$)(?!.*\\/$).*)$",
985
+ "description": "command 钩子执行目录(相对冻结 tag 工作树);preset 钩子禁止声明(运行时 fail-closed)"
986
+ },
987
+ "timeoutMs": {
988
+ "type": "integer",
989
+ "minimum": 1000,
990
+ "maximum": 7200000
991
+ },
992
+ "envAllowlist": {
993
+ "type": "array",
994
+ "items": {
995
+ "type": "string",
996
+ "pattern": "^(?!.*(?:TOKEN|SECRET|PASSWORD|PASSPHRASE|API_KEY|CREDENTIAL))[A-Z_][A-Z0-9_]*$"
997
+ },
998
+ "uniqueItems": true,
999
+ "description": "允许透传给钩子进程的环境变量白名单;TOKEN/SECRET 等敏感子串在 schema 层即被拒绝"
1000
+ },
1001
+ "requiresApproval": {
1002
+ "type": "boolean",
1003
+ "description": "执行前需要 checkpoint 级批准(绑定 planDigest + hookId);public-write preset 缺省 true"
1004
+ },
1005
+ "blocksVerified": {
1006
+ "type": "boolean",
1007
+ "description": "失败时是否阻断 VERIFIED;缺省 true;仅 preset 可声明 false(运行时 fail-closed 校验)"
1008
+ }
1009
+ }
1010
+ },
939
1011
  "postPublishMaterialize": {
940
1012
  "type": "object",
941
1013
  "description": "载荷物化钩子:executable+参数数组(禁止 shell 字符串),stdout 中 outputMarker 行之后为载荷目录路径;可选 requireReport 对 stdout 首个 JSON 对象做字段断言",
@@ -43,7 +43,8 @@
43
43
  "PARTIAL",
44
44
  "BLOCKED",
45
45
  "DISTRIBUTING",
46
- "DISTRIBUTED"
46
+ "DISTRIBUTED",
47
+ "NEEDS_INPUT"
47
48
  ],
48
49
  "description": "Current status of the run"
49
50
  },
@@ -55,7 +56,8 @@
55
56
  "publish",
56
57
  "reconcile",
57
58
  "verify",
58
- "distribute"
59
+ "distribute",
60
+ "postverify"
59
61
  ],
60
62
  "description": "The command that initiated this run"
61
63
  },
@@ -133,7 +135,8 @@
133
135
  "codebuddy-marketplace-install",
134
136
  "set-default-branch",
135
137
  "distribute-probe",
136
- "distribute-mirror"
138
+ "distribute-mirror",
139
+ "postpublish-hook"
137
140
  ],
138
141
  "description": "Type of external action"
139
142
  },
@@ -149,9 +152,10 @@
149
152
  "PASSED_AUTOMATIC",
150
153
  "PASSED_MANUAL",
151
154
  "NOT_REQUIRED_UNCHANGED",
152
- "NO_CHANGE"
155
+ "NO_CHANGE",
156
+ "AWAITING_APPROVAL"
153
157
  ],
154
- "description": "Result status of this checkpoint (uncertain = started, result unknown; deferred = marketplace action postponed to verify phase; PASSED_AUTOMATIC/PASSED_MANUAL/NOT_REQUIRED_UNCHANGED 为验证阶段专用状态; NO_CHANGE = distribute 目标暂存差异为空,未产生 commit/tag/push)"
158
+ "description": "Result status of this checkpoint (uncertain = started, result unknown; deferred = marketplace action postponed to verify phase; PASSED_AUTOMATIC/PASSED_MANUAL/NOT_REQUIRED_UNCHANGED 为验证阶段专用状态; NO_CHANGE = distribute 目标暂存差异为空,未产生 commit/tag/push; AWAITING_APPROVAL = requiresApproval postPublish hook 尚无 checkpoint 批准,v0.6.3 R1)"
155
159
  },
156
160
  "preObserve": {
157
161
  "type": "string",
@@ -621,7 +625,8 @@
621
625
  "enum": [
622
626
  "reconcile",
623
627
  "verify",
624
- "distribute"
628
+ "distribute",
629
+ "postverify"
625
630
  ]
626
631
  }
627
632
  },
@@ -237,7 +237,8 @@ Commands:
237
237
  publish Publish frozen GitHub/npm artifacts after approval
238
238
  reconcile Resume PARTIAL state from evidence; conflicts require a human
239
239
  verify Fresh remote and consumer verification; only this reaches VERIFIED
240
- ship Resume one durable prepare -> approve -> publish -> verify flow
240
+ ship Resume one durable prepare -> approve -> publish -> verify flow; completes a parked
241
+ postVerify hook once its checkpoint approval is provided (--hook-approval)
241
242
  attest Legacy only: record a Kimi/CodeBuddy result for an old frozen plan
242
243
  hooks Run declared development hooks and populate reusable receipts
243
244
  artifacts Artifact status, inspect, update/apply, resolution, and diagnostics
@@ -249,7 +250,8 @@ Commands:
249
250
  Options:
250
251
  --root <path> Project root directory (default: cwd)
251
252
  --plan <path> Path to the release plan file
252
- --run <path> Path to the release run file (required for reconcile/verify)
253
+ --run <path> Path to the release run file, or a run directory whose release-run.json
254
+ is resolved automatically (required for reconcile/verify)
253
255
  --approval <path> Path to the approval record
254
256
  --production Prepare immutable Git/npm production artifacts
255
257
  --output <path> Override prepare/approve output path (non-production only)
@@ -264,6 +266,12 @@ Options:
264
266
  --answers <path> Human-reviewed setup answers JSON
265
267
  --write Create an absent project.yaml during setup; never overwrites
266
268
  --confirm-setup <digest> Confirm exact setup facts and answers before create
269
+ --discover-downstream Read-only discovery of downstream candidates (git remotes, marketplace/docs
270
+ clues, artifact-graph.config.yaml, foundation profile). Never writes
271
+ --propose-hooks Produce postPublish hook declaration drafts; with --write and exact --confirm-setup,
272
+ performs an append-only hooks edit of an existing config (create-once is untouched)
273
+ --select-hooks <ids> Comma-separated proposal ids to adopt (propose-hooks mode)
274
+ --foundation-profile <path> Explicit foundation postPublish profile JSON (proposal input only; never auto-applied)
267
275
  --unit <id> Release unit whose declared release documents are refreshed (docs refresh)
268
276
  --confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
269
277
  --ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
@@ -274,6 +282,7 @@ Options:
274
282
  --install-path <path> Actual managed plugin path when closure verification requires it
275
283
  --install-channel <desktop|cli> CodeBuddy installation channel when required
276
284
  --approve Approve the ship plan (boolean; plan digest is auto-resolved)
285
+ --hook-approval <path> Checkpoint approval for a requiresApproval postPublish hook (ship/distribute; repeatable)
277
286
  --state <path> Override the durable ship state file
278
287
  --no-hook-cache Force every prepare hook to run in full; neither read nor write the hook cache
279
288
  --json Output results as JSON
@@ -467,6 +476,59 @@ if (command === 'setup') {
467
476
  ? args[confirmationIdx + 1]
468
477
  : undefined;
469
478
  const write = args.includes('--write');
479
+
480
+ // R5 — downstream discovery + postPublish hook proposal (guided config) and
481
+ // the independent incremental-proposal mode for existing projects. These
482
+ // modes are read-only until --write + exact --confirm-setup, and the write is
483
+ // an append-only hooks edit; they never touch the create-once setup path.
484
+ const discoverDownstreamMode = args.includes('--discover-downstream');
485
+ const proposeHooksMode = args.includes('--propose-hooks');
486
+ if (discoverDownstreamMode || proposeHooksMode) {
487
+ const flagValue = (flag) => {
488
+ const idx = args.indexOf(flag);
489
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
490
+ };
491
+ const foundationProfilePath = flagValue('--foundation-profile');
492
+ const unitId = flagValue('--unit');
493
+ const selectRaw = flagValue('--select-hooks');
494
+ const selectedHookIds = selectRaw
495
+ ? selectRaw.split(',').map((item) => item.trim()).filter(Boolean)
496
+ : undefined;
497
+ try {
498
+ const setupModule = await import('../src/commands/setup.mjs');
499
+ const report = discoverDownstreamMode
500
+ ? await setupModule.discoverDownstream({ root, foundationProfilePath })
501
+ : await setupModule.proposePostPublishHooks({
502
+ root, write, confirmSetup, selectedHookIds, foundationProfilePath, unitId,
503
+ });
504
+ if (hasJson) {
505
+ console.log(JSON.stringify(report, null, 2));
506
+ } else {
507
+ console.log(`Status: ${report.status ?? report.mode}`);
508
+ if (report.setupDigest) console.log(`Setup digest: ${report.setupDigest}`);
509
+ if (report.hookProposals) {
510
+ console.log(`Hook proposals: ${report.hookProposals.map((p) => p.id).join(', ') || '(none)'}`);
511
+ }
512
+ if (report.configPath) console.log(`Config: ${report.configPath}`);
513
+ if (report.next) console.log(`Next: ${report.next}`);
514
+ }
515
+ const okStatuses = ['HOOKS_PROPOSAL_READY', 'HOOKS_APPENDED', 'HOOKS_NO_CHANGE', 'postpublish-discovery'];
516
+ process.exit(okStatuses.includes(report.status ?? report.mode) ? 0 : 2);
517
+ } catch (err) {
518
+ if (hasJson) {
519
+ console.log(JSON.stringify({
520
+ error: err.code ?? 'UNKNOWN_ERROR',
521
+ message: err.message,
522
+ details: err.details ?? {},
523
+ exitCode: err.exitCode ?? 1,
524
+ }));
525
+ } else {
526
+ console.error(`Error: ${err.message}`);
527
+ }
528
+ process.exit(err.exitCode ?? 1);
529
+ }
530
+ }
531
+
470
532
  try {
471
533
  const { setupProject } = await import('../src/commands/setup.mjs');
472
534
  const report = await setupProject({ root, answersPath, write, confirmSetup });
@@ -575,6 +637,16 @@ if (command === 'ship') {
575
637
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
576
638
  };
577
639
  const root = resolve(value('--root') ?? process.cwd());
640
+ // Repeatable --hook-approval <path>: checkpoint approvals for
641
+ // requiresApproval postPublish hooks (same convention as distribute;
642
+ // review major-1 — without this the ship flow can never complete a
643
+ // parked AWAITING_APPROVAL postVerify hook).
644
+ const postpublishApprovalPaths = [];
645
+ for (let i = 0; i < args.length; i += 1) {
646
+ if (args[i] === '--hook-approval' && args[i + 1]) {
647
+ postpublishApprovalPaths.push(resolve(args[i + 1]));
648
+ }
649
+ }
578
650
  try {
579
651
  const [
580
652
  { advanceShip },
@@ -582,6 +654,7 @@ if (command === 'ship') {
582
654
  { createNpmAdapter },
583
655
  { createPluginMarketplaceAdapter },
584
656
  { createPushSnapshotAdapter },
657
+ { createDistributeGitAdapter },
585
658
  { createAdapterRegistry },
586
659
  ] = await Promise.all([
587
660
  import('../src/commands/ship.mjs'),
@@ -589,13 +662,17 @@ if (command === 'ship') {
589
662
  import('../src/adapters/npm.mjs'),
590
663
  import('../src/adapters/plugin-marketplace.mjs'),
591
664
  import('../src/adapters/push-snapshot.mjs'),
665
+ import('../src/adapters/distribute-git.mjs'),
592
666
  import('../src/adapters/contract.mjs'),
593
667
  ]);
668
+ // v0.6.3 R1: ship auto-routes into the distribute saga; the registry must
669
+ // carry the distribute-git adapter or that production path fails closed.
594
670
  const adapterRegistry = createAdapterRegistry([
595
671
  createGitGithubAdapter(),
596
672
  createNpmAdapter(),
597
673
  createPluginMarketplaceAdapter(),
598
674
  createPushSnapshotAdapter(),
675
+ createDistributeGitAdapter(),
599
676
  ]);
600
677
  const result = await advanceShip({
601
678
  root,
@@ -605,15 +682,27 @@ if (command === 'ship') {
605
682
  planApprovalDigest: value('--approve-plan'),
606
683
  actor: value('--actor'),
607
684
  adapterRegistry,
685
+ ...(postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}),
608
686
  });
609
687
  if (hasJson) {
610
688
  console.log(JSON.stringify(result, null, 2));
611
689
  } else {
612
690
  console.log(`Ship status: ${result.status}`);
613
691
  console.log(`State: ${result.statePath}`);
692
+ for (const warning of result.warnings ?? []) {
693
+ console.log(`Warning [${warning.code}] ${warning.message}`);
694
+ }
614
695
  if (result.planDigest && result.status === 'NEEDS_PLAN_APPROVAL') {
615
696
  console.log(`Approve plan: ship --approve --actor <person>`);
616
697
  }
698
+ if (result.postVerify) {
699
+ console.log(`PostVerify: ${result.postVerify.status}${result.postVerify.runPath ? ` (run: ${result.postVerify.runPath})` : ''}`);
700
+ if (['NEEDS_INPUT', 'PARTIAL', 'BLOCKED', 'FAILED'].includes(result.postVerify.status)) {
701
+ console.log('PostVerify incomplete: re-run release-skill ship to retry non-gated hooks (no approval needed for them).');
702
+ console.log('For requiresApproval hooks, mint a checkpoint approval (release-skill approve --plan <plan> --hook <hookId> --actor <person>)');
703
+ console.log('then re-run: release-skill ship --root <root> --hook-approval <approval-record>');
704
+ }
705
+ }
617
706
  for (const followUp of result.manualFollowUps ?? []) {
618
707
  console.log(`Manual follow-up [${followUp.platform}] ${followUp.plugin}: not verified by system`);
619
708
  }
@@ -757,6 +846,7 @@ if (command === 'prepare') {
757
846
  unitId: action.unitId,
758
847
  })),
759
848
  warnings: result.warnings,
849
+ nextSteps: result.nextSteps ?? [],
760
850
  }, null, 2));
761
851
  } else {
762
852
  for (const warning of result.warnings) {
@@ -765,6 +855,9 @@ if (command === 'prepare') {
765
855
  console.log(`Plan frozen at: ${result.planPath}`);
766
856
  console.log(`Plan digest: ${result.planDigest}`);
767
857
  console.log(`Evidence: ${result.evidenceDir}`);
858
+ for (const step of result.nextSteps ?? []) {
859
+ console.log(`Next [${step.code}] ${step.message}`);
860
+ }
768
861
  if (plan.workflowKind && plan.workflowKind !== 'full') {
769
862
  console.log(`Workflow: ${plan.workflowKind}`);
770
863
  }
@@ -804,6 +897,10 @@ if (command === 'approve') {
804
897
  const actor = actorIdx !== -1 && args[actorIdx + 1] ? args[actorIdx + 1] : undefined;
805
898
  const outputIdx = args.indexOf('--output');
806
899
  const outputPath = outputIdx !== -1 && args[outputIdx + 1] ? resolve(args[outputIdx + 1]) : undefined;
900
+ const hookIdx = args.indexOf('--hook');
901
+ const hookId = hookIdx !== -1 && args[hookIdx + 1] ? args[hookIdx + 1] : undefined;
902
+ const runIdIdx = args.indexOf('--run-id');
903
+ const runId = runIdIdx !== -1 && args[runIdIdx + 1] ? args[runIdIdx + 1] : undefined;
807
904
 
808
905
  if (!planPath || !actor) {
809
906
  const msg = 'approve requires --plan <path> and --actor <name>';
@@ -815,6 +912,44 @@ if (command === 'approve') {
815
912
  process.exit(1);
816
913
  }
817
914
 
915
+ // --- Checkpoint-level postPublish hook approval (v0.6.3 R1, review N-6) ---
916
+ if (hookId) {
917
+ try {
918
+ const { approvePostPublishHook } = await import('../src/commands/approve.mjs');
919
+ const record = await approvePostPublishHook({
920
+ planPath: resolve(planPath),
921
+ hookId,
922
+ actor,
923
+ ...(runId ? { runId } : {}),
924
+ });
925
+ if (hasJson) {
926
+ console.log(JSON.stringify(record, null, 2));
927
+ } else {
928
+ console.log(`Checkpoint approval for postPublish hook "${record.hookId}" by ${record.actor}`);
929
+ console.log(`Approval record: ${record.approvalPath}`);
930
+ console.log(`Approved at: ${record.approvedAt}`);
931
+ console.log(`Expires at: ${record.expiresAt}`);
932
+ console.log('Approved hook (normalized entry summary):');
933
+ const summary = record.hookSummary ?? {};
934
+ for (const [key, value] of Object.entries(summary)) {
935
+ console.log(` ${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
936
+ }
937
+ }
938
+ process.exit(0);
939
+ } catch (err) {
940
+ if (hasJson) {
941
+ console.log(JSON.stringify({
942
+ error: err.code ?? 'UNKNOWN_ERROR',
943
+ message: err.message,
944
+ exitCode: err.exitCode ?? 1,
945
+ }));
946
+ } else {
947
+ console.error(`Error: ${err.message}`);
948
+ }
949
+ process.exit(err.exitCode ?? 1);
950
+ }
951
+ }
952
+
818
953
  try {
819
954
  const { approvePlan } = await import('../src/commands/approve.mjs');
820
955
  const { computePlanDigest } = await import('../src/core/plan.mjs');
@@ -1259,7 +1394,41 @@ if (command === 'distribute') {
1259
1394
  const approvalPath = value('--approval');
1260
1395
  const runPath = value('--run');
1261
1396
  const dryRun = args.includes('--dry-run');
1397
+ // Repeatable --hook-approval <path>: checkpoint approvals for
1398
+ // requiresApproval postPublish hooks (v0.6.3 R1).
1399
+ const postpublishApprovalPaths = [];
1400
+ for (let i = 0; i < args.length; i += 1) {
1401
+ if (args[i] === '--hook-approval' && args[i + 1]) {
1402
+ postpublishApprovalPaths.push(resolve(args[i + 1]));
1403
+ }
1404
+ }
1262
1405
 
1406
+ // R2: enumerate the postPublish preset registry (ships with the bundle).
1407
+ // Read-only; needs no --plan/--run, so it short-circuits parameter checks.
1408
+ if (args.includes('--list-presets')) {
1409
+ const { listPostPublishPresets } = await import('../src/core/presets.mjs');
1410
+ const presets = listPostPublishPresets();
1411
+ if (hasJson) {
1412
+ console.log(JSON.stringify({ command: 'distribute', presets }, null, 2));
1413
+ } else {
1414
+ console.log('postPublish presets:');
1415
+ for (const preset of presets) {
1416
+ const status = preset.implemented ? 'implemented' : 'reserved (behavior lands in a later release)';
1417
+ console.log(` ${preset.name}`);
1418
+ console.log(` description: ${preset.description}`);
1419
+ console.log(` requiresApproval default: ${preset.defaultRequiresApproval}`);
1420
+ if (preset.requiresApprovalNote) {
1421
+ console.log(` requiresApproval note: ${preset.requiresApprovalNote}`);
1422
+ }
1423
+ if (preset.targetsFormOnly) {
1424
+ console.log(' declaration form: targets-form only (declare under postPublish.targets, not postPublish.hooks)');
1425
+ }
1426
+ console.log(` write-downstream: ${preset.writeDownstream}; target optional: ${preset.targetOptional}; status: ${status}`);
1427
+ }
1428
+ }
1429
+ process.exit(0);
1430
+ }
1431
+
1263
1432
  // Handle --help explicitly
1264
1433
  if (args.includes('--help')) {
1265
1434
  const helpText = `Distribute command: Distributes frozen artifacts to post-publish targets
@@ -1271,7 +1440,9 @@ Options:
1271
1440
  --plan Path to the release plan file (required)
1272
1441
  --run Path to the release run file (required)
1273
1442
  --approval Path to the approval record (optional but recommended)
1443
+ --hook-approval <path> Checkpoint approval for a requiresApproval postPublish hook (repeatable)
1274
1444
  --dry-run Preview distribution steps without executing (default: false)
1445
+ --list-presets Enumerate the postPublish preset registry (no plan/run needed)
1275
1446
 
1276
1447
  Description:
1277
1448
  After PUBLISHED state, distributes to configured postPublish targets:
@@ -1288,9 +1459,11 @@ Description:
1288
1459
  '--plan': 'Path to the release plan file (required)',
1289
1460
  '--run': 'Path to the release run file (required)',
1290
1461
  '--approval': 'Path to the approval record (optional but recommended)',
1462
+ '--hook-approval': 'Checkpoint approval for a requiresApproval postPublish hook (repeatable)',
1291
1463
  '--dry-run': 'Preview distribution steps without executing (default: false)',
1464
+ '--list-presets': 'Enumerate the postPublish preset registry (no plan/run needed)',
1292
1465
  },
1293
- description: 'After PUBLISHED state, distributes to configured postPublish targets: git mirrors + marketplace index updates. Requires plan.postPublish config. Reconciles PARTIAL runs.',
1466
+ details: 'After PUBLISHED state, distributes to configured postPublish targets: git mirrors + marketplace index updates. Requires plan.postPublish config. Reconciles PARTIAL runs.',
1294
1467
  }, null, 2));
1295
1468
  } else {
1296
1469
  console.log(helpText);
@@ -1314,13 +1487,17 @@ Description:
1314
1487
  const { createNpmAdapter } = await import('../src/adapters/npm.mjs');
1315
1488
  const { createPluginMarketplaceAdapter } = await import('../src/adapters/plugin-marketplace.mjs');
1316
1489
  const { createPushSnapshotAdapter } = await import('../src/adapters/push-snapshot.mjs');
1490
+ const { createDistributeGitAdapter } = await import('../src/adapters/distribute-git.mjs');
1317
1491
  const { createAdapterRegistry } = await import('../src/adapters/contract.mjs');
1318
1492
 
1493
+ // v0.6.3 R1: the distribute saga executes through the distribute-git
1494
+ // adapter; without this registration the production path fails closed.
1319
1495
  const registry = createAdapterRegistry([
1320
1496
  createGitGithubAdapter(),
1321
1497
  createNpmAdapter(),
1322
1498
  createPluginMarketplaceAdapter(),
1323
1499
  createPushSnapshotAdapter(),
1500
+ createDistributeGitAdapter(),
1324
1501
  ]);
1325
1502
 
1326
1503
  const result = await distributeRelease({
@@ -1330,6 +1507,7 @@ Description:
1330
1507
  root,
1331
1508
  dryRun,
1332
1509
  planPath,
1510
+ ...(postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}),
1333
1511
  });
1334
1512
 
1335
1513
  if (hasJson) {