@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
package/README.md CHANGED
@@ -22,7 +22,7 @@ See [docs/getting-started.md](docs/getting-started.md) for a full walkthrough.
22
22
 
23
23
  ## CLI Commands
24
24
 
25
- pragma organizes commands into 14 domains. Every command supports three output modes: plain text (default), `--llm` (condensed Markdown), and `--format json` (structured JSON).
25
+ pragma organizes commands into 17 domains — counting every directory under `src/domains/` except `shared/`, which holds cross-domain infrastructure rather than commands. The day-to-day domains are documented below; two are specialised and not covered here: `graphql` (compile TTL ontologies into GraphQL schema artifacts) and `trace` (query access tracing). Every command supports three output modes: plain text (default), `--llm` (condensed Markdown), and `--format json` (structured JSON).
26
26
 
27
27
  ### Block
28
28
 
@@ -36,7 +36,7 @@ pragma organizes commands into 14 domains. Every command supports three output m
36
36
  | Command | Description |
37
37
  |---------|-------------|
38
38
  | `pragma standard list` | List all code standards |
39
- | `pragma standard lookup <name>` | Show standard with do/don't examples |
39
+ | `pragma standard lookup <name>` | Show standard details (`--detailed` adds do/don't examples) |
40
40
  | `pragma standard categories` | List standard categories with counts |
41
41
 
42
42
  ### Modifier
@@ -46,13 +46,17 @@ pragma organizes commands into 14 domains. Every command supports three output m
46
46
  | `pragma modifier list` | List all modifier families |
47
47
  | `pragma modifier lookup <name>` | Show modifier family with values |
48
48
 
49
- ### Token
49
+ ### Tokens
50
50
 
51
51
  | Command | Description |
52
52
  |---------|-------------|
53
- | `pragma token list` | List all design tokens |
54
- | `pragma token lookup <name>` | Show token with theme values |
55
- | `pragma token add-config` | Add Terrazzo config for token build |
53
+ | `pragma tokens add-config` | Add Terrazzo config for token build |
54
+
55
+ > **Note:** The token *read* commands (`token list`, `token lookup`, `token sample`)
56
+ > are currently disabled behind a feature flag
57
+ > (`src/domains/token/featureFlag.ts`) because the published design-system
58
+ > data does not yet contain token instances. They will return once token
59
+ > data ships.
56
60
 
57
61
  ### Tier
58
62
 
@@ -65,7 +69,10 @@ pragma organizes commands into 14 domains. Every command supports three output m
65
69
  | Command | Description |
66
70
  |---------|-------------|
67
71
  | `pragma ontology list` | List loaded ontology namespaces |
68
- | `pragma ontology show <prefix>` | Show classes and properties for a namespace |
72
+ | `pragma ontology show <prefix>` | Show the TBox: name, metadata, class hierarchy tree with instance counts and relations |
73
+ | `pragma ontology show <prefix> --properties` | Also show datatype properties (attributes) |
74
+ | `pragma ontology show <prefix> --full-uris` | Show full URIs instead of compact prefixed IRIs |
75
+ | `pragma ontology show <prefix> --class <Class>` | Deep-dive into one class: super chain, direct/inherited properties, reverse references, sample instances, and ready-to-run follow-up queries |
69
76
 
70
77
  ### Graph
71
78
 
@@ -87,6 +94,7 @@ pragma organizes commands into 14 domains. Every command supports three output m
87
94
  | Command | Description |
88
95
  |---------|-------------|
89
96
  | `pragma skill list` | List discovered agent skills from installed packages |
97
+ | `pragma skill lookup <name...>` | Show full SKILL.md instructions for skills by name |
90
98
 
91
99
  ### LLM
92
100
 
@@ -96,15 +104,16 @@ pragma organizes commands into 14 domains. Every command supports three output m
96
104
 
97
105
  ### Setup
98
106
 
99
- All setup commands support `--dry-run`, `--yes`, and `--undo`.
107
+ All setup commands support `--dry-run`; `--yes` and `--undo` are supported
108
+ where shown below.
100
109
 
101
110
  | Command | Description |
102
111
  |---------|-------------|
103
- | `pragma setup all` | Run all setup steps (MCP + completions + skills) |
104
- | `pragma setup mcp` | Configure pragma MCP server for AI harnesses |
105
- | `pragma setup completions` | Install shell completions (bash/zsh/fish) |
106
- | `pragma setup skills` | Symlink agent skills into harness config |
107
- | `pragma setup lsp` | Configure LSP integration |
112
+ | `pragma setup` | Run all setup steps (completions + LSP + MCP); supports `--yes`, `--undo` |
113
+ | `pragma setup mcp` | Configure pragma MCP server for AI harnesses; supports `--yes`, `--undo` |
114
+ | `pragma setup completions` | Install shell completions (bash/zsh/fish); supports `--undo` |
115
+ | `pragma setup skills` | Symlink agent skills into harness config; supports `--yes` |
116
+ | `pragma setup lsp` | Install the Terrazzo LSP VS Code extension; supports `--undo` |
108
117
 
109
118
  ### Create
110
119
 
@@ -112,8 +121,9 @@ Create commands scaffold new code using summon generators. They support `--undo`
112
121
 
113
122
  | Command | Description |
114
123
  |---------|-------------|
115
- | `pragma create component <name>` | Scaffold a new design system component |
116
- | `pragma create package <name>` | Scaffold a new monorepo package |
124
+ | `pragma create component <framework> [path]` | Scaffold a new design system component |
125
+ | `pragma create package` | Scaffold a new monorepo package (`--name`, `--type`, …) |
126
+ | `pragma create application [path]` | Scaffold a complete React application with SSR and routing |
117
127
 
118
128
  ### Doctor
119
129
 
@@ -125,8 +135,19 @@ Create commands scaffold new code using summon generators. They support `--undo`
125
135
 
126
136
  | Command | Description |
127
137
  |---------|-------------|
128
- | `pragma info` | Show pragma version, store summary, installed packages |
129
- | `pragma info upgrade` | Check for newer pragma versions on the registry |
138
+ | `pragma info` | Show pragma version, config, update status, and store summary |
139
+
140
+ ### Upgrade
141
+
142
+ | Command | Description |
143
+ |---------|-------------|
144
+ | `pragma upgrade` | Upgrade the pragma CLI to the latest version (`--dry-run` to check only) |
145
+
146
+ ### Refs
147
+
148
+ | Command | Description |
149
+ |---------|-------------|
150
+ | `pragma update-refs` | Fetch or clone git-referenced semantic packages into the local cache (needed before `skill list` can discover skills) |
130
151
 
131
152
  ## Global Flags
132
153
 
@@ -143,7 +164,7 @@ Setup and create commands support `--undo` to reverse previous operations. The u
143
164
  ```bash
144
165
  pragma setup mcp --undo # Remove MCP configuration
145
166
  pragma setup completions --undo # Remove shell completions
146
- pragma create component Foo --undo # Remove scaffolded component files
167
+ pragma create component react src/lib/Foo --undo # Remove scaffolded component files
147
168
  ```
148
169
 
149
170
  ## MCP Integration
@@ -173,27 +194,27 @@ Or add manually to your `.mcp.json`:
173
194
  }
174
195
  ```
175
196
 
176
- ### MCP Tools (29)
197
+ ### MCP Tools (28)
177
198
 
178
199
  All tools return a consistent envelope: `{ ok: true, data, meta }` for success, `{ ok: true, condensed: true, text, tokens }` for condensed mode, or `{ ok: false, error }` with structured recovery on failure.
179
200
 
180
- #### Read Tools (20)
201
+ #### Read Tools (18)
202
+
203
+ Lookup tools accept a `names` array, so several entities can be fetched in
204
+ one call (partial results are returned alongside per-name errors).
181
205
 
182
206
  | Tool | Description |
183
207
  |------|-------------|
184
208
  | `block_list` | List blocks with optional tier filtering and disclosure levels |
185
209
  | `block_lookup` | Look up detailed block info (anatomy, modifiers, tokens, standards) |
186
- | `block_batch_lookup` | Look up multiple blocks by name in a single call |
210
+ | `block_sample` | Return random complete block instances for shape discovery |
187
211
  | `standard_list` | List code standards with optional category/search filtering |
188
212
  | `standard_lookup` | Look up standard with do/don't code examples |
189
- | `standard_batch_lookup` | Look up multiple standards by name in a single call |
190
213
  | `standard_categories` | List standard categories with counts |
214
+ | `standard_sample` | Return random complete standard instances for shape discovery |
191
215
  | `modifier_list` | List modifier families |
192
216
  | `modifier_lookup` | Look up modifier family with values |
193
- | `modifier_batch_lookup` | Look up multiple modifier families by name in a single call |
194
- | `token_list` | List design tokens with optional category filtering |
195
- | `token_lookup` | Look up token with theme-resolved values |
196
- | `token_batch_lookup` | Look up multiple tokens by name in a single call |
217
+ | `modifier_sample` | Return random complete modifier instances for shape discovery |
197
218
  | `tier_list` | List all tiers in the hierarchy |
198
219
  | `config_show` | Show current tier and channel configuration |
199
220
  | `ontology_list` | List loaded ontology namespaces with class/property counts |
@@ -201,8 +222,12 @@ All tools return a consistent envelope: `{ ok: true, data, meta }` for success,
201
222
  | `graph_query` | Execute raw SPARQL against the ke store |
202
223
  | `graph_inspect` | Inspect all triples for a URI |
203
224
  | `skill_list` | List discovered agent skills |
225
+ | `skill_lookup` | Get full SKILL.md content for skills by name |
204
226
 
205
- #### Write Tools (5)
227
+ `token_list`, `token_lookup`, and `token_sample` are feature-flagged off
228
+ until token data ships (see the Tokens note above).
229
+
230
+ #### Write Tools (6)
206
231
 
207
232
  | Tool | Description |
208
233
  |------|-------------|
@@ -211,6 +236,7 @@ All tools return a consistent envelope: `{ ok: true, data, meta }` for success,
211
236
  | `tokens_add_config` | Add Terrazzo token build configuration |
212
237
  | `create_component` | Scaffold a new design system component |
213
238
  | `create_package` | Scaffold a new monorepo package |
239
+ | `create_application` | Scaffold a complete React application |
214
240
 
215
241
  #### Orientation Tools (2)
216
242
 
@@ -280,12 +306,12 @@ Error responses include a `recovery` object that tells agents what tool to call
280
306
 
281
307
  ### Batch Results
282
308
 
283
- List tools that accept a `names` filter return partial results — valid items in `results`, failures in `errors`:
309
+ Lookup tools accept a `names` array and return partial results — valid items in `results`, failures in `errors`:
284
310
 
285
311
  ```json
286
312
  {
287
313
  "results": [{ "name": "Button", ... }],
288
- "errors": [{ "name": "Buton", "code": "ENTITY_NOT_FOUND", "message": "..." }]
314
+ "errors": [{ "query": "Buton", "code": "ENTITY_NOT_FOUND", "message": "...", "suggestions": ["Button"] }]
289
315
  }
290
316
  ```
291
317
 
@@ -295,27 +321,82 @@ Every subject URI in the ke graph is exposed as a discoverable MCP resource via
295
321
 
296
322
  ## Configuration
297
323
 
298
- Create a `pragma.config.toml` in your project root:
324
+ Configuration is layered: built-in defaults, then the global file at
325
+ `$XDG_CONFIG_HOME/pragma/config.json` (default `~/.config/pragma/config.json`),
326
+ then the nearest `pragma.config.json` at or above the current directory —
327
+ each field won by the most specific layer that sets it. Install pragma
328
+ globally, configure once, and override per project:
299
329
 
300
- ```toml
301
- tier = "apps/lxd"
302
- channel = "normal" # normal | experimental | prerelease
330
+ ```json
331
+ {
332
+ "tier": "apps/lxd",
333
+ "channel": "normal"
334
+ }
335
+ ```
336
+
337
+ `pragma config tier|channel|framework|trace` writes to the nearest existing
338
+ project file, or to the global file when no project is configured; pass
339
+ `--local` or `--global` to pick the layer explicitly. Every write echoes the
340
+ file it wrote, and `pragma config show` reports which layer supplied each
341
+ value.
342
+
343
+ ### Custom read stories (experimental)
344
+
345
+ A config (or a semantic package, via `stories/*.json`) can declare
346
+ declarative read stories — any ontology loaded through `packages` gets
347
+ `pragma <noun> list` / `<noun> lookup` on both the CLI and MCP:
348
+
349
+ ```json
350
+ {
351
+ "prefixes": { "ex": "http://example.org/recipes/" },
352
+ "packages": [{ "name": "my-recipes", "source": "file:///data/recipes" }],
353
+ "stories": [
354
+ {
355
+ "noun": "recipe",
356
+ "description": "List recipes",
357
+ "list": {
358
+ "query": "SELECT ?uri ?name ?category WHERE { ?uri a ex:Recipe ; ex:name ?name ; ex:category ?category } ORDER BY ?name",
359
+ "columns": [{ "field": "name" }, { "field": "category" }],
360
+ "filters": [
361
+ { "param": "category", "variable": "category", "values": ["breakfast", "soup"] }
362
+ ]
363
+ },
364
+ "lookup": {
365
+ "type": "ex:Recipe",
366
+ "by": "ex:name",
367
+ "fields": [{ "name": "category", "property": "ex:category" }],
368
+ "sections": [{ "name": "instructions", "property": "ex:instructions" }]
369
+ }
370
+ }
371
+ ]
372
+ }
303
373
  ```
304
374
 
305
- When no config file is present, pragma defaults to no tier and `normal` channel.
375
+ The lookup query is generated (user input is escaped, never interpolated by
376
+ the pack), the store stays read-only, and config-declared stories override
377
+ package-shipped ones on noun collisions. Declared `filters` become
378
+ `pragma recipe list --category soup` on the CLI and an enum parameter on the
379
+ MCP tool; they are row predicates applied after the query runs, so filter
380
+ input can never inject SPARQL and the query's ordering is preserved. The
381
+ format is experimental and may change.
306
382
 
307
383
  ## Error Handling
308
384
 
309
- Errors follow a three-part structure: message, context (suggestions or valid options), and recovery hint.
385
+ Lookup errors report each failed name with typo suggestions:
310
386
 
311
387
  ```
312
- Error: block "Buton" not found.
388
+ **Errors:**
389
+ - Buton: block "Buton" not found.
390
+ Did you mean: Button?
391
+ ```
313
392
 
314
- Did you mean?
315
- - Button
316
- - ButtonGroup
393
+ Other errors carry a code and, where available, a recovery hint:
317
394
 
318
- Run `pragma block list`
395
+ ```
396
+ ## Error: EMPTY_RESULTS
397
+ No standards found.
398
+ Filters: category=nope
399
+ Recovery: `pragma standard list`
319
400
  ```
320
401
 
321
402
  With `--format json`, errors are returned as structured JSON with error code, suggestions, and recovery. With `--llm`, errors are rendered as Markdown.
@@ -336,7 +417,7 @@ With `--format json`, errors are returned as structured JSON with error code, su
336
417
 
337
418
  pragma is structured as a domain-driven application using the federation pattern from `@canonical/cli-core`:
338
419
 
339
- 1. **Domains** — 14 domain modules (block, standard, modifier, token, tier, ontology, graph, config, skill, llm, setup, create, doctor, info), each exporting `CommandDefinition[]`
420
+ 1. **Domains** — 17 domain modules (block, config, create, doctor, graph, graphql, info, llm, modifier, ontology, refs, setup, skill, standard, tier, token, trace — every directory under `src/domains/` except the `shared/` infrastructure folder), each contributing `CommandDefinition`s
340
421
  2. **Operations** — data retrieval functions that query the ke triple store via SPARQL
341
422
  3. **Formatters** — three-mode output adapters (plain/llm/json) for each operation
342
423
  4. **Commands** — thin wiring layer connecting Commander.js parameters to operations + formatters
@@ -364,13 +445,13 @@ bun build --compile --minify src/bin.ts --outfile dist/pragma
364
445
 
365
446
  ## Dependencies
366
447
 
367
- - [`@canonical/ke`](../runtime/ke/) — triple store runtime (Oxigraph WASM)
448
+ - [`@canonical/ke`](../../runtime/ke/) — triple store runtime (Oxigraph WASM)
368
449
  - [`@canonical/cli-core`](../core/) — shared CLI machinery (Commander.js registration, help formatting)
369
- - [`@canonical/task`](../../lib/task/) — reversible task trees for setup/create with undo support
370
- - [`@canonical/harnesses`](../../lib/harnesses/) — AI harness detection (Claude Code, Cursor, Windsurf)
371
- - [`@canonical/summon-core`](../../lib/summon-core/) — code generation runtime
372
- - [`@canonical/summon-component`](../../lib/summon-component/) — component generator
373
- - [`@canonical/summon-package`](../../lib/summon-package/) — package generator
450
+ - [`@canonical/task`](../../runtime/task/) — reversible task trees for setup/create with undo support
451
+ - [`@canonical/harnesses`](../../runtime/harnesses/) — AI harness detection (Claude Code, Cursor, Windsurf)
452
+ - [`@canonical/summon-core`](../../summon/core/) — code generation runtime
453
+ - [`@canonical/summon-component`](../../summon/component/) — component generator
454
+ - [`@canonical/summon-package`](../../summon/package/) — package generator
374
455
 
375
456
  ## License
376
457
 
package/dist/pragma CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canonical/pragma-cli",
3
3
  "description": "CLI and MCP server for Canonical's design system.",
4
- "version": "0.29.0",
4
+ "version": "0.31.0",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
7
7
  "types": "src/index.ts",
@@ -60,9 +60,9 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "@biomejs/biome": "2.4.9",
63
- "@canonical/biome-config": "^0.29.0",
64
- "@canonical/typescript-config": "^0.29.0",
65
- "@canonical/webarchitect": "^0.29.0",
63
+ "@canonical/biome-config": "^0.31.0",
64
+ "@canonical/typescript-config": "^0.31.0",
65
+ "@canonical/webarchitect": "^0.31.0",
66
66
  "@types/bun": "^1.3.10",
67
67
  "@types/node": "^24.12.0",
68
68
  "@types/react": "^19.2.14",
@@ -72,16 +72,18 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@canonical/anatomy-dsl": "^0.2.2",
75
- "@canonical/cli-core": "^0.29.0",
75
+ "@canonical/cli-core": "^0.31.0",
76
76
  "@canonical/code-standards": "^0.1.2",
77
77
  "@canonical/design-system": "^0.1.2",
78
- "@canonical/harnesses": "^0.29.0",
79
- "@canonical/ke": "^0.29.0",
80
- "@canonical/ke-graphql": "^0.29.0",
81
- "@canonical/summon-component": "^0.29.0",
82
- "@canonical/summon-core": "^0.29.0",
83
- "@canonical/summon-package": "^0.29.0",
84
- "@canonical/task": "^0.29.0",
78
+ "@canonical/harnesses": "^0.31.0",
79
+ "@canonical/ke": "^0.31.0",
80
+ "@canonical/ke-graphql": "^0.31.0",
81
+ "@canonical/summon": "^0.31.0",
82
+ "@canonical/summon-application": "^0.31.0",
83
+ "@canonical/summon-component": "^0.31.0",
84
+ "@canonical/summon-core": "^0.31.0",
85
+ "@canonical/summon-package": "^0.31.0",
86
+ "@canonical/task": "^0.31.0",
85
87
  "@modelcontextprotocol/sdk": "^1.12.0",
86
88
  "chalk": "^5.6.2",
87
89
  "commander": "^14.0.3",
@@ -90,5 +92,5 @@
90
92
  "react": "^19.2.4",
91
93
  "zod": "^3.25.0"
92
94
  },
93
- "gitHead": "7c96ac31ff350a3bd099168d249680a8f9632568"
95
+ "gitHead": "d4a67ebe42e62b8cf3de77f42f537309ca88d672"
94
96
  }
@@ -16,3 +16,10 @@ export const POLL_INTERVAL_MS = 50;
16
16
 
17
17
  /** Maximum time to wait for a spawned server to become ready. */
18
18
  export const SPAWN_TIMEOUT_MS = 5_000;
19
+
20
+ /**
21
+ * Maximum time to wait for a query response before giving up. Bounds the
22
+ * whole client call so a wedged or slow server can never hang the shell's
23
+ * tab-completion — the query resolves to no candidates instead.
24
+ */
25
+ export const QUERY_TIMEOUT_MS = 3_000;
@@ -37,6 +37,16 @@ function makeTree(): CompletionTree {
37
37
  );
38
38
  },
39
39
  },
40
+ {
41
+ name: "detailed",
42
+ description: "Show full detail",
43
+ type: "boolean",
44
+ },
45
+ {
46
+ name: "anatomyOnly",
47
+ description: "Show anatomy only",
48
+ type: "boolean",
49
+ },
40
50
  ],
41
51
  execute: async () => createExitResult(0),
42
52
  },
@@ -90,4 +100,26 @@ describe("handleQuery", () => {
90
100
  const result = await handleQuery("block list something", makeTree(), ctx);
91
101
  expect(result).toBe("");
92
102
  });
103
+
104
+ it("strips the leading program name shells forward", async () => {
105
+ // Every generated shell script forwards the full command line, so the
106
+ // program name arrives as the first word.
107
+ const result = await handleQuery("pragma blo", makeTree(), ctx);
108
+ expect(result).toBe("block");
109
+ });
110
+
111
+ it("lists nouns when only the program name is present", async () => {
112
+ const result = await handleQuery("pragma", makeTree(), ctx);
113
+ expect(result).toBe("block\nstandard");
114
+ });
115
+
116
+ it("completes verb flags when the word starts with a dash", async () => {
117
+ const result = await handleQuery("block get --", makeTree(), ctx);
118
+ expect(result).toBe("--anatomy-only\n--detailed");
119
+ });
120
+
121
+ it("filters verb flags by the dash-prefixed partial", async () => {
122
+ const result = await handleQuery("block get --d", makeTree(), ctx);
123
+ expect(result).toBe("--detailed");
124
+ });
93
125
  });
@@ -1,5 +1,24 @@
1
1
  import type { CommandContext, CompletionTree } from "@canonical/cli-core";
2
2
  import { resolveCompletion } from "@canonical/cli-core";
3
+ import { PROGRAM_NAME } from "../constants.js";
4
+
5
+ /**
6
+ * Split a raw shell partial into completion words, dropping the leading
7
+ * program name.
8
+ *
9
+ * Every generated shell script forwards the full command line — including the
10
+ * `pragma` program name as the first word — so without this strip the resolver
11
+ * mistakes `pragma` for the noun and returns no candidates. An empty partial
12
+ * (first-Tab, no word yet) resolves to a single empty word so noun completion
13
+ * still fires.
14
+ *
15
+ * @param partial - The raw partial input string from the shell.
16
+ * @returns The completion words with any leading program name removed.
17
+ */
18
+ function splitCompletionWords(partial: string): string[] {
19
+ const words = partial.length === 0 ? [""] : partial.split(" ");
20
+ return words.at(0) === PROGRAM_NAME ? words.slice(1) : words;
21
+ }
3
22
 
4
23
  /**
5
24
  * Resolve a partial CLI input against a completion tree and return
@@ -22,7 +41,7 @@ export default async function handleQuery(
22
41
  tree: CompletionTree,
23
42
  ctx: CommandContext,
24
43
  ): Promise<string> {
25
- const words = partial.length === 0 ? [""] : partial.split(" ");
44
+ const words = splitCompletionWords(partial);
26
45
  const result = resolveCompletion(tree, words);
27
46
  if (!result.completer) return "";
28
47
  const candidates = await result.completer(result.partial, ctx);
@@ -26,12 +26,15 @@ vi.mock("./waitForSocket.js", () => ({
26
26
 
27
27
  describe("queryCompletions", () => {
28
28
  let stdoutSpy: ReturnType<typeof vi.spyOn>;
29
+ const unrefMock = vi.fn();
29
30
 
30
31
  beforeEach(() => {
31
32
  vi.clearAllMocks();
32
33
  stdoutSpy = vi
33
34
  .spyOn(process.stdout, "write")
34
35
  .mockImplementation(() => true);
36
+ // The spawned server handle is unref'd so the client can exit immediately.
37
+ spawnMock.mockReturnValue({ unref: unrefMock });
35
38
  vi.stubGlobal("Bun", { spawn: spawnMock });
36
39
  });
37
40
 
@@ -67,6 +70,8 @@ describe("queryCompletions", () => {
67
70
 
68
71
  expect(unlinkSyncMock).toHaveBeenCalledWith("/tmp/pragma-completions.sock");
69
72
  expect(spawnMock).toHaveBeenCalledTimes(1);
73
+ // The server handle is detached so the client is not held alive by it.
74
+ expect(unrefMock).toHaveBeenCalledTimes(1);
70
75
  expect(waitForSocketMock).toHaveBeenCalled();
71
76
  expect(querySocketMock).toHaveBeenCalledTimes(2);
72
77
  expect(stdoutSpy).toHaveBeenCalledWith("block\n");
@@ -38,12 +38,26 @@ export default async function queryCompletions(partial: string): Promise<void> {
38
38
  }
39
39
  }
40
40
 
41
- // No running server — spawn one.
42
- const binPath = resolve(import.meta.dirname, "../bin.ts");
43
- Bun.spawn(["bun", "run", binPath, "_completions-server"], {
41
+ // No running server — spawn one. Re-invoke THIS executable so completion
42
+ // works from the shipped binary as well as from source: when the entry file
43
+ // exists on disk (source run) spawn `bun run bin.ts`; otherwise (a compiled
44
+ // standalone binary, where bin.ts is embedded, not a file) spawn the binary
45
+ // itself. The old code always spawned `bun run <dir>/../bin.ts`, which does
46
+ // not exist inside a compiled binary, so its server never started and every
47
+ // completion silently returned nothing.
48
+ const entryPath = resolve(import.meta.dirname, "../bin.ts");
49
+ const spawnArgs = existsSync(entryPath)
50
+ ? [process.execPath, "run", entryPath, "_completions-server"]
51
+ : [process.execPath, "_completions-server"];
52
+ const server = Bun.spawn(spawnArgs, {
44
53
  stdio: ["ignore", "ignore", "ignore"],
45
54
  cwd: process.cwd(),
46
55
  });
56
+ // Detach the server from the client's lifecycle. Without this the child keeps
57
+ // the client's event loop alive until the server's idle timeout (~10s) fires,
58
+ // so every tab-completion appears to hang for ~10s and the client tears the
59
+ // server down on exit — defeating the persistent-server fast path entirely.
60
+ server.unref();
47
61
 
48
62
  const ready = await waitForSocket(socketPath, SPAWN_TIMEOUT_MS);
49
63
  if (!ready) {
@@ -67,4 +67,35 @@ describe("querySocket", () => {
67
67
  "connect failed",
68
68
  );
69
69
  });
70
+
71
+ it("times out and closes the socket when the server never replies", async () => {
72
+ vi.useFakeTimers();
73
+ const end = vi.fn();
74
+
75
+ connectMock.mockImplementation(
76
+ (options: {
77
+ socket: {
78
+ open: (socket: {
79
+ write: () => void;
80
+ flush: () => void;
81
+ end: () => void;
82
+ }) => void;
83
+ };
84
+ }) => {
85
+ // Open but never send data or close — simulate a wedged server.
86
+ options.socket.open({ write: () => {}, flush: () => {}, end });
87
+ return Promise.resolve({});
88
+ },
89
+ );
90
+
91
+ try {
92
+ const promise = querySocket("/tmp/pragma.sock", "blo");
93
+ const assertion = expect(promise).rejects.toThrow(/timed out/);
94
+ await vi.advanceTimersByTimeAsync(5000);
95
+ await assertion;
96
+ expect(end).toHaveBeenCalledTimes(1);
97
+ } finally {
98
+ vi.useRealTimers();
99
+ }
100
+ });
70
101
  });
@@ -1,10 +1,14 @@
1
+ import { QUERY_TIMEOUT_MS } from "./constants.js";
2
+
1
3
  /**
2
4
  * Connect to a Unix domain socket, send a partial string, and
3
5
  * collect the response.
4
6
  *
5
- * Opens a Bun TCP connection to the socket, writes the partial
6
- * terminated by a newline, and accumulates response chunks until
7
- * the server closes the connection.
7
+ * Opens a Bun connection to the socket, writes the partial terminated by a
8
+ * newline, and accumulates response chunks until the server closes the
9
+ * connection. The whole exchange is bounded by {@link QUERY_TIMEOUT_MS}: if a
10
+ * wedged or slow server never replies, the promise rejects rather than hanging
11
+ * the shell's tab-completion indefinitely.
8
12
  *
9
13
  * @param socketPath - Absolute path to the Unix domain socket.
10
14
  * @param partial - The partial CLI input to send for completion.
@@ -18,10 +22,31 @@ export default function querySocket(
18
22
  ): Promise<string> {
19
23
  return new Promise<string>((resolve, reject) => {
20
24
  const chunks: Buffer[] = [];
25
+ let settled = false;
26
+ let openSocket: { end: () => void } | undefined;
27
+
28
+ const timer = setTimeout(() => {
29
+ if (settled) return;
30
+ settled = true;
31
+ // Close the connection so a hung server does not leak an open socket.
32
+ openSocket?.end();
33
+ reject(
34
+ new Error(`Completion query timed out after ${QUERY_TIMEOUT_MS}ms`),
35
+ );
36
+ }, QUERY_TIMEOUT_MS);
37
+
38
+ const settle = (run: () => void): void => {
39
+ if (settled) return;
40
+ settled = true;
41
+ clearTimeout(timer);
42
+ run();
43
+ };
44
+
21
45
  Bun.connect({
22
46
  unix: socketPath,
23
47
  socket: {
24
48
  open(socket) {
49
+ openSocket = socket;
25
50
  socket.write(`${partial}\n`);
26
51
  socket.flush();
27
52
  },
@@ -29,16 +54,18 @@ export default function querySocket(
29
54
  chunks.push(Buffer.from(data));
30
55
  },
31
56
  close() {
32
- resolve(
33
- Buffer.concat(chunks)
34
- .toString("utf-8")
35
- .replace(/\r?\n$/, ""),
57
+ settle(() =>
58
+ resolve(
59
+ Buffer.concat(chunks)
60
+ .toString("utf-8")
61
+ .replace(/\r?\n$/, ""),
62
+ ),
36
63
  );
37
64
  },
38
65
  error(_socket, err) {
39
- reject(err);
66
+ settle(() => reject(err));
40
67
  },
41
68
  },
42
- }).catch(reject);
69
+ }).catch((err) => settle(() => reject(err)));
43
70
  });
44
71
  }