@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/README.md +67 -15
  2. package/dist/pragma +0 -0
  3. package/package.json +16 -17
  4. package/src/config/defaultConfig.test.ts +33 -0
  5. package/src/config/defaultConfig.ts +59 -0
  6. package/src/config/defaults.json +17 -0
  7. package/src/config/findProjectConfigPath.test.ts +53 -0
  8. package/src/config/findProjectConfigPath.ts +36 -0
  9. package/src/config/index.ts +17 -2
  10. package/src/config/mergeConfigFileUpdate.ts +58 -0
  11. package/src/config/parseConfigValues.ts +215 -0
  12. package/src/config/readConfig.test.ts +104 -13
  13. package/src/config/readConfig.ts +12 -117
  14. package/src/config/readConfigLayers.test.ts +107 -0
  15. package/src/config/readConfigLayers.ts +115 -0
  16. package/src/config/resolveGlobalConfigPath.ts +13 -0
  17. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  18. package/src/config/resolveWriteConfigPath.ts +31 -0
  19. package/src/config/types.ts +88 -2
  20. package/src/config/writeConfig.ts +25 -43
  21. package/src/constants.ts +12 -1
  22. package/src/domains/block/commands/index.ts +2 -1
  23. package/src/domains/block/commands/list.ts +5 -90
  24. package/src/domains/block/commands/lookup.ts +8 -167
  25. package/src/domains/block/commands/sample.ts +55 -0
  26. package/src/domains/block/formatters/index.ts +2 -1
  27. package/src/domains/block/formatters/sample.ts +21 -0
  28. package/src/domains/block/index.ts +3 -3
  29. package/src/domains/block/mcp/specs.ts +36 -168
  30. package/src/domains/block/operations/index.ts +2 -1
  31. package/src/domains/block/operations/lookup.ts +9 -0
  32. package/src/domains/block/operations/sample.ts +41 -0
  33. package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
  34. package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
  35. package/src/domains/block/stories.test.ts +28 -0
  36. package/src/domains/block/stories.ts +254 -0
  37. package/src/domains/config/commands/channel.test.ts +14 -3
  38. package/src/domains/config/commands/channel.ts +11 -7
  39. package/src/domains/config/commands/configScope.ts +46 -0
  40. package/src/domains/config/commands/framework.test.ts +105 -0
  41. package/src/domains/config/commands/framework.ts +91 -0
  42. package/src/domains/config/commands/index.ts +2 -0
  43. package/src/domains/config/commands/show.ts +5 -45
  44. package/src/domains/config/commands/tier.test.ts +13 -2
  45. package/src/domains/config/commands/tier.ts +11 -7
  46. package/src/domains/config/commands/trace.test.ts +165 -0
  47. package/src/domains/config/commands/trace.ts +83 -0
  48. package/src/domains/config/formatters/channel.test.ts +20 -6
  49. package/src/domains/config/formatters/channel.ts +14 -11
  50. package/src/domains/config/formatters/framework.ts +37 -0
  51. package/src/domains/config/formatters/index.ts +2 -0
  52. package/src/domains/config/formatters/show.test.ts +27 -0
  53. package/src/domains/config/formatters/show.ts +27 -4
  54. package/src/domains/config/formatters/tier.test.ts +26 -10
  55. package/src/domains/config/formatters/tier.ts +19 -11
  56. package/src/domains/config/formatters/trace.ts +28 -0
  57. package/src/domains/config/index.ts +15 -2
  58. package/src/domains/config/mcp/specs.ts +33 -53
  59. package/src/domains/config/operations/index.ts +1 -0
  60. package/src/domains/config/operations/show.test.ts +18 -0
  61. package/src/domains/config/operations/show.ts +7 -1
  62. package/src/domains/config/operations/types.ts +7 -0
  63. package/src/domains/config/operations/validateFramework.ts +24 -0
  64. package/src/domains/config/stories.ts +42 -0
  65. package/src/domains/config/tasks/index.ts +3 -0
  66. package/src/domains/config/tasks/setChannel.ts +13 -22
  67. package/src/domains/config/tasks/setFramework.ts +21 -0
  68. package/src/domains/config/tasks/setTier.ts +10 -23
  69. package/src/domains/config/tasks/setTrace.ts +20 -0
  70. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  71. package/src/domains/create/commands/component.ts +5 -5
  72. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  73. package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
  74. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
  75. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  76. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
  77. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  78. package/src/domains/doctor/operations/checks/index.ts +1 -0
  79. package/src/domains/doctor/operations/checks.test.ts +162 -8
  80. package/src/domains/doctor/operations/runChecks.test.ts +85 -4
  81. package/src/domains/doctor/operations/runChecks.ts +50 -12
  82. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  83. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  84. package/src/domains/graphql/commands/build.test.ts +196 -0
  85. package/src/domains/graphql/commands/build.ts +117 -0
  86. package/src/domains/graphql/commands/check.test.ts +141 -0
  87. package/src/domains/graphql/commands/check.ts +81 -0
  88. package/src/domains/graphql/commands/index.ts +5 -0
  89. package/src/domains/graphql/commands/serve.test.ts +40 -0
  90. package/src/domains/graphql/commands/serve.ts +131 -0
  91. package/src/domains/graphql/constants.ts +10 -0
  92. package/src/domains/graphql/formatters/index.ts +3 -0
  93. package/src/domains/graphql/formatters/report.ts +78 -0
  94. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  95. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  96. package/src/domains/graphql/helpers/index.ts +6 -0
  97. package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
  98. package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
  99. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
  100. package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
  101. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  102. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  103. package/src/domains/graphql/index.ts +24 -0
  104. package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
  105. package/src/domains/graphql/operations/compileSchema.ts +107 -0
  106. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  107. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  108. package/src/domains/graphql/operations/index.ts +12 -0
  109. package/src/domains/graphql/types.ts +29 -0
  110. package/src/domains/info/operations/collectInfo.test.ts +16 -5
  111. package/src/domains/info/operations/collectInfo.ts +16 -19
  112. package/src/domains/llm/capabilities.test.ts +91 -0
  113. package/src/domains/llm/commands/capabilities.ts +28 -0
  114. package/src/domains/llm/commands/index.ts +1 -0
  115. package/src/domains/llm/data/commandReference.ts +90 -18
  116. package/src/domains/llm/data/decisionTrees.ts +15 -13
  117. package/src/domains/llm/data/index.ts +2 -0
  118. package/src/domains/llm/data/toolCatalog.ts +190 -0
  119. package/src/domains/llm/formatters/capabilities.ts +50 -0
  120. package/src/domains/llm/formatters/index.ts +1 -0
  121. package/src/domains/llm/index.ts +3 -2
  122. package/src/domains/llm/mcp/index.ts +1 -1
  123. package/src/domains/llm/mcp/specs.ts +59 -52
  124. package/src/domains/llm/types.ts +40 -0
  125. package/src/domains/modifier/commands/index.ts +2 -1
  126. package/src/domains/modifier/commands/list.ts +5 -38
  127. package/src/domains/modifier/commands/lookup.ts +8 -79
  128. package/src/domains/modifier/commands/sample.ts +53 -0
  129. package/src/domains/modifier/formatters/index.ts +2 -1
  130. package/src/domains/modifier/formatters/sample.ts +15 -0
  131. package/src/domains/modifier/index.ts +7 -3
  132. package/src/domains/modifier/mcp/specs.ts +38 -70
  133. package/src/domains/modifier/operations/index.ts +2 -1
  134. package/src/domains/modifier/operations/lookup.ts +9 -0
  135. package/src/domains/modifier/operations/sample.ts +32 -0
  136. package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
  137. package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
  138. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
  139. package/src/domains/modifier/stories.test.ts +20 -0
  140. package/src/domains/modifier/stories.ts +77 -0
  141. package/src/domains/ontology/commands/list.ts +5 -58
  142. package/src/domains/ontology/commands/show.ts +6 -58
  143. package/src/domains/ontology/formatters/show.ts +7 -1
  144. package/src/domains/ontology/mcp/specs.ts +7 -84
  145. package/src/domains/ontology/stories.test.ts +36 -0
  146. package/src/domains/ontology/stories.ts +108 -0
  147. package/src/domains/refs/operations/index.ts +1 -1
  148. package/src/domains/refs/operations/paths.ts +29 -1
  149. package/src/domains/refs/operations/updateRefs.test.ts +6 -3
  150. package/src/domains/refs/operations/updateRefs.ts +2 -36
  151. package/src/domains/setup/commands/skills.test.ts +6 -2
  152. package/src/domains/setup/commands/skills.ts +6 -2
  153. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  154. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  155. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  156. package/src/domains/shared/bootStore.ts +92 -50
  157. package/src/domains/shared/compactUri.test.ts +11 -0
  158. package/src/domains/shared/compactUri.ts +10 -5
  159. package/src/domains/shared/context.ts +2 -2
  160. package/src/domains/shared/contracts.ts +12 -0
  161. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  162. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  163. package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
  164. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  165. package/src/domains/shared/loaders/bundledLoader.ts +206 -0
  166. package/src/domains/shared/loaders/gitLoader.ts +31 -0
  167. package/src/domains/shared/loaders/index.ts +9 -0
  168. package/src/domains/shared/loaders/localLoader.ts +87 -0
  169. package/src/domains/shared/loaders/readPackageDir.ts +147 -0
  170. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  171. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  172. package/src/domains/shared/lookupMany.test.ts +120 -0
  173. package/src/domains/shared/lookupMany.ts +43 -3
  174. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  175. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  176. package/src/domains/shared/packages.ts +14 -120
  177. package/src/domains/shared/pickRandom.ts +22 -0
  178. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  179. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  180. package/src/domains/shared/runtime.ts +7 -63
  181. package/src/domains/shared/sampleFormatters.ts +47 -0
  182. package/src/domains/shared/semanticPackage.ts +112 -0
  183. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  184. package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
  185. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  186. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  187. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  188. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  189. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  190. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  191. package/src/domains/shared/stories/condense.test.ts +18 -0
  192. package/src/domains/shared/stories/condense.ts +15 -0
  193. package/src/domains/shared/stories/index.ts +20 -0
  194. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  195. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  196. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
  197. package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
  198. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
  199. package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
  200. package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
  201. package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
  202. package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
  203. package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
  204. package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
  205. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
  206. package/src/domains/shared/stories/pack/index.ts +28 -0
  207. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  208. package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
  209. package/src/domains/shared/stories/pack/types.ts +104 -0
  210. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
  211. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
  212. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  213. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  214. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  215. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  216. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  217. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  218. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  219. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  220. package/src/domains/shared/stories/types.ts +171 -0
  221. package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
  222. package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
  223. package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
  224. package/src/domains/shared/suggestions/expandGlob.ts +52 -0
  225. package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
  226. package/src/domains/shared/suggestions/index.ts +7 -0
  227. package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
  228. package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
  229. package/src/domains/shared/suggestions/suggestNames.ts +130 -0
  230. package/src/domains/shared/types/index.ts +1 -0
  231. package/src/domains/shared/types/runtime.ts +4 -1
  232. package/src/domains/shared/types/sample.ts +17 -0
  233. package/src/domains/skill/commands/index.ts +3 -1
  234. package/src/domains/skill/commands/list.test.ts +6 -2
  235. package/src/domains/skill/commands/list.ts +5 -63
  236. package/src/domains/skill/commands/lookup.test.ts +118 -0
  237. package/src/domains/skill/commands/lookup.ts +14 -0
  238. package/src/domains/skill/formatters/index.ts +1 -0
  239. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  240. package/src/domains/skill/formatters/lookup.ts +57 -0
  241. package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
  242. package/src/domains/skill/index.ts +7 -3
  243. package/src/domains/skill/mcp/specs.ts +10 -29
  244. package/src/domains/skill/operations/discover.test.ts +61 -30
  245. package/src/domains/skill/operations/discover.ts +28 -37
  246. package/src/domains/skill/operations/index.ts +2 -1
  247. package/src/domains/skill/operations/list.test.ts +15 -7
  248. package/src/domains/skill/operations/lookup.test.ts +91 -0
  249. package/src/domains/skill/operations/lookup.ts +70 -0
  250. package/src/domains/skill/stories.test.ts +43 -0
  251. package/src/domains/skill/stories.ts +103 -0
  252. package/src/domains/skill/types.ts +8 -0
  253. package/src/domains/standard/commands/index.ts +2 -1
  254. package/src/domains/standard/commands/list.ts +5 -84
  255. package/src/domains/standard/commands/lookup.ts +8 -93
  256. package/src/domains/standard/commands/sample.ts +57 -0
  257. package/src/domains/standard/formatters/index.ts +2 -1
  258. package/src/domains/standard/formatters/sample.ts +15 -0
  259. package/src/domains/standard/index.ts +8 -1
  260. package/src/domains/standard/mcp/specs.ts +44 -152
  261. package/src/domains/standard/operations/index.ts +2 -1
  262. package/src/domains/standard/operations/lookup.ts +9 -0
  263. package/src/domains/standard/operations/sample.ts +35 -0
  264. package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
  265. package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
  266. package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
  267. package/src/domains/standard/stories.test.ts +24 -0
  268. package/src/domains/standard/stories.ts +151 -0
  269. package/src/domains/tier/commands/list.ts +5 -55
  270. package/src/domains/tier/mcp/specs.ts +5 -31
  271. package/src/domains/tier/stories.test.ts +22 -0
  272. package/src/domains/tier/stories.ts +52 -0
  273. package/src/domains/token/commands/index.ts +2 -1
  274. package/src/domains/token/commands/sample.ts +53 -0
  275. package/src/domains/token/formatters/index.ts +2 -1
  276. package/src/domains/token/formatters/sample.ts +16 -0
  277. package/src/domains/token/index.ts +8 -1
  278. package/src/domains/token/mcp/specs.ts +52 -2
  279. package/src/domains/token/operations/index.ts +2 -1
  280. package/src/domains/token/operations/lookup.ts +9 -0
  281. package/src/domains/token/operations/sample.ts +32 -0
  282. package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
  283. package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
  284. package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
  285. package/src/domains/trace/commands/clear.ts +71 -0
  286. package/src/domains/trace/commands/index.ts +5 -0
  287. package/src/domains/trace/commands/sessions.ts +32 -0
  288. package/src/domains/trace/commands/show.ts +145 -0
  289. package/src/domains/trace/formatters/index.ts +9 -0
  290. package/src/domains/trace/formatters/sessions.ts +80 -0
  291. package/src/domains/trace/formatters/show.ts +122 -0
  292. package/src/domains/trace/index.ts +17 -0
  293. package/src/domains/trace/operations/index.ts +8 -0
  294. package/src/domains/trace/operations/listSessions.test.ts +55 -0
  295. package/src/domains/trace/operations/listSessions.ts +52 -0
  296. package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
  297. package/src/domains/trace/operations/readTraceLog.ts +104 -0
  298. package/src/domains/trace/tracePlugin.test.ts +171 -0
  299. package/src/domains/trace/tracePlugin.ts +159 -0
  300. package/src/domains/trace/types.ts +65 -0
  301. package/src/error/PragmaError.ts +15 -2
  302. package/src/error/types.ts +15 -1
  303. package/src/mcp/error/serializeErrorPayload.ts +7 -0
  304. package/src/mcp/error/types.ts +8 -0
  305. package/src/mcp/registerResources.test.ts +405 -75
  306. package/src/mcp/registerResources.ts +5 -299
  307. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  308. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  309. package/src/mcp/resources/buildResourceList.ts +176 -0
  310. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  311. package/src/mcp/resources/classifyEntity.ts +27 -0
  312. package/src/mcp/resources/constants.ts +105 -0
  313. package/src/mcp/resources/displayPredicates.ts +62 -0
  314. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  315. package/src/mcp/resources/pickFirstValue.ts +26 -0
  316. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  317. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  318. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  319. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  320. package/src/mcp/resources/readEntity.ts +169 -0
  321. package/src/mcp/resources/registerResources.ts +126 -0
  322. package/src/mcp/resources/types.ts +128 -0
  323. package/src/mcp/tools/index.ts +9 -0
  324. package/src/mcp/tools/registerTools.test.ts +84 -12
  325. package/src/mcp/types.ts +4 -0
  326. package/src/mcp/utils/wrapTool.test.ts +1 -0
  327. package/src/pipeline/collectCommands.ts +15 -2
  328. package/src/pipeline/renderError.ts +15 -0
  329. package/src/pipeline/resolveCommandKind.ts +8 -1
  330. package/src/pipeline/runCli.ts +51 -6
  331. package/src/testing/createTestMcpClient.ts +5 -1
  332. package/src/testing/graphqlFixtures.ts +72 -0
  333. package/src/testing/index.ts +17 -0
  334. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  335. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  336. package/src/testing/integration/executor-execution.test.ts +101 -0
  337. package/src/testing/integration/executor-golden.test.ts +145 -0
  338. package/src/testing/integration/mcp-surface.test.ts +11 -5
  339. package/src/testing/integration/parity.test.ts +17 -12
  340. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  341. package/src/testing/setupXdgIsolation.ts +15 -0
  342. package/src/testing/standardParityFixtures.ts +92 -0
  343. package/src/testing/storyFixtures.ts +63 -0
  344. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { blockListStory, blockLookupStory } from "./stories.js";
3
+
4
+ describe("blockListStory", () => {
5
+ it("renders the ink list view", () => {
6
+ const view = blockListStory.renderInk?.([]);
7
+ expect(typeof view).toBe("string");
8
+ expect(view).toContain("Blocks");
9
+ });
10
+ });
11
+
12
+ describe("blockLookupStory", () => {
13
+ it("renders the ink lookup view", () => {
14
+ const view = blockLookupStory.renderInk?.(
15
+ { results: [], errors: [] },
16
+ { surface: "cli", detailed: false, params: {} },
17
+ );
18
+ expect(typeof view).toBe("string");
19
+ });
20
+
21
+ it("implies detail from aspect flags on the CLI only", () => {
22
+ const resolveDetailed = blockLookupStory.resolveDetailed;
23
+ expect(resolveDetailed?.("cli", { anatomy: true })).toBe(true);
24
+ expect(resolveDetailed?.("cli", {})).toBe(false);
25
+ expect(resolveDetailed?.("mcp", {})).toBe(true);
26
+ expect(resolveDetailed?.("mcp", { detailed: false })).toBe(false);
27
+ });
28
+ });
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Block read stories — the single declaration of `block list` and
3
+ * `block lookup` for both surfaces.
4
+ *
5
+ * The story kernel compiles these into the CLI commands and MCP tools,
6
+ * so descriptions, parameters, resolution, and rendering live here once.
7
+ */
8
+
9
+ import { PragmaError } from "#error";
10
+ import { createListView, createLookupView } from "#tui";
11
+ import type { ColumnDef } from "../shared/contracts.js";
12
+ import {
13
+ type LookupStory,
14
+ type LookupStoryView,
15
+ type ReadStory,
16
+ requirePragmaContext,
17
+ } from "../shared/stories/index.js";
18
+ import type { BlockDetailed } from "../shared/types/index.js";
19
+ import { blockConfig } from "./blockConfig.js";
20
+ import {
21
+ createLookupOptions,
22
+ listFormatters,
23
+ lookupFormatters,
24
+ } from "./formatters/index.js";
25
+ import type { BlockLookupInput } from "./formatters/types.js";
26
+ import { resolveAspects } from "./helpers/index.js";
27
+ import { listBlocks } from "./operations/index.js";
28
+ import {
29
+ buildBlockFilters,
30
+ resolveBlockList,
31
+ resolveBlockLookup,
32
+ } from "./orchestration/index.js";
33
+ import type { AspectFlags, BlockListContract } from "./types.js";
34
+
35
+ type BlockListItems = BlockListContract["result"]["items"];
36
+
37
+ const LIST_COLUMN_ORDER = ["name", "type", "tier", "modifiers", "uri"] as const;
38
+
39
+ /** The `block list` / `block_list` read story. */
40
+ export const blockListStory: ReadStory<BlockListContract, BlockListItems> = {
41
+ noun: "block",
42
+ verb: "list",
43
+ description: "List blocks in the design system",
44
+ toolDescription:
45
+ "List design system blocks visible under current tier and channel configuration.",
46
+ params: [
47
+ {
48
+ name: "allTiers",
49
+ type: "boolean",
50
+ description: "Show blocks from all tiers",
51
+ toolDescription: "Show blocks from all tiers, ignoring tier filter",
52
+ default: false,
53
+ },
54
+ {
55
+ name: "digest",
56
+ type: "boolean",
57
+ description: "Show enriched summary with implementations",
58
+ toolDescription: "Include implementations per block",
59
+ default: false,
60
+ },
61
+ {
62
+ name: "detailed",
63
+ type: "boolean",
64
+ description: "Show full details for each block",
65
+ toolDescription: "Include full details per block",
66
+ default: false,
67
+ },
68
+ ],
69
+ examples: [
70
+ "pragma block list",
71
+ "pragma block list --all-tiers",
72
+ "pragma block list --digest",
73
+ "pragma block list --detailed",
74
+ "pragma block list --llm",
75
+ "pragma block list --format json",
76
+ ],
77
+ resolve: (rt, params) =>
78
+ resolveBlockList(rt, {
79
+ allTiers: params.allTiers === true,
80
+ digest: params.digest === true,
81
+ detailed: params.detailed === true,
82
+ }),
83
+ toOutput: (contract) => contract.result.items,
84
+ formatters: listFormatters,
85
+ toEnvelope: (contract) => {
86
+ const { items, disclosure, filters } = contract.result;
87
+ if (disclosure.level === "summary") {
88
+ return { data: items, meta: { count: items.length, filters } };
89
+ }
90
+ return {
91
+ data: items,
92
+ meta: { count: items.length, disclosure: disclosure.level, filters },
93
+ };
94
+ },
95
+ renderInk: (items) =>
96
+ createListView({
97
+ heading: "Blocks",
98
+ domain: "block",
99
+ items,
100
+ columns: reorderColumns(blockConfig.listColumns, LIST_COLUMN_ORDER),
101
+ }),
102
+ };
103
+
104
+ /** The `block lookup` / `block_lookup` read story. */
105
+ export const blockLookupStory: LookupStory<BlockDetailed, BlockLookupInput> = {
106
+ noun: "block",
107
+ description: "Look up block details",
108
+ toolDescription:
109
+ "Get detailed information about one or more design system blocks including anatomy, modifiers, tokens, and applicable standards.",
110
+ namesDescription: "Block names or IRIs",
111
+ namesToolDescription:
112
+ "Block names or IRIs to look up (e.g. ['Button', 'ds:global.component.card'])",
113
+ complete: async (partial, cmdCtx) => {
114
+ const ctx = requirePragmaContext(cmdCtx);
115
+ const all = await listBlocks(ctx.store, ctx.config);
116
+ return all
117
+ .map((block) => block.name)
118
+ .filter((name) => name.toLowerCase().startsWith(partial.toLowerCase()));
119
+ },
120
+ detailedParam: {
121
+ description:
122
+ "Show full details including anatomy, modifiers, tokens, and implementations",
123
+ toolDescription: "Return full details (default: true for MCP)",
124
+ },
125
+ params: [
126
+ {
127
+ name: "anatomy",
128
+ type: "boolean",
129
+ description: "Show anatomy tree",
130
+ default: false,
131
+ surfaces: "cli",
132
+ },
133
+ {
134
+ name: "modifiers",
135
+ type: "boolean",
136
+ description: "Show modifier values",
137
+ default: false,
138
+ surfaces: "cli",
139
+ },
140
+ {
141
+ name: "tokens",
142
+ type: "boolean",
143
+ description: "Show token references",
144
+ default: false,
145
+ surfaces: "cli",
146
+ },
147
+ {
148
+ name: "implementations",
149
+ type: "boolean",
150
+ description: "Show implementation paths",
151
+ default: false,
152
+ surfaces: "cli",
153
+ },
154
+ ],
155
+ parameterGroups: {
156
+ "Aspect filters": ["anatomy", "modifiers", "tokens", "implementations"],
157
+ },
158
+ examples: [
159
+ "pragma block lookup Button",
160
+ "pragma block lookup Button Card",
161
+ "pragma block lookup Button --detailed",
162
+ "pragma block lookup Button --anatomy --modifiers",
163
+ "pragma block lookup Button --detailed --llm",
164
+ "pragma block lookup ds:global.component.button",
165
+ ],
166
+ resolve: async (rt, names) => {
167
+ const contract = await resolveBlockLookup(
168
+ rt.store,
169
+ names,
170
+ buildBlockFilters(rt),
171
+ );
172
+ return contract.result;
173
+ },
174
+ resolveDetailed: (surface, params) => {
175
+ if (surface === "mcp") {
176
+ return ((params.detailed as boolean | undefined) ?? true) === true;
177
+ }
178
+ return params.detailed === true || isAspectSelected(params);
179
+ },
180
+ toFmtInput: (block, view) => ({
181
+ block,
182
+ detailed: view.detailed,
183
+ aspects: resolveViewAspects(view),
184
+ }),
185
+ formatters: lookupFormatters,
186
+ project: (block) => {
187
+ const {
188
+ uri,
189
+ name,
190
+ type,
191
+ tier,
192
+ modifiers,
193
+ implementations,
194
+ nodeCount,
195
+ tokenCount,
196
+ summary,
197
+ } = block;
198
+ return {
199
+ uri,
200
+ name,
201
+ type,
202
+ tier,
203
+ modifiers,
204
+ implementations,
205
+ nodeCount,
206
+ tokenCount,
207
+ summary,
208
+ };
209
+ },
210
+ emptyNamesError: () =>
211
+ PragmaError.invalidInput("names", "(empty)", {
212
+ recovery: {
213
+ message: "List available blocks.",
214
+ cli: "pragma block list",
215
+ mcp: { tool: "block_list" },
216
+ },
217
+ }),
218
+ renderInk: (result, view) =>
219
+ createLookupView({
220
+ results: result.results,
221
+ errors: result.errors,
222
+ domain: "block",
223
+ options: createLookupOptions(view.detailed, resolveViewAspects(view)),
224
+ }),
225
+ };
226
+
227
+ function isAspectSelected(params: Record<string, unknown>): boolean {
228
+ return (
229
+ params.anatomy === true ||
230
+ params.modifiers === true ||
231
+ params.tokens === true ||
232
+ params.implementations === true
233
+ );
234
+ }
235
+
236
+ function resolveViewAspects(view: LookupStoryView): AspectFlags {
237
+ return resolveAspects({
238
+ anatomy: view.params.anatomy === true,
239
+ modifiers: view.params.modifiers === true,
240
+ tokens: view.params.tokens === true,
241
+ implementations: view.params.implementations === true,
242
+ });
243
+ }
244
+
245
+ function reorderColumns<T>(
246
+ columns: readonly ColumnDef<T>[],
247
+ order: readonly string[],
248
+ ): readonly ColumnDef<T>[] {
249
+ const byKey = new Map(columns.map((col) => [col.key, col]));
250
+ return order.flatMap((key) => {
251
+ const col = byKey.get(key as keyof T & string);
252
+ return col ? [col] : [];
253
+ });
254
+ }
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { Store } from "@canonical/ke";
@@ -18,13 +18,24 @@ function makeCtx(cwd: string): PragmaContext {
18
18
 
19
19
  describe("config channel command", () => {
20
20
  let dir: string;
21
+ let xdgDir: string;
22
+ let originalXdg: string | undefined;
21
23
 
22
24
  beforeEach(() => {
23
25
  dir = mkdtempSync(join(tmpdir(), "pragma-cmd-channel-"));
26
+ // Bound the config walk at the fixture root, seed an empty project
27
+ // file so writes stay local, and isolate the global XDG layer.
28
+ mkdirSync(join(dir, ".git"));
29
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
30
+ originalXdg = process.env.XDG_CONFIG_HOME;
31
+ xdgDir = mkdtempSync(join(tmpdir(), "pragma-cmd-channel-xdg-"));
32
+ process.env.XDG_CONFIG_HOME = xdgDir;
24
33
  });
25
34
 
26
35
  afterEach(() => {
36
+ process.env.XDG_CONFIG_HOME = originalXdg;
27
37
  rmSync(dir, { recursive: true, force: true });
38
+ rmSync(xdgDir, { recursive: true, force: true });
28
39
  });
29
40
 
30
41
  it("sets channel to experimental", async () => {
@@ -45,7 +56,7 @@ describe("config channel command", () => {
45
56
  expect(result.tag).toBe("output");
46
57
  if (result.tag === "output") {
47
58
  const text = result.render.plain(result.value);
48
- expect(text).toBe('Set channel to "prerelease".');
59
+ expect(text).toContain('Set channel to "prerelease".');
49
60
  }
50
61
  });
51
62
 
@@ -72,7 +83,7 @@ describe("config channel command", () => {
72
83
  expect(result.tag).toBe("output");
73
84
  if (result.tag === "output") {
74
85
  const text = result.render.plain(result.value);
75
- expect(text).toBe("Reset channel to default.");
86
+ expect(text).toContain("Reset channel to default.");
76
87
  }
77
88
  });
78
89
 
@@ -3,13 +3,14 @@ import {
3
3
  type CommandResult,
4
4
  createOutputResult,
5
5
  } from "@canonical/cli-core";
6
- import { runTask } from "@canonical/task";
6
+ import { runTask } from "@canonical/task/node";
7
7
  import { readConfig } from "#config";
8
8
  import type { PragmaContext } from "../../shared/context.js";
9
9
  import { selectFormatter } from "../../shared/formatters.js";
10
10
  import { channelFormatters } from "../formatters/index.js";
11
11
  import { validateChannel } from "../operations/index.js";
12
12
  import { setChannelTask } from "../tasks/index.js";
13
+ import { resolveConfigScope, SCOPE_PARAMETERS } from "./configScope.js";
13
14
 
14
15
  /**
15
16
  * Build the `pragma config channel` command definition.
@@ -40,6 +41,7 @@ export default function buildChannelCommand(
40
41
  type: "boolean",
41
42
  default: false,
42
43
  },
44
+ ...SCOPE_PARAMETERS,
43
45
  ],
44
46
  meta: {
45
47
  examples: [
@@ -50,15 +52,17 @@ export default function buildChannelCommand(
50
52
  execute: async (
51
53
  params: Record<string, unknown>,
52
54
  ): Promise<CommandResult> => {
55
+ const scope = resolveConfigScope(params);
53
56
  const reset = params.reset === true;
54
57
  const value = params.value as string | undefined;
55
58
 
56
59
  if (reset) {
57
- await runTask(setChannelTask(ctx.cwd, undefined));
60
+ const result = await runTask(setChannelTask(ctx.cwd, undefined, scope));
58
61
  const format = selectFormatter(ctx, channelFormatters.reset);
59
- return createOutputResult("Reset channel to default.", {
60
- plain: format,
61
- });
62
+ return createOutputResult(
63
+ { field: "channel", path: result.path },
64
+ { plain: format },
65
+ );
62
66
  }
63
67
 
64
68
  if (!value) {
@@ -71,10 +75,10 @@ export default function buildChannelCommand(
71
75
 
72
76
  const channel = validateChannel(value);
73
77
 
74
- await runTask(setChannelTask(ctx.cwd, channel));
78
+ const result = await runTask(setChannelTask(ctx.cwd, channel, scope));
75
79
  const format = selectFormatter(ctx, channelFormatters.set);
76
80
  return createOutputResult(
77
- { field: "channel", value: channel },
81
+ { field: "channel", value: channel, path: result.path },
78
82
  { plain: format },
79
83
  );
80
84
  },
@@ -0,0 +1,46 @@
1
+ import type { ParameterDefinition } from "@canonical/cli-core";
2
+ import { PragmaError } from "#error";
3
+ import type { ConfigScope } from "../../../config/index.js";
4
+
5
+ /**
6
+ * The `--global` / `--local` target-layer flags shared by every config
7
+ * write command. Without either flag the write follows the global-first
8
+ * rule: nearest existing project file, else the global XDG file.
9
+ */
10
+ export const SCOPE_PARAMETERS: readonly ParameterDefinition[] = [
11
+ {
12
+ name: "global",
13
+ description:
14
+ "Write to the global config ($XDG_CONFIG_HOME/pragma/config.json, default ~/.config/pragma/config.json)",
15
+ type: "boolean",
16
+ default: false,
17
+ },
18
+ {
19
+ name: "local",
20
+ description: "Write to ./pragma.config.json in the current directory",
21
+ type: "boolean",
22
+ default: false,
23
+ },
24
+ ];
25
+
26
+ /**
27
+ * Resolve the explicit write scope from parsed command parameters.
28
+ *
29
+ * @param params - Parsed command parameters.
30
+ * @returns The explicit scope, or `undefined` for the default rule.
31
+ * @throws PragmaError with code `INVALID_INPUT` when both flags are set.
32
+ */
33
+ export function resolveConfigScope(
34
+ params: Record<string, unknown>,
35
+ ): ConfigScope | undefined {
36
+ const global = params.global === true;
37
+ const local = params.local === true;
38
+ if (global && local) {
39
+ throw PragmaError.invalidInput("scope", "--global --local", {
40
+ recovery: { message: "Pass at most one of --global or --local." },
41
+ });
42
+ }
43
+ if (global) return "global";
44
+ if (local) return "local";
45
+ return undefined;
46
+ }
@@ -0,0 +1,105 @@
1
+ import {
2
+ mkdirSync,
3
+ mkdtempSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import type { Store } from "@canonical/ke";
11
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
12
+ import { readConfig } from "#config";
13
+ import type { PragmaContext } from "../../shared/context.js";
14
+ import buildFrameworkCommand from "./framework.js";
15
+
16
+ function makeCtx(cwd: string): PragmaContext {
17
+ return {
18
+ cwd,
19
+ globalFlags: { llm: false, format: "text" as const, verbose: false },
20
+ store: {} as Store,
21
+ config: { tier: undefined, channel: "normal" },
22
+ };
23
+ }
24
+
25
+ describe("config framework command", () => {
26
+ let dir: string;
27
+ let xdgDir: string;
28
+ let originalXdg: string | undefined;
29
+
30
+ beforeEach(() => {
31
+ dir = mkdtempSync(join(tmpdir(), "pragma-cmd-framework-"));
32
+ // Bound the config walk at the fixture root, seed an empty project
33
+ // file so writes stay local, and isolate the global XDG layer.
34
+ mkdirSync(join(dir, ".git"));
35
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
36
+ originalXdg = process.env.XDG_CONFIG_HOME;
37
+ xdgDir = mkdtempSync(join(tmpdir(), "pragma-cmd-framework-xdg-"));
38
+ process.env.XDG_CONFIG_HOME = xdgDir;
39
+ });
40
+
41
+ afterEach(() => {
42
+ process.env.XDG_CONFIG_HOME = originalXdg;
43
+ rmSync(dir, { recursive: true, force: true });
44
+ rmSync(xdgDir, { recursive: true, force: true });
45
+ });
46
+
47
+ it("sets framework to react", async () => {
48
+ const ctx = makeCtx(dir);
49
+ const result = await buildFrameworkCommand(ctx).execute(
50
+ { value: "react" },
51
+ ctx,
52
+ );
53
+ expect(result.tag).toBe("output");
54
+ expect(readConfig(dir).framework).toBe("react");
55
+ });
56
+
57
+ it("accepts svelte and lit", async () => {
58
+ const ctx = makeCtx(dir);
59
+ await buildFrameworkCommand(ctx).execute({ value: "svelte" }, ctx);
60
+ expect(readConfig(dir).framework).toBe("svelte");
61
+ await buildFrameworkCommand(ctx).execute({ value: "lit" }, ctx);
62
+ expect(readConfig(dir).framework).toBe("lit");
63
+ });
64
+
65
+ it("rejects an invalid framework", async () => {
66
+ const ctx = makeCtx(dir);
67
+ await expect(
68
+ buildFrameworkCommand(ctx).execute({ value: "angular" }, ctx),
69
+ ).rejects.toThrow(/framework/i);
70
+ });
71
+
72
+ it("reports 'none' when unset", async () => {
73
+ const ctx = makeCtx(dir);
74
+ const result = await buildFrameworkCommand(ctx).execute({}, ctx);
75
+ expect(result.tag).toBe("output");
76
+ if (result.tag === "output") {
77
+ expect(result.value).toBe("none");
78
+ }
79
+ });
80
+
81
+ it("resets framework via --reset (removes the field)", async () => {
82
+ writeFileSync(join(dir, "pragma.config.json"), '{"framework":"react"}');
83
+ const ctx = makeCtx(dir);
84
+ await buildFrameworkCommand(ctx).execute({ reset: true }, ctx);
85
+ expect(readConfig(dir).framework).toBeUndefined();
86
+ // The field is removed, not left as a stale value.
87
+ const raw = JSON.parse(
88
+ readFileSync(join(dir, "pragma.config.json"), "utf-8"),
89
+ ) as Record<string, unknown>;
90
+ expect(raw).not.toHaveProperty("framework");
91
+ });
92
+
93
+ it("preserves other config fields when setting framework", async () => {
94
+ writeFileSync(
95
+ join(dir, "pragma.config.json"),
96
+ JSON.stringify({ tier: "Apps/WPE", trace: true }),
97
+ );
98
+ const ctx = makeCtx(dir);
99
+ await buildFrameworkCommand(ctx).execute({ value: "lit" }, ctx);
100
+ const config = readConfig(dir);
101
+ expect(config.framework).toBe("lit");
102
+ expect(config.tier).toBe("Apps/WPE");
103
+ expect(config.trace).toBe(true);
104
+ });
105
+ });
@@ -0,0 +1,91 @@
1
+ import {
2
+ type CommandDefinition,
3
+ type CommandResult,
4
+ createOutputResult,
5
+ } from "@canonical/cli-core";
6
+ import { runTask } from "@canonical/task/node";
7
+ import { readConfig } from "#config";
8
+ import type { PragmaContext } from "../../shared/context.js";
9
+ import { selectFormatter } from "../../shared/formatters.js";
10
+ import { frameworkFormatters } from "../formatters/index.js";
11
+ import { validateFramework } from "../operations/index.js";
12
+ import { setFrameworkTask } from "../tasks/index.js";
13
+ import { resolveConfigScope, SCOPE_PARAMETERS } from "./configScope.js";
14
+
15
+ /**
16
+ * Build the `pragma config framework` command definition.
17
+ *
18
+ * Sets the preferred UI framework (`react` | `svelte` | `lit`). The value is
19
+ * currently advisory only — no behaviour reads it yet; it is persisted for
20
+ * future framework-defaulting. Supports set, reset, and query (no argument).
21
+ *
22
+ * @param ctx - Pragma context providing cwd and formatter selection.
23
+ * @returns The command definition for `pragma config framework`.
24
+ * @note - Impure — validates framework and writes config to disk.
25
+ */
26
+ export default function buildFrameworkCommand(
27
+ ctx: PragmaContext,
28
+ ): CommandDefinition {
29
+ return {
30
+ path: ["config", "framework"],
31
+ description: "Set or reset the preferred UI framework (advisory)",
32
+ parameters: [
33
+ {
34
+ name: "value",
35
+ description: "Framework (react, svelte, lit)",
36
+ type: "string",
37
+ positional: true,
38
+ },
39
+ {
40
+ name: "reset",
41
+ description: "Reset framework to default (none)",
42
+ type: "boolean",
43
+ default: false,
44
+ },
45
+ ...SCOPE_PARAMETERS,
46
+ ],
47
+ meta: {
48
+ examples: [
49
+ "pragma config framework react",
50
+ "pragma config framework --reset",
51
+ ],
52
+ extendedHelp:
53
+ "Advisory only for now: the value is persisted but no command reads it yet. Reserved for future framework defaulting.",
54
+ },
55
+ execute: async (
56
+ params: Record<string, unknown>,
57
+ ): Promise<CommandResult> => {
58
+ const scope = resolveConfigScope(params);
59
+ const reset = params.reset === true;
60
+ const value = params.value as string | undefined;
61
+
62
+ if (reset) {
63
+ const result = await runTask(
64
+ setFrameworkTask(ctx.cwd, undefined, scope),
65
+ );
66
+ const format = selectFormatter(ctx, frameworkFormatters.reset);
67
+ return createOutputResult(
68
+ { field: "framework", path: result.path },
69
+ { plain: format },
70
+ );
71
+ }
72
+
73
+ if (!value) {
74
+ const config = readConfig(ctx.cwd);
75
+ const format = selectFormatter(ctx, frameworkFormatters.query);
76
+ return createOutputResult(config.framework ?? "none", {
77
+ plain: format,
78
+ });
79
+ }
80
+
81
+ const framework = validateFramework(value);
82
+
83
+ const result = await runTask(setFrameworkTask(ctx.cwd, framework, scope));
84
+ const format = selectFormatter(ctx, frameworkFormatters.set);
85
+ return createOutputResult(
86
+ { field: "framework", value: framework, path: result.path },
87
+ { plain: format },
88
+ );
89
+ },
90
+ };
91
+ }
@@ -1,4 +1,6 @@
1
1
  /** @module Config command definitions for `pragma config`. */
2
2
  export { default as channelCommand } from "./channel.js";
3
+ export { default as frameworkCommand } from "./framework.js";
3
4
  export { default as showCommand } from "./show.js";
4
5
  export { default as tierCommand } from "./tier.js";
6
+ export { default as traceCommand } from "./trace.js";