release-skill 0.9.16 → 0.9.18

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 (356) 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/.cursor-plugin/plugin.json +10 -0
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/.qoder-plugin/plugin.json +10 -0
  7. package/CHANGELOG.md +64 -0
  8. package/INSTALL.md +104 -9
  9. package/INSTALL.zh-CN.md +78 -7
  10. package/README.md +87 -32
  11. package/README.zh-CN.md +71 -30
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/consumer-contract-vectors.json +7 -7
  14. package/adapters/claude/bin/error-codes.json +1 -1
  15. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  16. package/adapters/claude/bin/prebuild-manifest.json +8 -4
  17. package/adapters/claude/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  18. package/adapters/claude/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  19. package/adapters/claude/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  20. package/adapters/claude/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  21. package/adapters/claude/bin/registry.json +1 -1
  22. package/adapters/claude/bin/release-skill.bundle.mjs +4950 -1819
  23. package/adapters/claude/bin/rules.json +1 -1
  24. package/adapters/claude/data/hosts/cursor/host-descriptor.json +34 -0
  25. package/adapters/claude/data/hosts/registry.json +1 -0
  26. package/adapters/claude/schemas/release-plan.schema.json +255 -25
  27. package/adapters/claude/schemas/release-project.schema.json +228 -26
  28. package/adapters/claude/skills/release-finish/SKILL.md +29 -15
  29. package/adapters/claude/skills/release-help/SKILL.md +40 -39
  30. package/adapters/claude/skills/release-verify/SKILL.md +2 -2
  31. package/adapters/claude/src/schemas/host-descriptor.schema.json +9 -2
  32. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  33. package/adapters/codex/bin/consumer-contract-vectors.json +7 -7
  34. package/adapters/codex/bin/error-codes.json +1 -1
  35. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  36. package/adapters/codex/bin/prebuild-manifest.json +8 -4
  37. package/adapters/codex/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  38. package/adapters/codex/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  39. package/adapters/codex/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  40. package/adapters/codex/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  41. package/adapters/codex/bin/registry.json +1 -1
  42. package/adapters/codex/bin/release-skill.bundle.mjs +4950 -1819
  43. package/adapters/codex/bin/rules.json +1 -1
  44. package/adapters/codex/data/hosts/cursor/host-descriptor.json +34 -0
  45. package/adapters/codex/data/hosts/registry.json +1 -0
  46. package/adapters/codex/schemas/release-plan.schema.json +255 -25
  47. package/adapters/codex/schemas/release-project.schema.json +228 -26
  48. package/adapters/codex/skills/release-finish/SKILL.md +29 -15
  49. package/adapters/codex/skills/release-help/SKILL.md +40 -39
  50. package/adapters/codex/skills/release-verify/SKILL.md +2 -2
  51. package/adapters/codex/src/schemas/host-descriptor.schema.json +9 -2
  52. package/adapters/cursor/.cursor-plugin/plugin.json +10 -0
  53. package/adapters/cursor/bin/consumer-contract-vectors.json +110 -0
  54. package/adapters/cursor/bin/error-codes.json +161 -0
  55. package/adapters/cursor/bin/foundation-legal/skill-family-contracts/LICENSE +202 -0
  56. package/adapters/cursor/bin/foundation-legal/skill-family-contracts/NOTICE +10 -0
  57. package/adapters/cursor/bin/foundation-legal/skill-family-engineering-kit/LICENSE +202 -0
  58. package/adapters/cursor/bin/foundation-legal/skill-family-engineering-kit/NOTICE +10 -0
  59. package/adapters/cursor/bin/foundation-legal/skill-family-engineering-kit/THIRD_PARTY_NOTICES +142 -0
  60. package/adapters/cursor/bin/foundation-legal/skill-family-harness-node/LICENSE +202 -0
  61. package/adapters/cursor/bin/foundation-legal/skill-family-harness-node/NOTICE +10 -0
  62. package/adapters/cursor/bin/foundation-legal/skill-family-harness-node/native-NOTICE +2 -0
  63. package/adapters/cursor/bin/foundation-resource-binding.json +1 -0
  64. package/adapters/cursor/bin/kernel-protocol.json +60 -0
  65. package/adapters/cursor/bin/license-texts/Apache-2.0.txt +201 -0
  66. package/adapters/cursor/bin/license-texts/MIT.txt +21 -0
  67. package/adapters/cursor/bin/prebuild-manifest.json +83 -0
  68. package/adapters/cursor/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  69. package/adapters/cursor/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  70. package/adapters/cursor/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  71. package/adapters/cursor/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  72. package/adapters/cursor/bin/registry.json +347 -0
  73. package/adapters/cursor/bin/release-skill-local-finish.mjs +4 -0
  74. package/adapters/cursor/bin/release-skill.bundle.mjs +151806 -0
  75. package/adapters/cursor/bin/release-skill.mjs +54 -0
  76. package/adapters/cursor/bin/rules.json +104 -0
  77. package/adapters/cursor/bin/schemas/consumer-contract-vector.schema.json +149 -0
  78. package/adapters/cursor/data/hosts/claude/host-descriptor.json +45 -0
  79. package/adapters/cursor/data/hosts/codebuddy/host-descriptor.json +26 -0
  80. package/adapters/cursor/data/hosts/codex/host-descriptor.json +45 -0
  81. package/adapters/cursor/data/hosts/cursor/host-descriptor.json +34 -0
  82. package/adapters/cursor/data/hosts/deepseek-harness/host-descriptor.json +23 -0
  83. package/adapters/cursor/data/hosts/kimi-code/host-descriptor.json +36 -0
  84. package/adapters/cursor/data/hosts/qoder/host-descriptor.json +26 -0
  85. package/adapters/cursor/data/hosts/registry.json +14 -0
  86. package/adapters/cursor/data/hosts/workbuddy/host-descriptor.json +34 -0
  87. package/adapters/cursor/data/licensing/registry.json +211 -0
  88. package/adapters/cursor/data/licensing/schema.json +207 -0
  89. package/adapters/cursor/native/safe-write/binding.gyp +41 -0
  90. package/adapters/cursor/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  91. package/adapters/cursor/native/safe-write/prebuilds.json +24 -0
  92. package/adapters/cursor/native/safe-write/src/safe_write.cc +2032 -0
  93. package/adapters/cursor/schemas/.render-manifest.json +41 -0
  94. package/adapters/cursor/schemas/approval-record.schema.json +115 -0
  95. package/adapters/cursor/schemas/artifact-lock.schema.json +111 -0
  96. package/adapters/cursor/schemas/artifact-plan.schema.json +52 -0
  97. package/adapters/cursor/schemas/artifact-policy.schema.json +76 -0
  98. package/adapters/cursor/schemas/evidence-event-v2.schema.json +164 -0
  99. package/adapters/cursor/schemas/evidence-event.schema.json +89 -0
  100. package/adapters/cursor/schemas/postpublish-approval-record.schema.json +47 -0
  101. package/adapters/cursor/schemas/release-plan.schema.json +2226 -0
  102. package/adapters/cursor/schemas/release-project.schema.json +1815 -0
  103. package/adapters/cursor/schemas/release-run.schema.json +777 -0
  104. package/adapters/cursor/skills/release-assess/SKILL.md +61 -0
  105. package/adapters/cursor/skills/release-config/SKILL.md +151 -0
  106. package/adapters/cursor/skills/release-docs/SKILL.md +120 -0
  107. package/adapters/cursor/skills/release-finish/SKILL.md +155 -0
  108. package/adapters/cursor/skills/release-help/SKILL.md +155 -0
  109. package/adapters/cursor/skills/release-marketplace/SKILL.md +157 -0
  110. package/adapters/cursor/skills/release-prepare/SKILL.md +136 -0
  111. package/adapters/cursor/skills/release-publish/SKILL.md +92 -0
  112. package/adapters/cursor/skills/release-reconcile/SKILL.md +81 -0
  113. package/adapters/cursor/skills/release-setup/SKILL.md +132 -0
  114. package/adapters/cursor/skills/release-verify/SKILL.md +110 -0
  115. package/adapters/cursor/src/schemas/adapter-build-manifest.schema.json +41 -0
  116. package/adapters/cursor/src/schemas/adapter-peer-verification-request.schema.json +54 -0
  117. package/adapters/cursor/src/schemas/adapter-peer-verification-result.schema.json +54 -0
  118. package/adapters/cursor/src/schemas/adapter-source.schema.json +47 -0
  119. package/adapters/cursor/src/schemas/audit-baseline-pin.schema.json +163 -0
  120. package/adapters/cursor/src/schemas/declared-read-surface-result.schema.json +61 -0
  121. package/adapters/cursor/src/schemas/engineering-baseline.schema.json +77 -0
  122. package/adapters/cursor/src/schemas/executable-identity-observation.schema.json +162 -0
  123. package/adapters/cursor/src/schemas/filesystem-root-binding.schema.json +14 -0
  124. package/adapters/cursor/src/schemas/filesystem-tree-observation.schema.json +71 -0
  125. package/adapters/cursor/src/schemas/fixed-set-publication-manifest.schema.json +108 -0
  126. package/adapters/cursor/src/schemas/fixed-set-publication-receipt.schema.json +148 -0
  127. package/adapters/cursor/src/schemas/host-capability-fact.schema.json +31 -0
  128. package/adapters/cursor/src/schemas/host-descriptor.schema.json +144 -0
  129. package/adapters/cursor/src/schemas/host-operation-plan.schema.json +57 -0
  130. package/adapters/cursor/src/schemas/host-operation-receipt.schema.json +81 -0
  131. package/adapters/cursor/src/schemas/host-probe-result.schema.json +30 -0
  132. package/adapters/cursor/src/schemas/host-registry.schema.json +18 -0
  133. package/adapters/cursor/src/schemas/host-verification-request.schema.json +67 -0
  134. package/adapters/cursor/src/schemas/host-verification-result.schema.json +149 -0
  135. package/adapters/cursor/src/schemas/managed-file-lock.schema.json +77 -0
  136. package/adapters/cursor/src/schemas/migration-manifest.schema.json +412 -0
  137. package/adapters/cursor/src/schemas/observation-scope.schema.json +91 -0
  138. package/adapters/cursor/src/schemas/operation-request.schema.json +58 -0
  139. package/adapters/cursor/src/schemas/operation-result.schema.json +130 -0
  140. package/adapters/cursor/src/schemas/platform-difference-registry.schema.json +110 -0
  141. package/adapters/cursor/src/schemas/plugin-verification-request.schema.json +499 -0
  142. package/adapters/cursor/src/schemas/plugin-verification-result.schema.json +1217 -0
  143. package/adapters/cursor/src/schemas/profile-adoption-declaration.schema.json +111 -0
  144. package/adapters/cursor/src/schemas/profile-descriptor.schema.json +99 -0
  145. package/adapters/cursor/src/schemas/project-manifest.schema.json +67 -0
  146. package/adapters/cursor/src/schemas/project-profile.schema.json +25 -0
  147. package/adapters/cursor/src/schemas/public-boundary-declaration.schema.json +73 -0
  148. package/adapters/cursor/src/schemas/report-binding.schema.json +42 -0
  149. package/adapters/cursor/src/schemas/report-model.schema.json +308 -0
  150. package/adapters/cursor/src/schemas/skill-family-directory-verification-request.schema.json +100 -0
  151. package/adapters/cursor/src/schemas/skill-family-directory-verification-result.schema.json +193 -0
  152. package/adapters/cursor/src/schemas/source-authority-receipt.schema.json +31 -0
  153. package/adapters/cursor/src/schemas/state-event-envelope.schema.json +38 -0
  154. package/adapters/cursor/src/schemas/state-snapshot-metadata.schema.json +25 -0
  155. package/adapters/cursor/src/schemas/structured-scan-policy.schema.json +97 -0
  156. package/adapters/cursor/src/schemas/surface-scan-policy.schema.json +42 -0
  157. package/adapters/cursor/src/schemas/timeout-policy.schema.json +24 -0
  158. package/adapters/cursor/src/schemas/token-estimate-record.schema.json +75 -0
  159. package/adapters/cursor/src/schemas/token-estimate-result.schema.json +43 -0
  160. package/adapters/cursor/src/schemas/watchdog-termination-envelope.schema.json +156 -0
  161. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  162. package/adapters/kimi/bin/consumer-contract-vectors.json +7 -7
  163. package/adapters/kimi/bin/error-codes.json +1 -1
  164. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  165. package/adapters/kimi/bin/prebuild-manifest.json +8 -4
  166. package/adapters/kimi/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  167. package/adapters/kimi/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  168. package/adapters/kimi/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  169. package/adapters/kimi/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  170. package/adapters/kimi/bin/registry.json +1 -1
  171. package/adapters/kimi/bin/release-skill.bundle.mjs +4950 -1819
  172. package/adapters/kimi/bin/rules.json +1 -1
  173. package/adapters/kimi/data/hosts/cursor/host-descriptor.json +34 -0
  174. package/adapters/kimi/data/hosts/registry.json +1 -0
  175. package/adapters/kimi/schemas/release-plan.schema.json +255 -25
  176. package/adapters/kimi/schemas/release-project.schema.json +228 -26
  177. package/adapters/kimi/skills/release-finish/SKILL.md +29 -15
  178. package/adapters/kimi/skills/release-help/SKILL.md +40 -39
  179. package/adapters/kimi/skills/release-verify/SKILL.md +2 -2
  180. package/adapters/kimi/src/schemas/host-descriptor.schema.json +9 -2
  181. package/adapters/qoder/.qoder-plugin/plugin.json +10 -0
  182. package/adapters/qoder/bin/consumer-contract-vectors.json +110 -0
  183. package/adapters/qoder/bin/error-codes.json +161 -0
  184. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/LICENSE +202 -0
  185. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/NOTICE +10 -0
  186. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/LICENSE +202 -0
  187. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/NOTICE +10 -0
  188. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/THIRD_PARTY_NOTICES +142 -0
  189. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/LICENSE +202 -0
  190. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/NOTICE +10 -0
  191. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/native-NOTICE +2 -0
  192. package/adapters/qoder/bin/foundation-resource-binding.json +1 -0
  193. package/adapters/qoder/bin/kernel-protocol.json +60 -0
  194. package/adapters/qoder/bin/license-texts/Apache-2.0.txt +201 -0
  195. package/adapters/qoder/bin/license-texts/MIT.txt +21 -0
  196. package/adapters/qoder/bin/prebuild-manifest.json +83 -0
  197. package/adapters/qoder/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  198. package/adapters/qoder/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  199. package/adapters/qoder/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  200. package/adapters/qoder/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  201. package/adapters/qoder/bin/registry.json +347 -0
  202. package/adapters/qoder/bin/release-skill-local-finish.mjs +4 -0
  203. package/adapters/qoder/bin/release-skill.bundle.mjs +151806 -0
  204. package/adapters/qoder/bin/release-skill.mjs +54 -0
  205. package/adapters/qoder/bin/rules.json +104 -0
  206. package/adapters/qoder/bin/schemas/consumer-contract-vector.schema.json +149 -0
  207. package/adapters/qoder/data/hosts/claude/host-descriptor.json +45 -0
  208. package/adapters/qoder/data/hosts/codebuddy/host-descriptor.json +26 -0
  209. package/adapters/qoder/data/hosts/codex/host-descriptor.json +45 -0
  210. package/adapters/qoder/data/hosts/cursor/host-descriptor.json +34 -0
  211. package/adapters/qoder/data/hosts/deepseek-harness/host-descriptor.json +23 -0
  212. package/adapters/qoder/data/hosts/kimi-code/host-descriptor.json +36 -0
  213. package/adapters/qoder/data/hosts/qoder/host-descriptor.json +26 -0
  214. package/adapters/qoder/data/hosts/registry.json +14 -0
  215. package/adapters/qoder/data/hosts/workbuddy/host-descriptor.json +34 -0
  216. package/adapters/qoder/data/licensing/registry.json +211 -0
  217. package/adapters/qoder/data/licensing/schema.json +207 -0
  218. package/adapters/qoder/native/safe-write/binding.gyp +41 -0
  219. package/adapters/qoder/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  220. package/adapters/qoder/native/safe-write/prebuilds.json +24 -0
  221. package/adapters/qoder/native/safe-write/src/safe_write.cc +2032 -0
  222. package/adapters/qoder/schemas/.render-manifest.json +41 -0
  223. package/adapters/qoder/schemas/approval-record.schema.json +115 -0
  224. package/adapters/qoder/schemas/artifact-lock.schema.json +111 -0
  225. package/adapters/qoder/schemas/artifact-plan.schema.json +52 -0
  226. package/adapters/qoder/schemas/artifact-policy.schema.json +76 -0
  227. package/adapters/qoder/schemas/evidence-event-v2.schema.json +164 -0
  228. package/adapters/qoder/schemas/evidence-event.schema.json +89 -0
  229. package/adapters/qoder/schemas/postpublish-approval-record.schema.json +47 -0
  230. package/adapters/qoder/schemas/release-plan.schema.json +2226 -0
  231. package/adapters/qoder/schemas/release-project.schema.json +1815 -0
  232. package/adapters/qoder/schemas/release-run.schema.json +777 -0
  233. package/adapters/qoder/skills/release-assess/SKILL.md +60 -0
  234. package/adapters/qoder/skills/release-config/SKILL.md +150 -0
  235. package/adapters/qoder/skills/release-docs/SKILL.md +119 -0
  236. package/adapters/qoder/skills/release-finish/SKILL.md +154 -0
  237. package/adapters/qoder/skills/release-help/SKILL.md +154 -0
  238. package/adapters/qoder/skills/release-marketplace/SKILL.md +156 -0
  239. package/adapters/qoder/skills/release-prepare/SKILL.md +135 -0
  240. package/adapters/qoder/skills/release-publish/SKILL.md +91 -0
  241. package/adapters/qoder/skills/release-reconcile/SKILL.md +80 -0
  242. package/adapters/qoder/skills/release-setup/SKILL.md +131 -0
  243. package/adapters/qoder/skills/release-verify/SKILL.md +109 -0
  244. package/adapters/qoder/src/schemas/adapter-build-manifest.schema.json +41 -0
  245. package/adapters/qoder/src/schemas/adapter-peer-verification-request.schema.json +54 -0
  246. package/adapters/qoder/src/schemas/adapter-peer-verification-result.schema.json +54 -0
  247. package/adapters/qoder/src/schemas/adapter-source.schema.json +47 -0
  248. package/adapters/qoder/src/schemas/audit-baseline-pin.schema.json +163 -0
  249. package/adapters/qoder/src/schemas/declared-read-surface-result.schema.json +61 -0
  250. package/adapters/qoder/src/schemas/engineering-baseline.schema.json +77 -0
  251. package/adapters/qoder/src/schemas/executable-identity-observation.schema.json +162 -0
  252. package/adapters/qoder/src/schemas/filesystem-root-binding.schema.json +14 -0
  253. package/adapters/qoder/src/schemas/filesystem-tree-observation.schema.json +71 -0
  254. package/adapters/qoder/src/schemas/fixed-set-publication-manifest.schema.json +108 -0
  255. package/adapters/qoder/src/schemas/fixed-set-publication-receipt.schema.json +148 -0
  256. package/adapters/qoder/src/schemas/host-capability-fact.schema.json +31 -0
  257. package/adapters/qoder/src/schemas/host-descriptor.schema.json +144 -0
  258. package/adapters/qoder/src/schemas/host-operation-plan.schema.json +57 -0
  259. package/adapters/qoder/src/schemas/host-operation-receipt.schema.json +81 -0
  260. package/adapters/qoder/src/schemas/host-probe-result.schema.json +30 -0
  261. package/adapters/qoder/src/schemas/host-registry.schema.json +18 -0
  262. package/adapters/qoder/src/schemas/host-verification-request.schema.json +67 -0
  263. package/adapters/qoder/src/schemas/host-verification-result.schema.json +149 -0
  264. package/adapters/qoder/src/schemas/managed-file-lock.schema.json +77 -0
  265. package/adapters/qoder/src/schemas/migration-manifest.schema.json +412 -0
  266. package/adapters/qoder/src/schemas/observation-scope.schema.json +91 -0
  267. package/adapters/qoder/src/schemas/operation-request.schema.json +58 -0
  268. package/adapters/qoder/src/schemas/operation-result.schema.json +130 -0
  269. package/adapters/qoder/src/schemas/platform-difference-registry.schema.json +110 -0
  270. package/adapters/qoder/src/schemas/plugin-verification-request.schema.json +499 -0
  271. package/adapters/qoder/src/schemas/plugin-verification-result.schema.json +1217 -0
  272. package/adapters/qoder/src/schemas/profile-adoption-declaration.schema.json +111 -0
  273. package/adapters/qoder/src/schemas/profile-descriptor.schema.json +99 -0
  274. package/adapters/qoder/src/schemas/project-manifest.schema.json +67 -0
  275. package/adapters/qoder/src/schemas/project-profile.schema.json +25 -0
  276. package/adapters/qoder/src/schemas/public-boundary-declaration.schema.json +73 -0
  277. package/adapters/qoder/src/schemas/report-binding.schema.json +42 -0
  278. package/adapters/qoder/src/schemas/report-model.schema.json +308 -0
  279. package/adapters/qoder/src/schemas/skill-family-directory-verification-request.schema.json +100 -0
  280. package/adapters/qoder/src/schemas/skill-family-directory-verification-result.schema.json +193 -0
  281. package/adapters/qoder/src/schemas/source-authority-receipt.schema.json +31 -0
  282. package/adapters/qoder/src/schemas/state-event-envelope.schema.json +38 -0
  283. package/adapters/qoder/src/schemas/state-snapshot-metadata.schema.json +25 -0
  284. package/adapters/qoder/src/schemas/structured-scan-policy.schema.json +97 -0
  285. package/adapters/qoder/src/schemas/surface-scan-policy.schema.json +42 -0
  286. package/adapters/qoder/src/schemas/timeout-policy.schema.json +24 -0
  287. package/adapters/qoder/src/schemas/token-estimate-record.schema.json +75 -0
  288. package/adapters/qoder/src/schemas/token-estimate-result.schema.json +43 -0
  289. package/adapters/qoder/src/schemas/watchdog-termination-envelope.schema.json +156 -0
  290. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  291. package/adapters/workbuddy/bin/consumer-contract-vectors.json +7 -7
  292. package/adapters/workbuddy/bin/error-codes.json +1 -1
  293. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  294. package/adapters/workbuddy/bin/prebuild-manifest.json +8 -4
  295. package/adapters/workbuddy/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  296. package/adapters/workbuddy/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  297. package/adapters/workbuddy/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  298. package/adapters/workbuddy/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  299. package/adapters/workbuddy/bin/registry.json +1 -1
  300. package/adapters/workbuddy/bin/release-skill.bundle.mjs +4950 -1819
  301. package/adapters/workbuddy/bin/rules.json +1 -1
  302. package/adapters/workbuddy/data/hosts/cursor/host-descriptor.json +34 -0
  303. package/adapters/workbuddy/data/hosts/registry.json +1 -0
  304. package/adapters/workbuddy/schemas/release-plan.schema.json +255 -25
  305. package/adapters/workbuddy/schemas/release-project.schema.json +228 -26
  306. package/adapters/workbuddy/skills/release-finish/SKILL.md +29 -15
  307. package/adapters/workbuddy/skills/release-help/SKILL.md +40 -39
  308. package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -2
  309. package/adapters/workbuddy/src/schemas/host-descriptor.schema.json +9 -2
  310. package/bin/consumer-contract-vectors.json +7 -7
  311. package/bin/error-codes.json +1 -1
  312. package/bin/foundation-resource-binding.json +1 -1
  313. package/bin/prebuild-manifest.json +8 -4
  314. package/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  315. package/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  316. package/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  317. package/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  318. package/bin/registry.json +1 -1
  319. package/bin/release-skill-cli.mjs +148 -6
  320. package/bin/release-skill.bundle.mjs +4950 -1819
  321. package/bin/rules.json +1 -1
  322. package/data/hosts/cursor/host-descriptor.json +34 -0
  323. package/data/hosts/registry.json +1 -0
  324. package/package.json +9 -5
  325. package/platform-manifest.json +140 -8
  326. package/references/02-project-config.md +9 -2
  327. package/schemas/release-plan.schema.json +255 -25
  328. package/schemas/release-project.schema.json +228 -26
  329. package/scripts/sync-public-files.mjs +3 -22
  330. package/skills/release-finish/SKILL.md +29 -15
  331. package/skills/release-help/SKILL.md +40 -39
  332. package/skills/release-verify/SKILL.md +2 -2
  333. package/skills-src/release-finish/SKILL.md +29 -15
  334. package/skills-src/release-help/SKILL.md +40 -39
  335. package/skills-src/release-verify/SKILL.md +2 -2
  336. package/src/adapters/contract.mjs +1 -0
  337. package/src/adapters/plugin-marketplace.mjs +201 -9
  338. package/src/commands/post-release-local.mjs +618 -53
  339. package/src/commands/prepare.mjs +74 -9
  340. package/src/commands/publish.mjs +4 -1
  341. package/src/commands/ship.mjs +2 -0
  342. package/src/commands/verify-records.mjs +467 -0
  343. package/src/commands/verify.mjs +73 -0
  344. package/src/core/checkpoints.mjs +5 -2
  345. package/src/core/config.mjs +1 -1
  346. package/src/core/foundation-host-verification.mjs +168 -0
  347. package/src/core/plan.mjs +19 -1
  348. package/src/core/postpublish.mjs +9 -1
  349. package/src/core/run-retention.mjs +1 -1
  350. package/src/core/run.mjs +1 -1
  351. package/src/core/surface-host-bindings.mjs +1 -0
  352. package/src/core/verification-gates.mjs +1 -1
  353. package/src/platforms/registry.mjs +225 -16
  354. package/src/producers/build-adapters.mjs +43 -25
  355. package/src/producers/foundation-resource-projection.mjs +3 -3
  356. package/src/schemas/host-descriptor.schema.json +9 -2
@@ -1,12 +1,19 @@
1
- import { access, readFile } from 'node:fs/promises';
1
+ import { access, readFile, mkdir, mkdtemp, cp, rename, rm, lstat, realpath, chmod } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
- import { join, relative } from 'node:path';
3
+ import { join, relative, isAbsolute, normalize } from 'node:path';
4
4
 
5
5
  import {
6
6
  readFileContained,
7
+ readFileStrict,
7
8
  resolveContained,
8
9
  superviseProcess,
9
10
  withTemporaryWorkspace,
11
+ createTemporaryWorkspace,
12
+ createFilesystemRootBinding,
13
+ observeFilesystemTree,
14
+ createFixedSetPublicationManifest,
15
+ publishFixedSet,
16
+ replaceFixedSetAtomic,
10
17
  } from 'skill-family-harness-node';
11
18
 
12
19
  import { getPlatform } from '../platforms/registry.mjs';
@@ -14,6 +21,7 @@ import { resolveCodeBuddyMarketplace } from '../platforms/codebuddy.mjs';
14
21
  import { verifyInstalledMarketplacePayload } from '../adapters/plugin-marketplace.mjs';
15
22
  import { normalizePostPublishView, postPublishActionId } from '../core/postpublish.mjs';
16
23
  import { loadRun, validateRunLineage } from '../core/run.mjs';
24
+ import { verifyFrozenSnapshot } from '../snapshot/frozen.mjs';
17
25
 
18
26
  const HOSTS_BY_ACTION = Object.freeze({
19
27
  'claude-marketplace-install': ['claude'],
@@ -40,6 +48,9 @@ const SAFE_ENV_KEYS = Object.freeze([
40
48
  ]);
41
49
  const CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(['plugin', 'list', '--json']);
42
50
  const CODEBUDDY_MARKETPLACE_LIST_ARGS = Object.freeze(['plugin', 'marketplace', 'list']);
51
+ const QODER_PLUGIN_LIST_ARGS = Object.freeze(['plugins', 'list', '--json']);
52
+ const QODER_MARKETPLACE_LIST_ARGS = Object.freeze(['plugins', 'marketplace', 'list', '--json']);
53
+ const QODER_PAYLOAD_CONTRACT = 'external-marketplace-v1';
43
54
 
44
55
  function attachFoundationFailure(error, { envelope, stdout }) {
45
56
  Object.defineProperties(error, {
@@ -172,17 +183,49 @@ function hubTargets(plan) {
172
183
  codebuddy: 'Handle manually; CodeBuddy cannot pin a Hub ref in this flow.',
173
184
  workbuddy: 'Handle manually; WorkBuddy cannot pin a Hub ref in this flow (it follows the CodeBuddy manual boundary).',
174
185
  };
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
- }));
186
+ return local.hosts.map((host) => {
187
+ if (host === 'cursor') {
188
+ return {
189
+ targetKind: 'cursor-local', executionMode: 'executable',
190
+ unitId: declaration.unitId, host, plugin: local.plugin,
191
+ version: unit?.targetVersion,
192
+ snapshotPath: unit?.frozenSnapshot?.path,
193
+ manifestDigest: unit?.frozenSnapshot?.manifestDigest,
194
+ cursor: local.cursor, timeoutMs: 300_000,
195
+ message: `Install or replace the complete frozen ${local.plugin} Cursor Local plugin; quit Cursor first and restart it afterwards.`,
196
+ };
197
+ }
198
+ if (host === 'qoder') {
199
+ return {
200
+ targetKind: 'hub-backed',
201
+ executionMode: 'executable',
202
+ unitId: declaration.unitId,
203
+ host,
204
+ plugin: local.plugin,
205
+ marketplace: hub.name,
206
+ hub,
207
+ version: unit?.targetVersion,
208
+ pluginRepo: unit?.publicRepo,
209
+ pluginCommit: unit?.frozenSnapshot?.commit,
210
+ snapshotPath: unit?.frozenSnapshot?.path,
211
+ manifestDigest: unit?.frozenSnapshot?.manifestDigest,
212
+ timeoutMs: 300_000,
213
+ message: `Update ${local.plugin} from Qoder Hub ${hub.name}; a new session or /plugins reload is required before treating the updated plugin as loaded.`,
214
+ ...(tag ? { frozenTag: tag } : {}),
215
+ };
216
+ }
217
+ return {
218
+ targetKind: 'hub-backed',
219
+ executionMode: 'manual',
220
+ unitId: declaration.unitId,
221
+ host,
222
+ plugin: local.plugin,
223
+ hub,
224
+ message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
225
+ ...(unit?.publicRepo ? { publicRepo: unit.publicRepo } : {}),
226
+ ...(tag ? { frozenTag: tag } : {}),
227
+ };
228
+ });
186
229
  });
187
230
  }
188
231
 
@@ -240,6 +283,23 @@ function assertExecutableTarget(target) {
240
283
  }
241
284
  }
242
285
 
286
+ function assertQoderExecutableTarget(target) {
287
+ for (const field of [
288
+ 'unitId', 'plugin', 'marketplace', 'version', 'pluginRepo',
289
+ 'pluginCommit', 'snapshotPath', 'manifestDigest',
290
+ ]) {
291
+ if (typeof target[field] !== 'string' || target[field].length === 0) {
292
+ throw new Error(`Qoder local host update target is missing ${field}`);
293
+ }
294
+ }
295
+ if (!/^[a-f0-9]{40}$/u.test(target.pluginCommit)) {
296
+ throw new Error(`Qoder local host update requires the frozen public commit for unit ${target.unitId}`);
297
+ }
298
+ if (!/^[a-f0-9]{64}$/u.test(target.manifestDigest)) {
299
+ throw new Error(`Qoder local host update requires the frozen snapshot digest for unit ${target.unitId}`);
300
+ }
301
+ }
302
+
243
303
  function buildShipNextStep({ root, statePath, unitIds }) {
244
304
  const argv = ['release-skill', 'ship'];
245
305
  if (typeof root === 'string' && root.length > 0) argv.push('--root', root);
@@ -268,8 +328,12 @@ export function derivePostReleaseChecklist(plan, {
268
328
  const uncovered = units.filter((unit) => (
269
329
  !BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy)
270
330
  ));
271
- const executableTargets = pluginTargets(plan);
272
- const manualTargets = hubTargets(plan);
331
+ const declaredHubTargets = hubTargets(plan);
332
+ const executableTargets = [
333
+ ...pluginTargets(plan),
334
+ ...declaredHubTargets.filter((target) => target.executionMode === 'executable'),
335
+ ];
336
+ const manualTargets = declaredHubTargets.filter((target) => target.executionMode === 'manual');
273
337
  const targets = mergePostReleaseTargets(executableTargets, manualTargets);
274
338
  const hasPendingPostVerify = postVerifyHooks(plan).length > 0 && !postVerifyComplete && targets.length > 0;
275
339
  const hasStatePath = typeof statePath === 'string' && statePath.length > 0;
@@ -717,6 +781,215 @@ function normalizeGitSource(source) {
717
781
  .replace(/\/$/u, '');
718
782
  }
719
783
 
784
+ function normalizeHubGitSource(source, githubHost) {
785
+ return String(source ?? '')
786
+ .replace(/^git\+/, '')
787
+ .replace(new RegExp(`^https://${String(githubHost).replaceAll('.', '\\.').replaceAll('-', '\\-')}/`), '')
788
+ .replace(new RegExp(`^git@${String(githubHost).replaceAll('.', '\\.').replaceAll('-', '\\-')}:`), '')
789
+ .replace(/\.git$/u, '')
790
+ .replace(/\/$/u, '');
791
+ }
792
+
793
+ async function observeQoderMarketplace(target, command, env, run) {
794
+ const listed = await run(command, [...QODER_MARKETPLACE_LIST_ARGS], {
795
+ env,
796
+ timeout: target.timeoutMs,
797
+ });
798
+ const parsed = parseJson(listed.stdout, 'qoder marketplace list');
799
+ if (!Array.isArray(parsed)) throw new Error('qoder marketplace list did not return an array');
800
+ const matches = parsed.filter((entry) => entry?.name === target.marketplace);
801
+ if (matches.length === 0) return { installed: false };
802
+ if (matches.length !== 1) {
803
+ throw new Error(`qoder marketplace list returned conflicting entries for ${target.marketplace}`);
804
+ }
805
+ const [found] = matches;
806
+ if (
807
+ found.source?.source !== 'git'
808
+ || normalizeHubGitSource(found.source.url, target.hub.githubHost) !== target.hub.repo
809
+ ) {
810
+ throw new Error(`qoder marketplace ${target.marketplace} does not point to ${target.hub.repo}`);
811
+ }
812
+ if (typeof found.installLocation !== 'string' || found.installLocation.length === 0) {
813
+ throw new Error(`qoder marketplace ${target.marketplace} has no observable checkout root`);
814
+ }
815
+ const [remote, branch] = await Promise.all([
816
+ run('git', ['-C', found.installLocation, 'remote', 'get-url', 'origin'], {
817
+ env,
818
+ timeout: 30_000,
819
+ }),
820
+ run('git', ['-C', found.installLocation, 'symbolic-ref', '-q', 'HEAD'], {
821
+ env,
822
+ timeout: 30_000,
823
+ }),
824
+ ]);
825
+ if (
826
+ normalizeHubGitSource(remote.stdout.trim(), target.hub.githubHost) !== target.hub.repo
827
+ || branch.stdout.trim() !== target.hub.ref
828
+ ) {
829
+ throw new Error(`qoder marketplace ${target.marketplace} checkout does not match the frozen Hub source`);
830
+ }
831
+ return { installed: true, root: found.installLocation, found };
832
+ }
833
+
834
+ async function observeQoderPlugin(target, command, env, run) {
835
+ const listed = await run(command, [...QODER_PLUGIN_LIST_ARGS], {
836
+ env,
837
+ timeout: target.timeoutMs,
838
+ });
839
+ const parsed = parseJson(listed.stdout, 'qoder plugin list');
840
+ if (!Array.isArray(parsed)) throw new Error('qoder plugin list did not return an array');
841
+ const selector = `${target.plugin}@${target.marketplace}`;
842
+ const matches = parsed.filter((entry) => entry?.id === selector);
843
+ if (matches.length === 0) return { installed: false };
844
+ if (matches.length !== 1) {
845
+ throw new Error(`qoder plugin list returned conflicting entries for ${selector}`);
846
+ }
847
+ const [found] = matches;
848
+ if (
849
+ found.name !== target.plugin
850
+ || found.source !== selector
851
+ || found.scope !== 'user'
852
+ || typeof found.installPath !== 'string'
853
+ || found.installPath.length === 0
854
+ ) {
855
+ throw new Error(`qoder plugin ${selector} does not match its frozen user-scope identity`);
856
+ }
857
+ return {
858
+ installed: true,
859
+ exact: found.version === target.version,
860
+ installPath: found.installPath,
861
+ found,
862
+ };
863
+ }
864
+
865
+ async function readQoderManifest(root, label) {
866
+ const manifest = parseJson(
867
+ await readFileContained(root, '.qoder-plugin/plugin.json', { encoding: 'utf8' }),
868
+ label,
869
+ );
870
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
871
+ throw new Error(`${label} did not return an object`);
872
+ }
873
+ return manifest;
874
+ }
875
+
876
+ function assertQoderPluginManifest(target, manifest, label) {
877
+ if (
878
+ manifest.name !== target.plugin
879
+ || manifest.version !== target.version
880
+ || manifest.skills !== './adapters/qoder/skills/'
881
+ ) {
882
+ throw new Error(`${label} does not match the frozen Qoder plugin identity and projection path`);
883
+ }
884
+ }
885
+
886
+ async function assertFrozenQoderManifest(target, root) {
887
+ const { snapshotDir: snapshotRoot } = await verifyFrozenSnapshot({
888
+ root,
889
+ snapshotPath: target.snapshotPath,
890
+ expectedDigest: target.manifestDigest,
891
+ });
892
+ const manifest = await readQoderManifest(snapshotRoot, 'frozen Qoder plugin manifest');
893
+ assertQoderPluginManifest(target, manifest, 'frozen Qoder plugin manifest');
894
+ }
895
+
896
+ async function assertQoderHubEntry(target, marketplaceRoot) {
897
+ const marketplace = parseJson(
898
+ await readFileContained(marketplaceRoot, 'marketplace.json', { encoding: 'utf8' }),
899
+ 'Qoder Hub marketplace manifest',
900
+ );
901
+ if (marketplace?.name !== target.marketplace || !Array.isArray(marketplace.plugins)) {
902
+ throw new Error('Qoder Hub marketplace manifest does not match the frozen marketplace');
903
+ }
904
+ const matches = marketplace.plugins.filter((entry) => entry?.name === target.plugin);
905
+ if (matches.length !== 1) {
906
+ throw new Error(`Qoder Hub marketplace manifest must contain exactly one ${target.plugin} entry`);
907
+ }
908
+ const source = matches[0]?.source;
909
+ if (
910
+ source?.source !== 'url'
911
+ || normalizeHubGitSource(source.url, target.hub.githubHost) !== target.pluginRepo
912
+ || source.sha !== target.pluginCommit
913
+ ) {
914
+ throw new Error('Qoder Hub entry does not match the frozen public source identity');
915
+ }
916
+ }
917
+
918
+ async function verifyQoderInstalledPayload({
919
+ target,
920
+ root,
921
+ installPath,
922
+ verifyInstalledPayload,
923
+ }) {
924
+ const manifest = await readQoderManifest(installPath, 'installed Qoder plugin manifest');
925
+ assertQoderPluginManifest(target, manifest, 'installed Qoder plugin manifest');
926
+ await verifyInstalledPayload({
927
+ snapshotPath: target.snapshotPath,
928
+ manifestDigest: target.manifestDigest,
929
+ payloadContract: QODER_PAYLOAD_CONTRACT,
930
+ marketplaceLocation: 'external',
931
+ }, { root }, installPath, 'qoder');
932
+ }
933
+
934
+ async function runQoderUpdate(target, detected, run, {
935
+ root,
936
+ verifyInstalledPayload,
937
+ }) {
938
+ const env = hostEnvironment('qoder');
939
+ await assertFrozenQoderManifest(target, root);
940
+ const marketplace = await observeQoderMarketplace(target, detected.command, env, run);
941
+ if (!marketplace.installed) {
942
+ return {
943
+ status: 'MANUAL_REQUIRED',
944
+ reason: `qoder marketplace ${target.marketplace} is not installed; no marketplace was added`,
945
+ };
946
+ }
947
+ const before = await observeQoderPlugin(target, detected.command, env, run);
948
+ if (!before.installed) {
949
+ return {
950
+ status: 'MANUAL_REQUIRED',
951
+ reason: 'qoder target plugin is not installed; no initial installation was performed',
952
+ };
953
+ }
954
+ if (before.exact) {
955
+ await assertQoderHubEntry(target, marketplace.root);
956
+ await verifyQoderInstalledPayload({
957
+ target,
958
+ root,
959
+ installPath: before.installPath,
960
+ verifyInstalledPayload,
961
+ });
962
+ return { status: 'ALREADY_CURRENT', version: target.version };
963
+ }
964
+
965
+ await run(detected.command, ['plugins', 'marketplace', 'update', target.marketplace], {
966
+ env,
967
+ timeout: target.timeoutMs,
968
+ });
969
+ const refreshed = await observeQoderMarketplace(target, detected.command, env, run);
970
+ if (!refreshed.installed) {
971
+ throw new Error(`qoder marketplace ${target.marketplace} disappeared after refresh`);
972
+ }
973
+ await assertQoderHubEntry(target, refreshed.root);
974
+ await run(detected.command, [
975
+ 'plugins', 'update', `${target.plugin}@${target.marketplace}`, '--scope', 'user',
976
+ ], { env, timeout: target.timeoutMs });
977
+ const after = await observeQoderPlugin(target, detected.command, env, run);
978
+ if (!after.exact) throw new Error('qoder did not update to the frozen plugin version');
979
+ await verifyQoderInstalledPayload({
980
+ target,
981
+ root,
982
+ installPath: after.installPath,
983
+ verifyInstalledPayload,
984
+ });
985
+ return {
986
+ status: 'UPDATED',
987
+ version: target.version,
988
+ restartRequired: true,
989
+ reloadInstruction: 'Start a new Qoder session or run /plugins reload before checking the loaded version.',
990
+ };
991
+ }
992
+
720
993
  function parseFrozenRemoteRef(target, stdout) {
721
994
  const directRefs = new Map();
722
995
  const peeledRefs = new Map();
@@ -1046,14 +1319,14 @@ expect {
1046
1319
  eof { failEof remove-confirmation 126 }
1047
1320
  }
1048
1321
  expect {
1049
- -nocase -re {Trust this folder\\?} { directoryTrust }
1322
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1050
1323
  -re $promptPattern {}
1051
1324
  timeout { failTimeout remove-prompt 128 130 }
1052
1325
  eof { failEof remove-prompt 129 }
1053
1326
  }
1054
1327
  }
1055
1328
  -re $promptPattern {}
1056
- -nocase -re {Trust this folder\\?} { directoryTrust }
1329
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1057
1330
  timeout { failTimeout remove-dialog 122 124 }
1058
1331
  eof { failEof remove-dialog 123 }
1059
1332
  }
@@ -1128,9 +1401,133 @@ proc failEof {state code} {
1128
1401
  exit $code
1129
1402
  }
1130
1403
 
1131
- proc directoryTrust {} {
1132
- puts stderr "KIMI_TUI_STATE:directory-trust:manual-required"
1133
- exit 80
1404
+ proc unexpectedDirectoryTrust {} {
1405
+ puts stderr "KIMI_TUI_STATE:directory-trust:unexpected"
1406
+ exit 147
1407
+ }
1408
+
1409
+ proc readDirectoryTrustDialog {prefix state timeoutCode unknownCode eofCode} {
1410
+ global expect_out
1411
+ set dialogBuffer $prefix
1412
+ set cleanedPrefix [cleanScreen $dialogBuffer]
1413
+ if {![regexp -nocase {Trust this folder\\?} $cleanedPrefix]} {
1414
+ expect {
1415
+ -nocase -re {Trust this folder\\?} {
1416
+ append dialogBuffer $expect_out(buffer)
1417
+ }
1418
+ timeout { failTimeout $state $timeoutCode $unknownCode }
1419
+ eof { failEof $state $eofCode }
1420
+ }
1421
+ set cleanedPrefix [cleanScreen $dialogBuffer]
1422
+ }
1423
+ set framed [regexp -nocase {(^|\\n)[ \\t]*─{8,}[ \\t]*\\n[ \\t]*Trust this folder\\?} $cleanedPrefix]
1424
+ if {$framed} {
1425
+ expect {
1426
+ -re {(^|\\r|\\n)[ \\t]*─{8,}[ \\t]*\\r*\\n} {
1427
+ append dialogBuffer $expect_out(buffer)
1428
+ }
1429
+ timeout { failTimeout $state $timeoutCode $unknownCode }
1430
+ eof { failEof $state $eofCode }
1431
+ }
1432
+ } else {
1433
+ expect {
1434
+ -nocase -re {↑↓[^\\r\\n]*navigate[^\\r\\n]*(?:Esc[^\\r\\n]*)?\\r*\\n} {
1435
+ append dialogBuffer $expect_out(buffer)
1436
+ }
1437
+ timeout { failTimeout $state $timeoutCode $unknownCode }
1438
+ eof { failEof $state $eofCode }
1439
+ }
1440
+ }
1441
+ return [cleanScreen $dialogBuffer]
1442
+ }
1443
+
1444
+ proc directoryTrustAction {dialog state unknownCode} {
1445
+ set inDialog 0
1446
+ set labels {}
1447
+ set selectedCount 0
1448
+ set selectedIndex -1
1449
+ set selectedLabel ""
1450
+ set trustCount 0
1451
+ set trustIndex -1
1452
+ foreach rawLine [split $dialog "\\n"] {
1453
+ set line [string trim $rawLine]
1454
+ if {[regexp -nocase {^Trust this folder\\?$} $line]} {
1455
+ set inDialog 1
1456
+ continue
1457
+ }
1458
+ if {!$inDialog} { continue }
1459
+ if {$line eq ""} { continue }
1460
+ set selected [regexp {^❯[ \\t]*} $line]
1461
+ if {$selected} {
1462
+ regsub {^❯[ \\t]*} $line {} label
1463
+ set label [string trim $label]
1464
+ } else {
1465
+ set label $line
1466
+ }
1467
+ set knownAction [expr {
1468
+ [string equal -nocase $label "Trust this folder"]
1469
+ || [string equal -nocase $label "No, exit"]
1470
+ || [string equal -nocase $label "Don't trust"]
1471
+ }]
1472
+ if {!$knownAction && $selected} {
1473
+ puts stderr "KIMI_TUI_STATE:$state:selection-unknown"
1474
+ exit $unknownCode
1475
+ }
1476
+ if {!$knownAction} { continue }
1477
+ set index [llength $labels]
1478
+ lappend labels $label
1479
+ if {[string equal -nocase $label "Trust this folder"]} {
1480
+ incr trustCount
1481
+ set trustIndex $index
1482
+ }
1483
+ if {$selected} {
1484
+ incr selectedCount
1485
+ set selectedIndex $index
1486
+ set selectedLabel $label
1487
+ }
1488
+ }
1489
+ if {[llength $labels] != 2 || $trustCount != 1 || $selectedCount != 1} {
1490
+ puts stderr "KIMI_TUI_STATE:$state:selection-unknown"
1491
+ exit $unknownCode
1492
+ }
1493
+ if {[string equal -nocase $selectedLabel "Trust this folder"]} {
1494
+ return selected-trust
1495
+ }
1496
+ if {![string equal -nocase $selectedLabel "No, exit"]
1497
+ && ![string equal -nocase $selectedLabel "Don't trust"]} {
1498
+ puts stderr "KIMI_TUI_STATE:$state:selection-unknown"
1499
+ exit $unknownCode
1500
+ }
1501
+ if {$selectedIndex + 1 == $trustIndex} { return move-down }
1502
+ if {$selectedIndex - 1 == $trustIndex} { return move-up }
1503
+ puts stderr "KIMI_TUI_STATE:$state:selection-unknown"
1504
+ exit $unknownCode
1505
+ }
1506
+
1507
+ proc confirmInitialDirectoryTrust {} {
1508
+ global expect_out promptPattern
1509
+ set dialog [readDirectoryTrustDialog $expect_out(buffer) directory-trust-selected-row 140 143 141]
1510
+ set action [directoryTrustAction $dialog directory-trust 143]
1511
+ if {$action eq "move-down"} {
1512
+ send -- "\\033\\[B"
1513
+ } elseif {$action eq "move-up"} {
1514
+ send -- "\\033\\[A"
1515
+ }
1516
+ if {$action ne "selected-trust"} {
1517
+ set confirmedDialog [readDirectoryTrustDialog "" directory-trust-confirm-selection 144 146 145]
1518
+ set confirmedAction [directoryTrustAction $confirmedDialog directory-trust-confirm-selection 146]
1519
+ if {$confirmedAction ne "selected-trust"} {
1520
+ puts stderr "KIMI_TUI_STATE:directory-trust-confirm-selection:unknown"
1521
+ exit 146
1522
+ }
1523
+ }
1524
+ send -- "\\033\\[13u"
1525
+ expect {
1526
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1527
+ -re $promptPattern {}
1528
+ timeout { failTimeout directory-trust-prompt 151 153 }
1529
+ eof { failEof directory-trust-prompt 152 }
1530
+ }
1134
1531
  }
1135
1532
 
1136
1533
  proc submitCommand {command} {
@@ -1146,7 +1543,7 @@ if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]
1146
1543
  exit 131
1147
1544
  }
1148
1545
  expect {
1149
- -nocase -re {Trust this folder\\?} { directoryTrust }
1546
+ -nocase -re {Trust this folder\\?} { confirmInitialDirectoryTrust }
1150
1547
  -re $promptPattern {}
1151
1548
  timeout { failTimeout initial-prompt 101 103 }
1152
1549
  eof { failEof initial-prompt 102 }
@@ -1154,7 +1551,7 @@ expect {
1154
1551
  ${removeCommand}submitCommand "/plugins install $installUrl"
1155
1552
  set dialogBuffer ""
1156
1553
  expect {
1157
- -nocase -re {Trust this folder\\?} { directoryTrust }
1554
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1158
1555
  -nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
1159
1556
  append dialogBuffer $expect_out(buffer)
1160
1557
  }
@@ -1162,7 +1559,7 @@ expect {
1162
1559
  eof { failEof plugin-trust-anchor 105 }
1163
1560
  }
1164
1561
  expect {
1165
- -nocase -re {Trust this folder\\?} { directoryTrust }
1562
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1166
1563
  -nocase -re {❯[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
1167
1564
  append dialogBuffer $expect_out(buffer)
1168
1565
  }
@@ -1198,7 +1595,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*trust and install} $dialog]} {
1198
1595
  } elseif {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*(cancel|exit)} $dialog]} {
1199
1596
  send -- "\\033\\[B"
1200
1597
  expect {
1201
- -nocase -re {Trust this folder\\?} { directoryTrust }
1598
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1202
1599
  -nocase -re {❯[^\\r\\n]*Trust and install} {}
1203
1600
  -re {❯[^\\r\\n]*\\r*\\n} {
1204
1601
  puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
@@ -1214,7 +1611,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*trust and install} $dialog]} {
1214
1611
  }
1215
1612
 
1216
1613
  expect {
1217
- -nocase -re {Trust this folder\\?} { directoryTrust }
1614
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1218
1615
  -nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
1219
1616
  -nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
1220
1617
  exp_continue -continue_timer
@@ -1234,14 +1631,14 @@ expect {
1234
1631
  eof { failEof install-result 137 }
1235
1632
  }
1236
1633
  expect {
1237
- -nocase -re {Trust this folder\\?} { directoryTrust }
1634
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1238
1635
  -re $promptPattern {}
1239
1636
  timeout { failTimeout post-install-prompt 114 116 }
1240
1637
  eof { failEof post-install-prompt 115 }
1241
1638
  }
1242
1639
  submitCommand "/reload"
1243
1640
  expect {
1244
- -nocase -re {Trust this folder\\?} { directoryTrust }
1641
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
1245
1642
  -re $promptPattern {}
1246
1643
  timeout { failTimeout reload-prompt 117 119 }
1247
1644
  eof { failEof reload-prompt 118 }
@@ -1343,29 +1740,20 @@ async function runKimiUpdate(target, detected, run, kimiHome, {
1343
1740
  }
1344
1741
  const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
1345
1742
  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
- }
1743
+ await run(detected.expectCommand, ['-c', kimiExpectProgram({
1744
+ ...(removePlugin ? { removePlugin } : {}),
1745
+ })], {
1746
+ cwd: root,
1747
+ timeout: Math.max(300_000, target.timeoutMs),
1748
+ env: {
1749
+ ...env,
1750
+ RELEASE_SKILL_KIMI_COMMAND: detected.command,
1751
+ RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
1752
+ RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
1753
+ RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
1754
+ RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag,
1755
+ },
1756
+ });
1369
1757
  }, { prefix: 'release-skill-kimi-update-' });
1370
1758
  if (tuiOutcome) return tuiOutcome;
1371
1759
  const after = await observeKimiTarget(target, kimiHome, run);
@@ -1380,6 +1768,160 @@ async function runKimiUpdate(target, detected, run, kimiHome, {
1380
1768
  return { status: 'UPDATED', version: target.version, restartRequired: true };
1381
1769
  }
1382
1770
 
1771
+ async function observeCursorClosure(root) {
1772
+ return observeFilesystemTree({ root, rootBinding: await createFilesystemRootBinding(root) });
1773
+ }
1774
+
1775
+ async function cursorMainProcessRunning(run) {
1776
+ const result = await run('/bin/ps', ['-axo', 'comm='], { timeout: 30_000 });
1777
+ if (typeof result?.stdout !== 'string' || result.stdout.trim().length === 0) throw new Error('Cursor process observation is unavailable');
1778
+ return result.stdout.split('\n').some((line) => /(?:^|\/)Cursor$/u.test(line.trim()));
1779
+ }
1780
+
1781
+ async function runCursorLocalUpdate(target, {
1782
+ root, run, cursorPluginsRoot, cursorPlatform = process.platform,
1783
+ cursorIsRunning = () => cursorMainProcessRunning(run),
1784
+ cursorPublication = { publishFixedSet, replaceFixedSetAtomic },
1785
+ cursorMoveBackup = rename,
1786
+ }) {
1787
+ const frozen = await verifyFrozenSnapshot({ root, snapshotPath: target.snapshotPath, expectedDigest: target.manifestDigest });
1788
+ if (!target.cursor?.sourcePath) throw new Error('Cursor local update requires a frozen cursor.sourcePath');
1789
+ if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/u.test(target.plugin)) throw new Error('Cursor plugin identity must use a valid Cursor plugin name');
1790
+ const source = await resolveContained(frozen.snapshotDir, target.cursor.sourcePath);
1791
+ const identity = JSON.parse(await readFileContained(source, '.cursor-plugin/plugin.json', { encoding: 'utf8' }));
1792
+ if (typeof identity.name !== 'string' || !/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/u.test(identity.name)
1793
+ || identity.name !== target.plugin || identity.version !== target.version) throw new Error('Cursor frozen plugin manifest identity conflicts with the release unit');
1794
+ if (typeof cursorPluginsRoot !== 'string' || !isAbsolute(cursorPluginsRoot) || normalize(cursorPluginsRoot) !== cursorPluginsRoot) {
1795
+ throw new Error('Cursor local update requires --cursor-plugins-root <normalized-absolute-directory>');
1796
+ }
1797
+ if (cursorPlatform !== 'darwin') return { status: 'MANUAL_REQUIRED', reason: 'Cursor Local automatic installation currently supports macOS only' };
1798
+ try {
1799
+ if (await cursorIsRunning() !== false) return { status: 'MANUAL_REQUIRED', reason: 'Quit the Cursor main process before updating Local plugins' };
1800
+ } catch (cause) {
1801
+ return { status: 'MANUAL_REQUIRED', reason: `Cannot confirm Cursor has exited: ${cause.message}` };
1802
+ }
1803
+ // Bind the caller-selected root before creating any staging directory.
1804
+ await createFilesystemRootBinding(cursorPluginsRoot);
1805
+ const pluginsRoot = await realpath(cursorPluginsRoot);
1806
+ const sourceObservation = await observeCursorClosure(source);
1807
+ const workspace = await createTemporaryWorkspace({ prefix: 'release-skill-cursor-local-' });
1808
+ let sibling;
1809
+ let preserve = false;
1810
+ try {
1811
+ const candidate = join(workspace.root, 'plugin');
1812
+ await cp(source, candidate, { recursive: true, errorOnExist: true, force: false });
1813
+ if ((await observeCursorClosure(candidate)).membersDigest !== sourceObservation.membersDigest) throw new Error('Cursor candidate differs from the frozen source closure');
1814
+ await chmod(candidate, 0o700);
1815
+ if (target.cursor.dependencyInstall !== undefined) {
1816
+ if (target.cursor.dependencyInstall !== 'npm-ci-ignore-scripts') throw new Error('Unsupported Cursor dependency installation policy');
1817
+ await run('npm', ['ci', '--ignore-scripts', '--prefix', candidate], {
1818
+ cwd: candidate, timeout: target.timeoutMs, env: hostEnvironment('cursor'),
1819
+ });
1820
+ // npm may add dependencies, but must not mutate the frozen plugin inputs.
1821
+ for (const member of sourceObservation.members.filter((entry) => entry.type === 'file')) {
1822
+ await readFileStrict(candidate, member.path, { expectedSha256: member.sha256 });
1823
+ }
1824
+ }
1825
+ const prepared = await observeCursorClosure(candidate);
1826
+ await verifyFrozenSnapshot({ root, snapshotPath: target.snapshotPath, expectedDigest: target.manifestDigest });
1827
+ try {
1828
+ if (await cursorIsRunning() !== false) return { status: 'MANUAL_REQUIRED', reason: 'Cursor started while preparing the plugin; quit Cursor and rerun the update' };
1829
+ } catch (cause) {
1830
+ return { status: 'MANUAL_REQUIRED', reason: `Cannot confirm Cursor has exited before publication: ${cause.message}` };
1831
+ }
1832
+ const local = await resolveContained(pluginsRoot, 'local');
1833
+ await mkdir(local, { recursive: true });
1834
+ await createFilesystemRootBinding(local);
1835
+ const targetPath = await resolveContained(local, target.plugin);
1836
+ let installed = false;
1837
+ let previousClosure;
1838
+ try {
1839
+ await lstat(targetPath);
1840
+ installed = true;
1841
+ } catch (cause) { if (cause.code !== 'ENOENT') throw cause; }
1842
+ if (installed) {
1843
+ const current = JSON.parse(await readFileContained(targetPath, '.cursor-plugin/plugin.json', { encoding: 'utf8' }));
1844
+ if (current.name !== target.plugin || typeof current.version !== 'string') throw new Error('Cursor installed plugin manifest identity conflicts with the frozen plugin');
1845
+ const observed = await observeCursorClosure(targetPath);
1846
+ previousClosure = observed.membersDigest;
1847
+ if (current.version === target.version && observed.membersDigest === prepared.membersDigest) return { status: 'ALREADY_CURRENT', version: target.version, installPath: targetPath };
1848
+ }
1849
+ // Cursor is closed, so this same-parent candidate cannot be discovered by
1850
+ // a live scanner. Only this uniquely-created directory may be cleaned.
1851
+ sibling = await mkdtemp(join(local, `.${target.plugin}-stage-`));
1852
+ await cp(candidate, sibling, { recursive: true, errorOnExist: false, force: false });
1853
+ if ((await observeCursorClosure(sibling)).membersDigest !== prepared.membersDigest) throw new Error('Cursor publication candidate closure drift');
1854
+ const publication = { sourceRoot: sibling, targetParent: local, targetSegment: target.plugin };
1855
+ let backupPath;
1856
+ if (installed) {
1857
+ try {
1858
+ await cursorPublication.replaceFixedSetAtomic(publication);
1859
+ } catch (cause) {
1860
+ preserve = cause.details?.phase === 'post-commit' || cause.details?.publicationState === 'indeterminate' || cause.details?.commitState === 'indeterminate';
1861
+ throw cause;
1862
+ }
1863
+ // Foundation has proved the exchanged mapping. The old complete plugin
1864
+ // is now owned by the user and must leave the Local scanner namespace.
1865
+ preserve = true;
1866
+ try {
1867
+ const backups = await resolveContained(pluginsRoot, `backups/${target.plugin}`);
1868
+ await mkdir(backups, { recursive: true });
1869
+ await createFilesystemRootBinding(backups);
1870
+ const backupDirectory = await mkdtemp(join(backups, 'release-'));
1871
+ backupPath = join(backupDirectory, 'plugin');
1872
+ await cursorMoveBackup(sibling, backupPath);
1873
+ sibling = undefined;
1874
+ preserve = false;
1875
+ } catch (cause) {
1876
+ // One explicit reverse exchange is justified only by the verified
1877
+ // success above and an old directory still at the displaced path.
1878
+ if ((await observeCursorClosure(sibling)).membersDigest !== previousClosure
1879
+ || (await observeCursorClosure(targetPath)).membersDigest !== prepared.membersDigest) throw cause;
1880
+ await cursorPublication.replaceFixedSetAtomic(publication);
1881
+ preserve = false;
1882
+ throw new Error(`Cursor backup move failed; original plugin restored: ${cause.message}`);
1883
+ }
1884
+ } else {
1885
+ const manifest = await createFixedSetPublicationManifest(publication);
1886
+ const receipt = await cursorPublication.publishFixedSet({ ...publication, manifest });
1887
+ if (receipt.status !== 'succeeded') {
1888
+ preserve = receipt.status === 'indeterminate' || receipt.commitState !== 'not-committed';
1889
+ const error = new Error(`Cursor publication failed: ${receipt.error?.message ?? receipt.status}`);
1890
+ error.details = receipt;
1891
+ throw error;
1892
+ }
1893
+ sibling = undefined;
1894
+ }
1895
+ if ((await observeCursorClosure(targetPath)).membersDigest !== prepared.membersDigest) throw new Error('Cursor installed plugin closure drift after publication');
1896
+ return {
1897
+ status: 'UPDATED', version: target.version, installPath: targetPath,
1898
+ ...(backupPath ? { backupPath } : {}), restartRequired: true,
1899
+ reloadInstruction: 'Restart Cursor or run Reload Window, then verify the Local source, version and Skill invocation.',
1900
+ };
1901
+ } catch (error) {
1902
+ if (preserve) error.details = { ...error.details, candidatePath: sibling, workspacePath: workspace.root, manualAction: 'Keep both directories intact; inspect the Cursor Local publication mapping before retrying.' };
1903
+ throw error;
1904
+ } finally {
1905
+ if (!preserve) {
1906
+ // Sealed snapshots carry read-only directories. Restore owner access
1907
+ // only in this invocation's disposable copies before Foundation cleanup.
1908
+ for (const cleanupRoot of [sibling, workspace.root].filter(Boolean)) {
1909
+ const closure = await observeFilesystemTree({
1910
+ root: cleanupRoot,
1911
+ rootBinding: await createFilesystemRootBinding(cleanupRoot),
1912
+ symlinkPolicy: { mode: 'record' },
1913
+ });
1914
+ await chmod(cleanupRoot, 0o700);
1915
+ for (const member of closure.members.filter((entry) => entry.type === 'directory')) {
1916
+ await chmod(await resolveContained(cleanupRoot, member.path), member.statMode | 0o700);
1917
+ }
1918
+ }
1919
+ if (sibling) await rm(sibling, { recursive: true, force: false });
1920
+ await workspace.dispose();
1921
+ }
1922
+ }
1923
+ }
1924
+
1383
1925
  function aggregateStatus(results) {
1384
1926
  const statuses = new Set(results.map((entry) => entry.status));
1385
1927
  const completed = results.some((entry) => ['UPDATED', 'ALREADY_CURRENT'].includes(entry.status));
@@ -1419,6 +1961,11 @@ async function updateLocalHostPluginsInternal({
1419
1961
  detect = (host) => defaultDetect(host, defaultRun),
1420
1962
  run = defaultRun,
1421
1963
  kimiHome,
1964
+ cursorPluginsRoot,
1965
+ cursorPlatform,
1966
+ cursorIsRunning,
1967
+ cursorPublication,
1968
+ cursorMoveBackup,
1422
1969
  verifyInstalledPayload = verifyInstalledMarketplacePayload,
1423
1970
  } = {}) {
1424
1971
  const effectiveKimiHome = kimiHome ?? process.env.KIMI_CODE_HOME ?? join(homedir(), '.kimi-code');
@@ -1429,12 +1976,25 @@ async function updateLocalHostPluginsInternal({
1429
1976
  const selected = new Set(Array.isArray(selectedHosts) ? selectedHosts : []);
1430
1977
  const unknown = [...selected].filter((host) => !checklist.localHostUpdate.hosts.includes(host));
1431
1978
  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);
1979
+ const targets = checklist.localHostUpdate.targets.filter((item) => (
1980
+ item.executionMode !== 'manual' && selected.has(item.host)
1981
+ ));
1982
+ for (const target of targets) {
1983
+ if (target.host === 'cursor') continue;
1984
+ if (target.host === 'qoder') assertQoderExecutableTarget(target);
1985
+ else assertExecutableTarget(target);
1986
+ }
1434
1987
 
1435
1988
  const results = [];
1436
1989
  for (const target of targets) {
1437
1990
  try {
1991
+ if (target.host === 'cursor') {
1992
+ const outcome = await runCursorLocalUpdate(target, {
1993
+ root, run, cursorPluginsRoot, cursorPlatform, cursorIsRunning, cursorPublication, cursorMoveBackup,
1994
+ });
1995
+ results.push({ host: target.host, unitId: target.unitId, ...outcome });
1996
+ continue;
1997
+ }
1438
1998
  const detected = await detect(target.host);
1439
1999
  if (!detected?.available) {
1440
2000
  results.push({
@@ -1451,7 +2011,12 @@ async function updateLocalHostPluginsInternal({
1451
2011
  root,
1452
2012
  verifyInstalledPayload,
1453
2013
  })
1454
- : await runStructuredUpdate(target, detected, run, {
2014
+ : target.host === 'qoder'
2015
+ ? await runQoderUpdate(target, detected, run, {
2016
+ root,
2017
+ verifyInstalledPayload,
2018
+ })
2019
+ : await runStructuredUpdate(target, detected, run, {
1455
2020
  plan,
1456
2021
  root,
1457
2022
  verifyInstalledPayload,