@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
@@ -52,6 +52,30 @@ function parseJson<T>(result: CommandResult): T {
52
52
  }
53
53
 
54
54
  describe("CLI user stories", () => {
55
+ it("story: a first-time user is greeted once and gets a global config", () => {
56
+ const workspace = createWorkspace();
57
+ const freshXdg = mkdtempSync(join(tmpdir(), "pragma-firstrun-e2e-"));
58
+ tempDirs.add(freshXdg);
59
+ const env = { XDG_CONFIG_HOME: freshXdg };
60
+
61
+ // First invocation: greeting on stderr, config created, stdout untouched.
62
+ const first = runCommand(["info", "--format", "json"], workspace, env);
63
+ expect(first.exitCode).toBe(0);
64
+ expect(first.stderr).toContain("pre-release pragma CLI");
65
+ expect(first.stderr).toContain(join(freshXdg, "pragma", "config.json"));
66
+ expect(() => JSON.parse(first.stdout)).not.toThrow();
67
+ expect(
68
+ JSON.parse(
69
+ readFileSync(join(freshXdg, "pragma", "config.json"), "utf-8"),
70
+ ),
71
+ ).toEqual({});
72
+
73
+ // Second invocation: no greeting — first-run happens once.
74
+ const second = runCommand(["info", "--format", "json"], workspace, env);
75
+ expect(second.exitCode).toBe(0);
76
+ expect(second.stderr.trim()).toBe("");
77
+ }, 40_000);
78
+
55
79
  it("story: a new contributor verifies the local installation with info", () => {
56
80
  const workspace = createWorkspace();
57
81
 
@@ -195,4 +219,43 @@ describe("CLI user stories", () => {
195
219
  expect(existsSync(indexFile)).toBe(true);
196
220
  expect(readFileSync(indexFile, "utf-8")).toContain("./MySth");
197
221
  }, 20_000);
222
+
223
+ it("story: --version works at the root, noun, and verb levels", () => {
224
+ const workspace = createWorkspace();
225
+ const root = runCommand(["--version"], workspace);
226
+ const verb = runCommand(["block", "list", "--version"], workspace);
227
+
228
+ expectSuccess(root);
229
+ expectSuccess(verb);
230
+ expect(root.stdout.trim()).toBe(verb.stdout.trim());
231
+ expect(root.stdout.trim()).toMatch(/^\d+\.\d+\.\d+/);
232
+ }, 20_000);
233
+
234
+ it("story: an unknown verb under a known noun lists that noun's verbs", () => {
235
+ const workspace = createWorkspace();
236
+
237
+ const result = runCommand(["standard", "frobnicate"], workspace);
238
+
239
+ expect(result.exitCode).not.toBe(0);
240
+ // Errors, then defers to the category that exists.
241
+ expect(result.stderr).toContain("unknown command 'frobnicate'");
242
+ expect(result.stderr).toContain("pragma standard <verb>");
243
+ expect(result.stderr).toContain("list");
244
+ expect(result.stderr).toContain("lookup");
245
+ }, 20_000);
246
+
247
+ it("story: a global flag before the noun still lists that noun's verbs", () => {
248
+ const workspace = createWorkspace();
249
+
250
+ // `--format json` sits before the noun; the flag *value* (`json`) must not
251
+ // be mistaken for the noun, or no verbs would be suggested.
252
+ const result = runCommand(
253
+ ["--format", "json", "standard", "frobnicate"],
254
+ workspace,
255
+ );
256
+
257
+ expect(result.exitCode).not.toBe(0);
258
+ expect(result.stderr).toContain("pragma standard <verb>");
259
+ expect(result.stderr).toContain("lookup");
260
+ }, 20_000);
198
261
  });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The P.05 byte-equality gate: `pragma create X` and `summon X` must write
3
+ * identical bytes for the same generator invocation.
4
+ *
5
+ * Both binaries reduce to compositions over the shared execution core:
6
+ * pragma's batch path is `executeGenerator` (which stamps via
7
+ * `createStampOnEffectStart`), and summon's Ink executor runs
8
+ * `runGeneratorTask` with the same stamp transform and the defaults prompt
9
+ * handler. This test runs both compositions over the real generators into
10
+ * separate directories and asserts the trees are byte-identical — stamps
11
+ * included.
12
+ */
13
+
14
+ import {
15
+ mkdirSync,
16
+ mkdtempSync,
17
+ readdirSync,
18
+ readFileSync,
19
+ rmSync,
20
+ } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join, relative } from "node:path";
23
+ import {
24
+ answerPromptWithDefaults,
25
+ createGeneratorStamp,
26
+ createStampOnEffectStart,
27
+ executeGenerator,
28
+ runGeneratorTask,
29
+ } from "@canonical/cli-core";
30
+ import { generators as componentGenerators } from "@canonical/summon-component";
31
+ import type { GeneratorDefinition } from "@canonical/summon-core";
32
+ import { generators as packageGenerators } from "@canonical/summon-package";
33
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
34
+
35
+ interface Case {
36
+ readonly name: string;
37
+ readonly gen: GeneratorDefinition;
38
+ readonly answers: Record<string, unknown>;
39
+ }
40
+
41
+ const cases: Case[] = [
42
+ {
43
+ name: "component/react",
44
+ gen: componentGenerators["component/react"] as GeneratorDefinition,
45
+ answers: { componentPath: "src/components/Button" },
46
+ },
47
+ {
48
+ name: "package/library",
49
+ gen: packageGenerators.package as GeneratorDefinition,
50
+ answers: { name: "@canonical/example", type: "library" },
51
+ },
52
+ ];
53
+
54
+ /** Collect every file under root as relative path → raw content bytes. */
55
+ const readTree = (root: string, dir = root): Map<string, Buffer> => {
56
+ const files = new Map<string, Buffer>();
57
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
58
+ const full = join(dir, entry.name);
59
+ if (entry.isDirectory()) {
60
+ for (const [k, v] of readTree(root, full)) files.set(k, v);
61
+ } else {
62
+ files.set(relative(root, full), readFileSync(full));
63
+ }
64
+ }
65
+ return files;
66
+ };
67
+
68
+ describe("cross-binary byte equality — pragma create ≡ summon", () => {
69
+ let dir: string;
70
+ let originalCwd: string;
71
+
72
+ beforeEach(() => {
73
+ originalCwd = process.cwd();
74
+ dir = mkdtempSync(join(tmpdir(), "byte-equality-"));
75
+ });
76
+
77
+ afterEach(() => {
78
+ process.chdir(originalCwd);
79
+ rmSync(dir, { recursive: true, force: true });
80
+ });
81
+
82
+ for (const testCase of cases) {
83
+ it(`${testCase.name} — both binaries write identical bytes, stamps included`, async () => {
84
+ const pragmaDir = join(dir, "pragma");
85
+ const summonDir = join(dir, "summon");
86
+ mkdirSync(pragmaDir);
87
+ mkdirSync(summonDir);
88
+
89
+ // pragma path: the create command's batch execution (stamps by default).
90
+ const result = await executeGenerator(
91
+ testCase.gen,
92
+ { ...testCase.answers, yes: true },
93
+ {
94
+ cwd: pragmaDir,
95
+ globalFlags: { llm: false, format: "text", verbose: false },
96
+ },
97
+ );
98
+ expect(result.tag).toBe("output");
99
+
100
+ // summon path: the Ink executor's exact composition over the shared
101
+ // core. Both binaries fold prompt defaults into the answers before
102
+ // generating; do the same here.
103
+ const answers = { ...testCase.answers };
104
+ for (const prompt of testCase.gen.prompts) {
105
+ if (!(prompt.name in answers) && prompt.default !== undefined) {
106
+ answers[prompt.name] = prompt.default;
107
+ }
108
+ }
109
+ await runGeneratorTask(testCase.gen.generate(answers), {
110
+ cwd: summonDir,
111
+ promptHandler: answerPromptWithDefaults,
112
+ onEffectStart: createStampOnEffectStart(
113
+ createGeneratorStamp(testCase.gen),
114
+ ),
115
+ });
116
+
117
+ const pragmaTree = readTree(pragmaDir);
118
+ const summonTree = readTree(summonDir);
119
+
120
+ expect([...pragmaTree.keys()].sort()).toEqual(
121
+ [...summonTree.keys()].sort(),
122
+ );
123
+ for (const [path, content] of pragmaTree) {
124
+ expect(summonTree.get(path)?.equals(content), path).toBe(true);
125
+ }
126
+ expect(pragmaTree.size).toBeGreaterThan(0);
127
+ });
128
+ }
129
+ });
@@ -10,6 +10,7 @@
10
10
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
11
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
12
12
  import type { PragmaRuntime } from "../../domains/shared/runtime.js";
13
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../domains/token/featureFlag.js";
13
14
  import createTestMcpClient from "../helpers/createTestMcpClient.js";
14
15
  import createTestRuntime from "../helpers/createTestRuntime.js";
15
16
 
@@ -122,21 +123,24 @@ describe("all-fail batch lookups", () => {
122
123
  expect(data.errors[0]?.code).toBe("ENTITY_NOT_FOUND");
123
124
  });
124
125
 
125
- it("token_lookup: unknown token → error", async () => {
126
- const res = await client.callTool({
127
- name: "token_lookup",
128
- arguments: { names: ["color.nonexistent"] },
129
- });
130
- const body = parseEnvelope(res);
131
- expect(body.ok).toBe(true);
132
- const data = body.data as {
133
- results: unknown[];
134
- errors: { query: string; code: string }[];
135
- };
136
- expect(data.results).toHaveLength(0);
137
- expect(data.errors).toHaveLength(1);
138
- expect(data.errors[0]?.code).toBe("ENTITY_NOT_FOUND");
139
- });
126
+ it.skipIf(!TOKEN_READ_SURFACE_ENABLED)(
127
+ "token_lookup: unknown token → error",
128
+ async () => {
129
+ const res = await client.callTool({
130
+ name: "token_lookup",
131
+ arguments: { names: ["color.nonexistent"] },
132
+ });
133
+ const body = parseEnvelope(res);
134
+ expect(body.ok).toBe(true);
135
+ const data = body.data as {
136
+ results: unknown[];
137
+ errors: { query: string; code: string }[];
138
+ };
139
+ expect(data.results).toHaveLength(0);
140
+ expect(data.errors).toHaveLength(1);
141
+ expect(data.errors[0]?.code).toBe("ENTITY_NOT_FOUND");
142
+ },
143
+ );
140
144
  });
141
145
 
142
146
  // ---------------------------------------------------------------------------
@@ -144,33 +148,38 @@ describe("all-fail batch lookups", () => {
144
148
  // ---------------------------------------------------------------------------
145
149
 
146
150
  describe("empty result sets", () => {
147
- it("standard_list with nonexistent category → error envelope", async () => {
151
+ it("standard_list with nonexistent category → empty rows", async () => {
148
152
  const res = await client.callTool({
149
153
  name: "standard_list",
150
154
  arguments: { category: "nonexistent_category" },
151
155
  });
152
156
  const body = parseEnvelope(res);
153
- // resolveStandardList throws when no results found
154
- expect(body.ok).toBe(false);
155
- const error = body.error as { code: string };
156
- expect(error.code).toBeDefined();
157
+ // The bundled pack's value-free category filter returns zero rows for
158
+ // an unmatched value; the old EMPTY_RESULTS error is a pinned
159
+ // PARITY_GAP of the standard cutover (packs have no emptyError hook).
160
+ expect(body.ok).toBe(true);
161
+ expect(body.data).toEqual([]);
162
+ expect((body.meta as { count: number }).count).toBe(0);
157
163
  });
158
164
 
159
- it("token_list with nonexistent category → empty or error", async () => {
160
- const res = await client.callTool({
161
- name: "token_list",
162
- arguments: { category: "NonexistentType" },
163
- });
164
- const body = parseEnvelope(res);
165
- if (body.ok) {
166
- const data = body.data as unknown[];
167
- expect(data).toHaveLength(0);
168
- } else {
169
- // Token list may throw on empty results
170
- const error = body.error as { code: string };
171
- expect(error.code).toBeDefined();
172
- }
173
- });
165
+ it.skipIf(!TOKEN_READ_SURFACE_ENABLED)(
166
+ "token_list with nonexistent category → empty or error",
167
+ async () => {
168
+ const res = await client.callTool({
169
+ name: "token_list",
170
+ arguments: { category: "NonexistentType" },
171
+ });
172
+ const body = parseEnvelope(res);
173
+ if (body.ok) {
174
+ const data = body.data as unknown[];
175
+ expect(data).toHaveLength(0);
176
+ } else {
177
+ // Token list may throw on empty results
178
+ const error = body.error as { code: string };
179
+ expect(error.code).toBeDefined();
180
+ }
181
+ },
182
+ );
174
183
  });
175
184
 
176
185
  // ---------------------------------------------------------------------------
@@ -245,19 +254,22 @@ describe("condensed mode error rendering", () => {
245
254
  expect(text).toContain("nonexistent");
246
255
  });
247
256
 
248
- it("token_lookup condensed: includes error for unknown token", async () => {
249
- const res = await client.callTool({
250
- name: "token_lookup",
251
- arguments: {
252
- names: ["color.primary", "color.nonexistent"],
253
- condensed: true,
254
- },
255
- });
256
- const body = parseEnvelope(res);
257
- expect(body.condensed).toBe(true);
258
- const text = body.text as string;
259
- expect(text).toContain("color.primary");
260
- expect(text).toContain("Errors");
261
- expect(text).toContain("color.nonexistent");
262
- });
257
+ it.skipIf(!TOKEN_READ_SURFACE_ENABLED)(
258
+ "token_lookup condensed: includes error for unknown token",
259
+ async () => {
260
+ const res = await client.callTool({
261
+ name: "token_lookup",
262
+ arguments: {
263
+ names: ["color.primary", "color.nonexistent"],
264
+ condensed: true,
265
+ },
266
+ });
267
+ const body = parseEnvelope(res);
268
+ expect(body.condensed).toBe(true);
269
+ const text = body.text as string;
270
+ expect(text).toContain("color.primary");
271
+ expect(text).toContain("Errors");
272
+ expect(text).toContain("color.nonexistent");
273
+ },
274
+ );
263
275
  });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * End-to-end characterization of the real-execution path the executor merge
3
+ * (4a) rewired: every generator the CLI actually dispatches, and a real setup
4
+ * task, run through the shared UI-free core ({@link runGeneratorTask}) and
5
+ * produce their files on disk.
6
+ *
7
+ * The golden snapshots (executor-golden.test.ts) lock the *preview* modes
8
+ * (dry-run/json/llm), which never execute. This locks the *real* run: the
9
+ * merged core executes the genuine task trees — not test doubles — with the
10
+ * correct working-directory semantics.
11
+ */
12
+
13
+ import {
14
+ existsSync,
15
+ mkdirSync,
16
+ mkdtempSync,
17
+ readdirSync,
18
+ rmSync,
19
+ } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { runGeneratorTask } from "@canonical/cli-core";
23
+ import { generators as componentGenerators } from "@canonical/summon-component";
24
+ import { generators as packageGenerators } from "@canonical/summon-package";
25
+ import type { Task } from "@canonical/task";
26
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
27
+ import setupMcp from "../../domains/setup/operations/setupMcp.js";
28
+
29
+ interface Case {
30
+ readonly name: string;
31
+ readonly build: () => Task<unknown>;
32
+ }
33
+
34
+ const componentParams = { componentPath: "src/components/Button" };
35
+ const packageParams = { name: "@canonical/example", type: "library" };
36
+
37
+ const cases: Case[] = [
38
+ {
39
+ name: "component/react",
40
+ build: () =>
41
+ componentGenerators["component/react"].generate(componentParams),
42
+ },
43
+ {
44
+ name: "component/svelte",
45
+ build: () =>
46
+ componentGenerators["component/svelte"].generate(componentParams),
47
+ },
48
+ {
49
+ name: "component/lit",
50
+ build: () => componentGenerators["component/lit"].generate(componentParams),
51
+ },
52
+ {
53
+ name: "package/library",
54
+ build: () => packageGenerators.package.generate(packageParams),
55
+ },
56
+ ];
57
+
58
+ const countFiles = (root: string): number => {
59
+ let total = 0;
60
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
61
+ total += entry.isDirectory() ? countFiles(join(root, entry.name)) : 1;
62
+ }
63
+ return total;
64
+ };
65
+
66
+ describe("shared executor — real execution over the dispatched tasks", () => {
67
+ let dir: string;
68
+ let originalCwd: string;
69
+
70
+ beforeEach(() => {
71
+ originalCwd = process.cwd();
72
+ dir = mkdtempSync(join(tmpdir(), "executor-execution-"));
73
+ });
74
+
75
+ afterEach(() => {
76
+ process.chdir(originalCwd);
77
+ rmSync(dir, { recursive: true, force: true });
78
+ });
79
+
80
+ for (const testCase of cases) {
81
+ it(`${testCase.name} — executes for real in the target cwd`, async () => {
82
+ const targetDir = join(dir, "target");
83
+ mkdirSync(targetDir);
84
+
85
+ await runGeneratorTask(testCase.build(), { cwd: targetDir });
86
+
87
+ expect(countFiles(targetDir)).toBeGreaterThan(0);
88
+ expect(process.cwd()).toBe(originalCwd);
89
+ });
90
+ }
91
+
92
+ // A real setup task on the rewired production surface. setupMcp in force
93
+ // mode writes root-relative config (no prompts, no home-dir writes), so it
94
+ // is safe to execute in a sandbox.
95
+ it("setup/mcp (force) — writes the harness config for real", async () => {
96
+ const root = join(dir, "project");
97
+ mkdirSync(root);
98
+
99
+ await runGeneratorTask(setupMcp(root, "cursor"));
100
+
101
+ expect(existsSync(join(root, ".cursor", "mcp.json"))).toBe(true);
102
+ });
103
+ });
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Characterization (golden) snapshots of the two current executors —
3
+ * `executeGenerator` (cli-core, drives `pragma create`) and `runSetupTask`
4
+ * (drives `pragma setup`) — across the real generators and setup tasks.
5
+ *
6
+ * These lock the *observable output* of each executor for representative,
7
+ * fully-answered inputs in every non-interactive mode. Both executors now run
8
+ * real execution through the shared UI-free core (`runGeneratorTask`), and the
9
+ * merged path must keep reproducing these byte-for-byte; the execution
10
+ * characterization test alongside asserts the real-run path. Interactive
11
+ * behavior is characterized separately, since it runs through the injected
12
+ * `promptHandler` seam.
13
+ */
14
+
15
+ import { dirname, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import type { CommandContext, CommandResult } from "@canonical/cli-core";
18
+ import { executeGenerator } from "@canonical/cli-core";
19
+ import { generators as componentGenerators } from "@canonical/summon-component";
20
+ import { generators as packageGenerators } from "@canonical/summon-package";
21
+ import { describe, expect, it } from "vitest";
22
+ import runSetupTask from "../../domains/setup/helpers/runSetupTask.js";
23
+ import setupCompletions from "../../domains/setup/operations/setupCompletions.js";
24
+ import setupLsp from "../../domains/setup/operations/setupLsp.js";
25
+
26
+ const ROOT = "/tmp/characterization-project";
27
+
28
+ const ctx = (format: "text" | "json", llm = false): CommandContext => ({
29
+ cwd: ROOT,
30
+ globalFlags: { llm, format, verbose: false },
31
+ });
32
+
33
+ /**
34
+ * Render an executor result to the plain string a user would see, with
35
+ * environment-specific bytes normalised so a committed baseline compares
36
+ * equal on every machine and in CI: ANSI color codes are stripped (chalk
37
+ * enables them under nx's FORCE_COLOR in CI but not in a plain local run),
38
+ * then the repo checkout root (package-generator plans embed absolute
39
+ * template source paths) and the home directory (shell-completion install
40
+ * targets embed `$HOME`) become stable tokens. The repo root is replaced
41
+ * first — on a dev machine it usually lives under `$HOME`.
42
+ */
43
+ const plain = (result: CommandResult): string => {
44
+ let text =
45
+ result.tag === "output"
46
+ ? result.render.plain(result.value)
47
+ : `[${result.tag}]`;
48
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching the ANSI escape byte is the point
49
+ text = text.replace(/\u001b\[[0-9;]*m/g, "");
50
+ const repoRoot = resolve(
51
+ dirname(fileURLToPath(import.meta.url)),
52
+ "../../../../../..",
53
+ );
54
+ text = text.split(repoRoot).join("<repo>");
55
+ const home = process.env.HOME ?? "";
56
+ return home === "" ? text : text.split(home).join("<home>");
57
+ };
58
+
59
+ // =============================================================================
60
+ // create — executeGenerator across the real component + package generators
61
+ // =============================================================================
62
+
63
+ const componentParams = { componentPath: "src/components/Button" };
64
+ const packageParams = { name: "@canonical/example", type: "library" };
65
+
66
+ describe("golden: executeGenerator — components", () => {
67
+ for (const framework of ["react", "svelte", "lit"] as const) {
68
+ const gen = componentGenerators[`component/${framework}`];
69
+
70
+ it(`${framework} — dry-run plan`, async () => {
71
+ const result = await executeGenerator(
72
+ gen,
73
+ { ...componentParams, dryRun: true },
74
+ ctx("text"),
75
+ );
76
+ expect(plain(result)).toMatchSnapshot();
77
+ });
78
+
79
+ it(`${framework} — json plan`, async () => {
80
+ const result = await executeGenerator(
81
+ gen,
82
+ { ...componentParams },
83
+ ctx("json"),
84
+ );
85
+ expect(plain(result)).toMatchSnapshot();
86
+ });
87
+
88
+ it(`${framework} — llm plan`, async () => {
89
+ const result = await executeGenerator(
90
+ gen,
91
+ { ...componentParams },
92
+ ctx("text", true),
93
+ );
94
+ expect(plain(result)).toMatchSnapshot();
95
+ });
96
+ }
97
+ });
98
+
99
+ describe("golden: executeGenerator — package", () => {
100
+ const gen = packageGenerators.package;
101
+
102
+ it("library — dry-run plan", async () => {
103
+ const result = await executeGenerator(
104
+ gen,
105
+ { ...packageParams, dryRun: true },
106
+ ctx("text"),
107
+ );
108
+ expect(plain(result)).toMatchSnapshot();
109
+ });
110
+
111
+ it("library — json plan", async () => {
112
+ const result = await executeGenerator(
113
+ gen,
114
+ { ...packageParams },
115
+ ctx("json"),
116
+ );
117
+ expect(plain(result)).toMatchSnapshot();
118
+ });
119
+ });
120
+
121
+ // =============================================================================
122
+ // setup — runSetupTask across representative deterministic setup tasks
123
+ // =============================================================================
124
+
125
+ describe("golden: runSetupTask — setups", () => {
126
+ it("lsp — dry-run", async () => {
127
+ const result = await runSetupTask(setupLsp(ROOT), { dryRun: true });
128
+ expect(plain(result)).toMatchSnapshot();
129
+ });
130
+
131
+ it("completions (bash) — dry-run", async () => {
132
+ const result = await runSetupTask(setupCompletions("bash"), {
133
+ dryRun: true,
134
+ });
135
+ expect(plain(result)).toMatchSnapshot();
136
+ });
137
+
138
+ it("lsp — dry-run json", async () => {
139
+ const result = await runSetupTask(setupLsp(ROOT), {
140
+ dryRun: true,
141
+ format: "json",
142
+ });
143
+ expect(plain(result)).toMatchSnapshot();
144
+ });
145
+ });