@canonical/pragma-cli 0.29.0 → 0.30.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 (241) hide show
  1. package/README.md +67 -15
  2. package/dist/pragma +0 -0
  3. package/package.json +13 -13
  4. package/src/config/defaultConfig.test.ts +33 -0
  5. package/src/config/defaultConfig.ts +59 -0
  6. package/src/config/defaults.json +17 -0
  7. package/src/config/findProjectConfigPath.test.ts +53 -0
  8. package/src/config/findProjectConfigPath.ts +36 -0
  9. package/src/config/index.ts +17 -2
  10. package/src/config/mergeConfigFileUpdate.ts +58 -0
  11. package/src/config/parseConfigValues.ts +215 -0
  12. package/src/config/readConfig.test.ts +104 -13
  13. package/src/config/readConfig.ts +12 -153
  14. package/src/config/readConfigLayers.test.ts +107 -0
  15. package/src/config/readConfigLayers.ts +115 -0
  16. package/src/config/resolveGlobalConfigPath.ts +13 -0
  17. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  18. package/src/config/resolveWriteConfigPath.ts +31 -0
  19. package/src/config/types.ts +76 -1
  20. package/src/config/writeConfig.ts +25 -55
  21. package/src/domains/block/commands/list.ts +5 -90
  22. package/src/domains/block/commands/lookup.ts +8 -167
  23. package/src/domains/block/mcp/specs.ts +16 -196
  24. package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
  25. package/src/domains/block/stories.test.ts +28 -0
  26. package/src/domains/block/stories.ts +254 -0
  27. package/src/domains/config/commands/channel.test.ts +14 -3
  28. package/src/domains/config/commands/channel.ts +11 -7
  29. package/src/domains/config/commands/configScope.ts +46 -0
  30. package/src/domains/config/commands/framework.test.ts +18 -1
  31. package/src/domains/config/commands/framework.ts +13 -7
  32. package/src/domains/config/commands/show.ts +5 -45
  33. package/src/domains/config/commands/tier.test.ts +13 -2
  34. package/src/domains/config/commands/tier.ts +11 -7
  35. package/src/domains/config/commands/trace.test.ts +19 -2
  36. package/src/domains/config/commands/trace.ts +6 -3
  37. package/src/domains/config/formatters/channel.test.ts +20 -6
  38. package/src/domains/config/formatters/channel.ts +14 -11
  39. package/src/domains/config/formatters/framework.ts +14 -11
  40. package/src/domains/config/formatters/show.test.ts +27 -0
  41. package/src/domains/config/formatters/show.ts +27 -4
  42. package/src/domains/config/formatters/tier.test.ts +26 -10
  43. package/src/domains/config/formatters/tier.ts +19 -11
  44. package/src/domains/config/formatters/trace.ts +7 -7
  45. package/src/domains/config/mcp/specs.ts +33 -53
  46. package/src/domains/config/operations/show.test.ts +18 -0
  47. package/src/domains/config/operations/show.ts +7 -1
  48. package/src/domains/config/operations/types.ts +7 -0
  49. package/src/domains/config/stories.ts +42 -0
  50. package/src/domains/config/tasks/index.ts +1 -0
  51. package/src/domains/config/tasks/setChannel.ts +13 -28
  52. package/src/domains/config/tasks/setFramework.ts +11 -30
  53. package/src/domains/config/tasks/setTier.ts +10 -29
  54. package/src/domains/config/tasks/setTrace.ts +10 -28
  55. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  56. package/src/domains/create/commands/component.ts +5 -5
  57. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  58. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
  59. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  60. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +2 -14
  61. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  62. package/src/domains/doctor/operations/checks/index.ts +1 -0
  63. package/src/domains/doctor/operations/checks.test.ts +158 -7
  64. package/src/domains/doctor/operations/runChecks.test.ts +15 -6
  65. package/src/domains/doctor/operations/runChecks.ts +2 -0
  66. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  67. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  68. package/src/domains/graphql/commands/build.test.ts +5 -3
  69. package/src/domains/graphql/commands/build.ts +8 -16
  70. package/src/domains/graphql/commands/check.test.ts +5 -3
  71. package/src/domains/graphql/commands/check.ts +7 -13
  72. package/src/domains/graphql/commands/index.ts +1 -0
  73. package/src/domains/graphql/commands/serve.test.ts +40 -0
  74. package/src/domains/graphql/commands/serve.ts +131 -0
  75. package/src/domains/graphql/constants.ts +10 -0
  76. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  77. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  78. package/src/domains/graphql/helpers/index.ts +2 -0
  79. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  80. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  81. package/src/domains/graphql/index.ts +9 -6
  82. package/src/domains/graphql/operations/compileSchema.test.ts +21 -45
  83. package/src/domains/graphql/operations/compileSchema.ts +23 -25
  84. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  85. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  86. package/src/domains/graphql/operations/index.ts +5 -0
  87. package/src/domains/info/operations/collectInfo.test.ts +14 -3
  88. package/src/domains/info/operations/collectInfo.ts +12 -15
  89. package/src/domains/llm/capabilities.test.ts +4 -4
  90. package/src/domains/llm/data/toolCatalog.ts +6 -0
  91. package/src/domains/modifier/commands/list.ts +5 -38
  92. package/src/domains/modifier/commands/lookup.ts +8 -79
  93. package/src/domains/modifier/mcp/specs.ts +15 -94
  94. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
  95. package/src/domains/modifier/stories.test.ts +20 -0
  96. package/src/domains/modifier/stories.ts +77 -0
  97. package/src/domains/ontology/commands/list.ts +5 -58
  98. package/src/domains/ontology/commands/show.ts +6 -58
  99. package/src/domains/ontology/formatters/show.ts +7 -1
  100. package/src/domains/ontology/mcp/specs.ts +7 -84
  101. package/src/domains/ontology/stories.test.ts +36 -0
  102. package/src/domains/ontology/stories.ts +108 -0
  103. package/src/domains/refs/operations/updateRefs.ts +2 -37
  104. package/src/domains/setup/commands/skills.test.ts +6 -2
  105. package/src/domains/setup/commands/skills.ts +6 -2
  106. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  107. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  108. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  109. package/src/domains/shared/bootStore.ts +6 -3
  110. package/src/domains/shared/compactUri.test.ts +11 -0
  111. package/src/domains/shared/compactUri.ts +10 -5
  112. package/src/domains/shared/context.ts +2 -2
  113. package/src/domains/shared/contracts.ts +11 -0
  114. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  115. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  116. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  117. package/src/domains/shared/loaders/bundledLoader.ts +110 -12
  118. package/src/domains/shared/loaders/gitLoader.ts +2 -2
  119. package/src/domains/shared/loaders/index.ts +4 -1
  120. package/src/domains/shared/loaders/localLoader.ts +18 -4
  121. package/src/domains/shared/loaders/readPackageDir.ts +36 -1
  122. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  123. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  124. package/src/domains/shared/lookupMany.test.ts +120 -0
  125. package/src/domains/shared/lookupMany.ts +34 -2
  126. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  127. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  128. package/src/domains/shared/packages.ts +10 -17
  129. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  130. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  131. package/src/domains/shared/runtime.ts +4 -63
  132. package/src/domains/shared/semanticPackage.ts +17 -1
  133. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  134. package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
  135. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  136. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  137. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  138. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  139. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  140. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  141. package/src/domains/shared/stories/condense.test.ts +18 -0
  142. package/src/domains/shared/stories/condense.ts +15 -0
  143. package/src/domains/shared/stories/index.ts +20 -0
  144. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  145. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  146. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
  147. package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
  148. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
  149. package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
  150. package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
  151. package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
  152. package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
  153. package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
  154. package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
  155. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
  156. package/src/domains/shared/stories/pack/index.ts +28 -0
  157. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  158. package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
  159. package/src/domains/shared/stories/pack/types.ts +104 -0
  160. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
  161. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
  162. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  163. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  164. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  165. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  166. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  167. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  168. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  169. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  170. package/src/domains/shared/stories/types.ts +171 -0
  171. package/src/domains/shared/suggestions/expandGlob.ts +1 -0
  172. package/src/domains/shared/suggestions/suggestNames.ts +43 -10
  173. package/src/domains/skill/commands/index.ts +3 -1
  174. package/src/domains/skill/commands/list.test.ts +6 -2
  175. package/src/domains/skill/commands/list.ts +5 -63
  176. package/src/domains/skill/commands/lookup.test.ts +118 -0
  177. package/src/domains/skill/commands/lookup.ts +14 -0
  178. package/src/domains/skill/formatters/index.ts +1 -0
  179. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  180. package/src/domains/skill/formatters/lookup.ts +57 -0
  181. package/src/domains/skill/helpers/resolveSkillSources.ts +0 -1
  182. package/src/domains/skill/index.ts +7 -3
  183. package/src/domains/skill/mcp/specs.ts +10 -29
  184. package/src/domains/skill/operations/discover.test.ts +61 -30
  185. package/src/domains/skill/operations/discover.ts +28 -37
  186. package/src/domains/skill/operations/index.ts +2 -1
  187. package/src/domains/skill/operations/list.test.ts +15 -7
  188. package/src/domains/skill/operations/lookup.test.ts +91 -0
  189. package/src/domains/skill/operations/lookup.ts +70 -0
  190. package/src/domains/skill/stories.test.ts +43 -0
  191. package/src/domains/skill/stories.ts +103 -0
  192. package/src/domains/skill/types.ts +8 -0
  193. package/src/domains/standard/commands/list.ts +5 -84
  194. package/src/domains/standard/commands/lookup.ts +8 -93
  195. package/src/domains/standard/mcp/specs.ts +16 -171
  196. package/src/domains/standard/orchestration/resolveStandardLookup.ts +2 -1
  197. package/src/domains/standard/stories.test.ts +24 -0
  198. package/src/domains/standard/stories.ts +151 -0
  199. package/src/domains/tier/commands/list.ts +5 -55
  200. package/src/domains/tier/mcp/specs.ts +5 -31
  201. package/src/domains/tier/stories.test.ts +22 -0
  202. package/src/domains/tier/stories.ts +52 -0
  203. package/src/domains/token/mcp/specs.ts +5 -1
  204. package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
  205. package/src/domains/trace/tracePlugin.test.ts +1 -1
  206. package/src/mcp/registerResources.test.ts +382 -74
  207. package/src/mcp/registerResources.ts +5 -348
  208. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  209. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  210. package/src/mcp/resources/buildResourceList.ts +176 -0
  211. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  212. package/src/mcp/resources/classifyEntity.ts +27 -0
  213. package/src/mcp/resources/constants.ts +105 -0
  214. package/src/mcp/resources/displayPredicates.ts +62 -0
  215. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  216. package/src/mcp/resources/pickFirstValue.ts +26 -0
  217. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  218. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  219. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  220. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  221. package/src/mcp/resources/readEntity.ts +169 -0
  222. package/src/mcp/resources/registerResources.ts +126 -0
  223. package/src/mcp/resources/types.ts +128 -0
  224. package/src/mcp/tools/index.ts +9 -0
  225. package/src/mcp/tools/registerTools.test.ts +65 -8
  226. package/src/mcp/types.ts +4 -0
  227. package/src/pipeline/collectCommands.ts +8 -1
  228. package/src/pipeline/runCli.ts +40 -6
  229. package/src/testing/createTestMcpClient.ts +4 -1
  230. package/src/testing/index.ts +10 -0
  231. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  232. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  233. package/src/testing/integration/executor-execution.test.ts +101 -0
  234. package/src/testing/integration/executor-golden.test.ts +145 -0
  235. package/src/testing/integration/mcp-surface.test.ts +1 -1
  236. package/src/testing/integration/parity.test.ts +17 -12
  237. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  238. package/src/testing/setupXdgIsolation.ts +15 -0
  239. package/src/testing/standardParityFixtures.ts +92 -0
  240. package/src/testing/storyFixtures.ts +63 -0
  241. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -1,173 +1,14 @@
1
- /**
2
- * Wires the `pragma block lookup <name-or-uri...>` CLI command.
3
- *
4
- * Accepts a positional block name or IRI and optional aspect flags (anatomy,
5
- * modifiers, tokens, implementations). Delegates to {@link lookupBlock} for
6
- * data retrieval and {@link lookupFormatters} for output rendering.
7
- */
8
-
9
- import {
10
- type CommandDefinition,
11
- type CommandResult,
12
- createOutputResult,
13
- } from "@canonical/cli-core";
14
- import { PragmaError } from "#error";
15
- import { createLookupView } from "#tui";
1
+ import type { CommandDefinition } from "@canonical/cli-core";
16
2
  import type { PragmaContext } from "../../shared/context.js";
17
- import type { LookupResult } from "../../shared/contracts.js";
18
- import { renderLookupResults } from "../../shared/formatters.js";
19
- import type { BlockDetailed } from "../../shared/types/index.js";
20
- import { createLookupOptions, lookupFormatters } from "../formatters/index.js";
21
- import { resolveAspects } from "../helpers/index.js";
22
- import { listBlocks } from "../operations/index.js";
23
- import {
24
- buildBlockFilters,
25
- resolveBlockLookup,
26
- } from "../orchestration/index.js";
27
- import type { AspectFlags } from "../types.js";
28
-
29
- interface BlockLookupOutput {
30
- readonly result: LookupResult<BlockDetailed>;
31
- readonly detailed: boolean;
32
- readonly aspects: AspectFlags;
33
- }
3
+ import { compileLookupCommand } from "../../shared/stories/index.js";
4
+ import { blockLookupStory } from "../stories.js";
34
5
 
6
+ /**
7
+ * Wire the `pragma block lookup <name-or-uri...>` CLI command from the
8
+ * block lookup story.
9
+ */
35
10
  export default function buildLookupCommand(
36
11
  ctx: PragmaContext,
37
12
  ): CommandDefinition {
38
- return {
39
- path: ["block", "lookup"],
40
- description: "Look up block details",
41
- parameters: [
42
- {
43
- name: "names",
44
- description: "Block names or IRIs",
45
- type: "multiselect",
46
- positional: true,
47
- required: true,
48
- complete: async (partial: string) => {
49
- const all = await listBlocks(ctx.store, ctx.config);
50
- return all
51
- .map((c) => c.name)
52
- .filter((n) => n.toLowerCase().startsWith(partial.toLowerCase()));
53
- },
54
- },
55
- {
56
- name: "detailed",
57
- description:
58
- "Show full details including anatomy, modifiers, tokens, and implementations",
59
- type: "boolean",
60
- default: false,
61
- },
62
- {
63
- name: "anatomy",
64
- description: "Show anatomy tree",
65
- type: "boolean",
66
- default: false,
67
- },
68
- {
69
- name: "modifiers",
70
- description: "Show modifier values",
71
- type: "boolean",
72
- default: false,
73
- },
74
- {
75
- name: "tokens",
76
- description: "Show token references",
77
- type: "boolean",
78
- default: false,
79
- },
80
- {
81
- name: "implementations",
82
- description: "Show implementation paths",
83
- type: "boolean",
84
- default: false,
85
- },
86
- ],
87
- parameterGroups: {
88
- "Aspect filters": ["anatomy", "modifiers", "tokens", "implementations"],
89
- },
90
- meta: {
91
- examples: [
92
- "pragma block lookup Button",
93
- "pragma block lookup Button Card",
94
- "pragma block lookup Button --detailed",
95
- "pragma block lookup Button --anatomy --modifiers",
96
- "pragma block lookup Button --detailed --llm",
97
- "pragma block lookup ds:global.component.button",
98
- ],
99
- },
100
- execute: async (
101
- params: Record<string, unknown>,
102
- ): Promise<CommandResult> => {
103
- const names = normalizeNames(params.names, params.name);
104
- if (names.length === 0) {
105
- throw PragmaError.invalidInput("names", "(empty)", {
106
- recovery: {
107
- message: "List available blocks.",
108
- cli: "pragma block list",
109
- mcp: { tool: "block_list" },
110
- },
111
- });
112
- }
113
-
114
- const detailed = params.detailed === true;
115
- const aspectInput: Partial<AspectFlags> = {
116
- anatomy: params.anatomy === true,
117
- modifiers: params.modifiers === true,
118
- tokens: params.tokens === true,
119
- implementations: params.implementations === true,
120
- };
121
-
122
- const isAspectSelected =
123
- aspectInput.anatomy ||
124
- aspectInput.modifiers ||
125
- aspectInput.tokens ||
126
- aspectInput.implementations;
127
- const showDetailed = detailed || (isAspectSelected ?? false);
128
- const aspects = resolveAspects(aspectInput);
129
-
130
- const contract = await resolveBlockLookup(
131
- ctx.store,
132
- names,
133
- buildBlockFilters(ctx),
134
- );
135
-
136
- return createOutputResult<BlockLookupOutput>(
137
- { result: contract.result, detailed: showDetailed, aspects },
138
- {
139
- plain: ({ result, detailed: isDetailed, aspects: selectedAspects }) =>
140
- renderLookupResults({
141
- ctx,
142
- result,
143
- formatters: lookupFormatters,
144
- mapResult: (block) => ({
145
- block,
146
- detailed: isDetailed,
147
- aspects: selectedAspects,
148
- }),
149
- }),
150
- ink: ({ result, detailed: isDetailed, aspects: selectedAspects }) =>
151
- createLookupView({
152
- results: result.results,
153
- errors: result.errors,
154
- domain: "block",
155
- options: createLookupOptions(isDetailed, selectedAspects),
156
- }),
157
- },
158
- );
159
- },
160
- };
161
- }
162
-
163
- function normalizeNames(names: unknown, legacyName?: unknown): string[] {
164
- if (Array.isArray(names)) {
165
- return names.filter(
166
- (name): name is string => typeof name === "string" && name.length > 0,
167
- );
168
- }
169
- if (typeof legacyName === "string" && legacyName.length > 0) {
170
- return [legacyName];
171
- }
172
- return [];
13
+ return compileLookupCommand(ctx, blockLookupStory);
173
14
  }
@@ -1,201 +1,26 @@
1
1
  /**
2
- * MCP tool specs for the block domain — block_list, block_lookup.
2
+ * MCP tool specs for the block domain — block_list, block_lookup,
3
+ * block_sample.
3
4
  *
4
- * Declarative definitions — no MCP imports. The adapter layer
5
- * converts these into registered MCP tools via `registerFromSpec()`.
5
+ * The list and lookup tools are compiled from the block read stories in
6
+ * `../stories.ts`; only the sample tool is spec'd by hand. The adapter
7
+ * layer converts these into registered MCP tools via `registerFromSpec()`.
6
8
  */
7
9
 
8
- import type { ToolSpec } from "../../shared/ToolSpec.js";
9
10
  import {
10
- listFormatters as blockListFmt,
11
- lookupFormatters as blockLookupFmt,
12
- sampleFormatters as blockSampleFmt,
13
- } from "../formatters/index.js";
11
+ compileLookupTool,
12
+ compileReadTool,
13
+ condense,
14
+ } from "../../shared/stories/index.js";
15
+ import type { ToolSpec } from "../../shared/ToolSpec.js";
16
+ import { sampleFormatters as blockSampleFmt } from "../formatters/index.js";
14
17
  import { sampleBlocks } from "../operations/index.js";
15
- import {
16
- buildBlockFilters,
17
- resolveBlockList,
18
- resolveBlockLookup,
19
- } from "../orchestration/index.js";
18
+ import { buildBlockFilters } from "../orchestration/index.js";
19
+ import { blockListStory, blockLookupStory } from "../stories.js";
20
20
 
21
21
  const specs: readonly ToolSpec[] = [
22
- {
23
- name: "block_list",
24
- description:
25
- "List design system blocks visible under current tier and channel configuration.",
26
- params: {
27
- allTiers: {
28
- type: "boolean",
29
- description: "Show blocks from all tiers, ignoring tier filter",
30
- optional: true,
31
- },
32
- digest: {
33
- type: "boolean",
34
- description: "Include implementations per block",
35
- optional: true,
36
- },
37
- detailed: {
38
- type: "boolean",
39
- description: "Include full details per block",
40
- optional: true,
41
- },
42
- condensed: {
43
- type: "boolean",
44
- description: "Token-optimized output",
45
- optional: true,
46
- },
47
- },
48
- readOnly: true,
49
- async execute(rt, { allTiers, digest, detailed, condensed }) {
50
- const contract = await resolveBlockList(rt, {
51
- allTiers: allTiers === true,
52
- digest: digest === true,
53
- detailed: detailed === true,
54
- });
55
-
56
- if (condensed) {
57
- const text = blockListFmt.llm(contract.result.items);
58
- return {
59
- condensed: true,
60
- text,
61
- tokens: `~${Math.ceil(text.length / 4)}`,
62
- };
63
- }
64
-
65
- if (contract.result.disclosure.level === "detailed") {
66
- return {
67
- data: contract.result.items,
68
- meta: {
69
- count: contract.result.items.length,
70
- disclosure: "detailed",
71
- filters: contract.result.filters,
72
- },
73
- };
74
- }
75
-
76
- if (contract.result.disclosure.level === "digest") {
77
- return {
78
- data: contract.result.items,
79
- meta: {
80
- count: contract.result.items.length,
81
- disclosure: "digest",
82
- filters: contract.result.filters,
83
- },
84
- };
85
- }
86
-
87
- return {
88
- data: contract.result.items,
89
- meta: {
90
- count: contract.result.items.length,
91
- filters: contract.result.filters,
92
- },
93
- };
94
- },
95
- },
96
-
97
- {
98
- name: "block_lookup",
99
- description:
100
- "Get detailed information about one or more design system blocks including anatomy, modifiers, tokens, and applicable standards.",
101
- params: {
102
- names: {
103
- type: "string[]",
104
- description:
105
- "Block names or IRIs to look up (e.g. ['Button', 'ds:global.component.card'])",
106
- optional: false,
107
- },
108
- detailed: {
109
- type: "boolean",
110
- description: "Return full details (default: true for MCP)",
111
- optional: true,
112
- },
113
- condensed: {
114
- type: "boolean",
115
- description: "Token-optimized output",
116
- optional: true,
117
- },
118
- },
119
- readOnly: true,
120
- async execute(rt, { names, detailed, condensed }) {
121
- const queries = names as string[];
122
- const showDetailed = (detailed as boolean | undefined) ?? true;
123
- const contract = await resolveBlockLookup(
124
- rt.store,
125
- queries,
126
- buildBlockFilters(rt),
127
- );
128
- const result = contract.result;
129
-
130
- if (condensed) {
131
- const textParts = result.results.map((block) =>
132
- blockLookupFmt.llm({
133
- block,
134
- detailed: showDetailed,
135
- aspects: {
136
- anatomy: true,
137
- modifiers: true,
138
- tokens: true,
139
- implementations: true,
140
- },
141
- }),
142
- );
143
-
144
- if (result.errors.length > 0) {
145
- textParts.push(
146
- [
147
- "### Errors",
148
- ...result.errors.map(
149
- (error) => `- ${error.query}: ${error.message}`,
150
- ),
151
- ].join("\n"),
152
- );
153
- }
154
-
155
- return {
156
- condensed: true,
157
- text: textParts.join("\n\n"),
158
- tokens: `~${Math.ceil(textParts.join("\n\n").length / 4)}`,
159
- };
160
- }
161
-
162
- if (!showDetailed) {
163
- const summaries = result.results.map(
164
- ({
165
- uri,
166
- name,
167
- type,
168
- tier,
169
- modifiers,
170
- implementations,
171
- nodeCount,
172
- tokenCount,
173
- summary,
174
- }) => ({
175
- uri,
176
- name,
177
- type,
178
- tier,
179
- modifiers,
180
- implementations,
181
- nodeCount,
182
- tokenCount,
183
- summary,
184
- }),
185
- );
186
-
187
- return {
188
- data: {
189
- results: summaries,
190
- errors: result.errors,
191
- },
192
- meta: { count: summaries.length },
193
- };
194
- }
195
-
196
- return { data: result, meta: { count: result.results.length } };
197
- },
198
- },
22
+ compileReadTool(blockListStory),
23
+ compileLookupTool(blockLookupStory),
199
24
  {
200
25
  name: "block_sample",
201
26
  description:
@@ -224,12 +49,7 @@ const specs: readonly ToolSpec[] = [
224
49
  ];
225
50
 
226
51
  if (condensed) {
227
- const text = blockSampleFmt.llm({ ...result, nextSteps });
228
- return {
229
- condensed: true,
230
- text,
231
- tokens: `~${Math.ceil(text.length / 4)}`,
232
- };
52
+ return condense(blockSampleFmt.llm({ ...result, nextSteps }));
233
53
  }
234
54
 
235
55
  return {
@@ -19,12 +19,13 @@ export default async function resolveBlockLookup(
19
19
  const nested: LookupResult<BlockDetailed[]> =
20
20
  names.length > 0
21
21
  ? await lookupMany(names, (query) => lookupBlock(store, query, filters))
22
- : { results: [], errors: [] };
22
+ : { results: [], errors: [], meta: { internalErrorCount: 0 } };
23
23
 
24
24
  // lookupBlock returns BlockDetailed[] (multiple matches per name) — flatten
25
25
  const result: LookupResult<BlockDetailed> = {
26
26
  results: nested.results.flat(),
27
27
  errors: [...nested.errors, ...globErrors],
28
+ meta: nested.meta ?? { internalErrorCount: 0 },
28
29
  };
29
30
 
30
31
  return {
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { blockListStory, blockLookupStory } from "./stories.js";
3
+
4
+ describe("blockListStory", () => {
5
+ it("renders the ink list view", () => {
6
+ const view = blockListStory.renderInk?.([]);
7
+ expect(typeof view).toBe("string");
8
+ expect(view).toContain("Blocks");
9
+ });
10
+ });
11
+
12
+ describe("blockLookupStory", () => {
13
+ it("renders the ink lookup view", () => {
14
+ const view = blockLookupStory.renderInk?.(
15
+ { results: [], errors: [] },
16
+ { surface: "cli", detailed: false, params: {} },
17
+ );
18
+ expect(typeof view).toBe("string");
19
+ });
20
+
21
+ it("implies detail from aspect flags on the CLI only", () => {
22
+ const resolveDetailed = blockLookupStory.resolveDetailed;
23
+ expect(resolveDetailed?.("cli", { anatomy: true })).toBe(true);
24
+ expect(resolveDetailed?.("cli", {})).toBe(false);
25
+ expect(resolveDetailed?.("mcp", {})).toBe(true);
26
+ expect(resolveDetailed?.("mcp", { detailed: false })).toBe(false);
27
+ });
28
+ });
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Block read stories — the single declaration of `block list` and
3
+ * `block lookup` for both surfaces.
4
+ *
5
+ * The story kernel compiles these into the CLI commands and MCP tools,
6
+ * so descriptions, parameters, resolution, and rendering live here once.
7
+ */
8
+
9
+ import { PragmaError } from "#error";
10
+ import { createListView, createLookupView } from "#tui";
11
+ import type { ColumnDef } from "../shared/contracts.js";
12
+ import {
13
+ type LookupStory,
14
+ type LookupStoryView,
15
+ type ReadStory,
16
+ requirePragmaContext,
17
+ } from "../shared/stories/index.js";
18
+ import type { BlockDetailed } from "../shared/types/index.js";
19
+ import { blockConfig } from "./blockConfig.js";
20
+ import {
21
+ createLookupOptions,
22
+ listFormatters,
23
+ lookupFormatters,
24
+ } from "./formatters/index.js";
25
+ import type { BlockLookupInput } from "./formatters/types.js";
26
+ import { resolveAspects } from "./helpers/index.js";
27
+ import { listBlocks } from "./operations/index.js";
28
+ import {
29
+ buildBlockFilters,
30
+ resolveBlockList,
31
+ resolveBlockLookup,
32
+ } from "./orchestration/index.js";
33
+ import type { AspectFlags, BlockListContract } from "./types.js";
34
+
35
+ type BlockListItems = BlockListContract["result"]["items"];
36
+
37
+ const LIST_COLUMN_ORDER = ["name", "type", "tier", "modifiers", "uri"] as const;
38
+
39
+ /** The `block list` / `block_list` read story. */
40
+ export const blockListStory: ReadStory<BlockListContract, BlockListItems> = {
41
+ noun: "block",
42
+ verb: "list",
43
+ description: "List blocks in the design system",
44
+ toolDescription:
45
+ "List design system blocks visible under current tier and channel configuration.",
46
+ params: [
47
+ {
48
+ name: "allTiers",
49
+ type: "boolean",
50
+ description: "Show blocks from all tiers",
51
+ toolDescription: "Show blocks from all tiers, ignoring tier filter",
52
+ default: false,
53
+ },
54
+ {
55
+ name: "digest",
56
+ type: "boolean",
57
+ description: "Show enriched summary with implementations",
58
+ toolDescription: "Include implementations per block",
59
+ default: false,
60
+ },
61
+ {
62
+ name: "detailed",
63
+ type: "boolean",
64
+ description: "Show full details for each block",
65
+ toolDescription: "Include full details per block",
66
+ default: false,
67
+ },
68
+ ],
69
+ examples: [
70
+ "pragma block list",
71
+ "pragma block list --all-tiers",
72
+ "pragma block list --digest",
73
+ "pragma block list --detailed",
74
+ "pragma block list --llm",
75
+ "pragma block list --format json",
76
+ ],
77
+ resolve: (rt, params) =>
78
+ resolveBlockList(rt, {
79
+ allTiers: params.allTiers === true,
80
+ digest: params.digest === true,
81
+ detailed: params.detailed === true,
82
+ }),
83
+ toOutput: (contract) => contract.result.items,
84
+ formatters: listFormatters,
85
+ toEnvelope: (contract) => {
86
+ const { items, disclosure, filters } = contract.result;
87
+ if (disclosure.level === "summary") {
88
+ return { data: items, meta: { count: items.length, filters } };
89
+ }
90
+ return {
91
+ data: items,
92
+ meta: { count: items.length, disclosure: disclosure.level, filters },
93
+ };
94
+ },
95
+ renderInk: (items) =>
96
+ createListView({
97
+ heading: "Blocks",
98
+ domain: "block",
99
+ items,
100
+ columns: reorderColumns(blockConfig.listColumns, LIST_COLUMN_ORDER),
101
+ }),
102
+ };
103
+
104
+ /** The `block lookup` / `block_lookup` read story. */
105
+ export const blockLookupStory: LookupStory<BlockDetailed, BlockLookupInput> = {
106
+ noun: "block",
107
+ description: "Look up block details",
108
+ toolDescription:
109
+ "Get detailed information about one or more design system blocks including anatomy, modifiers, tokens, and applicable standards.",
110
+ namesDescription: "Block names or IRIs",
111
+ namesToolDescription:
112
+ "Block names or IRIs to look up (e.g. ['Button', 'ds:global.component.card'])",
113
+ complete: async (partial, cmdCtx) => {
114
+ const ctx = requirePragmaContext(cmdCtx);
115
+ const all = await listBlocks(ctx.store, ctx.config);
116
+ return all
117
+ .map((block) => block.name)
118
+ .filter((name) => name.toLowerCase().startsWith(partial.toLowerCase()));
119
+ },
120
+ detailedParam: {
121
+ description:
122
+ "Show full details including anatomy, modifiers, tokens, and implementations",
123
+ toolDescription: "Return full details (default: true for MCP)",
124
+ },
125
+ params: [
126
+ {
127
+ name: "anatomy",
128
+ type: "boolean",
129
+ description: "Show anatomy tree",
130
+ default: false,
131
+ surfaces: "cli",
132
+ },
133
+ {
134
+ name: "modifiers",
135
+ type: "boolean",
136
+ description: "Show modifier values",
137
+ default: false,
138
+ surfaces: "cli",
139
+ },
140
+ {
141
+ name: "tokens",
142
+ type: "boolean",
143
+ description: "Show token references",
144
+ default: false,
145
+ surfaces: "cli",
146
+ },
147
+ {
148
+ name: "implementations",
149
+ type: "boolean",
150
+ description: "Show implementation paths",
151
+ default: false,
152
+ surfaces: "cli",
153
+ },
154
+ ],
155
+ parameterGroups: {
156
+ "Aspect filters": ["anatomy", "modifiers", "tokens", "implementations"],
157
+ },
158
+ examples: [
159
+ "pragma block lookup Button",
160
+ "pragma block lookup Button Card",
161
+ "pragma block lookup Button --detailed",
162
+ "pragma block lookup Button --anatomy --modifiers",
163
+ "pragma block lookup Button --detailed --llm",
164
+ "pragma block lookup ds:global.component.button",
165
+ ],
166
+ resolve: async (rt, names) => {
167
+ const contract = await resolveBlockLookup(
168
+ rt.store,
169
+ names,
170
+ buildBlockFilters(rt),
171
+ );
172
+ return contract.result;
173
+ },
174
+ resolveDetailed: (surface, params) => {
175
+ if (surface === "mcp") {
176
+ return ((params.detailed as boolean | undefined) ?? true) === true;
177
+ }
178
+ return params.detailed === true || isAspectSelected(params);
179
+ },
180
+ toFmtInput: (block, view) => ({
181
+ block,
182
+ detailed: view.detailed,
183
+ aspects: resolveViewAspects(view),
184
+ }),
185
+ formatters: lookupFormatters,
186
+ project: (block) => {
187
+ const {
188
+ uri,
189
+ name,
190
+ type,
191
+ tier,
192
+ modifiers,
193
+ implementations,
194
+ nodeCount,
195
+ tokenCount,
196
+ summary,
197
+ } = block;
198
+ return {
199
+ uri,
200
+ name,
201
+ type,
202
+ tier,
203
+ modifiers,
204
+ implementations,
205
+ nodeCount,
206
+ tokenCount,
207
+ summary,
208
+ };
209
+ },
210
+ emptyNamesError: () =>
211
+ PragmaError.invalidInput("names", "(empty)", {
212
+ recovery: {
213
+ message: "List available blocks.",
214
+ cli: "pragma block list",
215
+ mcp: { tool: "block_list" },
216
+ },
217
+ }),
218
+ renderInk: (result, view) =>
219
+ createLookupView({
220
+ results: result.results,
221
+ errors: result.errors,
222
+ domain: "block",
223
+ options: createLookupOptions(view.detailed, resolveViewAspects(view)),
224
+ }),
225
+ };
226
+
227
+ function isAspectSelected(params: Record<string, unknown>): boolean {
228
+ return (
229
+ params.anatomy === true ||
230
+ params.modifiers === true ||
231
+ params.tokens === true ||
232
+ params.implementations === true
233
+ );
234
+ }
235
+
236
+ function resolveViewAspects(view: LookupStoryView): AspectFlags {
237
+ return resolveAspects({
238
+ anatomy: view.params.anatomy === true,
239
+ modifiers: view.params.modifiers === true,
240
+ tokens: view.params.tokens === true,
241
+ implementations: view.params.implementations === true,
242
+ });
243
+ }
244
+
245
+ function reorderColumns<T>(
246
+ columns: readonly ColumnDef<T>[],
247
+ order: readonly string[],
248
+ ): readonly ColumnDef<T>[] {
249
+ const byKey = new Map(columns.map((col) => [col.key, col]));
250
+ return order.flatMap((key) => {
251
+ const col = byKey.get(key as keyof T & string);
252
+ return col ? [col] : [];
253
+ });
254
+ }