@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
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Curated root `--help` for `pragma`.
2
+ * The curated root `--help` page.
3
3
  *
4
- * The front door is hand-curated, not auto-generated: nouns are grouped into
5
- * task-oriented sections with real one-line summaries, and each section is
6
- * reconciled against the *live* nouns (from the registered verbs) so a noun
7
- * that is not built yet is dropped and an uncatalogued one still surfaces under
8
- * "Other" rather than vanishing. Ported from the v1 root help, retargeted at
9
- * the grammar and the v2 noun set (no `llm`/`graphql`/`trace`/`tokens`).
4
+ * The kernel curates the nouns it ships — and only those. It names no domain:
5
+ * the nouns it does NOT curate lead the page untitled (the header one line up
6
+ * already carries the distribution's blurb) and describe themselves through
7
+ * their own verbs, so the front door follows the content instead of naming it.
8
+ * Every section is reconciled against the *live* nouns (from the registered
9
+ * verbs), so a noun that is not built yet is dropped and one the kernel has
10
+ * never heard of still surfaces.
10
11
  */
11
12
 
12
13
  import type { VerbSpec } from "../../spec/types.js";
@@ -24,88 +25,86 @@ interface NounSummary {
24
25
  }
25
26
 
26
27
  interface HelpGroup {
27
- readonly title: string;
28
+ /**
29
+ * Absent for the leading uncurated group: its rows sit directly under the
30
+ * usage line, since repeating the header's blurb as a heading three lines
31
+ * below it says the same thing twice.
32
+ */
33
+ readonly title?: string;
28
34
  readonly nouns: readonly NounSummary[];
29
35
  }
30
36
 
31
37
  /**
32
- * Task-oriented grouping with curated summaries. Order is intentional: what
33
- * people reach for most (exploring, generating) comes first; agent tooling
34
- * comes last.
38
+ * The kernel's own nouns, grouped by task with curated summaries. Order is
39
+ * intentional: what people reach for most comes first; agent tooling comes last.
40
+ *
41
+ * @param programName - The CLI binary name (the distribution's `name`).
42
+ * @returns The task-oriented groups, in display order.
35
43
  */
36
- const HELP_GROUPS: readonly HelpGroup[] = [
37
- {
38
- title: "Explore the design system",
39
- nouns: [
40
- {
41
- noun: "block",
42
- summary: "Inspect components & patterns and their anatomy",
43
- },
44
- { noun: "token", summary: "Look up design tokens and their values" },
45
- { noun: "modifier", summary: "Inspect modifiers and modifier families" },
46
- { noun: "standard", summary: "Browse code standards and categories" },
47
- {
48
- noun: "ontology",
49
- summary: "Explore the loaded ontologies (classes, properties)",
50
- },
51
- { noun: "tier", summary: "List the design-system tiers" },
52
- ],
53
- },
54
- {
55
- title: "Generate code",
56
- nouns: [
57
- { noun: "create", summary: "Scaffold components, packages, and apps" },
58
- ],
59
- },
60
- {
61
- title: "Query & serve the graph",
62
- nouns: [
63
- {
64
- noun: "graph",
65
- summary: "Run SPARQL queries or inspect a URI directly",
66
- },
67
- ],
68
- },
69
- {
70
- title: "Set up & maintain",
71
- nouns: [
72
- { noun: "doctor", summary: "Check environment health" },
73
- {
74
- noun: "setup",
75
- summary: "Configure MCP, skills, completions, and the LSP",
76
- },
77
- {
78
- noun: "sources",
79
- summary: "Build and refresh the local store from packages",
80
- },
81
- { noun: "config", summary: "Read and write pragma configuration" },
82
- { noun: "info", summary: "Show version, config, and update status" },
83
- {
84
- noun: "upgrade",
85
- summary: "Upgrade the pragma CLI to the latest version",
86
- },
87
- ],
88
- },
89
- {
90
- title: "For AI agents",
91
- nouns: [
92
- {
93
- noun: "capabilities",
94
- summary: "Discover conventions, tools, and the discovery sequence",
95
- },
96
- {
97
- noun: "colophon",
98
- summary: "Read how pragma and the active domain are made",
99
- },
100
- {
101
- noun: "skill",
102
- summary: "Browse agent skills from design-system packages",
103
- },
104
- { noun: "prompt", summary: "Browse reusable prompt templates" },
105
- { noun: "mcp", summary: "Start the MCP server over stdio" },
106
- ],
107
- },
108
- ];
44
+ function buildKernelGroups(programName: string): readonly HelpGroup[] {
45
+ return [
46
+ {
47
+ title: "Generate code",
48
+ nouns: [
49
+ { noun: "create", summary: "Scaffold components, packages, and apps" },
50
+ ],
51
+ },
52
+ {
53
+ title: "Query & serve the graph",
54
+ nouns: [
55
+ {
56
+ noun: "graph",
57
+ summary: "Run SPARQL queries or inspect a URI directly",
58
+ },
59
+ {
60
+ noun: "ontology",
61
+ summary: "Explore the loaded ontologies (classes, properties)",
62
+ },
63
+ ],
64
+ },
65
+ {
66
+ title: "Set up & maintain",
67
+ nouns: [
68
+ { noun: "doctor", summary: "Check environment health" },
69
+ {
70
+ noun: "setup",
71
+ summary: "Configure MCP, skills, completions, and the LSP",
72
+ },
73
+ {
74
+ noun: "sources",
75
+ summary: "Build and refresh the local store from packs",
76
+ },
77
+ {
78
+ noun: "config",
79
+ summary: `Read and write ${programName} configuration`,
80
+ },
81
+ { noun: "info", summary: "Show version, config, and update status" },
82
+ {
83
+ noun: "upgrade",
84
+ summary: `Upgrade the ${programName} CLI to the latest version`,
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ title: "For AI agents",
90
+ nouns: [
91
+ {
92
+ noun: "capabilities",
93
+ summary: "Discover conventions, tools, and the discovery sequence",
94
+ },
95
+ {
96
+ noun: "colophon",
97
+ summary: `Read how ${programName} and the active domain are made`,
98
+ },
99
+ { noun: "skill", summary: "Browse agent skills from the active packs" },
100
+ { noun: "prompt", summary: "Browse reusable prompt templates" },
101
+ // The one noun with no verb to speak for it (the bin special-cases it),
102
+ // so its summary is hand-written rather than derived.
103
+ { noun: "mcp", summary: "Start the MCP server over stdio" },
104
+ ],
105
+ },
106
+ ];
107
+ }
109
108
 
110
109
  /** All distinct, non-hidden top-level nouns present in the registered verbs. */
111
110
  function nounsFrom(verbs: readonly VerbSpec[]): Set<string> {
@@ -117,16 +116,21 @@ function nounsFrom(verbs: readonly VerbSpec[]): Set<string> {
117
116
  return nouns;
118
117
  }
119
118
 
120
- /** The summary to show for an uncatalogued but live noun. */
121
- function fallbackSummary(noun: string, verbs: readonly VerbSpec[]): string {
119
+ /**
120
+ * Summarise an uncurated noun from its first live verb — which is the pack's own
121
+ * `description` whenever the pack ships a `list` (`compilePack` compiles that
122
+ * verb first). The terminal period a pack sentence carries is dropped so the
123
+ * column reads as one voice with the kernel's own fragments.
124
+ */
125
+ function summarizeNoun(noun: string, verbs: readonly VerbSpec[]): string {
122
126
  const first = verbs.find((v) => v.path[0] === noun && !v.hidden);
123
- return first?.summary ?? `${noun} commands`;
127
+ return (first?.summary ?? `${noun} commands`).replace(/\.$/, "");
124
128
  }
125
129
 
126
130
  /**
127
131
  * Build the curated root help string.
128
132
  *
129
- * @param programName - The CLI binary name (`pragma`).
133
+ * @param programName - The CLI binary name (the distribution's `name`).
130
134
  * @param description - The program description shown in the header.
131
135
  * @param verbs - All registered verbs, used to derive the live noun set.
132
136
  * @returns The formatted, colorized help text.
@@ -140,27 +144,23 @@ export function formatRootHelp(
140
144
  // `mcp` is served by the bin's special-case, not a projected verb, but is
141
145
  // always available — surface it so the front door is complete.
142
146
  present.add("mcp");
143
- const catalogued = new Set(
144
- HELP_GROUPS.flatMap((g) => g.nouns.map((n) => n.noun)),
145
- );
147
+ const kernel = buildKernelGroups(programName);
148
+ const curated = new Set(kernel.flatMap((g) => g.nouns.map((n) => n.noun)));
146
149
 
147
- const uncatalogued = [...present].filter((n) => !catalogued.has(n)).sort();
148
150
  const groups: HelpGroup[] = [
149
- ...HELP_GROUPS.map((g) => ({
151
+ // Everything the kernel's curated table does not claim — in the shipped
152
+ // binary the bundled domain packs, in a fork whatever it ships. Untitled
153
+ // and first, so the domain leads the page without the kernel naming it.
154
+ {
155
+ nouns: [...present]
156
+ .filter((n) => !curated.has(n))
157
+ .sort()
158
+ .map((noun) => ({ noun, summary: summarizeNoun(noun, verbs) })),
159
+ },
160
+ ...kernel.map((g) => ({
150
161
  ...g,
151
162
  nouns: g.nouns.filter((n) => present.has(n.noun)),
152
163
  })),
153
- ...(uncatalogued.length > 0
154
- ? [
155
- {
156
- title: "Other",
157
- nouns: uncatalogued.map((noun) => ({
158
- noun,
159
- summary: fallbackSummary(noun, verbs),
160
- })),
161
- },
162
- ]
163
- : []),
164
164
  ].filter((g) => g.nouns.length > 0);
165
165
 
166
166
  // One column width across ALL groups so the noun column aligns section to
@@ -180,7 +180,7 @@ export function formatRootHelp(
180
180
  ];
181
181
 
182
182
  for (const group of groups) {
183
- lines.push(helpHeading(group.title));
183
+ if (group.title) lines.push(helpHeading(group.title));
184
184
  lines.push(
185
185
  ...helpColumns(
186
186
  group.nouns.map((n) => [n.noun, n.summary] as const),
@@ -195,8 +195,10 @@ export function formatRootHelp(
195
195
  // never these strings.
196
196
  lines.push(helpHeading("Global flags"));
197
197
  const flags: [string, string][] = [
198
- ["--llm", "Condensed Markdown output for agents"],
199
- ["--format <json|plain>", "Select output format"],
198
+ [
199
+ "--format <plain|llm|json>",
200
+ "Select output format (llm = condensed Markdown for agents)",
201
+ ],
200
202
  [
201
203
  "--detail <level>",
202
204
  "Progressive-disclosure level (summary, standard, detailed)",
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * Build the MCP server from the grammar.
3
3
  *
4
- * Constructs an {@link McpServer} under the stable `pragma` identity (matching
5
- * the CLI bin, so existing agent wiring resolves it), then registers every
6
- * exposed verb as a tool and each module's optional `mcpResources` surface (the
7
- * graph resource browser, wired below via `module.mcpResources?.register`). One
8
- * runtime is booted for the server's lifetime and shared by every tool; the
9
- * store stays lazy, booting only when a resource read needs it.
4
+ * Constructs an {@link McpServer} whose wire identity PROJECTS from the
5
+ * distribution: `serverInfo.name` is the declared distribution name (the same
6
+ * projection that names the CLI bin, so existing agent wiring resolves it) and
7
+ * `serverInfo.version` the package version — a fork's server introduces itself
8
+ * under the fork's own name with no code change. The covenant records the rule
9
+ * (`surface.v2.json` `mcpSurface.serverInfo`) and `identity.test.ts` pins it on
10
+ * the wire from a fork's config. Then registers every exposed verb as a tool
11
+ * and each module's optional `mcpResources` surface (the graph resource
12
+ * browser, wired below via `module.mcpResources?.register`). One runtime is
13
+ * booted for the server's lifetime and shared by every tool; the store stays
14
+ * lazy, booting only when a resource read needs it.
10
15
  */
11
16
 
12
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * It carries the same content the retired `llm` tool front-loaded: the
6
6
  * conventions + the discovery sequence. To guarantee it never diverges from the
7
- * `capabilities` tool, it derives BOTH from the SAME source — `CONVENTIONS` and
7
+ * `capabilities` tool, the WHOLE orientation — the opening line included —
8
+ * derives from the SAME source: `CONVENTIONS` and
8
9
  * `buildDiscoverySequence` in `capabilities/capabilities/catalog.ts`. Live
9
10
  * numbers (tier/channel/entity counts) are deliberately DROPPED (they would need
10
11
  * a store boot at handshake); agents fetch those via `info`/`config show`/
@@ -15,8 +16,8 @@
15
16
  import {
16
17
  buildDiscoverySequence,
17
18
  CONVENTIONS,
18
- liveTools,
19
19
  } from "../../../capabilities/capabilities/catalog.js";
20
+ import { emitSurface } from "../../spec/emitSurface.js";
20
21
  import type { CapabilityModule } from "../../spec/types.js";
21
22
 
22
23
  /** Hard ceiling on the instructions length (asserted by the protected test). */
@@ -31,18 +32,22 @@ export const INSTRUCTIONS_MAX_CHARS = 1500;
31
32
  export function buildInstructions(
32
33
  modules: readonly CapabilityModule[],
33
34
  ): string {
34
- const discovery = buildDiscoverySequence(liveTools(modules));
35
- const steps = discovery
35
+ // Tools AND resource templates come from the one emitted surface: the
36
+ // `<scheme>:{+uri}` template is declared by the module that serves it and
37
+ // frozen in the covenant, so quoting it here rather than deriving it would be
38
+ // a second copy of a string the kernel does not own.
39
+ const { tools, resources } = emitSurface(modules).mcpSurface;
40
+ const steps = buildDiscoverySequence(tools)
36
41
  .map((stage) => `${stage.stage}. ${stage.tool} — ${stage.purpose}`)
37
42
  .join("\n");
43
+ const templates = resources.map((template) => `\`${template}\``).join(", ");
38
44
 
39
45
  return [
40
- "pragma is a CLI and MCP server over a design-system knowledge graph.",
41
- `${CONVENTIONS.system} ${CONVENTIONS.model} ${CONVENTIONS.querying}`,
46
+ `${CONVENTIONS.system} ${CONVENTIONS.model} ${CONVENTIONS.querying} ${CONVENTIONS.mutations}`,
42
47
  "",
43
48
  "Discovery sequence:",
44
49
  steps,
45
50
  "",
46
- "Call the `capabilities` tool for the full annotated tool catalog; read `pragma:{+uri}` resources (or `graph_inspect`) for entity detail.",
51
+ `Call the \`capabilities\` tool for the full annotated tool catalog; read ${templates} resources (or \`graph_inspect\`) for entity detail.`,
47
52
  ].join("\n");
48
53
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Project a {@link PragmaError} into an SDK `McpError` for the NATIVE MCP
3
+ * surfaces (the `prompts/*` provider, the `pragma:{+uri}` resource browser),
4
+ * which signal failure by throwing a JSON-RPC error rather than by returning the
5
+ * `{ ok, error }` tool envelope.
6
+ *
7
+ * A native read must not swallow a failure into plain-text "success" content: it
8
+ * carries the machine `code` AND the structured `recovery` into the JSON-RPC
9
+ * `data`, so an agent that hits a cold store over `prompts/list` or a resource
10
+ * read sees the SAME `STORE_UNAVAILABLE` + `sources_update` recovery the tool
11
+ * envelope carries — never a bare string with the recovery dropped.
12
+ *
13
+ * The SDK's `McpError`/`ErrorCode` are PASSED IN by the caller (which already
14
+ * dynamic-imported them in its `register`), so this module — reachable on the
15
+ * capabilities import graph — never statically pulls the SDK onto the
16
+ * `--help`/`__complete` fast path.
17
+ */
18
+
19
+ import type { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
20
+ import type { PragmaError } from "../../error/PragmaError.js";
21
+
22
+ /** The SDK error primitives a provider dynamic-imports and hands to {@link mcpErrorFrom}. */
23
+ export interface McpErrorPrimitives {
24
+ readonly McpError: typeof McpError;
25
+ readonly ErrorCode: typeof ErrorCode;
26
+ }
27
+
28
+ /**
29
+ * Build an `McpError` from a {@link PragmaError}, mapping the machine code to a
30
+ * JSON-RPC code and preserving `code`/`recovery`/`suggestions`/`validOptions` in
31
+ * `data`.
32
+ *
33
+ * @param error - The structured pragma error to project.
34
+ * @param primitives - The SDK `McpError`/`ErrorCode` the caller imported.
35
+ * @returns An `McpError` carrying the diagnostic + recovery in its `data`.
36
+ */
37
+ export function mcpErrorFrom(
38
+ error: PragmaError,
39
+ { McpError: McpErrorCtor, ErrorCode: ErrorCodeEnum }: McpErrorPrimitives,
40
+ ): McpError {
41
+ // A bad/missing entity or rejected input is the caller's fault (InvalidParams);
42
+ // an unreachable store or internal fault is the server's (InternalError).
43
+ const jsonRpcCode =
44
+ error.code === "ENTITY_NOT_FOUND" || error.code === "INVALID_INPUT"
45
+ ? ErrorCodeEnum.InvalidParams
46
+ : ErrorCodeEnum.InternalError;
47
+ const data: Record<string, unknown> = { code: error.code };
48
+ if (error.recovery) data.recovery = error.recovery;
49
+ if (error.suggestions.length > 0) data.suggestions = error.suggestions;
50
+ if (error.validOptions) data.validOptions = error.validOptions;
51
+ return new McpErrorCtor(jsonRpcCode, error.message, data);
52
+ }
@@ -1,7 +1,10 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, mkdtempSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
2
4
  import { describe, expect, it } from "vitest";
3
5
  import {
4
6
  fixtureEffectsModule,
7
+ fixturePreviewModule,
5
8
  touchPath,
6
9
  } from "../../../testing/fixtures/fixtureCapability.js";
7
10
  import { projectMcp } from "../../../testing/helpers/projectMcp.js";
@@ -70,3 +73,38 @@ describe("MCP mutation plan-first / confirm (PROTECTED)", () => {
70
73
  expect(existsSync(touchPath(name))).toBe(true);
71
74
  });
72
75
  });
76
+
77
+ describe("MCP plan-first is honest (PRA-104)", () => {
78
+ it("returns the ERROR a confirmed run would hit, not a confident plan", async () => {
79
+ // The CLI twin of this guard lives in cli/dispatch.test.ts: the same
80
+ // mutation, the same missing source, exiting nonzero instead of printing a
81
+ // plan. Both surfaces run one interpreter, so they cannot drift.
82
+ const dir = mkdtempSync(join(tmpdir(), "pragma-graft-mcp-"));
83
+ const missing = join(dir, "gone");
84
+
85
+ const mcp = await projectMcp([fixturePreviewModule], dir);
86
+ const planned = await mcp.callTool("probe_graft", { source: missing });
87
+ await mcp.cleanup();
88
+
89
+ expect(planned.ok).toBe(false);
90
+ expect(JSON.stringify(planned.error)).toContain(missing);
91
+ expect(existsSync(`${missing}.grafted`)).toBe(false);
92
+ });
93
+
94
+ it("returns a plan, and writes nothing, when the read would succeed", async () => {
95
+ const dir = mkdtempSync(join(tmpdir(), "pragma-graft-mcp-"));
96
+ const source = join(dir, "present.txt");
97
+ writeFileSync(source, "content\n");
98
+
99
+ const mcp = await projectMcp([fixturePreviewModule], dir);
100
+ const planned = await mcp.callTool("probe_graft", { source });
101
+ await mcp.cleanup();
102
+
103
+ expect(planned.ok).toBe(true);
104
+ expect(planned.meta).toEqual({ planOnly: true, confirmRequired: true });
105
+ expect((planned.data as { plan: string[] }).plan).toContain(
106
+ `Write file: ${source}.grafted (8 bytes)`,
107
+ );
108
+ expect(readdirSync(dir)).toEqual(["present.txt"]);
109
+ });
110
+ });
@@ -1,13 +1,18 @@
1
1
  import { mkdtempSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
5
  import { afterEach, describe, expect, it } from "vitest";
6
+ import { CONVENTIONS } from "../../../../capabilities/capabilities/catalog.js";
5
7
  import { capabilities } from "../../../../capabilities/index.js";
6
8
  import type { McpHarness } from "../../../../testing/helpers/projectMcp.js";
7
9
  import { projectMcp } from "../../../../testing/helpers/projectMcp.js";
10
+ import type { PragmaRuntime } from "../../../runtime/types.js";
11
+ import { emitSurface } from "../../../spec/emitSurface.js";
8
12
  import type { CapabilityModule } from "../../../spec/types.js";
9
13
  import { buildInstructions, INSTRUCTIONS_MAX_CHARS } from "../instructions.js";
10
14
  import { fillTemplate, promptProvider } from "./provider.js";
15
+ import { readPrompts } from "./source.js";
11
16
 
12
17
  const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-prompts-"));
13
18
 
@@ -39,14 +44,33 @@ describe("instructions — handshake orientation (PROTECTED)", () => {
39
44
  );
40
45
  });
41
46
 
42
- it("derives its conventions from the shared catalog source", () => {
43
- // The system convention string is authored once (capabilities/catalog.ts);
44
- // instructions must carry it verbatim so the two can never diverge.
45
- expect(buildInstructions(capabilities)).toContain(
46
- "design-system knowledge graph",
47
+ it("opens with the shared catalog's conventions, verbatim and once", () => {
48
+ // The conventions are authored once (capabilities/catalog.ts); instructions
49
+ // must OPEN with them, so a second hand-written preamble cannot creep back
50
+ // in and drift from the `capabilities` tool the same handshake carries.
51
+ expect(buildInstructions(capabilities).startsWith(CONVENTIONS.system)).toBe(
52
+ true,
47
53
  );
48
54
  });
49
55
 
56
+ it("quotes the resource templates the MCP surface actually advertises", () => {
57
+ // The `pragma:` scheme is covenant-frozen protocol identity
58
+ // (surface.v2.json). The orientation DERIVES it from the emitted surface
59
+ // instead of hand-copying it, so the two cannot disagree.
60
+ const { resources } = emitSurface(capabilities).mcpSurface;
61
+ expect(resources.length).toBeGreaterThan(0);
62
+ const text = buildInstructions(capabilities);
63
+ for (const template of resources) expect(text).toContain(template);
64
+ });
65
+
66
+ it("states the plan-first/confirm convention in the orientation (D2)", () => {
67
+ // The confirm gate must be surfaced at handshake, not discovered by an agent
68
+ // tripping it. Same single source as the `capabilities` tool's conventions.
69
+ const text = buildInstructions(capabilities);
70
+ expect(text.toLowerCase()).toContain("plan-first");
71
+ expect(text).toContain("confirm: true");
72
+ });
73
+
50
74
  it("orients a cold agent to check/build the store before any store read", () => {
51
75
  // Store-blind guard: the handshake must point a cold agent at the store
52
76
  // pre-check (sources_status → sources_update) BEFORE the sample/query steps,
@@ -83,6 +107,105 @@ describe("MCP handshake — capabilities advertised (PROTECTED)", () => {
83
107
  });
84
108
  });
85
109
 
110
+ /** A runtime whose SPARQL facade always fails with `message`. */
111
+ function createFailingRuntime(message: string): PragmaRuntime {
112
+ return {
113
+ query: {
114
+ sparql: () => Promise.reject(new Error(message)),
115
+ },
116
+ } as unknown as PragmaRuntime;
117
+ }
118
+
119
+ /** Every field at the default layer — the embedded pack answers, so the store IS available. */
120
+ const DEFAULT_ORIGINS = {
121
+ tier: "default",
122
+ channel: "default",
123
+ detail: "default",
124
+ packs: "default",
125
+ stories: "default",
126
+ prefixes: "default",
127
+ } as const;
128
+
129
+ /**
130
+ * The same failing facade, wearing the shape `promptProvider.register` needs:
131
+ * a cwd whose store is AVAILABLE (so `guardStore` passes and the read is what
132
+ * fails) plus a `loadConfig` the readiness check can resolve.
133
+ */
134
+ function createFailingProviderRuntime(
135
+ cwd: string,
136
+ message: string,
137
+ ): PragmaRuntime {
138
+ return {
139
+ cwd,
140
+ loadConfig: async () => ({
141
+ config: { channel: "normal" as const },
142
+ origins: DEFAULT_ORIGINS,
143
+ global: { path: "/nonexistent", exists: false },
144
+ project: { exists: false },
145
+ }),
146
+ query: { sparql: () => Promise.reject(new Error(message)) },
147
+ } as unknown as PragmaRuntime;
148
+ }
149
+
150
+ describe("native prompts/get — a failed read reaches the agent as one", () => {
151
+ it("carries the machine code and recovery, not a bare Error", async () => {
152
+ // The native surface signals failure by THROWING, so a read that fails must
153
+ // be projected the way the resource browser projects its own — with `code`
154
+ // and `recovery` in the JSON-RPC `data`. Otherwise the store diagnosis is
155
+ // lost between `prompt_lookup` (which envelopes it) and `prompts/get`.
156
+ const handlers = new Map<unknown, (request: unknown) => Promise<unknown>>();
157
+ const stubServer = {
158
+ server: {
159
+ registerCapabilities: () => {},
160
+ setRequestHandler: (
161
+ schema: unknown,
162
+ handler: (request: unknown) => Promise<unknown>,
163
+ ) => {
164
+ handlers.set(schema, handler);
165
+ },
166
+ },
167
+ };
168
+ await promptProvider.register(
169
+ stubServer as never,
170
+ createFailingProviderRuntime(freshCwd(), "Prefix not found: ds"),
171
+ );
172
+
173
+ const get = handlers.get(GetPromptRequestSchema);
174
+ expect(get).toBeDefined();
175
+ await expect(
176
+ get?.({ params: { name: "build-a-block" } }),
177
+ ).rejects.toMatchObject({
178
+ data: {
179
+ code: "STORE_UNAVAILABLE",
180
+ recovery: { mcp: { tool: "sources_update" } },
181
+ },
182
+ });
183
+ });
184
+ });
185
+
186
+ describe("readPrompts — a failed read is never an empty graph", () => {
187
+ it("reports an unbound prefix as a store that cannot answer, not as no prompts", async () => {
188
+ // A pack whose store does not know the declared prompt namespace cannot
189
+ // answer, and the actionable form of that is STORE_UNAVAILABLE with the
190
+ // build command — not a silent zero-prompt listing.
191
+ await expect(
192
+ readPrompts(createFailingRuntime("Prefix not found: ds")),
193
+ ).rejects.toMatchObject({
194
+ code: "STORE_UNAVAILABLE",
195
+ recovery: { mcp: { tool: "sources_update" } },
196
+ });
197
+ });
198
+
199
+ it("propagates a query failure instead of returning no prompts", async () => {
200
+ // The exact defect a bare `catch {}` here caused: a malformed declaration
201
+ // becomes a parse error, the parse error becomes `[]`, and the user is told
202
+ // the distribution ships no prompts. It must surface as a failure.
203
+ await expect(
204
+ readPrompts(createFailingRuntime("SPARQL syntax error at line 2")),
205
+ ).rejects.toThrow(/syntax error/);
206
+ });
207
+ });
208
+
86
209
  describe("fillTemplate — argument substitution", () => {
87
210
  it("replaces {{arg}} placeholders and leaves unknowns intact", () => {
88
211
  expect(