@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
@@ -1,15 +1,21 @@
1
- /** @module Create domain -- generator commands for `pragma create` (component and package). */
1
+ /**
2
+ * Create domain — generator-pack-driven `create <noun>` commands.
3
+ *
4
+ * The create surface is compiled from bundled generator packs (a package's
5
+ * `generators` export), not hardcoded per generator. Every generator each
6
+ * bundled package ships is exposed as a `create <noun>` command and a
7
+ * `create_<noun>` MCP tool. See {@link ./generatorPack/compileGeneratorPack}.
8
+ */
2
9
 
3
10
  import type { CommandDefinition } from "@canonical/cli-core";
4
- import { componentCommand, packageCommand } from "./commands/index.js";
11
+ import bundledGeneratorPacks from "./generatorPack/bundled.js";
5
12
 
6
13
  /**
7
- * Return all create command definitions.
8
- *
9
- * @returns An array of CommandDefinitions for component and package generators.
14
+ * Return all create command definitions, compiled from the bundled
15
+ * generator packs.
10
16
  */
11
- export function commands(): CommandDefinition[] {
12
- return [componentCommand(), packageCommand()];
17
+ export function commands(): readonly CommandDefinition[] {
18
+ return bundledGeneratorPacks().commands;
13
19
  }
14
20
 
15
21
  export { specs as mcpSpecs } from "./mcp/index.js";
@@ -1,168 +1,13 @@
1
1
  /**
2
- * MCP tool specs for create domain — create_component, create_package.
2
+ * MCP tool specs for the create domain.
3
+ *
4
+ * Compiled from the bundled generator packs — one `create_<noun>` tool per
5
+ * generator noun — rather than hand-written per generator.
3
6
  */
4
7
 
5
- import { executeGenerator } from "@canonical/cli-core";
6
- import type { AnyGenerator } from "@canonical/summon-core";
7
- import { generators as packageGenerators } from "@canonical/summon-package";
8
- import { PragmaError } from "#error";
9
8
  import type { ToolSpec } from "../../shared/ToolSpec.js";
10
- import { COMPONENT_GENERATORS } from "../generators.js";
9
+ import bundledGeneratorPacks from "../generatorPack/bundled.js";
11
10
 
12
- const specs: readonly ToolSpec[] = [
13
- {
14
- name: "create_component",
15
- description:
16
- "Generate a design system component with TypeScript, tests, stories, and styles. Returns a JSON generation plan (dry-run).",
17
- params: {
18
- framework: {
19
- type: "string",
20
- description: "Component framework",
21
- optional: false,
22
- enum: ["react", "svelte", "lit"],
23
- },
24
- componentPath: {
25
- type: "string",
26
- description:
27
- "Path for the component directory (e.g. 'src/components/Button')",
28
- optional: false,
29
- },
30
- withStyles: {
31
- type: "boolean",
32
- description: "Include CSS styles (default: true)",
33
- optional: true,
34
- },
35
- withStories: {
36
- type: "boolean",
37
- description: "Include Storybook stories (default: true)",
38
- optional: true,
39
- },
40
- withSsrTests: {
41
- type: "boolean",
42
- description: "Include SSR tests (default: true)",
43
- optional: true,
44
- },
45
- },
46
- readOnly: false,
47
- destructive: false,
48
- async execute(rt, params) {
49
- const batchCtx = {
50
- cwd: rt.cwd,
51
- globalFlags: {
52
- llm: false,
53
- format: "json" as const,
54
- verbose: false,
55
- },
56
- };
57
-
58
- const framework = params.framework as string;
59
- const gen = COMPONENT_GENERATORS[framework];
60
- if (!gen) {
61
- throw PragmaError.invalidInput("framework", framework, {
62
- validOptions: Object.keys(COMPONENT_GENERATORS),
63
- });
64
- }
65
-
66
- const genParams: Record<string, unknown> = {
67
- componentPath: params.componentPath,
68
- ...(params.withStyles !== undefined && {
69
- withStyles: params.withStyles,
70
- }),
71
- ...(params.withStories !== undefined && {
72
- withStories: params.withStories,
73
- }),
74
- ...(params.withSsrTests !== undefined && {
75
- withSsrTests: params.withSsrTests,
76
- }),
77
- };
78
-
79
- const result = await executeGenerator(gen, genParams, batchCtx);
80
- if (result.tag === "output") {
81
- const text = result.render.plain(result.value);
82
- return { data: JSON.parse(text) };
83
- }
84
-
85
- throw PragmaError.invalidInput(
86
- "componentPath",
87
- String(params.componentPath),
88
- );
89
- },
90
- },
91
- {
92
- name: "create_package",
93
- description:
94
- "Generate a new npm package with proper configuration. Returns a JSON generation plan (dry-run).",
95
- params: {
96
- name: {
97
- type: "string",
98
- description: "Full package name (e.g. '@canonical/my-package')",
99
- optional: false,
100
- },
101
- type: {
102
- type: "string",
103
- description: "Package type",
104
- optional: false,
105
- enum: ["tool-ts", "library", "css"],
106
- },
107
- description: {
108
- type: "string",
109
- description: "Package description",
110
- optional: true,
111
- },
112
- withReact: {
113
- type: "boolean",
114
- description: "Add React dependencies (default: false)",
115
- optional: true,
116
- },
117
- withStorybook: {
118
- type: "boolean",
119
- description: "Add Storybook config (default: false)",
120
- optional: true,
121
- },
122
- withCli: {
123
- type: "boolean",
124
- description: "Add CLI entry point (default: false)",
125
- optional: true,
126
- },
127
- },
128
- readOnly: false,
129
- destructive: false,
130
- async execute(rt, params) {
131
- const batchCtx = {
132
- cwd: rt.cwd,
133
- globalFlags: {
134
- llm: false,
135
- format: "json" as const,
136
- verbose: false,
137
- },
138
- };
139
-
140
- const gen = packageGenerators.package as AnyGenerator | undefined;
141
- if (!gen) throw PragmaError.internalError("Package generator not found");
142
-
143
- const genParams: Record<string, unknown> = {
144
- name: params.name,
145
- type: params.type,
146
- ...(params.description !== undefined && {
147
- description: params.description,
148
- }),
149
- ...(params.withReact !== undefined && { withReact: params.withReact }),
150
- ...(params.withStorybook !== undefined && {
151
- withStorybook: params.withStorybook,
152
- }),
153
- ...(params.withCli !== undefined && { withCli: params.withCli }),
154
- runInstall: false,
155
- };
156
-
157
- const result = await executeGenerator(gen, genParams, batchCtx);
158
- if (result.tag === "output") {
159
- const text = result.render.plain(result.value);
160
- return { data: JSON.parse(text) };
161
- }
162
-
163
- throw PragmaError.invalidInput("name", String(params.name));
164
- },
165
- },
166
- ];
11
+ const specs: readonly ToolSpec[] = bundledGeneratorPacks().specs;
167
12
 
168
13
  export default specs;
@@ -0,0 +1,103 @@
1
+ import type { CommandContext } from "@canonical/cli-core";
2
+ import type { GeneratorDefinition } from "@canonical/summon-core";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+
5
+ const executeGeneratorMock = vi.fn(async () => ({
6
+ tag: "output" as const,
7
+ value: "",
8
+ render: { plain: () => "" },
9
+ }));
10
+ const renderAppMock = vi.fn(async () => {});
11
+
12
+ vi.mock("@canonical/cli-core", async (importOriginal) => {
13
+ const actual = await importOriginal<typeof import("@canonical/cli-core")>();
14
+ return {
15
+ ...actual,
16
+ executeGenerator: executeGeneratorMock,
17
+ createGeneratorStamp: () => ({ generatorName: "g", version: "1" }),
18
+ };
19
+ });
20
+
21
+ vi.mock("@canonical/summon/ui", () => ({ renderApp: renderAppMock }));
22
+
23
+ const gen = {
24
+ meta: {},
25
+ prompts: [],
26
+ generate: () => ({}),
27
+ } as unknown as GeneratorDefinition;
28
+
29
+ function makeCtx(overrides: Partial<CommandContext["globalFlags"]> = {}) {
30
+ return {
31
+ cwd: "/tmp",
32
+ globalFlags: { llm: false, format: "text", verbose: false, ...overrides },
33
+ } as CommandContext;
34
+ }
35
+
36
+ async function withTty<T>(
37
+ stdin: boolean,
38
+ stdout: boolean,
39
+ run: () => Promise<T>,
40
+ ): Promise<T> {
41
+ const inPrev = process.stdin.isTTY;
42
+ const outPrev = process.stdout.isTTY;
43
+ Object.defineProperty(process.stdin, "isTTY", {
44
+ value: stdin,
45
+ configurable: true,
46
+ });
47
+ Object.defineProperty(process.stdout, "isTTY", {
48
+ value: stdout,
49
+ configurable: true,
50
+ });
51
+ try {
52
+ return await run();
53
+ } finally {
54
+ Object.defineProperty(process.stdin, "isTTY", {
55
+ value: inPrev,
56
+ configurable: true,
57
+ });
58
+ Object.defineProperty(process.stdout, "isTTY", {
59
+ value: outPrev,
60
+ configurable: true,
61
+ });
62
+ }
63
+ }
64
+
65
+ describe("renderGeneratorUi", () => {
66
+ afterEach(() => vi.clearAllMocks());
67
+
68
+ it("renders summon's Ink UI on an interactive terminal", async () => {
69
+ const { default: renderGeneratorUi } = await import(
70
+ "./renderGeneratorUi.js"
71
+ );
72
+ const result = await withTty(true, true, () =>
73
+ renderGeneratorUi(gen, {}, makeCtx()),
74
+ );
75
+ expect(renderAppMock).toHaveBeenCalledTimes(1);
76
+ expect(executeGeneratorMock).not.toHaveBeenCalled();
77
+ expect(result).toEqual({ tag: "exit", code: 0 });
78
+ });
79
+
80
+ it("falls back to executeGenerator on a non-interactive terminal", async () => {
81
+ const { default: renderGeneratorUi } = await import(
82
+ "./renderGeneratorUi.js"
83
+ );
84
+ await withTty(false, false, () => renderGeneratorUi(gen, {}, makeCtx()));
85
+ expect(executeGeneratorMock).toHaveBeenCalledTimes(1);
86
+ expect(renderAppMock).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it.each([
90
+ ["--yes", { yes: true }],
91
+ ["--dry-run", { dryRun: true }],
92
+ ["--undo", { undo: true }],
93
+ ["--llm", { llm: true }],
94
+ ["--format json", { format: "json" }],
95
+ ])("falls back to executeGenerator for %s even on a TTY", async (_label, params) => {
96
+ const { default: renderGeneratorUi } = await import(
97
+ "./renderGeneratorUi.js"
98
+ );
99
+ await withTty(true, true, () => renderGeneratorUi(gen, params, makeCtx()));
100
+ expect(executeGeneratorMock).toHaveBeenCalledTimes(1);
101
+ expect(renderAppMock).not.toHaveBeenCalled();
102
+ });
103
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Route a generator run to the right front-end.
3
+ *
4
+ * On an interactive terminal (no `--yes`, not a machine-output or preview-only
5
+ * mode), render summon's Ink `App` — the same colored prompt sequence,
6
+ * operations recap, and "Proceed?" confirmation the `summon` binary shows — so
7
+ * `pragma create`/`setup` match summon exactly without duplicating the UI. In
8
+ * every other case (piped/CI, `--yes`, `--llm`/`--format json`, `--dry-run`,
9
+ * `--undo`) fall back to the shared UI-free {@link executeGenerator}, which owns
10
+ * the batch, preview, and machine-readable paths.
11
+ */
12
+
13
+ import {
14
+ type CommandContext,
15
+ type CommandResult,
16
+ createExitResult,
17
+ createGeneratorStamp,
18
+ executeGenerator,
19
+ } from "@canonical/cli-core";
20
+ import type { GeneratorDefinition } from "@canonical/summon-core";
21
+
22
+ /** Whether both stdin and stdout are attached to an interactive terminal. */
23
+ function isInteractiveTerminal(): boolean {
24
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
25
+ }
26
+
27
+ /**
28
+ * Run a generator, preferring summon's rich Ink UI when interactive.
29
+ *
30
+ * @param gen - The generator to run.
31
+ * @param params - Parsed CLI params (flags + positional answers).
32
+ * @param ctx - The command context (global flags, cwd).
33
+ * @returns A CommandResult; the Ink path exits 0 after the UI closes.
34
+ * @note Impure — may take over the terminal via Ink and perform the run.
35
+ */
36
+ export default async function renderGeneratorUi(
37
+ gen: GeneratorDefinition,
38
+ params: Record<string, unknown>,
39
+ ctx: CommandContext,
40
+ ): Promise<CommandResult> {
41
+ const isLlm = params.llm === true || ctx.globalFlags.llm;
42
+ const isJson = params.format === "json" || ctx.globalFlags.format === "json";
43
+ const wantsBatch =
44
+ params.yes === true ||
45
+ params.dryRun === true ||
46
+ params.undo === true ||
47
+ isLlm ||
48
+ isJson;
49
+
50
+ // Machine/preview/batch modes and non-interactive terminals stay on the
51
+ // UI-free executor.
52
+ if (wantsBatch || !isInteractiveTerminal()) {
53
+ return executeGenerator(gen, params, ctx);
54
+ }
55
+
56
+ const { renderApp } = await import("@canonical/summon/ui");
57
+ const stampEnabled = params.generatedStamp !== false;
58
+ await renderApp({
59
+ generator: gen,
60
+ preview: params.preview !== false,
61
+ verbose: ctx.globalFlags.verbose === true,
62
+ stamp: stampEnabled ? createGeneratorStamp(gen) : undefined,
63
+ });
64
+ return createExitResult(0);
65
+ }
@@ -48,6 +48,26 @@ const mixed: DoctorData = {
48
48
  skipped: 1,
49
49
  };
50
50
 
51
+ const withItems: DoctorData = {
52
+ checks: [
53
+ {
54
+ name: "package refs",
55
+ status: "pass",
56
+ detail: "1 package resolved",
57
+ items: [
58
+ {
59
+ label: "@canonical/design-system",
60
+ status: "pass",
61
+ detail: "git v0.1.2 · 362 graphs, 3 skills",
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ passed: 1,
67
+ failed: 0,
68
+ skipped: 0,
69
+ };
70
+
51
71
  describe("doctor formatters", () => {
52
72
  describe("plain", () => {
53
73
  it("renders passing checks with ✓", () => {
@@ -74,6 +94,29 @@ describe("doctor formatters", () => {
74
94
  const output = formatters.plain(allPass);
75
95
  expect(output).not.toContain("Run ");
76
96
  });
97
+
98
+ it("renders a summary tally", () => {
99
+ const output = formatters.plain(mixed);
100
+ expect(output).toContain("1 passed");
101
+ expect(output).toContain("2 failed");
102
+ expect(output).toContain("1 skipped");
103
+ });
104
+
105
+ it("renders sub-items indented under a check", () => {
106
+ const output = formatters.plain(withItems);
107
+ expect(output).toContain("@canonical/design-system");
108
+ expect(output).toContain("git v0.1.2 · 362 graphs, 3 skills");
109
+ // sub-item lines are indented beneath the check headline
110
+ const lines = output.split("\n");
111
+ const sub = lines.find((l) => l.includes("@canonical/design-system"));
112
+ expect(sub?.startsWith(" ")).toBe(true);
113
+ });
114
+
115
+ it("renders the remedy inline with a fix label", () => {
116
+ const output = formatters.plain(mixed);
117
+ expect(output).toContain("fix:");
118
+ expect(output).toContain("pragma config tier <tier>");
119
+ });
77
120
  });
78
121
 
79
122
  describe("llm", () => {
@@ -87,12 +130,17 @@ describe("doctor formatters", () => {
87
130
  expect(output).toContain("- ✓ **Node version**: v24.1.0");
88
131
  });
89
132
 
90
- it("includes remedial section for failures", () => {
133
+ it("includes inline fixes for failures", () => {
91
134
  const output = formatters.llm(mixed);
92
- expect(output).toContain("### Remedial");
135
+ expect(output).toContain("_fix:_");
93
136
  expect(output).toContain("`pragma config tier <tier>`");
94
137
  expect(output).toContain("`pragma setup completions`");
95
138
  });
139
+
140
+ it("renders sub-items as nested bullets", () => {
141
+ const output = formatters.llm(withItems);
142
+ expect(output).toContain(" - ✓ @canonical/design-system");
143
+ });
96
144
  });
97
145
 
98
146
  describe("json", () => {
@@ -1,78 +1,130 @@
1
1
  import chalk from "chalk";
2
2
  import type { Formatters } from "../../shared/formatters.js";
3
- import type { CheckResult, DoctorData } from "../operations/types.js";
3
+ import type {
4
+ CheckResult,
5
+ CheckStatus,
6
+ DoctorData,
7
+ } from "../operations/types.js";
4
8
 
5
- /** Status icons for pass/fail/skip check results. */
6
- const STATUS_ICONS = {
9
+ /** Status icons for pass/fail/skip results (checks and sub-items). */
10
+ const STATUS_ICONS: Record<CheckStatus, string> = {
7
11
  pass: chalk.green("✓"),
8
12
  fail: chalk.red("✗"),
9
13
  skip: chalk.yellow("○"),
10
- } as const;
14
+ };
15
+
16
+ const SUB_BULLET = chalk.dim("·");
17
+ const FIX_ARROW = chalk.cyan("↳");
18
+ const INDENT = " "; // aligns sub-lines under the check name
11
19
 
12
20
  /**
13
- * Format a single check result as a styled terminal line.
21
+ * Render one check as terminal lines: a headline row (icon, aligned name,
22
+ * detail), an optional indented breakdown of sub-items, and — for failures —
23
+ * an inline remedial instruction.
14
24
  *
15
- * @param check - The check result to format.
16
- * @returns A single-line string with status icon, name, and detail.
25
+ * @param check - The check result to render.
26
+ * @param nameWidth - Width to pad check names to so details align in a column.
27
+ * @returns The lines for this check.
17
28
  */
18
- function formatCheckPlain(check: CheckResult): string {
19
- return `${STATUS_ICONS[check.status]} ${check.name} ${chalk.dim(check.detail)}`;
29
+ function formatCheckPlain(check: CheckResult, nameWidth: number): string[] {
30
+ const name = check.name.padEnd(nameWidth);
31
+ const label = check.status === "fail" ? chalk.red(name) : chalk.bold(name);
32
+ const lines = [
33
+ ` ${STATUS_ICONS[check.status]} ${label} ${chalk.dim(check.detail)}`,
34
+ ];
35
+
36
+ if (check.items && check.items.length > 0) {
37
+ const itemWidth = Math.max(...check.items.map((i) => i.label.length));
38
+ const anyStatus = check.items.some((i) => i.status !== undefined);
39
+ for (const item of check.items) {
40
+ const icon = anyStatus
41
+ ? item.status
42
+ ? `${STATUS_ICONS[item.status]} `
43
+ : " "
44
+ : "";
45
+ const itemLabel = item.detail ? item.label.padEnd(itemWidth) : item.label;
46
+ const detail = item.detail ? ` ${chalk.dim(item.detail)}` : "";
47
+ lines.push(`${INDENT}${SUB_BULLET} ${icon}${itemLabel}${detail}`);
48
+ }
49
+ }
50
+
51
+ if (check.status === "fail" && check.remedy) {
52
+ lines.push(`${INDENT}${FIX_ARROW} ${chalk.cyan("fix:")} ${check.remedy}`);
53
+ }
54
+
55
+ return lines;
56
+ }
57
+
58
+ /** Render the pass/fail/skip tally, coloring non-zero fail/skip. */
59
+ function formatSummary(data: DoctorData): string {
60
+ const parts = [chalk.green(`${data.passed} passed`)];
61
+ parts.push(
62
+ data.failed > 0
63
+ ? chalk.red(`${data.failed} failed`)
64
+ : chalk.dim(`${data.failed} failed`),
65
+ );
66
+ parts.push(
67
+ data.skipped > 0
68
+ ? chalk.yellow(`${data.skipped} skipped`)
69
+ : chalk.dim(`${data.skipped} skipped`),
70
+ );
71
+ return ` ${parts.join(chalk.dim(" · "))}`;
20
72
  }
21
73
 
22
74
  /**
23
75
  * Formatters for `pragma doctor` output.
24
76
  *
25
- * - **plain** renders a check list with chalk icons and remedial commands.
26
- * - **llm** renders a markdown list with remedial section.
27
- * - **json** serializes the raw DoctorData.
77
+ * - **plain** renders an aligned check list with indented sub-items and
78
+ * inline remedies, followed by a summary tally.
79
+ * - **llm** renders a Markdown list with nested sub-items and inline fixes.
80
+ * - **json** serializes the raw DoctorData (including `items`).
28
81
  */
29
82
  const formatters: Formatters<DoctorData> = {
30
83
  plain(data) {
31
- const lines: string[] = [];
84
+ const nameWidth = Math.max(...data.checks.map((c) => c.name.length), 0);
85
+ const lines: string[] = [chalk.bold("pragma doctor"), ""];
32
86
 
33
87
  for (const check of data.checks) {
34
- lines.push(formatCheckPlain(check));
88
+ lines.push(...formatCheckPlain(check, nameWidth));
35
89
  }
36
90
 
37
- const remedies = data.checks.filter(
38
- (c): c is CheckResult & { remedy: string } =>
39
- c.status === "fail" && c.remedy !== undefined,
40
- );
41
-
42
- if (remedies.length > 0) {
43
- lines.push("");
44
- for (const r of remedies) {
45
- lines.push(
46
- `Run ${chalk.cyan(`\`${r.remedy}\``)} to fix ${chalk.dim(r.name)}.`,
47
- );
48
- }
49
- }
91
+ lines.push("");
92
+ lines.push(formatSummary(data));
50
93
 
51
94
  return lines.join("\n");
52
95
  },
53
96
 
54
97
  llm(data) {
55
- const lines: string[] = [];
56
-
57
- lines.push("## Doctor");
58
- lines.push("");
98
+ const lines: string[] = ["## Doctor", ""];
59
99
 
60
100
  for (const check of data.checks) {
61
101
  const icon =
62
102
  check.status === "pass" ? "✓" : check.status === "fail" ? "✗" : "○";
63
103
  lines.push(`- ${icon} **${check.name}**: ${check.detail}`);
64
- }
65
104
 
66
- const remedies = data.checks.filter((c) => c.status === "fail" && c.remedy);
105
+ for (const item of check.items ?? []) {
106
+ const itemIcon =
107
+ item.status === "pass"
108
+ ? "✓ "
109
+ : item.status === "fail"
110
+ ? "✗ "
111
+ : item.status === "skip"
112
+ ? "○ "
113
+ : "";
114
+ const detail = item.detail ? `: ${item.detail}` : "";
115
+ lines.push(` - ${itemIcon}${item.label}${detail}`);
116
+ }
67
117
 
68
- if (remedies.length > 0) {
69
- lines.push("");
70
- lines.push("### Remedial");
71
- for (const r of remedies) {
72
- lines.push(`- \`${r.remedy}\``);
118
+ if (check.status === "fail" && check.remedy) {
119
+ lines.push(` - _fix:_ \`${check.remedy}\``);
73
120
  }
74
121
  }
75
122
 
123
+ lines.push("");
124
+ lines.push(
125
+ `_${data.passed} passed, ${data.failed} failed, ${data.skipped} skipped_`,
126
+ );
127
+
76
128
  return lines.join("\n");
77
129
  },
78
130
 
@@ -1,21 +1,35 @@
1
- import { configExists } from "#config";
1
+ import { readConfigLayers } from "#config";
2
2
  import type { CheckContext, CheckResult } from "../types.js";
3
3
 
4
4
  /**
5
- * Check that `pragma.config.json` exists in the working directory.
5
+ * Check which config layer is active for the working directory.
6
+ *
7
+ * Passes when a project `pragma.config.json` is found up the tree, or —
8
+ * pragma is global-first — when only the global XDG config exists. Fails
9
+ * only when neither layer is configured.
6
10
  *
7
11
  * @param ctx - Check context with the working directory.
8
- * @returns A CheckResult indicating pass or fail.
12
+ * @returns A CheckResult indicating pass or fail with the active layer.
9
13
  * @note Impure
10
14
  */
11
15
  export default async function checkConfigFile(
12
16
  ctx: CheckContext,
13
17
  ): Promise<CheckResult> {
14
- if (configExists(ctx.cwd)) {
18
+ const layers = readConfigLayers(ctx.cwd);
19
+
20
+ if (layers.project.exists) {
21
+ return {
22
+ name: "pragma.config.json",
23
+ status: "pass",
24
+ detail: `found (${layers.project.path})`,
25
+ };
26
+ }
27
+
28
+ if (layers.global.exists) {
15
29
  return {
16
30
  name: "pragma.config.json",
17
31
  status: "pass",
18
- detail: "found",
32
+ detail: `no project config — global config active (${layers.global.path})`,
19
33
  };
20
34
  }
21
35