@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,85 +1,14 @@
1
- /**
2
- * Wires the `pragma modifier lookup <name...>` CLI command.
3
- *
4
- * Retrieves a single modifier family by name and renders its values.
5
- */
6
-
7
- import {
8
- type CommandDefinition,
9
- createOutputResult,
10
- } from "@canonical/cli-core";
11
- import { createLookupView } from "#tui";
1
+ import type { CommandDefinition } from "@canonical/cli-core";
12
2
  import type { PragmaContext } from "../../shared/context.js";
13
- import type { LookupResult } from "../../shared/contracts.js";
14
- import { renderLookupResults } from "../../shared/formatters.js";
15
- import {
16
- createInkLookupOptions,
17
- lookupFormatters,
18
- } from "../formatters/index.js";
19
- import type { lookupModifier } from "../operations/index.js";
20
- import { resolveModifierLookup } from "../orchestration/index.js";
21
-
22
- interface ModifierLookupOutput {
23
- readonly result: LookupResult<Awaited<ReturnType<typeof lookupModifier>>>;
24
- }
3
+ import { compileLookupCommand } from "../../shared/stories/index.js";
4
+ import { modifierLookupStory } from "../stories.js";
25
5
 
6
+ /**
7
+ * Wire the `pragma modifier lookup <name...>` CLI command from the
8
+ * modifier lookup story.
9
+ */
26
10
  export default function buildLookupCommand(
27
11
  ctx: PragmaContext,
28
12
  ): CommandDefinition {
29
- return {
30
- path: ["modifier", "lookup"],
31
- description: "Look up a modifier family and its values",
32
- parameters: [
33
- {
34
- name: "names",
35
- description: "Modifier family names or IRIs",
36
- type: "multiselect",
37
- positional: true,
38
- required: true,
39
- },
40
- ],
41
- meta: {
42
- examples: [
43
- "pragma modifier lookup importance",
44
- "pragma modifier lookup importance density",
45
- "pragma modifier lookup importance --llm",
46
- ],
47
- },
48
- async execute(params: Record<string, unknown>) {
49
- const names = normalizeNames(params.names, params.name);
50
- const contract = await resolveModifierLookup(ctx.store, names);
51
-
52
- return createOutputResult<ModifierLookupOutput>(
53
- { result: contract.result },
54
- {
55
- plain: ({ result }) =>
56
- renderLookupResults({
57
- ctx,
58
- result,
59
- formatters: lookupFormatters,
60
- mapResult: (family) => family,
61
- }),
62
- ink: ({ result }) =>
63
- createLookupView({
64
- results: result.results,
65
- errors: result.errors,
66
- domain: "modifier",
67
- options: createInkLookupOptions(),
68
- }),
69
- },
70
- );
71
- },
72
- };
73
- }
74
-
75
- function normalizeNames(names: unknown, legacyName?: unknown): string[] {
76
- if (Array.isArray(names)) {
77
- return names.filter(
78
- (name): name is string => typeof name === "string" && name.length > 0,
79
- );
80
- }
81
- if (typeof legacyName === "string" && legacyName.length > 0) {
82
- return [legacyName];
83
- }
84
- return [];
13
+ return compileLookupCommand(ctx, modifierLookupStory);
85
14
  }
@@ -1,99 +1,25 @@
1
1
  /**
2
- * MCP tool specs for the modifier domain.
2
+ * MCP tool specs for the modifier domain — modifier_list, modifier_lookup,
3
+ * modifier_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 modifier 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 modifierListFmt,
11
- lookupFormatters as modifierLookupFmt,
12
- sampleFormatters as modifierSampleFmt,
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 modifierSampleFmt } from "../formatters/index.js";
14
17
  import { sampleModifiers } from "../operations/index.js";
15
- import {
16
- resolveModifierList,
17
- resolveModifierLookup,
18
- } from "../orchestration/index.js";
18
+ import { modifierListStory, modifierLookupStory } from "../stories.js";
19
19
 
20
20
  const specs: readonly ToolSpec[] = [
21
- {
22
- name: "modifier_list",
23
- description: "List all modifier families with their values.",
24
- params: {
25
- condensed: {
26
- type: "boolean",
27
- description: "Token-optimized output",
28
- optional: true,
29
- },
30
- },
31
- readOnly: true,
32
- async execute(rt, { condensed }) {
33
- const resolution = await resolveModifierList(rt);
34
-
35
- if (condensed) {
36
- const text = modifierListFmt.llm([...resolution.items]);
37
- return {
38
- condensed: true,
39
- text,
40
- tokens: `~${Math.ceil(text.length / 4)}`,
41
- };
42
- }
43
-
44
- return {
45
- data: resolution.items,
46
- meta: { count: resolution.items.length },
47
- };
48
- },
49
- },
50
-
51
- {
52
- name: "modifier_lookup",
53
- description: "Get one or more modifier families and their values.",
54
- params: {
55
- names: {
56
- type: "string[]",
57
- description: "Modifier family names or IRIs to look up",
58
- optional: false,
59
- },
60
- condensed: {
61
- type: "boolean",
62
- description: "Token-optimized output",
63
- optional: true,
64
- },
65
- },
66
- readOnly: true,
67
- async execute(rt, { names, condensed }) {
68
- const contract = await resolveModifierLookup(rt.store, names as string[]);
69
- const result = contract.result;
70
-
71
- if (condensed) {
72
- const textParts = result.results.map((family) =>
73
- modifierLookupFmt.llm(family),
74
- );
75
-
76
- if (result.errors.length > 0) {
77
- textParts.push(
78
- [
79
- "### Errors",
80
- ...result.errors.map(
81
- (error) => `- ${error.query}: ${error.message}`,
82
- ),
83
- ].join("\n"),
84
- );
85
- }
86
-
87
- return {
88
- condensed: true,
89
- text: textParts.join("\n\n"),
90
- tokens: `~${Math.ceil(textParts.join("\n\n").length / 4)}`,
91
- };
92
- }
93
-
94
- return { data: result, meta: { count: result.results.length } };
95
- },
96
- },
21
+ compileReadTool(modifierListStory),
22
+ compileLookupTool(modifierLookupStory),
97
23
  {
98
24
  name: "modifier_sample",
99
25
  description:
@@ -121,12 +47,7 @@ const specs: readonly ToolSpec[] = [
121
47
  ];
122
48
 
123
49
  if (condensed) {
124
- const text = modifierSampleFmt.llm({ ...result, nextSteps });
125
- return {
126
- condensed: true,
127
- text,
128
- tokens: `~${Math.ceil(text.length / 4)}`,
129
- };
50
+ return condense(modifierSampleFmt.llm({ ...result, nextSteps }));
130
51
  }
131
52
 
132
53
  return {
@@ -18,11 +18,12 @@ export default async function resolveModifierLookup(
18
18
  const lookupResult: LookupResult<ModifierFamily> =
19
19
  names.length > 0
20
20
  ? await lookupMany(names, (query) => lookupModifier(store, query))
21
- : { results: [], errors: [] };
21
+ : { results: [], errors: [], meta: { internalErrorCount: 0 } };
22
22
 
23
23
  const result: LookupResult<ModifierFamily> = {
24
24
  results: lookupResult.results,
25
25
  errors: [...lookupResult.errors, ...globErrors],
26
+ meta: lookupResult.meta ?? { internalErrorCount: 0 },
26
27
  };
27
28
 
28
29
  return {
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { modifierListStory, modifierLookupStory } from "./stories.js";
3
+
4
+ describe("modifierListStory", () => {
5
+ it("renders the ink list view", () => {
6
+ const view = modifierListStory.renderInk?.([]);
7
+ expect(typeof view).toBe("string");
8
+ expect(view).toContain("Modifiers");
9
+ });
10
+ });
11
+
12
+ describe("modifierLookupStory", () => {
13
+ it("renders the ink lookup view", () => {
14
+ const view = modifierLookupStory.renderInk?.(
15
+ { results: [], errors: [] },
16
+ { surface: "cli", detailed: false, params: {} },
17
+ );
18
+ expect(typeof view).toBe("string");
19
+ });
20
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Modifier read stories — the single declaration of `modifier list` and
3
+ * `modifier 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 { createListView, createLookupView } from "#tui";
10
+ import type { LookupStory, ReadStory } from "../shared/stories/index.js";
11
+ import type { ModifierFamily } from "../shared/types/index.js";
12
+ import {
13
+ createInkLookupOptions,
14
+ listFormatters,
15
+ lookupFormatters,
16
+ } from "./formatters/index.js";
17
+ import { modifierConfig } from "./modifierConfig.js";
18
+ import {
19
+ resolveModifierList,
20
+ resolveModifierLookup,
21
+ } from "./orchestration/index.js";
22
+ import type { ModifierListResolution } from "./types.js";
23
+
24
+ /** The `modifier list` / `modifier_list` read story. */
25
+ export const modifierListStory: ReadStory<
26
+ ModifierListResolution,
27
+ ModifierFamily[]
28
+ > = {
29
+ noun: "modifier",
30
+ verb: "list",
31
+ description: "List all modifier families",
32
+ toolDescription: "List all modifier families with their values.",
33
+ params: [],
34
+ examples: ["pragma modifier list", "pragma modifier list --llm"],
35
+ resolve: (rt) => resolveModifierList(rt),
36
+ toOutput: (resolution) => [...resolution.items],
37
+ formatters: listFormatters,
38
+ toEnvelope: (resolution) => ({
39
+ data: resolution.items,
40
+ meta: { count: resolution.items.length },
41
+ }),
42
+ renderInk: (items) =>
43
+ createListView({
44
+ heading: "Modifiers",
45
+ domain: "modifier",
46
+ items,
47
+ columns: modifierConfig.listColumns,
48
+ }),
49
+ };
50
+
51
+ /** The `modifier lookup` / `modifier_lookup` read story. */
52
+ export const modifierLookupStory: LookupStory<ModifierFamily, ModifierFamily> =
53
+ {
54
+ noun: "modifier",
55
+ description: "Look up a modifier family and its values",
56
+ toolDescription: "Get one or more modifier families and their values.",
57
+ namesDescription: "Modifier family names or IRIs",
58
+ namesToolDescription: "Modifier family names or IRIs to look up",
59
+ examples: [
60
+ "pragma modifier lookup importance",
61
+ "pragma modifier lookup importance density",
62
+ "pragma modifier lookup importance --llm",
63
+ ],
64
+ resolve: async (rt, names) => {
65
+ const contract = await resolveModifierLookup(rt.store, names);
66
+ return contract.result;
67
+ },
68
+ toFmtInput: (family) => family,
69
+ formatters: lookupFormatters,
70
+ renderInk: (result) =>
71
+ createLookupView({
72
+ results: result.results,
73
+ errors: result.errors,
74
+ domain: "modifier",
75
+ options: createInkLookupOptions(),
76
+ }),
77
+ };
@@ -1,64 +1,11 @@
1
- import {
2
- type CommandDefinition,
3
- type CommandResult,
4
- createOutputResult,
5
- } from "@canonical/cli-core";
6
- import { PragmaError } from "#error";
7
- import { createListView } from "#tui";
1
+ import type { CommandDefinition } from "@canonical/cli-core";
8
2
  import type { PragmaContext } from "../../shared/context.js";
9
- import type { ColumnDef } from "../../shared/contracts.js";
10
- import { selectFormatter } from "../../shared/formatters.js";
11
- import type { OntologySummary } from "../../shared/types/ontology.js";
12
- import { listFormatters } from "../formatters/index.js";
13
- import { listOntologies } from "../operations/index.js";
3
+ import { compileReadCommand } from "../../shared/stories/index.js";
4
+ import { ontologyListStory } from "../stories.js";
14
5
 
15
- const ontologyListColumns: readonly ColumnDef<OntologySummary>[] = [
16
- { key: "prefix", label: "Prefix" },
17
- { key: "namespace", label: "Namespace" },
18
- { key: "classCount", label: "Classes" },
19
- { key: "propertyCount", label: "Properties" },
20
- { key: "anatomyCount", label: "Anatomies" },
21
- ];
22
-
23
- /**
24
- * Builds the `pragma ontology list` command definition.
25
- *
26
- * Lists all loaded ontology namespaces with class and property counts.
27
- *
28
- * @param ctx - Pragma runtime context providing the ke store and config.
29
- * @returns A command definition for `ontology list`.
30
- */
6
+ /** Wire the `pragma ontology list` CLI command from the ontology list story. */
31
7
  export default function buildListCommand(
32
8
  ctx: PragmaContext,
33
9
  ): CommandDefinition {
34
- return {
35
- path: ["ontology", "list"],
36
- description: "List loaded ontologies",
37
- parameters: [],
38
- meta: {
39
- examples: ["pragma ontology list", "pragma ontology list --llm"],
40
- },
41
- execute: async (): Promise<CommandResult> => {
42
- const ontologies = await listOntologies(ctx.store);
43
-
44
- if (ontologies.length === 0) {
45
- throw PragmaError.emptyResults("ontology", {
46
- recovery: {
47
- message: "Ensure design system packages are installed.",
48
- },
49
- });
50
- }
51
-
52
- return createOutputResult(ontologies, {
53
- plain: selectFormatter(ctx, listFormatters),
54
- ink: (data) =>
55
- createListView({
56
- heading: "Ontologies",
57
- domain: "ontology",
58
- items: data,
59
- columns: ontologyListColumns,
60
- }),
61
- });
62
- },
63
- };
10
+ return compileReadCommand(ctx, ontologyListStory);
64
11
  }
@@ -1,66 +1,14 @@
1
- import {
2
- type CommandDefinition,
3
- type CommandResult,
4
- createOutputResult,
5
- } from "@canonical/cli-core";
6
- import { PragmaError } from "#error";
1
+ import type { CommandDefinition } from "@canonical/cli-core";
7
2
  import type { PragmaContext } from "../../shared/context.js";
8
- import { selectFormatter } from "../../shared/formatters.js";
9
- import { showFormatters } from "../formatters/index.js";
10
- import { listOntologies, showOntology } from "../operations/index.js";
3
+ import { compileReadCommand } from "../../shared/stories/index.js";
4
+ import { ontologyShowStory } from "../stories.js";
11
5
 
12
6
  /**
13
- * Builds the `pragma ontology show` command definition.
14
- *
15
- * Displays detailed schema (classes and properties) for a given ontology
16
- * prefix. Provides tab-completion for the prefix parameter.
17
- *
18
- * @param ctx - Pragma runtime context providing the ke store and config.
19
- * @returns A command definition for `ontology show`.
7
+ * Wire the `pragma ontology show <prefix>` CLI command from the ontology
8
+ * show story.
20
9
  */
21
10
  export default function buildShowCommand(
22
11
  ctx: PragmaContext,
23
12
  ): CommandDefinition {
24
- return {
25
- path: ["ontology", "show"],
26
- description: "Show ontology schema details",
27
- parameters: [
28
- {
29
- name: "prefix",
30
- description: "Ontology prefix (e.g. 'ds', 'cs')",
31
- type: "string",
32
- positional: true,
33
- required: true,
34
- complete: async (partial: string) => {
35
- const all = await listOntologies(ctx.store);
36
- return all
37
- .map((o) => o.prefix)
38
- .filter((p) => p.toLowerCase().startsWith(partial.toLowerCase()));
39
- },
40
- },
41
- ],
42
- meta: {
43
- examples: ["pragma ontology show ds", "pragma ontology show cs --llm"],
44
- },
45
- execute: async (
46
- params: Record<string, unknown>,
47
- ): Promise<CommandResult> => {
48
- const prefix = params.prefix as string;
49
- if (!prefix) {
50
- throw PragmaError.invalidInput("prefix", "(empty)", {
51
- recovery: {
52
- message: "List loaded ontologies to find valid prefixes.",
53
- cli: "pragma ontology list",
54
- mcp: { tool: "ontology_list" },
55
- },
56
- });
57
- }
58
-
59
- const result = await showOntology(ctx.store, prefix);
60
-
61
- return createOutputResult(result, {
62
- plain: selectFormatter(ctx, showFormatters),
63
- });
64
- },
65
- };
13
+ return compileReadCommand(ctx, ontologyShowStory);
66
14
  }
@@ -1,5 +1,11 @@
1
1
  import chalk from "chalk";
2
- import { formatHeading, formatSection } from "#pipeline";
2
+ // Deep import, not #pipeline: the barrel re-exports collectCommands, which
3
+ // imports every domain barrel — including this one — so #pipeline here forms
4
+ // a module cycle that evaluates this domain's stories as undefined at load.
5
+ import {
6
+ formatHeading,
7
+ formatSection,
8
+ } from "../../../pipeline/formatTerminal.js";
3
9
  import compactUri from "../../shared/compactUri.js";
4
10
  import type { Formatters } from "../../shared/formatters.js";
5
11
  import { PREFIX_MAP } from "../../shared/prefixes.js";
@@ -1,93 +1,16 @@
1
1
  /**
2
- * MCP tool specs for the ontology domain.
2
+ * MCP tool specs for the ontology domain — ontology_list and
3
+ * ontology_show, compiled from the ontology read stories in
4
+ * `../stories.ts` so both surfaces share formatters and resolution.
3
5
  */
4
6
 
7
+ import { compileReadTool } from "../../shared/stories/index.js";
5
8
  import type { ToolSpec } from "../../shared/ToolSpec.js";
6
- import { listOntologies, showOntology } from "../operations/index.js";
9
+ import { ontologyListStory, ontologyShowStory } from "../stories.js";
7
10
 
8
11
  const specs: readonly ToolSpec[] = [
9
- {
10
- name: "ontology_list",
11
- description:
12
- "List all ontologies loaded in the knowledge graph with class and property counts.",
13
- params: {
14
- condensed: {
15
- type: "boolean",
16
- description: "Token-optimized output",
17
- optional: true,
18
- },
19
- },
20
- readOnly: true,
21
- async execute(rt, { condensed }) {
22
- const result = await listOntologies(rt.store);
23
-
24
- if (condensed) {
25
- const lines = ["## Ontologies", ""];
26
- for (const o of result) {
27
- lines.push(
28
- `- **${o.prefix}:** (${o.namespace}) | classes: ${o.classCount} | properties: ${o.propertyCount}`,
29
- );
30
- }
31
- const text = lines.join("\n");
32
- return {
33
- condensed: true,
34
- text,
35
- tokens: `~${Math.ceil(text.length / 4)}`,
36
- };
37
- }
38
-
39
- return { data: result, meta: { count: result.length } };
40
- },
41
- },
42
- {
43
- name: "ontology_show",
44
- description:
45
- "Show detailed schema for an ontology including classes and properties.",
46
- params: {
47
- prefix: {
48
- type: "string",
49
- description: "Ontology prefix (e.g. 'ds', 'cs')",
50
- optional: false,
51
- },
52
- condensed: {
53
- type: "boolean",
54
- description: "Token-optimized output",
55
- optional: true,
56
- },
57
- },
58
- readOnly: true,
59
- async execute(rt, { prefix, condensed }) {
60
- const result = await showOntology(rt.store, prefix as string);
61
-
62
- if (condensed) {
63
- const lines = [`## ${result.prefix}: (${result.namespace})`, ""];
64
- if (result.classes.length > 0) {
65
- lines.push("### Classes");
66
- for (const c of result.classes) {
67
- const sup = c.superclass ? ` extends ${c.superclass}` : "";
68
- lines.push(`- **${c.label}**${sup}`);
69
- }
70
- lines.push("");
71
- }
72
- if (result.properties.length > 0) {
73
- lines.push("### Properties");
74
- for (const p of result.properties) {
75
- const domain = p.domain ? ` domain: ${p.domain}` : "";
76
- const range = p.range ? ` range: ${p.range}` : "";
77
- lines.push(`- **${p.label}** (${p.type})${domain}${range}`);
78
- }
79
- }
80
- const text = lines.join("\n");
81
- return {
82
- condensed: true,
83
- text,
84
- tokens: `~${Math.ceil(text.length / 4)}`,
85
- };
86
- }
87
-
88
- return { data: result };
89
- },
90
- },
12
+ compileReadTool(ontologyListStory),
13
+ compileReadTool(ontologyShowStory),
91
14
  ];
92
15
 
93
16
  export default specs;
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ontologyListStory, ontologyShowStory } from "./stories.js";
3
+
4
+ describe("ontologyListStory", () => {
5
+ it("renders the ink list view", () => {
6
+ const view = ontologyListStory.renderInk?.([]);
7
+ expect(typeof view).toBe("string");
8
+ expect(view).toContain("Ontologies");
9
+ });
10
+
11
+ it("rejects only empty results", () => {
12
+ expect(ontologyListStory.emptyError?.([], {})).toBeDefined();
13
+ expect(
14
+ ontologyListStory.emptyError?.(
15
+ [
16
+ {
17
+ prefix: "ds",
18
+ namespace: "https://ds.example/",
19
+ classCount: 1,
20
+ propertyCount: 1,
21
+ anatomyCount: 0,
22
+ },
23
+ ],
24
+ {},
25
+ ),
26
+ ).toBeUndefined();
27
+ });
28
+ });
29
+
30
+ describe("ontologyShowStory", () => {
31
+ it("guards against a missing prefix", () => {
32
+ expect(ontologyShowStory.guardParams?.({})).toBeDefined();
33
+ expect(ontologyShowStory.guardParams?.({ prefix: "" })).toBeDefined();
34
+ expect(ontologyShowStory.guardParams?.({ prefix: "ds" })).toBeUndefined();
35
+ });
36
+ });