@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
@@ -1,44 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { CategorySummary } from "../../shared/types/index.js";
3
- import formatters from "./categories.js";
4
-
5
- const CATEGORIES: CategorySummary[] = [
6
- { name: "code", standardCount: 1 },
7
- { name: "react", standardCount: 2 },
8
- ];
9
-
10
- describe("formatters.plain", () => {
11
- it("renders singular for count 1", () => {
12
- const text = formatters.plain(CATEGORIES);
13
- expect(text).toContain("code (1 standard)");
14
- });
15
-
16
- it("renders plural for count > 1", () => {
17
- const text = formatters.plain(CATEGORIES);
18
- expect(text).toContain("react (2 standards)");
19
- });
20
- });
21
-
22
- describe("formatters.llm", () => {
23
- it("renders markdown heading and bold names", () => {
24
- const text = formatters.llm(CATEGORIES);
25
- expect(text).toContain("## Standard Categories");
26
- expect(text).toContain("**react** (2)");
27
- });
28
- });
29
-
30
- describe("formatters.json", () => {
31
- it("returns valid JSON array", () => {
32
- const text = formatters.json(CATEGORIES);
33
- const parsed = JSON.parse(text);
34
- expect(Array.isArray(parsed)).toBe(true);
35
- expect(parsed).toHaveLength(2);
36
- expect(parsed[0].name).toBe("code");
37
- expect(parsed[0].standardCount).toBe(1);
38
- });
39
-
40
- it("returns empty array for empty list", () => {
41
- const text = formatters.json([]);
42
- expect(JSON.parse(text)).toEqual([]);
43
- });
44
- });
@@ -1,38 +0,0 @@
1
- /**
2
- * Three-mode formatter for `pragma standard categories` output.
3
- *
4
- * - **plain** — one line per category with standard count.
5
- * - **llm** — condensed Markdown consumed by LLM agents and reused
6
- * by the MCP adapter when `condensed: true`.
7
- * - **json** — structured JSON array for programmatic consumption.
8
- */
9
-
10
- import type { Formatters } from "../../shared/formatters.js";
11
- import type { CategorySummary } from "../../shared/types/index.js";
12
-
13
- const formatters: Formatters<readonly CategorySummary[]> = {
14
- plain(categories) {
15
- const lines: string[] = [];
16
- for (const c of categories) {
17
- const plural = c.standardCount === 1 ? "standard" : "standards";
18
- lines.push(`${c.name} (${c.standardCount} ${plural})`);
19
- }
20
- return lines.join("\n");
21
- },
22
-
23
- llm(categories) {
24
- const lines: string[] = [];
25
- lines.push("## Standard Categories");
26
- lines.push("");
27
- for (const c of categories) {
28
- lines.push(`- **${c.name}** (${c.standardCount})`);
29
- }
30
- return lines.join("\n");
31
- },
32
-
33
- json(data) {
34
- return JSON.stringify(data, null, 2);
35
- },
36
- };
37
-
38
- export default formatters;
@@ -1,10 +0,0 @@
1
- /** @module Re-exports standard formatters (`categoriesFormatters`, `listFormatters`, `lookupFormatters`, `sampleFormatters`). */
2
-
3
- export { default as categoriesFormatters } from "./categories.js";
4
- export { default as listFormatters } from "./list.js";
5
- export {
6
- createInkLookupOptions,
7
- default as lookupFormatters,
8
- } from "./lookup.js";
9
- export { default as sampleFormatters } from "./sample.js";
10
- export type { StandardListOutput, StandardLookupInput } from "./types.js";
@@ -1,169 +0,0 @@
1
- import type { URI } from "@canonical/ke";
2
- import { describe, expect, it } from "vitest";
3
- import type {
4
- StandardDetailed,
5
- StandardSummary,
6
- } from "../../shared/types/index.js";
7
- import formatters from "./list.js";
8
- import type { StandardListOutput } from "./types.js";
9
-
10
- const SUMMARY: StandardSummary = {
11
- uri: "http://example.com/s1" as URI,
12
- name: "react/component/folder-structure",
13
- category: "react",
14
- description: "Components must follow the standard folder layout",
15
- };
16
-
17
- const DETAIL: StandardDetailed = {
18
- ...SUMMARY,
19
- dos: [
20
- { language: "typescript", code: "export function Button() { ... }" },
21
- { language: "typescript", code: "export function Input() { ... }" },
22
- ],
23
- donts: [
24
- { language: "typescript", code: "export default class Button { ... }" },
25
- ],
26
- };
27
-
28
- function summaryOutput(items: readonly StandardSummary[]): StandardListOutput {
29
- return { items, disclosure: { level: "summary" } };
30
- }
31
-
32
- function digestOutput(
33
- items: readonly StandardSummary[],
34
- details: readonly (StandardDetailed | null)[],
35
- maxExampleLength?: number,
36
- ): StandardListOutput {
37
- return {
38
- items,
39
- details,
40
- disclosure: { level: "digest", maxExampleLength },
41
- };
42
- }
43
-
44
- function detailedOutput(
45
- items: readonly StandardSummary[],
46
- details: readonly (StandardDetailed | null)[],
47
- ): StandardListOutput {
48
- return { items, details, disclosure: { level: "detailed" } };
49
- }
50
-
51
- // ---------------------------------------------------------------------------
52
- // Summary (default)
53
- // ---------------------------------------------------------------------------
54
-
55
- describe("formatters.plain — summary", () => {
56
- it("renders name with category and description", () => {
57
- const text = formatters.plain(summaryOutput([SUMMARY]));
58
- expect(text).toContain("react/component/folder-structure [react]");
59
- expect(text).toContain(" Components must follow");
60
- });
61
-
62
- it("omits category brackets when empty", () => {
63
- const text = formatters.plain(
64
- summaryOutput([{ ...SUMMARY, category: "" }]),
65
- );
66
- expect(text).not.toContain("[");
67
- });
68
- });
69
-
70
- describe("formatters.llm — summary", () => {
71
- it("renders markdown heading and bold names", () => {
72
- const text = formatters.llm(summaryOutput([SUMMARY]));
73
- expect(text).toContain("## Standards");
74
- expect(text).toContain("**react/component/folder-structure**");
75
- });
76
- });
77
-
78
- describe("formatters.json — summary", () => {
79
- it("returns valid JSON array", () => {
80
- const text = formatters.json(summaryOutput([SUMMARY]));
81
- const parsed = JSON.parse(text);
82
- expect(Array.isArray(parsed)).toBe(true);
83
- expect(parsed).toHaveLength(1);
84
- expect(parsed[0].name).toBe("react/component/folder-structure");
85
- });
86
-
87
- it("returns empty array for empty list", () => {
88
- const text = formatters.json(summaryOutput([]));
89
- expect(JSON.parse(text)).toEqual([]);
90
- });
91
- });
92
-
93
- // ---------------------------------------------------------------------------
94
- // Digest
95
- // ---------------------------------------------------------------------------
96
-
97
- describe("formatters.plain — digest", () => {
98
- it("includes first do example", () => {
99
- const text = formatters.plain(digestOutput([SUMMARY], [DETAIL]));
100
- expect(text).toContain("Example: export function Button()");
101
- });
102
-
103
- it("truncates long examples", () => {
104
- const text = formatters.plain(digestOutput([SUMMARY], [DETAIL], 20));
105
- expect(text).toContain("Example: ");
106
- // Truncated to 20 chars (19 + ellipsis)
107
- const exampleLine = text.split("\n").find((l) => l.includes("Example:"));
108
- expect(exampleLine).toBeDefined();
109
- if (!exampleLine) {
110
- throw new Error("Expected example line");
111
- }
112
- // 20 char max means the example text portion is at most 20 chars
113
- const exampleText = exampleLine.replace(/.*Example: /, "");
114
- expect(exampleText.length).toBeLessThanOrEqual(20);
115
- });
116
-
117
- it("omits example when detail is null", () => {
118
- const text = formatters.plain(digestOutput([SUMMARY], [null]));
119
- expect(text).not.toContain("Example:");
120
- });
121
- });
122
-
123
- describe("formatters.llm — digest", () => {
124
- it("includes first do example in backticks", () => {
125
- const text = formatters.llm(digestOutput([SUMMARY], [DETAIL]));
126
- expect(text).toContain("Example: `export function Button()");
127
- });
128
- });
129
-
130
- describe("formatters.json — digest", () => {
131
- it("includes example field in enriched items", () => {
132
- const text = formatters.json(digestOutput([SUMMARY], [DETAIL]));
133
- const parsed = JSON.parse(text);
134
- expect(parsed[0].example).toBeDefined();
135
- expect(parsed[0].example).toContain("export function Button()");
136
- });
137
- });
138
-
139
- // ---------------------------------------------------------------------------
140
- // Detailed
141
- // ---------------------------------------------------------------------------
142
-
143
- describe("formatters.plain — detailed", () => {
144
- it("includes full dos and donts", () => {
145
- const text = formatters.plain(detailedOutput([SUMMARY], [DETAIL]));
146
- expect(text).toContain("Do:");
147
- expect(text).toContain("export function Button()");
148
- expect(text).toContain("export function Input()");
149
- expect(text).toContain("Don't:");
150
- expect(text).toContain("export default class Button");
151
- });
152
- });
153
-
154
- describe("formatters.llm — detailed", () => {
155
- it("includes dos and donts in markdown", () => {
156
- const text = formatters.llm(detailedOutput([SUMMARY], [DETAIL]));
157
- expect(text).toContain("**Do**:");
158
- expect(text).toContain("**Don't**:");
159
- });
160
- });
161
-
162
- describe("formatters.json — detailed", () => {
163
- it("includes dos and donts arrays", () => {
164
- const text = formatters.json(detailedOutput([SUMMARY], [DETAIL]));
165
- const parsed = JSON.parse(text);
166
- expect(parsed[0].dos).toHaveLength(2);
167
- expect(parsed[0].donts).toHaveLength(1);
168
- });
169
- });
@@ -1,184 +0,0 @@
1
- import type { Formatters } from "../../shared/formatters.js";
2
- import type { CodeBlock, StandardDetailed } from "../../shared/types/index.js";
3
- import type { StandardListOutput } from "./types.js";
4
-
5
- // ---------------------------------------------------------------------------
6
- // Helpers
7
- // ---------------------------------------------------------------------------
8
-
9
- const DEFAULT_MAX_EXAMPLE_LENGTH = 120;
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
-
22
- function truncate(text: string, max: number): string {
23
- const oneLine = text.replace(/\n/g, " ").trim();
24
- return oneLine.length <= max ? oneLine : `${oneLine.slice(0, max - 1)}…`;
25
- }
26
-
27
- function firstDoExample(
28
- detail: StandardDetailed | null | undefined,
29
- maxLen: number,
30
- ): string | undefined {
31
- if (!detail || detail.dos.length === 0) return undefined;
32
- // Safe — length check above guarantees dos[0] exists
33
- return truncate((detail.dos[0] as CodeBlock).code, maxLen);
34
- }
35
-
36
- // ---------------------------------------------------------------------------
37
- // Plain
38
- // ---------------------------------------------------------------------------
39
-
40
- function plain(output: StandardListOutput): string {
41
- return buildRows(output)
42
- .map((row) => formatPlainRow(row, output.disclosure.level))
43
- .join("\n\n");
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // LLM
48
- // ---------------------------------------------------------------------------
49
-
50
- function llm(output: StandardListOutput): string {
51
- const lines = ["## Standards", ""];
52
-
53
- for (const row of buildRows(output)) {
54
- lines.push(formatLlmRow(row, output.disclosure.level));
55
- }
56
-
57
- return lines.join("\n");
58
- }
59
-
60
- // ---------------------------------------------------------------------------
61
- // JSON
62
- // ---------------------------------------------------------------------------
63
-
64
- function json({ items, details, disclosure }: StandardListOutput): string {
65
- if (disclosure.level === "summary" || !details) {
66
- return JSON.stringify(items, null, 2);
67
- }
68
-
69
- if (disclosure.level === "digest") {
70
- const maxLen = disclosure.maxExampleLength ?? DEFAULT_MAX_EXAMPLE_LENGTH;
71
- const enriched = items.map((s, i) => {
72
- const example = firstDoExample(details[i], maxLen);
73
- return example ? { ...s, example } : s;
74
- });
75
- return JSON.stringify(enriched, null, 2);
76
- }
77
-
78
- // detailed — merge full detail into each item
79
- const enriched = items.map((s, i) => {
80
- const detail = details[i];
81
- return detail ? { ...s, dos: detail.dos, donts: detail.donts } : s;
82
- });
83
- return JSON.stringify(enriched, null, 2);
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // Export
88
- // ---------------------------------------------------------------------------
89
-
90
- /** Three-mode formatter for `pragma standard list` output. */
91
- const formatters: Formatters<StandardListOutput> = { plain, llm, json };
92
-
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,95 +0,0 @@
1
- import type { URI } from "@canonical/ke";
2
- import { describe, expect, it } from "vitest";
3
- import type { StandardDetailed } from "../../shared/types/index.js";
4
- import formatters from "./lookup.js";
5
-
6
- const DETAILED: StandardDetailed = {
7
- uri: "http://example.com/s1" as URI,
8
- name: "react/component/folder-structure",
9
- category: "react",
10
- description: "Components must follow the standard folder layout",
11
- dos: [
12
- { language: "typescript", code: "Place files in src/lib/" },
13
- { language: "typescript", code: "Include index.ts barrel" },
14
- ],
15
- donts: [{ language: "typescript", code: "Use flat directory" }],
16
- };
17
-
18
- describe("formatters.plain", () => {
19
- it("renders summary without dos/donts by default", () => {
20
- const text = formatters.plain({ standard: DETAILED, detailed: false });
21
- expect(text).toContain("react/component/folder-structure");
22
- expect(text).toContain("URI: http://example.com/s1");
23
- expect(text).toContain("Category: react");
24
- expect(text).toContain("Description:");
25
- expect(text).not.toContain("Do:");
26
- expect(text).not.toContain("Don't:");
27
- });
28
-
29
- it("renders dos and donts when detailed", () => {
30
- const text = formatters.plain({ standard: DETAILED, detailed: true });
31
- expect(text).toContain("Do:");
32
- expect(text).toContain(" Place files in src/lib/");
33
- expect(text).toContain("Don't:");
34
- expect(text).toContain(" Use flat directory");
35
- });
36
-
37
- it("renders dash for empty category", () => {
38
- const text = formatters.plain({
39
- standard: { ...DETAILED, category: "" },
40
- detailed: false,
41
- });
42
- expect(text).toContain("Category: —");
43
- });
44
-
45
- it("renders extends when present", () => {
46
- const text = formatters.plain({
47
- standard: { ...DETAILED, extends: "react/component/base" },
48
- detailed: false,
49
- });
50
- expect(text).toContain("Extends: react/component/base");
51
- });
52
- });
53
-
54
- describe("formatters.llm", () => {
55
- it("renders markdown heading", () => {
56
- const text = formatters.llm({ standard: DETAILED, detailed: false });
57
- expect(text).toContain("## react/component/folder-structure");
58
- expect(text).toContain("URI: http://example.com/s1");
59
- expect(text).not.toContain("### Do");
60
- });
61
-
62
- it("renders extends in llm output when present", () => {
63
- const text = formatters.llm({
64
- standard: { ...DETAILED, extends: "cs:react_base" },
65
- detailed: false,
66
- });
67
- expect(text).toContain("Extends: cs:react_base");
68
- });
69
-
70
- it("renders do/dont sections when detailed", () => {
71
- const text = formatters.llm({ standard: DETAILED, detailed: true });
72
- expect(text).toContain("### Do");
73
- expect(text).toContain("### Don't");
74
- expect(text).toContain("- Place files in src/lib/");
75
- });
76
- });
77
-
78
- describe("formatters.json", () => {
79
- it("includes dos/donts when detailed", () => {
80
- const parsed = JSON.parse(
81
- formatters.json({ standard: DETAILED, detailed: true }),
82
- );
83
- expect(parsed.dos).toHaveLength(2);
84
- expect(parsed.donts).toHaveLength(1);
85
- });
86
-
87
- it("omits dos/donts when not detailed", () => {
88
- const parsed = JSON.parse(
89
- formatters.json({ standard: DETAILED, detailed: false }),
90
- );
91
- expect(parsed.dos).toBeUndefined();
92
- expect(parsed.donts).toBeUndefined();
93
- expect(parsed.name).toBe("react/component/folder-structure");
94
- });
95
- });
@@ -1,101 +0,0 @@
1
- import type { RenderLookupOptions } from "../../shared/contracts.js";
2
- import type { Formatters } from "../../shared/formatters.js";
3
- import { renderLookupLlm, renderLookupPlain } from "../../shared/renderers.js";
4
- import type { StandardDetailed } from "../../shared/types/index.js";
5
- import { standardConfig } from "../standardConfig.js";
6
- import type { StandardLookupInput } from "./types.js";
7
-
8
- /** Three-mode formatter for `pragma standard lookup` output. */
9
- const formatters: Formatters<StandardLookupInput> = {
10
- plain: ({ standard, detailed }) =>
11
- renderLookupPlain(standard, {
12
- title: (entry) => entry.name,
13
- fields: [
14
- { label: "URI", value: (entry) => entry.uri },
15
- { label: "Category", value: (entry) => entry.category || "—" },
16
- { label: "Description", value: (entry) => entry.description },
17
- { label: "Extends", value: (entry) => entry.extends },
18
- ],
19
- sections: detailed
20
- ? standardConfig.lookupSections.filter(
21
- (section) => section.key === "dos" || section.key === "donts",
22
- )
23
- : [],
24
- sectionOverrides: standardSectionOverrides,
25
- }),
26
-
27
- llm: ({ standard, detailed }) =>
28
- renderLookupLlm(standard, {
29
- title: (entry) => entry.name,
30
- fields: [
31
- { label: "URI", value: (entry) => entry.uri },
32
- { label: "Category", value: (entry) => entry.category || "—" },
33
- { label: "Description", value: (entry) => entry.description },
34
- { label: "Extends", value: (entry) => entry.extends },
35
- ],
36
- sections: detailed
37
- ? standardConfig.lookupSections.filter(
38
- (section) => section.key === "dos" || section.key === "donts",
39
- )
40
- : [],
41
- sectionOverrides: standardSectionOverrides,
42
- }),
43
-
44
- json({ standard, detailed }) {
45
- if (detailed) {
46
- return JSON.stringify(standard, null, 2);
47
- }
48
- const { dos: _dos, donts: _donts, ...summary } = standard;
49
- return JSON.stringify(summary, null, 2);
50
- },
51
- };
52
-
53
- export default formatters;
54
-
55
- /**
56
- * Build lookup rendering options for the Ink TUI view.
57
- *
58
- * @param detailed - whether to include dos/donts sections
59
- * @returns Options compatible with the LookupView component.
60
- */
61
- export function createInkLookupOptions(
62
- detailed: boolean,
63
- ): RenderLookupOptions<StandardDetailed> {
64
- return {
65
- title: (entry) => entry.name,
66
- fields: [
67
- { label: "URI", value: (entry) => entry.uri },
68
- { label: "Category", value: (entry) => entry.category || "—" },
69
- { label: "Description", value: (entry) => entry.description },
70
- { label: "Extends", value: (entry) => entry.extends },
71
- ],
72
- sections: detailed
73
- ? standardConfig.lookupSections.filter(
74
- (section) => section.key === "dos" || section.key === "donts",
75
- )
76
- : [],
77
- };
78
- }
79
-
80
- const standardSectionOverrides = {
81
- dos: {
82
- plain: (entry: StandardLookupInput["standard"]) =>
83
- entry.dos.length > 0
84
- ? entry.dos.map((item) => ` ${item.code}`).join("\n")
85
- : null,
86
- llm: (entry: StandardLookupInput["standard"]) =>
87
- entry.dos.length > 0
88
- ? entry.dos.map((item) => `- ${item.code}`).join("\n")
89
- : null,
90
- },
91
- donts: {
92
- plain: (entry: StandardLookupInput["standard"]) =>
93
- entry.donts.length > 0
94
- ? entry.donts.map((item) => ` ${item.code}`).join("\n")
95
- : null,
96
- llm: (entry: StandardLookupInput["standard"]) =>
97
- entry.donts.length > 0
98
- ? entry.donts.map((item) => `- ${item.code}`).join("\n")
99
- : null,
100
- },
101
- };
@@ -1,15 +0,0 @@
1
- /**
2
- * Sample formatter for the standard domain.
3
- *
4
- * Delegates to the shared factory, rendering each item via the
5
- * existing standard lookup formatter.
6
- */
7
-
8
- import createSampleFormatters from "../../shared/sampleFormatters.js";
9
- import type { StandardDetailed } from "../../shared/types/index.js";
10
- import lookupFormatters from "./lookup.js";
11
-
12
- export default createSampleFormatters<StandardDetailed>(
13
- "standards",
14
- (standard, mode) => lookupFormatters[mode]({ standard, detailed: true }),
15
- );
@@ -1,31 +0,0 @@
1
- /**
2
- * Formatter input types for standard commands.
3
- *
4
- * Composite types that bundle the data with display options,
5
- * so formatters conform to the `Formatters<T>` contract.
6
- */
7
-
8
- import type {
9
- Disclosure,
10
- StandardDetailed,
11
- StandardSummary,
12
- } from "../../shared/types/index.js";
13
-
14
- /** Input payload for the standard-lookup formatter, pairing data with a detail flag. */
15
- export interface StandardLookupInput {
16
- readonly standard: StandardDetailed;
17
- readonly detailed: boolean;
18
- }
19
-
20
- /**
21
- * Input for the list formatter with progressive disclosure.
22
- *
23
- * - summary: items only (name + category + description)
24
- * - digest: items enriched with first do example
25
- * - detailed: items enriched with full dos/donts
26
- */
27
- export interface StandardListOutput {
28
- readonly items: readonly StandardSummary[];
29
- readonly details?: readonly (StandardDetailed | null)[];
30
- readonly disclosure: Disclosure;
31
- }