@canonical/pragma-cli 0.20.0 → 0.21.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 (199) hide show
  1. package/dist/pragma +0 -0
  2. package/package.json +30 -18
  3. package/src/constants.ts +1 -1
  4. package/src/domains/block/blockConfig.ts +47 -3
  5. package/src/domains/block/commands/list.test.ts +1 -1
  6. package/src/domains/block/commands/list.ts +27 -0
  7. package/src/domains/block/commands/lookup.ts +26 -52
  8. package/src/domains/block/formatters/index.ts +1 -1
  9. package/src/domains/block/formatters/list.test.ts +1 -1
  10. package/src/domains/block/formatters/list.ts +28 -42
  11. package/src/domains/block/formatters/lookup.test.ts +3 -2
  12. package/src/domains/block/formatters/lookup.ts +91 -296
  13. package/src/domains/block/formatters/types.ts +1 -1
  14. package/src/domains/block/operations/list.test.ts +1 -1
  15. package/src/domains/block/operations/list.ts +1 -1
  16. package/src/domains/block/operations/lookup.test.ts +29 -8
  17. package/src/domains/block/operations/lookup.ts +94 -16
  18. package/src/domains/block/orchestration/blockEmptyError.ts +1 -1
  19. package/src/domains/block/orchestration/buildBlockFilters.ts +1 -1
  20. package/src/domains/block/orchestration/enrichBlocks.ts +6 -2
  21. package/src/domains/block/orchestration/resolveBlockList.ts +1 -1
  22. package/src/domains/block/orchestration/resolveBlockLookup.ts +8 -2
  23. package/src/domains/block/types.ts +1 -1
  24. package/src/domains/config/commands/channel.ts +6 -4
  25. package/src/domains/config/commands/tier.ts +6 -4
  26. package/src/domains/config/mcp/specs.ts +18 -6
  27. package/src/domains/config/operations/validateTier.ts +1 -1
  28. package/src/domains/config/tasks/index.ts +3 -0
  29. package/src/domains/config/tasks/setChannel.ts +30 -0
  30. package/src/domains/config/tasks/setTier.ts +33 -0
  31. package/src/domains/create/commands/package.test.ts +2 -2
  32. package/src/domains/doctor/mcp/index.ts +1 -1
  33. package/src/domains/doctor/mcp/specs.ts +2 -49
  34. package/src/domains/graph/commands/inspect.test.ts +1 -1
  35. package/src/domains/graph/formatters/inspect.ts +12 -7
  36. package/src/domains/graph/operations/inspectUri.ts +4 -1
  37. package/src/domains/info/commands/upgrade.ts +6 -3
  38. package/src/domains/info/index.ts +3 -1
  39. package/src/domains/info/mcp/index.ts +2 -0
  40. package/src/domains/info/mcp/specs.ts +65 -0
  41. package/src/domains/llm/data/commandReference.ts +4 -4
  42. package/src/domains/llm/data/decisionTrees.ts +14 -12
  43. package/src/domains/llm/operations/collectContext.ts +1 -1
  44. package/src/domains/modifier/commands/list.ts +9 -0
  45. package/src/domains/modifier/commands/lookup.ts +22 -42
  46. package/src/domains/modifier/formatters/index.ts +4 -1
  47. package/src/domains/modifier/formatters/list.test.ts +1 -1
  48. package/src/domains/modifier/formatters/list.ts +13 -26
  49. package/src/domains/modifier/formatters/lookup.test.ts +1 -1
  50. package/src/domains/modifier/formatters/lookup.ts +41 -29
  51. package/src/domains/modifier/index.ts +1 -0
  52. package/src/domains/modifier/modifierConfig.ts +36 -0
  53. package/src/domains/modifier/operations/list.ts +1 -1
  54. package/src/domains/modifier/operations/lookup.ts +4 -3
  55. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +1 -1
  56. package/src/domains/modifier/types.ts +1 -1
  57. package/src/domains/ontology/commands/list.test.ts +1 -1
  58. package/src/domains/ontology/commands/list.ts +18 -0
  59. package/src/domains/ontology/commands/show.test.ts +1 -1
  60. package/src/domains/ontology/formatters/list.ts +5 -3
  61. package/src/domains/ontology/formatters/show.ts +38 -13
  62. package/src/domains/ontology/helpers/queryClasses.ts +1 -1
  63. package/src/domains/ontology/helpers/queryProperties.ts +1 -1
  64. package/src/domains/ontology/operations/listOntologies.ts +1 -1
  65. package/src/domains/ontology/operations/showOntology.ts +1 -1
  66. package/src/domains/setup/helpers/completionScripts.test.ts +3 -3
  67. package/src/domains/setup/helpers/completionScripts.ts +4 -4
  68. package/src/domains/shared/ToolSpec.ts +1 -1
  69. package/src/domains/shared/context.ts +3 -2
  70. package/src/domains/shared/contracts.ts +40 -1
  71. package/src/domains/shared/filters/buildFilterConfig.ts +1 -2
  72. package/src/domains/shared/filters/buildFilters.ts +1 -1
  73. package/src/domains/shared/filters/describeFilters.ts +1 -1
  74. package/src/domains/shared/formatters.ts +47 -0
  75. package/src/domains/shared/renderers.ts +410 -0
  76. package/src/domains/shared/runtime.ts +7 -22
  77. package/src/domains/shared/types/block.ts +107 -0
  78. package/src/domains/shared/types/common.ts +33 -0
  79. package/src/domains/shared/types/graph.ts +17 -0
  80. package/src/domains/shared/types/index.ts +49 -0
  81. package/src/domains/shared/types/modifier.ts +22 -0
  82. package/src/domains/shared/types/ontology.ts +50 -0
  83. package/src/domains/shared/types/runtime.ts +21 -0
  84. package/src/domains/shared/types/standard.ts +64 -0
  85. package/src/domains/shared/types/tier.ts +15 -0
  86. package/src/domains/shared/types/token.ts +30 -0
  87. package/src/domains/standard/commands/list.ts +9 -0
  88. package/src/domains/standard/commands/lookup.ts +22 -50
  89. package/src/domains/standard/formatters/categories.test.ts +1 -1
  90. package/src/domains/standard/formatters/categories.ts +1 -1
  91. package/src/domains/standard/formatters/index.ts +4 -1
  92. package/src/domains/standard/formatters/list.test.ts +4 -1
  93. package/src/domains/standard/formatters/list.ts +112 -84
  94. package/src/domains/standard/formatters/lookup.test.ts +1 -1
  95. package/src/domains/standard/formatters/lookup.ts +85 -70
  96. package/src/domains/standard/formatters/types.ts +1 -1
  97. package/src/domains/standard/index.ts +1 -0
  98. package/src/domains/standard/operations/categories.ts +1 -1
  99. package/src/domains/standard/operations/list.ts +1 -1
  100. package/src/domains/standard/operations/lookup.ts +1 -1
  101. package/src/domains/standard/orchestration/buildStandardFilters.ts +1 -1
  102. package/src/domains/standard/orchestration/resolveStandardList.ts +4 -1
  103. package/src/domains/standard/orchestration/resolveStandardLookup.ts +1 -1
  104. package/src/domains/standard/orchestration/standardEmptyError.ts +1 -1
  105. package/src/domains/standard/standardConfig.ts +42 -0
  106. package/src/domains/standard/types.ts +1 -1
  107. package/src/domains/tier/commands/list.ts +16 -0
  108. package/src/domains/tier/formatters/list.test.ts +1 -1
  109. package/src/domains/tier/formatters/list.ts +11 -6
  110. package/src/domains/tier/operations/list.ts +1 -1
  111. package/src/domains/token/commands/list.ts +9 -0
  112. package/src/domains/token/commands/lookup.ts +22 -48
  113. package/src/domains/token/formatters/index.ts +4 -1
  114. package/src/domains/token/formatters/list.test.ts +1 -1
  115. package/src/domains/token/formatters/list.ts +15 -28
  116. package/src/domains/token/formatters/lookup.test.ts +2 -2
  117. package/src/domains/token/formatters/lookup.ts +80 -45
  118. package/src/domains/token/index.ts +1 -0
  119. package/src/domains/token/operations/list.ts +1 -1
  120. package/src/domains/token/operations/lookup.ts +1 -1
  121. package/src/domains/token/orchestration/resolveTokenLookup.ts +1 -1
  122. package/src/domains/token/tokenConfig.ts +38 -0
  123. package/src/domains/token/types.ts +1 -1
  124. package/src/index.ts +1 -1
  125. package/src/mcp/runMcpServer.test.ts +2 -2
  126. package/src/mcp/tools/index.ts +2 -0
  127. package/src/mcp/tools/registerTools.test.ts +8 -3
  128. package/src/package-manager/types.ts +1 -3
  129. package/src/pipeline/createProgram.ts +20 -1
  130. package/src/pipeline/index.ts +4 -1
  131. package/src/pipeline/parseGlobalFlags.test.ts +92 -0
  132. package/src/pipeline/parseGlobalFlags.ts +25 -0
  133. package/src/pipeline/runCli.orchestration.test.ts +1 -0
  134. package/src/pipeline/runCli.ts +11 -4
  135. package/src/pipeline/runInteractiveCommand.ts +198 -0
  136. package/src/testing/cli.ts +2 -2
  137. package/src/testing/fixtures.ts +2 -2
  138. package/src/testing/integration/boot.test.ts +2 -2
  139. package/src/testing/integration/cli-user-stories.test.ts +198 -0
  140. package/src/testing/integration/edge-cases.test.ts +263 -0
  141. package/src/testing/integration/parity.test.ts +377 -27
  142. package/src/testing/store.ts +1 -1
  143. package/src/tui/components/BulletList/BulletList.tsx +46 -0
  144. package/src/tui/components/BulletList/index.ts +2 -0
  145. package/src/tui/components/BulletList/types.ts +6 -0
  146. package/src/tui/components/Card/Card.tsx +73 -0
  147. package/src/tui/components/Card/index.ts +1 -0
  148. package/src/tui/components/Card/types.ts +12 -0
  149. package/src/tui/components/CodeBlock/CodeBlock.tsx +37 -0
  150. package/src/tui/components/CodeBlock/index.ts +2 -0
  151. package/src/tui/components/CodeBlock/types.ts +6 -0
  152. package/src/tui/components/DataRow/DataRow.tsx +80 -0
  153. package/src/tui/components/DataRow/index.ts +2 -0
  154. package/src/tui/components/DataRow/types.ts +14 -0
  155. package/src/tui/components/FieldRow/FieldRow.tsx +24 -0
  156. package/src/tui/components/FieldRow/index.ts +2 -0
  157. package/src/tui/components/FieldRow/types.ts +10 -0
  158. package/src/tui/components/HeaderRow/HeaderRow.tsx +32 -0
  159. package/src/tui/components/HeaderRow/index.ts +2 -0
  160. package/src/tui/components/HeaderRow/types.ts +8 -0
  161. package/src/tui/components/MarkdownText/MarkdownText.tsx +19 -0
  162. package/src/tui/components/MarkdownText/index.ts +2 -0
  163. package/src/tui/components/MarkdownText/types.ts +4 -0
  164. package/src/tui/components/MiniTable/MiniTable.tsx +82 -0
  165. package/src/tui/components/MiniTable/index.ts +2 -0
  166. package/src/tui/components/MiniTable/types.ts +6 -0
  167. package/src/tui/components/SectionRenderer/SectionRenderer.tsx +91 -0
  168. package/src/tui/components/SectionRenderer/index.ts +2 -0
  169. package/src/tui/components/SectionRenderer/types.ts +15 -0
  170. package/src/tui/components/TreeView/TreeView.tsx +84 -0
  171. package/src/tui/components/TreeView/index.ts +2 -0
  172. package/src/tui/components/TreeView/types.ts +6 -0
  173. package/src/tui/components/index.ts +19 -0
  174. package/src/tui/constants.ts +39 -0
  175. package/src/tui/createListView.ts +23 -0
  176. package/src/tui/createLookupView.ts +26 -0
  177. package/src/tui/helpers/computeWidths.ts +53 -0
  178. package/src/tui/helpers/fitColumns.ts +33 -0
  179. package/src/tui/helpers/formatMarkdown.ts +19 -0
  180. package/src/tui/helpers/getTerminalSize.ts +16 -0
  181. package/src/tui/helpers/index.ts +6 -0
  182. package/src/tui/helpers/syntaxColor.ts +67 -0
  183. package/src/tui/helpers/truncateText.ts +12 -0
  184. package/src/tui/hooks/index.ts +3 -0
  185. package/src/tui/hooks/types.ts +7 -0
  186. package/src/tui/hooks/useAutoExit.ts +17 -0
  187. package/src/tui/hooks/useTerminalSize.ts +19 -0
  188. package/src/tui/index.ts +15 -0
  189. package/src/tui/renderInk.tsx +18 -0
  190. package/src/tui/types.ts +12 -0
  191. package/src/tui/views/ListView/ListView.tsx +104 -0
  192. package/src/tui/views/ListView/index.ts +2 -0
  193. package/src/tui/views/ListView/types.ts +14 -0
  194. package/src/tui/views/LookupView/LookupView.tsx +244 -0
  195. package/src/tui/views/LookupView/index.ts +2 -0
  196. package/src/tui/views/LookupView/types.ts +16 -0
  197. package/src/tui/views/index.ts +4 -0
  198. package/src/domains/shared/types.test-d.ts +0 -127
  199. package/src/domains/shared/types.ts +0 -348
@@ -0,0 +1,50 @@
1
+ /** Ontology inspection return types. */
2
+
3
+ /** Summary of a loaded ontology namespace from `ontology list`. */
4
+ export interface OntologySummary {
5
+ /** Short prefix alias (e.g., `"ds"`, `"cs"`). */
6
+ readonly prefix: string;
7
+ /** Full namespace URI the prefix expands to. */
8
+ readonly namespace: string;
9
+ /** Number of OWL/RDFS classes defined in this namespace. */
10
+ readonly classCount: number;
11
+ /** Number of properties (object + datatype) defined in this namespace. */
12
+ readonly propertyCount: number;
13
+ readonly anatomyCount: number;
14
+ }
15
+
16
+ /** A class in an ontology's class hierarchy. */
17
+ export interface OntologyClass {
18
+ /** Full URI of the class. */
19
+ readonly uri: string;
20
+ /** Human-readable label (rdfs:label). */
21
+ readonly label: string;
22
+ /** URI of the direct superclass, if declared. */
23
+ readonly superclass?: string;
24
+ }
25
+
26
+ /** A property in an ontology namespace. */
27
+ export interface OntologyProperty {
28
+ /** Full URI of the property. */
29
+ readonly uri: string;
30
+ /** Human-readable label (rdfs:label). */
31
+ readonly label: string;
32
+ /** URI of the domain class this property applies to, if declared. */
33
+ readonly domain?: string;
34
+ /** URI of the range class or datatype, if declared. */
35
+ readonly range?: string;
36
+ /** Whether this is an object property (links classes) or datatype property (links to literals). */
37
+ readonly type: "object" | "datatype";
38
+ }
39
+
40
+ /** Detailed view of a single ontology namespace from `ontology show`. */
41
+ export interface OntologyDetailed {
42
+ /** Short prefix alias (e.g., `"ds"`). */
43
+ readonly prefix: string;
44
+ /** Full namespace URI the prefix expands to. */
45
+ readonly namespace: string;
46
+ /** All classes defined in this namespace. */
47
+ readonly classes: readonly OntologyClass[];
48
+ /** All properties defined in this namespace. */
49
+ readonly properties: readonly OntologyProperty[];
50
+ }
@@ -0,0 +1,21 @@
1
+ /** Boot-time state shared by CLI, MCP, and completions server. */
2
+
3
+ import type { Store } from "@canonical/ke";
4
+ import type { PragmaConfig } from "#config";
5
+
6
+ /**
7
+ * Boot-time state shared by CLI, MCP, and completions server.
8
+ *
9
+ * Four members:
10
+ * - `store` — the ke triple store, loaded and ready for SPARQL queries.
11
+ * - `config` — resolved pragma config (tier, channel).
12
+ * - `cwd` — working directory used for config and source resolution.
13
+ * - `dispose()` — tears down the store and frees WASM memory. Must be
14
+ * called exactly once; calling operations after dispose is undefined.
15
+ */
16
+ export interface PragmaRuntime {
17
+ readonly store: Store;
18
+ readonly config: PragmaConfig;
19
+ readonly cwd: string;
20
+ dispose(): void;
21
+ }
@@ -0,0 +1,64 @@
1
+ /** Standard domain return types. */
2
+
3
+ import type { URI } from "@canonical/ke";
4
+
5
+ /** Summary view of a design-system standard (guideline or rule). */
6
+ export interface StandardSummary {
7
+ /** Full RDF URI identifying this standard in the ke store. */
8
+ readonly uri: URI;
9
+ /** Human-readable name of the standard. */
10
+ readonly name: string;
11
+ /** Category grouping (e.g., `"accessibility"`, `"layout"`). */
12
+ readonly category: string;
13
+ /** Brief prose description of what the standard prescribes. */
14
+ readonly description: string;
15
+ /** URI of the parent standard this one extends, if any. */
16
+ readonly extends?: string;
17
+ }
18
+
19
+ /** Digest view of a standard for generic list contracts. */
20
+ export type StandardDigest = StandardSummary;
21
+
22
+ /** Fenced code block used in standard do/don't examples. */
23
+ export interface CodeBlock {
24
+ /** Programming language identifier for syntax highlighting. */
25
+ readonly language: string;
26
+ /** Source code content of the example. */
27
+ readonly code: string;
28
+ /** Optional explanatory caption displayed alongside the code. */
29
+ readonly caption?: string;
30
+ }
31
+
32
+ /** Detailed view of a standard, extending the summary with code examples. */
33
+ export interface StandardDetailed extends StandardSummary {
34
+ /** Positive examples demonstrating correct usage. */
35
+ readonly dos: readonly CodeBlock[];
36
+ /** Negative examples demonstrating incorrect usage. */
37
+ readonly donts: readonly CodeBlock[];
38
+ }
39
+
40
+ /** Summary of a standards category with its member count. */
41
+ export interface CategorySummary {
42
+ /** Category name (e.g., `"accessibility"`, `"layout"`). */
43
+ readonly name: string;
44
+ /** Number of standards belonging to this category. */
45
+ readonly standardCount: number;
46
+ }
47
+
48
+ /** Optional filters for narrowing the standards list. */
49
+ export interface StandardListFilters {
50
+ /** Restrict results to a single category name. */
51
+ readonly category?: string;
52
+ /** Free-text search term matched against standard names and descriptions. */
53
+ readonly search?: string;
54
+ }
55
+
56
+ /** Lightweight reference to a standard, used in block cross-references. */
57
+ export interface StandardRef {
58
+ /** Human-readable standard name. */
59
+ readonly name: string;
60
+ /** Full RDF URI of the referenced standard. */
61
+ readonly uri: URI;
62
+ /** Category the referenced standard belongs to. */
63
+ readonly category: string;
64
+ }
@@ -0,0 +1,15 @@
1
+ /** Tier hierarchy return types. */
2
+
3
+ import type { URI } from "@canonical/ke";
4
+
5
+ /** A single entry in the tier hierarchy (e.g., `global`, `apps`, `apps/lxd`). */
6
+ export interface TierEntry {
7
+ /** Full RDF URI identifying this tier in the ke store. */
8
+ readonly uri: URI;
9
+ /** Slash-separated tier path (e.g., `"apps/lxd"`). */
10
+ readonly path: string;
11
+ /** Path of the parent tier, if this is not the root. */
12
+ readonly parent?: string;
13
+ /** Nesting depth in the tier hierarchy (0 = root). */
14
+ readonly depth: number;
15
+ }
@@ -0,0 +1,30 @@
1
+ /** Token domain return types. */
2
+
3
+ import type { URI } from "@canonical/ke";
4
+
5
+ /** Summary view of a design token. */
6
+ export interface TokenSummary {
7
+ /** Full RDF URI identifying this token in the ke store. */
8
+ readonly uri: URI;
9
+ /** Human-readable token name (e.g., `"color-primary"`). */
10
+ readonly name: string;
11
+ /** Token category (e.g., `"color"`, `"spacing"`, `"typography"`). */
12
+ readonly category: string;
13
+ }
14
+
15
+ /** Digest view of a token for generic list contracts. */
16
+ export type TokenDigest = TokenSummary;
17
+
18
+ /** Detailed view of a token, extending the summary with per-theme resolved values. */
19
+ export interface TokenDetailed extends TokenSummary {
20
+ /** Resolved values keyed by theme (e.g., `{ theme: "dark", value: "#fff" }`). */
21
+ readonly values: readonly { theme: string; value: string }[];
22
+ }
23
+
24
+ /** Lightweight reference to a token (name + URI), used in cross-references. */
25
+ export interface TokenRef {
26
+ /** Human-readable token name. */
27
+ readonly name: string;
28
+ /** Full RDF URI of the referenced token. */
29
+ readonly uri: URI;
30
+ }
@@ -11,11 +11,13 @@ import {
11
11
  type CommandResult,
12
12
  createOutputResult,
13
13
  } from "@canonical/cli-core";
14
+ import { createListView } from "#tui";
14
15
  import type { PragmaContext } from "../../shared/context.js";
15
16
  import { selectFormatter } from "../../shared/formatters.js";
16
17
  import { listFormatters } from "../formatters/index.js";
17
18
  import type { StandardListOutput } from "../formatters/types.js";
18
19
  import { resolveStandardList } from "../orchestration/index.js";
20
+ import { standardConfig } from "../standardConfig.js";
19
21
 
20
22
  export default function buildListCommand(
21
23
  ctx: PragmaContext,
@@ -75,6 +77,13 @@ export default function buildListCommand(
75
77
 
76
78
  return createOutputResult(output, {
77
79
  plain: selectFormatter(ctx, listFormatters),
80
+ ink: (data) =>
81
+ createListView({
82
+ heading: "Standards",
83
+ domain: "standard",
84
+ items: data.items,
85
+ columns: standardConfig.listColumns,
86
+ }),
78
87
  });
79
88
  },
80
89
  };
@@ -10,9 +10,14 @@ import {
10
10
  type CommandResult,
11
11
  createOutputResult,
12
12
  } from "@canonical/cli-core";
13
+ import { createLookupView } from "#tui";
13
14
  import type { PragmaContext } from "../../shared/context.js";
14
15
  import type { LookupResult } from "../../shared/contracts.js";
15
- import { lookupFormatters } from "../formatters/index.js";
16
+ import { renderLookupResults } from "../../shared/formatters.js";
17
+ import {
18
+ createInkLookupOptions,
19
+ lookupFormatters,
20
+ } from "../formatters/index.js";
16
21
  import type { lookupStandard } from "../operations/index.js";
17
22
  import { resolveStandardLookup } from "../orchestration/index.js";
18
23
 
@@ -60,7 +65,22 @@ export default function buildLookupCommand(
60
65
 
61
66
  return createOutputResult<StandardLookupOutput>(
62
67
  { result: contract.result, detailed },
63
- { plain: renderStandardLookupOutput(ctx) },
68
+ {
69
+ plain: ({ result, detailed: isDetailed }) =>
70
+ renderLookupResults({
71
+ ctx,
72
+ result,
73
+ formatters: lookupFormatters,
74
+ mapResult: (standard) => ({ standard, detailed: isDetailed }),
75
+ }),
76
+ ink: ({ result, detailed: isDetailed }) =>
77
+ createLookupView({
78
+ results: result.results,
79
+ errors: result.errors,
80
+ domain: "standard",
81
+ options: createInkLookupOptions(isDetailed),
82
+ }),
83
+ },
64
84
  );
65
85
  },
66
86
  };
@@ -77,51 +97,3 @@ function normalizeNames(names: unknown, legacyName?: unknown): string[] {
77
97
  }
78
98
  return [];
79
99
  }
80
-
81
- function renderStandardLookupOutput(
82
- ctx: PragmaContext,
83
- ): (data: StandardLookupOutput) => string {
84
- return ({ result, detailed }) => {
85
- const formatOne =
86
- ctx.globalFlags.format === "json"
87
- ? lookupFormatters.json
88
- : ctx.globalFlags.llm
89
- ? lookupFormatters.llm
90
- : lookupFormatters.plain;
91
-
92
- if (ctx.globalFlags.format === "json") {
93
- if (result.results.length === 1 && result.errors.length === 0) {
94
- const only = result.results[0];
95
- return only
96
- ? lookupFormatters.json({ standard: only, detailed })
97
- : JSON.stringify({ results: [], errors: result.errors }, null, 2);
98
- }
99
-
100
- return JSON.stringify(
101
- {
102
- results: result.results.map((standard) =>
103
- JSON.parse(lookupFormatters.json({ standard, detailed })),
104
- ),
105
- errors: result.errors,
106
- },
107
- null,
108
- 2,
109
- );
110
- }
111
-
112
- const parts = result.results.map((standard) =>
113
- formatOne({ standard, detailed }),
114
- );
115
-
116
- if (result.errors.length > 0) {
117
- parts.push(
118
- [
119
- "Errors:",
120
- ...result.errors.map((error) => `- ${error.query}: ${error.message}`),
121
- ].join("\n"),
122
- );
123
- }
124
-
125
- return parts.join("\n\n");
126
- };
127
- }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import type { CategorySummary } from "../../shared/types.js";
2
+ import type { CategorySummary } from "../../shared/types/index.js";
3
3
  import formatters from "./categories.js";
4
4
 
5
5
  const CATEGORIES: CategorySummary[] = [
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { Formatters } from "../../shared/formatters.js";
11
- import type { CategorySummary } from "../../shared/types.js";
11
+ import type { CategorySummary } from "../../shared/types/index.js";
12
12
 
13
13
  const formatters: Formatters<readonly CategorySummary[]> = {
14
14
  plain(categories) {
@@ -2,5 +2,8 @@
2
2
 
3
3
  export { default as categoriesFormatters } from "./categories.js";
4
4
  export { default as listFormatters } from "./list.js";
5
- export { default as lookupFormatters } from "./lookup.js";
5
+ export {
6
+ createInkLookupOptions,
7
+ default as lookupFormatters,
8
+ } from "./lookup.js";
6
9
  export type { StandardListOutput, StandardLookupInput } from "./types.js";
@@ -1,6 +1,9 @@
1
1
  import type { URI } from "@canonical/ke";
2
2
  import { describe, expect, it } from "vitest";
3
- import type { StandardDetailed, StandardSummary } from "../../shared/types.js";
3
+ import type {
4
+ StandardDetailed,
5
+ StandardSummary,
6
+ } from "../../shared/types/index.js";
4
7
  import formatters from "./list.js";
5
8
  import type { StandardListOutput } from "./types.js";
6
9
 
@@ -1,19 +1,5 @@
1
- /**
2
- * Three-mode formatter for `pragma standard list` output.
3
- *
4
- * - **plain** — terminal text with progressive disclosure.
5
- * - **llm** — condensed Markdown consumed by LLM agents and reused
6
- * by the MCP adapter when `condensed: true`.
7
- * - **json** — structured JSON; enrichment varies by disclosure level.
8
- *
9
- * Disclosure levels:
10
- * - summary — name + category + description (default)
11
- * - digest — summary + first do example (truncated)
12
- * - detailed — summary + full dos/donts
13
- */
14
-
15
1
  import type { Formatters } from "../../shared/formatters.js";
16
- import type { CodeBlock, StandardDetailed } from "../../shared/types.js";
2
+ import type { CodeBlock, StandardDetailed } from "../../shared/types/index.js";
17
3
  import type { StandardListOutput } from "./types.js";
18
4
 
19
5
  // ---------------------------------------------------------------------------
@@ -22,6 +8,17 @@ import type { StandardListOutput } from "./types.js";
22
8
 
23
9
  const DEFAULT_MAX_EXAMPLE_LENGTH = 120;
24
10
 
11
+ interface StandardListRow {
12
+ readonly uri: StandardDetailed["uri"];
13
+ readonly name: StandardDetailed["name"];
14
+ readonly category: StandardDetailed["category"];
15
+ readonly description: StandardDetailed["description"];
16
+ readonly extends?: StandardDetailed["extends"];
17
+ readonly example?: string;
18
+ readonly dos?: StandardDetailed["dos"];
19
+ readonly donts?: StandardDetailed["donts"];
20
+ }
21
+
25
22
  function truncate(text: string, max: number): string {
26
23
  const oneLine = text.replace(/\n/g, " ").trim();
27
24
  return oneLine.length <= max ? oneLine : `${oneLine.slice(0, max - 1)}…`;
@@ -40,82 +37,21 @@ function firstDoExample(
40
37
  // Plain
41
38
  // ---------------------------------------------------------------------------
42
39
 
43
- function plain({ items, details, disclosure }: StandardListOutput): string {
44
- const lines: string[] = [];
45
-
46
- for (const [i, s] of items.entries()) {
47
- const cat = s.category ? ` [${s.category}]` : "";
48
- lines.push(`${s.name}${cat}`);
49
- lines.push(` ${s.description}`);
50
-
51
- if (disclosure.level === "digest") {
52
- const maxLen = disclosure.maxExampleLength ?? DEFAULT_MAX_EXAMPLE_LENGTH;
53
- const example = firstDoExample(details?.[i], maxLen);
54
- if (example) {
55
- lines.push(` Example: ${example}`);
56
- }
57
- }
58
-
59
- if (disclosure.level === "detailed") {
60
- const detail = details?.[i];
61
- if (detail) {
62
- if (detail.dos.length > 0) {
63
- lines.push(" Do:");
64
- for (const d of detail.dos) {
65
- lines.push(` ${d.code}`);
66
- }
67
- }
68
- if (detail.donts.length > 0) {
69
- lines.push(" Don't:");
70
- for (const d of detail.donts) {
71
- lines.push(` ${d.code}`);
72
- }
73
- }
74
- }
75
- }
76
- }
77
-
78
- return lines.join("\n");
40
+ function plain(output: StandardListOutput): string {
41
+ return buildRows(output)
42
+ .map((row) => formatPlainRow(row, output.disclosure.level))
43
+ .join("\n\n");
79
44
  }
80
45
 
81
46
  // ---------------------------------------------------------------------------
82
47
  // LLM
83
48
  // ---------------------------------------------------------------------------
84
49
 
85
- function llm({ items, details, disclosure }: StandardListOutput): string {
86
- const lines: string[] = [];
87
- lines.push("## Standards");
88
- lines.push("");
89
-
90
- for (const [i, s] of items.entries()) {
91
- const cat = s.category ? ` [${s.category}]` : "";
92
- lines.push(`- **${s.name}**${cat}: ${s.description}`);
93
-
94
- if (disclosure.level === "digest") {
95
- const maxLen = disclosure.maxExampleLength ?? DEFAULT_MAX_EXAMPLE_LENGTH;
96
- const example = firstDoExample(details?.[i], maxLen);
97
- if (example) {
98
- lines.push(` - Example: \`${example}\``);
99
- }
100
- }
50
+ function llm(output: StandardListOutput): string {
51
+ const lines = ["## Standards", ""];
101
52
 
102
- if (disclosure.level === "detailed") {
103
- const detail = details?.[i];
104
- if (detail) {
105
- if (detail.dos.length > 0) {
106
- lines.push(" - **Do**:");
107
- for (const d of detail.dos) {
108
- lines.push(` - ${d.code}`);
109
- }
110
- }
111
- if (detail.donts.length > 0) {
112
- lines.push(" - **Don't**:");
113
- for (const d of detail.donts) {
114
- lines.push(` - ${d.code}`);
115
- }
116
- }
117
- }
118
- }
53
+ for (const row of buildRows(output)) {
54
+ lines.push(formatLlmRow(row, output.disclosure.level));
119
55
  }
120
56
 
121
57
  return lines.join("\n");
@@ -151,6 +87,98 @@ function json({ items, details, disclosure }: StandardListOutput): string {
151
87
  // Export
152
88
  // ---------------------------------------------------------------------------
153
89
 
90
+ /** Three-mode formatter for `pragma standard list` output. */
154
91
  const formatters: Formatters<StandardListOutput> = { plain, llm, json };
155
92
 
156
93
  export default formatters;
94
+
95
+ function buildRows({
96
+ items,
97
+ details,
98
+ disclosure,
99
+ }: StandardListOutput): readonly StandardListRow[] {
100
+ if (disclosure.level === "summary") {
101
+ return items;
102
+ }
103
+
104
+ if (disclosure.level === "digest") {
105
+ const maxLen = disclosure.maxExampleLength ?? DEFAULT_MAX_EXAMPLE_LENGTH;
106
+ return items.map((item, index) => ({
107
+ ...item,
108
+ extends: details?.[index]?.extends,
109
+ example: firstDoExample(details?.[index], maxLen),
110
+ }));
111
+ }
112
+
113
+ return items.map((item, index) => ({
114
+ ...item,
115
+ extends: details?.[index]?.extends,
116
+ dos: details?.[index]?.dos,
117
+ donts: details?.[index]?.donts,
118
+ }));
119
+ }
120
+
121
+ function formatPlainRow(
122
+ row: StandardListRow,
123
+ level: StandardListOutput["disclosure"]["level"],
124
+ ): string {
125
+ const lines = [formatHeading(row), ` ${row.description}`];
126
+
127
+ if (row.extends) {
128
+ lines.push(` Extends: ${row.extends}`);
129
+ }
130
+
131
+ if (level === "digest" && row.example) {
132
+ lines.push(` Example: ${row.example}`);
133
+ }
134
+
135
+ if (level === "detailed") {
136
+ if (row.dos && row.dos.length > 0) {
137
+ lines.push(" Do:");
138
+ lines.push(...row.dos.map((item) => ` ${item.code}`));
139
+ }
140
+
141
+ if (row.donts && row.donts.length > 0) {
142
+ lines.push(" Don't:");
143
+ lines.push(...row.donts.map((item) => ` ${item.code}`));
144
+ }
145
+ }
146
+
147
+ return lines.join("\n");
148
+ }
149
+
150
+ function formatLlmRow(
151
+ row: StandardListRow,
152
+ level: StandardListOutput["disclosure"]["level"],
153
+ ): string {
154
+ const lines = [
155
+ `- **${row.name}**${row.category ? ` [${row.category}]` : ""}`,
156
+ ];
157
+ lines.push(` ${row.description}`);
158
+
159
+ if (row.extends) {
160
+ lines.push(` Extends: ${row.extends}`);
161
+ }
162
+
163
+ if (level === "digest" && row.example) {
164
+ lines.push(` Example: \`${row.example}\``);
165
+ }
166
+
167
+ if (level === "detailed") {
168
+ if (row.dos && row.dos.length > 0) {
169
+ lines.push(" **Do**:");
170
+ lines.push(...row.dos.map((item) => ` - ${item.code}`));
171
+ }
172
+
173
+ if (row.donts && row.donts.length > 0) {
174
+ lines.push(" **Don't**:");
175
+ lines.push(...row.donts.map((item) => ` - ${item.code}`));
176
+ }
177
+ }
178
+
179
+ return lines.join("\n");
180
+ }
181
+
182
+ function formatHeading(row: StandardListRow): string {
183
+ return row.category ? `${row.name} [${row.category}]` : row.name;
184
+ }
@@ -1,6 +1,6 @@
1
1
  import type { URI } from "@canonical/ke";
2
2
  import { describe, expect, it } from "vitest";
3
- import type { StandardDetailed } from "../../shared/types.js";
3
+ import type { StandardDetailed } from "../../shared/types/index.js";
4
4
  import formatters from "./lookup.js";
5
5
 
6
6
  const DETAILED: StandardDetailed = {