@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
@@ -0,0 +1,64 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { createTestStore, DS_ALL_TTL } from "#testing";
4
+ import type { FilterConfig } from "../../shared/types/index.js";
5
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../token/featureFlag.js";
6
+ import { renderLlmOrientation } from "../formatters/index.js";
7
+ import type { LlmData } from "../types.js";
8
+ import collectContext from "./collectContext.js";
9
+
10
+ let store: Store;
11
+ let cleanup: () => void;
12
+
13
+ beforeAll(async () => {
14
+ const result = await createTestStore({ ttl: DS_ALL_TTL });
15
+ store = result.store;
16
+ cleanup = result.cleanup;
17
+ });
18
+
19
+ afterAll(() => cleanup());
20
+
21
+ const config: FilterConfig = { tier: undefined, channel: "prerelease" };
22
+
23
+ /** Extract the `## Context` data lines from a rendered orientation doc. */
24
+ function contextBlock(rendered: string): string {
25
+ const lines = rendered.split("\n");
26
+ const start = lines.indexOf("## Context");
27
+ const block: string[] = [];
28
+ for (let i = start + 1; i < lines.length && lines[i] !== ""; i++) {
29
+ block.push(lines[i] ?? "");
30
+ }
31
+ return block.join("\n");
32
+ }
33
+
34
+ describe("collectContext", () => {
35
+ it("assembles counts and namespaces from the store", async () => {
36
+ const ctx = await collectContext(store, config);
37
+
38
+ expect(ctx.tier).toBeUndefined();
39
+ expect(ctx.channel).toBe("prerelease");
40
+ expect(ctx.counts.blocks).toBeGreaterThan(0);
41
+ expect(ctx.counts.standards).toBeGreaterThan(0);
42
+ expect(ctx.counts.modifierFamilies).toBeGreaterThan(0);
43
+ expect(ctx.counts.tokens).toBeGreaterThan(0);
44
+ expect(ctx.namespaces.length).toBeGreaterThan(0);
45
+ });
46
+
47
+ it("renders a stable `## Context` block", async () => {
48
+ const data: LlmData = {
49
+ context: await collectContext(store, config),
50
+ decisionTrees: [],
51
+ commandReference: [],
52
+ };
53
+
54
+ // The token count only renders while the token read surface is enabled.
55
+ const tokenSegment = TOKEN_READ_SURFACE_ENABLED ? ", 2 tokens" : "";
56
+ expect(contextBlock(renderLlmOrientation(data))).toBe(
57
+ [
58
+ "tier: (none) | channel: prerelease",
59
+ `data: 5 blocks, 3 standards, 2 modifier families${tokenSegment}`,
60
+ "namespaces: cs, ds",
61
+ ].join("\n"),
62
+ );
63
+ });
64
+ });
@@ -1,18 +1,16 @@
1
1
  import type { Store } from "@canonical/ke";
2
- import { listBlocks } from "../../block/operations/index.js";
3
- import { listModifiers } from "../../modifier/operations/index.js";
2
+ import { collectEntityCounts } from "../../info/operations/index.js";
4
3
  import { listOntologies } from "../../ontology/operations/index.js";
5
4
  import { resolveTierChain } from "../../shared/filters/buildTierFilter.js";
6
5
  import type { FilterConfig } from "../../shared/types/index.js";
7
- import { listStandards } from "../../standard/operations/index.js";
8
- import { listTokens } from "../../token/operations/index.js";
9
6
  import type { LlmContext } from "../types.js";
10
7
 
11
8
  /**
12
9
  * Collects dynamic context for the `pragma llm` orientation output.
13
10
  *
14
- * Queries the store in parallel for block, standard, modifier, token, and
15
- * ontology counts, and combines them with tier/channel config state.
11
+ * Queries the store in parallel for entity counts (via
12
+ * {@link collectEntityCounts}) and ontology namespaces, and combines them
13
+ * with tier/channel config state.
16
14
  *
17
15
  * @note Queries ke store
18
16
  *
@@ -24,11 +22,8 @@ export default async function collectContext(
24
22
  store: Store,
25
23
  config: FilterConfig,
26
24
  ): Promise<LlmContext> {
27
- const [blocks, standards, modifiers, tokens, ontologies] = await Promise.all([
28
- listBlocks(store, config),
29
- listStandards(store),
30
- listModifiers(store),
31
- listTokens(store),
25
+ const [counts, ontologies] = await Promise.all([
26
+ collectEntityCounts(store, config),
32
27
  listOntologies(store),
33
28
  ]);
34
29
 
@@ -36,12 +31,7 @@ export default async function collectContext(
36
31
  tier: config.tier,
37
32
  tierChain: resolveTierChain(config.tier),
38
33
  channel: config.channel,
39
- counts: {
40
- blocks: blocks.length,
41
- standards: standards.length,
42
- modifierFamilies: modifiers.length,
43
- tokens: tokens.length,
44
- },
34
+ counts,
45
35
  namespaces: ontologies.map((o) => o.prefix),
46
36
  };
47
37
  }
@@ -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 { ModifierFamily, SampleOutput } from "../../shared/types/index.js";
15
16
  import { sampleFormatters } from "../formatters/index.js";
16
17
  import { sampleModifiers } from "../operations/index.js";
@@ -37,7 +38,7 @@ export default function sampleCommand(ctx: PragmaContext): CommandDefinition {
37
38
  examples: ["pragma modifier sample", "pragma modifier sample --count 3"],
38
39
  },
39
40
  async execute(params) {
40
- const count = Number(params.count ?? 2);
41
+ const count = parseSampleCount(params.count);
41
42
  const result = await sampleModifiers(ctx.store, count);
42
43
 
43
44
  const output: SampleOutput<ModifierFamily> = {
@@ -28,7 +28,7 @@ export default async function lookupModifier(
28
28
 
29
29
  const result = await store.query(
30
30
  buildQuery(`
31
- SELECT ?family (GROUP_CONCAT(DISTINCT ?valueName; separator="|") AS ?values)
31
+ SELECT ?family ?familyName (GROUP_CONCAT(DISTINCT ?valueName; separator="|") AS ?values)
32
32
  WHERE {
33
33
  ?family a ${P.ds}ModifierFamily ;
34
34
  ${P.ds}name ?familyName .
@@ -39,7 +39,7 @@ export default async function lookupModifier(
39
39
  ${P.ds}name ?valueName .
40
40
  }
41
41
  }
42
- GROUP BY ?family
42
+ GROUP BY ?family ?familyName
43
43
  `),
44
44
  );
45
45
 
@@ -61,7 +61,9 @@ export default async function lookupModifier(
61
61
  const b = result.bindings[0] as (typeof result.bindings)[number];
62
62
  return {
63
63
  uri: (b.family ?? "") as URI,
64
- name,
64
+ // Return the canonical stored name, not the raw user query (which may
65
+ // differ in casing).
66
+ name: b.familyName ?? name,
65
67
  values: b.values ? b.values.split("|").filter(Boolean) : [],
66
68
  };
67
69
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { createListView, createLookupView } from "#tui";
10
+ import { PragmaError } from "../../error/index.js";
10
11
  import type { LookupStory, ReadStory } from "../shared/stories/index.js";
11
12
  import type { ModifierFamily } from "../shared/types/index.js";
12
13
  import {
@@ -67,6 +68,14 @@ export const modifierLookupStory: LookupStory<ModifierFamily, ModifierFamily> =
67
68
  },
68
69
  toFmtInput: (family) => family,
69
70
  formatters: lookupFormatters,
71
+ emptyNamesError: () =>
72
+ PragmaError.invalidInput("names", "(empty)", {
73
+ recovery: {
74
+ message: "List available modifier families.",
75
+ cli: "pragma modifier list",
76
+ mcp: { tool: "modifier_list" },
77
+ },
78
+ }),
70
79
  renderInk: (result) =>
71
80
  createLookupView({
72
81
  results: result.results,
@@ -52,8 +52,10 @@ describe("ontology list command", () => {
52
52
  const { text } = await executeOutput(cmd, {}, ctx);
53
53
 
54
54
  expect(text).toContain("ds");
55
- expect(text).toContain("classes:");
56
- expect(text).toContain("anatomy:");
55
+ expect(text).toContain("classes");
56
+ expect(text).toContain("relations");
57
+ expect(text).toContain("attributes");
58
+ expect(text).toContain("Next: pragma ontology show <prefix>");
57
59
  });
58
60
 
59
61
  it("renders llm output", async () => {
@@ -65,7 +67,7 @@ describe("ontology list command", () => {
65
67
 
66
68
  expect(text).toContain("## Ontologies");
67
69
  expect(text).toContain("**ds:**");
68
- expect(text).toContain("anatomy:");
70
+ expect(text).toContain("relations");
69
71
  });
70
72
 
71
73
  it("renders json output", async () => {
@@ -3,7 +3,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
3
  import createTestRuntime from "../../../testing/helpers/createTestRuntime.js";
4
4
  import type { PragmaContext } from "../../shared/context.js";
5
5
  import type { PragmaRuntime } from "../../shared/runtime.js";
6
- import type { OntologyDetailed } from "../../shared/types/index.js";
6
+ import type { OntologyShowInput } from "../formatters/index.js";
7
7
  import buildShowCommand from "./show.js";
8
8
 
9
9
  let runtime: PragmaRuntime;
@@ -41,10 +41,69 @@ describe("ontology show command", () => {
41
41
  const cmd = buildShowCommand(ctx);
42
42
  const { value } = await executeOutput(cmd, { prefix: "ds" }, ctx);
43
43
 
44
- const result = value as OntologyDetailed;
45
- expect(result.prefix).toBe("ds");
46
- expect(result.classes.length).toBeGreaterThan(0);
47
- expect(result.properties.length).toBeGreaterThan(0);
44
+ const { ontology } = value as OntologyShowInput;
45
+ expect(ontology.prefix).toBe("ds");
46
+ expect(ontology.classes.length).toBeGreaterThan(0);
47
+ const propertyCount =
48
+ ontology.classes.reduce((n, c) => n + c.properties.length, 0) +
49
+ ontology.unattached.length;
50
+ expect(propertyCount).toBeGreaterThan(0);
51
+ });
52
+
53
+ it("deep-dives into a class via the class param", async () => {
54
+ const ctx = makeCtx();
55
+ const cmd = buildShowCommand(ctx);
56
+ const { value, text } = await executeOutput(
57
+ cmd,
58
+ { prefix: "ds", class: "Component" },
59
+ ctx,
60
+ );
61
+
62
+ const { ontology } = value as OntologyShowInput;
63
+ expect(ontology.focus?.iri).toBe("ds:Component");
64
+ expect(text).toContain("Component");
65
+ expect(text).toContain("extends:");
66
+ // Follow-up querying: the deep dive hands over runnable SPARQL.
67
+ expect(text).toContain(
68
+ "SELECT ?instance WHERE { ?instance a ds:Component }",
69
+ );
70
+ });
71
+
72
+ it("hides attributes by default and shows them with --properties", async () => {
73
+ const ctx = makeCtx();
74
+ const cmd = buildShowCommand(ctx);
75
+
76
+ const bare = await executeOutput(cmd, { prefix: "ds" }, ctx);
77
+ // Attributes render as `label: range` — absent by default.
78
+ expect(bare.text).not.toContain(": xsd:");
79
+ // hasModifierFamily is an object property (relation) — always shown.
80
+ expect(bare.text).toContain("hasModifierFamily");
81
+
82
+ const withProps = await executeOutput(
83
+ cmd,
84
+ { prefix: "ds", properties: true },
85
+ ctx,
86
+ );
87
+ expect(withProps.text).toContain(": xsd:");
88
+ });
89
+
90
+ it("expands IRIs with --full-uris", async () => {
91
+ const ctx = makeCtx({
92
+ globalFlags: { llm: true, format: "text" as const, verbose: false },
93
+ });
94
+ const cmd = buildShowCommand(ctx);
95
+ const { value, text } = await executeOutput(
96
+ cmd,
97
+ { prefix: "ds", fullUris: true },
98
+ ctx,
99
+ );
100
+
101
+ const { ontology } = value as OntologyShowInput;
102
+ const component = ontology.classes.find((c) => c.label === "Component");
103
+ expect(component?.iri).toBe("https://ds.canonical.com/Component");
104
+ expect(text).toContain("https://ds.canonical.com/Component");
105
+ // The seeded footer query must stay valid SPARQL: full URIs need <...>.
106
+ expect(text).toMatch(/\?s a <https:\/\/ds\.canonical\.com\//);
48
107
  });
49
108
 
50
109
  it("completes ontology prefixes", async () => {
@@ -57,13 +116,30 @@ describe("ontology show command", () => {
57
116
  expect(matches).toContain("ds");
58
117
  });
59
118
 
119
+ it("completes class names for --class", async () => {
120
+ const ctx = makeCtx();
121
+ const cmd = buildShowCommand(ctx);
122
+ const classParam = cmd.parameters.find((p) => p.name === "class");
123
+ const complete = classParam?.complete;
124
+
125
+ expect(complete).toBeTypeOf("function");
126
+ const matches = await complete?.("comp", ctx);
127
+ expect(matches).toContain("Component");
128
+ // Case-insensitive prefix match, sorted, no non-matches.
129
+ expect(matches?.every((m) => m.toLowerCase().startsWith("comp"))).toBe(
130
+ true,
131
+ );
132
+ });
133
+
60
134
  it("renders plain output", async () => {
61
135
  const ctx = makeCtx();
62
136
  const cmd = buildShowCommand(ctx);
63
137
  const { text } = await executeOutput(cmd, { prefix: "ds" }, ctx);
64
138
 
65
- expect(text).toContain("Classes");
66
- expect(text).toContain("Properties");
139
+ expect(text).toContain("Ontology ds:");
140
+ expect(text).toContain("classes");
141
+ // Hierarchy: children render beneath their parent with tree branches.
142
+ expect(text).toContain("└─");
67
143
  });
68
144
 
69
145
  it("renders llm output", async () => {
@@ -73,7 +149,7 @@ describe("ontology show command", () => {
73
149
  const cmd = buildShowCommand(ctx);
74
150
  const { text } = await executeOutput(cmd, { prefix: "ds" }, ctx);
75
151
 
76
- expect(text).toContain("## ds:");
152
+ expect(text).toContain("## Ontology ds:");
77
153
  expect(text).toContain("### Classes");
78
154
  });
79
155
 
@@ -84,7 +160,7 @@ describe("ontology show command", () => {
84
160
  const cmd = buildShowCommand(ctx);
85
161
  const { text } = await executeOutput(cmd, { prefix: "ds" }, ctx);
86
162
 
87
- const parsed = JSON.parse(text) as OntologyDetailed;
163
+ const parsed = JSON.parse(text) as { prefix: string };
88
164
  expect(parsed.prefix).toBe("ds");
89
165
  });
90
166
 
@@ -1,4 +1,5 @@
1
1
  /** @module Barrel for ontology formatters. */
2
2
 
3
3
  export { default as listFormatters } from "./list.js";
4
+ export type { OntologyShowInput } from "./show.js";
4
5
  export { default as showFormatters } from "./show.js";
@@ -1,33 +1,39 @@
1
1
  import chalk from "chalk";
2
- import compactUri from "../../shared/compactUri.js";
3
2
  import type { Formatters } from "../../shared/formatters.js";
4
- import { PREFIX_MAP } from "../../shared/prefixes.js";
5
3
  import type { OntologySummary } from "../../shared/types/index.js";
6
4
 
7
5
  /**
8
6
  * Formatters for `pragma ontology list` output.
9
7
  *
10
- * - **plain**: one line per ontology with chalk-styled prefix, namespace, and counts.
11
- * - **llm**: condensed Markdown bullet list under an `## Ontologies` heading.
12
- * - **json**: serialized ontology summary array as indented JSON.
8
+ * One line per namespace mirroring the `ontology show` summary line —
9
+ * name, metadata, and split counts — plus a next-command footer, so the
10
+ * list is the entry point of the same drill-down path
11
+ * (`list → show <prefix> → show <prefix> --class <Class>`).
12
+ *
13
+ * - **plain**: chalk-styled lines with counts and a dimmed footer.
14
+ * - **llm**: the same content as condensed Markdown bullets.
15
+ * - **json**: the {@link OntologySummary} array itself.
13
16
  */
14
17
  const formatters: Formatters<readonly OntologySummary[]> = {
15
18
  plain(ontologies) {
16
- return ontologies
17
- .map(
18
- (o) =>
19
- `${chalk.bold(`${o.prefix}:`)} ${compactUri(o.namespace, PREFIX_MAP)} classes: ${o.classCount} properties: ${o.propertyCount} anatomy: ${o.anatomyCount}`,
20
- )
21
- .join("\n");
19
+ const lines = ontologies.map(
20
+ (o) =>
21
+ `${chalk.bold(prefixLabel(o))} ${o.namespace} ${chalk.dim(`— ${summaryLine(o)}`)}`,
22
+ );
23
+ lines.push("");
24
+ lines.push(chalk.dim("Next: pragma ontology show <prefix>"));
25
+ return lines.join("\n");
22
26
  },
23
27
 
24
28
  llm(ontologies) {
25
29
  const lines = ["## Ontologies", ""];
26
30
  for (const o of ontologies) {
27
31
  lines.push(
28
- `- **${o.prefix}:** \`${compactUri(o.namespace, PREFIX_MAP)}\` | classes: ${o.classCount} | properties: ${o.propertyCount} | anatomy: ${o.anatomyCount}`,
32
+ `- **${prefixLabel(o)}** \`${o.namespace}\` — ${summaryLine(o)}`,
29
33
  );
30
34
  }
35
+ lines.push("");
36
+ lines.push("Next: `ontology show <prefix>` for the class hierarchy.");
31
37
  return lines.join("\n");
32
38
  },
33
39
 
@@ -37,3 +43,22 @@ const formatters: Formatters<readonly OntologySummary[]> = {
37
43
  };
38
44
 
39
45
  export default formatters;
46
+
47
+ /** `ds:` for prefixed namespaces, a placeholder for unprefixed ones. */
48
+ function prefixLabel(o: OntologySummary): string {
49
+ return o.prefix ? `${o.prefix}:` : "(no prefix)";
50
+ }
51
+
52
+ /** The same counts-and-metadata line `ontology show` leads with. */
53
+ function summaryLine(o: OntologySummary): string {
54
+ const parts = [
55
+ `${o.classCount} classes`,
56
+ `${o.relationCount} relations`,
57
+ `${o.attributeCount} attributes`,
58
+ ];
59
+ if (o.shapeCount > 0) parts.push(`${o.shapeCount} shapes`);
60
+ if (o.anatomyCount > 0) parts.push(`${o.anatomyCount} anatomies`);
61
+ if (o.version) parts.push(`v${o.version}`);
62
+ if (o.title) parts.push(o.title);
63
+ return parts.join(" · ");
64
+ }