@skaile/workspaces 6.0.0 → 6.1.0

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 (320) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/{asset-feeds-PXUWCXVI.js → asset-feeds-HJFV7UPW.js} +26 -18
  3. package/dist/asset-feeds-HJFV7UPW.js.map +1 -0
  4. package/dist/asset-manager/catalog-deployer.js +7 -7
  5. package/dist/asset-manager/index.js +13 -13
  6. package/dist/asset-manager/installer.js +8 -8
  7. package/dist/asset-manager/renderers.js +7 -7
  8. package/dist/asset-manager/src/index.d.ts +192 -5
  9. package/dist/asset-manager/src/index.d.ts.map +1 -1
  10. package/dist/asset-manager/src/installer.d.ts +31 -0
  11. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  12. package/dist/asset-manager/src/mcp-config.d.ts +21 -0
  13. package/dist/asset-manager/src/mcp-config.d.ts.map +1 -1
  14. package/dist/bridge/harnesses/claude-sdk.js +13 -17
  15. package/dist/bridge/harnesses/claude-sdk.js.map +1 -1
  16. package/dist/bridge/harnesses/codex.js +10 -10
  17. package/dist/bridge/harnesses/continue.js +10 -10
  18. package/dist/bridge/harnesses/echo.js +2 -2
  19. package/dist/bridge/harnesses/gemini.js +6 -6
  20. package/dist/bridge/harnesses/goose.js +10 -10
  21. package/dist/bridge/harnesses/omp.js +4 -4
  22. package/dist/bridge/harnesses/opencode.js +10 -10
  23. package/dist/bridge/harnesses/qwen.js +6 -6
  24. package/dist/bridge/index.js +14 -14
  25. package/dist/bridge/src/claude-sdk-surface.d.ts +17 -0
  26. package/dist/bridge/src/claude-sdk-surface.d.ts.map +1 -0
  27. package/dist/bridge/src/harness-model-check.d.ts +15 -0
  28. package/dist/bridge/src/harness-model-check.d.ts.map +1 -1
  29. package/dist/bridge/src/harnesses/claude-sdk.d.ts.map +1 -1
  30. package/dist/bridge/src/index.d.ts +2 -1
  31. package/dist/bridge/src/index.d.ts.map +1 -1
  32. package/dist/bridge/src/normalizer.d.ts +3 -1
  33. package/dist/bridge/src/normalizer.d.ts.map +1 -1
  34. package/dist/{chunk-VJRTDKXS.js → chunk-2LAPHLDH.js} +144 -94
  35. package/dist/chunk-2LAPHLDH.js.map +1 -0
  36. package/dist/{chunk-VXEYX73S.js → chunk-3GMOPFWH.js} +4 -4
  37. package/dist/{chunk-VXEYX73S.js.map → chunk-3GMOPFWH.js.map} +1 -1
  38. package/dist/{chunk-MCUJTNY5.js → chunk-4FVIT5DI.js} +3 -3
  39. package/dist/{chunk-MCUJTNY5.js.map → chunk-4FVIT5DI.js.map} +1 -1
  40. package/dist/{chunk-SZXGRHQQ.js → chunk-4MUDQCBP.js} +4 -4
  41. package/dist/{chunk-SZXGRHQQ.js.map → chunk-4MUDQCBP.js.map} +1 -1
  42. package/dist/{chunk-7CV3HFWJ.js → chunk-5J4NPGFM.js} +3 -3
  43. package/dist/{chunk-7CV3HFWJ.js.map → chunk-5J4NPGFM.js.map} +1 -1
  44. package/dist/{chunk-4XWLB7JB.js → chunk-5Y2KGB4H.js} +69 -34
  45. package/dist/chunk-5Y2KGB4H.js.map +1 -0
  46. package/dist/{chunk-OULSJRXV.js → chunk-6YR4ALVX.js} +6 -6
  47. package/dist/{chunk-OULSJRXV.js.map → chunk-6YR4ALVX.js.map} +1 -1
  48. package/dist/chunk-7HX6FNKL.js +317 -0
  49. package/dist/chunk-7HX6FNKL.js.map +1 -0
  50. package/dist/{chunk-WDAIWDLK.js → chunk-7LP7RCZI.js} +4 -4
  51. package/dist/{chunk-WDAIWDLK.js.map → chunk-7LP7RCZI.js.map} +1 -1
  52. package/dist/{chunk-2UUKCROR.js → chunk-AATHJRKL.js} +4 -4
  53. package/dist/{chunk-2UUKCROR.js.map → chunk-AATHJRKL.js.map} +1 -1
  54. package/dist/{chunk-DUMTLYSO.js → chunk-ABFPSVIS.js} +3 -3
  55. package/dist/{chunk-DUMTLYSO.js.map → chunk-ABFPSVIS.js.map} +1 -1
  56. package/dist/{chunk-TKHJXPSB.js → chunk-AITJ7ELM.js} +140 -26
  57. package/dist/chunk-AITJ7ELM.js.map +1 -0
  58. package/dist/{chunk-Y2TO2U7W.js → chunk-ASWTQQ7E.js} +3 -3
  59. package/dist/{chunk-Y2TO2U7W.js.map → chunk-ASWTQQ7E.js.map} +1 -1
  60. package/dist/{chunk-WYHQUCHR.js → chunk-AZ2H6R5I.js} +51 -25
  61. package/dist/chunk-AZ2H6R5I.js.map +1 -0
  62. package/dist/{chunk-LN6QB7XI.js → chunk-BBBCJSSU.js} +4 -4
  63. package/dist/{chunk-LN6QB7XI.js.map → chunk-BBBCJSSU.js.map} +1 -1
  64. package/dist/{chunk-WEH33JSG.js → chunk-CEKW3KXP.js} +87 -26
  65. package/dist/chunk-CEKW3KXP.js.map +1 -0
  66. package/dist/{chunk-ARWKK325.js → chunk-COYF6IG2.js} +5 -5
  67. package/dist/{chunk-ARWKK325.js.map → chunk-COYF6IG2.js.map} +1 -1
  68. package/dist/{chunk-SNEE6UGY.js → chunk-DGTI65OC.js} +11 -11
  69. package/dist/{chunk-SNEE6UGY.js.map → chunk-DGTI65OC.js.map} +1 -1
  70. package/dist/{chunk-HK7HMEPT.js → chunk-DIGFA7Z5.js} +3 -3
  71. package/dist/{chunk-HK7HMEPT.js.map → chunk-DIGFA7Z5.js.map} +1 -1
  72. package/dist/{chunk-AF432MS3.js → chunk-EJPMNDTP.js} +3 -3
  73. package/dist/{chunk-AF432MS3.js.map → chunk-EJPMNDTP.js.map} +1 -1
  74. package/dist/chunk-EX3GLAL6.js +31 -0
  75. package/dist/chunk-EX3GLAL6.js.map +1 -0
  76. package/dist/{chunk-UVVVR4XO.js → chunk-FKQNBCDW.js} +3 -3
  77. package/dist/{chunk-UVVVR4XO.js.map → chunk-FKQNBCDW.js.map} +1 -1
  78. package/dist/{chunk-GEQSUC6U.js → chunk-GNZBFSRB.js} +3 -3
  79. package/dist/{chunk-GEQSUC6U.js.map → chunk-GNZBFSRB.js.map} +1 -1
  80. package/dist/{chunk-U56GGCC2.js → chunk-HMISULVD.js} +4 -4
  81. package/dist/{chunk-U56GGCC2.js.map → chunk-HMISULVD.js.map} +1 -1
  82. package/dist/{chunk-GN4VVSAA.js → chunk-HPMQRAIF.js} +7 -7
  83. package/dist/{chunk-GN4VVSAA.js.map → chunk-HPMQRAIF.js.map} +1 -1
  84. package/dist/{chunk-OAHVKSQJ.js → chunk-HWNK3F3X.js} +913 -80
  85. package/dist/chunk-HWNK3F3X.js.map +1 -0
  86. package/dist/{chunk-NMI2TWMH.js → chunk-IHFGPHCT.js} +131 -341
  87. package/dist/chunk-IHFGPHCT.js.map +1 -0
  88. package/dist/{chunk-F2GOZMRA.js → chunk-ILLTBKDJ.js} +47 -47
  89. package/dist/chunk-ILLTBKDJ.js.map +1 -0
  90. package/dist/{chunk-FKU57Q5Q.js → chunk-IRYKRKVQ.js} +3 -3
  91. package/dist/{chunk-FKU57Q5Q.js.map → chunk-IRYKRKVQ.js.map} +1 -1
  92. package/dist/{chunk-T2BDID2J.js → chunk-J72MO2WA.js} +8 -8
  93. package/dist/{chunk-T2BDID2J.js.map → chunk-J72MO2WA.js.map} +1 -1
  94. package/dist/{chunk-X3RMRJNR.js → chunk-JRN5QF4C.js} +8 -3
  95. package/dist/chunk-JRN5QF4C.js.map +1 -0
  96. package/dist/{chunk-E3GPGTW3.js → chunk-KUQAJVYT.js} +5 -5
  97. package/dist/{chunk-E3GPGTW3.js.map → chunk-KUQAJVYT.js.map} +1 -1
  98. package/dist/{chunk-WDXZA7QW.js → chunk-LSSYC5IC.js} +2 -2
  99. package/dist/{chunk-WDXZA7QW.js.map → chunk-LSSYC5IC.js.map} +1 -1
  100. package/dist/{chunk-3IXIEHUB.js → chunk-NB7SYQYP.js} +5 -5
  101. package/dist/{chunk-3IXIEHUB.js.map → chunk-NB7SYQYP.js.map} +1 -1
  102. package/dist/{chunk-3JKGY2YC.js → chunk-NUA33BYO.js} +149 -27
  103. package/dist/chunk-NUA33BYO.js.map +1 -0
  104. package/dist/{chunk-VBI7HGHB.js → chunk-QAKUD6QN.js} +2 -2
  105. package/dist/{chunk-VBI7HGHB.js.map → chunk-QAKUD6QN.js.map} +1 -1
  106. package/dist/{chunk-KX2PT5Q4.js → chunk-QEULL4Q3.js} +4 -4
  107. package/dist/{chunk-KX2PT5Q4.js.map → chunk-QEULL4Q3.js.map} +1 -1
  108. package/dist/{chunk-YGRZNFRO.js → chunk-QSYQOTWN.js} +3 -3
  109. package/dist/{chunk-YGRZNFRO.js.map → chunk-QSYQOTWN.js.map} +1 -1
  110. package/dist/{chunk-DHYSUJ3H.js → chunk-RDTLPOPD.js} +4 -4
  111. package/dist/{chunk-DHYSUJ3H.js.map → chunk-RDTLPOPD.js.map} +1 -1
  112. package/dist/{chunk-GOCWM7FN.js → chunk-RHN5MRQO.js} +21 -4
  113. package/dist/chunk-RHN5MRQO.js.map +1 -0
  114. package/dist/{chunk-UL22SDGR.js → chunk-S2XHGWQC.js} +161 -123
  115. package/dist/chunk-S2XHGWQC.js.map +1 -0
  116. package/dist/{chunk-4DWBSTYA.js → chunk-SVTMNSPI.js} +22 -5
  117. package/dist/chunk-SVTMNSPI.js.map +1 -0
  118. package/dist/{chunk-QOM5Z62G.js → chunk-UI745XC3.js} +9 -22
  119. package/dist/chunk-UI745XC3.js.map +1 -0
  120. package/dist/{chunk-L4YKW5AY.js → chunk-VMVDAZLA.js} +20 -8
  121. package/dist/chunk-VMVDAZLA.js.map +1 -0
  122. package/dist/{chunk-H6UVIKQ2.js → chunk-VYVTEMAW.js} +3 -3
  123. package/dist/{chunk-H6UVIKQ2.js.map → chunk-VYVTEMAW.js.map} +1 -1
  124. package/dist/{chunk-SRODW6M2.js → chunk-X3YGUKCG.js} +3 -3
  125. package/dist/{chunk-SRODW6M2.js.map → chunk-X3YGUKCG.js.map} +1 -1
  126. package/dist/{chunk-JDR6ILCW.js → chunk-XGULGQA3.js} +3 -3
  127. package/dist/{chunk-JDR6ILCW.js.map → chunk-XGULGQA3.js.map} +1 -1
  128. package/dist/{chunk-7XSAW5OC.js → chunk-YLLVSR2U.js} +4 -4
  129. package/dist/{chunk-7XSAW5OC.js.map → chunk-YLLVSR2U.js.map} +1 -1
  130. package/dist/{chunk-6Z5KTFJT.js → chunk-Z3VKUWAX.js} +3 -3
  131. package/dist/{chunk-6Z5KTFJT.js.map → chunk-Z3VKUWAX.js.map} +1 -1
  132. package/dist/{chunk-7TPYPVAH.js → chunk-ZAELRYKS.js} +3 -3
  133. package/dist/{chunk-7TPYPVAH.js.map → chunk-ZAELRYKS.js.map} +1 -1
  134. package/dist/{chunk-UQ33Y4DM.js → chunk-ZBHZLWV3.js} +4 -4
  135. package/dist/{chunk-UQ33Y4DM.js.map → chunk-ZBHZLWV3.js.map} +1 -1
  136. package/dist/{chunk-OTIB4D2K.js → chunk-ZDLUQLI6.js} +4 -4
  137. package/dist/{chunk-OTIB4D2K.js.map → chunk-ZDLUQLI6.js.map} +1 -1
  138. package/dist/{chunk-OHNKYHRR.js → chunk-ZHGKAZJ7.js} +214 -16
  139. package/dist/chunk-ZHGKAZJ7.js.map +1 -0
  140. package/dist/{claude-code-VJGYBXQR.js → claude-code-RE46FJXI.js} +4 -4
  141. package/dist/{claude-code-VJGYBXQR.js.map → claude-code-RE46FJXI.js.map} +1 -1
  142. package/dist/cli/index.js +704 -448
  143. package/dist/cli/index.js.map +1 -1
  144. package/dist/cli/src/agent-pins.d.ts +11 -0
  145. package/dist/cli/src/agent-pins.d.ts.map +1 -1
  146. package/dist/cli/src/commands/catalog.d.ts +1 -1
  147. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  148. package/dist/cli/src/commands/config.d.ts.map +1 -1
  149. package/dist/cli/src/commands/diff.d.ts.map +1 -1
  150. package/dist/cli/src/commands/harness.d.ts +24 -0
  151. package/dist/cli/src/commands/harness.d.ts.map +1 -0
  152. package/dist/cli/src/commands/integration.d.ts.map +1 -1
  153. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  154. package/dist/cli/src/commands/library-index.d.ts +20 -2
  155. package/dist/cli/src/commands/library-index.d.ts.map +1 -1
  156. package/dist/cli/src/commands/library-publish.d.ts.map +1 -1
  157. package/dist/cli/src/commands/migrate-assets.d.ts.map +1 -1
  158. package/dist/cli/src/commands/project.d.ts +19 -1
  159. package/dist/cli/src/commands/project.d.ts.map +1 -1
  160. package/dist/cli/src/commands/run.d.ts.map +1 -1
  161. package/dist/cli/src/commands/serve-preflight.d.ts.map +1 -1
  162. package/dist/cli/src/commands/session-logs/local-query.d.ts +2 -0
  163. package/dist/cli/src/commands/session-logs/local-query.d.ts.map +1 -1
  164. package/dist/cli/src/commands/source.d.ts +9 -2
  165. package/dist/cli/src/commands/source.d.ts.map +1 -1
  166. package/dist/cli/src/commands/store.d.ts.map +1 -1
  167. package/dist/cli/src/commands/update.d.ts.map +1 -1
  168. package/dist/cli/src/commands/validate.d.ts.map +1 -1
  169. package/dist/cli/src/config-checks.d.ts +37 -0
  170. package/dist/cli/src/config-checks.d.ts.map +1 -0
  171. package/dist/cli/src/skill-checks.d.ts +27 -0
  172. package/dist/cli/src/skill-checks.d.ts.map +1 -1
  173. package/dist/cli/src/store-addable.d.ts +11 -0
  174. package/dist/cli/src/store-addable.d.ts.map +1 -1
  175. package/dist/connectors/config.js +5 -5
  176. package/dist/connectors/index.js +18 -18
  177. package/dist/{connectors-CXZLXAFZ.js → connectors-7THEF7YA.js} +21 -21
  178. package/dist/{connectors-CXZLXAFZ.js.map → connectors-7THEF7YA.js.map} +1 -1
  179. package/dist/core/index.js +6 -6
  180. package/dist/core/manifest.js +2 -2
  181. package/dist/core/models.js +1 -1
  182. package/dist/core/publish-manifest.js +3 -3
  183. package/dist/core/ref.js +1 -1
  184. package/dist/core/runtime-assets.js +5 -5
  185. package/dist/core/src/agent-instances.d.ts.map +1 -1
  186. package/dist/core/src/config-key-diagnostics.d.ts +9 -0
  187. package/dist/core/src/config-key-diagnostics.d.ts.map +1 -0
  188. package/dist/core/src/index.d.ts +1 -1
  189. package/dist/core/src/index.d.ts.map +1 -1
  190. package/dist/core/src/manifest.d.ts +10 -0
  191. package/dist/core/src/manifest.d.ts.map +1 -1
  192. package/dist/core/src/models.d.ts +8 -0
  193. package/dist/core/src/models.d.ts.map +1 -1
  194. package/dist/core/src/workspace-config.d.ts +8 -0
  195. package/dist/core/src/workspace-config.d.ts.map +1 -1
  196. package/dist/core/src/workspace-yaml-editor.d.ts +9 -2
  197. package/dist/core/src/workspace-yaml-editor.d.ts.map +1 -1
  198. package/dist/core/workspace-config.js +4 -4
  199. package/dist/deploy/index.js +6 -6
  200. package/dist/discovery/index.js +4 -4
  201. package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
  202. package/dist/discovery/src/parse-manifest.d.ts.map +1 -1
  203. package/dist/{ensure-sources-UPTHZKGF.js → ensure-sources-IH7CHSJF.js} +16 -16
  204. package/dist/{ensure-sources-UPTHZKGF.js.map → ensure-sources-IH7CHSJF.js.map} +1 -1
  205. package/dist/factory-assets/connectors/box.js +18 -18
  206. package/dist/factory-assets/connectors/deploy.js +18 -18
  207. package/dist/factory-assets/connectors/devserver.js +18 -18
  208. package/dist/factory-assets/connectors/flow/adapter.js +18 -18
  209. package/dist/factory-assets/connectors/flow/contract/normalize.d.ts.map +1 -1
  210. package/dist/factory-assets/connectors/flow/contract.js +1 -1
  211. package/dist/factory-assets/connectors/flow/engine.js +8 -8
  212. package/dist/factory-assets/connectors/flow/run-flow.js +20 -20
  213. package/dist/factory-assets/connectors/flow.js +18 -18
  214. package/dist/factory-assets/connectors/git.js +18 -18
  215. package/dist/factory-assets/connectors/gmail.js +18 -18
  216. package/dist/factory-assets/connectors/googledrive.js +18 -18
  217. package/dist/factory-assets/connectors/local.js +18 -18
  218. package/dist/factory-assets/connectors/mattermost.js +18 -18
  219. package/dist/factory-assets/connectors/memory.js +18 -18
  220. package/dist/factory-assets/connectors/minio.js +18 -18
  221. package/dist/factory-assets/connectors/postgres.js +18 -18
  222. package/dist/factory-assets/connectors/s3.js +18 -18
  223. package/dist/factory-assets/connectors/sharepoint.js +18 -18
  224. package/dist/factory-assets/connectors/sqlite.js +18 -18
  225. package/dist/factory-assets/connectors/static-server.js +18 -18
  226. package/dist/factory-assets/connectors/tunnel.js +18 -18
  227. package/dist/factory-assets/connectors/webdav.js +18 -18
  228. package/dist/factory-assets/connectors/xstate-store.js +18 -18
  229. package/dist/factory-assets/connectors/xstate.js +18 -18
  230. package/dist/factory-assets/skills/authoring-skaile-assets/SKILL.md +1 -1
  231. package/dist/factory-assets/skills/authoring-skaile-manifests/SKILL.md +2 -2
  232. package/dist/factory-assets/skills/authoring-skaile-manifests/references/dependency-standard.md +3 -3
  233. package/dist/{flows-TJQIMGUX.js → flows-4JZQ3XFK.js} +11 -11
  234. package/dist/{flows-TJQIMGUX.js.map → flows-4JZQ3XFK.js.map} +1 -1
  235. package/dist/{helpers-DE53KG3E.js → helpers-JNMK3FC2.js} +16 -16
  236. package/dist/{helpers-DE53KG3E.js.map → helpers-JNMK3FC2.js.map} +1 -1
  237. package/dist/library/index.js +9 -9
  238. package/dist/library/install.js +4 -4
  239. package/dist/library/src/index.d.ts +1 -1
  240. package/dist/library/src/index.d.ts.map +1 -1
  241. package/dist/library/src/local/library-dir.d.ts +22 -5
  242. package/dist/library/src/local/library-dir.d.ts.map +1 -1
  243. package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
  244. package/dist/library/src/remote/remote-catalog-source.d.ts +6 -0
  245. package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
  246. package/dist/{library-publish-KKQ2ULLX.js → library-publish-I3UWXAHI.js} +52 -50
  247. package/dist/library-publish-I3UWXAHI.js.map +1 -0
  248. package/dist/model-endpoint/index.js +9 -9
  249. package/dist/{open-library-RJGJIW5H.js → open-library-NKKUJLWA.js} +19 -19
  250. package/dist/{open-library-RJGJIW5H.js.map → open-library-NKKUJLWA.js.map} +1 -1
  251. package/dist/{open-registry-3QS3J7KO.js → open-registry-BYAXSHUI.js} +14 -14
  252. package/dist/{open-registry-3QS3J7KO.js.map → open-registry-BYAXSHUI.js.map} +1 -1
  253. package/dist/{paths-QAV4C3KX.js → paths-UKWE35XS.js} +9 -9
  254. package/dist/{paths-QAV4C3KX.js.map → paths-UKWE35XS.js.map} +1 -1
  255. package/dist/{plugin-store-MSH6GK6T.js → plugin-store-ABE35NRX.js} +9 -9
  256. package/dist/{plugin-store-MSH6GK6T.js.map → plugin-store-ABE35NRX.js.map} +1 -1
  257. package/dist/project-BIPK47JK.js +86 -0
  258. package/dist/{project-HZW2MZPA.js.map → project-BIPK47JK.js.map} +1 -1
  259. package/dist/resolver/index.js +7 -7
  260. package/dist/runner/index.js +33 -33
  261. package/dist/runner/src/composition/content-source.d.ts.map +1 -1
  262. package/dist/runner/src/composition/errors.d.ts +16 -1
  263. package/dist/runner/src/composition/errors.d.ts.map +1 -1
  264. package/dist/runner/src/credential-notice.d.ts +3 -3
  265. package/dist/runner/src/credential-notice.d.ts.map +1 -1
  266. package/dist/runner/src/serve.d.ts +14 -2
  267. package/dist/runner/src/serve.d.ts.map +1 -1
  268. package/dist/sdk/asset-manager.js +13 -13
  269. package/dist/sdk/bridge.js +14 -14
  270. package/dist/sdk/core.js +6 -6
  271. package/dist/sdk/flow.js +8 -8
  272. package/dist/sdk/index.js +33 -33
  273. package/dist/sdk/resolver.js +7 -7
  274. package/dist/sdk/runner.js +33 -33
  275. package/dist/sdk/transport/ws/server.js +7 -7
  276. package/dist/sdk/transport/ws.js +7 -7
  277. package/dist/sdk/transport.js +7 -7
  278. package/dist/{serve-preflight-L4POEHNQ.js → serve-preflight-FLTIBTEX.js} +28 -29
  279. package/dist/serve-preflight-FLTIBTEX.js.map +1 -0
  280. package/dist/{setup-AGYB4PKR.js → setup-AG6NFHSN.js} +25 -25
  281. package/dist/{setup-AGYB4PKR.js.map → setup-AG6NFHSN.js.map} +1 -1
  282. package/dist/{skills-2LMQ2IAD.js → skills-UQYUHVKO.js} +12 -12
  283. package/dist/{skills-2LMQ2IAD.js.map → skills-UQYUHVKO.js.map} +1 -1
  284. package/dist/source-ASEHI2XS.js +85 -0
  285. package/dist/{source-GBMYD3RO.js.map → source-ASEHI2XS.js.map} +1 -1
  286. package/dist/{source-manifest-publish-FK22TVQT.js → source-manifest-publish-IEVV3W6J.js} +45 -45
  287. package/dist/{source-manifest-publish-FK22TVQT.js.map → source-manifest-publish-IEVV3W6J.js.map} +1 -1
  288. package/dist/transport/index.js +7 -7
  289. package/dist/transport/ws/server.js +7 -7
  290. package/dist/transport/ws.js +7 -7
  291. package/dist/tui/index.js +33 -33
  292. package/dist/types/src/install-manifest.d.ts +1 -1
  293. package/dist/types/src/install-manifest.d.ts.map +1 -1
  294. package/dist/workspace-plugin/adapters/mcp.js +25 -25
  295. package/dist/workspace-plugin/adapters/omp.js +14 -14
  296. package/dist/workspace-plugin/index.js +1 -1
  297. package/package.json +1 -1
  298. package/dist/asset-feeds-PXUWCXVI.js.map +0 -1
  299. package/dist/chunk-3JKGY2YC.js.map +0 -1
  300. package/dist/chunk-4DWBSTYA.js.map +0 -1
  301. package/dist/chunk-4XWLB7JB.js.map +0 -1
  302. package/dist/chunk-F2GOZMRA.js.map +0 -1
  303. package/dist/chunk-GOCWM7FN.js.map +0 -1
  304. package/dist/chunk-L4YKW5AY.js.map +0 -1
  305. package/dist/chunk-NMI2TWMH.js.map +0 -1
  306. package/dist/chunk-OAHVKSQJ.js.map +0 -1
  307. package/dist/chunk-OHNKYHRR.js.map +0 -1
  308. package/dist/chunk-PUK7TQT4.js +0 -13
  309. package/dist/chunk-PUK7TQT4.js.map +0 -1
  310. package/dist/chunk-QOM5Z62G.js.map +0 -1
  311. package/dist/chunk-TKHJXPSB.js.map +0 -1
  312. package/dist/chunk-UL22SDGR.js.map +0 -1
  313. package/dist/chunk-VJRTDKXS.js.map +0 -1
  314. package/dist/chunk-WEH33JSG.js.map +0 -1
  315. package/dist/chunk-WYHQUCHR.js.map +0 -1
  316. package/dist/chunk-X3RMRJNR.js.map +0 -1
  317. package/dist/library-publish-KKQ2ULLX.js.map +0 -1
  318. package/dist/project-HZW2MZPA.js +0 -84
  319. package/dist/serve-preflight-L4POEHNQ.js.map +0 -1
  320. package/dist/source-GBMYD3RO.js +0 -85
@@ -1,22 +1,23 @@
1
- import { unregisteredLibraryNames, renamedLibraryName, resolveConfig, RemoteCatalogSource, OfflineError, describeCatalogError, CatalogHttpError, scanUnlistedAssets, missingRootAssets, storeManifestFile, missingLibraryDirMessage, filesEntryPaths } from './chunk-UL22SDGR.js';
2
- import { SEMVER_RE, invalidSemverMessage, flowFileNameError, readManifestSourceConfig, discoverAssetsInTree, isManifestMode } from './chunk-QOM5Z62G.js';
3
- import { parseFlowDefinition } from './chunk-X3RMRJNR.js';
4
- import { installTargetOf, readKeptLegacyCopies, writeKeptLegacyCopies, canonicalEntryRef, diffDeployedAgent, deployedAssetDrift, assertNoLibrarySymlinks, deployAll, removeAsset, createScaffold, installAgent } from './chunk-3JKGY2YC.js';
1
+ import { unregisteredLibraryNames, renamedLibraryName, resolveConfig, RemoteCatalogSource, OfflineError, describeCatalogError, CatalogHttpError, scanUnlistedAssets, missingRootAssets, storeManifestFile, missingLibraryDirMessage, filesEntryPaths, lastLibraryName } from './chunk-S2XHGWQC.js';
2
+ import { SEMVER_RE, invalidSemverMessage, flowFileNameError, computeDeterministicHash, readManifestSourceConfig, discoverAssetsInTree, isManifestMode } from './chunk-UI745XC3.js';
3
+ import { parseFlowDefinition } from './chunk-JRN5QF4C.js';
4
+ import { installTargetOf, readKeptLegacyCopies, writeKeptLegacyCopies, canonicalEntryRef, diffDeployedAgent, deployedAssetDrift, assertNoLibrarySymlinks, deployAll, removeAsset, sourceComposedFiles, cachedComposedFiles, createScaffold, installAgent } from './chunk-NUA33BYO.js';
5
5
  import { appendHistory, getRecentHistory, clearHistory } from './chunk-KTBKW2FI.js';
6
6
  import { commitChanges } from './chunk-UQ6LFBPZ.js';
7
- import { harnessTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-DUMTLYSO.js';
7
+ import { harnessTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-ABFPSVIS.js';
8
8
  import { isScaffoldBody, isScaffoldDescription } from './chunk-E572OHGY.js';
9
- import { resolveConfiguredHarnessName, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, writeLock, computeHash, buildLockFile, readLock, assertLockHashes, verifyLock, mergeLockFile, resyncLockSources, extractForPatch, generatePatch, savePatch, applyPatch, LOCK_SCHEMA_VERSION } from './chunk-L4YKW5AY.js';
10
- import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-OTIB4D2K.js';
9
+ import { resolveConfiguredHarnessName, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, writeLock, computeHash, buildLockFile, readLock, assertLockHashes, verifyLock, mergeLockFile, resyncLockSources, extractForPatch, generatePatch, savePatch, applyPatch, LOCK_SCHEMA_VERSION } from './chunk-VMVDAZLA.js';
10
+ import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-ZDLUQLI6.js';
11
11
  import { deployedBase, isDeployed, deployedPaths, deployedDir } from './chunk-5KRMNUL4.js';
12
- import { resolveSkWorkspaceConfig, effectiveAgentInstances, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, buildProvenanceIndex, coerceVersion, sourceShortName, readLinks, isCommitOnRemote, resolveAll, agentSourceCacheDir, ensureRepo, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, resolveAgentDefinitionDir, resolveAsset, MAX_PRESET_DEPTH, PresetDepthError, DEFAULT_AGENT_INSTANCE_ID } from './chunk-OHNKYHRR.js';
12
+ import { resolveSkWorkspaceConfig, effectiveAgentInstances, mcpDeclarationFromMdContent, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, buildProvenanceIndex, coerceVersion, sourceShortName, readLinks, isCommitOnRemote, findWorkspaceRoot, resolveAll, agentSourceCacheDir, ensureRepo, resolveAgentDefinitionDir, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, resolveAsset, MAX_PRESET_DEPTH, PresetDepthError, DEFAULT_AGENT_INSTANCE_ID } from './chunk-ZHGKAZJ7.js';
13
13
  import { globalSkaileDir, globalCacheRoot, migrateLegacyDir } from './chunk-L5VSIPQD.js';
14
14
  import { HARNESS_TARGETS, SUPPORTED_HARNESSES } from './chunk-VTETQCR6.js';
15
- import { loadSkPublishManifest, listAssetFiles, isNonAssetDir, isNonAssetFile } from './chunk-UQ33Y4DM.js';
16
- import { scanDirectory, parseFrontmatter, parseRequires, dedupeRequirements, readAssetBody } from './chunk-GOCWM7FN.js';
17
- import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr, parseDep } from './chunk-WDXZA7QW.js';
15
+ import { listAssetFiles, loadSkPublishManifest, decodeSkaileManifest, isNonAssetDir, isNonAssetFile } from './chunk-ZBHZLWV3.js';
16
+ import { scanDirectory, parseFrontmatter, parseRequires, dedupeRequirements, readAssetBody } from './chunk-RHN5MRQO.js';
17
+ import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr, parseDep } from './chunk-LSSYC5IC.js';
18
18
  import { spawnSync } from 'child_process';
19
- import { existsSync, rmSync, readFileSync, readdirSync, lstatSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
19
+ import { existsSync, rmSync, readFileSync, writeFileSync, readdirSync, lstatSync, statSync, unlinkSync, mkdirSync } from 'fs';
20
+ import { homedir } from 'os';
20
21
  import path, { resolve, dirname, sep, join, relative, posix } from 'path';
21
22
  import { gt } from 'semver';
22
23
  import { parse, stringify } from 'yaml';
@@ -595,6 +596,14 @@ function requiredFieldsAdvice(id, keys) {
595
596
  const setting = keys.map((k) => `${k}: env:${k}`).join(", ");
596
597
  return `needs ${keys.join(", ")} (required): set it with an mcp_servers entry (id: ${id}, env: ${setting}) in skaile.yaml and export the variable, then run skaile install`;
597
598
  }
599
+ function mcpNotRunnableReason(mdContent, name) {
600
+ try {
601
+ const decl = mcpDeclarationFromMdContent(mdContent, name);
602
+ return decl ? skipReason(decl) : void 0;
603
+ } catch {
604
+ return void 0;
605
+ }
606
+ }
598
607
  function mcpConfigFileOf(harnessTarget) {
599
608
  return TARGET_MCP_CONFIG[harnessTarget];
600
609
  }
@@ -616,6 +625,38 @@ async function mcpRenderedFootprint(projectDir, harnessTarget, name) {
616
625
  ...dir && hasPayload ? { payloadDir: `${path.relative(projectDir, dir).split(path.sep).join("/")}/` } : {}
617
626
  };
618
627
  }
628
+ async function mcpCommittedRenderIntact(projectDir, harnessTarget, name) {
629
+ const relConfig = TARGET_MCP_CONFIG[harnessTarget];
630
+ if (!relConfig) return false;
631
+ if ((await readNotedSkips(projectDir, harnessTarget))[name] !== void 0) return true;
632
+ let ledger;
633
+ let servers;
634
+ try {
635
+ ledger = await readManaged(projectDir, harnessTarget);
636
+ const file = await readJsonStrict(path.join(projectDir, relConfig));
637
+ servers = file?.mcpServers ?? {};
638
+ } catch {
639
+ return false;
640
+ }
641
+ if (!Object.hasOwn(servers, name)) return false;
642
+ const entry = servers[name];
643
+ const recorded = ledger.ids.includes(name) ? ledger.rendered[name] : void 0;
644
+ if (recorded !== void 0 && entryHash(entry) !== recorded) return false;
645
+ const e = entry ?? {};
646
+ const strings = [e.command, ...Array.isArray(e.args) ? e.args : []].filter(
647
+ (v) => typeof v === "string"
648
+ );
649
+ for (const v of strings) {
650
+ const at = v.indexOf(".skaile/assets/");
651
+ if (at < 0) continue;
652
+ try {
653
+ await fs.stat(path.join(projectDir, v.slice(at)));
654
+ } catch {
655
+ return false;
656
+ }
657
+ }
658
+ return true;
659
+ }
619
660
  async function renderMcpServerConfig(projectDir, harnessTarget, opts) {
620
661
  const warn = opts?.warn ?? ((m) => console.warn(`skaile: ${m}`));
621
662
  const relConfig = TARGET_MCP_CONFIG[harnessTarget];
@@ -839,6 +880,16 @@ function libraryDiskHashes(libPath, cache, registry) {
839
880
  }
840
881
  return hashes;
841
882
  }
883
+ function repoManifestVersion(repoDir) {
884
+ try {
885
+ const { manifest } = decodeSkaileManifest(
886
+ readFileSync(join(repoDir, "skaile.manifest.yaml"), "utf8")
887
+ );
888
+ return manifest.version || void 0;
889
+ } catch {
890
+ return void 0;
891
+ }
892
+ }
842
893
  function manifestFileHasAssetsArray(libPath) {
843
894
  try {
844
895
  const doc = parse(readFileSync(join(libPath, "skaile.manifest.yaml"), "utf8"));
@@ -847,6 +898,10 @@ function manifestFileHasAssetsArray(libPath) {
847
898
  return false;
848
899
  }
849
900
  }
901
+ var libraryAssetCheck;
902
+ function setLibraryAssetCheck(check) {
903
+ libraryAssetCheck = check;
904
+ }
850
905
  function isUnlistedLibraryAsset(entry, libPath) {
851
906
  const manifest = loadSkPublishManifest(libPath);
852
907
  const listed = manifest?.assets ?? [];
@@ -1117,7 +1172,9 @@ function relockedRefs(prev, next) {
1117
1172
  e.library?.version ?? "",
1118
1173
  e.library && !e.library.installed ? e.library.name : "",
1119
1174
  e.library?.sha256 ?? e.sha256,
1120
- key.includes("#0.0.0-sha.") ? "" : e.source?.commit ?? ""
1175
+ key.includes("#0.0.0-sha.") ? "" : e.source?.commit ?? "",
1176
+ // The `../` compose folders are part of the lock: `update` moving them is a re-lock.
1177
+ (e.composed ?? []).map((f) => `${f.path}:${f.sha256}`).join("\n")
1121
1178
  ].join("\0");
1122
1179
  const out = [];
1123
1180
  for (const [key, entry] of Object.entries(next.assets)) {
@@ -1152,9 +1209,13 @@ function libraryMoves(prev, next) {
1152
1209
  }
1153
1210
  return out;
1154
1211
  }
1212
+ function unregisteredLibraryName(origin) {
1213
+ return lastLibraryName(origin.name) ?? origin.name;
1214
+ }
1155
1215
  function unregisteredLibraryFix(origin, ref) {
1156
- const register = origin.url ? `\`skaile library init ${origin.name} --git ${origin.url}\` (no registered library has the remote ${origin.url} either)` : `\`skaile library init ${origin.name} --path <dir>\``;
1157
- return `register library "${origin.name}" again with ${register}, or drop the dependency with \`skaile remove ${ref}\``;
1216
+ const name = unregisteredLibraryName(origin);
1217
+ const register = origin.url ? `\`skaile library init ${name} --git ${origin.url}\` (no registered library has the remote ${origin.url} either)` : `\`skaile library init ${name} --path <dir>\``;
1218
+ return `register library "${name}" again with ${register}, or drop the dependency with \`skaile remove ${ref}\``;
1158
1219
  }
1159
1220
  function declaredNoHolderReason(ref) {
1160
1221
  return `is declared but no library or source holds it \u2014 register the library that holds it with \`skaile library init <name> --path <dir>\`, or drop it with \`skaile remove ${ref.split("#")[0]}\``;
@@ -1180,7 +1241,7 @@ function libraryCollisionSummary(all) {
1180
1241
  return `${all.length} assets exist in several libraries (${libraries.join(", ")}); using ${usedText.join(", ")}. Highest version wins; an equal version goes to the default library, then by name. ` + (differing.length > 0 ? `Same version, different bytes for: ${differing.join(", ")}. ` : "") + "Pin a version (<ref>#<version>) to choose another; `--verbose` lists each asset.";
1181
1242
  }
1182
1243
  function lockedLibraryProblem(lookup, origin, ref) {
1183
- return lookup.state === "dir-missing" ? missingLibraryDirMessage({ name: origin.name, path: lookup.path }) : `library "${origin.name}" is not registered \u2014 renamed or removed? ${unregisteredLibraryFix(origin, ref)}`;
1244
+ return lookup.state === "dir-missing" ? missingLibraryDirMessage({ name: origin.name, path: lookup.path }) : `library "${unregisteredLibraryName(origin)}" is not registered \u2014 renamed or removed? ${unregisteredLibraryFix(origin, ref)}`;
1184
1245
  }
1185
1246
  function gapIdentity(ref) {
1186
1247
  const p = parseRefTolerant(ref);
@@ -1429,6 +1490,43 @@ var AssetManager = class {
1429
1490
  return !recorded || recorded.includes(target);
1430
1491
  });
1431
1492
  }
1493
+ /**
1494
+ * A 5.12.0 global record has no `global_asset_harnesses:` key, yet `add --global --harness`
1495
+ * wrote one copy only. On the first global `install`, an entry with exactly one existing copy
1496
+ * among several default harnesses is that harness's subset (OQ6): record it, so the install
1497
+ * does not fan the asset out to harnesses the user never asked for (p1442). A copy under the
1498
+ * 5.12.0 omp folder (`~/.omp/skills`) counts as an omp copy. Nothing is inferred once the key
1499
+ * exists (an entry without a row then means "every harness"), with one default harness, or
1500
+ * with no copy yet. Global mode only; returns the rows written.
1501
+ */
1502
+ inferLegacyHarnessSubsets(entries) {
1503
+ if (!this.global) return [];
1504
+ const configPath = join(this.stateRoot, "skaile.yaml");
1505
+ if (!existsSync(configPath)) return [];
1506
+ let raw;
1507
+ try {
1508
+ raw = parse(readFileSync(configPath, "utf8"));
1509
+ } catch {
1510
+ return [];
1511
+ }
1512
+ if (!raw || typeof raw !== "object" || ENTRY_HARNESSES_KEY in raw) return [];
1513
+ const all = this.defaultGlobalHarnesses();
1514
+ if (all.length < 2) return [];
1515
+ const rows = [];
1516
+ for (const e of entries) {
1517
+ const component = this.installTarget(e.kind);
1518
+ if (!component || !e.name) continue;
1519
+ const holding = all.filter(
1520
+ (h) => isDeployed(component, e.name, h, true) || h === "omp" && legacyOmpPaths(component, e.name).length > 0
1521
+ );
1522
+ if (holding.length !== 1) continue;
1523
+ const publisher = e.publisher?.replace(/^@/, "");
1524
+ const ref = `${e.kind}:${publisher ? `@${publisher}/` : ""}${e.name}`;
1525
+ if (this.setEntryHarnessRow(raw, ref, holding)) rows.push({ ref, harnesses: holding });
1526
+ }
1527
+ if (rows.length > 0) writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
1528
+ return rows;
1529
+ }
1432
1530
  /**
1433
1531
  * Set (or, with `harnesses` undefined, clear) the harness row of `ref` in the parsed
1434
1532
  * `~/.skaile/skaile.yaml` `raw`. Returns whether `raw` changed.
@@ -1556,16 +1654,36 @@ var AssetManager = class {
1556
1654
  }
1557
1655
  /** Deploy options for one harness. In global mode, `harness` is the
1558
1656
  * fan-out target (not the constructor's `harness`, which is project-only). */
1559
- deployOptsFor(target, link, onSecretFile) {
1657
+ deployOptsFor(target, link, onSecretFile, composed) {
1658
+ const records = this.global ? void 0 : this.composedRecords();
1560
1659
  return {
1561
1660
  registry: this.registry,
1562
1661
  harness: target,
1563
1662
  global: this.global,
1564
1663
  cwd: this.projectDir,
1565
1664
  link: link === true,
1566
- ...onSecretFile ? { onSecretFile: (entry, p) => onSecretFile(canonicalEntryRef(entry), p) } : {}
1665
+ ...onSecretFile ? { onSecretFile: (entry, p) => onSecretFile(canonicalEntryRef(entry), p) } : {},
1666
+ ...records ? { lockedComposed: (agent) => records.get(agent) } : {},
1667
+ ...composed ? {
1668
+ refreshComposed: composed.refresh,
1669
+ onComposedDrift: (entry, ref) => composed.onDrift(canonicalEntryRef(entry), ref)
1670
+ } : {}
1567
1671
  };
1568
1672
  }
1673
+ /** Agent name → the `../` compose folder files its lock entry records (`LockEntry.composed`). */
1674
+ composedRecords() {
1675
+ const out = /* @__PURE__ */ new Map();
1676
+ for (const [key, entry] of Object.entries(this.tryReadLock()?.assets ?? {})) {
1677
+ if (!entry.composed?.length) continue;
1678
+ const { name } = parseCanonicalLockKey(key);
1679
+ if (name) out.set(name, entry.composed);
1680
+ }
1681
+ return out;
1682
+ }
1683
+ /** `install`'s `Missing:` line for a `../` compose folder the library moved past the lock. */
1684
+ composedDriftLine(agentRef, composeRef) {
1685
+ return `${agentRef} (composes ${composeRef}: the library's copy changed since the lock; \`skaile update\` moves the lock to it)`;
1686
+ }
1569
1687
  /**
1570
1688
  * Names of the `mcp-server` assets the lock holds (the declaration ids, unscoped); a missing
1571
1689
  * `.mcp.json` entry of one counts as drift even before skaile owned it.
@@ -1766,6 +1884,15 @@ var AssetManager = class {
1766
1884
  for (const [slug, decl] of Object.entries(repositories)) {
1767
1885
  const localPath = this.resolveRepoDir(decl, slug);
1768
1886
  if (!localPath || !existsSync(localPath)) continue;
1887
+ if (decl.url && normalizeRepoUrl(decl.url) !== normalizeRepoUrl(FACTORY_SOURCE_URL) && !existsSync(join(localPath, ".git"))) {
1888
+ this.reportSourceError(
1889
+ { localPath, sourceUrl: decl.url, commit: "" },
1890
+ new Error(
1891
+ `${decl.url}: its clone ${localPath} is not a git repository, so git pull fails - run \`skaile source sync\` to clone it again`
1892
+ )
1893
+ );
1894
+ continue;
1895
+ }
1769
1896
  const commit = getRepoCommit(localPath) ?? "0".repeat(40);
1770
1897
  clones.push({
1771
1898
  localPath,
@@ -2089,6 +2216,33 @@ var AssetManager = class {
2089
2216
  if (!scan) return [];
2090
2217
  return [...scan.byKey.values()].flat();
2091
2218
  }
2219
+ /**
2220
+ * The version `add` deploys for each asset of a source clone registered as a `reader`
2221
+ * library, when its file declares none: the repo commit pseudo-version (p855). `search`
2222
+ * reads this where no project is in scope, so it lists what `add` locks, not the index
2223
+ * default.
2224
+ *
2225
+ * @returns `<library>\0<kind>:@<publisher>/<name>` -> version; only assets that declare none.
2226
+ */
2227
+ async sourceAssetVersions() {
2228
+ const out = /* @__PURE__ */ new Map();
2229
+ const scan = await this.scanLibraries();
2230
+ if (!scan) return out;
2231
+ const snap = await this.loadLibraryIndex();
2232
+ for (const [key, candidates] of scan.byKey) {
2233
+ for (const c of candidates) {
2234
+ if (c.entry.version) continue;
2235
+ const lib = snap?.libraries.find((l) => l.name === c.library);
2236
+ if (lib?.ownership !== "reader" || !("url" in lib.backendConfig)) continue;
2237
+ const manifestVersion = repoManifestVersion(lib.path);
2238
+ const commit = getRepoCommit(lib.path);
2239
+ if (manifestVersion) out.set(`${c.library}\0${key}`, manifestVersion);
2240
+ else if (commit)
2241
+ out.set(`${c.library}\0${key}`, `${PSEUDO_VERSION_PREFIX}${commit.slice(0, 7)}`);
2242
+ }
2243
+ }
2244
+ return out;
2245
+ }
2092
2246
  /**
2093
2247
  * Every origin that holds `ref` (`kind:@publisher/name`) with the version it holds, in
2094
2248
  * the order `add <ref>` prefers them, so the first row is the one `add` deploys: the
@@ -2598,7 +2752,14 @@ var AssetManager = class {
2598
2752
  if (!existsSync(indexPath)) return null;
2599
2753
  const index = new LocalIndex({ indexPath });
2600
2754
  try {
2601
- return { libraries: await index.listSources(), defs: await index.listAssetDefs() };
2755
+ const libraries = await index.listSources();
2756
+ const defs = await index.listAssetDefs();
2757
+ const hidden = this.undeclaredSourceClones(libraries);
2758
+ if (hidden.size === 0) return { libraries, defs };
2759
+ return {
2760
+ libraries: libraries.filter((l) => !hidden.has(l.id)),
2761
+ defs: defs.filter((d) => !d.libraryId || !hidden.has(d.libraryId))
2762
+ };
2602
2763
  } finally {
2603
2764
  index.close();
2604
2765
  }
@@ -2606,6 +2767,32 @@ var AssetManager = class {
2606
2767
  return null;
2607
2768
  }
2608
2769
  }
2770
+ /**
2771
+ * Ids of the registered libraries that are the clone of a source this project does not
2772
+ * declare. `source add` registers the clone as a machine-wide reader library so `search`
2773
+ * and the indexer can read it, but a `sources:` clone belongs to the projects that declare
2774
+ * it (OQ12, p321): another project neither resolves it, nor ties with it by name, nor has
2775
+ * `update` re-pin its locked assets to it. A reader library the user registered by hand
2776
+ * (`library init --reader --git`) lives outside the sources cache and is never hidden.
2777
+ */
2778
+ undeclaredSourceClones(libraries) {
2779
+ const hidden = /* @__PURE__ */ new Set();
2780
+ if (!this.global && !findWorkspaceRoot(this.projectDir)) return hidden;
2781
+ let declared;
2782
+ try {
2783
+ declared = new Set(this.loadConfig().sources.map((s) => sourceCacheKey(s.url)));
2784
+ } catch {
2785
+ return hidden;
2786
+ }
2787
+ const cloneRoots = [join(globalCacheRoot(), "sources"), this.reposDir].map((d) => resolve(d));
2788
+ for (const l of libraries) {
2789
+ if (l.ownership !== "reader" || !("url" in l.backendConfig)) continue;
2790
+ const at = resolve(l.path);
2791
+ if (!cloneRoots.some((root) => at.startsWith(root + sep))) continue;
2792
+ if (!declared.has(sourceCacheKey(l.backendConfig.url))) hidden.add(l.id);
2793
+ }
2794
+ return hidden;
2795
+ }
2609
2796
  /**
2610
2797
  * Library-origin lock entries for the assets {@link resolveLibraryEntries}
2611
2798
  * matched. The locked version is the one actually deployed: the on-disk
@@ -2749,14 +2936,19 @@ var AssetManager = class {
2749
2936
  * `install --locked`. An entry is restored only when its library is still
2750
2937
  * registered/on disk, still holds the asset, and still offers the locked
2751
2938
  * version; anything else is reported in `missing` with the reason.
2939
+ *
2940
+ * @param opts.storeFallback - `install --locked` only: an entry whose library is not registered
2941
+ * here is restored from a store that serves the locked sha256 (OQ15, p1490). Reads such as
2942
+ * the drift check never ask a store.
2752
2943
  */
2753
- async restoreLibraryLockEntries(lock) {
2944
+ async restoreLibraryLockEntries(lock, opts) {
2754
2945
  const entries = [];
2755
2946
  const missing = [];
2947
+ const viaStore = [];
2756
2948
  const locked = Object.entries(lock.assets).filter(
2757
2949
  ([, e]) => e.library !== void 0 && !e.library.installed
2758
2950
  );
2759
- if (locked.length === 0) return { entries, missing };
2951
+ if (locked.length === 0) return { entries, missing, viaStore };
2760
2952
  const snap = await this.loadLibraryIndex();
2761
2953
  const diskHashes = /* @__PURE__ */ new Map();
2762
2954
  const fileSets = /* @__PURE__ */ new Map();
@@ -2769,6 +2961,12 @@ var AssetManager = class {
2769
2961
  }
2770
2962
  const lookup = this.lockedLibrary(origin, snap, { kind, publisher, name });
2771
2963
  if (lookup.state !== "ok") {
2964
+ const fallback = lookup.state === "unregistered" && opts?.storeFallback ? await this.storeEntryForLockedLibraryAsset(publisher, kind, name, origin) : null;
2965
+ if (fallback) {
2966
+ entries.push(fallback);
2967
+ viaStore.push(ref);
2968
+ continue;
2969
+ }
2772
2970
  missing.push(
2773
2971
  `${ref} (${lockedLibraryProblem(lookup, origin, `${kind}:@${publisher}/${name}`)})`
2774
2972
  );
@@ -2811,22 +3009,71 @@ var AssetManager = class {
2811
3009
  found.version = sameVersion ? origin.version : current;
2812
3010
  entries.push(withLibraryFiles(found, libPath, fileSets, this.registry));
2813
3011
  }
2814
- return { entries, missing };
3012
+ return { entries, missing, viaStore };
3013
+ }
3014
+ /**
3015
+ * The store's copy of a library-origin lock entry whose library is not registered here: a
3016
+ * store (project `stores:` or the global catalog) that serves `kind:@publisher/name#version`
3017
+ * under the very sha256 the lock recorded holds the same bytes. The entry is fetched and
3018
+ * verified like any store asset; the lock stays as it is. `null` when the lock has no hash,
3019
+ * no store answers, or the hash differs (OQ15, p1490).
3020
+ */
3021
+ async storeEntryForLockedLibraryAsset(publisher, kind, name, origin) {
3022
+ if (!origin.sha256 || !origin.version || isPseudoVersion(origin.version)) return null;
3023
+ const { repositories, stores: projectStores } = this.loadConfig();
3024
+ const stores = [...projectStores, ...await this.globalStoreFallback(projectStores)];
3025
+ const fetcher = this.storeFetcherFor(stores);
3026
+ if (!fetcher) return null;
3027
+ for (const store of stores) {
3028
+ try {
3029
+ const m = await fetcher.getInstallManifest(
3030
+ store.url,
3031
+ `${kind}:@${publisher}/${name}#${origin.version}`
3032
+ );
3033
+ if (!m || m.sha256 !== origin.sha256) continue;
3034
+ const [entry] = await this.buildCatalogEntries(
3035
+ [
3036
+ {
3037
+ publisher,
3038
+ kind,
3039
+ name,
3040
+ version: origin.version,
3041
+ sourceUrl: m.sourceUrl,
3042
+ commit: m.commit,
3043
+ files: m.files,
3044
+ sha256: m.sha256,
3045
+ ...m.deps?.length ? { deps: m.deps, storeRequires: m.deps } : {}
3046
+ }
3047
+ ],
3048
+ repositories
3049
+ );
3050
+ if (entry) return entry;
3051
+ } catch (err) {
3052
+ this.noteStoreFailure(store.url, err);
3053
+ }
3054
+ }
3055
+ return null;
2815
3056
  }
2816
3057
  /**
2817
3058
  * Whether a deployed copy exists for the lock entries `install` kept for the unresolved
2818
- * `ref`. A kind with no install target has no deployed copy.
3059
+ * `ref`. A kind with no install target has none, except an `mcp-server`: its copy is the
3060
+ * `.mcp.json` entry skaile wrote and the payload that entry points into (p792).
2819
3061
  */
2820
- keptCopyDeployed(kept, ref) {
3062
+ async keptCopyDeployed(kept, ref) {
2821
3063
  const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
2822
3064
  if (!want) return false;
2823
- return Object.keys(kept.assets).some((key) => {
3065
+ for (const key of Object.keys(kept.assets)) {
2824
3066
  const id = parseCanonicalLockKey(key);
2825
- if (id.kind !== want.kind || id.name !== want.name) return false;
2826
- if (want.publisher && id.publisher !== want.publisher) return false;
3067
+ if (id.kind !== want.kind || id.name !== want.name) continue;
3068
+ if (want.publisher && id.publisher !== want.publisher) continue;
2827
3069
  const component = this.installTarget(id.kind);
2828
- return component !== void 0 && isDeployed(component, id.name, this.harness, this.global, this.projectDir);
2829
- });
3070
+ if (component !== void 0) {
3071
+ if (isDeployed(component, id.name, this.harness, this.global, this.projectDir)) return true;
3072
+ } else if (id.kind === "mcp-server" && !this.global && await mcpCommittedRenderIntact(this.projectDir, this.harness, id.name)) {
3073
+ return true;
3074
+ }
3075
+ }
3076
+ return false;
2830
3077
  }
2831
3078
  /** Whether the deployed copy of a library asset is on disk; a kind with no install target has none to check. */
2832
3079
  lockedCopyDeployed(kind, name) {
@@ -2949,6 +3196,24 @@ var AssetManager = class {
2949
3196
  lockedRefsNamed(kind, name) {
2950
3197
  return Object.keys(this.tryReadLock()?.assets ?? {}).map(parseCanonicalLockKey).filter((id) => id.kind === kind && id.name === name).map((id) => id.publisher ? `${kind}:@${id.publisher}/${name}` : `${kind}:${name}`);
2951
3198
  }
3199
+ /**
3200
+ * The library a library-origin lock entry of `kind` and `name` (and `publisher`, when given)
3201
+ * resolves to on this machine, or `undefined` when the lock holds no such entry, it is not
3202
+ * from a library, or its library is not here. `info` prints it next to `source:`.
3203
+ */
3204
+ async lockedLibraryOf(kind, name, publisher) {
3205
+ const bare = publisher?.replace(/^@/, "");
3206
+ const names = await this.lockedLibraryNames();
3207
+ for (const [key, entry] of Object.entries(this.tryReadLock()?.assets ?? {})) {
3208
+ if (!entry.library || entry.library.installed) continue;
3209
+ const id = parseCanonicalLockKey(key);
3210
+ if (id.kind !== kind || id.name !== name || !id.publisher) continue;
3211
+ if (bare && id.publisher !== bare) continue;
3212
+ const lib = names.get(`${kind}:${id.publisher}/${name}`);
3213
+ if (lib) return lib;
3214
+ }
3215
+ return void 0;
3216
+ }
2952
3217
  /**
2953
3218
  * Where each library-origin lock entry of this project resolves on this machine: identity
2954
3219
  * `kind:publisher/name` (no scope sigil, no version) → the name the locked library is
@@ -2991,6 +3256,88 @@ var AssetManager = class {
2991
3256
  }
2992
3257
  return { libraries: await this.lockedLibraryNames(), shas };
2993
3258
  }
3259
+ /**
3260
+ * Canonical refs of the locked source assets that share a source URL with `resolved` but
3261
+ * are locked at another commit than the one `resolved` carries for it. Assets in `resolved`
3262
+ * and library-origin entries are skipped.
3263
+ */
3264
+ staleSourceSiblingRefs(resolved, declared = []) {
3265
+ const pinOf = /* @__PURE__ */ new Map();
3266
+ for (const d of declared) {
3267
+ const id = refIdentity(d);
3268
+ const pin = parseAssetRefLoose(d)?.pin;
3269
+ if (id !== null && pin) pinOf.set(id.replace(/^@/, ""), pin);
3270
+ }
3271
+ const commitOf = /* @__PURE__ */ new Map();
3272
+ const held = /* @__PURE__ */ new Set();
3273
+ for (const c of resolved) {
3274
+ if (!c.sourceUrl || !c.commit) continue;
3275
+ commitOf.set(normalizeRepoUrl(c.sourceUrl), c.commit);
3276
+ held.add(`${c.publisher.replace(/^@/, "")}/${c.kind}:${c.name}`);
3277
+ }
3278
+ const out = [];
3279
+ for (const [key, entry] of Object.entries(this.tryReadLock()?.assets ?? {})) {
3280
+ const src = entry.source;
3281
+ if (!src?.url || entry.library) continue;
3282
+ const commit = commitOf.get(normalizeRepoUrl(src.url));
3283
+ if (!commit || src.commit === commit) continue;
3284
+ const { publisher, kind, name } = parseCanonicalLockKey(key);
3285
+ if (!publisher || !kind || !name || held.has(`${publisher}/${kind}:${name}`)) continue;
3286
+ const pin = pinOf.get(`${publisher}/${kind}:${name}`);
3287
+ out.push(`${kind}:@${publisher}/${name}${pin ? `#${pin}` : ""}`);
3288
+ }
3289
+ return out;
3290
+ }
3291
+ /**
3292
+ * The lock entries that came from a source or the store (a `source:`, no `library:`), by
3293
+ * identity `<publisher>/<kind>:<name>` -> locked version. The lock's origin
3294
+ * wins: a library registered later that holds the same ref does not take such an asset
3295
+ * (`update`, `install`, `add`; OQ15, p1490).
3296
+ */
3297
+ lockedOriginVersions() {
3298
+ const out = /* @__PURE__ */ new Map();
3299
+ for (const [key, e] of Object.entries(this.tryReadLock()?.assets ?? {})) {
3300
+ if (!e.source || e.library) continue;
3301
+ const { publisher, kind, name, version } = parseCanonicalLockKey(key);
3302
+ if (publisher && kind && name) out.set(`${publisher}/${kind}:${name}`, version ?? "");
3303
+ }
3304
+ return out;
3305
+ }
3306
+ /**
3307
+ * Say which registered library holds a ref the lock keeps on its source or the store: the
3308
+ * candidate that lost, with the way to switch (p1490). Printed once per `add`.
3309
+ */
3310
+ async noteLosingLibraries(refs) {
3311
+ const lock = this.tryReadLock();
3312
+ for (const ref of refs) {
3313
+ const plain = ref.split("#")[0] ?? ref;
3314
+ let hit;
3315
+ try {
3316
+ hit = await this.resolveLibraryEntries([plain], { quiet: true });
3317
+ } catch {
3318
+ continue;
3319
+ }
3320
+ const entry = hit.entries[0];
3321
+ const library = entry ? hit.libraryOf.get(entry) : void 0;
3322
+ if (!entry || !library) continue;
3323
+ const canonical = canonicalEntryRef(entry);
3324
+ const plainCanonical = canonical.split("#")[0] ?? canonical;
3325
+ const lockedUrl = Object.entries(lock?.assets ?? {}).find(([k, e]) => {
3326
+ const id = parseCanonicalLockKey(k);
3327
+ return e.source && !e.library && `${id.kind}:@${id.publisher}/${id.name}` === plainCanonical;
3328
+ })?.[1].source?.url;
3329
+ if (lockedUrl) {
3330
+ const ownClone = (await this.loadLibraryIndex())?.libraries.some(
3331
+ (l) => l.name === library && l.ownership === "reader" && "url" in l.backendConfig && sourceCacheKey(l.backendConfig.url) === sourceCacheKey(lockedUrl)
3332
+ );
3333
+ if (ownClone) continue;
3334
+ }
3335
+ this.emitLibraryCollision(
3336
+ `${plainCanonical} is locked from ${lockedUrl ?? "its source"}, which wins; library "${library}" also holds it${entry.version ? ` (${entry.version})` : ""} and is not used. To switch, run \`skaile remove ${plainCanonical}\`, then \`skaile add ${plainCanonical}\`.`,
3337
+ [plainCanonical]
3338
+ );
3339
+ }
3340
+ }
2994
3341
  // ── Store fetch-then-deploy ────────────────────────────────────────────
2995
3342
  /** A candidate's stable identity key (`<publisher>/<kind>:<name>@<version>`). */
2996
3343
  candidateKey(c) {
@@ -3150,10 +3497,19 @@ var AssetManager = class {
3150
3497
  fresh.push(ref);
3151
3498
  }
3152
3499
  if (fresh.length === 0) break;
3500
+ const keepsOrigin = (ref) => {
3501
+ const id = refIdentity(ref);
3502
+ const locked = id !== null ? opts.lockedOrigin?.get(id) : void 0;
3503
+ if (locked === void 0) return false;
3504
+ const pin = ref.includes("#") ? ref.slice(ref.indexOf("#") + 1).trim() : void 0;
3505
+ return !pin || locked === "" || matchPin(pin, locked);
3506
+ };
3507
+ const originKept = fresh.filter(keepsOrigin);
3508
+ if (opts.warnCollisions && originKept.length > 0) await this.noteLosingLibraries(originKept);
3153
3509
  const libraryTried = new Set(
3154
3510
  fresh.filter((ref) => {
3155
3511
  const id = refIdentity(ref);
3156
- return id !== null && (opts.provenanceIndex.get(id)?.length ?? 0) === 0;
3512
+ return id !== null && (opts.provenanceIndex.get(id)?.length ?? 0) === 0 && !keepsOrigin(ref);
3157
3513
  })
3158
3514
  );
3159
3515
  const hit = await this.resolveLibraryEntries([...libraryTried], {
@@ -3181,7 +3537,7 @@ var AssetManager = class {
3181
3537
  }
3182
3538
  presets.push(...r.presets);
3183
3539
  const hit2 = await this.resolveLibraryEntries(
3184
- r.missing.filter((m) => !libraryTried.has(m)),
3540
+ r.missing.filter((m) => !libraryTried.has(m) && !keepsOrigin(m)),
3185
3541
  {
3186
3542
  quiet: !opts.warnCollisions,
3187
3543
  lockedLibraries: opts.lockedLibraries,
@@ -3484,7 +3840,8 @@ var AssetManager = class {
3484
3840
  };
3485
3841
  }
3486
3842
  const orphans = await this.orphanedSourceGaps(
3487
- await this.declaredLockKeys(lockForLibraries, dependencies)
3843
+ await this.declaredLockKeys(lockForLibraries, dependencies),
3844
+ true
3488
3845
  );
3489
3846
  if (orphans.size > 0) {
3490
3847
  return {
@@ -3527,6 +3884,7 @@ var AssetManager = class {
3527
3884
  // library holding the same ref does not move a locked asset. `update` re-resolves.
3528
3885
  ...opts?.moveLibraries ? { preferLocked: await this.lockedPreference() } : { lockedLibraries: await this.lockedLibraryNames() },
3529
3886
  ...keepLocked ? { keepLocked } : {},
3887
+ lockedOrigin: this.lockedOriginVersions(),
3530
3888
  readStoreRequires: true
3531
3889
  });
3532
3890
  this.assertNoFolderCollision([...resolved, ...lib.entries.map((e) => this.entryIdentity(e))]);
@@ -3570,7 +3928,7 @@ var AssetManager = class {
3570
3928
  const missingHints = await this.publisherChangeHints(stillMissing);
3571
3929
  const prevLock = this.tryReadLock();
3572
3930
  const kept = keptLockEntries(prevLock, stillMissing);
3573
- const storeNotChecked = this.storeNotCheckedRefs(
3931
+ const storeNotChecked = await this.storeNotCheckedRefs(
3574
3932
  kept,
3575
3933
  sources.map((s) => s.url)
3576
3934
  );
@@ -3578,7 +3936,7 @@ var AssetManager = class {
3578
3936
  for (const ref of /* @__PURE__ */ new Set([...kept.refs, ...fixes.keys()])) {
3579
3937
  const fix = fixes.get(ref);
3580
3938
  const notes = [
3581
- kept.refs.has(ref) ? this.keptCopyDeployed(kept, ref) ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists" : void 0,
3939
+ kept.refs.has(ref) ? await this.keptCopyDeployed(kept, ref) ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists" : void 0,
3582
3940
  fix
3583
3941
  ].filter((n) => n !== void 0);
3584
3942
  missingHints[ref] = `${fix ? "" : missingHints[ref] ?? ""} (${notes.join("; ")})`;
@@ -3597,12 +3955,24 @@ var AssetManager = class {
3597
3955
  const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
3598
3956
  assertFlowsLoad(entries, "installed");
3599
3957
  assertNoLibrarySymlinks(entries, this.registry, repositories, opts?.link === true);
3958
+ const forceCommand = opts?.moveLibraries === true ? "skaile update --force" : "skaile install --force";
3600
3959
  if (!opts?.force) {
3601
3960
  await this.assertNoHandEditsPruned(
3602
3961
  resolved,
3603
- /* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)])
3962
+ /* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)]),
3963
+ forceCommand
3604
3964
  );
3605
3965
  }
3966
+ const relockRemoved = await this.relockDeletions(
3967
+ entries,
3968
+ prevLock,
3969
+ withKeptLockEntries(freshLock, kept),
3970
+ opts?.force === true,
3971
+ forceCommand,
3972
+ lib.libraryOf
3973
+ );
3974
+ if (!opts?.force) this.assertNoUnrecordedAgentOverwrite(entries, prevLock, forceCommand);
3975
+ const inferredHarnesses = this.inferLegacyHarnessSubsets(entries);
3606
3976
  const { removed, replaced } = this.reconcileRemovedAssets(
3607
3977
  resolved,
3608
3978
  /* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)])
@@ -3610,6 +3980,7 @@ var AssetManager = class {
3610
3980
  const copyDrift = await this.lockedCopyDrift();
3611
3981
  const missingCopies = await this.incompleteDeploys();
3612
3982
  const unrecordedAgents = this.unrecordedAgentFiles(prevLock);
3983
+ const composedDrift = /* @__PURE__ */ new Set();
3613
3984
  const deployed = [
3614
3985
  ...new Set(
3615
3986
  this.targetHarnesses().flatMap(
@@ -3617,7 +3988,10 @@ var AssetManager = class {
3617
3988
  this.entriesForHarness(entries, t),
3618
3989
  repositories,
3619
3990
  patches,
3620
- this.deployOptsFor(t, opts?.link)
3991
+ this.deployOptsFor(t, opts?.link, void 0, {
3992
+ refresh: opts?.moveLibraries === true,
3993
+ onDrift: (ref, composeRef) => composedDrift.add(this.composedDriftLine(ref, composeRef))
3994
+ })
3621
3995
  )
3622
3996
  )
3623
3997
  )
@@ -3625,14 +3999,21 @@ var AssetManager = class {
3625
3999
  this.sweepLegacyOmpCopies(entries);
3626
4000
  const mcpRestored = await this.renderHarnessMcpConfig();
3627
4001
  const agentRender = await this.renderAgentInstances();
3628
- const restored = [...this.restoredCopies(copyDrift, deployed, missingCopies), ...mcpRestored];
4002
+ const restored = [
4003
+ ...this.restoredCopies(copyDrift, deployed, missingCopies),
4004
+ ...relockRemoved,
4005
+ ...mcpRestored
4006
+ ];
3629
4007
  const rewritten = this.rewrittenAgentFiles(unrecordedAgents);
3630
4008
  const lockWanted = resolved.length > 0 || lib.entries.length > 0 || existsSync(this.lockPath);
3631
4009
  let lockWritten = false;
3632
4010
  let relocked = [];
3633
4011
  let moved = [];
3634
4012
  if (lockWanted) {
3635
- const lockData = this.withRenderedAgents(withKeptLockEntries(freshLock, kept));
4013
+ const lockData = this.withRenderedAgents(
4014
+ withKeptLockEntries(freshLock, kept),
4015
+ opts?.moveLibraries ? void 0 : prevLock
4016
+ );
3636
4017
  moved = libraryMoves(prevLock, libraryLock);
3637
4018
  const movedRefs = new Set(moved.map((m) => m.ref));
3638
4019
  relocked = relockedRefs(prevLock, lockData).filter((k) => !movedRefs.has(k.split("#")[0]));
@@ -3648,12 +4029,17 @@ var AssetManager = class {
3648
4029
  return {
3649
4030
  deployed,
3650
4031
  removed,
3651
- missing: [...reportedMissing, ...versionBlocked.filter((b) => !reportedMissing.includes(b))],
4032
+ missing: [
4033
+ ...reportedMissing,
4034
+ ...versionBlocked.filter((b) => !reportedMissing.includes(b)),
4035
+ ...composedDrift
4036
+ ],
3652
4037
  ...placeholderBodies.length > 0 ? { placeholderBodies } : {},
3653
4038
  ...placeholderDescriptions.length > 0 ? { placeholderDescriptions } : {},
3654
4039
  ...storeNotChecked.length > 0 ? { storeNotChecked } : {},
3655
4040
  ...Object.keys(missingHints).length > 0 ? { missingHints } : {},
3656
4041
  ...relocked.length > 0 ? { relocked } : {},
4042
+ ...inferredHarnesses.length > 0 ? { inferredHarnesses } : {},
3657
4043
  ...moved.length > 0 ? { moved } : {},
3658
4044
  ...replaced.length > 0 ? { replaced } : {},
3659
4045
  ...restored.length > 0 ? { restored } : {},
@@ -3674,7 +4060,7 @@ var AssetManager = class {
3674
4060
  * @param declaredSourceUrls - The `sources:` URLs of `skaile.yaml`; a lock entry from one of
3675
4061
  * them is that source's, not the store's.
3676
4062
  */
3677
- storeNotCheckedRefs(kept, declaredSourceUrls) {
4063
+ async storeNotCheckedRefs(kept, declaredSourceUrls) {
3678
4064
  const skipped = this.catalogUrlOverride === "";
3679
4065
  if (!skipped && this.storeFailures.size === 0) return [];
3680
4066
  const declared = new Set(declaredSourceUrls.map(normalizeRepoUrl));
@@ -3685,7 +4071,7 @@ var AssetManager = class {
3685
4071
  const entry = kept.assets[key];
3686
4072
  return entry !== void 0 && !entry.library && entry.source !== void 0 && !declared.has(normalizeRepoUrl(entry.source.url));
3687
4073
  });
3688
- if (!fromStore || !this.keptCopyDeployed(kept, ref)) continue;
4074
+ if (!fromStore || !await this.keptCopyDeployed(kept, ref)) continue;
3689
4075
  if (skipped && this.storeFailures.size === 0) {
3690
4076
  out.push({ ref, reason: "skipped", detail: "--no-catalog: the store was not asked" });
3691
4077
  continue;
@@ -3831,7 +4217,7 @@ var AssetManager = class {
3831
4217
  * other version taking its place) whose deployed folder holds hand edits: the files that differ
3832
4218
  * from the lock are named, nothing is removed, and `--force` prunes anyway (OQ6).
3833
4219
  */
3834
- async assertNoHandEditsPruned(resolved, keep) {
4220
+ async assertNoHandEditsPruned(resolved, keep, forceCommand) {
3835
4221
  const oldLock = this.tryReadLock();
3836
4222
  if (!oldLock) return;
3837
4223
  const resolvedRefs = [
@@ -3863,7 +4249,158 @@ var AssetManager = class {
3863
4249
  throw new Error(
3864
4250
  `Not pruned: skaile.yaml no longer declares ${lines.length === 1 ? "an asset" : "assets"} whose deployed copy holds your edits:
3865
4251
  ${lines.join("\n ")}
3866
- Nothing was changed. Declare it again to keep it, or run \`skaile install --force\` to prune it with its edits.`
4252
+ Nothing was changed. Declare it again to keep it, or run \`${forceCommand}\` to prune it with its edits.`
4253
+ );
4254
+ }
4255
+ /**
4256
+ * Report (and, without `force`, guard) the files a re-lock deletes from a deployed copy
4257
+ * (OQ5, p1441): the asset is locked already and its library or source now holds other content
4258
+ * (`next` re-locks it), and the deployed folder holds files the new content does not ship. The
4259
+ * deploy clears them. Two cases:
4260
+ * - the deployed copy still equals what `prevLock` recorded (per-file sha256 for a source
4261
+ * or store asset, the library hash for a library asset): the new version dropped those
4262
+ * files, nobody edited them, so they are deleted and come back as `removed` rows (an
4263
+ * `install`/`update` prints `Removed <file> (not part of <ref>)`), without a refusal;
4264
+ * - the copy diverged (a file the previous content did not ship, an edited file, or no hash
4265
+ * to compare): without `force` this throws one error naming the files and changes nothing.
4266
+ * A source or store asset names only the files the previous content did not ship (or that
4267
+ * were edited); a library asset records no file list, so it names every file to delete.
4268
+ *
4269
+ * @param entries - The catalog entries about to be deployed.
4270
+ * @param next - The lock the run is about to write (or merge).
4271
+ * @param libraryOf - The library each library-origin entry came from.
4272
+ */
4273
+ async relockDeletions(entries, prevLock, next, force, forceCommand, libraryOf) {
4274
+ if (!prevLock) return [];
4275
+ const relocked = relockedRefs(prevLock, next).map((key) => ({
4276
+ key,
4277
+ id: parseCanonicalLockKey(key)
4278
+ }));
4279
+ if (relocked.length === 0) return [];
4280
+ const shown = (p) => (this.global ? p : relative(this.projectDir, p)).split(sep).join("/");
4281
+ let snap;
4282
+ const rows = [];
4283
+ const refused = [];
4284
+ for (const e of entries) {
4285
+ const hit = relocked.find(({ id }) => id.kind === e.kind && id.name === e.name);
4286
+ const component = hit ? this.installTarget(e.kind) : void 0;
4287
+ if (!hit || !component) continue;
4288
+ const prevEntry = Object.entries(prevLock.assets).find(([k]) => {
4289
+ const o = parseCanonicalLockKey(k);
4290
+ return o.kind === hit.id.kind && o.name === hit.id.name && o.publisher === hit.id.publisher;
4291
+ })?.[1];
4292
+ const removed = [];
4293
+ const blocked = [];
4294
+ for (const t of this.targetHarnesses()) {
4295
+ const drift = deployedAssetDrift(e, this.deployOptsFor(t));
4296
+ if (!drift || drift.extra.length === 0) continue;
4297
+ const dir = deployedDir(component, e.name, t, this.global, this.projectDir);
4298
+ const files = drift.extra;
4299
+ let untouched;
4300
+ if (prevEntry && prevEntry.files.length > 0) {
4301
+ const locked = new Map(
4302
+ lockedAssetFiles(e.kind, prevEntry.files).map((f) => [f.rel, f.sha256])
4303
+ );
4304
+ untouched = files.filter((rel) => {
4305
+ const want = locked.get(rel);
4306
+ if (want === void 0) return false;
4307
+ try {
4308
+ return computeHash(join(dir, rel)) === want;
4309
+ } catch {
4310
+ return false;
4311
+ }
4312
+ });
4313
+ } else if (prevEntry?.library?.sha256 && !prevEntry.library.installed) {
4314
+ const libName = libraryOf.get(e);
4315
+ snap ??= await this.loadLibraryIndex();
4316
+ const libPath = libName ? this.libraryPath({ name: libName }, snap) : null;
4317
+ if (libPath) {
4318
+ const assetRoot = statSync(e.source).isDirectory() ? e.source : dirname(e.source);
4319
+ const hash = computeDeterministicHash(
4320
+ dir,
4321
+ listAssetFiles(dir),
4322
+ posix.normalize(relative(libPath, assetRoot).split(sep).join("/"))
4323
+ );
4324
+ if (hash === prevEntry.library.sha256) untouched = [...files];
4325
+ }
4326
+ }
4327
+ const own = new Set(untouched ?? []);
4328
+ for (const f of files) (own.has(f) ? removed : blocked).push(shown(join(dir, f)));
4329
+ }
4330
+ if (removed.length > 0) rows.push({ ref: hit.key, restored: [], removed });
4331
+ if (blocked.length > 0) refused.push({ ref: hit.key, restored: [], removed: blocked });
4332
+ }
4333
+ if (refused.length === 0 || force) return [...rows, ...refused];
4334
+ const lines = refused.map((r) => `${r.ref} (${r.removed.join(", ")})`);
4335
+ throw new Error(
4336
+ `Not re-locked: the deployed copy of ${refused.length === 1 ? "an asset" : "assets"} holds files its locked content did not ship or that were edited, and the deploy would delete them:
4337
+ ${lines.join("\n ")}
4338
+ Nothing was changed. Copy what you need, or run \`${forceCommand}\` to delete them.`
4339
+ );
4340
+ }
4341
+ /**
4342
+ * Stop `install` from overwriting a hand-edited rendered agent file that no lock entry
4343
+ * records (OQ5, p1440): the lock is gone (or never held the agent), so nothing tells an edit
4344
+ * from a stale render, and the deploy would replace the file without a word. Checks the file
4345
+ * named after each agent about to be deployed and the file of each agent instance whose bound
4346
+ * agent has no lock entry. Throws one error naming each file that exists and differs from its
4347
+ * render and changes nothing; `--force` (the caller skips this check) overwrites. A file that
4348
+ * equals its render is not an edit. Project mode only.
4349
+ */
4350
+ assertNoUnrecordedAgentOverwrite(entries, prevLock, forceCommand) {
4351
+ if (this.global) return;
4352
+ const lockedAgents = new Set(
4353
+ Object.keys(prevLock?.assets ?? {}).map(parseCanonicalLockKey).filter((k) => k.kind && this.installTarget(k.kind) === "agent").map((k) => k.name)
4354
+ );
4355
+ const opts = this.deployOptsFor(this.harness);
4356
+ const lines = /* @__PURE__ */ new Set();
4357
+ const collect = (diff, what) => {
4358
+ if (!diff) return;
4359
+ for (const m of diff.matchAll(/^--- (.+?)\t/gm)) {
4360
+ const abs = m[1];
4361
+ if (!existsSync(abs)) continue;
4362
+ lines.add(
4363
+ `${relative(this.projectDir, abs).split(sep).join("/")} (${what} differs from its render)`
4364
+ );
4365
+ }
4366
+ };
4367
+ for (const e of entries) {
4368
+ if (this.installTarget(e.kind) !== "agent" || lockedAgents.has(e.name)) continue;
4369
+ try {
4370
+ collect(diffDeployedAgent(e, opts), `agent ${e.name}`);
4371
+ } catch {
4372
+ }
4373
+ }
4374
+ let instances = {};
4375
+ try {
4376
+ instances = effectiveAgentInstances(resolveSkWorkspaceConfig(this.projectDir));
4377
+ } catch {
4378
+ }
4379
+ for (const [id, config] of Object.entries(instances)) {
4380
+ if (!config.ref.startsWith("agent:")) continue;
4381
+ const bound = parseRefTolerant(config.ref);
4382
+ if (bound?.kind === "agent" && lockedAgents.has(bound.name)) continue;
4383
+ try {
4384
+ const srcDir = resolveAgentDefinitionDir(this.projectDir, config.ref);
4385
+ if (srcDir === void 0) continue;
4386
+ const entry = {
4387
+ kind: "agent",
4388
+ name: id,
4389
+ description: "",
4390
+ source: srcDir,
4391
+ version: "",
4392
+ requires: [],
4393
+ dependencies: []
4394
+ };
4395
+ collect(diffDeployedAgent(entry, opts, { id, srcDir, config }), `agent instance ${id}`);
4396
+ } catch {
4397
+ }
4398
+ }
4399
+ if (lines.size === 0) return;
4400
+ throw new Error(
4401
+ `Not deployed: the deploy would overwrite ${lines.size === 1 ? "a rendered agent file" : "rendered agent files"} that no lock entry records:
4402
+ ${[...lines].join("\n ")}
4403
+ Nothing was changed. Move your edits away, or run \`${forceCommand}\` to overwrite the file.`
3867
4404
  );
3868
4405
  }
3869
4406
  /**
@@ -4091,7 +4628,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4091
4628
  }
4092
4629
  assertLockHashes(lock);
4093
4630
  this.assertNoFolderCollision(Object.keys(lock.assets).map(parseCanonicalLockKey));
4094
- const orphaned = [...(await this.orphanedSourceGaps()).values()];
4631
+ const orphaned = [...(await this.orphanedSourceGaps(void 0, true)).values()];
4095
4632
  if (orphaned.length > 0) {
4096
4633
  throw new Error(
4097
4634
  `Lock file verification failed:
@@ -4166,12 +4703,14 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4166
4703
  throw new Error(`Lock file verification failed:
4167
4704
  ${reason}`);
4168
4705
  }
4169
- const library = await this.restoreLibraryLockEntries(lock);
4706
+ const library = await this.restoreLibraryLockEntries(lock, { storeFallback: true });
4170
4707
  assertFlowsLoad([...resolved, ...library.entries], "installed");
4171
4708
  assertNoLibrarySymlinks([...resolved, ...library.entries], this.registry, repositories);
4709
+ const inferredHarnesses = this.inferLegacyHarnessSubsets([...resolved, ...library.entries]);
4172
4710
  const copyDrift = await this.lockedCopyDrift();
4173
4711
  const missingCopies = await this.incompleteDeploys();
4174
4712
  const unrecordedAgents = this.unrecordedAgentFiles(lock);
4713
+ const composedDrift = /* @__PURE__ */ new Set();
4175
4714
  const deployed = [
4176
4715
  ...new Set(
4177
4716
  this.targetHarnesses().flatMap(
@@ -4179,7 +4718,10 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4179
4718
  this.entriesForHarness([...resolved, ...library.entries], t),
4180
4719
  repositories,
4181
4720
  patches,
4182
- this.deployOptsFor(t)
4721
+ this.deployOptsFor(t, void 0, void 0, {
4722
+ refresh: false,
4723
+ onDrift: (ref, composeRef) => composedDrift.add(this.composedDriftLine(ref, composeRef))
4724
+ })
4183
4725
  )
4184
4726
  )
4185
4727
  )
@@ -4197,6 +4739,8 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4197
4739
  );
4198
4740
  const gaps = (await this.projectState()).gaps.filter((g) => {
4199
4741
  if (g.type === "agent") return false;
4742
+ if (g.unreadableSource) return false;
4743
+ if (g.type === "library" && library.viaStore.includes(g.ref)) return false;
4200
4744
  const p = parseRefTolerant(g.ref);
4201
4745
  return !(p && alreadyMissing.has(`${p.kind}:${p.name}`));
4202
4746
  }).map((g) => g.message);
@@ -4205,10 +4749,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4205
4749
  return {
4206
4750
  deployed,
4207
4751
  removed: [],
4208
- missing: [...library.missing, ...gaps],
4752
+ missing: [...library.missing, ...gaps, ...composedDrift],
4209
4753
  ...placeholderBodies.length > 0 ? { placeholderBodies } : {},
4210
4754
  ...placeholderDescriptions.length > 0 ? { placeholderDescriptions } : {},
4211
4755
  lockWritten: false,
4756
+ ...inferredHarnesses.length > 0 ? { inferredHarnesses } : {},
4212
4757
  ...restored.length > 0 ? { restored } : {},
4213
4758
  ...rewritten.length > 0 ? { rewritten } : {},
4214
4759
  ...agentRender
@@ -4236,6 +4781,72 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4236
4781
  }
4237
4782
  return [...found].sort((a, b) => gt(coerceVersion(a), coerceVersion(b)) ? -1 : 1);
4238
4783
  }
4784
+ /**
4785
+ * The refs of the other kinds that a source or a library holds under the same publisher and
4786
+ * name as `parsed` (`agent:@pub/captain` for a `@pub/captain` that is no skill), for the
4787
+ * `Did you mean` of an `add` that missed.
4788
+ */
4789
+ async otherKindRefs(parsed) {
4790
+ if (!parsed.publisher) return [];
4791
+ const publisher = parsed.publisher.replace(/^@/, "");
4792
+ const found = [];
4793
+ for (const kind of [...this.knownKinds].sort()) {
4794
+ if (kind === parsed.kind) continue;
4795
+ const candidate = `${kind}:@${publisher}/${parsed.name}`;
4796
+ try {
4797
+ if (await this.info(candidate) !== null) found.push(candidate);
4798
+ } catch {
4799
+ }
4800
+ }
4801
+ return found;
4802
+ }
4803
+ /**
4804
+ * What `add` would deploy for the resolved candidates and library entries, without touching
4805
+ * the project: one item per asset with its origin, version, deploy folders and files.
4806
+ */
4807
+ planAddItems(resolved, lib, stores, parsed) {
4808
+ const storeUrls = new Set(stores.map((s) => normalizeRepoUrl(s.url)));
4809
+ const factoryUrl = normalizeRepoUrl(FACTORY_SOURCE_URL);
4810
+ const folders = (kind, name) => {
4811
+ const component = this.installTarget(kind);
4812
+ if (!component) return [];
4813
+ return this.targetHarnesses().map((t) => {
4814
+ const dir = deployedDir(component, name, t, this.global, this.projectDir);
4815
+ const rel = relative(this.projectDir, dir);
4816
+ const shown = this.global || rel.startsWith("..") ? dir.replace(homedir(), "~") : `${rel}${dir.endsWith(sep) ? sep : ""}`;
4817
+ return shown;
4818
+ });
4819
+ };
4820
+ const asked = (kind, name, publisher) => kind === parsed.kind && name === parsed.name && (!parsed.publisher || (publisher ?? "").replace(/^@/, "") === parsed.publisher.replace(/^@/, ""));
4821
+ const items = [];
4822
+ for (const c of resolved) {
4823
+ const url = normalizeRepoUrl(c.sourceUrl);
4824
+ items.push({
4825
+ ref: assetRefToStr({
4826
+ kind: c.kind,
4827
+ name: c.name,
4828
+ publisher: c.publisher.replace(/^@/, "")
4829
+ }),
4830
+ version: c.version,
4831
+ origin: storeUrls.has(url) ? `store ${c.sourceUrl}` : url === factoryUrl ? "factory" : `source ${sourceShortName(c.sourceUrl)}`,
4832
+ requested: asked(c.kind, c.name, c.publisher),
4833
+ folders: folders(c.kind, c.name),
4834
+ files: c.files.map((f) => f.path)
4835
+ });
4836
+ }
4837
+ for (const e of lib.entries) {
4838
+ const id = this.entryIdentity(e);
4839
+ items.push({
4840
+ ref: canonicalEntryRef(e),
4841
+ version: e.version ?? "",
4842
+ origin: `library ${lib.libraryOf.get(e) ?? ""}`.trim(),
4843
+ requested: asked(e.kind, e.name, id.publisher),
4844
+ folders: folders(e.kind, e.name),
4845
+ files: [...e.files ?? []]
4846
+ });
4847
+ }
4848
+ return items.sort((a, b) => Number(b.requested) - Number(a.requested));
4849
+ }
4239
4850
  /**
4240
4851
  * Parse the ref `add` was given. A ref the grammar rejects fails with the parser's message
4241
4852
  * minus its manifest-migration advice (it points at a skill that is not for `add`), and:
@@ -4253,7 +4864,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4253
4864
  const plain = err.message.endsWith(advice) ? err.message.slice(0, -advice.length) : err.message;
4254
4865
  if (plain.startsWith("publisher required")) {
4255
4866
  const matches = await this.resolvableMatches(ref, repositories, sources);
4256
- const hint = matches.length === 0 ? "" : matches.length === 1 ? ` Did you mean ${matches[0]}?` : ` Did you mean one of: ${matches.join(", ")}?`;
4867
+ const hint = matches.length === 0 ? ` Find the publisher with \`skaile store search ${ref.trim().replace(/^[^:@/#\s]+:/, "").replace(/#.*$/, "")}\`.` : matches.length === 1 ? ` Did you mean ${matches[0]}?` : ` Did you mean one of: ${matches.join(", ")}?`;
4257
4868
  throw new Error(`${plain}${hint}`);
4258
4869
  }
4259
4870
  const pinned = /^(.*\/[^@#/]+)@([^@#/]+)$/.exec(ref.trim());
@@ -4334,6 +4945,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4334
4945
  stores,
4335
4946
  declaredRefs: dependencies,
4336
4947
  warnCollisions: true,
4948
+ lockedOrigin: this.lockedOriginVersions(),
4337
4949
  readStoreRequires: true,
4338
4950
  ...opts?.preferLockedLibrary ? { preferLocked: await this.lockedPreference() } : {}
4339
4951
  });
@@ -4377,8 +4989,9 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4377
4989
  );
4378
4990
  }
4379
4991
  const available = await this.availableVersions(parsed, stores);
4992
+ const otherKinds = /^[^@/#\s]+:/.test(ref.trim()) ? [] : await this.otherKindRefs(parsed);
4380
4993
  throw new Error(
4381
- `Asset not found: ${ref}${(await this.publisherChangeHints([ref]))[ref] ?? ""}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}${available.length ? `; ${parsed.name} has version(s): ${available.join(", ")}` : ""}`
4994
+ `Asset not found: ${ref}${(await this.publisherChangeHints([ref]))[ref] ?? ""}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}${available.length ? `; ${parsed.name} has version(s): ${available.join(", ")}` : ""}` + (otherKinds.length === 0 ? "" : otherKinds.length === 1 ? `. Did you mean ${otherKinds[0]}?` : `. Did you mean one of: ${otherKinds.join(", ")}?`)
4382
4995
  );
4383
4996
  }
4384
4997
  if (parsed.kind === "preset" && missing.length > 0) {
@@ -4401,6 +5014,62 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4401
5014
  )
4402
5015
  );
4403
5016
  }
5017
+ const problems = origin && libPath && !(e.kind === "flow" && flowLoadError(e) !== void 0) ? libraryAssetCheck?.({ kind: e.kind, name: e.name, source: e.source }, libPath) ?? [] : [];
5018
+ if (problems.length > 0) {
5019
+ throw new Error(
5020
+ `${canonicalEntryRef(e)} in library "${origin}" fails the checks of \`skaile validate\` - nothing was added:
5021
+ ${problems.map((l) => ` ${l}`).join("\n")}
5022
+ Fix the asset, then run \`skaile library index ${origin}\` and add it again.`
5023
+ );
5024
+ }
5025
+ }
5026
+ }
5027
+ if (libraryAssetCheck) {
5028
+ for (const c of resolved) {
5029
+ const decl = Object.entries(repositories).find(([, d]) => d.url === c.sourceUrl);
5030
+ const cloneDir = decl ? this.resolveRepoDir(decl[1], decl[0]) : null;
5031
+ const entry = cloneDir ? this.candidateToCatalogEntry(c, repositories) : null;
5032
+ if (!cloneDir || !entry) continue;
5033
+ if (entry.kind === "flow" && flowLoadError(entry) !== void 0) continue;
5034
+ const problems = libraryAssetCheck(
5035
+ { kind: entry.kind, name: entry.name, source: entry.source },
5036
+ cloneDir
5037
+ );
5038
+ if (problems.length > 0) {
5039
+ throw new Error(
5040
+ `${canonicalEntryRef(entry)} from source ${c.sourceUrl} fails the checks of \`skaile validate\` - nothing was added:
5041
+ ${problems.map((l) => ` ${l}`).join("\n")}
5042
+ Fix the asset in the source repo, then run \`skaile source sync\` and add it again.`
5043
+ );
5044
+ }
5045
+ }
5046
+ }
5047
+ const staleSiblings = this.staleSourceSiblingRefs(resolved, dependencies);
5048
+ if (staleSiblings.length > 0) {
5049
+ const siblings = await this.resolveLibraryFirst(staleSiblings, {
5050
+ provenanceIndex,
5051
+ overrides,
5052
+ stores,
5053
+ declaredRefs: dependencies,
5054
+ lockedOrigin: this.lockedOriginVersions(),
5055
+ readStoreRequires: true
5056
+ });
5057
+ for (const sib of staleSiblings) {
5058
+ const pin = parseAssetRefLoose(sib)?.pin;
5059
+ if (!pin) continue;
5060
+ const id = refIdentity(sib.replace(/#.*$/, ""));
5061
+ const kept = siblings.resolved.some(
5062
+ (c) => `${c.publisher.replace(/^@/, "")}/${c.kind}:${c.name}` === id?.replace(/^@/, "") && matchPin(pin, c.version ?? "")
5063
+ );
5064
+ if (!kept) {
5065
+ throw new Error(
5066
+ `${sib.replace(/#.*$/, "")} is pinned to ${pin} in skaile.yaml, but its source moved on and no longer holds ${pin}, so adding ${ref} would break the lock - nothing was added. Change the pin in skaile.yaml, or drop the pinned asset, then add again.`
5067
+ );
5068
+ }
5069
+ }
5070
+ const held = new Set(resolved.map((c) => this.candidateKey(c)));
5071
+ for (const c of siblings.resolved) {
5072
+ if (!held.has(this.candidateKey(c))) resolved.push(c);
4404
5073
  }
4405
5074
  }
4406
5075
  this.assertSourceCommitsPublished(resolved, repositories);
@@ -4437,6 +5106,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4437
5106
  `${ref.split("#")[0]} requires ${missing.join(", ")} (not found \u2014 ${REGISTER_HOLDER}, or remove the requirement from the asset's manifest) \u2014 nothing was added`
4438
5107
  );
4439
5108
  }
5109
+ if (opts?.dryRun) {
5110
+ const plan = this.planAddItems(resolved, lib, stores, parsed);
5111
+ opts.onPlan?.(plan);
5112
+ return [];
5113
+ }
4440
5114
  const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
4441
5115
  assertFlowsLoad(entries, "added");
4442
5116
  assertNoLibrarySymlinks(entries, this.registry, repositories, opts?.link === true);
@@ -4459,7 +5133,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4459
5133
  this.entriesForHarness(entries, t),
4460
5134
  repositories,
4461
5135
  patches,
4462
- this.deployOptsFor(t, opts?.link, opts?.onSecretFile)
5136
+ this.deployOptsFor(t, opts?.link, opts?.onSecretFile, {
5137
+ refresh: true,
5138
+ onDrift: () => {
5139
+ }
5140
+ })
4463
5141
  )
4464
5142
  )
4465
5143
  )
@@ -4816,10 +5494,18 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4816
5494
  }
4817
5495
  const kept = [];
4818
5496
  const ownedBy = [];
5497
+ const mcpPublishers = target.kind === "mcp-server" && !this.global ? [
5498
+ .../* @__PURE__ */ new Set([
5499
+ ...this.lockHolders("mcp-server", target.name).map((h) => h.publisher),
5500
+ target.publisher?.replace(/^@/, "")
5501
+ ])
5502
+ ].filter((p) => !!p) : [];
4819
5503
  const changed = this.removeResolved(target, { force: opts?.force, kept, ownedBy });
5504
+ const droppedSettings = changed && mcpPublishers.length > 0 ? this.dropRemovedMcpServerSettings(target.name, mcpPublishers) : [];
4820
5505
  const keptNote = {
4821
5506
  ...kept.length > 0 ? { kept } : {},
4822
- ...ownedBy.length > 0 ? { ownedBy } : {}
5507
+ ...ownedBy.length > 0 ? { ownedBy } : {},
5508
+ ...droppedSettings.length > 0 ? { droppedSettings } : {}
4823
5509
  };
4824
5510
  const pruned = changed && requiredRefs.length > 0 ? await this.pruneOrphans(requiredRefs) : [];
4825
5511
  if (changed && (target.kind === "mcp-server" || pruned.some((r) => r.startsWith("mcp-server:")))) {
@@ -4987,6 +5673,40 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
4987
5673
  }
4988
5674
  return changed;
4989
5675
  }
5676
+ /**
5677
+ * After an `mcp-server` asset left the project: drop what would keep it rendered. Its staged
5678
+ * store bytes under `.skaile/cache/store/<publisher>/mcp-server/<name>/` go (an `mcp_servers:`
5679
+ * entry of the same id would otherwise keep loading them), and so does an `mcp_servers:` entry
5680
+ * that only sets values for it: `env` and the like, with no `command`, `url` or recipe of its
5681
+ * own. An entry that can run alone is the user's own server and stays.
5682
+ *
5683
+ * @returns The dropped `mcp_servers` entries, as `mcp_servers entry "<id>"`.
5684
+ */
5685
+ dropRemovedMcpServerSettings(name, publishers) {
5686
+ for (const publisher of publishers) {
5687
+ rmSync(join(this.storeCacheDir, publisher, "mcp-server", name), {
5688
+ recursive: true,
5689
+ force: true
5690
+ });
5691
+ }
5692
+ const configPath = join(this.stateRoot, "skaile.yaml");
5693
+ if (!existsSync(configPath)) return [];
5694
+ const raw = parse(readFileSync(configPath, "utf8")) ?? {};
5695
+ if (!Array.isArray(raw.mcp_servers)) return [];
5696
+ const dropped = [];
5697
+ const standalone = ["command", "url", "recipe", "launcher"];
5698
+ raw.mcp_servers = raw.mcp_servers.filter((entry) => {
5699
+ const e = entry;
5700
+ if (!e || typeof e.id !== "string" || unscopedName(e.id) !== name) return true;
5701
+ if (standalone.some((k) => e[k] !== void 0 && e[k] !== null)) return true;
5702
+ dropped.push(`mcp_servers entry "${e.id}"`);
5703
+ return false;
5704
+ });
5705
+ if (dropped.length === 0) return [];
5706
+ if (raw.mcp_servers.length === 0) delete raw.mcp_servers;
5707
+ writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
5708
+ return dropped;
5709
+ }
4990
5710
  /**
4991
5711
  * After `remove --global --harness X`: the harnesses outside `X` that still hold a copy of the
4992
5712
  * asset, or `undefined` when none does (or this is not a harness-limited global remove).
@@ -5158,7 +5878,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5158
5878
  }
5159
5879
  }
5160
5880
  if (best) return best;
5161
- const lib = await this.resolveLibraryEntries([ref], { quiet: true });
5881
+ const lockedLibraries = await this.lockedLibraryNames();
5882
+ const lib = await this.resolveLibraryEntries([ref], {
5883
+ quiet: true,
5884
+ ...lockedLibraries.size > 0 ? { lockedLibraries } : {}
5885
+ });
5162
5886
  return lib.entries[0] ?? null;
5163
5887
  }
5164
5888
  /**
@@ -5325,7 +6049,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5325
6049
  state = "declared";
5326
6050
  } else if (parsed.kind === "mcp-server") {
5327
6051
  const wired = await wiredMcpNames();
5328
- const isWired = wired.has(parsed.name) || wired.has(unscopedName(parsed.name));
6052
+ const isWired = wired.has(parsed.name) || wired.has(unscopedName(parsed.name)) || !this.global && await mcpCommittedRenderIntact(
6053
+ this.projectDir,
6054
+ this.harness,
6055
+ unscopedName(parsed.name)
6056
+ );
5329
6057
  const skippedReason = Object.entries(
5330
6058
  await readSkippedMcpServers(this.projectDir, this.harness)
5331
6059
  ).find(([id]) => id === parsed.name || unscopedName(id) === parsed.name)?.[1];
@@ -5670,11 +6398,12 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5670
6398
  if (!lock) return [];
5671
6399
  const bare = (s) => s.includes("#") ? s.slice(0, s.indexOf("#")) : s;
5672
6400
  const asked = parseRefTolerant(ref);
6401
+ const askedPublisher = asked?.publisher?.replace(/^@/, "");
5673
6402
  const matchesIdentity = (k) => {
5674
6403
  if (bare(k) === bare(ref)) return true;
5675
- if (!asked || asked.publisher) return false;
6404
+ if (!asked) return false;
5676
6405
  const locked = parseCanonicalLockKey(k);
5677
- return locked.kind === asked.kind && locked.name === asked.name;
6406
+ return locked.kind === asked.kind && locked.name === asked.name && (!askedPublisher || locked.publisher === askedPublisher);
5678
6407
  };
5679
6408
  const key = lock.assets[ref] ? ref : Object.keys(lock.assets).find(matchesIdentity);
5680
6409
  if (!key) return asked?.kind === "preset" ? this.whyPreset(lock, asked) : [];
@@ -5810,6 +6539,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5810
6539
  const libraryAssets = Object.entries(lock.assets).filter(
5811
6540
  ([, e]) => e.library && !e.library.installed
5812
6541
  );
6542
+ const declaredPins = this.declaredPinsByIdentity();
5813
6543
  if (libraryAssets.length > 0) {
5814
6544
  const snap = await this.loadLibraryIndex();
5815
6545
  const diskHashes = /* @__PURE__ */ new Map();
@@ -5835,6 +6565,8 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5835
6565
  { kind, publisher, name },
5836
6566
  found.version
5837
6567
  );
6568
+ const declaredPin = declaredPins.get(`${kind}:${publisher}/${name}`);
6569
+ if (declaredPin && diskVersion && !matchPin(declaredPin, diskVersion)) continue;
5838
6570
  if (diskVersion && !isPseudoVersion(diskVersion) && gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
5839
6571
  result.push({
5840
6572
  kind,
@@ -5873,6 +6605,26 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5873
6605
  library: lookup.name,
5874
6606
  contentChanged: true
5875
6607
  });
6608
+ continue;
6609
+ }
6610
+ }
6611
+ if (entry.composed && kind === "agent") {
6612
+ const srcDir = statSync(found.source).isDirectory() ? found.source : dirname(found.source);
6613
+ const now = new Map(
6614
+ sourceComposedFiles(srcDir, name, this.projectDir).map((f) => [f.path, f.sha256])
6615
+ );
6616
+ const locked = new Map(entry.composed.map((f) => [f.path, f.sha256]));
6617
+ const same = now.size === locked.size && [...now].every(([f, h]) => locked.get(f) === h);
6618
+ if (!same) {
6619
+ result.push({
6620
+ kind,
6621
+ name,
6622
+ publisher,
6623
+ currentVersion: origin.version,
6624
+ behind: 0,
6625
+ library: lookup.name,
6626
+ contentChanged: true
6627
+ });
5876
6628
  }
5877
6629
  }
5878
6630
  }
@@ -5882,6 +6634,23 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5882
6634
  await this.addPinnedRows(lock, result);
5883
6635
  return this.addLockGapRows(result);
5884
6636
  }
6637
+ /**
6638
+ * The `#pin` of every publisher-qualified `skaile.yaml` dependency, keyed `kind:publisher/name`
6639
+ * (no scope sigil). `update` honours it, so `outdated` must not list a move it keeps.
6640
+ */
6641
+ declaredPinsByIdentity() {
6642
+ const pins = /* @__PURE__ */ new Map();
6643
+ for (const dep of this.loadConfig().dependencies) {
6644
+ try {
6645
+ const p = parseAssetRef(dep);
6646
+ if (p.pin && p.publisher) {
6647
+ pins.set(`${p.kind}:${p.publisher.replace(/^@/, "")}/${p.name}`, p.pin);
6648
+ }
6649
+ } catch {
6650
+ }
6651
+ }
6652
+ return pins;
6653
+ }
5885
6654
  /**
5886
6655
  * List a locked asset whose `skaile.yaml` ref carries a `#version` pin while a source, a
5887
6656
  * library or a store holds a newer one: `locked 0.7.0 -> 0.9.0 (pinned)`. The pin is the
@@ -5986,20 +6755,20 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
5986
6755
  async addLibraryMoves(libraryAssets, rows) {
5987
6756
  const refs = [];
5988
6757
  const lockedIn = /* @__PURE__ */ new Map();
6758
+ const declaredPins = this.declaredPinsByIdentity();
5989
6759
  for (const [key, entry] of libraryAssets) {
5990
6760
  const { publisher, kind, name } = parseCanonicalLockKey(key);
5991
6761
  if (!publisher || !kind || !name || !entry.library) continue;
5992
6762
  const ref = `${kind}:@${publisher}/${name}`;
5993
- refs.push(ref);
6763
+ const pin = declaredPins.get(`${kind}:${publisher}/${name}`);
6764
+ refs.push(pin ? `${ref}#${pin}` : ref);
5994
6765
  lockedIn.set(ref, { name: entry.library.name, key });
5995
6766
  }
5996
6767
  if (refs.length === 0) return;
5997
6768
  let res;
6769
+ const preferLocked = await this.lockedPreference();
5998
6770
  try {
5999
- res = await this.resolveLibraryEntries(refs, {
6000
- quiet: true,
6001
- preferLocked: await this.lockedPreference()
6002
- });
6771
+ res = await this.resolveLibraryEntries(refs, { quiet: true, preferLocked });
6003
6772
  } catch {
6004
6773
  return;
6005
6774
  }
@@ -6009,9 +6778,11 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6009
6778
  const ref = `${e.kind}:@${publisher}/${e.name}`;
6010
6779
  const from = lockedIn.get(ref);
6011
6780
  if (!to || !from || from.name === to) continue;
6781
+ const renamedOnly = preferLocked.libraries.get(`${e.kind}:${publisher}/${e.name}`) === to;
6012
6782
  const row = rows.find(
6013
6783
  (r) => r.kind === e.kind && r.name === e.name && r.publisher.replace(/^@/, "") === publisher
6014
6784
  );
6785
+ if (renamedOnly && !row) continue;
6015
6786
  if (row) {
6016
6787
  row.library = from.name;
6017
6788
  row.movesTo = to;
@@ -6143,7 +6914,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6143
6914
  (r) => unscopedName(r.drift.id) === unscopedName(parsed.name)
6144
6915
  );
6145
6916
  if (!row) return { status: "identical" };
6146
- const fileSetDiffers = row.drift.extra.length > 0 || row.drift.missing.some((f) => !f.includes(" entry "));
6917
+ const fileSetDiffers = row.drift.extra.length > 0 || row.drift.missing.length > 0;
6147
6918
  return {
6148
6919
  status: "differs",
6149
6920
  diff: mcpDriftDiff(row.drift),
@@ -6241,7 +7012,8 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6241
7012
  provenanceIndex,
6242
7013
  overrides,
6243
7014
  stores,
6244
- declaredRefs: dependencies
7015
+ declaredRefs: dependencies,
7016
+ lockedOrigin: this.lockedOriginVersions()
6245
7017
  });
6246
7018
  this.assertSourceCommitsPublished(resolved, repositories);
6247
7019
  const libraryLock = await this.libraryLockEntries(lib);
@@ -6250,6 +7022,8 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6250
7022
  for (const [key, entry] of Object.entries(lockData.assets)) {
6251
7023
  const rendered = prior?.assets[key]?.rendered;
6252
7024
  if (rendered) entry.rendered = rendered;
7025
+ const composed = prior?.assets[key]?.composed;
7026
+ if (composed) entry.composed = composed;
6253
7027
  }
6254
7028
  writeLock(this.lockPath, lockData);
6255
7029
  return lockData;
@@ -6609,7 +7383,8 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6609
7383
  ref: s.url,
6610
7384
  reason,
6611
7385
  message: `source ${s.url} ${reason}`,
6612
- manualFix: true
7386
+ manualFix: true,
7387
+ unreadableSource: true
6613
7388
  });
6614
7389
  }
6615
7390
  for (const u of unboundAgentInstances(this.projectDir, this.harness)) {
@@ -6835,7 +7610,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6835
7610
  continue;
6836
7611
  }
6837
7612
  if (lookup.state === "unregistered") {
6838
- const reason2 = `is locked, but library "${origin.name}" is not registered (renamed or removed?) \u2014 ${unregisteredLibraryFix(origin, ref)}`;
7613
+ const reason2 = `is locked, but library "${unregisteredLibraryName(origin)}" is not registered (renamed or removed?) \u2014 ${unregisteredLibraryFix(origin, ref)}`;
6839
7614
  gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}`, manualFix: true });
6840
7615
  continue;
6841
7616
  }
@@ -6855,12 +7630,15 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6855
7630
  * The locked source assets whose source is no longer in `skaile.yaml` `sources:` (`source
6856
7631
  * remove`, a hand edit), keyed by lock key. ONE sentence for it: `check`, `doctor`, `outdated`,
6857
7632
  * `install`, `install --locked` and `update` all say it (p1062), and none re-clones the source.
6858
- * A source of a store-resolved lock entry is not told apart from one, so no GitHub source is
6859
- * called missing while a `stores:` entry exists.
7633
+ * A GitHub lock entry that no source declares counts as store-supplied only when a store
7634
+ * lists the ref (OQ3): a configured store alone proves nothing, as it may never have held
7635
+ * the asset. `check`, `doctor` and `outdated` read the store's cached catalog and never touch
7636
+ * the network; `install` and `update` (`online`) ask the store itself.
6860
7637
  *
6861
7638
  * @param inScope - Only these lock keys count (`install` prunes an undeclared entry as usual).
7639
+ * @param online - Ask the stores when the cached catalog does not list the ref.
6862
7640
  */
6863
- async orphanedSourceGaps(inScope) {
7641
+ async orphanedSourceGaps(inScope, online = false) {
6864
7642
  const out = /* @__PURE__ */ new Map();
6865
7643
  let config;
6866
7644
  try {
@@ -6868,7 +7646,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6868
7646
  } catch {
6869
7647
  return out;
6870
7648
  }
6871
- const storeBacked = config.stores.length > 0 || (await this.globalStoreFallback(config.stores, false)).length > 0;
7649
+ const stores = [...config.stores, ...await this.globalStoreFallback(config.stores, false)];
6872
7650
  const registered = new Set(config.sources.map((s) => sourceCacheKey(s.url)));
6873
7651
  for (const [key, entry] of Object.entries(this.tryReadLock()?.assets ?? {})) {
6874
7652
  const url = entry.source?.url;
@@ -6878,13 +7656,38 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6878
7656
  const { publisher, kind, name } = parseCanonicalLockKey(key);
6879
7657
  if (!publisher || !kind || !name) continue;
6880
7658
  if (registered.has(sourceCacheKey(url))) continue;
6881
- if (storeBacked && GITHUB_REPO_URL.test(url)) continue;
6882
7659
  const ref = `${kind}:@${publisher}/${name}`;
7660
+ if (GITHUB_REPO_URL.test(url) && await this.storeListsRef(stores, ref, online)) continue;
6883
7661
  const reason = `is locked, but its source ${url} is not in skaile.yaml sources: any more \u2014 register it again with \`skaile source add ${url}\`, or drop the dependency with \`skaile remove ${ref}\``;
6884
7662
  out.set(key, { type: "source", ref, reason, message: `${ref} ${reason}`, manualFix: true });
6885
7663
  }
6886
7664
  return out;
6887
7665
  }
7666
+ /**
7667
+ * `true` when one of `stores` lists `ref` (a canonical lock key): its cached catalog holds it
7668
+ * or, with `online`, the store answers for it. An injected store fetcher is the store client
7669
+ * itself, so it is asked either way. A store an online install cannot ask (unreachable,
7670
+ * air-gapped, `--no-catalog`) may hold the ref; `check` never asks, so a store lists only what
7671
+ * its cached catalog holds.
7672
+ */
7673
+ async storeListsRef(stores, ref, online) {
7674
+ for (const store of stores) {
7675
+ try {
7676
+ if (this.injectedStoreFetcher) {
7677
+ if (await this.injectedStoreFetcher.getInstallManifest(store.url, ref)) return true;
7678
+ continue;
7679
+ }
7680
+ const remote = this.remoteStore(store.url);
7681
+ if (remote.holdsCached(ref)) return true;
7682
+ if (!online) continue;
7683
+ if (this.catalogUrlOverride === "") return true;
7684
+ if (await remote.resolve(ref)) return true;
7685
+ } catch {
7686
+ if (online) return true;
7687
+ }
7688
+ }
7689
+ return false;
7690
+ }
6888
7691
  /**
6889
7692
  * Locked source assets whose source cannot supply them any more: the source is no longer in
6890
7693
  * `skaile.yaml` `sources:` (`source remove`, a hand edit), or it is there but now publishes
@@ -6941,8 +7744,12 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6941
7744
  * rendering occupies and their sha256. Call it after the deploy, so the bytes hashed are the
6942
7745
  * ones `install` just wrote; {@link lockedCopyDrift} compares later edits with them.
6943
7746
  * Entries that carry a record (kept from an earlier lock) are left as they are.
7747
+ *
7748
+ * @param keepComposedOf - The lock an `install` read: an agent whose `../` compose folder is
7749
+ * not in the cache keeps that lock's `composed:` record. Omitted by `update` and `add`,
7750
+ * which copy the library's current bytes.
6944
7751
  */
6945
- withRenderedAgents(lock) {
7752
+ withRenderedAgents(lock, keepComposedOf) {
6946
7753
  if (this.global) return lock;
6947
7754
  for (const [key, entry] of Object.entries(lock.assets)) {
6948
7755
  if (entry.rendered || entry.library?.installed) continue;
@@ -6951,6 +7758,18 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
6951
7758
  const rendered = this.renderedAgentFiles(name);
6952
7759
  if (rendered.length > 0) entry.rendered = rendered;
6953
7760
  }
7761
+ for (const [key, entry] of Object.entries(lock.assets)) {
7762
+ if (entry.library?.installed) continue;
7763
+ const { kind, name } = parseCanonicalLockKey(key);
7764
+ if (!kind || !name || this.installTarget(kind) !== "agent") continue;
7765
+ const composed = cachedComposedFiles(name, this.projectDir);
7766
+ if (composed.length > 0) entry.composed = composed;
7767
+ else {
7768
+ const earlier = keepComposedOf?.assets[key]?.composed;
7769
+ if (earlier?.length) entry.composed = earlier;
7770
+ else delete entry.composed;
7771
+ }
7772
+ }
6954
7773
  return this.withInstanceRenderings(lock);
6955
7774
  }
6956
7775
  /**
@@ -7206,12 +8025,13 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
7206
8025
  out.push({ ref: refOf(key), key, modified, extra, library: false });
7207
8026
  }
7208
8027
  for (const [key, entry] of Object.entries(lock.assets)) {
7209
- if (!entry.rendered || entry.rendered.length === 0 || entry.library?.installed) continue;
8028
+ const recorded = [...entry.rendered ?? [], ...entry.composed ?? []];
8029
+ if (recorded.length === 0 || entry.library?.installed) continue;
7210
8030
  const { kind, name } = parseCanonicalLockKey(key);
7211
8031
  if (!kind || !name || this.installTarget(kind) !== "agent") continue;
7212
8032
  if (patches[`${kind}:${name}`]) continue;
7213
8033
  const modified = [];
7214
- for (const f of entry.rendered) {
8034
+ for (const f of recorded) {
7215
8035
  const file = join(this.projectDir, f.path);
7216
8036
  if (!existsSync(file)) continue;
7217
8037
  try {
@@ -7232,7 +8052,7 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
7232
8052
  extra: [],
7233
8053
  library: false,
7234
8054
  rendered: true,
7235
- lockedSha: new Map(entry.rendered.map((f) => [f.path, f.sha256])),
8055
+ lockedSha: new Map(recorded.map((f) => [f.path, f.sha256])),
7236
8056
  instanceOf
7237
8057
  });
7238
8058
  }
@@ -7345,10 +8165,23 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
7345
8165
  ({ id }) => id.kind === want.kind && id.name === want.name && (!want.publisher || id.publisher === want.publisher)
7346
8166
  );
7347
8167
  if (held.length > 0 && !held.some(({ id }) => !id.version || matchPin(want.pin, id.version))) {
7348
- gap = {
7349
- type: "pin",
7350
- reason: `is pinned in skaile.yaml but the lock holds ${held.map((h) => h.key).join(", ")} \u2014 run \`skaile install\``
7351
- };
8168
+ const lockHolds = held.map((h) => h.key).join(", ");
8169
+ const pinned = await this.info(d.ref);
8170
+ const pinHeld = pinned !== null && matchPin(want.pin, pinned.version ?? "");
8171
+ if (!pinHeld && this.loadConfig().stores.length === 0) {
8172
+ const plainRef = d.ref.split("#")[0] ?? d.ref;
8173
+ const other = pinned ?? await this.info(plainRef);
8174
+ gap = {
8175
+ type: "pin",
8176
+ reason: `is pinned in skaile.yaml but the lock holds ${lockHolds}, and no library or source holds ${want.pin}${other?.version ? ` (${want.name} has version(s): ${other.version})` : ""} \u2014 change the pin in skaile.yaml`,
8177
+ manualFix: true
8178
+ };
8179
+ } else {
8180
+ gap = {
8181
+ type: "pin",
8182
+ reason: `is pinned in skaile.yaml but the lock holds ${lockHolds} \u2014 run \`skaile install\``
8183
+ };
8184
+ }
7352
8185
  }
7353
8186
  }
7354
8187
  if (gap) gaps.push({ ...gap, ref: d.ref, message: `${d.ref} ${gap.reason}` });
@@ -7821,6 +8654,6 @@ Nothing was changed. Declare it again to keep it, or run \`skaile install --forc
7821
8654
  }
7822
8655
  };
7823
8656
 
7824
- export { AssetManager, FACTORY_SOURCE_URL, LibraryEntryError, MissingManifestError, installAgentInstances, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName };
7825
- //# sourceMappingURL=chunk-OAHVKSQJ.js.map
7826
- //# sourceMappingURL=chunk-OAHVKSQJ.js.map
8657
+ export { AssetManager, FACTORY_SOURCE_URL, LibraryEntryError, MissingManifestError, installAgentInstances, mcpNotRunnableReason, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, setLibraryAssetCheck, softDepName };
8658
+ //# sourceMappingURL=chunk-HWNK3F3X.js.map
8659
+ //# sourceMappingURL=chunk-HWNK3F3X.js.map