@canonical/pragma-cli 0.34.0 → 0.36.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 (832) hide show
  1. package/README.md +153 -30
  2. package/bundled-skills/add-standard/SKILL.md +338 -0
  3. package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
  4. package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
  5. package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
  6. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
  7. package/bundled-skills/anatomy-author/SKILL.md +1226 -0
  8. package/bundled-skills/design-auditor/SKILL.md +455 -0
  9. package/bundled-skills/implement-component/SKILL.md +333 -0
  10. package/bundled-skills/specify-component/SKILL.md +331 -0
  11. package/bundled-skills/specify-pattern/SKILL.md +292 -0
  12. package/dist/.build-ok +0 -0
  13. package/dist/package.json +84 -0
  14. package/dist/pragma.conf.js +1016 -0
  15. package/dist/pragma.conf.js.map +1 -0
  16. package/dist/src/bin.js +356 -0
  17. package/dist/src/bin.js.map +1 -0
  18. package/dist/src/capabilities/capabilities/capabilities.render.js +70 -0
  19. package/dist/src/capabilities/capabilities/capabilities.render.js.map +1 -0
  20. package/dist/src/capabilities/capabilities/capabilities.verb.js +59 -0
  21. package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -0
  22. package/dist/src/capabilities/capabilities/catalog.js +130 -0
  23. package/dist/src/capabilities/capabilities/catalog.js.map +1 -0
  24. package/dist/src/capabilities/capabilities/hints.js +195 -0
  25. package/dist/src/capabilities/capabilities/hints.js.map +1 -0
  26. package/dist/src/capabilities/capabilities/index.js +11 -0
  27. package/dist/src/capabilities/capabilities/index.js.map +1 -0
  28. package/dist/src/capabilities/capabilities/types.js +11 -0
  29. package/dist/src/capabilities/capabilities/types.js.map +1 -0
  30. package/dist/src/capabilities/colophon/collectColophon.js +83 -0
  31. package/dist/src/capabilities/colophon/collectColophon.js.map +1 -0
  32. package/dist/src/capabilities/colophon/colophon.render.js +59 -0
  33. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -0
  34. package/dist/src/capabilities/colophon/colophon.verb.js +43 -0
  35. package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -0
  36. package/dist/src/capabilities/colophon/index.js +5 -0
  37. package/dist/src/capabilities/colophon/index.js.map +1 -0
  38. package/dist/src/capabilities/colophon/markdownTerminal.js +90 -0
  39. package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -0
  40. package/dist/src/capabilities/colophon/types.js +10 -0
  41. package/dist/src/capabilities/colophon/types.js.map +1 -0
  42. package/dist/src/capabilities/config/collectConfigShow.js +59 -0
  43. package/dist/src/capabilities/config/collectConfigShow.js.map +1 -0
  44. package/dist/src/capabilities/config/field.render.js +22 -0
  45. package/dist/src/capabilities/config/field.render.js.map +1 -0
  46. package/dist/src/capabilities/config/fields.js +46 -0
  47. package/dist/src/capabilities/config/fields.js.map +1 -0
  48. package/dist/src/capabilities/config/get.verb.js +61 -0
  49. package/dist/src/capabilities/config/get.verb.js.map +1 -0
  50. package/dist/src/capabilities/config/index.js +5 -0
  51. package/dist/src/capabilities/config/index.js.map +1 -0
  52. package/dist/src/capabilities/config/runField.js +59 -0
  53. package/dist/src/capabilities/config/runField.js.map +1 -0
  54. package/dist/src/capabilities/config/runGet.js +28 -0
  55. package/dist/src/capabilities/config/runGet.js.map +1 -0
  56. package/dist/src/capabilities/config/runSet.js +53 -0
  57. package/dist/src/capabilities/config/runSet.js.map +1 -0
  58. package/dist/src/capabilities/config/set.verb.js +66 -0
  59. package/dist/src/capabilities/config/set.verb.js.map +1 -0
  60. package/dist/src/capabilities/config/show.render.js +84 -0
  61. package/dist/src/capabilities/config/show.render.js.map +1 -0
  62. package/dist/src/capabilities/config/show.verb.js +44 -0
  63. package/dist/src/capabilities/config/show.verb.js.map +1 -0
  64. package/dist/src/capabilities/config/types.js +5 -0
  65. package/dist/src/capabilities/config/types.js.map +1 -0
  66. package/dist/src/capabilities/config/unset.verb.js +49 -0
  67. package/dist/src/capabilities/config/unset.verb.js.map +1 -0
  68. package/dist/src/capabilities/create/cliProjection.js +195 -0
  69. package/dist/src/capabilities/create/cliProjection.js.map +1 -0
  70. package/dist/src/capabilities/create/constants.js +47 -0
  71. package/dist/src/capabilities/create/constants.js.map +1 -0
  72. package/dist/src/capabilities/create/create.render.js +100 -0
  73. package/dist/src/capabilities/create/create.render.js.map +1 -0
  74. package/dist/src/capabilities/create/create.verb.js +438 -0
  75. package/dist/src/capabilities/create/create.verb.js.map +1 -0
  76. package/dist/src/capabilities/create/createHelp.js +47 -0
  77. package/dist/src/capabilities/create/createHelp.js.map +1 -0
  78. package/dist/src/capabilities/create/createSurface.generated.js +394 -0
  79. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -0
  80. package/dist/src/capabilities/create/generatorToVerbSpec.js +106 -0
  81. package/dist/src/capabilities/create/generatorToVerbSpec.js.map +1 -0
  82. package/dist/src/capabilities/create/index.js +18 -0
  83. package/dist/src/capabilities/create/index.js.map +1 -0
  84. package/dist/src/capabilities/create/messages.js +16 -0
  85. package/dist/src/capabilities/create/messages.js.map +1 -0
  86. package/dist/src/capabilities/create/mount.js +294 -0
  87. package/dist/src/capabilities/create/mount.js.map +1 -0
  88. package/dist/src/capabilities/create/pathJail.js +65 -0
  89. package/dist/src/capabilities/create/pathJail.js.map +1 -0
  90. package/dist/src/capabilities/create/pickGenerator.js +53 -0
  91. package/dist/src/capabilities/create/pickGenerator.js.map +1 -0
  92. package/dist/src/capabilities/create/types.js +2 -0
  93. package/dist/src/capabilities/create/types.js.map +1 -0
  94. package/dist/src/capabilities/distribution.js +40 -0
  95. package/dist/src/capabilities/distribution.js.map +1 -0
  96. package/dist/src/capabilities/doctor/checks/checkKeStore.js +40 -0
  97. package/dist/src/capabilities/doctor/checks/checkKeStore.js.map +1 -0
  98. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +21 -0
  99. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -0
  100. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +148 -0
  101. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -0
  102. package/dist/src/capabilities/doctor/checks/checkPragmaVersion.js +19 -0
  103. package/dist/src/capabilities/doctor/checks/checkPragmaVersion.js.map +1 -0
  104. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +199 -0
  105. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -0
  106. package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
  107. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
  108. package/dist/src/capabilities/doctor/checks/index.js +27 -0
  109. package/dist/src/capabilities/doctor/checks/index.js.map +1 -0
  110. package/dist/src/capabilities/doctor/checks/mcpCommand.js +57 -0
  111. package/dist/src/capabilities/doctor/checks/mcpCommand.js.map +1 -0
  112. package/dist/src/capabilities/doctor/checks/targetHealth.js +349 -0
  113. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -0
  114. package/dist/src/capabilities/doctor/doctor.render.js +164 -0
  115. package/dist/src/capabilities/doctor/doctor.render.js.map +1 -0
  116. package/dist/src/capabilities/doctor/doctor.verb.js +49 -0
  117. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -0
  118. package/dist/src/capabilities/doctor/index.js +5 -0
  119. package/dist/src/capabilities/doctor/index.js.map +1 -0
  120. package/dist/src/capabilities/doctor/runChecks.js +86 -0
  121. package/dist/src/capabilities/doctor/runChecks.js.map +1 -0
  122. package/dist/src/capabilities/doctor/types.js +5 -0
  123. package/dist/src/capabilities/doctor/types.js.map +1 -0
  124. package/dist/src/capabilities/graph/index.js +19 -0
  125. package/dist/src/capabilities/graph/index.js.map +1 -0
  126. package/dist/src/capabilities/graph/inspect.render.js +76 -0
  127. package/dist/src/capabilities/graph/inspect.render.js.map +1 -0
  128. package/dist/src/capabilities/graph/inspect.verb.js +53 -0
  129. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -0
  130. package/dist/src/capabilities/graph/query.render.js +73 -0
  131. package/dist/src/capabilities/graph/query.render.js.map +1 -0
  132. package/dist/src/capabilities/graph/query.verb.js +49 -0
  133. package/dist/src/capabilities/graph/query.verb.js.map +1 -0
  134. package/dist/src/capabilities/graph/runQuery.js +41 -0
  135. package/dist/src/capabilities/graph/runQuery.js.map +1 -0
  136. package/dist/src/capabilities/index.js +56 -0
  137. package/dist/src/capabilities/index.js.map +1 -0
  138. package/dist/src/capabilities/info/collectInfo.js +72 -0
  139. package/dist/src/capabilities/info/collectInfo.js.map +1 -0
  140. package/dist/src/capabilities/info/index.js +5 -0
  141. package/dist/src/capabilities/info/index.js.map +1 -0
  142. package/dist/src/capabilities/info/info.render.js +57 -0
  143. package/dist/src/capabilities/info/info.render.js.map +1 -0
  144. package/dist/src/capabilities/info/info.verb.js +42 -0
  145. package/dist/src/capabilities/info/info.verb.js.map +1 -0
  146. package/dist/src/capabilities/info/types.js +5 -0
  147. package/dist/src/capabilities/info/types.js.map +1 -0
  148. package/dist/src/capabilities/meta/complete.verb.js +43 -0
  149. package/dist/src/capabilities/meta/complete.verb.js.map +1 -0
  150. package/dist/src/capabilities/meta/index.js +13 -0
  151. package/dist/src/capabilities/meta/index.js.map +1 -0
  152. package/dist/src/capabilities/meta/mcp.verb.js +34 -0
  153. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -0
  154. package/dist/src/capabilities/ontology/index.js +21 -0
  155. package/dist/src/capabilities/ontology/index.js.map +1 -0
  156. package/dist/src/capabilities/ontology/queries.js +123 -0
  157. package/dist/src/capabilities/ontology/queries.js.map +1 -0
  158. package/dist/src/capabilities/ontology/render.js +66 -0
  159. package/dist/src/capabilities/ontology/render.js.map +1 -0
  160. package/dist/src/capabilities/ontology/verbs.js +181 -0
  161. package/dist/src/capabilities/ontology/verbs.js.map +1 -0
  162. package/dist/src/capabilities/prompt/index.js +16 -0
  163. package/dist/src/capabilities/prompt/index.js.map +1 -0
  164. package/dist/src/capabilities/prompt/prompt.render.js +78 -0
  165. package/dist/src/capabilities/prompt/prompt.render.js.map +1 -0
  166. package/dist/src/capabilities/prompt/types.js +10 -0
  167. package/dist/src/capabilities/prompt/types.js.map +1 -0
  168. package/dist/src/capabilities/prompt/verbs.js +93 -0
  169. package/dist/src/capabilities/prompt/verbs.js.map +1 -0
  170. package/dist/src/capabilities/resources/index.js +10 -0
  171. package/dist/src/capabilities/resources/index.js.map +1 -0
  172. package/dist/src/capabilities/resources/provider.js +423 -0
  173. package/dist/src/capabilities/resources/provider.js.map +1 -0
  174. package/dist/src/capabilities/setup/buildPlan.js +172 -0
  175. package/dist/src/capabilities/setup/buildPlan.js.map +1 -0
  176. package/dist/src/capabilities/setup/index.js +5 -0
  177. package/dist/src/capabilities/setup/index.js.map +1 -0
  178. package/dist/src/capabilities/setup/mode.js +53 -0
  179. package/dist/src/capabilities/setup/mode.js.map +1 -0
  180. package/dist/src/capabilities/setup/operations/index.js +39 -0
  181. package/dist/src/capabilities/setup/operations/index.js.map +1 -0
  182. package/dist/src/capabilities/setup/operations/setupCompletions.js +160 -0
  183. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -0
  184. package/dist/src/capabilities/setup/operations/setupConfig.js +101 -0
  185. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -0
  186. package/dist/src/capabilities/setup/operations/setupGenerator.js +390 -0
  187. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -0
  188. package/dist/src/capabilities/setup/operations/setupLsp.js +329 -0
  189. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -0
  190. package/dist/src/capabilities/setup/operations/setupMcp.js +253 -0
  191. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -0
  192. package/dist/src/capabilities/setup/operations/setupSkills.js +594 -0
  193. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -0
  194. package/dist/src/capabilities/setup/plan.js +128 -0
  195. package/dist/src/capabilities/setup/plan.js.map +1 -0
  196. package/dist/src/capabilities/setup/plan.render.js +271 -0
  197. package/dist/src/capabilities/setup/plan.render.js.map +1 -0
  198. package/dist/src/capabilities/setup/setup.render.js +54 -0
  199. package/dist/src/capabilities/setup/setup.render.js.map +1 -0
  200. package/dist/src/capabilities/setup/setup.verb.js +387 -0
  201. package/dist/src/capabilities/setup/setup.verb.js.map +1 -0
  202. package/dist/src/capabilities/setup/shell.js +196 -0
  203. package/dist/src/capabilities/setup/shell.js.map +1 -0
  204. package/dist/src/capabilities/setup/targets.js +328 -0
  205. package/dist/src/capabilities/setup/targets.js.map +1 -0
  206. package/dist/src/capabilities/setup/types.js +5 -0
  207. package/dist/src/capabilities/setup/types.js.map +1 -0
  208. package/dist/src/capabilities/shared/assertExecOk.js +137 -0
  209. package/dist/src/capabilities/shared/assertExecOk.js.map +1 -0
  210. package/dist/src/capabilities/shared/index.js +27 -0
  211. package/dist/src/capabilities/shared/index.js.map +1 -0
  212. package/dist/src/capabilities/shared/packageManager.js +349 -0
  213. package/dist/src/capabilities/shared/packageManager.js.map +1 -0
  214. package/dist/src/capabilities/shared/registry.js +55 -0
  215. package/dist/src/capabilities/shared/registry.js.map +1 -0
  216. package/dist/src/capabilities/skill/discover.js +281 -0
  217. package/dist/src/capabilities/skill/discover.js.map +1 -0
  218. package/dist/src/capabilities/skill/index.js +10 -0
  219. package/dist/src/capabilities/skill/index.js.map +1 -0
  220. package/dist/src/capabilities/skill/render.js +49 -0
  221. package/dist/src/capabilities/skill/render.js.map +1 -0
  222. package/dist/src/capabilities/skill/verbs.js +83 -0
  223. package/dist/src/capabilities/skill/verbs.js.map +1 -0
  224. package/dist/src/capabilities/sources/collectStatus.js +103 -0
  225. package/dist/src/capabilities/sources/collectStatus.js.map +1 -0
  226. package/dist/src/capabilities/sources/index.js +13 -0
  227. package/dist/src/capabilities/sources/index.js.map +1 -0
  228. package/dist/src/capabilities/sources/installSkills.js +309 -0
  229. package/dist/src/capabilities/sources/installSkills.js.map +1 -0
  230. package/dist/src/capabilities/sources/runUpdate.js +460 -0
  231. package/dist/src/capabilities/sources/runUpdate.js.map +1 -0
  232. package/dist/src/capabilities/sources/status.render.js +64 -0
  233. package/dist/src/capabilities/sources/status.render.js.map +1 -0
  234. package/dist/src/capabilities/sources/status.verb.js +40 -0
  235. package/dist/src/capabilities/sources/status.verb.js.map +1 -0
  236. package/dist/src/capabilities/sources/types.js +6 -0
  237. package/dist/src/capabilities/sources/types.js.map +1 -0
  238. package/dist/src/capabilities/sources/update.render.js +34 -0
  239. package/dist/src/capabilities/sources/update.render.js.map +1 -0
  240. package/dist/src/capabilities/sources/update.verb.js +55 -0
  241. package/dist/src/capabilities/sources/update.verb.js.map +1 -0
  242. package/dist/src/capabilities/upgrade/index.js +5 -0
  243. package/dist/src/capabilities/upgrade/index.js.map +1 -0
  244. package/dist/src/capabilities/upgrade/runUpgrade.js +135 -0
  245. package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -0
  246. package/dist/src/capabilities/upgrade/types.js +5 -0
  247. package/dist/src/capabilities/upgrade/types.js.map +1 -0
  248. package/dist/src/capabilities/upgrade/upgrade.render.js +52 -0
  249. package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -0
  250. package/dist/src/capabilities/upgrade/upgrade.verb.js +52 -0
  251. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -0
  252. package/dist/src/capabilities/version/index.js +10 -0
  253. package/dist/src/capabilities/version/index.js.map +1 -0
  254. package/dist/src/capabilities/version/version.verb.js +36 -0
  255. package/dist/src/capabilities/version/version.verb.js.map +1 -0
  256. package/dist/src/constants.js +87 -0
  257. package/dist/src/constants.js.map +1 -0
  258. package/dist/src/index.js +12 -0
  259. package/dist/src/index.js.map +1 -0
  260. package/dist/src/kernel/completion/complete.js +66 -0
  261. package/dist/src/kernel/completion/complete.js.map +1 -0
  262. package/dist/src/kernel/completion/emitScripts.js +92 -0
  263. package/dist/src/kernel/completion/emitScripts.js.map +1 -0
  264. package/dist/src/kernel/completion/entitySource.js +285 -0
  265. package/dist/src/kernel/completion/entitySource.js.map +1 -0
  266. package/dist/src/kernel/completion/index.js +14 -0
  267. package/dist/src/kernel/completion/index.js.map +1 -0
  268. package/dist/src/kernel/completion/model.js +301 -0
  269. package/dist/src/kernel/completion/model.js.map +1 -0
  270. package/dist/src/kernel/completion/parse.js +253 -0
  271. package/dist/src/kernel/completion/parse.js.map +1 -0
  272. package/dist/src/kernel/completion/rank.js +101 -0
  273. package/dist/src/kernel/completion/rank.js.map +1 -0
  274. package/dist/src/kernel/completion/resolve.js +100 -0
  275. package/dist/src/kernel/completion/resolve.js.map +1 -0
  276. package/dist/src/kernel/completion/templates/bash.js +209 -0
  277. package/dist/src/kernel/completion/templates/bash.js.map +1 -0
  278. package/dist/src/kernel/completion/templates/fish.js +214 -0
  279. package/dist/src/kernel/completion/templates/fish.js.map +1 -0
  280. package/dist/src/kernel/completion/templates/index.js +23 -0
  281. package/dist/src/kernel/completion/templates/index.js.map +1 -0
  282. package/dist/src/kernel/completion/templates/shared.js +98 -0
  283. package/dist/src/kernel/completion/templates/shared.js.map +1 -0
  284. package/dist/src/kernel/completion/templates/zsh.js +201 -0
  285. package/dist/src/kernel/completion/templates/zsh.js.map +1 -0
  286. package/dist/src/kernel/completion/types.js +13 -0
  287. package/dist/src/kernel/completion/types.js.map +1 -0
  288. package/dist/src/kernel/config/defaults.js +17 -0
  289. package/dist/src/kernel/config/defaults.js.map +1 -0
  290. package/dist/src/kernel/config/evaluateProjectConfig.js +126 -0
  291. package/dist/src/kernel/config/evaluateProjectConfig.js.map +1 -0
  292. package/dist/src/kernel/config/findProjectConfig.js +43 -0
  293. package/dist/src/kernel/config/findProjectConfig.js.map +1 -0
  294. package/dist/src/kernel/config/firstRun.js +34 -0
  295. package/dist/src/kernel/config/firstRun.js.map +1 -0
  296. package/dist/src/kernel/config/globalConfig.js +79 -0
  297. package/dist/src/kernel/config/globalConfig.js.map +1 -0
  298. package/dist/src/kernel/config/index.js +16 -0
  299. package/dist/src/kernel/config/index.js.map +1 -0
  300. package/dist/src/kernel/config/paths.js +35 -0
  301. package/dist/src/kernel/config/paths.js.map +1 -0
  302. package/dist/src/kernel/config/readConfig.js +78 -0
  303. package/dist/src/kernel/config/readConfig.js.map +1 -0
  304. package/dist/src/kernel/config/schema.js +130 -0
  305. package/dist/src/kernel/config/schema.js.map +1 -0
  306. package/dist/src/kernel/config/types.js +19 -0
  307. package/dist/src/kernel/config/types.js.map +1 -0
  308. package/dist/src/kernel/config/writeConfigField.js +59 -0
  309. package/dist/src/kernel/config/writeConfigField.js.map +1 -0
  310. package/dist/src/kernel/copy.js +43 -0
  311. package/dist/src/kernel/copy.js.map +1 -0
  312. package/dist/src/kernel/error/PragmaError.js +149 -0
  313. package/dist/src/kernel/error/PragmaError.js.map +1 -0
  314. package/dist/src/kernel/error/constants.js +40 -0
  315. package/dist/src/kernel/error/constants.js.map +1 -0
  316. package/dist/src/kernel/error/fromTaskError.js +96 -0
  317. package/dist/src/kernel/error/fromTaskError.js.map +1 -0
  318. package/dist/src/kernel/error/index.js +13 -0
  319. package/dist/src/kernel/error/index.js.map +1 -0
  320. package/dist/src/kernel/error/recovery.js +52 -0
  321. package/dist/src/kernel/error/recovery.js.map +1 -0
  322. package/dist/src/kernel/error/renderError.js +115 -0
  323. package/dist/src/kernel/error/renderError.js.map +1 -0
  324. package/dist/src/kernel/error/serialize.js +25 -0
  325. package/dist/src/kernel/error/serialize.js.map +1 -0
  326. package/dist/src/kernel/error/types.js +9 -0
  327. package/dist/src/kernel/error/types.js.map +1 -0
  328. package/dist/src/kernel/index.js +34 -0
  329. package/dist/src/kernel/index.js.map +1 -0
  330. package/dist/src/kernel/interactivity.js +68 -0
  331. package/dist/src/kernel/interactivity.js.map +1 -0
  332. package/dist/src/kernel/packs/collect.js +187 -0
  333. package/dist/src/kernel/packs/collect.js.map +1 -0
  334. package/dist/src/kernel/packs/compile.js +304 -0
  335. package/dist/src/kernel/packs/compile.js.map +1 -0
  336. package/dist/src/kernel/packs/disclosure.js +76 -0
  337. package/dist/src/kernel/packs/disclosure.js.map +1 -0
  338. package/dist/src/kernel/packs/glob.js +38 -0
  339. package/dist/src/kernel/packs/glob.js.map +1 -0
  340. package/dist/src/kernel/packs/graphql/buildLookupDocument.js +258 -0
  341. package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -0
  342. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +157 -0
  343. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -0
  344. package/dist/src/kernel/packs/graphql/index.js +20 -0
  345. package/dist/src/kernel/packs/graphql/index.js.map +1 -0
  346. package/dist/src/kernel/packs/graphql/nameMap.js +63 -0
  347. package/dist/src/kernel/packs/graphql/nameMap.js.map +1 -0
  348. package/dist/src/kernel/packs/index.js +34 -0
  349. package/dist/src/kernel/packs/index.js.map +1 -0
  350. package/dist/src/kernel/packs/iri.js +110 -0
  351. package/dist/src/kernel/packs/iri.js.map +1 -0
  352. package/dist/src/kernel/packs/renderPack.js +129 -0
  353. package/dist/src/kernel/packs/renderPack.js.map +1 -0
  354. package/dist/src/kernel/packs/resolveEntity.js +274 -0
  355. package/dist/src/kernel/packs/resolveEntity.js.map +1 -0
  356. package/dist/src/kernel/packs/runBodies.js +140 -0
  357. package/dist/src/kernel/packs/runBodies.js.map +1 -0
  358. package/dist/src/kernel/packs/sample.js +56 -0
  359. package/dist/src/kernel/packs/sample.js.map +1 -0
  360. package/dist/src/kernel/packs/schema.js +421 -0
  361. package/dist/src/kernel/packs/schema.js.map +1 -0
  362. package/dist/src/kernel/packs/sparql/applyFilters.js +167 -0
  363. package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -0
  364. package/dist/src/kernel/packs/sparql/applySearch.js +25 -0
  365. package/dist/src/kernel/packs/sparql/applySearch.js.map +1 -0
  366. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +480 -0
  367. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -0
  368. package/dist/src/kernel/packs/sparql/escape.js +21 -0
  369. package/dist/src/kernel/packs/sparql/escape.js.map +1 -0
  370. package/dist/src/kernel/packs/sparql/index.js +31 -0
  371. package/dist/src/kernel/packs/sparql/index.js.map +1 -0
  372. package/dist/src/kernel/packs/sparql/runSelect.js +101 -0
  373. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -0
  374. package/dist/src/kernel/packs/types.js +30 -0
  375. package/dist/src/kernel/packs/types.js.map +1 -0
  376. package/dist/src/kernel/packs/uniqueness.js +30 -0
  377. package/dist/src/kernel/packs/uniqueness.js.map +1 -0
  378. package/dist/src/kernel/project/cli/buildProgram.js +263 -0
  379. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -0
  380. package/dist/src/kernel/project/cli/constants.js +27 -0
  381. package/dist/src/kernel/project/cli/constants.js.map +1 -0
  382. package/dist/src/kernel/project/cli/dispatch.js +420 -0
  383. package/dist/src/kernel/project/cli/dispatch.js.map +1 -0
  384. package/dist/src/kernel/project/cli/exitCodes.js +66 -0
  385. package/dist/src/kernel/project/cli/exitCodes.js.map +1 -0
  386. package/dist/src/kernel/project/cli/globalFlags.js +219 -0
  387. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -0
  388. package/dist/src/kernel/project/cli/helpFormat.js +58 -0
  389. package/dist/src/kernel/project/cli/helpFormat.js.map +1 -0
  390. package/dist/src/kernel/project/cli/index.js +14 -0
  391. package/dist/src/kernel/project/cli/index.js.map +1 -0
  392. package/dist/src/kernel/project/cli/rootHelp.js +227 -0
  393. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -0
  394. package/dist/src/kernel/project/cli/suggest.js +75 -0
  395. package/dist/src/kernel/project/cli/suggest.js.map +1 -0
  396. package/dist/src/kernel/project/cli/suggestNames.js +96 -0
  397. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -0
  398. package/dist/src/kernel/project/cli/verbHelp.js +131 -0
  399. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -0
  400. package/dist/src/kernel/project/mcp/buildServer.js +67 -0
  401. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -0
  402. package/dist/src/kernel/project/mcp/envelope.js +37 -0
  403. package/dist/src/kernel/project/mcp/envelope.js.map +1 -0
  404. package/dist/src/kernel/project/mcp/index.js +8 -0
  405. package/dist/src/kernel/project/mcp/index.js.map +1 -0
  406. package/dist/src/kernel/project/mcp/instructions.js +44 -0
  407. package/dist/src/kernel/project/mcp/instructions.js.map +1 -0
  408. package/dist/src/kernel/project/mcp/mcpError.js +42 -0
  409. package/dist/src/kernel/project/mcp/mcpError.js.map +1 -0
  410. package/dist/src/kernel/project/mcp/prompts/index.js +36 -0
  411. package/dist/src/kernel/project/mcp/prompts/index.js.map +1 -0
  412. package/dist/src/kernel/project/mcp/prompts/provider.js +98 -0
  413. package/dist/src/kernel/project/mcp/prompts/provider.js.map +1 -0
  414. package/dist/src/kernel/project/mcp/prompts/source.js +154 -0
  415. package/dist/src/kernel/project/mcp/prompts/source.js.map +1 -0
  416. package/dist/src/kernel/project/mcp/registerVerb.js +315 -0
  417. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -0
  418. package/dist/src/kernel/project/mcp/serve.js +35 -0
  419. package/dist/src/kernel/project/mcp/serve.js.map +1 -0
  420. package/dist/src/kernel/render/compactUri.js +24 -0
  421. package/dist/src/kernel/render/compactUri.js.map +1 -0
  422. package/dist/src/kernel/render/contracts.js +10 -0
  423. package/dist/src/kernel/render/contracts.js.map +1 -0
  424. package/dist/src/kernel/render/disclosure.js +34 -0
  425. package/dist/src/kernel/render/disclosure.js.map +1 -0
  426. package/dist/src/kernel/render/envelope.js +30 -0
  427. package/dist/src/kernel/render/envelope.js.map +1 -0
  428. package/dist/src/kernel/render/formatters.js +27 -0
  429. package/dist/src/kernel/render/formatters.js.map +1 -0
  430. package/dist/src/kernel/render/index.js +15 -0
  431. package/dist/src/kernel/render/index.js.map +1 -0
  432. package/dist/src/kernel/render/prefixes.js +46 -0
  433. package/dist/src/kernel/render/prefixes.js.map +1 -0
  434. package/dist/src/kernel/render/renderers.js +468 -0
  435. package/dist/src/kernel/render/renderers.js.map +1 -0
  436. package/dist/src/kernel/render/style.js +105 -0
  437. package/dist/src/kernel/render/style.js.map +1 -0
  438. package/dist/src/kernel/render/turtle.js +194 -0
  439. package/dist/src/kernel/render/turtle.js.map +1 -0
  440. package/dist/src/kernel/render/vocabulary.js +174 -0
  441. package/dist/src/kernel/render/vocabulary.js.map +1 -0
  442. package/dist/src/kernel/render/writeStdout.js +43 -0
  443. package/dist/src/kernel/render/writeStdout.js.map +1 -0
  444. package/dist/src/kernel/runtime/boot.js +49 -0
  445. package/dist/src/kernel/runtime/boot.js.map +1 -0
  446. package/dist/src/kernel/runtime/facade.js +39 -0
  447. package/dist/src/kernel/runtime/facade.js.map +1 -0
  448. package/dist/src/kernel/runtime/graphpack/build.js +122 -0
  449. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -0
  450. package/dist/src/kernel/runtime/graphpack/buildIndex.js +303 -0
  451. package/dist/src/kernel/runtime/graphpack/buildIndex.js.map +1 -0
  452. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +6 -0
  453. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -0
  454. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +4 -0
  455. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -0
  456. package/dist/src/kernel/runtime/graphpack/embedded/pack.stories.generated.js +4 -0
  457. package/dist/src/kernel/runtime/graphpack/embedded/pack.stories.generated.js.map +1 -0
  458. package/dist/src/kernel/runtime/graphpack/embedded.js +82 -0
  459. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -0
  460. package/dist/src/kernel/runtime/graphpack/hash.js +38 -0
  461. package/dist/src/kernel/runtime/graphpack/hash.js.map +1 -0
  462. package/dist/src/kernel/runtime/graphpack/index.js +11 -0
  463. package/dist/src/kernel/runtime/graphpack/index.js.map +1 -0
  464. package/dist/src/kernel/runtime/graphpack/manifest.js +145 -0
  465. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -0
  466. package/dist/src/kernel/runtime/graphpack/read.js +92 -0
  467. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -0
  468. package/dist/src/kernel/runtime/graphpack/schemas.js +70 -0
  469. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -0
  470. package/dist/src/kernel/runtime/graphpack/stories.js +68 -0
  471. package/dist/src/kernel/runtime/graphpack/stories.js.map +1 -0
  472. package/dist/src/kernel/runtime/graphpack/types.js +55 -0
  473. package/dist/src/kernel/runtime/graphpack/types.js.map +1 -0
  474. package/dist/src/kernel/runtime/index.js +8 -0
  475. package/dist/src/kernel/runtime/index.js.map +1 -0
  476. package/dist/src/kernel/runtime/loadSession.js +34 -0
  477. package/dist/src/kernel/runtime/loadSession.js.map +1 -0
  478. package/dist/src/kernel/runtime/paths.js +78 -0
  479. package/dist/src/kernel/runtime/paths.js.map +1 -0
  480. package/dist/src/kernel/runtime/probe.js +37 -0
  481. package/dist/src/kernel/runtime/probe.js.map +1 -0
  482. package/dist/src/kernel/runtime/readEntity.js +363 -0
  483. package/dist/src/kernel/runtime/readEntity.js.map +1 -0
  484. package/dist/src/kernel/runtime/refs/gitOps.js +65 -0
  485. package/dist/src/kernel/runtime/refs/gitOps.js.map +1 -0
  486. package/dist/src/kernel/runtime/refs/index.js +29 -0
  487. package/dist/src/kernel/runtime/refs/index.js.map +1 -0
  488. package/dist/src/kernel/runtime/refs/parseRef.js +70 -0
  489. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -0
  490. package/dist/src/kernel/runtime/refs/resolve.js +332 -0
  491. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -0
  492. package/dist/src/kernel/runtime/resolveSources.js +62 -0
  493. package/dist/src/kernel/runtime/resolveSources.js.map +1 -0
  494. package/dist/src/kernel/runtime/store.js +75 -0
  495. package/dist/src/kernel/runtime/store.js.map +1 -0
  496. package/dist/src/kernel/runtime/storeReadiness.js +51 -0
  497. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -0
  498. package/dist/src/kernel/runtime/types.js +11 -0
  499. package/dist/src/kernel/runtime/types.js.map +1 -0
  500. package/dist/src/kernel/spec/asVerb.js +21 -0
  501. package/dist/src/kernel/spec/asVerb.js.map +1 -0
  502. package/dist/src/kernel/spec/emitReference.js +578 -0
  503. package/dist/src/kernel/spec/emitReference.js.map +1 -0
  504. package/dist/src/kernel/spec/emitSurface.js +202 -0
  505. package/dist/src/kernel/spec/emitSurface.js.map +1 -0
  506. package/dist/src/kernel/spec/index.js +11 -0
  507. package/dist/src/kernel/spec/index.js.map +1 -0
  508. package/dist/src/kernel/spec/surfaceConformance.js +103 -0
  509. package/dist/src/kernel/spec/surfaceConformance.js.map +1 -0
  510. package/dist/src/kernel/spec/types.js +14 -0
  511. package/dist/src/kernel/spec/types.js.map +1 -0
  512. package/dist/src/kernel/spec/validate.js +110 -0
  513. package/dist/src/kernel/spec/validate.js.map +1 -0
  514. package/dist/src/kernel/vocabulary.js +97 -0
  515. package/dist/src/kernel/vocabulary.js.map +1 -0
  516. package/dist/src/types.js +11 -0
  517. package/dist/src/types.js.map +1 -0
  518. package/package.json +22 -23
  519. package/pragma.conf.ts +486 -90
  520. package/src/bin.ts +274 -82
  521. package/src/capabilities/block.parity.test.ts +29 -12
  522. package/src/capabilities/block.shipped.exec.test.ts +282 -0
  523. package/src/capabilities/block.tierRank.exec.test.ts +300 -0
  524. package/src/capabilities/capabilities/capabilities.render.ts +1 -1
  525. package/src/capabilities/capabilities/capabilities.verb.ts +1 -1
  526. package/src/capabilities/capabilities/catalog.ts +3 -3
  527. package/src/capabilities/capabilities/hints.ts +32 -7
  528. package/src/capabilities/capabilities/index.ts +1 -1
  529. package/src/capabilities/colophon/collectColophon.ts +35 -24
  530. package/src/capabilities/colophon/colophon.render.ts +24 -1
  531. package/src/capabilities/colophon/colophon.test.ts +42 -21
  532. package/src/capabilities/colophon/colophon.verb.ts +4 -4
  533. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  534. package/src/capabilities/config/collectConfigShow.ts +3 -3
  535. package/src/capabilities/config/field.render.ts +4 -5
  536. package/src/capabilities/config/fields.ts +17 -10
  537. package/src/capabilities/config/get.verb.ts +70 -0
  538. package/src/capabilities/config/getUnset.test.ts +264 -0
  539. package/src/capabilities/config/index.ts +5 -1
  540. package/src/capabilities/config/runField.ts +40 -22
  541. package/src/capabilities/config/runGet.ts +34 -0
  542. package/src/capabilities/config/runSet.ts +35 -9
  543. package/src/capabilities/config/set.test.ts +48 -14
  544. package/src/capabilities/config/set.verb.ts +8 -7
  545. package/src/capabilities/config/show.render.ts +1 -1
  546. package/src/capabilities/config/show.verb.ts +9 -6
  547. package/src/capabilities/config/types.ts +18 -4
  548. package/src/capabilities/config/unset.verb.ts +58 -0
  549. package/src/capabilities/create/byteEquality.subprocess.test.ts +2 -3
  550. package/src/capabilities/create/byteEquality.test.ts +25 -11
  551. package/src/capabilities/create/cliProjection.ts +227 -0
  552. package/src/capabilities/create/constants.ts +36 -61
  553. package/src/capabilities/create/create.mcp.test.ts +89 -5
  554. package/src/capabilities/create/create.render.ts +71 -7
  555. package/src/capabilities/create/create.test.ts +433 -219
  556. package/src/capabilities/create/create.verb.ts +297 -328
  557. package/src/capabilities/create/createGrammar.test.ts +865 -0
  558. package/src/capabilities/create/createHelp.ts +71 -0
  559. package/src/capabilities/create/createSurface.generated.ts +400 -0
  560. package/src/capabilities/create/crossCli.subprocess.test.ts +745 -0
  561. package/src/capabilities/create/cwdJail.test.ts +24 -5
  562. package/src/capabilities/create/generatorToVerbSpec.test.ts +186 -0
  563. package/src/capabilities/create/generatorToVerbSpec.ts +13 -11
  564. package/src/capabilities/create/index.ts +7 -4
  565. package/src/capabilities/create/messages.ts +18 -0
  566. package/src/capabilities/create/mount.ts +391 -0
  567. package/src/capabilities/create/pathJail.test.ts +90 -0
  568. package/src/capabilities/create/pathJail.ts +8 -5
  569. package/src/capabilities/create/pickGenerator.ts +23 -35
  570. package/src/capabilities/create/shippedCreate.subprocess.test.ts +164 -0
  571. package/src/capabilities/create/types.ts +20 -0
  572. package/src/capabilities/distribution.parity.test.ts +4 -0
  573. package/src/capabilities/distribution.test.ts +3 -1
  574. package/src/capabilities/distribution.ts +10 -15
  575. package/src/capabilities/doctor/checks/checkKeStore.ts +7 -5
  576. package/src/capabilities/doctor/checks/checkPackageRefs.ts +75 -8
  577. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +2 -2
  578. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +76 -29
  579. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -17
  580. package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
  581. package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
  582. package/src/capabilities/doctor/checks/index.ts +27 -0
  583. package/src/capabilities/doctor/checks/mcpCommand.test.ts +100 -0
  584. package/src/capabilities/doctor/checks/mcpCommand.ts +69 -0
  585. package/src/capabilities/doctor/checks/targetHealth.ts +444 -0
  586. package/src/capabilities/doctor/doctor.render.test.ts +125 -38
  587. package/src/capabilities/doctor/doctor.render.ts +74 -74
  588. package/src/capabilities/doctor/doctor.test.ts +338 -37
  589. package/src/capabilities/doctor/doctor.verb.ts +8 -3
  590. package/src/capabilities/doctor/runChecks.ts +65 -63
  591. package/src/capabilities/doctor/types.ts +22 -15
  592. package/src/capabilities/emptyRecovery.test.ts +21 -8
  593. package/src/capabilities/graph/examples.exec.test.ts +107 -0
  594. package/src/capabilities/graph/index.ts +1 -1
  595. package/src/capabilities/graph/inspect.render.ts +70 -22
  596. package/src/capabilities/graph/inspect.test.ts +260 -5
  597. package/src/capabilities/graph/inspect.verb.ts +18 -6
  598. package/src/capabilities/graph/query.render.ts +15 -3
  599. package/src/capabilities/graph/query.verb.ts +3 -3
  600. package/src/capabilities/graph/runQuery.ts +2 -3
  601. package/src/capabilities/index.ts +3 -1
  602. package/src/capabilities/info/collectInfo.ts +13 -5
  603. package/src/capabilities/info/info.render.ts +7 -3
  604. package/src/capabilities/info/info.test.ts +37 -1
  605. package/src/capabilities/info/info.verb.ts +1 -1
  606. package/src/capabilities/info/types.ts +10 -2
  607. package/src/capabilities/lazy.test.ts +32 -0
  608. package/src/capabilities/meta/complete.verb.ts +1 -1
  609. package/src/capabilities/meta/index.ts +1 -1
  610. package/src/capabilities/meta/mcp.verb.ts +10 -8
  611. package/src/capabilities/ontology/index.ts +15 -11
  612. package/src/capabilities/ontology/ontology.test.ts +6 -55
  613. package/src/capabilities/ontology/queries.ts +2 -2
  614. package/src/capabilities/ontology/render.ts +3 -4
  615. package/src/capabilities/ontology/verbs.ts +27 -68
  616. package/src/capabilities/prompt/index.ts +2 -2
  617. package/src/capabilities/prompt/prompt.render.ts +6 -2
  618. package/src/capabilities/prompt/types.ts +1 -1
  619. package/src/capabilities/prompt/verbs.ts +4 -5
  620. package/src/capabilities/reference.test.ts +63 -0
  621. package/src/capabilities/renderVocabularySync.test.ts +82 -0
  622. package/src/capabilities/resources/provider.ts +342 -85
  623. package/src/capabilities/resources/resources.test.ts +355 -59
  624. package/src/capabilities/scopeTypeSync.test.ts +29 -15
  625. package/src/capabilities/setup/buildPlan.ts +240 -0
  626. package/src/capabilities/setup/index.ts +7 -7
  627. package/src/capabilities/setup/mode.ts +74 -0
  628. package/src/capabilities/setup/operations/index.ts +79 -0
  629. package/src/capabilities/setup/operations/setupCompletions.ts +97 -24
  630. package/src/capabilities/setup/operations/setupConfig.ts +124 -0
  631. package/src/capabilities/setup/operations/setupGenerator.ts +478 -334
  632. package/src/capabilities/setup/operations/setupLsp.ts +420 -67
  633. package/src/capabilities/setup/operations/setupMcp.ts +238 -127
  634. package/src/capabilities/setup/operations/setupSkills.ts +667 -105
  635. package/src/capabilities/setup/plan.render.ts +336 -0
  636. package/src/capabilities/setup/plan.ts +241 -0
  637. package/src/capabilities/setup/setup.mcp.correctness.test.ts +432 -0
  638. package/src/capabilities/setup/setup.render.test.ts +297 -75
  639. package/src/capabilities/setup/setup.render.ts +45 -77
  640. package/src/capabilities/setup/setup.test.ts +1819 -225
  641. package/src/capabilities/setup/setup.verb.ts +288 -78
  642. package/src/capabilities/setup/shell.test.ts +86 -0
  643. package/src/capabilities/setup/shell.ts +137 -11
  644. package/src/capabilities/setup/targets.ts +435 -0
  645. package/src/capabilities/setup/types.ts +23 -70
  646. package/src/capabilities/shared/assertExecOk.ts +68 -10
  647. package/src/capabilities/shared/index.ts +43 -0
  648. package/src/capabilities/shared/packageManager.test.ts +470 -0
  649. package/src/capabilities/shared/packageManager.ts +425 -34
  650. package/src/capabilities/skill/bundledSkills.test.ts +347 -0
  651. package/src/capabilities/skill/discover.ts +131 -10
  652. package/src/capabilities/skill/index.ts +1 -1
  653. package/src/capabilities/skill/render.ts +10 -9
  654. package/src/capabilities/skill/skill.test.ts +22 -7
  655. package/src/capabilities/skill/verbs.ts +3 -4
  656. package/src/capabilities/sources/collectStatus.ts +7 -2
  657. package/src/capabilities/sources/index.ts +1 -1
  658. package/src/capabilities/sources/installSkills.ts +265 -10
  659. package/src/capabilities/sources/runUpdate.ts +131 -23
  660. package/src/capabilities/sources/sources.test.ts +382 -2
  661. package/src/capabilities/sources/status.render.ts +1 -1
  662. package/src/capabilities/sources/status.verb.ts +1 -1
  663. package/src/capabilities/sources/update.render.ts +1 -1
  664. package/src/capabilities/sources/update.verb.ts +4 -4
  665. package/src/capabilities/surface.test.ts +136 -19
  666. package/src/capabilities/upgrade/runUpgrade.ts +53 -14
  667. package/src/capabilities/upgrade/types.ts +10 -2
  668. package/src/capabilities/upgrade/upgrade.render.ts +13 -7
  669. package/src/capabilities/upgrade/upgrade.test.ts +75 -3
  670. package/src/capabilities/upgrade/upgrade.verb.ts +1 -1
  671. package/src/capabilities/version/index.ts +12 -0
  672. package/src/capabilities/version/version.test.ts +66 -0
  673. package/src/capabilities/version/version.verb.ts +39 -0
  674. package/src/constants.ts +36 -1
  675. package/src/identity.test.ts +57 -29
  676. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +15 -13
  677. package/src/kernel/completion/complete.test.ts +12 -2
  678. package/src/kernel/completion/complete.ts +1 -1
  679. package/src/kernel/completion/emitScripts.ts +1 -1
  680. package/src/kernel/completion/entitySource.test.ts +16 -3
  681. package/src/kernel/completion/entitySource.ts +21 -8
  682. package/src/kernel/completion/model.test.ts +164 -12
  683. package/src/kernel/completion/model.ts +94 -14
  684. package/src/kernel/completion/parse.test.ts +4 -7
  685. package/src/kernel/completion/parse.ts +15 -0
  686. package/src/kernel/completion/resolve.test.ts +0 -1
  687. package/src/kernel/completion/safety.test.ts +50 -35
  688. package/src/kernel/completion/shellDrive.test.ts +28 -9
  689. package/src/kernel/completion/templates/bash.ts +7 -2
  690. package/src/kernel/completion/templates/index.ts +23 -0
  691. package/src/kernel/completion/types.ts +7 -0
  692. package/src/kernel/config/defaults.test.ts +27 -3
  693. package/src/kernel/config/defaults.ts +4 -3
  694. package/src/kernel/config/evaluateProjectConfig.ts +31 -8
  695. package/src/kernel/config/firstRun.test.ts +44 -38
  696. package/src/kernel/config/firstRun.ts +24 -88
  697. package/src/kernel/config/index.ts +1 -1
  698. package/src/kernel/config/readConfig.test.ts +6 -3
  699. package/src/kernel/config/readConfig.ts +1 -1
  700. package/src/kernel/config/schema.ts +3 -1
  701. package/src/kernel/config/types.ts +3 -1
  702. package/src/kernel/copy.test.ts +34 -4
  703. package/src/kernel/copy.ts +54 -0
  704. package/src/kernel/error/PragmaError.test.ts +5 -2
  705. package/src/kernel/error/PragmaError.ts +4 -2
  706. package/src/kernel/error/constants.ts +7 -5
  707. package/src/kernel/error/fromTaskError.ts +28 -6
  708. package/src/kernel/error/recovery.ts +14 -0
  709. package/src/kernel/error/renderError.ts +42 -1
  710. package/src/kernel/error/types.ts +21 -1
  711. package/src/kernel/index.ts +35 -0
  712. package/src/kernel/interactivity.test.ts +76 -0
  713. package/src/kernel/interactivity.ts +69 -0
  714. package/src/kernel/packs/collect.ts +12 -16
  715. package/src/kernel/packs/compile.test.ts +222 -1
  716. package/src/kernel/packs/compile.ts +93 -3
  717. package/src/kernel/packs/disclosure.ts +2 -2
  718. package/src/kernel/packs/graphql/buildLookupDocument.ts +1 -1
  719. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +3 -4
  720. package/src/kernel/packs/graphql/index.ts +20 -0
  721. package/src/kernel/packs/index.ts +40 -0
  722. package/src/kernel/packs/iri.test.ts +73 -0
  723. package/src/kernel/packs/iri.ts +66 -14
  724. package/src/kernel/packs/renderPack.ts +25 -6
  725. package/src/kernel/packs/resolveEntity.test.ts +274 -0
  726. package/src/kernel/packs/resolveEntity.ts +200 -42
  727. package/src/kernel/packs/runBodies.ts +86 -22
  728. package/src/kernel/packs/sample.ts +1 -1
  729. package/src/kernel/packs/schema.ts +78 -5
  730. package/src/kernel/packs/sparql/applyFilters.ts +136 -16
  731. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
  732. package/src/kernel/packs/sparql/buildLookupQuery.ts +367 -17
  733. package/src/kernel/packs/sparql/index.ts +37 -0
  734. package/src/kernel/packs/sparql/runSelect.test.ts +1 -1
  735. package/src/kernel/packs/sparql/runSelect.ts +3 -4
  736. package/src/kernel/packs/toolDescription.test.ts +77 -4
  737. package/src/kernel/packs/types.ts +136 -0
  738. package/src/kernel/packs/uniqueness.ts +1 -1
  739. package/src/kernel/project/cli/buildProgram.test.ts +218 -12
  740. package/src/kernel/project/cli/buildProgram.ts +84 -18
  741. package/src/kernel/project/cli/constants.ts +34 -0
  742. package/src/kernel/project/cli/dispatch.test.ts +160 -2
  743. package/src/kernel/project/cli/dispatch.ts +226 -45
  744. package/src/kernel/project/cli/exitCodes.ts +1 -1
  745. package/src/kernel/project/cli/globalFlags.test.ts +117 -1
  746. package/src/kernel/project/cli/globalFlags.ts +134 -31
  747. package/src/kernel/project/cli/helpFormat.ts +14 -2
  748. package/src/kernel/project/cli/rootHelp.ts +74 -12
  749. package/src/kernel/project/cli/suggest.test.ts +33 -1
  750. package/src/kernel/project/cli/suggest.ts +25 -1
  751. package/src/kernel/project/cli/verbHelp.test.ts +30 -0
  752. package/src/kernel/project/cli/verbHelp.ts +23 -7
  753. package/src/kernel/project/mcp/buildServer.ts +7 -4
  754. package/src/kernel/project/mcp/envelope.ts +2 -2
  755. package/src/kernel/project/mcp/instructions.ts +2 -2
  756. package/src/kernel/project/mcp/mcpError.ts +1 -1
  757. package/src/kernel/project/mcp/parity.test.ts +5 -2
  758. package/src/kernel/project/mcp/prompts/index.ts +37 -0
  759. package/src/kernel/project/mcp/prompts/source.ts +2 -2
  760. package/src/kernel/project/mcp/registerVerb.ts +55 -10
  761. package/src/kernel/project/mcp/serve.ts +2 -2
  762. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +7 -4
  763. package/src/kernel/render/contracts.ts +21 -0
  764. package/src/kernel/render/envelope.ts +2 -3
  765. package/src/kernel/render/formatters.ts +1 -1
  766. package/src/kernel/render/renderers.test.ts +243 -6
  767. package/src/kernel/render/renderers.ts +222 -20
  768. package/src/kernel/render/style.test.ts +75 -24
  769. package/src/kernel/render/style.ts +59 -6
  770. package/src/kernel/render/turtle.ts +222 -0
  771. package/src/kernel/render/vocabulary.ts +276 -0
  772. package/src/kernel/runtime/graphpack/buildIndex.ts +1 -1
  773. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +3 -3
  774. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  775. package/src/kernel/runtime/graphpack/embedded.ts +3 -3
  776. package/src/kernel/runtime/graphpack/manifest.ts +1 -1
  777. package/src/kernel/runtime/graphpack/read.ts +1 -2
  778. package/src/kernel/runtime/loadSession.ts +1 -1
  779. package/src/kernel/runtime/probe.ts +6 -6
  780. package/src/kernel/runtime/readEntity.ts +472 -28
  781. package/src/kernel/runtime/refs/index.ts +39 -0
  782. package/src/kernel/runtime/refs/parseRef.ts +2 -2
  783. package/src/kernel/runtime/refs/resolve.ts +4 -4
  784. package/src/kernel/runtime/storeReadiness.ts +1 -2
  785. package/src/kernel/runtime/types.ts +15 -1
  786. package/src/kernel/runtime/wasmEmbed.test.ts +37 -39
  787. package/src/kernel/spec/emitReference.ts +105 -30
  788. package/src/kernel/spec/emitSurface.ts +48 -6
  789. package/src/kernel/spec/surfaceConformance.test.ts +4 -2
  790. package/src/kernel/spec/types.ts +277 -4
  791. package/src/testing/PARITY_GAPS.ts +9 -3
  792. package/src/testing/behavioral/docExamples.test.ts +16 -0
  793. package/src/testing/behavioral/errorMatrix.mcp.test.ts +112 -9
  794. package/src/testing/behavioral/firstRun.e2e.test.ts +24 -24
  795. package/src/testing/behavioral/journeys.cli.test.ts +1 -1
  796. package/src/testing/behavioral/journeys.defaultPack.test.ts +7 -7
  797. package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
  798. package/src/testing/behavioral/journeys.implementation.test.ts +206 -0
  799. package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +2 -2
  800. package/src/testing/behavioral/journeys.packageStories.test.ts +10 -7
  801. package/src/testing/behavioral/mcpServe.e2e.test.ts +57 -2
  802. package/src/testing/behavioral/mcpSurface.test.ts +1 -1
  803. package/src/testing/behavioral/rootCli.e2e.test.ts +26 -2
  804. package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
  805. package/src/testing/eval/cases/effectful.ts +3 -2
  806. package/src/testing/eval/cases/readNouns.ts +245 -9
  807. package/src/testing/fixtures/blockGraph.ts +104 -10
  808. package/src/testing/fixtures/graph/canonical.ts +182 -13
  809. package/src/testing/fixtures/graph/defaultPack.ts +1 -1
  810. package/src/testing/helpers/blankCanonicalRanges.ts +36 -0
  811. package/src/testing/helpers/projectMcp.ts +32 -6
  812. package/src/testing/helpers/runCli.ts +35 -18
  813. package/src/testing/perf/budgets.test.ts +20 -9
  814. package/src/testing/perf/budgets.ts +58 -9
  815. package/src/testing/perf/codegen.test.ts +126 -0
  816. package/src/testing/perf/globalSetup.test.ts +286 -0
  817. package/src/testing/perf/globalSetup.ts +505 -23
  818. package/src/testing/perf/measure.test.ts +38 -0
  819. package/src/testing/perf/measure.ts +23 -4
  820. package/src/testing/setupXdgIsolation.ts +81 -21
  821. package/src/testing/tempRoot.globalSetup.ts +122 -0
  822. package/dist/pragma +0 -0
  823. package/src/capabilities/create/compiledCreate.subprocess.test.ts +0 -189
  824. package/src/capabilities/create/templates.embedded.generated.ts +0 -28
  825. package/src/capabilities/doctor/checks/checkConfigFile.ts +0 -42
  826. package/src/capabilities/doctor/checks/checkMcpCommands.ts +0 -114
  827. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +0 -68
  828. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +0 -61
  829. package/src/capabilities/doctor/checks/deriveBand.test.ts +0 -42
  830. package/src/capabilities/doctor/checks/deriveBand.ts +0 -28
  831. package/src/capabilities/shared/bands.ts +0 -16
  832. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +0 -11
@@ -1,125 +1,561 @@
1
1
  /**
2
- * `setup skills` — symlink discovered skills into each harness skill directory
3
- * and the cross-client `.agents/skills`.
2
+ * `setup skills` — symlink discovered skills into harness skill directories.
4
3
  *
5
- * Split into `detectSkills` (skill discovery, harness detection, and the
6
- * per-link create/skip/replace DECISION all run against REAL fs up front, so the
7
- * wizard recap and a `--dry-run` plan reflect true state) and `composeSkills` (a
8
- * pure, re-runnable body performing only the symlink/delete effects the dry-run
9
- * interpreter mocks). Idempotent by construction.
4
+ * SCOPE-MATCHED: the scope picks the source root AND the link destination, and
5
+ * the two always match.
6
+ *
7
+ * - global (the default): INSTALLED skills (`$XDG_DATA_HOME/<bin>/skills`, where
8
+ * `sources update` puts package skills) AND the BUNDLED snapshot this package
9
+ * ships (`bundled-skills/`, last in precedence) link into user-level harness
10
+ * skill directories — the cross-client `~/.agents/skills`, plus the user-level
11
+ * directory of each detected harness whose user-level location is verified.
12
+ * The global scope is therefore a root SET, and every rule below that used to
13
+ * read one `sourceRoot` ranges over it: what a skill resolves to (first-seen
14
+ * wins, so an installed skill beats the shipped copy), what this command
15
+ * OWNS, and what its stale sweep is entitled to touch.
16
+ * - project (`--local`): PROJECT skills (`<cwd>/.<bin>/skills`) link into the
17
+ * project's harness directories — the checked-in, team-shared arrangement.
18
+ *
19
+ * Cross-scope linking is what this replaces: linking installed skills into a
20
+ * repository's directories leaks machine state into a checkout, and linking a
21
+ * repository's skills into the home directory applies one project's skills
22
+ * machine-wide. Neither is ever what the user asked for.
23
+ *
24
+ * A BUNDLED SKILL IS LINKED, NOT COPIED, and that is a decision about who owns
25
+ * the target directory. Every other link this command writes points into a
26
+ * directory pragma controls (`$XDG_DATA_HOME/<bin>/skills`, or the user's own
27
+ * repository); a bundled one points into the package directory, which the
28
+ * PACKAGE MANAGER owns and may move or delete without telling us — a pnpm or
29
+ * npx layout puts the version in the path, so an upgrade leaves the link
30
+ * dangling. Copying would avoid that and cost more than it saves: `linkState`
31
+ * classifies a real directory as `other`, and this module treats `other` as
32
+ * hand-placed and never touches it. So a copied skill could never be updated,
33
+ * never be retired, and never be removed by `--undo` — pragma would be writing
34
+ * permanent, silently stale directories into `~/.claude/skills`.
35
+ *
36
+ * A dangling link, by contrast, the reconcile ALREADY repairs, and this was
37
+ * checked rather than assumed: `detectSkills` classifies a link that does not
38
+ * resolve to the current skill as `replaced` (delete + relink) whatever it
39
+ * points at, so the next `setup skills` — or the layer-2 pass `sources update`
40
+ * runs — moves it onto the new package directory. The one thing that had to
41
+ * change is ownership: `withinRoot` against the installed root alone read a
42
+ * bundled-pointing link as somebody else's and skipped it, so the sweep and the
43
+ * relink both range over the root SET now ({@link withinAnyRoot}). The residual
44
+ * window is between an upgrade and the next reconcile, which is the same window
45
+ * a `sources update` link into a cleared ref cache already has.
46
+ *
47
+ * Split into `detectSkills` (discovery, harness detection, and the per-link
48
+ * create/skip/replace DECISION, all against the real filesystem up front, so
49
+ * the plan and the recap reflect true state) and pure compose bodies performing
50
+ * only the symlink/delete effects the dry-run interpreter mocks.
10
51
  */
11
52
 
12
- import { existsSync, readlinkSync } from "node:fs";
13
- import { resolve } from "node:path";
53
+ import {
54
+ existsSync,
55
+ lstatSync,
56
+ readdirSync,
57
+ readlinkSync,
58
+ statSync,
59
+ } from "node:fs";
60
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
14
61
  import {
15
62
  deleteFile,
16
- info,
17
63
  mkdir,
18
64
  sequence_,
19
65
  symlink,
20
66
  type Task,
21
67
  } from "@canonical/task";
22
68
  import { BIN_NAME } from "../../../constants.js";
23
- import { PragmaError } from "../../../kernel/error/PragmaError.js";
24
- import { cliRecovery } from "../../../kernel/error/recovery.js";
25
- import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
26
- import type { SetupSkillsResult, SymlinkAction } from "../types.js";
69
+ import type { PragmaRuntime } from "../../../kernel/runtime/index.js";
70
+ import type { Scope } from "../types.js";
27
71
 
28
72
  /** Cross-client skill directory, shared across all harnesses. */
29
73
  const CROSS_CLIENT_DIR = ".agents/skills";
30
74
 
31
75
  /**
32
- * The detected skill-linking plan: the target directories, the decided per-link
33
- * actions, and the counts/warnings the result surface reports. `available` is
34
- * false when no skills were discovered.
76
+ * The harnesses whose USER-LEVEL skills directory is verified against the
77
+ * harness's own documentation, and therefore safe to link into on the global
78
+ * scope. A harness absent from this set simply contributes no global link
79
+ * directory: an invented `~/.<something>/skills` would be a directory nothing
80
+ * reads, and creating it would be litter dressed up as configuration.
81
+ *
82
+ * The path itself is still the registry's — `skillsPath` is a function of a
83
+ * root, so a user-level location is `skillsPath(home)`. What lives here is only
84
+ * the JUDGEMENT that the location has been confirmed.
85
+ */
86
+ const VERIFIED_GLOBAL_SKILL_HARNESSES: ReadonlySet<string> = new Set([
87
+ "claude-code",
88
+ ]);
89
+
90
+ /** One symlink create/skip/replace action decided during detection. */
91
+ export interface SymlinkAction {
92
+ readonly skillName: string;
93
+ readonly target: string;
94
+ readonly linkPath: string;
95
+ readonly action: "created" | "skipped" | "replaced";
96
+ readonly harnessName: string;
97
+ /**
98
+ * Whether this link path currently holds a symlink INTO the scope's skill
99
+ * root — the ownership test removal turns on. A link pointing anywhere else
100
+ * is the user's and is never this command's to delete, and a real (non-link)
101
+ * entry is never touched at all.
102
+ */
103
+ readonly owned: boolean;
104
+ /**
105
+ * Whether a REAL (non-symlink) entry occupies the link path. Such a path is
106
+ * `skipped` for the same reason an already-correct link is — neither is
107
+ * anything to do — but they are not the same state: one is a link this
108
+ * command maintains, the other is a hand-placed directory it will never
109
+ * touch, and reporting both as "current" told the user a link exists where
110
+ * none does.
111
+ */
112
+ readonly blocked: boolean;
113
+ }
114
+
115
+ /**
116
+ * The detected skill-linking plan for ONE scope: the source root, the target
117
+ * directories, the decided per-link actions, and the counts the plan row
118
+ * reports. `available` is false when the scope's root holds no skills.
35
119
  */
36
120
  export interface SkillsDetection {
121
+ readonly scope: Scope;
122
+ /**
123
+ * The scope's source roots, in PRECEDENCE order — the directories skills are
124
+ * discovered from, and the set every ownership test ranges over. One entry
125
+ * for the project scope; installed-then-bundled for the global one.
126
+ */
127
+ readonly sourceRoots: readonly string[];
128
+ /**
129
+ * The scope's PRIMARY source root — `sourceRoots[0]`, and the one the skip
130
+ * reason and the doctor row name. It is the root a user can DO something
131
+ * about: the project directory they fill by hand, or the installed root
132
+ * `sources update` writes. Naming the bundled snapshot instead would point
133
+ * them at a directory inside their node_modules that they must never edit.
134
+ */
135
+ readonly sourceRoot: string;
136
+ /**
137
+ * Whether ANY of {@link sourceRoots} exists on disk — deliberately NOT the
138
+ * same question as {@link available}, which asks only whether they hold a
139
+ * skill.
140
+ *
141
+ * The two were one flag, and conflating them is a wipe hazard. A run where
142
+ * `XDG_DATA_HOME` points somewhere else, or where `sources update` has never
143
+ * run, discovers zero skills — and a reconcile keyed on that alone would read
144
+ * every link in `~/.claude/skills` as garbage and delete it. An ABSENT root
145
+ * means this command has NO OPINION about the scope and touches nothing. Only
146
+ * an EXISTING root that no longer holds a given skill is evidence the skill
147
+ * was retired, which is the one thing the sweep is entitled to act on.
148
+ */
149
+ readonly rootExists: boolean;
37
150
  readonly available: boolean;
38
151
  readonly targets: readonly { readonly dir: string; readonly name: string }[];
39
152
  readonly actions: readonly SymlinkAction[];
153
+ /**
154
+ * Links this scope OWNS that no CURRENT skill accounts for: the source root
155
+ * emptied, or one skill removed upstream. They are not part of the forward
156
+ * plan — there is nothing left to link them to — but they are exactly what
157
+ * removal exists to clear, and deriving removal from the per-skill actions
158
+ * alone left them on disk while `--undo` reported zero work.
159
+ */
160
+ readonly orphans: readonly SymlinkAction[];
40
161
  readonly skillCount: number;
41
- readonly harnessCount: number;
42
162
  readonly warnings: readonly string[];
43
163
  }
44
164
 
45
- /** The current target of a symlink, or `null` when absent / not a symlink. */
46
- function currentLink(linkPath: string): string | null {
47
- if (!existsSync(linkPath)) return null;
165
+ /** What currently occupies a candidate link path. */
166
+ type LinkState =
167
+ | { readonly kind: "absent" }
168
+ | { readonly kind: "symlink"; readonly target: string }
169
+ | { readonly kind: "other" };
170
+
171
+ /**
172
+ * Whether a link's destination lives INSIDE the scope's skill root — the
173
+ * ownership test, and the one that decides what `--undo` deletes.
174
+ *
175
+ * A raw `readlink` target may be relative, so it is resolved against the link's
176
+ * own directory first; then containment is tested by PATH SEGMENT. A string
177
+ * prefix answered yes for `<root>-backup/foo`, so a user's own link into a
178
+ * sibling directory was classified as ours and removed by `--undo` — a file we
179
+ * never created and had no business deleting.
180
+ *
181
+ * The root itself is not "inside" it: a link pointing AT the root is not one of
182
+ * the per-skill links this command writes.
183
+ *
184
+ * @param root - The scope's skill source root, absolute.
185
+ * @param linkPath - The absolute path of the link being classified.
186
+ * @param rawTarget - The link's destination exactly as `readlink` reported it.
187
+ * @returns Whether the destination is a path under the root.
188
+ * @note Pure — it resolves and compares strings, and reads nothing.
189
+ */
190
+ export function withinRoot(
191
+ root: string,
192
+ linkPath: string,
193
+ rawTarget: string,
194
+ ): boolean {
195
+ const destination = resolve(dirname(linkPath), rawTarget);
196
+ const rel = relative(root, destination);
197
+ return (
198
+ rel.length > 0 &&
199
+ rel !== ".." &&
200
+ !rel.startsWith(`..${sep}`) &&
201
+ !isAbsolute(rel)
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Whether a link's destination lives inside ANY of the scope's skill roots.
207
+ *
208
+ * The scope ownership test, once the global scope stopped being a single
209
+ * directory. Every caller that used to ask {@link withinRoot} about one path
210
+ * asks this about the set, so a link into the bundled snapshot is as much this
211
+ * command's to maintain as one into the installed root — and a link into
212
+ * neither is still nobody's business but the user's.
213
+ *
214
+ * @param roots - The scope's skill source roots, absolute.
215
+ * @param linkPath - The absolute path of the link being classified.
216
+ * @param rawTarget - The link's destination exactly as `readlink` reported it.
217
+ * @returns Whether the destination is a path under one of the roots.
218
+ * @note Pure — {@link withinRoot} reads nothing, and neither does this.
219
+ */
220
+ export function withinAnyRoot(
221
+ roots: readonly string[],
222
+ linkPath: string,
223
+ rawTarget: string,
224
+ ): boolean {
225
+ return roots.some((root) => withinRoot(root, linkPath, rawTarget));
226
+ }
227
+
228
+ /**
229
+ * The directory a release's bundled skills live in, inside the package.
230
+ *
231
+ * Named here because it is the ONE shape that identifies a link as pragma's
232
+ * without asking where the package currently sits.
233
+ */
234
+ const BUNDLED_DIR_SEGMENT = "bundled-skills";
235
+
236
+ /**
237
+ * Whether this link is pragma's to rewrite or remove.
238
+ *
239
+ * Residence in a current root is not sufficient, in EITHER direction.
240
+ *
241
+ * Too narrow: after an upgrade under a version-stamped layout (pnpm, npx,
242
+ * volta) a link points into `…/pragma-cli@0.34.0/bundled-skills/<name>`, a
243
+ * directory that no longer exists and is inside no current root. Ownership by
244
+ * residence alone reads pragma's own link as somebody else's, so the sweep
245
+ * declines it and the per-skill pass cannot reach it once the skill is gone —
246
+ * the dangling link then survives every reconcile, invisible to setup and to
247
+ * doctor. The path SHAPE still says whose it is.
248
+ *
249
+ * Too wide: the bundled root is present on every install now, so a folder name
250
+ * that collides with a bundled skill is reachable with no user action at all.
251
+ * A user's own `~/.claude/skills/design-auditor -> ~/work/design-auditor`
252
+ * must not become this command's to delete just because a bundled skill shares
253
+ * its name. That is the same rule a hand-placed real directory already gets.
254
+ *
255
+ * @param roots - The scope's source roots.
256
+ * @param linkPath - The link's own path, absolute.
257
+ * @param rawTarget - Its unresolved `readlink` value.
258
+ * @returns Whether pragma created this link.
259
+ * @note Pure — resolves strings, reads nothing.
260
+ */
261
+ function ownsLink(
262
+ roots: readonly string[],
263
+ linkPath: string,
264
+ rawTarget: string,
265
+ ): boolean {
266
+ if (withinAnyRoot(roots, linkPath, rawTarget)) return true;
267
+ return resolve(dirname(linkPath), rawTarget)
268
+ .split(sep)
269
+ .includes(BUNDLED_DIR_SEGMENT);
270
+ }
271
+
272
+ /**
273
+ * Classify a link path WITHOUT following it: absent, a symlink (with its raw
274
+ * target, resolving or not), or a real (non-symlink) entry. `lstat`-based, the
275
+ * same classification `sources/installSkills.ts`'s `linkState` uses — probing
276
+ * with `existsSync` FOLLOWS the link, so a DANGLING symlink read as "absent",
277
+ * the plan said `created`, and the real `symlink()` crashed EEXIST while the
278
+ * dry-run previewed a clean create.
279
+ */
280
+ function linkState(linkPath: string): LinkState {
281
+ let stat: ReturnType<typeof lstatSync>;
48
282
  try {
49
- return readlinkSync(linkPath);
283
+ stat = lstatSync(linkPath);
50
284
  } catch {
51
- return "";
285
+ return { kind: "absent" };
286
+ }
287
+ if (stat.isSymbolicLink()) {
288
+ try {
289
+ return { kind: "symlink", target: readlinkSync(linkPath) };
290
+ } catch {
291
+ return { kind: "other" };
292
+ }
52
293
  }
294
+ return { kind: "other" };
53
295
  }
54
296
 
55
297
  /**
56
- * Discover skills, detect harnesses, and decide each link action (real fs, up
57
- * front). Returns `available: false` (empty plan) when no skills exist — the
58
- * run-all omits the step; the direct sub-verb turns it into {@link skillsEmptyError}.
298
+ * The link directories for a scope. Project: every detected harness's
299
+ * project-rooted skills directory plus the cross-client one, exactly as
300
+ * before. Global: the same construction rooted at the user's home directory,
301
+ * but only for the harnesses in {@link VERIFIED_GLOBAL_SKILL_HARNESSES} —
302
+ * plus the cross-client directory, which every client reads by convention.
59
303
  *
60
- * @param rt - The per-invocation runtime.
61
- * @returns The link plan.
62
- * @note Impure — reads the filesystem (skills + harnesses + existing links).
304
+ * @param detected - The detected harnesses.
305
+ * @param scope - The scope being set up.
306
+ * @param root - The scope's link root (`cwd` for project, home for global).
307
+ * @returns The deduplicated target directories, first-seen wins.
63
308
  */
64
- export async function detectSkills(
65
- rt: PragmaRuntime,
66
- ): Promise<SkillsDetection> {
67
- const cwd = rt.cwd;
68
- const [{ discoverSkills }, { detectHarnesses }, { runTask }] =
309
+ function linkTargets(
310
+ detected: readonly {
311
+ harness: { id: string; name: string; skillsPath: (root: string) => string };
312
+ }[],
313
+ scope: Scope,
314
+ root: string,
315
+ ): { dir: string; name: string }[] {
316
+ const seen = new Set<string>();
317
+ const targets: { dir: string; name: string }[] = [];
318
+ for (const d of detected) {
319
+ if (
320
+ scope === "global" &&
321
+ !VERIFIED_GLOBAL_SKILL_HARNESSES.has(d.harness.id)
322
+ ) {
323
+ continue;
324
+ }
325
+ const dir = d.harness.skillsPath(root);
326
+ if (seen.has(dir)) continue;
327
+ seen.add(dir);
328
+ targets.push({ dir, name: d.harness.name });
329
+ }
330
+ const crossDir = resolve(root, CROSS_CLIENT_DIR);
331
+ if (!seen.has(crossDir))
332
+ targets.push({ dir: crossDir, name: CROSS_CLIENT_DIR });
333
+ return targets;
334
+ }
335
+
336
+ /**
337
+ * The GLOBAL scope's link directories that ALREADY EXIST — the converge-only set
338
+ * `sources update` is allowed to refresh.
339
+ *
340
+ * The filter is the whole policy, and it is why this is a separate export
341
+ * rather than `linkTargets` made public. `composeSkills` mkdirs its targets and
342
+ * `linkTargets` unconditionally appends `~/.agents/skills`, so an update that
343
+ * reused them would CREATE harness directories on a machine that never asked
344
+ * for skill linking — and `sources update` is `mcp: { expose: true }` with
345
+ * `mutates: true`, which would mean an agent, over MCP, after a network fetch,
346
+ * conjuring `~/.claude/skills` into existence. Refresh what is there; never
347
+ * bring a directory into being.
348
+ *
349
+ * The scope is not widened by this: `VERIFIED_GLOBAL_SKILL_HARNESSES` and the
350
+ * scope covenant are exactly the ones `setup skills --global` already honours,
351
+ * because this is the same `linkTargets` call it makes.
352
+ *
353
+ * @param cwd - The invocation's working directory (harness detection reads it).
354
+ * @returns The existing global link directories.
355
+ * @note Impure — detects harnesses and stats each candidate directory.
356
+ */
357
+ export async function existingGlobalSkillDirs(
358
+ cwd: string,
359
+ ): Promise<readonly { dir: string; name: string }[]> {
360
+ const [{ detectHarnesses, readPlatformEnv, userHome }, { runTask }] =
69
361
  await Promise.all([
70
- import("../../skill/discover.js"),
71
362
  import("@canonical/harnesses"),
72
363
  import("@canonical/task/node"),
73
364
  ]);
365
+ const detected = await runTask(detectHarnesses(cwd));
366
+ const home = userHome(readPlatformEnv());
367
+ return linkTargets(detected, "global", home).filter((t) =>
368
+ rootIsPresent(t.dir),
369
+ );
370
+ }
74
371
 
75
- const skills = discoverSkills(cwd);
76
- if (skills.length === 0) {
77
- return {
78
- available: false,
79
- targets: [],
80
- actions: [],
81
- skillCount: 0,
82
- harnessCount: 0,
83
- warnings: [],
84
- };
372
+ /**
373
+ * Whether the scope's source root exists as a directory.
374
+ *
375
+ * `stat`, not `lstat`: a root the user symlinked into place is still a root.
376
+ * Every failure — absent, unreadable, not a directory — answers false, which is
377
+ * the fail-closed direction: the sweep then declines to act at all.
378
+ *
379
+ * @param root - The scope's skill source root, absolute.
380
+ * @returns Whether it is a directory this run can have an opinion about.
381
+ * @note Impure — stats the real filesystem.
382
+ */
383
+ function rootIsPresent(root: string): boolean {
384
+ try {
385
+ return statSync(root).isDirectory();
386
+ } catch {
387
+ return false;
85
388
  }
389
+ }
86
390
 
87
- const detected = await runTask(detectHarnesses(cwd));
88
-
89
- // Target dirs (harness skill dirs + the cross-client dir), first-seen wins.
90
- const seen = new Set<string>();
91
- const targets: { dir: string; name: string }[] = [];
92
- for (const d of detected) {
93
- const dir = d.harness.skillsPath(cwd);
94
- if (!seen.has(dir)) {
95
- seen.add(dir);
96
- targets.push({ dir, name: d.harness.name });
391
+ /**
392
+ * Every owned link sitting in the target directories that the per-skill pass
393
+ * did not already visit.
394
+ *
395
+ * The forward pass enumerates CURRENT skills, so it can only ever see links a
396
+ * skill still exists for. Once the source root is emptied — or one skill is
397
+ * removed upstream — the link it left behind is invisible to that pass, and
398
+ * removal derived from it found nothing to do. Ownership is the same
399
+ * containment test the forward pass applies, so a user's own link is no more
400
+ * removable here than it is there.
401
+ *
402
+ * SAFETY GATE: nothing is orphaned when the source root does not EXIST. An
403
+ * absent root is "no opinion", never "everything here is stale" — see
404
+ * {@link SkillsDetection.rootExists}. The containment test below is a pure
405
+ * string test that reads nothing, so it still answers correctly once the link's
406
+ * target is gone; the root's own existence is the separate question of whether
407
+ * this command is entitled to answer at all.
408
+ *
409
+ * THE GATE IS PER ROOT, not per scope. A scope with two roots must not let one
410
+ * of them vouch for the other: if the installed root is deleted (or
411
+ * `XDG_DATA_HOME` points elsewhere for one invocation) while the bundled
412
+ * snapshot — which ships inside the package and so is essentially always there
413
+ * — still exists, a scope-wide "some root exists" gate would hand the sweep every
414
+ * link into the vanished root and call it stale. So `existingRoots` is passed
415
+ * already filtered, and a link is orphanable only when the root it points into
416
+ * is one of them.
417
+ *
418
+ * @param targets - The scope's link directories.
419
+ * @param existingRoots - The scope's source roots that EXIST (empty ⇒ no
420
+ * orphans at all).
421
+ * @param covered - Link paths the per-skill pass already decided.
422
+ * @returns The owned links no current skill accounts for.
423
+ * @note Impure — reads each target directory and lstats its entries.
424
+ */
425
+ function orphanedLinks(
426
+ targets: readonly { dir: string; name: string }[],
427
+ existingRoots: readonly string[],
428
+ covered: ReadonlySet<string>,
429
+ ): SymlinkAction[] {
430
+ if (existingRoots.length === 0) return [];
431
+ const orphans: SymlinkAction[] = [];
432
+ for (const { dir, name } of targets) {
433
+ let entries: string[];
434
+ try {
435
+ entries = readdirSync(dir);
436
+ } catch {
437
+ continue; // The directory does not exist — it holds no links.
438
+ }
439
+ for (const entry of entries) {
440
+ const linkPath = resolve(dir, entry);
441
+ if (covered.has(linkPath)) continue;
442
+ const state = linkState(linkPath);
443
+ if (state.kind !== "symlink") continue;
444
+ // `ownsLink`, not `withinAnyRoot`: a link into a REMOVED version-stamped
445
+ // package directory is inside no current root, and once its skill is gone
446
+ // from the release no per-skill action covers the path either. Residence
447
+ // alone would leave it dangling forever.
448
+ if (!ownsLink(existingRoots, linkPath, state.target)) continue;
449
+ orphans.push({
450
+ skillName: entry,
451
+ target: resolve(dirname(linkPath), state.target),
452
+ linkPath,
453
+ action: "skipped",
454
+ harnessName: name,
455
+ blocked: false,
456
+ owned: true,
457
+ });
97
458
  }
98
459
  }
99
- const crossDir = resolve(cwd, CROSS_CLIENT_DIR);
100
- if (!seen.has(crossDir)) {
101
- seen.add(crossDir);
102
- targets.push({ dir: crossDir, name: CROSS_CLIENT_DIR });
103
- }
460
+ return orphans;
461
+ }
104
462
 
105
- // Decide each action against REAL fs (so the preview is accurate).
463
+ /**
464
+ * Discover the scope's skills, detect harnesses, and decide each link action
465
+ * (real filesystem, up front).
466
+ *
467
+ * Target discovery runs even when the scope holds NO skills: the directories are
468
+ * where this command's own links live, and a removal has to be able to find
469
+ * them after the source root has been emptied.
470
+ *
471
+ * @param rt - The per-invocation runtime.
472
+ * @param scope - Which scope to plan: global (installed skills) or project.
473
+ * @returns The scope's link plan.
474
+ * @note Impure — reads the filesystem (skills + harnesses + existing links).
475
+ */
476
+ export async function detectSkills(
477
+ rt: PragmaRuntime,
478
+ scope: Scope,
479
+ ): Promise<SkillsDetection> {
480
+ const cwd = rt.cwd;
481
+ const [
482
+ { discoverSkillsFrom, globalSkillRoots, projectSkillsDir },
483
+ { detectHarnesses, readPlatformEnv, userHome },
484
+ { runTask },
485
+ ] = await Promise.all([
486
+ import("../../skill/discover.js"),
487
+ import("@canonical/harnesses"),
488
+ import("@canonical/task/node"),
489
+ ]);
490
+
491
+ // The scope picks the root SET; `globalSkillRoots` owns the global scope's
492
+ // order (installed, then bundled) so this module and `sources update` cannot
493
+ // drift on which directories the scope is.
494
+ const sourceRoots =
495
+ scope === "project" ? [projectSkillsDir(cwd)] : globalSkillRoots();
496
+ const sourceRoot = sourceRoots[0] as string;
497
+ const linkRoot = scope === "project" ? cwd : userHome(readPlatformEnv());
498
+ // First-seen wins, so an installed skill shadows the bundled copy of the same
499
+ // name: someone who ran `sources update` links the CURRENT skill.
500
+ const skills = discoverSkillsFrom(sourceRoots);
501
+ const existingRoots = sourceRoots.filter(rootIsPresent);
502
+ const rootExists = existingRoots.length > 0;
503
+ const detected = await runTask(detectHarnesses(cwd));
504
+ const targets = linkTargets(detected, scope, linkRoot);
505
+
506
+ // Decide each action against REAL fs (so the preview is accurate). Dangling
507
+ // or wrong-target symlinks are `replaced` (delete + relink); a real
508
+ // (non-symlink) file or directory at the path is `skipped` — a hand-placed
509
+ // skill is never this command's to delete.
106
510
  const actions: SymlinkAction[] = [];
107
511
  for (const { dir, name } of targets) {
108
512
  for (const skill of skills) {
109
513
  const linkPath = resolve(dir, skill.folderName);
110
- const current = currentLink(linkPath);
514
+ const state = linkState(linkPath);
515
+ // A link is already correct when it RESOLVES to the skill, not when its
516
+ // raw `readlink` string equals an absolute path. Comparing the raw target
517
+ // classified every RELATIVE link as `replaced`, so a functionally correct
518
+ // link was torn down and rebuilt on every run and doctor reported it as
519
+ // `N links point elsewhere`.
520
+ const resolvesToSkill =
521
+ state.kind === "symlink" &&
522
+ resolve(dirname(linkPath), state.target) === skill.sourcePath;
523
+ const owned =
524
+ state.kind === "symlink" &&
525
+ ownsLink(sourceRoots, linkPath, state.target);
526
+ // A link is FOREIGN when it resolves to something real that pragma does
527
+ // not own — a user's own `~/.claude/skills/design-auditor ->
528
+ // ~/work/design-auditor`. That is `skipped`, the same answer a
529
+ // hand-placed real directory gets: `composeSkills` composes every
530
+ // non-skipped row, so calling it `replaced` would delete the link. The
531
+ // bundled root is on every install now, so a name collision with a
532
+ // shipped skill is reachable with no user action at all.
533
+ //
534
+ // A DANGLING link is replaced regardless of where it pointed. Nothing
535
+ // depends on a broken link, so deleting it destroys nothing — while
536
+ // leaving it blocks the skill forever and, for pragma's own link into a
537
+ // cleared cache or a replaced package directory, is the repair. An
538
+ // unresolvable target cannot vouch for its owner either way.
539
+ const foreign =
540
+ state.kind === "symlink" &&
541
+ !owned &&
542
+ existsSync(resolve(dirname(linkPath), state.target));
111
543
  const action: SymlinkAction["action"] =
112
- current === null
544
+ state.kind === "absent"
113
545
  ? "created"
114
- : current === skill.sourcePath
546
+ : resolvesToSkill
115
547
  ? "skipped"
116
- : "replaced";
548
+ : state.kind === "symlink" && !foreign
549
+ ? "replaced"
550
+ : "skipped";
117
551
  actions.push({
118
552
  skillName: skill.name,
119
553
  target: skill.sourcePath,
120
554
  linkPath,
121
555
  action,
122
556
  harnessName: name,
557
+ blocked: state.kind === "other",
558
+ owned,
123
559
  });
124
560
  }
125
561
  }
@@ -127,79 +563,205 @@ export async function detectSkills(
127
563
  const warnings = actions
128
564
  .filter((a) => a.action === "replaced")
129
565
  .map(
130
- (a) => `Replaced stale symlink for ${a.skillName} in ${a.harnessName}`,
566
+ (a) => `Replaced a stale symlink for ${a.skillName} in ${a.harnessName}`,
131
567
  );
132
568
 
133
569
  return {
134
- available: true,
570
+ scope,
571
+ sourceRoots,
572
+ sourceRoot,
573
+ rootExists,
574
+ available: skills.length > 0,
135
575
  targets,
136
576
  actions,
577
+ // Ownership above spans EVERY root (a pure string test that reads nothing,
578
+ // so it still answers after a target is gone); the sweep below spans only
579
+ // the roots that EXIST. Those are two different questions and the split is
580
+ // deliberate — see {@link orphanedLinks}.
581
+ orphans: orphanedLinks(
582
+ targets,
583
+ existingRoots,
584
+ new Set(actions.map((a) => a.linkPath)),
585
+ ),
137
586
  skillCount: skills.length,
138
- harnessCount: detected.length,
139
587
  warnings,
140
588
  };
141
589
  }
142
590
 
591
+ /**
592
+ * The links a FORWARD run must retire: the ones this scope owns that no current
593
+ * skill accounts for.
594
+ *
595
+ * The forward pass used to read `d.actions` alone, and `d.actions` enumerates
596
+ * CURRENT skills — so once a skill was dropped upstream, the link it left behind
597
+ * was invisible to it. `d.orphans` was computed and then read only by `--undo`,
598
+ * which removes EVERY owned link rather than just the stale ones, so there was
599
+ * no command that reconciled. Forward is where a reconcile belongs: the plan row
600
+ * says so, and re-running the installer is what a user does to converge.
601
+ *
602
+ * Gated on {@link SkillsDetection.rootExists}: the sweep declines entirely when
603
+ * the source root is absent. `d.orphans` is already empty in that case; the
604
+ * guard is restated here so the safety rule is visible at the site that deletes.
605
+ *
606
+ * @param d - The detection gathered up front.
607
+ * @returns The owned links no current skill accounts for.
608
+ */
609
+ export const staleSkillLinks = (
610
+ d: SkillsDetection,
611
+ ): readonly SymlinkAction[] => (d.rootExists ? d.orphans : []);
612
+
143
613
  /**
144
614
  * Compose the (re)link effects from a detection (builds ABSOLUTE link paths
145
615
  * itself).
146
616
  *
147
617
  * Built from re-runnable combinators (NOT a single-use `gen`) because `execute`
148
618
  * interprets the task twice (preview + perform). `created` links carry an undo;
149
- * a `replaced` link is delete-then-relink (idempotent); `skipped` is a no-op.
619
+ * a `replaced` link is delete-then-relink (idempotent); `skipped` is a no-op —
620
+ * and a detection where every action is `skipped` and nothing is stale composes
621
+ * NOTHING, so a converged re-run performs zero filesystem mutations.
150
622
  *
151
- * FOLLOW-UP(AV-284): global-band skills — skills only link into per-project
152
- * harness dirs today; a `--scope global` machine-band skills location is
153
- * deferred. The manifest line is `[project]`-prefixed to make that explicit.
623
+ * A forward run is a RECONCILE, not an append: it also deletes the links this
624
+ * scope owns that no current skill accounts for (see {@link staleSkillLinks}), each
625
+ * carrying its own re-creation as `undo`.
154
626
  *
155
627
  * @param d - The detection gathered up front.
156
- * @returns A Task that mkdirs each target and creates/replaces each symlink.
628
+ * @returns A Task that mkdirs each needed target, creates/replaces links, and
629
+ * removes the stale ones.
157
630
  */
158
631
  export function composeSkills(d: SkillsDetection): Task<void> {
159
- if (!d.available) return sequence_([]);
160
- const tasks: Task<unknown>[] = [];
161
- for (const { dir } of d.targets) {
162
- tasks.push(mkdir(dir, true));
163
- }
164
- for (const a of d.actions) {
632
+ const pending = d.available
633
+ ? d.actions.filter((a) => a.action !== "skipped")
634
+ : [];
635
+ const stale = staleSkillLinks(d);
636
+ if (pending.length === 0 && stale.length === 0) return sequence_([]);
637
+ const dirs = new Set(pending.map((a) => dirname(a.linkPath)));
638
+ const tasks: Task<unknown>[] = [...dirs]
639
+ .sort()
640
+ .map((dir) => mkdir(dir, true));
641
+ for (const a of pending) {
165
642
  if (a.action === "created") {
166
643
  tasks.push(
167
644
  symlink(a.target, a.linkPath, { undo: deleteFile(a.linkPath) }),
168
645
  );
169
- } else if (a.action === "replaced") {
646
+ } else {
170
647
  tasks.push(deleteFile(a.linkPath));
171
- tasks.push(symlink(a.target, a.linkPath));
648
+ tasks.push(
649
+ symlink(a.target, a.linkPath, { undo: deleteFile(a.linkPath) }),
650
+ );
172
651
  }
173
652
  }
174
- tasks.push(
175
- info(
176
- `[project] Linked ${d.skillCount} skill(s) into ${d.targets.length} dir(s).`,
177
- ),
178
- );
653
+ // The undo clears the path before re-linking: the undo interpreter MOCKS the
654
+ // forward effects when collecting, so the delete above may never have run and
655
+ // `fs.symlink` refuses an occupied path. Same shape `sources update`'s own
656
+ // prune undo uses, for the same reason.
657
+ for (const a of stale) {
658
+ tasks.push(
659
+ deleteFile(a.linkPath, {
660
+ undo: sequence_([
661
+ deleteFile(a.linkPath),
662
+ symlink(a.target, a.linkPath),
663
+ ]),
664
+ }),
665
+ );
666
+ }
179
667
  return sequence_(tasks);
180
668
  }
181
669
 
182
- /** Project a detection onto the {@link SetupSkillsResult} output shape. */
183
- export function toSkillsResult(d: SkillsDetection): SetupSkillsResult {
184
- return {
185
- actions: d.actions,
186
- harnessCount: d.harnessCount,
187
- skillCount: d.skillCount,
188
- warnings: d.warnings,
189
- };
190
- }
670
+ /**
671
+ * The links this scope OWNS right now — every existing symlink pointing into the
672
+ * scope's skill root, including dangling ones, ones already correct, and the
673
+ * ones no current skill accounts for. This is what removal acts on, and it is
674
+ * deliberately NOT the forward plan: undoing a freshly composed forward plan on
675
+ * an already-linked tree reversed the mkdirs and removed nothing, while
676
+ * reporting a step count.
677
+ */
678
+ /**
679
+ * The named reason a skills row skips, shared by the setup plan and the doctor
680
+ * row so the two surfaces cannot word the same finding differently. Two strings
681
+ * for one condition is how a user ends up unsure whether they are looking at one
682
+ * problem or two.
683
+ *
684
+ * ABSENT AND EMPTY ARE NOT THE SAME SKIP. `available` is false for both — the
685
+ * root holds no skill either way — but the parenthetical names a filesystem
686
+ * fact, and telling a user their directory "is absent" while they are looking
687
+ * at it is a wrong diagnosis, not a terse one. {@link
688
+ * SkillsDetection.rootExists} is the flag that separates the two states
689
+ * everywhere else in this module, so it separates them here too, and both
690
+ * callers pass it rather than each re-deciding the wording.
691
+ *
692
+ * The GLOBAL sentence names where skills come from, because "no skills
693
+ * installed" on its own is a dead end: a reader has no way to know that skills
694
+ * arrive inside the configured packs rather than inside this CLI. The scope is
695
+ * named as `local project` — the phrase the `--local` flag that selects it
696
+ * points at — never by a type-layer word.
697
+ *
698
+ * @param sourceRoot - The scope's skill source root, already root-relative.
699
+ * @param scope - The scope being reported.
700
+ * @param rootExists - Whether that root exists ({@link
701
+ * SkillsDetection.rootExists}) — an existing root that holds no skill reports
702
+ * as empty, never as absent.
703
+ * @returns The reason line.
704
+ */
705
+ export const skillsSkipReason = (
706
+ sourceRoot: string,
707
+ scope: Scope,
708
+ rootExists: boolean,
709
+ ): string =>
710
+ scope === "project"
711
+ ? rootExists
712
+ ? `nothing to link — this project holds no skills (${sourceRoot} is empty)`
713
+ : `nothing to link — this project holds no skills (${sourceRoot} does not exist)`
714
+ : `nothing to link — no skills installed yet; they arrive with the packs \`${BIN_NAME} sources update\` builds`;
191
715
 
192
716
  /**
193
- * The EMPTY_RESULTS error the DIRECT `setup skills` sub-verb raises when no
194
- * skills are discoverable (the run-all instead omits the step). Recovery points
195
- * at where skills COME FROM (U8): package skills install on `sources update`.
717
+ * The remedy beneath that skip — {@link skillsSkipReason}'s twin, and authored
718
+ * here for the same reason: the setup row and the doctor row must not word the
719
+ * same finding differently.
720
+ *
721
+ * A skip with no remedy is a dead end. `setup` printed "no skills installed",
722
+ * exited 0 claiming success, and gave the user no next step — while the
723
+ * completions and lsp skips beside it each named an action. The scope decides
724
+ * which action that is, because the scope decides where the skills come from:
725
+ * the global root is filled ONLY by `sources update` (skills ship in the
726
+ * configured packages, not in this CLI), and the local project's is a directory
727
+ * the user fills by hand.
728
+ *
729
+ * It is the IMPERATIVE half of the pair. {@link skillsSkipReason} states the
730
+ * finding and where skills come from; this line is the command to type, so a
731
+ * surface that renders both reads as a fact followed by a next step rather than
732
+ * as the same sentence twice.
733
+ *
734
+ * @param sourceRoot - The scope's skill source root, already root-relative.
735
+ * @param scope - The scope being reported.
736
+ * @returns The remedy line — an action that works on THIS machine now.
737
+ */
738
+ export const skillsSkipRemedy = (sourceRoot: string, scope: Scope): string =>
739
+ scope === "project"
740
+ ? `add a skill at ${sourceRoot}/<name>/SKILL.md, then run this again`
741
+ : `run \`${BIN_NAME} sources update\`, then run this again`;
742
+
743
+ export const ownedSkillLinks = (
744
+ d: SkillsDetection,
745
+ ): readonly SymlinkAction[] => [
746
+ ...d.actions.filter((a) => a.owned),
747
+ ...d.orphans,
748
+ ];
749
+
750
+ /**
751
+ * Compose the removal of every owned link. Each forward effect re-asserts the
752
+ * link (idempotent) and carries its deletion as `undo`, which is what the undo
753
+ * interpreter executes — so the reversal is composed from what detection says
754
+ * this command owns, never from what a fresh forward plan would create.
755
+ *
756
+ * @param d - The detection gathered up front.
757
+ * @returns A Task whose undo removes every owned link.
196
758
  */
197
- export function skillsEmptyError(): PragmaError {
198
- return PragmaError.emptyResults("skill", {
199
- message: "No skills found to link.",
200
- recovery: cliRecovery(
201
- "sources update",
202
- `Add a design-system package that ships skills to your config, then run \`${BIN_NAME} sources update\` to install them.`,
759
+ export function composeSkillsRemoval(d: SkillsDetection): Task<void> {
760
+ const owned = ownedSkillLinks(d);
761
+ if (owned.length === 0) return sequence_([]);
762
+ return sequence_(
763
+ owned.map((a) =>
764
+ symlink(a.target, a.linkPath, { undo: deleteFile(a.linkPath) }),
203
765
  ),
204
- });
766
+ );
205
767
  }