release-skill 0.9.4 → 0.9.6
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 +56 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +21 -17
- package/README.zh-CN.md +18 -16
- 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 +1036 -174
- package/adapters/claude/schemas/release-plan.schema.json +36 -0
- package/adapters/claude/schemas/release-project.schema.json +31 -0
- package/adapters/claude/skills/release-finish/SKILL.md +5 -5
- package/adapters/claude/skills/release-help/SKILL.md +7 -3
- package/adapters/claude/skills/release-verify/SKILL.md +14 -1
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +1036 -174
- package/adapters/codex/schemas/release-plan.schema.json +36 -0
- package/adapters/codex/schemas/release-project.schema.json +31 -0
- package/adapters/codex/skills/release-finish/SKILL.md +5 -5
- package/adapters/codex/skills/release-help/SKILL.md +7 -3
- package/adapters/codex/skills/release-verify/SKILL.md +14 -1
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +1036 -174
- package/adapters/kimi/schemas/release-plan.schema.json +36 -0
- package/adapters/kimi/schemas/release-project.schema.json +31 -0
- package/adapters/kimi/skills/release-finish/SKILL.md +5 -5
- package/adapters/kimi/skills/release-help/SKILL.md +7 -3
- package/adapters/kimi/skills/release-verify/SKILL.md +14 -1
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +1036 -174
- package/adapters/workbuddy/schemas/release-plan.schema.json +36 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +31 -0
- package/adapters/workbuddy/skills/release-finish/SKILL.md +5 -5
- package/adapters/workbuddy/skills/release-help/SKILL.md +7 -3
- package/adapters/workbuddy/skills/release-verify/SKILL.md +14 -1
- package/bin/release-skill-cli.mjs +217 -26
- package/bin/release-skill.bundle.mjs +1036 -174
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/schemas/release-plan.schema.json +36 -0
- package/schemas/release-project.schema.json +31 -0
- package/skills/release-finish/SKILL.md +5 -5
- package/skills/release-help/SKILL.md +7 -3
- package/skills/release-verify/SKILL.md +14 -1
- package/skills-src/release-finish/SKILL.md +5 -5
- package/skills-src/release-help/SKILL.md +7 -3
- package/skills-src/release-verify/SKILL.md +14 -1
- package/src/adapters/plugin-marketplace.mjs +54 -5
- package/src/commands/post-release-local.mjs +204 -10
- package/src/commands/prepare.mjs +178 -9
- package/src/commands/ship.mjs +26 -11
- package/src/commands/verify.mjs +175 -2
- package/src/core/plan.mjs +45 -1
- package/src/platforms/registry.mjs +97 -0
- package/src/readme/contract.mjs +23 -3
|
@@ -539,6 +539,18 @@
|
|
|
539
539
|
],
|
|
540
540
|
"description": "市场来源类型:bundled-family(技能族自带市场文件)或 standalone-index(独立市场仓库);每个平台每次发布只冻结一种来源"
|
|
541
541
|
},
|
|
542
|
+
"firstReleaseBootstrap": {
|
|
543
|
+
"type": "string",
|
|
544
|
+
"enum": [
|
|
545
|
+
"manual-index-checkpoint"
|
|
546
|
+
],
|
|
547
|
+
"description": "首次 standalone-index 发布的人工索引停点;仅显式 opt-in 时启用"
|
|
548
|
+
},
|
|
549
|
+
"marketplaceIndexSha": {
|
|
550
|
+
"type": ["string", "null"],
|
|
551
|
+
"pattern": "^[0-9a-f]{40}$",
|
|
552
|
+
"description": "最终市场索引提交 SHA;首次 bootstrap prepare 阶段必须保持 null,verify 仅记录实际观察值"
|
|
553
|
+
},
|
|
542
554
|
"marketplaceCommitSha": {
|
|
543
555
|
"type": "string",
|
|
544
556
|
"pattern": "^[0-9a-f]{40}$",
|
|
@@ -697,6 +709,30 @@
|
|
|
697
709
|
]
|
|
698
710
|
}
|
|
699
711
|
},
|
|
712
|
+
{
|
|
713
|
+
"if": {
|
|
714
|
+
"required": [
|
|
715
|
+
"firstReleaseBootstrap"
|
|
716
|
+
]
|
|
717
|
+
},
|
|
718
|
+
"then": {
|
|
719
|
+
"required": [
|
|
720
|
+
"marketplaceSourceType",
|
|
721
|
+
"marketplaceRepo"
|
|
722
|
+
],
|
|
723
|
+
"properties": {
|
|
724
|
+
"type": {
|
|
725
|
+
"enum": [
|
|
726
|
+
"claude-plugin",
|
|
727
|
+
"codex-plugin"
|
|
728
|
+
]
|
|
729
|
+
},
|
|
730
|
+
"marketplaceSourceType": {
|
|
731
|
+
"const": "standalone-index"
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
},
|
|
700
736
|
{
|
|
701
737
|
"if": {
|
|
702
738
|
"required": [
|
|
@@ -257,6 +257,13 @@
|
|
|
257
257
|
],
|
|
258
258
|
"description": "市场来源类型:bundled-family(技能族自带市场文件)或 standalone-index(独立市场仓库);每个平台每次发布只冻结一种来源"
|
|
259
259
|
},
|
|
260
|
+
"firstReleaseBootstrap": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"enum": [
|
|
263
|
+
"manual-index-checkpoint"
|
|
264
|
+
],
|
|
265
|
+
"description": "首次 standalone-index 发布的人工索引停点;仅显式 opt-in 时启用"
|
|
266
|
+
},
|
|
260
267
|
"marketplaceIndexPath": {
|
|
261
268
|
"type": "string",
|
|
262
269
|
"minLength": 1,
|
|
@@ -447,6 +454,30 @@
|
|
|
447
454
|
]
|
|
448
455
|
}
|
|
449
456
|
},
|
|
457
|
+
{
|
|
458
|
+
"if": {
|
|
459
|
+
"required": [
|
|
460
|
+
"firstReleaseBootstrap"
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
"then": {
|
|
464
|
+
"required": [
|
|
465
|
+
"marketplaceSourceType",
|
|
466
|
+
"marketplaceRepo"
|
|
467
|
+
],
|
|
468
|
+
"properties": {
|
|
469
|
+
"type": {
|
|
470
|
+
"enum": [
|
|
471
|
+
"claude-plugin",
|
|
472
|
+
"codex-plugin"
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
"marketplaceSourceType": {
|
|
476
|
+
"const": "standalone-index"
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
},
|
|
450
481
|
{
|
|
451
482
|
"if": {
|
|
452
483
|
"required": [
|
|
@@ -13,7 +13,7 @@ description: 发布达到 VERIFIED 后处理可选的本地收尾:按发布分
|
|
|
13
13
|
|
|
14
14
|
这是发布后的独立收尾,不属于 `prepare → approve → publish → verify` 状态机。它不能把本机更新结果写成新的发布状态,也不能因为本机更新失败而降低 `VERIFIED`。
|
|
15
15
|
|
|
16
|
-
默认只读取冻结计划和 verify run。没有用户明确同意,不合并分支,不更新插件,不接受 Kimi 的安装信任提示。
|
|
16
|
+
默认只读取冻结计划和 verify 或 postVerify run。没有用户明确同意,不合并分支,不更新插件,不接受 Kimi 的安装信任提示。
|
|
17
17
|
|
|
18
18
|
## 先生成收尾清单
|
|
19
19
|
|
|
@@ -23,11 +23,11 @@ description: 发布达到 VERIFIED 后处理可选的本地收尾:按发布分
|
|
|
23
23
|
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
|
|
24
24
|
--root <project-root> \
|
|
25
25
|
--plan <plan-path> \
|
|
26
|
-
--run <
|
|
26
|
+
--run <verify-or-postverify-run-path> \
|
|
27
27
|
--json
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
脚本只接受与冻结计划一致的显式运行证据:计划未声明 `postVerify` hook 时,传入同计划的 `VERIFIED` verify run;计划声明了 `postVerify` hook 时,必须传入同计划、沿同一 `VERIFIED` verify run 继承谱系且所有 hook checkpoint 均为 `succeeded` 或 `NO_CHANGE` 的 `DISTRIBUTED` postVerify run。postVerify 尚未完成时,不能直接运行本机收尾;应先完成所需 checkpoint approval,再通过 `ship` 完成 postVerify,并使用结果中的 postVerify run 路径。
|
|
31
31
|
|
|
32
32
|
## 主动询问
|
|
33
33
|
|
|
@@ -45,7 +45,7 @@ node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
|
|
|
45
45
|
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
|
|
46
46
|
--root <project-root> \
|
|
47
47
|
--plan <plan-path> \
|
|
48
|
-
--run <
|
|
48
|
+
--run <verify-or-postverify-run-path> \
|
|
49
49
|
--update-local-hosts \
|
|
50
50
|
--hosts claude,codex,kimi,codebuddy,workbuddy \
|
|
51
51
|
--confirm-plan <planDigest> \
|
|
@@ -56,7 +56,7 @@ node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
|
|
|
56
56
|
|
|
57
57
|
- Claude 先重新绑定冻结市场,再重新观察安装状态;旧插件若仍存在,才调用正式更新命令。Codex 继续按正式市场协议移除并安装冻结的 Git 引用。两者都在安装后核对插件、市场、版本和市场检出提交。
|
|
58
58
|
- Kimi 的精确当前安装会在返回 `ALREADY_CURRENT` 前核对真实载荷;发生安装或迁移时,只在操作完成后核对结果。包名、版本、发布标签、已安装修订号和受管安装根必须与冻结计划一致;`.git` 只提供附加诊断,不是通过条件。旧的本地路径安装会在同一个受控终端交互界面(TUI)会话中先移除,再按发布标签安装、确认信任并重新加载。
|
|
59
|
-
- CodeBuddy/WorkBuddy 只处理同一 bundled-family
|
|
59
|
+
- CodeBuddy/WorkBuddy 只处理同一 bundled-family 插件和市场。CodeBuddy 仅探测全局 `codebuddy`/`cbc`,由收尾脚本把 `CODEBUDDY_CONFIG_DIR` 固定为有效 `HOME`(环境未提供时取操作系统用户主目录)下名为 `.codebuddy` 的目录;WorkBuddy 仅在 macOS 探测 WorkBuddy 应用内嵌 CLI,由收尾脚本把 `CODEBUDDY_CONFIG_DIR` 与 `WORKBUDDY_CONFIG_DIR` 同时固定为有效 `HOME` 下名为 `.workbuddy` 的目录,绝不把两者互作回退。只有冻结标签与计划声明的可变分支都从同一远端解析到冻结提交时,才调用正式市场更新和插件更新命令;完成后重新读取安装列表,并精确核对唯一条目的市场、版本和修订号。目标未安装、来源不符、远端不可访问或身份不一致时返回 `MANUAL_REQUIRED`,不修改宿主。非 macOS 的 WorkBuddy 返回 `SKIPPED_UNSUPPORTED_PLATFORM`。
|
|
60
60
|
|
|
61
61
|
任何宿主失败都保留其他宿主的实际结果,不回滚,也不把失败冒充成功。
|
|
62
62
|
|
|
@@ -12,12 +12,12 @@ description: "Discoverable entry point for release-skill: dependency and environ
|
|
|
12
12
|
## 职责
|
|
13
13
|
|
|
14
14
|
- 依赖和环境检查:Node.js >= 22、Git 决定本地准备就绪度;npm/gh 另行决定生产依赖就绪度
|
|
15
|
-
- 能力说明:缺少配置时走 `help → setup → assess`;已有配置的安全默认路径是 `help → assess → prepare --offline`;日常生产发布优先使用可恢复的 `ship`,兼容的分阶段闭环仍是 `prepare --online --production → approve → publish → verify`。冻结计划批准是正常发布级流程的唯一批准门;声明 `requiresApproval: true` 的 postPublish hook 仍须等待独立 checkpoint
|
|
15
|
+
- 能力说明:缺少配置时走 `help → setup → assess`;已有配置的安全默认路径是 `help → assess → prepare --offline`;日常生产发布优先使用可恢复的 `ship`,兼容的分阶段闭环仍是 `prepare --online --production → approve → publish → verify`。冻结计划批准是正常发布级流程的唯一批准门;声明 `requiresApproval: true` 的 postPublish hook 仍须等待独立 checkpoint 批准。若计划声明 `postVerify` hook,可用 `postverify` 直接执行独立收尾 run,也可由 `ship` 编排该阶段;本机收尾必须等待完成的 postVerify run,不能把仅有 `VERIFIED` 的 verify run 当作本机收尾授权。核心发布流程跨平台,WorkBuddy 本机收尾仅支持 macOS
|
|
16
16
|
- 最小示例:展示从 release-help 到 release-assess 的最短路径
|
|
17
17
|
- 只读诊断:运行 dry-run 检查,不修改任何文件
|
|
18
18
|
- 故障引导:根据错误码指向对应的修复 Skill
|
|
19
19
|
|
|
20
|
-
## 0.9.
|
|
20
|
+
## 0.9.5 候选边界
|
|
21
21
|
|
|
22
22
|
当前源码候选允许多发布单元项目在计划冻结前显式选择本轮范围。`prepare` 和新建的 `ship` 状态支持重复传入 `--unit <id>`;未传时继续选择全部配置单元。成功选择会列出选中与延期单元。延期单元不进入本轮计划,也不获得发布状态。
|
|
23
23
|
|
|
@@ -25,7 +25,7 @@ description: "Discoverable entry point for release-skill: dependency and environ
|
|
|
25
25
|
|
|
26
26
|
0.9.3 引入的四项工作流保护、Hook cache v2 和稳定隔离安装树记录在 0.9.4 继续保留。0.9.4 精确消费 Foundation 0.16.0 的公开包根 API。Hook cache 只复用绝对路径或经真实 cwd 校验的 cwd-relative executable identity;裸 PATH、PATHEXT、Windows 和观察不可用时,Hook 仍冷执行,缓存复用失败关闭且不写入 v2 cache。缓存没有 TTL。
|
|
27
27
|
|
|
28
|
-
稳定隔离安装树记录只在宿主命令退出、目录已隔离且扫描期间没有并发写入时执行;宿主附加链接只记录、不跟随,声明载荷中的 symlink 失败关闭,legacy 全树语义保持不变。0.9.
|
|
28
|
+
稳定隔离安装树记录只在宿主命令退出、目录已隔离且扫描期间没有并发写入时执行;宿主附加链接只记录、不跟随,声明载荷中的 symlink 失败关闭,legacy 全树语义保持不变。0.9.5 仍是源码候选,不能从本说明推断已批准、发布或验证。
|
|
29
29
|
|
|
30
30
|
**阶段通过规则**: `status` 与 `readiness.localPreparation.status` 只判断本地 help/assess/prepare;其充要条件是 `READY` 且 exit code 为 0。`missingRequired` 列出缺失的 Node/Git。生产发布必须另外读取 `readiness.productionPublish`:缺少 npm/gh 时为 `NOT_READY`,依赖存在时仍是 `AUTH_CHECK_REQUIRED`,因为 help 不访问网络、不验证认证。Agent 无权把本地就绪解释为生产就绪。
|
|
31
31
|
|
|
@@ -51,6 +51,10 @@ node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" ship --root <path> --targe
|
|
|
51
51
|
# 多发布单元项目显式选择本轮范围;未传 --unit 时仍为全部单元
|
|
52
52
|
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" ship --root <path> \
|
|
53
53
|
--target-version <version> --unit <unit-a> --unit <unit-b> --json
|
|
54
|
+
# 对已经 VERIFIED 的计划独立执行 postVerify hook;不读取或写入 ship state
|
|
55
|
+
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" postverify --root <path> \
|
|
56
|
+
--plan <plan-path> --approval <approval-path> --run <verified-run-path> \
|
|
57
|
+
--hook-approval <immutable-hook-approval-path> --json
|
|
54
58
|
# 开发阶段执行声明 hooks 并生成 prepare 可复用的内容绑定收据
|
|
55
59
|
# 配置时刻即授权(FM-16 处置 A):hook 是任意本地进程、无隔离、触发前无确认点,
|
|
56
60
|
# 命令调用本身即授权执行配置中的 hooks
|
|
@@ -15,6 +15,11 @@ verify 是发布流程的最终验证阶段,是唯一能将状态提升到 `VE
|
|
|
15
15
|
它执行远端状态重检、精确 npm 安装烟雾测试和消费者插件安装验证。
|
|
16
16
|
verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再次派生运行。
|
|
17
17
|
|
|
18
|
+
计划声明 `postVerify` hook 时,`postverify` 命令负责执行独立的收尾阶段。它接收
|
|
19
|
+
`VERIFIED` verify run、同一计划的 approval,以及逐个传入的 checkpoint approval,
|
|
20
|
+
然后原样调用现有 `postVerifyRelease`。该命令产生独立的 `postverify` run,不读取或写入
|
|
21
|
+
ship state;hook approval 缺失、错误或过期时,在 hook 执行前失败关闭。
|
|
22
|
+
|
|
18
23
|
**注意**: distribute gate (W1) 已经实现并集成在标准 verify 流程中。verify 现在会检查 postPublish 分发状态(git mirror + marketplace index),只有当所有外部动作都完成并通过验证时才达到 VERIFIED。
|
|
19
24
|
|
|
20
25
|
**工作流兼容性**:
|
|
@@ -45,13 +50,17 @@ verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再
|
|
|
45
50
|
2. 使用插件根相对路径运行 CLI;命令调用本身即授权执行已配置的 verification gate 和 smoke process
|
|
46
51
|
3. 检查 exit code 和结构化状态:`VERIFIED`(全部通过)/ 失败(具体错误)
|
|
47
52
|
4. 只有 `VERIFIED` 才是发布 happy end
|
|
48
|
-
5. 达到 `VERIFIED`
|
|
53
|
+
5. 达到 `VERIFIED` 后先检查计划是否声明 `postVerify` hook:直接收尾使用独立的 `postverify --plan --approval --run --hook-approval`,由该命令产生 `DISTRIBUTED` postVerify run;若仍由持久化 ship state 承担编排,则使用 `ship --hook-approval` 完成同一阶段,再把最终 run 路径交给 `release-finish`。没有 postVerify hook 时,把当前 verify run 路径交给 `release-finish`。随后按清单主动询问分支合并和本机宿主插件更新;发布策略已包含分支动作时略过合并询问
|
|
49
54
|
|
|
50
55
|
## 确定性脚本调用
|
|
51
56
|
|
|
52
57
|
```bash
|
|
53
58
|
# 从插件根运行
|
|
54
59
|
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" verify --root <path> --plan <plan-path> --run <run-path> --json
|
|
60
|
+
# 独立执行已 VERIFIED 计划的 postVerify hook;每个 requiresApproval hook 单独传入批准记录
|
|
61
|
+
node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" postverify --root <path> \
|
|
62
|
+
--plan <plan-path> --approval <approval-path> --run <verified-run-path> \
|
|
63
|
+
--hook-approval <immutable-hook-approval-path> --json
|
|
55
64
|
```
|
|
56
65
|
|
|
57
66
|
## 验证步骤
|
|
@@ -63,9 +72,13 @@ node "${CODEBUDDY_PLUGIN_ROOT}/bin/release-skill.mjs" verify --root <path> --pla
|
|
|
63
72
|
5. 对 Claude/Codex marketplace distribution 执行全新隔离消费者安装验证;收集 Kimi/CodeBuddy 的非阻塞 `manualFollowUps`
|
|
64
73
|
6. 全部通过 → `VERIFIED`
|
|
65
74
|
|
|
75
|
+
`postverify` 不属于上述远端验证步骤。它只处理已经达到 `VERIFIED` 的计划,并把
|
|
76
|
+
`postVerify` 阶段的执行结果写入独立 run。
|
|
77
|
+
|
|
66
78
|
## 发布后收尾
|
|
67
79
|
|
|
68
80
|
`VERIFIED` 不代表要自动修改开发分支或本机宿主。进入 `release-finish` 后,只有用户明确同意,才执行对应的本地动作。本机插件更新失败不会改变发布终态。
|
|
81
|
+
核心 prepare、publish、verify 流程跨平台;WorkBuddy 的本机收尾探测和更新仅支持 macOS。
|
|
69
82
|
|
|
70
83
|
## 烟雾测试
|
|
71
84
|
|
|
@@ -5,7 +5,7 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
5
5
|
import { execFile as execFileCb } from 'node:child_process';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { parseNodeMajor, meetsMinimum, computeReadinessStatus } from '../src/core/node-version.mjs';
|
|
8
|
-
import { registerPathRedactor } from '../src/core/errors.mjs';
|
|
8
|
+
import { EXIT_CODE_MAP, MISSING_PARAMETERS, registerPathRedactor } from '../src/core/errors.mjs';
|
|
9
9
|
import { redactSensitivePaths } from '../src/core/redact.mjs';
|
|
10
10
|
|
|
11
11
|
// Process output errors can be emitted immediately after a large console
|
|
@@ -37,7 +37,7 @@ registerPathRedactor(redactSensitivePaths);
|
|
|
37
37
|
|
|
38
38
|
const execFile = promisify(execFileCb);
|
|
39
39
|
|
|
40
|
-
const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'ship', 'post-release', 'attest', 'hooks', 'artifacts', 'docs', 'distribute', 'route', 'lineage']);
|
|
40
|
+
const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'postverify', 'ship', 'post-release', 'attest', 'hooks', 'artifacts', 'docs', 'distribute', 'route', 'lineage']);
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Wait until writes already queued on a process output stream have reached
|
|
@@ -91,6 +91,39 @@ async function exitAfterFlush(exitCode) {
|
|
|
91
91
|
process.exit(exitCode);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Keep command-line error JSON useful without exposing internal error objects,
|
|
96
|
+
* process envelopes, or filesystem details. Local-finish evidence errors
|
|
97
|
+
* intentionally expose only their stable cause and executable next steps.
|
|
98
|
+
*
|
|
99
|
+
* @param {unknown} error
|
|
100
|
+
* @returns {Record<string, unknown>}
|
|
101
|
+
*/
|
|
102
|
+
function publicErrorDetails(error) {
|
|
103
|
+
const details = error?.details;
|
|
104
|
+
if (!details || typeof details !== 'object' || Array.isArray(details)) return {};
|
|
105
|
+
const output = {};
|
|
106
|
+
if (details.cause && typeof details.cause === 'object' && !Array.isArray(details.cause)) {
|
|
107
|
+
const cause = {};
|
|
108
|
+
if (typeof details.cause.code === 'string') cause.code = details.cause.code;
|
|
109
|
+
if (typeof details.cause.message === 'string') cause.message = details.cause.message;
|
|
110
|
+
if (Object.keys(cause).length > 0) output.cause = cause;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(details.nextSteps)) {
|
|
113
|
+
const nextSteps = details.nextSteps
|
|
114
|
+
.filter((step) => step && typeof step === 'object' && !Array.isArray(step))
|
|
115
|
+
.map((step) => ({
|
|
116
|
+
...(typeof step.code === 'string' ? { code: step.code } : {}),
|
|
117
|
+
...(typeof step.message === 'string' ? { message: step.message } : {}),
|
|
118
|
+
...(Array.isArray(step.argv) && step.argv.every((arg) => typeof arg === 'string')
|
|
119
|
+
? { argv: [...step.argv] } : {}),
|
|
120
|
+
}))
|
|
121
|
+
.filter((step) => Object.keys(step).length > 0);
|
|
122
|
+
if (nextSteps.length > 0) output.nextSteps = nextSteps;
|
|
123
|
+
}
|
|
124
|
+
return output;
|
|
125
|
+
}
|
|
126
|
+
|
|
94
127
|
/**
|
|
95
128
|
* Check if a command is available and get its version.
|
|
96
129
|
*
|
|
@@ -274,6 +307,11 @@ function getCapabilityMaturity() {
|
|
|
274
307
|
mode: 'fresh consumer verification (protocol-tested; no OS/network sandbox)',
|
|
275
308
|
description: 'Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups',
|
|
276
309
|
},
|
|
310
|
+
postverify: {
|
|
311
|
+
available: true,
|
|
312
|
+
mode: 'independent postVerify execution',
|
|
313
|
+
description: 'Run approved postVerify hooks from a VERIFIED run in an independent run; never reads or writes ship state',
|
|
314
|
+
},
|
|
277
315
|
distribute: {
|
|
278
316
|
available: true,
|
|
279
317
|
mode: 'controlled production (protocol-tested; no OS/network sandbox)',
|
|
@@ -307,6 +345,7 @@ Commands:
|
|
|
307
345
|
publish Publish frozen GitHub/npm artifacts after approval
|
|
308
346
|
reconcile Resume PARTIAL state from evidence; conflicts require a human
|
|
309
347
|
verify Fresh remote and consumer verification; only this reaches VERIFIED
|
|
348
|
+
postverify Run approved postVerify hooks from a VERIFIED run in an independent run
|
|
310
349
|
ship Resume one durable prepare -> approve -> publish -> verify flow; completes a parked
|
|
311
350
|
postVerify hook once its checkpoint approval is provided (--hook-approval)
|
|
312
351
|
post-release Inspect optional local finishing work after VERIFIED; update installed host plugins
|
|
@@ -321,10 +360,10 @@ Commands:
|
|
|
321
360
|
|
|
322
361
|
Options:
|
|
323
362
|
--root <path> Project root directory (default: cwd)
|
|
324
|
-
--plan <path> Path to the release plan file
|
|
363
|
+
--plan <path> Path to the release plan file (required for approve/publish/reconcile/verify/postverify)
|
|
325
364
|
--run <path> Path to the release run file, or a run directory whose release-run.json
|
|
326
|
-
is resolved automatically (required for reconcile/verify)
|
|
327
|
-
--approval <path> Path to the approval record
|
|
365
|
+
is resolved automatically (required for reconcile/verify/postverify)
|
|
366
|
+
--approval <path> Path to the release-level approval record (required for publish/postverify)
|
|
328
367
|
--production Prepare immutable Git/npm production artifacts
|
|
329
368
|
--output <path> Override prepare/approve output path (non-production only)
|
|
330
369
|
--run-dir <path> Override prepare run directory; production requires one direct child of .release-skill/runs
|
|
@@ -361,7 +400,7 @@ Options:
|
|
|
361
400
|
--install-path <path> Actual managed plugin path when closure verification requires it
|
|
362
401
|
--install-channel <desktop|cli> CodeBuddy installation channel when required
|
|
363
402
|
--approve Approve the ship plan (boolean; plan digest is auto-resolved)
|
|
364
|
-
--hook-approval <path> Checkpoint approval for
|
|
403
|
+
--hook-approval <path> Checkpoint approval for one requiresApproval hook (ship/distribute/postverify; repeatable)
|
|
365
404
|
--state <path> Override the durable ship state file
|
|
366
405
|
--update-local-hosts Update installed plugins for selected local hosts after VERIFIED
|
|
367
406
|
--hosts <ids> Comma-separated local hosts for post-release update. No local host is updated unless --hosts contains at least one id
|
|
@@ -903,9 +942,15 @@ if (command === 'ship') {
|
|
|
903
942
|
} else {
|
|
904
943
|
console.log('Post-release: branch advancement was already included; skip the merge question.');
|
|
905
944
|
}
|
|
906
|
-
|
|
945
|
+
const localHostUpdate = result.postRelease.localHostUpdate;
|
|
946
|
+
if (localHostUpdate?.available === true && typeof localHostUpdate.runPath === 'string') {
|
|
907
947
|
console.log(`Post-release: ask whether to update local host plugins (${result.postRelease.localHostUpdate.hosts.join(', ')}).`);
|
|
908
|
-
console.log(`Post-release command: release-skill post-release --plan ${result.planPath} --run ${
|
|
948
|
+
console.log(`Post-release command: release-skill post-release --plan ${result.planPath} --run ${localHostUpdate.runPath}`);
|
|
949
|
+
console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
|
|
950
|
+
} else {
|
|
951
|
+
for (const step of localHostUpdate?.nextSteps ?? []) {
|
|
952
|
+
console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
|
|
953
|
+
}
|
|
909
954
|
}
|
|
910
955
|
}
|
|
911
956
|
for (const followUp of result.manualFollowUps ?? []) {
|
|
@@ -1293,14 +1338,13 @@ if (command === 'post-release') {
|
|
|
1293
1338
|
const planPath = value('--plan') ? resolve(value('--plan')) : undefined;
|
|
1294
1339
|
const runPath = value('--run') ? resolve(value('--run')) : undefined;
|
|
1295
1340
|
if (!planPath || !runPath) {
|
|
1296
|
-
const message = 'post-release requires --plan <path> and --run <
|
|
1341
|
+
const message = 'post-release requires --plan <path> and --run <verify-or-postverify-run-path>';
|
|
1297
1342
|
if (hasJson) console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message, exitCode: 1 }));
|
|
1298
1343
|
else console.error(`Error: ${message}`);
|
|
1299
1344
|
await exitAfterFlush(1);
|
|
1300
1345
|
}
|
|
1301
1346
|
try {
|
|
1302
1347
|
const {
|
|
1303
|
-
assertVerifiedReleaseRun,
|
|
1304
1348
|
derivePostReleaseChecklist,
|
|
1305
1349
|
updateLocalHostPlugins,
|
|
1306
1350
|
} = await import('../src/commands/post-release-local.mjs');
|
|
@@ -1308,24 +1352,26 @@ if (command === 'post-release') {
|
|
|
1308
1352
|
const {
|
|
1309
1353
|
loadRun,
|
|
1310
1354
|
resolveRunPath,
|
|
1311
|
-
validateRunLineage,
|
|
1312
1355
|
} = await import('../src/core/run.mjs');
|
|
1313
1356
|
const plan = JSON.parse(await readFile(planPath, 'utf8'));
|
|
1314
1357
|
validatePlan(plan);
|
|
1358
|
+
const updateRequested = args.includes('--update-local-hosts');
|
|
1315
1359
|
const resolvedRunPath = await resolveRunPath(runPath);
|
|
1316
1360
|
const runRecord = await loadRun(resolvedRunPath, {
|
|
1317
1361
|
requireDigest: true,
|
|
1318
1362
|
authorityPlanPath: planPath,
|
|
1319
1363
|
});
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1364
|
+
if (!updateRequested) {
|
|
1365
|
+
const { assertLocalFinishRun } = await import('../src/commands/post-release-local.mjs');
|
|
1366
|
+
await assertLocalFinishRun({
|
|
1367
|
+
plan,
|
|
1368
|
+
planPath,
|
|
1369
|
+
runPath: resolvedRunPath,
|
|
1370
|
+
runRecord,
|
|
1371
|
+
production: Boolean(plan.production),
|
|
1372
|
+
root: resolve(value('--root') ?? process.cwd()),
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1329
1375
|
const hostsIndex = args.indexOf('--hosts');
|
|
1330
1376
|
const rawHosts = hostsIndex !== -1
|
|
1331
1377
|
&& args[hostsIndex + 1]
|
|
@@ -1338,12 +1384,19 @@ if (command === 'post-release') {
|
|
|
1338
1384
|
.filter(Boolean);
|
|
1339
1385
|
const result = updateRequested
|
|
1340
1386
|
? await updateLocalHostPlugins({
|
|
1341
|
-
|
|
1387
|
+
planPath,
|
|
1388
|
+
runPath: resolvedRunPath,
|
|
1342
1389
|
root: resolve(value('--root') ?? process.cwd()),
|
|
1343
1390
|
confirmPlanDigest: value('--confirm-plan'),
|
|
1344
1391
|
selectedHosts,
|
|
1345
1392
|
})
|
|
1346
|
-
: derivePostReleaseChecklist(plan
|
|
1393
|
+
: derivePostReleaseChecklist(plan, {
|
|
1394
|
+
runPath: runRecord.command === 'postverify' && runRecord.status === 'DISTRIBUTED'
|
|
1395
|
+
? resolvedRunPath
|
|
1396
|
+
: runRecord.command === 'verify' ? resolvedRunPath : undefined,
|
|
1397
|
+
root: resolve(value('--root') ?? process.cwd()),
|
|
1398
|
+
postVerifyComplete: runRecord.command === 'postverify' && runRecord.status === 'DISTRIBUTED',
|
|
1399
|
+
});
|
|
1347
1400
|
|
|
1348
1401
|
if (hasJson) {
|
|
1349
1402
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1351,8 +1404,14 @@ if (command === 'post-release') {
|
|
|
1351
1404
|
console.log(`Post-release status: ${result.status}`);
|
|
1352
1405
|
if (result.merge.promptRequired) console.log('Ask whether the user wants to merge the remaining branch.');
|
|
1353
1406
|
else console.log('Branch advancement was already included in the release workflow; skip the merge question.');
|
|
1354
|
-
if (result.localHostUpdate.
|
|
1407
|
+
if (result.localHostUpdate.available === true && typeof result.localHostUpdate.runPath === 'string') {
|
|
1355
1408
|
console.log(`Ask whether to update local host plugins: ${result.localHostUpdate.hosts.join(', ')}`);
|
|
1409
|
+
console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result.localHostUpdate.runPath}`);
|
|
1410
|
+
console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
|
|
1411
|
+
} else {
|
|
1412
|
+
for (const step of result.localHostUpdate.nextSteps ?? []) {
|
|
1413
|
+
console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
|
|
1414
|
+
}
|
|
1356
1415
|
}
|
|
1357
1416
|
} else {
|
|
1358
1417
|
console.log(`Local host update: ${result.status}`);
|
|
@@ -1368,9 +1427,15 @@ if (command === 'post-release') {
|
|
|
1368
1427
|
console.log(JSON.stringify({
|
|
1369
1428
|
error: err.code ?? 'POST_RELEASE_FAILED',
|
|
1370
1429
|
message: err.message,
|
|
1430
|
+
details: publicErrorDetails(err),
|
|
1371
1431
|
exitCode: err.exitCode ?? 1,
|
|
1372
1432
|
}));
|
|
1373
|
-
} else
|
|
1433
|
+
} else {
|
|
1434
|
+
console.error(`Error: ${err.message}`);
|
|
1435
|
+
for (const step of err.details?.nextSteps ?? []) {
|
|
1436
|
+
console.error(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1374
1439
|
await exitAfterFlush(err.exitCode ?? 1);
|
|
1375
1440
|
}
|
|
1376
1441
|
}
|
|
@@ -1425,7 +1490,7 @@ if (command === 'verify') {
|
|
|
1425
1490
|
} = await import('../src/commands/post-release-local.mjs');
|
|
1426
1491
|
const plan = JSON.parse(await readFile(planPath, 'utf8'));
|
|
1427
1492
|
try {
|
|
1428
|
-
result.postRelease = derivePostReleaseChecklist(plan);
|
|
1493
|
+
result.postRelease = derivePostReleaseChecklist(plan, { root, runPath: result.runPath });
|
|
1429
1494
|
} catch (error) {
|
|
1430
1495
|
result.postRelease = unavailablePostReleaseChecklist(plan, error);
|
|
1431
1496
|
}
|
|
@@ -1451,8 +1516,15 @@ if (command === 'verify') {
|
|
|
1451
1516
|
} else if (result.postRelease?.merge.promptRequired) {
|
|
1452
1517
|
console.log('Post-release: ask whether to merge the remaining branch.');
|
|
1453
1518
|
}
|
|
1454
|
-
if (result.postRelease?.localHostUpdate
|
|
1519
|
+
if (result.postRelease?.localHostUpdate?.available === true
|
|
1520
|
+
&& typeof result.postRelease.localHostUpdate.runPath === 'string') {
|
|
1455
1521
|
console.log(`Post-release: ask whether to update local host plugins (${result.postRelease.localHostUpdate.hosts.join(', ')}).`);
|
|
1522
|
+
console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result.postRelease.localHostUpdate.runPath}`);
|
|
1523
|
+
console.log('Choose --hosts before adding --update-local-hosts to perform a local update.');
|
|
1524
|
+
} else {
|
|
1525
|
+
for (const step of result.postRelease?.localHostUpdate?.nextSteps ?? []) {
|
|
1526
|
+
console.log(`Next [${step.code}] ${step.message} (${step.argv.join(' ')})`);
|
|
1527
|
+
}
|
|
1456
1528
|
}
|
|
1457
1529
|
}
|
|
1458
1530
|
|
|
@@ -1462,6 +1534,7 @@ if (command === 'verify') {
|
|
|
1462
1534
|
const errOutput = {
|
|
1463
1535
|
error: err.code ?? 'UNKNOWN_ERROR',
|
|
1464
1536
|
message: err.message,
|
|
1537
|
+
details: err.details ?? {},
|
|
1465
1538
|
exitCode: err.exitCode ?? 1,
|
|
1466
1539
|
};
|
|
1467
1540
|
console.log(JSON.stringify(errOutput));
|
|
@@ -1472,6 +1545,124 @@ if (command === 'verify') {
|
|
|
1472
1545
|
}
|
|
1473
1546
|
}
|
|
1474
1547
|
|
|
1548
|
+
// --- PostVerify command routing ---
|
|
1549
|
+
if (command === 'postverify') {
|
|
1550
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
1551
|
+
const helpText = `release-skill postverify - Run approved postVerify hooks from a VERIFIED run
|
|
1552
|
+
|
|
1553
|
+
Usage:
|
|
1554
|
+
release-skill postverify [--root <path>] --plan <path> --approval <path> --run <path> [options]
|
|
1555
|
+
|
|
1556
|
+
Required options:
|
|
1557
|
+
--plan <path> Path to the release plan file
|
|
1558
|
+
--approval <path> Path to the release-level approval record
|
|
1559
|
+
--run <path> Path to the VERIFIED verify run file or directory
|
|
1560
|
+
|
|
1561
|
+
Optional options:
|
|
1562
|
+
--root <path> Project root directory (default: cwd)
|
|
1563
|
+
--hook-approval <path> Checkpoint approval for a requiresApproval postVerify hook (repeatable)
|
|
1564
|
+
--json Output results as JSON
|
|
1565
|
+
-h, --help Show this help message and exit
|
|
1566
|
+
|
|
1567
|
+
This command creates an independent postVerify run and never reads or writes ship state.`;
|
|
1568
|
+
if (hasJson) {
|
|
1569
|
+
console.log(JSON.stringify({
|
|
1570
|
+
command: 'postverify',
|
|
1571
|
+
status: 'HELP',
|
|
1572
|
+
usage: 'release-skill postverify [--root <path>] --plan <path> --approval <path> --run <path> [options]',
|
|
1573
|
+
options: {
|
|
1574
|
+
'--root': 'Project root directory (default: cwd)',
|
|
1575
|
+
'--plan': 'Path to the release plan file (required)',
|
|
1576
|
+
'--approval': 'Path to the release-level approval record (required)',
|
|
1577
|
+
'--run': 'Path to the VERIFIED verify run file or directory (required)',
|
|
1578
|
+
'--hook-approval': 'Checkpoint approval for a requiresApproval postVerify hook (repeatable)',
|
|
1579
|
+
'--json': 'Output results as JSON',
|
|
1580
|
+
'-h, --help': 'Show this help message and exit',
|
|
1581
|
+
},
|
|
1582
|
+
message: 'Creates an independent postVerify run and never reads or writes ship state.',
|
|
1583
|
+
}, null, 2));
|
|
1584
|
+
} else {
|
|
1585
|
+
console.log(helpText);
|
|
1586
|
+
}
|
|
1587
|
+
await exitAfterFlush(0);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
let malformedValuedFlag;
|
|
1591
|
+
const value = (flag) => {
|
|
1592
|
+
let firstValue;
|
|
1593
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1594
|
+
if (args[i] !== flag) continue;
|
|
1595
|
+
const next = args[i + 1];
|
|
1596
|
+
if (!next || next.startsWith('-')) {
|
|
1597
|
+
malformedValuedFlag ??= flag;
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
firstValue ??= next;
|
|
1601
|
+
}
|
|
1602
|
+
return firstValue;
|
|
1603
|
+
};
|
|
1604
|
+
const root = resolve(value('--root') ?? process.cwd());
|
|
1605
|
+
const planPath = value('--plan');
|
|
1606
|
+
const approvalPath = value('--approval');
|
|
1607
|
+
const runPath = value('--run');
|
|
1608
|
+
const postpublishApprovalPaths = [];
|
|
1609
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1610
|
+
if (args[i] === '--hook-approval') {
|
|
1611
|
+
const next = args[i + 1];
|
|
1612
|
+
if (!next || next.startsWith('-')) {
|
|
1613
|
+
malformedValuedFlag ??= '--hook-approval';
|
|
1614
|
+
} else {
|
|
1615
|
+
postpublishApprovalPaths.push(resolve(next));
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (malformedValuedFlag || !planPath || !approvalPath || !runPath) {
|
|
1621
|
+
const msg = 'postverify requires --plan <path>, --approval <path>, and --run <path>';
|
|
1622
|
+
if (hasJson) {
|
|
1623
|
+
console.log(JSON.stringify({ error: MISSING_PARAMETERS, message: msg, exitCode: EXIT_CODE_MAP[MISSING_PARAMETERS] }));
|
|
1624
|
+
} else {
|
|
1625
|
+
console.error(`Error: ${msg}`);
|
|
1626
|
+
}
|
|
1627
|
+
await exitAfterFlush(EXIT_CODE_MAP[MISSING_PARAMETERS]);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
try {
|
|
1631
|
+
const { postVerifyRelease } = await import('../src/commands/postverify.mjs');
|
|
1632
|
+
const result = await postVerifyRelease({
|
|
1633
|
+
planPath: resolve(planPath),
|
|
1634
|
+
approvalPath: resolve(approvalPath),
|
|
1635
|
+
sourceRunPath: resolve(runPath),
|
|
1636
|
+
root,
|
|
1637
|
+
...(postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}),
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
if (hasJson) {
|
|
1641
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1642
|
+
} else {
|
|
1643
|
+
console.log(`PostVerify status: ${result.status}`);
|
|
1644
|
+
for (const cp of result.checkpoints ?? []) {
|
|
1645
|
+
console.log(` ${cp.actionId}: ${cp.status}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (result.runPath) console.log(`PostVerify run: ${result.runPath}`);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
await exitAfterFlush(result.status === 'DISTRIBUTED' ? 0 : 1);
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
if (hasJson) {
|
|
1653
|
+
console.log(JSON.stringify({
|
|
1654
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
1655
|
+
message: err.message,
|
|
1656
|
+
details: err.details ?? {},
|
|
1657
|
+
exitCode: err.exitCode ?? 1,
|
|
1658
|
+
}));
|
|
1659
|
+
} else {
|
|
1660
|
+
console.error(`Error: ${err.message}`);
|
|
1661
|
+
}
|
|
1662
|
+
await exitAfterFlush(err.exitCode ?? 1);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1475
1666
|
// --- Publish command routing ---
|
|
1476
1667
|
if (command === 'publish') {
|
|
1477
1668
|
|