@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,18 +1,17 @@
1
- import { createInterface } from "node:readline";
2
1
  import {
2
+ answerPromptWithDefaults,
3
3
  type CommandResult,
4
4
  createExitResult,
5
5
  createOutputResult,
6
6
  formatEffectLine,
7
7
  isVisibleEffect,
8
+ runGeneratorTask,
8
9
  } from "@canonical/cli-core";
9
- import {
10
- dryRun,
11
- type Effect,
12
- runTask,
13
- runUndo,
14
- type Task,
15
- } from "@canonical/task";
10
+ import { dryRun, type Effect, type Task } from "@canonical/task";
11
+ import { runUndo } from "@canonical/task/node";
12
+ import createInteractivePromptSession, {
13
+ type InteractivePromptSession,
14
+ } from "../../shared/createInteractivePromptSession.js";
16
15
  import type { LogLevel } from "../types.js";
17
16
 
18
17
  /** Options controlling how {@link runSetupTask} executes. */
@@ -26,55 +25,41 @@ export interface SetupTaskOptions {
26
25
  }
27
26
 
28
27
  /**
29
- * Auto-confirm prompt handler for `--yes` mode.
30
- *
31
- * @param effect - A Prompt effect to resolve.
32
- * @returns The default value for the prompt type.
28
+ * Pick the prompt handler for a run: auto-confirm defaults under `--yes`, else
29
+ * an interactive readline session (whose lifetime the caller owns via the
30
+ * returned `dispose`).
33
31
  */
34
- function autoConfirmHandler(
35
- effect: Effect & { _tag: "Prompt" },
36
- ): Promise<unknown> {
37
- const q = effect.question;
38
- if (q.type === "confirm") return Promise.resolve(q.default ?? true);
39
- if (q.type === "select")
40
- return Promise.resolve(q.default ?? q.choices[0]?.value);
41
- if (q.type === "multiselect") return Promise.resolve(q.default ?? []);
42
- return Promise.resolve(q.default ?? "");
32
+ function selectPromptHandler(yes: boolean): {
33
+ handler: (effect: Effect & { _tag: "Prompt" }) => Promise<unknown>;
34
+ wasInterrupted: () => boolean;
35
+ dispose: () => void;
36
+ } {
37
+ if (yes) {
38
+ return {
39
+ handler: answerPromptWithDefaults,
40
+ wasInterrupted: () => false,
41
+ dispose: () => {},
42
+ };
43
+ }
44
+ const session: InteractivePromptSession = createInteractivePromptSession();
45
+ return {
46
+ handler: session.answerPrompt,
47
+ wasInterrupted: session.wasInterrupted,
48
+ dispose: session.dispose,
49
+ };
43
50
  }
44
51
 
45
52
  /**
46
- * Interactive prompt handler using readline on stdin/stderr.
47
- * Supports confirm prompts; other types fall back to defaults.
48
- *
49
- * @param effect - A Prompt effect to resolve interactively.
50
- * @returns The user's answer.
53
+ * Build a task log sink that writes to stderr, hiding debug lines unless
54
+ * verbose is set.
51
55
  */
52
- async function interactivePromptHandler(
53
- effect: Effect & { _tag: "Prompt" },
54
- ): Promise<unknown> {
55
- const q = effect.question;
56
-
57
- if (q.type === "confirm") {
58
- const defaultYes = q.default !== false;
59
- const hint = defaultYes ? "Y/n" : "y/N";
60
- const rl = createInterface({
61
- input: process.stdin,
62
- output: process.stderr,
63
- });
64
-
65
- const answer = await new Promise<string>((resolve) => {
66
- rl.question(`${q.message} [${hint}] `, (ans) => {
67
- rl.close();
68
- resolve(ans.trim());
69
- });
70
- });
71
-
72
- if (answer === "") return defaultYes;
73
- return answer.toLowerCase().startsWith("y");
74
- }
75
-
76
- // Non-confirm prompts fall back to defaults in D15
77
- return q.default;
56
+ function createStderrLogger(
57
+ verbose: boolean,
58
+ ): (level: LogLevel, message: string) => void {
59
+ return (level, message) => {
60
+ if (level === "debug" && !verbose) return;
61
+ process.stderr.write(`${message}\n`);
62
+ };
78
63
  }
79
64
 
80
65
  /**
@@ -150,7 +135,8 @@ function formatDryRunJson(effects: readonly Effect[]): string {
150
135
  * Supports three execution modes:
151
136
  * - `--dry-run`: collects effects without executing, formats for display.
152
137
  * - `--undo`: walks the task tree, collects undo operations, executes in reverse.
153
- * - default: interactive execution with readline-based prompts on stderr.
138
+ * - default: execution through {@link runGeneratorTask}, with readline-based
139
+ * prompts on stderr.
154
140
  *
155
141
  * @param task - The setup Task to execute.
156
142
  * @param options - Execution options (dryRun, undo, yes, verbose, llm, format).
@@ -182,14 +168,12 @@ export default async function runSetupTask(
182
168
 
183
169
  // Undo mode: walk the task tree, collect undos, execute in reverse
184
170
  if (options.undo) {
185
- const promptHandler = options.yes
186
- ? autoConfirmHandler
187
- : interactivePromptHandler;
188
-
189
- const onLog = (level: LogLevel, message: string): void => {
190
- if (level === "debug" && !verbose) return;
191
- process.stderr.write(`${message}\n`);
192
- };
171
+ const {
172
+ handler: promptHandler,
173
+ wasInterrupted,
174
+ dispose,
175
+ } = selectPromptHandler(options.yes ?? false);
176
+ const onLog = createStderrLogger(verbose);
193
177
 
194
178
  try {
195
179
  const result = await runUndo(task, { promptHandler, onLog });
@@ -202,28 +186,36 @@ export default async function runSetupTask(
202
186
  }
203
187
  return createExitResult(0);
204
188
  } catch (err) {
189
+ if (wasInterrupted()) {
190
+ return createExitResult(130);
191
+ }
205
192
  const message = err instanceof Error ? err.message : String(err);
206
193
  process.stderr.write(`Undo failed: ${message}\n`);
207
194
  return createExitResult(1);
195
+ } finally {
196
+ dispose();
208
197
  }
209
198
  }
210
199
 
211
200
  // Production execution
212
- const promptHandler = options.yes
213
- ? autoConfirmHandler
214
- : interactivePromptHandler;
215
-
216
- const onLog = (level: LogLevel, message: string): void => {
217
- if (level === "debug" && !verbose) return;
218
- process.stderr.write(`${message}\n`);
219
- };
201
+ const {
202
+ handler: promptHandler,
203
+ wasInterrupted,
204
+ dispose,
205
+ } = selectPromptHandler(options.yes ?? false);
206
+ const onLog = createStderrLogger(verbose);
220
207
 
221
208
  try {
222
- await runTask(task, { promptHandler, onLog });
209
+ await runGeneratorTask(task, { promptHandler, onLog });
223
210
  return createExitResult(0);
224
211
  } catch (err) {
212
+ if (wasInterrupted()) {
213
+ return createExitResult(130);
214
+ }
225
215
  const message = err instanceof Error ? err.message : String(err);
226
216
  process.stderr.write(`Setup failed: ${message}\n`);
227
217
  return createExitResult(1);
218
+ } finally {
219
+ dispose();
228
220
  }
229
221
  }
@@ -10,7 +10,12 @@ const mockExists =
10
10
  function mocks(
11
11
  existsPredicate: (path: string) => boolean,
12
12
  ): Map<string, (effect: Effect) => unknown> {
13
- return new Map([["Exists", mockExists(existsPredicate)]]);
13
+ return new Map<string, (effect: Effect) => unknown>([
14
+ ["Exists", mockExists(existsPredicate)],
15
+ // An existing config reads back as an empty-but-valid JSON object, so a
16
+ // write merges into it rather than failing closed on the dry-run placeholder.
17
+ ["ReadFile", () => "{}"],
18
+ ]);
14
19
  }
15
20
 
16
21
  describe("setupMcp", () => {
@@ -24,7 +24,7 @@ import {
24
24
  } from "./loaders/index.js";
25
25
  import { DEFAULT_PACKAGES } from "./packages.js";
26
26
  import { PREFIX_MAP } from "./prefixes.js";
27
- import type { GraphContent, SemanticPackage } from "./semanticPackage.js";
27
+ import type { SemanticPackage } from "./semanticPackage.js";
28
28
  import { resolveSemanticPackages } from "./semanticPackage.js";
29
29
 
30
30
  export interface BootStoreOptions {
@@ -38,6 +38,8 @@ export interface BootStoreOptions {
38
38
  refs?: ReadonlyArray<PackageRef>;
39
39
  /** Enable query tracing (from config). Env var PRAGMA_TRACE=1 overrides. */
40
40
  trace?: boolean;
41
+ /** Additional namespace prefixes merged over the built-in prefix map. */
42
+ prefixes?: Readonly<Record<string, string>>;
41
43
  }
42
44
 
43
45
  export interface BootResult {
@@ -59,12 +61,13 @@ export interface BootResult {
59
61
  export async function bootStore(
60
62
  options: BootStoreOptions = {},
61
63
  ): Promise<BootResult> {
64
+ const prefixes = { ...PREFIX_MAP, ...options.prefixes };
62
65
  try {
63
66
  // Testing/programmatic override — use explicit sources
64
67
  if (options.sources) {
65
68
  const store = await createStore({
66
69
  sources: options.sources,
67
- prefixes: PREFIX_MAP,
70
+ prefixes,
68
71
  cache: options.cache,
69
72
  cwd: options.cwd,
70
73
  });
@@ -110,7 +113,7 @@ export async function bootStore(
110
113
 
111
114
  const store = await createStore({
112
115
  sources: [],
113
- prefixes: PREFIX_MAP,
116
+ prefixes,
114
117
  cache: options.cache,
115
118
  cwd: options.cwd,
116
119
  plugins,
@@ -18,4 +18,15 @@ describe("compactUri", () => {
18
18
  "https://example.com/thing",
19
19
  );
20
20
  });
21
+
22
+ it("picks the longest matching namespace when namespaces overlap", () => {
23
+ const overlapping = {
24
+ base: "https://ex.org/",
25
+ sub: "https://ex.org/sub/",
26
+ } as const;
27
+ // Both `base` and `sub` are prefixes of the URI; the longer wins.
28
+ expect(compactUri("https://ex.org/sub/Thing", overlapping)).toBe(
29
+ "sub:Thing",
30
+ );
31
+ });
21
32
  });
@@ -1,16 +1,21 @@
1
1
  /**
2
- * Convert a full URI to its compact prefixed form when a matching prefix exists.
3
- * Falls back to the original URI when no namespace matches.
2
+ * Convert a full URI to its compact prefixed form using the longest matching
3
+ * namespace. When two registered namespaces are both prefixes of the URI
4
+ * (e.g. a base namespace and a sub-namespace), the longer — more specific —
5
+ * one wins. Falls back to the original URI when no namespace matches.
4
6
  */
5
7
  export default function compactUri(
6
8
  fullUri: string,
7
9
  prefixes: Readonly<Record<string, string>>,
8
10
  ): string {
11
+ let best: { prefix: string; namespace: string } | undefined;
9
12
  for (const [prefix, namespace] of Object.entries(prefixes)) {
10
- if (fullUri.startsWith(namespace)) {
11
- return `${prefix}:${fullUri.slice(namespace.length)}`;
13
+ if (!fullUri.startsWith(namespace)) continue;
14
+ if (best === undefined || namespace.length > best.namespace.length) {
15
+ best = { prefix, namespace };
12
16
  }
13
17
  }
14
18
 
15
- return fullUri;
19
+ if (best === undefined) return fullUri;
20
+ return `${best.prefix}:${fullUri.slice(best.namespace.length)}`;
16
21
  }
@@ -7,10 +7,10 @@
7
7
  * cli-core remains unaware of Store or FilterConfig.
8
8
  */
9
9
 
10
- import type { GlobalFlags, InteractiveHandler } from "@canonical/cli-core";
10
+ import type { GlobalFlags, PromptSessionFactory } from "@canonical/cli-core";
11
11
  import type { PragmaRuntime } from "./types/index.js";
12
12
 
13
13
  export interface PragmaContext extends PragmaRuntime {
14
14
  readonly globalFlags: GlobalFlags;
15
- readonly interactive?: InteractiveHandler | undefined;
15
+ readonly promptSession?: PromptSessionFactory | undefined;
16
16
  }
@@ -50,6 +50,15 @@ export interface ListResult<T> {
50
50
  | { readonly level: "detailed" };
51
51
  }
52
52
 
53
+ /** Aggregate metadata about a multi-query lookup. */
54
+ export interface LookupResultMeta {
55
+ /**
56
+ * Number of queries that failed with an unexpected (non-Pragma) error.
57
+ * Each such failure is also present in `errors` with code `INTERNAL_ERROR`.
58
+ */
59
+ readonly internalErrorCount: number;
60
+ }
61
+
53
62
  export interface LookupResult<T> {
54
63
  readonly results: readonly T[];
55
64
  readonly errors: readonly {
@@ -58,6 +67,8 @@ export interface LookupResult<T> {
58
67
  message: string;
59
68
  suggestions?: readonly string[];
60
69
  }[];
70
+ /** Populated by `lookupMany`; may be absent on hand-built results. */
71
+ readonly meta?: LookupResultMeta;
61
72
  }
62
73
 
63
74
  export interface ShowContract<T> {