@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
@@ -0,0 +1,624 @@
1
+ /**
2
+ * Reference emitter: project the live capability grammar into the committed
3
+ * Markdown reference under `docs/reference/`.
4
+ *
5
+ * A sibling of `emitSurface` — same input (the `CapabilityModule[]` catalog),
6
+ * same discipline: pure, zod-free, and reads ONLY the grammar (never a run
7
+ * body). Where `emitSurface` freezes the machine covenant, this renders the
8
+ * human/agent-facing pages the build hook writes back to disk and the
9
+ * drift-guard (`capabilities/reference.test.ts`) pins byte-for-byte.
10
+ *
11
+ * DETERMINISM is the contract: stable sort, params in declared order, no
12
+ * version, no dates, no `Date.now()` — the same catalog always yields the same
13
+ * bytes, so a rebuild is a no-op and the drift-guard stays green. `doc` strings
14
+ * are kept verbatim (the project-layer `stripToolCallExample` is deliberately
15
+ * NOT imported — this is a kernel-layer emitter).
16
+ */
17
+
18
+ import { BIN_NAME, PROJECT_CONFIG_FILENAME } from "../../constants.js";
19
+ import type { RawConfig } from "../config/types.js";
20
+ import { ERROR_CODES } from "../error/constants.js";
21
+ import {
22
+ FIXED_SURFACE,
23
+ kebabCase,
24
+ toolName,
25
+ verbLabel,
26
+ } from "./emitSurface.js";
27
+ import type {
28
+ CapabilityModule,
29
+ Example,
30
+ ParamSpec,
31
+ VerbSpec,
32
+ } from "./types.js";
33
+
34
+ /** The reference doc set: relative path under `docs/reference/` → file content. */
35
+ export type ReferenceDocs = ReadonlyMap<string, string>;
36
+
37
+ /**
38
+ * The closed error-code → one-line meaning map. Typed as an exhaustive
39
+ * `Record` over the `ERROR_CODES` tuple, so a code added to the tuple without a
40
+ * description here is a COMPILE error — the single authoring point stays in
41
+ * lockstep with the error kernel. Colocated (single-use) rather than in
42
+ * `constants.ts`: it is only read by `renderErrorsPage`.
43
+ */
44
+ const ERROR_CODE_DESCRIPTIONS: Record<(typeof ERROR_CODES)[number], string> = {
45
+ ENTITY_NOT_FOUND: "A named entity (block, standard, token, …) was not found.",
46
+ EMPTY_RESULTS:
47
+ "A query or listing resolved to nothing under the active scope.",
48
+ INVALID_INPUT: "An argument was malformed, out of range, or the wrong shape.",
49
+ AMBIGUOUS_INPUT:
50
+ "A name resolved to several entities (reserved; not yet raised).",
51
+ UNKNOWN_VERB: "The command noun or verb is not recognized.",
52
+ STORE_UNAVAILABLE: "The local store could not be reached or is not built.",
53
+ CONFIG_ERROR: "The layered configuration could not be resolved.",
54
+ INTERNAL_ERROR: "An unexpected failure — please report it.",
55
+ UNSUPPORTED: "A capability is unavailable in this build or environment.",
56
+ };
57
+
58
+ /** Escape a value for a Markdown table cell (pipes, then newlines to spaces). */
59
+ function escapeCell(text: string): string {
60
+ return text.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
61
+ }
62
+
63
+ /** Flatten every non-hidden verb, sorted by noun ascending then verb-label ascending. */
64
+ function collectDocVerbs(
65
+ modules: readonly CapabilityModule[],
66
+ ): readonly VerbSpec[] {
67
+ const verbs: VerbSpec[] = [];
68
+ for (const module of modules) {
69
+ for (const verb of module.verbs) {
70
+ if (verb.hidden) continue;
71
+ verbs.push(verb);
72
+ }
73
+ }
74
+ return verbs.sort(compareDocVerbs);
75
+ }
76
+
77
+ /** Order two verbs by noun, then by verb-label — a total, locale-free order. */
78
+ function compareDocVerbs(a: VerbSpec, b: VerbSpec): number {
79
+ const nounA = a.path[0];
80
+ const nounB = b.path[0];
81
+ if (nounA !== nounB) return nounA < nounB ? -1 : 1;
82
+ const labelA = verbLabel(a.path);
83
+ const labelB = verbLabel(b.path);
84
+ /* v8 ignore next -- unreachable: no registered grammar produces a `[noun,
85
+ noun]` path (a verb equal to its own noun), so within one noun every
86
+ verb-label is unique and this equal tie-break never fires. */
87
+ if (labelA === labelB) return 0;
88
+ return labelA < labelB ? -1 : 1;
89
+ }
90
+
91
+ /** The full CLI invocation head for a verb (`<bin> <noun>` or `<bin> <noun> <verb>`). */
92
+ function formatInvocation(verb: VerbSpec): string {
93
+ const sub = verb.path[1];
94
+ return sub === undefined
95
+ ? `${BIN_NAME} ${verb.path[0]}`
96
+ : `${BIN_NAME} ${verb.path[0]} ${sub}`;
97
+ }
98
+
99
+ /** Format a positional param as its usage token (`<name>`/`[name]`, `...` when variadic). */
100
+ function formatPositionalToken(param: ParamSpec): string {
101
+ const variadic = param.kind === "string[]" ? "..." : "";
102
+ return param.required
103
+ ? `<${param.name}${variadic}>`
104
+ : `[${param.name}${variadic}]`;
105
+ }
106
+
107
+ /** The value placeholder a flag takes (empty for a bare boolean switch). */
108
+ function formatFlagValue(param: ParamSpec): string {
109
+ switch (param.kind) {
110
+ case "boolean":
111
+ return "";
112
+ case "enum":
113
+ return `<${param.values.join("|")}>`;
114
+ case "number":
115
+ return "<number>";
116
+ case "string":
117
+ return "<string>";
118
+ case "string[]":
119
+ return "<value...>";
120
+ }
121
+ }
122
+
123
+ /**
124
+ * The tool-schema type label a param projects to (mirrors `buildZodSchema`).
125
+ * Enum values are comma-joined (never pipe-joined) so the label is safe inside
126
+ * a Markdown table cell without escaping.
127
+ */
128
+ function formatParamType(param: ParamSpec): string {
129
+ switch (param.kind) {
130
+ case "enum":
131
+ return `enum(${param.values.join(", ")})`;
132
+ case "string[]":
133
+ return "string[]";
134
+ default:
135
+ return param.kind;
136
+ }
137
+ }
138
+
139
+ /** Render a default value for prose (strings verbatim, everything else stringified). */
140
+ function formatDefault(value: unknown): string {
141
+ return typeof value === "string" ? value : String(value);
142
+ }
143
+
144
+ /** A param's description cell: its doc, plus enum values and any default. */
145
+ function describeParam(param: ParamSpec): string {
146
+ let description = escapeCell(param.doc);
147
+ if (param.kind === "enum") {
148
+ description += ` (one of: ${param.values.join(", ")})`;
149
+ }
150
+ if ("default" in param && param.default !== undefined) {
151
+ description += ` (default: ${escapeCell(formatDefault(param.default))})`;
152
+ }
153
+ return description;
154
+ }
155
+
156
+ /** The single-line usage string: invocation + positionals + `[options]`. */
157
+ function formatUsage(verb: VerbSpec): string {
158
+ const positionals = verb.params.filter((p) => p.positional);
159
+ const flags = verb.params.filter((p) => !p.positional);
160
+ const segments = [formatInvocation(verb)];
161
+ for (const positional of positionals) {
162
+ segments.push(formatPositionalToken(positional));
163
+ }
164
+ if (flags.length > 0) segments.push("[options]");
165
+ return segments.join(" ");
166
+ }
167
+
168
+ /** The Args table for a verb's positionals, or `""` when it has none. */
169
+ function formatArgsTable(params: readonly ParamSpec[]): string {
170
+ const positionals = params.filter((p) => p.positional);
171
+ if (positionals.length === 0) return "";
172
+ const rows = ["| Argument | Required | Description |", "| --- | --- | --- |"];
173
+ for (const param of positionals) {
174
+ const required = param.required ? "yes" : "no";
175
+ rows.push(
176
+ `| \`${formatPositionalToken(param)}\` | ${required} | ${describeParam(param)} |`,
177
+ );
178
+ }
179
+ return `**Arguments**\n\n${rows.join("\n")}`;
180
+ }
181
+
182
+ /** The Flags table for a verb's non-positional params, or `""` when it has none. */
183
+ function formatFlagsTable(params: readonly ParamSpec[]): string {
184
+ const flags = params.filter((p) => !p.positional);
185
+ if (flags.length === 0) return "";
186
+ const rows = ["| Flag | Value | Description |", "| --- | --- | --- |"];
187
+ for (const param of flags) {
188
+ // The `<a|b|c>` enum token carries pipes — escape them so the code-span cell
189
+ // does not split the table column.
190
+ const value = formatFlagValue(param);
191
+ const valueCell = value === "" ? "—" : `\`${escapeCell(value)}\``;
192
+ rows.push(
193
+ `| \`--${kebabCase(param.name)}\` | ${valueCell} | ${describeParam(param)} |`,
194
+ );
195
+ }
196
+ return `**Flags**\n\n${rows.join("\n")}`;
197
+ }
198
+
199
+ /** The Examples block for a verb, or `""` when none are declared. */
200
+ function formatExamples(examples: readonly Example[] | undefined): string {
201
+ if (!examples || examples.length === 0) return "";
202
+ const lines = ["```bash"];
203
+ for (const example of examples) {
204
+ lines.push(
205
+ example.note ? `${example.cmd} # ${example.note}` : example.cmd,
206
+ );
207
+ }
208
+ lines.push("```");
209
+ return `**Examples**\n\n${lines.join("\n")}`;
210
+ }
211
+
212
+ /** The Store / Mutation / MCP attribute bullets for one verb. */
213
+ function formatVerbAttributes(verb: VerbSpec): string {
214
+ const bullets = [
215
+ verb.capability.needsStore
216
+ ? `- Store: reads the local store (\`${BIN_NAME} sources update\` builds it).`
217
+ : "- Store: storeless.",
218
+ ];
219
+ if (verb.capability.mutates) {
220
+ bullets.push(
221
+ "- Mutation: plan-first — preview with `--dry-run`, apply with `--yes`, reverse with `--undo`.",
222
+ );
223
+ }
224
+ bullets.push(
225
+ verb.capability.mcp.expose
226
+ ? `- MCP: exposed as the \`${toolName(verb.path)}\` tool.`
227
+ : "- MCP: not exposed (CLI-only).",
228
+ );
229
+ return bullets.join("\n");
230
+ }
231
+
232
+ /** Join page blocks with blank lines, dropping empties, and end in ONE newline. */
233
+ function assemblePage(blocks: readonly string[]): string {
234
+ return `${blocks.filter((block) => block.length > 0).join("\n\n")}\n`;
235
+ }
236
+
237
+ /** Render one verb's `commands.md` section (heading through examples). */
238
+ function renderCommandSection(verb: VerbSpec): string {
239
+ const blocks = [
240
+ `### ${formatInvocation(verb)}`,
241
+ verb.summary,
242
+ verb.doc ?? "",
243
+ `\`\`\`\n${formatUsage(verb)}\n\`\`\``,
244
+ formatArgsTable(verb.params),
245
+ formatFlagsTable(verb.params),
246
+ formatVerbAttributes(verb),
247
+ formatExamples(verb.examples),
248
+ ];
249
+ return blocks.filter((block) => block.length > 0).join("\n\n");
250
+ }
251
+
252
+ /** Render the CLI command reference, grouped by noun. */
253
+ function renderCommandsPage(verbs: readonly VerbSpec[]): string {
254
+ const blocks = [
255
+ "# CLI command reference",
256
+ `Every \`${BIN_NAME}\` command, grouped by noun. Generated from the live capability grammar — do not edit by hand.`,
257
+ "Global flags apply to every command: `--format <plain|llm|json>` (auto-detected — the llm/condensed-Markdown form turns on when output is piped), `--verbose`, and `--detail <summary|standard|detailed>`.",
258
+ ];
259
+ let currentNoun = "";
260
+ for (const verb of verbs) {
261
+ const noun = verb.path[0];
262
+ if (noun !== currentNoun) {
263
+ currentNoun = noun;
264
+ blocks.push(`## ${noun}`);
265
+ }
266
+ blocks.push(renderCommandSection(verb));
267
+ }
268
+ return assemblePage(blocks);
269
+ }
270
+
271
+ /** One MCP tool input row: name, projected type, requiredness, description. */
272
+ interface ToolParamRow {
273
+ readonly name: string;
274
+ readonly type: string;
275
+ readonly required: boolean;
276
+ readonly description: string;
277
+ }
278
+
279
+ /** Collect a tool's input rows: the verb's params plus the injected MCP-only ones. */
280
+ function collectToolParams(verb: VerbSpec): readonly ToolParamRow[] {
281
+ const rows: ToolParamRow[] = [];
282
+ for (const param of verb.params) {
283
+ rows.push({
284
+ name: param.name,
285
+ type: formatParamType(param),
286
+ required: param.required === true,
287
+ description: describeParam(param),
288
+ });
289
+ }
290
+ if (verb.disclosure) {
291
+ rows.push({
292
+ name: "detail",
293
+ type: `enum(${verb.disclosure.levels.join(", ")})`,
294
+ required: false,
295
+ description: `Progressive-disclosure level (default ${verb.disclosure.default}).`,
296
+ });
297
+ }
298
+ if (verb.capability.mutates) {
299
+ rows.push({
300
+ name: "confirm",
301
+ type: "boolean",
302
+ required: false,
303
+ description:
304
+ "Set true to execute; otherwise a plan is returned (default false).",
305
+ });
306
+ rows.push({
307
+ name: "cwd",
308
+ type: "string",
309
+ required: false,
310
+ description:
311
+ "Absolute project directory to write into; defaults to the server's working directory.",
312
+ });
313
+ }
314
+ return rows;
315
+ }
316
+
317
+ /** The input-params table for a tool, or a storeless-note when it takes none. */
318
+ function formatToolParams(verb: VerbSpec): string {
319
+ const rows = collectToolParams(verb);
320
+ if (rows.length === 0) return "_No input parameters._";
321
+ const table = [
322
+ "| Parameter | Type | Required | Description |",
323
+ "| --- | --- | --- | --- |",
324
+ ];
325
+ for (const row of rows) {
326
+ const required = row.required ? "yes" : "no";
327
+ table.push(
328
+ `| \`${row.name}\` | ${row.type} | ${required} | ${row.description} |`,
329
+ );
330
+ }
331
+ return table.join("\n");
332
+ }
333
+
334
+ /** The read-only / mutation + destructive annotation line for a tool. */
335
+ function formatToolAnnotations(verb: VerbSpec): string {
336
+ if (!verb.capability.mutates) return "Read-only.";
337
+ const destructive =
338
+ verb.capability.destructive === true
339
+ ? " Marked destructive."
340
+ : verb.capability.destructive === false
341
+ ? " Non-destructive."
342
+ : "";
343
+ return `Mutation — plan-first (set \`confirm: true\` to apply).${destructive}`;
344
+ }
345
+
346
+ /** Render one tool's `tools.md` section. */
347
+ function renderToolSection(verb: VerbSpec): string {
348
+ const blocks = [
349
+ `### ${toolName(verb.path)}`,
350
+ verb.doc ?? verb.summary,
351
+ formatToolAnnotations(verb),
352
+ "**Input**",
353
+ formatToolParams(verb),
354
+ ];
355
+ return blocks.join("\n\n");
356
+ }
357
+
358
+ /** The non-tool MCP surface (resources + native prompts) declared by the modules. */
359
+ function collectMcpExtras(modules: readonly CapabilityModule[]): {
360
+ readonly resources: readonly string[];
361
+ readonly prompts: boolean;
362
+ } {
363
+ const resources: string[] = [];
364
+ let prompts = false;
365
+ for (const module of modules) {
366
+ if (module.mcpResources?.surface) {
367
+ resources.push(...module.mcpResources.surface.templates);
368
+ }
369
+ if (module.mcpPrompts) prompts = true;
370
+ }
371
+ // Default lexicographic sort — deterministic, and the same idiom `emitSurface`
372
+ // uses for its `resources`/`tools` lists.
373
+ return { resources: [...resources].sort(), prompts };
374
+ }
375
+
376
+ /** Render the non-tool MCP surface section for `tools.md`. */
377
+ function renderNonToolSurface(modules: readonly CapabilityModule[]): string {
378
+ const { resources, prompts } = collectMcpExtras(modules);
379
+ const bullets: string[] = [];
380
+ if (resources.length > 0) {
381
+ bullets.push(
382
+ `- **Resources**: ${resources.map((r) => `\`${r}\``).join(", ")} — entity reads addressed by URI (listing and autocomplete are storeless over the pack index). The template — its scheme and the \`_meta\` taxonomy keys its entries carry — is frozen protocol identity, served unchanged by every distribution: clients persist resource URIs, so the scheme never follows a fork's name.`,
383
+ );
384
+ }
385
+ if (prompts) {
386
+ bullets.push(
387
+ "- **Prompts**: the workflow prompt templates the active graph declares are offered natively over `prompts/list` and `prompts/get`, and as the `prompt_list` / `prompt_lookup` content tools. A graph declaring none leaves both views empty.",
388
+ );
389
+ }
390
+ bullets.push(
391
+ "- **Instructions**: the server always sends handshake instructions describing the conventions and the discovery sequence.",
392
+ );
393
+ return `## Non-tool surface\n\n${bullets.join("\n")}`;
394
+ }
395
+
396
+ /** Render the MCP tool reference plus the non-tool surface. */
397
+ function renderToolsPage(
398
+ verbs: readonly VerbSpec[],
399
+ modules: readonly CapabilityModule[],
400
+ ): string {
401
+ const tools = verbs.filter((verb) => verb.capability.mcp.expose);
402
+ const blocks = [
403
+ "# MCP tool reference",
404
+ `Every tool the ${BIN_NAME} MCP server exposes, plus its non-tool surface. Generated from the live capability grammar — do not edit by hand. The server's \`serverInfo\` is a projection, not a constant: it introduces itself on the wire under the distribution's declared name at the package version, so a client should read \`serverInfo\` rather than assume a name.`,
405
+ "Mutating tools are plan-first: called without `confirm: true` they return the plan they WOULD apply; called with `confirm: true` they execute. A mutating tool also accepts an optional absolute `cwd`.",
406
+ ];
407
+ for (const verb of tools) {
408
+ blocks.push(renderToolSection(verb));
409
+ }
410
+ blocks.push(renderNonToolSurface(modules));
411
+ return assemblePage(blocks);
412
+ }
413
+
414
+ /** Render the exit-code table, the response envelope, and the error catalog. */
415
+ function renderErrorsPage(): string {
416
+ const exitRows = ["| Exit code | Meaning |", "| --- | --- |"];
417
+ // Integer-like keys ("0".."3") are iterated in ascending numeric order by the
418
+ // ECMAScript own-key ordering rule, so `Object.entries` is already sorted.
419
+ for (const [code, meaning] of Object.entries(FIXED_SURFACE.exitCodes)) {
420
+ exitRows.push(`| \`${code}\` | ${meaning} |`);
421
+ }
422
+
423
+ const catalogRows = ["| Code | Meaning |", "| --- | --- |"];
424
+ for (const code of ERROR_CODES) {
425
+ const meaning = ERROR_CODE_DESCRIPTIONS[code];
426
+ /* v8 ignore next 3 -- exhaustiveness: ERROR_CODE_DESCRIPTIONS is a Record
427
+ over the ERROR_CODES tuple, so every code is present by construction. */
428
+ if (meaning === undefined) {
429
+ throw new Error(`missing error-code description for ${code}`);
430
+ }
431
+ catalogRows.push(`| \`${code}\` | ${meaning} |`);
432
+ }
433
+
434
+ return assemblePage([
435
+ "# Errors & exit codes",
436
+ "Every command returns a `{ ok, ... }` envelope and maps its failure to one of four process exit codes. Generated from the live error kernel — do not edit by hand.",
437
+ "## Exit codes",
438
+ exitRows.join("\n"),
439
+ "## Response envelope",
440
+ `\`\`\`json\n${JSON.stringify(FIXED_SURFACE.envelope, null, 2)}\n\`\`\``,
441
+ "## Error codes",
442
+ "Every `error.code` in a failure envelope is one of the following:",
443
+ catalogRows.join("\n"),
444
+ ]);
445
+ }
446
+
447
+ /** One documented config field: the type label a user sees, and the prose. */
448
+ interface ConfigFieldDoc {
449
+ /** The type as a reader needs it, e.g. `string (optional)`. */
450
+ readonly type: string;
451
+ /** What the field does, and anything true about it a reader would not guess. */
452
+ readonly notes: string;
453
+ }
454
+
455
+ /**
456
+ * Every field a config layer may declare. Typed as an exhaustive `Record` over
457
+ * `keyof RawConfig`, so a field added to the type without a row here — or a row
458
+ * left behind by a field that was removed — is a COMPILE error, exactly as
459
+ * {@link ERROR_CODE_DESCRIPTIONS} works for the error kernel. Colocated
460
+ * (single-use) rather than in a module of its own: only `renderConfigPage`
461
+ * reads it, and a third artifact for two consumers to keep in step is what this
462
+ * discipline exists to avoid.
463
+ *
464
+ * The `type` column is authored prose; the page says so. Its OPTIONALITY is
465
+ * checked against the zod validator by `kernel/config/schema.test.ts`.
466
+ */
467
+ const CONFIG_FIELD_DOCS: Record<keyof RawConfig, ConfigFieldDoc> = {
468
+ name: {
469
+ type: "string (optional)",
470
+ notes:
471
+ "Distribution-only — see below. The binary's own name, read from the distribution config at module load.",
472
+ },
473
+ help: {
474
+ type: "string (optional)",
475
+ notes:
476
+ "Distribution-only — see below. The one-line blurb on the front door and in the MCP handshake.",
477
+ },
478
+ colophon: {
479
+ type: "object (optional)",
480
+ notes:
481
+ "Distribution-only — see below. The toolchain's own story, rendered first by the `colophon` verb and titled with the distribution's name: `{ markdown, summary? }`, both Markdown bodies with no leading H1 (`summary` is the condensed `--format llm` form). Declared content, not code — a fork edits it to tell its own story.",
482
+ },
483
+ issuesUrl: {
484
+ type: "URL string (optional)",
485
+ notes:
486
+ "Distribution-only — see below. Where the first-run note asks users to report problems.",
487
+ },
488
+ tier: {
489
+ type: "string (optional)",
490
+ notes:
491
+ "Accepted by the validator and SCOPES NOTHING: since the block list became declared content, no read filters by tier — the value is only reported by `config show` and `info`. Set it with `config set tier <path>`; `none`, `default` or `-` clear it.",
492
+ },
493
+ channel: {
494
+ type: "`normal` | `experimental` | `prerelease` (optional)",
495
+ notes:
496
+ "Selects the npm dist-tag `upgrade` and `info` check the registry with. It no longer scopes graph reads — no read filters by channel. Defaults to `normal`. Set it with `config set channel <name>`.",
497
+ },
498
+ detail: {
499
+ type: "`summary` | `standard` | `detailed` (optional)",
500
+ notes:
501
+ "Default progressive-disclosure level. A closed enum, like `channel`: any other value fails at load with a `CONFIG_ERROR` naming the file and the three levels. Set it with `config set detail <level>`.",
502
+ },
503
+ packs: {
504
+ type: "array (optional)",
505
+ notes:
506
+ "Semantic pack sources built by `sources update`. Each entry is a bare npm name or `{ name, source, stories? }`; `stories` are read stories the pack supplies, in the pack grammar.",
507
+ },
508
+ stories: {
509
+ type: "array (optional)",
510
+ notes:
511
+ "Read stories not attached to any pack, in the pack grammar. Compiled at dispatch, and they win over the same noun declared under `packs[].stories`.",
512
+ },
513
+ prefixes: {
514
+ type: "record (optional)",
515
+ notes:
516
+ "Namespace prefixes the pack is built with — they win every harvest, so this decides which IRI a prefix binds in the store and the index. Every surface uses the compiled-in display/expansion map to compact and expand prefixed names; only the DISTRIBUTION layer seeds it, because it is also read on the storeless fast path, before any config layer exists.",
517
+ },
518
+ completion: {
519
+ type: "object (optional)",
520
+ notes:
521
+ "Completion policy read when `setup completions` emits a script: `minChars` and a per-noun `families` opt-out. It is the one field `config show` carries with NO origin at all.",
522
+ },
523
+ };
524
+
525
+ /**
526
+ * The fields `config show` prints with the layer that supplied them, in the
527
+ * order it prints them. NOT every field: `prefixes` and `completion` reach only
528
+ * the JSON payload, `stories` reaches only its `origins` key, and the four
529
+ * distribution-only fields reach neither. The page said the opposite — that
530
+ * every field but `completion` was reported — which is the claim this whole
531
+ * reference exists to make true rather than plausible.
532
+ *
533
+ * A literal, because the kernel emitter may not import a capability renderer.
534
+ * `capabilities/config/show.render.test.ts` derives the same list from the real
535
+ * formatter and fails if this page and that renderer disagree.
536
+ */
537
+ const REPORTED_FIELDS = ["tier", "channel", "detail", "packs"];
538
+
539
+ /** Render the layered-configuration reference: the layers, then the fields. */
540
+ function renderConfigPage(): string {
541
+ const rows = ["| Field | Type | Notes |", "| --- | --- | --- |"];
542
+ for (const [field, doc] of Object.entries(CONFIG_FIELD_DOCS)) {
543
+ rows.push(
544
+ `| \`${field}\` | ${escapeCell(doc.type)} | ${escapeCell(doc.notes)} |`,
545
+ );
546
+ }
547
+ return assemblePage([
548
+ "# Configuration reference",
549
+ `Every field a \`${BIN_NAME}\` config layer may declare. Generated from the config type — do not edit by hand. See [config-model.md](../config-model.md) for the authoring guide.`,
550
+ "## Layers",
551
+ "From lowest to highest precedence:",
552
+ [
553
+ `1. **Built-in defaults** — the distribution config compiled into the binary.`,
554
+ `2. **Global config** — \`$XDG_CONFIG_HOME/${BIN_NAME}/config.json\`, written by \`${BIN_NAME} config set\`.`,
555
+ `3. **Project config** — the nearest \`${PROJECT_CONFIG_FILENAME}\` (or \`${BIN_NAME}.config.js\`, the compiled-binary fallback), walking up from the working directory.`,
556
+ ].join("\n"),
557
+ "A higher layer REPLACES a lower one field by field. No field is deep-merged — not `packs`, not `prefixes`, not `completion`. A project declaring one prefix therefore replaces the distribution's whole prefix map, including the namespaces its own packs are built with; declare every prefix you need, not only the new one.",
558
+ "## Fields",
559
+ "The `Type` column is prose; the field set and each field's optionality are checked against the validator.",
560
+ rows.join("\n"),
561
+ "## Distribution-only fields",
562
+ `\`name\`, \`help\` and \`issuesUrl\` are read from the distribution config when the program loads, because the surfaces that need them — \`--help\`, shell completion, the MCP handshake, the first-run note — run before or without the config layer. \`colophon\` is read from the same file at render time: the \`colophon\` verb narrates whatever the distribution declares there. The validator ACCEPTS all four in a global or project layer, and they have **no effect there and are not reported** by \`config show\`. Changing them means forking: edit the distribution config and rebuild the binary. The distribution config's \`vocabulary\` export is not a config field at all — no layer may declare it, and a fork changes it in the same file it changes \`name\` in.`,
563
+ "## What `config show` reports",
564
+ `\`${BIN_NAME} config show\` prints ${REPORTED_FIELDS.map((field) => `\`${field}\``).join(", ")} — those and only those — each with the layer that supplied it. The rest resolve without being reported that way: \`prefixes\` and \`completion\` appear only in the \`--format json\` payload, \`prefixes\` with an origin and \`completion\` with none; \`stories\` carries an origin whose value the payload leaves out; and the four distribution-only fields above carry neither. The plain and llm forms print those rows and nothing else; \`--format json\` returns the resolved config and the origin map whole.`,
565
+ "## Renamed: `packages` → `packs`",
566
+ "The `packages` field was renamed to `packs`. A layer that still declares `packages:` fails loudly: the rename is detected before the schema's unknown-key stripping could hide it, and the error names it. Rename the key — the entry shape is unchanged.",
567
+ "## Removed: `generators`",
568
+ "The `generators` field was removed: it was accepted by the validator, layered, and read by nothing — the `create` verbs resolve their generators statically (a compiled binary can only run generators it was linked with), so declaring it changed only what `config show` printed. A layer that still declares it fails loudly at load with an error naming the removed field; delete it. Declared generators may return as a working feature in a later program.",
569
+ "## Removed: `completion.caseSensitive`",
570
+ "The `completion.caseSensitive` field was removed: it was accepted by the validator and read by nothing — completion matching is declared per parameter by the capability grammar, never configured. A layer that still sets it fails loudly at load with an error naming the removed field; delete the key. `completion.minChars` and `completion.families` are unchanged.",
571
+ "## Reading and writing",
572
+ `\`${BIN_NAME} config show\` prints the resolved config and each field's layer. \`${BIN_NAME} config set <key> <value>\` writes to the **global** layer only — project configs are authored by hand. Both are documented in the [command reference](./commands.md).`,
573
+ ]);
574
+ }
575
+
576
+ /** Render the reference index: overview, derived counts, and links. */
577
+ function renderIndexPage(
578
+ verbs: readonly VerbSpec[],
579
+ modules: readonly CapabilityModule[],
580
+ ): string {
581
+ const nouns = new Set(verbs.map((verb) => verb.path[0]));
582
+ const toolCount = verbs.filter((verb) => verb.capability.mcp.expose).length;
583
+ return assemblePage([
584
+ `# ${BIN_NAME} reference`,
585
+ `Machine-generated reference for the \`${BIN_NAME}\` CLI and MCP server, projected from the live capability grammar. Every page here is regenerated by the build and pinned by a drift-guard test, so it can never fall out of step with the code.`,
586
+ "## At a glance",
587
+ [
588
+ `- **${nouns.size}** command nouns`,
589
+ `- **${verbs.length}** CLI commands`,
590
+ `- **${toolCount}** MCP tools`,
591
+ `- **${collectMcpExtras(modules).resources.length}** resource template(s)`,
592
+ ].join("\n"),
593
+ "## Pages",
594
+ [
595
+ `- [CLI command reference](./commands.md) — every \`${BIN_NAME} <noun> <verb>\`, its arguments, flags, and examples.`,
596
+ "- [MCP tool reference](./tools.md) — every exposed tool, its input schema, and the non-tool surface.",
597
+ "- [Errors & exit codes](./errors.md) — the exit-code table, response envelope, and error catalog.",
598
+ "- [Configuration reference](./config.md) — every config field, the three layers, and how they combine.",
599
+ ].join("\n"),
600
+ ]);
601
+ }
602
+
603
+ /**
604
+ * Emit the Markdown reference doc set for a set of capability modules.
605
+ *
606
+ * Pure and deterministic: the same catalog always yields byte-identical pages
607
+ * (stable sort, declared param order, no version or timestamps).
608
+ *
609
+ * @param modules - The capability modules to project (hidden verbs excluded).
610
+ * @returns A map of `docs/reference/`-relative path → Markdown content, each
611
+ * ending in exactly one trailing newline.
612
+ */
613
+ export function emitReference(
614
+ modules: readonly CapabilityModule[],
615
+ ): ReferenceDocs {
616
+ const verbs = collectDocVerbs(modules);
617
+ return new Map<string, string>([
618
+ ["index.md", renderIndexPage(verbs, modules)],
619
+ ["commands.md", renderCommandsPage(verbs)],
620
+ ["tools.md", renderToolsPage(verbs, modules)],
621
+ ["errors.md", renderErrorsPage()],
622
+ ["config.md", renderConfigPage()],
623
+ ]);
624
+ }
@@ -8,6 +8,7 @@
8
8
  * envelope, exit codes, budgets, ...). Hidden verbs are excluded.
9
9
  */
10
10
 
11
+ import { BIN_NAME, PROJECT_CONFIG_FILENAME } from "../../constants.js";
11
12
  import type { CapabilityModule, ParamSpec, VerbSpec } from "./types.js";
12
13
 
13
14
  /** kebab-case a camelCase param name for its flag form (`allTiers` -> `all-tiers`). */
@@ -74,11 +75,13 @@ export interface EmittedSurface {
74
75
  */
75
76
  export const FIXED_SURFACE = {
76
77
  bins: {
77
- pragma: "pragma v2 CLI and MCP server host (stdio)",
78
+ [BIN_NAME]: `${BIN_NAME} CLI and MCP server host (stdio)`,
78
79
  },
79
80
  globalFlags: [
80
- { flag: "--llm", doc: "Condensed Markdown output for agents" },
81
- { flag: "--format <json|plain>", doc: "Select output format" },
81
+ {
82
+ flag: "--format <plain|llm|json>",
83
+ doc: "Select output format (llm = condensed Markdown for agents)",
84
+ },
82
85
  { flag: "--verbose", doc: "Diagnostic output on stderr" },
83
86
  {
84
87
  flag: "--detail <level>",
@@ -120,14 +123,17 @@ export const FIXED_SURFACE = {
120
123
  paramSource: "ParamSpec.complete",
121
124
  },
122
125
  configFiles: {
123
- project: "pragma.config.ts (evaluated, content-hash cached)",
124
- global: "$XDG_CONFIG_HOME/pragma/config.json",
125
- lock: "$XDG_STATE_HOME/pragma/config-cache/<sha256>.json",
126
+ project: `${PROJECT_CONFIG_FILENAME} (evaluated, content-hash cached)`,
127
+ global: `$XDG_CONFIG_HOME/${BIN_NAME}/config.json`,
128
+ // Named `configCache` because that is what it is: the evaluated project
129
+ // config's content-addressed cache. It was called `lock` for a project lock
130
+ // file that no longer exists.
131
+ configCache: `$XDG_STATE_HOME/${BIN_NAME}/config-cache/<sha256>.json`,
126
132
  defaults: "built-in defaults.ts",
127
133
  },
128
134
  budgets: {
129
135
  $comment:
130
- "help/complete are the designed <50ms aspiration; the ENFORCED ceilings (budgets.test.ts) are 130ms help / 100ms complete — ~2× the measured median on the build hardware, recorded in BUDGETS.md. Designed-aspiration vs enforced-measured.",
136
+ "help/complete/warmStoreVerb are designed aspirations; the ENFORCED ceilings (budgets.test.ts) are 130ms help / 100ms complete / 500ms warmStoreVerb, each derived from measurement on the build hardware and recorded in BUDGETS.md. Designed-aspiration vs enforced-measured. warmStoreVerb joined this list when the embedded pack became the distribution's real graph rather than a sample, which puts the reference box's projected p95 over the designed 300ms; BUDGETS.md carries the arithmetic.",
131
137
  help: "<50ms",
132
138
  complete: "<50ms",
133
139
  projectConfigLoad: "<10ms",
@@ -6,6 +6,8 @@
6
6
  * `--help`/`__complete` path that reaches it — stays free of zod.
7
7
  */
8
8
 
9
+ export type { ReferenceDocs } from "./emitReference.js";
10
+ export { emitReference } from "./emitReference.js";
9
11
  export type {
10
12
  EmittedSurface,
11
13
  EmittedVerb,