@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
package/src/bin.ts CHANGED
@@ -77,7 +77,7 @@ async function main(): Promise<void> {
77
77
  if (
78
78
  !explicitHelp &&
79
79
  rawFormat !== undefined &&
80
- !["plain", "json", "text"].includes(rawFormat)
80
+ !["plain", "llm", "json", "text"].includes(rawFormat)
81
81
  ) {
82
82
  const [
83
83
  { PragmaError },
@@ -89,7 +89,7 @@ async function main(): Promise<void> {
89
89
  import("./kernel/project/cli/exitCodes.js"),
90
90
  ]);
91
91
  const error = PragmaError.invalidInput("format", rawFormat, {
92
- validOptions: ["plain", "json"],
92
+ validOptions: ["plain", "llm", "json"],
93
93
  });
94
94
  const rendered =
95
95
  globalFlags.format === "json"
@@ -129,16 +129,25 @@ async function main(): Promise<void> {
129
129
  return;
130
130
  }
131
131
 
132
- // A real command merges config-declared story packs into the tree (DISPATCH
133
- // only); `--help` stays on the static, storeless capabilities so its budget
134
- // and the golden hold. An invalid config story surfaces as a rendered error.
132
+ // A real command merges the package- and config-declared story packs into the
133
+ // tree (DISPATCH only); `--help` stays on the static, storeless capabilities
134
+ // so its budget and the golden hold. An invalid CONFIG story surfaces as a
135
+ // rendered error; a package story that cannot be used is named on stderr and
136
+ // the command carries on (it is third-party data, and failing here would take
137
+ // `sources update` and `doctor` — the only recoveries — down with it).
135
138
  let modules = capabilities;
136
139
  if (!explicitHelp) {
137
140
  try {
138
141
  const { loadEffectiveModules } = await import(
139
142
  "./kernel/packs/collect.js"
140
143
  );
141
- modules = await loadEffectiveModules(capabilities, process.cwd());
144
+ const effective = await loadEffectiveModules(capabilities, process.cwd());
145
+ modules = effective.modules;
146
+ for (const problem of effective.problems) {
147
+ process.stderr.write(
148
+ `Ignored story ${problem.source}: ${problem.message}\n`,
149
+ );
150
+ }
142
151
  } catch (error) {
143
152
  await renderStartupError(error, globalFlags.format === "json");
144
153
  return;
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Block-noun semantic parity (PROTECTED) — the `block` story `pragma.conf.ts`
3
+ * declares, over the shared block fixture graph (the standard-noun pattern).
4
+ *
5
+ * The `block` noun has no hand-written code left (L-OPEN-9): `list` and
6
+ * `lookup` both compile from the declared story, so parity is asserted directly
7
+ * against the graph. Two halves:
8
+ *
9
+ * - `block list` — the UNFILTERED list. Every block in the store, whatever the
10
+ * tier or channel, including the UNTIERED subcomponent the old hand-written
11
+ * query's required `ds:tier` join dropped (A2, formerly reachable only under
12
+ * `--all-tiers`). The row shape the hand-written verb built in TypeScript —
13
+ * the name fallback, the lowercased type, the tier's local name — is now
14
+ * derived IN SPARQL by `BIND`/`COALESCE`, and this file is what holds those
15
+ * BINDs to the shapes the old `blockList.verb.ts` produced.
16
+ * - `block lookup` — unchanged GraphQL content parity, moved here from the
17
+ * deleted `capabilities/block/parity.test.ts`. Button and Modal resolve with
18
+ * the same content a direct SPARQL oracle returns (summary, guidance,
19
+ * anatomy, tier, modifier families with values, properties, subcomponents).
20
+ * The fixture DECLARES whenToUse/whenNotToUse (which the live graph superseded
21
+ * with ds:usage), so those sections are exercised here.
22
+ */
23
+
24
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
25
+ import type { LookupOutput } from "../kernel/packs/resolveEntity.js";
26
+ import type { PackRow } from "../kernel/packs/types.js";
27
+ import { verbKey } from "../kernel/packs/uniqueness.js";
28
+ import type { PragmaRuntime } from "../kernel/runtime/types.js";
29
+ import type { VerbSpec } from "../kernel/spec/types.js";
30
+ import { BLOCK_PREFIXES, BLOCK_TTL } from "../testing/fixtures/blockGraph.js";
31
+ import { buildFixtureRuntime } from "../testing/helpers/packRuntime.js";
32
+ import { storyModules } from "./distribution.js";
33
+
34
+ const blockModule = storyModules.get("block");
35
+ if (!blockModule) {
36
+ throw new Error('pragma.conf.ts declares no story for "block"');
37
+ }
38
+
39
+ const DS = "https://ds.canonical.com/";
40
+
41
+ const verb = (label: string): VerbSpec =>
42
+ blockModule.verbs.find(
43
+ (v) => verbKey(v.path) === `block ${label}`,
44
+ ) as VerbSpec;
45
+
46
+ let rt: PragmaRuntime;
47
+
48
+ beforeAll(async () => {
49
+ ({ rt } = await buildFixtureRuntime({
50
+ ttl: BLOCK_TTL,
51
+ prefixes: BLOCK_PREFIXES,
52
+ detail: "detailed",
53
+ }));
54
+ });
55
+
56
+ afterAll(async () => {
57
+ (await rt.store.get()).store.dispose();
58
+ });
59
+
60
+ async function lookup(name: string): Promise<Record<string, unknown>> {
61
+ const out = (await verb("lookup").run({ name: [name] }, rt)) as LookupOutput;
62
+ expect(out.errors).toEqual([]);
63
+ return out.results.at(0) as Record<string, unknown>;
64
+ }
65
+
66
+ describe("block list parity — the declared, unfiltered list", () => {
67
+ it("lists EVERY block, including the untiered subcomponent (A2)", async () => {
68
+ // `ds:button.icon` is a `ds:Subcomponent` with NO `ds:tier`. The
69
+ // hand-written query inner-joined `?c ds:tier ?t` in its default view and
70
+ // surfaced it only under `--all-tiers`; the declared query's OPTIONAL join
71
+ // means there is no view it can fall out of.
72
+ const rows = (await verb("list").run({}, rt)) as PackRow[];
73
+ expect(rows.map((row) => row.name)).toEqual([
74
+ "Button",
75
+ "Button Icon",
76
+ "Modal",
77
+ ]);
78
+ });
79
+
80
+ it("derives the summary row shape in SPARQL, matching the old hand-built row", async () => {
81
+ const rows = (await verb("list").run({}, rt)) as PackRow[];
82
+ const byName = new Map(rows.map((row) => [row.name, row]));
83
+ const button = byName.get("Button");
84
+ // `type`: the LOWERCASED local name of the matched class — the BIND that
85
+ // replaced `normalizeType`.
86
+ expect(button?.type).toBe("component");
87
+ expect(byName.get("Button Icon")?.type).toBe("subcomponent");
88
+ // `tier`: the tier IRI's local name — the BIND that replaced `localName`.
89
+ expect(button?.tier).toBe("global");
90
+ expect(button?.uri).toBe(`${DS}button`);
91
+ // GROUP_CONCAT of the family names (order-independent).
92
+ expect(String(button?.modifiers).split(", ").sort()).toEqual([
93
+ "density",
94
+ "importance",
95
+ ]);
96
+ });
97
+
98
+ it("an untiered block simply carries no tier — it is not dropped or blanked", async () => {
99
+ const rows = (await verb("list").run({}, rt)) as PackRow[];
100
+ const icon = rows.find((row) => row.name === "Button Icon");
101
+ expect(icon).toBeDefined();
102
+ expect(icon?.tier).toBeUndefined();
103
+ expect(icon?.uri).toBe(`${DS}button.icon`);
104
+ });
105
+
106
+ it("is CONFIG-INDEPENDENT: a configured tier and channel change nothing", async () => {
107
+ // The signed-off consequence of L-OPEN-9, pinned at the verb: the run body
108
+ // never reads `loadConfig()`, so the same rows come back under any scope.
109
+ const scoped = {
110
+ ...rt,
111
+ loadConfig: async () => ({
112
+ ...(await rt.loadConfig()),
113
+ config: {
114
+ ...(await rt.loadConfig()).config,
115
+ tier: "apps/lxd",
116
+ channel: "experimental" as const,
117
+ },
118
+ }),
119
+ } as PragmaRuntime;
120
+ const scopedRows = (await verb("list").run({}, scoped)) as PackRow[];
121
+ const plainRows = (await verb("list").run({}, rt)) as PackRow[];
122
+ expect(scopedRows).toEqual(plainRows);
123
+ });
124
+
125
+ it("zero rows is a calm empty list, not an error (the declared-family semantics)", async () => {
126
+ // D10-A, measured: `makeListRun` returns `[]` and never throws on
127
+ // emptiness — identical exit-code behaviour to the hand-written verb, which
128
+ // also returned an empty array. Only the empty MESSAGE changed (the pack's
129
+ // `emptyRecovery` replaces the `--all-tiers` hint that no longer exists).
130
+ const { rt: emptyRt } = await buildFixtureRuntime({
131
+ ttl: `
132
+ @prefix ds: <${DS}> .
133
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
134
+ ds:Component a owl:Class .
135
+ `,
136
+ prefixes: BLOCK_PREFIXES,
137
+ });
138
+ try {
139
+ const rows = (await verb("list").run({}, emptyRt)) as PackRow[];
140
+ expect(rows).toEqual([]);
141
+ const plain = verb("list").output.formatters.plain(rows);
142
+ expect(plain).toContain("No blocks in the store.");
143
+ expect(plain).toContain("pragma sources update");
144
+ } finally {
145
+ (await emptyRt.store.get()).store.dispose();
146
+ }
147
+ });
148
+ });
149
+
150
+ describe("block lookup — Button content parity (GraphQL, detailed)", () => {
151
+ it("resolves the full Button spec with content matching the SPARQL oracle", async () => {
152
+ const button = await lookup("Button");
153
+ expect(button.uri).toBe(`${DS}button`);
154
+ expect(button.name).toBe("Button");
155
+ expect(button.tier).toBe(`${DS}global`);
156
+
157
+ // Guidance + anatomy sections — each cross-checked against the graph.
158
+ expect(button.summary).toBe(await oracle(`${DS}button`, "ds:summary"));
159
+ expect(button.whenToUse).toBe(await oracle(`${DS}button`, "ds:whenToUse"));
160
+ expect(button.whenNotToUse).toBe(
161
+ await oracle(`${DS}button`, "ds:whenNotToUse"),
162
+ );
163
+ expect(button.guidelines).toBe(
164
+ await oracle(`${DS}button`, "ds:guidelines"),
165
+ );
166
+ expect(button.anatomyDsl).toBe(
167
+ await oracle(`${DS}button`, "ds:anatomyDsl"),
168
+ );
169
+ expect(button.figmaLink).toBe(await oracle(`${DS}button`, "ds:figmaLink"));
170
+ });
171
+
172
+ it("resolves the nested modifier families with values (inverse union)", async () => {
173
+ const button = await lookup("Button");
174
+ const families = button.modifierFamilies as {
175
+ name: string;
176
+ values?: string[];
177
+ }[];
178
+ expect(families.map((f) => f.name).sort()).toEqual([
179
+ "density",
180
+ "importance",
181
+ ]);
182
+ const importance = families.find((f) => f.name === "importance");
183
+ expect(importance?.values?.sort()).toEqual(["primary", "secondary"]);
184
+ });
185
+
186
+ it("resolves properties and the subtype-scoped subcomponents identity field", async () => {
187
+ const button = await lookup("Button");
188
+ expect(button.properties).toEqual([
189
+ { name: "disabled", type: "boolean", optional: "true" },
190
+ ]);
191
+ expect(button.subcomponents).toEqual([
192
+ { name: "Button Icon", uri: `${DS}button.icon` },
193
+ ]);
194
+ });
195
+
196
+ it("renders every declared section in the llm output", async () => {
197
+ const out = (await verb("lookup").run({ name: ["Button"] }, rt)) as never;
198
+ const llm = verb("lookup").output.formatters.llm(out);
199
+ expect(llm).toContain("## Button");
200
+ expect(llm).toContain("### When to use");
201
+ expect(llm).toContain("### Anatomy (DSL)");
202
+ expect(llm).toContain("### Modifier Families");
203
+ expect(llm).toContain("primary");
204
+ });
205
+ });
206
+
207
+ describe("block lookup — Modal content parity (GraphQL, detailed)", () => {
208
+ it("resolves the full Modal spec (a different block, same document engine)", async () => {
209
+ const modal = await lookup("Modal");
210
+ expect(modal.uri).toBe(`${DS}modal`);
211
+ expect(modal.name).toBe("Modal");
212
+ expect(modal.summary).toBe(await oracle(`${DS}modal`, "ds:summary"));
213
+ expect(modal.whenToUse).toBe(await oracle(`${DS}modal`, "ds:whenToUse"));
214
+ const families = modal.modifierFamilies as {
215
+ name: string;
216
+ values?: string[];
217
+ }[];
218
+ expect(families.map((f) => f.name)).toEqual(["size"]);
219
+ expect(families[0]?.values?.sort()).toEqual(["large", "small"]);
220
+ expect(modal.properties).toEqual([
221
+ { name: "open", type: "boolean", optional: "false" },
222
+ ]);
223
+ // Modal has no subcomponents — the subtype-scoped selection is simply absent.
224
+ expect(modal.subcomponents).toEqual([]);
225
+ });
226
+ });
227
+
228
+ describe("block lookup — disclosure trims to the base view at summary", () => {
229
+ it("summary keeps name/tier/summary and drops the detailed sections/expands", async () => {
230
+ const out = (await verb("lookup").run(
231
+ { name: ["Button"] },
232
+ { ...rt, globalFlags: { ...rt.globalFlags, detail: "summary" } },
233
+ )) as LookupOutput;
234
+ const button = out.results.at(0) as Record<string, unknown>;
235
+ expect(button.name).toBe("Button");
236
+ expect(button.summary).toBeDefined();
237
+ expect(button.whenToUse).toBeUndefined();
238
+ expect(button.anatomyDsl).toBeUndefined();
239
+ expect(button.modifierFamilies).toBeUndefined();
240
+ expect(button.figmaLink).toBeUndefined();
241
+ });
242
+ });
243
+
244
+ /** Oracle: a single scalar from the graph, queried directly. */
245
+ async function oracle(subject: string, predicate: string): Promise<string> {
246
+ const result = await rt.query.sparql(
247
+ `SELECT ?v WHERE { <${subject}> ${predicate} ?v }`,
248
+ );
249
+ return result.type === "select" ? (result.bindings[0]?.v ?? "") : "";
250
+ }
@@ -6,6 +6,7 @@
6
6
  * (what the MCP tool returns through the envelope).
7
7
  */
8
8
 
9
+ import { BIN_NAME } from "../../constants.js";
9
10
  import type { Formatters } from "../../kernel/spec/types.js";
10
11
  import type { CapabilitiesData, ToolCategory } from "./types.js";
11
12
 
@@ -19,7 +20,7 @@ const CATEGORY_ORDER: readonly ToolCategory[] = [
19
20
 
20
21
  export const capabilitiesFormatters: Formatters<CapabilitiesData> = {
21
22
  plain(data) {
22
- const lines: string[] = [`pragma v${data.version}`, ""];
23
+ const lines: string[] = [`${BIN_NAME} v${data.version}`, ""];
23
24
 
24
25
  lines.push("Conventions");
25
26
  lines.push(` ${data.conventions.system}`);
@@ -53,7 +54,7 @@ export const capabilitiesFormatters: Formatters<CapabilitiesData> = {
53
54
  },
54
55
 
55
56
  llm(data) {
56
- const lines: string[] = [`# pragma v${data.version}`, ""];
57
+ const lines: string[] = [`# ${BIN_NAME} v${data.version}`, ""];
57
58
 
58
59
  lines.push("## Conventions");
59
60
  lines.push(`- ${data.conventions.system}`);
@@ -99,10 +99,19 @@ describe("capabilities catalog — grammar-derived, drift-guarded (PROTECTED)",
99
99
  expect(data.limits.condensed_available).toBe(false);
100
100
  });
101
101
 
102
- it("carries the three orientation conventions", () => {
102
+ it("carries the four orientation conventions", () => {
103
103
  expect(data.conventions.system.length).toBeGreaterThan(0);
104
104
  expect(data.conventions.model).toContain("tier");
105
105
  expect(data.conventions.querying).toContain("RDF");
106
+ expect(data.conventions.mutations.length).toBeGreaterThan(0);
107
+ });
108
+
109
+ it("surfaces the plan-first/confirm convention so the confirm gate isn't trial-and-error (D2)", () => {
110
+ // A mutating tool without `confirm` returns a plan and writes nothing; agents
111
+ // must learn this from orientation, not by tripping the gate.
112
+ const { mutations } = data.conventions;
113
+ expect(mutations).toMatch(/plan/i);
114
+ expect(mutations).toContain("confirm");
106
115
  });
107
116
  });
108
117
 
@@ -7,12 +7,21 @@
7
7
  * counts) is deliberately DROPPED here (it would require a store boot at
8
8
  * orientation) and lives in `info` / `config show` / `sources status` instead.
9
9
  *
10
- * `run` is a lazy thunk that dynamic-imports the catalog + the capability
11
- * registry barrel, so building the command tree never pulls the derivation onto
12
- * the `--help`/`__complete` fast path, and the tool never boots the store
13
- * (`store.booted` stays false — the storeless-spy invariant).
10
+ * `run` is a lazy thunk that dynamic-imports the catalog, the capability
11
+ * registry barrel and the dispatch-time merge, so building the command tree
12
+ * never pulls the derivation onto the `--help`/`__complete` fast path, and the
13
+ * tool never boots the store (`store.booted` stays false — the storeless-spy
14
+ * invariant).
15
+ *
16
+ * It reports the EFFECTIVE modules, not the static registry: the MCP server
17
+ * registers its tools from the same merge, so a catalog built from the static
18
+ * set would omit exactly the config- and package-declared nouns `tools/list`
19
+ * advertises — the hand-maintained drift this catalog exists to end. With no
20
+ * declared stories the merge returns the registry by identity, so the payload is
21
+ * unchanged.
14
22
  */
15
23
 
24
+ import { BIN_NAME } from "../../constants.js";
16
25
  import { asVerb } from "../../kernel/spec/asVerb.js";
17
26
  import type { VerbSpec } from "../../kernel/spec/types.js";
18
27
  import { capabilitiesFormatters } from "./capabilities.render.js";
@@ -20,14 +29,16 @@ import type { CapabilitiesData } from "./types.js";
20
29
 
21
30
  const capabilitiesVerb: VerbSpec<Record<string, unknown>, CapabilitiesData> = {
22
31
  path: ["capabilities"],
23
- summary:
24
- "Discover pragma conventions, the annotated tool catalog, and the discovery sequence.",
25
- doc: "Storeless orientation for agents. Returns the conventions (KG / tier-channel / SPARQL model), a 3-stage discovery sequence, and every live tool with a behavioural use_when hint and category — all derived from the live grammar, so it never drifts. Call it first at session start.",
32
+ summary: `Discover ${BIN_NAME} conventions, the annotated tool catalog, and the discovery sequence.`,
33
+ doc: "Storeless orientation for agents. Returns the conventions (KG / tier-channel / SPARQL model), a four-stage discovery sequence, and every live tool with a behavioural use_when hint and category — all derived from the live grammar, so it never drifts. Call it first at session start.",
26
34
  params: [],
27
35
  output: { formatters: capabilitiesFormatters },
28
36
  examples: [
29
- { cmd: "pragma capabilities", note: "the annotated tool catalog" },
30
- { cmd: "pragma capabilities --format json", note: "the structured map" },
37
+ { cmd: `${BIN_NAME} capabilities`, note: "the annotated tool catalog" },
38
+ {
39
+ cmd: `${BIN_NAME} capabilities --format json`,
40
+ note: "the structured map",
41
+ },
31
42
  ],
32
43
  capability: {
33
44
  needsStore: false,
@@ -37,11 +48,18 @@ const capabilitiesVerb: VerbSpec<Record<string, unknown>, CapabilitiesData> = {
37
48
  annotations: { readOnlyHint: true, openWorldHint: false },
38
49
  },
39
50
  },
40
- run: (_params, _runtime) =>
41
- Promise.all([import("./catalog.js"), import("../index.js")]).then(
42
- ([catalog, registry]) =>
43
- catalog.buildCapabilitiesData(registry.capabilities),
44
- ),
51
+ run: async (_params, runtime) => {
52
+ const [catalog, registry, collect] = await Promise.all([
53
+ import("./catalog.js"),
54
+ import("../index.js"),
55
+ import("../../kernel/packs/collect.js"),
56
+ ]);
57
+ const { modules } = await collect.loadEffectiveModules(
58
+ registry.capabilities,
59
+ runtime.cwd,
60
+ );
61
+ return catalog.buildCapabilitiesData(modules);
62
+ },
45
63
  };
46
64
 
47
65
  /** The `capabilities` verb, widened for registry composition. */
@@ -14,7 +14,7 @@
14
14
  * orientation from the SAME source and the two can never diverge.
15
15
  */
16
16
 
17
- import { VERSION } from "../../constants.js";
17
+ import { BIN_NAME, PROGRAM_DESCRIPTION, VERSION } from "../../constants.js";
18
18
  import { emitSurface } from "../../kernel/spec/emitSurface.js";
19
19
  import type { CapabilityModule } from "../../kernel/spec/types.js";
20
20
  import { TOOL_HINTS } from "./hints.js";
@@ -26,16 +26,28 @@ import type {
26
26
  } from "./types.js";
27
27
 
28
28
  /**
29
- * The three orientation conventions, verbatim from the old shell. They describe
30
- * the KG / tier-channel / SPARQL model and are still accurate for v2.
29
+ * The four orientation conventions, and the single source both the
30
+ * `capabilities` tool and the MCP handshake read, so the two cannot contradict
31
+ * each other. `system` projects the distribution's identity rather than naming
32
+ * a domain — the live tool catalog the same handshake carries already says
33
+ * which nouns exist. `querying` is verbatim from the old shell (the SPARQL
34
+ * model is still accurate for v2); `model` stopped claiming reads are
35
+ * tier/channel-SCOPED when the hand-written filtering was removed (L-OPEN-9)
36
+ * — the hierarchy is graph data now, not a query gate; `mutations` is new in
37
+ * v2, surfacing the plan-first/confirm gate.
31
38
  */
32
39
  export const CONVENTIONS = {
33
- system:
34
- "Pragma is a CLI and MCP server for querying a design system knowledge graph — blocks, tokens, modifiers, standards, and ontologies.",
40
+ // `help` is authored as a bare phrase (`--help` renders it as one), so the
41
+ // self-description trails in parentheses rather than after a period this
42
+ // string would have to add — a fork writing "Explore the recipe graph."
43
+ // otherwise reads "recipe graph.. A CLI and MCP server…".
44
+ system: `${BIN_NAME} — ${PROGRAM_DESCRIPTION} (a CLI and MCP server over a knowledge graph).`,
35
45
  model:
36
- "Data is scoped by tier (hierarchical, e.g. global > apps > apps/lxd) and channel (normal, experimental, prerelease). Set these via config_tier and config_channel.",
46
+ "The graph models a tier hierarchy (e.g. global > apps > apps/lxd) and release channels (normal, experimental, prerelease) as DATA on each entity. Reads are unscoped — every list shows everything; filter by inspecting an entity's tier/release fields or with graph_query.",
37
47
  querying:
38
- "All queries run against an RDF triple store. Prefixed IRIs (e.g. ds:global.component.button) identify entities. Use ontology_list to discover namespaces.",
48
+ "All queries run against an RDF triple store. Prefixed IRIs (e.g. prefix:name) identify entities. Use ontology_list to discover the active namespaces.",
49
+ mutations:
50
+ "Mutating tools are plan-first: call once WITHOUT confirm to get a plan (meta.planOnly, no writes), then repeat the call with confirm: true to execute.",
39
51
  } as const;
40
52
 
41
53
  /** The output modes v2 renders (dropped "text" → "plain"; condensed retired). */
@@ -79,7 +91,7 @@ export function buildDiscoverySequence(
79
91
  stage: 2,
80
92
  tool: "sources_status",
81
93
  purpose:
82
- "Confirm the store is built before any query. If it reports unavailable, call sources_update (confirm: true) first — otherwise the sample and domain reads below fail with STORE_UNAVAILABLE.",
94
+ "See which pack is answering. A fresh install answers reads from the snapshot embedded in the binary and needs no build; only an `unavailable` status requires sources_update (confirm: true), which is a project that declared its own packs and has not built them.",
83
95
  },
84
96
  {
85
97
  stage: 3,
@@ -7,11 +7,12 @@
7
7
  * exists, or a hint's category disagrees with the tool's real `mutates` flag.
8
8
  *
9
9
  * `use_when` strings for the surviving tools are ported verbatim from the old
10
- * shell's `domains/llm/data/toolCatalog.ts`; the net-new v2 tools (config_detail,
11
- * graph_query, setup, upgrade, doctor, capabilities, prompt_*, token_add-config,
12
- * sources_*, tier_lookup, create_*) are authored here.
10
+ * shell's `domains/llm/data/toolCatalog.ts`; the net-new v2 tools (config_set,
11
+ * graph_query, setup, upgrade, doctor, capabilities, prompt_*, sources_*,
12
+ * tier_lookup, create_*) are authored here.
13
13
  */
14
14
 
15
+ import { BIN_NAME } from "../../constants.js";
15
16
  import type { ToolHint } from "./types.js";
16
17
 
17
18
  /**
@@ -36,14 +37,14 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
36
37
  },
37
38
  info: {
38
39
  category: "diagnostic",
39
- use_when: "Checking pragma version, configuration summary, and store state",
40
+ use_when: `Checking ${BIN_NAME} version, configuration summary, and store state`,
40
41
  },
41
42
 
42
43
  // — Read ——————————————————————————————————————————————————————————————————
43
44
  block_list: {
44
45
  category: "read",
45
46
  use_when:
46
- "Browsing available components, patterns, layouts under current tier/channel",
47
+ "Browsing all components, patterns, layouts, and subcomponents in the store",
47
48
  },
48
49
  block_lookup: {
49
50
  category: "read",
@@ -57,12 +58,12 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
57
58
  },
58
59
  colophon: {
59
60
  category: "read",
60
- use_when:
61
- "Understanding how pragma and the active domain are built — the toolchain + design-system colophon, for onboarding or a demo",
61
+ use_when: `Understanding how ${BIN_NAME} and the active domain are built — the toolchain + domain colophon, for onboarding or a demo`,
62
62
  },
63
63
  config_show: {
64
64
  category: "read",
65
- use_when: "Checking active tier and channel before querying",
65
+ use_when:
66
+ "Checking the active configuration (tier, channel, detail) and which layer set each field",
66
67
  },
67
68
  graph_inspect: {
68
69
  category: "read",
@@ -93,15 +94,19 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
93
94
  use_when:
94
95
  "Discovering loaded namespaces, prefixes, and class/property counts",
95
96
  },
97
+ ontology_lookup: {
98
+ category: "read",
99
+ use_when:
100
+ "Exploring the full schema of a namespace by name — classes, properties, and hierarchy",
101
+ },
96
102
  ontology_show: {
97
103
  category: "read",
98
104
  use_when:
99
- "Exploring the full schema of a namespace — classes, properties, and hierarchy",
105
+ "Deprecated alias of ontology_lookup — exploring a namespace's schema (prefer ontology_lookup)",
100
106
  },
101
107
  prompt_list: {
102
108
  category: "read",
103
- use_when:
104
- "Browsing the workflow prompt templates the design-system graph offers",
109
+ use_when: "Browsing the workflow prompt templates the active graph offers",
105
110
  },
106
111
  prompt_lookup: {
107
112
  category: "read",
@@ -166,29 +171,14 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
166
171
  },
167
172
 
168
173
  // — Write ——————————————————————————————————————————————————————————————————
169
- config_tier: {
170
- category: "write",
171
- use_when: "Setting or resetting the active tier filter",
172
- },
173
- config_channel: {
174
- category: "write",
175
- use_when:
176
- "Setting the release channel (normal, experimental, or prerelease)",
177
- },
178
- config_detail: {
179
- category: "write",
180
- use_when:
181
- "Setting the default progressive-disclosure level (summary, standard, detailed)",
182
- },
183
174
  config_set: {
184
175
  category: "write",
185
176
  use_when:
186
- "Setting any config field by name in one call — tier, channel, or detail",
177
+ "Setting any config field by name — tier, channel, or detail (e.g. `config set tier apps/lxd`)",
187
178
  },
188
179
  create_component: {
189
180
  category: "write",
190
- use_when:
191
- "Scaffolding a new design-system component (React, Svelte, or Lit)",
181
+ use_when: "Scaffolding a new component (React, Svelte, or Lit)",
192
182
  },
193
183
  create_package: {
194
184
  category: "write",
@@ -202,20 +192,15 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
202
192
  },
203
193
  setup: {
204
194
  category: "write",
205
- use_when:
206
- "Installing pragma's shell completions, MCP config, skills, and LSP into the environment",
195
+ use_when: `Installing ${BIN_NAME}'s shell completions, MCP config, skills, and LSP into the environment`,
207
196
  },
208
197
  sources_update: {
209
198
  category: "write",
210
199
  use_when:
211
- "Building or refreshing the knowledge-graph pack from the configured packages",
212
- },
213
- "token_add-config": {
214
- category: "write",
215
- use_when: "Generating a tokens.config.mjs for the terrazzo token pipeline",
200
+ "Building or refreshing the knowledge-graph pack from the configured packs",
216
201
  },
217
202
  upgrade: {
218
203
  category: "write",
219
- use_when: "Upgrading the pragma CLI itself to the latest release",
204
+ use_when: `Upgrading the ${BIN_NAME} CLI itself to the latest release`,
220
205
  },
221
206
  };
@@ -47,6 +47,8 @@ export interface CapabilitiesData {
47
47
  readonly system: string;
48
48
  readonly model: string;
49
49
  readonly querying: string;
50
+ /** The plan-first/confirm gate every mutating tool follows (D2). */
51
+ readonly mutations: string;
50
52
  };
51
53
  readonly discovery_sequence: readonly DiscoveryStage[];
52
54
  readonly tools: readonly CatalogTool[];