@skaile/workspaces 5.8.0 → 5.10.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 (348) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/app-sdk/index.js +1 -1
  3. package/dist/{asset-feeds-EYRZR4K5.js → asset-feeds-ELEBTNUS.js} +18 -18
  4. package/dist/{asset-feeds-EYRZR4K5.js.map → asset-feeds-ELEBTNUS.js.map} +1 -1
  5. package/dist/asset-manager/catalog-deployer.js +8 -8
  6. package/dist/asset-manager/index.js +14 -14
  7. package/dist/asset-manager/installer.js +10 -10
  8. package/dist/asset-manager/renderers.js +9 -9
  9. package/dist/asset-manager/src/index.d.ts +300 -36
  10. package/dist/asset-manager/src/index.d.ts.map +1 -1
  11. package/dist/asset-manager/src/install-agent.d.ts +3 -1
  12. package/dist/asset-manager/src/install-agent.d.ts.map +1 -1
  13. package/dist/asset-manager/src/install-instances.d.ts +31 -1
  14. package/dist/asset-manager/src/install-instances.d.ts.map +1 -1
  15. package/dist/asset-manager/src/installer.d.ts +20 -0
  16. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  17. package/dist/asset-manager/src/renderers.d.ts +4 -5
  18. package/dist/asset-manager/src/renderers.d.ts.map +1 -1
  19. package/dist/bridge/drivers/claude-sdk.js +170 -17
  20. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  21. package/dist/bridge/drivers/codex.js +13 -11
  22. package/dist/bridge/drivers/codex.js.map +1 -1
  23. package/dist/bridge/drivers/continue.js +10 -10
  24. package/dist/bridge/drivers/echo.js +4 -4
  25. package/dist/bridge/drivers/gemini.js +7 -7
  26. package/dist/bridge/drivers/goose.js +10 -10
  27. package/dist/bridge/drivers/omp.js +7 -5
  28. package/dist/bridge/drivers/omp.js.map +1 -1
  29. package/dist/bridge/drivers/opencode.js +10 -10
  30. package/dist/bridge/drivers/qwen.js +7 -7
  31. package/dist/bridge/index.js +14 -14
  32. package/dist/bridge/src/codex/managed-driver.d.ts.map +1 -1
  33. package/dist/bridge/src/drivers/claude-sdk.d.ts +31 -1
  34. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  35. package/dist/bridge/src/drivers/claude-tool-allowlist.d.ts +43 -0
  36. package/dist/bridge/src/drivers/claude-tool-allowlist.d.ts.map +1 -0
  37. package/dist/bridge/src/drivers/omp.d.ts.map +1 -1
  38. package/dist/bridge/src/types.d.ts +20 -1
  39. package/dist/bridge/src/types.d.ts.map +1 -1
  40. package/dist/{chunk-36AFURKZ.js → chunk-2FMEGSBN.js} +38 -11
  41. package/dist/chunk-2FMEGSBN.js.map +1 -0
  42. package/dist/{chunk-GCICGXDD.js → chunk-2H73V6XR.js} +3 -3
  43. package/dist/{chunk-GCICGXDD.js.map → chunk-2H73V6XR.js.map} +1 -1
  44. package/dist/{chunk-H7QUDDKH.js → chunk-2JMLDZLP.js} +1069 -182
  45. package/dist/chunk-2JMLDZLP.js.map +1 -0
  46. package/dist/{chunk-B6GIW55K.js → chunk-32XKCNCL.js} +6 -3
  47. package/dist/chunk-32XKCNCL.js.map +1 -0
  48. package/dist/{chunk-4MQS7CAF.js → chunk-3BEY4WK3.js} +371 -175
  49. package/dist/chunk-3BEY4WK3.js.map +1 -0
  50. package/dist/{chunk-SH5D7U7N.js → chunk-3KHYGOEY.js} +3 -3
  51. package/dist/{chunk-SH5D7U7N.js.map → chunk-3KHYGOEY.js.map} +1 -1
  52. package/dist/{chunk-CGT45WN7.js → chunk-3MKPZZRE.js} +3 -3
  53. package/dist/{chunk-CGT45WN7.js.map → chunk-3MKPZZRE.js.map} +1 -1
  54. package/dist/{chunk-QVNRCDTV.js → chunk-4OV5DSBB.js} +4 -4
  55. package/dist/chunk-4OV5DSBB.js.map +1 -0
  56. package/dist/{chunk-GXXFLBUQ.js → chunk-4P3KTUZH.js} +33 -33
  57. package/dist/chunk-4P3KTUZH.js.map +1 -0
  58. package/dist/{chunk-I6NLLJHZ.js → chunk-5QI2JYJC.js} +2 -2
  59. package/dist/{chunk-I6NLLJHZ.js.map → chunk-5QI2JYJC.js.map} +1 -1
  60. package/dist/{chunk-EF7NPDDU.js → chunk-6HHE63CO.js} +31 -17
  61. package/dist/chunk-6HHE63CO.js.map +1 -0
  62. package/dist/{chunk-FTJJO3EM.js → chunk-7CR6HT2X.js} +10 -10
  63. package/dist/{chunk-FTJJO3EM.js.map → chunk-7CR6HT2X.js.map} +1 -1
  64. package/dist/{chunk-FZNG4V4J.js → chunk-7I5VRVDP.js} +3 -3
  65. package/dist/{chunk-FZNG4V4J.js.map → chunk-7I5VRVDP.js.map} +1 -1
  66. package/dist/{chunk-HG34RNTK.js → chunk-CLTHEKHR.js} +10 -9
  67. package/dist/chunk-CLTHEKHR.js.map +1 -0
  68. package/dist/{chunk-UMVKJM4Q.js → chunk-DALV4HHP.js} +18 -3
  69. package/dist/chunk-DALV4HHP.js.map +1 -0
  70. package/dist/{chunk-F3RDEH5U.js → chunk-F4NJDSR7.js} +6 -5
  71. package/dist/chunk-F4NJDSR7.js.map +1 -0
  72. package/dist/{chunk-LSXHED6I.js → chunk-FRPBSUFH.js} +6 -6
  73. package/dist/{chunk-LSXHED6I.js.map → chunk-FRPBSUFH.js.map} +1 -1
  74. package/dist/{chunk-W5DWWE4Y.js → chunk-G54USEUP.js} +4 -4
  75. package/dist/{chunk-W5DWWE4Y.js.map → chunk-G54USEUP.js.map} +1 -1
  76. package/dist/{chunk-Y7QA2OLD.js → chunk-GEWAIQZ5.js} +24 -20
  77. package/dist/chunk-GEWAIQZ5.js.map +1 -0
  78. package/dist/{chunk-GB7WDMH2.js → chunk-GFCXNFTW.js} +4 -4
  79. package/dist/{chunk-GB7WDMH2.js.map → chunk-GFCXNFTW.js.map} +1 -1
  80. package/dist/{chunk-AWQYXZVG.js → chunk-HJ5MCB3L.js} +28 -4
  81. package/dist/chunk-HJ5MCB3L.js.map +1 -0
  82. package/dist/{chunk-2LWYNQK7.js → chunk-HOHUEKES.js} +3 -3
  83. package/dist/{chunk-2LWYNQK7.js.map → chunk-HOHUEKES.js.map} +1 -1
  84. package/dist/{chunk-ZKSJMZKD.js → chunk-IJHH4DSW.js} +8 -8
  85. package/dist/{chunk-ZKSJMZKD.js.map → chunk-IJHH4DSW.js.map} +1 -1
  86. package/dist/{chunk-BTE2V4IW.js → chunk-IXWODEED.js} +40 -7
  87. package/dist/chunk-IXWODEED.js.map +1 -0
  88. package/dist/{chunk-OFBIWVCR.js → chunk-JFXQEVLF.js} +3 -3
  89. package/dist/{chunk-OFBIWVCR.js.map → chunk-JFXQEVLF.js.map} +1 -1
  90. package/dist/{chunk-JAKOMFC6.js → chunk-JZCSNEHK.js} +77 -31
  91. package/dist/chunk-JZCSNEHK.js.map +1 -0
  92. package/dist/{chunk-CKQEIY43.js → chunk-MVQ3Q2HM.js} +4 -4
  93. package/dist/{chunk-CKQEIY43.js.map → chunk-MVQ3Q2HM.js.map} +1 -1
  94. package/dist/{chunk-LEMHVDET.js → chunk-NRDSGLCQ.js} +188 -33
  95. package/dist/chunk-NRDSGLCQ.js.map +1 -0
  96. package/dist/{chunk-V2DXZYBB.js → chunk-NWDSLXOX.js} +3 -3
  97. package/dist/{chunk-V2DXZYBB.js.map → chunk-NWDSLXOX.js.map} +1 -1
  98. package/dist/{chunk-6GRX5L6A.js → chunk-OBVJ7RPW.js} +4 -4
  99. package/dist/{chunk-6GRX5L6A.js.map → chunk-OBVJ7RPW.js.map} +1 -1
  100. package/dist/{chunk-PTSGWKHR.js → chunk-OOWHXOK6.js} +4 -4
  101. package/dist/{chunk-PTSGWKHR.js.map → chunk-OOWHXOK6.js.map} +1 -1
  102. package/dist/{chunk-YSHVQGUS.js → chunk-PE36IF2D.js} +119 -30
  103. package/dist/chunk-PE36IF2D.js.map +1 -0
  104. package/dist/{chunk-S53LOR7B.js → chunk-QG3HOJEK.js} +3 -3
  105. package/dist/{chunk-S53LOR7B.js.map → chunk-QG3HOJEK.js.map} +1 -1
  106. package/dist/{chunk-2FRUUDZY.js → chunk-SBFW3CJH.js} +5 -5
  107. package/dist/{chunk-2FRUUDZY.js.map → chunk-SBFW3CJH.js.map} +1 -1
  108. package/dist/{chunk-UAROCOXF.js → chunk-SX5U422E.js} +3 -3
  109. package/dist/{chunk-UAROCOXF.js.map → chunk-SX5U422E.js.map} +1 -1
  110. package/dist/{chunk-QOP6KDFR.js → chunk-TH4CTXHX.js} +6 -6
  111. package/dist/{chunk-QOP6KDFR.js.map → chunk-TH4CTXHX.js.map} +1 -1
  112. package/dist/{chunk-F2EKONM7.js → chunk-TRSQP3JD.js} +3 -3
  113. package/dist/{chunk-F2EKONM7.js.map → chunk-TRSQP3JD.js.map} +1 -1
  114. package/dist/{chunk-FVJK4QWM.js → chunk-V5TZH4GF.js} +4 -4
  115. package/dist/{chunk-FVJK4QWM.js.map → chunk-V5TZH4GF.js.map} +1 -1
  116. package/dist/{chunk-HYDRDNKN.js → chunk-V7PQZOBR.js} +5 -5
  117. package/dist/{chunk-HYDRDNKN.js.map → chunk-V7PQZOBR.js.map} +1 -1
  118. package/dist/{chunk-IONAUNWB.js → chunk-VLXDXRRX.js} +3 -3
  119. package/dist/{chunk-IONAUNWB.js.map → chunk-VLXDXRRX.js.map} +1 -1
  120. package/dist/{chunk-CIFS5K6T.js → chunk-VMKMFS2Q.js} +28 -7
  121. package/dist/chunk-VMKMFS2Q.js.map +1 -0
  122. package/dist/{chunk-Y4N2NUZD.js → chunk-WBYDAL6X.js} +4 -4
  123. package/dist/{chunk-Y4N2NUZD.js.map → chunk-WBYDAL6X.js.map} +1 -1
  124. package/dist/{chunk-WTQ3TATB.js → chunk-X4BAYGMT.js} +3 -3
  125. package/dist/{chunk-WTQ3TATB.js.map → chunk-X4BAYGMT.js.map} +1 -1
  126. package/dist/{chunk-NPLPCRW5.js → chunk-XCNK4UE5.js} +5 -5
  127. package/dist/{chunk-NPLPCRW5.js.map → chunk-XCNK4UE5.js.map} +1 -1
  128. package/dist/{chunk-OJ4N4T4H.js → chunk-XDVR7QIT.js} +20 -6
  129. package/dist/chunk-XDVR7QIT.js.map +1 -0
  130. package/dist/{chunk-GTILM2CI.js → chunk-Y34MSWEZ.js} +43 -12
  131. package/dist/chunk-Y34MSWEZ.js.map +1 -0
  132. package/dist/{chunk-4PTSR4UU.js → chunk-Y63UQZ7P.js} +3 -3
  133. package/dist/{chunk-4PTSR4UU.js.map → chunk-Y63UQZ7P.js.map} +1 -1
  134. package/dist/chunk-YBQONMH2.js +465 -0
  135. package/dist/chunk-YBQONMH2.js.map +1 -0
  136. package/dist/{chunk-P23M65LB.js → chunk-Z2K6BGUR.js} +71 -24
  137. package/dist/chunk-Z2K6BGUR.js.map +1 -0
  138. package/dist/{chunk-BJNUNQAW.js → chunk-ZGQTS3Y4.js} +3 -3
  139. package/dist/{chunk-BJNUNQAW.js.map → chunk-ZGQTS3Y4.js.map} +1 -1
  140. package/dist/cli/index.js +913 -720
  141. package/dist/cli/index.js.map +1 -1
  142. package/dist/cli/src/commands/asset-cmd.d.ts.map +1 -1
  143. package/dist/cli/src/commands/catalog.d.ts +8 -5
  144. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  145. package/dist/cli/src/commands/diff.d.ts +3 -1
  146. package/dist/cli/src/commands/diff.d.ts.map +1 -1
  147. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  148. package/dist/cli/src/commands/library-index.d.ts +34 -2
  149. package/dist/cli/src/commands/library-index.d.ts.map +1 -1
  150. package/dist/cli/src/commands/library-publish.d.ts.map +1 -1
  151. package/dist/cli/src/commands/manage.d.ts +6 -5
  152. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  153. package/dist/cli/src/commands/migrate-assets.d.ts.map +1 -1
  154. package/dist/cli/src/commands/outdated.d.ts +7 -5
  155. package/dist/cli/src/commands/outdated.d.ts.map +1 -1
  156. package/dist/cli/src/commands/project.d.ts +11 -8
  157. package/dist/cli/src/commands/project.d.ts.map +1 -1
  158. package/dist/cli/src/commands/source.d.ts +30 -1
  159. package/dist/cli/src/commands/source.d.ts.map +1 -1
  160. package/dist/cli/src/commands/update.d.ts.map +1 -1
  161. package/dist/cli/src/commands/validate.d.ts.map +1 -1
  162. package/dist/cli/src/commands/why.d.ts.map +1 -1
  163. package/dist/cli/src/skill-checks.d.ts +47 -2
  164. package/dist/cli/src/skill-checks.d.ts.map +1 -1
  165. package/dist/client/index.js +1 -1
  166. package/dist/client/src/client.d.ts +10 -3
  167. package/dist/client/src/client.d.ts.map +1 -1
  168. package/dist/connectors/config.js +5 -5
  169. package/dist/connectors/index.js +18 -18
  170. package/dist/{connectors-O6Y6VESP.js → connectors-OXP2JBGI.js} +21 -21
  171. package/dist/{connectors-O6Y6VESP.js.map → connectors-OXP2JBGI.js.map} +1 -1
  172. package/dist/connectors-shared/index.js +2 -2
  173. package/dist/core/index.js +7 -7
  174. package/dist/core/logging.js +1 -1
  175. package/dist/core/manifest.js +2 -2
  176. package/dist/core/models.js +1 -1
  177. package/dist/core/publish-manifest.js +2 -2
  178. package/dist/core/ref.js +1 -1
  179. package/dist/core/runtime-assets.js +5 -5
  180. package/dist/core/src/index.d.ts +2 -1
  181. package/dist/core/src/index.d.ts.map +1 -1
  182. package/dist/core/src/lock.d.ts +5 -2
  183. package/dist/core/src/lock.d.ts.map +1 -1
  184. package/dist/core/src/logging/sinks/sqlite-runtime.d.ts.map +1 -1
  185. package/dist/core/src/manifest.d.ts.map +1 -1
  186. package/dist/core/src/models.d.ts +21 -5
  187. package/dist/core/src/models.d.ts.map +1 -1
  188. package/dist/core/src/publish-manifest.d.ts +19 -3
  189. package/dist/core/src/publish-manifest.d.ts.map +1 -1
  190. package/dist/core/src/repo-manager.d.ts.map +1 -1
  191. package/dist/core/src/requirements.d.ts +26 -0
  192. package/dist/core/src/requirements.d.ts.map +1 -0
  193. package/dist/core/src/walker.d.ts.map +1 -1
  194. package/dist/core/src/workspace-config.d.ts +18 -0
  195. package/dist/core/src/workspace-config.d.ts.map +1 -1
  196. package/dist/core/src/workspace-yaml-editor.d.ts +4 -1
  197. package/dist/core/src/workspace-yaml-editor.d.ts.map +1 -1
  198. package/dist/core/workspace-config.js +4 -4
  199. package/dist/deploy/index.js +7 -7
  200. package/dist/discovery/index.js +5 -5
  201. package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
  202. package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
  203. package/dist/discovery/src/discover.d.ts.map +1 -1
  204. package/dist/discovery/src/source-config.d.ts.map +1 -1
  205. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  206. package/dist/{ensure-sources-SKLNM4UZ.js → ensure-sources-5ISVIA4T.js} +17 -17
  207. package/dist/{ensure-sources-SKLNM4UZ.js.map → ensure-sources-5ISVIA4T.js.map} +1 -1
  208. package/dist/factory-assets/connectors/box.js +18 -18
  209. package/dist/factory-assets/connectors/deploy.js +18 -18
  210. package/dist/factory-assets/connectors/devserver.js +18 -18
  211. package/dist/factory-assets/connectors/flow/adapter.js +18 -18
  212. package/dist/factory-assets/connectors/flow/engine.js +8 -8
  213. package/dist/factory-assets/connectors/flow/run-flow.js +20 -20
  214. package/dist/factory-assets/connectors/flow.js +18 -18
  215. package/dist/factory-assets/connectors/git.js +18 -18
  216. package/dist/factory-assets/connectors/gmail.js +18 -18
  217. package/dist/factory-assets/connectors/googledrive.js +18 -18
  218. package/dist/factory-assets/connectors/local.js +18 -18
  219. package/dist/factory-assets/connectors/mattermost.js +18 -18
  220. package/dist/factory-assets/connectors/memory.js +18 -18
  221. package/dist/factory-assets/connectors/minio.js +18 -18
  222. package/dist/factory-assets/connectors/postgres.js +18 -18
  223. package/dist/factory-assets/connectors/s3.js +18 -18
  224. package/dist/factory-assets/connectors/sharepoint.js +18 -18
  225. package/dist/factory-assets/connectors/sqlite.js +18 -18
  226. package/dist/factory-assets/connectors/static-server.js +18 -18
  227. package/dist/factory-assets/connectors/tunnel.js +18 -18
  228. package/dist/factory-assets/connectors/webdav.js +18 -18
  229. package/dist/factory-assets/connectors/xstate-store.js +18 -18
  230. package/dist/factory-assets/connectors/xstate.js +18 -18
  231. package/dist/factory-assets/skills/authoring-skaile-manifests/SKILL.md +9 -3
  232. package/dist/{flows-CBYRSAGS.js → flows-HMSE76PO.js} +12 -12
  233. package/dist/{flows-CBYRSAGS.js.map → flows-HMSE76PO.js.map} +1 -1
  234. package/dist/{helpers-MWZDRBLD.js → helpers-X6URBGSF.js} +17 -17
  235. package/dist/{helpers-MWZDRBLD.js.map → helpers-X6URBGSF.js.map} +1 -1
  236. package/dist/library/index.js +11 -11
  237. package/dist/library/install.js +3 -3
  238. package/dist/library/src/index.d.ts +2 -1
  239. package/dist/library/src/index.d.ts.map +1 -1
  240. package/dist/library/src/local/library-dir.d.ts +42 -0
  241. package/dist/library/src/local/library-dir.d.ts.map +1 -0
  242. package/dist/library/src/local/library-manager.d.ts +10 -1
  243. package/dist/library/src/local/library-manager.d.ts.map +1 -1
  244. package/dist/library/src/local/library.d.ts +2 -2
  245. package/dist/library/src/local/library.d.ts.map +1 -1
  246. package/dist/library/src/local/local-catalog-source.d.ts +9 -0
  247. package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
  248. package/dist/library/src/manifest-writer.d.ts +55 -4
  249. package/dist/library/src/manifest-writer.d.ts.map +1 -1
  250. package/dist/library/src/remote/cache.d.ts +18 -1
  251. package/dist/library/src/remote/cache.d.ts.map +1 -1
  252. package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
  253. package/dist/library/src/remote/rest-catalog-source.d.ts.map +1 -1
  254. package/dist/library/src/sync/git-driver.d.ts +5 -6
  255. package/dist/library/src/sync/git-driver.d.ts.map +1 -1
  256. package/dist/{library-publish-PICMDSB2.js → library-publish-OS3JDW4S.js} +123 -41
  257. package/dist/library-publish-OS3JDW4S.js.map +1 -0
  258. package/dist/open-library-4QSUORI6.js +40 -0
  259. package/dist/{open-library-OG457THY.js.map → open-library-4QSUORI6.js.map} +1 -1
  260. package/dist/open-registry-VCKS3MP5.js +32 -0
  261. package/dist/{open-registry-ZUZJ7MS7.js.map → open-registry-VCKS3MP5.js.map} +1 -1
  262. package/dist/paths-BVO56A25.js +22 -0
  263. package/dist/{paths-FQ64OLJG.js.map → paths-BVO56A25.js.map} +1 -1
  264. package/dist/{plugin-store-MPAMD5OT.js → plugin-store-X7GPWGBG.js} +9 -9
  265. package/dist/{plugin-store-MPAMD5OT.js.map → plugin-store-X7GPWGBG.js.map} +1 -1
  266. package/dist/{project-2GFT5LEZ.js → project-PAXP46RD.js} +20 -20
  267. package/dist/{project-2GFT5LEZ.js.map → project-PAXP46RD.js.map} +1 -1
  268. package/dist/resolver/index.js +8 -8
  269. package/dist/runner/index.js +32 -32
  270. package/dist/runner/src/agent.d.ts.map +1 -1
  271. package/dist/runner/src/context-usage/compose.d.ts.map +1 -1
  272. package/dist/runner/src/platform-blocks.d.ts +55 -0
  273. package/dist/runner/src/platform-blocks.d.ts.map +1 -0
  274. package/dist/runner/src/serve.d.ts.map +1 -1
  275. package/dist/runner/src/session-builder.d.ts.map +1 -1
  276. package/dist/sdk/asset-manager.js +14 -14
  277. package/dist/sdk/bridge.js +14 -14
  278. package/dist/sdk/client.js +1 -1
  279. package/dist/sdk/core.js +7 -7
  280. package/dist/sdk/flow.js +8 -8
  281. package/dist/sdk/index.js +33 -33
  282. package/dist/sdk/resolver.js +8 -8
  283. package/dist/sdk/runner.js +32 -32
  284. package/dist/sdk/session.js +2 -2
  285. package/dist/sdk/types.js +1 -1
  286. package/dist/session/index.js +2 -2
  287. package/dist/session/src/dispatcher.d.ts +25 -4
  288. package/dist/session/src/dispatcher.d.ts.map +1 -1
  289. package/dist/session/src/index.d.ts +1 -1
  290. package/dist/session/src/index.d.ts.map +1 -1
  291. package/dist/{setup-WHMCSIF2.js → setup-BV55VHWC.js} +24 -24
  292. package/dist/{setup-WHMCSIF2.js.map → setup-BV55VHWC.js.map} +1 -1
  293. package/dist/{skills-TVO26Y4V.js → skills-EDFUCJPX.js} +13 -13
  294. package/dist/{skills-TVO26Y4V.js.map → skills-EDFUCJPX.js.map} +1 -1
  295. package/dist/source-E4VAT5RZ.js +41 -0
  296. package/dist/{source-LKOFTOEK.js.map → source-E4VAT5RZ.js.map} +1 -1
  297. package/dist/{source-manifest-publish-L5J3QXER.js → source-manifest-publish-CLEE6TNH.js} +23 -23
  298. package/dist/{source-manifest-publish-L5J3QXER.js.map → source-manifest-publish-CLEE6TNH.js.map} +1 -1
  299. package/dist/tui/index.js +32 -32
  300. package/dist/types/index.js +1 -1
  301. package/dist/types/manifests.js +1 -1
  302. package/dist/types/src/events.d.ts +1 -1
  303. package/dist/types/src/events.d.ts.map +1 -1
  304. package/dist/types/src/index.d.ts +2 -2
  305. package/dist/types/src/index.d.ts.map +1 -1
  306. package/dist/types/src/manifests/agent.d.ts +2 -0
  307. package/dist/types/src/manifests/agent.d.ts.map +1 -1
  308. package/dist/types/src/manifests/contract.d.ts +2 -0
  309. package/dist/types/src/manifests/contract.d.ts.map +1 -1
  310. package/dist/types/src/manifests/index.d.ts +3 -3
  311. package/dist/types/src/manifests/index.d.ts.map +1 -1
  312. package/dist/types/src/manifests/prompt.d.ts +2 -0
  313. package/dist/types/src/manifests/prompt.d.ts.map +1 -1
  314. package/dist/types/src/protocol.d.ts +43 -1
  315. package/dist/types/src/protocol.d.ts.map +1 -1
  316. package/dist/types/src/version.d.ts +17 -1
  317. package/dist/types/src/version.d.ts.map +1 -1
  318. package/dist/workspace-plugin/adapters/mcp.js +25 -25
  319. package/dist/workspace-plugin/adapters/omp.js +14 -14
  320. package/dist/workspace-plugin/index.js +2 -2
  321. package/package.json +1 -1
  322. package/dist/chunk-36AFURKZ.js.map +0 -1
  323. package/dist/chunk-4MQS7CAF.js.map +0 -1
  324. package/dist/chunk-AWQYXZVG.js.map +0 -1
  325. package/dist/chunk-B6GIW55K.js.map +0 -1
  326. package/dist/chunk-BTE2V4IW.js.map +0 -1
  327. package/dist/chunk-CIFS5K6T.js.map +0 -1
  328. package/dist/chunk-EF7NPDDU.js.map +0 -1
  329. package/dist/chunk-F3RDEH5U.js.map +0 -1
  330. package/dist/chunk-GTILM2CI.js.map +0 -1
  331. package/dist/chunk-GXXFLBUQ.js.map +0 -1
  332. package/dist/chunk-H7QUDDKH.js.map +0 -1
  333. package/dist/chunk-HG34RNTK.js.map +0 -1
  334. package/dist/chunk-JAKOMFC6.js.map +0 -1
  335. package/dist/chunk-LEMHVDET.js.map +0 -1
  336. package/dist/chunk-OJ4N4T4H.js.map +0 -1
  337. package/dist/chunk-P23M65LB.js.map +0 -1
  338. package/dist/chunk-QVNRCDTV.js.map +0 -1
  339. package/dist/chunk-UMVKJM4Q.js.map +0 -1
  340. package/dist/chunk-Y7QA2OLD.js.map +0 -1
  341. package/dist/chunk-YSHVQGUS.js.map +0 -1
  342. package/dist/chunk-ZAOGFXDO.js +0 -112
  343. package/dist/chunk-ZAOGFXDO.js.map +0 -1
  344. package/dist/library-publish-PICMDSB2.js.map +0 -1
  345. package/dist/open-library-OG457THY.js +0 -40
  346. package/dist/open-registry-ZUZJ7MS7.js +0 -32
  347. package/dist/paths-FQ64OLJG.js +0 -22
  348. package/dist/source-LKOFTOEK.js +0 -41
@@ -1,20 +1,21 @@
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';
1
+ import { unregisteredLibraryNames, scanUnlistedAssets, getStoreManifestsDir, missingLibraryDirMessage } from './chunk-3BEY4WK3.js';
2
+ import { readManifestSourceConfig, isManifestMode, discoverAssetsInTree } from './chunk-GEWAIQZ5.js';
3
+ import { installTargetOf, canonicalEntryRef, deployAll, removeAsset, diffDeployedAgent, createScaffold, installAgent } from './chunk-PE36IF2D.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-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';
6
+ import { driverTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-6HHE63CO.js';
7
+ import { resolveConfiguredDriverTarget, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, buildLockFile, writeLock, readLock, verifyLock, mergeLockFile, extractForPatch, generatePatch, savePatch, applyPatch } from './chunk-2FMEGSBN.js';
8
+ import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-GFCXNFTW.js';
9
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';
10
+ import { resolveSkWorkspaceConfig, effectiveAgentInstances, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, buildProvenanceIndex, coerceVersion, sourceShortName, resolveAll, ensureRepo, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, resolveAsset, readLinks, MAX_PRESET_DEPTH, PresetDepthError, DEFAULT_AGENT_INSTANCE_ID, resolveAgentDefinitionDir, storeManifestSlug } from './chunk-JZCSNEHK.js';
11
11
  import { globalSkaileDir, globalCacheRoot, migrateLegacyDir } from './chunk-L5VSIPQD.js';
12
12
  import { DRIVER_TARGETS } from './chunk-CPSIPIX7.js';
13
- import { scanDirectory, parseRequires } from './chunk-GTILM2CI.js';
14
- import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr } from './chunk-QVNRCDTV.js';
13
+ import { loadSkPublishManifest } from './chunk-IXWODEED.js';
14
+ import { scanDirectory, parseRequires, dedupeRequirements } from './chunk-Y34MSWEZ.js';
15
+ import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr } from './chunk-4OV5DSBB.js';
15
16
  import { spawnSync } from 'child_process';
16
17
  import { existsSync, rmSync, readdirSync, lstatSync, unlinkSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
17
- import path, { join, resolve, dirname, sep, relative, posix } from 'path';
18
+ import path, { resolve, dirname, sep, join, relative, posix } from 'path';
18
19
  import { gt } from 'semver';
19
20
  import { stringify, parse } from 'yaml';
20
21
  import fs from 'fs/promises';
@@ -36,9 +37,79 @@ function writeLedger(projectDir, ledger) {
36
37
  writeFileSync(p, `${JSON.stringify(ledger, null, 2)}
37
38
  `, "utf-8");
38
39
  }
40
+ function bindInstance(projectDir, id, instance) {
41
+ const errors = [];
42
+ let srcDir;
43
+ const addHint = instance.ref.startsWith("agent:") ? ` \u2014 add the agent first: run skaile add ${instance.ref}` : "";
44
+ try {
45
+ srcDir = resolveAgentDefinitionDir(projectDir, instance.ref);
46
+ } catch (err) {
47
+ errors.push(
48
+ `agent instance "${id}": ${err instanceof Error ? err.message : String(err)}${addHint}`
49
+ );
50
+ }
51
+ if (srcDir !== void 0 && !existsSync(join(srcDir, "agent.yaml"))) {
52
+ errors.push(
53
+ `agent instance "${id}": ${instance.ref} resolves to ${srcDir}, which has no agent.yaml`
54
+ );
55
+ }
56
+ return { srcDir, errors };
57
+ }
58
+ function unboundAgentInstances(projectDir, driverTarget) {
59
+ if (!driverTargetSupportsAgents(driverTarget)) return [];
60
+ let instances;
61
+ try {
62
+ instances = effectiveAgentInstances(resolveSkWorkspaceConfig(projectDir));
63
+ } catch {
64
+ return [];
65
+ }
66
+ const out = [];
67
+ for (const [id, instance] of Object.entries(instances)) {
68
+ const { errors } = bindInstance(projectDir, id, instance);
69
+ if (errors.length > 0) out.push({ id, ref: instance.ref, error: errors.join("; ") });
70
+ }
71
+ return out;
72
+ }
73
+ function removeAgentInstanceRenderings(opts) {
74
+ const { projectDir, driverTarget, ids } = opts;
75
+ if (!driverTargetSupportsAgents(driverTarget) || ids.length === 0) return [];
76
+ const ledger = readLedger(projectDir);
77
+ const previous = ledger.targets[driverTarget];
78
+ if (!previous) return [];
79
+ const gone = new Set(ids);
80
+ const claimed = new Set(
81
+ Object.entries(previous).filter(([id]) => !gone.has(id)).flatMap(([, files]) => files)
82
+ );
83
+ const agentDeployDir = deployedBase("agent", driverTarget, false, projectDir);
84
+ const removable = [resolve(agentDeployDir), resolve(dirname(agentDeployDir), CODEX_IDENTITY_DIR)];
85
+ const root = resolve(projectDir);
86
+ const removed = [];
87
+ for (const id of gone) {
88
+ for (const rel of previous[id] ?? []) {
89
+ if (claimed.has(rel)) continue;
90
+ const abs = resolve(root, rel);
91
+ if (!removable.some((dir) => abs.startsWith(dir + sep)) || !existsSync(abs)) continue;
92
+ rmSync(abs, { recursive: true, force: true });
93
+ removed.push(rel);
94
+ }
95
+ }
96
+ const owned = Object.fromEntries(Object.entries(previous).filter(([id]) => !gone.has(id)));
97
+ const nextTargets = { ...ledger.targets };
98
+ if (Object.keys(owned).length > 0) nextTargets[driverTarget] = owned;
99
+ else delete nextTargets[driverTarget];
100
+ if (JSON.stringify(nextTargets) !== JSON.stringify(ledger.targets)) {
101
+ writeLedger(projectDir, { version: 1, targets: nextTargets });
102
+ }
103
+ return removed;
104
+ }
39
105
  function installAgentInstances(opts) {
40
106
  const { projectDir, driverTarget, log } = opts;
41
- const result = { instances: [], removed: [], warnings: [] };
107
+ const result = {
108
+ instances: [],
109
+ removed: [],
110
+ warnings: [],
111
+ errors: []
112
+ };
42
113
  if (!driverTargetSupportsAgents(driverTarget)) return result;
43
114
  const wsConfig = opts.wsConfig ?? resolveSkWorkspaceConfig(projectDir);
44
115
  const instances = effectiveAgentInstances(wsConfig);
@@ -48,20 +119,9 @@ function installAgentInstances(opts) {
48
119
  const previous = ledger.targets[driverTarget] ?? {};
49
120
  const owned = {};
50
121
  for (const [id, instance] of Object.entries(instances)) {
51
- let srcDir;
52
- try {
53
- srcDir = resolveAgentDefinitionDir(projectDir, instance.ref);
54
- } catch (err) {
55
- result.warnings.push(
56
- `agent instance "${id}": ${err instanceof Error ? err.message : String(err)}`
57
- );
58
- }
59
- if (srcDir === void 0 || !existsSync(join(srcDir, "agent.yaml"))) {
60
- if (srcDir !== void 0) {
61
- result.warnings.push(
62
- `agent instance "${id}": ${instance.ref} resolves to ${srcDir}, which has no agent.yaml`
63
- );
64
- }
122
+ const { srcDir, errors } = bindInstance(projectDir, id, instance);
123
+ result.errors.push(...errors);
124
+ if (srcDir === void 0 || errors.length > 0) {
65
125
  if (previous[id]) owned[id] = previous[id];
66
126
  continue;
67
127
  }
@@ -273,7 +333,30 @@ async function renderMcpServerConfig(projectDir, driverTarget, opts) {
273
333
 
274
334
  // asset-manager/src/index.ts
275
335
  function normalizeRepoUrl(url) {
276
- return url.trim().replace(/^git@([^:]+):/, "https://$1/").replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
336
+ return url.trim().replace(/^git@([^:]+):/, "https://$1/").replace(/^[a-z]+:\/\//i, "").replace(/\/+$/, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
337
+ }
338
+ function withoutUrlSecrets(url) {
339
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) return url;
340
+ try {
341
+ const parsed = new URL(url);
342
+ if (parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.password) {
343
+ parsed.username = "";
344
+ }
345
+ parsed.password = "";
346
+ parsed.search = "";
347
+ parsed.hash = "";
348
+ return parsed.toString();
349
+ } catch {
350
+ return url.replace(/[?#].*$/, "").replace(/^([a-z][a-z0-9+.-]*:\/\/).*@/i, "$1");
351
+ }
352
+ }
353
+ function libraryRemoteUrl(library) {
354
+ const config = library.backendConfig;
355
+ if (library.backend !== "git" || !("url" in config) || typeof config.url !== "string") {
356
+ return void 0;
357
+ }
358
+ const url = config.url.trim();
359
+ return url ? withoutUrlSecrets(url) : void 0;
277
360
  }
278
361
  function storeOverlayPathForUrl(url) {
279
362
  return join(getStoreManifestsDir(), `${storeManifestSlug(url)}.yaml`);
@@ -375,6 +458,26 @@ function libraryDiskHashes(libPath, cache, registry) {
375
458
  }
376
459
  return hashes;
377
460
  }
461
+ function manifestFileHasAssetsArray(libPath) {
462
+ try {
463
+ const doc = parse(readFileSync(join(libPath, "skaile.manifest.yaml"), "utf8"));
464
+ return Array.isArray(doc?.assets);
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+ function isUnlistedLibraryAsset(entry, libPath) {
470
+ const manifest = loadSkPublishManifest(libPath);
471
+ const listed = manifest?.assets ?? [];
472
+ if (!manifest || manifest.source) return false;
473
+ if (listed.length === 0 && !manifestFileHasAssetsArray(libPath)) return false;
474
+ const file = resolve(entry.source);
475
+ return !listed.some((a) => {
476
+ if (!a.root || a.source) return false;
477
+ const root = resolve(libPath, a.root);
478
+ return file === root || file.startsWith(root + sep);
479
+ });
480
+ }
378
481
  function withLibraryFiles(entry, libPath, cache, registry) {
379
482
  let byManifest = cache.get(libPath);
380
483
  if (!byManifest) {
@@ -539,6 +642,10 @@ function keptLockEntries(prev, missing) {
539
642
  }
540
643
  return out;
541
644
  }
645
+ function presetsLockSlice(presets) {
646
+ const refs = [...new Set(presets.map((p) => p.ref))].sort();
647
+ return refs.length > 0 ? { presets: refs } : {};
648
+ }
542
649
  function withKeptLockEntries(lock, kept) {
543
650
  if (Object.keys(kept.assets).length === 0) return lock;
544
651
  const sources = new Map(lock.sources.map((s) => [s.url, s]));
@@ -567,8 +674,16 @@ function relockedRefs(prev, next) {
567
674
  }
568
675
  return out;
569
676
  }
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}\``;
677
+ function unregisteredLibraryFix(origin, ref) {
678
+ const register = origin.url ? `\`skaile library init ${origin.name} --git ${origin.url}\` (no registered library has the remote ${origin.url} either)` : `\`skaile library init ${origin.name} --path <dir>\``;
679
+ return `register library "${origin.name}" again with ${register}, or drop the dependency with \`skaile remove ${ref}\``;
680
+ }
681
+ function lockedLibraryProblem(lookup, origin, ref) {
682
+ return lookup.state === "dir-missing" ? missingLibraryDirMessage({ name: origin.name, path: lookup.path }) : `library "${origin.name}" is not registered \u2014 renamed or removed? ${unregisteredLibraryFix(origin, ref)}`;
683
+ }
684
+ function gapIdentity(ref) {
685
+ const p = parseRefTolerant(ref);
686
+ return p ? `${p.kind}:${p.name}` : ref;
572
687
  }
573
688
  function memberIdentity(ref) {
574
689
  const parsed = parseRefTolerant(ref.trim());
@@ -726,13 +841,14 @@ var AssetManager = class {
726
841
  }
727
842
  /** Deploy options for one harness. In global mode, `driverTarget` is the
728
843
  * fan-out target (not the constructor's `driverTarget`, which is project-only). */
729
- deployOptsFor(target, link) {
844
+ deployOptsFor(target, link, onSkippedSymlink) {
730
845
  return {
731
846
  registry: this.registry,
732
847
  driverTarget: target,
733
848
  global: this.global,
734
849
  cwd: this.projectDir,
735
- link: link === true
850
+ link: link === true,
851
+ ...onSkippedSymlink ? { onSkippedSymlink: (entry, p) => onSkippedSymlink(canonicalEntryRef(entry), p) } : {}
736
852
  };
737
853
  }
738
854
  /**
@@ -759,8 +875,8 @@ var AssetManager = class {
759
875
  * Render every agent instance of `skaile.yaml` into the driver target's native
760
876
  * agent files (`.claude/agents/<id>.md`, …) and prune the renderings of
761
877
  * instances that left the config. Runs after the skills deploy, so composed
762
- * skills resolve. Project scope only. Never fails an install — a render error
763
- * is warned and swallowed.
878
+ * skills resolve. Project scope only. An instance whose `ref` resolves to no agent is
879
+ * reported in `agentInstanceErrors`; any other render error is warned and swallowed.
764
880
  */
765
881
  renderAgentInstances() {
766
882
  if (this.global) return {};
@@ -769,8 +885,12 @@ var AssetManager = class {
769
885
  projectDir: this.projectDir,
770
886
  driverTarget: this.driverTarget
771
887
  });
772
- for (const w of result.warnings) console.warn(`skaile: ${w}`);
773
- return { agentInstances: result.instances, agentFilesRemoved: result.removed };
888
+ for (const w of [...result.warnings, ...result.errors]) console.warn(`skaile: ${w}`);
889
+ return {
890
+ agentInstances: result.instances,
891
+ agentFilesRemoved: result.removed,
892
+ ...result.errors.length > 0 ? { agentInstanceErrors: result.errors } : {}
893
+ };
774
894
  } catch (err) {
775
895
  console.warn(
776
896
  `skaile: could not render agent instances for ${this.driverTarget}: ${err instanceof Error ? err.message : String(err)}`
@@ -902,6 +1022,8 @@ var AssetManager = class {
902
1022
  *
903
1023
  * @param opts.quiet - Skip the cross-library collision warning (a lookup that only
904
1024
  * builds an error hint must not warn).
1025
+ * @param opts.library - Resolve only from this library (the one a lock entry names), not
1026
+ * from every registered one. Never warns: no choice is made between libraries.
905
1027
  * @returns the matched deploy entries, the set of `missing` refs covered, and
906
1028
  * each entry's originating library name.
907
1029
  */
@@ -914,6 +1036,7 @@ var AssetManager = class {
914
1036
  const { byKey, isDefaultLib, libraryDir } = scan;
915
1037
  const entries = [];
916
1038
  const fileSets = /* @__PURE__ */ new Map();
1039
+ let snap;
917
1040
  const allCandidates = [...byKey.values()].flat();
918
1041
  for (const ref of missing) {
919
1042
  const parsed = parseRefTolerant(ref);
@@ -921,21 +1044,33 @@ var AssetManager = class {
921
1044
  const hash = ref.indexOf("#");
922
1045
  const pin = parsed.pin ?? (hash >= 0 ? ref.slice(hash + 1) : void 0);
923
1046
  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);
1047
+ const pool = (key ? byKey.get(key) ?? [] : allCandidates.filter(
1048
+ (c) => c.entry.kind === parsed.kind && c.entry.name === parsed.name
1049
+ )).filter((c) => !opts.library || c.library === opts.library);
925
1050
  const candidates = pool.filter((c) => !pin || matchPin(pin, c.entry.version ?? ""));
926
1051
  if (candidates.length === 0) continue;
927
1052
  candidates.sort(libraryPreference(isDefaultLib));
928
1053
  const hit = candidates[0];
929
- if (!opts.quiet) {
1054
+ if (!opts.quiet && !opts.library) {
930
1055
  this.warnLibraryCollision(
931
1056
  `${hit.entry.kind}:@${hit.entry.publisher}/${hit.entry.name}`,
932
1057
  candidates,
933
1058
  hit
934
1059
  );
935
1060
  }
936
- withLibraryFiles(hit.entry, libraryDir.get(hit.library), fileSets, this.registry);
937
- entries.push(hit.entry);
938
- libraryOf.set(hit.entry, hit.library);
1061
+ let entry = hit.entry;
1062
+ if (!entry.version) {
1063
+ if (snap === void 0) snap = await this.loadLibraryIndex();
1064
+ const def = newestLibraryDef(snap, hit.library, {
1065
+ kind: entry.kind,
1066
+ publisher: entry.publisher ?? "",
1067
+ name: entry.name
1068
+ });
1069
+ if (def?.version) entry = { ...entry, version: def.version };
1070
+ }
1071
+ withLibraryFiles(entry, libraryDir.get(hit.library), fileSets, this.registry);
1072
+ entries.push(entry);
1073
+ libraryOf.set(entry, hit.library);
939
1074
  resolved.add(ref);
940
1075
  }
941
1076
  return { entries, resolved, libraryOf };
@@ -955,11 +1090,12 @@ var AssetManager = class {
955
1090
  if (existsSync(l.path)) roots.push({ name: l.name, dir: l.path, isDefault: l.isDefault });
956
1091
  }
957
1092
  const registered = new Set(roots.map((r) => r.name));
1093
+ const unregistered = unregisteredLibraryNames();
958
1094
  try {
959
1095
  const librariesRoot = resolveLibraryDir();
960
1096
  if (existsSync(librariesRoot)) {
961
1097
  for (const dirent of readdirSync(librariesRoot, { withFileTypes: true })) {
962
- if (dirent.isDirectory() && dirent.name !== GLOBAL_INSTALL_DIR_NAME && !registered.has(dirent.name)) {
1098
+ if (dirent.isDirectory() && dirent.name !== GLOBAL_INSTALL_DIR_NAME && !registered.has(dirent.name) && !unregistered.has(dirent.name)) {
963
1099
  roots.push({
964
1100
  name: dirent.name,
965
1101
  dir: join(librariesRoot, dirent.name),
@@ -1012,10 +1148,117 @@ var AssetManager = class {
1012
1148
  }
1013
1149
  return out;
1014
1150
  }
1151
+ /**
1152
+ * Every asset a registered library holds, read from the library directories themselves
1153
+ * (not the local index, which keeps one row per ref and version and loses a ref when
1154
+ * another library re-publishes it). One row per holder, all versions.
1155
+ *
1156
+ * @returns `[]` when no library holds an asset. Never warns.
1157
+ */
1158
+ async libraryAssets() {
1159
+ const scan = await this.scanLibraries();
1160
+ if (!scan) return [];
1161
+ return [...scan.byKey.values()].flat();
1162
+ }
1163
+ /**
1164
+ * Every origin that holds `ref` (`kind:@publisher/name`) with the version it holds, in
1165
+ * the order `add <ref>` prefers them, so the first row is the one `add` deploys: the
1166
+ * project's source clones and the bundled factory source (highest version first), then
1167
+ * the registered libraries. `add`, `info` and `search` all read this, so the three name
1168
+ * one origin for one ref. Stores are not asked (no network).
1169
+ *
1170
+ * @returns one row per origin and version; `[]` for a ref without a publisher or one
1171
+ * nothing holds. Never warns.
1172
+ */
1173
+ async refOrigins(ref) {
1174
+ const parsed = parseRefTolerant(ref);
1175
+ if (!parsed?.publisher) return [];
1176
+ const key = `${parsed.kind}:@${parsed.publisher}/${parsed.name}`;
1177
+ return (await this.collectOrigins(key)).get(key) ?? [];
1178
+ }
1179
+ /**
1180
+ * Every ref (`kind:@publisher/name`) that several origins hold at different versions,
1181
+ * with its origins in {@link refOrigins} order. A ref the origins hold at one version
1182
+ * has nothing to choose between and is left out.
1183
+ */
1184
+ async competingOrigins() {
1185
+ const out = /* @__PURE__ */ new Map();
1186
+ for (const [key, origins] of await this.collectOrigins()) {
1187
+ if (new Set(origins.map((o) => o.version)).size > 1) out.set(key, origins);
1188
+ }
1189
+ return out;
1190
+ }
1191
+ /**
1192
+ * The origins of {@link refOrigins} for every ref, or for the one `only` names
1193
+ * (`kind:@publisher/name`), keyed by `kind:@publisher/name`.
1194
+ */
1195
+ async collectOrigins(only) {
1196
+ const { repositories, sources } = this.loadConfig();
1197
+ this.ensureSourceClonesQuiet(repositories, sources);
1198
+ const clones = this.buildClones(repositories);
1199
+ const cloneDirs = new Set(clones.map((c) => resolve(c.localPath)));
1200
+ const index = await buildProvenanceIndex(clones, {
1201
+ projectDir: this.projectDir,
1202
+ knownKinds: this.knownKinds
1203
+ });
1204
+ const factoryUrl = normalizeRepoUrl(FACTORY_SOURCE_URL);
1205
+ const hasFactory = Object.values(repositories).some((d) => d.factory);
1206
+ const out = /* @__PURE__ */ new Map();
1207
+ const originsOf = (key) => {
1208
+ let list = out.get(key);
1209
+ if (!list) {
1210
+ list = [];
1211
+ out.set(key, list);
1212
+ }
1213
+ return list;
1214
+ };
1215
+ for (const candidates of index.values()) {
1216
+ const first = candidates[0];
1217
+ if (!first) continue;
1218
+ const key = `${first.kind}:@${first.publisher}/${first.name}`;
1219
+ if (only !== void 0 && key !== only) continue;
1220
+ const held = [...candidates].sort((a, b) => {
1221
+ const va = coerceVersion(a.version);
1222
+ const vb = coerceVersion(b.version);
1223
+ if (gt(va, vb)) return -1;
1224
+ if (gt(vb, va)) return 1;
1225
+ return a.sourceUrl.localeCompare(b.sourceUrl);
1226
+ });
1227
+ for (const c of held) {
1228
+ const factory = hasFactory && normalizeRepoUrl(c.sourceUrl) === factoryUrl;
1229
+ originsOf(key).push({
1230
+ kind: factory ? "factory" : "source",
1231
+ label: factory ? "factory" : sourceShortName(c.sourceUrl),
1232
+ version: c.version,
1233
+ description: c.description ?? ""
1234
+ });
1235
+ }
1236
+ }
1237
+ const scan = await this.scanLibraries();
1238
+ const prefer = libraryPreference(scan?.isDefaultLib ?? /* @__PURE__ */ new Map());
1239
+ for (const [key, holders] of scan?.byKey ?? []) {
1240
+ if (only !== void 0 && key !== only) continue;
1241
+ for (const c of [...holders].sort(prefer)) {
1242
+ const dir = scan?.libraryDir.get(c.library);
1243
+ if (dir && cloneDirs.has(resolve(dir))) continue;
1244
+ originsOf(key).push({
1245
+ kind: "library",
1246
+ label: c.library,
1247
+ version: c.entry.version ?? "",
1248
+ description: c.entry.description ?? ""
1249
+ });
1250
+ }
1251
+ }
1252
+ return out;
1253
+ }
1015
1254
  /**
1016
1255
  * Where each missed publisher-qualified ref went when its publisher changed, for every
1017
1256
  * ref at once (one library scan, one lock read):
1018
- * - `moved` when a registered library holds the same kind + name under another publisher;
1257
+ * - `moved` when a registered library holds the same kind + name under another publisher.
1258
+ * When the lock ties the missed ref to a library, only that library is looked at (an
1259
+ * asset of the same name in another library is a different asset). Without a lock
1260
+ * entry, a library asset the project already declares or locks under its own ref is
1261
+ * not offered, and two candidate publishers are ambiguous: no hint;
1019
1262
  * - `lockedAs` when the lock holds the same kind + name under another publisher, records
1020
1263
  * the library it came from, and that library now holds the asset under yet another
1021
1264
  * publisher than the locked one. An unrelated asset that merely shares kind + name does
@@ -1035,11 +1278,28 @@ var AssetManager = class {
1035
1278
  }
1036
1279
  if (wanted.size === 0) return out;
1037
1280
  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 });
1281
+ const scan = await this.scanLibraries();
1282
+ if (!scan) return out;
1283
+ const held = /* @__PURE__ */ new Set();
1284
+ for (const [key] of lockEntries) {
1285
+ const { publisher, kind, name } = parseCanonicalLockKey(key);
1286
+ if (publisher && kind && name) held.add(`${publisher}/${kind}:${name}`);
1287
+ }
1288
+ for (const d of this.loadConfig().dependencies) {
1289
+ const id = refIdentity(d);
1290
+ if (id !== null) held.add(id);
1291
+ }
1040
1292
  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;
1293
+ const origin = lockEntries.map(([key, e]) => ({ id: parseCanonicalLockKey(key), library: e.library })).find(
1294
+ ({ id, library }) => library && !library.installed && id.kind === w.kind && id.name === w.name && id.publisher === w.publisher
1295
+ )?.library?.name;
1296
+ const pool = [...scan.byKey.values()].flat().filter(
1297
+ (c) => c.entry.kind === w.kind && c.entry.name === w.name && c.entry.publisher !== w.publisher && (origin ? c.library === origin : !held.has(`${c.entry.publisher}/${c.entry.kind}:${c.entry.name}`))
1298
+ );
1299
+ const publishers = new Set(pool.map((c) => c.entry.publisher));
1300
+ const best = publishers.size === 1 ? pool.sort(libraryPreference(scan.isDefaultLib))[0] : void 0;
1301
+ const moved = best?.entry;
1302
+ const movedLibrary = best?.library;
1043
1303
  const finding = {};
1044
1304
  for (const [key, entry] of lockEntries) {
1045
1305
  const id = parseCanonicalLockKey(key);
@@ -1104,17 +1364,20 @@ var AssetManager = class {
1104
1364
  if (!origin) continue;
1105
1365
  const plain = want.publisher ? `${want.kind}:@${want.publisher}/${want.name}` : `${want.kind}:${want.name}`;
1106
1366
  const moved = findings.get(ref)?.moved;
1367
+ const lookup = this.lockedLibrary(origin, snap);
1107
1368
  if (moved?.library && want.publisher) {
1108
1369
  out.set(
1109
1370
  ref,
1110
1371
  `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
1372
  );
1112
- } else if (!this.libraryPath(origin.name, snap)) {
1113
- out.set(ref, unregisteredLibraryFix(origin.name, plain));
1373
+ } else if (lookup.state === "dir-missing") {
1374
+ out.set(ref, lockedLibraryProblem(lookup, origin, plain));
1375
+ } else if (lookup.state === "unregistered") {
1376
+ out.set(ref, unregisteredLibraryFix(origin, plain));
1114
1377
  } else {
1115
1378
  out.set(
1116
1379
  ref,
1117
- `library "${origin.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${plain}\``
1380
+ `library "${lookup.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${plain}\``
1118
1381
  );
1119
1382
  }
1120
1383
  }
@@ -1214,27 +1477,120 @@ var AssetManager = class {
1214
1477
  * preferring it would lock a stale version after an in-place bump. The hash is
1215
1478
  * computed from the files on disk, so it matches what `install --locked`
1216
1479
  * re-hashes.
1480
+ *
1481
+ * A git-backed library also records its remote URL. When the previous lock names the
1482
+ * library under another name that {@link lockedLibrary} matched to this one by URL (a
1483
+ * teammate registered the same remote under a different name), the entry keeps the locked
1484
+ * name and URL: the lock does not change because it was installed on another machine.
1217
1485
  */
1218
1486
  async libraryLockEntries(lib) {
1219
1487
  if (lib.entries.length === 0) return {};
1220
1488
  const snap = await this.loadLibraryIndex();
1489
+ const previous = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
1490
+ ([, e]) => e.library !== void 0 && !e.library.installed
1491
+ );
1221
1492
  const diskHashes = /* @__PURE__ */ new Map();
1222
1493
  return buildLibraryLockEntries(
1223
1494
  lib.entries.map((e) => {
1224
1495
  const name = lib.libraryOf.get(e) ?? "";
1225
1496
  const publisher = e.publisher ?? "";
1226
1497
  const def = newestLibraryDef(snap, name, { kind: e.kind, publisher, name: e.name });
1227
- const libPath = this.libraryPath(name, snap);
1498
+ const libPath = this.libraryPath({ name }, snap);
1228
1499
  const sha256 = (libPath ? libraryDiskHashes(libPath, diskHashes, this.registry).get(`${e.kind}:${e.name}`) : void 0) ?? def?.sha256;
1500
+ const registered = snap?.libraries.find((l) => l.name === name);
1501
+ let lockName = name;
1502
+ let url = registered ? libraryRemoteUrl(registered) : void 0;
1503
+ const locked = previous.find(([key]) => {
1504
+ const id = parseCanonicalLockKey(key);
1505
+ return id.kind === e.kind && id.name === e.name && id.publisher === publisher;
1506
+ })?.[1].library;
1507
+ if (locked) {
1508
+ const lookup = this.lockedLibrary(locked, snap);
1509
+ if (lookup.state === "ok" && lookup.name === name) {
1510
+ lockName = locked.name;
1511
+ if (locked.url && url && normalizeRepoUrl(locked.url) === normalizeRepoUrl(url)) {
1512
+ url = locked.url;
1513
+ }
1514
+ }
1515
+ }
1229
1516
  return {
1230
1517
  kind: e.kind,
1231
1518
  publisher,
1232
1519
  name: e.name,
1233
- library: { name, version: e.version || def?.version || "0.0.0", sha256 }
1520
+ library: {
1521
+ name: lockName,
1522
+ version: e.version || def?.version || "0.0.0",
1523
+ sha256,
1524
+ ...url ? { url } : {}
1525
+ }
1234
1526
  };
1235
1527
  })
1236
1528
  );
1237
1529
  }
1530
+ /**
1531
+ * Lock keys that `skaile.yaml` still wants: the entries a declared ref names, plus every
1532
+ * entry such an asset requires, transitively. All keys when a `preset:` is declared.
1533
+ */
1534
+ async declaredLockKeys(lock, declared) {
1535
+ const keys = Object.keys(lock.assets);
1536
+ const wanted = declared.map((d) => parseRefTolerant(d.split(" ")[0] ?? d));
1537
+ if (wanted.some((w) => w?.kind === "preset")) return new Set(keys);
1538
+ const matching = (want) => keys.filter((key) => {
1539
+ const id = parseCanonicalLockKey(key);
1540
+ return id.kind === want.kind && id.name === want.name && (!want.publisher || !id.publisher || id.publisher === want.publisher);
1541
+ });
1542
+ const inScope = /* @__PURE__ */ new Set();
1543
+ const queue = [];
1544
+ const add = (key) => {
1545
+ if (inScope.has(key)) return;
1546
+ inScope.add(key);
1547
+ queue.push(key);
1548
+ };
1549
+ for (const w of wanted) if (w) matching(w).forEach(add);
1550
+ for (const key of queue) {
1551
+ for (const dep of await this.deployedRequirementsOf(parseCanonicalLockKey(key))) {
1552
+ const want = memberIdentity(dep);
1553
+ if (want) matching(want).forEach(add);
1554
+ }
1555
+ }
1556
+ return inScope;
1557
+ }
1558
+ /**
1559
+ * The `Missing:` lines of every library-origin lock entry whose library cannot supply
1560
+ * assets on this machine (not registered, or registered with its directory gone). Plain
1561
+ * `install` stops on a non-empty list, the same lines `install --locked` prints: it must
1562
+ * not re-resolve a locked asset from another origin. The ONE function that decides "the
1563
+ * locked library is not here", so a lock-recorded URL fallback extends it in one place.
1564
+ *
1565
+ * Only entries `skaile.yaml` still declares, or that a declared asset requires
1566
+ * (transitively, read from the deployed copy), count. A stale entry nothing declares is
1567
+ * pruned by `install` as usual, so a removed dependency whose library is gone can be
1568
+ * cleaned up. With a declared `preset:` every entry counts: the lock records no edge
1569
+ * from a preset to its items, and the preset's own library may be the one that is gone.
1570
+ *
1571
+ * @param declared - The `dependencies:` refs as written in `skaile.yaml`.
1572
+ */
1573
+ async unavailableLockedLibraries(lock, declared) {
1574
+ const inScope = await this.declaredLockKeys(lock, declared);
1575
+ const locked = Object.entries(lock.assets).filter(
1576
+ ([key, e]) => e.library !== void 0 && !e.library.installed && inScope.has(key)
1577
+ );
1578
+ if (locked.length === 0) return [];
1579
+ const snap = await this.loadLibraryIndex();
1580
+ const lines = [];
1581
+ for (const [ref, e] of locked) {
1582
+ const { publisher, kind, name } = parseCanonicalLockKey(ref);
1583
+ if (!publisher || !kind || !name) continue;
1584
+ const lookup = this.lockedLibrary(e.library, snap);
1585
+ if (lookup.state === "ok") continue;
1586
+ const component = this.installTarget(kind);
1587
+ const deployed = component !== void 0 && isDeployed(component, name, this.driverTarget, this.global, this.projectDir);
1588
+ const problem = lockedLibraryProblem(lookup, e.library, `${kind}:@${publisher}/${name}`);
1589
+ const kept = deployed ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists";
1590
+ lines.push(`${ref} (${problem}; ${kept})`);
1591
+ }
1592
+ return lines;
1593
+ }
1238
1594
  /**
1239
1595
  * Re-resolve every library-origin lock entry from its named library, for
1240
1596
  * `install --locked`. An entry is restored only when its library is still
@@ -1258,25 +1614,26 @@ var AssetManager = class {
1258
1614
  missing.push(`${ref} (unparseable lock key)`);
1259
1615
  continue;
1260
1616
  }
1261
- const libPath = this.libraryPath(origin.name, snap);
1262
- if (!libPath) {
1617
+ const lookup = this.lockedLibrary(origin, snap);
1618
+ if (lookup.state !== "ok") {
1263
1619
  missing.push(
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)`
1620
+ `${ref} (${lockedLibraryProblem(lookup, origin, `${kind}:@${publisher}/${name}`)})`
1265
1621
  );
1266
1622
  continue;
1267
1623
  }
1624
+ const libPath = lookup.path;
1268
1625
  const found = (await scanDirectory(libPath, publisher, { knownKinds: this.knownKinds })).find(
1269
1626
  (x) => x.kind === kind && x.name === name
1270
1627
  );
1271
1628
  if (!found) {
1272
- missing.push(`${ref} (not found in library "${origin.name}")`);
1629
+ missing.push(`${ref} (not found in library "${lookup.name}")`);
1273
1630
  continue;
1274
1631
  }
1275
- const def = newestLibraryDef(snap, origin.name, { kind, publisher, name });
1632
+ const def = newestLibraryDef(snap, lookup.name, { kind, publisher, name });
1276
1633
  const current = found.version || def?.version || "0.0.0";
1277
1634
  if (current !== origin.version) {
1278
1635
  missing.push(
1279
- `${ref} (library "${origin.name}" is at ${current}, locked ${origin.version})`
1636
+ `${ref} (library "${lookup.name}" is at ${current}, locked ${origin.version})`
1280
1637
  );
1281
1638
  continue;
1282
1639
  }
@@ -1284,29 +1641,103 @@ var AssetManager = class {
1284
1641
  const onDisk = libraryDiskHashes(libPath, diskHashes, this.registry).get(`${kind}:${name}`);
1285
1642
  if (onDisk !== void 0 && onDisk !== origin.sha256) {
1286
1643
  missing.push(
1287
- `${ref} (library "${origin.name}" content changed since lock \u2014 or the lock predates the one shared asset hash; re-lock with \`skaile install\`)`
1644
+ `${ref} (library "${lookup.name}" content changed since lock \u2014 or the lock predates the one shared asset hash; re-lock with \`skaile install\`)`
1288
1645
  );
1289
1646
  continue;
1290
1647
  }
1291
1648
  }
1649
+ found.version = current;
1292
1650
  entries.push(withLibraryFiles(found, libPath, fileSets, this.registry));
1293
1651
  }
1294
1652
  return { entries, missing };
1295
1653
  }
1296
1654
  /**
1297
- * On-disk root of the library named `name`: the registered path from the index
1298
- * when it exists, else `<libraries dir>/<name>`; `null` when neither is a directory.
1655
+ * Whether a deployed copy exists for the lock entries `install` kept for the unresolved
1656
+ * `ref`. A kind with no install target has no deployed copy.
1657
+ */
1658
+ keptCopyDeployed(kept, ref) {
1659
+ const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
1660
+ if (!want) return false;
1661
+ return Object.keys(kept.assets).some((key) => {
1662
+ const id = parseCanonicalLockKey(key);
1663
+ if (id.kind !== want.kind || id.name !== want.name) return false;
1664
+ if (want.publisher && id.publisher !== want.publisher) return false;
1665
+ const component = this.installTarget(id.kind);
1666
+ return component !== void 0 && isDeployed(component, id.name, this.driverTarget, this.global, this.projectDir);
1667
+ });
1668
+ }
1669
+ /**
1670
+ * How the library `origin` names stands on this machine. THE one lookup of a locked library
1671
+ * ("is it registered here?"): `check`, `outdated`, `install` and `install --locked` all ask it.
1672
+ *
1673
+ * - registered in the index under its name: `ok` at its registered path, or `dir-missing` when
1674
+ * that directory is gone;
1675
+ * - not registered: a directory `<libraries dir>/<name>` nobody unregistered still counts as a
1676
+ * hand-made library (`ok`). One `library remove` unregistered without `--purge` does not
1677
+ * (it stays on disk but no longer resolves), and neither does the reserved `installed` folder.
1678
+ * - otherwise, when the lock recorded the library's remote URL: another registered library
1679
+ * whose git remote is the same repository (`.git` suffix, trailing `/` and the
1680
+ * `git@host:o/r` / `https://host/o/r` forms do not matter) and whose directory exists is
1681
+ * `ok`, with `name` set to the name it is registered under here. The library name is
1682
+ * machine-local; the remote is what a clone shares.
1299
1683
  */
1300
- libraryPath(name, snap) {
1301
- if (name === GLOBAL_INSTALL_DIR_NAME) return null;
1684
+ lockedLibrary(origin, snap) {
1685
+ const { name } = origin;
1686
+ if (name === GLOBAL_INSTALL_DIR_NAME) return { state: "unregistered" };
1687
+ let byName = { state: "unregistered" };
1302
1688
  const registered = snap?.libraries.find((l) => l.name === name)?.path;
1303
- if (registered && existsSync(registered)) return registered;
1304
- try {
1305
- const guess = join(resolveLibraryDir(), name);
1306
- return existsSync(guess) ? guess : null;
1307
- } catch {
1308
- return null;
1689
+ if (registered) {
1690
+ byName = existsSync(registered) ? { state: "ok", path: registered, name } : { state: "dir-missing", path: registered };
1691
+ } else {
1692
+ try {
1693
+ const guess = join(resolveLibraryDir(), name);
1694
+ if (existsSync(guess) && !unregisteredLibraryNames().has(name)) {
1695
+ byName = { state: "ok", path: guess, name };
1696
+ }
1697
+ } catch {
1698
+ }
1309
1699
  }
1700
+ if (byName.state === "ok" || !origin.url) return byName;
1701
+ const wanted = normalizeRepoUrl(origin.url);
1702
+ const byUrl = snap?.libraries.find((l) => {
1703
+ const url = libraryRemoteUrl(l);
1704
+ return l.name !== name && url !== void 0 && normalizeRepoUrl(url) === wanted && existsSync(l.path);
1705
+ });
1706
+ return byUrl ? { state: "ok", path: byUrl.path, name: byUrl.name } : byName;
1707
+ }
1708
+ /** On-disk root of the library `origin` names; `null` unless {@link lockedLibrary} says `ok`. */
1709
+ libraryPath(origin, snap) {
1710
+ const found = this.lockedLibrary(origin, snap);
1711
+ return found.state === "ok" ? found.path : null;
1712
+ }
1713
+ /**
1714
+ * Where each library-origin lock entry of this project resolves on this machine: identity
1715
+ * `kind:publisher/name` (no scope sigil, no version) → the name the locked library is
1716
+ * registered under here. The name differs from the locked one when the lock's remote URL
1717
+ * matched another registered library ({@link lockedLibrary}). A deployed entry whose library
1718
+ * resolves nowhere (not registered, directory gone) maps to `null`.
1719
+ *
1720
+ * Direct `asset install` entries (`library.installed`) are included and resolved the same
1721
+ * way, but they map to the locked library name when that lookup fails: such a copy sits in
1722
+ * the project and does not depend on the library still being registered. A missing or
1723
+ * unreadable lock yields an empty map. `skaile manage` uses it to tick the right library row.
1724
+ */
1725
+ async lockedLibraryNames() {
1726
+ const out = /* @__PURE__ */ new Map();
1727
+ const locked = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
1728
+ ([, e]) => e.library !== void 0
1729
+ );
1730
+ if (locked.length === 0) return out;
1731
+ const snap = await this.loadLibraryIndex();
1732
+ for (const [key, e] of locked) {
1733
+ const { publisher, kind, name } = parseCanonicalLockKey(key);
1734
+ if (!publisher || !kind || !name) continue;
1735
+ const origin = e.library;
1736
+ const lookup = this.lockedLibrary(origin, snap);
1737
+ const resolved = lookup.state === "ok" ? lookup.name : origin.installed ? origin.name : null;
1738
+ out.set(`${kind}:${publisher}/${name}`, resolved);
1739
+ }
1740
+ return out;
1310
1741
  }
1311
1742
  // ── Store fetch-then-deploy ────────────────────────────────────────────
1312
1743
  /** A candidate's stable identity key (`<publisher>/<kind>:<name>@<version>`). */
@@ -1443,7 +1874,9 @@ var AssetManager = class {
1443
1874
  return id !== null && (opts.provenanceIndex.get(id)?.length ?? 0) === 0;
1444
1875
  })
1445
1876
  );
1446
- const hit = await this.resolveLibraryEntries([...libraryTried]);
1877
+ const hit = await this.resolveLibraryEntries([...libraryTried], {
1878
+ quiet: !opts.warnCollisions
1879
+ });
1447
1880
  const next = absorb(hit);
1448
1881
  const rest = fresh.filter((ref) => !hit.resolved.has(ref));
1449
1882
  if (rest.length > 0) {
@@ -1458,7 +1891,8 @@ var AssetManager = class {
1458
1891
  for (const o of r.overridesApplied) overridesApplied.add(o);
1459
1892
  presets.push(...r.presets);
1460
1893
  const hit2 = await this.resolveLibraryEntries(
1461
- r.missing.filter((m) => !libraryTried.has(m))
1894
+ r.missing.filter((m) => !libraryTried.has(m)),
1895
+ { quiet: !opts.warnCollisions }
1462
1896
  );
1463
1897
  next.push(...absorb(hit2));
1464
1898
  for (const m of r.missing) if (!hit2.resolved.has(m)) missing.add(m);
@@ -1638,6 +2072,20 @@ var AssetManager = class {
1638
2072
  if (opts?.locked) {
1639
2073
  return this.installLocked(repositories, patches, dependencies);
1640
2074
  }
2075
+ const lockForLibraries = this.tryReadLock();
2076
+ if (lockForLibraries) {
2077
+ const unavailable = await this.unavailableLockedLibraries(lockForLibraries, dependencies);
2078
+ if (unavailable.length > 0) {
2079
+ return {
2080
+ deployed: [],
2081
+ removed: [],
2082
+ missing: unavailable,
2083
+ lockWritten: false,
2084
+ npmInstalled: [],
2085
+ npmFailed: []
2086
+ };
2087
+ }
2088
+ }
1641
2089
  const cloneError = this.ensureSourceClones(repositories, sources);
1642
2090
  if (cloneError) return cloneError;
1643
2091
  const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
@@ -1657,16 +2105,19 @@ var AssetManager = class {
1657
2105
  for (const ref of /* @__PURE__ */ new Set([...kept.refs, ...fixes.keys()])) {
1658
2106
  const fix = fixes.get(ref);
1659
2107
  const notes = [
1660
- kept.refs.has(ref) ? "the deployed copy and its lock entry are kept" : void 0,
2108
+ kept.refs.has(ref) ? this.keptCopyDeployed(kept, ref) ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists" : void 0,
1661
2109
  fix
1662
2110
  ].filter((n) => n !== void 0);
1663
2111
  missingHints[ref] = `${fix ? "" : missingHints[ref] ?? ""} (${notes.join("; ")})`;
1664
2112
  }
1665
2113
  const directLock = this.directInstallLockEntries();
1666
2114
  const libraryLock = { ...directLock, ...await this.libraryLockEntries(lib) };
1667
- const freshLock = buildLockFile(resolved, overridesApplied, libraryLock);
2115
+ const freshLock = {
2116
+ ...buildLockFile(resolved, overridesApplied, libraryLock),
2117
+ ...presetsLockSlice(presets)
2118
+ };
1668
2119
  await this.keepRequiredByKept(prevLock, kept, Object.keys(freshLock.assets));
1669
- const removed = this.reconcileRemovedAssets(
2120
+ const { removed, replaced } = this.reconcileRemovedAssets(
1670
2121
  resolved,
1671
2122
  /* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)])
1672
2123
  );
@@ -1680,12 +2131,13 @@ var AssetManager = class {
1680
2131
  ];
1681
2132
  await this.renderDriverMcpConfig();
1682
2133
  const agentRender = this.renderAgentInstances();
1683
- const lockWritten = resolved.length > 0 || lib.entries.length > 0 || existsSync(this.lockPath);
2134
+ const lockWanted = resolved.length > 0 || lib.entries.length > 0 || existsSync(this.lockPath);
2135
+ let lockWritten = false;
1684
2136
  let relocked = [];
1685
- if (lockWritten) {
2137
+ if (lockWanted) {
1686
2138
  const lockData = withKeptLockEntries(freshLock, kept);
1687
2139
  relocked = relockedRefs(prevLock, lockData);
1688
- writeLock(this.lockPath, lockData);
2140
+ lockWritten = writeLock(this.lockPath, lockData);
1689
2141
  }
1690
2142
  this.recordInstallHistory(deployed, removed);
1691
2143
  const { npmInstalled, npmFailed } = await this.installRuntimeAssetDeps();
@@ -1695,6 +2147,7 @@ var AssetManager = class {
1695
2147
  missing: stillMissing,
1696
2148
  ...Object.keys(missingHints).length > 0 ? { missingHints } : {},
1697
2149
  ...relocked.length > 0 ? { relocked } : {},
2150
+ ...replaced.length > 0 ? { replaced } : {},
1698
2151
  lockWritten,
1699
2152
  npmInstalled,
1700
2153
  npmFailed,
@@ -1714,7 +2167,7 @@ var AssetManager = class {
1714
2167
  const fresh = freshKeys.map(parseCanonicalLockKey);
1715
2168
  const queue = Object.keys(kept.assets);
1716
2169
  for (const key of queue) {
1717
- for (const dep of await this.deployedRequirements(key)) {
2170
+ for (const dep of await this.deployedRequirementsOf(parseCanonicalLockKey(key))) {
1718
2171
  const want = memberIdentity(dep);
1719
2172
  if (!want) continue;
1720
2173
  const matches = (id) => id.kind === want.kind && id.name === want.name && (!want.publisher || !id.publisher || id.publisher === want.publisher);
@@ -1730,9 +2183,14 @@ var AssetManager = class {
1730
2183
  }
1731
2184
  }
1732
2185
  }
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);
2186
+ /**
2187
+ * Publisher-qualified refs the deployed copy of `id` requires, read from its own
2188
+ * manifest file (`metadata.requires`, a manifest `dependencies:` entry): the one place
2189
+ * the requirements stay knowable when no source or library holds the asset any more.
2190
+ * A copy that is not deployed or cannot be read has no known requirements.
2191
+ */
2192
+ async deployedRequirementsOf(id) {
2193
+ const { kind, name, publisher } = id;
1736
2194
  const component = kind ? this.installTarget(kind) : void 0;
1737
2195
  if (!kind || !name || !component) return [];
1738
2196
  const dir = deployedDir(component, name, this.driverTarget, this.global, this.projectDir);
@@ -1805,21 +2263,32 @@ var AssetManager = class {
1805
2263
  ...keep
1806
2264
  ]);
1807
2265
  const removed = [];
2266
+ const replaced = [];
1808
2267
  if (oldLock) {
1809
2268
  for (const ref of Object.keys(oldLock.assets)) {
1810
2269
  if (!resolvedRefs.has(ref)) {
1811
- const { kind, name } = parseCanonicalLockKey(ref);
2270
+ const { kind, name, publisher, version } = parseCanonicalLockKey(ref);
1812
2271
  if (kind === "preset") continue;
1813
2272
  if (kind && name) {
1814
2273
  for (const t of this.targetHarnesses()) removeAsset(kind, name, this.deployOptsFor(t));
1815
- removed.push(ref);
2274
+ const successor = [...resolvedRefs].find((r) => {
2275
+ const o = parseCanonicalLockKey(r);
2276
+ return o.kind === kind && o.name === name && o.publisher === publisher;
2277
+ });
2278
+ if (successor) {
2279
+ replaced.push({
2280
+ ref: `${kind}:${publisher ? `@${publisher}/` : ""}${name}`,
2281
+ from: version ?? "",
2282
+ to: parseCanonicalLockKey(successor).version ?? ""
2283
+ });
2284
+ } else removed.push(ref);
1816
2285
  } else {
1817
2286
  console.warn(`skaile: cannot reconcile unparseable lock key "${ref}" \u2014 left in place.`);
1818
2287
  }
1819
2288
  }
1820
2289
  }
1821
2290
  }
1822
- return removed;
2291
+ return { removed, replaced };
1823
2292
  }
1824
2293
  /** Append `add`/`remove` history entries for a completed install reconcile. */
1825
2294
  recordInstallHistory(deployed, removed) {
@@ -2019,7 +2488,8 @@ var AssetManager = class {
2019
2488
  return p ? `${p.kind}:${p.name}` : m;
2020
2489
  })
2021
2490
  );
2022
- const gaps = (await this.lockGaps()).filter((g) => {
2491
+ const gaps = (await this.projectState()).gaps.filter((g) => {
2492
+ if (g.type === "agent") return false;
2023
2493
  const p = parseRefTolerant(g.ref);
2024
2494
  return !(p && alreadyMissing.has(`${p.kind}:${p.name}`));
2025
2495
  }).map((g) => g.message);
@@ -2031,6 +2501,64 @@ var AssetManager = class {
2031
2501
  ...agentRender
2032
2502
  };
2033
2503
  }
2504
+ /**
2505
+ * Parse the ref `add` was given. A ref the grammar rejects fails with the parser's message
2506
+ * minus its manifest-migration advice (it points at a skill that is not for `add`), and:
2507
+ * - a publisher-less ref (`report-html`, `skill:report-html`) names the assets of that
2508
+ * kind and name that `add` can resolve (a source, the bundled factory source or a
2509
+ * library holds them), so `Did you mean` never names an asset that does not resolve;
2510
+ * - a `@version` suffix (`skill:@acme/x@1.0.0`) says the pin is written `#<version>`.
2511
+ */
2512
+ async parseAddRef(ref, repositories, sources) {
2513
+ try {
2514
+ return parseAssetRef(ref);
2515
+ } catch (err) {
2516
+ if (!(err instanceof Error)) throw err;
2517
+ const advice = " Run the `migrate-skaile-manifest` skill on legacy manifests.";
2518
+ const plain = err.message.endsWith(advice) ? err.message.slice(0, -advice.length) : err.message;
2519
+ if (plain.startsWith("publisher required")) {
2520
+ const matches = await this.resolvableMatches(ref, repositories, sources);
2521
+ const hint = matches.length === 0 ? "" : matches.length === 1 ? ` Did you mean ${matches[0]}?` : ` Did you mean one of: ${matches.join(", ")}?`;
2522
+ throw new Error(`${plain}${hint}`);
2523
+ }
2524
+ const pinned = /^(.*\/[^@#/]+)@([^@#/]+)$/.exec(ref.trim());
2525
+ if (pinned && plain.startsWith("invalid asset name")) {
2526
+ throw new Error(`${plain} To pin a version, write ${pinned[1]}#${pinned[2]}.`);
2527
+ }
2528
+ throw new Error(plain);
2529
+ }
2530
+ }
2531
+ /**
2532
+ * The publisher-qualified refs of the assets named like the publisher-less `ref` that
2533
+ * `add` resolves: held by a source clone, the bundled factory source or a registered
2534
+ * library. A ref without a kind means a skill, as the grammar says.
2535
+ */
2536
+ async resolvableMatches(ref, repositories, sources) {
2537
+ const trimmed = ref.trim();
2538
+ const colon = trimmed.indexOf(":");
2539
+ const rawKind = colon === -1 ? "skill" : trimmed.slice(0, colon).trim();
2540
+ const kind = rawKind === "mcp" ? "mcp-server" : rawKind;
2541
+ const hash = trimmed.indexOf("#", colon + 1);
2542
+ const name = trimmed.slice(colon + 1, hash === -1 ? void 0 : hash).trim();
2543
+ if (!name || /[@/\s]/.test(name)) return [];
2544
+ const found = /* @__PURE__ */ new Set();
2545
+ const note = (c) => {
2546
+ if (c.kind === kind && c.name === name && c.publisher) {
2547
+ found.add(assetRefToStr({ kind, name, publisher: c.publisher.replace(/^@/, "") }));
2548
+ }
2549
+ };
2550
+ try {
2551
+ this.ensureSourceClonesQuiet(repositories, sources);
2552
+ const index = await buildProvenanceIndex(this.buildClones(repositories), {
2553
+ projectDir: this.projectDir,
2554
+ knownKinds: this.knownKinds
2555
+ });
2556
+ for (const candidates of index.values()) for (const c of candidates) note(c);
2557
+ for (const { entry } of await this.libraryAssets()) note(entry);
2558
+ } catch {
2559
+ }
2560
+ return [...found].sort();
2561
+ }
2034
2562
  /**
2035
2563
  * Add a single asset (and its transitive dependencies) to the workspace.
2036
2564
  *
@@ -2052,7 +2580,7 @@ var AssetManager = class {
2052
2580
  */
2053
2581
  async add(ref, opts) {
2054
2582
  const { repositories, sources, stores, dependencies, overrides, patches } = this.loadConfig();
2055
- const parsed = parseAssetRef(ref);
2583
+ const parsed = await this.parseAddRef(ref, repositories, sources);
2056
2584
  this.ensureSourceClonesQuiet(repositories, sources);
2057
2585
  const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
2058
2586
  projectDir: this.projectDir,
@@ -2060,9 +2588,24 @@ var AssetManager = class {
2060
2588
  });
2061
2589
  const { resolved, missing, overridesApplied, lib, presets } = await this.resolveLibraryFirst(
2062
2590
  [ref],
2063
- { provenanceIndex, overrides, stores, declaredRefs: dependencies }
2591
+ { provenanceIndex, overrides, stores, declaredRefs: dependencies, warnCollisions: true }
2064
2592
  );
2065
2593
  if (resolved.length === 0 && lib.entries.length === 0 && presets.length === 0) {
2594
+ const snap = await this.loadLibraryIndex();
2595
+ const bare = (p) => (p ?? "").replace(/^@/, "");
2596
+ for (const l of snap?.libraries ?? []) {
2597
+ const libPath = this.libraryPath({ name: l.name }, snap);
2598
+ if (!libPath || !manifestFileHasAssetsArray(libPath)) continue;
2599
+ const unlisted = await scanUnlistedAssets(libPath, l.name, this.registry);
2600
+ const hit = unlisted.find(
2601
+ (u) => !u.registeredAt && u.entry.kind === parsed.kind && u.entry.name === parsed.name && (!parsed.publisher || bare(u.entry.publisher) === bare(parsed.publisher))
2602
+ );
2603
+ if (hit) {
2604
+ throw new Error(
2605
+ `${canonicalEntryRef(hit.entry)} is in library "${l.name}" but not listed in its skaile.manifest.yaml, so only part of its files would be deployed. Run \`skaile library index ${l.name}\`, then add it again.`
2606
+ );
2607
+ }
2608
+ }
2066
2609
  throw new Error(
2067
2610
  `Asset not found: ${ref}${(await this.publisherChangeHints([ref]))[ref] ?? ""}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`
2068
2611
  );
@@ -2072,6 +2615,18 @@ var AssetManager = class {
2072
2615
  `Preset ${ref} has items that do not resolve: ${missing.join(", ")} \u2014 nothing was added`
2073
2616
  );
2074
2617
  }
2618
+ if (lib.entries.length > 0) {
2619
+ const snap = await this.loadLibraryIndex();
2620
+ for (const e of lib.entries) {
2621
+ const origin = lib.libraryOf.get(e);
2622
+ const libPath = origin ? this.libraryPath({ name: origin }, snap) : null;
2623
+ if (origin && libPath && isUnlistedLibraryAsset(e, libPath)) {
2624
+ throw new Error(
2625
+ `${canonicalEntryRef(e)} is in library "${origin}" but not listed in its skaile.manifest.yaml, so only part of its files would be deployed. Run \`skaile library index ${origin}\`, then add it again.`
2626
+ );
2627
+ }
2628
+ }
2629
+ }
2075
2630
  this.assertNoFolderCollision(
2076
2631
  [...resolved, ...lib.entries.map((e) => this.entryIdentity(e))],
2077
2632
  [
@@ -2088,7 +2643,12 @@ var AssetManager = class {
2088
2643
  const deployed = [
2089
2644
  ...new Set(
2090
2645
  this.targetHarnesses().flatMap(
2091
- (t) => deployAll(entries, repositories, patches, this.deployOptsFor(t, opts?.link))
2646
+ (t) => deployAll(
2647
+ entries,
2648
+ repositories,
2649
+ patches,
2650
+ this.deployOptsFor(t, opts?.link, opts?.onSkippedSymlink)
2651
+ )
2092
2652
  )
2093
2653
  )
2094
2654
  ];
@@ -2104,7 +2664,10 @@ var AssetManager = class {
2104
2664
  await this.renderDriverMcpConfig();
2105
2665
  this.renderAgentInstances();
2106
2666
  if (resolved.length > 0 || lib.entries.length > 0) {
2107
- const fresh = buildLockFile(resolved, overridesApplied, await this.libraryLockEntries(lib));
2667
+ const fresh = {
2668
+ ...buildLockFile(resolved, overridesApplied, await this.libraryLockEntries(lib)),
2669
+ ...presetsLockSlice(presets)
2670
+ };
2108
2671
  writeLock(this.lockPath, mergeLockFile(this.tryReadLock(), fresh));
2109
2672
  }
2110
2673
  for (const d of deployed) {
@@ -2383,19 +2946,39 @@ var AssetManager = class {
2383
2946
  * preset's items count as its requirements. They are undeployed and unlocked;
2384
2947
  * a dependency still declared or required elsewhere stays.
2385
2948
  *
2949
+ * An asset another locked asset (or a declared preset) requires is kept and reported in
2950
+ * `blocked`, nothing changes, unless `opts.force` (see {@link requiredByOthers}).
2951
+ *
2386
2952
  * @returns `changed` as {@link remove}; `pruned` the canonical refs
2387
- * (`kind:@publisher/name`) of the dependencies dropped as well.
2953
+ * (`kind:@publisher/name`) of the dependencies dropped as well; `blocked` when the
2954
+ * removal was refused.
2388
2955
  */
2389
- async removeWithDependencies(ref) {
2956
+ async removeWithDependencies(ref, opts) {
2390
2957
  const target = this.resolveRemoveTarget(ref);
2391
2958
  if (!target) return { changed: false, pruned: [] };
2959
+ if (!opts?.force) {
2960
+ const blocked = await this.requiredByOthers(target);
2961
+ if (blocked) return { changed: false, pruned: [], blocked };
2962
+ }
2392
2963
  const asked = target.publisher ? assetRefToStr({ kind: target.kind, name: target.name, publisher: target.publisher }) : `${target.kind}:${target.name}`;
2393
2964
  let entry = null;
2394
2965
  try {
2395
2966
  entry = await this.info(asked);
2396
2967
  } catch {
2397
2968
  }
2398
- const requiredRefs = entry ? libraryEntryDepRefs(entry) : [];
2969
+ let requiredRefs;
2970
+ if (entry) {
2971
+ requiredRefs = libraryEntryDepRefs(entry);
2972
+ } else {
2973
+ const lockedAs = Object.keys(this.tryReadLock()?.assets ?? {}).map(parseCanonicalLockKey).find(
2974
+ (id) => id.kind === target.kind && id.name === target.name && (!target.publisher || id.publisher === target.publisher)
2975
+ );
2976
+ requiredRefs = await this.deployedRequirementsOf({
2977
+ kind: target.kind,
2978
+ name: target.name,
2979
+ publisher: target.publisher ?? lockedAs?.publisher
2980
+ });
2981
+ }
2399
2982
  const changed = this.removeResolved(target);
2400
2983
  if (!changed || requiredRefs.length === 0) return { changed, pruned: [] };
2401
2984
  return { changed, pruned: await this.pruneOrphans(requiredRefs) };
@@ -2449,6 +3032,31 @@ var AssetManager = class {
2449
3032
  return kind ? { kind, name } : null;
2450
3033
  }
2451
3034
  }
3035
+ /**
3036
+ * The reverse guard of {@link removeWithDependencies}: which other locked assets (and
3037
+ * declared presets) require `target`. Their requirements are read the way {@link lockedGraph}
3038
+ * reads them for pruning. Never touches the project.
3039
+ *
3040
+ * @returns `ref` and `requiredBy` (both `kind:@publisher/name`, version-agnostic), or `null`
3041
+ * when `target` is not locked or nothing else requires it.
3042
+ */
3043
+ async requiredByOthers(target) {
3044
+ const lock = this.tryReadLock();
3045
+ if (!lock) return null;
3046
+ const { requires, findKey } = await this.lockedGraph(lock);
3047
+ const targetKey = findKey(
3048
+ target.publisher ? assetRefToStr({ kind: target.kind, name: target.name, publisher: target.publisher }) : `${target.kind}:${target.name}`
3049
+ );
3050
+ if (!targetKey) return null;
3051
+ const display = (node) => {
3052
+ if (!lock.assets[node]) return node;
3053
+ const { kind, name, publisher } = parseCanonicalLockKey(node);
3054
+ if (!kind || !name) return node;
3055
+ return publisher ? assetRefToStr({ kind, name, publisher }) : `${kind}:${name}`;
3056
+ };
3057
+ const requiredBy = [...requires].filter(([node, deps]) => node !== targetKey && deps.includes(targetKey)).map(([node]) => display(node));
3058
+ return requiredBy.length > 0 ? { ref: display(targetKey), requiredBy } : null;
3059
+ }
2452
3060
  /** Undeploy `parsed` from every target harness and drop its declaration and lock entry. */
2453
3061
  removeResolved(parsed) {
2454
3062
  let undeployed = false;
@@ -2456,8 +3064,9 @@ var AssetManager = class {
2456
3064
  if (removeAsset(parsed.kind, parsed.name, this.deployOptsFor(t))) undeployed = true;
2457
3065
  }
2458
3066
  const undeclared = this.removeDependencyFromConfig(parsed);
2459
- const unlocked = undeployed || undeclared ? this.pruneLockEntry(parsed) : false;
2460
- const changed = undeployed || undeclared || unlocked;
3067
+ const unbound = this.removeAgentBindings(parsed);
3068
+ const unlocked = undeployed || undeclared || unbound ? this.pruneLockEntry(parsed) : false;
3069
+ const changed = undeployed || undeclared || unbound || unlocked;
2461
3070
  if (changed) {
2462
3071
  appendHistory(this.projectDir, {
2463
3072
  ref: `${parsed.kind}:${parsed.name}`,
@@ -2518,6 +3127,17 @@ var AssetManager = class {
2518
3127
  changed = true;
2519
3128
  }
2520
3129
  }
3130
+ if (parsed.kind === "preset" && lock.presets) {
3131
+ const kept = lock.presets.filter((key) => {
3132
+ const id = parseCanonicalLockKey(key);
3133
+ return !(id.kind === parsed.kind && id.name === parsed.name && (!parsed.publisher || id.publisher === parsed.publisher));
3134
+ });
3135
+ if (kept.length !== lock.presets.length) {
3136
+ if (kept.length > 0) lock.presets = kept;
3137
+ else delete lock.presets;
3138
+ changed = true;
3139
+ }
3140
+ }
2521
3141
  if (changed) writeLock(this.lockPath, lock);
2522
3142
  return changed;
2523
3143
  }
@@ -2578,6 +3198,7 @@ var AssetManager = class {
2578
3198
  const { repositories } = this.loadConfig();
2579
3199
  const parsed = parseRefTolerant(ref);
2580
3200
  if (!parsed) return null;
3201
+ let best;
2581
3202
  for (const [name, decl] of Object.entries(repositories)) {
2582
3203
  const repoDir = this.resolveRepoDir(decl, name);
2583
3204
  if (!repoDir) continue;
@@ -2585,9 +3206,12 @@ var AssetManager = class {
2585
3206
  const match = entries.find(
2586
3207
  (e) => e.kind === parsed.kind && e.name === parsed.name && (!parsed.publisher || e.publisher === parsed.publisher)
2587
3208
  );
2588
- if (match) return match;
3209
+ if (match && (!best || gt(coerceVersion(match.version ?? ""), coerceVersion(best.version ?? "")))) {
3210
+ best = match;
3211
+ }
2589
3212
  }
2590
- const lib = await this.resolveLibraryEntries([ref]);
3213
+ if (best) return best;
3214
+ const lib = await this.resolveLibraryEntries([ref], { quiet: true });
2591
3215
  return lib.entries[0] ?? null;
2592
3216
  }
2593
3217
  /**
@@ -2929,7 +3553,8 @@ var AssetManager = class {
2929
3553
  const out = [];
2930
3554
  try {
2931
3555
  const entry = await this.info(ref);
2932
- for (const dep of entry ? libraryEntryDepRefs(entry) : []) {
3556
+ const deps = entry ? libraryEntryDepRefs(entry) : lock.assets[node] ? await this.deployedRequirementsOf(parseCanonicalLockKey(node)) : [];
3557
+ for (const dep of deps) {
2933
3558
  const key = findKey(dep);
2934
3559
  if (key && key !== node && !out.includes(key)) out.push(key);
2935
3560
  }
@@ -3057,11 +3682,12 @@ var AssetManager = class {
3057
3682
  * - **Library assets** — a library-origin lock entry whose library files on disk
3058
3683
  * (not the index, which lags until `skaile library index`) hold a newer version
3059
3684
  * 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`.
3685
+ * different bytes (`contentChanged`). `behind` is 0.
3686
+ * - **Project state** — every {@link AssetManager.projectState} gap (an unregistered or
3687
+ * renamed library, a missing deployed copy, a declared dependency or a deployed asset's
3688
+ * `requires` with no lock entry, a lock that contradicts a `skaile.yaml` pin) as a
3689
+ * `problem` row, so `outdated` lists what `check`, `doctor` and `install --locked` fail
3690
+ * on. An asset that also has a version row is listed once, with that row.
3065
3691
  *
3066
3692
  * @returns Array of {@link OutdatedEntry} objects.
3067
3693
  */
@@ -3070,7 +3696,6 @@ var AssetManager = class {
3070
3696
  const lock = this.tryReadLock();
3071
3697
  if (!lock) return this.addLockGapRows([]);
3072
3698
  const result = [];
3073
- const reported = /* @__PURE__ */ new Set();
3074
3699
  const commitCounts = /* @__PURE__ */ new Map();
3075
3700
  for (const src of lock.sources) {
3076
3701
  const slug = sourceCacheKey(src.url);
@@ -3094,7 +3719,6 @@ var AssetManager = class {
3094
3719
  currentVersion: version ?? "",
3095
3720
  behind
3096
3721
  });
3097
- reported.add(ref);
3098
3722
  }
3099
3723
  }
3100
3724
  const libraryAssets = Object.entries(lock.assets).filter(
@@ -3108,28 +3732,9 @@ var AssetManager = class {
3108
3732
  const origin = entry.library;
3109
3733
  const { publisher, kind, name } = parseCanonicalLockKey(ref);
3110
3734
  if (!publisher || !kind || !name) continue;
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);
3116
- result.push({
3117
- kind,
3118
- name,
3119
- publisher,
3120
- currentVersion: origin.version,
3121
- behind: 0,
3122
- library: origin.name,
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
- }
3129
- });
3130
- reported.add(ref);
3131
- continue;
3132
- }
3735
+ const lookup = this.lockedLibrary(origin, snap);
3736
+ if (lookup.state !== "ok") continue;
3737
+ const libPath = lookup.path;
3133
3738
  const scanKey = `${libPath}\0${publisher}`;
3134
3739
  let scanned = scans.get(scanKey);
3135
3740
  if (!scanned) {
@@ -3138,7 +3743,7 @@ var AssetManager = class {
3138
3743
  }
3139
3744
  const found = scanned.find((x) => x.kind === kind && x.name === name);
3140
3745
  if (!found) continue;
3141
- const diskVersion = found.version || newestLibraryDef(snap, origin.name, { kind, publisher, name })?.version;
3746
+ const diskVersion = found.version || newestLibraryDef(snap, lookup.name, { kind, publisher, name })?.version;
3142
3747
  if (diskVersion && gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
3143
3748
  result.push({
3144
3749
  kind,
@@ -3147,9 +3752,8 @@ var AssetManager = class {
3147
3752
  currentVersion: origin.version,
3148
3753
  behind: 0,
3149
3754
  latestVersion: diskVersion,
3150
- library: origin.name
3755
+ library: lookup.name
3151
3756
  });
3152
- reported.add(ref);
3153
3757
  continue;
3154
3758
  }
3155
3759
  if (origin.sha256) {
@@ -3163,41 +3767,24 @@ var AssetManager = class {
3163
3767
  publisher,
3164
3768
  currentVersion: origin.version,
3165
3769
  behind: 0,
3166
- library: origin.name,
3770
+ library: lookup.name,
3167
3771
  contentChanged: true
3168
3772
  });
3169
- reported.add(ref);
3170
3773
  }
3171
3774
  }
3172
3775
  }
3173
3776
  }
3174
- for (const [ref, entry] of Object.entries(lock.assets)) {
3175
- if (reported.has(ref) || entry.library?.installed) continue;
3176
- const { publisher, kind, name, version } = parseCanonicalLockKey(ref);
3177
- const component = kind ? this.installTarget(kind) : void 0;
3178
- if (!kind || !name || !component) continue;
3179
- if (isDeployed(component, name, this.driverTarget, this.global, this.projectDir)) {
3180
- continue;
3181
- }
3182
- result.push({
3183
- kind,
3184
- name,
3185
- publisher: publisher ?? "",
3186
- currentVersion: entry.library?.version ?? version ?? "",
3187
- behind: 0,
3188
- ...entry.library ? { library: entry.library.name } : {},
3189
- problem: "deployed copy missing"
3190
- });
3191
- }
3192
3777
  return this.addLockGapRows(result);
3193
3778
  }
3194
3779
  /**
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.
3780
+ * Add one `problem` row per {@link AssetManager.projectState} gap whose asset is not
3781
+ * listed yet, so `outdated` lists the same assets `check`, `doctor` and `install --locked`
3782
+ * fail on. An asset that is behind its source and also broken is listed once, with its
3783
+ * version row.
3197
3784
  */
3198
3785
  async addLockGapRows(rows) {
3199
3786
  const listed = new Set(rows.map((r) => `${r.kind}:${r.name}`));
3200
- for (const gap of await this.lockGaps()) {
3787
+ for (const gap of (await this.projectState()).gaps) {
3201
3788
  const parsed = parseRefTolerant(gap.ref);
3202
3789
  const id = parsed ? `${parsed.kind}:${parsed.name}` : gap.ref;
3203
3790
  if (listed.has(id)) continue;
@@ -3214,6 +3801,33 @@ var AssetManager = class {
3214
3801
  }
3215
3802
  return rows;
3216
3803
  }
3804
+ /**
3805
+ * The deploy entry of a library-origin asset from the library its lock entry records,
3806
+ * at the locked version when that library still holds it (else its newest). Other
3807
+ * libraries holding the same ref are not consulted: the lock already chose.
3808
+ *
3809
+ * @param want - Kind, name and optional publisher of the asset.
3810
+ * @returns `null` when the lock has no library-origin entry for it, or the recorded
3811
+ * library is gone or no longer holds it (callers fall back to a general lookup).
3812
+ */
3813
+ async lockedLibraryEntry(want) {
3814
+ const snap = await this.loadLibraryIndex();
3815
+ for (const [key, e] of Object.entries(this.tryReadLock()?.assets ?? {})) {
3816
+ const id = parseCanonicalLockKey(key);
3817
+ if (!e.library || e.library.installed || !id.publisher) continue;
3818
+ if (id.kind !== want.kind || id.name !== want.name) continue;
3819
+ if (want.publisher && id.publisher !== want.publisher) continue;
3820
+ const canonical = `${id.kind}:@${id.publisher}/${id.name}`;
3821
+ const refs = e.library.version ? [`${canonical}#${e.library.version}`, canonical] : [canonical];
3822
+ const lookup = this.lockedLibrary(e.library, snap);
3823
+ const library = lookup.state === "ok" ? lookup.name : e.library.name;
3824
+ for (const r of refs) {
3825
+ const hit = (await this.resolveLibraryEntries([r], { library })).entries[0];
3826
+ if (hit) return hit;
3827
+ }
3828
+ }
3829
+ return null;
3830
+ }
3217
3831
  /**
3218
3832
  * Produce a unified diff from the deployed asset to its catalog source: `-` lines
3219
3833
  * are in the deployed copy only (local edits `update` would overwrite), `+` lines
@@ -3231,10 +3845,15 @@ var AssetManager = class {
3231
3845
  async diff(ref) {
3232
3846
  const parsed = parseRefTolerant(ref);
3233
3847
  if (!parsed) return { status: "not-found" };
3234
- const entry = await this.info(ref);
3848
+ const entry = await this.lockedLibraryEntry(parsed) ?? await this.info(ref);
3235
3849
  if (!entry || !existsSync(entry.source)) return { status: "not-found" };
3236
3850
  const component = this.installTarget(parsed.kind);
3237
3851
  if (!component) return { status: "not-deployed" };
3852
+ if (component === "agent") {
3853
+ const diff = diffDeployedAgent(entry, this.deployOptsFor(this.driverTarget));
3854
+ if (diff === null) return { status: "not-deployed" };
3855
+ return diff === "" ? { status: "identical" } : { status: "differs", diff };
3856
+ }
3238
3857
  const deployedPath = join(
3239
3858
  deployedBase(component, this.driverTarget, this.global, this.projectDir),
3240
3859
  parsed.name
@@ -3413,7 +4032,10 @@ var AssetManager = class {
3413
4032
  continue;
3414
4033
  }
3415
4034
  const gaps = [];
3416
- for (const req of full.requires) {
4035
+ const requirements = dedupeRequirements(full.requires, publisher).map(
4036
+ (r) => r.publisher || !publisher ? r : { ...r, publisher }
4037
+ );
4038
+ for (const req of requirements) {
3417
4039
  const dep = memberIdentity(depToStr(req));
3418
4040
  if (!dep) continue;
3419
4041
  const depComponent = this.installTarget(dep.kind);
@@ -3432,7 +4054,10 @@ var AssetManager = class {
3432
4054
  if (!inRepos) gaps.push({ issue, ref: depToStr(req) });
3433
4055
  }
3434
4056
  }
3435
- const inLibrary = await this.resolveLibraryEntries(gaps.map((g) => g.ref));
4057
+ const inLibrary = await this.resolveLibraryEntries(
4058
+ gaps.map((g) => g.ref),
4059
+ { quiet: true }
4060
+ );
3436
4061
  for (const g of gaps) g.issue.inRepos = inLibrary.resolved.has(g.ref);
3437
4062
  }
3438
4063
  for (const [presetRef, gaps] of await this.declaredPresetGaps(dependencies, lock)) {
@@ -3453,30 +4078,48 @@ var AssetManager = class {
3453
4078
  return issues;
3454
4079
  }
3455
4080
  /**
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.
4081
+ * The project's state as one verdict: every way the project differs from what it
4082
+ * declares and locks. `skaile check`, `skaile doctor`, `skaile outdated`, `skaile diff`
4083
+ * and `skaile install --locked` all take their verdict and exit code from this one list,
4084
+ * so one situation reads the same in all five.
3458
4085
  *
3459
4086
  * - `declared` — a `skaile.yaml` dependency with no lock entry (library or source gone,
3460
4087
  * unresolvable ref, never installed);
3461
4088
  * - `invalid` — a `dependencies:` entry that is not a parseable ref;
3462
- * - `drift` — declared + locked but its deployed files are missing;
4089
+ * - `drift` — a locked asset whose deployed files are missing;
3463
4090
  * - `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.
4091
+ * - `requires` — a deployed asset's `requires` (or a declared preset's item) that is not
4092
+ * deployed (so not locked either);
4093
+ * - `unresolved` — a lock-managed deployed asset that no source or library holds any more;
4094
+ * - `library` — a locked library asset whose library is not registered (renamed or
4095
+ * removed) or no longer holds it, while another library holds it now;
4096
+ * - `incomplete` — a deployed library asset that lacks files of its library entry;
4097
+ * - `content` — a locked library asset whose files in the library changed since the lock
4098
+ * (or whose lock hash predates the one shared asset hash); `skaile install` re-locks it;
4099
+ * - `agent` — an `agents.<id>.ref` (or `agent.definition`) that binds to no agent.
3466
4100
  *
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.
4101
+ * An asset appears once for what the lock says about it (`unresolved`, else `library`,
4102
+ * else `drift`). A declared ref no registered library or source holds names
4103
+ * `skaile library init` / `skaile remove`, never `skaile install`: `install` has nothing
4104
+ * to resolve it from.
3470
4105
  *
3471
- * @returns One {@link LockGap} per gap; empty when the lock covers everything declared.
4106
+ * Project mode only: a global manager (`global: true`) has no gaps. A project without
4107
+ * `skaile.yaml` has no declared set, so only the lock and deployed-asset gaps can appear.
4108
+ *
4109
+ * @returns The {@link ProjectState}; `gaps` is empty when the project is as declared and locked.
3472
4110
  */
3473
- async lockGaps() {
3474
- if (this.global) return [];
4111
+ async projectState() {
4112
+ if (this.global) return { gaps: [], requirements: [] };
4113
+ const lock = this.tryReadLock();
4114
+ const lockedIds = Object.keys(lock?.assets ?? {}).map(parseCanonicalLockKey);
4115
+ const requirements = await this.doctor();
3475
4116
  const gaps = [];
3476
- const issues = await this.doctor();
3477
- for (const i of issues) {
4117
+ for (const i of requirements) {
3478
4118
  if (i.unresolved) {
3479
- const ref = `${i.assetKind}:${i.assetName}`;
4119
+ const publisher = lockedIds.find(
4120
+ (id) => id.kind === i.assetKind && id.name === i.assetName
4121
+ )?.publisher;
4122
+ const ref = publisher ? `${i.assetKind}:@${publisher}/${i.assetName}` : `${i.assetKind}:${i.assetName}`;
3480
4123
  const reason = `is locked but no source or library provides it any more \u2014 its requirements cannot be checked${i.fix ? ` (${i.fix})` : ""}`;
3481
4124
  gaps.push({
3482
4125
  type: "unresolved",
@@ -3489,25 +4132,183 @@ var AssetManager = class {
3489
4132
  }
3490
4133
  const asset = `${i.assetKind}:${i.assetName}`;
3491
4134
  const dep = `${i.depKind}:${i.depName}`;
3492
- const fix = i.inRepos ? "fixable" : "not found";
4135
+ if (i.inRepos) {
4136
+ gaps.push({
4137
+ type: "requires",
4138
+ ref: dep,
4139
+ asset,
4140
+ reason: `is required by ${asset} but neither deployed nor locked (fixable)`,
4141
+ message: `${asset} requires ${dep} (fixable)`
4142
+ });
4143
+ } else {
4144
+ const fix = `register the library that holds it with \`skaile library init <name> --path <dir>\`, or drop ${asset} with \`skaile remove ${asset}\``;
4145
+ gaps.push({
4146
+ type: "requires",
4147
+ ref: dep,
4148
+ asset,
4149
+ reason: `is required by ${asset} but neither deployed nor locked (not found \u2014 ${fix})`,
4150
+ message: `${asset} requires ${dep} (not found \u2014 ${fix})`,
4151
+ manualFix: true
4152
+ });
4153
+ }
4154
+ }
4155
+ const named = new Set(
4156
+ gaps.filter((g) => g.type === "unresolved").map((g) => gapIdentity(g.ref))
4157
+ );
4158
+ for (const gap of await this.libraryOriginGaps(named)) {
4159
+ gaps.push(gap);
4160
+ named.add(gapIdentity(gap.ref));
4161
+ }
4162
+ for (const gap of this.undeployedLockGaps(lock, named)) {
4163
+ gaps.push(gap);
4164
+ named.add(gapIdentity(gap.ref));
4165
+ }
4166
+ gaps.push(...await this.declaredGaps(requirements, named));
4167
+ for (const d of await this.incompleteDeploys()) {
4168
+ const reason = `is deployed without ${d.missing.join(", ")} \u2014 run \`skaile install\` to restore it`;
4169
+ gaps.push({ type: "incomplete", ref: d.ref, reason, message: `${d.ref} ${reason}` });
4170
+ }
4171
+ for (const u of unboundAgentInstances(this.projectDir, this.driverTarget)) {
4172
+ const reason = u.ref.startsWith("agent:") ? `is bound by agent instance "${u.id}" but no agent was added for it \u2014 run \`skaile add ${u.ref}\` first, or drop the binding from skaile.yaml` : `is bound by agent instance "${u.id}" but resolves to no agent.yaml \u2014 fix the path, or drop the binding from skaile.yaml`;
3493
4173
  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})`
4174
+ type: "agent",
4175
+ ref: u.ref,
4176
+ reason,
4177
+ message: `${u.ref} ${reason}`,
4178
+ manualFix: true
3498
4179
  });
3499
4180
  }
3500
- let declared = [];
4181
+ return { gaps, requirements };
4182
+ }
4183
+ /** @deprecated Use `(await projectState()).gaps`; removed in the next major. */
4184
+ async lockGaps() {
4185
+ return (await this.projectState()).gaps;
4186
+ }
4187
+ /**
4188
+ * The {@link ProjectState} gaps that name `ref`: as the gap's own ref, or as the asset
4189
+ * whose requirement is unmet. A ref without a publisher matches any publisher.
4190
+ *
4191
+ * @param ref - Asset ref in `kind:name` or `kind:@publisher/name` form.
4192
+ */
4193
+ async gapsOf(ref) {
4194
+ const want = parseRefTolerant(ref);
4195
+ if (!want) return [];
4196
+ const names = (gapRef) => {
4197
+ const p = parseRefTolerant(gapRef);
4198
+ return p !== null && p.kind === want.kind && p.name === want.name && (!want.publisher || !p.publisher || p.publisher === want.publisher);
4199
+ };
4200
+ return (await this.projectState()).gaps.filter(
4201
+ (g) => names(g.ref) || g.asset && names(g.asset)
4202
+ );
4203
+ }
4204
+ /**
4205
+ * Locked library assets whose library cannot supply them: the library is not registered
4206
+ * (renamed or removed) or no longer holds the asset. Another library holding it makes the
4207
+ * gap fixable by `skaile install` (it re-locks); otherwise the user registers the library
4208
+ * again or drops the dependency.
4209
+ *
4210
+ * @param named - `kind:name` identities already reported; they are skipped.
4211
+ */
4212
+ async libraryOriginGaps(named) {
4213
+ const locked = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
4214
+ ([, e]) => e.library !== void 0 && !e.library.installed
4215
+ );
4216
+ if (locked.length === 0) return [];
4217
+ const snap = await this.loadLibraryIndex();
4218
+ const scans = /* @__PURE__ */ new Map();
4219
+ const diskHashes = /* @__PURE__ */ new Map();
4220
+ const gaps = [];
4221
+ for (const [key, entry] of locked) {
4222
+ const origin = entry.library;
4223
+ const { publisher, kind, name } = parseCanonicalLockKey(key);
4224
+ if (!publisher || !kind || !name || named.has(`${kind}:${name}`)) continue;
4225
+ const ref = `${kind}:@${publisher}/${name}`;
4226
+ const lookup = this.lockedLibrary(origin, snap);
4227
+ const libPath = lookup.state === "ok" ? lookup.path : null;
4228
+ if (lookup.state === "ok" && libPath) {
4229
+ const scanKey = `${libPath}\0${publisher}`;
4230
+ let scanned = scans.get(scanKey);
4231
+ if (!scanned) {
4232
+ scanned = await scanDirectory(libPath, publisher, { knownKinds: this.knownKinds });
4233
+ scans.set(scanKey, scanned);
4234
+ }
4235
+ const found = scanned.find((x) => x.kind === kind && x.name === name);
4236
+ if (found) {
4237
+ const onDisk = origin.sha256 ? libraryDiskHashes(libPath, diskHashes, this.registry).get(`${kind}:${name}`) : void 0;
4238
+ if (onDisk !== void 0 && onDisk !== origin.sha256) {
4239
+ const diskVersion = found.version || newestLibraryDef(snap, lookup.name, { kind, publisher, name })?.version;
4240
+ if (!diskVersion || !gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
4241
+ const reason2 = `is locked, but library "${lookup.name}" content changed since lock \u2014 or the lock predates the one shared asset hash; run \`skaile install\` to re-lock it`;
4242
+ gaps.push({ type: "content", ref, reason: reason2, message: `${ref} ${reason2}` });
4243
+ }
4244
+ }
4245
+ continue;
4246
+ }
4247
+ }
4248
+ if (lookup.state === "dir-missing") {
4249
+ const reason2 = `is locked, but ${lockedLibraryProblem(lookup, origin, ref)}`;
4250
+ gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}`, manualFix: true });
4251
+ continue;
4252
+ }
4253
+ if (lookup.state === "unregistered") {
4254
+ const reason2 = `is locked, but library "${origin.name}" is not registered (renamed or removed?) \u2014 ${unregisteredLibraryFix(origin, ref)}`;
4255
+ gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}`, manualFix: true });
4256
+ continue;
4257
+ }
4258
+ const heldElsewhere = (await this.resolveLibraryEntries([ref], { quiet: true })).resolved.size > 0;
4259
+ if (heldElsewhere) {
4260
+ const reason2 = `is locked, but library "${lookup.name}" no longer holds it \u2014 run \`skaile install\` to re-lock it against the library that holds it now`;
4261
+ gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}` });
4262
+ continue;
4263
+ }
4264
+ const reason = `is locked, but library "${lookup.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${ref}\``;
4265
+ gaps.push({ type: "library", ref, reason, message: `${ref} ${reason}`, manualFix: true });
4266
+ }
4267
+ return gaps;
4268
+ }
4269
+ /**
4270
+ * Locked assets (declared or required by one) whose deployed copy is gone: `skaile install`
4271
+ * restores them. Same deployability rule as {@link AssetManager.listDeclared}'s `drift`.
4272
+ *
4273
+ * @param named - `kind:name` identities already reported; they are skipped.
4274
+ */
4275
+ undeployedLockGaps(lock, named) {
4276
+ const gaps = [];
4277
+ for (const [key, entry] of Object.entries(lock?.assets ?? {})) {
4278
+ if (entry.library?.installed) continue;
4279
+ const { publisher, kind, name } = parseCanonicalLockKey(key);
4280
+ const component = kind ? this.installTarget(kind) : void 0;
4281
+ if (!kind || !name || !component || named.has(`${kind}:${name}`)) continue;
4282
+ if (isDeployed(component, name, this.driverTarget, this.global, this.projectDir)) continue;
4283
+ const ref = publisher ? `${kind}:@${publisher}/${name}` : `${kind}:${name}`;
4284
+ const reason = "is locked but its deployed copy is missing \u2014 run `skaile install`";
4285
+ gaps.push({ type: "drift", ref, reason, message: `${ref} ${reason}` });
4286
+ }
4287
+ return gaps;
4288
+ }
4289
+ /**
4290
+ * The `skaile.yaml` side of {@link AssetManager.projectState}: declared refs that do not
4291
+ * parse, never resolved into the lock, are not materialized, or contradict a `#pin`.
4292
+ * A project with no (or an unreadable) `skaile.yaml` has no declared set to reconcile.
4293
+ *
4294
+ * @param requirements - What {@link AssetManager.doctor} found; a declared preset whose
4295
+ * items it already named needs no second line.
4296
+ * @param named - `kind:name` identities already reported; a declared asset among them
4297
+ * needs no `drift` line.
4298
+ */
4299
+ async declaredGaps(requirements, named) {
4300
+ let declared;
3501
4301
  try {
3502
4302
  declared = await this.listDeclared();
3503
4303
  } catch {
3504
- return gaps;
4304
+ return [];
3505
4305
  }
4306
+ const gaps = [];
3506
4307
  const unlocked = declared.filter((d) => d.state === "declared").map((d) => d.ref);
3507
4308
  const hints = await this.publisherChangeHints(unlocked);
3508
4309
  const lockKeys = Object.keys(this.tryReadLock()?.assets ?? {});
3509
4310
  for (const d of declared) {
3510
- if (d.kind === "preset" && issues.some((i) => i.assetKind === "preset" && i.assetName === d.name)) {
4311
+ if (d.kind === "preset" && requirements.some((i) => i.assetKind === "preset" && i.assetName === d.name)) {
3511
4312
  continue;
3512
4313
  }
3513
4314
  let gap;
@@ -3517,19 +4318,41 @@ var AssetManager = class {
3517
4318
  reason: "is not a valid asset ref (expected kind:@<publisher>/<name>#<version>)"
3518
4319
  };
3519
4320
  } else if (d.state === "drift") {
4321
+ if (named.has(`${d.kind}:${d.name}`)) continue;
3520
4322
  gap = {
3521
4323
  type: "drift",
3522
4324
  reason: "is declared and locked but not materialized \u2014 run `skaile install`"
3523
4325
  };
3524
4326
  } else if (d.state === "declared") {
3525
4327
  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
- };
4328
+ if (hint) {
4329
+ gap = {
4330
+ type: "declared",
4331
+ reason: `is declared but does not resolve${hint}`,
4332
+ manualFix: true
4333
+ };
4334
+ } else if (await this.declaredRefResolvable(d.ref)) {
4335
+ gap = { type: "declared", reason: "is declared but not locked \u2014 run `skaile install`" };
4336
+ } else {
4337
+ gap = {
4338
+ type: "declared",
4339
+ reason: `is declared but no library or source holds it \u2014 register the library that holds it with \`skaile library init <name> --path <dir>\`, or drop it with \`skaile remove ${d.ref.split("#")[0]}\``,
4340
+ manualFix: true
4341
+ };
4342
+ }
3530
4343
  } else {
3531
4344
  const want = parseAssetRef(d.ref);
3532
- if (!want.pin || want.kind === "preset") continue;
4345
+ if (want.kind === "preset") {
4346
+ if (this.presetRecorded(want) || await this.info(d.ref) !== null) continue;
4347
+ gap = await this.declaredRefResolvable(d.ref) ? { type: "declared", reason: "is declared but not locked \u2014 run `skaile install`" } : {
4348
+ type: "declared",
4349
+ reason: `is declared but no library or source holds it \u2014 register the library that holds it with \`skaile library init <name> --path <dir>\`, or drop it with \`skaile remove ${d.ref.split("#")[0]}\``,
4350
+ manualFix: true
4351
+ };
4352
+ gaps.push({ ...gap, ref: d.ref, message: `${d.ref} ${gap.reason}` });
4353
+ continue;
4354
+ }
4355
+ if (!want.pin) continue;
3533
4356
  const held = lockKeys.map((key) => ({ key, id: parseCanonicalLockKey(key) })).filter(
3534
4357
  ({ id }) => id.kind === want.kind && id.name === want.name && (!want.publisher || id.publisher === want.publisher)
3535
4358
  );
@@ -3544,6 +4367,22 @@ var AssetManager = class {
3544
4367
  }
3545
4368
  return gaps;
3546
4369
  }
4370
+ /** Whether the lock's `presets` slice records `want` (kind, name and, when both carry one, publisher). */
4371
+ presetRecorded(want) {
4372
+ return (this.tryReadLock()?.presets ?? []).some((ref) => {
4373
+ const p = parseRefTolerant(ref);
4374
+ return p !== null && p.kind === want.kind && p.name === want.name && (!want.publisher || !p.publisher || p.publisher === want.publisher);
4375
+ });
4376
+ }
4377
+ /**
4378
+ * Whether `skaile install` can resolve a declared ref: a source or library holds it, or the
4379
+ * project declares a store that may. The global catalog is not asked: a check never goes
4380
+ * to the network.
4381
+ */
4382
+ async declaredRefResolvable(ref) {
4383
+ if (await this.info(ref) !== null) return true;
4384
+ return this.loadConfig().stores.length > 0;
4385
+ }
3547
4386
  /**
3548
4387
  * Items of each declared `preset:` dependency that the lock does not cover.
3549
4388
  *
@@ -3810,6 +4649,54 @@ var AssetManager = class {
3810
4649
  writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
3811
4650
  return true;
3812
4651
  }
4652
+ /**
4653
+ * Drop the agent instances of `skaile.yaml` (`agents.<id>`, or the `agent.definition`
4654
+ * default) whose `ref` binds to `target`, and the native agent files an install rendered for
4655
+ * them. Only an `agent` target has bindings; project mode only. A `ref` without a publisher
4656
+ * matches any publisher, and a pin is ignored.
4657
+ *
4658
+ * @returns `true` when a binding was removed.
4659
+ */
4660
+ removeAgentBindings(target) {
4661
+ if (this.global || target.kind !== "agent") return false;
4662
+ const configPath = join(this.stateRoot, "skaile.yaml");
4663
+ if (!existsSync(configPath)) return false;
4664
+ let raw;
4665
+ try {
4666
+ raw = parse(readFileSync(configPath, "utf8"));
4667
+ } catch {
4668
+ return false;
4669
+ }
4670
+ if (!raw || typeof raw !== "object") return false;
4671
+ const wantPub = target.publisher?.replace(/^@/, "");
4672
+ const binds = (ref) => {
4673
+ const p = typeof ref === "string" ? parseRefTolerant(ref) : null;
4674
+ if (!p || p.kind !== "agent" || p.name !== target.name) return false;
4675
+ const havePub = p.publisher?.replace(/^@/, "");
4676
+ return wantPub === void 0 || havePub === void 0 || havePub === wantPub;
4677
+ };
4678
+ const ids = [];
4679
+ const instances = raw.agents;
4680
+ if (instances && typeof instances === "object") {
4681
+ for (const [id, instance] of Object.entries(instances)) {
4682
+ if (!binds(instance?.ref)) continue;
4683
+ ids.push(id);
4684
+ delete instances[id];
4685
+ }
4686
+ if (Object.keys(instances).length === 0) delete raw.agents;
4687
+ }
4688
+ if (raw.agent && typeof raw.agent === "object" && binds(raw.agent.definition)) {
4689
+ ids.push(DEFAULT_AGENT_INSTANCE_ID);
4690
+ delete raw.agent.definition;
4691
+ if (Object.keys(raw.agent).length === 0) delete raw.agent;
4692
+ }
4693
+ if (ids.length === 0) return false;
4694
+ writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
4695
+ for (const driverTarget of this.targetHarnesses()) {
4696
+ removeAgentInstanceRenderings({ projectDir: this.projectDir, driverTarget, ids });
4697
+ }
4698
+ return true;
4699
+ }
3813
4700
  resolveRepoDir(decl, name) {
3814
4701
  const links = readLinks(this.projectDir);
3815
4702
  if (links[name]) {
@@ -3828,5 +4715,5 @@ var AssetManager = class {
3828
4715
  };
3829
4716
 
3830
4717
  export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, installAgentInstances, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName };
3831
- //# sourceMappingURL=chunk-H7QUDDKH.js.map
3832
- //# sourceMappingURL=chunk-H7QUDDKH.js.map
4718
+ //# sourceMappingURL=chunk-2JMLDZLP.js.map
4719
+ //# sourceMappingURL=chunk-2JMLDZLP.js.map