release-skill 0.9.5 → 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.
Files changed (36) 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 +27 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +16 -16
  10. package/README.zh-CN.md +14 -15
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +172 -10
  14. package/adapters/claude/skills/release-help/SKILL.md +5 -1
  15. package/adapters/claude/skills/release-verify/SKILL.md +13 -1
  16. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  17. package/adapters/codex/bin/release-skill.bundle.mjs +172 -10
  18. package/adapters/codex/skills/release-help/SKILL.md +5 -1
  19. package/adapters/codex/skills/release-verify/SKILL.md +13 -1
  20. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  21. package/adapters/kimi/bin/release-skill.bundle.mjs +172 -10
  22. package/adapters/kimi/skills/release-help/SKILL.md +5 -1
  23. package/adapters/kimi/skills/release-verify/SKILL.md +13 -1
  24. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  25. package/adapters/workbuddy/bin/release-skill.bundle.mjs +172 -10
  26. package/adapters/workbuddy/skills/release-help/SKILL.md +5 -1
  27. package/adapters/workbuddy/skills/release-verify/SKILL.md +13 -1
  28. package/bin/release-skill-cli.mjs +130 -6
  29. package/bin/release-skill.bundle.mjs +172 -10
  30. package/package.json +1 -1
  31. package/platform-manifest.json +4 -4
  32. package/skills/release-help/SKILL.md +5 -1
  33. package/skills/release-verify/SKILL.md +13 -1
  34. package/skills-src/release-help/SKILL.md +5 -1
  35. package/skills-src/release-verify/SKILL.md +13 -1
  36. package/src/adapters/plugin-marketplace.mjs +54 -5
@@ -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` 后先检查计划是否声明 `postVerify` hook:有未完成 hook 时,先按 approval 合同批准并通过 `ship` 完成 postVerify,再把最终 `DISTRIBUTED` postVerify run 路径交给 `release-finish`;没有 postVerify hook 时,才把当前 verify run 路径交给 `release-finish`。随后按清单主动询问分支合并和本机宿主插件更新;发布策略已包含分支动作时略过合并询问
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 "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" verify --root <path> --plan <plan-path> --run <run-path> --json
60
+ # 独立执行已 VERIFIED 计划的 postVerify hook;每个 requiresApproval hook 单独传入批准记录
61
+ node "${CLAUDE_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,6 +72,9 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" verify --root <path> --plan <
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` 后,只有用户明确同意,才执行对应的本地动作。本机插件更新失败不会改变发布终态。
@@ -35,6 +35,7 @@ import {
35
35
  } from '../core/installation-contract.mjs';
36
36
 
37
37
  import { createHash } from 'node:crypto';
38
+ import { createFilesystemRootBinding, observeFilesystemTree } from 'skill-family-harness-node';
38
39
  import { computeFrozenSnapshot, resolveFrozenPath } from '../snapshot/frozen.mjs';
39
40
  import { PLATFORMS, getPlatform, resolvePlatformRoute, resolveCapabilityConflicts } from '../platforms/registry.mjs';
40
41
  import {
@@ -101,6 +102,41 @@ const EXTRA_INSTALLED_PATHS_CAP = 200;
101
102
  /** Diagnostic cap: at most this many conflict paths are listed per error. */
102
103
  const PAYLOAD_CONFLICT_REPORT_CAP = 10;
103
104
 
105
+ function foundationPayloadMembers(observation) {
106
+ return observation.members.map((member) => {
107
+ if (member.type === 'file') {
108
+ return {
109
+ path: member.path,
110
+ type: member.type,
111
+ mode: member.statMode & ~0o222,
112
+ size: member.bytes,
113
+ contentDigest: member.sha256,
114
+ };
115
+ }
116
+ if (member.type === 'directory') {
117
+ return { path: member.path, type: member.type, mode: member.statMode };
118
+ }
119
+ return {
120
+ path: member.path,
121
+ type: member.type,
122
+ mode: member.statMode,
123
+ size: member.bytes,
124
+ targetBase64: member.targetBase64,
125
+ };
126
+ });
127
+ }
128
+
129
+ async function observeMarketplaceInstallTree(installPath) {
130
+ const canonicalInstallPath = await realpath(installPath);
131
+ const rootBinding = await createFilesystemRootBinding(canonicalInstallPath);
132
+ const observation = await observeFilesystemTree({
133
+ root: canonicalInstallPath,
134
+ rootBinding,
135
+ symlinkPolicy: { mode: 'record' },
136
+ });
137
+ return foundationPayloadMembers(observation);
138
+ }
139
+
104
140
  /** 消费端安装验证配方版本(与 prepare.mjs 一致)。 */
105
141
  const CONSUMER_INSTALL_RECIPE_VERSION = 'consumer-install-v1';
106
142
 
@@ -534,10 +570,10 @@ export async function verifyInstalledMarketplacePayload(action, context, install
534
570
  // containment semantics; they differ only in the authority subtree source
535
571
  // (declared-manifest reads the entry's declared source subpath; external
536
572
  // short-circuits to the whole tree '.').
537
- const installedSnapshot = await computeFrozenSnapshot(installPath);
573
+ const installedMembers = await observeMarketplaceInstallTree(installPath);
538
574
  const authorityPayload = transportPayload(authorityEntries);
539
575
  const installedByPath = new Map(
540
- transportPayload(installedSnapshot.entries).map((entry) => [entry.path, entry]),
576
+ installedMembers.map((entry) => [entry.path, entry]),
541
577
  );
542
578
  const conflicts = [];
543
579
  for (const authorityEntry of authorityPayload) {
@@ -567,15 +603,25 @@ export async function verifyInstalledMarketplacePayload(action, context, install
567
603
  );
568
604
  }
569
605
  const authorityPaths = new Set(authorityPayload.map((entry) => entry.path));
570
- const extraPaths = installedSnapshot.entries
571
- .map((entry) => entry.path)
572
- .filter((path) => !authorityPaths.has(path));
606
+ const extraMembers = installedMembers
607
+ .filter((entry) => entry.type !== 'directory' && !authorityPaths.has(entry.path));
608
+ const extraPaths = extraMembers.map((entry) => entry.path);
573
609
  const extraInstalledPaths = extraPaths.slice(0, EXTRA_INSTALLED_PATHS_CAP);
610
+ const extraInstalledLinks = extraMembers
611
+ .filter((entry) => entry.type === 'symlink')
612
+ .slice(0, EXTRA_INSTALLED_PATHS_CAP)
613
+ .map((entry) => ({
614
+ path: entry.path,
615
+ targetBase64: entry.targetBase64,
616
+ bytes: entry.size,
617
+ statMode: entry.mode,
618
+ }));
574
619
  // This is not an expected-value backfill: the sealed authority digest was
575
620
  // revalidated above and every declared file was independently compared.
576
621
  return {
577
622
  manifestDigest: action.manifestDigest,
578
623
  extraInstalledPaths,
624
+ ...(extraInstalledLinks.length > 0 ? { extraInstalledLinks } : {}),
579
625
  ...(extraPaths.length > EXTRA_INSTALLED_PATHS_CAP
580
626
  ? { extraInstalledPathsTotal: extraPaths.length }
581
627
  : {}),
@@ -605,6 +651,9 @@ function extraInstalledPathsAudit(binding) {
605
651
  if (!binding || !Array.isArray(binding.extraInstalledPaths)) return {};
606
652
  return {
607
653
  extraInstalledPaths: binding.extraInstalledPaths,
654
+ ...(Array.isArray(binding.extraInstalledLinks) && binding.extraInstalledLinks.length > 0
655
+ ? { extraInstalledLinks: binding.extraInstalledLinks }
656
+ : {}),
608
657
  ...(binding.extraInstalledPathsTotal !== undefined
609
658
  ? { extraInstalledPathsTotal: binding.extraInstalledPathsTotal }
610
659
  : {}),