@canonical/pragma-cli 0.29.0 → 0.31.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 (410) hide show
  1. package/README.md +128 -47
  2. package/dist/pragma +0 -0
  3. package/package.json +15 -13
  4. package/src/completions/constants.ts +7 -0
  5. package/src/completions/handleQuery.test.ts +32 -0
  6. package/src/completions/handleQuery.ts +20 -1
  7. package/src/completions/queryCompletions.test.ts +5 -0
  8. package/src/completions/queryCompletions.ts +17 -3
  9. package/src/completions/querySocket.test.ts +31 -0
  10. package/src/completions/querySocket.ts +36 -9
  11. package/src/config/defaultConfig.test.ts +33 -0
  12. package/src/config/defaultConfig.ts +59 -0
  13. package/src/config/defaults.json +17 -0
  14. package/src/config/findProjectConfigPath.test.ts +53 -0
  15. package/src/config/findProjectConfigPath.ts +36 -0
  16. package/src/config/index.ts +17 -2
  17. package/src/config/mergeConfigFileUpdate.ts +58 -0
  18. package/src/config/parseConfigValues.ts +217 -0
  19. package/src/config/readConfig.test.ts +104 -13
  20. package/src/config/readConfig.ts +12 -153
  21. package/src/config/readConfigLayers.test.ts +109 -0
  22. package/src/config/readConfigLayers.ts +118 -0
  23. package/src/config/resolveGlobalConfigPath.ts +13 -0
  24. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  25. package/src/config/resolveWriteConfigPath.ts +31 -0
  26. package/src/config/types.ts +83 -1
  27. package/src/config/writeConfig.ts +25 -55
  28. package/src/constants.ts +3 -1
  29. package/src/domains/block/commands/list.ts +5 -90
  30. package/src/domains/block/commands/lookup.ts +8 -167
  31. package/src/domains/block/commands/sample.ts +2 -1
  32. package/src/domains/block/formatters/list.test.ts +3 -3
  33. package/src/domains/block/formatters/lookup.test.ts +5 -5
  34. package/src/domains/block/mcp/specs.ts +16 -196
  35. package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
  36. package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
  37. package/src/domains/block/stories.test.ts +28 -0
  38. package/src/domains/block/stories.ts +260 -0
  39. package/src/domains/config/commands/channel.test.ts +30 -3
  40. package/src/domains/config/commands/channel.ts +25 -8
  41. package/src/domains/config/commands/configScope.ts +46 -0
  42. package/src/domains/config/commands/framework.test.ts +32 -1
  43. package/src/domains/config/commands/framework.ts +26 -8
  44. package/src/domains/config/commands/show.ts +5 -45
  45. package/src/domains/config/commands/tier.test.ts +20 -2
  46. package/src/domains/config/commands/tier.ts +25 -8
  47. package/src/domains/config/commands/trace.test.ts +19 -2
  48. package/src/domains/config/commands/trace.ts +6 -3
  49. package/src/domains/config/formatters/channel.test.ts +20 -6
  50. package/src/domains/config/formatters/channel.ts +14 -11
  51. package/src/domains/config/formatters/framework.ts +14 -11
  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 +7 -7
  57. package/src/domains/config/mcp/specs.ts +33 -53
  58. package/src/domains/config/operations/show.test.ts +18 -0
  59. package/src/domains/config/operations/show.ts +7 -1
  60. package/src/domains/config/operations/types.ts +7 -0
  61. package/src/domains/config/operations/validateTier.test.ts +9 -0
  62. package/src/domains/config/operations/validateTier.ts +10 -2
  63. package/src/domains/config/stories.ts +42 -0
  64. package/src/domains/config/tasks/index.ts +1 -0
  65. package/src/domains/config/tasks/setChannel.ts +13 -28
  66. package/src/domains/config/tasks/setFramework.ts +11 -30
  67. package/src/domains/config/tasks/setTier.ts +10 -29
  68. package/src/domains/config/tasks/setTrace.ts +10 -28
  69. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  70. package/src/domains/create/generatorPack/bundled.ts +49 -0
  71. package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
  72. package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
  73. package/src/domains/create/index.ts +13 -7
  74. package/src/domains/create/mcp/specs.ts +6 -161
  75. package/src/domains/create/renderGeneratorUi.test.ts +103 -0
  76. package/src/domains/create/renderGeneratorUi.ts +65 -0
  77. package/src/domains/doctor/formatters/doctor.test.ts +50 -2
  78. package/src/domains/doctor/formatters/doctor.ts +90 -38
  79. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  80. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +133 -0
  81. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  82. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +32 -26
  83. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  84. package/src/domains/doctor/operations/checks/index.ts +1 -0
  85. package/src/domains/doctor/operations/checks.test.ts +160 -7
  86. package/src/domains/doctor/operations/runChecks.test.ts +15 -6
  87. package/src/domains/doctor/operations/runChecks.ts +2 -0
  88. package/src/domains/doctor/operations/types.ts +23 -1
  89. package/src/domains/graph/commands/inspect.test.ts +2 -2
  90. package/src/domains/graph/commands/query.test.ts +5 -3
  91. package/src/domains/graph/formatters/inspect.ts +7 -7
  92. package/src/domains/graph/formatters/query.test.ts +87 -0
  93. package/src/domains/graph/formatters/query.ts +42 -5
  94. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  95. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  96. package/src/domains/graph/operations/inspectUri.test.ts +5 -5
  97. package/src/domains/graphql/commands/build.test.ts +5 -3
  98. package/src/domains/graphql/commands/build.ts +8 -16
  99. package/src/domains/graphql/commands/check.test.ts +5 -3
  100. package/src/domains/graphql/commands/check.ts +7 -13
  101. package/src/domains/graphql/commands/index.ts +1 -0
  102. package/src/domains/graphql/commands/serve.test.ts +46 -0
  103. package/src/domains/graphql/commands/serve.ts +141 -0
  104. package/src/domains/graphql/constants.ts +10 -0
  105. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  106. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  107. package/src/domains/graphql/helpers/index.ts +2 -0
  108. package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
  109. package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
  110. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
  111. package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
  112. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  113. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  114. package/src/domains/graphql/index.ts +9 -6
  115. package/src/domains/graphql/operations/compileSchema.test.ts +23 -47
  116. package/src/domains/graphql/operations/compileSchema.ts +23 -25
  117. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  118. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  119. package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
  120. package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
  121. package/src/domains/graphql/operations/index.ts +10 -0
  122. package/src/domains/info/formatters/upgrade.test.ts +7 -4
  123. package/src/domains/info/formatters/upgrade.ts +3 -1
  124. package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
  125. package/src/domains/info/operations/collectEntityCounts.ts +123 -0
  126. package/src/domains/info/operations/collectInfo.test.ts +14 -3
  127. package/src/domains/info/operations/collectInfo.ts +12 -15
  128. package/src/domains/info/operations/index.ts +1 -0
  129. package/src/domains/info/types.ts +13 -0
  130. package/src/domains/llm/capabilities.test.ts +24 -4
  131. package/src/domains/llm/commands/capabilities.ts +12 -3
  132. package/src/domains/llm/commands/llm.ts +5 -1
  133. package/src/domains/llm/data/commandReference.ts +20 -11
  134. package/src/domains/llm/data/decisionTrees.ts +10 -5
  135. package/src/domains/llm/data/toolCatalog.ts +55 -18
  136. package/src/domains/llm/formatters/orientation.ts +5 -1
  137. package/src/domains/llm/llmCommand.test.ts +11 -3
  138. package/src/domains/llm/mcp/specs.ts +3 -4
  139. package/src/domains/llm/operations/collectContext.test.ts +64 -0
  140. package/src/domains/llm/operations/collectContext.ts +7 -17
  141. package/src/domains/modifier/commands/list.ts +5 -38
  142. package/src/domains/modifier/commands/lookup.ts +8 -79
  143. package/src/domains/modifier/commands/sample.ts +2 -1
  144. package/src/domains/modifier/mcp/specs.ts +15 -94
  145. package/src/domains/modifier/operations/lookup.ts +5 -3
  146. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
  147. package/src/domains/modifier/stories.test.ts +20 -0
  148. package/src/domains/modifier/stories.ts +86 -0
  149. package/src/domains/ontology/commands/list.test.ts +5 -3
  150. package/src/domains/ontology/commands/list.ts +5 -58
  151. package/src/domains/ontology/commands/show.test.ts +85 -9
  152. package/src/domains/ontology/commands/show.ts +6 -58
  153. package/src/domains/ontology/formatters/index.ts +1 -0
  154. package/src/domains/ontology/formatters/list.ts +37 -12
  155. package/src/domains/ontology/formatters/show.ts +361 -64
  156. package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
  157. package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
  158. package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
  159. package/src/domains/ontology/helpers/index.ts +14 -0
  160. package/src/domains/ontology/helpers/queryClasses.ts +32 -16
  161. package/src/domains/ontology/helpers/queryProperties.ts +39 -6
  162. package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
  163. package/src/domains/ontology/mcp/specs.ts +7 -84
  164. package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
  165. package/src/domains/ontology/operations/listOntologies.ts +126 -30
  166. package/src/domains/ontology/operations/showOntology.test.ts +131 -12
  167. package/src/domains/ontology/operations/showOntology.ts +289 -7
  168. package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
  169. package/src/domains/ontology/stories.test.ts +36 -0
  170. package/src/domains/ontology/stories.ts +153 -0
  171. package/src/domains/refs/operations/updateRefs.ts +2 -38
  172. package/src/domains/setup/commands/index.ts +1 -0
  173. package/src/domains/setup/commands/setup.ts +70 -0
  174. package/src/domains/setup/commands/skills.test.ts +6 -2
  175. package/src/domains/setup/commands/skills.ts +6 -2
  176. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  177. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  178. package/src/domains/setup/index.ts +7 -3
  179. package/src/domains/setup/operations/setupLsp.test.ts +3 -2
  180. package/src/domains/setup/operations/setupLsp.ts +7 -2
  181. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  182. package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
  183. package/src/domains/shared/bootStore.test.ts +82 -0
  184. package/src/domains/shared/bootStore.ts +57 -18
  185. package/src/domains/shared/compactUri.test.ts +11 -0
  186. package/src/domains/shared/compactUri.ts +10 -5
  187. package/src/domains/shared/context.ts +2 -2
  188. package/src/domains/shared/contracts.ts +11 -0
  189. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  190. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  191. package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
  192. package/src/domains/shared/extractLocalName.test.ts +3 -3
  193. package/src/domains/shared/formatters.ts +23 -2
  194. package/src/domains/shared/listTiers.ts +40 -0
  195. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  196. package/src/domains/shared/loaders/bundledLoader.ts +158 -12
  197. package/src/domains/shared/loaders/gitLoader.ts +1 -2
  198. package/src/domains/shared/loaders/index.ts +4 -1
  199. package/src/domains/shared/loaders/localLoader.ts +2 -4
  200. package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
  201. package/src/domains/shared/loaders/readPackageDir.ts +82 -9
  202. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  203. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  204. package/src/domains/shared/lookupMany.test.ts +135 -0
  205. package/src/domains/shared/lookupMany.ts +50 -2
  206. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  207. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  208. package/src/domains/shared/namespaces.ts +13 -5
  209. package/src/domains/shared/packages.ts +10 -17
  210. package/src/domains/shared/parseSampleCount.test.ts +47 -0
  211. package/src/domains/shared/parseSampleCount.ts +45 -0
  212. package/src/domains/shared/prefixes.test.ts +211 -0
  213. package/src/domains/shared/prefixes.ts +182 -15
  214. package/src/domains/shared/renderers.ts +5 -5
  215. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  216. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  217. package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
  218. package/src/domains/shared/resolveLookupExitCode.ts +42 -0
  219. package/src/domains/shared/runtime.ts +4 -63
  220. package/src/domains/shared/semanticPackage.test.ts +89 -0
  221. package/src/domains/shared/semanticPackage.ts +34 -3
  222. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  223. package/src/domains/shared/stories/compileLookupCommand.ts +102 -0
  224. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  225. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  226. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  227. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  228. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  229. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  230. package/src/domains/shared/stories/condense.test.ts +18 -0
  231. package/src/domains/shared/stories/condense.ts +15 -0
  232. package/src/domains/shared/stories/index.ts +20 -0
  233. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  234. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  235. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +151 -0
  236. package/src/domains/shared/stories/pack/applyPackFilters.ts +111 -0
  237. package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
  238. package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
  239. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +81 -0
  240. package/src/domains/shared/stories/pack/buildLookupQuery.ts +154 -0
  241. package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
  242. package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
  243. package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
  244. package/src/domains/shared/stories/pack/collectPackStories.test.ts +406 -0
  245. package/src/domains/shared/stories/pack/collectPackStories.ts +158 -0
  246. package/src/domains/shared/stories/pack/compilePackCommands.ts +50 -0
  247. package/src/domains/shared/stories/pack/compilePackStories.test.ts +751 -0
  248. package/src/domains/shared/stories/pack/compilePackStories.ts +724 -0
  249. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +55 -0
  250. package/src/domains/shared/stories/pack/index.ts +42 -0
  251. package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
  252. package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
  253. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  254. package/src/domains/shared/stories/pack/standardParity.test.ts +381 -0
  255. package/src/domains/shared/stories/pack/types.ts +224 -0
  256. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +742 -0
  257. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +859 -0
  258. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  259. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  260. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  261. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  262. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  263. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  264. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  265. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  266. package/src/domains/shared/stories/types.ts +179 -0
  267. package/src/domains/shared/suggestions/expandGlob.ts +1 -0
  268. package/src/domains/shared/suggestions/suggestNames.ts +43 -10
  269. package/src/domains/shared/types/index.ts +5 -9
  270. package/src/domains/shared/types/ontology.ts +119 -24
  271. package/src/domains/shared/types/standard.ts +9 -52
  272. package/src/domains/skill/commands/index.ts +3 -1
  273. package/src/domains/skill/commands/list.test.ts +6 -2
  274. package/src/domains/skill/commands/list.ts +5 -63
  275. package/src/domains/skill/commands/lookup.test.ts +118 -0
  276. package/src/domains/skill/commands/lookup.ts +14 -0
  277. package/src/domains/skill/formatters/index.ts +1 -0
  278. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  279. package/src/domains/skill/formatters/lookup.ts +57 -0
  280. package/src/domains/skill/helpers/resolveSkillSources.ts +0 -1
  281. package/src/domains/skill/index.ts +7 -3
  282. package/src/domains/skill/mcp/specs.ts +10 -29
  283. package/src/domains/skill/operations/discover.test.ts +61 -30
  284. package/src/domains/skill/operations/discover.ts +28 -37
  285. package/src/domains/skill/operations/index.ts +2 -1
  286. package/src/domains/skill/operations/list.test.ts +15 -7
  287. package/src/domains/skill/operations/lookup.test.ts +91 -0
  288. package/src/domains/skill/operations/lookup.ts +70 -0
  289. package/src/domains/skill/stories.test.ts +43 -0
  290. package/src/domains/skill/stories.ts +103 -0
  291. package/src/domains/skill/types.ts +8 -0
  292. package/src/domains/token/commands/lookup.test.ts +8 -0
  293. package/src/domains/token/commands/lookup.ts +12 -0
  294. package/src/domains/token/commands/sample.ts +2 -1
  295. package/src/domains/token/featureFlag.ts +17 -0
  296. package/src/domains/token/index.ts +5 -0
  297. package/src/domains/token/mcp/specs.ts +18 -2
  298. package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
  299. package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
  300. package/src/domains/trace/commands/show.ts +30 -4
  301. package/src/domains/trace/tracePlugin.test.ts +1 -1
  302. package/src/index.ts +1 -13
  303. package/src/mcp/registerResources.test.ts +382 -74
  304. package/src/mcp/registerResources.ts +5 -348
  305. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  306. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  307. package/src/mcp/resources/buildResourceList.ts +176 -0
  308. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  309. package/src/mcp/resources/classifyEntity.ts +27 -0
  310. package/src/mcp/resources/constants.ts +105 -0
  311. package/src/mcp/resources/displayPredicates.ts +62 -0
  312. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  313. package/src/mcp/resources/pickFirstValue.ts +26 -0
  314. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  315. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  316. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  317. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  318. package/src/mcp/resources/readEntity.ts +169 -0
  319. package/src/mcp/resources/registerResources.ts +126 -0
  320. package/src/mcp/resources/types.ts +128 -0
  321. package/src/mcp/tools/index.ts +22 -5
  322. package/src/mcp/tools/registerTools.test.ts +167 -34
  323. package/src/mcp/types.ts +4 -0
  324. package/src/pipeline/collectCommands.test.ts +151 -1
  325. package/src/pipeline/collectCommands.ts +35 -13
  326. package/src/pipeline/createProgram.test.ts +5 -1
  327. package/src/pipeline/createProgram.ts +2 -2
  328. package/src/pipeline/firstRun.test.ts +80 -0
  329. package/src/pipeline/firstRun.ts +99 -0
  330. package/src/pipeline/parseGlobalFlags.test.ts +102 -1
  331. package/src/pipeline/parseGlobalFlags.ts +98 -9
  332. package/src/pipeline/resolveCommandKind.test.ts +12 -0
  333. package/src/pipeline/resolveCommandKind.ts +4 -0
  334. package/src/pipeline/rootHelp.test.ts +87 -0
  335. package/src/pipeline/rootHelp.ts +265 -0
  336. package/src/pipeline/runCli.orchestration.test.ts +28 -12
  337. package/src/pipeline/runCli.test.ts +17 -4
  338. package/src/pipeline/runCli.ts +202 -25
  339. package/src/testing/cli.ts +30 -2
  340. package/src/testing/createTestMcpClient.ts +4 -1
  341. package/src/testing/dsFixtures.ts +21 -4
  342. package/src/testing/index.ts +7 -0
  343. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  344. package/src/testing/integration/cli-user-stories.test.ts +63 -0
  345. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  346. package/src/testing/integration/edge-cases.test.ts +62 -50
  347. package/src/testing/integration/executor-execution.test.ts +103 -0
  348. package/src/testing/integration/executor-golden.test.ts +145 -0
  349. package/src/testing/integration/mcp-surface.test.ts +40 -15
  350. package/src/testing/integration/parity.test.ts +136 -82
  351. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  352. package/src/testing/setupXdgIsolation.ts +26 -0
  353. package/src/testing/standardParityFixtures.ts +35 -0
  354. package/src/testing/store.ts +4 -4
  355. package/src/testing/storyFixtures.ts +63 -0
  356. package/src/domains/create/commands/component.test.ts +0 -166
  357. package/src/domains/create/commands/component.ts +0 -123
  358. package/src/domains/create/commands/index.ts +0 -3
  359. package/src/domains/create/commands/package.test.ts +0 -35
  360. package/src/domains/create/commands/package.ts +0 -21
  361. package/src/domains/create/generators.ts +0 -8
  362. package/src/domains/standard/commands/categories.test.ts +0 -70
  363. package/src/domains/standard/commands/categories.ts +0 -48
  364. package/src/domains/standard/commands/index.ts +0 -6
  365. package/src/domains/standard/commands/list.test.ts +0 -105
  366. package/src/domains/standard/commands/list.ts +0 -90
  367. package/src/domains/standard/commands/lookup.test.ts +0 -135
  368. package/src/domains/standard/commands/lookup.ts +0 -99
  369. package/src/domains/standard/commands/sample.ts +0 -57
  370. package/src/domains/standard/formatters/categories.test.ts +0 -44
  371. package/src/domains/standard/formatters/categories.ts +0 -38
  372. package/src/domains/standard/formatters/index.ts +0 -10
  373. package/src/domains/standard/formatters/list.test.ts +0 -169
  374. package/src/domains/standard/formatters/list.ts +0 -184
  375. package/src/domains/standard/formatters/lookup.test.ts +0 -95
  376. package/src/domains/standard/formatters/lookup.ts +0 -101
  377. package/src/domains/standard/formatters/sample.ts +0 -15
  378. package/src/domains/standard/formatters/types.ts +0 -31
  379. package/src/domains/standard/index.ts +0 -40
  380. package/src/domains/standard/mcp/index.ts +0 -2
  381. package/src/domains/standard/mcp/specs.ts +0 -244
  382. package/src/domains/standard/operations/categories.test.ts +0 -61
  383. package/src/domains/standard/operations/categories.ts +0 -38
  384. package/src/domains/standard/operations/index.ts +0 -6
  385. package/src/domains/standard/operations/list.test.ts +0 -75
  386. package/src/domains/standard/operations/list.ts +0 -64
  387. package/src/domains/standard/operations/lookup.test.ts +0 -58
  388. package/src/domains/standard/operations/lookup.ts +0 -151
  389. package/src/domains/standard/operations/sample.ts +0 -35
  390. package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
  391. package/src/domains/standard/orchestration/index.ts +0 -4
  392. package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
  393. package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -32
  394. package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
  395. package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
  396. package/src/domains/standard/standardConfig.ts +0 -42
  397. package/src/domains/standard/types.ts +0 -15
  398. package/src/domains/tier/commands/index.ts +0 -3
  399. package/src/domains/tier/commands/list.test.ts +0 -67
  400. package/src/domains/tier/commands/list.ts +0 -59
  401. package/src/domains/tier/formatters/index.ts +0 -3
  402. package/src/domains/tier/formatters/list.test.ts +0 -33
  403. package/src/domains/tier/formatters/list.ts +0 -45
  404. package/src/domains/tier/index.ts +0 -17
  405. package/src/domains/tier/mcp/index.ts +0 -2
  406. package/src/domains/tier/mcp/specs.ts +0 -38
  407. package/src/domains/tier/operations/index.ts +0 -3
  408. package/src/domains/tier/operations/list.test.ts +0 -32
  409. package/src/domains/tier/operations/list.ts +0 -33
  410. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -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
  });
@@ -1,18 +1,17 @@
1
- import { createInterface } from "node:readline";
2
1
  import {
2
+ answerPromptWithDefaults,
3
3
  type CommandResult,
4
4
  createExitResult,
5
5
  createOutputResult,
6
6
  formatEffectLine,
7
7
  isVisibleEffect,
8
+ runGeneratorTask,
8
9
  } from "@canonical/cli-core";
9
- import {
10
- dryRun,
11
- type Effect,
12
- runTask,
13
- runUndo,
14
- type Task,
15
- } from "@canonical/task";
10
+ import { dryRun, type Effect, type Task } from "@canonical/task";
11
+ import { runUndo } from "@canonical/task/node";
12
+ import createInteractivePromptSession, {
13
+ type InteractivePromptSession,
14
+ } from "../../shared/createInteractivePromptSession.js";
16
15
  import type { LogLevel } from "../types.js";
17
16
 
18
17
  /** Options controlling how {@link runSetupTask} executes. */
@@ -26,55 +25,41 @@ export interface SetupTaskOptions {
26
25
  }
27
26
 
28
27
  /**
29
- * Auto-confirm prompt handler for `--yes` mode.
30
- *
31
- * @param effect - A Prompt effect to resolve.
32
- * @returns The default value for the prompt type.
28
+ * Pick the prompt handler for a run: auto-confirm defaults under `--yes`, else
29
+ * an interactive readline session (whose lifetime the caller owns via the
30
+ * returned `dispose`).
33
31
  */
34
- function autoConfirmHandler(
35
- effect: Effect & { _tag: "Prompt" },
36
- ): Promise<unknown> {
37
- const q = effect.question;
38
- if (q.type === "confirm") return Promise.resolve(q.default ?? true);
39
- if (q.type === "select")
40
- return Promise.resolve(q.default ?? q.choices[0]?.value);
41
- if (q.type === "multiselect") return Promise.resolve(q.default ?? []);
42
- return Promise.resolve(q.default ?? "");
32
+ function selectPromptHandler(yes: boolean): {
33
+ handler: (effect: Effect & { _tag: "Prompt" }) => Promise<unknown>;
34
+ wasInterrupted: () => boolean;
35
+ dispose: () => void;
36
+ } {
37
+ if (yes) {
38
+ return {
39
+ handler: answerPromptWithDefaults,
40
+ wasInterrupted: () => false,
41
+ dispose: () => {},
42
+ };
43
+ }
44
+ const session: InteractivePromptSession = createInteractivePromptSession();
45
+ return {
46
+ handler: session.answerPrompt,
47
+ wasInterrupted: session.wasInterrupted,
48
+ dispose: session.dispose,
49
+ };
43
50
  }
44
51
 
45
52
  /**
46
- * Interactive prompt handler using readline on stdin/stderr.
47
- * Supports confirm prompts; other types fall back to defaults.
48
- *
49
- * @param effect - A Prompt effect to resolve interactively.
50
- * @returns The user's answer.
53
+ * Build a task log sink that writes to stderr, hiding debug lines unless
54
+ * verbose is set.
51
55
  */
52
- async function interactivePromptHandler(
53
- effect: Effect & { _tag: "Prompt" },
54
- ): Promise<unknown> {
55
- const q = effect.question;
56
-
57
- if (q.type === "confirm") {
58
- const defaultYes = q.default !== false;
59
- const hint = defaultYes ? "Y/n" : "y/N";
60
- const rl = createInterface({
61
- input: process.stdin,
62
- output: process.stderr,
63
- });
64
-
65
- const answer = await new Promise<string>((resolve) => {
66
- rl.question(`${q.message} [${hint}] `, (ans) => {
67
- rl.close();
68
- resolve(ans.trim());
69
- });
70
- });
71
-
72
- if (answer === "") return defaultYes;
73
- return answer.toLowerCase().startsWith("y");
74
- }
75
-
76
- // Non-confirm prompts fall back to defaults in D15
77
- return q.default;
56
+ function createStderrLogger(
57
+ verbose: boolean,
58
+ ): (level: LogLevel, message: string) => void {
59
+ return (level, message) => {
60
+ if (level === "debug" && !verbose) return;
61
+ process.stderr.write(`${message}\n`);
62
+ };
78
63
  }
79
64
 
80
65
  /**
@@ -150,7 +135,8 @@ function formatDryRunJson(effects: readonly Effect[]): string {
150
135
  * Supports three execution modes:
151
136
  * - `--dry-run`: collects effects without executing, formats for display.
152
137
  * - `--undo`: walks the task tree, collects undo operations, executes in reverse.
153
- * - default: interactive execution with readline-based prompts on stderr.
138
+ * - default: execution through {@link runGeneratorTask}, with readline-based
139
+ * prompts on stderr.
154
140
  *
155
141
  * @param task - The setup Task to execute.
156
142
  * @param options - Execution options (dryRun, undo, yes, verbose, llm, format).
@@ -182,14 +168,12 @@ export default async function runSetupTask(
182
168
 
183
169
  // Undo mode: walk the task tree, collect undos, execute in reverse
184
170
  if (options.undo) {
185
- const promptHandler = options.yes
186
- ? autoConfirmHandler
187
- : interactivePromptHandler;
188
-
189
- const onLog = (level: LogLevel, message: string): void => {
190
- if (level === "debug" && !verbose) return;
191
- process.stderr.write(`${message}\n`);
192
- };
171
+ const {
172
+ handler: promptHandler,
173
+ wasInterrupted,
174
+ dispose,
175
+ } = selectPromptHandler(options.yes ?? false);
176
+ const onLog = createStderrLogger(verbose);
193
177
 
194
178
  try {
195
179
  const result = await runUndo(task, { promptHandler, onLog });
@@ -202,28 +186,36 @@ export default async function runSetupTask(
202
186
  }
203
187
  return createExitResult(0);
204
188
  } catch (err) {
189
+ if (wasInterrupted()) {
190
+ return createExitResult(130);
191
+ }
205
192
  const message = err instanceof Error ? err.message : String(err);
206
193
  process.stderr.write(`Undo failed: ${message}\n`);
207
194
  return createExitResult(1);
195
+ } finally {
196
+ dispose();
208
197
  }
209
198
  }
210
199
 
211
200
  // Production execution
212
- const promptHandler = options.yes
213
- ? autoConfirmHandler
214
- : interactivePromptHandler;
215
-
216
- const onLog = (level: LogLevel, message: string): void => {
217
- if (level === "debug" && !verbose) return;
218
- process.stderr.write(`${message}\n`);
219
- };
201
+ const {
202
+ handler: promptHandler,
203
+ wasInterrupted,
204
+ dispose,
205
+ } = selectPromptHandler(options.yes ?? false);
206
+ const onLog = createStderrLogger(verbose);
220
207
 
221
208
  try {
222
- await runTask(task, { promptHandler, onLog });
209
+ await runGeneratorTask(task, { promptHandler, onLog });
223
210
  return createExitResult(0);
224
211
  } catch (err) {
212
+ if (wasInterrupted()) {
213
+ return createExitResult(130);
214
+ }
225
215
  const message = err instanceof Error ? err.message : String(err);
226
216
  process.stderr.write(`Setup failed: ${message}\n`);
227
217
  return createExitResult(1);
218
+ } finally {
219
+ dispose();
228
220
  }
229
221
  }
@@ -2,21 +2,25 @@
2
2
 
3
3
  import type { CommandDefinition } from "@canonical/cli-core";
4
4
  import {
5
- allCommand,
6
5
  completionsCommand,
7
6
  lspCommand,
8
7
  mcpCommand,
8
+ setupCommand,
9
9
  skillsCommand,
10
10
  } from "./commands/index.js";
11
11
 
12
12
  /**
13
13
  * Return all setup command definitions.
14
14
  *
15
- * @returns An array of CommandDefinitions for the setup subcommands.
15
+ * The top-level `pragma setup` is a summon-generator-backed command (replacing
16
+ * the former `setup all`); the granular `setup <step>` verbs remain for
17
+ * targeted, scriptable configuration of a single concern.
18
+ *
19
+ * @returns An array of CommandDefinitions for the setup command and its verbs.
16
20
  */
17
21
  export function commands(): CommandDefinition[] {
18
22
  return [
19
- allCommand,
23
+ setupCommand(),
20
24
  lspCommand,
21
25
  mcpCommand,
22
26
  completionsCommand,