@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.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 (344) hide show
  1. package/README.md +67 -15
  2. package/dist/pragma +0 -0
  3. package/package.json +16 -17
  4. package/src/config/defaultConfig.test.ts +33 -0
  5. package/src/config/defaultConfig.ts +59 -0
  6. package/src/config/defaults.json +17 -0
  7. package/src/config/findProjectConfigPath.test.ts +53 -0
  8. package/src/config/findProjectConfigPath.ts +36 -0
  9. package/src/config/index.ts +17 -2
  10. package/src/config/mergeConfigFileUpdate.ts +58 -0
  11. package/src/config/parseConfigValues.ts +215 -0
  12. package/src/config/readConfig.test.ts +104 -13
  13. package/src/config/readConfig.ts +12 -117
  14. package/src/config/readConfigLayers.test.ts +107 -0
  15. package/src/config/readConfigLayers.ts +115 -0
  16. package/src/config/resolveGlobalConfigPath.ts +13 -0
  17. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  18. package/src/config/resolveWriteConfigPath.ts +31 -0
  19. package/src/config/types.ts +88 -2
  20. package/src/config/writeConfig.ts +25 -43
  21. package/src/constants.ts +12 -1
  22. package/src/domains/block/commands/index.ts +2 -1
  23. package/src/domains/block/commands/list.ts +5 -90
  24. package/src/domains/block/commands/lookup.ts +8 -167
  25. package/src/domains/block/commands/sample.ts +55 -0
  26. package/src/domains/block/formatters/index.ts +2 -1
  27. package/src/domains/block/formatters/sample.ts +21 -0
  28. package/src/domains/block/index.ts +3 -3
  29. package/src/domains/block/mcp/specs.ts +36 -168
  30. package/src/domains/block/operations/index.ts +2 -1
  31. package/src/domains/block/operations/lookup.ts +9 -0
  32. package/src/domains/block/operations/sample.ts +41 -0
  33. package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
  34. package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
  35. package/src/domains/block/stories.test.ts +28 -0
  36. package/src/domains/block/stories.ts +254 -0
  37. package/src/domains/config/commands/channel.test.ts +14 -3
  38. package/src/domains/config/commands/channel.ts +11 -7
  39. package/src/domains/config/commands/configScope.ts +46 -0
  40. package/src/domains/config/commands/framework.test.ts +105 -0
  41. package/src/domains/config/commands/framework.ts +91 -0
  42. package/src/domains/config/commands/index.ts +2 -0
  43. package/src/domains/config/commands/show.ts +5 -45
  44. package/src/domains/config/commands/tier.test.ts +13 -2
  45. package/src/domains/config/commands/tier.ts +11 -7
  46. package/src/domains/config/commands/trace.test.ts +165 -0
  47. package/src/domains/config/commands/trace.ts +83 -0
  48. package/src/domains/config/formatters/channel.test.ts +20 -6
  49. package/src/domains/config/formatters/channel.ts +14 -11
  50. package/src/domains/config/formatters/framework.ts +37 -0
  51. package/src/domains/config/formatters/index.ts +2 -0
  52. package/src/domains/config/formatters/show.test.ts +27 -0
  53. package/src/domains/config/formatters/show.ts +27 -4
  54. package/src/domains/config/formatters/tier.test.ts +26 -10
  55. package/src/domains/config/formatters/tier.ts +19 -11
  56. package/src/domains/config/formatters/trace.ts +28 -0
  57. package/src/domains/config/index.ts +15 -2
  58. package/src/domains/config/mcp/specs.ts +33 -53
  59. package/src/domains/config/operations/index.ts +1 -0
  60. package/src/domains/config/operations/show.test.ts +18 -0
  61. package/src/domains/config/operations/show.ts +7 -1
  62. package/src/domains/config/operations/types.ts +7 -0
  63. package/src/domains/config/operations/validateFramework.ts +24 -0
  64. package/src/domains/config/stories.ts +42 -0
  65. package/src/domains/config/tasks/index.ts +3 -0
  66. package/src/domains/config/tasks/setChannel.ts +13 -22
  67. package/src/domains/config/tasks/setFramework.ts +21 -0
  68. package/src/domains/config/tasks/setTier.ts +10 -23
  69. package/src/domains/config/tasks/setTrace.ts +20 -0
  70. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  71. package/src/domains/create/commands/component.ts +5 -5
  72. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  73. package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
  74. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
  75. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  76. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
  77. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  78. package/src/domains/doctor/operations/checks/index.ts +1 -0
  79. package/src/domains/doctor/operations/checks.test.ts +162 -8
  80. package/src/domains/doctor/operations/runChecks.test.ts +85 -4
  81. package/src/domains/doctor/operations/runChecks.ts +50 -12
  82. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  83. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  84. package/src/domains/graphql/commands/build.test.ts +196 -0
  85. package/src/domains/graphql/commands/build.ts +117 -0
  86. package/src/domains/graphql/commands/check.test.ts +141 -0
  87. package/src/domains/graphql/commands/check.ts +81 -0
  88. package/src/domains/graphql/commands/index.ts +5 -0
  89. package/src/domains/graphql/commands/serve.test.ts +40 -0
  90. package/src/domains/graphql/commands/serve.ts +131 -0
  91. package/src/domains/graphql/constants.ts +10 -0
  92. package/src/domains/graphql/formatters/index.ts +3 -0
  93. package/src/domains/graphql/formatters/report.ts +78 -0
  94. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  95. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  96. package/src/domains/graphql/helpers/index.ts +6 -0
  97. package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
  98. package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
  99. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
  100. package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
  101. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  102. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  103. package/src/domains/graphql/index.ts +24 -0
  104. package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
  105. package/src/domains/graphql/operations/compileSchema.ts +107 -0
  106. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  107. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  108. package/src/domains/graphql/operations/index.ts +12 -0
  109. package/src/domains/graphql/types.ts +29 -0
  110. package/src/domains/info/operations/collectInfo.test.ts +16 -5
  111. package/src/domains/info/operations/collectInfo.ts +16 -19
  112. package/src/domains/llm/capabilities.test.ts +91 -0
  113. package/src/domains/llm/commands/capabilities.ts +28 -0
  114. package/src/domains/llm/commands/index.ts +1 -0
  115. package/src/domains/llm/data/commandReference.ts +90 -18
  116. package/src/domains/llm/data/decisionTrees.ts +15 -13
  117. package/src/domains/llm/data/index.ts +2 -0
  118. package/src/domains/llm/data/toolCatalog.ts +190 -0
  119. package/src/domains/llm/formatters/capabilities.ts +50 -0
  120. package/src/domains/llm/formatters/index.ts +1 -0
  121. package/src/domains/llm/index.ts +3 -2
  122. package/src/domains/llm/mcp/index.ts +1 -1
  123. package/src/domains/llm/mcp/specs.ts +59 -52
  124. package/src/domains/llm/types.ts +40 -0
  125. package/src/domains/modifier/commands/index.ts +2 -1
  126. package/src/domains/modifier/commands/list.ts +5 -38
  127. package/src/domains/modifier/commands/lookup.ts +8 -79
  128. package/src/domains/modifier/commands/sample.ts +53 -0
  129. package/src/domains/modifier/formatters/index.ts +2 -1
  130. package/src/domains/modifier/formatters/sample.ts +15 -0
  131. package/src/domains/modifier/index.ts +7 -3
  132. package/src/domains/modifier/mcp/specs.ts +38 -70
  133. package/src/domains/modifier/operations/index.ts +2 -1
  134. package/src/domains/modifier/operations/lookup.ts +9 -0
  135. package/src/domains/modifier/operations/sample.ts +32 -0
  136. package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
  137. package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
  138. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
  139. package/src/domains/modifier/stories.test.ts +20 -0
  140. package/src/domains/modifier/stories.ts +77 -0
  141. package/src/domains/ontology/commands/list.ts +5 -58
  142. package/src/domains/ontology/commands/show.ts +6 -58
  143. package/src/domains/ontology/formatters/show.ts +7 -1
  144. package/src/domains/ontology/mcp/specs.ts +7 -84
  145. package/src/domains/ontology/stories.test.ts +36 -0
  146. package/src/domains/ontology/stories.ts +108 -0
  147. package/src/domains/refs/operations/index.ts +1 -1
  148. package/src/domains/refs/operations/paths.ts +29 -1
  149. package/src/domains/refs/operations/updateRefs.test.ts +6 -3
  150. package/src/domains/refs/operations/updateRefs.ts +2 -36
  151. package/src/domains/setup/commands/skills.test.ts +6 -2
  152. package/src/domains/setup/commands/skills.ts +6 -2
  153. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  154. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  155. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  156. package/src/domains/shared/bootStore.ts +92 -50
  157. package/src/domains/shared/compactUri.test.ts +11 -0
  158. package/src/domains/shared/compactUri.ts +10 -5
  159. package/src/domains/shared/context.ts +2 -2
  160. package/src/domains/shared/contracts.ts +12 -0
  161. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  162. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  163. package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
  164. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  165. package/src/domains/shared/loaders/bundledLoader.ts +206 -0
  166. package/src/domains/shared/loaders/gitLoader.ts +31 -0
  167. package/src/domains/shared/loaders/index.ts +9 -0
  168. package/src/domains/shared/loaders/localLoader.ts +87 -0
  169. package/src/domains/shared/loaders/readPackageDir.ts +147 -0
  170. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  171. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  172. package/src/domains/shared/lookupMany.test.ts +120 -0
  173. package/src/domains/shared/lookupMany.ts +43 -3
  174. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  175. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  176. package/src/domains/shared/packages.ts +14 -120
  177. package/src/domains/shared/pickRandom.ts +22 -0
  178. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  179. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  180. package/src/domains/shared/runtime.ts +7 -63
  181. package/src/domains/shared/sampleFormatters.ts +47 -0
  182. package/src/domains/shared/semanticPackage.ts +112 -0
  183. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  184. package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
  185. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  186. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  187. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  188. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  189. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  190. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  191. package/src/domains/shared/stories/condense.test.ts +18 -0
  192. package/src/domains/shared/stories/condense.ts +15 -0
  193. package/src/domains/shared/stories/index.ts +20 -0
  194. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  195. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  196. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
  197. package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
  198. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
  199. package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
  200. package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
  201. package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
  202. package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
  203. package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
  204. package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
  205. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
  206. package/src/domains/shared/stories/pack/index.ts +28 -0
  207. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  208. package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
  209. package/src/domains/shared/stories/pack/types.ts +104 -0
  210. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
  211. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
  212. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  213. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  214. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  215. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  216. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  217. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  218. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  219. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  220. package/src/domains/shared/stories/types.ts +171 -0
  221. package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
  222. package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
  223. package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
  224. package/src/domains/shared/suggestions/expandGlob.ts +52 -0
  225. package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
  226. package/src/domains/shared/suggestions/index.ts +7 -0
  227. package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
  228. package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
  229. package/src/domains/shared/suggestions/suggestNames.ts +130 -0
  230. package/src/domains/shared/types/index.ts +1 -0
  231. package/src/domains/shared/types/runtime.ts +4 -1
  232. package/src/domains/shared/types/sample.ts +17 -0
  233. package/src/domains/skill/commands/index.ts +3 -1
  234. package/src/domains/skill/commands/list.test.ts +6 -2
  235. package/src/domains/skill/commands/list.ts +5 -63
  236. package/src/domains/skill/commands/lookup.test.ts +118 -0
  237. package/src/domains/skill/commands/lookup.ts +14 -0
  238. package/src/domains/skill/formatters/index.ts +1 -0
  239. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  240. package/src/domains/skill/formatters/lookup.ts +57 -0
  241. package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
  242. package/src/domains/skill/index.ts +7 -3
  243. package/src/domains/skill/mcp/specs.ts +10 -29
  244. package/src/domains/skill/operations/discover.test.ts +61 -30
  245. package/src/domains/skill/operations/discover.ts +28 -37
  246. package/src/domains/skill/operations/index.ts +2 -1
  247. package/src/domains/skill/operations/list.test.ts +15 -7
  248. package/src/domains/skill/operations/lookup.test.ts +91 -0
  249. package/src/domains/skill/operations/lookup.ts +70 -0
  250. package/src/domains/skill/stories.test.ts +43 -0
  251. package/src/domains/skill/stories.ts +103 -0
  252. package/src/domains/skill/types.ts +8 -0
  253. package/src/domains/standard/commands/index.ts +2 -1
  254. package/src/domains/standard/commands/list.ts +5 -84
  255. package/src/domains/standard/commands/lookup.ts +8 -93
  256. package/src/domains/standard/commands/sample.ts +57 -0
  257. package/src/domains/standard/formatters/index.ts +2 -1
  258. package/src/domains/standard/formatters/sample.ts +15 -0
  259. package/src/domains/standard/index.ts +8 -1
  260. package/src/domains/standard/mcp/specs.ts +44 -152
  261. package/src/domains/standard/operations/index.ts +2 -1
  262. package/src/domains/standard/operations/lookup.ts +9 -0
  263. package/src/domains/standard/operations/sample.ts +35 -0
  264. package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
  265. package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
  266. package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
  267. package/src/domains/standard/stories.test.ts +24 -0
  268. package/src/domains/standard/stories.ts +151 -0
  269. package/src/domains/tier/commands/list.ts +5 -55
  270. package/src/domains/tier/mcp/specs.ts +5 -31
  271. package/src/domains/tier/stories.test.ts +22 -0
  272. package/src/domains/tier/stories.ts +52 -0
  273. package/src/domains/token/commands/index.ts +2 -1
  274. package/src/domains/token/commands/sample.ts +53 -0
  275. package/src/domains/token/formatters/index.ts +2 -1
  276. package/src/domains/token/formatters/sample.ts +16 -0
  277. package/src/domains/token/index.ts +8 -1
  278. package/src/domains/token/mcp/specs.ts +52 -2
  279. package/src/domains/token/operations/index.ts +2 -1
  280. package/src/domains/token/operations/lookup.ts +9 -0
  281. package/src/domains/token/operations/sample.ts +32 -0
  282. package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
  283. package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
  284. package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
  285. package/src/domains/trace/commands/clear.ts +71 -0
  286. package/src/domains/trace/commands/index.ts +5 -0
  287. package/src/domains/trace/commands/sessions.ts +32 -0
  288. package/src/domains/trace/commands/show.ts +145 -0
  289. package/src/domains/trace/formatters/index.ts +9 -0
  290. package/src/domains/trace/formatters/sessions.ts +80 -0
  291. package/src/domains/trace/formatters/show.ts +122 -0
  292. package/src/domains/trace/index.ts +17 -0
  293. package/src/domains/trace/operations/index.ts +8 -0
  294. package/src/domains/trace/operations/listSessions.test.ts +55 -0
  295. package/src/domains/trace/operations/listSessions.ts +52 -0
  296. package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
  297. package/src/domains/trace/operations/readTraceLog.ts +104 -0
  298. package/src/domains/trace/tracePlugin.test.ts +171 -0
  299. package/src/domains/trace/tracePlugin.ts +159 -0
  300. package/src/domains/trace/types.ts +65 -0
  301. package/src/error/PragmaError.ts +15 -2
  302. package/src/error/types.ts +15 -1
  303. package/src/mcp/error/serializeErrorPayload.ts +7 -0
  304. package/src/mcp/error/types.ts +8 -0
  305. package/src/mcp/registerResources.test.ts +405 -75
  306. package/src/mcp/registerResources.ts +5 -299
  307. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  308. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  309. package/src/mcp/resources/buildResourceList.ts +176 -0
  310. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  311. package/src/mcp/resources/classifyEntity.ts +27 -0
  312. package/src/mcp/resources/constants.ts +105 -0
  313. package/src/mcp/resources/displayPredicates.ts +62 -0
  314. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  315. package/src/mcp/resources/pickFirstValue.ts +26 -0
  316. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  317. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  318. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  319. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  320. package/src/mcp/resources/readEntity.ts +169 -0
  321. package/src/mcp/resources/registerResources.ts +126 -0
  322. package/src/mcp/resources/types.ts +128 -0
  323. package/src/mcp/tools/index.ts +9 -0
  324. package/src/mcp/tools/registerTools.test.ts +84 -12
  325. package/src/mcp/types.ts +4 -0
  326. package/src/mcp/utils/wrapTool.test.ts +1 -0
  327. package/src/pipeline/collectCommands.ts +15 -2
  328. package/src/pipeline/renderError.ts +15 -0
  329. package/src/pipeline/resolveCommandKind.ts +8 -1
  330. package/src/pipeline/runCli.ts +51 -6
  331. package/src/testing/createTestMcpClient.ts +5 -1
  332. package/src/testing/graphqlFixtures.ts +72 -0
  333. package/src/testing/index.ts +17 -0
  334. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  335. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  336. package/src/testing/integration/executor-execution.test.ts +101 -0
  337. package/src/testing/integration/executor-golden.test.ts +145 -0
  338. package/src/testing/integration/mcp-surface.test.ts +11 -5
  339. package/src/testing/integration/parity.test.ts +17 -12
  340. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  341. package/src/testing/setupXdgIsolation.ts +15 -0
  342. package/src/testing/standardParityFixtures.ts +92 -0
  343. package/src/testing/storyFixtures.ts +63 -0
  344. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -8,12 +8,11 @@
8
8
  import { existsSync, readdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { readConfig } from "#config";
11
- import { DEFAULT_PACKAGES } from "../../shared/packages.js";
11
+ import { mergeAndParseRefs } from "../../shared/mergeAndParseRefs.js";
12
12
  import { cloneRef, fetchRef, pruneCache } from "./gitOps.js";
13
13
  import type { PackageRef, RawPackageEntry } from "./parseRef.js";
14
14
  import { parsePackageEntry } from "./parseRef.js";
15
15
  import { cacheRoot, gitCacheDir } from "./paths.js";
16
- import readGlobalRefs from "./readGlobalRefs.js";
17
16
 
18
17
  // ---------------------------------------------------------------------------
19
18
  // Types
@@ -56,7 +55,7 @@ export default async function updateRefs(
56
55
  options: UpdateRefsOptions,
57
56
  ): Promise<UpdateResult[]> {
58
57
  const config = readConfig(options.cwd);
59
- const refs = mergeEntries(config.packages);
58
+ const refs = mergeAndParseRefs(config.packages);
60
59
  const results: UpdateResult[] = [];
61
60
 
62
61
  for (const ref of refs) {
@@ -137,39 +136,6 @@ export default async function updateRefs(
137
136
  // Helpers
138
137
  // ---------------------------------------------------------------------------
139
138
 
140
- /** Merge global + project entries, parse into PackageRef[]. */
141
- function mergeEntries(
142
- projectPackages?: ReadonlyArray<RawPackageEntry>,
143
- ): PackageRef[] {
144
- const globalEntries = readGlobalRefs();
145
-
146
- if (
147
- (!projectPackages || projectPackages.length === 0) &&
148
- globalEntries.length === 0
149
- ) {
150
- return DEFAULT_PACKAGES.map((pkg) => parsePackageEntry(pkg));
151
- }
152
-
153
- const merged = new Map<string, RawPackageEntry>();
154
-
155
- for (const pkg of DEFAULT_PACKAGES) {
156
- merged.set(pkg, pkg);
157
- }
158
- for (const entry of globalEntries) {
159
- const name = typeof entry === "string" ? entry : entry.name;
160
- merged.set(name, entry);
161
- }
162
- if (projectPackages) {
163
- merged.clear();
164
- for (const entry of projectPackages) {
165
- const name = typeof entry === "string" ? entry : entry.name;
166
- merged.set(name, entry);
167
- }
168
- }
169
-
170
- return [...merged.values()].map(parsePackageEntry);
171
- }
172
-
173
139
  /**
174
140
  * Remove cache directories that don't match any configured git ref.
175
141
  * Returns list of removed directories.
@@ -7,9 +7,13 @@ import buildSkillsCommand from "./skills.js";
7
7
 
8
8
  const TMP_ROOT = join(tmpdir(), `pragma-setup-cmd-${Date.now()}`);
9
9
 
10
+ /** One source per skill directory, matching the loader granularity. */
10
11
  const TEST_SOURCES = [
11
12
  {
12
- dir: join(TMP_ROOT, "node_modules/@canonical/design-system/skills"),
13
+ dir: join(
14
+ TMP_ROOT,
15
+ "node_modules/@canonical/design-system/skills/design-audit",
16
+ ),
13
17
  packageName: "@canonical/design-system",
14
18
  },
15
19
  ];
@@ -21,7 +25,7 @@ vi.mock("../../skill/helpers/resolveSkillSources.js", () => ({
21
25
  beforeAll(() => {
22
26
  mkdirSync(TMP_ROOT, { recursive: true });
23
27
 
24
- const dir = join(TEST_SOURCES[0].dir, "design-audit");
28
+ const dir = TEST_SOURCES.at(0)?.dir ?? "";
25
29
  mkdirSync(dir, { recursive: true });
26
30
  writeFileSync(
27
31
  join(dir, "SKILL.md"),
@@ -2,9 +2,11 @@ import {
2
2
  type CommandDefinition,
3
3
  type CommandResult,
4
4
  createOutputResult,
5
+ runGeneratorTask,
5
6
  } from "@canonical/cli-core";
6
7
  import { detectHarnesses } from "@canonical/harnesses";
7
- import { collectEffects, runTask } from "@canonical/task";
8
+ import { collectEffects } from "@canonical/task";
9
+ import { runTask } from "@canonical/task/node";
8
10
  import { PragmaError } from "#error";
9
11
  import type { PragmaContext } from "../../shared/context.js";
10
12
  import { selectFormatter } from "../../shared/formatters.js";
@@ -94,7 +96,9 @@ export default function buildSkillsCommand(): CommandDefinition {
94
96
  });
95
97
  }
96
98
 
97
- const result = await runTask(task);
99
+ // Run the symlink task through the shared execution core, like the
100
+ // other setup commands.
101
+ const result = await runGeneratorTask(task);
98
102
 
99
103
  const output: SetupSkillsOutput = { result, dryRun: false };
100
104
 
@@ -1,33 +1,44 @@
1
- import { info, pure, writeFile } from "@canonical/task";
1
+ import { answerPromptWithDefaults } from "@canonical/cli-core";
2
+ import { pure, type Task, writeFile } from "@canonical/task";
2
3
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
-
4
- const { createInterfaceMock, dryRunMock, runTaskMock, runUndoMock } =
5
- vi.hoisted(() => ({
6
- createInterfaceMock: vi.fn(),
7
- dryRunMock: vi.fn(() => ({ effects: [] })),
8
- runTaskMock: vi.fn(),
9
- runUndoMock: vi.fn(),
10
- }));
11
-
12
- async function loadRunSetupTask() {
13
- vi.resetModules();
14
- vi.doMock("node:readline", () => ({
15
- createInterface: createInterfaceMock,
16
- }));
17
- vi.doMock("@canonical/task", async (importOriginal) => {
18
- const actual = await importOriginal<typeof import("@canonical/task")>();
19
- return {
20
- ...actual,
21
- dryRun: dryRunMock,
22
- runTask: runTaskMock,
23
- runUndo: runUndoMock,
24
- };
25
- });
26
- return (await import("./runSetupTask.js")).default;
27
- }
28
-
29
4
  import runSetupTask from "./runSetupTask.js";
30
5
 
6
+ const {
7
+ runGeneratorTaskMock,
8
+ runUndoMock,
9
+ sessionAnswerMock,
10
+ sessionDisposeMock,
11
+ } = vi.hoisted(() => ({
12
+ runGeneratorTaskMock: vi.fn(),
13
+ runUndoMock: vi.fn(),
14
+ sessionAnswerMock: vi.fn(),
15
+ sessionDisposeMock: vi.fn(),
16
+ }));
17
+
18
+ // runSetupTask orchestrates: dry-run/undo use real formatting and runUndo,
19
+ // while production runs through the shared execution core. Mock the core,
20
+ // runUndo, and the interactive prompt session, so the wiring and lifecycle
21
+ // stay under test with real effect formatting.
22
+ vi.mock("@canonical/cli-core", async (importOriginal) => ({
23
+ ...(await importOriginal<typeof import("@canonical/cli-core")>()),
24
+ runGeneratorTask: runGeneratorTaskMock,
25
+ }));
26
+
27
+ vi.mock("@canonical/task/node", async (importOriginal) => ({
28
+ ...(await importOriginal<typeof import("@canonical/task/node")>()),
29
+ runUndo: runUndoMock,
30
+ }));
31
+
32
+ vi.mock("../../shared/createInteractivePromptSession.js", () => ({
33
+ default: () => ({
34
+ answerPrompt: sessionAnswerMock,
35
+ wasInterrupted: () => false,
36
+ dispose: sessionDisposeMock,
37
+ }),
38
+ }));
39
+
40
+ const succeed = async (): Promise<undefined> => undefined;
41
+
31
42
  describe("runSetupTask", () => {
32
43
  let stderrSpy: ReturnType<typeof vi.spyOn>;
33
44
 
@@ -40,13 +51,16 @@ describe("runSetupTask", () => {
40
51
 
41
52
  afterEach(() => {
42
53
  stderrSpy.mockRestore();
43
- vi.doUnmock("node:readline");
44
- vi.doUnmock("@canonical/task");
45
54
  });
46
55
 
56
+ // ===========================================================================
57
+ // Dry-run mode
58
+ // ===========================================================================
59
+
47
60
  it("returns output result in dry-run mode", async () => {
48
- const task = writeFile("/tmp/test.txt", "hello");
49
- const result = await runSetupTask(task, { dryRun: true });
61
+ const result = await runSetupTask(writeFile("/tmp/test.txt", "hello"), {
62
+ dryRun: true,
63
+ });
50
64
 
51
65
  expect(result.tag).toBe("output");
52
66
  if (result.tag === "output") {
@@ -54,176 +68,152 @@ describe("runSetupTask", () => {
54
68
  expect(text).toContain("Dry run");
55
69
  expect(text).toContain("/tmp/test.txt");
56
70
  }
71
+ expect(runGeneratorTaskMock).not.toHaveBeenCalled();
57
72
  });
58
73
 
59
74
  it("returns json output in dry-run + json mode", async () => {
60
- const task = writeFile("/tmp/test.txt", "hello");
61
- const result = await runSetupTask(task, {
75
+ const result = await runSetupTask(writeFile("/tmp/test.txt", "hello"), {
62
76
  dryRun: true,
63
77
  format: "json",
64
78
  });
65
79
 
66
80
  expect(result.tag).toBe("output");
67
81
  if (result.tag === "output") {
68
- const text = result.render.plain(result.value);
69
- const parsed = JSON.parse(text);
82
+ const parsed = JSON.parse(result.render.plain(result.value));
70
83
  expect(Array.isArray(parsed)).toBe(true);
71
84
  expect(parsed[0].action).toBe("WriteFile");
72
85
  }
73
86
  });
74
87
 
75
88
  it("returns llm markdown in dry-run + llm mode", async () => {
76
- const task = writeFile("/tmp/test.txt", "hello");
77
- const result = await runSetupTask(task, { dryRun: true, llm: true });
89
+ const result = await runSetupTask(writeFile("/tmp/test.txt", "hello"), {
90
+ dryRun: true,
91
+ llm: true,
92
+ });
78
93
 
79
94
  expect(result.tag).toBe("output");
80
95
  if (result.tag === "output") {
81
- const text = result.render.plain(result.value);
82
- expect(text).toContain("## Dry Run");
83
- expect(text).toContain("WriteFile");
96
+ expect(result.render.plain(result.value)).toContain("Dry Run");
84
97
  }
85
98
  });
86
99
 
87
- it("returns exit 0 on successful execution with --yes", async () => {
88
- const task = info("test message");
89
- const result = await runSetupTask(task, { yes: true });
100
+ it("shows the no-visible-effects message when a dry-run collects none", async () => {
101
+ const result = await runSetupTask(pure(undefined) as Task<void>, {
102
+ dryRun: true,
103
+ verbose: false,
104
+ });
90
105
 
91
- expect(result.tag).toBe("exit");
92
- if (result.tag === "exit") {
93
- expect(result.code).toBe(0);
106
+ expect(result.tag).toBe("output");
107
+ if (result.tag === "output") {
108
+ expect(result.render.plain(result.value)).toContain("no visible effects");
94
109
  }
95
110
  });
96
111
 
97
- it("returns exit 0 for pure task", async () => {
98
- const task = pure(undefined);
99
- const result = await runSetupTask(task, { yes: true });
112
+ // ===========================================================================
113
+ // Production execution — through the shared execution core
114
+ // ===========================================================================
115
+
116
+ it("runs production through the shared execution core and returns exit 0", async () => {
117
+ runGeneratorTaskMock.mockImplementation(succeed);
100
118
 
119
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), { yes: true });
120
+
121
+ expect(runGeneratorTaskMock).toHaveBeenCalledTimes(1);
101
122
  expect(result.tag).toBe("exit");
102
- if (result.tag === "exit") {
103
- expect(result.code).toBe(0);
104
- }
123
+ if (result.tag === "exit") expect(result.code).toBe(0);
105
124
  });
106
125
 
107
- it("shows no visible effects message for pure dry-run", async () => {
108
- const task = pure(undefined);
109
- const result = await runSetupTask(task, { dryRun: true });
126
+ it("wires the auto-confirm handler in yes mode", async () => {
127
+ runGeneratorTaskMock.mockImplementation(async (_task, options) => {
128
+ expect(options.promptHandler).toBe(answerPromptWithDefaults);
129
+ return succeed();
130
+ });
110
131
 
111
- expect(result.tag).toBe("output");
112
- if (result.tag === "output") {
113
- const text = result.render.plain(result.value);
114
- expect(text).toContain("no visible effects");
115
- }
132
+ await runSetupTask(writeFile("/tmp/x", "y"), { yes: true });
133
+ expect(runGeneratorTaskMock).toHaveBeenCalledTimes(1);
116
134
  });
117
135
 
118
- it("returns exit 0 in undo mode for a pure task", async () => {
119
- const task = pure(undefined);
120
- const result = await runSetupTask(task, { undo: true, yes: true });
136
+ it("wires an interactive prompt session when not in yes mode and disposes it", async () => {
137
+ runGeneratorTaskMock.mockImplementation(async (_task, options) => {
138
+ expect(options.promptHandler).toBe(sessionAnswerMock);
139
+ expect(sessionDisposeMock).not.toHaveBeenCalled();
140
+ return succeed();
141
+ });
121
142
 
122
- expect(result.tag).toBe("exit");
123
- if (result.tag === "exit") {
124
- expect(result.code).toBe(0);
125
- }
126
- expect(stderrSpy).toHaveBeenCalledWith("Nothing to undo.\n");
143
+ await runSetupTask(writeFile("/tmp/x", "y"), {});
144
+ expect(runGeneratorTaskMock).toHaveBeenCalledTimes(1);
145
+ expect(sessionDisposeMock).toHaveBeenCalledTimes(1);
127
146
  });
128
147
 
129
- it("auto-confirms select, multiselect, confirm, and text prompts in yes mode", async () => {
130
- runTaskMock.mockImplementation(async (_task, options) => {
131
- expect(
132
- await options.promptHandler({
133
- _tag: "Prompt",
134
- question: { type: "confirm", default: false, message: "Continue?" },
135
- }),
136
- ).toBe(false);
137
- expect(
138
- await options.promptHandler({
139
- _tag: "Prompt",
140
- question: {
141
- type: "select",
142
- choices: [{ value: "zsh" }],
143
- message: "Shell?",
144
- },
145
- }),
146
- ).toBe("zsh");
147
- expect(
148
- await options.promptHandler({
149
- _tag: "Prompt",
150
- question: { type: "multiselect", message: "Pick many" },
151
- }),
152
- ).toEqual([]);
153
- expect(
154
- await options.promptHandler({
155
- _tag: "Prompt",
156
- question: { type: "input", message: "Name?" },
157
- }),
158
- ).toBe("");
159
- });
148
+ it("disposes the prompt session even when production execution fails", async () => {
149
+ runGeneratorTaskMock.mockRejectedValue(new Error("boom"));
160
150
 
161
- const runSetupTaskWithMocks = await loadRunSetupTask();
162
- const result = await runSetupTaskWithMocks({} as never, { yes: true });
151
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {});
163
152
 
164
153
  expect(result.tag).toBe("exit");
165
- if (result.tag === "exit") {
166
- expect(result.code).toBe(0);
167
- }
154
+ expect(sessionDisposeMock).toHaveBeenCalledTimes(1);
168
155
  });
169
156
 
170
- it("uses interactive confirm defaults and suppresses debug logs unless verbose", async () => {
171
- createInterfaceMock.mockReturnValue({
172
- question: (_prompt: string, cb: (answer: string) => void) => cb(""),
173
- close: vi.fn(),
174
- });
175
- runTaskMock.mockImplementation(async (_task, options) => {
176
- expect(
177
- await options.promptHandler({
178
- _tag: "Prompt",
179
- question: { type: "confirm", default: true, message: "Continue?" },
180
- }),
181
- ).toBe(true);
157
+ it("suppresses debug logs unless verbose during production", async () => {
158
+ runGeneratorTaskMock.mockImplementation(async (_task, options) => {
182
159
  options.onLog("debug", "hidden");
183
160
  options.onLog("info", "shown");
161
+ return succeed();
184
162
  });
185
163
 
186
- const runSetupTaskWithMocks = await loadRunSetupTask();
187
- const result = await runSetupTaskWithMocks({} as never, {});
164
+ await runSetupTask(writeFile("/tmp/x", "y"), { yes: true });
188
165
 
189
- expect(result.tag).toBe("exit");
190
166
  expect(stderrSpy).toHaveBeenCalledWith("shown\n");
191
167
  expect(stderrSpy).not.toHaveBeenCalledWith("hidden\n");
192
168
  });
193
169
 
194
- it("returns false for explicit negative interactive confirmation", async () => {
195
- createInterfaceMock.mockReturnValue({
196
- question: (_prompt: string, cb: (answer: string) => void) => cb("n"),
197
- close: vi.fn(),
170
+ it("emits debug logs when verbose during production", async () => {
171
+ runGeneratorTaskMock.mockImplementation(async (_task, options) => {
172
+ options.onLog("debug", "shown-debug");
173
+ return succeed();
198
174
  });
199
- runTaskMock.mockImplementation(async (_task, options) => {
200
- expect(
201
- await options.promptHandler({
202
- _tag: "Prompt",
203
- question: { type: "confirm", default: false, message: "Continue?" },
204
- }),
205
- ).toBe(false);
206
- });
207
-
208
- const runSetupTaskWithMocks = await loadRunSetupTask();
209
- const result = await runSetupTaskWithMocks({} as never, {});
210
175
 
211
- expect(result.tag).toBe("exit");
176
+ await runSetupTask(writeFile("/tmp/x", "y"), { yes: true, verbose: true });
177
+ expect(stderrSpy).toHaveBeenCalledWith("shown-debug\n");
212
178
  });
213
179
 
214
180
  it("returns exit 1 when production execution fails", async () => {
215
- runTaskMock.mockRejectedValue(new Error("permission denied"));
181
+ runGeneratorTaskMock.mockRejectedValue(new Error("permission denied"));
216
182
 
217
- const runSetupTaskWithMocks = await loadRunSetupTask();
218
- const result = await runSetupTaskWithMocks({} as never, { yes: true });
183
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), { yes: true });
219
184
 
220
185
  expect(result.tag).toBe("exit");
221
- if (result.tag === "exit") {
222
- expect(result.code).toBe(1);
223
- }
186
+ if (result.tag === "exit") expect(result.code).toBe(1);
224
187
  expect(stderrSpy).toHaveBeenCalledWith("Setup failed: permission denied\n");
225
188
  });
226
189
 
190
+ it("stringifies a non-Error production rejection", async () => {
191
+ runGeneratorTaskMock.mockRejectedValue("raw failure");
192
+
193
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), { yes: true });
194
+
195
+ expect(result.tag).toBe("exit");
196
+ if (result.tag === "exit") expect(result.code).toBe(1);
197
+ expect(stderrSpy).toHaveBeenCalledWith("Setup failed: raw failure\n");
198
+ });
199
+
200
+ // ===========================================================================
201
+ // Undo mode
202
+ // ===========================================================================
203
+
204
+ it("reports nothing to undo when no undos are collected", async () => {
205
+ runUndoMock.mockResolvedValue({ undoCount: 0 });
206
+
207
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {
208
+ undo: true,
209
+ yes: true,
210
+ });
211
+
212
+ expect(result.tag).toBe("exit");
213
+ if (result.tag === "exit") expect(result.code).toBe(0);
214
+ expect(stderrSpy).toHaveBeenCalledWith("Nothing to undo.\n");
215
+ });
216
+
227
217
  it("writes verbose undo logs and the singular completion message", async () => {
228
218
  runUndoMock.mockImplementation(async (_task, options) => {
229
219
  options.onLog("debug", "debug line");
@@ -231,8 +221,7 @@ describe("runSetupTask", () => {
231
221
  return { undoCount: 1 };
232
222
  });
233
223
 
234
- const runSetupTaskWithMocks = await loadRunSetupTask();
235
- const result = await runSetupTaskWithMocks({} as never, {
224
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {
236
225
  undo: true,
237
226
  yes: true,
238
227
  verbose: true,
@@ -249,8 +238,7 @@ describe("runSetupTask", () => {
249
238
  it("writes the plural undo completion message", async () => {
250
239
  runUndoMock.mockResolvedValue({ undoCount: 2 });
251
240
 
252
- const runSetupTaskWithMocks = await loadRunSetupTask();
253
- const result = await runSetupTaskWithMocks({} as never, {
241
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {
254
242
  undo: true,
255
243
  yes: true,
256
244
  });
@@ -264,16 +252,26 @@ describe("runSetupTask", () => {
264
252
  it("returns exit 1 when undo fails", async () => {
265
253
  runUndoMock.mockRejectedValue(new Error("cannot undo"));
266
254
 
267
- const runSetupTaskWithMocks = await loadRunSetupTask();
268
- const result = await runSetupTaskWithMocks({} as never, {
255
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {
269
256
  undo: true,
270
257
  yes: true,
271
258
  });
272
259
 
273
260
  expect(result.tag).toBe("exit");
274
- if (result.tag === "exit") {
275
- expect(result.code).toBe(1);
276
- }
261
+ if (result.tag === "exit") expect(result.code).toBe(1);
277
262
  expect(stderrSpy).toHaveBeenCalledWith("Undo failed: cannot undo\n");
278
263
  });
264
+
265
+ it("stringifies a non-Error undo rejection", async () => {
266
+ runUndoMock.mockRejectedValue("raw undo failure");
267
+
268
+ const result = await runSetupTask(writeFile("/tmp/x", "y"), {
269
+ undo: true,
270
+ yes: true,
271
+ });
272
+
273
+ expect(result.tag).toBe("exit");
274
+ if (result.tag === "exit") expect(result.code).toBe(1);
275
+ expect(stderrSpy).toHaveBeenCalledWith("Undo failed: raw undo failure\n");
276
+ });
279
277
  });