@canonical/pragma-cli 0.32.0 → 0.34.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 (295) hide show
  1. package/README.md +72 -0
  2. package/dist/pragma +0 -0
  3. package/package.json +19 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +15 -6
  6. package/src/capabilities/block.parity.test.ts +250 -0
  7. package/src/capabilities/capabilities/capabilities.render.ts +3 -2
  8. package/src/capabilities/capabilities/capabilities.test.ts +10 -1
  9. package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
  10. package/src/capabilities/capabilities/catalog.ts +20 -8
  11. package/src/capabilities/capabilities/hints.ts +21 -36
  12. package/src/capabilities/capabilities/types.ts +2 -0
  13. package/src/capabilities/colophon/collectColophon.ts +34 -19
  14. package/src/capabilities/colophon/colophon.render.test.ts +31 -4
  15. package/src/capabilities/colophon/colophon.render.ts +13 -4
  16. package/src/capabilities/colophon/colophon.test.ts +3 -3
  17. package/src/capabilities/colophon/colophon.verb.ts +11 -4
  18. package/src/capabilities/colophon/markdownTerminal.ts +33 -14
  19. package/src/capabilities/colophon/types.ts +16 -7
  20. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  21. package/src/capabilities/config/collectConfigShow.ts +41 -4
  22. package/src/capabilities/config/fields.ts +10 -108
  23. package/src/capabilities/config/set.test.ts +6 -6
  24. package/src/capabilities/config/set.verb.ts +12 -8
  25. package/src/capabilities/config/show.render.test.ts +137 -0
  26. package/src/capabilities/config/show.render.ts +57 -16
  27. package/src/capabilities/config/show.verb.ts +9 -8
  28. package/src/capabilities/config/types.ts +8 -0
  29. package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
  30. package/src/capabilities/create/byteEquality.test.ts +82 -101
  31. package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
  32. package/src/capabilities/create/constants.ts +75 -0
  33. package/src/capabilities/create/create.mcp.test.ts +2 -2
  34. package/src/capabilities/create/create.test.ts +192 -16
  35. package/src/capabilities/create/create.verb.ts +208 -79
  36. package/src/capabilities/create/index.ts +2 -10
  37. package/src/capabilities/create/pickGenerator.ts +34 -28
  38. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  39. package/src/capabilities/create/types.ts +10 -6
  40. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  41. package/src/capabilities/distribution.test.ts +120 -0
  42. package/src/capabilities/distribution.ts +59 -0
  43. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  44. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  45. package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
  46. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
  47. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  48. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  49. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  50. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  51. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  52. package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
  53. package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
  54. package/src/capabilities/doctor/doctor.render.test.ts +170 -0
  55. package/src/capabilities/doctor/doctor.render.ts +117 -34
  56. package/src/capabilities/doctor/doctor.test.ts +68 -9
  57. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  58. package/src/capabilities/doctor/runChecks.ts +33 -9
  59. package/src/capabilities/doctor/types.ts +16 -1
  60. package/src/capabilities/emptyRecovery.test.ts +32 -15
  61. package/src/capabilities/graph/inspect.verb.ts +3 -2
  62. package/src/capabilities/graph/query.verb.ts +3 -2
  63. package/src/capabilities/graph/runQuery.ts +8 -5
  64. package/src/capabilities/index.ts +22 -18
  65. package/src/capabilities/info/collectInfo.ts +10 -6
  66. package/src/capabilities/info/info.render.ts +3 -2
  67. package/src/capabilities/info/info.verb.ts +3 -2
  68. package/src/capabilities/lazy.test.ts +93 -2
  69. package/src/capabilities/ontology/index.ts +10 -3
  70. package/src/capabilities/ontology/ontology.test.ts +157 -5
  71. package/src/capabilities/ontology/verbs.ts +203 -104
  72. package/src/capabilities/prompt/prompt.render.test.ts +94 -0
  73. package/src/capabilities/prompt/prompt.render.ts +23 -6
  74. package/src/capabilities/prompt/prompt.test.ts +68 -1
  75. package/src/capabilities/prompt/verbs.ts +24 -11
  76. package/src/capabilities/reference.test.ts +32 -0
  77. package/src/capabilities/resources/provider.ts +58 -25
  78. package/src/capabilities/resources/resources.test.ts +176 -21
  79. package/src/capabilities/scopeTypeSync.test.ts +37 -0
  80. package/src/capabilities/setup/index.ts +7 -1
  81. package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
  82. package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
  83. package/src/capabilities/setup/operations/setupLsp.ts +56 -12
  84. package/src/capabilities/setup/operations/setupMcp.ts +216 -39
  85. package/src/capabilities/setup/operations/setupSkills.ts +10 -4
  86. package/src/capabilities/setup/setup.render.test.ts +109 -0
  87. package/src/capabilities/setup/setup.render.ts +61 -14
  88. package/src/capabilities/setup/setup.test.ts +352 -1
  89. package/src/capabilities/setup/setup.verb.ts +68 -9
  90. package/src/capabilities/setup/shell.ts +29 -3
  91. package/src/capabilities/setup/types.ts +59 -2
  92. package/src/capabilities/shared/bands.ts +16 -0
  93. package/src/capabilities/skill/discover.ts +11 -5
  94. package/src/capabilities/skill/render.ts +4 -5
  95. package/src/capabilities/skill/verbs.ts +7 -7
  96. package/src/capabilities/sources/collectStatus.ts +69 -49
  97. package/src/capabilities/sources/index.ts +1 -1
  98. package/src/capabilities/sources/installSkills.ts +2 -2
  99. package/src/capabilities/sources/runUpdate.ts +146 -98
  100. package/src/capabilities/sources/sources.test.ts +264 -84
  101. package/src/capabilities/sources/status.render.test.ts +94 -0
  102. package/src/capabilities/sources/status.render.ts +55 -33
  103. package/src/capabilities/sources/status.verb.ts +15 -8
  104. package/src/capabilities/sources/types.ts +18 -25
  105. package/src/capabilities/sources/update.render.ts +9 -2
  106. package/src/capabilities/sources/update.verb.ts +12 -23
  107. package/src/capabilities/surface.test.ts +65 -17
  108. package/src/capabilities/tier.parity.test.ts +144 -0
  109. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  110. package/src/constants.test.ts +9 -7
  111. package/src/constants.ts +49 -23
  112. package/src/identity.test.ts +333 -0
  113. package/src/index.ts +2 -2
  114. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
  115. package/src/kernel/completion/complete.test.ts +3 -5
  116. package/src/kernel/completion/emitScripts.test.ts +1 -1
  117. package/src/kernel/completion/emitScripts.ts +4 -2
  118. package/src/kernel/completion/entitySource.test.ts +185 -41
  119. package/src/kernel/completion/entitySource.ts +131 -113
  120. package/src/kernel/completion/model.test.ts +3 -3
  121. package/src/kernel/completion/model.ts +11 -1
  122. package/src/kernel/completion/parse.test.ts +2 -4
  123. package/src/kernel/completion/safety.test.ts +82 -36
  124. package/src/kernel/completion/shellDrive.test.ts +592 -0
  125. package/src/kernel/completion/templates/fish.ts +28 -6
  126. package/src/kernel/completion/types.ts +3 -3
  127. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  128. package/src/kernel/config/defaults.test.ts +85 -0
  129. package/src/kernel/config/defaults.ts +12 -23
  130. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  131. package/src/kernel/config/findProjectConfig.ts +4 -3
  132. package/src/kernel/config/firstRun.ts +12 -5
  133. package/src/kernel/config/globalConfig.ts +2 -2
  134. package/src/kernel/config/index.ts +4 -5
  135. package/src/kernel/config/paths.ts +17 -17
  136. package/src/kernel/config/readConfig.test.ts +242 -6
  137. package/src/kernel/config/readConfig.ts +8 -8
  138. package/src/kernel/config/schema.test.ts +50 -0
  139. package/src/kernel/config/schema.ts +97 -10
  140. package/src/kernel/config/types.ts +73 -19
  141. package/src/kernel/copy.test.ts +453 -0
  142. package/src/kernel/error/PragmaError.test.ts +5 -5
  143. package/src/kernel/error/constants.ts +3 -2
  144. package/src/kernel/error/index.ts +1 -1
  145. package/src/kernel/error/recovery.test.ts +22 -45
  146. package/src/kernel/error/recovery.ts +22 -27
  147. package/src/kernel/error/renderError.ts +1 -1
  148. package/src/kernel/error/types.ts +5 -4
  149. package/src/kernel/packs/collect.test.ts +195 -11
  150. package/src/kernel/packs/collect.ts +213 -58
  151. package/src/kernel/packs/compile.test.ts +163 -7
  152. package/src/kernel/packs/compile.ts +15 -12
  153. package/src/kernel/packs/disclosure.test.ts +5 -2
  154. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  155. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  156. package/src/kernel/packs/iri.ts +5 -7
  157. package/src/kernel/packs/renderPack.ts +5 -3
  158. package/src/kernel/packs/resolveEntity.ts +21 -17
  159. package/src/kernel/packs/runBodies.ts +15 -14
  160. package/src/kernel/packs/schema.ts +96 -10
  161. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  162. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  163. package/src/kernel/packs/toolDescription.test.ts +10 -2
  164. package/src/kernel/packs/types.ts +39 -4
  165. package/src/kernel/packs/uniqueness.ts +4 -4
  166. package/src/kernel/project/cli/buildProgram.test.ts +60 -11
  167. package/src/kernel/project/cli/buildProgram.ts +11 -0
  168. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  169. package/src/kernel/project/cli/dispatch.ts +34 -12
  170. package/src/kernel/project/cli/globalFlags.test.ts +12 -4
  171. package/src/kernel/project/cli/globalFlags.ts +14 -14
  172. package/src/kernel/project/cli/helpFormat.ts +1 -1
  173. package/src/kernel/project/cli/rootHelp.ts +109 -107
  174. package/src/kernel/project/mcp/buildServer.ts +11 -6
  175. package/src/kernel/project/mcp/instructions.ts +12 -7
  176. package/src/kernel/project/mcp/mcpError.ts +52 -0
  177. package/src/kernel/project/mcp/parity.test.ts +39 -1
  178. package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
  179. package/src/kernel/project/mcp/prompts/provider.ts +51 -7
  180. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  181. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  182. package/src/kernel/project/mcp/serve.ts +11 -3
  183. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  184. package/src/kernel/render/disclosure.ts +4 -2
  185. package/src/kernel/render/formatters.ts +5 -4
  186. package/src/kernel/render/prefixes.test.ts +36 -0
  187. package/src/kernel/render/prefixes.ts +40 -13
  188. package/src/kernel/render/renderers.test.ts +35 -1
  189. package/src/kernel/render/renderers.ts +25 -7
  190. package/src/kernel/render/style.test.ts +31 -0
  191. package/src/kernel/render/style.ts +74 -0
  192. package/src/kernel/runtime/graphpack/build.ts +57 -3
  193. package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
  194. package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
  195. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
  196. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
  197. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  198. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  199. package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
  200. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  201. package/src/kernel/runtime/graphpack/index.ts +1 -1
  202. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  203. package/src/kernel/runtime/graphpack/read.ts +32 -22
  204. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  205. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  206. package/src/kernel/runtime/graphpack/types.ts +72 -45
  207. package/src/kernel/runtime/loadSession.ts +4 -12
  208. package/src/kernel/runtime/paths.ts +64 -15
  209. package/src/kernel/runtime/probe.test.ts +8 -4
  210. package/src/kernel/runtime/readEntity.ts +6 -5
  211. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  212. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  213. package/src/kernel/runtime/refs/resolve.ts +141 -65
  214. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  215. package/src/kernel/runtime/resolveSources.ts +36 -25
  216. package/src/kernel/runtime/storeReadiness.ts +62 -0
  217. package/src/kernel/runtime/storeless.test.ts +1 -1
  218. package/src/kernel/runtime/types.ts +1 -1
  219. package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
  220. package/src/kernel/spec/emitReference.test.ts +196 -0
  221. package/src/kernel/spec/emitReference.ts +624 -0
  222. package/src/kernel/spec/emitSurface.ts +13 -7
  223. package/src/kernel/spec/index.ts +2 -0
  224. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  225. package/src/kernel/spec/surfaceConformance.ts +8 -0
  226. package/src/kernel/spec/types.ts +29 -9
  227. package/src/kernel/spec/validate.ts +2 -1
  228. package/src/kernel/vocabulary.test.ts +164 -0
  229. package/src/kernel/vocabulary.ts +146 -0
  230. package/src/testing/PARITY_GAPS.ts +9 -3
  231. package/src/testing/behavioral/README.md +3 -3
  232. package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
  233. package/src/testing/behavioral/completion.test.ts +21 -8
  234. package/src/testing/behavioral/disclosure.test.ts +12 -0
  235. package/src/testing/behavioral/docExamples.test.ts +283 -0
  236. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  237. package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
  238. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  239. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  240. package/src/testing/behavioral/journeys.cli.test.ts +116 -73
  241. package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
  242. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  243. package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
  244. package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
  245. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
  246. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  247. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  248. package/src/testing/behavioral/mcpSurface.test.ts +10 -5
  249. package/src/testing/behavioral/staleCommands.test.ts +86 -0
  250. package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
  251. package/src/testing/eval/cases/assembly.ts +16 -9
  252. package/src/testing/eval/cases/effectful.ts +31 -16
  253. package/src/testing/eval/cases/readNouns.ts +23 -18
  254. package/src/testing/eval/cases/stable.ts +5 -1
  255. package/src/testing/eval/eval.test.ts +7 -1
  256. package/src/testing/fixtures/fixtureCapability.ts +201 -1
  257. package/src/testing/fixtures/graph/canonical.ts +6 -3
  258. package/src/testing/fixtures/graph/defaultPack.ts +179 -0
  259. package/src/testing/fixtures/graph/owlExport.ts +97 -0
  260. package/src/testing/helpers/extractSnippets.ts +29 -0
  261. package/src/testing/helpers/fixtureGraph.ts +33 -16
  262. package/src/testing/helpers/golden.ts +1 -1
  263. package/src/testing/helpers/packRuntime.ts +2 -3
  264. package/src/testing/helpers/projectCli.ts +2 -1
  265. package/src/testing/helpers/projectMcp.ts +6 -0
  266. package/src/testing/perf/budgets.test.ts +4 -0
  267. package/src/testing/perf/budgets.ts +22 -7
  268. package/src/testing/perf/globalSetup.ts +121 -6
  269. package/src/types.ts +2 -2
  270. package/src/capabilities/block/blockList.verb.ts +0 -150
  271. package/src/capabilities/block/index.ts +0 -25
  272. package/src/capabilities/block/pack.ts +0 -155
  273. package/src/capabilities/block/parity.test.ts +0 -180
  274. package/src/capabilities/block/tierChain.ts +0 -78
  275. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  276. package/src/capabilities/config/field.test.ts +0 -188
  277. package/src/capabilities/modifier/index.ts +0 -15
  278. package/src/capabilities/modifier/pack.ts +0 -64
  279. package/src/capabilities/standard/index.ts +0 -18
  280. package/src/capabilities/standard/pack.ts +0 -128
  281. package/src/capabilities/tier/index.ts +0 -19
  282. package/src/capabilities/tier/lookup.render.ts +0 -34
  283. package/src/capabilities/tier/lookup.test.ts +0 -68
  284. package/src/capabilities/tier/lookup.verb.ts +0 -44
  285. package/src/capabilities/tier/pack.ts +0 -28
  286. package/src/capabilities/tier/runLookup.ts +0 -59
  287. package/src/capabilities/token/addConfig.render.ts +0 -27
  288. package/src/capabilities/token/addConfig.test.ts +0 -62
  289. package/src/capabilities/token/addConfig.verb.ts +0 -40
  290. package/src/capabilities/token/index.ts +0 -19
  291. package/src/capabilities/token/pack.ts +0 -58
  292. package/src/capabilities/token/runAddConfig.ts +0 -95
  293. package/src/kernel/completion/bashDrive.test.ts +0 -73
  294. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  295. package/src/kernel/runtime/lock.ts +0 -90
@@ -3,24 +3,34 @@
3
3
  *
4
4
  * Listing zero rows is a calm SUCCESS, not an error. The run body returns `[]`
5
5
  * (no throw), dispatch renders a non-blank message and exits 0, and JSON stays
6
- * `[]`. A pack's authored `emptyRecovery` becomes that message's HINT — the v2
7
- * story still points at `pragma sources update` — but as guidance on the
8
- * success path, never a thrown EMPTY_RESULTS (which maps to exit 1 and would
9
- * break the uniform `ok:true` list contract).
6
+ * `[]`. A story's declared `emptyRecovery` becomes that message's HINT — but as
7
+ * guidance on the success path, never a thrown EMPTY_RESULTS (which maps to
8
+ * exit 1 and would break the uniform `ok:true` list contract).
9
+ *
10
+ * The story declares the command WITHOUT a binary name (`sources update`); the
11
+ * renderer prepends the CONSUMING distribution's. The hints asserted below are
12
+ * therefore the JOINED string, which is what a user reads — a story that named
13
+ * a binary itself would render it twice, and the grammar now refuses one.
10
14
  */
11
15
 
12
16
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
13
17
  import { compilePack } from "../kernel/packs/compile.js";
14
18
  import type { PackDefinition } from "../kernel/packs/types.js";
19
+ import { distributionSource } from "../kernel/packs/types.js";
15
20
  import { verbKey } from "../kernel/packs/uniqueness.js";
16
21
  import { executeVerb } from "../kernel/project/cli/dispatch.js";
17
22
  import { DEFAULT_PREFIX_MAP } from "../kernel/render/prefixes.js";
18
23
  import type { PragmaRuntime } from "../kernel/runtime/types.js";
19
24
  import type { VerbSpec } from "../kernel/spec/types.js";
20
25
  import { buildFixtureRuntime } from "../testing/helpers/packRuntime.js";
21
- import { modifierPack } from "./modifier/pack.js";
22
- import { standardPack } from "./standard/pack.js";
23
- import { tokenPack } from "./token/pack.js";
26
+ import { declaredStories } from "./distribution.js";
27
+
28
+ /** A story `pragma.conf.ts` declares — absent means it was renamed or dropped. */
29
+ function storyFor(noun: string): PackDefinition {
30
+ const story = declaredStories.get(noun);
31
+ if (!story) throw new Error(`pragma.conf.ts declares no story for "${noun}"`);
32
+ return story;
33
+ }
24
34
 
25
35
  const PREFIXES = {
26
36
  ds: "https://ds.canonical.com/",
@@ -61,19 +71,21 @@ afterAll(async () => {
61
71
  });
62
72
 
63
73
  const listVerb = (pack: PackDefinition): VerbSpec =>
64
- compilePack(pack, `bundled:${pack.noun}`, DEFAULT_PREFIX_MAP).find(
65
- (v) => verbKey(v.path) === `${pack.noun} list`,
66
- ) as VerbSpec;
74
+ compilePack(
75
+ pack,
76
+ distributionSource("pragma.conf.ts"),
77
+ DEFAULT_PREFIX_MAP,
78
+ ).find((v) => verbKey(v.path) === `${pack.noun} list`) as VerbSpec;
67
79
 
68
80
  /**
69
- * Every bundled pack list verb, with the hint expected in its empty message:
81
+ * Every declared story's list verb, with the hint expected in its empty message:
70
82
  * an authored `emptyRecovery` surfaces `pragma sources update`; the others fall
71
83
  * back to the generic build/broaden hint.
72
84
  */
73
85
  const PACK_LISTS: readonly { pack: PackDefinition; hint: RegExp }[] = [
74
- { pack: modifierPack, hint: /pragma sources update/ },
75
- { pack: tokenPack, hint: /pragma sources update/ },
76
- { pack: standardPack, hint: /broaden your filter/ },
86
+ { pack: storyFor("modifier"), hint: /pragma sources update/ },
87
+ { pack: storyFor("token"), hint: /pragma sources update/ },
88
+ { pack: storyFor("standard"), hint: /broaden your filter/ },
77
89
  ];
78
90
 
79
91
  const REAL = { dryRun: false, undo: false, yes: false };
@@ -104,7 +116,12 @@ describe("pack list empty-state (U5, PROTECTED)", () => {
104
116
  });
105
117
 
106
118
  it("dispatch prints the empty message on stdout and exits 0 (end-to-end)", async () => {
107
- const outcome = await executeVerb(listVerb(modifierPack), {}, REAL, rt);
119
+ const outcome = await executeVerb(
120
+ listVerb(storyFor("modifier")),
121
+ {},
122
+ REAL,
123
+ rt,
124
+ );
108
125
  expect(outcome.exitCode).toBe(0);
109
126
  expect(outcome.stdout).toContain("No modifier entries found.");
110
127
  expect(outcome.stdout).toContain("pragma sources update");
@@ -8,6 +8,7 @@
8
8
  * name or absolute IRI addresses the subject exactly.
9
9
  */
10
10
 
11
+ import { BIN_NAME } from "../../constants.js";
11
12
  import type { InspectResult } from "../../kernel/runtime/readEntity.js";
12
13
  import { readEntity } from "../../kernel/runtime/readEntity.js";
13
14
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
@@ -32,8 +33,8 @@ const inspectVerb: VerbSpec<Record<string, unknown>, InspectResult> = {
32
33
  ],
33
34
  output: { formatters: inspectFormatters },
34
35
  examples: [
35
- { cmd: "pragma graph inspect ds:button" },
36
- { cmd: "pragma graph inspect https://ds.canonical.com/button" },
36
+ { cmd: `${BIN_NAME} graph inspect ds:button` },
37
+ { cmd: `${BIN_NAME} graph inspect https://ds.canonical.com/button` },
37
38
  ],
38
39
  capability: {
39
40
  needsStore: true,
@@ -9,6 +9,7 @@
9
9
  * query surfaces as INVALID_INPUT with a recovery pointer to `ontology list`.
10
10
  */
11
11
 
12
+ import { BIN_NAME } from "../../constants.js";
12
13
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
13
14
  import { asVerb } from "../../kernel/spec/asVerb.js";
14
15
  import type { VerbSpec } from "../../kernel/spec/types.js";
@@ -35,11 +36,11 @@ const queryVerb: VerbSpec<Record<string, unknown>, QueryResult> = {
35
36
  output: { formatters: queryFormatters },
36
37
  examples: [
37
38
  {
38
- cmd: 'pragma graph query "SELECT ?s WHERE { ?s a ds:Component }"',
39
+ cmd: `${BIN_NAME} graph query "SELECT ?s WHERE { ?s a ds:Component }"`,
39
40
  note: "list every component subject",
40
41
  },
41
42
  {
42
- cmd: 'pragma graph query "ASK { ds:button a ds:Component }" --format json',
43
+ cmd: `${BIN_NAME} graph query "ASK { ds:button a ds:Component }" --format json`,
43
44
  },
44
45
  ],
45
46
  capability: {
@@ -11,6 +11,7 @@
11
11
 
12
12
  import type { QueryResult } from "@canonical/ke";
13
13
  import { PragmaError } from "../../kernel/error/PragmaError.js";
14
+ import { cliRecovery } from "../../kernel/error/recovery.js";
14
15
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
15
16
 
16
17
  /**
@@ -39,11 +40,13 @@ export async function runQuery(
39
40
  message: detail
40
41
  ? `Invalid SPARQL query: ${detail}`
41
42
  : "Invalid SPARQL query.",
42
- recovery: {
43
- message: "Check your SPARQL syntax and the loaded namespaces.",
44
- cli: "pragma ontology list",
45
- mcp: { tool: "ontology_list" },
46
- },
43
+ recovery: cliRecovery(
44
+ "ontology list",
45
+ "Check your SPARQL syntax and the loaded namespaces.",
46
+ {
47
+ tool: "ontology_list",
48
+ },
49
+ ),
47
50
  });
48
51
  }
49
52
  }
@@ -1,46 +1,42 @@
1
1
  /**
2
- * The capability registry: the literal array of every capability module the
3
- * projectors consume (D8 — test-only fixtures are never listed here).
2
+ * The capability registry: every capability module the projectors consume
3
+ * (D8 — test-only fixtures are never listed here).
4
4
  *
5
- * The order is the authoring order; projectors sort where they need to. PR1
6
- * ships `info` + `config show` as real verbs and the hidden `meta` verbs
7
- * (`__complete`, `mcp`). Importing this barrel pulls only spec + formatter
8
- * modules — every run body is behind a lazy dynamic import — so the
5
+ * Two sources, in this order: the literal array of AUTHORED modules (in
6
+ * authoring order; projectors sort where they need to), then every story
7
+ * `pragma.conf.ts` declares that no authored module already claims. Since
8
+ * L-OPEN-9 no DATA noun is authored — the authored array is the kernel's own
9
+ * nouns and nothing else, and every design-system noun appears only in the
10
+ * config. No noun literal is written here: the second half is derived.
11
+ *
12
+ * Importing this barrel pulls only spec + formatter modules — every run body is
13
+ * behind a lazy dynamic import, and `distribution.ts` is zod-free — so the
9
14
  * `--help`/`__complete` fast paths stay free of the config reader and zod.
10
15
  */
11
16
 
12
17
  import type { CapabilityModule } from "../kernel/spec/types.js";
13
- import { blockModule } from "./block/index.js";
14
18
  import { capabilitiesModule } from "./capabilities/index.js";
15
19
  import { colophonModule } from "./colophon/index.js";
16
20
  import { configModule } from "./config/index.js";
17
21
  import { createModule } from "./create/index.js";
22
+ import { storyModules } from "./distribution.js";
18
23
  import { doctorModule } from "./doctor/index.js";
19
24
  import { graphModule } from "./graph/index.js";
20
25
  import { infoModule } from "./info/index.js";
21
26
  import { metaModule } from "./meta/index.js";
22
- import { modifierModule } from "./modifier/index.js";
23
27
  import { ontologyModule } from "./ontology/index.js";
24
28
  import { promptModule } from "./prompt/index.js";
25
29
  import { setupModule } from "./setup/index.js";
26
30
  import { skillModule } from "./skill/index.js";
27
31
  import { sourcesModule } from "./sources/index.js";
28
- import { standardModule } from "./standard/index.js";
29
- import { tierModule } from "./tier/index.js";
30
- import { tokenModule } from "./token/index.js";
31
32
  import { upgradeModule } from "./upgrade/index.js";
32
33
 
33
- /** Every capability module, in authoring order. */
34
- export const capabilities: readonly CapabilityModule[] = [
34
+ /** The modules with hand-written code, in authoring order. */
35
+ const authored: readonly CapabilityModule[] = [
35
36
  infoModule,
36
37
  configModule,
37
38
  createModule,
38
39
  sourcesModule,
39
- standardModule,
40
- tierModule,
41
- modifierModule,
42
- tokenModule,
43
- blockModule,
44
40
  ontologyModule,
45
41
  skillModule,
46
42
  graphModule,
@@ -52,3 +48,11 @@ export const capabilities: readonly CapabilityModule[] = [
52
48
  colophonModule,
53
49
  metaModule,
54
50
  ];
51
+
52
+ const claimed = new Set(authored.map((module) => module.name));
53
+
54
+ /** Every capability module: the authored ones, then the unclaimed declared stories. */
55
+ export const capabilities: readonly CapabilityModule[] = [
56
+ ...authored,
57
+ ...[...storyModules.values()].filter((module) => !claimed.has(module.name)),
58
+ ];
@@ -6,10 +6,12 @@
6
6
  * the npm registry — this deliberately REVERSES PR1's networkless-D11 stance for
7
7
  * `info` (the reversal IS the enrichment), unconditional because the covenant
8
8
  * `info` verb carries no flag, 3s timeout, silent-fail — and (b) a storeless
9
- * entity total from the pack index (`readPackIndex`, the same reader `sources
10
- * status` uses). Neither enrichment boots the store, so the storeless-guarantee
11
- * spy still sees `store.booted === false`. Kept out of the spec module so
12
- * building the command tree never pulls it onto the `--help` fast path.
9
+ * entity total, read from the index of whichever pack the boot decision names
10
+ * (`resolveSources` → `readPackIndex`, the same predicate `sources status` and
11
+ * `doctor` switch on) so `info` never counts a graph this project's reads
12
+ * refuse. Neither enrichment boots the store, so the storeless-guarantee spy
13
+ * still sees `store.booted === false`. Kept out of the spec module so building
14
+ * the command tree never pulls it onto the `--help` fast path.
13
15
  */
14
16
 
15
17
  import {
@@ -17,6 +19,7 @@ import {
17
19
  readPackIndex,
18
20
  } from "../../kernel/completion/entitySource.js";
19
21
  import { readConfig } from "../../kernel/config/readConfig.js";
22
+ import { resolveSources } from "../../kernel/runtime/resolveSources.js";
20
23
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
21
24
  import {
22
25
  detectInstallSource,
@@ -50,8 +53,9 @@ export async function collectInfo(runtime: PragmaRuntime): Promise<InfoData> {
50
53
  : undefined;
51
54
  const updateSkipped = registry === undefined;
52
55
 
53
- // Entity total: storeless — the same pack-index reader `sources status` uses.
54
- const index = readPackIndex(runtime.cwd);
56
+ // Entity total: storeless, over the pack the boot decision names — so a
57
+ // configured-but-unbuilt project reports no total rather than the snapshot's.
58
+ const index = readPackIndex(resolveSources(layers, runtime.cwd));
55
59
  const entities = index ? entityTotal(index) : undefined;
56
60
 
57
61
  return {
@@ -2,6 +2,7 @@
2
2
  * Formatters for `pragma info` — plain, llm, json (no ink).
3
3
  */
4
4
 
5
+ import { BIN_NAME } from "../../constants.js";
5
6
  import type { ConfigOrigin } from "../../kernel/config/types.js";
6
7
  import type { Formatters } from "../../kernel/spec/types.js";
7
8
  import type { InfoData } from "./types.js";
@@ -15,7 +16,7 @@ export const infoFormatters: Formatters<InfoData> = {
15
16
  plain(data) {
16
17
  const { config } = data;
17
18
  const lines = [
18
- `pragma v${data.version}`,
19
+ `${BIN_NAME} v${data.version}`,
19
20
  ` Installed via: ${data.installSource}`,
20
21
  ` tier: ${config.tier ?? "(none)"}${originMarker(config.origins.tier)}`,
21
22
  ` channel: ${config.channel}${originMarker(config.origins.channel)}`,
@@ -45,7 +46,7 @@ export const infoFormatters: Formatters<InfoData> = {
45
46
  llm(data) {
46
47
  const { config } = data;
47
48
  const lines = [
48
- `# pragma v${data.version}`,
49
+ `# ${BIN_NAME} v${data.version}`,
49
50
  `- Installed via: ${data.installSource}`,
50
51
  `- Tier: ${config.tier ?? "(none)"}${originMarker(config.origins.tier)}`,
51
52
  `- Channel: ${config.channel}${originMarker(config.origins.channel)}`,
@@ -6,6 +6,7 @@
6
6
  * the config reader or zod onto the fast path.
7
7
  */
8
8
 
9
+ import { BIN_NAME } from "../../constants.js";
9
10
  import { asVerb } from "../../kernel/spec/asVerb.js";
10
11
  import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
11
12
  import { infoFormatters } from "./info.render.js";
@@ -18,9 +19,9 @@ const infoVerb: VerbSpec<Record<string, unknown>, InfoData> = {
18
19
  params: [],
19
20
  output: { formatters: infoFormatters },
20
21
  examples: [
21
- { cmd: "pragma info", note: "human-readable summary" },
22
+ { cmd: `${BIN_NAME} info`, note: "human-readable summary" },
22
23
  {
23
- cmd: "pragma info --format json",
24
+ cmd: `${BIN_NAME} info --format json`,
24
25
  note: "the full {ok,data,meta} envelope",
25
26
  },
26
27
  ],
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
2
+ import { dirname, relative, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { describe, expect, it } from "vitest";
5
5
 
@@ -45,10 +45,63 @@ describe("lazy dispatch — module-graph probe (PROTECTED)", () => {
45
45
  expect(has(graph, "info/info.render.ts")).toBe(true);
46
46
  expect(has(graph, "config/show.render.ts")).toBe(true);
47
47
 
48
- // ...but run bodies and the config reader are only dynamic-imported.
48
+ // ...but run bodies and the config LAYER stay behind the dynamic boundary:
49
+ // no reader, no defaults layer, no zod schema. The distribution config
50
+ // itself IS on the graph — `constants.ts` projects the program's identity
51
+ // from it, `render/prefixes.ts` its domain namespaces and
52
+ // `kernel/vocabulary.ts` its domain terms — which is safe only because it
53
+ // is inert data (pinned below).
49
54
  expect(has(graph, "info/collectInfo.ts")).toBe(false);
50
55
  expect(has(graph, "config/collectConfigShow.ts")).toBe(false);
51
56
  expect(has(graph, "kernel/config/readConfig.ts")).toBe(false);
57
+ expect(has(graph, "kernel/config/defaults.ts")).toBe(false);
58
+ expect(has(graph, "kernel/config/schema.ts")).toBe(false);
59
+ expect(has(graph, "pragma.conf.ts")).toBe(true);
60
+ });
61
+
62
+ it("the distribution config is inert data — it imports nothing that runs", () => {
63
+ const conf = resolve(here, "../../pragma.conf.ts");
64
+ const pkgRoot = resolve(here, "../..");
65
+ // The graph walker follows `from "…"` textually and cannot tell an
66
+ // `import type` from a value import, so the type-only `config/types.ts`
67
+ // and `packs/types.ts` show up here even though nothing of either survives
68
+ // compilation. Assert that NO file on the graph has a value import — not
69
+ // just the conf. That is the claim in the title, and it is what keeps
70
+ // `--help`/`__complete`/`--version` free of module-init work when another
71
+ // lane or a fork edits any of these three (`packs/types.ts` in particular
72
+ // is the grammar that type-checks the declared stories, and its own
73
+ // docblock promises it stays zod-free BECAUSE it lands here).
74
+ const graph = [...staticImportGraph(conf)];
75
+ for (const file of graph) {
76
+ expect(readFileSync(file, "utf-8"), file).not.toMatch(
77
+ /^import (?!type\b)/m,
78
+ );
79
+ }
80
+ // The enumeration is exact, not a subset: a type import from a module that
81
+ // itself imports something grows this list and fails here.
82
+ expect(graph.map((f) => relative(pkgRoot, f)).sort()).toEqual([
83
+ "pragma.conf.ts",
84
+ "src/kernel/config/types.ts",
85
+ "src/kernel/packs/types.ts",
86
+ ]);
87
+ });
88
+
89
+ it("the dispatch path never reaches the embedded n-quads module (PROTECTED)", () => {
90
+ // `activeStories` reads the pack's carried stories from their OWN generated
91
+ // module rather than through `graphpack/embedded.ts`, which statically
92
+ // imports the ~1.9 MB `pack.generated.ts`. Restoring that edge costs a
93
+ // measured +23 ms on EVERY dispatched command, and the whole test suite
94
+ // passes with it restored — the split is prose in three docblocks and
95
+ // nothing else. This is the constraint those docblocks describe.
96
+ for (const entry of [
97
+ "../kernel/packs/collect.ts",
98
+ "../kernel/runtime/graphpack/stories.ts",
99
+ "../kernel/runtime/resolveSources.ts",
100
+ ]) {
101
+ const graph = staticImportGraph(resolve(here, entry));
102
+ expect(has(graph, "embedded/pack.generated.ts"), entry).toBe(false);
103
+ expect(has(graph, "graphpack/embedded.ts"), entry).toBe(false);
104
+ }
52
105
  });
53
106
 
54
107
  it("the help path (buildProgram) imports no zod schema module", () => {
@@ -86,4 +139,42 @@ describe("lazy dispatch — module-graph probe (PROTECTED)", () => {
86
139
  }
87
140
  }
88
141
  });
142
+
143
+ it("NO module on that graph statically imports zod (PROTECTED)", () => {
144
+ // This used to pin an exact set of one — `graphpack/types.ts`, reached via
145
+ // `resolveSources` → `packIsComplete` → `readManifest` → `manifestSchema
146
+ // .parse` — as the one genuine zod value import on the storeless graph,
147
+ // measured at ~3–4 ms of a ~30 ms budget. Its own comment said an exact set
148
+ // was what would make the day it got fixed impossible to miss. That day is
149
+ // this commit: the schemas moved to `graphpack/schemas.ts`, imported only by
150
+ // the two readers already off the fast path, and `readManifest` validates
151
+ // structurally instead.
152
+ //
153
+ // The set is now EMPTY, which is strictly stronger than the pin it replaces:
154
+ // it no longer tolerates one named module, so a brand-new module value-
155
+ // importing zod fails here too — the gap the previous form left open.
156
+ const graph = staticImportGraph(resolve(here, "index.ts"));
157
+ const pkgRoot = resolve(here, "..", "..");
158
+ const zodImporters = [...graph]
159
+ .filter((file) => /from\s*["']zod["']/.test(readFileSync(file, "utf-8")))
160
+ .map((file) => relative(pkgRoot, file))
161
+ .sort();
162
+ expect(zodImporters).toEqual([]);
163
+ });
164
+
165
+ it("the help path (buildProgram) imports zod from nowhere (PROTECTED)", () => {
166
+ // The named-module check above (`kernel/config/schema.ts`,
167
+ // `kernel/spec/validate.ts`) could only catch the two modules it names — a
168
+ // third zod importer reaching the help path would have passed it silently.
169
+ // Generalized (ruling R4) to the property those names were standing in for.
170
+ const graph = staticImportGraph(
171
+ resolve(here, "../kernel/project/cli/buildProgram.ts"),
172
+ );
173
+ const pkgRoot = resolve(here, "..", "..");
174
+ const zodImporters = [...graph]
175
+ .filter((file) => /from\s*["']zod["']/.test(readFileSync(file, "utf-8")))
176
+ .map((file) => relative(pkgRoot, file))
177
+ .sort();
178
+ expect(zodImporters).toEqual([]);
179
+ });
89
180
  });
@@ -3,10 +3,17 @@
3
3
  */
4
4
 
5
5
  import type { CapabilityModule } from "../../kernel/spec/types.js";
6
- import { ontologyListVerb, ontologyShowVerb } from "./verbs.js";
6
+ import {
7
+ ontologyListVerb,
8
+ ontologyLookupVerb,
9
+ ontologyShowVerb,
10
+ } from "./verbs.js";
7
11
 
8
- /** The `ontology` capability module (`list`, `show`). */
12
+ /**
13
+ * The `ontology` capability module (`list`, `lookup`, and the deprecated `show`
14
+ * alias — AV-228 B1).
15
+ */
9
16
  export const ontologyModule: CapabilityModule = {
10
17
  name: "ontology",
11
- verbs: [ontologyListVerb, ontologyShowVerb],
18
+ verbs: [ontologyListVerb, ontologyLookupVerb, ontologyShowVerb],
12
19
  };
@@ -15,13 +15,23 @@ import {
15
15
  BLOCK_PREFIXES,
16
16
  BLOCK_TTL,
17
17
  } from "../../testing/fixtures/blockGraph.js";
18
+ import { CANONICAL_TTL } from "../../testing/fixtures/graph/canonical.js";
19
+ import {
20
+ bootFixtureRuntime,
21
+ type FixtureGraph,
22
+ } from "../../testing/helpers/fixtureGraph.js";
18
23
  import { buildFixtureRuntime } from "../../testing/helpers/packRuntime.js";
24
+ import { projectMcp } from "../../testing/helpers/projectMcp.js";
25
+ import { capabilities } from "../index.js";
19
26
  import { ontologyModule } from "./index.js";
20
27
  import type { OntologyShowData, OntologySummary } from "./queries.js";
21
28
 
22
29
  const listVerb = ontologyModule.verbs.find(
23
30
  (v) => verbKey(v.path) === "ontology list",
24
31
  ) as VerbSpec;
32
+ const lookupVerb = ontologyModule.verbs.find(
33
+ (v) => verbKey(v.path) === "ontology lookup",
34
+ ) as VerbSpec;
25
35
  const showVerb = ontologyModule.verbs.find(
26
36
  (v) => verbKey(v.path) === "ontology show",
27
37
  ) as VerbSpec;
@@ -48,9 +58,12 @@ describe("ontology list", () => {
48
58
  });
49
59
  });
50
60
 
51
- describe("ontology show", () => {
61
+ describe("ontology lookup (primary by-name read)", () => {
52
62
  it("renders the class hierarchy with per-class instance counts from the index", async () => {
53
- const data = (await showVerb.run({ prefix: "ds" }, rt)) as OntologyShowData;
63
+ const data = (await lookupVerb.run(
64
+ { prefix: "ds" },
65
+ rt,
66
+ )) as OntologyShowData;
54
67
  const component = data.classes.find((c) => c.uri.endsWith("Component"));
55
68
  expect(component?.superclass).toBe("https://ds.canonical.com/UIBlock");
56
69
  // Two Component individuals (Button, Modal) in the fixture.
@@ -58,8 +71,34 @@ describe("ontology show", () => {
58
71
  expect(data.properties).toEqual([]);
59
72
  });
60
73
 
74
+ it("--detail detailed folds onto disclosure and includes properties (B5)", async () => {
75
+ // Ontology honours the canonical `--detail` instead of only its bespoke
76
+ // `--properties`: standard/detailed add the properties section.
77
+ const detailed = {
78
+ ...rt,
79
+ globalFlags: { ...rt.globalFlags, detail: "detailed" as const },
80
+ };
81
+ const data = (await lookupVerb.run(
82
+ { prefix: "ds" },
83
+ detailed,
84
+ )) as OntologyShowData;
85
+ expect(data.properties.length).toBeGreaterThan(0);
86
+ });
87
+
88
+ it("--detail summary keeps classes only (no properties)", async () => {
89
+ const summary = {
90
+ ...rt,
91
+ globalFlags: { ...rt.globalFlags, detail: "summary" as const },
92
+ };
93
+ const data = (await lookupVerb.run(
94
+ { prefix: "ds" },
95
+ summary,
96
+ )) as OntologyShowData;
97
+ expect(data.properties).toEqual([]);
98
+ });
99
+
61
100
  it("--class focuses on one class and its properties", async () => {
62
- const data = (await showVerb.run(
101
+ const data = (await lookupVerb.run(
63
102
  { prefix: "ds", class: "BlockProperty" },
64
103
  rt,
65
104
  )) as OntologyShowData;
@@ -77,18 +116,131 @@ describe("ontology show", () => {
77
116
  });
78
117
 
79
118
  it("--full-uris flag rides on the payload for the renderer", async () => {
80
- const data = (await showVerb.run(
119
+ const data = (await lookupVerb.run(
81
120
  { prefix: "ds", fullUris: true },
82
121
  rt,
83
122
  )) as OntologyShowData;
84
123
  expect(data.fullUris).toBe(true);
85
- const llm = showVerb.output.formatters.llm(data);
124
+ const llm = lookupVerb.output.formatters.llm(data);
86
125
  expect(llm).toContain("https://ds.canonical.com/Component");
87
126
  });
88
127
 
89
128
  it("rejects an unknown prefix with recovery", async () => {
129
+ await expect(lookupVerb.run({ prefix: "nope" }, rt)).rejects.toThrow(
130
+ /prefix/i,
131
+ );
132
+ });
133
+ });
134
+
135
+ describe("ontology show — deprecated alias of lookup (AV-228 B1)", () => {
136
+ it("is marked deprecated in its summary and steers callers to `ontology lookup`", () => {
137
+ expect(showVerb.summary.toLowerCase()).toContain("deprecated");
138
+ expect(showVerb.summary).toContain("ontology lookup");
139
+ });
140
+
141
+ it("shares the lookup handler — same payload for the same input", async () => {
142
+ const viaLookup = (await lookupVerb.run(
143
+ { prefix: "ds", properties: true },
144
+ rt,
145
+ )) as OntologyShowData;
146
+ const viaShow = (await showVerb.run(
147
+ { prefix: "ds", properties: true },
148
+ rt,
149
+ )) as OntologyShowData;
150
+ expect(viaShow).toEqual(viaLookup);
151
+ });
152
+
153
+ it("still rejects an unknown prefix (the alias is a full, callable verb)", async () => {
90
154
  await expect(showVerb.run({ prefix: "nope" }, rt)).rejects.toThrow(
91
155
  /prefix/i,
92
156
  );
93
157
  });
94
158
  });
159
+
160
+ describe("ontology_show honours detail over MCP (B5)", () => {
161
+ // The CLI B5 cases above seed `globalFlags.detail` and call `showVerb.run`
162
+ // directly. Over MCP there are NO global flags — the ONLY detail channel is
163
+ // the per-tool `detail` param the projector injects from the VerbSpec
164
+ // disclosure. Under an AMBIENT config `detail: standard`, an `ontology_show`
165
+ // with no declared disclosure would force the properties section with no
166
+ // per-call escape (the AV-228 MCP-opt-out asymmetry `block`/`standard` lack);
167
+ // declaring disclosure lights the param up, so an agent can ask for
168
+ // classes-only per call and can equally override upward.
169
+ let fixture: FixtureGraph;
170
+ let mcp: Awaited<ReturnType<typeof projectMcp>>;
171
+
172
+ beforeAll(async () => {
173
+ // Ambient config detail=standard → origins.detail === "project", so it
174
+ // outranks the verb's `summary` default inside resolvePackDetail.
175
+ fixture = await bootFixtureRuntime({
176
+ ttl: CANONICAL_TTL,
177
+ config: { detail: "standard" },
178
+ });
179
+ mcp = await projectMcp(capabilities, fixture.cwd);
180
+ });
181
+
182
+ afterAll(async () => {
183
+ await mcp.cleanup();
184
+ await fixture.dispose();
185
+ });
186
+
187
+ it("injects a detail param onto the ontology_show tool (symmetric with block/standard)", async () => {
188
+ const tools = await mcp.listTools();
189
+ const show = tools.find((t) => t.name === "ontology_show");
190
+ const schema = show?.inputSchema as {
191
+ properties?: Record<string, unknown>;
192
+ };
193
+ expect(schema.properties?.detail).toBeDefined();
194
+ });
195
+
196
+ it("exposes the primary ontology_lookup tool with the same injected detail param (AV-228 B1)", async () => {
197
+ const tools = await mcp.listTools();
198
+ const lookup = tools.find((t) => t.name === "ontology_lookup");
199
+ expect(lookup).toBeDefined();
200
+ const schema = lookup?.inputSchema as {
201
+ properties?: Record<string, unknown>;
202
+ };
203
+ expect(schema.properties?.detail).toBeDefined();
204
+ });
205
+
206
+ it("ontology_lookup honours a per-call detail=summary despite the ambient standard", async () => {
207
+ const result = await mcp.callTool("ontology_lookup", {
208
+ prefix: "ds",
209
+ detail: "summary",
210
+ });
211
+ expect(result.ok).toBe(true);
212
+ const data = result.data as { classes: unknown[]; properties: unknown[] };
213
+ expect(data.classes.length).toBeGreaterThan(0);
214
+ expect(data.properties).toEqual([]);
215
+ });
216
+
217
+ it("honours the ambient detail=standard config when no per-call detail is set", async () => {
218
+ // Proves the ambient level really is standard, so the summary case below is
219
+ // a genuine per-call override rather than merely the spec default.
220
+ const result = await mcp.callTool("ontology_show", { prefix: "ds" });
221
+ expect(result.ok).toBe(true);
222
+ const data = result.data as { properties: unknown[] };
223
+ expect(data.properties.length).toBeGreaterThan(0);
224
+ });
225
+
226
+ it("a per-call detail=summary returns classes-only despite the ambient standard", async () => {
227
+ const result = await mcp.callTool("ontology_show", {
228
+ prefix: "ds",
229
+ detail: "summary",
230
+ });
231
+ expect(result.ok).toBe(true);
232
+ const data = result.data as { classes: unknown[]; properties: unknown[] };
233
+ expect(data.classes.length).toBeGreaterThan(0);
234
+ expect(data.properties).toEqual([]);
235
+ });
236
+
237
+ it("an explicit detail=detailed overrides upward to include properties", async () => {
238
+ const result = await mcp.callTool("ontology_show", {
239
+ prefix: "ds",
240
+ detail: "detailed",
241
+ });
242
+ expect(result.ok).toBe(true);
243
+ const data = result.data as { properties: unknown[] };
244
+ expect(data.properties.length).toBeGreaterThan(0);
245
+ });
246
+ });