release-skill 0.9.16 → 0.9.17

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 (164) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.kimi-plugin/plugin.json +1 -1
  5. package/.qoder-plugin/plugin.json +10 -0
  6. package/CHANGELOG.md +30 -0
  7. package/INSTALL.md +57 -9
  8. package/INSTALL.zh-CN.md +44 -7
  9. package/README.md +75 -31
  10. package/README.zh-CN.md +60 -29
  11. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  12. package/adapters/claude/bin/release-skill.bundle.mjs +1443 -672
  13. package/adapters/claude/schemas/release-plan.schema.json +1 -1
  14. package/adapters/claude/schemas/release-project.schema.json +1 -1
  15. package/adapters/claude/skills/release-finish/SKILL.md +14 -13
  16. package/adapters/claude/skills/release-help/SKILL.md +3 -3
  17. package/adapters/claude/skills/release-verify/SKILL.md +2 -2
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +1443 -672
  20. package/adapters/codex/schemas/release-plan.schema.json +1 -1
  21. package/adapters/codex/schemas/release-project.schema.json +1 -1
  22. package/adapters/codex/skills/release-finish/SKILL.md +14 -13
  23. package/adapters/codex/skills/release-help/SKILL.md +3 -3
  24. package/adapters/codex/skills/release-verify/SKILL.md +2 -2
  25. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  26. package/adapters/kimi/bin/release-skill.bundle.mjs +1443 -672
  27. package/adapters/kimi/schemas/release-plan.schema.json +1 -1
  28. package/adapters/kimi/schemas/release-project.schema.json +1 -1
  29. package/adapters/kimi/skills/release-finish/SKILL.md +14 -13
  30. package/adapters/kimi/skills/release-help/SKILL.md +3 -3
  31. package/adapters/kimi/skills/release-verify/SKILL.md +2 -2
  32. package/adapters/qoder/.qoder-plugin/plugin.json +10 -0
  33. package/adapters/qoder/bin/consumer-contract-vectors.json +110 -0
  34. package/adapters/qoder/bin/error-codes.json +161 -0
  35. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/LICENSE +202 -0
  36. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/NOTICE +10 -0
  37. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/LICENSE +202 -0
  38. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/NOTICE +10 -0
  39. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/THIRD_PARTY_NOTICES +142 -0
  40. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/LICENSE +202 -0
  41. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/NOTICE +10 -0
  42. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/native-NOTICE +2 -0
  43. package/adapters/qoder/bin/foundation-resource-binding.json +1 -0
  44. package/adapters/qoder/bin/kernel-protocol.json +60 -0
  45. package/adapters/qoder/bin/license-texts/Apache-2.0.txt +201 -0
  46. package/adapters/qoder/bin/license-texts/MIT.txt +21 -0
  47. package/adapters/qoder/bin/prebuild-manifest.json +79 -0
  48. package/adapters/qoder/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  49. package/adapters/qoder/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  50. package/adapters/qoder/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  51. package/adapters/qoder/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  52. package/adapters/qoder/bin/registry.json +347 -0
  53. package/adapters/qoder/bin/release-skill-local-finish.mjs +4 -0
  54. package/adapters/qoder/bin/release-skill.bundle.mjs +149446 -0
  55. package/adapters/qoder/bin/release-skill.mjs +54 -0
  56. package/adapters/qoder/bin/rules.json +104 -0
  57. package/adapters/qoder/bin/schemas/consumer-contract-vector.schema.json +149 -0
  58. package/adapters/qoder/data/hosts/claude/host-descriptor.json +45 -0
  59. package/adapters/qoder/data/hosts/codebuddy/host-descriptor.json +26 -0
  60. package/adapters/qoder/data/hosts/codex/host-descriptor.json +45 -0
  61. package/adapters/qoder/data/hosts/deepseek-harness/host-descriptor.json +23 -0
  62. package/adapters/qoder/data/hosts/kimi-code/host-descriptor.json +36 -0
  63. package/adapters/qoder/data/hosts/qoder/host-descriptor.json +26 -0
  64. package/adapters/qoder/data/hosts/registry.json +13 -0
  65. package/adapters/qoder/data/hosts/workbuddy/host-descriptor.json +34 -0
  66. package/adapters/qoder/data/licensing/registry.json +211 -0
  67. package/adapters/qoder/data/licensing/schema.json +207 -0
  68. package/adapters/qoder/native/safe-write/binding.gyp +41 -0
  69. package/adapters/qoder/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  70. package/adapters/qoder/native/safe-write/prebuilds.json +24 -0
  71. package/adapters/qoder/native/safe-write/src/safe_write.cc +2032 -0
  72. package/adapters/qoder/schemas/.render-manifest.json +41 -0
  73. package/adapters/qoder/schemas/approval-record.schema.json +115 -0
  74. package/adapters/qoder/schemas/artifact-lock.schema.json +111 -0
  75. package/adapters/qoder/schemas/artifact-plan.schema.json +52 -0
  76. package/adapters/qoder/schemas/artifact-policy.schema.json +76 -0
  77. package/adapters/qoder/schemas/evidence-event-v2.schema.json +164 -0
  78. package/adapters/qoder/schemas/evidence-event.schema.json +89 -0
  79. package/adapters/qoder/schemas/postpublish-approval-record.schema.json +47 -0
  80. package/adapters/qoder/schemas/release-plan.schema.json +1996 -0
  81. package/adapters/qoder/schemas/release-project.schema.json +1613 -0
  82. package/adapters/qoder/schemas/release-run.schema.json +777 -0
  83. package/adapters/qoder/skills/release-assess/SKILL.md +60 -0
  84. package/adapters/qoder/skills/release-config/SKILL.md +150 -0
  85. package/adapters/qoder/skills/release-docs/SKILL.md +119 -0
  86. package/adapters/qoder/skills/release-finish/SKILL.md +141 -0
  87. package/adapters/qoder/skills/release-help/SKILL.md +153 -0
  88. package/adapters/qoder/skills/release-marketplace/SKILL.md +156 -0
  89. package/adapters/qoder/skills/release-prepare/SKILL.md +135 -0
  90. package/adapters/qoder/skills/release-publish/SKILL.md +91 -0
  91. package/adapters/qoder/skills/release-reconcile/SKILL.md +80 -0
  92. package/adapters/qoder/skills/release-setup/SKILL.md +131 -0
  93. package/adapters/qoder/skills/release-verify/SKILL.md +109 -0
  94. package/adapters/qoder/src/schemas/adapter-build-manifest.schema.json +41 -0
  95. package/adapters/qoder/src/schemas/adapter-peer-verification-request.schema.json +54 -0
  96. package/adapters/qoder/src/schemas/adapter-peer-verification-result.schema.json +54 -0
  97. package/adapters/qoder/src/schemas/adapter-source.schema.json +47 -0
  98. package/adapters/qoder/src/schemas/audit-baseline-pin.schema.json +163 -0
  99. package/adapters/qoder/src/schemas/declared-read-surface-result.schema.json +61 -0
  100. package/adapters/qoder/src/schemas/engineering-baseline.schema.json +77 -0
  101. package/adapters/qoder/src/schemas/executable-identity-observation.schema.json +162 -0
  102. package/adapters/qoder/src/schemas/filesystem-root-binding.schema.json +14 -0
  103. package/adapters/qoder/src/schemas/filesystem-tree-observation.schema.json +71 -0
  104. package/adapters/qoder/src/schemas/fixed-set-publication-manifest.schema.json +108 -0
  105. package/adapters/qoder/src/schemas/fixed-set-publication-receipt.schema.json +148 -0
  106. package/adapters/qoder/src/schemas/host-capability-fact.schema.json +31 -0
  107. package/adapters/qoder/src/schemas/host-descriptor.schema.json +137 -0
  108. package/adapters/qoder/src/schemas/host-operation-plan.schema.json +57 -0
  109. package/adapters/qoder/src/schemas/host-operation-receipt.schema.json +81 -0
  110. package/adapters/qoder/src/schemas/host-probe-result.schema.json +30 -0
  111. package/adapters/qoder/src/schemas/host-registry.schema.json +18 -0
  112. package/adapters/qoder/src/schemas/host-verification-request.schema.json +67 -0
  113. package/adapters/qoder/src/schemas/host-verification-result.schema.json +149 -0
  114. package/adapters/qoder/src/schemas/managed-file-lock.schema.json +77 -0
  115. package/adapters/qoder/src/schemas/migration-manifest.schema.json +412 -0
  116. package/adapters/qoder/src/schemas/observation-scope.schema.json +91 -0
  117. package/adapters/qoder/src/schemas/operation-request.schema.json +58 -0
  118. package/adapters/qoder/src/schemas/operation-result.schema.json +130 -0
  119. package/adapters/qoder/src/schemas/platform-difference-registry.schema.json +110 -0
  120. package/adapters/qoder/src/schemas/plugin-verification-request.schema.json +499 -0
  121. package/adapters/qoder/src/schemas/plugin-verification-result.schema.json +1217 -0
  122. package/adapters/qoder/src/schemas/profile-adoption-declaration.schema.json +111 -0
  123. package/adapters/qoder/src/schemas/profile-descriptor.schema.json +99 -0
  124. package/adapters/qoder/src/schemas/project-manifest.schema.json +67 -0
  125. package/adapters/qoder/src/schemas/project-profile.schema.json +25 -0
  126. package/adapters/qoder/src/schemas/public-boundary-declaration.schema.json +73 -0
  127. package/adapters/qoder/src/schemas/report-binding.schema.json +42 -0
  128. package/adapters/qoder/src/schemas/report-model.schema.json +308 -0
  129. package/adapters/qoder/src/schemas/skill-family-directory-verification-request.schema.json +100 -0
  130. package/adapters/qoder/src/schemas/skill-family-directory-verification-result.schema.json +193 -0
  131. package/adapters/qoder/src/schemas/source-authority-receipt.schema.json +31 -0
  132. package/adapters/qoder/src/schemas/state-event-envelope.schema.json +38 -0
  133. package/adapters/qoder/src/schemas/state-snapshot-metadata.schema.json +25 -0
  134. package/adapters/qoder/src/schemas/structured-scan-policy.schema.json +97 -0
  135. package/adapters/qoder/src/schemas/surface-scan-policy.schema.json +42 -0
  136. package/adapters/qoder/src/schemas/timeout-policy.schema.json +24 -0
  137. package/adapters/qoder/src/schemas/token-estimate-record.schema.json +75 -0
  138. package/adapters/qoder/src/schemas/token-estimate-result.schema.json +43 -0
  139. package/adapters/qoder/src/schemas/watchdog-termination-envelope.schema.json +156 -0
  140. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  141. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1443 -672
  142. package/adapters/workbuddy/schemas/release-plan.schema.json +1 -1
  143. package/adapters/workbuddy/schemas/release-project.schema.json +1 -1
  144. package/adapters/workbuddy/skills/release-finish/SKILL.md +14 -13
  145. package/adapters/workbuddy/skills/release-help/SKILL.md +3 -3
  146. package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -2
  147. package/bin/release-skill-cli.mjs +124 -6
  148. package/bin/release-skill.bundle.mjs +1443 -672
  149. package/package.json +4 -2
  150. package/platform-manifest.json +70 -4
  151. package/references/02-project-config.md +9 -2
  152. package/schemas/release-plan.schema.json +1 -1
  153. package/schemas/release-project.schema.json +1 -1
  154. package/skills/release-finish/SKILL.md +14 -13
  155. package/skills/release-help/SKILL.md +3 -3
  156. package/skills/release-verify/SKILL.md +2 -2
  157. package/skills-src/release-finish/SKILL.md +14 -13
  158. package/skills-src/release-help/SKILL.md +3 -3
  159. package/skills-src/release-verify/SKILL.md +2 -2
  160. package/src/commands/post-release-local.mjs +360 -51
  161. package/src/commands/verify-records.mjs +467 -0
  162. package/src/core/postpublish.mjs +1 -1
  163. package/src/core/run.mjs +1 -1
  164. package/src/producers/build-adapters.mjs +36 -13
@@ -14,6 +14,7 @@ import { resolveCodeBuddyMarketplace } from '../platforms/codebuddy.mjs';
14
14
  import { verifyInstalledMarketplacePayload } from '../adapters/plugin-marketplace.mjs';
15
15
  import { normalizePostPublishView, postPublishActionId } from '../core/postpublish.mjs';
16
16
  import { loadRun, validateRunLineage } from '../core/run.mjs';
17
+ import { verifyFrozenSnapshot } from '../snapshot/frozen.mjs';
17
18
 
18
19
  const HOSTS_BY_ACTION = Object.freeze({
19
20
  'claude-marketplace-install': ['claude'],
@@ -40,6 +41,9 @@ const SAFE_ENV_KEYS = Object.freeze([
40
41
  ]);
41
42
  const CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(['plugin', 'list', '--json']);
42
43
  const CODEBUDDY_MARKETPLACE_LIST_ARGS = Object.freeze(['plugin', 'marketplace', 'list']);
44
+ const QODER_PLUGIN_LIST_ARGS = Object.freeze(['plugins', 'list', '--json']);
45
+ const QODER_MARKETPLACE_LIST_ARGS = Object.freeze(['plugins', 'marketplace', 'list', '--json']);
46
+ const QODER_PAYLOAD_CONTRACT = 'external-marketplace-v1';
43
47
 
44
48
  function attachFoundationFailure(error, { envelope, stdout }) {
45
49
  Object.defineProperties(error, {
@@ -172,17 +176,38 @@ function hubTargets(plan) {
172
176
  codebuddy: 'Handle manually; CodeBuddy cannot pin a Hub ref in this flow.',
173
177
  workbuddy: 'Handle manually; WorkBuddy cannot pin a Hub ref in this flow (it follows the CodeBuddy manual boundary).',
174
178
  };
175
- return local.hosts.map((host) => ({
176
- targetKind: 'hub-backed',
177
- executionMode: 'manual',
178
- unitId: declaration.unitId,
179
- host,
180
- plugin: local.plugin,
181
- hub,
182
- message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
183
- ...(unit?.publicRepo ? { publicRepo: unit.publicRepo } : {}),
184
- ...(tag ? { frozenTag: tag } : {}),
185
- }));
179
+ return local.hosts.map((host) => {
180
+ if (host === 'qoder') {
181
+ return {
182
+ targetKind: 'hub-backed',
183
+ executionMode: 'executable',
184
+ unitId: declaration.unitId,
185
+ host,
186
+ plugin: local.plugin,
187
+ marketplace: hub.name,
188
+ hub,
189
+ version: unit?.targetVersion,
190
+ pluginRepo: unit?.publicRepo,
191
+ pluginCommit: unit?.frozenSnapshot?.commit,
192
+ snapshotPath: unit?.frozenSnapshot?.path,
193
+ manifestDigest: unit?.frozenSnapshot?.manifestDigest,
194
+ timeoutMs: 300_000,
195
+ message: `Update ${local.plugin} from Qoder Hub ${hub.name}; a new session or /plugins reload is required before treating the updated plugin as loaded.`,
196
+ ...(tag ? { frozenTag: tag } : {}),
197
+ };
198
+ }
199
+ return {
200
+ targetKind: 'hub-backed',
201
+ executionMode: 'manual',
202
+ unitId: declaration.unitId,
203
+ host,
204
+ plugin: local.plugin,
205
+ hub,
206
+ message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
207
+ ...(unit?.publicRepo ? { publicRepo: unit.publicRepo } : {}),
208
+ ...(tag ? { frozenTag: tag } : {}),
209
+ };
210
+ });
186
211
  });
187
212
  }
188
213
 
@@ -240,6 +265,23 @@ function assertExecutableTarget(target) {
240
265
  }
241
266
  }
242
267
 
268
+ function assertQoderExecutableTarget(target) {
269
+ for (const field of [
270
+ 'unitId', 'plugin', 'marketplace', 'version', 'pluginRepo',
271
+ 'pluginCommit', 'snapshotPath', 'manifestDigest',
272
+ ]) {
273
+ if (typeof target[field] !== 'string' || target[field].length === 0) {
274
+ throw new Error(`Qoder local host update target is missing ${field}`);
275
+ }
276
+ }
277
+ if (!/^[a-f0-9]{40}$/u.test(target.pluginCommit)) {
278
+ throw new Error(`Qoder local host update requires the frozen public commit for unit ${target.unitId}`);
279
+ }
280
+ if (!/^[a-f0-9]{64}$/u.test(target.manifestDigest)) {
281
+ throw new Error(`Qoder local host update requires the frozen snapshot digest for unit ${target.unitId}`);
282
+ }
283
+ }
284
+
243
285
  function buildShipNextStep({ root, statePath, unitIds }) {
244
286
  const argv = ['release-skill', 'ship'];
245
287
  if (typeof root === 'string' && root.length > 0) argv.push('--root', root);
@@ -268,8 +310,12 @@ export function derivePostReleaseChecklist(plan, {
268
310
  const uncovered = units.filter((unit) => (
269
311
  !BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy)
270
312
  ));
271
- const executableTargets = pluginTargets(plan);
272
- const manualTargets = hubTargets(plan);
313
+ const declaredHubTargets = hubTargets(plan);
314
+ const executableTargets = [
315
+ ...pluginTargets(plan),
316
+ ...declaredHubTargets.filter((target) => target.executionMode === 'executable'),
317
+ ];
318
+ const manualTargets = declaredHubTargets.filter((target) => target.executionMode === 'manual');
273
319
  const targets = mergePostReleaseTargets(executableTargets, manualTargets);
274
320
  const hasPendingPostVerify = postVerifyHooks(plan).length > 0 && !postVerifyComplete && targets.length > 0;
275
321
  const hasStatePath = typeof statePath === 'string' && statePath.length > 0;
@@ -717,6 +763,215 @@ function normalizeGitSource(source) {
717
763
  .replace(/\/$/u, '');
718
764
  }
719
765
 
766
+ function normalizeHubGitSource(source, githubHost) {
767
+ return String(source ?? '')
768
+ .replace(/^git\+/, '')
769
+ .replace(new RegExp(`^https://${String(githubHost).replaceAll('.', '\\.').replaceAll('-', '\\-')}/`), '')
770
+ .replace(new RegExp(`^git@${String(githubHost).replaceAll('.', '\\.').replaceAll('-', '\\-')}:`), '')
771
+ .replace(/\.git$/u, '')
772
+ .replace(/\/$/u, '');
773
+ }
774
+
775
+ async function observeQoderMarketplace(target, command, env, run) {
776
+ const listed = await run(command, [...QODER_MARKETPLACE_LIST_ARGS], {
777
+ env,
778
+ timeout: target.timeoutMs,
779
+ });
780
+ const parsed = parseJson(listed.stdout, 'qoder marketplace list');
781
+ if (!Array.isArray(parsed)) throw new Error('qoder marketplace list did not return an array');
782
+ const matches = parsed.filter((entry) => entry?.name === target.marketplace);
783
+ if (matches.length === 0) return { installed: false };
784
+ if (matches.length !== 1) {
785
+ throw new Error(`qoder marketplace list returned conflicting entries for ${target.marketplace}`);
786
+ }
787
+ const [found] = matches;
788
+ if (
789
+ found.source?.source !== 'git'
790
+ || normalizeHubGitSource(found.source.url, target.hub.githubHost) !== target.hub.repo
791
+ ) {
792
+ throw new Error(`qoder marketplace ${target.marketplace} does not point to ${target.hub.repo}`);
793
+ }
794
+ if (typeof found.installLocation !== 'string' || found.installLocation.length === 0) {
795
+ throw new Error(`qoder marketplace ${target.marketplace} has no observable checkout root`);
796
+ }
797
+ const [remote, branch] = await Promise.all([
798
+ run('git', ['-C', found.installLocation, 'remote', 'get-url', 'origin'], {
799
+ env,
800
+ timeout: 30_000,
801
+ }),
802
+ run('git', ['-C', found.installLocation, 'symbolic-ref', '-q', 'HEAD'], {
803
+ env,
804
+ timeout: 30_000,
805
+ }),
806
+ ]);
807
+ if (
808
+ normalizeHubGitSource(remote.stdout.trim(), target.hub.githubHost) !== target.hub.repo
809
+ || branch.stdout.trim() !== target.hub.ref
810
+ ) {
811
+ throw new Error(`qoder marketplace ${target.marketplace} checkout does not match the frozen Hub source`);
812
+ }
813
+ return { installed: true, root: found.installLocation, found };
814
+ }
815
+
816
+ async function observeQoderPlugin(target, command, env, run) {
817
+ const listed = await run(command, [...QODER_PLUGIN_LIST_ARGS], {
818
+ env,
819
+ timeout: target.timeoutMs,
820
+ });
821
+ const parsed = parseJson(listed.stdout, 'qoder plugin list');
822
+ if (!Array.isArray(parsed)) throw new Error('qoder plugin list did not return an array');
823
+ const selector = `${target.plugin}@${target.marketplace}`;
824
+ const matches = parsed.filter((entry) => entry?.id === selector);
825
+ if (matches.length === 0) return { installed: false };
826
+ if (matches.length !== 1) {
827
+ throw new Error(`qoder plugin list returned conflicting entries for ${selector}`);
828
+ }
829
+ const [found] = matches;
830
+ if (
831
+ found.name !== target.plugin
832
+ || found.source !== selector
833
+ || found.scope !== 'user'
834
+ || typeof found.installPath !== 'string'
835
+ || found.installPath.length === 0
836
+ ) {
837
+ throw new Error(`qoder plugin ${selector} does not match its frozen user-scope identity`);
838
+ }
839
+ return {
840
+ installed: true,
841
+ exact: found.version === target.version,
842
+ installPath: found.installPath,
843
+ found,
844
+ };
845
+ }
846
+
847
+ async function readQoderManifest(root, label) {
848
+ const manifest = parseJson(
849
+ await readFileContained(root, '.qoder-plugin/plugin.json', { encoding: 'utf8' }),
850
+ label,
851
+ );
852
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
853
+ throw new Error(`${label} did not return an object`);
854
+ }
855
+ return manifest;
856
+ }
857
+
858
+ function assertQoderPluginManifest(target, manifest, label) {
859
+ if (
860
+ manifest.name !== target.plugin
861
+ || manifest.version !== target.version
862
+ || manifest.skills !== './adapters/qoder/skills/'
863
+ ) {
864
+ throw new Error(`${label} does not match the frozen Qoder plugin identity and projection path`);
865
+ }
866
+ }
867
+
868
+ async function assertFrozenQoderManifest(target, root) {
869
+ const { snapshotDir: snapshotRoot } = await verifyFrozenSnapshot({
870
+ root,
871
+ snapshotPath: target.snapshotPath,
872
+ expectedDigest: target.manifestDigest,
873
+ });
874
+ const manifest = await readQoderManifest(snapshotRoot, 'frozen Qoder plugin manifest');
875
+ assertQoderPluginManifest(target, manifest, 'frozen Qoder plugin manifest');
876
+ }
877
+
878
+ async function assertQoderHubEntry(target, marketplaceRoot) {
879
+ const marketplace = parseJson(
880
+ await readFileContained(marketplaceRoot, 'marketplace.json', { encoding: 'utf8' }),
881
+ 'Qoder Hub marketplace manifest',
882
+ );
883
+ if (marketplace?.name !== target.marketplace || !Array.isArray(marketplace.plugins)) {
884
+ throw new Error('Qoder Hub marketplace manifest does not match the frozen marketplace');
885
+ }
886
+ const matches = marketplace.plugins.filter((entry) => entry?.name === target.plugin);
887
+ if (matches.length !== 1) {
888
+ throw new Error(`Qoder Hub marketplace manifest must contain exactly one ${target.plugin} entry`);
889
+ }
890
+ const source = matches[0]?.source;
891
+ if (
892
+ source?.source !== 'url'
893
+ || normalizeHubGitSource(source.url, target.hub.githubHost) !== target.pluginRepo
894
+ || source.sha !== target.pluginCommit
895
+ ) {
896
+ throw new Error('Qoder Hub entry does not match the frozen public source identity');
897
+ }
898
+ }
899
+
900
+ async function verifyQoderInstalledPayload({
901
+ target,
902
+ root,
903
+ installPath,
904
+ verifyInstalledPayload,
905
+ }) {
906
+ const manifest = await readQoderManifest(installPath, 'installed Qoder plugin manifest');
907
+ assertQoderPluginManifest(target, manifest, 'installed Qoder plugin manifest');
908
+ await verifyInstalledPayload({
909
+ snapshotPath: target.snapshotPath,
910
+ manifestDigest: target.manifestDigest,
911
+ payloadContract: QODER_PAYLOAD_CONTRACT,
912
+ marketplaceLocation: 'external',
913
+ }, { root }, installPath, 'qoder');
914
+ }
915
+
916
+ async function runQoderUpdate(target, detected, run, {
917
+ root,
918
+ verifyInstalledPayload,
919
+ }) {
920
+ const env = hostEnvironment('qoder');
921
+ await assertFrozenQoderManifest(target, root);
922
+ const marketplace = await observeQoderMarketplace(target, detected.command, env, run);
923
+ if (!marketplace.installed) {
924
+ return {
925
+ status: 'MANUAL_REQUIRED',
926
+ reason: `qoder marketplace ${target.marketplace} is not installed; no marketplace was added`,
927
+ };
928
+ }
929
+ const before = await observeQoderPlugin(target, detected.command, env, run);
930
+ if (!before.installed) {
931
+ return {
932
+ status: 'MANUAL_REQUIRED',
933
+ reason: 'qoder target plugin is not installed; no initial installation was performed',
934
+ };
935
+ }
936
+ if (before.exact) {
937
+ await assertQoderHubEntry(target, marketplace.root);
938
+ await verifyQoderInstalledPayload({
939
+ target,
940
+ root,
941
+ installPath: before.installPath,
942
+ verifyInstalledPayload,
943
+ });
944
+ return { status: 'ALREADY_CURRENT', version: target.version };
945
+ }
946
+
947
+ await run(detected.command, ['plugins', 'marketplace', 'update', target.marketplace], {
948
+ env,
949
+ timeout: target.timeoutMs,
950
+ });
951
+ const refreshed = await observeQoderMarketplace(target, detected.command, env, run);
952
+ if (!refreshed.installed) {
953
+ throw new Error(`qoder marketplace ${target.marketplace} disappeared after refresh`);
954
+ }
955
+ await assertQoderHubEntry(target, refreshed.root);
956
+ await run(detected.command, [
957
+ 'plugins', 'update', `${target.plugin}@${target.marketplace}`, '--scope', 'user',
958
+ ], { env, timeout: target.timeoutMs });
959
+ const after = await observeQoderPlugin(target, detected.command, env, run);
960
+ if (!after.exact) throw new Error('qoder did not update to the frozen plugin version');
961
+ await verifyQoderInstalledPayload({
962
+ target,
963
+ root,
964
+ installPath: after.installPath,
965
+ verifyInstalledPayload,
966
+ });
967
+ return {
968
+ status: 'UPDATED',
969
+ version: target.version,
970
+ restartRequired: true,
971
+ reloadInstruction: 'Start a new Qoder session or run /plugins reload before checking the loaded version.',
972
+ };
973
+ }
974
+
720
975
  function parseFrozenRemoteRef(target, stdout) {
721
976
  const directRefs = new Map();
722
977
  const peeledRefs = new Map();
@@ -1046,14 +1301,14 @@ expect {
1046
1301
  eof { failEof remove-confirmation 126 }
1047
1302
  }
1048
1303
  expect {
1049
- -nocase -re {Trust this folder\\?} { directoryTrust }
1304
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1050
1305
  -re $promptPattern {}
1051
1306
  timeout { failTimeout remove-prompt 128 130 }
1052
1307
  eof { failEof remove-prompt 129 }
1053
1308
  }
1054
1309
  }
1055
1310
  -re $promptPattern {}
1056
- -nocase -re {Trust this folder\\?} { directoryTrust }
1311
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1057
1312
  timeout { failTimeout remove-dialog 122 124 }
1058
1313
  eof { failEof remove-dialog 123 }
1059
1314
  }
@@ -1128,9 +1383,62 @@ proc failEof {state code} {
1128
1383
  exit $code
1129
1384
  }
1130
1385
 
1131
- proc directoryTrust {} {
1132
- puts stderr "KIMI_TUI_STATE:directory-trust:manual-required"
1133
- exit 80
1386
+ proc unexpectedDirectoryTrust {} {
1387
+ puts stderr "KIMI_TUI_STATE:directory-trust:unexpected"
1388
+ exit 147
1389
+ }
1390
+
1391
+ proc confirmInitialDirectoryTrust {} {
1392
+ global expect_out promptPattern
1393
+ set dialogBuffer $expect_out(buffer)
1394
+ expect {
1395
+ -nocase -re {❯[^\\r\\n]*No,[ \\t]*exit} {
1396
+ append dialogBuffer $expect_out(buffer)
1397
+ }
1398
+ -re {❯[^\\r\\n]*\\r*\\n} {
1399
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
1400
+ exit 143
1401
+ }
1402
+ timeout {
1403
+ puts stderr "KIMI_TUI_STATE:directory-trust-selected-row:timeout"
1404
+ exit 140
1405
+ }
1406
+ eof { failEof directory-trust-selected-row 141 }
1407
+ }
1408
+ expect {
1409
+ -nocase -re {(?:^|\\r|\\n)[ \\t]+Trust this folder[ \\t]*\\r*\\n} {
1410
+ append dialogBuffer $expect_out(buffer)
1411
+ }
1412
+ -re {(?:^|\\r|\\n)[^\\r\\n]*\\r*\\n} {
1413
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
1414
+ exit 143
1415
+ }
1416
+ timeout { failTimeout directory-trust-target-row 148 150 }
1417
+ eof { failEof directory-trust-target-row 149 }
1418
+ }
1419
+ set dialog [cleanScreen $dialogBuffer]
1420
+ if {![regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*No,[ \\t]*exit} $dialog]
1421
+ || ![regexp -nocase {(^|\\n)[ \\t]+Trust this folder[ \\t]*($|\\n)} $dialog]} {
1422
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
1423
+ exit 143
1424
+ }
1425
+ send -- "\\033\\[B"
1426
+ expect {
1427
+ -nocase -re {❯[^\\r\\n]*Trust this folder} {}
1428
+ -re {❯[^\\r\\n]*\\r*\\n} {
1429
+ puts stderr "KIMI_TUI_STATE:directory-trust-confirm-selection:unknown"
1430
+ exit 146
1431
+ }
1432
+ timeout { failTimeout directory-trust-confirm-selection 144 146 }
1433
+ eof { failEof directory-trust-confirm-selection 145 }
1434
+ }
1435
+ send -- "\\033\\[13u"
1436
+ expect {
1437
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1438
+ -re $promptPattern {}
1439
+ timeout { failTimeout directory-trust-prompt 151 153 }
1440
+ eof { failEof directory-trust-prompt 152 }
1441
+ }
1134
1442
  }
1135
1443
 
1136
1444
  proc submitCommand {command} {
@@ -1146,7 +1454,7 @@ if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]
1146
1454
  exit 131
1147
1455
  }
1148
1456
  expect {
1149
- -nocase -re {Trust this folder\\?} { directoryTrust }
1457
+ -nocase -re {Trust this folder\\?} { confirmInitialDirectoryTrust }
1150
1458
  -re $promptPattern {}
1151
1459
  timeout { failTimeout initial-prompt 101 103 }
1152
1460
  eof { failEof initial-prompt 102 }
@@ -1154,7 +1462,7 @@ expect {
1154
1462
  ${removeCommand}submitCommand "/plugins install $installUrl"
1155
1463
  set dialogBuffer ""
1156
1464
  expect {
1157
- -nocase -re {Trust this folder\\?} { directoryTrust }
1465
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1158
1466
  -nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
1159
1467
  append dialogBuffer $expect_out(buffer)
1160
1468
  }
@@ -1162,7 +1470,7 @@ expect {
1162
1470
  eof { failEof plugin-trust-anchor 105 }
1163
1471
  }
1164
1472
  expect {
1165
- -nocase -re {Trust this folder\\?} { directoryTrust }
1473
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1166
1474
  -nocase -re {❯[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
1167
1475
  append dialogBuffer $expect_out(buffer)
1168
1476
  }
@@ -1198,7 +1506,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*trust and install} $dialog]} {
1198
1506
  } elseif {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*(cancel|exit)} $dialog]} {
1199
1507
  send -- "\\033\\[B"
1200
1508
  expect {
1201
- -nocase -re {Trust this folder\\?} { directoryTrust }
1509
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1202
1510
  -nocase -re {❯[^\\r\\n]*Trust and install} {}
1203
1511
  -re {❯[^\\r\\n]*\\r*\\n} {
1204
1512
  puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
@@ -1214,7 +1522,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*trust and install} $dialog]} {
1214
1522
  }
1215
1523
 
1216
1524
  expect {
1217
- -nocase -re {Trust this folder\\?} { directoryTrust }
1525
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1218
1526
  -nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
1219
1527
  -nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
1220
1528
  exp_continue -continue_timer
@@ -1234,14 +1542,14 @@ expect {
1234
1542
  eof { failEof install-result 137 }
1235
1543
  }
1236
1544
  expect {
1237
- -nocase -re {Trust this folder\\?} { directoryTrust }
1545
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1238
1546
  -re $promptPattern {}
1239
1547
  timeout { failTimeout post-install-prompt 114 116 }
1240
1548
  eof { failEof post-install-prompt 115 }
1241
1549
  }
1242
1550
  submitCommand "/reload"
1243
1551
  expect {
1244
- -nocase -re {Trust this folder\\?} { directoryTrust }
1552
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1245
1553
  -re $promptPattern {}
1246
1554
  timeout { failTimeout reload-prompt 117 119 }
1247
1555
  eof { failEof reload-prompt 118 }
@@ -1343,29 +1651,20 @@ async function runKimiUpdate(target, detected, run, kimiHome, {
1343
1651
  }
1344
1652
  const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
1345
1653
  const removePlugin = before.source === 'legacy' ? target.plugin : '';
1346
- try {
1347
- await run(detected.expectCommand, ['-c', kimiExpectProgram({
1348
- ...(removePlugin ? { removePlugin } : {}),
1349
- })], {
1350
- timeout: Math.max(300_000, target.timeoutMs),
1351
- env: {
1352
- ...env,
1353
- RELEASE_SKILL_KIMI_COMMAND: detected.command,
1354
- RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
1355
- RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
1356
- RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
1357
- RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag,
1358
- },
1359
- });
1360
- } catch (error) {
1361
- if (error?.exitStatus === 80) {
1362
- return {
1363
- status: 'MANUAL_REQUIRED',
1364
- reason: 'Kimi requires folder trust; release-finish did not confirm the folder or continue installation',
1365
- };
1366
- }
1367
- throw error;
1368
- }
1654
+ await run(detected.expectCommand, ['-c', kimiExpectProgram({
1655
+ ...(removePlugin ? { removePlugin } : {}),
1656
+ })], {
1657
+ cwd: root,
1658
+ timeout: Math.max(300_000, target.timeoutMs),
1659
+ env: {
1660
+ ...env,
1661
+ RELEASE_SKILL_KIMI_COMMAND: detected.command,
1662
+ RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
1663
+ RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
1664
+ RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
1665
+ RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag,
1666
+ },
1667
+ });
1369
1668
  }, { prefix: 'release-skill-kimi-update-' });
1370
1669
  if (tuiOutcome) return tuiOutcome;
1371
1670
  const after = await observeKimiTarget(target, kimiHome, run);
@@ -1429,8 +1728,13 @@ async function updateLocalHostPluginsInternal({
1429
1728
  const selected = new Set(Array.isArray(selectedHosts) ? selectedHosts : []);
1430
1729
  const unknown = [...selected].filter((host) => !checklist.localHostUpdate.hosts.includes(host));
1431
1730
  if (unknown.length > 0) throw new Error(`selected hosts are not declared by the plan: ${unknown.join(', ')}`);
1432
- const targets = pluginTargets(plan).filter((item) => selected.has(item.host));
1433
- for (const target of targets) assertExecutableTarget(target);
1731
+ const targets = checklist.localHostUpdate.targets.filter((item) => (
1732
+ item.executionMode !== 'manual' && selected.has(item.host)
1733
+ ));
1734
+ for (const target of targets) {
1735
+ if (target.host === 'qoder') assertQoderExecutableTarget(target);
1736
+ else assertExecutableTarget(target);
1737
+ }
1434
1738
 
1435
1739
  const results = [];
1436
1740
  for (const target of targets) {
@@ -1451,7 +1755,12 @@ async function updateLocalHostPluginsInternal({
1451
1755
  root,
1452
1756
  verifyInstalledPayload,
1453
1757
  })
1454
- : await runStructuredUpdate(target, detected, run, {
1758
+ : target.host === 'qoder'
1759
+ ? await runQoderUpdate(target, detected, run, {
1760
+ root,
1761
+ verifyInstalledPayload,
1762
+ })
1763
+ : await runStructuredUpdate(target, detected, run, {
1455
1764
  plan,
1456
1765
  root,
1457
1766
  verifyInstalledPayload,