@canonical/pragma-cli 0.33.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 (250) hide show
  1. package/README.md +7 -7
  2. package/dist/pragma +0 -0
  3. package/package.json +18 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +13 -4
  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.verb.ts +31 -13
  9. package/src/capabilities/capabilities/catalog.ts +18 -10
  10. package/src/capabilities/capabilities/hints.ts +13 -19
  11. package/src/capabilities/colophon/collectColophon.ts +34 -19
  12. package/src/capabilities/colophon/colophon.verb.ts +8 -4
  13. package/src/capabilities/colophon/types.ts +16 -7
  14. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  15. package/src/capabilities/config/collectConfigShow.ts +41 -4
  16. package/src/capabilities/config/fields.ts +0 -27
  17. package/src/capabilities/config/set.verb.ts +7 -3
  18. package/src/capabilities/config/show.render.test.ts +77 -5
  19. package/src/capabilities/config/show.render.ts +7 -12
  20. package/src/capabilities/config/show.verb.ts +4 -3
  21. package/src/capabilities/config/types.ts +8 -0
  22. package/src/capabilities/create/byteEquality.test.ts +78 -100
  23. package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
  24. package/src/capabilities/create/constants.ts +75 -0
  25. package/src/capabilities/create/create.mcp.test.ts +2 -2
  26. package/src/capabilities/create/create.test.ts +162 -15
  27. package/src/capabilities/create/create.verb.ts +140 -69
  28. package/src/capabilities/create/index.ts +2 -10
  29. package/src/capabilities/create/pickGenerator.ts +34 -28
  30. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  31. package/src/capabilities/create/types.ts +10 -6
  32. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  33. package/src/capabilities/distribution.test.ts +120 -0
  34. package/src/capabilities/distribution.ts +59 -0
  35. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  36. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  37. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +7 -6
  38. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  39. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  40. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  41. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  42. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  43. package/src/capabilities/doctor/doctor.render.test.ts +6 -6
  44. package/src/capabilities/doctor/doctor.render.ts +2 -1
  45. package/src/capabilities/doctor/doctor.test.ts +37 -8
  46. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  47. package/src/capabilities/doctor/runChecks.ts +5 -5
  48. package/src/capabilities/doctor/types.ts +1 -1
  49. package/src/capabilities/emptyRecovery.test.ts +32 -15
  50. package/src/capabilities/graph/inspect.verb.ts +3 -2
  51. package/src/capabilities/graph/query.verb.ts +3 -2
  52. package/src/capabilities/graph/runQuery.ts +8 -5
  53. package/src/capabilities/index.ts +22 -18
  54. package/src/capabilities/info/collectInfo.ts +10 -6
  55. package/src/capabilities/info/info.render.ts +3 -2
  56. package/src/capabilities/info/info.verb.ts +3 -2
  57. package/src/capabilities/lazy.test.ts +93 -2
  58. package/src/capabilities/ontology/verbs.ts +21 -21
  59. package/src/capabilities/prompt/prompt.test.ts +12 -6
  60. package/src/capabilities/prompt/verbs.ts +24 -11
  61. package/src/capabilities/resources/provider.ts +30 -11
  62. package/src/capabilities/resources/resources.test.ts +85 -29
  63. package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
  64. package/src/capabilities/setup/operations/setupMcp.ts +2 -2
  65. package/src/capabilities/setup/operations/setupSkills.ts +3 -3
  66. package/src/capabilities/setup/setup.verb.ts +12 -5
  67. package/src/capabilities/setup/shell.ts +29 -3
  68. package/src/capabilities/skill/discover.ts +11 -5
  69. package/src/capabilities/skill/render.ts +4 -5
  70. package/src/capabilities/skill/verbs.ts +7 -7
  71. package/src/capabilities/sources/collectStatus.ts +55 -50
  72. package/src/capabilities/sources/index.ts +1 -1
  73. package/src/capabilities/sources/installSkills.ts +2 -2
  74. package/src/capabilities/sources/runUpdate.ts +113 -122
  75. package/src/capabilities/sources/sources.test.ts +111 -117
  76. package/src/capabilities/sources/status.render.test.ts +53 -24
  77. package/src/capabilities/sources/status.render.ts +24 -29
  78. package/src/capabilities/sources/status.verb.ts +15 -8
  79. package/src/capabilities/sources/types.ts +18 -25
  80. package/src/capabilities/sources/update.render.ts +9 -2
  81. package/src/capabilities/sources/update.verb.ts +12 -23
  82. package/src/capabilities/surface.test.ts +33 -10
  83. package/src/capabilities/tier.parity.test.ts +144 -0
  84. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  85. package/src/constants.test.ts +8 -6
  86. package/src/constants.ts +47 -21
  87. package/src/identity.test.ts +333 -0
  88. package/src/index.ts +2 -2
  89. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
  90. package/src/kernel/completion/emitScripts.test.ts +1 -1
  91. package/src/kernel/completion/emitScripts.ts +4 -2
  92. package/src/kernel/completion/entitySource.test.ts +185 -41
  93. package/src/kernel/completion/entitySource.ts +117 -107
  94. package/src/kernel/completion/model.test.ts +1 -1
  95. package/src/kernel/completion/model.ts +11 -0
  96. package/src/kernel/completion/safety.test.ts +82 -36
  97. package/src/kernel/completion/shellDrive.test.ts +592 -0
  98. package/src/kernel/completion/templates/fish.ts +28 -6
  99. package/src/kernel/completion/types.ts +3 -3
  100. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  101. package/src/kernel/config/defaults.test.ts +85 -0
  102. package/src/kernel/config/defaults.ts +12 -23
  103. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  104. package/src/kernel/config/findProjectConfig.ts +4 -3
  105. package/src/kernel/config/firstRun.ts +12 -5
  106. package/src/kernel/config/globalConfig.ts +2 -2
  107. package/src/kernel/config/index.ts +4 -5
  108. package/src/kernel/config/paths.ts +17 -17
  109. package/src/kernel/config/readConfig.test.ts +242 -6
  110. package/src/kernel/config/readConfig.ts +8 -8
  111. package/src/kernel/config/schema.test.ts +50 -0
  112. package/src/kernel/config/schema.ts +97 -10
  113. package/src/kernel/config/types.ts +73 -19
  114. package/src/kernel/copy.test.ts +453 -0
  115. package/src/kernel/error/PragmaError.test.ts +1 -1
  116. package/src/kernel/error/constants.ts +3 -2
  117. package/src/kernel/error/index.ts +1 -1
  118. package/src/kernel/error/recovery.test.ts +22 -45
  119. package/src/kernel/error/recovery.ts +22 -27
  120. package/src/kernel/error/types.ts +5 -4
  121. package/src/kernel/packs/collect.test.ts +195 -11
  122. package/src/kernel/packs/collect.ts +213 -58
  123. package/src/kernel/packs/compile.test.ts +163 -7
  124. package/src/kernel/packs/compile.ts +15 -12
  125. package/src/kernel/packs/disclosure.test.ts +5 -2
  126. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  127. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  128. package/src/kernel/packs/iri.ts +5 -7
  129. package/src/kernel/packs/renderPack.ts +5 -3
  130. package/src/kernel/packs/resolveEntity.ts +21 -17
  131. package/src/kernel/packs/runBodies.ts +15 -14
  132. package/src/kernel/packs/schema.ts +96 -10
  133. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  134. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  135. package/src/kernel/packs/toolDescription.test.ts +10 -2
  136. package/src/kernel/packs/types.ts +39 -4
  137. package/src/kernel/packs/uniqueness.ts +4 -4
  138. package/src/kernel/project/cli/buildProgram.test.ts +9 -4
  139. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  140. package/src/kernel/project/cli/dispatch.ts +34 -12
  141. package/src/kernel/project/cli/helpFormat.ts +1 -1
  142. package/src/kernel/project/cli/rootHelp.ts +105 -105
  143. package/src/kernel/project/mcp/buildServer.ts +11 -6
  144. package/src/kernel/project/mcp/instructions.ts +11 -6
  145. package/src/kernel/project/mcp/parity.test.ts +39 -1
  146. package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
  147. package/src/kernel/project/mcp/prompts/provider.ts +22 -8
  148. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  149. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  150. package/src/kernel/project/mcp/serve.ts +11 -3
  151. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  152. package/src/kernel/render/disclosure.ts +4 -2
  153. package/src/kernel/render/prefixes.test.ts +36 -0
  154. package/src/kernel/render/prefixes.ts +40 -13
  155. package/src/kernel/render/renderers.test.ts +1 -1
  156. package/src/kernel/runtime/graphpack/build.ts +29 -3
  157. package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
  158. package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
  159. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
  160. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
  161. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  162. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  163. package/src/kernel/runtime/graphpack/graphpack.test.ts +205 -5
  164. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  165. package/src/kernel/runtime/graphpack/index.ts +1 -1
  166. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  167. package/src/kernel/runtime/graphpack/read.ts +4 -9
  168. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  169. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  170. package/src/kernel/runtime/graphpack/types.ts +61 -48
  171. package/src/kernel/runtime/paths.ts +64 -15
  172. package/src/kernel/runtime/probe.test.ts +8 -4
  173. package/src/kernel/runtime/readEntity.ts +6 -5
  174. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  175. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  176. package/src/kernel/runtime/refs/resolve.ts +78 -62
  177. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  178. package/src/kernel/runtime/resolveSources.ts +36 -25
  179. package/src/kernel/runtime/storeReadiness.ts +9 -8
  180. package/src/kernel/runtime/storeless.test.ts +1 -1
  181. package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
  182. package/src/kernel/spec/emitReference.test.ts +3 -3
  183. package/src/kernel/spec/emitReference.ts +144 -11
  184. package/src/kernel/spec/emitSurface.ts +9 -5
  185. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  186. package/src/kernel/spec/surfaceConformance.ts +8 -0
  187. package/src/kernel/spec/types.ts +29 -9
  188. package/src/kernel/spec/validate.ts +2 -1
  189. package/src/kernel/vocabulary.test.ts +164 -0
  190. package/src/kernel/vocabulary.ts +146 -0
  191. package/src/testing/PARITY_GAPS.ts +8 -2
  192. package/src/testing/behavioral/README.md +3 -3
  193. package/src/testing/behavioral/completion.test.ts +19 -6
  194. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  195. package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
  196. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  197. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  198. package/src/testing/behavioral/journeys.cli.test.ts +116 -75
  199. package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
  200. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  201. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
  202. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  203. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  204. package/src/testing/behavioral/mcpSurface.test.ts +9 -4
  205. package/src/testing/behavioral/staleCommands.test.ts +10 -0
  206. package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
  207. package/src/testing/eval/cases/assembly.ts +13 -6
  208. package/src/testing/eval/cases/effectful.ts +25 -13
  209. package/src/testing/eval/cases/readNouns.ts +22 -20
  210. package/src/testing/eval/cases/stable.ts +5 -1
  211. package/src/testing/eval/eval.test.ts +7 -1
  212. package/src/testing/fixtures/fixtureCapability.ts +45 -1
  213. package/src/testing/fixtures/graph/canonical.ts +6 -3
  214. package/src/testing/fixtures/graph/defaultPack.ts +6 -5
  215. package/src/testing/fixtures/graph/owlExport.ts +7 -5
  216. package/src/testing/helpers/fixtureGraph.ts +33 -16
  217. package/src/testing/helpers/packRuntime.ts +2 -3
  218. package/src/testing/helpers/projectCli.ts +2 -1
  219. package/src/testing/helpers/projectMcp.ts +6 -0
  220. package/src/testing/perf/budgets.test.ts +4 -0
  221. package/src/testing/perf/budgets.ts +22 -7
  222. package/src/testing/perf/globalSetup.ts +121 -6
  223. package/src/types.ts +2 -2
  224. package/src/capabilities/block/blockList.test.ts +0 -79
  225. package/src/capabilities/block/blockList.verb.ts +0 -163
  226. package/src/capabilities/block/index.ts +0 -25
  227. package/src/capabilities/block/pack.ts +0 -166
  228. package/src/capabilities/block/parity.test.ts +0 -187
  229. package/src/capabilities/block/tierChain.ts +0 -78
  230. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  231. package/src/capabilities/modifier/index.ts +0 -15
  232. package/src/capabilities/modifier/pack.ts +0 -64
  233. package/src/capabilities/standard/index.ts +0 -18
  234. package/src/capabilities/standard/pack.ts +0 -128
  235. package/src/capabilities/tier/index.ts +0 -19
  236. package/src/capabilities/tier/lookup.render.test.ts +0 -72
  237. package/src/capabilities/tier/lookup.render.ts +0 -59
  238. package/src/capabilities/tier/lookup.test.ts +0 -83
  239. package/src/capabilities/tier/lookup.verb.ts +0 -44
  240. package/src/capabilities/tier/pack.ts +0 -28
  241. package/src/capabilities/tier/runLookup.ts +0 -87
  242. package/src/capabilities/token/addConfig.render.ts +0 -27
  243. package/src/capabilities/token/addConfig.test.ts +0 -62
  244. package/src/capabilities/token/addConfig.verb.ts +0 -40
  245. package/src/capabilities/token/index.ts +0 -19
  246. package/src/capabilities/token/pack.ts +0 -58
  247. package/src/capabilities/token/runAddConfig.ts +0 -95
  248. package/src/kernel/completion/bashDrive.test.ts +0 -73
  249. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  250. package/src/kernel/runtime/lock.ts +0 -90
@@ -14,7 +14,7 @@ export type CheckStatus = "pass" | "fail" | "skip";
14
14
 
15
15
  /**
16
16
  * A structured sub-item under a check — e.g. one resolved package under
17
- * `package refs`, or one unresolvable server under `MCP commands`. Lets the
17
+ * `pack refs`, or one unresolvable server under `MCP commands`. Lets the
18
18
  * formatter render an indented, aligned breakdown instead of cramming
19
19
  * everything into a single `detail` string.
20
20
  */
@@ -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
  });
@@ -22,7 +22,9 @@
22
22
  * any level), so the covenant is untouched.
23
23
  */
24
24
 
25
+ import { BIN_NAME } from "../../constants.js";
25
26
  import { PragmaError } from "../../kernel/error/PragmaError.js";
27
+ import { cliRecovery } from "../../kernel/error/recovery.js";
26
28
  import { resolvePackDetail } from "../../kernel/packs/disclosure.js";
27
29
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
28
30
  import { asVerb } from "../../kernel/spec/asVerb.js";
@@ -51,22 +53,22 @@ function resolvePrefix(
51
53
  if (entry) return { prefix: entry[0], namespace: entry[1] };
52
54
  throw PragmaError.invalidInput("namespace", input, {
53
55
  validOptions: Object.values(prefixes),
54
- recovery: {
55
- message: "List loaded ontology namespaces.",
56
- cli: "pragma ontology list",
57
- mcp: { tool: "ontology_list" },
58
- },
56
+ recovery: cliRecovery(
57
+ "ontology list",
58
+ "List loaded ontology namespaces.",
59
+ {
60
+ tool: "ontology_list",
61
+ },
62
+ ),
59
63
  });
60
64
  }
61
65
  const namespace = prefixes[input];
62
66
  if (namespace === undefined) {
63
67
  throw PragmaError.invalidInput("prefix", input, {
64
68
  validOptions: Object.keys(prefixes),
65
- recovery: {
66
- message: "List loaded ontologies.",
67
- cli: "pragma ontology list",
68
- mcp: { tool: "ontology_list" },
69
- },
69
+ recovery: cliRecovery("ontology list", "List loaded ontologies.", {
70
+ tool: "ontology_list",
71
+ }),
70
72
  });
71
73
  }
72
74
  return { prefix: input, namespace };
@@ -77,7 +79,7 @@ const listVerb: VerbSpec<Record<string, unknown>, OntologySummary[]> = {
77
79
  summary: "List loaded ontology namespaces with class and property counts.",
78
80
  params: [],
79
81
  output: { formatters: ontologyListFormatters },
80
- examples: [{ cmd: "pragma ontology list" }],
82
+ examples: [{ cmd: `${BIN_NAME} ontology list` }],
81
83
  capability: {
82
84
  needsStore: true,
83
85
  mutates: false,
@@ -193,11 +195,9 @@ async function runByName(
193
195
 
194
196
  if (classes.length === 0 && properties.length === 0) {
195
197
  throw PragmaError.notFound("ontology", String(params.prefix), {
196
- recovery: {
197
- message: "List loaded ontologies.",
198
- cli: "pragma ontology list",
199
- mcp: { tool: "ontology_list" },
200
- },
198
+ recovery: cliRecovery("ontology list", "List loaded ontologies.", {
199
+ tool: "ontology_list",
200
+ }),
201
201
  });
202
202
  }
203
203
 
@@ -244,9 +244,9 @@ export const ontologyLookupVerb = asVerb(
244
244
  "lookup",
245
245
  "Look up a namespace's classes (hierarchy + counts) and properties.",
246
246
  [
247
- { cmd: "pragma ontology lookup ds" },
248
- { cmd: "pragma ontology lookup ds --properties" },
249
- { cmd: "pragma ontology lookup ds --class Component" },
247
+ { cmd: `${BIN_NAME} ontology lookup ds` },
248
+ { cmd: `${BIN_NAME} ontology lookup ds --properties` },
249
+ { cmd: `${BIN_NAME} ontology lookup ds --class Component` },
250
250
  ],
251
251
  ),
252
252
  );
@@ -261,8 +261,8 @@ export const ontologyShowVerb = asVerb(
261
261
  "show",
262
262
  "(deprecated: use `ontology lookup`) Show a namespace's classes (hierarchy + counts) and properties.",
263
263
  [
264
- { cmd: "pragma ontology lookup ds", note: "prefer `lookup`" },
265
- { cmd: "pragma ontology show ds", note: "deprecated alias" },
264
+ { cmd: `${BIN_NAME} ontology lookup ds`, note: "prefer `lookup`" },
265
+ { cmd: `${BIN_NAME} ontology show ds`, note: "deprecated alias" },
266
266
  ],
267
267
  "Deprecated alias of `ontology lookup` — retained for compatibility. Prefer `ontology lookup <prefix>`.",
268
268
  ),
@@ -1,10 +1,10 @@
1
- import { mkdtempSync } from "node:fs";
1
+ import { mkdtempSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
5
  import { listPromptSummaries } from "../../kernel/project/mcp/prompts/source.js";
6
6
  import { bootRuntime } from "../../kernel/runtime/boot.js";
7
- import { writeLock } from "../../kernel/runtime/lock.js";
7
+ import { resolveSources } from "../../kernel/runtime/resolveSources.js";
8
8
  import type { GlobalFlags } from "../../kernel/spec/types.js";
9
9
  import { CANONICAL_TTL } from "../../testing/fixtures/graph/canonical.js";
10
10
  import {
@@ -116,7 +116,9 @@ describe("prompt — native MCP prompts surface (PROTECTED)", () => {
116
116
  describe("prompt — storeless native listing (PROTECTED)", () => {
117
117
  it("listing the prompts does NOT boot the store", async () => {
118
118
  const runtime = bootRuntime(FLAGS, fixture.cwd);
119
- const summaries = listPromptSummaries(runtime);
119
+ const summaries = listPromptSummaries(
120
+ resolveSources(await runtime.loadConfig(), runtime.cwd),
121
+ );
120
122
  expect(summaries.map((s) => s.name).sort()).toEqual(EXPECTED);
121
123
  expect(runtime.store.booted).toBe(false);
122
124
  });
@@ -127,15 +129,19 @@ describe("prompt — storeless native listing (PROTECTED)", () => {
127
129
  * (`needsStore`), so a cold store throws STORE_UNAVAILABLE through its pre-check;
128
130
  * native `prompts/list`/`prompts/get` must present the SAME diagnostic rather
129
131
  * than silently listing a stale index (D1) or reporting a bogus "not found"
130
- * (D4). A lock pointing at an evicted pack is the cold "configured but unbuilt"
131
- * store (the pack is absent from the XDG-isolated cache).
132
+ * (D4). A project that declares its own packs and has never built them is the
133
+ * cold "configured but unbuilt" store — the row of the boot decision table
134
+ * that must never be served the distribution's embedded graph.
132
135
  */
133
136
  describe("prompt — cold-store convergence, tool + native agree (D1/D4)", () => {
134
137
  let cold: McpHarness;
135
138
 
136
139
  beforeAll(async () => {
137
140
  const cwd = mkdtempSync(join(tmpdir(), "pragma-prompt-cold-"));
138
- writeLock(cwd, { version: 1, contentHash: "b".repeat(64), packs: [] });
141
+ writeFileSync(
142
+ join(cwd, "pragma.config.ts"),
143
+ `export default { packs: [{ name: "unbuilt", source: "file:///pragma-never-built" }] };\n`,
144
+ );
139
145
  cold = await projectMcp(capabilities, cwd);
140
146
  });
141
147
  afterAll(async () => {