release-skill 0.9.5 → 0.9.7

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 (41) 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 +55 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +19 -19
  10. package/README.zh-CN.md +17 -18
  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 +249 -17
  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 +249 -17
  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 +249 -17
  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 +249 -17
  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 +249 -17
  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
  37. package/src/commands/prepare.mjs +5 -3
  38. package/src/commands/publish.mjs +30 -3
  39. package/src/commands/verify.mjs +14 -7
  40. package/src/core/skill-resource-closure.mjs +19 -1
  41. package/src/core/surface-host-bindings.mjs +35 -0
@@ -9,9 +9,9 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
9
9
  // Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
10
10
  const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
11
11
  // Package identity injected at build time — closure-independent --version probe.
12
- const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.5"});
12
+ const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.7"});
13
13
  // Build-time source digest for the BUNDLE_STALE freshness gate (see above).
14
- const __bundleSourceDigest = "cbcc967eb1306a8f2e88e404a9ffbdb8bfaf0ffb8863fe8c5521cbb4d44346c1";
14
+ const __bundleSourceDigest = "938bc6828f17e2248a4fe167bcd3353b43848ec1a494dc17dc663d390847150c";
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -109520,9 +109520,27 @@ async function checkSkillResourceClosure({
109520
109520
  result2.receiptDigest = digestDocument(receiptProjection(result2));
109521
109521
  return result2;
109522
109522
  }
109523
- function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces) {
109523
+ function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces, coverageClaims = []) {
109524
109524
  const missing = [];
109525
+ const observedById = new Map((surfaces ?? []).map((surface) => [surface.id, surface]));
109526
+ const claimsByHost = /* @__PURE__ */ new Map();
109527
+ for (const claim of coverageClaims ?? []) {
109528
+ const hostClaims = claimsByHost.get(claim.host) ?? [];
109529
+ hostClaims.push(claim);
109530
+ claimsByHost.set(claim.host, hostClaims);
109531
+ }
109525
109532
  for (const expectedHost of [...new Set(expectedHosts ?? [])].sort((a, b) => a.localeCompare(b))) {
109533
+ const declaredClaims = claimsByHost.get(expectedHost) ?? [];
109534
+ if (declaredClaims.length > 0) {
109535
+ const missingClaim = declaredClaims.find((claim) => {
109536
+ const surface3 = observedById.get(claim.surfaceId);
109537
+ return !surface3 || !(surface3.skillCount >= 1);
109538
+ });
109539
+ if (!missingClaim) continue;
109540
+ const surface2 = observedById.get(missingClaim.surfaceId);
109541
+ missing.push({ host: expectedHost, skillCount: surface2?.skillCount ?? 0 });
109542
+ continue;
109543
+ }
109526
109544
  const surface = (surfaces ?? []).find((item) => item.host === expectedHost);
109527
109545
  if (!surface || !(surface.skillCount >= 1)) {
109528
109546
  missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
@@ -109640,6 +109658,39 @@ function transportPayload(entries) {
109640
109658
  contentDigest
109641
109659
  }));
109642
109660
  }
109661
+ function foundationPayloadMembers(observation) {
109662
+ return observation.members.map((member) => {
109663
+ if (member.type === "file") {
109664
+ return {
109665
+ path: member.path,
109666
+ type: member.type,
109667
+ mode: member.statMode & ~146,
109668
+ size: member.bytes,
109669
+ contentDigest: member.sha256
109670
+ };
109671
+ }
109672
+ if (member.type === "directory") {
109673
+ return { path: member.path, type: member.type, mode: member.statMode };
109674
+ }
109675
+ return {
109676
+ path: member.path,
109677
+ type: member.type,
109678
+ mode: member.statMode,
109679
+ size: member.bytes,
109680
+ targetBase64: member.targetBase64
109681
+ };
109682
+ });
109683
+ }
109684
+ async function observeMarketplaceInstallTree(installPath) {
109685
+ const canonicalInstallPath = await realpath22(installPath);
109686
+ const rootBinding = await createFilesystemRootBinding(canonicalInstallPath);
109687
+ const observation = await observeFilesystemTree({
109688
+ root: canonicalInstallPath,
109689
+ rootBinding,
109690
+ symlinkPolicy: { mode: "record" }
109691
+ });
109692
+ return foundationPayloadMembers(observation);
109693
+ }
109643
109694
  function resolveMarketplaceRoot(platform, marketplaceIndexPath) {
109644
109695
  const defaultMarketplace = platform.manifestPaths.marketplace;
109645
109696
  if (!defaultMarketplace) {
@@ -109869,10 +109920,10 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
109869
109920
  throw new Error(`unsupported marketplace payload contract: ${JSON.stringify(payloadContract)}`);
109870
109921
  }
109871
109922
  if (payloadContract === PAYLOAD_CONTRACT_DECLARED_MANIFEST || payloadContract === PAYLOAD_CONTRACT_EXTERNAL_MARKETPLACE) {
109872
- const installedSnapshot2 = await computeFrozenSnapshot(installPath);
109923
+ const installedMembers = await observeMarketplaceInstallTree(installPath);
109873
109924
  const authorityPayload = transportPayload(authorityEntries);
109874
109925
  const installedByPath = new Map(
109875
- transportPayload(installedSnapshot2.entries).map((entry) => [entry.path, entry])
109926
+ installedMembers.map((entry) => [entry.path, entry])
109876
109927
  );
109877
109928
  const conflicts = [];
109878
109929
  for (const authorityEntry of authorityPayload) {
@@ -109897,11 +109948,19 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
109897
109948
  );
109898
109949
  }
109899
109950
  const authorityPaths = new Set(authorityPayload.map((entry) => entry.path));
109900
- const extraPaths = installedSnapshot2.entries.map((entry) => entry.path).filter((path40) => !authorityPaths.has(path40));
109951
+ const extraMembers = installedMembers.filter((entry) => entry.type !== "directory" && !authorityPaths.has(entry.path));
109952
+ const extraPaths = extraMembers.map((entry) => entry.path);
109901
109953
  const extraInstalledPaths = extraPaths.slice(0, EXTRA_INSTALLED_PATHS_CAP);
109954
+ const extraInstalledLinks = extraMembers.filter((entry) => entry.type === "symlink").slice(0, EXTRA_INSTALLED_PATHS_CAP).map((entry) => ({
109955
+ path: entry.path,
109956
+ targetBase64: entry.targetBase64,
109957
+ bytes: entry.size,
109958
+ statMode: entry.mode
109959
+ }));
109902
109960
  return {
109903
109961
  manifestDigest: action.manifestDigest,
109904
109962
  extraInstalledPaths,
109963
+ ...extraInstalledLinks.length > 0 ? { extraInstalledLinks } : {},
109905
109964
  ...extraPaths.length > EXTRA_INSTALLED_PATHS_CAP ? { extraInstalledPathsTotal: extraPaths.length } : {}
109906
109965
  };
109907
109966
  }
@@ -109917,6 +109976,7 @@ function extraInstalledPathsAudit(binding) {
109917
109976
  if (!binding || !Array.isArray(binding.extraInstalledPaths)) return {};
109918
109977
  return {
109919
109978
  extraInstalledPaths: binding.extraInstalledPaths,
109979
+ ...Array.isArray(binding.extraInstalledLinks) && binding.extraInstalledLinks.length > 0 ? { extraInstalledLinks: binding.extraInstalledLinks } : {},
109920
109980
  ...binding.extraInstalledPathsTotal !== void 0 ? { extraInstalledPathsTotal: binding.extraInstalledPathsTotal } : {}
109921
109981
  };
109922
109982
  }
@@ -112320,6 +112380,7 @@ var init_plugin_marketplace = __esm({
112320
112380
  async "src/adapters/plugin-marketplace.mjs"() {
112321
112381
  init_contract();
112322
112382
  init_installation_contract();
112383
+ init_src2();
112323
112384
  init_frozen();
112324
112385
  await init_registry2();
112325
112386
  await init_kimi();
@@ -112332,6 +112393,8 @@ var init_plugin_marketplace = __esm({
112332
112393
  PAYLOAD_CONTRACT_EXTERNAL_MARKETPLACE = "external-marketplace-v1";
112333
112394
  EXTRA_INSTALLED_PATHS_CAP = 200;
112334
112395
  PAYLOAD_CONFLICT_REPORT_CAP = 10;
112396
+ __name(foundationPayloadMembers, "foundationPayloadMembers");
112397
+ __name(observeMarketplaceInstallTree, "observeMarketplaceInstallTree");
112335
112398
  CONSUMER_INSTALL_RECIPE_VERSION = "consumer-install-v1";
112336
112399
  __name(resolveMarketplaceRoot, "resolveMarketplaceRoot");
112337
112400
  __name(extractDeclaredPluginSource, "extractDeclaredPluginSource");
@@ -112451,6 +112514,23 @@ async function deriveSurfaceHostBinding({ manifest, pluginRoot, platform, snapsh
112451
112514
  const host = await normalizeHostId(platform.buildAdapter.name);
112452
112515
  return { surfaceId, host };
112453
112516
  }
112517
+ function groupSurfaceHostBindings(claims = []) {
112518
+ if (!Array.isArray(claims)) {
112519
+ throw new TypeError("surfaceHostBindings must be an array");
112520
+ }
112521
+ const bySurface = /* @__PURE__ */ new Map();
112522
+ for (const claim of claims) {
112523
+ if (!claim || typeof claim !== "object" || typeof claim.surfaceId !== "string" || typeof claim.host !== "string") {
112524
+ throw new TypeError("surfaceHostBindings entries must be objects with string surfaceId and host");
112525
+ }
112526
+ const existing = bySurface.get(claim.surfaceId) ?? [];
112527
+ existing.push({ surfaceId: claim.surfaceId, host: claim.host });
112528
+ bySurface.set(claim.surfaceId, existing);
112529
+ }
112530
+ const coverageClaims = [...bySurface.values()].flat().sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
112531
+ const checkerBindings = [...bySurface.values()].filter((surfaceClaims) => surfaceClaims.length === 1).map(([claim]) => claim).sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
112532
+ return { coverageClaims, checkerBindings };
112533
+ }
112454
112534
  var SKILLS_NORMALIZERS;
112455
112535
  var init_surface_host_bindings = __esm({
112456
112536
  async "src/core/surface-host-bindings.mjs"() {
@@ -112468,6 +112548,7 @@ var init_surface_host_bindings = __esm({
112468
112548
  __name(pluginRootFromManifestRelativePath, "pluginRootFromManifestRelativePath");
112469
112549
  __name(assertDeclaredSkillsDirExists, "assertDeclaredSkillsDirExists");
112470
112550
  __name(deriveSurfaceHostBinding, "deriveSurfaceHostBinding");
112551
+ __name(groupSurfaceHostBindings, "groupSurfaceHostBindings");
112471
112552
  }
112472
112553
  });
112473
112554
 
@@ -113898,11 +113979,16 @@ async function verifyRelease(options) {
113898
113979
  const unit = (plan.units ?? []).find((u) => u.id === unitId);
113899
113980
  const dist = unit?.distributions?.find((d) => d.type === platform.distributionType);
113900
113981
  let binding = null;
113982
+ let frozenPluginRoot = null;
113901
113983
  const contract2 = dist?.installationContract;
113902
113984
  if (contract2?.normalizedManifest) {
113985
+ frozenPluginRoot = pluginRootFromManifestRelativePath(contract2.manifestRelativePath);
113903
113986
  binding = await deriveSurfaceHostBinding({
113904
113987
  manifest: contract2.normalizedManifest,
113905
- pluginRoot: pluginRootFromManifestRelativePath(contract2.manifestRelativePath),
113988
+ // Adapter observations are already rooted at the installed
113989
+ // plugin. The frozen manifest's skills path is therefore
113990
+ // relative to '.', not to the snapshot-root manifest path.
113991
+ pluginRoot: ".",
113906
113992
  platform,
113907
113993
  snapshotDir: check.observation.installPath
113908
113994
  });
@@ -113915,6 +114001,7 @@ async function verifyRelease(options) {
113915
114001
  unitId,
113916
114002
  surfaceId: binding?.surfaceId ?? ".",
113917
114003
  binding,
114004
+ frozenPluginRoot,
113918
114005
  unit
113919
114006
  });
113920
114007
  }
@@ -113997,7 +114084,7 @@ async function verifyRelease(options) {
113997
114084
  );
113998
114085
  }
113999
114086
  const pluginDistCount = (surface.unit?.distributions ?? []).filter((d) => d.type !== "npm").length;
114000
- if (pluginDistCount === 1) {
114087
+ if (pluginDistCount === 1 && surface.frozenPluginRoot === ".") {
114001
114088
  const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
114002
114089
  unitId: surface.unitId,
114003
114090
  preparedAt: expectedUnitReceipt.preparedAt ?? null,
@@ -129591,10 +129678,11 @@ async function runPrepareSkillResourceClosureGate({
129591
129678
  });
129592
129679
  if (binding) surfaceHostBindings.push(binding);
129593
129680
  }
129681
+ const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
129594
129682
  const closureResult = await checkSkillResourceClosure({
129595
129683
  snapshotDir: manifest.outputDir,
129596
129684
  host: "root",
129597
- surfaceHostBindings
129685
+ surfaceHostBindings: checkerBindings
129598
129686
  });
129599
129687
  const receipt = createSkillResourceClosureReceipt(closureResult, {
129600
129688
  unitId: unit.id,
@@ -129639,7 +129727,8 @@ async function runPrepareSkillResourceClosureGate({
129639
129727
  }
129640
129728
  const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
129641
129729
  expectedHosts,
129642
- closureResult.surfaces
129730
+ closureResult.surfaces,
129731
+ coverageClaims
129643
129732
  );
129644
129733
  if (!hostCoverage.passed) {
129645
129734
  await evidence.append({
@@ -129663,7 +129752,7 @@ async function runPrepareSkillResourceClosureGate({
129663
129752
  }
129664
129753
  );
129665
129754
  }
129666
- for (const binding of surfaceHostBindings) {
129755
+ for (const binding of coverageClaims) {
129667
129756
  const boundSurface = closureResult.surfaces.find(
129668
129757
  (surface) => surface.id === binding.surfaceId
129669
129758
  );
@@ -133273,10 +133362,11 @@ async function publishRelease(options) {
133273
133362
  });
133274
133363
  if (binding) surfaceHostBindings.push(binding);
133275
133364
  }
133365
+ const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
133276
133366
  const closureResult = await checkSkillResourceClosure({
133277
133367
  snapshotDir,
133278
133368
  host: "root",
133279
- surfaceHostBindings
133369
+ surfaceHostBindings: checkerBindings
133280
133370
  });
133281
133371
  if (closureResult.findings.length > 0) {
133282
133372
  await evidence.append({
@@ -133292,6 +133382,31 @@ async function publishRelease(options) {
133292
133382
  { unitId, findings: closureResult.findings }
133293
133383
  );
133294
133384
  }
133385
+ const expectedHosts = [];
133386
+ for (const distribution of frozenUnit?.distributions ?? []) {
133387
+ const platform = PLATFORMS.find((item) => item.distributionType === distribution.type);
133388
+ if (platform) expectedHosts.push(await normalizeHostId(platform.buildAdapter.name));
133389
+ }
133390
+ const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
133391
+ expectedHosts,
133392
+ closureResult.surfaces,
133393
+ coverageClaims
133394
+ );
133395
+ if (!hostCoverage.passed) {
133396
+ await evidence.append({
133397
+ phase: "safety-gate",
133398
+ gate: "skill-resource-closure",
133399
+ status: "failed",
133400
+ unitId,
133401
+ reason: "declared-host-surface-missing",
133402
+ missingHosts: hostCoverage.missing
133403
+ });
133404
+ throw new ReleaseError(
133405
+ GATE_FAILED,
133406
+ `skill resource closure recheck failed for unit "${unitId}": declared host surface(s) missing or empty: ${hostCoverage.missing.map((item) => item.host).join(", ")}`,
133407
+ { unitId, missingHosts: hostCoverage.missing }
133408
+ );
133409
+ }
133295
133410
  const observed = createSkillResourceClosureReceipt(closureResult, {
133296
133411
  unitId,
133297
133412
  preparedAt: expected.preparedAt ?? null,
@@ -145779,7 +145894,7 @@ guardOutputStream(process.stdout);
145779
145894
  guardOutputStream(process.stderr);
145780
145895
  registerPathRedactor(redactSensitivePaths);
145781
145896
  var execFile19 = promisify26(execFileCb22);
145782
- var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
145897
+ var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "postverify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
145783
145898
  function flushOutputStream(stream) {
145784
145899
  if (!stream?.writable || stream.destroyed || stream.writableEnded || outputStreamStates.get(stream)?.failed) {
145785
145900
  return Promise.resolve();
@@ -145970,6 +146085,11 @@ function getCapabilityMaturity() {
145970
146085
  mode: "fresh consumer verification (protocol-tested; no OS/network sandbox)",
145971
146086
  description: "Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups"
145972
146087
  },
146088
+ postverify: {
146089
+ available: true,
146090
+ mode: "independent postVerify execution",
146091
+ description: "Run approved postVerify hooks from a VERIFIED run in an independent run; never reads or writes ship state"
146092
+ },
145973
146093
  distribute: {
145974
146094
  available: true,
145975
146095
  mode: "controlled production (protocol-tested; no OS/network sandbox)",
@@ -146003,6 +146123,7 @@ Commands:
146003
146123
  publish Publish frozen GitHub/npm artifacts after approval
146004
146124
  reconcile Resume PARTIAL state from evidence; conflicts require a human
146005
146125
  verify Fresh remote and consumer verification; only this reaches VERIFIED
146126
+ postverify Run approved postVerify hooks from a VERIFIED run in an independent run
146006
146127
  ship Resume one durable prepare -> approve -> publish -> verify flow; completes a parked
146007
146128
  postVerify hook once its checkpoint approval is provided (--hook-approval)
146008
146129
  post-release Inspect optional local finishing work after VERIFIED; update installed host plugins
@@ -146017,10 +146138,10 @@ Commands:
146017
146138
 
146018
146139
  Options:
146019
146140
  --root <path> Project root directory (default: cwd)
146020
- --plan <path> Path to the release plan file
146141
+ --plan <path> Path to the release plan file (required for approve/publish/reconcile/verify/postverify)
146021
146142
  --run <path> Path to the release run file, or a run directory whose release-run.json
146022
- is resolved automatically (required for reconcile/verify)
146023
- --approval <path> Path to the approval record
146143
+ is resolved automatically (required for reconcile/verify/postverify)
146144
+ --approval <path> Path to the release-level approval record (required for publish/postverify)
146024
146145
  --production Prepare immutable Git/npm production artifacts
146025
146146
  --output <path> Override prepare/approve output path (non-production only)
146026
146147
  --run-dir <path> Override prepare run directory; production requires one direct child of .release-skill/runs
@@ -146057,7 +146178,7 @@ Options:
146057
146178
  --install-path <path> Actual managed plugin path when closure verification requires it
146058
146179
  --install-channel <desktop|cli> CodeBuddy installation channel when required
146059
146180
  --approve Approve the ship plan (boolean; plan digest is auto-resolved)
146060
- --hook-approval <path> Checkpoint approval for a requiresApproval postPublish hook (ship/distribute; repeatable)
146181
+ --hook-approval <path> Checkpoint approval for one requiresApproval hook (ship/distribute/postverify; repeatable)
146061
146182
  --state <path> Override the durable ship state file
146062
146183
  --update-local-hosts Update installed plugins for selected local hosts after VERIFIED
146063
146184
  --hosts <ids> Comma-separated local hosts for post-release update. No local host is updated unless --hosts contains at least one id
@@ -147076,6 +147197,117 @@ if (command === "verify") {
147076
147197
  await exitAfterFlush(err.exitCode ?? 1);
147077
147198
  }
147078
147199
  }
147200
+ if (command === "postverify") {
147201
+ if (args.includes("--help") || args.includes("-h")) {
147202
+ const helpText = `release-skill postverify - Run approved postVerify hooks from a VERIFIED run
147203
+
147204
+ Usage:
147205
+ release-skill postverify [--root <path>] --plan <path> --approval <path> --run <path> [options]
147206
+
147207
+ Required options:
147208
+ --plan <path> Path to the release plan file
147209
+ --approval <path> Path to the release-level approval record
147210
+ --run <path> Path to the VERIFIED verify run file or directory
147211
+
147212
+ Optional options:
147213
+ --root <path> Project root directory (default: cwd)
147214
+ --hook-approval <path> Checkpoint approval for a requiresApproval postVerify hook (repeatable)
147215
+ --json Output results as JSON
147216
+ -h, --help Show this help message and exit
147217
+
147218
+ This command creates an independent postVerify run and never reads or writes ship state.`;
147219
+ if (hasJson) {
147220
+ console.log(JSON.stringify({
147221
+ command: "postverify",
147222
+ status: "HELP",
147223
+ usage: "release-skill postverify [--root <path>] --plan <path> --approval <path> --run <path> [options]",
147224
+ options: {
147225
+ "--root": "Project root directory (default: cwd)",
147226
+ "--plan": "Path to the release plan file (required)",
147227
+ "--approval": "Path to the release-level approval record (required)",
147228
+ "--run": "Path to the VERIFIED verify run file or directory (required)",
147229
+ "--hook-approval": "Checkpoint approval for a requiresApproval postVerify hook (repeatable)",
147230
+ "--json": "Output results as JSON",
147231
+ "-h, --help": "Show this help message and exit"
147232
+ },
147233
+ message: "Creates an independent postVerify run and never reads or writes ship state."
147234
+ }, null, 2));
147235
+ } else {
147236
+ console.log(helpText);
147237
+ }
147238
+ await exitAfterFlush(0);
147239
+ }
147240
+ let malformedValuedFlag;
147241
+ const value = /* @__PURE__ */ __name((flag) => {
147242
+ let firstValue;
147243
+ for (let i = 0; i < args.length; i += 1) {
147244
+ if (args[i] !== flag) continue;
147245
+ const next = args[i + 1];
147246
+ if (!next || next.startsWith("-")) {
147247
+ malformedValuedFlag ??= flag;
147248
+ continue;
147249
+ }
147250
+ firstValue ??= next;
147251
+ }
147252
+ return firstValue;
147253
+ }, "value");
147254
+ const root = resolve41(value("--root") ?? process.cwd());
147255
+ const planPath = value("--plan");
147256
+ const approvalPath = value("--approval");
147257
+ const runPath = value("--run");
147258
+ const postpublishApprovalPaths = [];
147259
+ for (let i = 0; i < args.length; i += 1) {
147260
+ if (args[i] === "--hook-approval") {
147261
+ const next = args[i + 1];
147262
+ if (!next || next.startsWith("-")) {
147263
+ malformedValuedFlag ??= "--hook-approval";
147264
+ } else {
147265
+ postpublishApprovalPaths.push(resolve41(next));
147266
+ }
147267
+ }
147268
+ }
147269
+ if (malformedValuedFlag || !planPath || !approvalPath || !runPath) {
147270
+ const msg = "postverify requires --plan <path>, --approval <path>, and --run <path>";
147271
+ if (hasJson) {
147272
+ console.log(JSON.stringify({ error: MISSING_PARAMETERS, message: msg, exitCode: EXIT_CODE_MAP[MISSING_PARAMETERS] }));
147273
+ } else {
147274
+ console.error(`Error: ${msg}`);
147275
+ }
147276
+ await exitAfterFlush(EXIT_CODE_MAP[MISSING_PARAMETERS]);
147277
+ }
147278
+ try {
147279
+ const { postVerifyRelease: postVerifyRelease2 } = await init_postverify().then(() => postverify_exports);
147280
+ const result2 = await postVerifyRelease2({
147281
+ planPath: resolve41(planPath),
147282
+ approvalPath: resolve41(approvalPath),
147283
+ sourceRunPath: resolve41(runPath),
147284
+ root,
147285
+ ...postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}
147286
+ });
147287
+ if (hasJson) {
147288
+ console.log(JSON.stringify(result2, null, 2));
147289
+ } else {
147290
+ console.log(`PostVerify status: ${result2.status}`);
147291
+ for (const cp4 of result2.checkpoints ?? []) {
147292
+ console.log(` ${cp4.actionId}: ${cp4.status}`);
147293
+ }
147294
+ if (result2.runPath) console.log(`PostVerify run: ${result2.runPath}`);
147295
+ }
147296
+ await exitAfterFlush(result2.status === "DISTRIBUTED" ? 0 : 1);
147297
+ } catch (err) {
147298
+ if (hasJson) {
147299
+ console.log(JSON.stringify({
147300
+ error: err.code ?? "UNKNOWN_ERROR",
147301
+ message: err.message,
147302
+ details: err.details ?? {},
147303
+ exitCode: err.exitCode ?? 1
147304
+ }));
147305
+ } else {
147306
+ console.error(`Error: ${err.message}`);
147307
+ }
147308
+ await exitAfterFlush(err.exitCode ?? 1);
147309
+ }
147310
+ }
147079
147311
  if (command === "publish") {
147080
147312
  const rootIdx = args.indexOf("--root");
147081
147313
  const rawRoot = rootIdx !== -1 && args[rootIdx + 1] ? args[rootIdx + 1] : process.cwd();
@@ -19,7 +19,7 @@ description: "Discoverable entry point for release-skill: dependency and environ
19
19
  ## 职责
20
20
 
21
21
  - 依赖和环境检查:Node.js >= 22、Git 决定本地准备就绪度;npm/gh 另行决定生产依赖就绪度
22
- - 能力说明:缺少配置时走 `help → setup → assess`;已有配置的安全默认路径是 `help → assess → prepare --offline`;日常生产发布优先使用可恢复的 `ship`,兼容的分阶段闭环仍是 `prepare --online --production → approve → publish → verify`。冻结计划批准是正常发布级流程的唯一批准门;声明 `requiresApproval: true` 的 postPublish hook 仍须等待独立 checkpoint 批准。若计划声明 `postVerify` hook,本机收尾必须等待 `ship` 产出的完成 postVerify run,不能把仅有 `VERIFIED` 的 verify run 当作本机收尾授权。核心发布流程跨平台,WorkBuddy 本机收尾仅支持 macOS
22
+ - 能力说明:缺少配置时走 `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
23
23
  - 最小示例:展示从 release-help 到 release-assess 的最短路径
24
24
  - 只读诊断:运行 dry-run 检查,不修改任何文件
25
25
  - 故障引导:根据错误码指向对应的修复 Skill
@@ -58,6 +58,10 @@ node "$RELEASE_SKILL_ENTRY" ship --root <path> --target-version <version> --json
58
58
  # 多发布单元项目显式选择本轮范围;未传 --unit 时仍为全部单元
59
59
  node "$RELEASE_SKILL_ENTRY" ship --root <path> \
60
60
  --target-version <version> --unit <unit-a> --unit <unit-b> --json
61
+ # 对已经 VERIFIED 的计划独立执行 postVerify hook;不读取或写入 ship state
62
+ node "$RELEASE_SKILL_ENTRY" postverify --root <path> \
63
+ --plan <plan-path> --approval <approval-path> --run <verified-run-path> \
64
+ --hook-approval <immutable-hook-approval-path> --json
61
65
  # 开发阶段执行声明 hooks 并生成 prepare 可复用的内容绑定收据
62
66
  # 配置时刻即授权(FM-16 处置 A):hook 是任意本地进程、无隔离、触发前无确认点,
63
67
  # 命令调用本身即授权执行配置中的 hooks
@@ -22,6 +22,11 @@ verify 是发布流程的最终验证阶段,是唯一能将状态提升到 `VE
22
22
  它执行远端状态重检、精确 npm 安装烟雾测试和消费者插件安装验证。
23
23
  verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再次派生运行。
24
24
 
25
+ 计划声明 `postVerify` hook 时,`postverify` 命令负责执行独立的收尾阶段。它接收
26
+ `VERIFIED` verify run、同一计划的 approval,以及逐个传入的 checkpoint approval,
27
+ 然后原样调用现有 `postVerifyRelease`。该命令产生独立的 `postverify` run,不读取或写入
28
+ ship state;hook approval 缺失、错误或过期时,在 hook 执行前失败关闭。
29
+
25
30
  **注意**: distribute gate (W1) 已经实现并集成在标准 verify 流程中。verify 现在会检查 postPublish 分发状态(git mirror + marketplace index),只有当所有外部动作都完成并通过验证时才达到 VERIFIED。
26
31
 
27
32
  **工作流兼容性**:
@@ -52,13 +57,17 @@ verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再
52
57
  2. 使用插件根相对路径运行 CLI;命令调用本身即授权执行已配置的 verification gate 和 smoke process
53
58
  3. 检查 exit code 和结构化状态:`VERIFIED`(全部通过)/ 失败(具体错误)
54
59
  4. 只有 `VERIFIED` 才是发布 happy end
55
- 5. 达到 `VERIFIED` 后先检查计划是否声明 `postVerify` hook:有未完成 hook 时,先按 approval 合同批准并通过 `ship` 完成 postVerify,再把最终 `DISTRIBUTED` postVerify run 路径交给 `release-finish`;没有 postVerify hook 时,才把当前 verify run 路径交给 `release-finish`。随后按清单主动询问分支合并和本机宿主插件更新;发布策略已包含分支动作时略过合并询问
60
+ 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`。随后按清单主动询问分支合并和本机宿主插件更新;发布策略已包含分支动作时略过合并询问
56
61
 
57
62
  ## 确定性脚本调用
58
63
 
59
64
  ```bash
60
65
  # 从插件根运行
61
66
  node "$RELEASE_SKILL_ENTRY" verify --root <path> --plan <plan-path> --run <run-path> --json
67
+ # 独立执行已 VERIFIED 计划的 postVerify hook;每个 requiresApproval hook 单独传入批准记录
68
+ node "$RELEASE_SKILL_ENTRY" postverify --root <path> \
69
+ --plan <plan-path> --approval <approval-path> --run <verified-run-path> \
70
+ --hook-approval <immutable-hook-approval-path> --json
62
71
  ```
63
72
 
64
73
  ## 验证步骤
@@ -70,6 +79,9 @@ node "$RELEASE_SKILL_ENTRY" verify --root <path> --plan <plan-path> --run <run-p
70
79
  5. 对 Claude/Codex marketplace distribution 执行全新隔离消费者安装验证;收集 Kimi/CodeBuddy 的非阻塞 `manualFollowUps`
71
80
  6. 全部通过 → `VERIFIED`
72
81
 
82
+ `postverify` 不属于上述远端验证步骤。它只处理已经达到 `VERIFIED` 的计划,并把
83
+ `postVerify` 阶段的执行结果写入独立 run。
84
+
73
85
  ## 发布后收尾
74
86
 
75
87
  `VERIFIED` 不代表要自动修改开发分支或本机宿主。进入 `release-finish` 后,只有用户明确同意,才执行对应的本地动作。本机插件更新失败不会改变发布终态。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
5
5
  "author": {
6
6
  "name": "广州市风荷科技有限公司"