@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
@@ -0,0 +1,156 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { createTestStore, DS_ALL_TTL } from "#testing";
4
+ import listBlocks from "../../block/operations/list.js";
5
+ import listModifiers from "../../modifier/operations/list.js";
6
+ import { TTL_PREFIXES } from "../../shared/prefixes.js";
7
+ import { standardPack } from "../../shared/stories/pack/bundled/standardPack.js";
8
+ import runSelectQuery from "../../shared/stories/pack/runSelectQuery.js";
9
+ import type { FilterConfig } from "../../shared/types/index.js";
10
+ import listTokens from "../../token/operations/list.js";
11
+ import { collectEntityCounts } from "./collectEntityCounts.js";
12
+
13
+ /**
14
+ * The user-facing standard row count: the bundled pack's list query is
15
+ * what `standard list` serves since the standard cutover, so the info
16
+ * count must stay in lockstep with it (same WHERE grain the retired
17
+ * listStandards op used, including the OPTIONAL fan-out).
18
+ */
19
+ async function countStandardRows(store: Store): Promise<number> {
20
+ const rows = await runSelectQuery(store, standardPack.list.query, "test");
21
+ return rows.length;
22
+ }
23
+
24
+ let store: Store;
25
+ let cleanup: () => void;
26
+
27
+ beforeAll(async () => {
28
+ const result = await createTestStore({ ttl: DS_ALL_TTL });
29
+ store = result.store;
30
+ cleanup = result.cleanup;
31
+ });
32
+
33
+ afterAll(() => cleanup());
34
+
35
+ // The block count is the only filtered count, so its parity must hold under
36
+ // every tier/channel combination the list op supports.
37
+ const configs: Record<string, FilterConfig> = {
38
+ "no tier, prerelease": { tier: undefined, channel: "prerelease" },
39
+ "no tier, normal": { tier: undefined, channel: "normal" },
40
+ "global tier": { tier: "global", channel: "prerelease" },
41
+ "apps/lxd tier": { tier: "apps/lxd", channel: "normal" },
42
+ };
43
+
44
+ describe("collectEntityCounts", () => {
45
+ it.each(
46
+ Object.entries(configs),
47
+ )("matches legacy list-op lengths for %s", async (_label, config) => {
48
+ const counts = await collectEntityCounts(store, config);
49
+
50
+ expect(counts.blocks).toBe((await listBlocks(store, config)).length);
51
+ expect(counts.standards).toBe(await countStandardRows(store));
52
+ expect(counts.modifierFamilies).toBe((await listModifiers(store)).length);
53
+ expect(counts.tokens).toBe((await listTokens(store)).length);
54
+ });
55
+
56
+ it("returns positive counts on the full fixture", async () => {
57
+ const counts = await collectEntityCounts(store, {
58
+ tier: undefined,
59
+ channel: "prerelease",
60
+ });
61
+
62
+ expect(counts.blocks).toBeGreaterThan(0);
63
+ expect(counts.standards).toBeGreaterThan(0);
64
+ expect(counts.modifierFamilies).toBeGreaterThan(0);
65
+ expect(counts.tokens).toBeGreaterThan(0);
66
+ });
67
+ });
68
+
69
+ // A dedicated fan-out store (kept separate from the shared dsFixtures so its
70
+ // enriched cardinality does not ripple into other suites). Every entity fans
71
+ // its list op to MORE rows than there are distinct subjects, which is exactly
72
+ // where a single-variable `COUNT(DISTINCT ?subject)` would undercount — so
73
+ // these fixtures pin the grain-faithful subquery counts (FIX 4).
74
+ const FAN_OUT_TTL = `
75
+ ${TTL_PREFIXES}
76
+
77
+ # One block asserted as TWO types -> two GROUP BY (?component ?type ?name ?tier)
78
+ # groups, so listBlocks emits two rows for a single subject.
79
+ ds:fanout.multitype a ds:Component, ds:Pattern ;
80
+ ds:name "MultiType" ;
81
+ ds:tier ds:global ;
82
+ ds:release ds:stable .
83
+
84
+ # A beta-only single-type block -> present under prerelease, filtered under
85
+ # normal, so the block count also tracks channel scoping.
86
+ ds:fanout.solo a ds:Component ;
87
+ ds:name "Solo" ;
88
+ ds:tier ds:global ;
89
+ ds:release ds:beta .
90
+
91
+ # One modifier family with TWO names -> two GROUP BY (?family ?name) groups.
92
+ ds:fanout.family a ds:ModifierFamily ;
93
+ ds:name "Alpha", "Beta" .
94
+
95
+ # One standard in TWO categories -> the OPTIONAL hasCategory fans two rows.
96
+ cs:fanout_cat_a a cs:Category ; cs:slug "cat-a" .
97
+ cs:fanout_cat_b a cs:Category ; cs:slug "cat-b" .
98
+ cs:fanout_std a cs:CodeStandard ;
99
+ cs:name "fanout/standard" ;
100
+ cs:description "A standard in two categories." ;
101
+ cs:hasCategory cs:fanout_cat_a, cs:fanout_cat_b .
102
+
103
+ # One token with TWO token types -> the OPTIONAL tokenType fans two rows.
104
+ ds:fanout_tt_a a ds:TokenType ; rdfs:label "TypeA" .
105
+ ds:fanout_tt_b a ds:TokenType ; rdfs:label "TypeB" .
106
+ ds:fanout.token a ds:Token ;
107
+ ds:tokenId "fanout.token" ;
108
+ ds:tokenType ds:fanout_tt_a, ds:fanout_tt_b .
109
+ `;
110
+
111
+ describe("collectEntityCounts fan-out parity", () => {
112
+ let fanStore: Store;
113
+ let fanCleanup: () => void;
114
+
115
+ beforeAll(async () => {
116
+ const result = await createTestStore({ ttl: FAN_OUT_TTL });
117
+ fanStore = result.store;
118
+ fanCleanup = result.cleanup;
119
+ });
120
+
121
+ afterAll(() => fanCleanup());
122
+
123
+ const fanConfigs: Record<string, FilterConfig> = {
124
+ prerelease: { tier: undefined, channel: "prerelease" },
125
+ normal: { tier: undefined, channel: "normal" },
126
+ };
127
+
128
+ it.each(
129
+ Object.entries(fanConfigs),
130
+ )("each count equals its legacy list-op length under %s channel", async (_label, config) => {
131
+ const counts = await collectEntityCounts(fanStore, config);
132
+
133
+ expect(counts.blocks).toBe((await listBlocks(fanStore, config)).length);
134
+ expect(counts.standards).toBe(await countStandardRows(fanStore));
135
+ expect(counts.modifierFamilies).toBe(
136
+ (await listModifiers(fanStore)).length,
137
+ );
138
+ expect(counts.tokens).toBe((await listTokens(fanStore)).length);
139
+ });
140
+
141
+ it("actually fans each entity beyond its distinct-subject count", async () => {
142
+ // These are the cardinalities a naive `COUNT(DISTINCT ?subject)` would
143
+ // undercount (blocks 3 vs 2 subjects, families 2 vs 1, etc.).
144
+ const config: FilterConfig = { tier: undefined, channel: "prerelease" };
145
+ expect((await listBlocks(fanStore, config)).length).toBe(3);
146
+ expect((await listModifiers(fanStore)).length).toBe(2);
147
+ expect(await countStandardRows(fanStore)).toBe(2);
148
+ expect((await listTokens(fanStore)).length).toBe(2);
149
+
150
+ // And under normal channel the beta block drops out (2 block rows).
151
+ expect(
152
+ (await listBlocks(fanStore, { tier: undefined, channel: "normal" }))
153
+ .length,
154
+ ).toBe(2);
155
+ });
156
+ });
@@ -0,0 +1,123 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { buildQuery } from "../../shared/buildQuery.js";
3
+ import { buildFilters } from "../../shared/filters/buildFilters.js";
4
+ import { P } from "../../shared/prefixes.js";
5
+ import type { FilterConfig } from "../../shared/types/index.js";
6
+ import type { EntityCounts } from "../types.js";
7
+
8
+ /**
9
+ * Run a `COUNT` query and parse its single `?count` binding.
10
+ *
11
+ * SPARQL `COUNT` yields a typed literal serialized as a string, so the
12
+ * value is parsed defensively and falls back to `0` when the query does
13
+ * not resolve to a select result.
14
+ *
15
+ * @param store - The ke store to query.
16
+ * @param query - A `SELECT (COUNT(...) AS ?count)` query string.
17
+ * @returns The parsed count, or `0` when unavailable.
18
+ * @note Queries ke store
19
+ */
20
+ async function countRows(store: Store, query: string): Promise<number> {
21
+ const result = await store.query(buildQuery(query));
22
+ if (result.type !== "select") return 0;
23
+ const first = result.bindings[0];
24
+ return first ? Number.parseInt(first.count ?? "0", 10) || 0 : 0;
25
+ }
26
+
27
+ /**
28
+ * Count blocks, standards, modifier families, and tokens for the
29
+ * `pragma llm` orientation summary.
30
+ *
31
+ * Each `COUNT` reproduces the row cardinality of the matching leaf list
32
+ * operation, so the results equal `(await listX(...)).length` exactly:
33
+ *
34
+ * - **blocks** count the distinct `(?component, ?type, ?name, ?tier)` groups
35
+ * `listBlocks` emits via `GROUP BY ?component ?type ?name ?tier`, so a
36
+ * `COUNT(*)` over a `SELECT DISTINCT` of that exact grain (incl.
37
+ * `buildFilters` for identical tier/channel scoping). This stays exact even
38
+ * for a block asserted as several types or tiers, which would fan a
39
+ * single-variable `COUNT(DISTINCT ?component)` short of the grouped rows.
40
+ * - **modifier families** count the distinct `(?family, ?name)` groups
41
+ * `listModifiers` emits via `GROUP BY ?family ?name`, so a `COUNT(*)` over a
42
+ * `SELECT DISTINCT` of that grain — exact even for a family with two names.
43
+ * - **standards** and **tokens** have no `GROUP BY` and an `OPTIONAL` that
44
+ * fans rows out, so `COUNT(*)` over the identical `WHERE` matches row for
45
+ * row (including the fan-out).
46
+ *
47
+ * @note Queries ke store
48
+ *
49
+ * @param store - The ke store to query.
50
+ * @param config - Filter config providing tier and channel settings.
51
+ * @returns Entity counts matching the four leaf list operations' lengths.
52
+ */
53
+ async function collectEntityCounts(
54
+ store: Store,
55
+ config: FilterConfig,
56
+ ): Promise<EntityCounts> {
57
+ const filterClauses = buildFilters(config);
58
+
59
+ const [blocks, standards, modifierFamilies, tokens] = await Promise.all([
60
+ countRows(
61
+ store,
62
+ `
63
+ SELECT (COUNT(*) AS ?count)
64
+ WHERE {
65
+ SELECT DISTINCT ?component ?type ?name ?tier
66
+ WHERE {
67
+ VALUES ?type { ${P.ds}Component ${P.ds}Pattern ${P.ds}Layout ${P.ds}Subcomponent }
68
+ ?component a ?type ;
69
+ ${P.ds}name ?name ;
70
+ ${P.ds}tier ?tier .
71
+ ${filterClauses}
72
+ }
73
+ }
74
+ `,
75
+ ),
76
+ countRows(
77
+ store,
78
+ `
79
+ SELECT (COUNT(*) AS ?count)
80
+ WHERE {
81
+ ?standard a ${P.cs}CodeStandard ;
82
+ ${P.cs}name ?name ;
83
+ ${P.cs}description ?description .
84
+ OPTIONAL {
85
+ ?standard ${P.cs}hasCategory ?cat .
86
+ ?cat ${P.cs}slug ?categoryName .
87
+ }
88
+ }
89
+ `,
90
+ ),
91
+ countRows(
92
+ store,
93
+ `
94
+ SELECT (COUNT(*) AS ?count)
95
+ WHERE {
96
+ SELECT DISTINCT ?family ?name
97
+ WHERE {
98
+ ?family a ${P.ds}ModifierFamily ;
99
+ ${P.ds}name ?name .
100
+ }
101
+ }
102
+ `,
103
+ ),
104
+ countRows(
105
+ store,
106
+ `
107
+ SELECT (COUNT(*) AS ?count)
108
+ WHERE {
109
+ ?token a ${P.ds}Token ;
110
+ ${P.ds}tokenId ?tokenId .
111
+ OPTIONAL {
112
+ ?token ${P.ds}tokenType ?type .
113
+ ?type ${P.rdfs}label ?typeName .
114
+ }
115
+ }
116
+ `,
117
+ ),
118
+ ]);
119
+
120
+ return { blocks, standards, modifierFamilies, tokens };
121
+ }
122
+
123
+ export { collectEntityCounts };
@@ -1,6 +1,6 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
- const readConfigMock = vi.fn();
3
+ const readConfigLayersMock = vi.fn();
4
4
  const detectInstallSourceMock = vi.fn();
5
5
  const bootStoreMock = vi.fn();
6
6
  const checkRegistryVersionMock = vi.fn();
@@ -8,7 +8,7 @@ const collectStoreSummaryMock = vi.fn();
8
8
 
9
9
  vi.mock("#config", async (importOriginal) => ({
10
10
  ...(await importOriginal<typeof import("#config")>()),
11
- readConfig: readConfigMock,
11
+ readConfigLayers: readConfigLayersMock,
12
12
  }));
13
13
 
14
14
  vi.mock("#package-manager", async (importOriginal) => {
@@ -46,7 +46,18 @@ describe("collectInfo", () => {
46
46
  scope: "global",
47
47
  label: "bun (global)",
48
48
  });
49
- readConfigMock.mockReturnValue({ tier: "apps/lxd", channel: "normal" });
49
+ readConfigLayersMock.mockReturnValue({
50
+ config: { tier: "apps/lxd", channel: "normal" },
51
+ origins: {
52
+ tier: "project",
53
+ channel: "default",
54
+ packages: "default",
55
+ trace: "default",
56
+ framework: "default",
57
+ },
58
+ global: { path: "/home/u/.config/pragma/config.json", exists: false },
59
+ project: { path: "/repo/pragma.config.json", exists: true },
60
+ });
50
61
  });
51
62
 
52
63
  it("includes update information and store summary when available", async () => {
@@ -1,15 +1,14 @@
1
- import { readConfig } from "#config";
1
+ import { readConfigLayers } from "#config";
2
2
  import { VERSION } from "#constants";
3
3
  import { detectInstallSource, PM_COMMANDS } from "#package-manager";
4
- import {
5
- type PackageRef,
6
- parsePackageEntry,
4
+ import type {
5
+ PackageRef,
6
+ RawPackageEntry,
7
7
  } from "../../refs/operations/parseRef.js";
8
- import readGlobalRefs from "../../refs/operations/readGlobalRefs.js";
9
8
  import { bootStore } from "../../shared/bootStore.js";
10
9
  import { CHANNEL_RELEASES } from "../../shared/filters/buildChannelFilter.js";
11
10
  import { resolveTierChain } from "../../shared/filters/buildTierFilter.js";
12
- import { DEFAULT_PACKAGES } from "../../shared/packages.js";
11
+ import { mergeAndParseRefs } from "../../shared/mergeAndParseRefs.js";
13
12
  import type { InfoData, PackageRefSummary } from "../types.js";
14
13
  import checkRegistryVersion from "./checkRegistryVersion.js";
15
14
  import { collectStoreSummary } from "./collectStoreSummary.js";
@@ -28,7 +27,8 @@ import { collectStoreSummary } from "./collectStoreSummary.js";
28
27
  export default async function collectInfo(cwd: string): Promise<InfoData> {
29
28
  const install = detectInstallSource();
30
29
  const pm = install.packageManager;
31
- const config = readConfig(cwd);
30
+ const layers = readConfigLayers(cwd);
31
+ const config = layers.config;
32
32
  const tierChain = resolveTierChain(config.tier);
33
33
  const channelReleases = CHANNEL_RELEASES[config.channel];
34
34
 
@@ -65,7 +65,9 @@ export default async function collectInfo(cwd: string): Promise<InfoData> {
65
65
  version: VERSION,
66
66
  pm,
67
67
  installSource: install.label,
68
- configPath: "pragma.config.json",
68
+ configPath: layers.project.exists
69
+ ? layers.project.path
70
+ : layers.global.path,
69
71
  tier: config.tier,
70
72
  tierChain,
71
73
  channel: config.channel,
@@ -78,14 +80,9 @@ export default async function collectInfo(cwd: string): Promise<InfoData> {
78
80
  }
79
81
 
80
82
  function collectPackageRefSummaries(
81
- projectPackages?: ReadonlyArray<
82
- string | { readonly name: string; readonly source?: string }
83
- >,
83
+ configPackages?: ReadonlyArray<RawPackageEntry>,
84
84
  ): PackageRefSummary[] {
85
- const entries = projectPackages ?? readGlobalRefs();
86
- const raw = entries.length > 0 ? entries : DEFAULT_PACKAGES.map((pkg) => pkg);
87
-
88
- return raw.map((entry) => refToSummary(parsePackageEntry(entry)));
85
+ return mergeAndParseRefs(configPackages).map(refToSummary);
89
86
  }
90
87
 
91
88
  function refToSummary(ref: PackageRef): PackageRefSummary {
@@ -1,5 +1,6 @@
1
1
  /** @module Barrel for info operations. */
2
2
 
3
3
  export { default as checkRegistryVersion } from "./checkRegistryVersion.js";
4
+ export { collectEntityCounts } from "./collectEntityCounts.js";
4
5
  export { default as collectInfo } from "./collectInfo.js";
5
6
  export { collectStoreSummary } from "./collectStoreSummary.js";
@@ -52,3 +52,16 @@ export interface StoreSummary {
52
52
  readonly tripleCount: number;
53
53
  readonly graphNames: readonly string[];
54
54
  }
55
+
56
+ /**
57
+ * Entity counts for the `pragma llm` orientation summary.
58
+ *
59
+ * Mirrors the row cardinality of the four leaf list operations
60
+ * (`listBlocks`, `listStandards`, `listModifiers`, `listTokens`).
61
+ */
62
+ export interface EntityCounts {
63
+ readonly blocks: number;
64
+ readonly standards: number;
65
+ readonly modifierFamilies: number;
66
+ readonly tokens: number;
67
+ }
@@ -1,9 +1,14 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { VERSION } from "#constants";
3
+ import { TOKEN_READ_SURFACE_ENABLED } from "../token/featureFlag.js";
3
4
  import { buildCapabilitiesCommand } from "./commands/index.js";
4
5
  import { TOOL_CATALOG } from "./data/index.js";
5
6
  import { buildCapabilitiesData } from "./mcp/index.js";
6
7
 
8
+ // token_list, token_lookup, and token_sample are gated behind the token
9
+ // read-surface feature flag.
10
+ const EXPECTED_TOOL_COUNT = TOKEN_READ_SURFACE_ENABLED ? 34 : 31;
11
+
7
12
  describe("buildCapabilitiesData", () => {
8
13
  it("returns correct version", () => {
9
14
  const data = buildCapabilitiesData();
@@ -26,10 +31,10 @@ describe("buildCapabilitiesData", () => {
26
31
  expect(data.discovery_sequence[2]?.tool).toBe("domain tools");
27
32
  });
28
33
 
29
- it("includes all 29 tools from TOOL_CATALOG", () => {
34
+ it("includes every tool from TOOL_CATALOG", () => {
30
35
  const data = buildCapabilitiesData();
31
36
  expect(data.tools).toHaveLength(TOOL_CATALOG.length);
32
- expect(data.tools).toHaveLength(29);
37
+ expect(data.tools).toHaveLength(EXPECTED_TOOL_COUNT);
33
38
  });
34
39
 
35
40
  it("every tool has a non-empty use_when", () => {
@@ -41,7 +46,7 @@ describe("buildCapabilitiesData", () => {
41
46
 
42
47
  it("counts match tool catalog category lengths", () => {
43
48
  const data = buildCapabilitiesData();
44
- expect(data.counts.total).toBe(29);
49
+ expect(data.counts.total).toBe(EXPECTED_TOOL_COUNT);
45
50
  expect(data.counts.read).toBe(
46
51
  TOOL_CATALOG.filter((t) => t.category === "read").length,
47
52
  );
@@ -79,7 +84,22 @@ describe("buildCapabilitiesCommand", () => {
79
84
  expect(text).toContain("Discovery Sequence");
80
85
  expect(text).toContain("block_list");
81
86
  expect(text).toContain("read");
82
- expect(text).toContain("29 tools");
87
+ expect(text).toContain(`${EXPECTED_TOOL_COUNT} tools`);
88
+ });
89
+
90
+ it("emits valid JSON when --format json is requested", async () => {
91
+ const cmd = buildCapabilitiesCommand();
92
+ const ctx = {
93
+ cwd: "/test",
94
+ globalFlags: { llm: false, format: "json" as const, verbose: false },
95
+ };
96
+
97
+ const result = await cmd.execute({}, ctx);
98
+ if (result.tag !== "output") throw new Error("Expected output result");
99
+
100
+ const text = result.render.plain(result.value) as string;
101
+ expect(() => JSON.parse(text)).not.toThrow();
102
+ expect(JSON.parse(text)).toMatchObject({ version: expect.any(String) });
83
103
  });
84
104
 
85
105
  it("does not require a store or runtime", async () => {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type CommandContext,
2
3
  type CommandDefinition,
3
4
  type CommandResult,
4
5
  createOutputResult,
@@ -10,7 +11,8 @@ import { buildCapabilitiesData } from "../mcp/specs.js";
10
11
  * Builds the `pragma capabilities` command definition.
11
12
  *
12
13
  * Returns a static system map: conventions, tool catalog with behavioral
13
- * hints, discovery sequence, and output modes. No store needed.
14
+ * hints, discovery sequence, and output modes. No store needed. Honours
15
+ * `--format json` by emitting the structured payload so agents can parse it.
14
16
  *
15
17
  * @returns A command definition for `capabilities`.
16
18
  */
@@ -20,9 +22,16 @@ export default function buildCapabilitiesCommand(): CommandDefinition {
20
22
  description:
21
23
  "Discover conventions, available tools, and discovery sequence",
22
24
  parameters: [],
23
- execute: async (): Promise<CommandResult> => {
25
+ execute: async (
26
+ _params: Record<string, unknown>,
27
+ ctx: CommandContext,
28
+ ): Promise<CommandResult> => {
24
29
  const data = buildCapabilitiesData();
25
- return createOutputResult(data, { plain: renderCapabilities });
30
+ const asJson = ctx?.globalFlags?.format === "json";
31
+ return createOutputResult(data, {
32
+ plain: (value) =>
33
+ asJson ? JSON.stringify(value, null, 2) : renderCapabilities(value),
34
+ });
26
35
  },
27
36
  };
28
37
  }
@@ -31,7 +31,11 @@ export default function buildLlmCommand(ctx: PragmaContext): CommandDefinition {
31
31
  decisionTrees: DECISION_TREES,
32
32
  commandReference: COMMAND_REFERENCE,
33
33
  };
34
- return createOutputResult(data, { plain: renderLlmOrientation });
34
+ const asJson = ctx?.globalFlags?.format === "json";
35
+ return createOutputResult(data, {
36
+ plain: (value) =>
37
+ asJson ? JSON.stringify(value, null, 2) : renderLlmOrientation(value),
38
+ });
35
39
  },
36
40
  };
37
41
  }
@@ -1,3 +1,4 @@
1
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../token/featureFlag.js";
1
2
  import type { CommandRefEntry } from "../types.js";
2
3
  import { TOOL_CATALOG } from "./toolCatalog.js";
3
4
 
@@ -60,17 +61,25 @@ export const COMMAND_REFERENCE: readonly CommandRefEntry[] = [
60
61
  tokens: "~80",
61
62
  use_when: hintFor("modifier_lookup"),
62
63
  },
63
- {
64
- command: "token sample",
65
- tokens: "~300",
66
- use_when: hintFor("token_sample"),
67
- },
68
- { command: "token list", tokens: "~400", use_when: hintFor("token_list") },
69
- {
70
- command: "token lookup <name-or-iri...> --detailed",
71
- tokens: "~150",
72
- use_when: hintFor("token_lookup"),
73
- },
64
+ ...(TOKEN_READ_SURFACE_ENABLED
65
+ ? ([
66
+ {
67
+ command: "token sample",
68
+ tokens: "~300",
69
+ use_when: hintFor("token_sample"),
70
+ },
71
+ {
72
+ command: "token list",
73
+ tokens: "~400",
74
+ use_when: hintFor("token_list"),
75
+ },
76
+ {
77
+ command: "token lookup <name-or-iri...> --detailed",
78
+ tokens: "~150",
79
+ use_when: hintFor("token_lookup"),
80
+ },
81
+ ] as const)
82
+ : []),
74
83
  { command: "tier list", tokens: "~50", use_when: hintFor("tier_list") },
75
84
  {
76
85
  command: "ontology list",
@@ -1,3 +1,4 @@
1
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../token/featureFlag.js";
1
2
  import type { DecisionTree } from "../types.js";
2
3
 
3
4
  /**
@@ -26,16 +27,20 @@ export const DECISION_TREES: readonly DecisionTree[] = [
26
27
  no → standard list --category <cat> [~100]
27
28
  → standard lookup <std-iri...> --detailed [~400] Σ500`,
28
29
  },
29
- {
30
- intent: "Find a token",
31
- tree: `? Know token IRI or name?
30
+ ...(TOKEN_READ_SURFACE_ENABLED
31
+ ? ([
32
+ {
33
+ intent: "Find a token",
34
+ tree: `? Know token IRI or name?
32
35
  yes → token lookup <name-or-iri...> --detailed [~150]
33
36
  no → token list --category <cat> [~100]
34
37
  → token lookup <name-or-iri...> --detailed [~150] Σ250`,
35
- },
38
+ },
39
+ ] as const)
40
+ : []),
36
41
  {
37
42
  intent: "Explore the design system",
38
- tree: `first time → block sample / token sample / modifier sample [~300] → see real shapes
43
+ tree: `first time → block sample / ${TOKEN_READ_SURFACE_ENABLED ? "token sample / " : ""}modifier sample [~300] → see real shapes
39
44
  high-level → ontology list [~80] → ontology show <ns> [~300]
40
45
  modifiers → modifier list [~100] → modifier lookup <name-or-iri...> [~80]
41
46
  tiers → tier list [~50]