release-skill 0.9.4 → 0.9.5

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 (58) 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 +29 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +20 -16
  10. package/README.zh-CN.md +19 -16
  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 +866 -166
  14. package/adapters/claude/schemas/release-plan.schema.json +36 -0
  15. package/adapters/claude/schemas/release-project.schema.json +31 -0
  16. package/adapters/claude/skills/release-finish/SKILL.md +5 -5
  17. package/adapters/claude/skills/release-help/SKILL.md +3 -3
  18. package/adapters/claude/skills/release-verify/SKILL.md +2 -1
  19. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  20. package/adapters/codex/bin/release-skill.bundle.mjs +866 -166
  21. package/adapters/codex/schemas/release-plan.schema.json +36 -0
  22. package/adapters/codex/schemas/release-project.schema.json +31 -0
  23. package/adapters/codex/skills/release-finish/SKILL.md +5 -5
  24. package/adapters/codex/skills/release-help/SKILL.md +3 -3
  25. package/adapters/codex/skills/release-verify/SKILL.md +2 -1
  26. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  27. package/adapters/kimi/bin/release-skill.bundle.mjs +866 -166
  28. package/adapters/kimi/schemas/release-plan.schema.json +36 -0
  29. package/adapters/kimi/schemas/release-project.schema.json +31 -0
  30. package/adapters/kimi/skills/release-finish/SKILL.md +5 -5
  31. package/adapters/kimi/skills/release-help/SKILL.md +3 -3
  32. package/adapters/kimi/skills/release-verify/SKILL.md +2 -1
  33. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  34. package/adapters/workbuddy/bin/release-skill.bundle.mjs +866 -166
  35. package/adapters/workbuddy/schemas/release-plan.schema.json +36 -0
  36. package/adapters/workbuddy/schemas/release-project.schema.json +31 -0
  37. package/adapters/workbuddy/skills/release-finish/SKILL.md +5 -5
  38. package/adapters/workbuddy/skills/release-help/SKILL.md +3 -3
  39. package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -1
  40. package/bin/release-skill-cli.mjs +87 -20
  41. package/bin/release-skill.bundle.mjs +866 -166
  42. package/package.json +1 -1
  43. package/platform-manifest.json +4 -4
  44. package/schemas/release-plan.schema.json +36 -0
  45. package/schemas/release-project.schema.json +31 -0
  46. package/skills/release-finish/SKILL.md +5 -5
  47. package/skills/release-help/SKILL.md +3 -3
  48. package/skills/release-verify/SKILL.md +2 -1
  49. package/skills-src/release-finish/SKILL.md +5 -5
  50. package/skills-src/release-help/SKILL.md +3 -3
  51. package/skills-src/release-verify/SKILL.md +2 -1
  52. package/src/commands/post-release-local.mjs +204 -10
  53. package/src/commands/prepare.mjs +178 -9
  54. package/src/commands/ship.mjs +26 -11
  55. package/src/commands/verify.mjs +175 -2
  56. package/src/core/plan.mjs +45 -1
  57. package/src/platforms/registry.mjs +97 -0
  58. package/src/readme/contract.mjs +23 -3
@@ -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.4"});
12
+ const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.5"});
13
13
  // Build-time source digest for the BUNDLE_STALE freshness gate (see above).
14
- const __bundleSourceDigest = "f9cf3c6e981293ac4d26c870433f0d8cad33e8a838e48d0d9c027f2cefe8bec6";
14
+ const __bundleSourceDigest = "cbcc967eb1306a8f2e88e404a9ffbdb8bfaf0ffb8863fe8c5521cbb4d44346c1";
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -19972,8 +19972,8 @@ var require_graceful_fs = __commonJS({
19972
19972
  fs2.createReadStream = createReadStream2;
19973
19973
  fs2.createWriteStream = createWriteStream;
19974
19974
  var fs$readFile = fs2.readFile;
19975
- fs2.readFile = readFile56;
19976
- function readFile56(path40, options, cb) {
19975
+ fs2.readFile = readFile57;
19976
+ function readFile57(path40, options, cb) {
19977
19977
  if (typeof options === "function")
19978
19978
  cb = options, options = null;
19979
19979
  return go$readFile(path40, options, cb);
@@ -19989,7 +19989,7 @@ var require_graceful_fs = __commonJS({
19989
19989
  }
19990
19990
  __name(go$readFile, "go$readFile");
19991
19991
  }
19992
- __name(readFile56, "readFile");
19992
+ __name(readFile57, "readFile");
19993
19993
  var fs$writeFile = fs2.writeFile;
19994
19994
  fs2.writeFile = writeFile16;
19995
19995
  function writeFile16(path40, data, options, cb) {
@@ -41768,6 +41768,91 @@ function getPlatform(id) {
41768
41768
  if (!platform) throw new Error(`unknown platform: ${id}`);
41769
41769
  return platform;
41770
41770
  }
41771
+ function standaloneIdentityCore(platformOrId, fields) {
41772
+ const platform = typeof platformOrId === "string" ? getPlatform(platformOrId) : platformOrId;
41773
+ const id = platform?.id;
41774
+ if (id !== "claude" && id !== "codex") {
41775
+ throw new Error(`standalone-index install identity is unsupported for platform "${id ?? "<unknown>"}"`);
41776
+ }
41777
+ const { name, source, version } = fields;
41778
+ if (typeof name !== "string" || name.length === 0 || !source || typeof source !== "object" || Array.isArray(source) || typeof source.source !== "string" || typeof source.ref !== "string" || source.ref.length === 0 || typeof source.sha !== "string" || !STANDALONE_SHA_RE.test(source.sha)) {
41779
+ throw new Error(`${id} standalone-index install identity has invalid owned fields`);
41780
+ }
41781
+ if (id === "claude") {
41782
+ if (source.source !== "github" || typeof source.repo !== "string" || source.repo.length === 0 || typeof version !== "string" || version.length === 0) {
41783
+ throw new Error("Claude standalone-index install identity requires github repo, ref, sha, and version");
41784
+ }
41785
+ return {
41786
+ name,
41787
+ source: { source: "github", repo: source.repo, ref: source.ref, sha: source.sha },
41788
+ version
41789
+ };
41790
+ }
41791
+ if (source.source !== "url" || typeof source.url !== "string" || source.url.length === 0) {
41792
+ throw new Error("Codex standalone-index install identity requires url, ref, and sha");
41793
+ }
41794
+ return {
41795
+ name,
41796
+ source: { source: "url", url: source.url, ref: source.ref, sha: source.sha }
41797
+ };
41798
+ }
41799
+ function buildExpectedStandaloneIndexInstallIdentity(platformOrId, frozenIdentity = {}) {
41800
+ const platform = typeof platformOrId === "string" ? getPlatform(platformOrId) : platformOrId;
41801
+ const id = platform?.id;
41802
+ const { name, repo, tag, sha, version } = frozenIdentity;
41803
+ if (typeof repo !== "string" || repo.length === 0 || typeof tag !== "string" || tag.length === 0) {
41804
+ throw new Error("standalone-index expected identity requires a plugin repository and tag");
41805
+ }
41806
+ if (id === "claude") {
41807
+ return standaloneIdentityCore(platform, {
41808
+ name,
41809
+ version,
41810
+ source: { source: "github", repo, ref: tag, sha }
41811
+ });
41812
+ }
41813
+ if (id === "codex") {
41814
+ return standaloneIdentityCore(platform, {
41815
+ name,
41816
+ source: { source: "url", url: `https://github.com/${repo}.git`, ref: `refs/tags/${tag}`, sha }
41817
+ });
41818
+ }
41819
+ return standaloneIdentityCore(platform, { name, source: {}, version });
41820
+ }
41821
+ function projectObservedStandaloneIndexInstallIdentity(platformOrId, entry) {
41822
+ const platform = typeof platformOrId === "string" ? getPlatform(platformOrId) : platformOrId;
41823
+ const id = platform?.id;
41824
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
41825
+ throw new Error("standalone-index observed identity requires an entry object");
41826
+ }
41827
+ const source = entry.source;
41828
+ if (!source || typeof source !== "object" || Array.isArray(source)) {
41829
+ throw new Error("standalone-index observed identity requires a source object");
41830
+ }
41831
+ if (id === "claude") {
41832
+ return standaloneIdentityCore(platform, {
41833
+ name: entry.name,
41834
+ version: entry.version,
41835
+ source: {
41836
+ source: source.source,
41837
+ repo: source.repo,
41838
+ ref: source.ref,
41839
+ sha: source.sha
41840
+ }
41841
+ });
41842
+ }
41843
+ if (id === "codex") {
41844
+ return standaloneIdentityCore(platform, {
41845
+ name: entry.name,
41846
+ source: {
41847
+ source: source.source,
41848
+ url: source.url,
41849
+ ref: source.ref,
41850
+ sha: source.sha
41851
+ }
41852
+ });
41853
+ }
41854
+ return standaloneIdentityCore(platform, { name: entry.name, source });
41855
+ }
41771
41856
  function resolveSkillProjectionSurfaceHost(surfaceId, registry = PLATFORMS) {
41772
41857
  const platform = registry.find((item) => item.skillProjectionSurface === surfaceId);
41773
41858
  return platform?.buildAdapter?.name ?? null;
@@ -41982,7 +42067,7 @@ async function normalizeHostId(hostId, { fallback = false } = {}) {
41982
42067
  throw err;
41983
42068
  }
41984
42069
  }
41985
- var CLAUDE, CODEX, KIMI, CODEBUDDY, PLATFORMS, VALID_DISTRIBUTION_TYPES, VALID_ACTION_TYPES, VALID_SOURCE_FORMS, VALID_MARKETPLACE_REF_FORMS, VALID_LIST_OUTPUTS, VALID_CLI_OUTPUTS, VALID_ENTRY_VERSION_BINDING, VALID_INSTALL_METHODS, VALID_REF_STRENGTHS, VALID_OUTPUT_PROTOCOLS, VALID_IDENTITY_EVIDENCE, VALID_DEGRADATION_POLICIES, SKILL_PROJECTION_SURFACE_PATTERN, cachedHostsRoot, normalizedHostIds;
42070
+ var CLAUDE, CODEX, KIMI, CODEBUDDY, PLATFORMS, VALID_DISTRIBUTION_TYPES, VALID_ACTION_TYPES, VALID_SOURCE_FORMS, VALID_MARKETPLACE_REF_FORMS, VALID_LIST_OUTPUTS, VALID_CLI_OUTPUTS, VALID_ENTRY_VERSION_BINDING, VALID_INSTALL_METHODS, VALID_REF_STRENGTHS, VALID_OUTPUT_PROTOCOLS, VALID_IDENTITY_EVIDENCE, VALID_DEGRADATION_POLICIES, SKILL_PROJECTION_SURFACE_PATTERN, STANDALONE_SHA_RE, cachedHostsRoot, normalizedHostIds;
41986
42071
  var init_registry2 = __esm({
41987
42072
  async "src/platforms/registry.mjs"() {
41988
42073
  init_src3();
@@ -42269,6 +42354,10 @@ var init_registry2 = __esm({
42269
42354
  VALID_DEGRADATION_POLICIES = /* @__PURE__ */ new Set(["block", "human-attestation", "human-attestation-with-fallback"]);
42270
42355
  SKILL_PROJECTION_SURFACE_PATTERN = /^platforms\/[a-z0-9]+(?:-[a-z0-9]+)*$/u;
42271
42356
  __name(getPlatform, "getPlatform");
42357
+ STANDALONE_SHA_RE = /^[0-9a-f]{40}$/u;
42358
+ __name(standaloneIdentityCore, "standaloneIdentityCore");
42359
+ __name(buildExpectedStandaloneIndexInstallIdentity, "buildExpectedStandaloneIndexInstallIdentity");
42360
+ __name(projectObservedStandaloneIndexInstallIdentity, "projectObservedStandaloneIndexInstallIdentity");
42272
42361
  __name(resolveSkillProjectionSurfaceHost, "resolveSkillProjectionSurfaceHost");
42273
42362
  __name(assertRegistry, "assertRegistry");
42274
42363
  assertRegistry();
@@ -43160,6 +43249,7 @@ function validatePlanActionCompleteness(plan, options = {}) {
43160
43249
  }
43161
43250
  if (production && externalMarketplace && dist.marketplaceSourceType === "standalone-index") {
43162
43251
  const params = action.parameters;
43252
+ const bootstrap = dist.firstReleaseBootstrap === "manual-index-checkpoint";
43163
43253
  if (params) {
43164
43254
  if (!params.marketplaceIndexPath) {
43165
43255
  failures.push(
@@ -43176,6 +43266,38 @@ function validatePlanActionCompleteness(plan, options = {}) {
43176
43266
  `unit "${unitId}", action "${action.id}": parameters.selectedEntry is required for production standalone-index`
43177
43267
  );
43178
43268
  }
43269
+ if (bootstrap) {
43270
+ if (params.firstReleaseBootstrap !== "manual-index-checkpoint") {
43271
+ failures.push(
43272
+ `unit "${unitId}", action "${action.id}": parameters.firstReleaseBootstrap must be manual-index-checkpoint for a bootstrap distribution`
43273
+ );
43274
+ }
43275
+ if (params.marketplaceIndexSha !== null) {
43276
+ failures.push(
43277
+ `unit "${unitId}", action "${action.id}": parameters.marketplaceIndexSha must remain null until the manual index checkpoint`
43278
+ );
43279
+ }
43280
+ let expectedEntry = null;
43281
+ try {
43282
+ expectedEntry = buildExpectedStandaloneIndexInstallIdentity(platform, {
43283
+ name: plugin,
43284
+ version: targetVersion,
43285
+ repo: publicRepo,
43286
+ tag: expectedTag,
43287
+ sha: frozen?.commit
43288
+ });
43289
+ } catch {
43290
+ }
43291
+ if (!expectedEntry || canonicalJson2(params.selectedEntry) !== canonicalJson2(expectedEntry)) {
43292
+ failures.push(
43293
+ `unit "${unitId}", action "${action.id}": bootstrap selectedEntry does not match the frozen plugin repository, tag, commit, and version`
43294
+ );
43295
+ }
43296
+ } else if (params.firstReleaseBootstrap !== void 0 || params.marketplaceIndexSha !== void 0) {
43297
+ failures.push(
43298
+ `unit "${unitId}", action "${action.id}": bootstrap-only fields are not allowed without firstReleaseBootstrap`
43299
+ );
43300
+ }
43179
43301
  }
43180
43302
  }
43181
43303
  if (!externalMarketplace && dist.marketplaceSourceType === "bundled-family") {
@@ -43205,6 +43327,14 @@ function validatePlanActionCompleteness(plan, options = {}) {
43205
43327
  _checkRequired(action, "expected.ref", action.expected?.ref, action.parameters?.ref, unitId, failures);
43206
43328
  _checkRequired(action, "expected.marketplaceLocation", action.expected?.marketplaceLocation, "external", unitId, failures);
43207
43329
  _checkRequired(action, "expected.marketplaceCommitSha", action.expected?.marketplaceCommitSha, action.parameters?.marketplaceCommitSha, unitId, failures);
43330
+ if (dist.firstReleaseBootstrap === "manual-index-checkpoint") {
43331
+ _checkRequired(action, "expected.firstReleaseBootstrap", action.expected?.firstReleaseBootstrap, "manual-index-checkpoint", unitId, failures);
43332
+ if (action.expected?.marketplaceIndexSha !== null) {
43333
+ failures.push(
43334
+ `unit "${unitId}", action "${action.id}": expected.marketplaceIndexSha must be null until the manual index checkpoint`
43335
+ );
43336
+ }
43337
+ }
43208
43338
  } else {
43209
43339
  _checkRequired(action, "expected.repo", action.expected?.repo, publicRepo, unitId, failures);
43210
43340
  _checkRequired(action, "expected.ref", action.expected?.ref, expectedTag, unitId, failures);
@@ -59992,7 +60122,7 @@ var require_entry_index = __commonJS({
59992
60122
  var {
59993
60123
  appendFile,
59994
60124
  mkdir: mkdir35,
59995
- readFile: readFile56,
60125
+ readFile: readFile57,
59996
60126
  readdir: readdir34,
59997
60127
  rm: rm19,
59998
60128
  writeFile: writeFile16
@@ -60184,7 +60314,7 @@ ${hashEntry(stringified)} ${stringified}`);
60184
60314
  __name(ls, "ls");
60185
60315
  module.exports.bucketEntries = bucketEntries;
60186
60316
  async function bucketEntries(bucket, filter) {
60187
- const data = await readFile56(bucket, "utf8");
60317
+ const data = await readFile57(bucket, "utf8");
60188
60318
  return _bucketEntries(data, filter);
60189
60319
  }
60190
60320
  __name(bucketEntries, "bucketEntries");
@@ -64494,7 +64624,7 @@ var require_verify = __commonJS({
64494
64624
  "use strict";
64495
64625
  var {
64496
64626
  mkdir: mkdir35,
64497
- readFile: readFile56,
64627
+ readFile: readFile57,
64498
64628
  rm: rm19,
64499
64629
  stat: stat22,
64500
64630
  truncate,
@@ -64721,7 +64851,7 @@ var require_verify = __commonJS({
64721
64851
  __name(writeVerifile, "writeVerifile");
64722
64852
  module.exports.lastRun = lastRun;
64723
64853
  async function lastRun(cache) {
64724
- const data = await readFile56(path40.join(cache, "_lastverified"), { encoding: "utf8" });
64854
+ const data = await readFile57(path40.join(cache, "_lastverified"), { encoding: "utf8" });
64725
64855
  return /* @__PURE__ */ new Date(+data);
64726
64856
  }
64727
64857
  __name(lastRun, "lastRun");
@@ -76643,11 +76773,11 @@ var require_normalize = __commonJS({
76643
76773
  // ../../node_modules/.pnpm/@npmcli+package-json@7.0.5/node_modules/@npmcli/package-json/lib/read-package.js
76644
76774
  var require_read_package = __commonJS({
76645
76775
  "../../node_modules/.pnpm/@npmcli+package-json@7.0.5/node_modules/@npmcli/package-json/lib/read-package.js"(exports, module) {
76646
- var { readFile: readFile56 } = __require("fs/promises");
76776
+ var { readFile: readFile57 } = __require("fs/promises");
76647
76777
  var parseJSON = require_lib33();
76648
76778
  async function read(filename) {
76649
76779
  try {
76650
- const data = await readFile56(filename, "utf8");
76780
+ const data = await readFile57(filename, "utf8");
76651
76781
  return data;
76652
76782
  } catch (err) {
76653
76783
  err.message = `Could not read package.json: ${err}`;
@@ -76780,7 +76910,7 @@ var require_sort2 = __commonJS({
76780
76910
  // ../../node_modules/.pnpm/@npmcli+package-json@7.0.5/node_modules/@npmcli/package-json/lib/index.js
76781
76911
  var require_lib40 = __commonJS({
76782
76912
  "../../node_modules/.pnpm/@npmcli+package-json@7.0.5/node_modules/@npmcli/package-json/lib/index.js"(exports, module) {
76783
- var { readFile: readFile56, writeFile: writeFile16 } = __require("node:fs/promises");
76913
+ var { readFile: readFile57, writeFile: writeFile16 } = __require("node:fs/promises");
76784
76914
  var { resolve: resolve42 } = __require("node:path");
76785
76915
  var parseJSON = require_lib33();
76786
76916
  var updateDeps = require_update_dependencies();
@@ -76906,7 +77036,7 @@ var require_lib40 = __commonJS({
76906
77036
  const indexFile = resolve42(this.path, "index.js");
76907
77037
  let indexFileContent;
76908
77038
  try {
76909
- indexFileContent = await readFile56(indexFile, "utf8");
77039
+ indexFileContent = await readFile57(indexFile, "utf8");
76910
77040
  } catch (err) {
76911
77041
  throw parseErr;
76912
77042
  }
@@ -89207,7 +89337,7 @@ var require_dist16 = __commonJS({
89207
89337
  var require_provenance = __commonJS({
89208
89338
  "../../node_modules/.pnpm/libnpmpublish@11.2.0/node_modules/libnpmpublish/lib/provenance.js"(exports, module) {
89209
89339
  var sigstore = require_dist16();
89210
- var { readFile: readFile56 } = __require("node:fs/promises");
89340
+ var { readFile: readFile57 } = __require("node:fs/promises");
89211
89341
  var ci = require_ci_info();
89212
89342
  var { env } = process;
89213
89343
  var INTOTO_PAYLOAD_TYPE = "application/vnd.in-toto+json";
@@ -89399,7 +89529,7 @@ var require_provenance = __commonJS({
89399
89529
  var verifyProvenance = /* @__PURE__ */ __name(async (subject, provenancePath) => {
89400
89530
  let provenanceBundle;
89401
89531
  try {
89402
- provenanceBundle = JSON.parse(await readFile56(provenancePath));
89532
+ provenanceBundle = JSON.parse(await readFile57(provenancePath));
89403
89533
  } catch (err) {
89404
89534
  err.message = `Invalid provenance provided: ${err.message}`;
89405
89535
  throw err;
@@ -99484,7 +99614,7 @@ var require_entry_index2 = __commonJS({
99484
99614
  var {
99485
99615
  appendFile,
99486
99616
  mkdir: mkdir35,
99487
- readFile: readFile56,
99617
+ readFile: readFile57,
99488
99618
  readdir: readdir34,
99489
99619
  rm: rm19,
99490
99620
  writeFile: writeFile16
@@ -99676,7 +99806,7 @@ ${hashEntry(stringified)} ${stringified}`);
99676
99806
  __name(ls, "ls");
99677
99807
  module.exports.bucketEntries = bucketEntries;
99678
99808
  async function bucketEntries(bucket, filter) {
99679
- const data = await readFile56(bucket, "utf8");
99809
+ const data = await readFile57(bucket, "utf8");
99680
99810
  return _bucketEntries(data, filter);
99681
99811
  }
99682
99812
  __name(bucketEntries, "bucketEntries");
@@ -105365,7 +105495,7 @@ var require_verify3 = __commonJS({
105365
105495
  "use strict";
105366
105496
  var {
105367
105497
  mkdir: mkdir35,
105368
- readFile: readFile56,
105498
+ readFile: readFile57,
105369
105499
  rm: rm19,
105370
105500
  stat: stat22,
105371
105501
  truncate,
@@ -105592,7 +105722,7 @@ var require_verify3 = __commonJS({
105592
105722
  __name(writeVerifile, "writeVerifile");
105593
105723
  module.exports.lastRun = lastRun;
105594
105724
  async function lastRun(cache) {
105595
- const data = await readFile56(path40.join(cache, "_lastverified"), { encoding: "utf8" });
105725
+ const data = await readFile57(path40.join(cache, "_lastverified"), { encoding: "utf8" });
105596
105726
  return /* @__PURE__ */ new Date(+data);
105597
105727
  }
105598
105728
  __name(lastRun, "lastRun");
@@ -112391,6 +112521,136 @@ async function verifyPublicSourceAuthorityReceiptOffline({ plan, root }) {
112391
112521
  function defaultClock() {
112392
112522
  return (/* @__PURE__ */ new Date()).toISOString();
112393
112523
  }
112524
+ function parseMarketplaceHead(stdout) {
112525
+ if (typeof stdout !== "string") return null;
112526
+ let sha = null;
112527
+ let defaultBranch = null;
112528
+ for (const line of stdout.trim().split("\n").filter(Boolean)) {
112529
+ const tab = line.indexOf(" ");
112530
+ if (tab < 0) continue;
112531
+ const left = line.slice(0, tab);
112532
+ const right = line.slice(tab + 1);
112533
+ if (right !== "HEAD") continue;
112534
+ if (left.startsWith("ref: refs/heads/")) defaultBranch = left.slice("ref: refs/heads/".length);
112535
+ else if (EXTERNAL_MARKETPLACE_SHA_RE2.test(left)) sha = left;
112536
+ }
112537
+ return sha && defaultBranch ? { sha, defaultBranch } : null;
112538
+ }
112539
+ async function defaultObserveMarketplaceHead(repo, { githubHost = "github.com" } = {}) {
112540
+ try {
112541
+ const { stdout } = await execFile6("git", [
112542
+ "ls-remote",
112543
+ "--symref",
112544
+ `https://${githubHost}/${repo}.git`,
112545
+ "HEAD"
112546
+ ], { shell: false, encoding: "utf8", timeout: 3e4 });
112547
+ const parsed = parseMarketplaceHead(stdout);
112548
+ return parsed ? { status: "observed", ...parsed } : { status: "unknown", error: "could not resolve marketplace HEAD" };
112549
+ } catch (error) {
112550
+ return { status: "unknown", error: error.message };
112551
+ }
112552
+ }
112553
+ function decodeMarketplaceIndex(content) {
112554
+ if (typeof content !== "string") return null;
112555
+ try {
112556
+ return JSON.parse(Buffer.from(content.replace(/\s/g, ""), "base64").toString("utf8"));
112557
+ } catch {
112558
+ return null;
112559
+ }
112560
+ }
112561
+ async function defaultFetchMarketplaceIndex(repo, path40, ref, { githubHost = "github.com" } = {}) {
112562
+ try {
112563
+ const { stdout } = await execFile6("gh", [
112564
+ "api",
112565
+ `repos/${repo}/contents/${path40}?ref=${ref}`,
112566
+ "--jq",
112567
+ ".content"
112568
+ ], {
112569
+ shell: false,
112570
+ encoding: "utf8",
112571
+ timeout: 3e4,
112572
+ env: { ...process.env, GH_HOST: githubHost }
112573
+ });
112574
+ const index = decodeMarketplaceIndex(stdout);
112575
+ return index && typeof index === "object" ? { status: "fetched", index } : { status: "unknown", error: "could not decode marketplace index" };
112576
+ } catch (error) {
112577
+ return { status: "unknown", error: error.message };
112578
+ }
112579
+ }
112580
+ async function verifyFirstReleaseBootstrapIndexes({
112581
+ actions,
112582
+ plan,
112583
+ evidence,
112584
+ observeHeadFn,
112585
+ fetchIndexFn
112586
+ }) {
112587
+ const bindings = /* @__PURE__ */ new Map();
112588
+ for (const action of actions) {
112589
+ if (action.parameters?.firstReleaseBootstrap !== FIRST_RELEASE_BOOTSTRAP_MODE) continue;
112590
+ const params = action.parameters;
112591
+ const githubHost = params.githubHost ?? "github.com";
112592
+ const failDeferred = /* @__PURE__ */ __name(async (reason, observed2 = null) => {
112593
+ await evidence.append({
112594
+ phase: "verify-marketplace-bootstrap-index",
112595
+ actionId: action.id,
112596
+ actionType: action.type,
112597
+ status: "needs-input",
112598
+ reason,
112599
+ ...observed2 ? { observed: observed2 } : {}
112600
+ });
112601
+ throw new ReleaseError(
112602
+ CONSUMER_VERIFICATION_DEFERRED,
112603
+ `first-release bootstrap marketplace index is not an exact match for action "${action.id}"; complete the manual index checkpoint and retry verify with the same plan`,
112604
+ { actionId: action.id, reason, observed: observed2, nextState: "NEEDS_MANUAL_ATTESTATIONS" }
112605
+ );
112606
+ }, "failDeferred");
112607
+ const observed = await observeHeadFn(params.repo, { githubHost });
112608
+ if (observed?.status !== "observed" || !EXTERNAL_MARKETPLACE_SHA_RE2.test(observed.sha ?? "") || !observed.defaultBranch) {
112609
+ await failDeferred(`could not observe current marketplace HEAD: ${observed?.error ?? "unknown"}`, observed);
112610
+ }
112611
+ const path40 = params.marketplaceIndexPath;
112612
+ const fetched = await fetchIndexFn(params.repo, path40, observed.sha, { githubHost });
112613
+ if (fetched?.status !== "fetched" || !fetched.index || typeof fetched.index !== "object") {
112614
+ await failDeferred(`could not read marketplace index at ${observed.sha}: ${fetched?.error ?? "unknown"}`, observed);
112615
+ }
112616
+ const index = fetched.index;
112617
+ if (index.name !== params.marketplaceName) {
112618
+ await failDeferred(`marketplace name mismatch: expected ${params.marketplaceName}, got ${index.name}`, { ...observed, name: index.name });
112619
+ }
112620
+ if (!Array.isArray(index.plugins)) {
112621
+ await failDeferred("marketplace index plugins must be an array", { ...observed, pluginsType: typeof index.plugins });
112622
+ }
112623
+ const entries = index.plugins.filter((entry) => entry && entry.name === params.plugin);
112624
+ let projectedEntry = null;
112625
+ if (entries.length === 1) {
112626
+ try {
112627
+ projectedEntry = projectObservedStandaloneIndexInstallIdentity(params.consumer, entries[0]);
112628
+ } catch {
112629
+ projectedEntry = null;
112630
+ }
112631
+ }
112632
+ if (entries.length !== 1 || !projectedEntry || canonicalJson2(projectedEntry) !== canonicalJson2(params.selectedEntry)) {
112633
+ await failDeferred("marketplace selectedEntry is missing, duplicated, or does not exactly match the plan-bound expected entry", {
112634
+ ...observed,
112635
+ entryCount: entries.length,
112636
+ selectedEntry: projectedEntry ?? entries[0] ?? null
112637
+ });
112638
+ }
112639
+ bindings.set(action.id, {
112640
+ marketplaceIndexSha: observed.sha,
112641
+ marketplaceIndexRef: action.type === "claude-marketplace-install" ? observed.defaultBranch : observed.sha
112642
+ });
112643
+ await evidence.append({
112644
+ phase: "verify-marketplace-bootstrap-index",
112645
+ actionId: action.id,
112646
+ actionType: action.type,
112647
+ status: "observed",
112648
+ marketplaceIndexSha: observed.sha,
112649
+ marketplaceIndexRef: action.type === "claude-marketplace-install" ? observed.defaultBranch : observed.sha
112650
+ });
112651
+ }
112652
+ return bindings;
112653
+ }
112394
112654
  function isValidDigest2(digest3) {
112395
112655
  return typeof digest3 === "string" && /^[a-f0-9]{64}$/.test(digest3);
112396
112656
  }
@@ -112925,7 +113185,9 @@ async function verifyRelease(options) {
112925
113185
  previousVerifyRun,
112926
113186
  execFn,
112927
113187
  configPath: configPathOpt,
112928
- runPluginVerificationFn
113188
+ runPluginVerificationFn,
113189
+ observeFirstReleaseMarketplaceHeadFn,
113190
+ fetchFirstReleaseMarketplaceIndexFn
112929
113191
  } = options ?? {};
112930
113192
  const clockFn = typeof clockOpt === "function" ? clockOpt : defaultClock;
112931
113193
  if (!sourceRunPath) {
@@ -113139,6 +113401,13 @@ async function verifyRelease(options) {
113139
113401
  const consumerGateResults = [];
113140
113402
  const consumerVerificationReceipts = [];
113141
113403
  const actions = plan.externalActions ?? [];
113404
+ const bootstrapIndexBindings = await verifyFirstReleaseBootstrapIndexes({
113405
+ actions,
113406
+ plan,
113407
+ evidence,
113408
+ observeHeadFn: observeFirstReleaseMarketplaceHeadFn ?? defaultObserveMarketplaceHead,
113409
+ fetchIndexFn: fetchFirstReleaseMarketplaceIndexFn ?? defaultFetchMarketplaceIndex
113410
+ });
113142
113411
  const trustedVerifyRuns = [];
113143
113412
  const planDir = dirname10(planPath);
113144
113413
  const releaseDir = basename9(planDir) === "plans" ? dirname10(planDir) : planDir;
@@ -113335,10 +113604,21 @@ async function verifyRelease(options) {
113335
113604
  root,
113336
113605
  runDir
113337
113606
  };
113607
+ const bootstrapBinding = bootstrapIndexBindings.get(action.id) ?? null;
113338
113608
  const actionInput = {
113339
113609
  actionType: adapterActionType,
113340
113610
  ...action.parameters
113341
113611
  };
113612
+ if (bootstrapBinding) {
113613
+ actionInput.marketplaceCommitSha = bootstrapBinding.marketplaceIndexSha;
113614
+ actionInput.marketplaceIndexSha = bootstrapBinding.marketplaceIndexSha;
113615
+ actionInput.ref = bootstrapBinding.marketplaceIndexRef;
113616
+ actionInput.sourceDescriptor = {
113617
+ ...actionInput.sourceDescriptor,
113618
+ marketplaceCommitSha: bootstrapBinding.marketplaceIndexSha,
113619
+ ref: bootstrapBinding.marketplaceIndexRef
113620
+ };
113621
+ }
113342
113622
  const unit = (plan.units ?? []).find((u) => u.id === action.unitId);
113343
113623
  const typeToDist = {
113344
113624
  "claude-marketplace-install": "claude-plugin",
@@ -113392,6 +113672,7 @@ async function verifyRelease(options) {
113392
113672
  actionType: action.type,
113393
113673
  status: VERIFICATION_RESOLVED_TYPES.NOT_REQUIRED_UNCHANGED,
113394
113674
  installationContractDigest: currentDigest,
113675
+ ...bootstrapBinding ? { marketplaceIndexSha: bootstrapBinding.marketplaceIndexSha, marketplaceIndexRef: bootstrapBinding.marketplaceIndexRef } : {},
113395
113676
  reason: "\u5B89\u88C5\u5951\u7EA6\u6458\u8981\u672A\u53D8\u5316\uFF0C\u8DF3\u8FC7\u9A8C\u8BC1"
113396
113677
  });
113397
113678
  consumerVerificationReceipts.push({
@@ -113409,7 +113690,8 @@ async function verifyRelease(options) {
113409
113690
  actionId: action.id,
113410
113691
  actionType: action.type,
113411
113692
  status: VERIFICATION_RESOLVED_TYPES.NOT_REQUIRED_UNCHANGED,
113412
- installationContractDigest: currentDigest
113693
+ installationContractDigest: currentDigest,
113694
+ ...bootstrapBinding ? { marketplaceIndexSha: bootstrapBinding.marketplaceIndexSha, marketplaceIndexRef: bootstrapBinding.marketplaceIndexRef } : {}
113413
113695
  });
113414
113696
  return;
113415
113697
  }
@@ -113428,8 +113710,14 @@ async function verifyRelease(options) {
113428
113710
  { actionId: action.id }
113429
113711
  );
113430
113712
  }
113713
+ const expectedInput = bootstrapBinding ? {
113714
+ ...action.expected,
113715
+ marketplaceCommitSha: bootstrapBinding.marketplaceIndexSha,
113716
+ marketplaceIndexSha: bootstrapBinding.marketplaceIndexSha,
113717
+ ref: bootstrapBinding.marketplaceIndexRef
113718
+ } : action.expected;
113431
113719
  const verifyResult = await adapter.verify(
113432
- { ...actionInput, expected: action.expected },
113720
+ { ...actionInput, expected: expectedInput },
113433
113721
  marketplaceContext
113434
113722
  );
113435
113723
  const isHumanConfirmed = verifyResult.observation?.humanConfirmed === true;
@@ -113440,6 +113728,7 @@ async function verifyRelease(options) {
113440
113728
  status: resolvedStatus,
113441
113729
  observation: verifyResult.observation,
113442
113730
  error: verifyResult.error,
113731
+ ...bootstrapBinding ? { marketplaceIndexSha: bootstrapBinding.marketplaceIndexSha, marketplaceIndexRef: bootstrapBinding.marketplaceIndexRef } : {},
113443
113732
  ...dist?.installationContractDigest ? { installationContractDigest: dist.installationContractDigest } : {}
113444
113733
  };
113445
113734
  adapterChecks.push(check);
@@ -113459,7 +113748,8 @@ async function verifyRelease(options) {
113459
113748
  phase: "verify-marketplace",
113460
113749
  actionId: action.id,
113461
113750
  actionType: action.type,
113462
- status: check.status
113751
+ status: check.status,
113752
+ ...bootstrapBinding ? { marketplaceIndexSha: bootstrapBinding.marketplaceIndexSha, marketplaceIndexRef: bootstrapBinding.marketplaceIndexRef } : {}
113463
113753
  });
113464
113754
  if (check.status === "FAILED") {
113465
113755
  throw new ReleaseError(
@@ -113899,7 +114189,7 @@ async function verifyRelease(options) {
113899
114189
  throw err;
113900
114190
  }
113901
114191
  }
113902
- var execFile6, VERIFICATION_RESOLVED_TYPES, ADAPTER_ACTION_TYPE_MAP2, defaultNpmExecutor;
114192
+ var execFile6, VERIFICATION_RESOLVED_TYPES, ADAPTER_ACTION_TYPE_MAP2, FIRST_RELEASE_BOOTSTRAP_MODE, EXTERNAL_MARKETPLACE_SHA_RE2, defaultNpmExecutor;
113903
114193
  var init_verify = __esm({
113904
114194
  async "src/commands/verify.mjs"() {
113905
114195
  init_src2();
@@ -113918,6 +114208,7 @@ var init_verify = __esm({
113918
114208
  init_frozen_marker();
113919
114209
  await init_foundation_plugin_verification();
113920
114210
  init_public_path();
114211
+ init_digest();
113921
114212
  init_npm();
113922
114213
  init_verification_gates();
113923
114214
  await init_skill_resource_closure();
@@ -113948,6 +114239,13 @@ var init_verify = __esm({
113948
114239
  "codebuddy-marketplace-install": "codebuddy-marketplace-install"
113949
114240
  };
113950
114241
  __name(defaultClock, "defaultClock");
114242
+ FIRST_RELEASE_BOOTSTRAP_MODE = "manual-index-checkpoint";
114243
+ EXTERNAL_MARKETPLACE_SHA_RE2 = /^[0-9a-f]{40}$/;
114244
+ __name(parseMarketplaceHead, "parseMarketplaceHead");
114245
+ __name(defaultObserveMarketplaceHead, "defaultObserveMarketplaceHead");
114246
+ __name(decodeMarketplaceIndex, "decodeMarketplaceIndex");
114247
+ __name(defaultFetchMarketplaceIndex, "defaultFetchMarketplaceIndex");
114248
+ __name(verifyFirstReleaseBootstrapIndexes, "verifyFirstReleaseBootstrapIndexes");
113951
114249
  __name(isValidDigest2, "isValidDigest");
113952
114250
  __name(collectMissingManualAttestations, "collectMissingManualAttestations");
113953
114251
  __name(matchesSubset2, "matchesSubset");
@@ -121372,6 +121670,17 @@ function extractManifestSkillNames(skills) {
121372
121670
  function findSkillNames(content, manifestSkillNames) {
121373
121671
  return manifestSkillNames.filter((name) => content.includes(name));
121374
121672
  }
121673
+ function hasInstallCommand(content) {
121674
+ if (/npm\s+install|npx\s+release-skill|npm\s+i\s+release-skill/i.test(content)) {
121675
+ return true;
121676
+ }
121677
+ const codeBlocks = [
121678
+ ...[...content.matchAll(/```[^\n]*\n([\s\S]*?)```/g)].map((match) => match[1]),
121679
+ ...[...content.matchAll(/`([^`\n]+)`/g)].map((match) => match[1])
121680
+ ];
121681
+ const pluginInstall = /^(?:[$>]\s*)?(?:codex\s+plugin\s+add|claude\s+plugin\s+install)\s+\S+/i;
121682
+ return codeBlocks.some((block) => block.split("\n").some((line) => pluginInstall.test(line.trim())));
121683
+ }
121375
121684
  async function evaluateReadme({ snapshotDir, pluginManifest }) {
121376
121685
  const findings = [];
121377
121686
  const enPath = path39.join(snapshotDir, "README.md");
@@ -121412,7 +121721,7 @@ async function evaluateReadme({ snapshotDir, pluginManifest }) {
121412
121721
  safeFirstCommand: enMarkers.has("safe-first-command")
121413
121722
  };
121414
121723
  const readabilityChecks = {
121415
- hasInstall: enContent ? /npm\s+install|npx\s+release-skill|npm\s+i\s+release-skill/i.test(enContent) : false,
121724
+ hasInstall: enContent ? hasInstallCommand(enContent) : false,
121416
121725
  hasMinimalExample: enContent ? /```[\s\S]*?(release-skill|assess|prepare|help)[\s\S]*?```/i.test(enContent) : false,
121417
121726
  hasFailureDiagnosis: enContent ? /CONFIG_INVALID|PARTIAL|GATE_FAILED|if.*fail|如果.*失败|故障|troubleshoot/i.test(enContent) : false,
121418
121727
  hasNextSteps: enContent ? /next.*step|下一步|后续|see.*also|参阅|进一步/i.test(enContent) : false
@@ -121472,6 +121781,7 @@ var init_contract2 = __esm({
121472
121781
  __name(resolveSkillName, "resolveSkillName");
121473
121782
  __name(extractManifestSkillNames, "extractManifestSkillNames");
121474
121783
  __name(findSkillNames, "findSkillNames");
121784
+ __name(hasInstallCommand, "hasInstallCommand");
121475
121785
  __name(evaluateReadme, "evaluateReadme");
121476
121786
  }
121477
121787
  });
@@ -127335,6 +127645,7 @@ __export(prepare_exports, {
127335
127645
  decodeExternalMarketplaceIndex: () => decodeExternalMarketplaceIndex,
127336
127646
  observeOriginAhead: () => observeOriginAhead,
127337
127647
  parseExternalMarketplaceLsRemote: () => parseExternalMarketplaceLsRemote,
127648
+ parseFirstReleasePluginRepositoryRefs: () => parseFirstReleasePluginRepositoryRefs,
127338
127649
  prepareRelease: () => prepareRelease,
127339
127650
  readHeadCommitTimestamp: () => readHeadCommitTimestamp,
127340
127651
  readLatestFrozenPlan: () => readLatestFrozenPlan,
@@ -128204,7 +128515,7 @@ function parseExternalMarketplaceLsRemote(stdout) {
128204
128515
  if (right !== "HEAD") continue;
128205
128516
  if (left.startsWith("ref: refs/heads/")) {
128206
128517
  defaultBranch = left.slice("ref: refs/heads/".length);
128207
- } else if (EXTERNAL_MARKETPLACE_SHA_RE2.test(left)) {
128518
+ } else if (EXTERNAL_MARKETPLACE_SHA_RE3.test(left)) {
128208
128519
  sha = left;
128209
128520
  }
128210
128521
  }
@@ -128259,13 +128570,74 @@ async function defaultFetchExternalMarketplaceIndex(repo, manifestPath, ref, { g
128259
128570
  return { status: "unknown", error: error.message };
128260
128571
  }
128261
128572
  }
128573
+ function parseFirstReleasePluginRepositoryRefs(stdout, tag) {
128574
+ if (typeof stdout !== "string" || typeof tag !== "string" || tag.length === 0) {
128575
+ return { hasRefs: false, tagExists: false };
128576
+ }
128577
+ const refs = [];
128578
+ let hasRefs = false;
128579
+ for (const line of stdout.trim().split("\n").filter(Boolean)) {
128580
+ const tabIndex = line.indexOf(" ");
128581
+ if (tabIndex < 0) continue;
128582
+ const sha = line.slice(0, tabIndex);
128583
+ const ref = line.slice(tabIndex + 1);
128584
+ if (EXTERNAL_MARKETPLACE_SHA_RE3.test(sha)) {
128585
+ hasRefs = true;
128586
+ if (ref && ref !== "HEAD") refs.push(ref);
128587
+ }
128588
+ }
128589
+ const target = `refs/tags/${tag}`;
128590
+ return {
128591
+ hasRefs,
128592
+ tagExists: refs.includes(target) || refs.includes(`${target}^{}`)
128593
+ };
128594
+ }
128595
+ async function defaultObserveFirstReleasePluginRepository(repo, tag, { githubHost = "github.com" } = {}) {
128596
+ const url = `https://${githubHost}/${repo}.git`;
128597
+ let repositoryRefs;
128598
+ try {
128599
+ const { stdout } = await execFile9(
128600
+ "git",
128601
+ ["ls-remote", "--symref", url],
128602
+ { shell: false, encoding: "utf8", timeout: 3e4 }
128603
+ );
128604
+ repositoryRefs = parseFirstReleasePluginRepositoryRefs(String(stdout ?? ""), tag);
128605
+ } catch (error) {
128606
+ return { status: "unknown", error: error.message };
128607
+ }
128608
+ let releaseExists = false;
128609
+ try {
128610
+ await execFile9(
128611
+ "gh",
128612
+ ["api", `repos/${repo}/releases/tags/${tag}`],
128613
+ {
128614
+ shell: false,
128615
+ encoding: "utf8",
128616
+ timeout: 3e4,
128617
+ env: { ...process.env, GH_HOST: githubHost }
128618
+ }
128619
+ );
128620
+ releaseExists = true;
128621
+ } catch (error) {
128622
+ const diagnostic = `${error?.stderr ?? ""} ${error?.message ?? ""}`;
128623
+ if (!/\b404\b|not found/i.test(diagnostic)) {
128624
+ return { status: "unknown", error: error.message };
128625
+ }
128626
+ }
128627
+ return { status: repositoryRefs.hasRefs ? "non-empty" : "empty", tagExists: repositoryRefs.tagExists, releaseExists };
128628
+ }
128629
+ function isFirstReleaseBootstrap(dist) {
128630
+ return dist?.firstReleaseBootstrap === FIRST_RELEASE_BOOTSTRAP_MODE2;
128631
+ }
128262
128632
  async function resolveExternalMarketplaceFreezes({
128263
128633
  unitResults,
128264
128634
  resolvedVersions,
128265
128635
  offline,
128266
128636
  evidence,
128267
128637
  observeHeadFn,
128268
- fetchIndexFn
128638
+ fetchIndexFn,
128639
+ productionAssets = null,
128640
+ observePluginRepositoryFn = defaultObserveFirstReleasePluginRepository
128269
128641
  }) {
128270
128642
  const freezes = /* @__PURE__ */ new Map();
128271
128643
  for (let index = 0; index < unitResults.length; index += 1) {
@@ -128273,8 +128645,24 @@ async function resolveExternalMarketplaceFreezes({
128273
128645
  const version = resolvedVersions[index];
128274
128646
  const githubHost = unit.production?.githubHost ?? "github.com";
128275
128647
  for (const dist of unit.distributions ?? []) {
128648
+ if (isFirstReleaseBootstrap(dist) && dist.marketplaceSourceType !== "standalone-index") {
128649
+ throw new ReleaseError(
128650
+ GATE_FAILED,
128651
+ `unit "${unit.id}" ${dist.type} first-release bootstrap requires marketplaceSourceType standalone-index`,
128652
+ { unitId: unit.id, distributionType: dist.type }
128653
+ );
128654
+ }
128276
128655
  if (dist.marketplaceSourceType !== "standalone-index") continue;
128277
- if (!dist.marketplaceRepo) continue;
128656
+ if (!dist.marketplaceRepo) {
128657
+ if (isFirstReleaseBootstrap(dist)) {
128658
+ throw new ReleaseError(
128659
+ GATE_FAILED,
128660
+ `unit "${unit.id}" ${dist.type} first-release bootstrap requires marketplaceRepo`,
128661
+ { unitId: unit.id, distributionType: dist.type }
128662
+ );
128663
+ }
128664
+ continue;
128665
+ }
128278
128666
  const platform = PLATFORMS.find((p) => p.distributionType === dist.type);
128279
128667
  if (!platform) {
128280
128668
  throw new ReleaseError(
@@ -128290,8 +128678,15 @@ async function resolveExternalMarketplaceFreezes({
128290
128678
  { unitId: unit.id, marketplaceSourceType: dist.marketplaceSourceType }
128291
128679
  );
128292
128680
  }
128681
+ if (isFirstReleaseBootstrap(dist) && dist.type !== "claude-plugin" && dist.type !== "codex-plugin") {
128682
+ throw new ReleaseError(
128683
+ GATE_FAILED,
128684
+ `unit "${unit.id}" ${dist.type} first-release bootstrap is supported only for claude-plugin or codex-plugin standalone-index distributions`,
128685
+ { unitId: unit.id, distributionType: dist.type }
128686
+ );
128687
+ }
128293
128688
  const observed = await observeHeadFn(dist.marketplaceRepo, { githubHost });
128294
- if (observed.status !== "observed" || !EXTERNAL_MARKETPLACE_SHA_RE2.test(observed.sha ?? "") || !observed.defaultBranch) {
128689
+ if (observed.status !== "observed" || !EXTERNAL_MARKETPLACE_SHA_RE3.test(observed.sha ?? "") || !observed.defaultBranch) {
128295
128690
  throw new ReleaseError(
128296
128691
  GATE_FAILED,
128297
128692
  `unit "${unit.id}" could not freeze external marketplace "${dist.marketplaceRepo}" HEAD: ${observed.error ?? "unknown"}`,
@@ -128323,21 +128718,61 @@ async function resolveExternalMarketplaceFreezes({
128323
128718
  { unitId: unit.id, marketplaceRepo: dist.marketplaceRepo }
128324
128719
  );
128325
128720
  }
128326
- const pluginEntries = Array.isArray(marketplaceIndex.plugins) ? marketplaceIndex.plugins.filter((entry) => entry && entry.name === dist.plugin) : [];
128327
- if (pluginEntries.length !== 1) {
128721
+ if (!Array.isArray(marketplaceIndex.plugins)) {
128722
+ throw new ReleaseError(
128723
+ GATE_FAILED,
128724
+ `unit "${unit.id}" ${dist.type} external marketplace index plugins must be an array`,
128725
+ { unitId: unit.id, marketplaceRepo: dist.marketplaceRepo }
128726
+ );
128727
+ }
128728
+ const pluginEntries = marketplaceIndex.plugins.filter((entry) => entry && entry.name === dist.plugin);
128729
+ if (isFirstReleaseBootstrap(dist) && pluginEntries.length > 0) {
128730
+ throw new ReleaseError(
128731
+ GATE_FAILED,
128732
+ `unit "${unit.id}" first-release bootstrap requires the marketplace entry to be absent, found ${pluginEntries.length} matching entries`,
128733
+ { unitId: unit.id, marketplaceRepo: dist.marketplaceRepo }
128734
+ );
128735
+ }
128736
+ if (pluginEntries.length !== 1 && !isFirstReleaseBootstrap(dist)) {
128328
128737
  throw new ReleaseError(
128329
128738
  GATE_FAILED,
128330
128739
  `unit "${unit.id}" external marketplace index must contain exactly one plugin entry named "${dist.plugin}", found ${pluginEntries.length}`,
128331
128740
  { unitId: unit.id, marketplaceRepo: dist.marketplaceRepo }
128332
128741
  );
128333
128742
  }
128334
- if (platform.marketplaceEntryCarriesVersion && pluginEntries[0].version !== version) {
128743
+ let selectedEntry = pluginEntries[0];
128744
+ if (!isFirstReleaseBootstrap(dist) && platform.marketplaceEntryCarriesVersion && selectedEntry.version !== version) {
128335
128745
  throw new ReleaseError(
128336
128746
  GATE_FAILED,
128337
128747
  `unit "${unit.id}" external marketplace index entry version "${pluginEntries[0].version}" does not match target version "${version}"`,
128338
128748
  { unitId: unit.id, marketplaceRepo: dist.marketplaceRepo }
128339
128749
  );
128340
128750
  }
128751
+ if (isFirstReleaseBootstrap(dist)) {
128752
+ const asset = productionAssets?.[index];
128753
+ if (!asset || !EXTERNAL_MARKETPLACE_SHA_RE3.test(asset.commit ?? "") || !asset.tag) {
128754
+ throw new ReleaseError(
128755
+ GATE_FAILED,
128756
+ `unit "${unit.id}" first-release bootstrap requires a frozen plugin tag and commit`,
128757
+ { unitId: unit.id, distributionType: dist.type }
128758
+ );
128759
+ }
128760
+ const pluginState = await observePluginRepositoryFn(unit.publicRepo, asset.tag, { githubHost });
128761
+ if (pluginState?.status !== "empty" || pluginState.tagExists !== false || pluginState.releaseExists !== false) {
128762
+ throw new ReleaseError(
128763
+ GATE_FAILED,
128764
+ `unit "${unit.id}" first-release bootstrap requires an empty plugin repository without the target tag or release`,
128765
+ { unitId: unit.id, publicRepo: unit.publicRepo, tag: asset.tag, observed: pluginState }
128766
+ );
128767
+ }
128768
+ selectedEntry = buildExpectedStandaloneIndexInstallIdentity(platform, {
128769
+ name: dist.plugin,
128770
+ repo: unit.publicRepo,
128771
+ tag: asset.tag,
128772
+ sha: asset.commit,
128773
+ version
128774
+ });
128775
+ }
128341
128776
  const marketplaceRef = platform.marketplaceRefForm === "name" ? observed.defaultBranch : sha;
128342
128777
  freezes.set(`${unit.id} ${dist.type}`, {
128343
128778
  // 向后兼容字段(buildExternalActions 使用 repo / ref / marketplace)
@@ -128350,7 +128785,8 @@ async function resolveExternalMarketplaceFreezes({
128350
128785
  marketplaceRef,
128351
128786
  marketplaceIndexPath: manifestPath,
128352
128787
  marketplaceName: marketplaceIndex.name,
128353
- selectedEntry: pluginEntries[0]
128788
+ selectedEntry,
128789
+ ...isFirstReleaseBootstrap(dist) ? { firstReleaseBootstrap: FIRST_RELEASE_BOOTSTRAP_MODE2, marketplaceIndexSha: null } : {}
128354
128790
  });
128355
128791
  await evidence.append({
128356
128792
  phase: "external-marketplace-freeze",
@@ -128362,7 +128798,8 @@ async function resolveExternalMarketplaceFreezes({
128362
128798
  marketplaceRef,
128363
128799
  marketplaceIndexPath: manifestPath,
128364
128800
  marketplaceName: marketplaceIndex.name,
128365
- selectedEntry: pluginEntries[0],
128801
+ selectedEntry,
128802
+ ...isFirstReleaseBootstrap(dist) ? { firstReleaseBootstrap: FIRST_RELEASE_BOOTSTRAP_MODE2, marketplaceIndexSha: null } : {},
128366
128803
  defaultBranch: observed.defaultBranch
128367
128804
  });
128368
128805
  }
@@ -128697,7 +129134,11 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets, e
128697
129134
  ...externalMarketplace && freeze ? {
128698
129135
  marketplaceIndexPath: freeze.marketplaceIndexPath,
128699
129136
  marketplaceName: freeze.marketplaceName,
128700
- selectedEntry: freeze.selectedEntry
129137
+ selectedEntry: freeze.selectedEntry,
129138
+ ...freeze.firstReleaseBootstrap ? {
129139
+ firstReleaseBootstrap: freeze.firstReleaseBootstrap,
129140
+ marketplaceIndexSha: null
129141
+ } : {}
128701
129142
  } : {}
128702
129143
  },
128703
129144
  expected: {
@@ -128712,7 +129153,11 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets, e
128712
129153
  entrySkill: dist.entrySkill,
128713
129154
  entrySkillFound: true,
128714
129155
  manifestDigest: asset.manifestDigest,
128715
- ...externalMarketplace ? { marketplaceLocation: "external", marketplaceCommitSha: freeze.marketplaceCommitSha } : {}
129156
+ ...externalMarketplace ? { marketplaceLocation: "external", marketplaceCommitSha: freeze.marketplaceCommitSha } : {},
129157
+ ...externalMarketplace && freeze?.firstReleaseBootstrap ? {
129158
+ firstReleaseBootstrap: freeze.firstReleaseBootstrap,
129159
+ marketplaceIndexSha: null
129160
+ } : {}
128716
129161
  },
128717
129162
  status: "PENDING"
128718
129163
  });
@@ -130340,13 +130785,26 @@ async function prepareRelease(options) {
130340
130785
  skipSkillResourceClosure,
130341
130786
  workflow
130342
130787
  });
130788
+ for (const { unit } of unitResults) {
130789
+ for (const dist of unit.distributions ?? []) {
130790
+ if (isFirstReleaseBootstrap(dist) && (!production || offline)) {
130791
+ throw new ReleaseError(
130792
+ GATE_FAILED,
130793
+ `unit "${unit.id}" ${dist.type} first-release bootstrap requires online production prepare`,
130794
+ { unitId: unit.id, distributionType: dist.type }
130795
+ );
130796
+ }
130797
+ }
130798
+ }
130343
130799
  const externalMarketplaceFreezes = production ? await resolveExternalMarketplaceFreezes({
130344
130800
  unitResults,
130345
130801
  resolvedVersions,
130346
130802
  offline,
130347
130803
  evidence,
130348
130804
  observeHeadFn: options.observeExternalMarketplaceHeadFn ?? defaultObserveExternalMarketplaceHead,
130349
- fetchIndexFn: options.fetchExternalMarketplaceIndexFn ?? defaultFetchExternalMarketplaceIndex
130805
+ fetchIndexFn: options.fetchExternalMarketplaceIndexFn ?? defaultFetchExternalMarketplaceIndex,
130806
+ productionAssets,
130807
+ observePluginRepositoryFn: options.observeFirstReleasePluginRepositoryFn ?? defaultObserveFirstReleasePluginRepository
130350
130808
  }) : /* @__PURE__ */ new Map();
130351
130809
  const units = await Promise.all(unitResults.map(async ({ unit, manifest }, idx) => {
130352
130810
  const unitVersion = resolvedVersions[idx];
@@ -130426,6 +130884,10 @@ async function prepareRelease(options) {
130426
130884
  frozenDist.marketplaceIndexPath = freeze.marketplaceIndexPath;
130427
130885
  frozenDist.marketplaceName = freeze.marketplaceName;
130428
130886
  frozenDist.selectedEntry = freeze.selectedEntry;
130887
+ if (freeze.firstReleaseBootstrap) {
130888
+ frozenDist.firstReleaseBootstrap = freeze.firstReleaseBootstrap;
130889
+ frozenDist.marketplaceIndexSha = null;
130890
+ }
130429
130891
  }
130430
130892
  }
130431
130893
  return frozenDist;
@@ -130795,7 +131257,7 @@ async function prepareRelease(options) {
130795
131257
  await lock.release();
130796
131258
  }
130797
131259
  }
130798
- var execFile9, CONSUMER_INSTALL_RECIPE_VERSION2, HOOK_EXECUTION_ORDER, EXTERNAL_MARKETPLACE_SHA_RE2;
131260
+ var execFile9, CONSUMER_INSTALL_RECIPE_VERSION2, FIRST_RELEASE_BOOTSTRAP_MODE2, HOOK_EXECUTION_ORDER, EXTERNAL_MARKETPLACE_SHA_RE3;
130799
131261
  var init_prepare = __esm({
130800
131262
  async "src/commands/prepare.mjs"() {
130801
131263
  init_src2();
@@ -130836,6 +131298,7 @@ var init_prepare = __esm({
130836
131298
  init_bounded_output();
130837
131299
  execFile9 = promisify12(execFileCb12);
130838
131300
  CONSUMER_INSTALL_RECIPE_VERSION2 = "consumer-install-v1";
131301
+ FIRST_RELEASE_BOOTSTRAP_MODE2 = "manual-index-checkpoint";
130839
131302
  __name(resolveUnitVersion, "resolveUnitVersion");
130840
131303
  __name(resolveAllUnitVersions, "resolveAllUnitVersions");
130841
131304
  HOOK_EXECUTION_ORDER = Object.freeze(["lint", "docs", "build", "test", "typecheck"]);
@@ -130854,11 +131317,14 @@ var init_prepare = __esm({
130854
131317
  __name(enumerateFrozenTagPaths, "enumerateFrozenTagPaths");
130855
131318
  __name(collectPostPublishCommandCandidates, "collectPostPublishCommandCandidates");
130856
131319
  __name(assertPrivateExecutionDeclarations, "assertPrivateExecutionDeclarations");
130857
- EXTERNAL_MARKETPLACE_SHA_RE2 = /^[0-9a-f]{40}$/;
131320
+ EXTERNAL_MARKETPLACE_SHA_RE3 = /^[0-9a-f]{40}$/;
130858
131321
  __name(parseExternalMarketplaceLsRemote, "parseExternalMarketplaceLsRemote");
130859
131322
  __name(decodeExternalMarketplaceIndex, "decodeExternalMarketplaceIndex");
130860
131323
  __name(defaultObserveExternalMarketplaceHead, "defaultObserveExternalMarketplaceHead");
130861
131324
  __name(defaultFetchExternalMarketplaceIndex, "defaultFetchExternalMarketplaceIndex");
131325
+ __name(parseFirstReleasePluginRepositoryRefs, "parseFirstReleasePluginRepositoryRefs");
131326
+ __name(defaultObserveFirstReleasePluginRepository, "defaultObserveFirstReleasePluginRepository");
131327
+ __name(isFirstReleaseBootstrap, "isFirstReleaseBootstrap");
130862
131328
  __name(resolveExternalMarketplaceFreezes, "resolveExternalMarketplaceFreezes");
130863
131329
  __name(buildExternalActions, "buildExternalActions");
130864
131330
  __name(computeSourceAuthorityClosure, "computeSourceAuthorityClosure");
@@ -130948,12 +131414,13 @@ var init_hooks2 = __esm({
130948
131414
  // src/commands/post-release-local.mjs
130949
131415
  var post_release_local_exports = {};
130950
131416
  __export(post_release_local_exports, {
131417
+ assertLocalFinishRun: () => assertLocalFinishRun,
130951
131418
  assertVerifiedReleaseRun: () => assertVerifiedReleaseRun,
130952
131419
  derivePostReleaseChecklist: () => derivePostReleaseChecklist,
130953
131420
  unavailablePostReleaseChecklist: () => unavailablePostReleaseChecklist,
130954
131421
  updateLocalHostPlugins: () => updateLocalHostPlugins
130955
131422
  });
130956
- import { access as access2 } from "node:fs/promises";
131423
+ import { access as access2, readFile as readFile36 } from "node:fs/promises";
130957
131424
  import { homedir } from "node:os";
130958
131425
  import { join as join27, relative as relative27 } from "node:path";
130959
131426
  function attachFoundationFailure(error, { envelope, stdout }) {
@@ -131047,13 +131514,35 @@ function assertExecutableTarget(target) {
131047
131514
  throw new Error(`local host update requires the frozen public commit for unit ${target.unitId}`);
131048
131515
  }
131049
131516
  }
131050
- function derivePostReleaseChecklist(plan) {
131517
+ function buildShipNextStep({ root, statePath, unitIds }) {
131518
+ const argv = ["release-skill", "ship"];
131519
+ if (typeof root === "string" && root.length > 0) argv.push("--root", root);
131520
+ if (typeof statePath === "string" && statePath.length > 0) argv.push("--state", statePath);
131521
+ if (Array.isArray(unitIds) && unitIds.length > 0) {
131522
+ for (const unitId of unitIds) argv.push("--unit", unitId);
131523
+ }
131524
+ return {
131525
+ code: "COMPLETE_POST_VERIFY",
131526
+ message: "Complete the postVerify phase with ship before running post-release.",
131527
+ argv
131528
+ };
131529
+ }
131530
+ function derivePostReleaseChecklist(plan, {
131531
+ runPath,
131532
+ root,
131533
+ statePath,
131534
+ unitIds,
131535
+ postVerifyComplete = false
131536
+ } = {}) {
131051
131537
  if (!plan || typeof plan !== "object" || typeof plan.digest !== "string") {
131052
131538
  throw new Error("a frozen release plan with digest is required");
131053
131539
  }
131054
131540
  const units = plan.units ?? [];
131055
131541
  const uncovered = units.filter((unit) => !BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy));
131056
131542
  const targets = pluginTargets(plan);
131543
+ const hasPendingPostVerify = postVerifyHooks(plan).length > 0 && !postVerifyComplete && targets.length > 0;
131544
+ const hasStatePath = typeof statePath === "string" && statePath.length > 0;
131545
+ const selectedUnitIds = Array.isArray(unitIds) ? unitIds : void 0;
131057
131546
  return {
131058
131547
  command: "post-release",
131059
131548
  status: "AWAITING_USER_DECISION",
@@ -131069,13 +131558,143 @@ function derivePostReleaseChecklist(plan) {
131069
131558
  }))
131070
131559
  },
131071
131560
  localHostUpdate: {
131072
- promptRequired: targets.length > 0,
131073
- available: targets.length > 0,
131561
+ promptRequired: targets.length > 0 && !hasPendingPostVerify,
131562
+ available: targets.length > 0 && !hasPendingPostVerify,
131563
+ ...!hasPendingPostVerify && runPath ? { runPath } : {},
131564
+ ...hasPendingPostVerify ? {
131565
+ ...hasStatePath ? { nextSteps: [buildShipNextStep({ root, statePath, unitIds: selectedUnitIds })] } : {}
131566
+ } : {},
131074
131567
  hosts: [...new Set(targets.map((target) => target.host))].sort(),
131075
131568
  targets
131076
131569
  }
131077
131570
  };
131078
131571
  }
131572
+ function postVerifyHooks(plan) {
131573
+ if (plan?.planVersion === void 0) return [];
131574
+ return normalizePostPublishView(plan).flatMap((declaration) => (declaration.hooks ?? []).filter((hook) => hook.phase === "postVerify").map((hook) => ({
131575
+ actionId: postPublishActionId({ planVersion: plan.planVersion, unitId: declaration.unitId, localId: hook.id }),
131576
+ hook,
131577
+ unitId: declaration.unitId
131578
+ })));
131579
+ }
131580
+ function localFinishEvidenceError(message, { cause, root, statePath, unitIds } = {}) {
131581
+ const error = new Error(`local host update evidence is not ready: ${message}; next step: obtain approval if required, then complete the postVerify phase with ship before rerunning post-release`);
131582
+ error.code = "LOCAL_FINISH_EVIDENCE_NOT_READY";
131583
+ const hasStatePath = typeof statePath === "string" && statePath.length > 0;
131584
+ error.details = {
131585
+ cause: {
131586
+ code: cause?.code ?? "LOCAL_FINISH_EVIDENCE_NOT_READY",
131587
+ message: cause?.message ?? message
131588
+ },
131589
+ ...hasStatePath ? {
131590
+ nextSteps: [buildShipNextStep({ root, statePath, unitIds })]
131591
+ } : {}
131592
+ };
131593
+ return error;
131594
+ }
131595
+ async function updateLocalHostPlugins({
131596
+ plan: _suppliedPlan,
131597
+ planPath,
131598
+ runPath,
131599
+ runRecord: _suppliedRunRecord,
131600
+ production: _suppliedProduction,
131601
+ root = process.cwd(),
131602
+ statePath,
131603
+ unitIds,
131604
+ ...options
131605
+ } = {}) {
131606
+ if (!planPath || !runPath) {
131607
+ throw localFinishEvidenceError(
131608
+ "planPath and runPath are required before local host updates",
131609
+ { root, statePath, unitIds }
131610
+ );
131611
+ }
131612
+ let plan;
131613
+ let runRecord;
131614
+ try {
131615
+ plan = JSON.parse(await readFile36(planPath, "utf8"));
131616
+ runRecord = await loadRun(runPath, {
131617
+ requireDigest: true,
131618
+ authorityPlanPath: planPath
131619
+ });
131620
+ } catch (cause) {
131621
+ throw localFinishEvidenceError(cause.message, { cause, root, statePath, unitIds });
131622
+ }
131623
+ await assertLocalFinishRun({
131624
+ plan,
131625
+ planPath,
131626
+ runPath,
131627
+ runRecord,
131628
+ production: Boolean(plan.production),
131629
+ root,
131630
+ statePath,
131631
+ unitIds
131632
+ });
131633
+ return updateLocalHostPluginsInternal({
131634
+ plan,
131635
+ root,
131636
+ ...options
131637
+ });
131638
+ }
131639
+ async function assertLocalFinishRun({
131640
+ plan,
131641
+ planPath,
131642
+ runPath,
131643
+ runRecord,
131644
+ production = false,
131645
+ root,
131646
+ statePath,
131647
+ unitIds
131648
+ } = {}) {
131649
+ const hooks = postVerifyHooks(plan);
131650
+ if (hooks.length === 0) {
131651
+ try {
131652
+ await validateRunLineage(runRecord, { plan, planPath, runPath, production });
131653
+ assertVerifiedReleaseRun(plan, runRecord);
131654
+ } catch (cause) {
131655
+ throw localFinishEvidenceError(cause.message, { cause, root, statePath, unitIds, plan });
131656
+ }
131657
+ return { runPath, phase: "verify" };
131658
+ }
131659
+ if (runRecord?.command !== "postverify" || runRecord?.status !== "DISTRIBUTED") {
131660
+ throw localFinishEvidenceError("the plan declares postVerify hooks, so the supplied verify run cannot authorize local-finish", { root, statePath, unitIds, plan });
131661
+ }
131662
+ if (runRecord.planDigest !== plan?.digest) {
131663
+ throw localFinishEvidenceError("postVerify run is not bound to the frozen plan", { root, statePath, unitIds, plan });
131664
+ }
131665
+ if (typeof runRecord.sourceRunPath !== "string" || typeof runRecord.sourceRunId !== "string" || typeof runRecord.sourceRunDigest !== "string") {
131666
+ throw localFinishEvidenceError("completed postVerify run has incomplete verify-run lineage", { root, statePath, unitIds, plan });
131667
+ }
131668
+ const checkpoints = Array.isArray(runRecord.checkpoints) ? runRecord.checkpoints : null;
131669
+ const expectedIds = hooks.map(({ actionId }) => actionId);
131670
+ const actualIds = checkpoints?.map((checkpoint) => checkpoint?.actionId) ?? [];
131671
+ if (!checkpoints || actualIds.length !== expectedIds.length || new Set(actualIds).size !== actualIds.length || actualIds.some((id) => !expectedIds.includes(id)) || expectedIds.some((id) => !actualIds.includes(id)) || checkpoints.some((checkpoint) => checkpoint.actionType !== "postpublish-hook" || !["succeeded", "NO_CHANGE"].includes(checkpoint.status))) {
131672
+ throw localFinishEvidenceError("postVerify checkpoints must match each declared hook exactly and be succeeded or NO_CHANGE", { root, statePath, unitIds, plan });
131673
+ }
131674
+ let sourceRun;
131675
+ try {
131676
+ sourceRun = await loadRun(runRecord.sourceRunPath, {
131677
+ requireDigest: true,
131678
+ authorityPlanPath: planPath
131679
+ });
131680
+ } catch (cause) {
131681
+ throw localFinishEvidenceError(cause.message, { cause, root, statePath, unitIds, plan });
131682
+ }
131683
+ if (sourceRun.command !== "verify" || sourceRun.status !== "VERIFIED" || sourceRun.runId !== runRecord.sourceRunId || sourceRun.runDigest !== runRecord.sourceRunDigest || sourceRun.planDigest !== plan.digest) {
131684
+ throw localFinishEvidenceError("postVerify lineage does not point to the same-plan VERIFIED run", { root, statePath, unitIds, plan });
131685
+ }
131686
+ try {
131687
+ await validateRunLineage(sourceRun, {
131688
+ plan,
131689
+ planPath,
131690
+ runPath: runRecord.sourceRunPath,
131691
+ production
131692
+ });
131693
+ } catch (cause) {
131694
+ throw localFinishEvidenceError(cause.message, { cause, root, statePath, unitIds, plan });
131695
+ }
131696
+ return { runPath, sourceRunPath: runRecord.sourceRunPath, phase: "postverify" };
131697
+ }
131079
131698
  function unavailablePostReleaseChecklist(plan, error) {
131080
131699
  return {
131081
131700
  command: "post-release",
@@ -131101,7 +131720,11 @@ function hostEnvironment(host, { kimiHome } = {}) {
131101
131720
  env.HOME ??= homedir();
131102
131721
  env.PATH ??= "/usr/bin:/bin";
131103
131722
  env.GIT_TERMINAL_PROMPT = "0";
131104
- if (host === "workbuddy") env.CODEBUDDY_CONFIG_DIR = join27(env.HOME, ".workbuddy");
131723
+ if (host === "codebuddy") env.CODEBUDDY_CONFIG_DIR = join27(env.HOME, ".codebuddy");
131724
+ if (host === "workbuddy") {
131725
+ env.CODEBUDDY_CONFIG_DIR = join27(env.HOME, ".workbuddy");
131726
+ env.WORKBUDDY_CONFIG_DIR = join27(env.HOME, ".workbuddy");
131727
+ }
131105
131728
  if (host === "kimi" && kimiHome) env.KIMI_CONFIG_DIR = kimiHome;
131106
131729
  return env;
131107
131730
  }
@@ -131152,12 +131775,17 @@ async function commandAvailable(command2, host, run6) {
131152
131775
  }
131153
131776
  }
131154
131777
  async function defaultDetect(host, run6 = defaultRun) {
131155
- if (host === "codebuddy" || host === "workbuddy") {
131156
- for (const command2 of ["codebuddy", "cbc", CODEBUDDY_MACOS_PATH]) {
131778
+ if (host === "codebuddy") {
131779
+ for (const command2 of ["codebuddy", "cbc"]) {
131157
131780
  if (await commandAvailable(command2, host, run6)) return { available: true, command: command2 };
131158
131781
  }
131159
131782
  return { available: false, reason: "CodeBuddy/WorkBuddy CLI not found" };
131160
131783
  }
131784
+ if (host === "workbuddy") {
131785
+ if (process.platform !== "darwin") return { available: false, status: "SKIPPED_UNSUPPORTED_PLATFORM", reason: "WorkBuddy local update is supported only on macOS" };
131786
+ if (await commandAvailable(CODEBUDDY_MACOS_PATH, host, run6)) return { available: true, command: CODEBUDDY_MACOS_PATH };
131787
+ return { available: false, reason: "WorkBuddy embedded CLI not found" };
131788
+ }
131161
131789
  if (host === "kimi") {
131162
131790
  if (!await commandAvailable("kimi", host, run6)) return { available: false, reason: "kimi CLI not found" };
131163
131791
  try {
@@ -131690,7 +132318,7 @@ function failedHostResult(target, error) {
131690
132318
  ...publicDetails !== void 0 ? { details: publicDetails } : {}
131691
132319
  };
131692
132320
  }
131693
- async function updateLocalHostPlugins({
132321
+ async function updateLocalHostPluginsInternal({
131694
132322
  plan,
131695
132323
  root = process.cwd(),
131696
132324
  confirmPlanDigest,
@@ -131701,7 +132329,7 @@ async function updateLocalHostPlugins({
131701
132329
  verifyInstalledPayload = verifyInstalledMarketplacePayload
131702
132330
  } = {}) {
131703
132331
  const effectiveKimiHome = kimiHome ?? process.env.KIMI_CONFIG_DIR ?? join27(homedir(), ".kimi-code");
131704
- const checklist = derivePostReleaseChecklist(plan);
132332
+ const checklist = derivePostReleaseChecklist(plan, { postVerifyComplete: true });
131705
132333
  if (confirmPlanDigest !== plan.digest) {
131706
132334
  throw new Error("plan digest confirmation does not match the frozen release plan");
131707
132335
  }
@@ -131718,7 +132346,7 @@ async function updateLocalHostPlugins({
131718
132346
  results.push({
131719
132347
  host: target.host,
131720
132348
  unitId: target.unitId,
131721
- status: "SKIPPED_NOT_INSTALLED",
132349
+ status: detected?.status ?? "SKIPPED_NOT_INSTALLED",
131722
132350
  reason: detected?.reason ?? "host unavailable"
131723
132351
  });
131724
132352
  continue;
@@ -131753,6 +132381,8 @@ var init_post_release_local = __esm({
131753
132381
  await init_registry2();
131754
132382
  init_codebuddy();
131755
132383
  await init_plugin_marketplace();
132384
+ init_postpublish();
132385
+ await init_run();
131756
132386
  HOSTS_BY_ACTION = Object.freeze({
131757
132387
  "claude-marketplace-install": ["claude"],
131758
132388
  "codex-marketplace-install": ["codex"],
@@ -131787,7 +132417,9 @@ var init_post_release_local = __esm({
131787
132417
  "NODE_EXTRA_CA_CERTS",
131788
132418
  "CLAUDE_CONFIG_DIR",
131789
132419
  "CODEX_HOME",
131790
- "KIMI_CONFIG_DIR"
132420
+ "KIMI_CONFIG_DIR",
132421
+ "CODEBUDDY_CONFIG_DIR",
132422
+ "WORKBUDDY_CONFIG_DIR"
131791
132423
  ]);
131792
132424
  CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(["plugin", "list", "--json"]);
131793
132425
  CODEBUDDY_MARKETPLACE_LIST_ARGS = Object.freeze(["plugin", "marketplace", "list"]);
@@ -131795,7 +132427,12 @@ var init_post_release_local = __esm({
131795
132427
  __name(actionTarget, "actionTarget");
131796
132428
  __name(pluginTargets, "pluginTargets");
131797
132429
  __name(assertExecutableTarget, "assertExecutableTarget");
132430
+ __name(buildShipNextStep, "buildShipNextStep");
131798
132431
  __name(derivePostReleaseChecklist, "derivePostReleaseChecklist");
132432
+ __name(postVerifyHooks, "postVerifyHooks");
132433
+ __name(localFinishEvidenceError, "localFinishEvidenceError");
132434
+ __name(updateLocalHostPlugins, "updateLocalHostPlugins");
132435
+ __name(assertLocalFinishRun, "assertLocalFinishRun");
131799
132436
  __name(unavailablePostReleaseChecklist, "unavailablePostReleaseChecklist");
131800
132437
  __name(assertVerifiedReleaseRun, "assertVerifiedReleaseRun");
131801
132438
  __name(hostEnvironment, "hostEnvironment");
@@ -131825,7 +132462,7 @@ var init_post_release_local = __esm({
131825
132462
  __name(runKimiUpdate, "runKimiUpdate");
131826
132463
  __name(aggregateStatus, "aggregateStatus");
131827
132464
  __name(failedHostResult, "failedHostResult");
131828
- __name(updateLocalHostPlugins, "updateLocalHostPlugins");
132465
+ __name(updateLocalHostPluginsInternal, "updateLocalHostPluginsInternal");
131829
132466
  }
131830
132467
  });
131831
132468
 
@@ -131835,7 +132472,7 @@ __export(approve_exports, {
131835
132472
  approvePlan: () => approvePlan,
131836
132473
  approvePostPublishHook: () => approvePostPublishHook
131837
132474
  });
131838
- import { readFile as readFile36, writeFile as writeFile8 } from "node:fs/promises";
132475
+ import { readFile as readFile37, writeFile as writeFile8 } from "node:fs/promises";
131839
132476
  import { resolve as resolve33, dirname as dirname17, basename as basename13 } from "node:path";
131840
132477
  function defaultClock2() {
131841
132478
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -131859,7 +132496,7 @@ async function approvePlan(options) {
131859
132496
  }
131860
132497
  let planRaw;
131861
132498
  try {
131862
- planRaw = await readFile36(planPath, "utf8");
132499
+ planRaw = await readFile37(planPath, "utf8");
131863
132500
  } catch (err) {
131864
132501
  throw new ReleaseError(
131865
132502
  GATE_FAILED,
@@ -131987,7 +132624,7 @@ async function approvePlan(options) {
131987
132624
  await writeFile8(immutableApprovalPath, json, { encoding: "utf8", flag: "wx", mode: 384 });
131988
132625
  } catch (error) {
131989
132626
  if (error.code !== "EEXIST") throw error;
131990
- const existing = await readFile36(immutableApprovalPath, "utf8");
132627
+ const existing = await readFile37(immutableApprovalPath, "utf8");
131991
132628
  if (existing !== json) {
131992
132629
  throw new ReleaseError(
131993
132630
  GATE_FAILED,
@@ -132038,7 +132675,7 @@ async function approvePostPublishHook(options) {
132038
132675
  }
132039
132676
  let planRaw;
132040
132677
  try {
132041
- planRaw = await readFile36(planPath, "utf8");
132678
+ planRaw = await readFile37(planPath, "utf8");
132042
132679
  } catch (err) {
132043
132680
  throw new ReleaseError(GATE_FAILED, `cannot read release plan: ${err.message}`, { planPath, cause: err.code });
132044
132681
  }
@@ -132098,7 +132735,7 @@ async function approvePostPublishHook(options) {
132098
132735
  await writeFile8(immutableApprovalPath, json, { encoding: "utf8", flag: "wx", mode: 384 });
132099
132736
  } catch (error) {
132100
132737
  if (error.code !== "EEXIST") throw error;
132101
- const existing = await readFile36(immutableApprovalPath, "utf8");
132738
+ const existing = await readFile37(immutableApprovalPath, "utf8");
132102
132739
  if (existing !== json) {
132103
132740
  throw new ReleaseError(
132104
132741
  GATE_FAILED,
@@ -132249,7 +132886,7 @@ __export(publish_exports, {
132249
132886
  classifyPreObservation: () => classifyPreObservation,
132250
132887
  publishRelease: () => publishRelease
132251
132888
  });
132252
- import { readFile as readFile37, mkdir as mkdir22 } from "node:fs/promises";
132889
+ import { readFile as readFile38, mkdir as mkdir22 } from "node:fs/promises";
132253
132890
  import { isAbsolute as isAbsolute23, join as join28, relative as relative28 } from "node:path";
132254
132891
  function assertInsideAssetRoot(assetRoot, candidate, label) {
132255
132892
  const rel = relative28(assetRoot, candidate);
@@ -132482,7 +133119,7 @@ async function publishRelease(options) {
132482
133119
  const captureBaselineActual = typeof captureBaselineFn === "function" ? captureBaselineFn : captureBaseline;
132483
133120
  let planRaw;
132484
133121
  try {
132485
- planRaw = await readFile37(planPath, "utf8");
133122
+ planRaw = await readFile38(planPath, "utf8");
132486
133123
  } catch (err) {
132487
133124
  throw new ReleaseError(GATE_FAILED, `cannot read release plan: ${err.message}`, { planPath, cause: err.code });
132488
133125
  }
@@ -132673,7 +133310,7 @@ async function publishRelease(options) {
132673
133310
  await evidence.append({ phase: "safety-gate", gate: "approval-load", status: "started" });
132674
133311
  let approvalRaw;
132675
133312
  try {
132676
- approvalRaw = await readFile37(approvalPath, "utf8");
133313
+ approvalRaw = await readFile38(approvalPath, "utf8");
132677
133314
  } catch (err) {
132678
133315
  throw new ReleaseError(
132679
133316
  GATE_FAILED,
@@ -133310,7 +133947,7 @@ var reconcile_exports = {};
133310
133947
  __export(reconcile_exports, {
133311
133948
  reconcileRelease: () => reconcileRelease
133312
133949
  });
133313
- import { readFile as readFile38, mkdir as mkdir23 } from "node:fs/promises";
133950
+ import { readFile as readFile39, mkdir as mkdir23 } from "node:fs/promises";
133314
133951
  import { join as join29 } from "node:path";
133315
133952
  function defaultClock5() {
133316
133953
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -133339,7 +133976,7 @@ async function reconcileRelease(options) {
133339
133976
  }
133340
133977
  let planRaw;
133341
133978
  try {
133342
- planRaw = await readFile38(planPath, "utf8");
133979
+ planRaw = await readFile39(planPath, "utf8");
133343
133980
  } catch (err) {
133344
133981
  throw new ReleaseError(GATE_FAILED, `cannot read release plan: ${err.message}`, { planPath, cause: err.code });
133345
133982
  }
@@ -133462,7 +134099,7 @@ async function reconcileRelease(options) {
133462
134099
  { sourceRunId: sourceRun.runId }
133463
134100
  );
133464
134101
  }
133465
- const sourceApprovalRaw = await readFile38(consumedApprovalPath, "utf8").catch((error) => {
134102
+ const sourceApprovalRaw = await readFile39(consumedApprovalPath, "utf8").catch((error) => {
133466
134103
  throw new ReleaseError(GATE_FAILED, "source run approval authority is unavailable", {
133467
134104
  sourceRunId: sourceRun.runId,
133468
134105
  cause: error.code
@@ -133624,7 +134261,7 @@ async function reconcileRelease(options) {
133624
134261
  if (approvalPath) {
133625
134262
  let approvalRaw;
133626
134263
  try {
133627
- approvalRaw = await readFile38(approvalPath, "utf8");
134264
+ approvalRaw = await readFile39(approvalPath, "utf8");
133628
134265
  } catch (err) {
133629
134266
  throw new ReleaseError(
133630
134267
  GATE_FAILED,
@@ -135090,7 +135727,7 @@ var init_notify_handoff = __esm({
135090
135727
  // src/core/proposal-inbox.mjs
135091
135728
  import { execFile as execFileCb14 } from "node:child_process";
135092
135729
  import { promisify as promisify14 } from "node:util";
135093
- import { mkdir as mkdir26, mkdtemp as mkdtemp8, readFile as readFile39, rm as rm12, writeFile as writeFile11 } from "node:fs/promises";
135730
+ import { mkdir as mkdir26, mkdtemp as mkdtemp8, readFile as readFile40, rm as rm12, writeFile as writeFile11 } from "node:fs/promises";
135094
135731
  import { tmpdir as tmpdir6 } from "node:os";
135095
135732
  import { dirname as dirname19, join as join32 } from "node:path";
135096
135733
  function defaultExec2(command2, args2, options = {}) {
@@ -135270,7 +135907,7 @@ async function observeProposalInboxGitPush(params) {
135270
135907
  const targetPath2 = join32(cloneDir, proposalPath);
135271
135908
  let existing = null;
135272
135909
  try {
135273
- existing = await readFile39(targetPath2, "utf8");
135910
+ existing = await readFile40(targetPath2, "utf8");
135274
135911
  } catch {
135275
135912
  existing = null;
135276
135913
  }
@@ -135431,7 +136068,7 @@ async function deliverProposalLocalFile(params) {
135431
136068
  const targetPath2 = join32(workspaceRealpath, proposalPath);
135432
136069
  let existing = null;
135433
136070
  try {
135434
- existing = await readFile39(targetPath2, "utf8");
136071
+ existing = await readFile40(targetPath2, "utf8");
135435
136072
  } catch {
135436
136073
  existing = null;
135437
136074
  }
@@ -135928,7 +136565,7 @@ var init_preset_gitwrite = __esm({
135928
136565
  });
135929
136566
 
135930
136567
  // src/core/marketplace-registry-entry.mjs
135931
- import { readFile as readFile40 } from "node:fs/promises";
136568
+ import { readFile as readFile41 } from "node:fs/promises";
135932
136569
  import { join as join34 } from "node:path";
135933
136570
  function updateRegistryEntry(registry, params) {
135934
136571
  const { entryKey, fieldsFromPlan, contextProjection } = params ?? {};
@@ -135994,7 +136631,7 @@ async function executeMarketplaceRegistryEntryHook(params) {
135994
136631
  const absoluteRegistry = join34(worktree, registryPath);
135995
136632
  let raw;
135996
136633
  try {
135997
- raw = await readFile40(absoluteRegistry, "utf8");
136634
+ raw = await readFile41(absoluteRegistry, "utf8");
135998
136635
  } catch {
135999
136636
  throw new ReleaseError(
136000
136637
  REMOTE_CONFLICT,
@@ -136046,7 +136683,7 @@ var init_marketplace_registry_entry = __esm({
136046
136683
  });
136047
136684
 
136048
136685
  // src/core/docs-refresh-preset.mjs
136049
- import { mkdir as mkdir27, readFile as readFile41, writeFile as writeFile12 } from "node:fs/promises";
136686
+ import { mkdir as mkdir27, readFile as readFile42, writeFile as writeFile12 } from "node:fs/promises";
136050
136687
  import { dirname as dirname20, isAbsolute as isAbsolute24, join as join35, relative as relative29, resolve as resolve34 } from "node:path";
136051
136688
  function resolvePayloadSource(payloadDir, from) {
136052
136689
  const sourcePath = resolve34(payloadDir, from);
@@ -136084,7 +136721,7 @@ async function executeDocsRefreshHook(params) {
136084
136721
  const mutate = /* @__PURE__ */ __name(async (worktree) => {
136085
136722
  for (const mapping of mappings) {
136086
136723
  const sourcePath = resolvePayloadSource(payloadDir, mapping.from);
136087
- let content = await readFile41(sourcePath).catch(() => null);
136724
+ let content = await readFile42(sourcePath).catch(() => null);
136088
136725
  if (content === null) {
136089
136726
  throw new ReleaseError(
136090
136727
  GATE_FAILED,
@@ -136301,7 +136938,7 @@ __export(distribute_exports, {
136301
136938
  });
136302
136939
  import { execFile as execFileCb16 } from "node:child_process";
136303
136940
  import { promisify as promisify16 } from "node:util";
136304
- import { realpath as realpath32, lstat as lstat43, mkdir as mkdir28, readFile as readFile42, rm as rm15 } from "node:fs/promises";
136941
+ import { realpath as realpath32, lstat as lstat43, mkdir as mkdir28, readFile as readFile43, rm as rm15 } from "node:fs/promises";
136305
136942
  import { mkdtemp as mkdtemp10 } from "node:fs/promises";
136306
136943
  import { tmpdir as tmpdir8 } from "node:os";
136307
136944
  import { isAbsolute as isAbsolute25, join as join36, relative as relative30, resolve as resolve35 } from "node:path";
@@ -136400,7 +137037,7 @@ async function distributeRelease(options) {
136400
137037
  const hookRunner = typeof runHookFn === "function" ? runHookFn : runHook;
136401
137038
  let planRaw;
136402
137039
  try {
136403
- planRaw = await readFile42(planPath, "utf8");
137040
+ planRaw = await readFile43(planPath, "utf8");
136404
137041
  } catch (err) {
136405
137042
  throw new ReleaseError(GATE_FAILED, `cannot read release plan: ${err.message}`, { planPath, cause: err.code });
136406
137043
  }
@@ -136515,7 +137152,7 @@ async function distributeRelease(options) {
136515
137152
  await evidence.append({ phase: "safety-gate", gate: "approval-load", status: "started" });
136516
137153
  let approvalRaw;
136517
137154
  try {
136518
- approvalRaw = await readFile42(approvalPath, "utf8");
137155
+ approvalRaw = await readFile43(approvalPath, "utf8");
136519
137156
  } catch (err) {
136520
137157
  throw new ReleaseError(GATE_FAILED, `cannot read approval record: ${err.message}`, { approvalPath, cause: err.code });
136521
137158
  }
@@ -136627,7 +137264,7 @@ async function distributeRelease(options) {
136627
137264
  for (const hookApprovalPath of hookApprovalPaths) {
136628
137265
  let hookApprovalRaw;
136629
137266
  try {
136630
- hookApprovalRaw = await readFile42(hookApprovalPath, "utf8");
137267
+ hookApprovalRaw = await readFile43(hookApprovalPath, "utf8");
136631
137268
  } catch (err) {
136632
137269
  await failBlocked(new ReleaseError(
136633
137270
  GATE_FAILED,
@@ -137732,7 +138369,7 @@ __export(postverify_exports, {
137732
138369
  });
137733
138370
  import { execFile as execFileCb17 } from "node:child_process";
137734
138371
  import { promisify as promisify17 } from "node:util";
137735
- import { mkdir as mkdir29, readFile as readFile43, realpath as realpath33, rm as rm16 } from "node:fs/promises";
138372
+ import { mkdir as mkdir29, readFile as readFile44, realpath as realpath33, rm as rm16 } from "node:fs/promises";
137736
138373
  import { mkdtemp as mkdtemp11 } from "node:fs/promises";
137737
138374
  import { tmpdir as tmpdir9 } from "node:os";
137738
138375
  import { join as join37 } from "node:path";
@@ -137765,7 +138402,7 @@ async function postVerifyRelease(options) {
137765
138402
  const hookRunner = typeof runHookFn === "function" ? runHookFn : runHook;
137766
138403
  let planRaw;
137767
138404
  try {
137768
- planRaw = await readFile43(planPath, "utf8");
138405
+ planRaw = await readFile44(planPath, "utf8");
137769
138406
  } catch (err) {
137770
138407
  throw new ReleaseError(GATE_FAILED, `cannot read release plan: ${err.message}`, { planPath, cause: err.code });
137771
138408
  }
@@ -137799,7 +138436,7 @@ async function postVerifyRelease(options) {
137799
138436
  }
137800
138437
  let approvalRaw;
137801
138438
  try {
137802
- approvalRaw = await readFile43(approvalPath, "utf8");
138439
+ approvalRaw = await readFile44(approvalPath, "utf8");
137803
138440
  } catch (err) {
137804
138441
  throw new ReleaseError(GATE_FAILED, `cannot read approval record: ${err.message}`, { approvalPath, cause: err.code });
137805
138442
  }
@@ -137872,16 +138509,16 @@ async function postVerifyRelease(options) {
137872
138509
  validatePostPublishDeclaration(declaration, { unitId: declaration.unitId });
137873
138510
  await verifyExecutionBundle({ planPath, postPublish: declaration });
137874
138511
  const declaredHooks = declaration.hooks ?? [];
137875
- const postVerifyHooks2 = declaredHooks.filter((hook) => (hook.phase ?? "distribute") === "postVerify");
137876
- prepared.push({ declaration, postVerifyHooks: postVerifyHooks2 });
138512
+ const postVerifyHooks3 = declaredHooks.filter((hook) => (hook.phase ?? "distribute") === "postVerify");
138513
+ prepared.push({ declaration, postVerifyHooks: postVerifyHooks3 });
137877
138514
  }
137878
- const postVerifyHooks = prepared.flatMap((prep) => prep.postVerifyHooks);
138515
+ const postVerifyHooks2 = prepared.flatMap((prep) => prep.postVerifyHooks);
137879
138516
  const approvedHookIds = /* @__PURE__ */ new Set();
137880
138517
  const hookApprovalPaths = postpublishApprovalPaths ?? [];
137881
138518
  for (const hookApprovalPath of hookApprovalPaths) {
137882
138519
  let hookApprovalRaw;
137883
138520
  try {
137884
- hookApprovalRaw = await readFile43(hookApprovalPath, "utf8");
138521
+ hookApprovalRaw = await readFile44(hookApprovalPath, "utf8");
137885
138522
  } catch (err) {
137886
138523
  throw new ReleaseError(
137887
138524
  GATE_FAILED,
@@ -138036,7 +138673,7 @@ async function postVerifyRelease(options) {
138036
138673
  status: "started",
138037
138674
  sourceVerifyRunId: verifyRun.runId,
138038
138675
  sourcePublishRunId: publishRun.runId,
138039
- hookCount: postVerifyHooks.length,
138676
+ hookCount: postVerifyHooks2.length,
138040
138677
  dryRun: dryRun === true
138041
138678
  });
138042
138679
  await snapshot(DISTRIBUTING2);
@@ -138516,7 +139153,7 @@ var release_metadata_exports = {};
138516
139153
  __export(release_metadata_exports, {
138517
139154
  updatePreviousPublicBaselines: () => updatePreviousPublicBaselines
138518
139155
  });
138519
- import { lstat as lstat44, readFile as readFile44 } from "node:fs/promises";
139156
+ import { lstat as lstat44, readFile as readFile45 } from "node:fs/promises";
138520
139157
  import { resolve as resolve36 } from "node:path";
138521
139158
  function assertOid(value, label) {
138522
139159
  if (typeof value !== "string" || !/^[a-f0-9]{40,64}$/.test(value)) {
@@ -138532,8 +139169,8 @@ async function updatePreviousPublicBaselines(options = {}) {
138532
139169
  let configStat;
138533
139170
  try {
138534
139171
  [plan, configRaw, configStat] = await Promise.all([
138535
- readFile44(planPath, "utf8").then(JSON.parse),
138536
- readFile44(configPath, "utf8"),
139172
+ readFile45(planPath, "utf8").then(JSON.parse),
139173
+ readFile45(configPath, "utf8"),
138537
139174
  lstat44(configPath)
138538
139175
  ]);
138539
139176
  } catch (error) {
@@ -138618,7 +139255,7 @@ var ship_exports = {};
138618
139255
  __export(ship_exports, {
138619
139256
  advanceShip: () => advanceShip
138620
139257
  });
138621
- import { readFile as readFile45, lstat as lstat45, mkdir as mkdir30 } from "node:fs/promises";
139258
+ import { readFile as readFile46, lstat as lstat45, mkdir as mkdir30 } from "node:fs/promises";
138622
139259
  import { basename as basename14, dirname as dirname21, resolve as resolve37 } from "node:path";
138623
139260
  function normalizeNewShipUnitIds(releaseUnits, requestedUnitIds) {
138624
139261
  if (requestedUnitIds === void 0) return void 0;
@@ -138674,7 +139311,7 @@ function assertFrozenPlanUnitScope({ statePath, stateUnitIds, expectedUnitIds, p
138674
139311
  }
138675
139312
  async function readFrozenPlanForUnitScope(planPath, statePath, stateUnitIds) {
138676
139313
  try {
138677
- return JSON.parse(await readFile45(planPath, "utf8"));
139314
+ return JSON.parse(await readFile46(planPath, "utf8"));
138678
139315
  } catch (error) {
138679
139316
  throw unitScopeMismatch(
138680
139317
  statePath,
@@ -138702,7 +139339,7 @@ async function readState(path40) {
138702
139339
  if (!stat22.isFile() || stat22.isSymbolicLink()) {
138703
139340
  throw new Error("ship state must be a regular non-symlink file");
138704
139341
  }
138705
- const state = JSON.parse(await readFile45(path40, "utf8"));
139342
+ const state = JSON.parse(await readFile46(path40, "utf8"));
138706
139343
  const { stateDigest, ...body } = state;
138707
139344
  if (typeof stateDigest !== "string" || stateDigest !== sha256Hex(canonicalJson2(body)) || body.statePath !== path40) {
138708
139345
  throw new Error("ship state digest or authority path does not match");
@@ -138751,7 +139388,8 @@ async function defaultDependencies() {
138751
139388
  readMaxApprovalMs: /* @__PURE__ */ __name(() => MAX_APPROVAL_MS, "readMaxApprovalMs")
138752
139389
  };
138753
139390
  }
138754
- function publicState2(state) {
139391
+ function publicState2(state, { postRelease } = {}) {
139392
+ const visiblePostRelease = postRelease === void 0 ? state.postRelease : postRelease;
138755
139393
  return {
138756
139394
  command: "ship",
138757
139395
  status: state.status,
@@ -138776,7 +139414,7 @@ function publicState2(state) {
138776
139414
  ...state.requirements ? { requirements: state.requirements } : {},
138777
139415
  ...state.manualFollowUps ? { manualFollowUps: state.manualFollowUps } : {},
138778
139416
  ...state.metadataUpdate ? { metadataUpdate: state.metadataUpdate } : {},
138779
- ...state.postRelease ? { postRelease: state.postRelease } : {},
139417
+ ...visiblePostRelease ? { postRelease: visiblePostRelease } : {},
138780
139418
  externalActionsIncludedInPlanApproval: true,
138781
139419
  verificationGatesIncludedInPlanApproval: true,
138782
139420
  postPublishCheckpointApprovalsIncludedInPlanApproval: false
@@ -138800,7 +139438,7 @@ function projectActionTarget(action) {
138800
139438
  }
138801
139439
  async function buildApprovalSummary(planPath, maxApprovalMs = MAX_APPROVAL_MS) {
138802
139440
  try {
138803
- const plan = JSON.parse(await readFile45(planPath, "utf8"));
139441
+ const plan = JSON.parse(await readFile46(planPath, "utf8"));
138804
139442
  const approvalWindowHours = deriveApprovalWindowHours(maxApprovalMs);
138805
139443
  const actions = (plan.externalActions ?? []).map((action) => ({
138806
139444
  id: action.id,
@@ -138865,19 +139503,19 @@ async function buildApprovalSummary(planPath, maxApprovalMs = MAX_APPROVAL_MS) {
138865
139503
  );
138866
139504
  }
138867
139505
  }
138868
- async function allUnclosedPostVerifyHooksUngated(state, postVerifyHooks, planVersion) {
138869
- if (!Array.isArray(postVerifyHooks) || postVerifyHooks.length === 0) return false;
138870
- let candidates = postVerifyHooks;
139506
+ async function allUnclosedPostVerifyHooksUngated(state, postVerifyHooks2, planVersion) {
139507
+ if (!Array.isArray(postVerifyHooks2) || postVerifyHooks2.length === 0) return false;
139508
+ let candidates = postVerifyHooks2;
138871
139509
  if (state.postVerify?.runPath) {
138872
139510
  try {
138873
- const record = JSON.parse(await readFile45(state.postVerify.runPath, "utf8"));
139511
+ const record = JSON.parse(await readFile46(state.postVerify.runPath, "utf8"));
138874
139512
  const checkpoints = Array.isArray(record.checkpoints) ? record.checkpoints : [];
138875
139513
  const closedIds = new Set(
138876
139514
  checkpoints.filter((cp4) => cp4?.actionType === "postpublish-hook" && (cp4.status === "succeeded" || cp4.status === "NO_CHANGE")).map((cp4) => cp4.actionId)
138877
139515
  );
138878
- candidates = postVerifyHooks.filter(({ hook, unitId }) => !closedIds.has(postPublishActionId({ planVersion, unitId, localId: hook.id })));
139516
+ candidates = postVerifyHooks2.filter(({ hook, unitId }) => !closedIds.has(postPublishActionId({ planVersion, unitId, localId: hook.id })));
138879
139517
  } catch {
138880
- candidates = postVerifyHooks;
139518
+ candidates = postVerifyHooks2;
138881
139519
  }
138882
139520
  }
138883
139521
  if (candidates.length === 0) return false;
@@ -139021,7 +139659,7 @@ async function advanceShip(options = {}, injected = {}) {
139021
139659
  });
139022
139660
  let frozenPlan = null;
139023
139661
  if (Array.isArray(state.selectedUnitIds) || deps.preflightGitTransports) {
139024
- frozenPlan = Array.isArray(state.selectedUnitIds) ? await readFrozenPlanForUnitScope(prepared.planPath, statePath, state.selectedUnitIds) : JSON.parse(await readFile45(prepared.planPath, "utf8"));
139662
+ frozenPlan = Array.isArray(state.selectedUnitIds) ? await readFrozenPlanForUnitScope(prepared.planPath, statePath, state.selectedUnitIds) : JSON.parse(await readFile46(prepared.planPath, "utf8"));
139025
139663
  }
139026
139664
  if (Array.isArray(state.selectedUnitIds)) {
139027
139665
  assertFrozenPlanUnitScope({
@@ -139118,7 +139756,7 @@ async function advanceShip(options = {}, injected = {}) {
139118
139756
  }
139119
139757
  let postVerifyRanThisCall = false;
139120
139758
  if (state.status === "PUBLISHED" || state.status === "NEEDS_MANUAL_ATTESTATIONS") {
139121
- const plan = JSON.parse(await readFile45(state.planPath, "utf8"));
139759
+ const plan = JSON.parse(await readFile46(state.planPath, "utf8"));
139122
139760
  const hasDistributeWork = normalizePostPublishView(plan).some((declaration) => (declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
139123
139761
  const needsDistribution = hasDistributeWork;
139124
139762
  if (needsDistribution && deps.distributeRelease) {
@@ -139173,15 +139811,6 @@ async function advanceShip(options = {}, injected = {}) {
139173
139811
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
139174
139812
  };
139175
139813
  await writeJsonAtomic(statePath, state);
139176
- if (verified.status === "VERIFIED") {
139177
- try {
139178
- state.postRelease = derivePostReleaseChecklist(plan);
139179
- } catch (error) {
139180
- state.postRelease = unavailablePostReleaseChecklist(plan, error);
139181
- }
139182
- state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
139183
- await writeJsonAtomic(statePath, state);
139184
- }
139185
139814
  } catch (error) {
139186
139815
  if (error?.code !== CONSUMER_VERIFICATION_DEFERRED) throw error;
139187
139816
  state = {
@@ -139192,8 +139821,8 @@ async function advanceShip(options = {}, injected = {}) {
139192
139821
  };
139193
139822
  await writeJsonAtomic(statePath, state);
139194
139823
  }
139195
- const postVerifyHooks = normalizePostPublishView(plan).flatMap((declaration) => declaration.hooks ?? []).filter((hook) => hook.phase === "postVerify");
139196
- if (state.status === "VERIFIED" && postVerifyHooks.length > 0 && deps.postVerifyRelease) {
139824
+ const postVerifyHooks2 = normalizePostPublishView(plan).flatMap((declaration) => declaration.hooks ?? []).filter((hook) => hook.phase === "postVerify");
139825
+ if (state.status === "VERIFIED" && postVerifyHooks2.length > 0 && deps.postVerifyRelease) {
139197
139826
  postVerifyRanThisCall = true;
139198
139827
  let postVerifyOutcome;
139199
139828
  try {
@@ -139227,7 +139856,7 @@ async function advanceShip(options = {}, injected = {}) {
139227
139856
  }
139228
139857
  const reentryApprovalPaths = options.postpublishApprovalPaths ?? [];
139229
139858
  if (state.status === "VERIFIED" && deps.postVerifyRelease && !postVerifyRanThisCall && (!state.postVerify || state.postVerify.status !== "DISTRIBUTED")) {
139230
- const reentryPlan = JSON.parse(await readFile45(state.planPath, "utf8"));
139859
+ const reentryPlan = JSON.parse(await readFile46(state.planPath, "utf8"));
139231
139860
  const reentryPostVerifyHooks = normalizePostPublishView(reentryPlan).flatMap((declaration) => (declaration.hooks ?? []).map((hook) => ({ hook, unitId: declaration.unitId }))).filter(({ hook }) => hook.phase === "postVerify");
139232
139861
  const approvallessRetryAllowed = reentryApprovalPaths.length === 0 && await allUnclosedPostVerifyHooksUngated(state, reentryPostVerifyHooks, reentryPlan.planVersion);
139233
139862
  if (reentryPostVerifyHooks.length > 0 && (reentryApprovalPaths.length > 0 || approvallessRetryAllowed)) {
@@ -139261,6 +139890,24 @@ async function advanceShip(options = {}, injected = {}) {
139261
139890
  await writeJsonAtomic(statePath, state);
139262
139891
  }
139263
139892
  }
139893
+ if (state.status === "VERIFIED") {
139894
+ const finalPlan = JSON.parse(await readFile46(state.planPath, "utf8"));
139895
+ const hasPostVerify = normalizePostPublishView(finalPlan).some((declaration) => (declaration.hooks ?? []).some((hook) => hook.phase === "postVerify"));
139896
+ const localFinishRunPath = hasPostVerify ? state.postVerify?.status === "DISTRIBUTED" ? state.postVerify.runPath : void 0 : state.verifyRunPath;
139897
+ let postRelease;
139898
+ try {
139899
+ postRelease = derivePostReleaseChecklist(finalPlan, {
139900
+ root,
139901
+ statePath,
139902
+ unitIds: state.selectedUnitIds,
139903
+ runPath: localFinishRunPath,
139904
+ postVerifyComplete: !hasPostVerify || state.postVerify?.status === "DISTRIBUTED"
139905
+ });
139906
+ } catch (error) {
139907
+ postRelease = unavailablePostReleaseChecklist(finalPlan, error);
139908
+ }
139909
+ return publicState2(state, { postRelease });
139910
+ }
139264
139911
  return publicState2(state);
139265
139912
  }
139266
139913
  var HOUR_MS, ACTION_TARGET_FIELDS;
@@ -140232,7 +140879,7 @@ var attest_exports = {};
140232
140879
  __export(attest_exports, {
140233
140880
  recordManualAttestation: () => recordManualAttestation
140234
140881
  });
140235
- import { readFile as readFile46 } from "node:fs/promises";
140882
+ import { readFile as readFile47 } from "node:fs/promises";
140236
140883
  import { resolve as resolve38, join as join39 } from "node:path";
140237
140884
  async function validateInstalledConsumerClosure({
140238
140885
  root,
@@ -140250,7 +140897,7 @@ async function validateInstalledConsumerClosure({
140250
140897
  ]);
140251
140898
  let plan;
140252
140899
  try {
140253
- plan = JSON.parse(await readFile46(planPath, "utf8"));
140900
+ plan = JSON.parse(await readFile47(planPath, "utf8"));
140254
140901
  } catch (error) {
140255
140902
  throw new ReleaseError(
140256
140903
  GATE_FAILED,
@@ -140324,7 +140971,7 @@ async function recordManualAttestation(options = {}, injected = {}) {
140324
140971
  const requirementPath = join39(authorityDir, descriptor.requirementFile);
140325
140972
  let requirement;
140326
140973
  try {
140327
- requirement = JSON.parse(await readFile46(requirementPath, "utf8"));
140974
+ requirement = JSON.parse(await readFile47(requirementPath, "utf8"));
140328
140975
  } catch (error) {
140329
140976
  throw new ReleaseError(
140330
140977
  GATE_FAILED,
@@ -140408,7 +141055,7 @@ var init_attest = __esm({
140408
141055
  });
140409
141056
 
140410
141057
  // src/artifacts/policy.mjs
140411
- import { readFile as readFile47 } from "node:fs/promises";
141058
+ import { readFile as readFile48 } from "node:fs/promises";
140412
141059
  import { join as join40 } from "node:path";
140413
141060
  import { createHash as createHash19 } from "node:crypto";
140414
141061
  function validateArtifactPolicy(policy) {
@@ -140508,7 +141155,7 @@ async function parseSafeYamlWithinRoot(root, policyPath) {
140508
141155
  const fullPath = join40(root, policyPath);
140509
141156
  let content;
140510
141157
  try {
140511
- content = await readFile47(fullPath, "utf8");
141158
+ content = await readFile48(fullPath, "utf8");
140512
141159
  } catch (err) {
140513
141160
  throw new ReleaseError(
140514
141161
  ARTIFACT_POLICY_INVALID,
@@ -140619,7 +141266,7 @@ var init_policy = __esm({
140619
141266
  });
140620
141267
 
140621
141268
  // src/artifacts/entry.mjs
140622
- import { lstat as lstat46, readdir as readdir30, readFile as readFile48 } from "node:fs/promises";
141269
+ import { lstat as lstat46, readdir as readdir30, readFile as readFile49 } from "node:fs/promises";
140623
141270
  import { join as join41 } from "node:path";
140624
141271
  import { promisify as promisify21 } from "node:util";
140625
141272
  import { execFile as execFile14 } from "node:child_process";
@@ -140664,7 +141311,7 @@ async function enumerateTreeEntries(root, dirPath, relBase) {
140664
141311
  const subEntries = await enumerateTreeEntries(root, absPath, relPath);
140665
141312
  entries.push(...subEntries);
140666
141313
  } else {
140667
- const content = await readFile48(absPath);
141314
+ const content = await readFile49(absPath);
140668
141315
  entries.push(
140669
141316
  Object.freeze({
140670
141317
  path: relPath,
@@ -140727,7 +141374,7 @@ async function readEntry({ root, path: path40, source = "worktree" } = {}) {
140727
141374
  { path: path40, nlink: st.nlink }
140728
141375
  );
140729
141376
  }
140730
- const content = await readFile48(absPath);
141377
+ const content = await readFile49(absPath);
140731
141378
  return Object.freeze({
140732
141379
  kind: "regular",
140733
141380
  path: path40,
@@ -140991,7 +141638,7 @@ var init_graph = __esm({
140991
141638
  });
140992
141639
 
140993
141640
  // src/artifacts/producer-registry.mjs
140994
- import { readFile as readFile49, readdir as readdir31, stat as stat19, mkdir as mkdir32, writeFile as writeFile14, rm as rm18 } from "node:fs/promises";
141641
+ import { readFile as readFile50, readdir as readdir31, stat as stat19, mkdir as mkdir32, writeFile as writeFile14, rm as rm18 } from "node:fs/promises";
140995
141642
  import { join as join42 } from "node:path";
140996
141643
  import { mkdtemp as mkdtemp13 } from "node:fs/promises";
140997
141644
  import { tmpdir as tmpdir11 } from "node:os";
@@ -141023,7 +141670,7 @@ async function createBuiltInProducerRegistry() {
141023
141670
  const lockfilePath = join42(new URL("../../..", import.meta.url).pathname, "pnpm-lock.yaml");
141024
141671
  let lockfileBytes;
141025
141672
  try {
141026
- lockfileBytes = await readFile49(lockfilePath);
141673
+ lockfileBytes = await readFile50(lockfilePath);
141027
141674
  } catch {
141028
141675
  lockfileBytes = Buffer.from("");
141029
141676
  }
@@ -141040,7 +141687,7 @@ async function createBuiltInProducerRegistry() {
141040
141687
  const allModuleBytes = await Promise.all(
141041
141688
  allModulePaths.map(async (p) => {
141042
141689
  try {
141043
- return await readFile49(p);
141690
+ return await readFile50(p);
141044
141691
  } catch {
141045
141692
  return Buffer.from("");
141046
141693
  }
@@ -141066,7 +141713,7 @@ async function readDirEntries(dirPath, relBase = "") {
141066
141713
  if (st.isDirectory()) {
141067
141714
  entries.push(...await readDirEntries(absPath, relPath));
141068
141715
  } else {
141069
- const content = await readFile49(absPath);
141716
+ const content = await readFile50(absPath);
141070
141717
  entries.push(Object.freeze({
141071
141718
  path: relPath,
141072
141719
  type: "blob",
@@ -141593,7 +142240,7 @@ var init_state = __esm({
141593
142240
  });
141594
142241
 
141595
142242
  // src/artifacts/artifact-plan.mjs
141596
- import { writeFile as writeFile15, mkdir as mkdir33, readFile as readFile50, rename as rename5, open as open14 } from "node:fs/promises";
142243
+ import { writeFile as writeFile15, mkdir as mkdir33, readFile as readFile51, rename as rename5, open as open14 } from "node:fs/promises";
141597
142244
  import { dirname as dirname23 } from "node:path";
141598
142245
  function assemblePlan({
141599
142246
  operation,
@@ -142282,7 +142929,7 @@ var init_adoption = __esm({
142282
142929
  // src/artifacts/inspect.mjs
142283
142930
  import { promisify as promisify24 } from "node:util";
142284
142931
  import { execFile as execFile17 } from "node:child_process";
142285
- import { readdir as readdir32, stat as stat20, readFile as readFile51 } from "node:fs/promises";
142932
+ import { readdir as readdir32, stat as stat20, readFile as readFile52 } from "node:fs/promises";
142286
142933
  import { join as join43 } from "node:path";
142287
142934
  async function hasNestedGitRoots(root) {
142288
142935
  try {
@@ -142554,7 +143201,7 @@ var init_inspect = __esm({
142554
143201
  });
142555
143202
 
142556
143203
  // src/artifacts/resolution.mjs
142557
- import { mkdir as mkdir34, open as open15, readFile as readFile52, stat as stat21, lstat as lstat47, chmod as chmod6 } from "node:fs/promises";
143204
+ import { mkdir as mkdir34, open as open15, readFile as readFile53, stat as stat21, lstat as lstat47, chmod as chmod6 } from "node:fs/promises";
142558
143205
  import { join as join44, resolve as resolve39, relative as relative31, isAbsolute as isAbsolute27, basename as basename15 } from "node:path";
142559
143206
  function decodeBuffer(value, label) {
142560
143207
  if (value == null) return null;
@@ -142886,7 +143533,7 @@ async function submitResolution({
142886
143533
  async function readAndValidateResolvedFile(resolvedPath) {
142887
143534
  let content;
142888
143535
  try {
142889
- content = await readFile52(resolvedPath);
143536
+ content = await readFile53(resolvedPath);
142890
143537
  } catch (err) {
142891
143538
  throw new ReleaseError(MISSING_PARAMETERS, `cannot read resolved file: ${err.message}`, { resolvedPath, cause: err.code });
142892
143539
  }
@@ -143054,7 +143701,7 @@ var artifacts_exports = {};
143054
143701
  __export(artifacts_exports, {
143055
143702
  runArtifactsCommand: () => runArtifactsCommand
143056
143703
  });
143057
- import { readFile as readFile53 } from "node:fs/promises";
143704
+ import { readFile as readFile54 } from "node:fs/promises";
143058
143705
  import { join as join45 } from "node:path";
143059
143706
  async function runArtifactsCommand({ subcommand, args: args2, root } = {}) {
143060
143707
  if (!VALID_SUBCOMMANDS.has(subcommand)) {
@@ -143183,7 +143830,7 @@ async function handleAdopt({ args: args2, root }) {
143183
143830
  { subcommand: "adopt" }
143184
143831
  );
143185
143832
  }
143186
- const planRaw = await readFile53(planPath, "utf8");
143833
+ const planRaw = await readFile54(planPath, "utf8");
143187
143834
  const plan = JSON.parse(planRaw);
143188
143835
  if (expectedDigest && plan.planDigest !== expectedDigest) {
143189
143836
  throw new ReleaseError(
@@ -143198,7 +143845,7 @@ async function handleAdopt({ args: args2, root }) {
143198
143845
  if (artifact.path) {
143199
143846
  const entry = await readEntry({ root, path: artifact.path, source: "worktree" });
143200
143847
  if (entry.kind === "regular") {
143201
- const bytes = await readFile53(join45(root, artifact.path));
143848
+ const bytes = await readFile54(join45(root, artifact.path));
143202
143849
  currentEntries.set(artifact.id, Object.freeze({ ...entry, bytes, content: bytes }));
143203
143850
  } else {
143204
143851
  currentEntries.set(artifact.id, entry);
@@ -143263,7 +143910,7 @@ async function handleBootstrap({ args: args2, root }) {
143263
143910
  { subcommand: "bootstrap" }
143264
143911
  );
143265
143912
  }
143266
- const planRaw = await readFile53(planPath, "utf8");
143913
+ const planRaw = await readFile54(planPath, "utf8");
143267
143914
  const adoptionPlan = JSON.parse(planRaw);
143268
143915
  const currentEntries = /* @__PURE__ */ new Map();
143269
143916
  for (const id of new Set((adoptionPlan.protectedHunks ?? []).map((h) => h.artifactId))) {
@@ -143275,12 +143922,12 @@ async function handleBootstrap({ args: args2, root }) {
143275
143922
  if (entry.kind !== "regular") {
143276
143923
  throw new ReleaseError("PLAN_STALE", `artifact is no longer a regular file: ${id}`, { id });
143277
143924
  }
143278
- const bytes = await readFile53(join45(root, artifactPath));
143925
+ const bytes = await readFile54(join45(root, artifactPath));
143279
143926
  currentEntries.set(id, Object.freeze({ ...entry, bytes, content: bytes }));
143280
143927
  }
143281
143928
  let replacementBytes;
143282
143929
  if (action === "replace" && replacementPath) {
143283
- replacementBytes = await readFile53(replacementPath);
143930
+ replacementBytes = await readFile54(replacementPath);
143284
143931
  }
143285
143932
  const updated = await discardBootstrapHunk({
143286
143933
  adoptionPlan,
@@ -143318,7 +143965,7 @@ async function handleResolve({ args: args2, root }) {
143318
143965
  { subcommand: "resolve" }
143319
143966
  );
143320
143967
  }
143321
- const planRaw = await readFile53(planPath, "utf8");
143968
+ const planRaw = await readFile54(planPath, "utf8");
143322
143969
  const plan = JSON.parse(planRaw);
143323
143970
  if (plan.planDigest !== expectedDigest) {
143324
143971
  throw new ReleaseError(
@@ -144136,7 +144783,7 @@ __export(route_exports, {
144136
144783
  resolvePreviousReleaseCommit: () => resolvePreviousReleaseCommit
144137
144784
  });
144138
144785
  import { execFileSync } from "node:child_process";
144139
- import { lstat as lstat48, readdir as readdir33, readFile as readFile54 } from "node:fs/promises";
144786
+ import { lstat as lstat48, readdir as readdir33, readFile as readFile55 } from "node:fs/promises";
144140
144787
  import { relative as relative32, resolve as resolve40, basename as basename16, dirname as dirname24, join as join46 } from "node:path";
144141
144788
  function classifyPath(path40) {
144142
144789
  if (typeof path40 !== "string" || path40.length === 0) return "ignore";
@@ -144223,7 +144870,7 @@ async function resolvePreviousReleaseCommit(root) {
144223
144870
  if (!file.endsWith(".json")) continue;
144224
144871
  let plan;
144225
144872
  try {
144226
- plan = JSON.parse(await readFile54(resolve40(plansDir, file), "utf8"));
144873
+ plan = JSON.parse(await readFile55(resolve40(plansDir, file), "utf8"));
144227
144874
  } catch {
144228
144875
  continue;
144229
144876
  }
@@ -144349,7 +144996,7 @@ async function readRunRouting(root, options = {}) {
144349
144996
  let authorityMissing = false;
144350
144997
  let markerlessRunDebris = false;
144351
144998
  try {
144352
- await readFile54(runPath);
144999
+ await readFile55(runPath);
144353
145000
  authorityExists = true;
144354
145001
  } catch (error) {
144355
145002
  if (error.code === "ENOENT") {
@@ -144504,7 +145151,7 @@ async function isMarkerlessRunDebris(runDirectory) {
144504
145151
  const evidenceFiles = await readDirectory(join46(runDirectory, "evidence", evidenceEntry.name));
144505
145152
  if (!evidenceFiles || evidenceFiles.length !== 1 || evidenceFiles[0].name !== "release-skill-install-evidence.json" || !evidenceFiles[0].isFile() || evidenceFiles[0].isSymbolicLink()) return false;
144506
145153
  try {
144507
- const evidenceRecord = JSON.parse(await readFile54(
145154
+ const evidenceRecord = JSON.parse(await readFile55(
144508
145155
  join46(runDirectory, "evidence", evidenceEntry.name, evidenceFiles[0].name),
144509
145156
  "utf8"
144510
145157
  ));
@@ -144589,8 +145236,8 @@ async function isProvenPreAuthorityFailure(runDir, runName) {
144589
145236
  let summary;
144590
145237
  let events;
144591
145238
  try {
144592
- summary = JSON.parse(await readFile54(summaryPath, "utf8"));
144593
- events = (await readFile54(evidencePath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
145239
+ summary = JSON.parse(await readFile55(summaryPath, "utf8"));
145240
+ events = (await readFile55(evidencePath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
144594
145241
  } catch {
144595
145242
  return false;
144596
145243
  }
@@ -144675,7 +145322,7 @@ async function readLegacyPostverifyRecovery(runPath) {
144675
145322
  if (run6.command !== "postverify" || run6.status !== "DISTRIBUTED") {
144676
145323
  throw new ReleaseError(GATE_FAILED, "legacy postverify status is not terminal DISTRIBUTED");
144677
145324
  }
144678
- const planRaw = await readFile54(run6.planPath, "utf8");
145325
+ const planRaw = await readFile55(run6.planPath, "utf8");
144679
145326
  const plan = JSON.parse(planRaw);
144680
145327
  validatePlan(plan);
144681
145328
  if (!plan.digest || plan.digest !== computePlanDigest(plan)) {
@@ -144706,7 +145353,7 @@ async function readLegacyPostverifyRecovery(runPath) {
144706
145353
  runPath: run6.sourceRunPath,
144707
145354
  production
144708
145355
  });
144709
- const summary = JSON.parse(await readFile54(join46(resolve40(runPath, ".."), "summary.json"), "utf8"));
145356
+ const summary = JSON.parse(await readFile55(join46(resolve40(runPath, ".."), "summary.json"), "utf8"));
144710
145357
  if (!summary || summary.status !== run6.status) {
144711
145358
  throw new ReleaseError(GATE_FAILED, "legacy postverify summary status does not match its sealed run status");
144712
145359
  }
@@ -145080,7 +145727,7 @@ Workflow Profiles:
145080
145727
  });
145081
145728
 
145082
145729
  // bin/release-skill-cli.mjs
145083
- import { readFile as readFile55 } from "node:fs/promises";
145730
+ import { readFile as readFile56 } from "node:fs/promises";
145084
145731
  import { basename as basename17, dirname as dirname25, join as join47, resolve as resolve41 } from "node:path";
145085
145732
  import { execFile as execFileCb22 } from "node:child_process";
145086
145733
  import { promisify as promisify26 } from "node:util";
@@ -145163,6 +145810,27 @@ async function exitAfterFlush(exitCode) {
145163
145810
  process.exit(exitCode);
145164
145811
  }
145165
145812
  __name(exitAfterFlush, "exitAfterFlush");
145813
+ function publicErrorDetails(error) {
145814
+ const details = error?.details;
145815
+ if (!details || typeof details !== "object" || Array.isArray(details)) return {};
145816
+ const output = {};
145817
+ if (details.cause && typeof details.cause === "object" && !Array.isArray(details.cause)) {
145818
+ const cause = {};
145819
+ if (typeof details.cause.code === "string") cause.code = details.cause.code;
145820
+ if (typeof details.cause.message === "string") cause.message = details.cause.message;
145821
+ if (Object.keys(cause).length > 0) output.cause = cause;
145822
+ }
145823
+ if (Array.isArray(details.nextSteps)) {
145824
+ const nextSteps = details.nextSteps.filter((step) => step && typeof step === "object" && !Array.isArray(step)).map((step) => ({
145825
+ ...typeof step.code === "string" ? { code: step.code } : {},
145826
+ ...typeof step.message === "string" ? { message: step.message } : {},
145827
+ ...Array.isArray(step.argv) && step.argv.every((arg) => typeof arg === "string") ? { argv: [...step.argv] } : {}
145828
+ })).filter((step) => Object.keys(step).length > 0);
145829
+ if (nextSteps.length > 0) output.nextSteps = nextSteps;
145830
+ }
145831
+ return output;
145832
+ }
145833
+ __name(publicErrorDetails, "publicErrorDetails");
145166
145834
  async function checkDependency(command2, versionArgs = ["--version"]) {
145167
145835
  try {
145168
145836
  const { stdout } = await execFile19(command2, versionArgs, {
@@ -145871,9 +146539,15 @@ if (command === "ship") {
145871
146539
  } else {
145872
146540
  console.log("Post-release: branch advancement was already included; skip the merge question.");
145873
146541
  }
145874
- if (result2.postRelease.localHostUpdate?.promptRequired) {
146542
+ const localHostUpdate = result2.postRelease.localHostUpdate;
146543
+ if (localHostUpdate?.available === true && typeof localHostUpdate.runPath === "string") {
145875
146544
  console.log(`Post-release: ask whether to update local host plugins (${result2.postRelease.localHostUpdate.hosts.join(", ")}).`);
145876
- console.log(`Post-release command: release-skill post-release --plan ${result2.planPath} --run ${result2.verifyRunPath}`);
146545
+ console.log(`Post-release command: release-skill post-release --plan ${result2.planPath} --run ${localHostUpdate.runPath}`);
146546
+ console.log("Choose --hosts before adding --update-local-hosts to perform a local update.");
146547
+ } else {
146548
+ for (const step of localHostUpdate?.nextSteps ?? []) {
146549
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(" ")})`);
146550
+ }
145877
146551
  }
145878
146552
  }
145879
146553
  for (const followUp of result2.manualFollowUps ?? []) {
@@ -146217,55 +146891,68 @@ if (command === "post-release") {
146217
146891
  const planPath = value("--plan") ? resolve41(value("--plan")) : void 0;
146218
146892
  const runPath = value("--run") ? resolve41(value("--run")) : void 0;
146219
146893
  if (!planPath || !runPath) {
146220
- const message = "post-release requires --plan <path> and --run <verified-run-path>";
146894
+ const message = "post-release requires --plan <path> and --run <verify-or-postverify-run-path>";
146221
146895
  if (hasJson) console.log(JSON.stringify({ error: "MISSING_PARAMETERS", message, exitCode: 1 }));
146222
146896
  else console.error(`Error: ${message}`);
146223
146897
  await exitAfterFlush(1);
146224
146898
  }
146225
146899
  try {
146226
146900
  const {
146227
- assertVerifiedReleaseRun: assertVerifiedReleaseRun2,
146228
146901
  derivePostReleaseChecklist: derivePostReleaseChecklist2,
146229
146902
  updateLocalHostPlugins: updateLocalHostPlugins2
146230
146903
  } = await init_post_release_local().then(() => post_release_local_exports);
146231
146904
  const { validatePlan: validatePlan2 } = await init_plan().then(() => plan_exports);
146232
146905
  const {
146233
146906
  loadRun: loadRun2,
146234
- resolveRunPath: resolveRunPath2,
146235
- validateRunLineage: validateRunLineage2
146907
+ resolveRunPath: resolveRunPath2
146236
146908
  } = await init_run().then(() => run_exports);
146237
- const plan = JSON.parse(await readFile55(planPath, "utf8"));
146909
+ const plan = JSON.parse(await readFile56(planPath, "utf8"));
146238
146910
  validatePlan2(plan);
146911
+ const updateRequested = args.includes("--update-local-hosts");
146239
146912
  const resolvedRunPath = await resolveRunPath2(runPath);
146240
146913
  const runRecord = await loadRun2(resolvedRunPath, {
146241
146914
  requireDigest: true,
146242
146915
  authorityPlanPath: planPath
146243
146916
  });
146244
- await validateRunLineage2(runRecord, {
146245
- plan,
146246
- planPath,
146247
- runPath: resolvedRunPath,
146248
- production: Boolean(plan.production)
146249
- });
146250
- assertVerifiedReleaseRun2(plan, runRecord);
146251
- const updateRequested = args.includes("--update-local-hosts");
146917
+ if (!updateRequested) {
146918
+ const { assertLocalFinishRun: assertLocalFinishRun2 } = await init_post_release_local().then(() => post_release_local_exports);
146919
+ await assertLocalFinishRun2({
146920
+ plan,
146921
+ planPath,
146922
+ runPath: resolvedRunPath,
146923
+ runRecord,
146924
+ production: Boolean(plan.production),
146925
+ root: resolve41(value("--root") ?? process.cwd())
146926
+ });
146927
+ }
146252
146928
  const hostsIndex = args.indexOf("--hosts");
146253
146929
  const rawHosts = hostsIndex !== -1 && args[hostsIndex + 1] && !args[hostsIndex + 1].startsWith("--") ? args[hostsIndex + 1] : "";
146254
146930
  const selectedHosts = rawHosts.split(",").map((host) => host.trim()).filter(Boolean);
146255
146931
  const result2 = updateRequested ? await updateLocalHostPlugins2({
146256
- plan,
146932
+ planPath,
146933
+ runPath: resolvedRunPath,
146257
146934
  root: resolve41(value("--root") ?? process.cwd()),
146258
146935
  confirmPlanDigest: value("--confirm-plan"),
146259
146936
  selectedHosts
146260
- }) : derivePostReleaseChecklist2(plan);
146937
+ }) : derivePostReleaseChecklist2(plan, {
146938
+ runPath: runRecord.command === "postverify" && runRecord.status === "DISTRIBUTED" ? resolvedRunPath : runRecord.command === "verify" ? resolvedRunPath : void 0,
146939
+ root: resolve41(value("--root") ?? process.cwd()),
146940
+ postVerifyComplete: runRecord.command === "postverify" && runRecord.status === "DISTRIBUTED"
146941
+ });
146261
146942
  if (hasJson) {
146262
146943
  console.log(JSON.stringify(result2, null, 2));
146263
146944
  } else if (!updateRequested) {
146264
146945
  console.log(`Post-release status: ${result2.status}`);
146265
146946
  if (result2.merge.promptRequired) console.log("Ask whether the user wants to merge the remaining branch.");
146266
146947
  else console.log("Branch advancement was already included in the release workflow; skip the merge question.");
146267
- if (result2.localHostUpdate.promptRequired) {
146948
+ if (result2.localHostUpdate.available === true && typeof result2.localHostUpdate.runPath === "string") {
146268
146949
  console.log(`Ask whether to update local host plugins: ${result2.localHostUpdate.hosts.join(", ")}`);
146950
+ console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result2.localHostUpdate.runPath}`);
146951
+ console.log("Choose --hosts before adding --update-local-hosts to perform a local update.");
146952
+ } else {
146953
+ for (const step of result2.localHostUpdate.nextSteps ?? []) {
146954
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(" ")})`);
146955
+ }
146269
146956
  }
146270
146957
  } else {
146271
146958
  console.log(`Local host update: ${result2.status}`);
@@ -146281,9 +146968,15 @@ if (command === "post-release") {
146281
146968
  console.log(JSON.stringify({
146282
146969
  error: err.code ?? "POST_RELEASE_FAILED",
146283
146970
  message: err.message,
146971
+ details: publicErrorDetails(err),
146284
146972
  exitCode: err.exitCode ?? 1
146285
146973
  }));
146286
- } else console.error(`Error: ${err.message}`);
146974
+ } else {
146975
+ console.error(`Error: ${err.message}`);
146976
+ for (const step of err.details?.nextSteps ?? []) {
146977
+ console.error(`Next [${step.code}] ${step.message} (${step.argv.join(" ")})`);
146978
+ }
146979
+ }
146287
146980
  await exitAfterFlush(err.exitCode ?? 1);
146288
146981
  }
146289
146982
  }
@@ -146330,9 +147023,9 @@ if (command === "verify") {
146330
147023
  derivePostReleaseChecklist: derivePostReleaseChecklist2,
146331
147024
  unavailablePostReleaseChecklist: unavailablePostReleaseChecklist2
146332
147025
  } = await init_post_release_local().then(() => post_release_local_exports);
146333
- const plan = JSON.parse(await readFile55(planPath, "utf8"));
147026
+ const plan = JSON.parse(await readFile56(planPath, "utf8"));
146334
147027
  try {
146335
- result2.postRelease = derivePostReleaseChecklist2(plan);
147028
+ result2.postRelease = derivePostReleaseChecklist2(plan, { root, runPath: result2.runPath });
146336
147029
  } catch (error) {
146337
147030
  result2.postRelease = unavailablePostReleaseChecklist2(plan, error);
146338
147031
  }
@@ -146357,8 +147050,14 @@ if (command === "verify") {
146357
147050
  } else if (result2.postRelease?.merge.promptRequired) {
146358
147051
  console.log("Post-release: ask whether to merge the remaining branch.");
146359
147052
  }
146360
- if (result2.postRelease?.localHostUpdate.promptRequired) {
147053
+ if (result2.postRelease?.localHostUpdate?.available === true && typeof result2.postRelease.localHostUpdate.runPath === "string") {
146361
147054
  console.log(`Post-release: ask whether to update local host plugins (${result2.postRelease.localHostUpdate.hosts.join(", ")}).`);
147055
+ console.log(`Post-release command: release-skill post-release --plan ${planPath} --run ${result2.postRelease.localHostUpdate.runPath}`);
147056
+ console.log("Choose --hosts before adding --update-local-hosts to perform a local update.");
147057
+ } else {
147058
+ for (const step of result2.postRelease?.localHostUpdate?.nextSteps ?? []) {
147059
+ console.log(`Next [${step.code}] ${step.message} (${step.argv.join(" ")})`);
147060
+ }
146362
147061
  }
146363
147062
  }
146364
147063
  await exitAfterFlush(result2.status === "VERIFIED" ? 0 : 1);
@@ -146367,6 +147066,7 @@ if (command === "verify") {
146367
147066
  const errOutput = {
146368
147067
  error: err.code ?? "UNKNOWN_ERROR",
146369
147068
  message: err.message,
147069
+ details: err.details ?? {},
146370
147070
  exitCode: err.exitCode ?? 1
146371
147071
  };
146372
147072
  console.log(JSON.stringify(errOutput));