@canonical/pragma-cli 0.20.0 → 0.22.0-experimental.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 (203) hide show
  1. package/dist/pragma +0 -0
  2. package/package.json +31 -18
  3. package/src/bin.test.ts +2 -0
  4. package/src/bin.ts +1 -0
  5. package/src/compile-validation.ts +3 -4
  6. package/src/constants.ts +1 -1
  7. package/src/domains/block/blockConfig.ts +47 -3
  8. package/src/domains/block/commands/list.test.ts +1 -1
  9. package/src/domains/block/commands/list.ts +27 -0
  10. package/src/domains/block/commands/lookup.ts +26 -52
  11. package/src/domains/block/formatters/index.ts +1 -1
  12. package/src/domains/block/formatters/list.test.ts +1 -1
  13. package/src/domains/block/formatters/list.ts +28 -42
  14. package/src/domains/block/formatters/lookup.test.ts +3 -2
  15. package/src/domains/block/formatters/lookup.ts +91 -296
  16. package/src/domains/block/formatters/types.ts +1 -1
  17. package/src/domains/block/operations/list.test.ts +1 -1
  18. package/src/domains/block/operations/list.ts +1 -1
  19. package/src/domains/block/operations/lookup.test.ts +29 -8
  20. package/src/domains/block/operations/lookup.ts +94 -16
  21. package/src/domains/block/orchestration/blockEmptyError.ts +1 -1
  22. package/src/domains/block/orchestration/buildBlockFilters.ts +1 -1
  23. package/src/domains/block/orchestration/enrichBlocks.ts +6 -2
  24. package/src/domains/block/orchestration/resolveBlockList.ts +1 -1
  25. package/src/domains/block/orchestration/resolveBlockLookup.ts +8 -2
  26. package/src/domains/block/types.ts +1 -1
  27. package/src/domains/config/commands/channel.ts +6 -4
  28. package/src/domains/config/commands/tier.ts +6 -4
  29. package/src/domains/config/mcp/specs.ts +18 -6
  30. package/src/domains/config/operations/validateTier.ts +1 -1
  31. package/src/domains/config/tasks/index.ts +3 -0
  32. package/src/domains/config/tasks/setChannel.ts +30 -0
  33. package/src/domains/config/tasks/setTier.ts +33 -0
  34. package/src/domains/create/commands/package.test.ts +2 -2
  35. package/src/domains/doctor/mcp/index.ts +1 -1
  36. package/src/domains/doctor/mcp/specs.ts +2 -49
  37. package/src/domains/graph/commands/inspect.test.ts +1 -1
  38. package/src/domains/graph/formatters/inspect.ts +12 -7
  39. package/src/domains/graph/operations/inspectUri.ts +4 -1
  40. package/src/domains/info/commands/upgrade.ts +6 -3
  41. package/src/domains/info/index.ts +3 -1
  42. package/src/domains/info/mcp/index.ts +2 -0
  43. package/src/domains/info/mcp/specs.ts +65 -0
  44. package/src/domains/llm/data/commandReference.ts +4 -4
  45. package/src/domains/llm/data/decisionTrees.ts +14 -12
  46. package/src/domains/llm/operations/collectContext.ts +1 -1
  47. package/src/domains/modifier/commands/list.ts +9 -0
  48. package/src/domains/modifier/commands/lookup.ts +22 -42
  49. package/src/domains/modifier/formatters/index.ts +4 -1
  50. package/src/domains/modifier/formatters/list.test.ts +1 -1
  51. package/src/domains/modifier/formatters/list.ts +13 -26
  52. package/src/domains/modifier/formatters/lookup.test.ts +1 -1
  53. package/src/domains/modifier/formatters/lookup.ts +41 -29
  54. package/src/domains/modifier/index.ts +1 -0
  55. package/src/domains/modifier/modifierConfig.ts +36 -0
  56. package/src/domains/modifier/operations/list.ts +1 -1
  57. package/src/domains/modifier/operations/lookup.ts +4 -3
  58. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +1 -1
  59. package/src/domains/modifier/types.ts +1 -1
  60. package/src/domains/ontology/commands/list.test.ts +1 -1
  61. package/src/domains/ontology/commands/list.ts +18 -0
  62. package/src/domains/ontology/commands/show.test.ts +1 -1
  63. package/src/domains/ontology/formatters/list.ts +5 -3
  64. package/src/domains/ontology/formatters/show.ts +38 -13
  65. package/src/domains/ontology/helpers/queryClasses.ts +1 -1
  66. package/src/domains/ontology/helpers/queryProperties.ts +1 -1
  67. package/src/domains/ontology/operations/listOntologies.ts +1 -1
  68. package/src/domains/ontology/operations/showOntology.ts +1 -1
  69. package/src/domains/setup/helpers/completionScripts.test.ts +3 -3
  70. package/src/domains/setup/helpers/completionScripts.ts +4 -4
  71. package/src/domains/shared/ToolSpec.ts +1 -1
  72. package/src/domains/shared/context.ts +3 -2
  73. package/src/domains/shared/contracts.ts +40 -1
  74. package/src/domains/shared/filters/buildFilterConfig.ts +1 -2
  75. package/src/domains/shared/filters/buildFilters.ts +1 -1
  76. package/src/domains/shared/filters/describeFilters.ts +1 -1
  77. package/src/domains/shared/formatters.ts +47 -0
  78. package/src/domains/shared/renderers.ts +410 -0
  79. package/src/domains/shared/runtime.ts +7 -22
  80. package/src/domains/shared/types/block.ts +107 -0
  81. package/src/domains/shared/types/common.ts +33 -0
  82. package/src/domains/shared/types/graph.ts +17 -0
  83. package/src/domains/shared/types/index.ts +49 -0
  84. package/src/domains/shared/types/modifier.ts +22 -0
  85. package/src/domains/shared/types/ontology.ts +50 -0
  86. package/src/domains/shared/types/runtime.ts +21 -0
  87. package/src/domains/shared/types/standard.ts +64 -0
  88. package/src/domains/shared/types/tier.ts +15 -0
  89. package/src/domains/shared/types/token.ts +30 -0
  90. package/src/domains/standard/commands/list.ts +9 -0
  91. package/src/domains/standard/commands/lookup.ts +22 -50
  92. package/src/domains/standard/formatters/categories.test.ts +1 -1
  93. package/src/domains/standard/formatters/categories.ts +1 -1
  94. package/src/domains/standard/formatters/index.ts +4 -1
  95. package/src/domains/standard/formatters/list.test.ts +4 -1
  96. package/src/domains/standard/formatters/list.ts +112 -84
  97. package/src/domains/standard/formatters/lookup.test.ts +1 -1
  98. package/src/domains/standard/formatters/lookup.ts +85 -70
  99. package/src/domains/standard/formatters/types.ts +1 -1
  100. package/src/domains/standard/index.ts +1 -0
  101. package/src/domains/standard/operations/categories.ts +1 -1
  102. package/src/domains/standard/operations/list.ts +1 -1
  103. package/src/domains/standard/operations/lookup.ts +1 -1
  104. package/src/domains/standard/orchestration/buildStandardFilters.ts +1 -1
  105. package/src/domains/standard/orchestration/resolveStandardList.ts +4 -1
  106. package/src/domains/standard/orchestration/resolveStandardLookup.ts +1 -1
  107. package/src/domains/standard/orchestration/standardEmptyError.ts +1 -1
  108. package/src/domains/standard/standardConfig.ts +42 -0
  109. package/src/domains/standard/types.ts +1 -1
  110. package/src/domains/tier/commands/list.ts +16 -0
  111. package/src/domains/tier/formatters/list.test.ts +1 -1
  112. package/src/domains/tier/formatters/list.ts +11 -6
  113. package/src/domains/tier/operations/list.ts +1 -1
  114. package/src/domains/token/commands/list.ts +9 -0
  115. package/src/domains/token/commands/lookup.ts +22 -48
  116. package/src/domains/token/formatters/index.ts +4 -1
  117. package/src/domains/token/formatters/list.test.ts +1 -1
  118. package/src/domains/token/formatters/list.ts +15 -28
  119. package/src/domains/token/formatters/lookup.test.ts +2 -2
  120. package/src/domains/token/formatters/lookup.ts +80 -45
  121. package/src/domains/token/index.ts +1 -0
  122. package/src/domains/token/operations/list.ts +1 -1
  123. package/src/domains/token/operations/lookup.ts +1 -1
  124. package/src/domains/token/orchestration/resolveTokenLookup.ts +1 -1
  125. package/src/domains/token/tokenConfig.ts +38 -0
  126. package/src/domains/token/types.ts +1 -1
  127. package/src/embedWasm.ts +42 -0
  128. package/src/index.ts +1 -1
  129. package/src/mcp/runMcpServer.test.ts +2 -2
  130. package/src/mcp/tools/index.ts +2 -0
  131. package/src/mcp/tools/registerTools.test.ts +8 -3
  132. package/src/package-manager/types.ts +1 -3
  133. package/src/pipeline/createProgram.ts +20 -1
  134. package/src/pipeline/index.ts +4 -1
  135. package/src/pipeline/parseGlobalFlags.test.ts +92 -0
  136. package/src/pipeline/parseGlobalFlags.ts +25 -0
  137. package/src/pipeline/runCli.orchestration.test.ts +1 -0
  138. package/src/pipeline/runCli.ts +11 -4
  139. package/src/pipeline/runInteractiveCommand.ts +198 -0
  140. package/src/testing/cli.ts +2 -2
  141. package/src/testing/fixtures.ts +2 -2
  142. package/src/testing/integration/boot.test.ts +2 -2
  143. package/src/testing/integration/cli-user-stories.test.ts +198 -0
  144. package/src/testing/integration/edge-cases.test.ts +263 -0
  145. package/src/testing/integration/parity.test.ts +377 -27
  146. package/src/testing/store.ts +1 -1
  147. package/src/tui/components/BulletList/BulletList.tsx +46 -0
  148. package/src/tui/components/BulletList/index.ts +2 -0
  149. package/src/tui/components/BulletList/types.ts +6 -0
  150. package/src/tui/components/Card/Card.tsx +73 -0
  151. package/src/tui/components/Card/index.ts +1 -0
  152. package/src/tui/components/Card/types.ts +12 -0
  153. package/src/tui/components/CodeBlock/CodeBlock.tsx +37 -0
  154. package/src/tui/components/CodeBlock/index.ts +2 -0
  155. package/src/tui/components/CodeBlock/types.ts +6 -0
  156. package/src/tui/components/DataRow/DataRow.tsx +80 -0
  157. package/src/tui/components/DataRow/index.ts +2 -0
  158. package/src/tui/components/DataRow/types.ts +14 -0
  159. package/src/tui/components/FieldRow/FieldRow.tsx +24 -0
  160. package/src/tui/components/FieldRow/index.ts +2 -0
  161. package/src/tui/components/FieldRow/types.ts +10 -0
  162. package/src/tui/components/HeaderRow/HeaderRow.tsx +32 -0
  163. package/src/tui/components/HeaderRow/index.ts +2 -0
  164. package/src/tui/components/HeaderRow/types.ts +8 -0
  165. package/src/tui/components/MarkdownText/MarkdownText.tsx +19 -0
  166. package/src/tui/components/MarkdownText/index.ts +2 -0
  167. package/src/tui/components/MarkdownText/types.ts +4 -0
  168. package/src/tui/components/MiniTable/MiniTable.tsx +82 -0
  169. package/src/tui/components/MiniTable/index.ts +2 -0
  170. package/src/tui/components/MiniTable/types.ts +6 -0
  171. package/src/tui/components/SectionRenderer/SectionRenderer.tsx +91 -0
  172. package/src/tui/components/SectionRenderer/index.ts +2 -0
  173. package/src/tui/components/SectionRenderer/types.ts +15 -0
  174. package/src/tui/components/TreeView/TreeView.tsx +84 -0
  175. package/src/tui/components/TreeView/index.ts +2 -0
  176. package/src/tui/components/TreeView/types.ts +6 -0
  177. package/src/tui/components/index.ts +19 -0
  178. package/src/tui/constants.ts +39 -0
  179. package/src/tui/createListView.ts +23 -0
  180. package/src/tui/createLookupView.ts +26 -0
  181. package/src/tui/helpers/computeWidths.ts +53 -0
  182. package/src/tui/helpers/fitColumns.ts +33 -0
  183. package/src/tui/helpers/formatMarkdown.ts +19 -0
  184. package/src/tui/helpers/getTerminalSize.ts +16 -0
  185. package/src/tui/helpers/index.ts +6 -0
  186. package/src/tui/helpers/syntaxColor.ts +67 -0
  187. package/src/tui/helpers/truncateText.ts +12 -0
  188. package/src/tui/hooks/index.ts +3 -0
  189. package/src/tui/hooks/types.ts +7 -0
  190. package/src/tui/hooks/useAutoExit.ts +17 -0
  191. package/src/tui/hooks/useTerminalSize.ts +19 -0
  192. package/src/tui/index.ts +15 -0
  193. package/src/tui/renderInk.tsx +18 -0
  194. package/src/tui/types.ts +12 -0
  195. package/src/tui/views/ListView/ListView.tsx +104 -0
  196. package/src/tui/views/ListView/index.ts +2 -0
  197. package/src/tui/views/ListView/types.ts +14 -0
  198. package/src/tui/views/LookupView/LookupView.tsx +244 -0
  199. package/src/tui/views/LookupView/index.ts +2 -0
  200. package/src/tui/views/LookupView/types.ts +16 -0
  201. package/src/tui/views/index.ts +4 -0
  202. package/src/domains/shared/types.test-d.ts +0 -127
  203. package/src/domains/shared/types.ts +0 -348
@@ -1,140 +1,18 @@
1
- /**
2
- * Three-mode formatter for `pragma block lookup` output.
3
- *
4
- * - **plain** — styled terminal output with chalk; shows summary or
5
- * detailed view depending on the `detailed` / aspect flags.
6
- * - **llm** — condensed Markdown consumed by LLM agents and reused
7
- * by the MCP adapter when `condensed: true`.
8
- * - **json** — structured JSON for programmatic consumption.
9
- *
10
- * Supports aspect filtering (anatomy, modifiers, tokens, implementations)
11
- * via the `aspects` field on {@link BlockLookupInput}.
12
- */
13
-
14
- import chalk from "chalk";
15
- import {
16
- formatField,
17
- formatHeading,
18
- formatList,
19
- formatSection,
20
- } from "#pipeline";
1
+ import type { RenderLookupOptions } from "../../shared/contracts.js";
21
2
  import type { Formatters } from "../../shared/formatters.js";
22
- import type { AnatomyNode, BlockDetailed } from "../../shared/types.js";
3
+ import { renderLookupLlm, renderLookupPlain } from "../../shared/renderers.js";
4
+ import type { BlockDetailed } from "../../shared/types/index.js";
5
+ import { blockConfig } from "../blockConfig.js";
23
6
  import type { AspectFlags } from "../types.js";
24
7
  import type { BlockLookupInput } from "./types.js";
25
8
 
9
+ /** Three-mode formatter for `pragma block lookup` output. */
26
10
  const formatters: Formatters<BlockLookupInput> = {
27
- plain({ block: component, detailed, aspects }) {
28
- if (!detailed) return formatSummary(component);
29
- return formatDetailed(component, aspects);
30
- },
31
-
32
- llm({ block: component, detailed, aspects }) {
33
- const lines: string[] = [];
34
-
35
- lines.push(`## ${component.name}`);
36
- lines.push("");
37
- lines.push(`- URI: ${component.uri}`);
38
- lines.push(`- Tier: ${component.tier}`);
39
-
40
- if (component.summary) {
41
- lines.push(`- Summary: ${component.summary}`);
42
- }
43
-
44
- if (component.modifiers.length > 0) {
45
- lines.push(`- Modifiers: ${component.modifiers.join(", ")}`);
46
- }
47
-
48
- const impl = component.implementations
49
- .filter((i) => i.available)
50
- .map((i) => i.framework);
51
- if (impl.length > 0) {
52
- lines.push(`- Implementations: ${impl.join(", ")}`);
53
- }
54
-
55
- lines.push(`- Anatomy nodes: ${component.nodeCount}`);
56
- lines.push(`- Tokens: ${component.tokenCount}`);
57
-
58
- if (!detailed) return lines.join("\n");
59
-
60
- if (component.whenToUse) {
61
- lines.push("");
62
- lines.push("### When to use");
63
- lines.push(component.whenToUse);
64
- }
65
-
66
- if (component.whenNotToUse) {
67
- lines.push("");
68
- lines.push("### When not to use");
69
- lines.push(component.whenNotToUse);
70
- }
71
-
72
- if (component.guidelines) {
73
- lines.push("");
74
- lines.push("### Guidelines");
75
- lines.push(component.guidelines);
76
- }
77
-
78
- if (aspects.modifiers && component.modifierValues.length > 0) {
79
- lines.push("");
80
- lines.push("### Modifiers");
81
- for (const m of component.modifierValues) {
82
- lines.push(`- **${m.family}**: ${m.values.join(", ")}`);
83
- }
84
- }
85
-
86
- if (component.properties.length > 0) {
87
- lines.push("");
88
- lines.push("### Properties");
89
- for (const property of component.properties) {
90
- const bits = [property.propertyType || "unknown"];
91
- bits.push(property.optional ? "optional" : "required");
92
- if (property.defaultValue) {
93
- bits.push(`default=${property.defaultValue}`);
94
- }
95
- if (property.constraints) {
96
- bits.push(property.constraints);
97
- }
98
- lines.push(`- **${property.name}**: ${bits.join("; ")}`);
99
- }
100
- }
101
-
102
- if (component.subcomponents.length > 0) {
103
- lines.push("");
104
- lines.push("### Subcomponents");
105
- for (const subcomponent of component.subcomponents) {
106
- lines.push(`- ${subcomponent.name} (${subcomponent.uri})`);
107
- }
108
- }
109
-
110
- if (aspects.implementations && component.implementationPaths.length > 0) {
111
- lines.push("");
112
- lines.push("### Implementations");
113
- for (const i of component.implementationPaths) {
114
- lines.push(`- ${i.framework}: \`${i.path}\``);
115
- }
116
- }
117
-
118
- if (aspects.tokens && component.tokens.length > 0) {
119
- lines.push("");
120
- lines.push("### Tokens");
121
- for (const t of component.tokens) {
122
- lines.push(`- ${t.name}`);
123
- }
124
- }
125
-
126
- if (aspects.anatomy && component.anatomy) {
127
- lines.push("");
128
- lines.push("### Anatomy");
129
- lines.push(formatAnatomyTreeLlm(component.anatomy.root, 0));
130
- } else if (aspects.anatomy && component.anatomyDsl) {
131
- lines.push("");
132
- lines.push("### Anatomy DSL");
133
- lines.push(component.anatomyDsl);
134
- }
11
+ plain: ({ block, detailed, aspects }) =>
12
+ renderLookupPlain(block, createLookupOptions(detailed, aspects)),
135
13
 
136
- return lines.join("\n");
137
- },
14
+ llm: ({ block, detailed, aspects }) =>
15
+ renderLookupLlm(block, createLookupOptions(detailed, aspects)),
138
16
 
139
17
  json({ block: component, detailed, aspects }) {
140
18
  if (!detailed) {
@@ -178,177 +56,94 @@ const formatters: Formatters<BlockLookupInput> = {
178
56
 
179
57
  export default formatters;
180
58
 
181
- // =============================================================================
182
- // Helpers
183
- // =============================================================================
184
-
185
- function formatSummary(component: BlockDetailed): string {
186
- const lines: string[] = [];
187
-
188
- lines.push(formatHeading(component.name));
189
- lines.push("");
190
- lines.push(formatField("URI:", component.uri));
191
- lines.push(formatField("Tier:", component.tier));
192
-
193
- if (component.summary) {
194
- lines.push(formatField("Summary:", component.summary));
195
- }
196
-
197
- if (component.modifiers.length > 0) {
198
- lines.push(formatField("Modifiers:", component.modifiers.join(", ")));
199
- }
200
-
201
- const impl = component.implementations
202
- .filter((i) => i.available)
203
- .map((i) => i.framework);
204
- if (impl.length > 0) {
205
- lines.push(formatField("Implementations:", impl.join(", ")));
206
- }
207
-
208
- lines.push(formatField("Anatomy nodes:", String(component.nodeCount)));
209
- lines.push(formatField("Tokens:", String(component.tokenCount)));
210
-
211
- return lines.join("\n");
212
- }
213
-
214
- function formatDetailed(
215
- component: BlockDetailed,
59
+ export function createLookupOptions(
60
+ detailed: boolean,
216
61
  aspects: AspectFlags,
217
- ): string {
218
- const lines: string[] = [];
219
-
220
- lines.push(formatHeading(component.name));
221
- lines.push("");
222
- lines.push(formatField("URI:", component.uri));
223
- lines.push(formatField("Tier:", component.tier));
224
- if (component.summary) {
225
- lines.push(formatField("Summary:", component.summary));
226
- }
227
- lines.push(formatField("Anatomy nodes:", String(component.nodeCount)));
228
- lines.push(formatField("Tokens:", String(component.tokenCount)));
229
-
230
- if (component.whenToUse) {
231
- lines.push("");
232
- lines.push(formatSection("When to use", component.whenToUse));
233
- }
234
-
235
- if (component.whenNotToUse) {
236
- lines.push("");
237
- lines.push(formatSection("When not to use", component.whenNotToUse));
238
- }
239
-
240
- if (component.guidelines) {
241
- lines.push("");
242
- lines.push(formatSection("Guidelines", component.guidelines));
243
- }
244
-
245
- if (aspects.modifiers && component.modifierValues.length > 0) {
246
- lines.push("");
247
- lines.push(
248
- formatSection(
249
- "Modifiers",
250
- component.modifierValues
251
- .map((m) => ` ${chalk.bold(m.family)}: ${m.values.join(", ")}`)
252
- .join("\n"),
253
- ),
254
- );
255
- }
256
-
257
- if (aspects.implementations && component.implementationPaths.length > 0) {
258
- lines.push("");
259
- lines.push(
260
- formatSection(
261
- "Implementations",
262
- formatList(
263
- component.implementationPaths.map(
264
- (i) => `${chalk.bold(i.framework)}: ${i.path}`,
265
- ),
266
- ),
267
- ),
268
- );
269
- }
270
-
271
- if (aspects.tokens && component.tokens.length > 0) {
272
- lines.push("");
273
- lines.push(
274
- formatSection("Tokens", formatList(component.tokens.map((t) => t.name))),
275
- );
276
- }
277
-
278
- if (component.properties.length > 0) {
279
- lines.push("");
280
- lines.push(
281
- formatSection(
282
- "Properties",
283
- formatList(
284
- component.properties.map((property) => {
285
- const parts = [
286
- chalk.bold(property.name),
287
- property.propertyType || "unknown",
288
- property.optional ? "optional" : "required",
289
- ];
290
- if (property.defaultValue) {
291
- parts.push(`default=${property.defaultValue}`);
292
- }
293
- if (property.constraints) {
294
- parts.push(property.constraints);
295
- }
296
- return parts.join(" — ");
297
- }),
298
- ),
299
- ),
300
- );
301
- }
302
-
303
- if (component.subcomponents.length > 0) {
304
- lines.push("");
305
- lines.push(
306
- formatSection(
307
- "Subcomponents",
308
- formatList(
309
- component.subcomponents.map(
310
- (subcomponent) => `${subcomponent.name}: ${subcomponent.uri}`,
311
- ),
312
- ),
313
- ),
314
- );
315
- }
316
-
317
- if (aspects.anatomy && component.anatomy) {
318
- lines.push("");
319
- lines.push(
320
- formatSection("Anatomy", formatAnatomyTree(component.anatomy.root, 0)),
321
- );
322
- } else if (aspects.anatomy && component.anatomyDsl) {
323
- lines.push("");
324
- lines.push(formatSection("Anatomy (DSL)", component.anatomyDsl));
325
- }
326
-
327
- return lines.join("\n");
62
+ ): RenderLookupOptions<BlockDetailed> {
63
+ return {
64
+ title: (block) => block.name,
65
+ fields: [
66
+ { label: "IRI", value: (block) => block.uri },
67
+ { label: "Type", value: (block) => block.type },
68
+ { label: "Tier", value: (block) => block.tier },
69
+ ...(detailed
70
+ ? []
71
+ : ([
72
+ {
73
+ label: "Summary",
74
+ value: (block: BlockDetailed) => block.summary,
75
+ },
76
+ ] as const)),
77
+ { label: "Modifiers", value: (block) => block.modifiers },
78
+ {
79
+ label: "Implementations",
80
+ value: (block) =>
81
+ block.implementations
82
+ .filter((implementation) => implementation.available)
83
+ .map((implementation) => implementation.framework),
84
+ },
85
+ { label: "Anatomy nodes", value: (block) => block.nodeCount || null },
86
+ { label: "Tokens", value: (block) => block.tokenCount || null },
87
+ ],
88
+ sections: selectLookupSections(detailed, aspects),
89
+ sectionOverrides: {
90
+ modifierFamilies: {
91
+ plain: (block) =>
92
+ block.modifierValues.length > 0
93
+ ? block.modifierValues
94
+ .map(
95
+ (modifier) =>
96
+ ` ${modifier.family}: ${modifier.values.join(", ")}`,
97
+ )
98
+ .join("\n")
99
+ : null,
100
+ llm: (block) =>
101
+ block.modifierValues.length > 0
102
+ ? block.modifierValues
103
+ .map(
104
+ (modifier) =>
105
+ `- **${modifier.family}**: ${modifier.values.join(", ")}`,
106
+ )
107
+ .join("\n")
108
+ : null,
109
+ },
110
+ },
111
+ codeLanguage: (_section, value) =>
112
+ typeof value === "string" && value.includes("@prefix") ? "ttl" : "yaml",
113
+ };
328
114
  }
329
115
 
330
- function formatAnatomyTree(node: AnatomyNode, depth: number): string {
331
- const indent = " ".repeat(depth);
332
- const typeTag = node.type === "anonymous" ? chalk.dim(" (anon)") : "";
333
- const slotTag = node.slot ? chalk.dim(` slot=${node.slot}`) : "";
334
- let result = `${indent}${node.name}${typeTag}${slotTag}`;
116
+ function selectLookupSections(
117
+ detailed: boolean,
118
+ aspects: AspectFlags,
119
+ ): typeof blockConfig.lookupSections {
120
+ const sections = detailed
121
+ ? blockConfig.lookupSections
122
+ : blockConfig.lookupSections.filter(
123
+ (section) =>
124
+ section.key === "subcomponents" || section.key === "anatomyDsl",
125
+ );
335
126
 
336
- for (const child of node.children) {
337
- result += `\n${formatAnatomyTree(child, depth + 1)}`;
338
- }
127
+ return sections.filter((section) => {
128
+ if (
129
+ !aspects.anatomy &&
130
+ ["anatomy", "anatomyClassic"].includes(section.key)
131
+ ) {
132
+ return false;
133
+ }
339
134
 
340
- return result;
341
- }
135
+ if (!aspects.modifiers && section.key === "modifierFamilies") {
136
+ return false;
137
+ }
342
138
 
343
- function formatAnatomyTreeLlm(node: AnatomyNode, depth: number): string {
344
- const indent = " ".repeat(depth);
345
- const typeTag = node.type === "anonymous" ? " (anonymous)" : "";
346
- const slotTag = node.slot ? ` [slot: ${node.slot}]` : "";
347
- let result = `${indent}- ${node.name}${typeTag}${slotTag}`;
139
+ if (!aspects.implementations && section.key === "implementationPaths") {
140
+ return false;
141
+ }
348
142
 
349
- for (const child of node.children) {
350
- result += `\n${formatAnatomyTreeLlm(child, depth + 1)}`;
351
- }
143
+ if (!aspects.tokens && section.key === "tokens") {
144
+ return false;
145
+ }
352
146
 
353
- return result;
147
+ return true;
148
+ });
354
149
  }
@@ -5,7 +5,7 @@
5
5
  * so formatters conform to the `Formatters<T>` contract.
6
6
  */
7
7
 
8
- import type { BlockDetailed } from "../../shared/types.js";
8
+ import type { BlockDetailed } from "../../shared/types/index.js";
9
9
  import type { AspectFlags } from "../types.js";
10
10
 
11
11
  /** Input payload for the block-lookup formatter, pairing data with display flags. */
@@ -1,7 +1,7 @@
1
1
  import type { Store } from "@canonical/ke";
2
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
3
  import { createTestStore, DS_ALL_TTL } from "#testing";
4
- import type { FilterConfig } from "../../shared/types.js";
4
+ import type { FilterConfig } from "../../shared/types/index.js";
5
5
  import listBlocks from "./list.js";
6
6
 
7
7
  let store: Store;
@@ -15,7 +15,7 @@ import { buildQuery } from "../../shared/buildQuery.js";
15
15
  import extractLocalName from "../../shared/extractLocalName.js";
16
16
  import { buildFilters } from "../../shared/filters/buildFilters.js";
17
17
  import { P } from "../../shared/prefixes.js";
18
- import type { BlockSummary, FilterConfig } from "../../shared/types.js";
18
+ import type { BlockSummary, FilterConfig } from "../../shared/types/index.js";
19
19
 
20
20
  function normalizeBlockType(value: string | undefined): BlockSummary["type"] {
21
21
  const localName = extractLocalName(value ?? "").toLowerCase();
@@ -2,7 +2,7 @@ import type { Store } from "@canonical/ke";
2
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
3
  import { PragmaError } from "#error";
4
4
  import { createTestStore, DS_ALL_TTL } from "#testing";
5
- import type { FilterConfig } from "../../shared/types.js";
5
+ import type { FilterConfig } from "../../shared/types/index.js";
6
6
  import lookupBlock from "./lookup.js";
7
7
 
8
8
  let store: Store;
@@ -21,7 +21,10 @@ const normalChannel: FilterConfig = { tier: undefined, channel: "normal" };
21
21
 
22
22
  describe("lookupBlock", () => {
23
23
  it("returns detailed data for Button", async () => {
24
- const result = await lookupBlock(store, "Button", noFilter);
24
+ const results = await lookupBlock(store, "Button", noFilter);
25
+ expect(results.length).toBeGreaterThan(0);
26
+ const result = results[0];
27
+ expect(result).toBeDefined();
25
28
  expect(result.name).toBe("Button");
26
29
  expect(result.tier).toBe("global");
27
30
  expect(result.modifierValues.length).toBeGreaterThan(0);
@@ -34,7 +37,9 @@ describe("lookupBlock", () => {
34
37
  });
35
38
 
36
39
  it("includes implementation paths", async () => {
37
- const result = await lookupBlock(store, "Button", noFilter);
40
+ const results = await lookupBlock(store, "Button", noFilter);
41
+ const result = results[0];
42
+ expect(result).toBeDefined();
38
43
  const react = result.implementationPaths.find(
39
44
  (i) => i.framework === "react",
40
45
  );
@@ -43,19 +48,25 @@ describe("lookupBlock", () => {
43
48
  });
44
49
 
45
50
  it("includes token references", async () => {
46
- const result = await lookupBlock(store, "Button", noFilter);
51
+ const results = await lookupBlock(store, "Button", noFilter);
52
+ const result = results[0];
53
+ expect(result).toBeDefined();
47
54
  expect(result.tokens.length).toBe(1);
48
55
  expect(result.tokens[0].name).toBe("color.primary");
49
56
  });
50
57
 
51
58
  it("populates nodeCount from anatomy nodes", async () => {
52
- const result = await lookupBlock(store, "Button", noFilter);
59
+ const results = await lookupBlock(store, "Button", noFilter);
60
+ const result = results[0];
61
+ expect(result).toBeDefined();
53
62
  expect(result.nodeCount).toBe(3);
54
63
  expect(result.anatomy?.root.name).toBe("button");
55
64
  });
56
65
 
57
66
  it("returns enriched summary, properties, and subcomponents", async () => {
58
- const result = await lookupBlock(store, "Button", noFilter);
67
+ const results = await lookupBlock(store, "Button", noFilter);
68
+ const result = results[0];
69
+ expect(result).toBeDefined();
59
70
  expect(result.summary).toContain("Primary action trigger");
60
71
  expect(result.whenToUse).toContain("high-priority actions");
61
72
  expect(result.properties.map((property) => property.name)).toEqual(
@@ -87,22 +98,32 @@ describe("lookupBlock", () => {
87
98
  });
88
99
 
89
100
  it("resolves prefixed IRIs", async () => {
90
- const result = await lookupBlock(
101
+ const results = await lookupBlock(
91
102
  store,
92
103
  "ds:global.component.button",
93
104
  noFilter,
94
105
  );
106
+ const result = results[0];
107
+ expect(result).toBeDefined();
95
108
  expect(result.name).toBe("Button");
96
109
  expect(result.uri).toBe("https://ds.canonical.com/global.component.button");
97
110
  });
98
111
 
99
112
  it("resolves full IRIs", async () => {
100
- const result = await lookupBlock(
113
+ const results = await lookupBlock(
101
114
  store,
102
115
  "https://ds.canonical.com/global.component.button",
103
116
  noFilter,
104
117
  );
118
+ const result = results[0];
119
+ expect(result).toBeDefined();
105
120
  expect(result.name).toBe("Button");
106
121
  expect(result.uri).toBe("https://ds.canonical.com/global.component.button");
107
122
  });
123
+
124
+ it("is case-insensitive for name lookup", async () => {
125
+ const results = await lookupBlock(store, "button", noFilter);
126
+ expect(results.length).toBeGreaterThan(0);
127
+ expect(results[0]?.name).toBe("Button");
128
+ });
108
129
  });