@canonical/pragma-cli 0.38.0 → 0.40.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 (330) hide show
  1. package/README.md +1 -1
  2. package/dist/package.json +14 -14
  3. package/dist/pragma.conf.js +273 -176
  4. package/dist/pragma.conf.js.map +1 -1
  5. package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
  6. package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
  7. package/dist/src/capabilities/capabilities/catalog.js +47 -51
  8. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  9. package/dist/src/capabilities/capabilities/index.js +2 -0
  10. package/dist/src/capabilities/capabilities/index.js.map +1 -1
  11. package/dist/src/capabilities/capabilities/types.js +5 -5
  12. package/dist/src/capabilities/colophon/colophon.render.js +7 -4
  13. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  14. package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
  15. package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
  16. package/dist/src/capabilities/config/get.verb.js +2 -0
  17. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  18. package/dist/src/capabilities/config/runField.js +2 -2
  19. package/dist/src/capabilities/config/runField.js.map +1 -1
  20. package/dist/src/capabilities/config/set.verb.js +2 -0
  21. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  22. package/dist/src/capabilities/config/show.verb.js +1 -0
  23. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  24. package/dist/src/capabilities/config/unset.verb.js +2 -0
  25. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  26. package/dist/src/capabilities/create/constants.js +2 -2
  27. package/dist/src/capabilities/create/create.verb.js +14 -4
  28. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  29. package/dist/src/capabilities/distribution.js +3 -1
  30. package/dist/src/capabilities/distribution.js.map +1 -1
  31. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  32. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  33. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  34. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  35. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  37. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  38. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  39. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  40. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  41. package/dist/src/capabilities/graph/query.render.js +37 -5
  42. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  43. package/dist/src/capabilities/graph/query.verb.js +2 -0
  44. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/queryText.js +28 -0
  46. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  47. package/dist/src/capabilities/graph/runConnect.js +3 -3
  48. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  49. package/dist/src/capabilities/graph/runQuery.js +8 -6
  50. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  51. package/dist/src/capabilities/info/info.verb.js +2 -0
  52. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  53. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  54. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  55. package/dist/src/capabilities/ontology/verbs.js +7 -10
  56. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  57. package/dist/src/capabilities/prompt/verbs.js +5 -4
  58. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  59. package/dist/src/capabilities/resources/provider.js +6 -3
  60. package/dist/src/capabilities/resources/provider.js.map +1 -1
  61. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  62. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  63. package/dist/src/capabilities/shared/calls.js +16 -0
  64. package/dist/src/capabilities/shared/calls.js.map +1 -0
  65. package/dist/src/capabilities/skill/render.js +7 -5
  66. package/dist/src/capabilities/skill/render.js.map +1 -1
  67. package/dist/src/capabilities/skill/verbs.js +5 -4
  68. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  69. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  70. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  71. package/dist/src/capabilities/sources/index.js +4 -3
  72. package/dist/src/capabilities/sources/index.js.map +1 -1
  73. package/dist/src/capabilities/sources/reset.render.js +37 -0
  74. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  75. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  76. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  77. package/dist/src/capabilities/sources/runReset.js +59 -0
  78. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  79. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  80. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  81. package/dist/src/capabilities/sources/status.render.js +24 -8
  82. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  83. package/dist/src/capabilities/sources/status.verb.js +1 -0
  84. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  85. package/dist/src/capabilities/sources/update.verb.js +2 -0
  86. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  87. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  88. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  89. package/dist/src/capabilities/version/version.verb.js +1 -0
  90. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  91. package/dist/src/constants.js +4 -3
  92. package/dist/src/constants.js.map +1 -1
  93. package/dist/src/kernel/error/index.js +1 -1
  94. package/dist/src/kernel/error/index.js.map +1 -1
  95. package/dist/src/kernel/error/recovery.js +24 -42
  96. package/dist/src/kernel/error/recovery.js.map +1 -1
  97. package/dist/src/kernel/packs/collect.js +123 -9
  98. package/dist/src/kernel/packs/collect.js.map +1 -1
  99. package/dist/src/kernel/packs/compile.js +74 -15
  100. package/dist/src/kernel/packs/compile.js.map +1 -1
  101. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  102. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  103. package/dist/src/kernel/packs/iri.js +2 -4
  104. package/dist/src/kernel/packs/iri.js.map +1 -1
  105. package/dist/src/kernel/packs/renderPack.js +90 -50
  106. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  107. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  108. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  109. package/dist/src/kernel/packs/runBodies.js +89 -30
  110. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  111. package/dist/src/kernel/packs/schema.js +66 -12
  112. package/dist/src/kernel/packs/schema.js.map +1 -1
  113. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  114. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  115. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  116. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  117. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  118. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  119. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  120. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  121. package/dist/src/kernel/packs/storyRules.js +31 -4
  122. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  123. package/dist/src/kernel/packs/tierScope.js +3 -3
  124. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  125. package/dist/src/kernel/packs/types.js +4 -0
  126. package/dist/src/kernel/packs/types.js.map +1 -1
  127. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  128. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  129. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  130. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  131. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  132. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  133. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  134. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  135. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  136. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  137. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  138. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  139. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  140. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  141. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  142. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  143. package/dist/src/kernel/render/renderers.js +3 -3
  144. package/dist/src/kernel/render/turtle.js +21 -4
  145. package/dist/src/kernel/render/turtle.js.map +1 -1
  146. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  147. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  148. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  149. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  150. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  151. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  152. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  153. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  154. package/dist/src/kernel/runtime/loadSession.js +8 -1
  155. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  156. package/dist/src/kernel/runtime/packVersion.js +75 -0
  157. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  158. package/dist/src/kernel/runtime/readEntity.js +43 -4
  159. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  160. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  161. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  162. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  163. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  164. package/dist/src/kernel/spec/call.js +167 -0
  165. package/dist/src/kernel/spec/call.js.map +1 -0
  166. package/dist/src/kernel/spec/emitReference.js +9 -13
  167. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  168. package/dist/src/kernel/spec/emitSurface.js +8 -1
  169. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  170. package/dist/src/kernel/spec/guidance.js +63 -0
  171. package/dist/src/kernel/spec/guidance.js.map +1 -0
  172. package/dist/src/kernel/spec/wireType.js +36 -0
  173. package/dist/src/kernel/spec/wireType.js.map +1 -0
  174. package/package.json +14 -14
  175. package/pragma.conf.ts +294 -177
  176. package/src/capabilities/block.parity.test.ts +1 -1
  177. package/src/capabilities/callRule.test.ts +147 -0
  178. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  179. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  180. package/src/capabilities/capabilities/catalog.ts +60 -55
  181. package/src/capabilities/capabilities/index.ts +2 -0
  182. package/src/capabilities/capabilities/types.ts +16 -19
  183. package/src/capabilities/colophon/colophon.render.ts +9 -5
  184. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  185. package/src/capabilities/config/get.verb.ts +2 -0
  186. package/src/capabilities/config/runField.ts +3 -3
  187. package/src/capabilities/config/set.verb.ts +3 -0
  188. package/src/capabilities/config/show.verb.ts +2 -0
  189. package/src/capabilities/config/unset.verb.ts +2 -0
  190. package/src/capabilities/create/constants.ts +2 -2
  191. package/src/capabilities/create/create.verb.ts +16 -0
  192. package/src/capabilities/create/createGrammar.test.ts +1 -1
  193. package/src/capabilities/distribution.test.ts +6 -3
  194. package/src/capabilities/distribution.ts +6 -0
  195. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  196. package/src/capabilities/doctor/doctor.test.ts +38 -1
  197. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  198. package/src/capabilities/emptyRecovery.test.ts +5 -0
  199. package/src/capabilities/graph/connect.verb.ts +2 -0
  200. package/src/capabilities/graph/inspect.render.ts +9 -1
  201. package/src/capabilities/graph/inspect.test.ts +73 -0
  202. package/src/capabilities/graph/inspect.verb.ts +2 -0
  203. package/src/capabilities/graph/query.render.test.ts +35 -0
  204. package/src/capabilities/graph/query.render.ts +45 -7
  205. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  206. package/src/capabilities/graph/query.verb.ts +2 -0
  207. package/src/capabilities/graph/queryText.test.ts +55 -1
  208. package/src/capabilities/graph/queryText.ts +47 -0
  209. package/src/capabilities/graph/runConnect.ts +4 -5
  210. package/src/capabilities/graph/runQuery.ts +23 -9
  211. package/src/capabilities/info/info.verb.ts +2 -0
  212. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  213. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  214. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  215. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  216. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  217. package/src/capabilities/meta/mcp.verb.ts +2 -0
  218. package/src/capabilities/ontology/ontology.test.ts +24 -0
  219. package/src/capabilities/ontology/verbs.ts +16 -12
  220. package/src/capabilities/prompt/verbs.ts +10 -4
  221. package/src/capabilities/resources/provider.ts +7 -3
  222. package/src/capabilities/resources/resources.test.ts +2 -1
  223. package/src/capabilities/setup/setup.verb.ts +16 -0
  224. package/src/capabilities/shared/calls.ts +20 -0
  225. package/src/capabilities/skill/render.ts +9 -6
  226. package/src/capabilities/skill/verbs.ts +8 -4
  227. package/src/capabilities/sources/collectStatus.ts +6 -1
  228. package/src/capabilities/sources/index.ts +4 -3
  229. package/src/capabilities/sources/reset.render.test.ts +33 -0
  230. package/src/capabilities/sources/reset.render.ts +43 -0
  231. package/src/capabilities/sources/reset.verb.ts +59 -0
  232. package/src/capabilities/sources/runReset.ts +65 -0
  233. package/src/capabilities/sources/runUpdate.ts +12 -11
  234. package/src/capabilities/sources/sources.test.ts +158 -3
  235. package/src/capabilities/sources/status.render.test.ts +63 -1
  236. package/src/capabilities/sources/status.render.ts +25 -8
  237. package/src/capabilities/sources/status.verb.ts +2 -0
  238. package/src/capabilities/sources/types.ts +26 -0
  239. package/src/capabilities/sources/update.verb.ts +3 -0
  240. package/src/capabilities/surface.test.ts +2 -2
  241. package/src/capabilities/token.parity.test.ts +3 -2
  242. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  243. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  244. package/src/capabilities/variable.parity.test.ts +2 -1
  245. package/src/capabilities/version/version.verb.ts +1 -0
  246. package/src/capabilities/wireContract.test.ts +110 -0
  247. package/src/constants.ts +4 -3
  248. package/src/kernel/copy.test.ts +1 -1
  249. package/src/kernel/error/PragmaError.test.ts +7 -3
  250. package/src/kernel/error/index.ts +1 -1
  251. package/src/kernel/error/recovery.test.ts +52 -18
  252. package/src/kernel/error/recovery.ts +25 -46
  253. package/src/kernel/error/types.ts +1 -1
  254. package/src/kernel/packs/collect.test.ts +79 -0
  255. package/src/kernel/packs/collect.ts +151 -11
  256. package/src/kernel/packs/compile.test.ts +107 -10
  257. package/src/kernel/packs/compile.ts +100 -11
  258. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  259. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  260. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  261. package/src/kernel/packs/iri.ts +5 -4
  262. package/src/kernel/packs/listNotice.test.ts +1 -1
  263. package/src/kernel/packs/nounLink.test.ts +385 -0
  264. package/src/kernel/packs/renderPack.ts +137 -57
  265. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  266. package/src/kernel/packs/resolveEntity.ts +281 -101
  267. package/src/kernel/packs/runBodies.ts +133 -31
  268. package/src/kernel/packs/schema.ts +77 -12
  269. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  270. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  271. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  272. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  273. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  274. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  275. package/src/kernel/packs/storyRules.ts +37 -4
  276. package/src/kernel/packs/tierScope.test.ts +120 -6
  277. package/src/kernel/packs/tierScope.ts +5 -7
  278. package/src/kernel/packs/toolDescription.test.ts +59 -74
  279. package/src/kernel/packs/types.ts +93 -11
  280. package/src/kernel/project/cli/buildProgram.ts +4 -0
  281. package/src/kernel/project/cli/dispatch.ts +34 -8
  282. package/src/kernel/project/cli/suggest.test.ts +77 -0
  283. package/src/kernel/project/cli/suggestNames.ts +47 -1
  284. package/src/kernel/project/cli/verbHelp.ts +16 -19
  285. package/src/kernel/project/mcp/buildServer.ts +15 -4
  286. package/src/kernel/project/mcp/envelope.ts +3 -2
  287. package/src/kernel/project/mcp/instructions.ts +123 -35
  288. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  289. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  290. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  291. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  292. package/src/kernel/render/renderers.test.ts +2 -2
  293. package/src/kernel/render/renderers.ts +3 -3
  294. package/src/kernel/render/turtle.ts +24 -4
  295. package/src/kernel/runtime/graphpack/build.ts +58 -1
  296. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  297. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  298. package/src/kernel/runtime/graphpack/read.ts +4 -6
  299. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  300. package/src/kernel/runtime/graphpack/types.ts +13 -0
  301. package/src/kernel/runtime/loadSession.ts +8 -1
  302. package/src/kernel/runtime/packVersion.test.ts +61 -0
  303. package/src/kernel/runtime/packVersion.ts +84 -0
  304. package/src/kernel/runtime/readEntity.ts +61 -6
  305. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  306. package/src/kernel/runtime/resolveSources.ts +91 -10
  307. package/src/kernel/runtime/storeReadiness.ts +4 -4
  308. package/src/kernel/runtime/types.ts +12 -0
  309. package/src/kernel/spec/call.test.ts +95 -0
  310. package/src/kernel/spec/call.ts +186 -0
  311. package/src/kernel/spec/emitReference.ts +9 -13
  312. package/src/kernel/spec/emitSurface.ts +9 -1
  313. package/src/kernel/spec/guidance.test.ts +49 -0
  314. package/src/kernel/spec/guidance.ts +68 -0
  315. package/src/kernel/spec/index.ts +4 -0
  316. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  317. package/src/kernel/spec/types.ts +64 -7
  318. package/src/kernel/spec/wireType.ts +53 -0
  319. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  320. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  321. package/src/testing/behavioral/parity.test.ts +40 -0
  322. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  323. package/src/testing/eval/cases/assembly.ts +4 -3
  324. package/src/testing/eval/cases/stable.ts +2 -3
  325. package/src/testing/helpers/callSchema.ts +40 -0
  326. package/src/testing/helpers/parity.ts +35 -2
  327. package/src/testing/setupCallChecking.ts +30 -0
  328. package/dist/src/capabilities/capabilities/hints.js +0 -223
  329. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  330. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -137,7 +137,7 @@ describe("block list parity — the declared, tier-scoped list", () => {
137
137
  {},
138
138
  withConfig("global", "experimental"),
139
139
  )) as PackPage;
140
- expect(scoped.scope).toEqual({ tiers: ["global"] });
140
+ expect(scoped.scope?.tiers).toEqual(["global"]);
141
141
  expect(scoped.rows.map((row) => row.name)).toEqual([
142
142
  "Button",
143
143
  "Button Icon",
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The rule every tool is held to.
3
+ *
4
+ * 1. Every registered verb states the question it answers (`useWhen`, as a
5
+ * bare clause), and declares an example call when it has a required param —
6
+ * a verb callable with no arguments has nothing an example would teach.
7
+ * 2. Every call anywhere names a registered verb and carries params that
8
+ * validate against that verb's OWN MCP input schema, so none can rot.
9
+ *
10
+ * The verbs, their examples and the stories' empty-state recoveries are DERIVED
11
+ * from the registry and `declaredStories`. The generic dead ends are constants
12
+ * their renderers export, and are imported below by name: a new one has to be
13
+ * added here. Error recoveries are built where they are thrown and cannot be
14
+ * listed at all; `testing/setupCallChecking.ts` holds each to the same
15
+ * validator as it is constructed, and the last case proves that is live.
16
+ */
17
+
18
+ import { describe, expect, it } from "vitest";
19
+ import { DEFAULT_EMPTY_RECOVERY } from "../kernel/packs/renderPack.js";
20
+ import { renderCall } from "../kernel/spec/call.js";
21
+ import { exampleCall } from "../kernel/spec/guidance.js";
22
+ import type { Call, VerbSpec } from "../kernel/spec/index.js";
23
+ import { wireType } from "../kernel/spec/wireType.js";
24
+ import { findCallProblem } from "../testing/helpers/callSchema.js";
25
+ import { declaredStories } from "./distribution.js";
26
+ import { EMPTY_QUERY_CALLS } from "./graph/query.render.js";
27
+ import { capabilities } from "./index.js";
28
+ import {
29
+ BUILD_STORE_CALL,
30
+ LINK_SKILLS_CALL,
31
+ VERBOSE_BUILD_CALL,
32
+ } from "./shared/calls.js";
33
+
34
+ const verbs: readonly VerbSpec[] = capabilities
35
+ .flatMap((module) => module.verbs)
36
+ .filter((verb) => !verb.hidden);
37
+ const byPath = new Map(verbs.map((verb) => [verb.path.join(" "), verb]));
38
+ const findProblem = (call: Call): string | undefined =>
39
+ findCallProblem(call, byPath.get(call.verb));
40
+ const label = (verb: VerbSpec): string => verb.path.join(" ");
41
+
42
+ const storyEmpties = [...declaredStories.values()].flatMap((story) =>
43
+ [story.list, ...(story.verbs ?? [])].flatMap((half) =>
44
+ half?.emptyRecovery?.call ? [half.emptyRecovery.call] : [],
45
+ ),
46
+ );
47
+
48
+ describe("every tool states the question it answers", () => {
49
+ it("every registered verb declares useWhen, as a clause the prose can lead", () => {
50
+ expect(verbs.filter((verb) => !verb.useWhen).map(label)).toEqual([]);
51
+ // The lead ("Use …") is added where prose needs it; stored, it would be
52
+ // said twice in the catalogue's `use_when` field.
53
+ expect(
54
+ verbs.filter((verb) => /^use\b|\.$/i.test(verb.useWhen ?? "")).map(label),
55
+ ).toEqual([]);
56
+ });
57
+
58
+ it("every verb with a required param declares an example, and none is empty", () => {
59
+ const needs = verbs.filter((verb) => verb.params.some((p) => p.required));
60
+ expect(needs.filter((verb) => !verb.example).map(label)).toEqual([]);
61
+ expect(
62
+ verbs
63
+ .filter(
64
+ (verb) => verb.example && Object.keys(verb.example).length === 0,
65
+ )
66
+ .map(label),
67
+ ).toEqual([]);
68
+ });
69
+ });
70
+
71
+ describe("an example teaches batching", () => {
72
+ it("shows every list-accepting param as an array of at least two distinct values", () => {
73
+ // Recorded: a model that knew `symbol` was a list still made twenty calls
74
+ // of one symbol each — the example it had been shown held a single string.
75
+ const single = verbs.flatMap((verb) =>
76
+ verb.params
77
+ .filter((param) => wireType(param).list && verb.example?.[param.name])
78
+ .filter((param) => {
79
+ const value = verb.example?.[param.name];
80
+ return !Array.isArray(value) || new Set(value).size < 2;
81
+ })
82
+ .map((param) => `${label(verb)} { ${param.name} }`),
83
+ );
84
+ expect(single).toEqual([]);
85
+ });
86
+ });
87
+
88
+ describe("every call names a registered verb and params its schema accepts", () => {
89
+ const calls: readonly (readonly [string, Call])[] = [
90
+ ...verbs.flatMap((verb) => {
91
+ const call = exampleCall(verb);
92
+ return call ? [[`example of ${call.verb}`, call] as const] : [];
93
+ }),
94
+ ...storyEmpties.map(
95
+ (call) => [`story empty → ${call.verb}`, call] as const,
96
+ ),
97
+ ["default empty", DEFAULT_EMPTY_RECOVERY.call as Call],
98
+ ["empty query → catalogue", EMPTY_QUERY_CALLS.catalogue],
99
+ ["empty query → namespaces", EMPTY_QUERY_CALLS.namespaces],
100
+ ["build the store", BUILD_STORE_CALL],
101
+ ["build the store, verbosely", VERBOSE_BUILD_CALL],
102
+ ["link skills", LINK_SKILLS_CALL],
103
+ ];
104
+
105
+ it("holds for every example, story empty and generic dead end", () => {
106
+ const offenders = calls.flatMap(([name, call]) => {
107
+ const problem = findProblem(call);
108
+ return problem ? [`${name}: ${problem}`] : [];
109
+ });
110
+ expect(offenders).toEqual([]);
111
+ // Guard against a vacuous pass: the stories do declare empties.
112
+ expect(storyEmpties.length).toBeGreaterThan(0);
113
+ });
114
+
115
+ it("the validator is the verb's tool schema: it knows confirm and detail, and rejects the rest", () => {
116
+ // A next step or an example NEVER skips the plan: a mutating tool is
117
+ // plan-first, and the plan it returns says how to proceed.
118
+ expect(
119
+ findProblem({ verb: "sources update", params: { confirm: true } }),
120
+ ).toMatch(/confirm/i);
121
+ expect(
122
+ findProblem({ verb: "sources update", params: { skipInvalid: true } }),
123
+ ).toBeUndefined();
124
+ expect(
125
+ findProblem({
126
+ verb: "block lookup",
127
+ params: { name: ["Button"], detail: "summary" },
128
+ }),
129
+ ).toBeUndefined();
130
+ expect(
131
+ findProblem({ verb: "block list", params: { confirm: true } }),
132
+ ).toMatch(/confirm/i);
133
+ expect(
134
+ findProblem({ verb: "standard sample", params: { count: 2 } }),
135
+ ).toMatch(/count/);
136
+ });
137
+
138
+ it("checks a recovery as it is built, anywhere in this suite", () => {
139
+ expect(() => renderCall({ verb: "no such verb" }, "cli")).toThrow(
140
+ /names no registered verb/,
141
+ );
142
+ expect(() =>
143
+ renderCall({ verb: "config unset", params: { field: "tier" } }, "mcp"),
144
+ ).toThrow(/Unsound call config unset/);
145
+ expect(() => renderCall({ verb: "graph inspect" }, "cli")).toThrow(/uri/);
146
+ });
147
+ });
@@ -4,12 +4,13 @@ import { join } from "node:path";
4
4
  import { describe, expect, it } from "vitest";
5
5
  import { executeVerb } from "../../kernel/project/cli/dispatch.js";
6
6
  import { bootRuntime } from "../../kernel/runtime/boot.js";
7
- import { emitSurface } from "../../kernel/spec/emitSurface.js";
7
+ import { renderCall } from "../../kernel/spec/call.js";
8
+ import { emitSurface, toolName } from "../../kernel/spec/emitSurface.js";
9
+ import { exampleCall } from "../../kernel/spec/guidance.js";
8
10
  import type { GlobalFlags, VerbSpec } from "../../kernel/spec/types.js";
9
11
  import { capabilities } from "../index.js";
10
12
  import { capabilitiesSelfVerb } from "./capabilities.verb.js";
11
- import { buildCapabilitiesData, liveTools, mutatingTools } from "./catalog.js";
12
- import { TOOL_HINTS } from "./hints.js";
13
+ import { buildCapabilitiesData, liveTools } from "./catalog.js";
13
14
  import type { CapabilitiesData } from "./types.js";
14
15
 
15
16
  const NO_MUT = { dryRun: false, undo: false, yes: false };
@@ -23,27 +24,54 @@ const FLAGS: GlobalFlags = {
23
24
  const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-caps-"));
24
25
  const tools = liveTools(capabilities);
25
26
  const data = buildCapabilitiesData(capabilities);
27
+ /** The tools whose verbs mutate, read straight off the registry. */
28
+ const mutating = new Set(
29
+ capabilities
30
+ .flatMap((module) => module.verbs)
31
+ .filter((verb) => verb.capability.mutates && verb.capability.mcp.expose)
32
+ .map((verb) => toolName(verb.path)),
33
+ );
26
34
 
27
35
  describe("capabilities catalog — grammar-derived, drift-guarded (PROTECTED)", () => {
28
- it("every live tool has a TOOL_HINTS entry (no missing hint)", () => {
29
- const missing = tools.filter((tool) => !(tool in TOOL_HINTS));
30
- expect(missing).toEqual([]);
36
+ // The catalogue has no table of its own to drift: each entry is read off the
37
+ // tool's verb. That every verb DECLARES its guidance is `callRule.test.ts`.
38
+ it("every entry is its own verb's guidance: question, category, example", () => {
39
+ const verbs = new Map(
40
+ capabilities.flatMap((module) =>
41
+ module.verbs.map((verb) => [toolName(verb.path), verb] as const),
42
+ ),
43
+ );
44
+ for (const tool of data.tools) {
45
+ const verb = verbs.get(tool.name) as VerbSpec;
46
+ expect(tool.use_when, tool.name).toBe(verb.useWhen);
47
+ const example = exampleCall(verb);
48
+ expect(tool.example, tool.name).toBe(
49
+ example ? renderCall(example, "mcp") : undefined,
50
+ );
51
+ }
31
52
  });
32
53
 
33
- it("no TOOL_HINTS key is stale (every hint names a live tool)", () => {
34
- const live = new Set(tools);
35
- const stale = Object.keys(TOOL_HINTS).filter((name) => !live.has(name));
36
- expect(stale).toEqual([]);
54
+ it("a verb declaring no guidance (a third-party story may) degrades to its summary", () => {
55
+ const {
56
+ useWhen: _useWhen,
57
+ example: _example,
58
+ ...bare
59
+ } = capabilitiesSelfVerb;
60
+ const [tool] = buildCapabilitiesData([
61
+ { name: "capabilities", verbs: [bare as VerbSpec] },
62
+ ]).tools;
63
+ expect(tool).toEqual({
64
+ name: "capabilities",
65
+ category: "orientation",
66
+ use_when: capabilitiesSelfVerb.summary,
67
+ });
37
68
  });
38
69
 
39
- it("hint categories agree with the live surface (write ⟺ mutates)", () => {
40
- const mutating = mutatingTools(capabilities);
41
- for (const tool of tools) {
42
- const isWrite = TOOL_HINTS[tool]?.category === "write";
43
- expect(
44
- isWrite,
45
- `${tool}: hint category "${TOOL_HINTS[tool]?.category}" vs mutates=${mutating.has(tool)}`,
46
- ).toBe(mutating.has(tool));
70
+ it("categories agree with the live surface (write ⟺ mutates)", () => {
71
+ for (const tool of data.tools) {
72
+ expect(tool.category === "write", tool.name).toBe(
73
+ mutating.has(tool.name),
74
+ );
47
75
  }
48
76
  });
49
77
 
@@ -67,7 +95,7 @@ describe("capabilities catalog — grammar-derived, drift-guarded (PROTECTED)",
67
95
  ).toBe(counts.total);
68
96
  expect(counts.orientation).toBe(1); // exactly `capabilities`
69
97
  expect(counts.diagnostic).toBe(2); // `doctor` + `info`
70
- expect(counts.write).toBe(mutatingTools(capabilities).size);
98
+ expect(counts.write).toBe(mutating.size);
71
99
  });
72
100
 
73
101
  it("the discovery sequence's sample stage lists the live *_sample tools", () => {
@@ -31,6 +31,8 @@ const capabilitiesVerb: VerbSpec<Record<string, unknown>, CapabilitiesData> = {
31
31
  path: ["capabilities"],
32
32
  summary: `Discover ${BIN_NAME} conventions, the annotated tool catalog, and the discovery sequence.`,
33
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.",
34
+ useWhen: "when unsure which tool answers a question",
35
+ category: "orientation",
34
36
  params: [],
35
37
  output: { formatters: capabilitiesFormatters },
36
38
  examples: [
@@ -3,21 +3,28 @@
3
3
  *
4
4
  * `buildCapabilitiesData(modules)` projects the LIVE grammar: it emits the
5
5
  * surface, walks `mcpSurface.tools` (the sorted, covenant-conformant set), and
6
- * annotates each tool from the authored `TOOL_HINTS` table. The tool set, the
6
+ * annotates each tool from the guidance its OWN verb declares (`useWhen`,
7
+ * `example` — see `kernel/spec/guidance.ts`). The tool set, the
7
8
  * category counts, and the discovery-sample list are all DERIVED — never pinned
8
9
  * — so the catalog tracks the surface automatically (the fix for the old shell's
9
10
  * hand-maintained list, which drifted to name retired tools).
10
11
  *
11
12
  * Pure + zod-free: it reads only `emitSurface` (itself fast-path-safe), so the
12
- * verb's storeless guarantee holds. The conventions + discovery strings are
13
- * exported so `kernel/project/mcp/instructions.ts` derives the handshake
14
- * orientation from the SAME source and the two can never diverge.
13
+ * verb's storeless guarantee holds. The conventions + discovery strings live
14
+ * here, on the distribution's side — they name its tools and tiers — and reach
15
+ * the kernel's handshake instructions as data ({@link ORIENTATION}), so the two
16
+ * can never diverge and the kernel names no tool.
15
17
  */
16
18
 
17
19
  import { BIN_NAME, PROGRAM_DESCRIPTION, VERSION } from "../../constants.js";
18
- import type { CapabilityModule } from "../../kernel/spec/index.js";
19
- import { emitSurface } from "../../kernel/spec/index.js";
20
- import { TOOL_HINTS } from "./hints.js";
20
+ import { renderCall } from "../../kernel/spec/call.js";
21
+ import { exampleCall, verbCategory } from "../../kernel/spec/guidance.js";
22
+ import type {
23
+ CapabilityModule,
24
+ McpOrientation,
25
+ VerbSpec,
26
+ } from "../../kernel/spec/index.js";
27
+ import { emitSurface, toolName } from "../../kernel/spec/index.js";
21
28
  import type {
22
29
  CapabilitiesData,
23
30
  CatalogTool,
@@ -26,24 +33,11 @@ import type {
26
33
  } from "./types.js";
27
34
 
28
35
  /**
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); `mutations` is new in v2, surfacing the
35
- * plan-first/confirm gate.
36
- *
37
- * `model` is the one that has had to be rewritten twice, and both times because
38
- * it was describing a gate that had moved. It stopped claiming reads were
39
- * tier/channel-scoped when the hand-written filtering was removed (the
40
- * hierarchy became graph data, not a query gate) — and "Reads are unscoped —
41
- * every list shows everything" became false the day the tier scope landed. An
42
- * agent reading it took a scoped `block_list` for the whole design system,
43
- * which is the exact misreading the sentence existed to prevent. So it now says
44
- * what is true of each half separately: the TIER hierarchy gates a read of a
45
- * tiered noun and names the argument that widens it, while the CHANNEL is still
46
- * data on the entity and gates nothing.
36
+ * The four orientation conventions — the single source both the `capabilities`
37
+ * tool and the MCP handshake read, so the two cannot contradict each other.
38
+ * `model` says what is true of each half separately: the TIER hierarchy scopes
39
+ * a read of a tiered noun and names the argument that widens it; the CHANNEL is
40
+ * data on the entity and scopes nothing.
47
41
  */
48
42
  export const CONVENTIONS = {
49
43
  // `help` is authored as a bare phrase (`--help` renders it as one), so the
@@ -52,31 +46,20 @@ export const CONVENTIONS = {
52
46
  // otherwise reads "recipe graph.. A CLI and MCP server…".
53
47
  system: `${BIN_NAME} — ${PROGRAM_DESCRIPTION} (a CLI and MCP server over a knowledge graph).`,
54
48
  model:
55
- 'The tier hierarchy (global > apps > apps_lxd) SCOPES every read of a tiered entity: lists answer from the top-level tiers, lookups prefer them. Pass tier: "<name>" for a tier plus its ancestors, or tier: "all" for every tier; each answer states its scope. Channels scope nothing.',
49
+ 'Reads of tiered entities are SCOPED to the top-level tiers (global > apps > apps_lxd): pass tier: "<name>" for that tier plus its ancestors, or tier: "all". Each answer states its scope; channels scope nothing.',
56
50
  querying:
57
- "All queries run against an RDF triple store. Prefixed IRIs (e.g. prefix:name) identify entities. Use ontology_list to discover the active namespaces.",
51
+ "The data is an RDF graph; prefixed IRIs (prefix:name) identify entities, and ontology_list names the prefixes that exist.",
58
52
  mutations:
59
53
  "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.",
60
54
  } as const;
61
55
 
62
- /** The output modes v2 renders (dropped "text" → "plain"; condensed retired). */
63
- const OUTPUT_MODES = ["plain", "json", "llm"] as const;
64
-
65
- /** The set of tool names that mutate, read from the emitted surface. */
66
- function mutatingTools(modules: readonly CapabilityModule[]): Set<string> {
67
- const mutating = new Set<string>();
68
- for (const { verbs } of Object.values(emitSurface(modules).nouns)) {
69
- for (const verb of verbs) {
70
- if (verb.mutates && typeof verb.mcp === "string") mutating.add(verb.mcp);
71
- }
72
- }
73
- return mutating;
74
- }
75
-
76
- /** The live sorted tool names the covenant blesses, from the emitted surface. */
77
- export function liveTools(modules: readonly CapabilityModule[]): string[] {
78
- return emitSurface(modules).mcpSurface.tools;
79
- }
56
+ /**
57
+ * The words people use are not the noun the tools carry: an agent looking for a
58
+ * "component" tool finds none. Said exactly twice — here, in the handshake, and
59
+ * in `block_list`'s own `useWhen` — and not a third time in the catalogue.
60
+ */
61
+ export const BLOCKS_SENTENCE =
62
+ "Components, patterns, layouts and subcomponents are all blocks: read every one of them through the block tools (block_list, block_lookup).";
80
63
 
81
64
  /**
82
65
  * Build the discovery sequence, deriving the sample list from the tools that
@@ -100,7 +83,7 @@ export function buildDiscoverySequence(
100
83
  stage: 2,
101
84
  tool: "sources_status",
102
85
  purpose:
103
- "See which pack is answering. A fresh install answers reads from the snapshot shipped with the CLI 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.",
86
+ "See which pack is answering. A fresh install answers reads from the snapshot shipped with the CLI and needs no build; only an `unavailable` status requires sources_update, which is a project that declared its own packs and has not built them.",
104
87
  },
105
88
  {
106
89
  stage: 3,
@@ -111,11 +94,28 @@ export function buildDiscoverySequence(
111
94
  stage: 4,
112
95
  tool: "domain tools",
113
96
  purpose:
114
- "Query specific entities — block_list, standard_lookup, etc. Use the use_when hints above to pick the right tool.",
97
+ "Query specific entities — block_list, standard_lookup, etc. Each tool description opens with the question it answers.",
115
98
  },
116
99
  ];
117
100
  }
118
101
 
102
+ /**
103
+ * What the handshake instructions open with (see
104
+ * `kernel/project/mcp/instructions.ts`, which adds the generated question →
105
+ * tool index and states the plan-first rule beside the write tools it lists).
106
+ */
107
+ export const ORIENTATION: McpOrientation = {
108
+ conventions: [CONVENTIONS.system, CONVENTIONS.model, BLOCKS_SENTENCE],
109
+ };
110
+
111
+ /** The output modes v2 renders (dropped "text" → "plain"; condensed retired). */
112
+ const OUTPUT_MODES = ["plain", "json", "llm"] as const;
113
+
114
+ /** The live sorted tool names the covenant blesses, from the emitted surface. */
115
+ export function liveTools(modules: readonly CapabilityModule[]): string[] {
116
+ return emitSurface(modules).mcpSurface.tools;
117
+ }
118
+
119
119
  /** Tally the catalog tools by category (all counts DERIVED, never pinned). */
120
120
  function countByCategory(tools: readonly CatalogTool[]): ToolCounts {
121
121
  const of = (category: CatalogTool["category"]) =>
@@ -140,15 +140,22 @@ export function buildCapabilitiesData(
140
140
  modules: readonly CapabilityModule[],
141
141
  ): CapabilitiesData {
142
142
  const tools = liveTools(modules);
143
+ const verbs = new Map(
144
+ modules.flatMap((module) =>
145
+ module.verbs.map((verb) => [toolName(verb.path), verb] as const),
146
+ ),
147
+ );
143
148
  const catalogTools: CatalogTool[] = tools.map((name) => {
144
- const hint = TOOL_HINTS[name];
145
- // A missing hint is a drift bug caught by `capabilities.test.ts`; degrade
146
- // to a truthful placeholder rather than throwing inside a tool call.
149
+ // Every emitted tool IS a verb of these modules, so the lookup cannot miss.
150
+ const verb = verbs.get(name) as VerbSpec;
151
+ const example = exampleCall(verb);
152
+ // Missing guidance is a drift bug caught by `callRule.test.ts`; degrade to
153
+ // the summary rather than throwing inside a tool call.
147
154
  return {
148
155
  name,
149
- category: hint?.category ?? "read",
150
- use_when:
151
- hint?.use_when ?? "(no hint authored — see capabilities/hints.ts)",
156
+ category: verbCategory(verb),
157
+ use_when: verb.useWhen ?? verb.summary,
158
+ ...(example ? { example: renderCall(example, "mcp") } : {}),
152
159
  };
153
160
  });
154
161
 
@@ -161,5 +168,3 @@ export function buildCapabilitiesData(
161
168
  limits: { output_modes: OUTPUT_MODES, condensed_available: false },
162
169
  };
163
170
  }
164
-
165
- export { mutatingTools };
@@ -5,9 +5,11 @@
5
5
 
6
6
  import type { CapabilityModule } from "../../kernel/spec/index.js";
7
7
  import { capabilitiesSelfVerb } from "./capabilities.verb.js";
8
+ import { ORIENTATION } from "./catalog.js";
8
9
 
9
10
  /** The `capabilities` capability module (a single storeless self-verb). */
10
11
  export const capabilitiesModule: CapabilityModule = {
11
12
  name: "capabilities",
12
13
  verbs: [capabilitiesSelfVerb],
14
+ mcpOrientation: ORIENTATION,
13
15
  };
@@ -1,34 +1,31 @@
1
1
  /**
2
2
  * Types for the `capabilities` orientation tool.
3
3
  *
4
- * The catalog is DERIVED from the live grammar (`emitSurface`) plus an authored
5
- * hint table — never a hand-maintained tool list — so it cannot drift from the
6
- * real surface (the exact failure mode of the old shell's `TOOL_CATALOG`, which
7
- * still named retired tools). See {@link ToolHint} + `hints.ts` for the one
8
- * authored input, and `catalog.ts` for the derivation.
4
+ * The catalog is DERIVED from the live grammar (`emitSurface`) plus the guidance
5
+ * each verb declares — never a hand-maintained tool list — so it cannot drift
6
+ * from the real surface (the exact failure mode of the old shell's
7
+ * `TOOL_CATALOG`, which still named retired tools). See `catalog.ts` for the
8
+ * derivation.
9
9
  */
10
10
 
11
- /** A tool's behavioural category, used for grouping + counts. */
12
- export type ToolCategory = "read" | "write" | "orientation" | "diagnostic";
11
+ import type { ToolCategory } from "../../kernel/spec/index.js";
13
12
 
14
- /** The one authored fact per tool: its category + a one-line "use when" hint. */
15
- export interface ToolHint {
16
- readonly category: ToolCategory;
17
- readonly use_when: string;
13
+ export type { ToolCategory };
14
+
15
+ /** A single stage in the discovery flow the `capabilities` answer lays out. */
16
+ export interface DiscoveryStage {
17
+ readonly stage: number;
18
+ readonly tool: string;
19
+ readonly purpose: string;
18
20
  }
19
21
 
20
- /** One tool as it appears in the catalog — its live name plus its hint. */
22
+ /** One tool as it appears in the catalog — its live name plus its verb's guidance. */
21
23
  export interface CatalogTool {
22
24
  readonly name: string;
23
25
  readonly category: ToolCategory;
24
26
  readonly use_when: string;
25
- }
26
-
27
- /** A single stage in the discovery flow an agent follows at session start. */
28
- export interface DiscoveryStage {
29
- readonly stage: number;
30
- readonly tool: string;
31
- readonly purpose: string;
27
+ /** One real call, as an MCP tool call; absent only if the verb declares none. */
28
+ readonly example?: string;
32
29
  }
33
30
 
34
31
  /** Tool counts by category (all DERIVED from the live catalog, never pinned). */
@@ -14,9 +14,10 @@
14
14
  * it there is what lets these stay pure.
15
15
  */
16
16
 
17
- import { BIN_NAME } from "../../constants.js";
18
17
  import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
19
- import type { Formatters } from "../../kernel/spec/index.js";
18
+ import { renderNextStep } from "../../kernel/spec/call.js";
19
+ import type { Formatters, Surface } from "../../kernel/spec/index.js";
20
+ import { BUILD_STORE_CALL } from "../shared/calls.js";
20
21
  import { renderMarkdownToTerminal } from "./markdownTerminal.js";
21
22
  import type { ColophonData, ColophonSection } from "./types.js";
22
23
 
@@ -27,7 +28,9 @@ import type { ColophonData, ColophonSection } from "./types.js";
27
28
  * Like every empty state it goes to stderr with exit 0 (a calm success), so a
28
29
  * script reading stdout still reads nothing.
29
30
  */
30
- const EMPTY_NOTICE = `No colophon declared. A colophon comes from an active pack — build the store with \`${BIN_NAME} sources update\`.`;
31
+ function describeEmpty(surface: Surface): string {
32
+ return `No colophon declared. A colophon comes from an active pack — build the store. ${renderNextStep(BUILD_STORE_CALL, surface)}`;
33
+ }
31
34
 
32
35
  /** Render one section (title as H1 + body) as styled terminal Markdown. */
33
36
  function plainSection(section: ColophonSection, style: RenderStyle): string {
@@ -56,7 +59,7 @@ export const colophonFormatters: Formatters<ColophonData> = {
56
59
  // llm returns before the dispatcher's empty-state routing, so the
57
60
  // condensed form carries its own zero-section line (the same shape the
58
61
  // other empty states use) rather than handing an agent zero bytes.
59
- if (data.sections.length === 0) return `_${EMPTY_NOTICE}_`;
62
+ if (data.sections.length === 0) return `_${describeEmpty("cli")}_`;
60
63
  return data.sections.map(llmSection).join("\n\n");
61
64
  },
62
65
 
@@ -66,5 +69,6 @@ export const colophonFormatters: Formatters<ColophonData> = {
66
69
 
67
70
  // Zero sections: the dispatcher routes this to stderr (exit 0) so plain
68
71
  // stdout stays empty; llm/json keep their own empty shapes.
69
- notice: (data) => (data.sections.length === 0 ? EMPTY_NOTICE : undefined),
72
+ notice: (data, surface = "cli") =>
73
+ data.sections.length === 0 ? describeEmpty(surface) : undefined,
70
74
  };
@@ -17,6 +17,7 @@ const colophonVerb: VerbSpec<Record<string, unknown>, ColophonData> = {
17
17
  path: ["colophon"],
18
18
  summary: "Narrate how the active domain is made.",
19
19
  doc: `Storeless — the colophon each active pack declares for its domain. With no pack telling a story, it prints the one ${BIN_NAME} declares for itself instead; with neither, it says so. Also available as a condensed Markdown narration for agents, or as a structured JSON projection of the sections.`,
20
+ useWhen: "when asked which packs the data comes from",
20
21
  params: [],
21
22
  output: { formatters: colophonFormatters },
22
23
  examples: [
@@ -39,6 +39,8 @@ const getVerb: VerbSpec<Record<string, unknown>, ConfigGetData> = {
39
39
  path: ["config", "get"],
40
40
  summary: "Print one resolved config value.",
41
41
  doc: "Reads the effective value of a single field after layering — built-in defaults, the global config, and the nearest project config. Prints the bare value (nothing when the field is unset), so the output substitutes directly into a shell.",
42
+ useWhen: "when asked for the value of one setting",
43
+ example: { key: "tier" },
42
44
  params: [
43
45
  {
44
46
  kind: "enum",
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { map, type Task } from "@canonical/task";
14
14
  import { writeConfigField } from "../../kernel/config/index.js";
15
- import { cliRecovery, PragmaError } from "../../kernel/error/index.js";
15
+ import { callRecovery, PragmaError } from "../../kernel/error/index.js";
16
16
  import { type ConfigFieldSpec, RESERVED_CLEAR_VALUES } from "./fields.js";
17
17
  import type { ConfigFieldResult } from "./types.js";
18
18
 
@@ -34,8 +34,8 @@ export function runField(
34
34
 
35
35
  if (spec.kind === "string" && RESERVED_CLEAR_VALUES.includes(value)) {
36
36
  throw PragmaError.invalidInput(spec.field, value, {
37
- recovery: cliRecovery(
38
- `config unset ${spec.field}`,
37
+ recovery: callRecovery(
38
+ { verb: "config unset", params: { key: spec.field } },
39
39
  "Clear the field with the unset command.",
40
40
  ),
41
41
  });
@@ -29,6 +29,9 @@ const setVerb: VerbSpec<Record<string, unknown>, ConfigFieldResult> = {
29
29
  path: ["config", "set"],
30
30
  summary: "Set a config field by name.",
31
31
  doc: "Write a global config field by name. `key` is one of `tier`, `channel`, or `detail`; clearing a field is `config unset <key>`'s job, and the values that used to double as clear-markers are refused. Written to the global layer only — project configs are authored by hand.",
32
+ useWhen:
33
+ "when asked to change a setting, such as making one product's tier the default for every read",
34
+ example: { key: "tier", value: "apps/lxd" },
32
35
  params: [
33
36
  {
34
37
  kind: "enum",
@@ -18,6 +18,8 @@ const showVerb: VerbSpec<Record<string, unknown>, ConfigShowData> = {
18
18
  path: ["config", "show"],
19
19
  summary: "Show the resolved config and per-field provenance.",
20
20
  doc: `Merges built-in defaults, the global XDG config, and the nearest ${PROJECT_CONFIG_FILENAME}, marking which layer supplied each value.`,
21
+ useWhen:
22
+ "when asked how the tool is configured and which file set each value",
21
23
  params: [],
22
24
  output: { formatters: configShowFormatters },
23
25
  examples: [
@@ -23,6 +23,8 @@ const unsetVerb: VerbSpec<Record<string, unknown>, ConfigFieldResult> = {
23
23
  path: ["config", "unset"],
24
24
  summary: "Clear a config field by name.",
25
25
  doc: "Removes a field from the global config so the built-in default (or a project config) applies again. The counterpart of `config set` — setting writes a value, unsetting removes one; no value doubles as a remove-marker.",
26
+ useWhen: "when asked to put a setting back to its built-in default",
27
+ example: { key: "tier" },
26
28
  params: [
27
29
  {
28
30
  kind: "enum",
@@ -2,8 +2,8 @@
2
2
  * The command-path ALLOWLIST of the `create` surface — the one list the
3
3
  * surface's mechanical copies derive from: the `CreateKind` union
4
4
  * (`types.ts`) and the generator lookup (`pickGenerator.ts`). Prose is NOT
5
- * derived: the verb summaries in `create.verb.ts` and
6
- * `capabilities/hints.ts` still name the frameworks by hand.
5
+ * derived: the verb summaries and `useWhen` sentences in `create.verb.ts`
6
+ * still name the frameworks by hand.
7
7
  *
8
8
  * This table is the SINGLE authoring point for which generator packages the
9
9
  * distribution ships: it binds each `create` noun to the command PATHS it