@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
@@ -25,7 +25,8 @@ import { executeVerb } from "../project/cli/dispatch.js";
25
25
  import type { GlobalFlags, PragmaRuntime } from "../runtime/types.js";
26
26
  import type { VerbSpec } from "../spec/types.js";
27
27
  import { compilePack } from "./compile.js";
28
- import type { LookupOutput } from "./resolveEntity.js";
28
+ import { lookupFormatters } from "./renderPack.js";
29
+ import { GLOB_EXPANSION_CAP, type LookupOutput } from "./resolveEntity.js";
29
30
  import { buildTierHierarchy, resolveTierScope } from "./tierScope.js";
30
31
  import {
31
32
  distributionSource,
@@ -334,6 +335,7 @@ describe("a scoped answer says so, in every format", () => {
334
335
  it("the page carries the scope as data", async () => {
335
336
  expect((await page({})).scope).toEqual({
336
337
  tiers: ["global", "apps", "sites"],
338
+ counts: { global: 2, apps: 1, sites: 1, apps_lxd: 2, "no tier": 1 },
337
339
  });
338
340
  expect((await page({ tier: "all" })).scope).toBeUndefined();
339
341
  });
@@ -346,7 +348,7 @@ describe("a scoped answer says so, in every format", () => {
346
348
  withTier(undefined, LLM),
347
349
  );
348
350
  expect(outcome.stdout).toContain(
349
- "## Widget (5, tier scope: global, apps, sites)",
351
+ "## Widget (5, tier scope: global 2, apps 1, sites 1, no tier 1)",
350
352
  );
351
353
  });
352
354
 
@@ -358,7 +360,8 @@ describe("a scoped answer says so, in every format", () => {
358
360
  withTier(undefined, LLM),
359
361
  );
360
362
  expect(outcome.stdout).toContain(
361
- "## Widget (2, more exist, tier scope: global, apps, sites)",
363
+ // The counts are of the WHOLE answer, not of the two rows in hand.
364
+ "## Widget (2, more exist, tier scope: global 2, apps 1, sites 1, no tier 1)",
362
365
  );
363
366
  });
364
367
 
@@ -372,7 +375,9 @@ describe("a scoped answer says so, in every format", () => {
372
375
  withTier(undefined, PLAIN),
373
376
  );
374
377
  expect(outcome.stdout).toContain("Button");
375
- expect(outcome.stderr).toContain("Tier scope: global, apps, sites.");
378
+ expect(outcome.stderr).toContain(
379
+ "Tier scope: global 2, apps 1, sites 1, no tier 1.",
380
+ );
376
381
  expect(outcome.stderr).toContain("`--tier all` for every tier");
377
382
  });
378
383
 
@@ -384,12 +389,72 @@ describe("a scoped answer says so, in every format", () => {
384
389
  withTier(undefined, JSON_FLAGS),
385
390
  );
386
391
  const envelope = JSON.parse(outcome.stdout as string);
387
- expect(envelope.meta.scope).toEqual({ tiers: ["global", "apps", "sites"] });
388
- expect(envelope.meta.notice).toContain("Tier scope: global, apps, sites.");
392
+ expect(envelope.meta.scope).toEqual({
393
+ tiers: ["global", "apps", "sites"],
394
+ counts: { global: 2, apps: 1, sites: 1, apps_lxd: 2, "no tier": 1 },
395
+ });
396
+ expect(envelope.meta.notice).toContain("Tier scope: global 2, apps 1,");
389
397
  // `data` keeps its shape: the bare row array it has always been.
390
398
  expect(Array.isArray(envelope.data)).toBe(true);
391
399
  });
392
400
 
401
+ it("counts the FILTERED answer, and names the tier a miss lives in", async () => {
402
+ // The case the breakdown is for: nothing in scope matches, and the answer
403
+ // still says which tier to pass.
404
+ const missed = await page({ search: "meter" });
405
+ expect(missed.rows).toEqual([]);
406
+ expect(missed.scope?.counts).toEqual({
407
+ global: 0,
408
+ apps: 0,
409
+ sites: 0,
410
+ apps_lxd: 1,
411
+ });
412
+ const outcome = await executeVerb(
413
+ verbFor(KIT, "list"),
414
+ { search: "meter" },
415
+ REAL,
416
+ withTier(undefined, LLM),
417
+ );
418
+ expect(outcome.stdout).toContain("other tiers: apps_lxd 1");
419
+ });
420
+
421
+ it("counts on the first page only, and a failed count never fails the list", async () => {
422
+ const first = await page({ limit: 2 });
423
+ expect(first.scope?.counts).toBeDefined();
424
+ const second = await page({ limit: 2, after: first.nextAfter });
425
+ expect(second.scope).toEqual({ tiers: ["global", "apps", "sites"] });
426
+
427
+ const failing: PragmaRuntime = {
428
+ ...rt,
429
+ query: {
430
+ ...rt.query,
431
+ sparql: (text: string) =>
432
+ text.includes("GROUP BY")
433
+ ? Promise.reject(new Error("count failed"))
434
+ : rt.query.sparql(text),
435
+ },
436
+ };
437
+ const answer = await page({}, failing);
438
+ expect(answer.rows).toHaveLength(5);
439
+ expect(answer.scope).toEqual({ tiers: ["global", "apps", "sites"] });
440
+ });
441
+
442
+ it("counts ROWS per tier: an entity in two tiers counts under each, and its row is returned once", async () => {
443
+ const { rt: twice } = await buildFixtureRuntime({
444
+ ttl: `${TTL}\nex:button ex:tier ex:apps .`,
445
+ prefixes: PREFIXES,
446
+ });
447
+ try {
448
+ const answer = (await verbFor(KIT, "list").run({}, twice)) as PackPage;
449
+ expect(answer.rows.filter((row) => row.name === "Button")).toHaveLength(
450
+ 1,
451
+ );
452
+ expect(answer.scope?.counts).toMatchObject({ global: 2, apps: 2 });
453
+ } finally {
454
+ (await twice.store.get()).store.dispose();
455
+ }
456
+ });
457
+
393
458
  it("an unscoped read says nothing about a scope", async () => {
394
459
  const outcome = await executeVerb(
395
460
  verbFor(KIT, "list"),
@@ -425,6 +490,55 @@ describe("a lookup prefers the scope and falls back rather than refusing", () =>
425
490
  runtime,
426
491
  )) as LookupOutput;
427
492
 
493
+ it("a pattern applies the scope BEFORE the cap", async () => {
494
+ // Five LXD widgets sort ahead of 55 global ones; none of them takes a slot.
495
+ const wide = [
496
+ ...Array.from({ length: 5 }, (_, i) => ["a", i, "apps_lxd"] as const),
497
+ ...Array.from({ length: 55 }, (_, i) => ["w", i, "global"] as const),
498
+ ]
499
+ .map(
500
+ ([stem, i, tier]) =>
501
+ `ex:${stem}${i} a ex:Widget ; ex:name "${stem}.${String(i).padStart(2, "0")}" ; ex:tier ex:${tier} .`,
502
+ )
503
+ .join("\n");
504
+ const { rt: crowded } = await buildFixtureRuntime({
505
+ ttl: `${TTL}\n${wide}`,
506
+ prefixes: PREFIXES,
507
+ });
508
+ try {
509
+ const out = await lookup("*.*", {}, crowded);
510
+ // Cut inside the scope: the total is the scope's, the rest is named.
511
+ expect(out).toMatchObject({ total: 55, elsewhere: 5 });
512
+ expect(
513
+ lookupFormatters({ by: "ex:name" }, PREFIXES).notice?.(out, "mcp"),
514
+ ).toContain(
515
+ '50 of 55 matches shown, and 5 more in other tiers: pass `tier: "all"`.',
516
+ );
517
+ expect(out.results).toHaveLength(GLOB_EXPANSION_CAP);
518
+ expect(out.results.every((e) => String(e.name).startsWith("w."))).toBe(
519
+ true,
520
+ );
521
+ expect(out.outOfScope).toBeUndefined();
522
+ } finally {
523
+ (await crowded.store.get()).store.dispose();
524
+ }
525
+ });
526
+
527
+ it("a pattern answers a shared name with the in-scope one alone, and an out-of-scope one with a line", async () => {
528
+ expect((await lookup("Chi*")).results.map((e) => e.uri)).toEqual([
529
+ "https://example.org/kit#chip",
530
+ ]);
531
+ const out = await lookup("Met*");
532
+ expect(out.results.map((e) => e.name)).toEqual(["Meter"]);
533
+ expect(out.outOfScope?.at(0)).toMatchObject({ tiers: ["apps_lxd"] });
534
+ // The same redirect reached by a pattern AND by name is said once.
535
+ const both = (await verbFor(KIT, "lookup").run(
536
+ { name: ["Met*", "Meter"] },
537
+ rt,
538
+ )) as LookupOutput;
539
+ expect(both.outOfScope).toHaveLength(1);
540
+ });
541
+
428
542
  it("answers a shared name with the in-scope one alone", async () => {
429
543
  const out = await lookup("Chip");
430
544
  expect(out.results.map((entity) => entity.uri)).toEqual([
@@ -38,7 +38,7 @@
38
38
  * next read re-learns the hierarchy from the pack that replaced it.
39
39
  */
40
40
 
41
- import { cliRecovery, PragmaError } from "../error/index.js";
41
+ import { callRecovery, PragmaError } from "../error/index.js";
42
42
  import type { PragmaRuntime } from "../runtime/index.js";
43
43
  import type { StoreSession } from "../runtime/types.js";
44
44
  import { formatTerm } from "./sparql/escape.js";
@@ -330,15 +330,13 @@ export function resolveTierScope(
330
330
  validOptions,
331
331
  recovery:
332
332
  perCall === undefined
333
- ? cliRecovery(
334
- "config unset tier",
333
+ ? callRecovery(
334
+ { verb: "config unset", params: { key: "tier" } },
335
335
  `The \`tier\` config key names "${asked}", which is not one of this pack's ${hierarchy.tiers.length} tiers. Clear it, or set one that exists.`,
336
- { tool: "config_unset", params: { key: "tier" } },
337
336
  )
338
- : cliRecovery(
339
- "tier list",
337
+ : callRecovery(
338
+ { verb: "tier list" },
340
339
  `Name one of the ${hierarchy.tiers.length} tiers, or "${EVERY_TIER}" for every tier.`,
341
- { tool: "tier_list", params: {} },
342
340
  ),
343
341
  });
344
342
  }
@@ -1,21 +1,31 @@
1
1
  /**
2
- * PROTECTED — the pack `toolDescription` → MCP tool description wiring.
2
+ * PROTECTED — the pack guidance → MCP tool description wiring.
3
3
  *
4
- * A pack authors `toolDescription` FOR the agent-facing MCP tool description
5
- * (see kernel/packs/types.ts, "MCP tool description"). The compiler routes it
6
- * into `VerbSpec.doc`, the MCP projector emits `doc ?? summary` as the tool
7
- * description, and the CLI projector renders `doc` in verb help WITHOUT the
8
- * MCP tool-call example (`noun_verb {…}` is MCP-transport syntax).
4
+ * A story half authors three things for a caller choosing a tool:
5
+ * `toolDescription` (what the tool returns), `useWhen` (the question it
6
+ * answers) and `example` (one call's params). The compiler routes them onto
7
+ * the verb, and ONE generator (`kernel/spec/guidance.ts`) builds the MCP
8
+ * description from them; verb help renders the same example as a COMMAND.
9
9
  *
10
10
  * This pins representative text on BOTH surfaces so the routing can't silently
11
- * regress: the rich description must reach MCP, and the MCP tool-call example
12
- * must never leak into CLI `--help`.
11
+ * regress: the question, the prose and the generated example must reach MCP,
12
+ * and MCP tool-call syntax must never leak into CLI `--help`.
13
+ *
14
+ * These cases used to regex-match a hand-typed `Example: tool {…}` suffix in
15
+ * the authored prose. The suffix is gone — the example is declared as data and
16
+ * rendered — so they assert the GENERATED description instead, and whether an
17
+ * example's params are ones the tool accepts is no longer sampled by regex
18
+ * here: `capabilities/callRule.test.ts` validates every example against its
19
+ * verb's whole input schema.
13
20
  */
14
21
 
15
22
  import { describe, expect, it } from "vitest";
16
23
  import { storyModules } from "../../capabilities/distribution.js";
17
24
  import { projectMcp } from "../../testing/helpers/projectMcp.js";
18
25
  import { formatVerbHelp } from "../project/cli/verbHelp.js";
26
+ import { renderCall } from "../spec/call.js";
27
+ import { describeTool, exampleCall } from "../spec/guidance.js";
28
+ import { toolName } from "../spec/index.js";
19
29
  import type { VerbSpec } from "../spec/types.js";
20
30
 
21
31
  const standardModule = storyModules.get("standard");
@@ -51,8 +61,10 @@ describe("pack toolDescription wiring (PROTECTED)", () => {
51
61
  )?.description;
52
62
  await mcp.cleanup();
53
63
  expect(desc).toContain("Get one design-token symbol in full");
54
- // The authored MCP tool-call example survives on the MCP surface.
55
- expect(desc).toContain('Example: token_lookup { name: ["color.text"] }');
64
+ // The declared example reaches MCP, rendered as a tool call.
65
+ expect(desc).toContain(
66
+ 'Example: token_lookup { name: ["color.text","color.border"] }.',
67
+ );
56
68
  });
57
69
 
58
70
  it("routes the definition-level toolDescription to the MCP list tool", async () => {
@@ -62,9 +74,12 @@ describe("pack toolDescription wiring (PROTECTED)", () => {
62
74
  )?.description;
63
75
  await mcp.cleanup();
64
76
  // The rich description reaches MCP (was previously dropped — only `summary`
65
- // reached the tool), including the authored call example.
77
+ // reached the tool), behind the question it answers and before its example.
78
+ expect(desc).toMatch(/^Use when asked which design tokens exist/);
66
79
  expect(desc).toContain("List the design-token SYMBOLS");
67
- expect(desc).toContain('Example: token_list { type: "color" }');
80
+ expect(desc).toContain(
81
+ 'Example: token_list { type: ["color","dimension"] }.',
82
+ );
68
83
  });
69
84
 
70
85
  it("routes an extra-verb toolDescription to its MCP tool", async () => {
@@ -77,12 +92,16 @@ describe("pack toolDescription wiring (PROTECTED)", () => {
77
92
  // fragment rather than by exact string: the wiring is what this pins, and
78
93
  // tool descriptions are explicitly not frozen (`registerVerb.ts:324-326`).
79
94
  expect(desc).toContain("List all code standard categories");
80
- expect(desc).toContain("Example: standard_categories {}");
95
+ // Callable with no arguments, so there is no example to show.
96
+ expect(desc).not.toContain("Example:");
81
97
  });
82
98
 
83
- it("CLI --help shows the rich prose but NEVER the MCP tool-call syntax", () => {
99
+ it("CLI --help shows the prose and the example as a COMMAND, never as a tool call", () => {
84
100
  const lookupHelp = formatVerbHelp("pragma", verb(tokenModule, "lookup"));
85
101
  expect(lookupHelp).toContain("Get one design-token symbol in full");
102
+ expect(lookupHelp).toContain("Use when asked everything about one token");
103
+ // The same declared example, spelled for this surface.
104
+ expect(lookupHelp).toContain("pragma token lookup color.text");
86
105
  // No-leaks: the `token_lookup {…}` MCP call shape must not reach CLI help.
87
106
  expect(lookupHelp).not.toContain("token_lookup {");
88
107
  expect(lookupHelp).not.toContain("Example:");
@@ -93,71 +112,37 @@ describe("pack toolDescription wiring (PROTECTED)", () => {
93
112
  });
94
113
  });
95
114
 
96
- describe("a tool-call example names a parameter the tool ACCEPTS (PROTECTED)", () => {
97
- // Four `*_lookup` descriptions taught agents `{ names: [...] }` while every
98
- // schema required `name`. An agent copying the example — which is what an
99
- // example is for — got `-32602 Invalid arguments`, and the description is
100
- // the only instruction it has. Prose that contradicts the schema beside it
101
- // is worse than no prose: it is a documented wrong answer.
102
- it("every `Example: tool { key: … }` uses a declared property", async () => {
103
- const mcp = await projectMcp([...storyModules.values()]);
115
+ describe("every story tool's description is GENERATED from its verb (PROTECTED)", () => {
116
+ // Four `*_lookup` descriptions once taught agents `{ names: [...] }` while
117
+ // every schema required `name`, and `standard` once shipped no example at
118
+ // all. Both were failures of hand-typed prose. The description is now built
119
+ // from what the verb declares, so this asserts the build — for EVERY story
120
+ // tool, against the one generator — rather than pattern-matching the prose.
121
+ it("is the question, then the prose, then the verb's own example as a tool call", async () => {
122
+ const modules = [...storyModules.values()];
123
+ const mcp = await projectMcp(modules);
104
124
  try {
105
125
  const tools = await mcp.listTools();
106
- const offenders: string[] = [];
107
-
108
- for (const tool of tools) {
109
- const example = /Example:\s*\w+\s*\{\s*([A-Za-z_$][\w$]*)\s*:/.exec(
110
- tool.description ?? "",
111
- );
112
- const key = example?.[1];
113
- if (key === undefined) continue; // No call example to check.
114
- const properties = Object.keys(
115
- (tool.inputSchema as { properties?: Record<string, unknown> })
116
- ?.properties ?? {},
117
- );
118
- if (!properties.includes(key)) {
119
- offenders.push(
120
- `${tool.name}: example says \`${key}\`, schema declares ${properties.join(", ")}`,
126
+ const verbs = modules.flatMap((module) => module.verbs);
127
+ expect(tools.length).toBe(verbs.length);
128
+ for (const storyVerb of verbs) {
129
+ const name = toolName(storyVerb.path);
130
+ const desc = tools.find((tool) => tool.name === name)?.description;
131
+ expect(desc, name).toBe(describeTool(storyVerb));
132
+ expect(desc, name).toMatch(/^Use /);
133
+ expect(desc, name).toContain(storyVerb.useWhen);
134
+ // Spelled with its OWN name and its declared params, so an example
135
+ // cannot be copied from a sibling tool and left stale. A verb callable
136
+ // with no arguments declares none, and shows none.
137
+ const example = exampleCall(storyVerb);
138
+ if (example) {
139
+ expect(desc, name).toContain(
140
+ `Example: ${renderCall(example, "mcp")}.`,
121
141
  );
142
+ } else {
143
+ expect(desc, name).not.toContain("Example:");
122
144
  }
123
145
  }
124
-
125
- expect(offenders).toEqual([]);
126
- // Guard against a vacuous pass: some tool must actually carry an example.
127
- expect(
128
- tools.filter((t) => /Example:\s*\w+\s*\{/.test(t.description ?? ""))
129
- .length,
130
- ).toBeGreaterThan(0);
131
- } finally {
132
- await mcp.cleanup();
133
- }
134
- });
135
- });
136
-
137
- describe("every story tool CARRIES a call example (PROTECTED)", () => {
138
- // The check above only polices examples that exist. `standard` authored NONE,
139
- // and the auto-generated one fires only for a filter declaring `values` —
140
- // which the category filter deliberately does not, because the graph is the
141
- // vocabulary. So the four `standard_*` tools shipped with no worked call at
142
- // all, and the model that could not form one had nothing to copy.
143
- //
144
- // This asserts the gap cannot reappear on a future story: a compiled story
145
- // tool must show an agent one call it can make, spelled with its OWN name so
146
- // the example cannot be copied from a sibling tool and left stale.
147
- it("names its own tool in an `Example: <tool_name> {…}` fragment", async () => {
148
- const mcp = await projectMcp([...storyModules.values()]);
149
- try {
150
- const tools = await mcp.listTools();
151
- expect(tools.length).toBeGreaterThan(0);
152
- const missing = tools
153
- .filter(
154
- (tool) =>
155
- !new RegExp(`Example:\\s*${tool.name}\\s*\\{`).test(
156
- tool.description ?? "",
157
- ),
158
- )
159
- .map((tool) => tool.name);
160
- expect(missing).toEqual([]);
161
146
  } finally {
162
147
  await mcp.cleanup();
163
148
  }
@@ -93,8 +93,22 @@ export const TIER_PARAM = "tier";
93
93
  */
94
94
  export const ENTITY_VARIABLE = "uri";
95
95
 
96
+ /**
97
+ * What a cell may say about the values it prints, on a list column, a lookup
98
+ * field or an expand field alike.
99
+ */
100
+ export interface PackCellLink {
101
+ /**
102
+ * The noun whose entities this cell NAMES: every value it prints is a name
103
+ * that noun's lookup resolves, so a reader can take the cell to
104
+ * `<noun> lookup` verbatim. On a list column it also promises a filter of
105
+ * the same `noun` over that column.
106
+ */
107
+ readonly noun?: string;
108
+ }
109
+
96
110
  /** A list column: a SELECT variable to display. */
97
- export interface PackColumn {
111
+ export interface PackColumn extends PackCellLink {
98
112
  /** SELECT variable name (without `?`). */
99
113
  readonly field: string;
100
114
  /** Column heading (defaults to the field name). */
@@ -102,7 +116,7 @@ export interface PackColumn {
102
116
  }
103
117
 
104
118
  /** A looked-up value: an output name bound to a property of the entity. */
105
- export interface PackField {
119
+ export interface PackField extends PackCellLink {
106
120
  /** Output field name on the looked-up entity. */
107
121
  readonly name: string;
108
122
  /** Property to read — a prefixed name (`ds:tier`) or absolute IRI. */
@@ -141,7 +155,7 @@ export interface PackSection extends PackField {
141
155
  }
142
156
 
143
157
  /** A field read from each child node of a {@link PackExpand}. */
144
- export interface PackExpandField {
158
+ export interface PackExpandField extends PackCellLink {
145
159
  /** Output field name on the child record. */
146
160
  readonly name: string;
147
161
  /** Property to read on the child node — prefixed name, IRI, or path. */
@@ -166,6 +180,15 @@ export interface PackExpandField {
166
180
  * and filter against it, and a GraphQL document cannot.
167
181
  */
168
182
  readonly blankWhenSelf?: true;
183
+ /**
184
+ * The property reaches SEVERAL values per child, and the cell is all of them:
185
+ * distinct, sorted, space-separated, one row per child. Without it each value is a
186
+ * row of its own, repeating every other cell.
187
+ *
188
+ * SPARQL lane only. Not combinable with {@link blankWhenSelf}, and not a
189
+ * name an expand may order by — a set has no order to sort on.
190
+ */
191
+ readonly many?: true;
169
192
  }
170
193
 
171
194
  /**
@@ -365,6 +388,23 @@ export interface PackFilter {
365
388
  * names neither is refused where it is declared.
366
389
  */
367
390
  readonly vocabulary?: PackFilterVocabulary;
391
+ /**
392
+ * In place of {@link values} or a {@link vocabulary}: a value is a literal
393
+ * name or IRI of that noun (never a pattern), resolved as `<noun> lookup`
394
+ * resolves one, and rows are kept by the IRIs it reaches, matched against
395
+ * {@link entity}.
396
+ */
397
+ readonly noun?: string;
398
+ /**
399
+ * The projected SELECT variable holding the IRI a {@link noun} filter
400
+ * constrains. Left out of the rows unless a column displays it.
401
+ */
402
+ readonly entity?: string;
403
+ /**
404
+ * The path from {@link entity} to the named entity when they are different
405
+ * nodes (`^ex:madeBy` from a row's maker to its widgets); absent, they are one.
406
+ */
407
+ readonly via?: string;
368
408
  /** Help text (defaults to a generated description). */
369
409
  readonly description?: string;
370
410
  }
@@ -393,16 +433,41 @@ export interface PackSearch {
393
433
  readonly description?: string;
394
434
  }
395
435
 
436
+ /** The shape of a verb path (`sources update`): a noun, optionally a verb — never a command line. */
437
+ export const VERB_PATH_PATTERN = /^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)?$/;
438
+
439
+ /**
440
+ * A call as a story writes it: a verb path and the params to make it with.
441
+ * Structurally the kernel's `Call` (`spec/types.ts`), restated here because this
442
+ * file is on the distribution config's import graph and may import nothing.
443
+ */
444
+ export interface PackCall {
445
+ readonly verb: string;
446
+ readonly params?: Readonly<Record<string, unknown>>;
447
+ }
448
+
396
449
  /** Opt-in empty-result recovery for a list story. */
397
450
  export interface PackEmptyRecovery {
398
451
  /** Human-readable cause + fix (e.g. which packages provide the data). */
399
452
  readonly message: string;
400
453
  /**
401
- * The command that fixes the emptiness, WITHOUT the binary name
402
- * (`sources update`). The consuming distribution's renderer prepends its own
403
- * name, so a story stays portable across distributions.
454
+ * The call that fixes the emptiness (`{ verb: "sources update" }`). A verb
455
+ * path, never a binary name: the consuming distribution's renderer spells it
456
+ * for the surface it prints on, so a story stays portable across both.
404
457
  */
405
- readonly cli?: string;
458
+ readonly call?: PackCall;
459
+ }
460
+
461
+ /**
462
+ * What a story half tells a caller choosing between tools. Optional in the
463
+ * grammar, because third-party packs predate it; the distribution's own stories
464
+ * are held to declaring both (`callRule.test.ts`).
465
+ */
466
+ export interface PackGuidance {
467
+ /** The question a person would ask, in their words, as a bare clause ("when asked …"). */
468
+ readonly useWhen?: string;
469
+ /** One real call's params (never empty) — the verb is implied, and both spellings derive. */
470
+ readonly example?: Readonly<Record<string, unknown>>;
406
471
  }
407
472
 
408
473
  /** The list half of a pack (always SPARQL-sourced). */
@@ -424,7 +489,7 @@ export interface PackList {
424
489
  * machinery as `list` (e.g. standard's `categories`). May not collide with the
425
490
  * compiled `list`/`lookup`/`sample` verbs.
426
491
  */
427
- export interface PackVerb extends PackList {
492
+ export interface PackVerb extends PackList, PackGuidance {
428
493
  /** Verb name (kebab-case), e.g. `"categories"`. */
429
494
  readonly verb: string;
430
495
  /** CLI description for the command. */
@@ -437,7 +502,7 @@ export interface PackVerb extends PackList {
437
502
  * The sample capability: `<noun> sample [count]` returns 1–5 randomly selected
438
503
  * complete entities (resolved through the lookup path at the HIGHEST level).
439
504
  */
440
- export interface PackSample {
505
+ export interface PackSample extends PackGuidance {
441
506
  /** Default sample count when none is requested (1–5; default 2). */
442
507
  readonly count?: number;
443
508
  /**
@@ -516,7 +581,7 @@ export interface PackScopeWeight {
516
581
  * ALWAYS generated SPARQL regardless of source (an implementation detail, not a
517
582
  * second declared source).
518
583
  */
519
- export interface PackLookup {
584
+ export interface PackLookup extends PackGuidance {
520
585
  /**
521
586
  * Field-fetch strategy (default `"sparql"`). `"graphql"` keeps the SPARQL
522
587
  * name→URI resolve, then fetches all fields/sections/expands in ONE generated
@@ -604,6 +669,14 @@ export interface PackLookup {
604
669
  readonly completion?: PackCompletion;
605
670
  }
606
671
 
672
+ /**
673
+ * The lookup another story's noun declares, by noun — how a {@link PackFilter}
674
+ * naming a `noun` reaches that noun's resolver. A story is compiled alone, so
675
+ * whoever compiles it supplies this over the stories it knows. It may throw a
676
+ * CONFIG_ERROR of its own when it knows why a noun has no lookup.
677
+ */
678
+ export type NounLookups = (noun: string) => PackLookup | undefined;
679
+
607
680
  /** A pack list row / flat lookup base: variable name → string value. */
608
681
  export type PackRow = Record<string, string>;
609
682
 
@@ -664,8 +737,17 @@ export interface PageTierScope {
664
737
  * use (the tier IRI's local name), base first and then shallowest first.
665
738
  */
666
739
  readonly tiers: readonly string[];
740
+ /**
741
+ * Rows of the WHOLE filtered answer per tier, keyed by local name: every
742
+ * in-scope tier (0 when empty), the out-of-scope tiers holding some, and
743
+ * {@link UNTIERED_KEY} for rows in no tier. First page only.
744
+ */
745
+ readonly counts?: Readonly<Record<string, number>>;
667
746
  }
668
747
 
748
+ /** The `counts` key for rows whose entity is in no tier (no tier is named so). */
749
+ export const UNTIERED_KEY = "no tier";
750
+
669
751
  /** One filter a list read was narrowed by, as the caller spelled it. */
670
752
  export interface PackAppliedFilter {
671
753
  /** The parameter name (its CLI flag is `--<param>`). */
@@ -692,7 +774,7 @@ export type PackChildRow = Record<
692
774
  export type PackEntity = Record<string, string | readonly PackChildRow[]>;
693
775
 
694
776
  /** One declarative read story: a noun with its preferred queries. */
695
- export interface PackDefinition {
777
+ export interface PackDefinition extends PackGuidance {
696
778
  /** Command noun (kebab-case), e.g. `"standard"` → `pragma standard list`. */
697
779
  readonly noun: string;
698
780
  /** CLI description for the list command. */
@@ -19,6 +19,7 @@ import {
19
19
  VERSION,
20
20
  } from "../../../constants.js";
21
21
  import type { GlobalFlags } from "../../runtime/index.js";
22
+ import { declareVerbs } from "../../spec/call.js";
22
23
  import { kebabCase } from "../../spec/index.js";
23
24
  import type { CliProjection, ParamSpec, VerbSpec } from "../../spec/types.js";
24
25
  import { MUTATION_FLAG_DOCS, negationFlagDoc } from "./constants.js";
@@ -302,6 +303,9 @@ export function buildProgram(
302
303
  const version = options.version ?? VERSION;
303
304
  const issuesUrl = options.issuesUrl ?? ISSUES_URL;
304
305
  const live = verbs.filter((verb) => !verb.hidden);
306
+ // A recovery or an example names a call; spelling it as a command needs the
307
+ // named verb's positionals, so the renderer is told which verbs exist.
308
+ declareVerbs(verbs);
305
309
 
306
310
  const program = new Command();
307
311
  program.name(programName).description(description);