release-skill 0.9.6 → 0.9.8

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 (38) hide show
  1. package/.agents/plugins/marketplace.json +9 -0
  2. package/.claude-plugin/marketplace.json +11 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codebuddy-plugin/plugin.json +1 -1
  5. package/.codex-plugin/plugin.json +2 -2
  6. package/.kimi-plugin/plugin.json +1 -1
  7. package/CHANGELOG.md +55 -0
  8. package/INSTALL.md +17 -9
  9. package/INSTALL.zh-CN.md +15 -9
  10. package/README.md +33 -28
  11. package/README.zh-CN.md +28 -26
  12. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  13. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  14. package/adapters/claude/bin/release-skill.bundle.mjs +357 -52
  15. package/adapters/claude/skills/release-finish/SKILL.md +12 -4
  16. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  17. package/adapters/codex/bin/release-skill.bundle.mjs +357 -52
  18. package/adapters/codex/skills/release-finish/SKILL.md +12 -4
  19. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  20. package/adapters/kimi/bin/release-skill.bundle.mjs +357 -52
  21. package/adapters/kimi/skills/release-finish/SKILL.md +12 -4
  22. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  23. package/adapters/workbuddy/bin/release-skill.bundle.mjs +357 -52
  24. package/adapters/workbuddy/skills/release-finish/SKILL.md +12 -4
  25. package/bin/release-skill.bundle.mjs +357 -52
  26. package/package.json +1 -1
  27. package/platform-manifest.json +4 -4
  28. package/skills/release-finish/SKILL.md +12 -4
  29. package/skills-src/release-finish/SKILL.md +12 -4
  30. package/src/commands/post-release-local.mjs +274 -39
  31. package/src/commands/prepare.mjs +5 -3
  32. package/src/commands/publish.mjs +30 -3
  33. package/src/commands/ship.mjs +4 -6
  34. package/src/commands/verify.mjs +22 -15
  35. package/src/core/postpublish.mjs +24 -0
  36. package/src/core/recovery.mjs +8 -6
  37. package/src/core/skill-resource-closure.mjs +19 -1
  38. package/src/core/surface-host-bindings.mjs +35 -0
@@ -9,9 +9,9 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
9
9
  // Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
10
10
  const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
11
11
  // Package identity injected at build time — closure-independent --version probe.
12
- const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.6"});
12
+ const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.8"});
13
13
  // Build-time source digest for the BUNDLE_STALE freshness gate (see above).
14
- const __bundleSourceDigest = "8fd8e6c5f7796efe9e1f7153382b50c74d78a4b5f836f872837ff2f11e4daeb5";
14
+ const __bundleSourceDigest = "7fa9e4b55133e888e3f3ace288f0194eb1ac063c0616252d2dda8e58be7eee21";
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -45592,6 +45592,9 @@ function normalizePostPublishView(plan) {
45592
45592
  { planVersion: version }
45593
45593
  );
45594
45594
  }
45595
+ function requiresPostPublishDistribution(plan) {
45596
+ return normalizePostPublishView(plan).some((declaration) => (declaration.targets?.length ?? 0) > 0 || (declaration.hooks ?? []).some((hook) => hook.phase === void 0 || hook.phase === "distribute"));
45597
+ }
45595
45598
  function validatePostPublishHookIdUniqueness(declarations) {
45596
45599
  const explicitHookIdOwner = /* @__PURE__ */ new Map();
45597
45600
  for (const declaration of declarations ?? []) {
@@ -45759,6 +45762,7 @@ var init_postpublish = __esm({
45759
45762
  __name(validatePostPublishHookEntry, "validatePostPublishHookEntry");
45760
45763
  __name(validatePostPublishDeclaration, "validatePostPublishDeclaration");
45761
45764
  __name(normalizePostPublishView, "normalizePostPublishView");
45765
+ __name(requiresPostPublishDistribution, "requiresPostPublishDistribution");
45762
45766
  __name(validatePostPublishHookIdUniqueness, "validatePostPublishHookIdUniqueness");
45763
45767
  __name(postPublishActionId, "postPublishActionId");
45764
45768
  __name(orderTargetsByDependency, "orderTargetsByDependency");
@@ -109520,9 +109524,27 @@ async function checkSkillResourceClosure({
109520
109524
  result2.receiptDigest = digestDocument(receiptProjection(result2));
109521
109525
  return result2;
109522
109526
  }
109523
- function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces) {
109527
+ function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces, coverageClaims = []) {
109524
109528
  const missing = [];
109529
+ const observedById = new Map((surfaces ?? []).map((surface) => [surface.id, surface]));
109530
+ const claimsByHost = /* @__PURE__ */ new Map();
109531
+ for (const claim of coverageClaims ?? []) {
109532
+ const hostClaims = claimsByHost.get(claim.host) ?? [];
109533
+ hostClaims.push(claim);
109534
+ claimsByHost.set(claim.host, hostClaims);
109535
+ }
109525
109536
  for (const expectedHost of [...new Set(expectedHosts ?? [])].sort((a, b) => a.localeCompare(b))) {
109537
+ const declaredClaims = claimsByHost.get(expectedHost) ?? [];
109538
+ if (declaredClaims.length > 0) {
109539
+ const missingClaim = declaredClaims.find((claim) => {
109540
+ const surface3 = observedById.get(claim.surfaceId);
109541
+ return !surface3 || !(surface3.skillCount >= 1);
109542
+ });
109543
+ if (!missingClaim) continue;
109544
+ const surface2 = observedById.get(missingClaim.surfaceId);
109545
+ missing.push({ host: expectedHost, skillCount: surface2?.skillCount ?? 0 });
109546
+ continue;
109547
+ }
109526
109548
  const surface = (surfaces ?? []).find((item) => item.host === expectedHost);
109527
109549
  if (!surface || !(surface.skillCount >= 1)) {
109528
109550
  missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
@@ -112496,6 +112518,23 @@ async function deriveSurfaceHostBinding({ manifest, pluginRoot, platform, snapsh
112496
112518
  const host = await normalizeHostId(platform.buildAdapter.name);
112497
112519
  return { surfaceId, host };
112498
112520
  }
112521
+ function groupSurfaceHostBindings(claims = []) {
112522
+ if (!Array.isArray(claims)) {
112523
+ throw new TypeError("surfaceHostBindings must be an array");
112524
+ }
112525
+ const bySurface = /* @__PURE__ */ new Map();
112526
+ for (const claim of claims) {
112527
+ if (!claim || typeof claim !== "object" || typeof claim.surfaceId !== "string" || typeof claim.host !== "string") {
112528
+ throw new TypeError("surfaceHostBindings entries must be objects with string surfaceId and host");
112529
+ }
112530
+ const existing = bySurface.get(claim.surfaceId) ?? [];
112531
+ existing.push({ surfaceId: claim.surfaceId, host: claim.host });
112532
+ bySurface.set(claim.surfaceId, existing);
112533
+ }
112534
+ const coverageClaims = [...bySurface.values()].flat().sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
112535
+ const checkerBindings = [...bySurface.values()].filter((surfaceClaims) => surfaceClaims.length === 1).map(([claim]) => claim).sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
112536
+ return { coverageClaims, checkerBindings };
112537
+ }
112499
112538
  var SKILLS_NORMALIZERS;
112500
112539
  var init_surface_host_bindings = __esm({
112501
112540
  async "src/core/surface-host-bindings.mjs"() {
@@ -112513,6 +112552,7 @@ var init_surface_host_bindings = __esm({
112513
112552
  __name(pluginRootFromManifestRelativePath, "pluginRootFromManifestRelativePath");
112514
112553
  __name(assertDeclaredSkillsDirExists, "assertDeclaredSkillsDirExists");
112515
112554
  __name(deriveSurfaceHostBinding, "deriveSurfaceHostBinding");
112555
+ __name(groupSurfaceHostBindings, "groupSurfaceHostBindings");
112516
112556
  }
112517
112557
  });
112518
112558
 
@@ -113321,8 +113361,7 @@ async function verifyRelease(options) {
113321
113361
  { sourceRunStatus: sourceRun.status }
113322
113362
  );
113323
113363
  }
113324
- const postPublishDeclarations = normalizePostPublishView(plan);
113325
- const requiresDistribution = postPublishDeclarations.some((declaration) => (declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
113364
+ const requiresDistribution = requiresPostPublishDistribution(plan);
113326
113365
  if (requiresDistribution) {
113327
113366
  await evidence.append({ phase: "verify", step: "distribute-run-discovery", status: "started" });
113328
113367
  const distributeCandidates = await discoverDistributeRuns({ planPath, plan }) ?? [];
@@ -113943,11 +113982,16 @@ async function verifyRelease(options) {
113943
113982
  const unit = (plan.units ?? []).find((u) => u.id === unitId);
113944
113983
  const dist = unit?.distributions?.find((d) => d.type === platform.distributionType);
113945
113984
  let binding = null;
113985
+ let frozenPluginRoot = null;
113946
113986
  const contract2 = dist?.installationContract;
113947
113987
  if (contract2?.normalizedManifest) {
113988
+ frozenPluginRoot = pluginRootFromManifestRelativePath(contract2.manifestRelativePath);
113948
113989
  binding = await deriveSurfaceHostBinding({
113949
113990
  manifest: contract2.normalizedManifest,
113950
- pluginRoot: pluginRootFromManifestRelativePath(contract2.manifestRelativePath),
113991
+ // Adapter observations are already rooted at the installed
113992
+ // plugin. The frozen manifest's skills path is therefore
113993
+ // relative to '.', not to the snapshot-root manifest path.
113994
+ pluginRoot: ".",
113951
113995
  platform,
113952
113996
  snapshotDir: check.observation.installPath
113953
113997
  });
@@ -113960,6 +114004,7 @@ async function verifyRelease(options) {
113960
114004
  unitId,
113961
114005
  surfaceId: binding?.surfaceId ?? ".",
113962
114006
  binding,
114007
+ frozenPluginRoot,
113963
114008
  unit
113964
114009
  });
113965
114010
  }
@@ -114042,7 +114087,7 @@ async function verifyRelease(options) {
114042
114087
  );
114043
114088
  }
114044
114089
  const pluginDistCount = (surface.unit?.distributions ?? []).filter((d) => d.type !== "npm").length;
114045
- if (pluginDistCount === 1) {
114090
+ if (pluginDistCount === 1 && surface.frozenPluginRoot === ".") {
114046
114091
  const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
114047
114092
  unitId: surface.unitId,
114048
114093
  preparedAt: expectedUnitReceipt.preparedAt ?? null,
@@ -114505,7 +114550,7 @@ async function readRunRecovery(runPath, options = {}) {
114505
114550
  }
114506
114551
  } else if (["publish", "reconcile"].includes(command2)) {
114507
114552
  if (run6.status === "PUBLISHED") {
114508
- const needsDistribution = normalizePostPublishView(plan).some((declaration) => (declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
114553
+ const needsDistribution = requiresPostPublishDistribution(plan);
114509
114554
  code = needsDistribution ? "DISTRIBUTE" : "VERIFY";
114510
114555
  } else if (run6.status === "PARTIAL") {
114511
114556
  code = "RECONCILE";
@@ -129636,10 +129681,11 @@ async function runPrepareSkillResourceClosureGate({
129636
129681
  });
129637
129682
  if (binding) surfaceHostBindings.push(binding);
129638
129683
  }
129684
+ const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
129639
129685
  const closureResult = await checkSkillResourceClosure({
129640
129686
  snapshotDir: manifest.outputDir,
129641
129687
  host: "root",
129642
- surfaceHostBindings
129688
+ surfaceHostBindings: checkerBindings
129643
129689
  });
129644
129690
  const receipt = createSkillResourceClosureReceipt(closureResult, {
129645
129691
  unitId: unit.id,
@@ -129684,7 +129730,8 @@ async function runPrepareSkillResourceClosureGate({
129684
129730
  }
129685
129731
  const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
129686
129732
  expectedHosts,
129687
- closureResult.surfaces
129733
+ closureResult.surfaces,
129734
+ coverageClaims
129688
129735
  );
129689
129736
  if (!hostCoverage.passed) {
129690
129737
  await evidence.append({
@@ -129708,7 +129755,7 @@ async function runPrepareSkillResourceClosureGate({
129708
129755
  }
129709
129756
  );
129710
129757
  }
129711
- for (const binding of surfaceHostBindings) {
129758
+ for (const binding of coverageClaims) {
129712
129759
  const boundSurface = closureResult.surfaces.find(
129713
129760
  (surface) => surface.id === binding.surfaceId
129714
129761
  );
@@ -131770,7 +131817,7 @@ function hostEnvironment(host, { kimiHome } = {}) {
131770
131817
  env.CODEBUDDY_CONFIG_DIR = join27(env.HOME, ".workbuddy");
131771
131818
  env.WORKBUDDY_CONFIG_DIR = join27(env.HOME, ".workbuddy");
131772
131819
  }
131773
- if (host === "kimi" && kimiHome) env.KIMI_CONFIG_DIR = kimiHome;
131820
+ if (host === "kimi" && kimiHome) env.KIMI_CODE_HOME = kimiHome;
131774
131821
  return env;
131775
131822
  }
131776
131823
  async function defaultRun(command2, args2, options = {}) {
@@ -131947,6 +131994,64 @@ function exactPluginObservation(target, stdout) {
131947
131994
  function normalizeGitSource(source) {
131948
131995
  return String(source ?? "").replace(/^git\+/, "").replace(/^https:\/\/github\.com\//, "").replace(/^git@github\.com:/, "").replace(/\.git$/u, "").replace(/\/$/u, "");
131949
131996
  }
131997
+ function parseFrozenRemoteRef(target, stdout) {
131998
+ const directRefs = /* @__PURE__ */ new Map();
131999
+ const peeledRefs = /* @__PURE__ */ new Map();
132000
+ const expected = target.marketplaceRef;
132001
+ const allowedDirect = expected.startsWith("refs/") ? /* @__PURE__ */ new Set([expected]) : /* @__PURE__ */ new Set([`refs/heads/${expected}`, `refs/tags/${expected}`]);
132002
+ for (const line of String(stdout ?? "").split(/\r?\n/u)) {
132003
+ if (line.length === 0) continue;
132004
+ const match = /^([a-f0-9]{40})\t([^\s]+)$/u.exec(line);
132005
+ if (!match) throw new Error("git ls-remote returned an invalid line");
132006
+ const [, commit, remoteRef] = match;
132007
+ const peeled = remoteRef.endsWith("^{}");
132008
+ const direct = peeled ? remoteRef.slice(0, -3) : remoteRef;
132009
+ if (!allowedDirect.has(direct)) {
132010
+ throw new Error(`git ls-remote returned an unexpected ref ${remoteRef}`);
132011
+ }
132012
+ const destination = peeled ? peeledRefs : directRefs;
132013
+ const previous = destination.get(direct);
132014
+ if (previous && previous !== commit) {
132015
+ throw new Error(`git ls-remote returned conflicting values for ${remoteRef}`);
132016
+ }
132017
+ destination.set(direct, commit);
132018
+ }
132019
+ const resolved = [...allowedDirect].filter((remoteRef) => directRefs.has(remoteRef) || peeledRefs.has(remoteRef)).map((remoteRef) => peeledRefs.get(remoteRef) ?? directRefs.get(remoteRef));
132020
+ return {
132021
+ found: resolved.length > 0,
132022
+ exact: resolved.length > 0 && resolved.every((commit) => commit === target.marketplaceCommit)
132023
+ };
132024
+ }
132025
+ async function preflightStructuredMarketplace(target, env, run6) {
132026
+ try {
132027
+ const observed = await run6("git", [
132028
+ "ls-remote",
132029
+ "--exit-code",
132030
+ `https://${target.githubHost}/${target.marketplaceRepo}.git`,
132031
+ target.marketplaceRef,
132032
+ `${target.marketplaceRef}^{}`
132033
+ ], { env, timeout: 3e4 });
132034
+ const remote = parseFrozenRemoteRef(target, observed.stdout);
132035
+ if (!remote.found) {
132036
+ return {
132037
+ ok: false,
132038
+ reason: `${target.host} frozen marketplace ref ${target.marketplaceRef} is missing`
132039
+ };
132040
+ }
132041
+ if (!remote.exact) {
132042
+ return {
132043
+ ok: false,
132044
+ reason: `${target.host} frozen marketplace ref ${target.marketplaceRef} does not resolve to ${target.marketplaceCommit}`
132045
+ };
132046
+ }
132047
+ return { ok: true };
132048
+ } catch (error) {
132049
+ return {
132050
+ ok: false,
132051
+ reason: `${target.host} could not prove the frozen marketplace ref: ${error?.message ?? String(error)}`
132052
+ };
132053
+ }
132054
+ }
131950
132055
  async function observeMarketplace(target, command2, env, run6) {
131951
132056
  const listed = await run6(command2, ["plugin", "marketplace", "list", "--json"], { env });
131952
132057
  const parsed = parseJson2(listed.stdout, `${target.host} marketplace list`);
@@ -132132,6 +132237,10 @@ async function runStructuredUpdate(target, detected, run6, {
132132
132237
  });
132133
132238
  return { status: "ALREADY_CURRENT", version: target.version };
132134
132239
  }
132240
+ if (!before.marketplace.exact) {
132241
+ const preflight = await preflightStructuredMarketplace(target, env, run6);
132242
+ if (!preflight.ok) return { status: "MANUAL_REQUIRED", reason: preflight.reason };
132243
+ }
132135
132244
  const marketplaceRebound = await bindStructuredMarketplace(target, command2, env, run6, before);
132136
132245
  const current = target.host === "claude" && marketplaceRebound ? await observeStructuredTarget(target, command2, env, run6) : before;
132137
132246
  const platform = getPlatform(target.host);
@@ -132185,48 +132294,204 @@ function parseCodeBuddyRemoteObservation(target, stdout) {
132185
132294
  };
132186
132295
  }
132187
132296
  function kimiExpectProgram({ removePlugin } = {}) {
132188
- const removeCommand = removePlugin ? `send -- "/plugins remove $removePlugin\\r"
132297
+ const removeCommand = removePlugin ? `submitCommand "/plugins remove $removePlugin"
132189
132298
  expect {
132190
132299
  -nocase -re {(remove|delete|uninstall).*(confirm|sure)|(confirm|sure).*(remove|delete|uninstall)} {
132191
132300
  expect {
132192
- -ex $removePlugin { send -- "\\033\\[B\\r" }
132193
- timeout { exit 95 }
132194
- eof { exit 96 }
132301
+ -ex $removePlugin {
132302
+ send -- "\\033\\[B"
132303
+ send -- "\\033\\[13u"
132304
+ }
132305
+ timeout { failTimeout remove-confirmation 125 127 }
132306
+ eof { failEof remove-confirmation 126 }
132307
+ }
132308
+ expect {
132309
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132310
+ -re $promptPattern {}
132311
+ timeout { failTimeout remove-prompt 128 130 }
132312
+ eof { failEof remove-prompt 129 }
132195
132313
  }
132196
- expect -re $promptPattern
132197
132314
  }
132198
132315
  -re $promptPattern {}
132199
- timeout { exit 93 }
132200
- eof { exit 94 }
132316
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132317
+ timeout { failTimeout remove-dialog 122 124 }
132318
+ eof { failEof remove-dialog 123 }
132201
132319
  }
132202
132320
  ` : "";
132203
- return `set timeout 180
132204
- foreach variable {RELEASE_SKILL_KIMI_COMMAND RELEASE_SKILL_KIMI_INSTALL_URL RELEASE_SKILL_KIMI_REMOVE_PLUGIN} {
132321
+ return `set timeout 240
132322
+ foreach variable {RELEASE_SKILL_KIMI_COMMAND RELEASE_SKILL_KIMI_INSTALL_URL RELEASE_SKILL_KIMI_REMOVE_PLUGIN RELEASE_SKILL_KIMI_EXPECTED_REPO RELEASE_SKILL_KIMI_EXPECTED_TAG} {
132205
132323
  if {![info exists env($variable)]} { exit 90 }
132206
132324
  }
132207
132325
  set kimiCommand $env(RELEASE_SKILL_KIMI_COMMAND)
132208
132326
  set installUrl $env(RELEASE_SKILL_KIMI_INSTALL_URL)
132209
132327
  set removePlugin $env(RELEASE_SKILL_KIMI_REMOVE_PLUGIN)
132210
- set promptPattern {(?:(?:^|\\r|\\n)> |(?:^|\\r|\\n)(?:\\033\\[[0-9;?]*[ -/]*[@-~])*\u2502[ \\t]*>[ \\t]*\u2502(?:\\033\\[[0-9;?]*[ -/]*[@-~])*)(?![^\\n]|\\n)}
132328
+ set expectedRepo $env(RELEASE_SKILL_KIMI_EXPECTED_REPO)
132329
+ set expectedTag $env(RELEASE_SKILL_KIMI_EXPECTED_TAG)
132330
+ set promptPattern {(?:(?:^|\\r|\\n)> (?:\\r*\\n|$)|(?:^|\\r|\\n)(?:(?:\\033\\[[0-9;?]*[ -/]*[@-~])|\\033\\][^\\x07]*\\x07|[ \\t])*\u2502[^\\r\\n]*>[^\\r\\n]*\u2502(?:(?:\\033\\[[0-9;?]*[ -/]*[@-~])|\\033\\][^\\x07]*\\x07|[ \\t])*(?:\\r*\\n|$))}
132331
+
132332
+ proc cleanScreen {value} {
132333
+ regsub -all {\\033\\[[0-9;?]*[ -/]*[@-~]} $value {} value
132334
+ regsub -all {\\033\\][^\\x07]*\\x07} $value {} value
132335
+ regsub -all {\\r} $value {} value
132336
+ return $value
132337
+ }
132338
+
132339
+ proc compactScreen {value} {
132340
+ set value [cleanScreen $value]
132341
+ regsub -all {[[:space:]]+} $value {} value
132342
+ return [string tolower $value]
132343
+ }
132344
+
132345
+ proc extractPluginTrustDialog {value} {
132346
+ set cleaned [cleanScreen $value]
132347
+ set lowered [string tolower $cleaned]
132348
+ set dialogStart -1
132349
+ foreach marker {"install third-party plugin " "trust and install from "} {
132350
+ set markerStart [string last $marker $lowered]
132351
+ if {$markerStart > $dialogStart} { set dialogStart $markerStart }
132352
+ }
132353
+ if {$dialogStart < 0} { return "" }
132354
+ return [string range $cleaned $dialogStart end]
132355
+ }
132356
+
132357
+ proc failTimeout {state timeoutCode unknownCode} {
132358
+ global expect_out
132359
+ set buffer ""
132360
+ if {[info exists expect_out(buffer)]} { set buffer [string trim [cleanScreen $expect_out(buffer)]] }
132361
+ if {$buffer ne ""} {
132362
+ puts stderr "KIMI_TUI_STATE:$state:unknown"
132363
+ exit $unknownCode
132364
+ }
132365
+ puts stderr "KIMI_TUI_STATE:$state:timeout"
132366
+ exit $timeoutCode
132367
+ }
132368
+
132369
+ proc failEof {state code} {
132370
+ puts stderr "KIMI_TUI_STATE:$state:eof"
132371
+ exit $code
132372
+ }
132373
+
132374
+ proc directoryTrust {} {
132375
+ puts stderr "KIMI_TUI_STATE:directory-trust:manual-required"
132376
+ exit 80
132377
+ }
132378
+
132379
+ proc submitCommand {command} {
132380
+ send -- "\\033\\[200~"
132381
+ send -- $command
132382
+ send -- "\\033\\[201~"
132383
+ send -- "\\033\\[13u"
132384
+ }
132385
+
132211
132386
  spawn $kimiCommand
132212
- expect -re $promptPattern
132213
- ${removeCommand}send -- "/plugins install $installUrl\\r"
132387
+ if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]} {
132388
+ puts stderr "KIMI_TUI_STATE:terminal-size:failed"
132389
+ exit 131
132390
+ }
132214
132391
  expect {
132215
- -nocase -re {trust and install} {
132216
- expect {
132217
- -ex $installUrl { send -- "\\033\\[B\\r" }
132218
- timeout { exit 97 }
132219
- eof { exit 98 }
132392
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132393
+ -re $promptPattern {}
132394
+ timeout { failTimeout initial-prompt 101 103 }
132395
+ eof { failEof initial-prompt 102 }
132396
+ }
132397
+ ${removeCommand}submitCommand "/plugins install $installUrl"
132398
+ set dialogBuffer ""
132399
+ expect {
132400
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132401
+ -nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
132402
+ append dialogBuffer $expect_out(buffer)
132403
+ }
132404
+ timeout { failTimeout plugin-trust-anchor 104 106 }
132405
+ eof { failEof plugin-trust-anchor 105 }
132406
+ }
132407
+ expect {
132408
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132409
+ -nocase -re {\u276F[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
132410
+ append dialogBuffer $expect_out(buffer)
132411
+ }
132412
+ -re {\u276F[^\\r\\n]*\\r*\\n} {
132413
+ puts stderr "KIMI_TUI_STATE:plugin-trust-selected-row:unknown"
132414
+ exit 134
132415
+ }
132416
+ timeout {
132417
+ puts stderr "KIMI_TUI_STATE:plugin-trust-selected-row:timeout"
132418
+ exit 132
132419
+ }
132420
+ eof { failEof plugin-trust-selected-row 133 }
132421
+ }
132422
+
132423
+ set dialog [extractPluginTrustDialog $dialogBuffer]
132424
+ if {$dialog eq ""} {
132425
+ puts stderr "KIMI_TUI_STATE:plugin-trust:dialog-unknown"
132426
+ exit 113
132427
+ }
132428
+ set compactDialog [compactScreen $dialog]
132429
+ if {[string first [string tolower [compactScreen $expectedRepo]] $compactDialog] < 0} {
132430
+ puts stderr "KIMI_TUI_STATE:plugin-trust:repo-mismatch"
132431
+ exit 111
132432
+ }
132433
+ if {[string first [string tolower [compactScreen $expectedTag]] $compactDialog] < 0} {
132434
+ puts stderr "KIMI_TUI_STATE:plugin-trust:tag-mismatch"
132435
+ exit 112
132436
+ }
132437
+ if {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*trust and install} $dialog]} {
132438
+ send -- "\\033\\[13u"
132439
+ } elseif {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*(cancel|exit)} $dialog]} {
132440
+ send -- "\\033\\[B"
132441
+ expect {
132442
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132443
+ -nocase -re {\u276F[^\\r\\n]*Trust and install} {}
132444
+ -re {\u276F[^\\r\\n]*\\r*\\n} {
132445
+ puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
132446
+ exit 109
132220
132447
  }
132448
+ timeout { failTimeout plugin-trust-confirm-selection 107 109 }
132449
+ eof { failEof plugin-trust-confirm-selection 108 }
132450
+ }
132451
+ send -- "\\033\\[13u"
132452
+ } else {
132453
+ puts stderr "KIMI_TUI_STATE:plugin-trust:selection-unknown"
132454
+ exit 113
132455
+ }
132456
+
132457
+ expect {
132458
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132459
+ -nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
132460
+ -nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
132461
+ exp_continue -continue_timer
132462
+ }
132463
+ -nocase -re {Install failed:[^\\r\\n]*} {
132464
+ puts stderr "KIMI_TUI_STATE:install-result:failed"
132465
+ exit 135
132466
+ }
132467
+ -nocase -re {(^|\\r|\\n)Install[^\\r\\n]*\\r*\\n} {
132468
+ puts stderr "KIMI_TUI_STATE:install-result:unknown"
132469
+ exit 138
132470
+ }
132471
+ timeout {
132472
+ puts stderr "KIMI_TUI_STATE:install-result:timeout"
132473
+ exit 136
132221
132474
  }
132222
- timeout { exit 91 }
132223
- eof { exit 92 }
132475
+ eof { failEof install-result 137 }
132476
+ }
132477
+ expect {
132478
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132479
+ -re $promptPattern {}
132480
+ timeout { failTimeout post-install-prompt 114 116 }
132481
+ eof { failEof post-install-prompt 115 }
132482
+ }
132483
+ submitCommand "/reload"
132484
+ expect {
132485
+ -nocase -re {Trust this folder\\?} { directoryTrust }
132486
+ -re $promptPattern {}
132487
+ timeout { failTimeout reload-prompt 117 119 }
132488
+ eof { failEof reload-prompt 118 }
132489
+ }
132490
+ submitCommand "/exit"
132491
+ expect {
132492
+ eof { puts stderr "KIMI_TUI_STATE:exit-eof:eof" }
132493
+ timeout { failTimeout exit-eof 120 121 }
132224
132494
  }
132225
- expect -re $promptPattern
132226
- send -- "/reload\\r"
132227
- expect -re $promptPattern
132228
- send -- "/exit\\r"
132229
- expect eof
132230
132495
  `;
132231
132496
  }
132232
132497
  async function observeKimiTarget(target, kimiHome, run6) {
@@ -132294,7 +132559,7 @@ async function runKimiUpdate(target, detected, run6, kimiHome, {
132294
132559
  });
132295
132560
  return { status: "ALREADY_CURRENT", version: target.version };
132296
132561
  }
132297
- await withTemporaryWorkspace(async (workspace) => {
132562
+ const tuiOutcome = await withTemporaryWorkspace(async (workspace) => {
132298
132563
  const checkout = join27(workspace.root, "plugin");
132299
132564
  await run6("git", [
132300
132565
  "clone",
@@ -132315,18 +132580,31 @@ async function runKimiUpdate(target, detected, run6, kimiHome, {
132315
132580
  }
132316
132581
  const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
132317
132582
  const removePlugin = before.source === "legacy" ? target.plugin : "";
132318
- await run6(detected.expectCommand, ["-c", kimiExpectProgram({
132319
- ...removePlugin ? { removePlugin } : {}
132320
- })], {
132321
- timeout: 24e4,
132322
- env: {
132323
- ...env,
132324
- RELEASE_SKILL_KIMI_COMMAND: detected.command,
132325
- RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
132326
- RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin
132583
+ try {
132584
+ await run6(detected.expectCommand, ["-c", kimiExpectProgram({
132585
+ ...removePlugin ? { removePlugin } : {}
132586
+ })], {
132587
+ timeout: Math.max(3e5, target.timeoutMs),
132588
+ env: {
132589
+ ...env,
132590
+ RELEASE_SKILL_KIMI_COMMAND: detected.command,
132591
+ RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
132592
+ RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
132593
+ RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
132594
+ RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag
132595
+ }
132596
+ });
132597
+ } catch (error) {
132598
+ if (error?.exitStatus === 80) {
132599
+ return {
132600
+ status: "MANUAL_REQUIRED",
132601
+ reason: "Kimi requires folder trust; release-finish did not confirm the folder or continue installation"
132602
+ };
132327
132603
  }
132328
- });
132604
+ throw error;
132605
+ }
132329
132606
  }, { prefix: "release-skill-kimi-update-" });
132607
+ if (tuiOutcome) return tuiOutcome;
132330
132608
  const after = await observeKimiTarget(target, kimiHome, run6);
132331
132609
  if (!after.exact) throw new Error("Kimi did not report the frozen plugin identity after TUI installation");
132332
132610
  await verifyStructuredInstalledPayload({
@@ -132373,7 +132651,7 @@ async function updateLocalHostPluginsInternal({
132373
132651
  kimiHome,
132374
132652
  verifyInstalledPayload = verifyInstalledMarketplacePayload
132375
132653
  } = {}) {
132376
- const effectiveKimiHome = kimiHome ?? process.env.KIMI_CONFIG_DIR ?? join27(homedir(), ".kimi-code");
132654
+ const effectiveKimiHome = kimiHome ?? process.env.KIMI_CODE_HOME ?? join27(homedir(), ".kimi-code");
132377
132655
  const checklist = derivePostReleaseChecklist(plan, { postVerifyComplete: true });
132378
132656
  if (confirmPlanDigest !== plan.digest) {
132379
132657
  throw new Error("plan digest confirmation does not match the frozen release plan");
@@ -132462,7 +132740,7 @@ var init_post_release_local = __esm({
132462
132740
  "NODE_EXTRA_CA_CERTS",
132463
132741
  "CLAUDE_CONFIG_DIR",
132464
132742
  "CODEX_HOME",
132465
- "KIMI_CONFIG_DIR",
132743
+ "KIMI_CODE_HOME",
132466
132744
  "CODEBUDDY_CONFIG_DIR",
132467
132745
  "WORKBUDDY_CONFIG_DIR"
132468
132746
  ]);
@@ -132495,6 +132773,8 @@ var init_post_release_local = __esm({
132495
132773
  __name(observeCodeBuddyMarketplaceAfterResidual, "observeCodeBuddyMarketplaceAfterResidual");
132496
132774
  __name(exactPluginObservation, "exactPluginObservation");
132497
132775
  __name(normalizeGitSource, "normalizeGitSource");
132776
+ __name(parseFrozenRemoteRef, "parseFrozenRemoteRef");
132777
+ __name(preflightStructuredMarketplace, "preflightStructuredMarketplace");
132498
132778
  __name(observeMarketplace, "observeMarketplace");
132499
132779
  __name(observeStructuredTarget, "observeStructuredTarget");
132500
132780
  __name(bindStructuredMarketplace, "bindStructuredMarketplace");
@@ -133318,10 +133598,11 @@ async function publishRelease(options) {
133318
133598
  });
133319
133599
  if (binding) surfaceHostBindings.push(binding);
133320
133600
  }
133601
+ const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
133321
133602
  const closureResult = await checkSkillResourceClosure({
133322
133603
  snapshotDir,
133323
133604
  host: "root",
133324
- surfaceHostBindings
133605
+ surfaceHostBindings: checkerBindings
133325
133606
  });
133326
133607
  if (closureResult.findings.length > 0) {
133327
133608
  await evidence.append({
@@ -133337,6 +133618,31 @@ async function publishRelease(options) {
133337
133618
  { unitId, findings: closureResult.findings }
133338
133619
  );
133339
133620
  }
133621
+ const expectedHosts = [];
133622
+ for (const distribution of frozenUnit?.distributions ?? []) {
133623
+ const platform = PLATFORMS.find((item) => item.distributionType === distribution.type);
133624
+ if (platform) expectedHosts.push(await normalizeHostId(platform.buildAdapter.name));
133625
+ }
133626
+ const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
133627
+ expectedHosts,
133628
+ closureResult.surfaces,
133629
+ coverageClaims
133630
+ );
133631
+ if (!hostCoverage.passed) {
133632
+ await evidence.append({
133633
+ phase: "safety-gate",
133634
+ gate: "skill-resource-closure",
133635
+ status: "failed",
133636
+ unitId,
133637
+ reason: "declared-host-surface-missing",
133638
+ missingHosts: hostCoverage.missing
133639
+ });
133640
+ throw new ReleaseError(
133641
+ GATE_FAILED,
133642
+ `skill resource closure recheck failed for unit "${unitId}": declared host surface(s) missing or empty: ${hostCoverage.missing.map((item) => item.host).join(", ")}`,
133643
+ { unitId, missingHosts: hostCoverage.missing }
133644
+ );
133645
+ }
133340
133646
  const observed = createSkillResourceClosureReceipt(closureResult, {
133341
133647
  unitId,
133342
133648
  preparedAt: expected.preparedAt ?? null,
@@ -139802,8 +140108,7 @@ async function advanceShip(options = {}, injected = {}) {
139802
140108
  let postVerifyRanThisCall = false;
139803
140109
  if (state.status === "PUBLISHED" || state.status === "NEEDS_MANUAL_ATTESTATIONS") {
139804
140110
  const plan = JSON.parse(await readFile46(state.planPath, "utf8"));
139805
- const hasDistributeWork = normalizePostPublishView(plan).some((declaration) => (declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
139806
- const needsDistribution = hasDistributeWork;
140111
+ const needsDistribution = requiresPostPublishDistribution(plan);
139807
140112
  if (needsDistribution && deps.distributeRelease) {
139808
140113
  state.status = "DISTRIBUTING";
139809
140114
  await writeJsonAtomic(statePath, state);
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: release-finish
3
- description: 发布达到 VERIFIED 后处理可选的本地收尾:按发布分支策略决定是否询问合并,并在用户确认后更新本机 Claude、Codex、Kimi、CodeBuddy/WorkBuddy 插件
3
+ description: 发布达到 VERIFIED 后处理发布收尾:确认 postVerify 提案送达边界,按发布分支策略决定是否询问合并,并在用户确认后更新本机 Claude、Codex、Kimi、CodeBuddy/WorkBuddy 插件
4
4
  ---
5
5
 
6
6
  # release-finish
@@ -13,7 +13,7 @@ description: 发布达到 VERIFIED 后处理可选的本地收尾:按发布分
13
13
 
14
14
  这是发布后的独立收尾,不属于 `prepare → approve → publish → verify` 状态机。它不能把本机更新结果写成新的发布状态,也不能因为本机更新失败而降低 `VERIFIED`。
15
15
 
16
- 默认只读取冻结计划和 verify 或 postVerify run。没有用户明确同意,不合并分支,不更新插件,不接受 Kimi 的安装信任提示。
16
+ 默认只读取冻结计划和 verify 或 postVerify run。没有用户明确同意,不合并分支,不更新插件。Kimi 只在插件信任界面中的仓库和标签都与冻结目标一致时确认安装;目录信任不自动确认。
17
17
 
18
18
  ## 先生成收尾清单
19
19
 
@@ -37,6 +37,14 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
37
37
  2. `merge.promptRequired=true` 时,向用户说明尚未覆盖的发布分支,并询问是否需要合并。用户同意后,先只读核对源分支、目标分支、工作区状态和项目既有合并方式,再用明确的分支名执行;本脚本不猜分支,也不自动推送。
38
38
  3. `localHostUpdate.promptRequired=true` 时,列出计划覆盖的宿主,询问是否更新本机插件。两个问题可以一次问完。
39
39
 
40
+ ## postVerify 提案送达边界
41
+
42
+ `proposal-inbox` postVerify hook 只负责把冻结提案送到配置的接收端,并在 hook checkpoint 中记录送达结果。送达成功不表示提案已经应用,也不表示接收端完成了渲染或公开同步。
43
+
44
+ 接收端按照自己的 runbook 和治理要求审阅、应用、渲染并公开同步。release-finish 不内置某个接收端的仓库、命令或推送步骤,也不增加另一套收据、账本、Schema、状态机或 hook。
45
+
46
+ 本机宿主是否依赖某个市场,只根据冻结计划记录的真实安装来源判断。提案送达或某个接收端的处理结果不是所有宿主更新的统一前置条件;只有宿主的冻结安装来源确实指向该接收端产物时,才按接收端自己的 runbook 完成必要处理。
47
+
40
48
  ## 用户同意更新本机宿主
41
49
 
42
50
  把用户选择的宿主和清单返回的精确 `planDigest` 传回脚本:
@@ -54,8 +62,8 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs" \
54
62
 
55
63
  只传用户选择且计划声明的宿主。宿主 CLI 不存在时返回 `SKIPPED_NOT_INSTALLED`。各宿主按以下规则处理:
56
64
 
57
- - Claude 先重新绑定冻结市场,再重新观察安装状态;旧插件若仍存在,才调用正式更新命令。Codex 继续按正式市场协议移除并安装冻结的 Git 引用。两者都在安装后核对插件、市场、版本和市场检出提交。
58
- - Kimi 的精确当前安装会在返回 `ALREADY_CURRENT` 前核对真实载荷;发生安装或迁移时,只在操作完成后核对结果。包名、版本、发布标签、已安装修订号和受管安装根必须与冻结计划一致;`.git` 只提供附加诊断,不是通过条件。旧的本地路径安装会在同一个受控终端交互界面(TUI)会话中先移除,再按发布标签安装、确认信任并重新加载。
65
+ - Claude Codex 只有在市场需要重绑时,才会在该宿主第一条写命令之前只读查询冻结的市场仓库、引用和提交。远端不可达、引用缺失或提交不一致时返回 `MANUAL_REQUIRED`,该宿主不执行市场或插件写入;其他已选宿主继续处理。精确当前安装仍核对真实载荷,但不强制联网。
66
+ - Kimi 的精确当前安装会在返回 `ALREADY_CURRENT` 前核对真实载荷;发生安装或迁移时,只在操作完成后核对结果。配置根依次取显式 `kimiHome`、`KIMI_CODE_HOME`、用户主目录下的 `.kimi-code`,TUI 与安装后观察使用同一根。release-finish 当前只采用并验证 Kimi Code 的受控终端交互界面(TUI)路径:清理 ANSI/OSC 控制序列和软换行后,在插件信任对话框内分别核对冻结仓库与标签,确认选中 `Trust and install` 后才提交,再重新加载。出现 `Trust this folder?`、未知界面、超时、提前退出,或无法确认身份和选中项时返回 `MANUAL_REQUIRED` 或失败结果,不确认目录信任,也不继续安装。包名、版本、发布标签、已安装修订号和受管安装根必须与冻结计划一致;`.git` 只提供附加诊断,不是通过条件。旧的本地路径安装会在同一 TUI 会话中先移除,再按发布标签安装。
59
67
  - CodeBuddy/WorkBuddy 只处理同一 bundled-family 插件和市场。CodeBuddy 仅探测全局 `codebuddy`/`cbc`,由收尾脚本把 `CODEBUDDY_CONFIG_DIR` 固定为有效 `HOME`(环境未提供时取操作系统用户主目录)下名为 `.codebuddy` 的目录;WorkBuddy 仅在 macOS 探测 WorkBuddy 应用内嵌 CLI,由收尾脚本把 `CODEBUDDY_CONFIG_DIR` 与 `WORKBUDDY_CONFIG_DIR` 同时固定为有效 `HOME` 下名为 `.workbuddy` 的目录,绝不把两者互作回退。只有冻结标签与计划声明的可变分支都从同一远端解析到冻结提交时,才调用正式市场更新和插件更新命令;完成后重新读取安装列表,并精确核对唯一条目的市场、版本和修订号。目标未安装、来源不符、远端不可访问或身份不一致时返回 `MANUAL_REQUIRED`,不修改宿主。非 macOS 的 WorkBuddy 返回 `SKIPPED_UNSUPPORTED_PLATFORM`。
60
68
 
61
69
  任何宿主失败都保留其他宿主的实际结果,不回滚,也不把失败冒充成功。