@canonical/pragma-cli 0.38.0 → 0.42.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 (365) hide show
  1. package/README.md +3 -3
  2. package/dist/package.json +16 -16
  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/colophon/markdownTerminal.js.map +1 -1
  17. package/dist/src/capabilities/config/get.verb.js +2 -0
  18. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  19. package/dist/src/capabilities/config/runField.js +2 -2
  20. package/dist/src/capabilities/config/runField.js.map +1 -1
  21. package/dist/src/capabilities/config/set.verb.js +2 -0
  22. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  23. package/dist/src/capabilities/config/show.render.js.map +1 -1
  24. package/dist/src/capabilities/config/show.verb.js +1 -0
  25. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  26. package/dist/src/capabilities/config/unset.verb.js +2 -0
  27. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  28. package/dist/src/capabilities/create/constants.js +8 -5
  29. package/dist/src/capabilities/create/constants.js.map +1 -1
  30. package/dist/src/capabilities/create/create.render.js.map +1 -1
  31. package/dist/src/capabilities/create/create.verb.js +36 -7
  32. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  33. package/dist/src/capabilities/create/createSurface.generated.js +22 -0
  34. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  35. package/dist/src/capabilities/create/index.js +1 -1
  36. package/dist/src/capabilities/create/pickGenerator.js +1 -0
  37. package/dist/src/capabilities/create/pickGenerator.js.map +1 -1
  38. package/dist/src/capabilities/distribution.js +3 -1
  39. package/dist/src/capabilities/distribution.js.map +1 -1
  40. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  41. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  42. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  43. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  44. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  46. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  47. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  48. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  49. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  50. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  51. package/dist/src/capabilities/graph/query.render.js +37 -5
  52. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  53. package/dist/src/capabilities/graph/query.verb.js +2 -0
  54. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  55. package/dist/src/capabilities/graph/queryText.js +28 -0
  56. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  57. package/dist/src/capabilities/graph/runConnect.js +3 -3
  58. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  59. package/dist/src/capabilities/graph/runQuery.js +8 -6
  60. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  61. package/dist/src/capabilities/info/info.verb.js +2 -0
  62. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  63. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  64. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  65. package/dist/src/capabilities/ontology/verbs.js +7 -10
  66. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  67. package/dist/src/capabilities/prompt/verbs.js +5 -4
  68. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  69. package/dist/src/capabilities/resources/provider.js +6 -3
  70. package/dist/src/capabilities/resources/provider.js.map +1 -1
  71. package/dist/src/capabilities/setup/operations/writability.js.map +1 -1
  72. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  73. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  74. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  75. package/dist/src/capabilities/shared/calls.js +16 -0
  76. package/dist/src/capabilities/shared/calls.js.map +1 -0
  77. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  78. package/dist/src/capabilities/skill/discover.js.map +1 -1
  79. package/dist/src/capabilities/skill/render.js +7 -5
  80. package/dist/src/capabilities/skill/render.js.map +1 -1
  81. package/dist/src/capabilities/skill/verbs.js +5 -4
  82. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  83. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  84. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  85. package/dist/src/capabilities/sources/index.js +4 -3
  86. package/dist/src/capabilities/sources/index.js.map +1 -1
  87. package/dist/src/capabilities/sources/reset.render.js +37 -0
  88. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  89. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  90. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  91. package/dist/src/capabilities/sources/runReset.js +59 -0
  92. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  93. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  94. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  95. package/dist/src/capabilities/sources/status.render.js +24 -8
  96. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  97. package/dist/src/capabilities/sources/status.verb.js +1 -0
  98. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  99. package/dist/src/capabilities/sources/update.verb.js +2 -0
  100. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  101. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  102. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  103. package/dist/src/capabilities/version/version.verb.js +1 -0
  104. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  105. package/dist/src/constants.js +4 -3
  106. package/dist/src/constants.js.map +1 -1
  107. package/dist/src/kernel/completion/complete.js.map +1 -1
  108. package/dist/src/kernel/completion/emitScripts.js.map +1 -1
  109. package/dist/src/kernel/completion/parse.js.map +1 -1
  110. package/dist/src/kernel/completion/rank.js.map +1 -1
  111. package/dist/src/kernel/completion/resolve.js.map +1 -1
  112. package/dist/src/kernel/config/readConfig.js.map +1 -1
  113. package/dist/src/kernel/error/PragmaError.js.map +1 -1
  114. package/dist/src/kernel/error/index.js +1 -1
  115. package/dist/src/kernel/error/index.js.map +1 -1
  116. package/dist/src/kernel/error/recovery.js +24 -42
  117. package/dist/src/kernel/error/recovery.js.map +1 -1
  118. package/dist/src/kernel/packs/collect.js +123 -9
  119. package/dist/src/kernel/packs/collect.js.map +1 -1
  120. package/dist/src/kernel/packs/compile.js +74 -15
  121. package/dist/src/kernel/packs/compile.js.map +1 -1
  122. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  123. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  124. package/dist/src/kernel/packs/iri.js +2 -4
  125. package/dist/src/kernel/packs/iri.js.map +1 -1
  126. package/dist/src/kernel/packs/renderPack.js +90 -50
  127. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  128. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  129. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  130. package/dist/src/kernel/packs/runBodies.js +89 -30
  131. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  132. package/dist/src/kernel/packs/schema.js +66 -12
  133. package/dist/src/kernel/packs/schema.js.map +1 -1
  134. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  135. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  136. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  137. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  138. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  139. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  140. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  141. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  142. package/dist/src/kernel/packs/storyRules.js +31 -4
  143. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  144. package/dist/src/kernel/packs/tierScope.js +3 -3
  145. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  146. package/dist/src/kernel/packs/types.js +4 -0
  147. package/dist/src/kernel/packs/types.js.map +1 -1
  148. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  149. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  150. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  151. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  152. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  153. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  154. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  155. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  156. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  157. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  158. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  159. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  160. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  161. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  162. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  163. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  164. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  165. package/dist/src/kernel/render/envelope.js.map +1 -1
  166. package/dist/src/kernel/render/renderers.js +3 -3
  167. package/dist/src/kernel/render/renderers.js.map +1 -1
  168. package/dist/src/kernel/render/turtle.js +21 -4
  169. package/dist/src/kernel/render/turtle.js.map +1 -1
  170. package/dist/src/kernel/runtime/boot.js.map +1 -1
  171. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  172. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  173. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  174. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  175. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  176. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  177. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  178. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  179. package/dist/src/kernel/runtime/loadSession.js +8 -1
  180. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  181. package/dist/src/kernel/runtime/packVersion.js +75 -0
  182. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  183. package/dist/src/kernel/runtime/readEntity.js +43 -4
  184. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  185. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  186. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  187. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  188. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  189. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  190. package/dist/src/kernel/spec/call.js +167 -0
  191. package/dist/src/kernel/spec/call.js.map +1 -0
  192. package/dist/src/kernel/spec/emitReference.js +9 -13
  193. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  194. package/dist/src/kernel/spec/emitSurface.js +8 -1
  195. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  196. package/dist/src/kernel/spec/guidance.js +63 -0
  197. package/dist/src/kernel/spec/guidance.js.map +1 -0
  198. package/dist/src/kernel/spec/wireType.js +36 -0
  199. package/dist/src/kernel/spec/wireType.js.map +1 -0
  200. package/package.json +16 -16
  201. package/pragma.conf.ts +294 -177
  202. package/src/capabilities/block.parity.test.ts +1 -1
  203. package/src/capabilities/callRule.test.ts +147 -0
  204. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  205. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  206. package/src/capabilities/capabilities/catalog.ts +60 -55
  207. package/src/capabilities/capabilities/index.ts +2 -0
  208. package/src/capabilities/capabilities/types.ts +16 -19
  209. package/src/capabilities/colophon/colophon.render.ts +9 -5
  210. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  211. package/src/capabilities/config/get.verb.ts +2 -0
  212. package/src/capabilities/config/runField.ts +3 -3
  213. package/src/capabilities/config/set.verb.ts +3 -0
  214. package/src/capabilities/config/show.verb.ts +2 -0
  215. package/src/capabilities/config/unset.verb.ts +2 -0
  216. package/src/capabilities/create/constants.ts +8 -5
  217. package/src/capabilities/create/create.mcp.test.ts +173 -1
  218. package/src/capabilities/create/create.test.ts +4 -2
  219. package/src/capabilities/create/create.verb.ts +45 -3
  220. package/src/capabilities/create/createGrammar.test.ts +41 -5
  221. package/src/capabilities/create/createSurface.generated.ts +22 -0
  222. package/src/capabilities/create/crossCli.subprocess.test.ts +53 -14
  223. package/src/capabilities/create/index.ts +1 -1
  224. package/src/capabilities/create/pickGenerator.ts +1 -0
  225. package/src/capabilities/create/shippedCreate.subprocess.test.ts +28 -7
  226. package/src/capabilities/distribution.test.ts +6 -3
  227. package/src/capabilities/distribution.ts +6 -0
  228. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  229. package/src/capabilities/doctor/doctor.test.ts +38 -1
  230. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  231. package/src/capabilities/emptyRecovery.test.ts +5 -0
  232. package/src/capabilities/graph/connect.verb.ts +2 -0
  233. package/src/capabilities/graph/inspect.render.ts +9 -1
  234. package/src/capabilities/graph/inspect.test.ts +73 -0
  235. package/src/capabilities/graph/inspect.verb.ts +2 -0
  236. package/src/capabilities/graph/query.render.test.ts +35 -0
  237. package/src/capabilities/graph/query.render.ts +45 -7
  238. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  239. package/src/capabilities/graph/query.verb.ts +2 -0
  240. package/src/capabilities/graph/queryText.test.ts +55 -1
  241. package/src/capabilities/graph/queryText.ts +47 -0
  242. package/src/capabilities/graph/runConnect.ts +4 -5
  243. package/src/capabilities/graph/runQuery.ts +23 -9
  244. package/src/capabilities/info/info.verb.ts +2 -0
  245. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  246. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  247. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  248. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  249. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  250. package/src/capabilities/meta/mcp.verb.ts +2 -0
  251. package/src/capabilities/ontology/ontology.test.ts +24 -0
  252. package/src/capabilities/ontology/verbs.ts +16 -12
  253. package/src/capabilities/prompt/verbs.ts +10 -4
  254. package/src/capabilities/resources/provider.ts +7 -3
  255. package/src/capabilities/resources/resources.test.ts +2 -1
  256. package/src/capabilities/setup/setup.verb.ts +16 -0
  257. package/src/capabilities/shared/calls.ts +20 -0
  258. package/src/capabilities/skill/render.ts +9 -6
  259. package/src/capabilities/skill/verbs.ts +8 -4
  260. package/src/capabilities/sources/collectStatus.ts +6 -1
  261. package/src/capabilities/sources/index.ts +4 -3
  262. package/src/capabilities/sources/reset.render.test.ts +33 -0
  263. package/src/capabilities/sources/reset.render.ts +43 -0
  264. package/src/capabilities/sources/reset.verb.ts +59 -0
  265. package/src/capabilities/sources/runReset.ts +65 -0
  266. package/src/capabilities/sources/runUpdate.ts +12 -11
  267. package/src/capabilities/sources/sources.test.ts +158 -3
  268. package/src/capabilities/sources/status.render.test.ts +63 -1
  269. package/src/capabilities/sources/status.render.ts +25 -8
  270. package/src/capabilities/sources/status.verb.ts +2 -0
  271. package/src/capabilities/sources/types.ts +26 -0
  272. package/src/capabilities/sources/update.verb.ts +3 -0
  273. package/src/capabilities/surface.test.ts +29 -4
  274. package/src/capabilities/token.parity.test.ts +3 -2
  275. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  276. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  277. package/src/capabilities/variable.parity.test.ts +2 -1
  278. package/src/capabilities/version/version.verb.ts +1 -0
  279. package/src/capabilities/wireContract.test.ts +110 -0
  280. package/src/constants.ts +4 -3
  281. package/src/kernel/completion/shellDrive.test.ts +2 -2
  282. package/src/kernel/copy.test.ts +1 -1
  283. package/src/kernel/error/PragmaError.test.ts +7 -3
  284. package/src/kernel/error/index.ts +1 -1
  285. package/src/kernel/error/recovery.test.ts +52 -18
  286. package/src/kernel/error/recovery.ts +25 -46
  287. package/src/kernel/error/types.ts +1 -1
  288. package/src/kernel/packs/collect.test.ts +79 -0
  289. package/src/kernel/packs/collect.ts +151 -11
  290. package/src/kernel/packs/compile.test.ts +107 -10
  291. package/src/kernel/packs/compile.ts +100 -11
  292. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  293. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  294. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  295. package/src/kernel/packs/iri.ts +5 -4
  296. package/src/kernel/packs/listNotice.test.ts +1 -1
  297. package/src/kernel/packs/nounLink.test.ts +385 -0
  298. package/src/kernel/packs/renderPack.ts +137 -57
  299. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  300. package/src/kernel/packs/resolveEntity.ts +281 -101
  301. package/src/kernel/packs/runBodies.ts +133 -31
  302. package/src/kernel/packs/schema.ts +77 -12
  303. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  304. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  305. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  306. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  307. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  308. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  309. package/src/kernel/packs/storyRules.ts +37 -4
  310. package/src/kernel/packs/tierScope.test.ts +120 -6
  311. package/src/kernel/packs/tierScope.ts +5 -7
  312. package/src/kernel/packs/toolDescription.test.ts +59 -74
  313. package/src/kernel/packs/types.ts +93 -11
  314. package/src/kernel/project/cli/buildProgram.ts +4 -0
  315. package/src/kernel/project/cli/dispatch.ts +34 -8
  316. package/src/kernel/project/cli/suggest.test.ts +77 -0
  317. package/src/kernel/project/cli/suggestNames.ts +47 -1
  318. package/src/kernel/project/cli/verbHelp.ts +16 -19
  319. package/src/kernel/project/mcp/buildServer.ts +15 -4
  320. package/src/kernel/project/mcp/envelope.ts +3 -2
  321. package/src/kernel/project/mcp/instructions.ts +123 -35
  322. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  323. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  324. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  325. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  326. package/src/kernel/render/renderers.test.ts +2 -2
  327. package/src/kernel/render/renderers.ts +3 -3
  328. package/src/kernel/render/turtle.ts +24 -4
  329. package/src/kernel/runtime/graphpack/build.ts +58 -1
  330. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  331. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  332. package/src/kernel/runtime/graphpack/read.ts +4 -6
  333. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  334. package/src/kernel/runtime/graphpack/types.ts +13 -0
  335. package/src/kernel/runtime/loadSession.ts +8 -1
  336. package/src/kernel/runtime/packVersion.test.ts +61 -0
  337. package/src/kernel/runtime/packVersion.ts +84 -0
  338. package/src/kernel/runtime/readEntity.ts +61 -6
  339. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  340. package/src/kernel/runtime/resolveSources.ts +91 -10
  341. package/src/kernel/runtime/storeReadiness.ts +4 -4
  342. package/src/kernel/runtime/types.ts +12 -0
  343. package/src/kernel/spec/call.test.ts +95 -0
  344. package/src/kernel/spec/call.ts +186 -0
  345. package/src/kernel/spec/emitReference.ts +9 -13
  346. package/src/kernel/spec/emitSurface.ts +9 -1
  347. package/src/kernel/spec/guidance.test.ts +49 -0
  348. package/src/kernel/spec/guidance.ts +68 -0
  349. package/src/kernel/spec/index.ts +4 -0
  350. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  351. package/src/kernel/spec/types.ts +64 -7
  352. package/src/kernel/spec/wireType.ts +53 -0
  353. package/src/testing/behavioral/README.md +1 -1
  354. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  355. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  356. package/src/testing/behavioral/parity.test.ts +40 -0
  357. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  358. package/src/testing/eval/cases/assembly.ts +4 -3
  359. package/src/testing/eval/cases/stable.ts +2 -3
  360. package/src/testing/helpers/callSchema.ts +40 -0
  361. package/src/testing/helpers/parity.ts +35 -2
  362. package/src/testing/setupCallChecking.ts +30 -0
  363. package/dist/src/capabilities/capabilities/hints.js +0 -223
  364. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  365. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ quoteArgument,
4
+ quoteCall,
5
+ renderCall,
6
+ renderNextStep,
7
+ } from "./call.js";
8
+
9
+ describe("a call is spelled for the surface it is printed on", () => {
10
+ const call = { verb: "token lookup", params: { name: ["color.text"] } };
11
+
12
+ it("ends a dead end with a command on the CLI and a tool call over MCP", () => {
13
+ expect(renderNextStep(call, "cli")).toBe(
14
+ "Run `pragma token lookup color.text`.",
15
+ );
16
+ expect(renderNextStep(call, "mcp")).toBe(
17
+ 'Call `token_lookup { name: ["color.text"] }`.',
18
+ );
19
+ });
20
+
21
+ it("a next step to a mutating tool never confirms: the tool is plan-first, and its plan says how to proceed", () => {
22
+ const update = { verb: "sources update" };
23
+ expect(renderNextStep(update, "mcp")).toBe("Call `sources_update {}`.");
24
+ expect(renderNextStep(update, "cli")).toBe("Run `pragma sources update`.");
25
+ expect(renderCall(update, "mcp")).toBe("sources_update {}");
26
+ });
27
+
28
+ it("a verb withheld from MCP is spelled as the command it is, on both surfaces", () => {
29
+ expect(renderNextStep({ verb: "setup skills" }, "mcp")).toBe(
30
+ "Run `pragma setup skills`.",
31
+ );
32
+ });
33
+
34
+ it("carries global CLI flags on the command only", () => {
35
+ const verbose = { verb: "sources update", cliFlags: ["--verbose"] };
36
+ expect(renderCall(verbose, "cli")).toBe("pragma sources update --verbose");
37
+ expect(renderCall(verbose, "mcp")).toBe("sources_update {}");
38
+ });
39
+
40
+ it("spells flags the way the CLI parses them", () => {
41
+ // A default-true boolean is negated; a default-false one is simply absent.
42
+ expect(
43
+ renderCall(
44
+ {
45
+ verb: "create package",
46
+ params: { name: "x", runInstall: false, withCli: false },
47
+ },
48
+ "cli",
49
+ ),
50
+ ).toBe("pragma create package --name x --no-run-install");
51
+ // `undefined` is not an argument, on either surface.
52
+ const sparse = {
53
+ verb: "ontology lookup",
54
+ params: { prefix: "ds", class: undefined },
55
+ };
56
+ expect(renderCall(sparse, "mcp")).toBe('ontology_lookup { prefix: "ds" }');
57
+ expect(renderCall(sparse, "cli")).toBe("pragma ontology lookup ds");
58
+ });
59
+
60
+ it("fences a flag-like positional, and quotes a call containing a backtick safely", () => {
61
+ expect(
62
+ quoteCall({ verb: "graph inspect", params: { uri: "-x" } }, "cli"),
63
+ ).toBe("`pragma graph inspect -- -x`");
64
+ expect(
65
+ quoteCall(
66
+ { verb: "graph query", params: { sparql: 'ASK { ?s ?p "`" }' } },
67
+ "mcp",
68
+ ),
69
+ ).toMatch(/^`` graph_query .* ``$/);
70
+ expect(quoteArgument("channelOf", "x", "cli")).toBe("`--channel-of x`");
71
+ // Shell-quoted like any other word, so the printed flag can be pasted.
72
+ expect(quoteArgument("search", "two words; rm", "cli")).toBe(
73
+ "`--search 'two words; rm'`",
74
+ );
75
+ expect(quoteArgument("channelOf", "x", "mcp")).toBe('`channelOf: "x"`');
76
+ });
77
+
78
+ it("outside the suite's checking, renders an undeclared verb's params as flags rather than throwing", async () => {
79
+ // A shipped process never enables checking, and this runs inside error
80
+ // rendering — a second failure there helps no one. A fresh module instance
81
+ // is the unchecked, undeclared state a shipped process starts in.
82
+ vi.resetModules();
83
+ const fresh = await import("./call.js");
84
+ expect(
85
+ fresh.renderCall(
86
+ { verb: "widget list", params: { kind: "input" } },
87
+ "cli",
88
+ ),
89
+ ).toBe("pragma widget list --kind input");
90
+ expect(fresh.callTool({ verb: "widget list" })).toEqual({
91
+ tool: "widget_list",
92
+ params: {},
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * A call: one verb plus the params to make it with — the single type behind
3
+ * every example and every recovery.
4
+ *
5
+ * An author names the call ONCE and both spellings derive from it:
6
+ * `token_lookup { name: ["color.text"] }` for an agent on MCP,
7
+ * `pragma token lookup color.text` for a person at a terminal. A hand-typed
8
+ * pair can disagree, and a hand-typed string cannot be checked against the
9
+ * verb it names, where a call can (`capabilities/callRule.test.ts`).
10
+ *
11
+ * The CLI spelling needs facts the call does not carry — which params are
12
+ * positional, which booleans default to true, whether the verb mutates. They
13
+ * live on the verb, so the projectors declare their verbs here as they boot
14
+ * ({@link declareVerbs}). A call naming a verb nobody declared renders every
15
+ * param as a flag rather than throwing: this runs inside error rendering,
16
+ * where a second failure helps no one.
17
+ *
18
+ * NOT derivable here: a noun whose CLI is mounted by its own projection
19
+ * (`create`, whose commands carry a generator segment the verb's params do
20
+ * not). Its calls spell correctly as tool calls only.
21
+ *
22
+ * Zod-free: it is reachable from the `--help` path.
23
+ */
24
+
25
+ import { RECOVERY_CLI_PREFIX } from "../../constants.js";
26
+ import { kebabCase, toolName } from "./emitSurface.js";
27
+ import type { Call, Surface, VerbSpec } from "./types.js";
28
+
29
+ const declared = new Map<string, VerbSpec>();
30
+ let findProblem:
31
+ | ((call: Call, verb: VerbSpec | undefined) => string | undefined)
32
+ | undefined;
33
+
34
+ /**
35
+ * Declare the verbs a call may name. Additive, so a fixture's verbs join the
36
+ * distribution's rather than replacing them.
37
+ *
38
+ * @note Impure — fills the module-level index {@link renderCall} reads. A call
39
+ * is rendered deep inside error construction and formatters, which hold no
40
+ * registry to pass in.
41
+ */
42
+ export function declareVerbs(verbs: readonly VerbSpec[]): void {
43
+ for (const verb of verbs) declared.set(verb.path.join(" "), verb);
44
+ }
45
+
46
+ /**
47
+ * Make every rendered call prove itself. The test setup installs the ONE
48
+ * validator — the verb's real MCP input schema — so a recovery built on any
49
+ * line a test runs is checked exactly as the conformance test checks examples.
50
+ * It is handed the DECLARED verb the call names, so a fixture that declares its
51
+ * verbs is held to them too.
52
+ *
53
+ * @note Impure — sets module-level state; never installed in a shipped process.
54
+ */
55
+ export function checkCallsWith(
56
+ validator: (call: Call, verb: VerbSpec | undefined) => string | undefined,
57
+ ): void {
58
+ findProblem = validator;
59
+ }
60
+
61
+ /** The tool name a call addresses (`"sources update"` → `sources_update`). */
62
+ function callToolName(call: Call): string {
63
+ return toolName(call.verb.split(" ") as [string, string?]);
64
+ }
65
+
66
+ /**
67
+ * Build (or rebuild) the store from the configured packs — the one call the
68
+ * kernel itself points at, from every unavailable store and every empty list.
69
+ * Named once, here, because the kernel owns the verb's name.
70
+ */
71
+ export const BUILD_STORE_CALL: Call = { verb: "sources update" };
72
+
73
+ /** The MCP tool a recovery names, or nothing when its verb is withheld from MCP. */
74
+ export function callTool(
75
+ call: Call,
76
+ ): { tool: string; params: Record<string, unknown> } | undefined {
77
+ if (declared.get(call.verb)?.capability.mcp.expose === false) {
78
+ return undefined;
79
+ }
80
+ return { tool: callToolName(call), params: { ...call.params } };
81
+ }
82
+
83
+ /** Quote a CLI word unless the shell would read it bare. */
84
+ function quoteWord(value: unknown): string {
85
+ const word = String(value);
86
+ return /^[\w.:/@=,+-]+$/.test(word)
87
+ ? word
88
+ : `'${word.replaceAll("'", "'\\''")}'`;
89
+ }
90
+
91
+ /** The CLI spelling: global flags, then flags, then positionals in the verb's order. */
92
+ function renderCliCall(call: Call): string {
93
+ const params = call.params ?? {};
94
+ const specs = declared.get(call.verb)?.params ?? [];
95
+ const positional = specs.filter((p) => p.positional).map((p) => p.name);
96
+ const flags: string[] = [];
97
+ for (const [name, value] of Object.entries(params)) {
98
+ if (positional.includes(name) || value === undefined) continue;
99
+ const flag = `--${kebabCase(name)}`;
100
+ if (typeof value === "boolean") {
101
+ const spec = specs.find((p) => p.name === name);
102
+ const defaultsTrue = spec?.kind === "boolean" && spec.default === true;
103
+ if (value) flags.push(flag);
104
+ else if (defaultsTrue) flags.push(`--no-${kebabCase(name)}`);
105
+ continue;
106
+ }
107
+ // A list-valued flag is repeated, one value each.
108
+ for (const item of [value].flat()) flags.push(`${flag} ${quoteWord(item)}`);
109
+ }
110
+ const words = positional
111
+ .filter((name) => params[name] !== undefined)
112
+ .flatMap((name) => [params[name]].flat().map(quoteWord));
113
+ // A positional that looks like a flag needs the end-of-options marker.
114
+ const guard = words.some((word) => word.startsWith("-")) ? ["--"] : [];
115
+ return [
116
+ `${RECOVERY_CLI_PREFIX}${call.verb}`,
117
+ ...(call.cliFlags ?? []),
118
+ ...flags,
119
+ ...guard,
120
+ ...words,
121
+ ].join(" ");
122
+ }
123
+
124
+ /** The MCP spelling: the tool name and its argument bag. */
125
+ function renderMcpCall(call: Call): string {
126
+ const body = Object.entries(call.params ?? {})
127
+ .filter(([, value]) => value !== undefined)
128
+ .map(([name, value]) => `${name}: ${JSON.stringify(value)}`)
129
+ .join(", ");
130
+ return `${callToolName(call)} ${body ? `{ ${body} }` : "{}"}`;
131
+ }
132
+
133
+ /**
134
+ * Spell a call for the surface it is about to be printed on.
135
+ *
136
+ * @param call - The verb and params to make.
137
+ * @param surface - `"cli"` for a command line, `"mcp"` for a tool call.
138
+ * @returns `pragma token lookup color.text`, or `token_lookup { name: ["color.text"] }`.
139
+ * @throws Error when a validator is installed (tests only) and rejects the call.
140
+ */
141
+ export function renderCall(call: Call, surface: Surface): string {
142
+ const problem = findProblem?.(call, declared.get(call.verb));
143
+ if (problem) throw new Error(`Unsound call ${call.verb}: ${problem}`);
144
+ return spellingFor(call, surface) === "mcp"
145
+ ? renderMcpCall(call)
146
+ : renderCliCall(call);
147
+ }
148
+
149
+ /**
150
+ * A verb withheld from MCP has no tool to name, so even over MCP it is spelled
151
+ * as the command it is — something an agent can hand to its user.
152
+ */
153
+ function spellingFor(call: Call, surface: Surface): Surface {
154
+ return declared.get(call.verb)?.capability.mcp.expose === false
155
+ ? "cli"
156
+ : surface;
157
+ }
158
+
159
+ /**
160
+ * One argument, quoted as code, in the surface's own spelling: `--tier all` on
161
+ * the CLI, `tier: "all"` over MCP. For the sentences that name an argument to
162
+ * change rather than a whole call to make. The CLI value is shell-quoted like
163
+ * any other word, so what is printed can be pasted.
164
+ */
165
+ export function quoteArgument(
166
+ name: string,
167
+ value: string | number,
168
+ surface: Surface,
169
+ ): string {
170
+ return surface === "mcp"
171
+ ? `\`${name}: ${JSON.stringify(value)}\``
172
+ : `\`--${kebabCase(name)} ${quoteWord(value)}\``;
173
+ }
174
+
175
+ /** A call quoted as code; one containing a backtick takes the doubled fence. */
176
+ export function quoteCall(call: Call, surface: Surface): string {
177
+ const text = renderCall(call, surface);
178
+ return text.includes("`") ? `\`\` ${text} \`\`` : `\`${text}\``;
179
+ }
180
+
181
+ /** The sentence that ends a dead end: the exact next call, for this surface. */
182
+ export function renderNextStep(call: Call, surface: Surface): string {
183
+ return spellingFor(call, surface) === "mcp"
184
+ ? `Call ${quoteCall(call, surface)}.`
185
+ : `Run ${quoteCall(call, surface)}.`;
186
+ }
@@ -24,6 +24,7 @@ import {
24
24
  toolName,
25
25
  verbLabel,
26
26
  } from "./emitSurface.js";
27
+ import { describeTool, useWhenSentence } from "./guidance.js";
27
28
  import type {
28
29
  CapabilityModule,
29
30
  Example,
@@ -31,6 +32,7 @@ import type {
31
32
  ReferenceCliSyntax,
32
33
  VerbSpec,
33
34
  } from "./types.js";
35
+ import { formatWireType, wireType } from "./wireType.js";
34
36
 
35
37
  /** The reference doc set: relative path under `docs/reference/` → file content. */
36
38
  export type ReferenceDocs = ReadonlyMap<string, string>;
@@ -122,19 +124,11 @@ function formatFlagValue(param: ParamSpec): string {
122
124
  }
123
125
 
124
126
  /**
125
- * The tool-schema type label a param projects to (mirrors `buildZodSchema`).
126
- * Enum values are comma-joined (never pipe-joined) so the label is safe inside
127
- * a Markdown table cell without escaping.
127
+ * The tool-schema type label a param projects to: the SAME {@link wireType}
128
+ * `buildZodSchema` validates against, printed — never a mirror of it.
128
129
  */
129
130
  function formatParamType(param: ParamSpec): string {
130
- switch (param.kind) {
131
- case "enum":
132
- return `enum(${param.values.join(", ")})`;
133
- case "string[]":
134
- return "string[]";
135
- default:
136
- return param.kind;
137
- }
131
+ return formatWireType(wireType(param));
138
132
  }
139
133
 
140
134
  /** Render a default value for prose (strings verbatim, everything else stringified). */
@@ -260,6 +254,7 @@ function renderCommandSection(
260
254
  `### ${formatInvocation(verb)}`,
261
255
  verb.summary,
262
256
  verb.doc ?? "",
257
+ useWhenSentence(verb) ?? "",
263
258
  `\`\`\`\n${syntax ? `${BIN_NAME} ${syntax.usage}` : formatUsage(verb)}\n\`\`\``,
264
259
  formatArgsTable(verb.params, syntax?.positionalTokens),
265
260
  formatFlagsTable(verb.params, syntax?.flagTokens),
@@ -410,7 +405,8 @@ function formatToolAnnotations(verb: VerbSpec): string {
410
405
  function renderToolSection(verb: VerbSpec): string {
411
406
  const blocks = [
412
407
  `### ${toolName(verb.path)}`,
413
- verb.doc ?? verb.summary,
408
+ // The description an agent is actually served, from the one generator.
409
+ describeTool(verb),
414
410
  formatToolAnnotations(verb),
415
411
  "**Input**",
416
412
  formatToolParams(verb),
@@ -451,7 +447,7 @@ function renderNonToolSurface(modules: readonly CapabilityModule[]): string {
451
447
  );
452
448
  }
453
449
  bullets.push(
454
- "- **Instructions**: the server always sends handshake instructions describing the conventions and the discovery sequence.",
450
+ "- **Instructions**: the server always sends handshake instructions: the conventions, and a generated index of tools by the question each answers, fitted to 2,000 characters.",
455
451
  );
456
452
  return `## Non-tool surface\n\n${bullets.join("\n")}`;
457
453
  }
@@ -16,6 +16,14 @@ export function kebabCase(name: string): string {
16
16
  return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
17
17
  }
18
18
 
19
+ /**
20
+ * The ceiling on the whole MCP tool catalogue (name + description + input
21
+ * schema per tool, ~4 characters a token). ONE constant: the emitted surface
22
+ * states it, the covenant freezes it, and the eval case enforces it. Set from
23
+ * measurement plus about a tenth — `BUDGETS.md` records each raise.
24
+ */
25
+ export const CONDENSED_SDL_TOKEN_BUDGET = 9600;
26
+
19
27
  /** The MCP tool name for a verb path (`["config","show"]` -> `config_show`). */
20
28
  export function toolName(path: readonly [string, string?]): string {
21
29
  const [noun, verb] = path;
@@ -147,7 +155,7 @@ export const FIXED_SURFACE = {
147
155
  projectConfigLoad: "<10ms",
148
156
  warmStoreVerb: "<300ms",
149
157
  mcpP95Warm: "<100ms",
150
- condensedSDL: "<=8000 tokens",
158
+ condensedSDL: `<=${CONDENSED_SDL_TOKEN_BUDGET} tokens`,
151
159
  },
152
160
  } as const;
153
161
 
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { capabilities } from "../../capabilities/index.js";
3
+ import { describeTool, useWhenSentence, verbCategory } from "./guidance.js";
4
+ import type { VerbSpec } from "./types.js";
5
+
6
+ const find = (path: string): VerbSpec =>
7
+ capabilities
8
+ .flatMap((module) => module.verbs)
9
+ .find((verb) => verb.path.join(" ") === path) as VerbSpec;
10
+
11
+ describe("the one generator of tool guidance", () => {
12
+ it("leads with the question, adds authored prose, ends with the example", () => {
13
+ const lookup = find("token lookup");
14
+ const description = describeTool(lookup);
15
+ expect(description.startsWith(`Use ${lookup.useWhen}.`)).toBe(true);
16
+ expect(description).toContain(lookup.doc);
17
+ expect(
18
+ description.endsWith(
19
+ 'Example: token_lookup { name: ["color.text","color.border"] }.',
20
+ ),
21
+ ).toBe(true);
22
+ });
23
+
24
+ it("does not repeat the one-line summary after the question, and shows no example for a verb callable bare", () => {
25
+ const list = find("skill list");
26
+ expect(list.doc).toBeUndefined();
27
+ expect(describeTool(list)).toBe(useWhenSentence(list));
28
+ });
29
+
30
+ it("falls back to doc or summary for a verb declaring no question (a third-party story may)", () => {
31
+ const { useWhen: _useWhen, doc: _doc, ...bare } = find("skill list");
32
+ expect(describeTool(bare as VerbSpec)).toBe(bare.summary);
33
+ expect(useWhenSentence(bare as VerbSpec)).toBeUndefined();
34
+ });
35
+
36
+ it("does not add a second example to a description that still hand-types one", () => {
37
+ const legacy = {
38
+ ...find("token lookup"),
39
+ doc: 'Get a token. Example: token_lookup { name: ["x"] }.',
40
+ } as VerbSpec;
41
+ expect(describeTool(legacy).match(/Example:/g)).toHaveLength(1);
42
+ });
43
+
44
+ it("derives write from mutates, and reads the rest off the verb", () => {
45
+ expect(verbCategory(find("sources update"))).toBe("write");
46
+ expect(verbCategory(find("doctor"))).toBe("diagnostic");
47
+ expect(verbCategory(find("block list"))).toBe("read");
48
+ });
49
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The ONE generator of tool guidance.
3
+ *
4
+ * A verb declares the question it answers (`useWhen`) and one real call
5
+ * (`example`) once. Everything a caller reads when choosing a tool is built
6
+ * here from those two and the summary: the MCP tool description, the
7
+ * `capabilities` catalogue entry, the verb's help and the reference. A sentence
8
+ * that lives in one place cannot be true in the catalogue and missing from the
9
+ * description, which is how an agent that never called `capabilities` came to
10
+ * choose between fifty tools without it.
11
+ *
12
+ * It names no tool: what the distribution says about its own tools arrives as
13
+ * data (a verb's fields, a module's `mcpOrientation`).
14
+ *
15
+ * Zod-free: the catalogue and help read it on the fast path.
16
+ */
17
+
18
+ import { renderCall } from "./call.js";
19
+ import type { Call, ToolCategory, VerbSpec } from "./types.js";
20
+
21
+ /** A verb's category: `write` ⟺ it mutates, else what it declares, else `read`. */
22
+ export function verbCategory(verb: VerbSpec): ToolCategory {
23
+ return verb.capability.mutates ? "write" : (verb.category ?? "read");
24
+ }
25
+
26
+ /**
27
+ * A verb's declared example as a call. A verb with no required param may
28
+ * declare none — called bare, its only call is its own name — and then none
29
+ * is rendered.
30
+ */
31
+ export function exampleCall(verb: VerbSpec): Call | undefined {
32
+ return verb.example
33
+ ? { verb: verb.path.join(" "), params: verb.example }
34
+ : undefined;
35
+ }
36
+
37
+ /**
38
+ * `useWhen` as a sentence. It is stored as the bare clause ("when asked which
39
+ * components use a token") because the catalogue's field is already named
40
+ * `use_when`; prose adds the lead.
41
+ */
42
+ export function useWhenSentence(verb: VerbSpec): string | undefined {
43
+ return verb.useWhen ? `Use ${verb.useWhen}.` : undefined;
44
+ }
45
+
46
+ /**
47
+ * The agent-facing MCP tool description: the question first — it is what a
48
+ * caller scanning fifty descriptions matches its task against — then what the
49
+ * tool returns when a richer `doc` is authored, then one call to copy. The
50
+ * one-line summary is the fallback for a verb declaring no question, never a
51
+ * second sentence after one: it says the same thing less usefully.
52
+ *
53
+ * @param verb - The verb to describe.
54
+ * @returns The description.
55
+ */
56
+ export function describeTool(verb: VerbSpec): string {
57
+ const question = useWhenSentence(verb);
58
+ const example = exampleCall(verb);
59
+ const rendered = example && `Example: ${renderCall(example, "mcp")}.`;
60
+ return [
61
+ question,
62
+ verb.doc ?? (question ? undefined : verb.summary),
63
+ // A third-party description may still end in its own hand-typed example.
64
+ rendered && !verb.doc?.includes("Example:") ? rendered : undefined,
65
+ ]
66
+ .filter(Boolean)
67
+ .join(" ");
68
+ }
@@ -23,14 +23,18 @@ export {
23
23
  export type { Covenant } from "./surfaceConformance.js";
24
24
  export { assertConforms, deepEqual } from "./surfaceConformance.js";
25
25
  export type {
26
+ Call,
26
27
  Capability,
27
28
  CapabilityModule,
28
29
  DisclosureSpec,
29
30
  Example,
30
31
  Formatters,
31
32
  McpAnnotations,
33
+ McpOrientation,
32
34
  McpResourceProvider,
33
35
  ParamComplete,
34
36
  ParamSpec,
37
+ Surface,
38
+ ToolCategory,
35
39
  VerbSpec,
36
40
  } from "./types.js";
@@ -84,8 +84,8 @@ describe("surface conformance (PROTECTED)", () => {
84
84
  );
85
85
  });
86
86
 
87
- it("freezes exactly 49 designed MCP tools including info, the config family, colophon", () => {
88
- expect(golden.mcpSurface.tools).toHaveLength(49);
87
+ it("freezes exactly 51 designed MCP tools including info, the config family, colophon", () => {
88
+ expect(golden.mcpSurface.tools).toHaveLength(51);
89
89
  expect(golden.mcpSurface.tools).toContain("info");
90
90
  expect(golden.mcpSurface.tools).toContain("config_show");
91
91
  expect(golden.mcpSurface.tools).toContain("config_get");
@@ -12,7 +12,28 @@
12
12
  */
13
13
 
14
14
  import type { Effect, Task } from "@canonical/task";
15
- import type { PragmaRuntime } from "../runtime/types.js";
15
+ import type { InteractionRuntime, PragmaRuntime } from "../runtime/types.js";
16
+
17
+ /** Where text is about to be printed — the projector driving the run. */
18
+ export type Surface = InteractionRuntime["transport"];
19
+
20
+ /**
21
+ * A verb path (`"sources update"`) and the params to call it with — the one
22
+ * type behind every example and every recovery (`call.ts` spells it).
23
+ */
24
+ export interface Call {
25
+ readonly verb: string;
26
+ readonly params?: Readonly<Record<string, unknown>>;
27
+ /**
28
+ * GLOBAL CLI flags the command should carry (`--verbose`). They are not
29
+ * params of the verb and have no MCP counterpart, so only the CLI spelling
30
+ * renders them.
31
+ */
32
+ readonly cliFlags?: readonly string[];
33
+ }
34
+
35
+ /** A tool's behavioural category, used for grouping + counts. */
36
+ export type ToolCategory = "read" | "write" | "orientation" | "diagnostic";
16
37
 
17
38
  /** MCP tool annotations mirrored onto exposed verbs. */
18
39
  export type McpAnnotations = {
@@ -115,8 +136,8 @@ export type ParamSpec =
115
136
  * A repeatable flag ACCUMULATES: `--category css --category git` is
116
137
  * the union, never last-wins (repetition is the sanctioned multi-value
117
138
  * form, and silently dropping all but the last value is data loss).
118
- * CLI-side only: the MCP arg schema keeps its scalar shape, and the
119
- * run body accepts one value or many.
139
+ * Over MCP the same param is an ARRAY that also takes one bare value
140
+ * (`spec/wireType.ts`); the run body accepts one value or many.
120
141
  */
121
142
  repeatable?: true;
122
143
  }
@@ -173,8 +194,9 @@ export type ParamSpec =
173
194
  * mutes it (success-path guidance).
174
195
  * - `--format json` AND the MCP tool result: it rides the envelope as
175
196
  * `meta.notice` (`project/cli/dispatch.ts#renderData`,
176
- * `project/mcp/registerVerb.ts#emptyMeta` — the same key from the same seam,
177
- * so the two MACHINE surfaces stay byte-equal). `data` keeps its uniform empty
197
+ * `project/mcp/registerVerb.ts#noticeMeta` — the same key from the same
198
+ * seam; the sentence differs only in how a next step inside it is spelled
199
+ * for its surface). `data` keeps its uniform empty
178
200
  * shape; `[]` stays `[]`. Without it an agent could not tell an unbuilt store,
179
201
  * a mistyped filter and a genuinely empty result apart — all three were
180
202
  * `{"ok":true,"data":[],"meta":{}}`.
@@ -191,7 +213,12 @@ export interface Formatters<T> {
191
213
  ) => string;
192
214
  readonly llm: (d: T) => string;
193
215
  readonly json: (d: T) => string;
194
- readonly notice?: (d: T) => string | undefined;
216
+ /**
217
+ * `surface` says where the sentence is about to be printed, so a notice that
218
+ * ends in a next call spells it as a tool call on MCP and a command on the
219
+ * CLI (the default).
220
+ */
221
+ readonly notice?: (d: T, surface?: Surface) => string | undefined;
195
222
  /**
196
223
  * The MACHINE half of the notice seam: facts about the read that belong in
197
224
  * the envelope's `meta` as DATA, merged there by both projectors under the
@@ -200,7 +227,7 @@ export interface Formatters<T> {
200
227
  * `notice` is a sentence, and a sentence is the right shape for a human
201
228
  * reading stderr and the wrong shape for an agent deciding what to do next.
202
229
  * The standing case is the tier scope: `meta.scope` names the tiers a list
203
- * answered from, so a caller that wants the rest can widen the read without
230
+ * answered from and counts the whole filtered answer per tier, so a caller that wants the rest can widen the read without
204
231
  * parsing prose for tier names. Both machine surfaces read this ONE seam, the
205
232
  * way they already read one `notice`, so CLI-JSON and MCP stay byte-equal.
206
233
  *
@@ -248,6 +275,24 @@ export interface VerbSpec<P = Record<string, unknown>, R = unknown> {
248
275
  readonly path: readonly [noun: string, verb?: string];
249
276
  readonly summary: string;
250
277
  readonly doc?: string;
278
+ /**
279
+ * The question a person would ask that this verb answers, in THEIR words, as
280
+ * a bare clause ("when asked which components use a token" — prose adds the
281
+ * "Use" lead, the catalogue's `use_when` field needs none). With {@link example} it is
282
+ * the one source of guidance: `guidance.ts` builds the MCP tool description,
283
+ * the `capabilities` catalogue and verb help from it, so none is typed twice.
284
+ * Optional in the type because a third-party story may omit it; every verb
285
+ * this distribution registers is held to it by `callRule.test.ts`.
286
+ */
287
+ readonly useWhen?: string;
288
+ /**
289
+ * One real call's params — the verb is this one, and both spellings derive.
290
+ * Required of a verb with a required param; a verb callable with no arguments
291
+ * declares none, because `tool {}` teaches nothing.
292
+ */
293
+ readonly example?: Readonly<Record<string, unknown>>;
294
+ /** The catalogue group, where it is not the default: a mutating verb is a `write`, any other a `read`. */
295
+ readonly category?: "orientation" | "diagnostic";
251
296
  readonly params: readonly ParamSpec[];
252
297
  readonly output: {
253
298
  schema?: unknown;
@@ -505,6 +550,16 @@ export interface CliProjection {
505
550
  ) => ReferenceCliSyntax | undefined;
506
551
  }
507
552
 
553
+ /**
554
+ * What a distribution tells an agent at the MCP handshake, as DATA: the words
555
+ * that name its own tiers and nouns. The kernel adds the generated question →
556
+ * tool index and holds the whole to a hard length ceiling.
557
+ */
558
+ export interface McpOrientation {
559
+ /** The sentences the instructions open with, in order. */
560
+ readonly conventions: readonly string[];
561
+ }
562
+
508
563
  /** A capability module: a named bundle of verbs with optional boot/resources/prompts hooks. */
509
564
  export interface CapabilityModule {
510
565
  readonly name: string;
@@ -527,6 +582,8 @@ export interface CapabilityModule {
527
582
  */
528
583
  readonly story?: true;
529
584
  readonly boot?: (rt: PragmaRuntime) => void;
585
+ /** The handshake orientation (a module hook; one module declares it). */
586
+ readonly mcpOrientation?: McpOrientation;
530
587
  /** An optional MCP resource surface (NOT a VerbSpec field — a module hook). */
531
588
  readonly mcpResources?: McpResourceProvider;
532
589
  /**