@skaile/workspaces 5.6.1 → 5.8.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 (428) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +4 -2
  3. package/dist/app-sdk/index.js +1 -1
  4. package/dist/{asset-feeds-LU7NHOAP.js → asset-feeds-EYRZR4K5.js} +55 -31
  5. package/dist/asset-feeds-EYRZR4K5.js.map +1 -0
  6. package/dist/asset-manager/catalog-deployer.js +10 -10
  7. package/dist/asset-manager/index.js +16 -16
  8. package/dist/asset-manager/installer.js +12 -12
  9. package/dist/asset-manager/renderers.js +11 -11
  10. package/dist/asset-manager/src/index.d.ts +364 -30
  11. package/dist/asset-manager/src/index.d.ts.map +1 -1
  12. package/dist/asset-manager/src/install-agent.d.ts +11 -0
  13. package/dist/asset-manager/src/install-agent.d.ts.map +1 -1
  14. package/dist/asset-manager/src/installer.d.ts +6 -3
  15. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  16. package/dist/bridge/drivers/claude-sdk.js +148 -212
  17. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  18. package/dist/bridge/drivers/codex.js +91 -67
  19. package/dist/bridge/drivers/codex.js.map +1 -1
  20. package/dist/bridge/drivers/continue.js +16 -17
  21. package/dist/bridge/drivers/continue.js.map +1 -1
  22. package/dist/bridge/drivers/echo.js +4 -4
  23. package/dist/bridge/drivers/gemini.js +8 -8
  24. package/dist/bridge/drivers/goose.js +16 -17
  25. package/dist/bridge/drivers/goose.js.map +1 -1
  26. package/dist/bridge/drivers/omp.js +68 -104
  27. package/dist/bridge/drivers/omp.js.map +1 -1
  28. package/dist/bridge/drivers/opencode.js +16 -17
  29. package/dist/bridge/drivers/opencode.js.map +1 -1
  30. package/dist/bridge/drivers/qwen.js +8 -8
  31. package/dist/bridge/index.js +17 -18
  32. package/dist/bridge/src/codex/managed-driver.d.ts +13 -10
  33. package/dist/bridge/src/codex/managed-driver.d.ts.map +1 -1
  34. package/dist/bridge/src/drivers/_subprocess-cli.d.ts +12 -2
  35. package/dist/bridge/src/drivers/_subprocess-cli.d.ts.map +1 -1
  36. package/dist/bridge/src/drivers/claude-sdk.d.ts +37 -62
  37. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  38. package/dist/bridge/src/drivers/codex.d.ts +26 -10
  39. package/dist/bridge/src/drivers/codex.d.ts.map +1 -1
  40. package/dist/bridge/src/drivers/continue.d.ts +2 -2
  41. package/dist/bridge/src/drivers/continue.d.ts.map +1 -1
  42. package/dist/bridge/src/drivers/gemini.d.ts +2 -2
  43. package/dist/bridge/src/drivers/gemini.d.ts.map +1 -1
  44. package/dist/bridge/src/drivers/goose.d.ts +2 -2
  45. package/dist/bridge/src/drivers/goose.d.ts.map +1 -1
  46. package/dist/bridge/src/drivers/omp.d.ts +24 -35
  47. package/dist/bridge/src/drivers/omp.d.ts.map +1 -1
  48. package/dist/bridge/src/drivers/opencode.d.ts +2 -2
  49. package/dist/bridge/src/drivers/opencode.d.ts.map +1 -1
  50. package/dist/bridge/src/drivers/recovery-policy.d.ts +79 -0
  51. package/dist/bridge/src/drivers/recovery-policy.d.ts.map +1 -0
  52. package/dist/bridge/src/index.d.ts +1 -0
  53. package/dist/bridge/src/index.d.ts.map +1 -1
  54. package/dist/bridge/src/reconfigure-ledger.d.ts +117 -0
  55. package/dist/bridge/src/reconfigure-ledger.d.ts.map +1 -0
  56. package/dist/bridge/src/types.d.ts +57 -6
  57. package/dist/bridge/src/types.d.ts.map +1 -1
  58. package/dist/{chunk-OLLAQLFJ.js → chunk-2FRUUDZY.js} +5 -5
  59. package/dist/{chunk-OLLAQLFJ.js.map → chunk-2FRUUDZY.js.map} +1 -1
  60. package/dist/{chunk-SDFROXSA.js → chunk-2LWYNQK7.js} +3 -3
  61. package/dist/{chunk-SDFROXSA.js.map → chunk-2LWYNQK7.js.map} +1 -1
  62. package/dist/{chunk-TZCQ7ZWQ.js → chunk-36AFURKZ.js} +10 -11
  63. package/dist/chunk-36AFURKZ.js.map +1 -0
  64. package/dist/{chunk-MX3UQXLU.js → chunk-4MQS7CAF.js} +256 -92
  65. package/dist/chunk-4MQS7CAF.js.map +1 -0
  66. package/dist/{chunk-45VFGT7A.js → chunk-4PTSR4UU.js} +3 -3
  67. package/dist/{chunk-45VFGT7A.js.map → chunk-4PTSR4UU.js.map} +1 -1
  68. package/dist/{chunk-VX3INFBA.js → chunk-6GRX5L6A.js} +5 -5
  69. package/dist/{chunk-VX3INFBA.js.map → chunk-6GRX5L6A.js.map} +1 -1
  70. package/dist/{chunk-NFGRW3L7.js → chunk-7RGTAEG6.js} +3 -3
  71. package/dist/{chunk-NFGRW3L7.js.map → chunk-7RGTAEG6.js.map} +1 -1
  72. package/dist/{chunk-5B5IZVEP.js → chunk-AWQYXZVG.js} +15 -6
  73. package/dist/chunk-AWQYXZVG.js.map +1 -0
  74. package/dist/{chunk-XBSQ4KMC.js → chunk-B4X2N7JQ.js} +205 -3
  75. package/dist/chunk-B4X2N7JQ.js.map +1 -0
  76. package/dist/{chunk-FRHE7FFB.js → chunk-B6GIW55K.js} +5 -3
  77. package/dist/chunk-B6GIW55K.js.map +1 -0
  78. package/dist/{chunk-LLKKLKZJ.js → chunk-BJNUNQAW.js} +3 -3
  79. package/dist/{chunk-LLKKLKZJ.js.map → chunk-BJNUNQAW.js.map} +1 -1
  80. package/dist/{chunk-7ZHLYXTI.js → chunk-BTE2V4IW.js} +3 -3
  81. package/dist/{chunk-7ZHLYXTI.js.map → chunk-BTE2V4IW.js.map} +1 -1
  82. package/dist/{chunk-ZBBM6O6P.js → chunk-CGT45WN7.js} +3 -3
  83. package/dist/{chunk-ZBBM6O6P.js.map → chunk-CGT45WN7.js.map} +1 -1
  84. package/dist/{chunk-47ZD34KB.js → chunk-CIFS5K6T.js} +3 -3
  85. package/dist/{chunk-47ZD34KB.js.map → chunk-CIFS5K6T.js.map} +1 -1
  86. package/dist/{chunk-BTISGSK4.js → chunk-CKQEIY43.js} +4 -4
  87. package/dist/{chunk-BTISGSK4.js.map → chunk-CKQEIY43.js.map} +1 -1
  88. package/dist/{chunk-KXTO7HON.js → chunk-EAZECHPM.js} +3 -3
  89. package/dist/{chunk-KXTO7HON.js.map → chunk-EAZECHPM.js.map} +1 -1
  90. package/dist/{chunk-VKSXST23.js → chunk-EF7NPDDU.js} +4 -4
  91. package/dist/{chunk-VKSXST23.js.map → chunk-EF7NPDDU.js.map} +1 -1
  92. package/dist/chunk-EPBKISS5.js +84 -0
  93. package/dist/chunk-EPBKISS5.js.map +1 -0
  94. package/dist/{chunk-VJENKWXF.js → chunk-F2EKONM7.js} +3 -3
  95. package/dist/{chunk-VJENKWXF.js.map → chunk-F2EKONM7.js.map} +1 -1
  96. package/dist/{chunk-DMLDYFDS.js → chunk-F3RDEH5U.js} +7 -4
  97. package/dist/chunk-F3RDEH5U.js.map +1 -0
  98. package/dist/{chunk-UAP2XHRF.js → chunk-FTJJO3EM.js} +39 -29
  99. package/dist/chunk-FTJJO3EM.js.map +1 -0
  100. package/dist/{chunk-2GRB5ITE.js → chunk-FVJK4QWM.js} +4 -4
  101. package/dist/{chunk-2GRB5ITE.js.map → chunk-FVJK4QWM.js.map} +1 -1
  102. package/dist/{chunk-AOAZCSDI.js → chunk-FZNG4V4J.js} +16 -30
  103. package/dist/chunk-FZNG4V4J.js.map +1 -0
  104. package/dist/{chunk-BF33SCLY.js → chunk-GB7WDMH2.js} +4 -4
  105. package/dist/{chunk-BF33SCLY.js.map → chunk-GB7WDMH2.js.map} +1 -1
  106. package/dist/{chunk-NARITJS2.js → chunk-GCICGXDD.js} +3 -3
  107. package/dist/{chunk-NARITJS2.js.map → chunk-GCICGXDD.js.map} +1 -1
  108. package/dist/{chunk-PA7WLID3.js → chunk-GTILM2CI.js} +56 -5
  109. package/dist/chunk-GTILM2CI.js.map +1 -0
  110. package/dist/{chunk-WIHQWVWL.js → chunk-GXXFLBUQ.js} +26 -27
  111. package/dist/chunk-GXXFLBUQ.js.map +1 -0
  112. package/dist/{chunk-PTO2YDDG.js → chunk-H7QUDDKH.js} +1337 -168
  113. package/dist/chunk-H7QUDDKH.js.map +1 -0
  114. package/dist/{chunk-I4QBZLOR.js → chunk-HG34RNTK.js} +7 -13
  115. package/dist/chunk-HG34RNTK.js.map +1 -0
  116. package/dist/{chunk-65EYRFZJ.js → chunk-HYDRDNKN.js} +10 -6
  117. package/dist/chunk-HYDRDNKN.js.map +1 -0
  118. package/dist/{chunk-4GNJELPH.js → chunk-I6NLLJHZ.js} +2 -2
  119. package/dist/{chunk-4GNJELPH.js.map → chunk-I6NLLJHZ.js.map} +1 -1
  120. package/dist/{chunk-VSQFGU5D.js → chunk-ILFGVSTJ.js} +3 -3
  121. package/dist/{chunk-VSQFGU5D.js.map → chunk-ILFGVSTJ.js.map} +1 -1
  122. package/dist/{chunk-2XFUXGV6.js → chunk-IONAUNWB.js} +3 -3
  123. package/dist/{chunk-2XFUXGV6.js.map → chunk-IONAUNWB.js.map} +1 -1
  124. package/dist/{chunk-CT6NQSA4.js → chunk-ISHJITXJ.js} +3 -3
  125. package/dist/{chunk-CT6NQSA4.js.map → chunk-ISHJITXJ.js.map} +1 -1
  126. package/dist/chunk-IZCR27MU.js +60 -0
  127. package/dist/chunk-IZCR27MU.js.map +1 -0
  128. package/dist/{chunk-2W4LBLIN.js → chunk-JAKOMFC6.js} +68 -20
  129. package/dist/chunk-JAKOMFC6.js.map +1 -0
  130. package/dist/{chunk-MFHKHXDE.js → chunk-L5VSIPQD.js} +5 -5
  131. package/dist/chunk-L5VSIPQD.js.map +1 -0
  132. package/dist/{chunk-RGXVQZOV.js → chunk-LEMHVDET.js} +89 -67
  133. package/dist/chunk-LEMHVDET.js.map +1 -0
  134. package/dist/{chunk-37FTLYFP.js → chunk-LNFKL23K.js} +3 -3
  135. package/dist/{chunk-37FTLYFP.js.map → chunk-LNFKL23K.js.map} +1 -1
  136. package/dist/{chunk-6NPINANG.js → chunk-LSXHED6I.js} +6 -6
  137. package/dist/{chunk-6NPINANG.js.map → chunk-LSXHED6I.js.map} +1 -1
  138. package/dist/{chunk-XUUYAS3Z.js → chunk-M3WPBTGJ.js} +3 -3
  139. package/dist/{chunk-XUUYAS3Z.js.map → chunk-M3WPBTGJ.js.map} +1 -1
  140. package/dist/{chunk-QZLWJETK.js → chunk-MGGFBZWA.js} +3 -3
  141. package/dist/{chunk-QZLWJETK.js.map → chunk-MGGFBZWA.js.map} +1 -1
  142. package/dist/{chunk-QD772CGQ.js → chunk-NPLPCRW5.js} +5 -5
  143. package/dist/{chunk-QD772CGQ.js.map → chunk-NPLPCRW5.js.map} +1 -1
  144. package/dist/{chunk-QCI6D7E4.js → chunk-NUELZWCF.js} +266 -170
  145. package/dist/chunk-NUELZWCF.js.map +1 -0
  146. package/dist/{chunk-GP6ZL7PE.js → chunk-OFBIWVCR.js} +3 -3
  147. package/dist/{chunk-GP6ZL7PE.js.map → chunk-OFBIWVCR.js.map} +1 -1
  148. package/dist/{chunk-D2V42LUE.js → chunk-P23M65LB.js} +93 -52
  149. package/dist/chunk-P23M65LB.js.map +1 -0
  150. package/dist/{chunk-46OVMT76.js → chunk-PMECEZK3.js} +16 -10
  151. package/dist/chunk-PMECEZK3.js.map +1 -0
  152. package/dist/{chunk-27HS2DHB.js → chunk-PTSGWKHR.js} +4 -4
  153. package/dist/{chunk-27HS2DHB.js.map → chunk-PTSGWKHR.js.map} +1 -1
  154. package/dist/{chunk-OKGUDNDT.js → chunk-Q6E6FM7P.js} +35 -33
  155. package/dist/chunk-Q6E6FM7P.js.map +1 -0
  156. package/dist/{chunk-PJS2YWGG.js → chunk-QOP6KDFR.js} +8 -9
  157. package/dist/chunk-QOP6KDFR.js.map +1 -0
  158. package/dist/{chunk-AO7VJ6G6.js → chunk-QVNRCDTV.js} +29 -8
  159. package/dist/chunk-QVNRCDTV.js.map +1 -0
  160. package/dist/{chunk-VBBBZDI7.js → chunk-S53LOR7B.js} +3 -3
  161. package/dist/{chunk-VBBBZDI7.js.map → chunk-S53LOR7B.js.map} +1 -1
  162. package/dist/{chunk-KUFNKVBM.js → chunk-SH5D7U7N.js} +20 -7
  163. package/dist/{chunk-KUFNKVBM.js.map → chunk-SH5D7U7N.js.map} +1 -1
  164. package/dist/{chunk-SX4UIDKT.js → chunk-UAROCOXF.js} +3 -3
  165. package/dist/{chunk-SX4UIDKT.js.map → chunk-UAROCOXF.js.map} +1 -1
  166. package/dist/{chunk-JMKB7IB4.js → chunk-UMVKJM4Q.js} +4 -4
  167. package/dist/chunk-UMVKJM4Q.js.map +1 -0
  168. package/dist/{chunk-HCYS7GFP.js → chunk-V2DXZYBB.js} +3 -3
  169. package/dist/{chunk-HCYS7GFP.js.map → chunk-V2DXZYBB.js.map} +1 -1
  170. package/dist/{chunk-KYXOGHY6.js → chunk-W5DWWE4Y.js} +4 -4
  171. package/dist/{chunk-KYXOGHY6.js.map → chunk-W5DWWE4Y.js.map} +1 -1
  172. package/dist/{chunk-FK6HPLQA.js → chunk-WTQ3TATB.js} +3 -3
  173. package/dist/{chunk-FK6HPLQA.js.map → chunk-WTQ3TATB.js.map} +1 -1
  174. package/dist/{chunk-OGLDAJFL.js → chunk-Y4N2NUZD.js} +4 -4
  175. package/dist/{chunk-OGLDAJFL.js.map → chunk-Y4N2NUZD.js.map} +1 -1
  176. package/dist/{chunk-XOGG7VDZ.js → chunk-Y7QA2OLD.js} +17 -25
  177. package/dist/chunk-Y7QA2OLD.js.map +1 -0
  178. package/dist/{chunk-2KOKBTR4.js → chunk-YSHVQGUS.js} +123 -28
  179. package/dist/chunk-YSHVQGUS.js.map +1 -0
  180. package/dist/chunk-ZAOGFXDO.js +112 -0
  181. package/dist/chunk-ZAOGFXDO.js.map +1 -0
  182. package/dist/{chunk-7KLC4LZO.js → chunk-ZKSJMZKD.js} +17 -13
  183. package/dist/chunk-ZKSJMZKD.js.map +1 -0
  184. package/dist/cli/index.js +1169 -672
  185. package/dist/cli/index.js.map +1 -1
  186. package/dist/cli/src/asset-feeds.d.ts +5 -2
  187. package/dist/cli/src/asset-feeds.d.ts.map +1 -1
  188. package/dist/cli/src/commands/asset-cmd.d.ts.map +1 -1
  189. package/dist/cli/src/commands/catalog.d.ts +27 -5
  190. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  191. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  192. package/dist/cli/src/commands/library-index.d.ts +49 -4
  193. package/dist/cli/src/commands/library-index.d.ts.map +1 -1
  194. package/dist/cli/src/commands/library-publish.d.ts +9 -0
  195. package/dist/cli/src/commands/library-publish.d.ts.map +1 -1
  196. package/dist/cli/src/commands/manage.d.ts +8 -2
  197. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  198. package/dist/cli/src/commands/outdated.d.ts +10 -4
  199. package/dist/cli/src/commands/outdated.d.ts.map +1 -1
  200. package/dist/cli/src/commands/project.d.ts +15 -4
  201. package/dist/cli/src/commands/project.d.ts.map +1 -1
  202. package/dist/cli/src/commands/publish-core.d.ts +10 -1
  203. package/dist/cli/src/commands/publish-core.d.ts.map +1 -1
  204. package/dist/cli/src/commands/session.d.ts.map +1 -1
  205. package/dist/cli/src/commands/source.d.ts +18 -3
  206. package/dist/cli/src/commands/source.d.ts.map +1 -1
  207. package/dist/cli/src/commands/tree.d.ts.map +1 -1
  208. package/dist/cli/src/commands/update.d.ts +1 -1
  209. package/dist/cli/src/commands/update.d.ts.map +1 -1
  210. package/dist/cli/src/commands/validate.d.ts.map +1 -1
  211. package/dist/cli/src/flow-discovery.d.ts +8 -5
  212. package/dist/cli/src/flow-discovery.d.ts.map +1 -1
  213. package/dist/cli/src/open-library.d.ts +0 -11
  214. package/dist/cli/src/open-library.d.ts.map +1 -1
  215. package/dist/cli/src/skill-checks.d.ts +30 -10
  216. package/dist/cli/src/skill-checks.d.ts.map +1 -1
  217. package/dist/connectors/config.js +6 -6
  218. package/dist/connectors/index.js +26 -27
  219. package/dist/connectors/src/access-policy.d.ts +33 -0
  220. package/dist/connectors/src/access-policy.d.ts.map +1 -0
  221. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  222. package/dist/connectors/src/index.d.ts +2 -0
  223. package/dist/connectors/src/index.d.ts.map +1 -1
  224. package/dist/connectors-O6Y6VESP.js +44 -0
  225. package/dist/{connectors-HWENZFKW.js.map → connectors-O6Y6VESP.js.map} +1 -1
  226. package/dist/connectors-shared/index.js +2 -2
  227. package/dist/core/index.js +9 -9
  228. package/dist/core/logging.js +1 -1
  229. package/dist/core/manifest.js +2 -2
  230. package/dist/core/models.js +1 -1
  231. package/dist/core/publish-manifest.js +2 -2
  232. package/dist/core/ref.js +1 -1
  233. package/dist/core/runtime-assets.js +6 -6
  234. package/dist/core/src/cache-paths.d.ts.map +1 -1
  235. package/dist/core/src/index.d.ts +4 -4
  236. package/dist/core/src/index.d.ts.map +1 -1
  237. package/dist/core/src/lock.d.ts +3 -3
  238. package/dist/core/src/lock.d.ts.map +1 -1
  239. package/dist/core/src/logging/config.d.ts +9 -1
  240. package/dist/core/src/logging/config.d.ts.map +1 -1
  241. package/dist/core/src/manifest.d.ts +17 -2
  242. package/dist/core/src/manifest.d.ts.map +1 -1
  243. package/dist/core/src/models.d.ts +22 -6
  244. package/dist/core/src/models.d.ts.map +1 -1
  245. package/dist/core/src/repo-manager.d.ts +41 -6
  246. package/dist/core/src/repo-manager.d.ts.map +1 -1
  247. package/dist/core/src/store.d.ts +16 -2
  248. package/dist/core/src/store.d.ts.map +1 -1
  249. package/dist/core/src/workspace-config.d.ts +10 -1
  250. package/dist/core/src/workspace-config.d.ts.map +1 -1
  251. package/dist/core/store.js +2 -2
  252. package/dist/core/workspace-config.js +5 -5
  253. package/dist/deploy/index.js +9 -9
  254. package/dist/discovery/index.js +5 -5
  255. package/dist/discovery/src/index.d.ts +1 -1
  256. package/dist/discovery/src/index.d.ts.map +1 -1
  257. package/dist/discovery/src/source-config.d.ts +12 -0
  258. package/dist/discovery/src/source-config.d.ts.map +1 -1
  259. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  260. package/dist/{ensure-sources-DSTSXJWK.js → ensure-sources-SKLNM4UZ.js} +19 -19
  261. package/dist/{ensure-sources-DSTSXJWK.js.map → ensure-sources-SKLNM4UZ.js.map} +1 -1
  262. package/dist/factory-assets/connectors/box.js +26 -27
  263. package/dist/factory-assets/connectors/deploy.js +26 -27
  264. package/dist/factory-assets/connectors/devserver.js +26 -27
  265. package/dist/factory-assets/connectors/flow/adapter.d.ts +6 -0
  266. package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
  267. package/dist/factory-assets/connectors/flow/adapter.js +26 -27
  268. package/dist/factory-assets/connectors/flow/contract/expression.d.ts +3 -0
  269. package/dist/factory-assets/connectors/flow/contract/expression.d.ts.map +1 -1
  270. package/dist/factory-assets/connectors/flow/contract/output-fields.d.ts +1 -1
  271. package/dist/factory-assets/connectors/flow/contract/output-fields.d.ts.map +1 -1
  272. package/dist/factory-assets/connectors/flow/contract/schema.d.ts.map +1 -1
  273. package/dist/factory-assets/connectors/flow/contract.js +4 -4
  274. package/dist/factory-assets/connectors/flow/engine/flow-output.d.ts +4 -3
  275. package/dist/factory-assets/connectors/flow/engine/flow-output.d.ts.map +1 -1
  276. package/dist/factory-assets/connectors/flow/engine/output-schema.d.ts +6 -1
  277. package/dist/factory-assets/connectors/flow/engine/output-schema.d.ts.map +1 -1
  278. package/dist/factory-assets/connectors/flow/engine.js +13 -13
  279. package/dist/factory-assets/connectors/flow/inline-sub-flow-host.d.ts.map +1 -1
  280. package/dist/factory-assets/connectors/flow/prompt-fragments.js +3 -3
  281. package/dist/factory-assets/connectors/flow/run-flow.d.ts.map +1 -1
  282. package/dist/factory-assets/connectors/flow/run-flow.js +28 -29
  283. package/dist/factory-assets/connectors/flow.js +26 -27
  284. package/dist/factory-assets/connectors/git/config.d.ts.map +1 -1
  285. package/dist/factory-assets/connectors/git.js +26 -27
  286. package/dist/factory-assets/connectors/gmail.js +26 -27
  287. package/dist/factory-assets/connectors/googledrive.js +26 -27
  288. package/dist/factory-assets/connectors/local.js +26 -27
  289. package/dist/factory-assets/connectors/mattermost.js +26 -27
  290. package/dist/factory-assets/connectors/memory.js +26 -27
  291. package/dist/factory-assets/connectors/minio.js +26 -27
  292. package/dist/factory-assets/connectors/postgres.js +26 -27
  293. package/dist/factory-assets/connectors/s3.js +26 -27
  294. package/dist/factory-assets/connectors/sharepoint.js +26 -27
  295. package/dist/factory-assets/connectors/sqlite.js +26 -27
  296. package/dist/factory-assets/connectors/static-server.js +26 -27
  297. package/dist/factory-assets/connectors/tunnel.js +26 -27
  298. package/dist/factory-assets/connectors/webdav.js +26 -27
  299. package/dist/factory-assets/connectors/xstate-store.js +26 -27
  300. package/dist/factory-assets/connectors/xstate.js +26 -27
  301. package/dist/factory-assets/skills/skaile-author-asset/SKILL.md +10 -7
  302. package/dist/flows-CBYRSAGS.js +26 -0
  303. package/dist/{flows-ENQY2DV7.js.map → flows-CBYRSAGS.js.map} +1 -1
  304. package/dist/{helpers-R44RUTDY.js → helpers-MWZDRBLD.js} +19 -19
  305. package/dist/{helpers-R44RUTDY.js.map → helpers-MWZDRBLD.js.map} +1 -1
  306. package/dist/library/index.js +13 -13
  307. package/dist/library/install.js +3 -3
  308. package/dist/library/src/index.d.ts +5 -3
  309. package/dist/library/src/index.d.ts.map +1 -1
  310. package/dist/library/src/local/library-manager.d.ts +6 -0
  311. package/dist/library/src/local/library-manager.d.ts.map +1 -1
  312. package/dist/library/src/local/local-catalog-source.d.ts +5 -2
  313. package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
  314. package/dist/library/src/local/scan-authoring-libraries.d.ts +43 -0
  315. package/dist/library/src/local/scan-authoring-libraries.d.ts.map +1 -0
  316. package/dist/library/src/manifest-writer.d.ts +46 -1
  317. package/dist/library/src/manifest-writer.d.ts.map +1 -1
  318. package/dist/library/src/sync/git-driver.d.ts +12 -0
  319. package/dist/library/src/sync/git-driver.d.ts.map +1 -1
  320. package/dist/{library-publish-LFOJJVMK.js → library-publish-PICMDSB2.js} +129 -49
  321. package/dist/library-publish-PICMDSB2.js.map +1 -0
  322. package/dist/open-library-OG457THY.js +40 -0
  323. package/dist/{open-library-4WUMQHES.js.map → open-library-OG457THY.js.map} +1 -1
  324. package/dist/open-registry-ZUZJ7MS7.js +32 -0
  325. package/dist/{open-registry-NVPCQPND.js.map → open-registry-ZUZJ7MS7.js.map} +1 -1
  326. package/dist/paths-FQ64OLJG.js +22 -0
  327. package/dist/{paths-YUCP7K6U.js.map → paths-FQ64OLJG.js.map} +1 -1
  328. package/dist/{plugin-store-66QFBNEO.js → plugin-store-MPAMD5OT.js} +11 -11
  329. package/dist/{plugin-store-66QFBNEO.js.map → plugin-store-MPAMD5OT.js.map} +1 -1
  330. package/dist/project-2GFT5LEZ.js +41 -0
  331. package/dist/{project-VMOHUXBD.js.map → project-2GFT5LEZ.js.map} +1 -1
  332. package/dist/resolver/index.js +10 -10
  333. package/dist/runner/index.js +41 -42
  334. package/dist/runner/src/logging-bootstrap.d.ts +6 -0
  335. package/dist/runner/src/logging-bootstrap.d.ts.map +1 -1
  336. package/dist/runner/src/reconfigure-handler.d.ts +38 -17
  337. package/dist/runner/src/reconfigure-handler.d.ts.map +1 -1
  338. package/dist/runner/src/serve.d.ts.map +1 -1
  339. package/dist/runner/src/session-capability-wiring.d.ts +0 -2
  340. package/dist/runner/src/session-capability-wiring.d.ts.map +1 -1
  341. package/dist/sdk/asset-manager.js +16 -16
  342. package/dist/sdk/bridge.js +17 -18
  343. package/dist/sdk/core.js +9 -9
  344. package/dist/sdk/flow.js +13 -13
  345. package/dist/sdk/index.js +69 -46
  346. package/dist/sdk/index.js.map +1 -1
  347. package/dist/sdk/resolver.js +10 -10
  348. package/dist/sdk/runner.js +41 -42
  349. package/dist/sdk/session.js +2 -2
  350. package/dist/sdk/src/agent-session.d.ts +8 -0
  351. package/dist/sdk/src/agent-session.d.ts.map +1 -1
  352. package/dist/sdk/store.js +2 -1
  353. package/dist/sdk/types.js +1 -1
  354. package/dist/secrets/index.js +2 -2
  355. package/dist/session/index.js +2 -2
  356. package/dist/{setup-PYY23HGZ.js → setup-WHMCSIF2.js} +27 -28
  357. package/dist/setup-WHMCSIF2.js.map +1 -0
  358. package/dist/skills-TVO26Y4V.js +26 -0
  359. package/dist/{skills-CS7NE5TT.js.map → skills-TVO26Y4V.js.map} +1 -1
  360. package/dist/source-LKOFTOEK.js +41 -0
  361. package/dist/{source-JCF6KSQN.js.map → source-LKOFTOEK.js.map} +1 -1
  362. package/dist/{source-manifest-publish-O6P7G2M7.js → source-manifest-publish-L5J3QXER.js} +28 -28
  363. package/dist/{source-manifest-publish-O6P7G2M7.js.map → source-manifest-publish-L5J3QXER.js.map} +1 -1
  364. package/dist/store/index.js +2 -1
  365. package/dist/store/src/store.d.ts +8 -5
  366. package/dist/store/src/store.d.ts.map +1 -1
  367. package/dist/tui/index.js +41 -42
  368. package/dist/tui/index.js.map +1 -1
  369. package/dist/types/index.js +1 -1
  370. package/dist/types/manifests.js +1 -1
  371. package/dist/types/src/ask-slots.d.ts +68 -0
  372. package/dist/types/src/ask-slots.d.ts.map +1 -0
  373. package/dist/types/src/delivery.d.ts.map +1 -1
  374. package/dist/types/src/events.d.ts +29 -1
  375. package/dist/types/src/events.d.ts.map +1 -1
  376. package/dist/types/src/index.d.ts +1 -1
  377. package/dist/types/src/index.d.ts.map +1 -1
  378. package/dist/types/src/manifests/index.d.ts +1 -1
  379. package/dist/types/src/manifests/index.d.ts.map +1 -1
  380. package/dist/types/src/manifests/preset.d.ts +4 -0
  381. package/dist/types/src/manifests/preset.d.ts.map +1 -1
  382. package/dist/types/src/version.d.ts +12 -1
  383. package/dist/types/src/version.d.ts.map +1 -1
  384. package/dist/workspace-plugin/adapters/mcp.js +33 -34
  385. package/dist/workspace-plugin/adapters/omp.js +14 -14
  386. package/dist/workspace-plugin/index.js +2 -2
  387. package/package.json +1 -1
  388. package/dist/asset-feeds-LU7NHOAP.js.map +0 -1
  389. package/dist/chunk-2KOKBTR4.js.map +0 -1
  390. package/dist/chunk-2NYXQYHN.js +0 -22
  391. package/dist/chunk-2NYXQYHN.js.map +0 -1
  392. package/dist/chunk-2W4LBLIN.js.map +0 -1
  393. package/dist/chunk-46OVMT76.js.map +0 -1
  394. package/dist/chunk-5B5IZVEP.js.map +0 -1
  395. package/dist/chunk-65EYRFZJ.js.map +0 -1
  396. package/dist/chunk-7KLC4LZO.js.map +0 -1
  397. package/dist/chunk-AO7VJ6G6.js.map +0 -1
  398. package/dist/chunk-AOAZCSDI.js.map +0 -1
  399. package/dist/chunk-D2V42LUE.js.map +0 -1
  400. package/dist/chunk-DMLDYFDS.js.map +0 -1
  401. package/dist/chunk-EINLFFP6.js +0 -59
  402. package/dist/chunk-EINLFFP6.js.map +0 -1
  403. package/dist/chunk-FRHE7FFB.js.map +0 -1
  404. package/dist/chunk-I4QBZLOR.js.map +0 -1
  405. package/dist/chunk-JMKB7IB4.js.map +0 -1
  406. package/dist/chunk-MFHKHXDE.js.map +0 -1
  407. package/dist/chunk-MX3UQXLU.js.map +0 -1
  408. package/dist/chunk-OKGUDNDT.js.map +0 -1
  409. package/dist/chunk-PA7WLID3.js.map +0 -1
  410. package/dist/chunk-PJS2YWGG.js.map +0 -1
  411. package/dist/chunk-PTO2YDDG.js.map +0 -1
  412. package/dist/chunk-QCI6D7E4.js.map +0 -1
  413. package/dist/chunk-RGXVQZOV.js.map +0 -1
  414. package/dist/chunk-TZCQ7ZWQ.js.map +0 -1
  415. package/dist/chunk-UAP2XHRF.js.map +0 -1
  416. package/dist/chunk-WIHQWVWL.js.map +0 -1
  417. package/dist/chunk-XBSQ4KMC.js.map +0 -1
  418. package/dist/chunk-XOGG7VDZ.js.map +0 -1
  419. package/dist/connectors-HWENZFKW.js +0 -45
  420. package/dist/flows-ENQY2DV7.js +0 -26
  421. package/dist/library-publish-LFOJJVMK.js.map +0 -1
  422. package/dist/open-library-4WUMQHES.js +0 -40
  423. package/dist/open-registry-NVPCQPND.js +0 -32
  424. package/dist/paths-YUCP7K6U.js +0 -22
  425. package/dist/project-VMOHUXBD.js +0 -41
  426. package/dist/setup-PYY23HGZ.js.map +0 -1
  427. package/dist/skills-CS7NE5TT.js +0 -26
  428. package/dist/source-JCF6KSQN.js +0 -41
@@ -1,20 +1,20 @@
1
- import { getStoreManifestsDir } from './chunk-MX3UQXLU.js';
2
- import { readManifestSourceConfig, discoverAssetsInTree } from './chunk-XOGG7VDZ.js';
3
- import { installAgent, installTargetOf, deployAll, removeAsset, canonicalEntryRef, createScaffold } from './chunk-2KOKBTR4.js';
1
+ import { getStoreManifestsDir } from './chunk-4MQS7CAF.js';
2
+ import { readManifestSourceConfig, isManifestMode, discoverAssetsInTree } from './chunk-Y7QA2OLD.js';
3
+ import { installAgent, installTargetOf, canonicalEntryRef, deployAll, removeAsset, createScaffold } from './chunk-YSHVQGUS.js';
4
4
  import { appendHistory, getRecentHistory, clearHistory } from './chunk-KTBKW2FI.js';
5
5
  import { commitChanges } from './chunk-UQ6LFBPZ.js';
6
- import { driverTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-VKSXST23.js';
7
- import { resolveConfiguredDriverTarget, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, writeLock, buildLockFile, readLock, verifyLock, mergeLockFile, extractForPatch, generatePatch, savePatch, applyPatch } from './chunk-TZCQ7ZWQ.js';
8
- import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-BF33SCLY.js';
9
- import { deployedBase, deployedDir, isDeployed } from './chunk-46OVMT76.js';
10
- import { resolveSkWorkspaceConfig, effectiveAgentInstances, resolveAgentDefinitionDir, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, coerceVersion, resolveAll, buildProvenanceIndex, ensureRepo, sourceShortName, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, resolveAsset, readLinks, storeManifestSlug } from './chunk-2W4LBLIN.js';
11
- import { globalSkaileDir, globalCacheRoot, migrateLegacyDir } from './chunk-MFHKHXDE.js';
6
+ import { driverTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-EF7NPDDU.js';
7
+ import { resolveConfiguredDriverTarget, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, buildLockFile, writeLock, readLock, verifyLock, mergeLockFile, extractForPatch, generatePatch, savePatch, applyPatch } from './chunk-36AFURKZ.js';
8
+ import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-GB7WDMH2.js';
9
+ import { deployedBase, deployedDir, isDeployed, deployedPaths } from './chunk-PMECEZK3.js';
10
+ import { resolveSkWorkspaceConfig, effectiveAgentInstances, resolveAgentDefinitionDir, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, resolveAll, buildProvenanceIndex, ensureRepo, sourceShortName, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, coerceVersion, resolveAsset, readLinks, MAX_PRESET_DEPTH, PresetDepthError, storeManifestSlug } from './chunk-JAKOMFC6.js';
11
+ import { globalSkaileDir, globalCacheRoot, migrateLegacyDir } from './chunk-L5VSIPQD.js';
12
12
  import { DRIVER_TARGETS } from './chunk-CPSIPIX7.js';
13
- import { scanDirectory, parseRequires } from './chunk-PA7WLID3.js';
14
- import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName } from './chunk-AO7VJ6G6.js';
13
+ import { scanDirectory, parseRequires } from './chunk-GTILM2CI.js';
14
+ import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr } from './chunk-QVNRCDTV.js';
15
15
  import { spawnSync } from 'child_process';
16
16
  import { existsSync, rmSync, readdirSync, lstatSync, unlinkSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
17
- import path, { join, resolve, dirname, sep, posix } from 'path';
17
+ import path, { join, resolve, dirname, sep, relative, posix } from 'path';
18
18
  import { gt } from 'semver';
19
19
  import { stringify, parse } from 'yaml';
20
20
  import fs from 'fs/promises';
@@ -336,6 +336,25 @@ function indexLockByKindName(lock) {
336
336
  }
337
337
  return lockByKindName;
338
338
  }
339
+ function commitsBetween(repoDir, from, to, cache) {
340
+ const key = `${from}..${to}`;
341
+ const hit = cache.get(key);
342
+ if (hit !== void 0) return hit;
343
+ let count = 1;
344
+ if (COMMIT_ID.test(from) && COMMIT_ID.test(to)) {
345
+ const r = spawnSync("git", ["-C", repoDir, "rev-list", "--count", key], {
346
+ encoding: "utf8",
347
+ stdio: "pipe"
348
+ });
349
+ if (r.status === 0) {
350
+ const n = Number.parseInt(r.stdout.trim(), 10);
351
+ if (Number.isFinite(n) && n >= 0) count = n;
352
+ }
353
+ }
354
+ cache.set(key, count);
355
+ return count;
356
+ }
357
+ var COMMIT_ID = /^[0-9a-f]{7,64}$/;
339
358
  function libraryDiskHashes(libPath, cache, registry) {
340
359
  let hashes = cache.get(libPath);
341
360
  if (!hashes) {
@@ -356,6 +375,26 @@ function libraryDiskHashes(libPath, cache, registry) {
356
375
  }
357
376
  return hashes;
358
377
  }
378
+ function withLibraryFiles(entry, libPath, cache, registry) {
379
+ let byManifest = cache.get(libPath);
380
+ if (!byManifest) {
381
+ byManifest = /* @__PURE__ */ new Map();
382
+ const sourceConfig = readManifestSourceConfig(libPath, "library");
383
+ if (sourceConfig && isManifestMode(sourceConfig)) {
384
+ for (const a of discoverAssetsInTree(libPath, { sourceConfig, registry }).assets) {
385
+ const { assetRoot, files: files2 } = a.fileFilter;
386
+ byManifest.set(
387
+ resolve(a.manifestPath),
388
+ filesUnderManifestDir(files2, relative(assetRoot, a.manifestPath).split(sep).join("/"))
389
+ );
390
+ }
391
+ }
392
+ cache.set(libPath, byManifest);
393
+ }
394
+ const files = byManifest.get(resolve(entry.source));
395
+ if (files) entry.files = files;
396
+ return entry;
397
+ }
359
398
  function newestLibraryDef(snap, libraryName, id) {
360
399
  const lib = snap?.libraries.find((l) => l.name === libraryName);
361
400
  if (!snap || !lib) return void 0;
@@ -367,6 +406,50 @@ function newestLibraryDef(snap, libraryName, id) {
367
406
  }
368
407
  return best;
369
408
  }
409
+ function libraryPreference(isDefaultLib) {
410
+ return (a, b) => {
411
+ const va = coerceVersion(a.entry.version ?? "");
412
+ const vb = coerceVersion(b.entry.version ?? "");
413
+ if (gt(va, vb)) return -1;
414
+ if (gt(vb, va)) return 1;
415
+ const da = isDefaultLib.get(a.library) ? 0 : 1;
416
+ const db = isDefaultLib.get(b.library) ? 0 : 1;
417
+ return da - db || a.library.localeCompare(b.library);
418
+ };
419
+ }
420
+ function qualifyDepRef(ref, fallback) {
421
+ const hash = ref.indexOf("#");
422
+ const head = hash === -1 ? ref : ref.slice(0, hash);
423
+ if (head.includes("@") || !fallback) return ref;
424
+ const pin = hash === -1 ? void 0 : ref.slice(hash + 1) || void 0;
425
+ const colon = head.indexOf(":");
426
+ return assetRefToStr({
427
+ kind: colon === -1 ? "skill" : head.slice(0, colon),
428
+ name: colon === -1 ? head : head.slice(colon + 1),
429
+ publisher: fallback,
430
+ pin
431
+ });
432
+ }
433
+ function libraryEntryDepRefs(e) {
434
+ const publisher = e.publisher ?? "";
435
+ const refs = /* @__PURE__ */ new Set();
436
+ for (const d of e.dependencies ?? []) {
437
+ if (d) refs.add(qualifyDepRef(d, publisher));
438
+ }
439
+ for (const r of e.requires ?? []) {
440
+ const p = r.publisher ?? publisher;
441
+ if (p) refs.add(assetRefToStr({ kind: r.kind, name: r.name, publisher: p, pin: r.pin }));
442
+ }
443
+ return [...refs];
444
+ }
445
+ function refIdentity(ref) {
446
+ try {
447
+ const p = parseAssetRef(ref);
448
+ return p.publisher ? `${p.publisher}/${p.kind}:${p.name}` : null;
449
+ } catch {
450
+ return null;
451
+ }
452
+ }
370
453
  function computeSyncStatus(repoStatus) {
371
454
  if (!repoStatus) return "unknown";
372
455
  if (repoStatus.kind === "local") return "local";
@@ -437,6 +520,56 @@ function parseRefTolerant(ref) {
437
520
  return { kind, name, publisher: void 0 };
438
521
  }
439
522
  }
523
+ function keptLockEntries(prev, missing) {
524
+ const out = { assets: {}, sources: [], refs: /* @__PURE__ */ new Set() };
525
+ if (!prev) return out;
526
+ for (const ref of missing) {
527
+ const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
528
+ if (!want) continue;
529
+ for (const [key, entry] of Object.entries(prev.assets)) {
530
+ if (entry.library?.installed) continue;
531
+ const id = parseCanonicalLockKey(key);
532
+ if (id.kind !== want.kind || id.name !== want.name) continue;
533
+ if (want.publisher && id.publisher !== want.publisher) continue;
534
+ out.assets[key] = entry;
535
+ out.refs.add(ref);
536
+ const src = entry.source ? prev.sources.find((s) => s.url === entry.source?.url) : void 0;
537
+ if (src) out.sources.push(src);
538
+ }
539
+ }
540
+ return out;
541
+ }
542
+ function withKeptLockEntries(lock, kept) {
543
+ if (Object.keys(kept.assets).length === 0) return lock;
544
+ const sources = new Map(lock.sources.map((s) => [s.url, s]));
545
+ for (const s of kept.sources) if (!sources.has(s.url)) sources.set(s.url, s);
546
+ return {
547
+ ...lock,
548
+ // v4 once a library-origin entry is present (see buildLockFile).
549
+ schema_version: lock.schema_version === 4 || Object.values(kept.assets).some((e) => e.library) ? 4 : 3,
550
+ assets: { ...lock.assets, ...kept.assets },
551
+ sources: [...sources.values()].sort((a, b) => a.url.localeCompare(b.url))
552
+ };
553
+ }
554
+ function relockedRefs(prev, next) {
555
+ if (!prev) return [];
556
+ const fingerprint = (e) => [e.library?.version ?? "", e.library?.sha256 ?? e.sha256, e.source?.commit ?? ""].join("\0");
557
+ const out = [];
558
+ for (const [key, entry] of Object.entries(next.assets)) {
559
+ if (entry.library?.installed) continue;
560
+ const id = parseCanonicalLockKey(key);
561
+ const before = prev.assets[key] ? [key, prev.assets[key]] : Object.entries(prev.assets).find(([k]) => {
562
+ const o = parseCanonicalLockKey(k);
563
+ return o.kind === id.kind && o.name === id.name && o.publisher === id.publisher;
564
+ });
565
+ if (!before) continue;
566
+ if (before[0] !== key || fingerprint(before[1]) !== fingerprint(entry)) out.push(key);
567
+ }
568
+ return out;
569
+ }
570
+ function unregisteredLibraryFix(library, ref) {
571
+ return `register library "${library}" again with \`skaile library init ${library} --path <dir>\`, or drop the dependency with \`skaile remove ${ref}\``;
572
+ }
440
573
  function memberIdentity(ref) {
441
574
  const parsed = parseRefTolerant(ref.trim());
442
575
  if (!parsed) return null;
@@ -517,6 +650,8 @@ var AssetManager = class {
517
650
  indexPathOverride;
518
651
  /** Sink for cross-library collision warnings; else `console.warn`. */
519
652
  onLibraryCollision;
653
+ /** Store URLs whose failed version lookup was already reported (one warning per URL). */
654
+ warnedStoreVersionLookups = /* @__PURE__ */ new Set();
520
655
  constructor(opts) {
521
656
  this.projectDir = resolve(opts.projectDir);
522
657
  this.global = opts.global ?? false;
@@ -765,13 +900,55 @@ var AssetManager = class {
765
900
  * content. The returned `libraryOf` map names the library each entry came from
766
901
  * so callers can lock it as library-origin (see {@link libraryLockEntries}).
767
902
  *
903
+ * @param opts.quiet - Skip the cross-library collision warning (a lookup that only
904
+ * builds an error hint must not warn).
768
905
  * @returns the matched deploy entries, the set of `missing` refs covered, and
769
906
  * each entry's originating library name.
770
907
  */
771
- async resolveLibraryEntries(missing) {
908
+ async resolveLibraryEntries(missing, opts = {}) {
772
909
  const resolved = /* @__PURE__ */ new Set();
773
910
  const libraryOf = /* @__PURE__ */ new Map();
774
911
  if (missing.length === 0) return { entries: [], resolved, libraryOf };
912
+ const scan = await this.scanLibraries();
913
+ if (!scan) return { entries: [], resolved, libraryOf };
914
+ const { byKey, isDefaultLib, libraryDir } = scan;
915
+ const entries = [];
916
+ const fileSets = /* @__PURE__ */ new Map();
917
+ const allCandidates = [...byKey.values()].flat();
918
+ for (const ref of missing) {
919
+ const parsed = parseRefTolerant(ref);
920
+ if (!parsed) continue;
921
+ const hash = ref.indexOf("#");
922
+ const pin = parsed.pin ?? (hash >= 0 ? ref.slice(hash + 1) : void 0);
923
+ const key = parsed.publisher ? `${parsed.kind}:@${parsed.publisher}/${parsed.name}` : void 0;
924
+ const pool = key ? byKey.get(key) ?? [] : allCandidates.filter((c) => c.entry.kind === parsed.kind && c.entry.name === parsed.name);
925
+ const candidates = pool.filter((c) => !pin || matchPin(pin, c.entry.version ?? ""));
926
+ if (candidates.length === 0) continue;
927
+ candidates.sort(libraryPreference(isDefaultLib));
928
+ const hit = candidates[0];
929
+ if (!opts.quiet) {
930
+ this.warnLibraryCollision(
931
+ `${hit.entry.kind}:@${hit.entry.publisher}/${hit.entry.name}`,
932
+ candidates,
933
+ hit
934
+ );
935
+ }
936
+ withLibraryFiles(hit.entry, libraryDir.get(hit.library), fileSets, this.registry);
937
+ entries.push(hit.entry);
938
+ libraryOf.set(hit.entry, hit.library);
939
+ resolved.add(ref);
940
+ }
941
+ return { entries, resolved, libraryOf };
942
+ }
943
+ /**
944
+ * Scan every candidate library (the set {@link resolveLibraryEntries} resolves from)
945
+ * into `kind:@publisher/name` → every holder, across all libraries and versions.
946
+ *
947
+ * @param minRoots - Return `null` without scanning when fewer candidate libraries
948
+ * exist (a cross-library question needs at least two).
949
+ * @returns `null` when there is no candidate library or none holds an asset.
950
+ */
951
+ async scanLibraries(minRoots = 1) {
775
952
  const roots = [];
776
953
  for (const l of (await this.loadLibraryIndex())?.libraries ?? []) {
777
954
  if (l.name === GLOBAL_INSTALL_DIR_NAME) continue;
@@ -793,8 +970,9 @@ var AssetManager = class {
793
970
  }
794
971
  } catch {
795
972
  }
796
- if (roots.length === 0) return { entries: [], resolved, libraryOf };
973
+ if (roots.length === 0 || roots.length < minRoots) return null;
797
974
  const byKey = /* @__PURE__ */ new Map();
975
+ const libraryDir = new Map(roots.map((r) => [r.name, r.dir]));
798
976
  const isDefaultLib = new Map(roots.map((r) => [r.name, r.isDefault]));
799
977
  for (const { name, dir } of roots) {
800
978
  const publisher = readDeclaredPublisher(dir) ?? slugifyAssetName(name);
@@ -805,37 +983,142 @@ var AssetManager = class {
805
983
  else byKey.set(key, [{ entry, library: name }]);
806
984
  }
807
985
  }
808
- if (byKey.size === 0) return { entries: [], resolved, libraryOf };
809
- const entries = [];
810
- for (const ref of missing) {
811
- let parsed;
812
- try {
813
- parsed = parseAssetRef(ref);
814
- } catch {
815
- continue;
816
- }
817
- if (!parsed.publisher) continue;
818
- const key = `${parsed.kind}:@${parsed.publisher}/${parsed.name}`;
819
- const candidates = (byKey.get(key) ?? []).filter(
820
- (c) => !parsed.pin || matchPin(parsed.pin, c.entry.version ?? "")
986
+ return byKey.size === 0 ? null : { byKey, isDefaultLib, libraryDir };
987
+ }
988
+ /**
989
+ * Every ref that more than one registered library holds, with one row per holder
990
+ * (all libraries, all versions) in the order `add` prefers them: the first row is
991
+ * the one `add <ref>` deploys. The local index keeps a single row per ref and
992
+ * version, so `search` reads this to show equal-version copies in both libraries.
993
+ *
994
+ * @returns `kind:@publisher/name` → holders; refs held by one library are absent.
995
+ */
996
+ async libraryHolders() {
997
+ const out = /* @__PURE__ */ new Map();
998
+ const scan = await this.scanLibraries(2);
999
+ if (!scan) return out;
1000
+ for (const [key, candidates] of scan.byKey) {
1001
+ if (new Set(candidates.map((c) => c.library)).size < 2) continue;
1002
+ const sorted = [...candidates].sort(libraryPreference(scan.isDefaultLib));
1003
+ out.set(
1004
+ key,
1005
+ sorted.map((c, i) => ({
1006
+ library: c.library,
1007
+ version: c.entry.version ?? "",
1008
+ description: c.entry.description ?? "",
1009
+ picked: i === 0
1010
+ }))
821
1011
  );
822
- if (candidates.length === 0) continue;
823
- candidates.sort((a, b) => {
824
- const va = coerceVersion(a.entry.version ?? "");
825
- const vb = coerceVersion(b.entry.version ?? "");
826
- if (gt(va, vb)) return -1;
827
- if (gt(vb, va)) return 1;
828
- const da = isDefaultLib.get(a.library) ? 0 : 1;
829
- const db = isDefaultLib.get(b.library) ? 0 : 1;
830
- return da - db || a.library.localeCompare(b.library);
831
- });
832
- const hit = candidates[0];
833
- this.warnLibraryCollision(key, candidates, hit);
834
- entries.push(hit.entry);
835
- libraryOf.set(hit.entry, hit.library);
836
- resolved.add(ref);
837
1012
  }
838
- return { entries, resolved, libraryOf };
1013
+ return out;
1014
+ }
1015
+ /**
1016
+ * Where each missed publisher-qualified ref went when its publisher changed, for every
1017
+ * ref at once (one library scan, one lock read):
1018
+ * - `moved` when a registered library holds the same kind + name under another publisher;
1019
+ * - `lockedAs` when the lock holds the same kind + name under another publisher, records
1020
+ * the library it came from, and that library now holds the asset under yet another
1021
+ * publisher than the locked one. An unrelated asset that merely shares kind + name does
1022
+ * not qualify.
1023
+ *
1024
+ * @param refs - Missed refs; a trailing `(reason)` after the first space is ignored.
1025
+ * @returns one finding per ref that has one. Never warns.
1026
+ */
1027
+ async publisherChangeFindings(refs) {
1028
+ const out = /* @__PURE__ */ new Map();
1029
+ const wanted = /* @__PURE__ */ new Map();
1030
+ for (const ref of refs) {
1031
+ const parsed = parseRefTolerant(ref.split(" ")[0] ?? ref);
1032
+ if (parsed?.publisher) {
1033
+ wanted.set(ref, { kind: parsed.kind, name: parsed.name, publisher: parsed.publisher });
1034
+ }
1035
+ }
1036
+ if (wanted.size === 0) return out;
1037
+ const lockEntries = Object.entries(this.tryReadLock()?.assets ?? {});
1038
+ const bare = [...new Set([...wanted.values()].map((w) => `${w.kind}:${w.name}`))];
1039
+ const lib = await this.resolveLibraryEntries(bare, { quiet: true });
1040
+ for (const [ref, w] of wanted) {
1041
+ const moved = lib.entries.find((e) => e.kind === w.kind && e.name === w.name);
1042
+ const movedLibrary = moved ? lib.libraryOf.get(moved) : void 0;
1043
+ const finding = {};
1044
+ for (const [key, entry] of lockEntries) {
1045
+ const id = parseCanonicalLockKey(key);
1046
+ if (id.kind === w.kind && id.name === w.name && id.publisher && id.publisher !== w.publisher && entry.library && moved && movedLibrary === entry.library.name && moved.publisher !== id.publisher) {
1047
+ finding.lockedAs = `${id.kind}:@${id.publisher}/${id.name}`;
1048
+ break;
1049
+ }
1050
+ }
1051
+ if (moved && moved.publisher !== w.publisher) {
1052
+ finding.moved = {
1053
+ ref: `${moved.kind}:@${moved.publisher}/${moved.name}`,
1054
+ ...movedLibrary ? { library: movedLibrary } : {}
1055
+ };
1056
+ }
1057
+ if (finding.lockedAs || finding.moved) out.set(ref, finding);
1058
+ }
1059
+ return out;
1060
+ }
1061
+ /**
1062
+ * Explain misses caused by a publisher change, as parenthesized hint text per ref:
1063
+ * `did you mean <ref>?` when a registered library holds the same kind + name under
1064
+ * another publisher, and `locked as <lockRef>; run skaile install to re-resolve` when
1065
+ * the lock holds it under the library's previous publisher (see
1066
+ * {@link AssetManager.publisherChangeFindings}).
1067
+ *
1068
+ * @param refs - Missed refs; a trailing `(reason)` after the first space is ignored.
1069
+ * @returns hint text per ref, only for refs that have one. Never warns.
1070
+ */
1071
+ async publisherChangeHints(refs) {
1072
+ const out = {};
1073
+ for (const [ref, f] of await this.publisherChangeFindings(refs)) {
1074
+ const hints = [];
1075
+ if (f.lockedAs) hints.push(`locked as ${f.lockedAs}; run skaile install to re-resolve`);
1076
+ if (f.moved) hints.push(`did you mean ${f.moved.ref}?`);
1077
+ out[ref] = hints.map((h) => ` (${h})`).join("");
1078
+ }
1079
+ return out;
1080
+ }
1081
+ /**
1082
+ * What the user has to change for each ref that is locked from a library but resolves
1083
+ * nowhere any more, where `skaile install` cannot help: the library is not registered
1084
+ * (renamed away or removed), the library now publishes the asset under another
1085
+ * publisher, or the library no longer holds it. A ref the lock does not tie to a
1086
+ * library has no entry.
1087
+ *
1088
+ * @param refs - Declared or locked refs, `kind:@publisher/name` (a trailing `(reason)`
1089
+ * after the first space is ignored).
1090
+ * @returns a phrase per ref that follows the ref name, without parentheses.
1091
+ */
1092
+ async unresolvedFixes(refs) {
1093
+ const out = /* @__PURE__ */ new Map();
1094
+ if (refs.length === 0) return out;
1095
+ const lockEntries = Object.entries(this.tryReadLock()?.assets ?? {});
1096
+ const snap = await this.loadLibraryIndex();
1097
+ const findings = await this.publisherChangeFindings(refs);
1098
+ for (const ref of refs) {
1099
+ const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
1100
+ if (!want) continue;
1101
+ const origin = lockEntries.map(([key, e]) => ({ id: parseCanonicalLockKey(key), library: e.library })).find(
1102
+ ({ id, library }) => library && !library.installed && id.kind === want.kind && id.name === want.name && (!want.publisher || id.publisher === want.publisher)
1103
+ )?.library;
1104
+ if (!origin) continue;
1105
+ const plain = want.publisher ? `${want.kind}:@${want.publisher}/${want.name}` : `${want.kind}:${want.name}`;
1106
+ const moved = findings.get(ref)?.moved;
1107
+ if (moved?.library && want.publisher) {
1108
+ out.set(
1109
+ ref,
1110
+ `did you mean ${moved.ref}? Change the ref in skaile.yaml to it, or keep the old ref with \`skaile library set ${moved.library} publisher=${want.publisher}\``
1111
+ );
1112
+ } else if (!this.libraryPath(origin.name, snap)) {
1113
+ out.set(ref, unregisteredLibraryFix(origin.name, plain));
1114
+ } else {
1115
+ out.set(
1116
+ ref,
1117
+ `library "${origin.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${plain}\``
1118
+ );
1119
+ }
1120
+ }
1121
+ return out;
839
1122
  }
840
1123
  /** Collision messages already printed by this instance (one warning per ref + library set). */
841
1124
  warnedLibraryCollisions = /* @__PURE__ */ new Set();
@@ -853,6 +1136,56 @@ var AssetManager = class {
853
1136
  if (this.onLibraryCollision) this.onLibraryCollision(msg);
854
1137
  else console.warn(`skaile: ${msg}`);
855
1138
  }
1139
+ /** Kind, name and bare publisher of a deploy entry; the publisher comes from its canonical ref. */
1140
+ entryIdentity(entry) {
1141
+ const ref = parseAssetRefLoose(canonicalEntryRef(entry));
1142
+ return {
1143
+ kind: entry.kind,
1144
+ name: entry.name,
1145
+ publisher: ref?.publisher ?? entry.publisher?.replace(/^@/, "")
1146
+ };
1147
+ }
1148
+ /**
1149
+ * Refuse assets of one kind and name from different publishers: they deploy into the
1150
+ * same folder, so the later one would silently overwrite the earlier. Throws one line
1151
+ * naming the folder and the asset that holds it.
1152
+ *
1153
+ * @param incoming - Assets about to be deployed. Two of them clashing is refused.
1154
+ * @param held - Assets the project already declares or has locked; one of them
1155
+ * clashing with an incoming asset is refused when its folder is on disk.
1156
+ */
1157
+ assertNoFolderCollision(incoming, held = []) {
1158
+ const refOf = (a) => assetRefToStr({ kind: a.kind, name: a.name, publisher: a.publisher });
1159
+ const differs = (a, b) => !!a.publisher && !!b.publisher && a.publisher !== b.publisher;
1160
+ const shown = (dir) => this.global ? dir : relative(this.projectDir, dir);
1161
+ const seen = /* @__PURE__ */ new Map();
1162
+ for (const a of incoming) {
1163
+ if (!a.kind || !a.name) continue;
1164
+ const component = installTargetOf(this.registry, a.kind);
1165
+ if (!component) continue;
1166
+ const slot = `${component}/${a.name}`;
1167
+ const prior = seen.get(slot);
1168
+ if (prior && differs(prior, a)) {
1169
+ const dir2 = deployedDir(component, a.name, this.driverTarget, this.global, this.projectDir);
1170
+ throw new Error(
1171
+ `${refOf(prior)} and ${refOf(a)} both deploy to ${shown(dir2)} \u2014 keep only one of them`
1172
+ );
1173
+ }
1174
+ if (!prior) seen.set(slot, a);
1175
+ const holder = held.find(
1176
+ (h) => !!h.kind && h.name === a.name && installTargetOf(this.registry, h.kind) === component && differs(h, a)
1177
+ );
1178
+ if (!holder) continue;
1179
+ const target = this.targetHarnesses().find(
1180
+ (t) => isDeployed(component, a.name, t, this.global, this.projectDir)
1181
+ );
1182
+ if (!target) continue;
1183
+ const dir = deployedDir(component, a.name, target, this.global, this.projectDir);
1184
+ throw new Error(
1185
+ `${refOf(a)} would overwrite ${shown(dir)}, which ${refOf(holder)} already holds \u2014 run skaile remove ${refOf(holder)} first`
1186
+ );
1187
+ }
1188
+ }
856
1189
  /**
857
1190
  * Open the local index and snapshot its libraries + asset definitions.
858
1191
  * Best-effort: no index file (never indexed), or any open/guard failure,
@@ -875,10 +1208,12 @@ var AssetManager = class {
875
1208
  }
876
1209
  /**
877
1210
  * Library-origin lock entries for the assets {@link resolveLibraryEntries}
878
- * matched. The locked version comes from the library index when it knows the
879
- * asset (its manifest-declared version is authoritative), else from the
880
- * on-disk asset. The hash is computed from the files on disk, so it matches
881
- * what `install --locked` re-hashes.
1211
+ * matched. The locked version is the one actually deployed: the on-disk
1212
+ * entry's version, falling back to the library index only when the disk entry
1213
+ * carries none — the index lags until the next `skaile library index`, so
1214
+ * preferring it would lock a stale version after an in-place bump. The hash is
1215
+ * computed from the files on disk, so it matches what `install --locked`
1216
+ * re-hashes.
882
1217
  */
883
1218
  async libraryLockEntries(lib) {
884
1219
  if (lib.entries.length === 0) return {};
@@ -895,7 +1230,7 @@ var AssetManager = class {
895
1230
  kind: e.kind,
896
1231
  publisher,
897
1232
  name: e.name,
898
- library: { name, version: def?.version || e.version || "0.0.0", sha256 }
1233
+ library: { name, version: e.version || def?.version || "0.0.0", sha256 }
899
1234
  };
900
1235
  })
901
1236
  );
@@ -915,6 +1250,7 @@ var AssetManager = class {
915
1250
  if (locked.length === 0) return { entries, missing };
916
1251
  const snap = await this.loadLibraryIndex();
917
1252
  const diskHashes = /* @__PURE__ */ new Map();
1253
+ const fileSets = /* @__PURE__ */ new Map();
918
1254
  for (const [ref, e] of locked) {
919
1255
  const origin = e.library;
920
1256
  const { publisher, kind, name } = parseCanonicalLockKey(ref);
@@ -925,7 +1261,7 @@ var AssetManager = class {
925
1261
  const libPath = this.libraryPath(origin.name, snap);
926
1262
  if (!libPath) {
927
1263
  missing.push(
928
- `${ref} (library "${origin.name}" is not registered \u2014 renamed or removed? re-lock with \`skaile install\`)`
1264
+ `${ref} (library "${origin.name}" is not registered \u2014 renamed or removed? ${unregisteredLibraryFix(origin.name, `${kind}:@${publisher}/${name}`)}; \`skaile install\` re-locks it when another library holds it now)`
929
1265
  );
930
1266
  continue;
931
1267
  }
@@ -937,7 +1273,7 @@ var AssetManager = class {
937
1273
  continue;
938
1274
  }
939
1275
  const def = newestLibraryDef(snap, origin.name, { kind, publisher, name });
940
- const current = def?.version || found.version || "0.0.0";
1276
+ const current = found.version || def?.version || "0.0.0";
941
1277
  if (current !== origin.version) {
942
1278
  missing.push(
943
1279
  `${ref} (library "${origin.name}" is at ${current}, locked ${origin.version})`
@@ -953,7 +1289,7 @@ var AssetManager = class {
953
1289
  continue;
954
1290
  }
955
1291
  }
956
- entries.push(found);
1292
+ entries.push(withLibraryFiles(found, libPath, fileSets, this.registry));
957
1293
  }
958
1294
  return { entries, missing };
959
1295
  }
@@ -992,7 +1328,8 @@ var AssetManager = class {
992
1328
  stores: opts.stores,
993
1329
  storeFetcher: this.storeFetcherFor(opts.stores),
994
1330
  projectDir: this.projectDir,
995
- knownKinds: this.knownKinds
1331
+ knownKinds: this.knownKinds,
1332
+ declaredRefs: opts.declaredRefs
996
1333
  });
997
1334
  if (first.missing.length === 0) return first;
998
1335
  const fallback = await this.globalStoreFallback(opts.stores);
@@ -1005,13 +1342,135 @@ var AssetManager = class {
1005
1342
  stores,
1006
1343
  storeFetcher: this.storeFetcherFor(stores),
1007
1344
  projectDir: this.projectDir,
1008
- knownKinds: this.knownKinds
1345
+ knownKinds: this.knownKinds,
1346
+ declaredRefs: opts.declaredRefs
1009
1347
  });
1010
1348
  return retry.missing.length < first.missing.length ? retry : first;
1011
1349
  } catch {
1012
1350
  return first;
1013
1351
  }
1014
1352
  }
1353
+ /**
1354
+ * Resolve `deps` library-first, then walk the dependencies of every library
1355
+ * asset it picks.
1356
+ *
1357
+ * A publisher-qualified ref that no project source declares is looked up in the
1358
+ * registered **libraries** before any store is asked, so a locally authored
1359
+ * asset resolves (and deploys) with the store unreachable and without a
1360
+ * version-discovery round trip. Only refs the libraries did not satisfy go to
1361
+ * {@link resolveWithStores}; its misses get one more library try (a ref a source
1362
+ * knows only under another version). A `#version` pin still selects through
1363
+ * `matchPin` inside {@link resolveLibraryEntries}.
1364
+ *
1365
+ * Library assets declare `dependencies` / `requires`. Those are fed back through
1366
+ * the same resolution with a `seen` identity set (mirrors `resolveAll`), so deps
1367
+ * deploy, get lock entries, and a dependency cycle terminates.
1368
+ *
1369
+ * @returns source/store candidates, still-unresolved refs, applied overrides,
1370
+ * expanded presets, and the library leg (entries + originating library per entry).
1371
+ */
1372
+ async resolveLibraryFirst(deps, opts) {
1373
+ const resolved = [];
1374
+ const overridesApplied = /* @__PURE__ */ new Set();
1375
+ const presets = [];
1376
+ const lib = { entries: [], resolved: /* @__PURE__ */ new Set(), libraryOf: /* @__PURE__ */ new Map() };
1377
+ const missing = /* @__PURE__ */ new Set();
1378
+ const seen = /* @__PURE__ */ new Set();
1379
+ const covered = /* @__PURE__ */ new Set();
1380
+ const declaredById = /* @__PURE__ */ new Map();
1381
+ for (const d of opts.declaredRefs ?? []) {
1382
+ const id = refIdentity(d);
1383
+ if (id !== null) declaredById.set(id, d);
1384
+ }
1385
+ const presetParent = /* @__PURE__ */ new Map();
1386
+ const absorb = (hit) => {
1387
+ const next = [];
1388
+ for (const e of hit.entries) {
1389
+ const origin = hit.libraryOf.get(e);
1390
+ const id = `${e.publisher}/${e.kind}:${e.name}`;
1391
+ seen.add(id);
1392
+ covered.add(id);
1393
+ const deps2 = libraryEntryDepRefs(e);
1394
+ if (e.kind === "preset") {
1395
+ const chain = [id];
1396
+ for (let up = presetParent.get(id); up !== void 0; up = presetParent.get(up)) {
1397
+ chain.unshift(up);
1398
+ }
1399
+ if (chain.length > MAX_PRESET_DEPTH) {
1400
+ throw new PresetDepthError("depth", canonicalEntryRef(e), chain);
1401
+ }
1402
+ for (const dep of deps2) {
1403
+ const depId = refIdentity(dep);
1404
+ if (depId === null || !depId.includes("/preset:")) continue;
1405
+ if (chain.includes(depId)) throw new PresetDepthError("cycle", dep, [...chain, depId]);
1406
+ if (!presetParent.has(depId)) presetParent.set(depId, id);
1407
+ }
1408
+ presets.push({
1409
+ ref: canonicalEntryRef(e),
1410
+ assets: deps2.filter((d) => !d.startsWith("preset:")).length,
1411
+ skipped: e.skippedItems ?? 0,
1412
+ ...origin ? { library: origin } : {}
1413
+ });
1414
+ } else {
1415
+ lib.entries.push(e);
1416
+ if (origin) lib.libraryOf.set(e, origin);
1417
+ }
1418
+ next.push(
1419
+ ...deps2.map((dep) => {
1420
+ const depId = refIdentity(dep);
1421
+ return depId !== null && declaredById.get(depId) || dep;
1422
+ })
1423
+ );
1424
+ }
1425
+ for (const r of hit.resolved) lib.resolved.add(r);
1426
+ return next;
1427
+ };
1428
+ let wave = deps;
1429
+ while (wave.length > 0) {
1430
+ const fresh = [];
1431
+ for (const ref of wave) {
1432
+ const id = refIdentity(ref);
1433
+ if (id !== null) {
1434
+ if (seen.has(id)) continue;
1435
+ seen.add(id);
1436
+ }
1437
+ fresh.push(ref);
1438
+ }
1439
+ if (fresh.length === 0) break;
1440
+ const libraryTried = new Set(
1441
+ fresh.filter((ref) => {
1442
+ const id = refIdentity(ref);
1443
+ return id !== null && (opts.provenanceIndex.get(id)?.length ?? 0) === 0;
1444
+ })
1445
+ );
1446
+ const hit = await this.resolveLibraryEntries([...libraryTried]);
1447
+ const next = absorb(hit);
1448
+ const rest = fresh.filter((ref) => !hit.resolved.has(ref));
1449
+ if (rest.length > 0) {
1450
+ const r = await this.resolveWithStores(rest, opts);
1451
+ for (const c of r.resolved) {
1452
+ const id = `${c.publisher}/${c.kind}:${c.name}`;
1453
+ if (covered.has(id)) continue;
1454
+ seen.add(id);
1455
+ covered.add(id);
1456
+ resolved.push(c);
1457
+ }
1458
+ for (const o of r.overridesApplied) overridesApplied.add(o);
1459
+ presets.push(...r.presets);
1460
+ const hit2 = await this.resolveLibraryEntries(
1461
+ r.missing.filter((m) => !libraryTried.has(m))
1462
+ );
1463
+ next.push(...absorb(hit2));
1464
+ for (const m of r.missing) if (!hit2.resolved.has(m)) missing.add(m);
1465
+ }
1466
+ wave = next;
1467
+ }
1468
+ const stillMissing = [...missing].filter((m) => {
1469
+ const id = refIdentity(m);
1470
+ return !lib.resolved.has(m) && !(id !== null && covered.has(id));
1471
+ });
1472
+ return { resolved, missing: stillMissing, overridesApplied, lib, presets };
1473
+ }
1015
1474
  /** The catalog fetcher to use: an injected one, else built from `stores`. */
1016
1475
  storeFetcherFor(stores) {
1017
1476
  return this.injectedStoreFetcher ?? this.buildStoreFetcher(stores);
@@ -1177,7 +1636,7 @@ var AssetManager = class {
1177
1636
  async install(opts) {
1178
1637
  const { repositories, sources, stores, dependencies, overrides, patches } = this.loadConfig();
1179
1638
  if (opts?.locked) {
1180
- return this.installLocked(repositories, patches);
1639
+ return this.installLocked(repositories, patches, dependencies);
1181
1640
  }
1182
1641
  const cloneError = this.ensureSourceClones(repositories, sources);
1183
1642
  if (cloneError) return cloneError;
@@ -1185,18 +1644,32 @@ var AssetManager = class {
1185
1644
  projectDir: this.projectDir,
1186
1645
  knownKinds: this.knownKinds
1187
1646
  });
1188
- const { resolved, missing, overridesApplied, presets } = await this.resolveWithStores(
1647
+ const { resolved, missing, overridesApplied, lib, presets } = await this.resolveLibraryFirst(
1189
1648
  dependencies,
1190
- {
1191
- provenanceIndex,
1192
- overrides,
1193
- stores
1194
- }
1649
+ { provenanceIndex, overrides, stores, declaredRefs: dependencies }
1195
1650
  );
1196
- const lib = await this.resolveLibraryEntries(missing);
1651
+ this.assertNoFolderCollision([...resolved, ...lib.entries.map((e) => this.entryIdentity(e))]);
1652
+ const stillMissing = missing.filter((m) => !lib.resolved.has(m));
1653
+ const missingHints = await this.publisherChangeHints(stillMissing);
1654
+ const prevLock = this.tryReadLock();
1655
+ const kept = keptLockEntries(prevLock, stillMissing);
1656
+ const fixes = await this.unresolvedFixes(stillMissing);
1657
+ for (const ref of /* @__PURE__ */ new Set([...kept.refs, ...fixes.keys()])) {
1658
+ const fix = fixes.get(ref);
1659
+ const notes = [
1660
+ kept.refs.has(ref) ? "the deployed copy and its lock entry are kept" : void 0,
1661
+ fix
1662
+ ].filter((n) => n !== void 0);
1663
+ missingHints[ref] = `${fix ? "" : missingHints[ref] ?? ""} (${notes.join("; ")})`;
1664
+ }
1197
1665
  const directLock = this.directInstallLockEntries();
1198
1666
  const libraryLock = { ...directLock, ...await this.libraryLockEntries(lib) };
1199
- const removed = this.reconcileRemovedAssets(resolved, new Set(Object.keys(libraryLock)));
1667
+ const freshLock = buildLockFile(resolved, overridesApplied, libraryLock);
1668
+ await this.keepRequiredByKept(prevLock, kept, Object.keys(freshLock.assets));
1669
+ const removed = this.reconcileRemovedAssets(
1670
+ resolved,
1671
+ /* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)])
1672
+ );
1200
1673
  const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
1201
1674
  const deployed = [
1202
1675
  ...new Set(
@@ -1208,16 +1681,20 @@ var AssetManager = class {
1208
1681
  await this.renderDriverMcpConfig();
1209
1682
  const agentRender = this.renderAgentInstances();
1210
1683
  const lockWritten = resolved.length > 0 || lib.entries.length > 0 || existsSync(this.lockPath);
1684
+ let relocked = [];
1211
1685
  if (lockWritten) {
1212
- writeLock(this.lockPath, buildLockFile(resolved, overridesApplied, libraryLock));
1686
+ const lockData = withKeptLockEntries(freshLock, kept);
1687
+ relocked = relockedRefs(prevLock, lockData);
1688
+ writeLock(this.lockPath, lockData);
1213
1689
  }
1214
1690
  this.recordInstallHistory(deployed, removed);
1215
1691
  const { npmInstalled, npmFailed } = await this.installRuntimeAssetDeps();
1216
- const stillMissing = missing.filter((m) => !lib.resolved.has(m));
1217
1692
  return {
1218
1693
  deployed,
1219
1694
  removed,
1220
1695
  missing: stillMissing,
1696
+ ...Object.keys(missingHints).length > 0 ? { missingHints } : {},
1697
+ ...relocked.length > 0 ? { relocked } : {},
1221
1698
  lockWritten,
1222
1699
  npmInstalled,
1223
1700
  npmFailed,
@@ -1225,6 +1702,49 @@ var AssetManager = class {
1225
1702
  ...agentRender
1226
1703
  };
1227
1704
  }
1705
+ /**
1706
+ * Extend `kept` (see {@link keptLockEntries}) with every previous lock entry a kept asset
1707
+ * requires, transitively, that the new lock (`freshKeys`) does not already carry. The lock
1708
+ * records no edges, so they are read from the kept asset's deployed copy, where the
1709
+ * requirement is publisher-qualified the same way a library scan does it. A copy that
1710
+ * cannot be read has no known requirements.
1711
+ */
1712
+ async keepRequiredByKept(prev, kept, freshKeys) {
1713
+ if (!prev) return;
1714
+ const fresh = freshKeys.map(parseCanonicalLockKey);
1715
+ const queue = Object.keys(kept.assets);
1716
+ for (const key of queue) {
1717
+ for (const dep of await this.deployedRequirements(key)) {
1718
+ const want = memberIdentity(dep);
1719
+ if (!want) continue;
1720
+ const matches = (id) => id.kind === want.kind && id.name === want.name && (!want.publisher || !id.publisher || id.publisher === want.publisher);
1721
+ if (fresh.some(matches)) continue;
1722
+ for (const [depKey, entry] of Object.entries(prev.assets)) {
1723
+ if (depKey in kept.assets || entry.library?.installed) continue;
1724
+ if (!matches(parseCanonicalLockKey(depKey))) continue;
1725
+ kept.assets[depKey] = entry;
1726
+ queue.push(depKey);
1727
+ const src = entry.source ? prev.sources.find((s) => s.url === entry.source?.url) : void 0;
1728
+ if (src) kept.sources.push(src);
1729
+ }
1730
+ }
1731
+ }
1732
+ }
1733
+ /** Publisher-qualified refs the deployed copy of the asset behind lock `key` requires. */
1734
+ async deployedRequirements(key) {
1735
+ const { kind, name, publisher } = parseCanonicalLockKey(key);
1736
+ const component = kind ? this.installTarget(kind) : void 0;
1737
+ if (!kind || !name || !component) return [];
1738
+ const dir = deployedDir(component, name, this.driverTarget, this.global, this.projectDir);
1739
+ if (!existsSync(dir)) return [];
1740
+ try {
1741
+ const scanned = await scanDirectory(dir, publisher ?? "", { knownKinds: this.knownKinds });
1742
+ const own = scanned.find((e) => e.kind === kind && e.name === name);
1743
+ return own ? libraryEntryDepRefs(own) : [];
1744
+ } catch {
1745
+ return [];
1746
+ }
1747
+ }
1228
1748
  /**
1229
1749
  * Ensure all source clones are present/up to date. Returns an early
1230
1750
  * {@link InstallResult} describing the failed source on the first error, or
@@ -1289,6 +1809,7 @@ var AssetManager = class {
1289
1809
  for (const ref of Object.keys(oldLock.assets)) {
1290
1810
  if (!resolvedRefs.has(ref)) {
1291
1811
  const { kind, name } = parseCanonicalLockKey(ref);
1812
+ if (kind === "preset") continue;
1292
1813
  if (kind && name) {
1293
1814
  for (const t of this.targetHarnesses()) removeAsset(kind, name, this.deployOptsFor(t));
1294
1815
  removed.push(ref);
@@ -1374,7 +1895,10 @@ var AssetManager = class {
1374
1895
  try {
1375
1896
  return await src.listVersions(ref);
1376
1897
  } catch (err) {
1377
- console.warn(`skaile: store ${storeUrl} version lookup for ${ref} failed \u2014 ${err}`);
1898
+ if (!this.warnedStoreVersionLookups.has(storeUrl)) {
1899
+ this.warnedStoreVersionLookups.add(storeUrl);
1900
+ console.warn(`skaile: store ${storeUrl} version lookup for ${ref} failed \u2014 ${err}`);
1901
+ }
1378
1902
  return [];
1379
1903
  }
1380
1904
  }
@@ -1411,11 +1935,12 @@ var AssetManager = class {
1411
1935
  }
1412
1936
  return { npmInstalled, npmFailed };
1413
1937
  }
1414
- async installLocked(repositories, patches) {
1938
+ async installLocked(repositories, patches, dependencies) {
1415
1939
  const lock = this.tryReadLock();
1416
1940
  if (!lock) {
1417
1941
  throw new Error("No lock file found. Run `skaile install` first.");
1418
1942
  }
1943
+ this.assertNoFolderCollision(Object.keys(lock.assets).map(parseCanonicalLockKey));
1419
1944
  const declBySlug = new Map(Object.entries(repositories));
1420
1945
  const factoryUrl = normalizeRepoUrl(FACTORY_SOURCE_URL);
1421
1946
  const factoryDir = Object.values(repositories).find((d) => d.factory)?.path ?? null;
@@ -1440,6 +1965,16 @@ var AssetManager = class {
1440
1965
  throw new Error(`Lock file verification failed:
1441
1966
  ${issues.join("\n ")}`);
1442
1967
  }
1968
+ const presetGaps = await this.declaredPresetGaps(dependencies, lock);
1969
+ if (presetGaps.size > 0) {
1970
+ const lines = [...presetGaps].flatMap(
1971
+ ([preset, gaps2]) => gaps2.map((g) => `${preset} item ${g.ref}`)
1972
+ );
1973
+ throw new Error(
1974
+ `Lock file does not pin every item of the declared presets (not resolved by \`skaile install\`):
1975
+ ${lines.join("\n ")}`
1976
+ );
1977
+ }
1443
1978
  const resolved = [];
1444
1979
  for (const [ref, entry] of Object.entries(lock.assets)) {
1445
1980
  if (!entry.source) continue;
@@ -1478,10 +2013,20 @@ var AssetManager = class {
1478
2013
  ];
1479
2014
  await this.renderDriverMcpConfig();
1480
2015
  const agentRender = this.renderAgentInstances();
2016
+ const alreadyMissing = new Set(
2017
+ library.missing.map((m) => {
2018
+ const p = parseRefTolerant(m.split(" ")[0] ?? m);
2019
+ return p ? `${p.kind}:${p.name}` : m;
2020
+ })
2021
+ );
2022
+ const gaps = (await this.lockGaps()).filter((g) => {
2023
+ const p = parseRefTolerant(g.ref);
2024
+ return !(p && alreadyMissing.has(`${p.kind}:${p.name}`));
2025
+ }).map((g) => g.message);
1481
2026
  return {
1482
2027
  deployed,
1483
2028
  removed: [],
1484
- missing: library.missing,
2029
+ missing: [...library.missing, ...gaps],
1485
2030
  lockWritten: false,
1486
2031
  ...agentRender
1487
2032
  };
@@ -1499,26 +2044,44 @@ var AssetManager = class {
1499
2044
  * store/factory sources. Defaults to copy.
1500
2045
  * @param opts.onLibraryOrigin - Called once per deployed library-origin asset with its
1501
2046
  * canonical ref and the name of the library it was resolved from.
2047
+ * @param opts.onPresets - Called once with the presets expanded into this add (the
2048
+ * added preset and any it nests); a library-held preset names its library.
1502
2049
  * @returns Array of asset refs that were deployed in this call.
1503
- * @throws When the asset cannot be found in any configured repository.
2050
+ * @throws When the asset cannot be found in any configured repository, or when a
2051
+ * preset's items do not all resolve (nothing is deployed or recorded then).
1504
2052
  */
1505
2053
  async add(ref, opts) {
1506
- const { repositories, sources, stores, overrides, patches } = this.loadConfig();
1507
- parseAssetRef(ref);
2054
+ const { repositories, sources, stores, dependencies, overrides, patches } = this.loadConfig();
2055
+ const parsed = parseAssetRef(ref);
1508
2056
  this.ensureSourceClonesQuiet(repositories, sources);
1509
2057
  const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
1510
2058
  projectDir: this.projectDir,
1511
2059
  knownKinds: this.knownKinds
1512
2060
  });
1513
- const { resolved, missing, overridesApplied } = await this.resolveWithStores([ref], {
1514
- provenanceIndex,
1515
- overrides,
1516
- stores
1517
- });
1518
- const lib = await this.resolveLibraryEntries(missing);
1519
- if (resolved.length === 0 && lib.entries.length === 0) {
2061
+ const { resolved, missing, overridesApplied, lib, presets } = await this.resolveLibraryFirst(
2062
+ [ref],
2063
+ { provenanceIndex, overrides, stores, declaredRefs: dependencies }
2064
+ );
2065
+ if (resolved.length === 0 && lib.entries.length === 0 && presets.length === 0) {
1520
2066
  throw new Error(
1521
- `Asset not found: ${ref}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`
2067
+ `Asset not found: ${ref}${(await this.publisherChangeHints([ref]))[ref] ?? ""}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`
2068
+ );
2069
+ }
2070
+ if (parsed.kind === "preset" && missing.length > 0) {
2071
+ throw new Error(
2072
+ `Preset ${ref} has items that do not resolve: ${missing.join(", ")} \u2014 nothing was added`
2073
+ );
2074
+ }
2075
+ this.assertNoFolderCollision(
2076
+ [...resolved, ...lib.entries.map((e) => this.entryIdentity(e))],
2077
+ [
2078
+ ...Object.keys(this.tryReadLock()?.assets ?? {}).map(parseCanonicalLockKey),
2079
+ ...dependencies.map((d) => parseAssetRefLoose(d) ?? {})
2080
+ ]
2081
+ );
2082
+ if (missing.length > 0) {
2083
+ console.warn(
2084
+ `skaile: ${ref} was added without unresolved dependencies: ${missing.join(", ")}`
1522
2085
  );
1523
2086
  }
1524
2087
  const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
@@ -1536,6 +2099,7 @@ var AssetManager = class {
1536
2099
  if (origin && deployed.includes(deployedRef)) opts.onLibraryOrigin(deployedRef, origin);
1537
2100
  }
1538
2101
  }
2102
+ if (presets.length > 0) opts?.onPresets?.(presets);
1539
2103
  this.addDependencyToConfig(ref);
1540
2104
  await this.renderDriverMcpConfig();
1541
2105
  this.renderAgentInstances();
@@ -1604,7 +2168,7 @@ var AssetManager = class {
1604
2168
  const out = [];
1605
2169
  const seen = /* @__PURE__ */ new Set();
1606
2170
  for (const p of await this.search(void 0, "preset")) {
1607
- const members = [...p.dependencies, ...p.requires.map((d) => `${d.kind}:${d.name}`)];
2171
+ const members = [...p.dependencies, ...p.requires.map(depToStr)];
1608
2172
  if (!members.some((m) => sameAsset(memberIdentity(m), target))) continue;
1609
2173
  const publisher = p.publisher?.replace(/^@/, "");
1610
2174
  const presetId = { kind: "preset", name: p.name, publisher };
@@ -1784,7 +2348,14 @@ var AssetManager = class {
1784
2348
  return refs;
1785
2349
  }
1786
2350
  /**
1787
- * Remove a deployed asset and unregister it from `skaile.yaml`.
2351
+ * Remove a deployed asset and unregister it from `skaile.yaml` and the lock.
2352
+ *
2353
+ * A declared asset is always unregistered, even when nothing is deployed any
2354
+ * more (e.g. its library file was deleted): the declaration and lock entry
2355
+ * would otherwise be stuck behind a "not deployed" refusal.
2356
+ *
2357
+ * Removes only `ref` itself; use {@link removeWithDependencies} to also drop the
2358
+ * dependencies it leaves orphaned.
1788
2359
  *
1789
2360
  * Deliberately does NOT re-render the driver target's MCP config: the render
1790
2361
  * step is async (the declaration resolver is), and making this synchronous
@@ -1792,38 +2363,153 @@ var AssetManager = class {
1792
2363
  * generated entry is dropped by the next {@link AssetManager.install} — see
1793
2364
  * {@link renderMcpServerConfig}.
1794
2365
  *
1795
- * @param ref - Asset ref in `kind:name` form (e.g. `skill:my-skill`).
1796
- * @returns `true` if the asset was found and removed, `false` if it was not deployed.
1797
- * A `preset:` ref has no deployed files: it is found when declared in `skaile.yaml`, and
1798
- * only its declaration goes — its expanded items leave with the next `skaile install`.
2366
+ * @param ref - Asset ref: `kind:@publisher/name`, or the short `kind:name` /
2367
+ * `name` form when exactly one declared, locked or deployed asset matches it.
2368
+ * @returns `true` if anything changed (files undeployed, declaration or lock
2369
+ * entry removed), `false` if the asset was neither deployed nor declared.
2370
+ * A `preset:` ref has no deployed files: only its declaration goes — its
2371
+ * expanded items leave with the next `skaile install`.
2372
+ * @throws When `ref` is malformed, or a short ref matches several assets (the
2373
+ * message lists them).
1799
2374
  */
1800
2375
  remove(ref) {
1801
- const parsed = parseAssetRef(ref);
1802
- let ok = parsed.kind === "preset" ? this.removeDependencyFromConfig(parsed) : false;
2376
+ const target = this.resolveRemoveTarget(ref);
2377
+ return target ? this.removeResolved(target) : false;
2378
+ }
2379
+ /**
2380
+ * {@link remove} `ref`, then drop the dependencies that removal left orphaned:
2381
+ * locked assets `ref` required (directly or through one another) that
2382
+ * `skaile.yaml` no longer declares and no remaining locked asset requires. A
2383
+ * preset's items count as its requirements. They are undeployed and unlocked;
2384
+ * a dependency still declared or required elsewhere stays.
2385
+ *
2386
+ * @returns `changed` as {@link remove}; `pruned` the canonical refs
2387
+ * (`kind:@publisher/name`) of the dependencies dropped as well.
2388
+ */
2389
+ async removeWithDependencies(ref) {
2390
+ const target = this.resolveRemoveTarget(ref);
2391
+ if (!target) return { changed: false, pruned: [] };
2392
+ const asked = target.publisher ? assetRefToStr({ kind: target.kind, name: target.name, publisher: target.publisher }) : `${target.kind}:${target.name}`;
2393
+ let entry = null;
2394
+ try {
2395
+ entry = await this.info(asked);
2396
+ } catch {
2397
+ }
2398
+ const requiredRefs = entry ? libraryEntryDepRefs(entry) : [];
2399
+ const changed = this.removeResolved(target);
2400
+ if (!changed || requiredRefs.length === 0) return { changed, pruned: [] };
2401
+ return { changed, pruned: await this.pruneOrphans(requiredRefs) };
2402
+ }
2403
+ /**
2404
+ * Resolve the identity {@link remove} acts on. A publisher-qualified ref is taken
2405
+ * as written; a short ref (`kind:name`, or just `name`) is matched on kind + name
2406
+ * against the declared dependencies, the lock and the deployed assets, like
2407
+ * `why` / `diff` do.
2408
+ *
2409
+ * @returns the identity, or `null` when a name-only ref matches nothing.
2410
+ * @throws On a malformed ref, or when a short ref matches more than one asset.
2411
+ */
2412
+ resolveRemoveTarget(ref) {
2413
+ const trimmed = ref.trim();
2414
+ try {
2415
+ return parseAssetRef(trimmed);
2416
+ } catch (err) {
2417
+ if (!(err instanceof Error) || !err.message.startsWith("publisher required")) throw err;
2418
+ const colon = trimmed.indexOf(":");
2419
+ const rawKind = colon === -1 ? void 0 : trimmed.slice(0, colon).trim();
2420
+ const kind = rawKind === "mcp" ? "mcp-server" : rawKind;
2421
+ const hash = trimmed.indexOf("#", colon + 1);
2422
+ const name = trimmed.slice(colon + 1, hash === -1 ? void 0 : hash).trim();
2423
+ if (!name || /[@/\s]/.test(name)) throw err;
2424
+ const found = [];
2425
+ const note = (id) => {
2426
+ if (!id?.kind || id.name !== name || kind && id.kind !== kind) return;
2427
+ found.push({ kind: id.kind, name: id.name, publisher: id.publisher });
2428
+ };
2429
+ try {
2430
+ for (const dep of this.loadConfig().dependencies) note(memberIdentity(dep));
2431
+ } catch {
2432
+ }
2433
+ for (const key of Object.keys(this.tryReadLock()?.assets ?? {})) {
2434
+ note(parseCanonicalLockKey(key));
2435
+ }
2436
+ for (const e of this.listDeployed()) note(e);
2437
+ const qualified = found.filter((f) => f.publisher);
2438
+ const distinct = /* @__PURE__ */ new Map();
2439
+ for (const f of found) {
2440
+ if (!f.publisher && qualified.some((q) => q.kind === f.kind)) continue;
2441
+ distinct.set(`${f.kind}:${f.publisher ?? ""}/${f.name}`, f);
2442
+ }
2443
+ const matches = [...distinct.values()];
2444
+ if (matches.length > 1) {
2445
+ const list = matches.map((m) => m.publisher ? `${m.kind}:@${m.publisher}/${m.name}` : `${m.kind}:${m.name}`).join(", ");
2446
+ throw new Error(`"${ref}" matches several assets (${list}) \u2014 name one with its full ref.`);
2447
+ }
2448
+ if (matches.length === 1) return matches[0];
2449
+ return kind ? { kind, name } : null;
2450
+ }
2451
+ }
2452
+ /** Undeploy `parsed` from every target harness and drop its declaration and lock entry. */
2453
+ removeResolved(parsed) {
2454
+ let undeployed = false;
1803
2455
  for (const t of this.targetHarnesses()) {
1804
- if (removeAsset(parsed.kind, parsed.name, this.deployOptsFor(t))) ok = true;
2456
+ if (removeAsset(parsed.kind, parsed.name, this.deployOptsFor(t))) undeployed = true;
1805
2457
  }
1806
- if (ok) {
1807
- const depRef = `${parsed.kind}:${parsed.name}`;
1808
- this.removeDependencyFromConfig(parsed);
1809
- this.pruneLockEntry(parsed);
2458
+ const undeclared = this.removeDependencyFromConfig(parsed);
2459
+ const unlocked = undeployed || undeclared ? this.pruneLockEntry(parsed) : false;
2460
+ const changed = undeployed || undeclared || unlocked;
2461
+ if (changed) {
1810
2462
  appendHistory(this.projectDir, {
1811
- ref: depRef,
2463
+ ref: `${parsed.kind}:${parsed.name}`,
1812
2464
  action: "remove",
1813
2465
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1814
2466
  context: this.global ? "global" : "project"
1815
2467
  });
1816
2468
  }
1817
- return ok;
2469
+ return changed;
2470
+ }
2471
+ /**
2472
+ * Drop the locked assets among `requiredRefs` (and what they require in turn) that
2473
+ * nothing needs any more: not declared in `skaile.yaml`, not required by a locked
2474
+ * asset outside that set. Call after the requiring asset is gone.
2475
+ *
2476
+ * @returns the canonical refs of the assets dropped.
2477
+ */
2478
+ async pruneOrphans(requiredRefs) {
2479
+ const lock = this.tryReadLock();
2480
+ if (!lock) return [];
2481
+ const { requires, roots, findKey } = await this.lockedGraph(lock);
2482
+ const closure = (seeds) => {
2483
+ const out = new Set(seeds);
2484
+ for (const node of out) for (const dep of requires.get(node) ?? []) out.add(dep);
2485
+ return out;
2486
+ };
2487
+ const candidates = closure(requiredRefs.map(findKey).filter((k) => !!k));
2488
+ const needed = closure([
2489
+ ...roots,
2490
+ ...Object.keys(lock.assets).filter((k) => !candidates.has(k))
2491
+ ]);
2492
+ const pruned = [];
2493
+ for (const key of candidates) {
2494
+ if (needed.has(key) || !lock.assets[key]) continue;
2495
+ const { kind, name, publisher } = parseCanonicalLockKey(key);
2496
+ if (!kind || !name) continue;
2497
+ this.removeResolved({ kind, name, publisher });
2498
+ this.pruneLockEntry({ kind, name, publisher });
2499
+ pruned.push(publisher ? assetRefToStr({ kind, name, publisher }) : `${kind}:${name}`);
2500
+ }
2501
+ return pruned;
1818
2502
  }
1819
2503
  /**
1820
2504
  * Drop every lock entry matching a removed asset's identity, version-agnostic so
1821
2505
  * a content-sha pseudo-version key is pruned too. Symmetric with {@link add}'s
1822
2506
  * lock-merge — stops a stale entry reporting a phantom "removed" on next install.
2507
+ *
2508
+ * @returns `true` when at least one lock entry was dropped.
1823
2509
  */
1824
2510
  pruneLockEntry(parsed) {
1825
2511
  const lock = this.tryReadLock();
1826
- if (!lock) return;
2512
+ if (!lock) return false;
1827
2513
  let changed = false;
1828
2514
  for (const key of Object.keys(lock.assets)) {
1829
2515
  const id = parseCanonicalLockKey(key);
@@ -1833,6 +2519,7 @@ var AssetManager = class {
1833
2519
  }
1834
2520
  }
1835
2521
  if (changed) writeLock(this.lockPath, lock);
2522
+ return changed;
1836
2523
  }
1837
2524
  // ── Query ──────────────────────────────────────────────────────────────
1838
2525
  /**
@@ -1993,9 +2680,15 @@ var AssetManager = class {
1993
2680
  for (const { kind, name, component: k } of tracked.values()) {
1994
2681
  const legacy = deployedDir(k, name, "claude-code", false, this.projectDir);
1995
2682
  if (legacy === deployedDir(k, name, this.driverTarget, false, this.projectDir)) continue;
1996
- if (isDeployed(k, name, "claude-code", false, this.projectDir)) {
1997
- out.push({ kind, name, path: legacy });
1998
- }
2683
+ const found = deployedPaths(k, name, "claude-code", false, this.projectDir).find((p) => {
2684
+ try {
2685
+ lstatSync(p);
2686
+ return true;
2687
+ } catch {
2688
+ return false;
2689
+ }
2690
+ });
2691
+ if (found) out.push({ kind, name, path: found });
1999
2692
  }
2000
2693
  return out.sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name));
2001
2694
  }
@@ -2029,6 +2722,7 @@ var AssetManager = class {
2029
2722
  const dependencies = resolveSkWorkspaceConfig(this.projectDir).dependencies ?? [];
2030
2723
  const lock = this.tryReadLock();
2031
2724
  const lockKeys = lock ? Object.keys(lock.assets).map(parseCanonicalLockKey) : [];
2725
+ const presetGaps = await this.declaredPresetGaps(dependencies, lock);
2032
2726
  let wiredMcps;
2033
2727
  const wiredMcpNames = async () => {
2034
2728
  if (wiredMcps === void 0) {
@@ -2051,7 +2745,7 @@ var AssetManager = class {
2051
2745
  continue;
2052
2746
  }
2053
2747
  if (kind && parsed.kind !== kind) continue;
2054
- const locked = parsed.kind === "preset" ? lock !== null : lockKeys.some(
2748
+ const locked = parsed.kind === "preset" ? lock !== null && !presetGaps.get(ref)?.length : lockKeys.some(
2055
2749
  (k) => k.kind === parsed.kind && k.name === parsed.name && (!parsed.publisher || k.publisher === parsed.publisher)
2056
2750
  );
2057
2751
  let state;
@@ -2079,6 +2773,38 @@ var AssetManager = class {
2079
2773
  }
2080
2774
  return out;
2081
2775
  }
2776
+ /**
2777
+ * Library-locked assets whose deployed directory lacks files the deploy writes —
2778
+ * a deleted `build.py` / `scripts/` helper next to an intact `SKILL.md`. The
2779
+ * expected set is the library entry's file set (every file under its manifest
2780
+ * `root:`), the same bytes the lock hashes; only presence is checked, so a
2781
+ * local edit never counts. An asset with no deployed directory is not listed
2782
+ * (`listDeclared` reports it as drift); an agent is rendered, not copied, so it
2783
+ * has no file mirror to compare. `skaile install` restores what is listed.
2784
+ *
2785
+ * @returns One {@link IncompleteDeploy} per affected asset; empty when healthy
2786
+ * or when the project has no lock.
2787
+ */
2788
+ async incompleteDeploys() {
2789
+ const lock = this.tryReadLock();
2790
+ if (!lock) return [];
2791
+ const locked = Object.entries(lock.assets).filter(([, e]) => e.library !== void 0 && !e.library.installed).map(([key]) => key);
2792
+ if (locked.length === 0) return [];
2793
+ const lib = await this.resolveLibraryEntries(locked, { quiet: true });
2794
+ const out = [];
2795
+ for (const entry of lib.entries) {
2796
+ const component = this.installTarget(entry.kind);
2797
+ if (!entry.files || !component || component === "agent") continue;
2798
+ const dest = join(
2799
+ deployedBase(component, this.driverTarget, this.global, this.projectDir),
2800
+ entry.name
2801
+ );
2802
+ if (!existsSync(dest)) continue;
2803
+ const missing = entry.files.filter((rel) => !existsSync(join(dest, rel)));
2804
+ if (missing.length > 0) out.push({ ref: canonicalEntryRef(entry), missing });
2805
+ }
2806
+ return out;
2807
+ }
2082
2808
  /**
2083
2809
  * Per-harness reconciliation of the **global** declared deps
2084
2810
  * (`~/.skaile/skaile.yaml`) against each global harness's deploy dir. The
@@ -2176,70 +2902,197 @@ var AssetManager = class {
2176
2902
  }
2177
2903
  return repoStatuses;
2178
2904
  }
2905
+ /**
2906
+ * Requirement graph of the locked assets, for {@link tree} / {@link why} / removal pruning.
2907
+ *
2908
+ * The lock records no edges, so they are rebuilt from each locked asset's own
2909
+ * declaration (`requires`, manifest `dependencies`, a preset's items) — the same
2910
+ * source the install walk reads, via {@link info}. An asset that no source or
2911
+ * library can be found for has no known requirements. Nodes are lock keys, plus
2912
+ * each declared `preset:` that the lock does not carry (a preset is expanded at
2913
+ * install, never locked), keyed by its declared ref.
2914
+ *
2915
+ * @returns `requires` (node → the lock keys it requires), `roots` (nodes
2916
+ * `skaile.yaml` declares) and `findKey` (a ref → its lock key, version-agnostic).
2917
+ */
2918
+ async lockedGraph(lock) {
2919
+ const locked = Object.keys(lock.assets).map((key) => ({ key, ...parseCanonicalLockKey(key) }));
2920
+ const findKey = (ref) => {
2921
+ const id = memberIdentity(ref);
2922
+ if (!id) return void 0;
2923
+ return locked.find(
2924
+ (l) => l.kind === id.kind && l.name === id.name && (!id.publisher || !l.publisher || l.publisher === id.publisher)
2925
+ )?.key;
2926
+ };
2927
+ const requires = /* @__PURE__ */ new Map();
2928
+ const link = async (node, ref) => {
2929
+ const out = [];
2930
+ try {
2931
+ const entry = await this.info(ref);
2932
+ for (const dep of entry ? libraryEntryDepRefs(entry) : []) {
2933
+ const key = findKey(dep);
2934
+ if (key && key !== node && !out.includes(key)) out.push(key);
2935
+ }
2936
+ } catch {
2937
+ }
2938
+ requires.set(node, out);
2939
+ };
2940
+ for (const l of locked) {
2941
+ await link(
2942
+ l.key,
2943
+ l.publisher ? `${l.kind}:@${l.publisher}/${l.name}` : `${l.kind}:${l.name}`
2944
+ );
2945
+ }
2946
+ const roots = [];
2947
+ let declared = [];
2948
+ try {
2949
+ declared = this.loadConfig().dependencies;
2950
+ } catch {
2951
+ }
2952
+ for (const dep of declared) {
2953
+ const key = findKey(dep);
2954
+ if (key) {
2955
+ if (!roots.includes(key)) roots.push(key);
2956
+ } else if (memberIdentity(dep)?.kind === "preset" && !roots.includes(dep)) {
2957
+ await link(dep, dep);
2958
+ roots.push(dep);
2959
+ }
2960
+ }
2961
+ return { requires, roots, findKey };
2962
+ }
2179
2963
  /**
2180
2964
  * Build the resolved dependency tree from the lock file.
2181
2965
  *
2966
+ * Required assets nest under the asset that requires them (see {@link lockedGraph});
2967
+ * an asset required by several parents appears under each, and a dependency cycle
2968
+ * is cut where it would repeat.
2969
+ *
2182
2970
  * @returns Root {@link DependencyNode} with `ref === "project"` whose children
2183
- * are the directly-declared dependencies, each with their own transitive subtrees.
2971
+ * are the directly-declared assets, followed by locked assets nothing requires,
2972
+ * each with the assets it requires as children.
2184
2973
  * Returns a single node with `ref === "(no lock file)"` when no lock exists.
2185
2974
  */
2186
- tree() {
2975
+ async tree() {
2187
2976
  const lock = this.tryReadLock();
2188
2977
  if (!lock) return { ref: "(no lock file)", children: [] };
2189
- return {
2190
- ref: "project",
2191
- children: Object.keys(lock.assets).map((ref) => ({ ref, children: [] }))
2978
+ const { requires, roots } = await this.lockedGraph(lock);
2979
+ const required = new Set([...requires.values()].flat());
2980
+ const build = (ref, path2) => ({
2981
+ ref,
2982
+ children: (requires.get(ref) ?? []).filter((child) => !path2.has(child)).map((child) => build(child, /* @__PURE__ */ new Set([...path2, child])))
2983
+ });
2984
+ const shown = /* @__PURE__ */ new Set();
2985
+ const top = [];
2986
+ const addTop = (ref) => {
2987
+ const node = build(ref, /* @__PURE__ */ new Set([ref]));
2988
+ top.push(node);
2989
+ const walk = (n) => {
2990
+ shown.add(n.ref);
2991
+ for (const c of n.children) walk(c);
2992
+ };
2993
+ walk(node);
2192
2994
  };
2995
+ const lockKeys = Object.keys(lock.assets);
2996
+ for (const ref of roots) addTop(ref);
2997
+ for (const ref of lockKeys) if (!roots.includes(ref) && !required.has(ref)) addTop(ref);
2998
+ for (const ref of lockKeys) if (!shown.has(ref)) addTop(ref);
2999
+ return { ref: "project", children: top };
2193
3000
  }
2194
3001
  /**
2195
- * Explain why an asset is installed by tracing its dependency chain in the lock file.
3002
+ * Explain why an asset is installed by tracing its dependency chain.
3003
+ *
3004
+ * The chain is read from the requirement graph ({@link lockedGraph}): the asset,
3005
+ * then the asset that requires it, and so on up to a dependency `skaile.yaml`
3006
+ * declares (the shortest such path; cycles are not followed twice).
2196
3007
  *
2197
3008
  * @param ref - Asset ref in `kind:name` form.
2198
- * @returns Chain of refs from the asset back to `"direct (skaile.yaml)"`, or `[]` when
2199
- * the asset is not in the lock file.
3009
+ * @returns Chain of refs from the asset back to `"direct (skaile.yaml)"`; an asset
3010
+ * no declared dependency reaches ends in `"not required by a declared dependency"`
3011
+ * instead. `[]` when the asset is not in the lock file.
2200
3012
  */
2201
- why(ref) {
3013
+ async why(ref) {
2202
3014
  const lock = this.tryReadLock();
2203
3015
  if (!lock) return [];
2204
3016
  const bare = (s) => s.includes("#") ? s.slice(0, s.indexOf("#")) : s;
2205
- const key = lock.assets[ref] ? ref : Object.keys(lock.assets).find((k) => bare(k) === bare(ref));
3017
+ const asked = parseRefTolerant(ref);
3018
+ const matchesIdentity = (k) => {
3019
+ if (bare(k) === bare(ref)) return true;
3020
+ if (!asked || asked.publisher) return false;
3021
+ const locked = parseCanonicalLockKey(k);
3022
+ return locked.kind === asked.kind && locked.name === asked.name;
3023
+ };
3024
+ const key = lock.assets[ref] ? ref : Object.keys(lock.assets).find(matchesIdentity);
2206
3025
  if (!key) return [];
2207
- return [key, "direct (skaile.yaml)"];
3026
+ const { requires, roots } = await this.lockedGraph(lock);
3027
+ if (roots.includes(key)) return [key, "direct (skaile.yaml)"];
3028
+ const requirers = /* @__PURE__ */ new Map();
3029
+ for (const [node, deps] of requires) {
3030
+ for (const dep of deps) requirers.set(dep, [...requirers.get(dep) ?? [], node]);
3031
+ }
3032
+ const child = /* @__PURE__ */ new Map();
3033
+ const seen = /* @__PURE__ */ new Set([key]);
3034
+ const queue = [key];
3035
+ for (let node = queue.shift(); node !== void 0; node = queue.shift()) {
3036
+ for (const parent of requirers.get(node) ?? []) {
3037
+ if (seen.has(parent)) continue;
3038
+ seen.add(parent);
3039
+ child.set(parent, node);
3040
+ if (roots.includes(parent)) {
3041
+ const chain = [];
3042
+ for (let c = parent; c !== void 0; c = child.get(c)) chain.push(c);
3043
+ return [...chain.reverse(), "direct (skaile.yaml)"];
3044
+ }
3045
+ queue.push(parent);
3046
+ }
3047
+ }
3048
+ return [key, "not required by a declared dependency"];
2208
3049
  }
2209
3050
  /**
2210
3051
  * List installed assets that have a newer upstream available.
2211
3052
  *
2212
3053
  * Two kinds of staleness are reported:
2213
3054
  * - **Source assets** — only remote repositories (not local paths) are checked;
2214
- * `behind` is the number of commits the local clone is missing.
2215
- * - **Library assets** — a library-origin lock entry whose library index now
2216
- * offers a newer version than the locked one; `behind` is 0, `latestVersion`
2217
- * and `library` are set. A library not indexed (or removed) is not reported.
3055
+ * `behind` counts the commits the clone is missing from the remote plus the
3056
+ * commits the clone moved past the locked commit (`source sync` after `add`).
3057
+ * - **Library assets** — a library-origin lock entry whose library files on disk
3058
+ * (not the index, which lags until `skaile library index`) hold a newer version
3059
+ * than the locked one (`latestVersion`, `library` set), or the same version with
3060
+ * different bytes (`contentChanged`). `behind` is 0. An unregistered library is a
3061
+ * `problem` row.
3062
+ * - **Lock gaps** — every {@link AssetManager.lockGaps} gap (a declared dependency or a
3063
+ * deployed asset's `requires` with no lock entry, a lock that contradicts a
3064
+ * `skaile.yaml` pin) as a `problem` row, so `outdated` agrees with `check`.
2218
3065
  *
2219
3066
  * @returns Array of {@link OutdatedEntry} objects.
2220
3067
  */
2221
3068
  async outdated() {
2222
3069
  const { repositories } = this.loadConfig();
2223
3070
  const lock = this.tryReadLock();
2224
- if (!lock) return [];
3071
+ if (!lock) return this.addLockGapRows([]);
2225
3072
  const result = [];
2226
3073
  const reported = /* @__PURE__ */ new Set();
3074
+ const commitCounts = /* @__PURE__ */ new Map();
2227
3075
  for (const src of lock.sources) {
2228
3076
  const slug = sourceCacheKey(src.url);
2229
3077
  const decl = repositories[slug];
2230
3078
  if (!decl) continue;
2231
3079
  const status = checkRepoStatus(decl, slug, this.reposDir);
2232
- if (status.behind <= 0) continue;
3080
+ if (status.kind !== "remote" || !status.localHead) continue;
3081
+ const clone = decl.path ?? join(this.reposDir, slug);
2233
3082
  for (const [ref, entry] of Object.entries(lock.assets)) {
2234
3083
  if (entry.source?.url !== src.url) continue;
2235
3084
  const { publisher, kind, name, version } = parseCanonicalLockKey(ref);
2236
3085
  if (!kind || !name) continue;
3086
+ const lockedCommit = entry.source.commit || src.commit;
3087
+ const moved = lockedCommit && !/^0+$/.test(lockedCommit) && lockedCommit !== status.localHead ? commitsBetween(clone, lockedCommit, status.localHead, commitCounts) : 0;
3088
+ const behind = status.behind + moved;
3089
+ if (behind <= 0) continue;
2237
3090
  result.push({
2238
3091
  kind,
2239
3092
  name,
2240
3093
  publisher: publisher ?? sourceShortName(src.url),
2241
3094
  currentVersion: version ?? "",
2242
- behind: status.behind
3095
+ behind
2243
3096
  });
2244
3097
  reported.add(ref);
2245
3098
  }
@@ -2249,11 +3102,17 @@ var AssetManager = class {
2249
3102
  );
2250
3103
  if (libraryAssets.length > 0) {
2251
3104
  const snap = await this.loadLibraryIndex();
3105
+ const diskHashes = /* @__PURE__ */ new Map();
3106
+ const scans = /* @__PURE__ */ new Map();
2252
3107
  for (const [ref, entry] of libraryAssets) {
2253
3108
  const origin = entry.library;
2254
3109
  const { publisher, kind, name } = parseCanonicalLockKey(ref);
2255
3110
  if (!publisher || !kind || !name) continue;
2256
- if (!this.libraryPath(origin.name, snap)) {
3111
+ const libPath = this.libraryPath(origin.name, snap);
3112
+ if (!libPath) {
3113
+ const canonical = `${kind}:@${publisher}/${name}`;
3114
+ const heldElsewhere = (await this.resolveLibraryEntries([canonical], { quiet: true })).resolved.size > 0;
3115
+ const fix = heldElsewhere ? void 0 : (await this.unresolvedFixes([canonical])).get(canonical) ?? unregisteredLibraryFix(origin.name, canonical);
2257
3116
  result.push({
2258
3117
  kind,
2259
3118
  name,
@@ -2261,23 +3120,55 @@ var AssetManager = class {
2261
3120
  currentVersion: origin.version,
2262
3121
  behind: 0,
2263
3122
  library: origin.name,
2264
- problem: `library "${origin.name}" is not registered (renamed or removed?)`
3123
+ ...fix === void 0 ? {
3124
+ problem: `library "${origin.name}" is not registered (renamed?) \u2014 run \`skaile install\` to re-lock it against the library that holds it now`
3125
+ } : {
3126
+ problem: `library "${origin.name}" is not registered (renamed or removed?) \u2014 ${fix}`,
3127
+ manualFix: true
3128
+ }
2265
3129
  });
2266
3130
  reported.add(ref);
2267
3131
  continue;
2268
3132
  }
2269
- const def = newestLibraryDef(snap, origin.name, { kind, publisher, name });
2270
- if (!def || !gt(coerceVersion(def.version), coerceVersion(origin.version))) continue;
2271
- result.push({
2272
- kind,
2273
- name,
2274
- publisher,
2275
- currentVersion: origin.version,
2276
- behind: 0,
2277
- latestVersion: def.version,
2278
- library: origin.name
2279
- });
2280
- reported.add(ref);
3133
+ const scanKey = `${libPath}\0${publisher}`;
3134
+ let scanned = scans.get(scanKey);
3135
+ if (!scanned) {
3136
+ scanned = await scanDirectory(libPath, publisher, { knownKinds: this.knownKinds });
3137
+ scans.set(scanKey, scanned);
3138
+ }
3139
+ const found = scanned.find((x) => x.kind === kind && x.name === name);
3140
+ if (!found) continue;
3141
+ const diskVersion = found.version || newestLibraryDef(snap, origin.name, { kind, publisher, name })?.version;
3142
+ if (diskVersion && gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
3143
+ result.push({
3144
+ kind,
3145
+ name,
3146
+ publisher,
3147
+ currentVersion: origin.version,
3148
+ behind: 0,
3149
+ latestVersion: diskVersion,
3150
+ library: origin.name
3151
+ });
3152
+ reported.add(ref);
3153
+ continue;
3154
+ }
3155
+ if (origin.sha256) {
3156
+ const onDisk = libraryDiskHashes(libPath, diskHashes, this.registry).get(
3157
+ `${kind}:${name}`
3158
+ );
3159
+ if (onDisk !== void 0 && onDisk !== origin.sha256) {
3160
+ result.push({
3161
+ kind,
3162
+ name,
3163
+ publisher,
3164
+ currentVersion: origin.version,
3165
+ behind: 0,
3166
+ library: origin.name,
3167
+ contentChanged: true
3168
+ });
3169
+ reported.add(ref);
3170
+ }
3171
+ }
2281
3172
  }
2282
3173
  }
2283
3174
  for (const [ref, entry] of Object.entries(lock.assets)) {
@@ -2298,7 +3189,30 @@ var AssetManager = class {
2298
3189
  problem: "deployed copy missing"
2299
3190
  });
2300
3191
  }
2301
- return result;
3192
+ return this.addLockGapRows(result);
3193
+ }
3194
+ /**
3195
+ * Append one `problem` row per {@link AssetManager.lockGaps} gap whose asset is not
3196
+ * listed yet, so `outdated` reports the same set `check` and `install --locked` fail on.
3197
+ */
3198
+ async addLockGapRows(rows) {
3199
+ const listed = new Set(rows.map((r) => `${r.kind}:${r.name}`));
3200
+ for (const gap of await this.lockGaps()) {
3201
+ const parsed = parseRefTolerant(gap.ref);
3202
+ const id = parsed ? `${parsed.kind}:${parsed.name}` : gap.ref;
3203
+ if (listed.has(id)) continue;
3204
+ listed.add(id);
3205
+ rows.push({
3206
+ kind: parsed?.kind ?? "",
3207
+ name: parsed?.name ?? gap.ref,
3208
+ publisher: parsed?.publisher ?? "",
3209
+ currentVersion: "",
3210
+ behind: 0,
3211
+ problem: gap.reason,
3212
+ ...gap.manualFix ? { manualFix: true } : {}
3213
+ });
3214
+ }
3215
+ return rows;
2302
3216
  }
2303
3217
  /**
2304
3218
  * Produce a unified diff from the deployed asset to its catalog source: `-` lines
@@ -2315,7 +3229,8 @@ var AssetManager = class {
2315
3229
  * @throws When `diff(1)` itself fails (exit status other than 0/1).
2316
3230
  */
2317
3231
  async diff(ref) {
2318
- const parsed = parseAssetRef(ref);
3232
+ const parsed = parseRefTolerant(ref);
3233
+ if (!parsed) return { status: "not-found" };
2319
3234
  const entry = await this.info(ref);
2320
3235
  if (!entry || !existsSync(entry.source)) return { status: "not-found" };
2321
3236
  const component = this.installTarget(parsed.kind);
@@ -2325,10 +3240,20 @@ var AssetManager = class {
2325
3240
  parsed.name
2326
3241
  );
2327
3242
  if (!existsSync(deployedPath)) return { status: "not-deployed" };
2328
- const r = spawnSync("diff", ["-ruN", deployedPath, entry.source], { encoding: "utf8" });
2329
- if (r.status === 1) return { status: "differs", diff: r.stdout };
2330
- if (r.status === 0) return { status: "identical" };
2331
- throw new Error(`diff failed for ${ref}: ${r.stderr || r.error?.message || "unknown error"}`);
3243
+ const pairs = entry.files ? entry.files.map((rel) => [join(deployedPath, rel), join(dirname(entry.source), rel)]) : [[deployedPath, entry.source]];
3244
+ const diffs = [];
3245
+ for (const [from, to] of pairs) {
3246
+ const r = spawnSync("diff", [entry.files ? "-uN" : "-ruN", from, to], {
3247
+ encoding: "utf8"
3248
+ });
3249
+ if (r.status === 1) diffs.push(r.stdout);
3250
+ else if (r.status !== 0) {
3251
+ throw new Error(
3252
+ `diff failed for ${ref}: ${r.stderr || r.error?.message || "unknown error"}`
3253
+ );
3254
+ }
3255
+ }
3256
+ return diffs.length > 0 ? { status: "differs", diff: diffs.join("") } : { status: "identical" };
2332
3257
  }
2333
3258
  // ── Lock ───────────────────────────────────────────────────────────────
2334
3259
  /**
@@ -2346,12 +3271,13 @@ var AssetManager = class {
2346
3271
  projectDir: this.projectDir,
2347
3272
  knownKinds: this.knownKinds
2348
3273
  });
2349
- const { resolved, missing, overridesApplied } = await this.resolveWithStores(dependencies, {
3274
+ const { resolved, overridesApplied, lib } = await this.resolveLibraryFirst(dependencies, {
2350
3275
  provenanceIndex,
2351
3276
  overrides,
2352
- stores
3277
+ stores,
3278
+ declaredRefs: dependencies
2353
3279
  });
2354
- const libraryLock = await this.libraryLockEntries(await this.resolveLibraryEntries(missing));
3280
+ const libraryLock = await this.libraryLockEntries(lib);
2355
3281
  const lockData = buildLockFile(resolved, overridesApplied, libraryLock);
2356
3282
  writeLock(this.lockPath, lockData);
2357
3283
  return lockData;
@@ -2445,37 +3371,245 @@ var AssetManager = class {
2445
3371
  *
2446
3372
  * For each deployed asset, checks that all entries in its `requires` list are
2447
3373
  * also deployed. Returns a list of issues with a flag indicating whether the
2448
- * missing dependency can be resolved from a configured repository.
3374
+ * missing dependency can be resolved from a configured repository or library.
3375
+ *
3376
+ * A deployed asset is looked up by its publisher-qualified identity taken from
3377
+ * the lock key (`kind:@pub/name`), so library and source assets resolve alike.
3378
+ * A lock-managed asset that no source or library can be found for is reported
3379
+ * as an issue with `unresolved: true` (its `requires` cannot be verified);
3380
+ * unmanaged deployed files carry no declared origin and are skipped.
2449
3381
  *
2450
3382
  * @returns Array of {@link DepIssue} objects. An empty array means the workspace is healthy.
2451
3383
  */
2452
3384
  async doctor() {
2453
- const { repositories } = this.loadConfig();
3385
+ const { repositories, dependencies } = this.loadConfig();
2454
3386
  const deployed = this.listDeployed();
2455
3387
  const issues = [];
3388
+ const lock = this.tryReadLock();
3389
+ const lockedPublisher = {};
3390
+ for (const key of Object.keys(lock?.assets ?? {})) {
3391
+ const { kind, name, publisher } = parseCanonicalLockKey(key);
3392
+ if (kind && name && publisher) lockedPublisher[`${kind}:${name}`] = publisher;
3393
+ }
2456
3394
  for (const entry of deployed) {
2457
- const full = await this.info(`${entry.kind}:${entry.name}`);
2458
- if (!full) continue;
3395
+ const publisher = lockedPublisher[`${entry.kind}:${entry.name}`];
3396
+ const full = await this.info(
3397
+ publisher ? `${entry.kind}:@${publisher}/${entry.name}` : `${entry.kind}:${entry.name}`
3398
+ );
3399
+ if (!full) {
3400
+ if (publisher) {
3401
+ const canonical = `${entry.kind}:@${publisher}/${entry.name}`;
3402
+ const fix = (await this.unresolvedFixes([canonical])).get(canonical);
3403
+ issues.push({
3404
+ assetKind: entry.kind,
3405
+ assetName: entry.name,
3406
+ depKind: entry.kind,
3407
+ depName: entry.name,
3408
+ inRepos: false,
3409
+ unresolved: true,
3410
+ ...fix ? { fix } : {}
3411
+ });
3412
+ }
3413
+ continue;
3414
+ }
3415
+ const gaps = [];
2459
3416
  for (const req of full.requires) {
2460
- const dep = memberIdentity(`${req.kind}:${req.name}`);
3417
+ const dep = memberIdentity(depToStr(req));
2461
3418
  if (!dep) continue;
2462
3419
  const depComponent = this.installTarget(dep.kind);
2463
3420
  if (depComponent && !isDeployed(depComponent, dep.name, this.driverTarget, this.global, this.projectDir)) {
2464
3421
  const inRepos = await resolveAsset({ kind: dep.kind, name: dep.name }, repositories, this.reposDir, {
2465
3422
  knownKinds: this.knownKinds
2466
3423
  }) !== null;
2467
- issues.push({
3424
+ const issue = {
2468
3425
  assetKind: entry.kind,
2469
3426
  assetName: entry.name,
2470
3427
  depKind: req.kind,
2471
- depName: req.name,
3428
+ depName: req.publisher ? `@${req.publisher}/${req.name}` : req.name,
2472
3429
  inRepos
2473
- });
3430
+ };
3431
+ issues.push(issue);
3432
+ if (!inRepos) gaps.push({ issue, ref: depToStr(req) });
2474
3433
  }
2475
3434
  }
3435
+ const inLibrary = await this.resolveLibraryEntries(gaps.map((g) => g.ref));
3436
+ for (const g of gaps) g.issue.inRepos = inLibrary.resolved.has(g.ref);
3437
+ }
3438
+ for (const [presetRef, gaps] of await this.declaredPresetGaps(dependencies, lock)) {
3439
+ const preset = parseRefTolerant(presetRef);
3440
+ if (!preset) continue;
3441
+ for (const g of gaps) {
3442
+ const item = parseRefTolerant(g.ref);
3443
+ if (!item) continue;
3444
+ issues.push({
3445
+ assetKind: "preset",
3446
+ assetName: preset.name,
3447
+ depKind: item.kind,
3448
+ depName: item.publisher ? `@${item.publisher}/${item.name}` : item.name,
3449
+ inRepos: g.resolvable
3450
+ });
3451
+ }
2476
3452
  }
2477
3453
  return issues;
2478
3454
  }
3455
+ /**
3456
+ * Everything the lock does not cover or contradicts, in one list: the set
3457
+ * `skaile check`, `skaile install --locked` and `skaile outdated` all report.
3458
+ *
3459
+ * - `declared` — a `skaile.yaml` dependency with no lock entry (library or source gone,
3460
+ * unresolvable ref, never installed);
3461
+ * - `invalid` — a `dependencies:` entry that is not a parseable ref;
3462
+ * - `drift` — declared + locked but its deployed files are missing;
3463
+ * - `pin` — a declared `#pin` that no lock entry for that asset satisfies;
3464
+ * - `requires` — a deployed asset's `requires` that is not deployed (so not locked either);
3465
+ * - `unresolved` — a lock-managed asset that no source or library holds any more.
3466
+ *
3467
+ * Project mode only: a global manager (`global: true`) returns `[]`. A project without
3468
+ * `skaile.yaml` has no declared set, so only the `requires` / `unresolved` gaps of
3469
+ * deployed assets can appear.
3470
+ *
3471
+ * @returns One {@link LockGap} per gap; empty when the lock covers everything declared.
3472
+ */
3473
+ async lockGaps() {
3474
+ if (this.global) return [];
3475
+ const gaps = [];
3476
+ const issues = await this.doctor();
3477
+ for (const i of issues) {
3478
+ if (i.unresolved) {
3479
+ const ref = `${i.assetKind}:${i.assetName}`;
3480
+ const reason = `is locked but no source or library provides it any more \u2014 its requirements cannot be checked${i.fix ? ` (${i.fix})` : ""}`;
3481
+ gaps.push({
3482
+ type: "unresolved",
3483
+ ref,
3484
+ reason,
3485
+ message: `${ref} ${reason}`,
3486
+ ...i.fix ? { manualFix: true } : {}
3487
+ });
3488
+ continue;
3489
+ }
3490
+ const asset = `${i.assetKind}:${i.assetName}`;
3491
+ const dep = `${i.depKind}:${i.depName}`;
3492
+ const fix = i.inRepos ? "fixable" : "not found";
3493
+ gaps.push({
3494
+ type: "requires",
3495
+ ref: dep,
3496
+ reason: `is required by ${asset} but neither deployed nor locked (${fix})`,
3497
+ message: `${asset} requires ${dep} (${fix})`
3498
+ });
3499
+ }
3500
+ let declared = [];
3501
+ try {
3502
+ declared = await this.listDeclared();
3503
+ } catch {
3504
+ return gaps;
3505
+ }
3506
+ const unlocked = declared.filter((d) => d.state === "declared").map((d) => d.ref);
3507
+ const hints = await this.publisherChangeHints(unlocked);
3508
+ const lockKeys = Object.keys(this.tryReadLock()?.assets ?? {});
3509
+ for (const d of declared) {
3510
+ if (d.kind === "preset" && issues.some((i) => i.assetKind === "preset" && i.assetName === d.name)) {
3511
+ continue;
3512
+ }
3513
+ let gap;
3514
+ if (d.state === "invalid") {
3515
+ gap = {
3516
+ type: "invalid",
3517
+ reason: "is not a valid asset ref (expected kind:@<publisher>/<name>#<version>)"
3518
+ };
3519
+ } else if (d.state === "drift") {
3520
+ gap = {
3521
+ type: "drift",
3522
+ reason: "is declared and locked but not materialized \u2014 run `skaile install`"
3523
+ };
3524
+ } else if (d.state === "declared") {
3525
+ const hint = hints[d.ref];
3526
+ gap = hint ? { type: "declared", reason: `is declared but does not resolve${hint}`, manualFix: true } : {
3527
+ type: "declared",
3528
+ reason: "is declared but does not resolve \u2014 run `skaile install`"
3529
+ };
3530
+ } else {
3531
+ const want = parseAssetRef(d.ref);
3532
+ if (!want.pin || want.kind === "preset") continue;
3533
+ const held = lockKeys.map((key) => ({ key, id: parseCanonicalLockKey(key) })).filter(
3534
+ ({ id }) => id.kind === want.kind && id.name === want.name && (!want.publisher || id.publisher === want.publisher)
3535
+ );
3536
+ if (held.length > 0 && !held.some(({ id }) => !id.version || matchPin(want.pin, id.version))) {
3537
+ gap = {
3538
+ type: "pin",
3539
+ reason: `is pinned in skaile.yaml but the lock holds ${held.map((h) => h.key).join(", ")} \u2014 run \`skaile install\``
3540
+ };
3541
+ }
3542
+ }
3543
+ if (gap) gaps.push({ ...gap, ref: d.ref, message: `${d.ref} ${gap.reason}` });
3544
+ }
3545
+ return gaps;
3546
+ }
3547
+ /**
3548
+ * Items of each declared `preset:` dependency that the lock does not cover.
3549
+ *
3550
+ * A preset is never locked itself; the lock lists only its expanded items, so an
3551
+ * item missing from the lock never resolved (or was declared after the last
3552
+ * install). Presets are read from the local sources and libraries (nested presets
3553
+ * followed); one only a store holds cannot be read without the network and
3554
+ * reports no gaps. Without a lock nothing is reported: `list` and `check` already
3555
+ * show a never-installed declaration as `declared`.
3556
+ *
3557
+ * @param declared - The `dependencies:` refs as written in `skaile.yaml`.
3558
+ * @param lock - The current lock, or `null` when none exists.
3559
+ * @returns Declared preset ref → its uncovered items; `resolvable` is `true` when a
3560
+ * local source or library offers the item (so `skaile install` can fix it).
3561
+ * Presets without gaps are absent.
3562
+ */
3563
+ async declaredPresetGaps(declared, lock) {
3564
+ const gaps = /* @__PURE__ */ new Map();
3565
+ if (!lock) return gaps;
3566
+ const presetRefs = declared.filter((d) => parseRefTolerant(d)?.kind === "preset");
3567
+ if (presetRefs.length === 0) return gaps;
3568
+ const { repositories } = this.loadConfig();
3569
+ const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
3570
+ projectDir: this.projectDir,
3571
+ knownKinds: this.knownKinds
3572
+ });
3573
+ const lockKeys = Object.keys(lock.assets).map(parseCanonicalLockKey);
3574
+ const sourceCandidate = (ref) => {
3575
+ const p = parseRefTolerant(ref);
3576
+ if (!p?.publisher) return void 0;
3577
+ const hash = ref.indexOf("#");
3578
+ const pin = p.pin ?? (hash >= 0 ? ref.slice(hash + 1) : void 0);
3579
+ return (provenanceIndex.get(`${p.publisher}/${p.kind}:${p.name}`) ?? []).filter((c) => matchPin(pin, c.version)).sort((a, b) => {
3580
+ const va = coerceVersion(a.version);
3581
+ const vb = coerceVersion(b.version);
3582
+ return gt(va, vb) ? -1 : gt(vb, va) ? 1 : 0;
3583
+ })[0];
3584
+ };
3585
+ const itemsOf = async (ref) => {
3586
+ const src = sourceCandidate(ref);
3587
+ if (src) return (src.deps ?? []).map((d) => qualifyDepRef(d, src.publisher));
3588
+ const entry = (await this.resolveLibraryEntries([ref], { quiet: true })).entries[0];
3589
+ return entry ? libraryEntryDepRefs(entry) : null;
3590
+ };
3591
+ const resolvableLocally = async (ref) => sourceCandidate(ref) !== void 0 || (await this.resolveLibraryEntries([ref], { quiet: true })).resolved.has(ref);
3592
+ const isLocked = (item) => lockKeys.some(
3593
+ (k) => k.kind === item.kind && k.name === item.name && (!item.publisher || k.publisher === item.publisher)
3594
+ );
3595
+ for (const presetRef of presetRefs) {
3596
+ const out = [];
3597
+ const visited = /* @__PURE__ */ new Set();
3598
+ const walk = async (ref) => {
3599
+ if (visited.has(ref)) return;
3600
+ visited.add(ref);
3601
+ for (const item of await itemsOf(ref) ?? []) {
3602
+ const p = parseRefTolerant(item);
3603
+ if (!p) continue;
3604
+ if (p.kind === "preset") await walk(item);
3605
+ else if (!isLocked(p)) out.push({ ref: item, resolvable: await resolvableLocally(item) });
3606
+ }
3607
+ };
3608
+ await walk(presetRef);
3609
+ if (out.length > 0) gaps.set(presetRef, out);
3610
+ }
3611
+ return gaps;
3612
+ }
2479
3613
  // ── Scaffold ───────────────────────────────────────────────────────────
2480
3614
  /**
2481
3615
  * Create a minimal asset scaffold directory (delegates to {@link createScaffold}).
@@ -2485,7 +3619,7 @@ var AssetManager = class {
2485
3619
  * @param destDir - Parent directory to create the asset in.
2486
3620
  * @param opts - Optional scaffold content (`description`).
2487
3621
  * @returns `{ ok: true, path }` on success, `{ ok: false, path: errorMessage }` if the
2488
- * directory already exists.
3622
+ * directory already exists or `kind` has no scaffold template.
2489
3623
  */
2490
3624
  create(name, kind, destDir, opts) {
2491
3625
  return createScaffold(name, kind, destDir, opts);
@@ -2544,15 +3678,21 @@ var AssetManager = class {
2544
3678
  const { kind, name } = parseCanonicalLockKey(ref);
2545
3679
  const component = kind ? this.installTarget(kind) : void 0;
2546
3680
  if (!kind || !name || !component) continue;
2547
- const dest = deployedDir(component, name, this.driverTarget, this.global, this.projectDir);
2548
- try {
2549
- const stat = lstatSync(dest);
2550
- if (stat.isSymbolicLink()) unlinkSync(dest);
2551
- else rmSync(dest, { recursive: true, force: true });
2552
- removed.push(ref);
2553
- } catch {
2554
- removed.push(ref);
3681
+ for (const dest of deployedPaths(
3682
+ component,
3683
+ name,
3684
+ this.driverTarget,
3685
+ this.global,
3686
+ this.projectDir
3687
+ )) {
3688
+ try {
3689
+ const stat = lstatSync(dest);
3690
+ if (stat.isSymbolicLink()) unlinkSync(dest);
3691
+ else rmSync(dest, { recursive: true, force: true });
3692
+ } catch {
3693
+ }
2555
3694
  }
3695
+ removed.push(ref);
2556
3696
  }
2557
3697
  return removed;
2558
3698
  }
@@ -2613,15 +3753,44 @@ var AssetManager = class {
2613
3753
  return;
2614
3754
  }
2615
3755
  const raw = parse(readFileSync(configPath, "utf8")) ?? {};
2616
- if (!raw.dependencies) raw.dependencies = [];
2617
- if (!raw.dependencies.includes(ref)) {
2618
- raw.dependencies.push(ref);
2619
- writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
3756
+ if (!Array.isArray(raw.dependencies)) raw.dependencies = [];
3757
+ const deps = raw.dependencies;
3758
+ if (deps.includes(ref) && deps.filter((d) => this.sameDeclaredAsset(d, ref)).length === 1) {
3759
+ return;
3760
+ }
3761
+ const next = [];
3762
+ let placed = false;
3763
+ for (const dep of deps) {
3764
+ if (!this.sameDeclaredAsset(dep, ref)) {
3765
+ next.push(dep);
3766
+ } else if (!placed) {
3767
+ next.push(ref);
3768
+ placed = true;
3769
+ }
2620
3770
  }
3771
+ if (!placed) next.push(ref);
3772
+ raw.dependencies = next;
3773
+ writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
3774
+ }
3775
+ /**
3776
+ * Whether the declared `dep` names the same asset (kind, publisher, name) as
3777
+ * `ref`, ignoring the version pin. A publisher must agree on both sides —
3778
+ * unlike {@link removeDependencyFromConfig}, a bare `kind:name` never replaces a
3779
+ * publisher-qualified line. Unparseable entries only match on string equality.
3780
+ */
3781
+ sameDeclaredAsset(dep, ref) {
3782
+ if (typeof dep !== "string") return false;
3783
+ if (dep === ref) return true;
3784
+ const a = parseAssetRefLoose(dep);
3785
+ const b = parseAssetRefLoose(ref);
3786
+ if (!a || !b) return false;
3787
+ return a.kind === b.kind && a.name === b.name && a.publisher?.replace(/^@/, "") === b.publisher?.replace(/^@/, "");
2621
3788
  }
2622
3789
  /**
2623
3790
  * Remove a dependency ref from the manifest `dependencies:`. Targets the same
2624
3791
  * file as {@link addDependencyToConfig} (`~/.skaile/skaile.yaml` in global mode).
3792
+ *
3793
+ * @returns `true` when a declaration was removed.
2625
3794
  */
2626
3795
  removeDependencyFromConfig(target) {
2627
3796
  const configPath = join(this.stateRoot, "skaile.yaml");
@@ -2629,15 +3798,15 @@ var AssetManager = class {
2629
3798
  const raw = parse(readFileSync(configPath, "utf8")) ?? {};
2630
3799
  if (!Array.isArray(raw.dependencies)) return false;
2631
3800
  const wantPub = target.publisher?.replace(/^@/, "");
2632
- const idx = raw.dependencies.findIndex((dep) => {
2633
- if (typeof dep !== "string") return false;
3801
+ const before = raw.dependencies.length;
3802
+ raw.dependencies = raw.dependencies.filter((dep) => {
3803
+ if (typeof dep !== "string") return true;
2634
3804
  const p = parseAssetRefLoose(dep);
2635
- if (!p || p.kind !== target.kind || p.name !== target.name) return false;
3805
+ if (!p || p.kind !== target.kind || p.name !== target.name) return true;
2636
3806
  const havePub = p.publisher?.replace(/^@/, "");
2637
- return wantPub === void 0 || havePub === void 0 || havePub === wantPub;
3807
+ return !(wantPub === void 0 || havePub === void 0 || havePub === wantPub);
2638
3808
  });
2639
- if (idx === -1) return false;
2640
- raw.dependencies.splice(idx, 1);
3809
+ if (raw.dependencies.length === before) return false;
2641
3810
  writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
2642
3811
  return true;
2643
3812
  }
@@ -2659,5 +3828,5 @@ var AssetManager = class {
2659
3828
  };
2660
3829
 
2661
3830
  export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, installAgentInstances, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName };
2662
- //# sourceMappingURL=chunk-PTO2YDDG.js.map
2663
- //# sourceMappingURL=chunk-PTO2YDDG.js.map
3831
+ //# sourceMappingURL=chunk-H7QUDDKH.js.map
3832
+ //# sourceMappingURL=chunk-H7QUDDKH.js.map