@canonical/pragma-cli 0.30.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 (254) hide show
  1. package/README.md +64 -35
  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/parseConfigValues.ts +2 -0
  12. package/src/config/readConfigLayers.test.ts +2 -0
  13. package/src/config/readConfigLayers.ts +3 -0
  14. package/src/config/types.ts +7 -0
  15. package/src/constants.ts +3 -1
  16. package/src/domains/block/commands/sample.ts +2 -1
  17. package/src/domains/block/formatters/list.test.ts +3 -3
  18. package/src/domains/block/formatters/lookup.test.ts +5 -5
  19. package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
  20. package/src/domains/block/stories.ts +9 -3
  21. package/src/domains/config/commands/channel.test.ts +16 -0
  22. package/src/domains/config/commands/channel.ts +14 -1
  23. package/src/domains/config/commands/framework.test.ts +14 -0
  24. package/src/domains/config/commands/framework.ts +13 -1
  25. package/src/domains/config/commands/tier.test.ts +7 -0
  26. package/src/domains/config/commands/tier.ts +14 -1
  27. package/src/domains/config/operations/validateTier.test.ts +9 -0
  28. package/src/domains/config/operations/validateTier.ts +10 -2
  29. package/src/domains/create/generatorPack/bundled.ts +49 -0
  30. package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
  31. package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
  32. package/src/domains/create/index.ts +13 -7
  33. package/src/domains/create/mcp/specs.ts +6 -161
  34. package/src/domains/create/renderGeneratorUi.test.ts +103 -0
  35. package/src/domains/create/renderGeneratorUi.ts +65 -0
  36. package/src/domains/doctor/formatters/doctor.test.ts +50 -2
  37. package/src/domains/doctor/formatters/doctor.ts +90 -38
  38. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
  39. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
  40. package/src/domains/doctor/operations/checks.test.ts +6 -4
  41. package/src/domains/doctor/operations/types.ts +23 -1
  42. package/src/domains/graph/commands/inspect.test.ts +2 -2
  43. package/src/domains/graph/commands/query.test.ts +5 -3
  44. package/src/domains/graph/formatters/inspect.ts +7 -7
  45. package/src/domains/graph/formatters/query.test.ts +87 -0
  46. package/src/domains/graph/formatters/query.ts +42 -5
  47. package/src/domains/graph/operations/inspectUri.test.ts +5 -5
  48. package/src/domains/graphql/commands/serve.test.ts +12 -6
  49. package/src/domains/graphql/commands/serve.ts +16 -6
  50. package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
  51. package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
  52. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
  53. package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
  54. package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
  55. package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
  56. package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
  57. package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
  58. package/src/domains/graphql/operations/index.ts +5 -0
  59. package/src/domains/info/formatters/upgrade.test.ts +7 -4
  60. package/src/domains/info/formatters/upgrade.ts +3 -1
  61. package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
  62. package/src/domains/info/operations/collectEntityCounts.ts +123 -0
  63. package/src/domains/info/operations/index.ts +1 -0
  64. package/src/domains/info/types.ts +13 -0
  65. package/src/domains/llm/capabilities.test.ts +23 -3
  66. package/src/domains/llm/commands/capabilities.ts +12 -3
  67. package/src/domains/llm/commands/llm.ts +5 -1
  68. package/src/domains/llm/data/commandReference.ts +20 -11
  69. package/src/domains/llm/data/decisionTrees.ts +10 -5
  70. package/src/domains/llm/data/toolCatalog.ts +49 -18
  71. package/src/domains/llm/formatters/orientation.ts +5 -1
  72. package/src/domains/llm/llmCommand.test.ts +11 -3
  73. package/src/domains/llm/mcp/specs.ts +3 -4
  74. package/src/domains/llm/operations/collectContext.test.ts +64 -0
  75. package/src/domains/llm/operations/collectContext.ts +7 -17
  76. package/src/domains/modifier/commands/sample.ts +2 -1
  77. package/src/domains/modifier/operations/lookup.ts +5 -3
  78. package/src/domains/modifier/stories.ts +9 -0
  79. package/src/domains/ontology/commands/list.test.ts +5 -3
  80. package/src/domains/ontology/commands/show.test.ts +85 -9
  81. package/src/domains/ontology/formatters/index.ts +1 -0
  82. package/src/domains/ontology/formatters/list.ts +37 -12
  83. package/src/domains/ontology/formatters/show.ts +358 -67
  84. package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
  85. package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
  86. package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
  87. package/src/domains/ontology/helpers/index.ts +14 -0
  88. package/src/domains/ontology/helpers/queryClasses.ts +32 -16
  89. package/src/domains/ontology/helpers/queryProperties.ts +39 -6
  90. package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
  91. package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
  92. package/src/domains/ontology/operations/listOntologies.ts +126 -30
  93. package/src/domains/ontology/operations/showOntology.test.ts +131 -12
  94. package/src/domains/ontology/operations/showOntology.ts +289 -7
  95. package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
  96. package/src/domains/ontology/stories.ts +53 -8
  97. package/src/domains/refs/operations/updateRefs.ts +0 -1
  98. package/src/domains/setup/commands/index.ts +1 -0
  99. package/src/domains/setup/commands/setup.ts +70 -0
  100. package/src/domains/setup/index.ts +7 -3
  101. package/src/domains/setup/operations/setupLsp.test.ts +3 -2
  102. package/src/domains/setup/operations/setupLsp.ts +7 -2
  103. package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
  104. package/src/domains/shared/bootStore.test.ts +82 -0
  105. package/src/domains/shared/bootStore.ts +56 -20
  106. package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
  107. package/src/domains/shared/extractLocalName.test.ts +3 -3
  108. package/src/domains/shared/formatters.ts +23 -2
  109. package/src/domains/shared/listTiers.ts +40 -0
  110. package/src/domains/shared/loaders/bundledLoader.ts +48 -0
  111. package/src/domains/shared/loaders/gitLoader.ts +1 -2
  112. package/src/domains/shared/loaders/localLoader.ts +2 -18
  113. package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
  114. package/src/domains/shared/loaders/readPackageDir.ts +46 -8
  115. package/src/domains/shared/lookupMany.test.ts +15 -0
  116. package/src/domains/shared/lookupMany.ts +22 -6
  117. package/src/domains/shared/namespaces.ts +13 -5
  118. package/src/domains/shared/parseSampleCount.test.ts +47 -0
  119. package/src/domains/shared/parseSampleCount.ts +45 -0
  120. package/src/domains/shared/prefixes.test.ts +211 -0
  121. package/src/domains/shared/prefixes.ts +182 -15
  122. package/src/domains/shared/renderers.ts +5 -5
  123. package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
  124. package/src/domains/shared/resolveLookupExitCode.ts +42 -0
  125. package/src/domains/shared/semanticPackage.test.ts +89 -0
  126. package/src/domains/shared/semanticPackage.ts +17 -2
  127. package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
  128. package/src/domains/shared/stories/compileLookupTool.ts +1 -1
  129. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
  130. package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
  131. package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
  132. package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
  133. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
  134. package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
  135. package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
  136. package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
  137. package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
  138. package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
  139. package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
  140. package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
  141. package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
  142. package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
  143. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
  144. package/src/domains/shared/stories/pack/index.ts +14 -0
  145. package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
  146. package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
  147. package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
  148. package/src/domains/shared/stories/pack/types.ts +123 -3
  149. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
  150. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
  151. package/src/domains/shared/stories/types.ts +9 -1
  152. package/src/domains/shared/types/index.ts +5 -9
  153. package/src/domains/shared/types/ontology.ts +119 -24
  154. package/src/domains/shared/types/standard.ts +9 -52
  155. package/src/domains/token/commands/lookup.test.ts +8 -0
  156. package/src/domains/token/commands/lookup.ts +12 -0
  157. package/src/domains/token/commands/sample.ts +2 -1
  158. package/src/domains/token/featureFlag.ts +17 -0
  159. package/src/domains/token/index.ts +5 -0
  160. package/src/domains/token/mcp/specs.ts +13 -1
  161. package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
  162. package/src/domains/trace/commands/show.ts +30 -4
  163. package/src/index.ts +1 -13
  164. package/src/mcp/registerResources.test.ts +2 -2
  165. package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
  166. package/src/mcp/tools/index.ts +18 -10
  167. package/src/mcp/tools/registerTools.test.ts +106 -30
  168. package/src/pipeline/collectCommands.test.ts +151 -1
  169. package/src/pipeline/collectCommands.ts +34 -19
  170. package/src/pipeline/createProgram.test.ts +5 -1
  171. package/src/pipeline/createProgram.ts +2 -2
  172. package/src/pipeline/firstRun.test.ts +80 -0
  173. package/src/pipeline/firstRun.ts +99 -0
  174. package/src/pipeline/parseGlobalFlags.test.ts +102 -1
  175. package/src/pipeline/parseGlobalFlags.ts +98 -9
  176. package/src/pipeline/resolveCommandKind.test.ts +12 -0
  177. package/src/pipeline/resolveCommandKind.ts +4 -0
  178. package/src/pipeline/rootHelp.test.ts +87 -0
  179. package/src/pipeline/rootHelp.ts +265 -0
  180. package/src/pipeline/runCli.orchestration.test.ts +28 -12
  181. package/src/pipeline/runCli.test.ts +17 -4
  182. package/src/pipeline/runCli.ts +162 -19
  183. package/src/testing/cli.ts +30 -2
  184. package/src/testing/dsFixtures.ts +21 -4
  185. package/src/testing/index.ts +1 -4
  186. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
  187. package/src/testing/integration/cli-user-stories.test.ts +63 -0
  188. package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
  189. package/src/testing/integration/edge-cases.test.ts +62 -50
  190. package/src/testing/integration/executor-execution.test.ts +6 -4
  191. package/src/testing/integration/executor-golden.test.ts +2 -2
  192. package/src/testing/integration/mcp-surface.test.ts +40 -15
  193. package/src/testing/integration/parity.test.ts +119 -70
  194. package/src/testing/setupXdgIsolation.ts +16 -5
  195. package/src/testing/standardParityFixtures.ts +26 -83
  196. package/src/testing/store.ts +4 -4
  197. package/src/domains/create/commands/component.test.ts +0 -166
  198. package/src/domains/create/commands/component.ts +0 -123
  199. package/src/domains/create/commands/index.ts +0 -3
  200. package/src/domains/create/commands/package.test.ts +0 -35
  201. package/src/domains/create/commands/package.ts +0 -21
  202. package/src/domains/create/generators.ts +0 -8
  203. package/src/domains/standard/commands/categories.test.ts +0 -70
  204. package/src/domains/standard/commands/categories.ts +0 -48
  205. package/src/domains/standard/commands/index.ts +0 -6
  206. package/src/domains/standard/commands/list.test.ts +0 -105
  207. package/src/domains/standard/commands/list.ts +0 -11
  208. package/src/domains/standard/commands/lookup.test.ts +0 -135
  209. package/src/domains/standard/commands/lookup.ts +0 -14
  210. package/src/domains/standard/commands/sample.ts +0 -57
  211. package/src/domains/standard/formatters/categories.test.ts +0 -44
  212. package/src/domains/standard/formatters/categories.ts +0 -38
  213. package/src/domains/standard/formatters/index.ts +0 -10
  214. package/src/domains/standard/formatters/list.test.ts +0 -169
  215. package/src/domains/standard/formatters/list.ts +0 -184
  216. package/src/domains/standard/formatters/lookup.test.ts +0 -95
  217. package/src/domains/standard/formatters/lookup.ts +0 -101
  218. package/src/domains/standard/formatters/sample.ts +0 -15
  219. package/src/domains/standard/formatters/types.ts +0 -31
  220. package/src/domains/standard/index.ts +0 -40
  221. package/src/domains/standard/mcp/index.ts +0 -2
  222. package/src/domains/standard/mcp/specs.ts +0 -89
  223. package/src/domains/standard/operations/categories.test.ts +0 -61
  224. package/src/domains/standard/operations/categories.ts +0 -38
  225. package/src/domains/standard/operations/index.ts +0 -6
  226. package/src/domains/standard/operations/list.test.ts +0 -75
  227. package/src/domains/standard/operations/list.ts +0 -64
  228. package/src/domains/standard/operations/lookup.test.ts +0 -58
  229. package/src/domains/standard/operations/lookup.ts +0 -151
  230. package/src/domains/standard/operations/sample.ts +0 -35
  231. package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
  232. package/src/domains/standard/orchestration/index.ts +0 -4
  233. package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
  234. package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
  235. package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
  236. package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
  237. package/src/domains/standard/standardConfig.ts +0 -42
  238. package/src/domains/standard/stories.test.ts +0 -24
  239. package/src/domains/standard/stories.ts +0 -151
  240. package/src/domains/standard/types.ts +0 -15
  241. package/src/domains/tier/commands/index.ts +0 -3
  242. package/src/domains/tier/commands/list.test.ts +0 -67
  243. package/src/domains/tier/commands/list.ts +0 -9
  244. package/src/domains/tier/formatters/index.ts +0 -3
  245. package/src/domains/tier/formatters/list.test.ts +0 -33
  246. package/src/domains/tier/formatters/list.ts +0 -45
  247. package/src/domains/tier/index.ts +0 -17
  248. package/src/domains/tier/mcp/index.ts +0 -2
  249. package/src/domains/tier/mcp/specs.ts +0 -12
  250. package/src/domains/tier/operations/index.ts +0 -3
  251. package/src/domains/tier/operations/list.test.ts +0 -32
  252. package/src/domains/tier/operations/list.ts +0 -33
  253. package/src/domains/tier/stories.test.ts +0 -22
  254. package/src/domains/tier/stories.ts +0 -52
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 17 domains — counting every directory under `src/domains/` except `shared/`, which holds cross-domain infrastructure rather than commands. The 14 day-to-day domains are documented below; the remaining three are specialised: `graphql` (compile TTL ontologies into GraphQL schema artifacts), `refs` (`pragma update-refs`, package reference data), and `trace` (query access tracing). 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 17 domains — counting every directory under `sr
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 17 domains — counting every directory under `sr
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 17 domains — counting every directory under `sr
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
 
@@ -97,15 +104,16 @@ pragma organizes commands into 17 domains — counting every directory under `sr
97
104
 
98
105
  ### Setup
99
106
 
100
- 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.
101
109
 
102
110
  | Command | Description |
103
111
  |---------|-------------|
104
- | `pragma setup all` | Run all setup steps (MCP + completions + skills) |
105
- | `pragma setup mcp` | Configure pragma MCP server for AI harnesses |
106
- | `pragma setup completions` | Install shell completions (bash/zsh/fish) |
107
- | `pragma setup skills` | Symlink agent skills into harness config |
108
- | `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` |
109
117
 
110
118
  ### Create
111
119
 
@@ -113,8 +121,9 @@ Create commands scaffold new code using summon generators. They support `--undo`
113
121
 
114
122
  | Command | Description |
115
123
  |---------|-------------|
116
- | `pragma create component <name>` | Scaffold a new design system component |
117
- | `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 |
118
127
 
119
128
  ### Doctor
120
129
 
@@ -126,8 +135,19 @@ Create commands scaffold new code using summon generators. They support `--undo`
126
135
 
127
136
  | Command | Description |
128
137
  |---------|-------------|
129
- | `pragma info` | Show pragma version, store summary, installed packages |
130
- | `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) |
131
151
 
132
152
  ## Global Flags
133
153
 
@@ -144,7 +164,7 @@ Setup and create commands support `--undo` to reverse previous operations. The u
144
164
  ```bash
145
165
  pragma setup mcp --undo # Remove MCP configuration
146
166
  pragma setup completions --undo # Remove shell completions
147
- pragma create component Foo --undo # Remove scaffolded component files
167
+ pragma create component react src/lib/Foo --undo # Remove scaffolded component files
148
168
  ```
149
169
 
150
170
  ## MCP Integration
@@ -174,27 +194,27 @@ Or add manually to your `.mcp.json`:
174
194
  }
175
195
  ```
176
196
 
177
- ### MCP Tools (30)
197
+ ### MCP Tools (28)
178
198
 
179
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.
180
200
 
181
- #### Read Tools (21)
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).
182
205
 
183
206
  | Tool | Description |
184
207
  |------|-------------|
185
208
  | `block_list` | List blocks with optional tier filtering and disclosure levels |
186
209
  | `block_lookup` | Look up detailed block info (anatomy, modifiers, tokens, standards) |
187
- | `block_batch_lookup` | Look up multiple blocks by name in a single call |
210
+ | `block_sample` | Return random complete block instances for shape discovery |
188
211
  | `standard_list` | List code standards with optional category/search filtering |
189
212
  | `standard_lookup` | Look up standard with do/don't code examples |
190
- | `standard_batch_lookup` | Look up multiple standards by name in a single call |
191
213
  | `standard_categories` | List standard categories with counts |
214
+ | `standard_sample` | Return random complete standard instances for shape discovery |
192
215
  | `modifier_list` | List modifier families |
193
216
  | `modifier_lookup` | Look up modifier family with values |
194
- | `modifier_batch_lookup` | Look up multiple modifier families by name in a single call |
195
- | `token_list` | List design tokens with optional category filtering |
196
- | `token_lookup` | Look up token with theme-resolved values |
197
- | `token_batch_lookup` | Look up multiple tokens by name in a single call |
217
+ | `modifier_sample` | Return random complete modifier instances for shape discovery |
198
218
  | `tier_list` | List all tiers in the hierarchy |
199
219
  | `config_show` | Show current tier and channel configuration |
200
220
  | `ontology_list` | List loaded ontology namespaces with class/property counts |
@@ -204,7 +224,10 @@ All tools return a consistent envelope: `{ ok: true, data, meta }` for success,
204
224
  | `skill_list` | List discovered agent skills |
205
225
  | `skill_lookup` | Get full SKILL.md content for skills by name |
206
226
 
207
- #### 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)
208
231
 
209
232
  | Tool | Description |
210
233
  |------|-------------|
@@ -213,6 +236,7 @@ All tools return a consistent envelope: `{ ok: true, data, meta }` for success,
213
236
  | `tokens_add_config` | Add Terrazzo token build configuration |
214
237
  | `create_component` | Scaffold a new design system component |
215
238
  | `create_package` | Scaffold a new monorepo package |
239
+ | `create_application` | Scaffold a complete React application |
216
240
 
217
241
  #### Orientation Tools (2)
218
242
 
@@ -282,12 +306,12 @@ Error responses include a `recovery` object that tells agents what tool to call
282
306
 
283
307
  ### Batch Results
284
308
 
285
- 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`:
286
310
 
287
311
  ```json
288
312
  {
289
313
  "results": [{ "name": "Button", ... }],
290
- "errors": [{ "name": "Buton", "code": "ENTITY_NOT_FOUND", "message": "..." }]
314
+ "errors": [{ "query": "Buton", "code": "ENTITY_NOT_FOUND", "message": "...", "suggestions": ["Button"] }]
291
315
  }
292
316
  ```
293
317
 
@@ -358,16 +382,21 @@ format is experimental and may change.
358
382
 
359
383
  ## Error Handling
360
384
 
361
- 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:
362
386
 
363
387
  ```
364
- Error: block "Buton" not found.
388
+ **Errors:**
389
+ - Buton: block "Buton" not found.
390
+ Did you mean: Button?
391
+ ```
365
392
 
366
- Did you mean?
367
- - Button
368
- - ButtonGroup
393
+ Other errors carry a code and, where available, a recovery hint:
369
394
 
370
- Run `pragma block list`
395
+ ```
396
+ ## Error: EMPTY_RESULTS
397
+ No standards found.
398
+ Filters: category=nope
399
+ Recovery: `pragma standard list`
371
400
  ```
372
401
 
373
402
  With `--format json`, errors are returned as structured JSON with error code, suggestions, and recovery. With `--llm`, errors are rendered as Markdown.
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.30.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.30.0",
64
- "@canonical/typescript-config": "^0.30.0",
65
- "@canonical/webarchitect": "^0.30.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.30.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.30.0",
79
- "@canonical/ke": "^0.30.0",
80
- "@canonical/ke-graphql": "^0.30.0",
81
- "@canonical/summon-component": "^0.30.0",
82
- "@canonical/summon-core": "^0.30.0",
83
- "@canonical/summon-package": "^0.30.0",
84
- "@canonical/task": "^0.30.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": "40fe86ac955473536ac14f074812a9821f5b65c0"
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
  }
@@ -92,6 +92,7 @@ export default function parseConfigValues(
92
92
  const packages = parsePackagesField(parsed.packages);
93
93
  const stories = parseStoriesField(parsed.stories, sourcePath);
94
94
  const prefixes = parsePrefixesField(parsed.prefixes);
95
+ const detail = typeof parsed.detail === "string" ? parsed.detail : undefined;
95
96
 
96
97
  return {
97
98
  ...(tier !== undefined ? { tier } : {}),
@@ -101,6 +102,7 @@ export default function parseConfigValues(
101
102
  ...(framework !== undefined ? { framework } : {}),
102
103
  ...(stories ? { stories } : {}),
103
104
  ...(prefixes ? { prefixes } : {}),
105
+ ...(detail !== undefined ? { detail } : {}),
104
106
  };
105
107
  }
106
108
 
@@ -44,6 +44,7 @@ describe("readConfigLayers", () => {
44
44
  framework: "default",
45
45
  stories: "default",
46
46
  prefixes: "default",
47
+ detail: "default",
47
48
  });
48
49
  expect(layers.global.exists).toBe(false);
49
50
  expect(layers.project.exists).toBe(false);
@@ -77,6 +78,7 @@ describe("readConfigLayers", () => {
77
78
  framework: "default",
78
79
  stories: "default",
79
80
  prefixes: "default",
81
+ detail: "default",
80
82
  });
81
83
  });
82
84
 
@@ -68,6 +68,7 @@ export default function readConfigLayers(
68
68
  const framework = pick("framework");
69
69
  const stories = pick("stories");
70
70
  const prefixes = pick("prefixes");
71
+ const detail = pick("detail");
71
72
 
72
73
  const config: PragmaConfig = {
73
74
  tier: tier.value,
@@ -77,6 +78,7 @@ export default function readConfigLayers(
77
78
  ...(framework.value !== undefined ? { framework: framework.value } : {}),
78
79
  ...(stories.value ? { stories: stories.value } : {}),
79
80
  ...(prefixes.value ? { prefixes: prefixes.value } : {}),
81
+ ...(detail.value !== undefined ? { detail: detail.value } : {}),
80
82
  };
81
83
 
82
84
  return {
@@ -89,6 +91,7 @@ export default function readConfigLayers(
89
91
  framework: framework.origin,
90
92
  stories: stories.origin,
91
93
  prefixes: prefixes.origin,
94
+ detail: detail.origin,
92
95
  },
93
96
  global: { path: globalPath, exists: globalFile.exists },
94
97
  project: { path: projectPath, exists: projectFile.exists },
@@ -39,6 +39,11 @@ interface PragmaConfig {
39
39
  * registered on the store (usable in queries) and used for display.
40
40
  */
41
41
  prefixes?: Readonly<Record<string, string>> | undefined;
42
+ /**
43
+ * Default progressive-disclosure level for disclosure-capable commands
44
+ * (e.g. `"digest"`, `"detailed"`). An explicit `--detail` flag overrides it.
45
+ */
46
+ detail?: string | undefined;
42
47
  }
43
48
 
44
49
  /** Partial update payload for writing config changes. */
@@ -67,6 +72,7 @@ interface ConfigFileValues {
67
72
  readonly framework?: Framework;
68
73
  readonly stories?: ReadonlyArray<StoryPackDefinition>;
69
74
  readonly prefixes?: Readonly<Record<string, string>>;
75
+ readonly detail?: string;
70
76
  }
71
77
 
72
78
  /** Which config layer supplied an effective field value. */
@@ -81,6 +87,7 @@ interface ConfigOrigins {
81
87
  readonly framework: ConfigOrigin;
82
88
  readonly stories: ConfigOrigin;
83
89
  readonly prefixes: ConfigOrigin;
90
+ readonly detail: ConfigOrigin;
84
91
  }
85
92
 
86
93
  /** A resolved config file layer. */
package/src/constants.ts CHANGED
@@ -7,7 +7,9 @@
7
7
  */
8
8
 
9
9
  import pkg from "../package.json" with { type: "json" };
10
- import { PREFIX_MAP } from "./domains/shared/prefixes.js";
10
+ // DEFAULT_PREFIX_MAP carries the transitional DS prefixes (ds/cs) this map
11
+ // still keys on; remove in P4 when DS prefixes ship from their packages.
12
+ import { DEFAULT_PREFIX_MAP as PREFIX_MAP } from "./domains/shared/prefixes.js";
11
13
 
12
14
  /** CLI program name used in help text and error messages. */
13
15
  const PROGRAM_NAME = "pragma";
@@ -11,6 +11,7 @@ import {
11
11
  } from "@canonical/cli-core";
12
12
  import type { PragmaContext } from "../../shared/context.js";
13
13
  import { selectFormatter } from "../../shared/formatters.js";
14
+ import parseSampleCount from "../../shared/parseSampleCount.js";
14
15
  import type { BlockDetailed, SampleOutput } from "../../shared/types/index.js";
15
16
  import { sampleFormatters } from "../formatters/index.js";
16
17
  import { sampleBlocks } from "../operations/index.js";
@@ -38,7 +39,7 @@ export default function sampleCommand(ctx: PragmaContext): CommandDefinition {
38
39
  examples: ["pragma block sample", "pragma block sample --count 3"],
39
40
  },
40
41
  async execute(params) {
41
- const count = Number(params.count ?? 2);
42
+ const count = parseSampleCount(params.count);
42
43
  const filters = buildBlockFilters(ctx);
43
44
  const result = await sampleBlocks(ctx.store, filters, count);
44
45