@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
@@ -42,20 +42,10 @@ import {
42
42
  } from "../../domains/ontology/operations/index.js";
43
43
  import { CHANNEL_RELEASES } from "../../domains/shared/filters/buildChannelFilter.js";
44
44
  import { resolveTierChain } from "../../domains/shared/filters/buildTierFilter.js";
45
+ import { listTiers } from "../../domains/shared/listTiers.js";
45
46
  import type { PragmaRuntime } from "../../domains/shared/runtime.js";
46
- import {
47
- type StandardListOutput,
48
- categoriesFormatters as standardCatFmt,
49
- listFormatters as standardListFmt,
50
- lookupFormatters as standardLookupFmt,
51
- } from "../../domains/standard/formatters/index.js";
52
- import {
53
- listCategories,
54
- listStandards,
55
- lookupStandard,
56
- } from "../../domains/standard/operations/index.js";
57
- import { listFormatters as tierListFmt } from "../../domains/tier/formatters/index.js";
58
- import { listTiers } from "../../domains/tier/operations/index.js";
47
+ import { listDomainNames } from "../../domains/shared/suggestions/index.js";
48
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../domains/token/featureFlag.js";
59
49
  import {
60
50
  createLookupFormatters as createTokenLookupFmt,
61
51
  listFormatters as tokenListFmt,
@@ -176,58 +166,83 @@ describe("block parity", () => {
176
166
  // Standard parity
177
167
  // ---------------------------------------------------------------------------
178
168
 
179
- describe("standard parity", () => {
180
- it("standard_list: count and names match", async () => {
181
- const opResult = await listStandards(rt.store);
169
+ // `standard *` is now served by the bundled `standard` story pack (the
170
+ // hand-written domain was deleted). Pack rows/entities are uniform
171
+ // `Record<string,string>` projections with expand arrays, and rendering is
172
+ // the generic pack renderer — so these tests assert *semantic* parity
173
+ // against the store (every standard reachable, by the same names, with the
174
+ // same category/description/dos/donts values) rather than byte equality
175
+ // with the removed bespoke formatters. See PARITY_GAPS in #testing for the
176
+ // pinned, accepted divergences.
177
+ describe("standard parity (bundled pack)", () => {
178
+ it("standard_list returns one {uri, name, category, description} row per standard", async () => {
179
+ const allNames = await listDomainNames(rt.store, "standard");
180
+ expect(allNames.length).toBeGreaterThan(0);
181
+
182
182
  const mcpRes = await client.callTool({
183
183
  name: "standard_list",
184
184
  arguments: {},
185
185
  });
186
- assertParity(opResult, mcpRes);
186
+ const body = parseEnvelope(mcpRes);
187
+ expect(body.ok).toBe(true);
188
+ const rows = body.data as {
189
+ uri?: string;
190
+ name?: string;
191
+ category?: string;
192
+ description?: string;
193
+ }[];
194
+
195
+ // Same standards, same names — nothing dropped or invented.
196
+ const listed = new Set(rows.map((row) => row.name));
197
+ for (const name of allNames) {
198
+ expect(listed.has(name), `"${name}" missing from standard_list`).toBe(
199
+ true,
200
+ );
201
+ }
202
+ for (const row of rows) {
203
+ expect(row.uri).toBeTruthy();
204
+ expect(row.description).toBeTruthy();
205
+ }
187
206
  });
188
207
 
189
- it("standard_list condensed: matches llm formatter", async () => {
190
- const opResult = await listStandards(rt.store);
191
- const output: StandardListOutput = {
192
- items: opResult,
193
- details: undefined,
194
- disclosure: { level: "summary" },
195
- };
196
- const expectedText = standardListFmt.llm(output);
197
-
208
+ it("standard_list condensed lists every standard under the pack heading", async () => {
209
+ const allNames = await listDomainNames(rt.store, "standard");
198
210
  const mcpRes = await client.callTool({
199
211
  name: "standard_list",
200
212
  arguments: { condensed: true },
201
213
  });
202
214
  const body = parseCondensed(mcpRes);
203
- expect(body.text).toBe(expectedText);
215
+ expect(body.text).toContain("## Standard");
216
+ for (const name of allNames) {
217
+ expect(body.text).toContain(name);
218
+ }
204
219
  });
205
220
 
206
- it("standard_lookup: detailed fields match", async () => {
207
- const opResult = await lookupStandard(
208
- rt.store,
209
- "react/component/folder-structure",
210
- );
221
+ it("standard_lookup returns full detail by default (MCP full-data contract)", async () => {
211
222
  const mcpRes = await client.callTool({
212
223
  name: "standard_lookup",
213
224
  arguments: { names: ["react/component/folder-structure"] },
214
225
  });
215
226
  const body = parseEnvelope(mcpRes);
216
- const data = body.data as { results: unknown[]; errors: unknown[] };
217
- expect(data.results[0]).toEqual(opResult);
227
+ const data = body.data as {
228
+ results: {
229
+ name: string;
230
+ category: string;
231
+ dos: { code?: string; language?: string }[];
232
+ donts: { code?: string }[];
233
+ }[];
234
+ errors: unknown[];
235
+ };
218
236
  expect(data.errors).toEqual([]);
237
+ const entity = data.results[0];
238
+ expect(entity?.name).toBe("react/component/folder-structure");
239
+ expect(entity?.category).toBe("react");
240
+ expect(entity?.dos.length).toBeGreaterThan(0);
241
+ expect(entity?.dos.every((example) => example.code)).toBe(true);
242
+ expect(entity?.donts.length).toBeGreaterThan(0);
219
243
  });
220
244
 
221
- it("standard_lookup condensed: matches llm formatter", async () => {
222
- const opResult = await lookupStandard(
223
- rt.store,
224
- "react/component/folder-structure",
225
- );
226
- const expectedText = standardLookupFmt.llm({
227
- standard: opResult,
228
- detailed: true,
229
- });
230
-
245
+ it("standard_lookup condensed renders the entity with its code examples", async () => {
231
246
  const mcpRes = await client.callTool({
232
247
  name: "standard_lookup",
233
248
  arguments: {
@@ -236,28 +251,37 @@ describe("standard parity", () => {
236
251
  },
237
252
  });
238
253
  const body = parseCondensed(mcpRes);
239
- expect(body.text).toBe(expectedText);
254
+ expect(body.text).toContain("## react/component/folder-structure");
255
+ expect(body.text).toContain("Do");
256
+ expect(body.text).toContain("src/lib/Button/Button.tsx");
240
257
  });
241
258
 
242
- it("standard_categories: data matches", async () => {
243
- const opResult = await listCategories(rt.store);
244
- const mcpRes = await client.callTool({
245
- name: "standard_categories",
259
+ it("standard_categories counts agree with the standard list", async () => {
260
+ const listRes = await client.callTool({
261
+ name: "standard_list",
246
262
  arguments: {},
247
263
  });
248
- assertParity(opResult, mcpRes);
249
- });
250
-
251
- it("standard_categories condensed: matches llm formatter", async () => {
252
- const opResult = await listCategories(rt.store);
253
- const expectedText = standardCatFmt.llm(opResult);
264
+ const rows = parseEnvelope(listRes).data as { category?: string }[];
265
+ const expected = new Map<string, number>();
266
+ for (const row of rows) {
267
+ if (row.category) {
268
+ expected.set(row.category, (expected.get(row.category) ?? 0) + 1);
269
+ }
270
+ }
254
271
 
255
272
  const mcpRes = await client.callTool({
256
273
  name: "standard_categories",
257
- arguments: { condensed: true },
274
+ arguments: {},
258
275
  });
259
- const body = parseCondensed(mcpRes);
260
- expect(body.text).toBe(expectedText);
276
+ const body = parseEnvelope(mcpRes);
277
+ expect(body.ok).toBe(true);
278
+ const categories = body.data as { name: string; count: string }[];
279
+ expect(categories.length).toBeGreaterThan(0);
280
+ for (const [category, count] of expected) {
281
+ const row = categories.find((entry) => entry.name === category);
282
+ expect(row, `category "${category}" missing`).toBeDefined();
283
+ expect(Number(row?.count)).toBe(count);
284
+ }
261
285
  });
262
286
  });
263
287
 
@@ -265,26 +289,51 @@ describe("standard parity", () => {
265
289
  // Tier parity
266
290
  // ---------------------------------------------------------------------------
267
291
 
268
- describe("tier parity", () => {
269
- it("tier_list: paths and depths match", async () => {
292
+ // `tier list` / `tier_list` is now served by the bundled `tier` story pack
293
+ // (the hand-written domain was deleted). Pack rows are `Record<string,string>`
294
+ // projected from the SELECT, so the shape is intentionally uniform: `{uri,
295
+ // name}` with no always-zero `depth` field, and the llm renderer is the
296
+ // generic pack renderer. These tests assert *semantic* parity — every tier the
297
+ // shared operation sees is present, by the same names — rather than byte
298
+ // equality with the removed bespoke formatter.
299
+ describe("tier parity (bundled pack)", () => {
300
+ it("tier_list returns one {uri, name} row per ontology tier", async () => {
270
301
  const opResult = await listTiers(rt.store);
271
- const mcpRes = await client.callTool({
272
- name: "tier_list",
273
- arguments: {},
274
- });
275
- assertParity(opResult, mcpRes);
302
+ const mcpRes = await client.callTool({ name: "tier_list", arguments: {} });
303
+
304
+ const content = mcpRes.content as { type: string; text: string }[];
305
+ const envelope = JSON.parse(content[0].text) as {
306
+ ok: boolean;
307
+ data: Array<{ uri?: string; name?: string }>;
308
+ };
309
+ expect(envelope.ok).toBe(true);
310
+
311
+ // Same tiers, same names — no tier dropped or invented by the cutover.
312
+ // The shared op names the field `path`; the pack names it `name` (the
313
+ // honest `ds:name` mapping) — the values are identical.
314
+ expect(new Set(envelope.data.map((r) => r.name))).toEqual(
315
+ new Set(opResult.map((t) => t.path)),
316
+ );
317
+ // Uniform pack shape: uri + name, no bespoke `depth` field.
318
+ for (const row of envelope.data) {
319
+ expect(row.uri).toBeTruthy();
320
+ expect(row).not.toHaveProperty("depth");
321
+ }
276
322
  });
277
323
 
278
- it("tier_list condensed: matches llm formatter", async () => {
324
+ it("tier_list condensed lists every tier under a markdown heading", async () => {
279
325
  const opResult = await listTiers(rt.store);
280
- const expectedText = tierListFmt.llm(opResult);
281
-
282
326
  const mcpRes = await client.callTool({
283
327
  name: "tier_list",
284
328
  arguments: { condensed: true },
285
329
  });
286
330
  const body = parseCondensed(mcpRes);
287
- expect(body.text).toBe(expectedText);
331
+
332
+ // The generic pack llm renderer headings by the capitalized noun: `## Tier`.
333
+ expect(body.text).toContain("## Tier");
334
+ for (const tier of opResult) {
335
+ expect(body.text).toContain(tier.path);
336
+ }
288
337
  });
289
338
  });
290
339
 
@@ -342,7 +391,7 @@ describe("modifier parity", () => {
342
391
  // Token parity
343
392
  // ---------------------------------------------------------------------------
344
393
 
345
- describe("token parity", () => {
394
+ describe.skipIf(!TOKEN_READ_SURFACE_ENABLED)("token parity", () => {
346
395
  it("token_list: count matches", async () => {
347
396
  const opResult = await listTokens(rt.store);
348
397
  const mcpRes = await client.callTool({
@@ -1,11 +1,18 @@
1
1
  /**
2
- * Global test setup: isolate the XDG config layer.
2
+ * Global test setup: isolate the XDG config and data layers.
3
3
  *
4
4
  * The layered config reader consults `$XDG_CONFIG_HOME/pragma/config.json`
5
- * on every read, and global-first writes create it. Point XDG at a
6
- * per-run temp directory so tests neither observe nor pollute the
7
- * developer's real global configuration. Individual tests that exercise
8
- * XDG behavior override this value themselves.
5
+ * on every read, and global-first writes create it; the schema artifact
6
+ * writes to `$XDG_DATA_HOME/pragma/schema.graphql`. Point both at
7
+ * per-run temp directories so tests neither observe nor pollute the
8
+ * developer's real global state. Individual tests that exercise XDG
9
+ * behavior override these values themselves.
10
+ *
11
+ * The boot-time schema artifact emission is disabled globally: it would
12
+ * add a full ke-graphql compile to every test that drives the real CLI
13
+ * pipeline and print an "Updated ..." notice on first boot. Its behavior
14
+ * is covered directly by ensureSchemaArtifact.test.ts; tests exercising
15
+ * the boot hook re-enable it explicitly.
9
16
  */
10
17
 
11
18
  import { mkdtempSync } from "node:fs";
@@ -13,3 +20,7 @@ import { tmpdir } from "node:os";
13
20
  import { join } from "node:path";
14
21
 
15
22
  process.env.XDG_CONFIG_HOME = mkdtempSync(join(tmpdir(), "pragma-test-xdg-"));
23
+ process.env.XDG_DATA_HOME = mkdtempSync(
24
+ join(tmpdir(), "pragma-test-xdg-data-"),
25
+ );
26
+ process.env.PRAGMA_SCHEMA_ARTIFACT = "off";
@@ -1,92 +1,35 @@
1
1
  /**
2
- * Standard-noun parity pilot fixtures.
2
+ * Standard-noun cutover parity contract.
3
3
  *
4
- * The built-in `standard` list/lookup read stories re-expressed as a v0
5
- * story-pack definition, plus the honest record of what v0 cannot yet
6
- * reproduce. Together they form the pilot's contract: the definition is
7
- * asserted byte-compatible with the built-in output where the format
8
- * reaches (`standardParity.test.ts`), and every remaining divergence
9
- * must appear in {@link PARITY_GAPS} rather than being papered over.
10
- */
11
-
12
- import type { StoryPackDefinition } from "../domains/shared/stories/pack/types.js";
13
-
14
- /**
15
- * The built-in `standard` stories as a declarative pack definition.
4
+ * The hand-written `standard` domain was deleted; the noun is served by
5
+ * the bundled pack-v1 definition (`pack/bundled/standardPack.ts`), and
6
+ * `standardParity.test.ts` asserts SEMANTIC parity against the real
7
+ * @canonical/code-standards graphs: same entities and values reachable,
8
+ * uniform pack row/entity shapes, generic renderers.
16
9
  *
17
- * The list SELECT mirrors the query `listStandards` sends the store,
18
- * with variables renamed to the built-in's output fields (`?standard` →
19
- * `?uri`, `?categoryName` → `?category`) so pack rows carry the same
20
- * keys as the built-in summary items. The lookup mirrors the built-in
21
- * title and inline fields as far as v0 reaches — {@link PARITY_GAPS}
22
- * lists what it cannot express.
10
+ * {@link PARITY_GAPS} is the honest record of the differences that
11
+ * remain BY DESIGN after the cutover. Each entry names a concrete,
12
+ * accepted divergence from the deleted built-in — remove an entry only
13
+ * when the pack format gains the capability and the parity test asserts
14
+ * it.
23
15
  */
24
- export const STANDARD_PACK_STORY: StoryPackDefinition = {
25
- noun: "standard",
26
- description: "List all code standards",
27
- list: {
28
- query: [
29
- "SELECT ?uri ?name ?category ?description",
30
- "WHERE {",
31
- " ?uri a cs:CodeStandard ;",
32
- " cs:name ?name ;",
33
- " cs:description ?description .",
34
- " OPTIONAL {",
35
- " ?uri cs:hasCategory ?cat .",
36
- " ?cat cs:slug ?category .",
37
- " }",
38
- "}",
39
- "ORDER BY ?name",
40
- ].join("\n"),
41
- columns: [
42
- { field: "uri", label: "IRI" },
43
- { field: "name", label: "Name" },
44
- { field: "category", label: "Category" },
45
- { field: "description", label: "Description" },
46
- ],
47
- },
48
- lookup: {
49
- type: "cs:CodeStandard",
50
- by: "cs:name",
51
- fields: [
52
- {
53
- name: "category",
54
- property: "cs:hasCategory/cs:slug",
55
- label: "Category",
56
- },
57
- {
58
- name: "description",
59
- property: "cs:description",
60
- label: "Description",
61
- },
62
- { name: "extends", property: "cs:extends", label: "Extends" },
63
- ],
64
- },
65
- };
66
16
 
67
17
  /**
68
- * Every place the v0 pack format cannot reproduce the built-in standard
69
- * stories — the pilot's primary output. Each entry names the concrete
70
- * missing pack-format capability; remove an entry only when the format
71
- * gains the capability and the parity test asserts it.
18
+ * Accepted post-cutover divergences from the deleted built-in standard
19
+ * domain. These are doctrine (semantic parity, not byte parity) plus the
20
+ * capabilities pack v1 deliberately does not carry.
72
21
  */
73
22
  export const PARITY_GAPS: readonly string[] = [
74
- "list params: packs declare no story parameters, so the built-in --category and --search filters (and their MCP tool arguments) cannot be expressed",
75
- "list disclosure: no digest/detailed progressive-disclosure levels — the built-in --digest/--detailed flags enrich rows with extends/example/dos/donts and add disclosure meta to the MCP envelope; pack lists always render summary rows",
76
- "list empty guard: the built-in raises EMPTY_RESULTS with an install-hint recovery when no standards load; the pack format has no emptyError hook, so a pack list renders nothing",
77
- "list plain template: packs render the shared aligned-column table; the built-in renders stacked `name [category]` blocks with an indented description — v0 columns carry no layout or template control",
78
- "list llm template: pack headings are `## <Noun> (<count>)` with `` `iri` — **name** value | value `` items; the built-in emits `## Standards` with `- **name** [category]` plus an indented description line — heading text and item template are not authorable",
79
- "lookup uri field: the entity IRI cannot be declared as an inline field — v0 fields are property-derived, and a field named `uri` generates a duplicate ?uri SELECT variable the store rejects; the built-in renders `URI: cs:...` as its first lookup field",
80
- "lookup property paths: `category` needs the two-hop path cs:hasCategory/cs:slug; the documented v0 `property` contract is a single predicate, and the path only passes validation because the prefixed-name pattern rejects `/` in the first character alone — multi-hop fields need first-class support",
81
- "lookup data compaction: the built-in compacts `extends` in resolved data (JSON shows `cs:ComponentFolderStructure`); pack rows keep the raw IRI, so lookup JSON diverges for standards with cs:extends (plain/llm still match because renderers compact at display time)",
82
- "lookup sections: dos/donts (cs:do/cs:dont → Example nodes with description/language/code) cannot be declared — v0 sections are single-valued field/code kinds, and the generated single-row lookup query cannot collect arrays of structured code blocks",
83
- "lookup detailed toggle: no detailedParam — the built-in CLI adds Do/Don't sections under --detailed and MCP defaults to detailed with a summary projection (project); a pack lookup has one fixed output shape",
84
- "lookup empty placeholders: the built-in renders `Category: —` for a standard without a category; pack renderers skip empty fields — no fallback/placeholder declaration (every current standard has a category, so outputs match today)",
85
- "lookup glob expansion: the built-in expands glob queries (e.g. `react/*`) via expandLookupQueries before resolving; pack lookups pass names through verbatim",
86
- "lookup by IRI: the built-in resolves an IRI or prefixed name (e.g. `cs:ComponentProps`) as the query; a pack lookup only matches the `by` property's string value, and only case-insensitively — exact-match semantics are not selectable",
87
- "lookup recovery copy: built-in not-found errors carry cross-domain hints and the recovery message `List available standards.`; pack errors generate `List available standard entries.` — recovery copy is not authorable",
88
- "lookup descriptions and examples: the pack lookup CLI description, tool description, names description, and examples are generated from the noun; the built-in curates all of them (e.g. `Look up detailed information for a standard by name or IRI`)",
89
- "ink renderers: the built-in stories declare renderInk TUI views; the pack format has none",
90
- "extra verbs: the standard noun also ships `categories` and `sample` commands; v0 packs compile only list and lookup",
91
- "noun cutover: a pack story cannot register the noun `standard` while the built-in exists (reserved-noun guard in collectPackStories), so cutover must remove the built-in stories in the same release",
23
+ "list disclosure: pack lists are single-level — the old `standard list --digest/--detailed` row enrichment (extends/example/dos/donts merged into every row, disclosure meta on the MCP envelope) is gone; the same data is reachable per-entity via `standard lookup --detail digest|detailed` and `standard sample`",
24
+ "list empty guard: the old list raised EMPTY_RESULTS with an install-hint recovery when no standards load or a --category matched nothing; the pack format has no emptyError hook, so an empty or fully-filtered list renders zero rows successfully",
25
+ "lookup digest rendering: the old list digest truncated the first do-example to 120 chars; the pack `digest` level fetches the full `dos` expand instead (richer, not byte-equal)",
26
+ "lookup data compaction: the old domain compacted cs:extends in resolved data (JSON showed `cs:ComponentFolderStructure`); pack entities keep the raw IRI in JSON — plain/llm output still compacts at display time",
27
+ "lookup recovery copy: cross-domain hints (detectCrossDomain) and the curated `List available standards.` message are not authorable; pack misses carry ranked suggestions with the generic `List available standard entries.` copy",
28
+ 'typed shapes: pack rows/entities are all-string records — category counts are strings, and dos/donts entries are `{caption?, language?, code?}` string records rather than typed CodeBlock objects (no `language: "typescript"` default, absent captions are omitted)',
29
+ "render templates: plain/llm output uses the generic pack renderers (aligned columns, `## Standard (n)` headings, `- key: value` collection entries), not the old bespoke standard templates",
30
+ "sample count flag: the pack sample takes a positional count (`standard sample 3`) instead of the old `--count 3` flag",
31
+ "lookup IRI display: plain/llm lookup output does not print the entity IRI (the pack renderer titles by name and projects no uri field); the IRI stays reachable via the list's IRI column, `--format json`, and the non-condensed MCP envelope",
32
+ "categories JSON shape: the old CategorySummary carried a numeric `standardCount`; pack category rows carry the key `count` with a string value (all-string pack rows, until pack v2 typed columns)",
33
+ "sample population: totalCount counts name-addressable standards (the set the sampler can actually resolve) rather than all list rows; the old op counted every list row but failed outright when it drew a standard without cs:name",
34
+ "ink renderers: the pack format declares no renderInk TUI views",
92
35
  ];
@@ -2,12 +2,12 @@
2
2
  * Test store bridge — createTestStore, PragmaTestStoreOptions.
3
3
  *
4
4
  * Wraps `@canonical/ke/testing` with pragma's default prefix map
5
- * pre-configured so test authors do not need to repeat PREFIX_MAP.
5
+ * pre-configured so test authors do not need to repeat DEFAULT_PREFIX_MAP.
6
6
  */
7
7
 
8
8
  import type { TestStoreOptions, TestStoreResult } from "@canonical/ke/testing";
9
9
  import { createTestStore as keCreateTestStore } from "@canonical/ke/testing";
10
- import { PREFIX_MAP } from "../domains/shared/prefixes.js";
10
+ import { DEFAULT_PREFIX_MAP } from "../domains/shared/prefixes.js";
11
11
 
12
12
  /** Options for creating a test store, with pragma prefixes pre-applied. */
13
13
  interface PragmaTestStoreOptions extends Omit<TestStoreOptions, "prefixes"> {
@@ -18,7 +18,7 @@ interface PragmaTestStoreOptions extends Omit<TestStoreOptions, "prefixes"> {
18
18
  * Create a test ke store with pragma's default prefix map.
19
19
  *
20
20
  * @param options - Optional overrides; additional prefixes are merged
21
- * on top of the default PREFIX_MAP.
21
+ * on top of the default DEFAULT_PREFIX_MAP.
22
22
  * @returns The test store result from ke/testing.
23
23
  *
24
24
  * @note Impure
@@ -29,7 +29,7 @@ async function createTestStore(
29
29
  return keCreateTestStore({
30
30
  ...options,
31
31
  prefixes: {
32
- ...PREFIX_MAP,
32
+ ...DEFAULT_PREFIX_MAP,
33
33
  ...options.prefixes,
34
34
  },
35
35
  });
@@ -1,166 +0,0 @@
1
- import type { CommandContext, CommandDefinition } from "@canonical/cli-core";
2
- import { describe, expect, it } from "vitest";
3
- import { PragmaError } from "#error";
4
- import buildComponentCommand from "./component.js";
5
-
6
- const ctx: CommandContext = {
7
- cwd: "/tmp",
8
- globalFlags: { llm: false, format: "text" as const, verbose: false },
9
- };
10
-
11
- const llmCtx: CommandContext = {
12
- cwd: "/tmp",
13
- globalFlags: { llm: true, format: "text" as const, verbose: false },
14
- };
15
-
16
- const jsonCtx: CommandContext = {
17
- cwd: "/tmp",
18
- globalFlags: { llm: false, format: "json" as const, verbose: false },
19
- };
20
-
21
- async function executeOutput(
22
- cmd: CommandDefinition,
23
- params: Record<string, unknown>,
24
- context: CommandContext,
25
- ): Promise<{ value: unknown; text: string }> {
26
- const result = await cmd.execute(params, context);
27
- expect(result.tag).toBe("output");
28
- if (result.tag !== "output") throw new Error("Expected output result");
29
- const text = result.render.plain(result.value);
30
- return { value: result.value, text };
31
- }
32
-
33
- describe("buildComponentCommand", () => {
34
- it("has correct path", () => {
35
- const cmd = buildComponentCommand();
36
- expect(cmd.path).toEqual(["create", "component"]);
37
- });
38
-
39
- it("includes framework as positional required select parameter", () => {
40
- const cmd = buildComponentCommand();
41
- const framework = cmd.parameters.find((p) => p.name === "framework");
42
- expect(framework).toBeDefined();
43
- expect(framework?.positional).toBe(true);
44
- expect(framework?.required).toBe(true);
45
- expect(framework?.type).toBe("select");
46
- expect(framework?.choices?.map((c) => c.value)).toEqual([
47
- "react",
48
- "svelte",
49
- "lit",
50
- ]);
51
- });
52
-
53
- it("includes prompt-derived parameters", () => {
54
- const cmd = buildComponentCommand();
55
- const names = cmd.parameters.map((p) => p.name);
56
- expect(names).toContain("componentPath");
57
- expect(names).toContain("withStyles");
58
- expect(names).toContain("withStories");
59
- expect(names).toContain("withSsrTests");
60
- });
61
-
62
- it("includes execution-mode parameters", () => {
63
- const cmd = buildComponentCommand();
64
- const names = cmd.parameters.map((p) => p.name);
65
- expect(names).toContain("dryRun");
66
- expect(names).toContain("yes");
67
- expect(names).toContain("showFiles");
68
- expect(names).toContain("preview");
69
- expect(names).toContain("generatedStamp");
70
- });
71
-
72
- it("dry-run with react returns output with file paths", async () => {
73
- const cmd = buildComponentCommand();
74
- const { text } = await executeOutput(
75
- cmd,
76
- {
77
- framework: "react",
78
- componentPath: "src/components/Button",
79
- dryRun: true,
80
- yes: true,
81
- },
82
- ctx,
83
- );
84
- expect(text).toContain("Button");
85
- expect(text).toContain("src/components/Button");
86
- });
87
-
88
- it("dry-run with svelte returns output with svelte files", async () => {
89
- const cmd = buildComponentCommand();
90
- const { text } = await executeOutput(
91
- cmd,
92
- {
93
- framework: "svelte",
94
- componentPath: "src/lib/components/Toggle",
95
- dryRun: true,
96
- yes: true,
97
- },
98
- ctx,
99
- );
100
- expect(text).toContain("Toggle");
101
- expect(text).toContain("src/lib/components/Toggle");
102
- });
103
-
104
- it("LLM mode returns markdown", async () => {
105
- const cmd = buildComponentCommand();
106
- const { text } = await executeOutput(
107
- cmd,
108
- {
109
- framework: "react",
110
- componentPath: "src/components/Card",
111
- },
112
- llmCtx,
113
- );
114
- // LLM markdown output includes generator name header
115
- expect(text).toContain("component/react");
116
- });
117
-
118
- it("JSON mode returns parseable JSON", async () => {
119
- const cmd = buildComponentCommand();
120
- const { text } = await executeOutput(
121
- cmd,
122
- {
123
- framework: "react",
124
- componentPath: "src/components/Card",
125
- },
126
- jsonCtx,
127
- );
128
- const data = JSON.parse(text);
129
- expect(data.generator.name).toBe("component/react");
130
- expect(Array.isArray(data.plan)).toBe(true);
131
- });
132
-
133
- it("throws PragmaError for invalid framework", async () => {
134
- const cmd = buildComponentCommand();
135
- await expect(cmd.execute({ framework: "angular" }, ctx)).rejects.toThrow(
136
- PragmaError,
137
- );
138
- });
139
-
140
- it("throws PragmaError when framework is missing", async () => {
141
- const cmd = buildComponentCommand();
142
- await expect(cmd.execute({}, ctx)).rejects.toThrow(PragmaError);
143
- });
144
-
145
- it("returns interactive result when required answers are missing", async () => {
146
- const cmd = buildComponentCommand();
147
- // framework provided but componentPath has a default, so it should NOT
148
- // trigger interactive mode — all prompts have defaults
149
- const result = await cmd.execute({ framework: "lit", dryRun: true }, ctx);
150
- // componentPath has a default, so executeGenerator applies it and proceeds
151
- expect(result.tag).toBe("output");
152
- });
153
-
154
- it("has parameter groups", () => {
155
- const cmd = buildComponentCommand();
156
- expect(cmd.parameterGroups).toEqual({
157
- Component: ["componentPath"],
158
- Options: ["withStyles", "withStories", "withSsrTests"],
159
- });
160
- });
161
-
162
- it("has examples in meta", () => {
163
- const cmd = buildComponentCommand();
164
- expect(cmd.meta?.examples?.length).toBeGreaterThan(0);
165
- });
166
- });