@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
@@ -9,7 +9,6 @@
9
9
  * them (disclosure gates the fetch), never HOW it is laid out.
10
10
  */
11
11
 
12
- import { BIN_NAME, RECOVERY_CLI_PREFIX } from "../../constants.js";
13
12
  import type {
14
13
  ColumnDef,
15
14
  LookupField,
@@ -25,18 +24,25 @@ import {
25
24
  renderLookupLlm,
26
25
  renderLookupPlain,
27
26
  } from "../render/renderers.js";
28
- import type { Formatters } from "../spec/index.js";
29
- import { kebabCase } from "../spec/index.js";
27
+ import {
28
+ BUILD_STORE_CALL,
29
+ quoteArgument,
30
+ renderNextStep,
31
+ } from "../spec/call.js";
32
+ import type { Formatters, Surface } from "../spec/index.js";
30
33
  import type { LookupOutput } from "./resolveEntity.js";
31
34
  import {
32
35
  EVERY_TIER,
33
36
  type PackAppliedFilter,
34
37
  type PackChildRow,
38
+ type PackEmptyRecovery,
35
39
  type PackEntity,
36
40
  type PackList,
37
41
  type PackLookup,
38
42
  type PackPage,
39
43
  type PackRow,
44
+ TIER_PARAM,
45
+ UNTIERED_KEY,
40
46
  } from "./types.js";
41
47
 
42
48
  /** Sample output: the drawn exemplars, the population size, and agent follow-ups. */
@@ -59,7 +65,15 @@ export interface RenderMeta {
59
65
  * list on a BUILT store (a cold store would have failed with STORE_UNAVAILABLE
60
66
  * first) means "nothing matched", so point at both possible fixes.
61
67
  */
62
- const DEFAULT_EMPTY_HINT = `Either nothing matched — try a wider filter — or the store has nothing in it yet: build it with \`${BIN_NAME} sources update\`.`;
68
+ export const DEFAULT_EMPTY_RECOVERY: PackEmptyRecovery = {
69
+ message:
70
+ "Either nothing matched — try a wider filter — or the store has nothing in it yet and needs building.",
71
+ call: BUILD_STORE_CALL,
72
+ };
73
+
74
+ /** What a filtered empty page says first: the store is fine, the filter missed. */
75
+ const FILTERED_EMPTY_HINT =
76
+ "The store answered and nothing carries that — drop the argument, or loosen it, to widen the read.";
63
77
 
64
78
  /**
65
79
  * Build the list formatters for a list-shaped verb (list or an extra verb).
@@ -93,42 +107,67 @@ export function listFormatters(
93
107
  // Zero results is a calm success, not an error (see runBodies.makeListRun):
94
108
  // render a non-blank message, exit 0, JSON stays []. A pack's authored
95
109
  // `emptyRecovery` becomes the hint; otherwise the generic build/broaden hint.
96
- const emptyHint = shape.emptyRecovery
97
- ? `${shape.emptyRecovery.message}${
98
- shape.emptyRecovery.cli
99
- ? ` Run \`${RECOVERY_CLI_PREFIX}${shape.emptyRecovery.cli}\`.`
100
- : ""
101
- }`
102
- : DEFAULT_EMPTY_HINT;
103
- const options: RenderListOptions<PackRow> = {
110
+ // Built per call, not once: the hint ends in the next call to make, spelled
111
+ // for the surface that is about to print it.
112
+ const { message, call } = shape.emptyRecovery ?? DEFAULT_EMPTY_RECOVERY;
113
+ const hintFor = (surface: Surface): string =>
114
+ call ? `${message} ${renderNextStep(call, surface)}` : message;
115
+ // A FILTERED empty page is a miss, not an empty store: it says how to widen,
116
+ // keeps the story's own account of the emptiness, and drops the rebuild call
117
+ // (any other next step a story declares — listing the values — still helps).
118
+ const declared = shape.emptyRecovery;
119
+ const filteredHintFor = (surface: Surface): string =>
120
+ [
121
+ FILTERED_EMPTY_HINT,
122
+ declared?.message,
123
+ declared?.call && declared.call.verb !== BUILD_STORE_CALL.verb
124
+ ? renderNextStep(declared.call, surface)
125
+ : undefined,
126
+ ]
127
+ .filter(Boolean)
128
+ .join(" ");
129
+ const optionsFor = (surface: Surface): RenderListOptions<PackRow> => ({
104
130
  heading: meta.heading,
105
131
  columns,
106
132
  prefixes: meta.prefixes,
107
133
  emptyMessage: `No ${meta.noun} entries found.`,
108
- emptyHint,
109
- };
134
+ emptyHint: hintFor(surface),
135
+ });
110
136
  return {
111
- plain: (page, context) => renderListPlain(page.rows, options, context),
137
+ plain: (page, context) =>
138
+ renderListPlain(page.rows, optionsFor("cli"), context),
112
139
  llm: (page) => {
113
- const body = renderListLlm(page.rows, emptyCopy(page, meta, options), {
114
- more: page.nextAfter !== undefined,
115
- ...(scopeText(page) === undefined
116
- ? {}
117
- : { scope: scopeText(page) as string }),
118
- });
119
- const notice = listNotice(page, meta);
140
+ const body = renderListLlm(
141
+ page.rows,
142
+ emptyCopy(page, meta, optionsFor("cli"), "cli", filteredHintFor("cli")),
143
+ {
144
+ more: page.nextAfter !== undefined,
145
+ ...(scopeText(page) === undefined
146
+ ? {}
147
+ : { scope: scopeText(page) as string }),
148
+ },
149
+ );
150
+ const notice = listNotice(page, meta, "cli");
120
151
  return notice ? `${body}\n\n${notice}` : body;
121
152
  },
122
153
  json: (page) => JSON.stringify(page.rows, null, 2),
123
154
  // Zero rows: the dispatcher routes this to stderr (exit 0) so the plain
124
155
  // stdout stream stays pure data; llm/json keep their own empty shapes.
125
- notice: (page) =>
156
+ notice: (page, surface = "cli") =>
126
157
  page.rows.length === 0
127
158
  ? joinNotices([
128
- renderListEmptyNotice(emptyCopy(page, meta, options)),
129
- scopeNotice(page),
159
+ renderListEmptyNotice(
160
+ emptyCopy(
161
+ page,
162
+ meta,
163
+ optionsFor(surface),
164
+ surface,
165
+ filteredHintFor(surface),
166
+ ),
167
+ ),
168
+ scopeNotice(page, surface),
130
169
  ])
131
- : listNotice(page, meta),
170
+ : listNotice(page, meta, surface),
132
171
  // The scope rides the envelope as DATA as well as prose: an agent deciding
133
172
  // whether to widen the read should not have to parse a sentence to learn
134
173
  // which tiers it got.
@@ -136,9 +175,22 @@ export function listFormatters(
136
175
  };
137
176
  }
138
177
 
139
- /** The tier scope in the words `--tier` accepts, or nothing when unscoped. */
178
+ /**
179
+ * The tier scope in the words `--tier` accepts, each tier with its share of
180
+ * the whole filtered answer; an EMPTY page also names the tiers outside the
181
+ * scope that hold a match. Nothing when unscoped.
182
+ */
140
183
  function scopeText(page: PackPage): string | undefined {
141
- return page.scope ? page.scope.tiers.join(", ") : undefined;
184
+ if (!page.scope) return undefined;
185
+ const { tiers, counts } = page.scope;
186
+ if (!counts) return tiers.join(", ");
187
+ const held = (tier: string): string => `${tier} ${counts[tier] ?? 0}`;
188
+ const inside = [...tiers, ...(UNTIERED_KEY in counts ? [UNTIERED_KEY] : [])];
189
+ const outside = Object.keys(counts).filter((tier) => !inside.includes(tier));
190
+ const text = inside.map(held).join(", ");
191
+ return page.rows.length > 0 || outside.length === 0
192
+ ? text
193
+ : `${text}; other tiers: ${outside.map(held).join(", ")}`;
142
194
  }
143
195
 
144
196
  /**
@@ -150,18 +202,25 @@ function scopeText(page: PackPage): string | undefined {
150
202
  * the scope — and the notice seam is where this package already puts what the
151
203
  * data cannot say about itself.
152
204
  */
153
- function scopeNotice(page: PackPage): string | undefined {
205
+ function scopeNotice(page: PackPage, surface: Surface): string | undefined {
154
206
  const scope = scopeText(page);
155
207
  if (scope === undefined) return undefined;
156
208
  return (
157
209
  `Tier scope: ${scope}. ` +
158
- `Pass \`--tier <name>\` for one tier and its ancestors, or \`--tier ${EVERY_TIER}\` for every tier.`
210
+ `Pass ${quoteArgument(TIER_PARAM, "<name>", surface)} for one tier and its ancestors, or ${quoteArgument(TIER_PARAM, EVERY_TIER, surface)} for every tier.`
159
211
  );
160
212
  }
161
213
 
162
214
  /** The page's notices, in the order a reader needs them: scope, then paging. */
163
- function listNotice(page: PackPage, meta: RenderMeta): string | undefined {
164
- return joinNotices([scopeNotice(page), pageNotice(page, meta)]);
215
+ function listNotice(
216
+ page: PackPage,
217
+ meta: RenderMeta,
218
+ surface: Surface,
219
+ ): string | undefined {
220
+ return joinNotices([
221
+ scopeNotice(page, surface),
222
+ pageNotice(page, meta, surface),
223
+ ]);
165
224
  }
166
225
 
167
226
  /** Join what a page has to say into one notice, dropping what it has not. */
@@ -175,46 +234,40 @@ function joinNotices(
175
234
  /**
176
235
  * The empty-state copy this page deserves.
177
236
  *
178
- * Two facts can empty a list and zero rows cannot tell them apart: nothing was
179
- * there, or a filter missed what was. Both are worth saying and they are not
180
- * exclusive, so a filtered empty page says both — the kernel's own sentence
181
- * names the arguments the caller typed, and the story's `emptyRecovery` follows
182
- * it on the next line, exactly as it does on an unfiltered page.
183
- *
184
- * It did not, briefly: a filtered empty page reported only the narrowing,
185
- * because the recoveries of the day asserted an empty store ("No token symbols
186
- * in the store … run `pragma sources update`") and so told a reader with 745
187
- * symbols in it to rebuild for nothing. That is a WORDING defect, and it is
188
- * fixed where it lives — a story's recovery is now written to hold whether the
189
- * population is empty or a filter missed a populated one, or the story declares
190
- * none. Suppressing it here cost more than it saved: `token consumers --symbol
191
- * color.text` narrows a table that records no bindings at all, and "No token
192
- * matches `--symbol color.text`." on its own read as a mistyped symbol while
193
- * withholding the one account of the emptiness that verb has.
237
+ * Two facts can empty a list: nothing was there, or a filter missed what was.
238
+ * An unfiltered empty page gets the story's `emptyRecovery` whole. A filtered
239
+ * one names the arguments the caller typed and takes `filteredHint` instead —
240
+ * see {@link listFormatters} for what that keeps and drops.
194
241
  *
195
242
  * @param page - The rendered page (its rows and the filters that cut them).
196
243
  * @param meta - The noun, for the sentence.
197
244
  * @param base - The story's own empty copy.
198
- * @returns `base` unchanged, or `base` with the filter-shaped message — the
199
- * story's own hint is kept either way.
245
+ * @param filteredHint - The hint a filtered empty page carries.
246
+ * @returns `base` unchanged, or `base` with the filter-shaped message and hint.
200
247
  */
201
248
  function emptyCopy(
202
249
  page: PackPage,
203
250
  meta: RenderMeta,
204
251
  base: RenderListOptions<PackRow>,
252
+ surface: Surface,
253
+ filteredHint: string,
205
254
  ): RenderListOptions<PackRow> {
206
255
  const applied = page.filters ?? [];
207
256
  if (page.rows.length > 0 || applied.length === 0) return base;
208
257
  return {
209
258
  ...base,
210
- emptyMessage: `No ${meta.noun} matches ${listFilters(applied)}.`,
259
+ emptyMessage: `No ${meta.noun} matches ${listFilters(applied, surface)}.`,
260
+ emptyHint: filteredHint,
211
261
  };
212
262
  }
213
263
 
214
264
  /** The filters in force, as flags a reader can edit: `\`--kind input\`` … */
215
- function listFilters(applied: readonly PackAppliedFilter[]): string {
216
- const flags = applied.map(
217
- (filter) => `\`--${kebabCase(filter.param)} ${filter.value}\``,
265
+ function listFilters(
266
+ applied: readonly PackAppliedFilter[],
267
+ surface: Surface,
268
+ ): string {
269
+ const flags = applied.map((filter) =>
270
+ quoteArgument(filter.param, filter.value, surface),
218
271
  );
219
272
  const last = flags.at(-1) as string;
220
273
  return flags.length === 1
@@ -231,12 +284,16 @@ function listFilters(applied: readonly PackAppliedFilter[]): string {
231
284
  * that carry this are read by agents — so the sentence contains the flag, the
232
285
  * value, and the tool parameter, ready to copy.
233
286
  */
234
- function pageNotice(page: PackPage, meta: RenderMeta): string | undefined {
287
+ function pageNotice(
288
+ page: PackPage,
289
+ meta: RenderMeta,
290
+ surface: Surface,
291
+ ): string | undefined {
235
292
  if (page.nextAfter === undefined) return undefined;
236
293
  return (
237
294
  `Showing ${page.rows.length} ${meta.noun} entries, and more exist. ` +
238
- `For the next page pass \`--after ${page.nextAfter}\` ` +
239
- `(\`after\` over MCP), or raise \`--limit\` (currently ${page.limit}).`
295
+ `For the next page pass ${quoteArgument("after", page.nextAfter, surface)}, ` +
296
+ `or raise ${surface === "mcp" ? "`limit`" : "`--limit`"} (currently ${page.limit}).`
240
297
  );
241
298
  }
242
299
 
@@ -292,9 +349,29 @@ export function lookupFormatters(
292
349
  llm: (output) =>
293
350
  renderOutput(output, (entity) => renderLookupLlm(entity, options), "llm"),
294
351
  json: (output) => JSON.stringify(output, null, 2),
352
+ notice: (output, surface = "cli") => truncationNotice(output, surface),
295
353
  };
296
354
  }
297
355
 
356
+ /** What a capped pattern says for itself, on the notice seam and in the llm body. */
357
+ function truncationNotice(
358
+ output: LookupOutput,
359
+ surface: Surface,
360
+ ): string | undefined {
361
+ if (!output.truncated) return undefined;
362
+ const advice =
363
+ output.detail === "summary"
364
+ ? ""
365
+ : `, or pass ${quoteArgument("detail", "summary", surface)} for smaller entries`;
366
+ const elsewhere = output.elsewhere
367
+ ? `, and ${output.elsewhere} more in other tiers: pass ${quoteArgument(TIER_PARAM, EVERY_TIER, surface)}`
368
+ : "";
369
+ return (
370
+ `${output.results.length} of ${output.total} matches shown${elsewhere}. ` +
371
+ `Narrow the pattern to reach the rest${advice}.`
372
+ );
373
+ }
374
+
298
375
  /** Build the sample formatters (renders each exemplar, then the follow-ups). */
299
376
  export function sampleFormatters(
300
377
  lookup: PackLookup,
@@ -366,6 +443,9 @@ function renderOutput(
366
443
  `${mode === "llm" ? "### Not found" : "Not found:"}\n${lines.join("\n")}`,
367
444
  );
368
445
  }
446
+ const truncation =
447
+ mode === "llm" ? truncationNotice(output, "cli") : undefined;
448
+ if (truncation) bodies.push(truncation);
369
449
  return bodies.join("\n\n").trimEnd();
370
450
  }
371
451
 
@@ -32,7 +32,7 @@ import { buildFixtureRuntime } from "../../testing/helpers/packRuntime.js";
32
32
  import type { PragmaRuntime } from "../runtime/types.js";
33
33
  import { compilePack } from "./compile.js";
34
34
  import { lookupFormatters, lookupOptions } from "./renderPack.js";
35
- import type { LookupOutput } from "./resolveEntity.js";
35
+ import { GLOB_EXPANSION_CAP, type LookupOutput } from "./resolveEntity.js";
36
36
  import type { PackDefinition, PackLookup } from "./types.js";
37
37
  import { distributionSource } from "./types.js";
38
38
  import { verbKey } from "./uniqueness.js";
@@ -259,6 +259,70 @@ describe("pack lookup addressing (PROTECTED)", () => {
259
259
  });
260
260
  });
261
261
 
262
+ describe("a glob with no prefix reads names AND IRI local names", () => {
263
+ it("reaches an entity through its local name", async () => {
264
+ // The recorded case: `*.component.meter` is a pattern over what every
265
+ // list prints, and it matched nothing while names were the only thing a
266
+ // prefix-less glob was tried against.
267
+ const out = await lookupVia(SPQ, "*.timeline");
268
+ expect(out.errors).toEqual([]);
269
+ expect(uris(out)).toEqual([`${DS}padded.timeline`]);
270
+ });
271
+
272
+ it("reaches an entity that carries no name at all", async () => {
273
+ expect(uris(await lookupVia(SPQ, "nameless.*"))).toEqual([
274
+ `${DS}nameless.widget`,
275
+ ]);
276
+ });
277
+
278
+ it("answers ONCE for an entity both its name and its local name match", async () => {
279
+ // `*chip` fits the name "Chip" and the local names `alpha.chip` and
280
+ // `zeta.chip`. The name match already reaches both, so neither is looked
281
+ // up a second time by IRI.
282
+ const out = await lookupVia(SPQ, "*chip");
283
+ expect(uris(out).sort()).toEqual([`${DS}alpha.chip`, `${DS}zeta.chip`]);
284
+ });
285
+
286
+ it("does not make a bare local name an address", async () => {
287
+ await expect(lookupVia(SPQ, "alpha.chip")).rejects.toMatchObject({
288
+ code: "ENTITY_NOT_FOUND",
289
+ });
290
+ });
291
+ });
292
+
293
+ describe("a miss suggests the real thing", () => {
294
+ const suggestionsFor = async (query: string): Promise<string[]> => {
295
+ const reason = await lookupVia(SPQ, query).catch(
296
+ (error: unknown) => error,
297
+ );
298
+ expect(reason).toMatchObject({ code: "ENTITY_NOT_FOUND" });
299
+ return (reason as { suggestions: string[] }).suggestions;
300
+ };
301
+
302
+ it("answers a pasted local name with its prefixed IRI, then the name inside it", async () => {
303
+ expect((await suggestionsFor("alpha.chip")).slice(0, 2)).toEqual([
304
+ "ds:alpha.chip",
305
+ "Chip",
306
+ ]);
307
+ });
308
+
309
+ it("answers a mistyped local name with the prefixed IRI it was near", async () => {
310
+ expect(await suggestionsFor("nameless.widgit")).toContain(
311
+ "ds:nameless.widget",
312
+ );
313
+ });
314
+
315
+ it("every suggestion it prints resolves", async () => {
316
+ const suggestions = await suggestionsFor("alpha.chip");
317
+ const out = await lookupVia(SPQ, ...suggestions);
318
+ expect(out.errors).toEqual([]);
319
+ });
320
+
321
+ it("keeps the five-suggestion cap", async () => {
322
+ expect((await suggestionsFor("a")).length).toBeLessThanOrEqual(5);
323
+ });
324
+ });
325
+
262
326
  describe("an ambiguous name answers with EVERY entity it reaches", () => {
263
327
  // `ds:zeta.chip` is declared BEFORE `ds:alpha.chip` in the fixture, so the
264
328
  // store enumerates it first while IRI order puts `alpha` first. Neither of
@@ -331,3 +395,108 @@ describe("pack lookup addressing (PROTECTED)", () => {
331
395
  });
332
396
  });
333
397
  });
398
+
399
+ describe("a glob says what it did", () => {
400
+ /** More same-family entities than one lookup's patterns may expand to. */
401
+ const FAMILY = GLOB_EXPANSION_CAP + 12;
402
+ const pad = (index: number): string => String(index).padStart(3, "0");
403
+ const FAMILY_TTL = Array.from(
404
+ { length: FAMILY },
405
+ (_, index) =>
406
+ `ds:swatch.${pad(index)} a ds:Component ; ds:name "swatch.${pad(index)}" .`,
407
+ ).join("\n");
408
+
409
+ let rt: PragmaRuntime;
410
+ beforeAll(async () => {
411
+ ({ rt } = await buildFixtureRuntime({
412
+ ttl: BLOCK_TTL + AMBIGUOUS_TTL + FAMILY_TTL,
413
+ prefixes: BLOCK_PREFIXES,
414
+ detail: "detailed",
415
+ }));
416
+ });
417
+ afterAll(async () => {
418
+ (await rt.store.get()).store.dispose();
419
+ });
420
+
421
+ const lookup = (...name: string[]): Promise<LookupOutput> => {
422
+ const verb = compilePack(SPQ, distributionSource("t"), BLOCK_PREFIXES).find(
423
+ (v) => verbKey(v.path) === "sblock lookup",
424
+ );
425
+ if (!verb) throw new Error("no lookup verb");
426
+ return verb.run({ name }, rt) as Promise<LookupOutput>;
427
+ };
428
+ const formatters = lookupFormatters(SPQ.lookup as PackLookup, BLOCK_PREFIXES);
429
+
430
+ it("cuts a wide pattern at the cap and reports the true total", async () => {
431
+ const out = await lookup("swatch.*");
432
+ expect(out.results).toHaveLength(GLOB_EXPANSION_CAP);
433
+ expect(out).toMatchObject({ truncated: true, total: FAMILY });
434
+ });
435
+
436
+ it("says so in each surface's own spelling, and in the condensed body", async () => {
437
+ const out = await lookup("swatch.*");
438
+ const counted = `${GLOB_EXPANSION_CAP} of ${FAMILY} matches shown.`;
439
+ expect(formatters.notice?.(out, "cli")).toContain(counted);
440
+ expect(formatters.notice?.(out, "cli")).toContain("`--detail summary`");
441
+ expect(formatters.notice?.(out, "mcp")).toContain('`detail: "summary"`');
442
+ expect(formatters.llm(out).split("\n").at(-1)).toContain(counted);
443
+ expect(JSON.parse(formatters.json(out)).total).toBe(FAMILY);
444
+ });
445
+
446
+ it("does not advise summary to a caller already there", () => {
447
+ const out: LookupOutput = {
448
+ results: [],
449
+ errors: [],
450
+ truncated: true,
451
+ total: FAMILY,
452
+ detail: "summary",
453
+ };
454
+ expect(formatters.notice?.(out, "mcp")).toBe(
455
+ `0 of ${FAMILY} matches shown. Narrow the pattern to reach the rest.`,
456
+ );
457
+ });
458
+
459
+ it("counts ENTITIES: a name route and an IRI route to the same ones are one total", async () => {
460
+ const out = await lookup("swatch.*", "ds:swatch.*");
461
+ expect(out.total).toBe(FAMILY);
462
+ expect(out.results).toHaveLength(GLOB_EXPANSION_CAP);
463
+ });
464
+
465
+ it("cuts the same fifty every time, in name order", async () => {
466
+ const names = (await lookup("swatch.*")).results.map((e) => e.name);
467
+ expect(names).toEqual(
468
+ Array.from({ length: GLOB_EXPANSION_CAP }, (_, i) => `swatch.${pad(i)}`),
469
+ );
470
+ expect(
471
+ (await lookup("*.0*", "swatch.*")).results.map((e) => e.name),
472
+ ).toEqual(names);
473
+ });
474
+
475
+ it("says nothing about a pattern answered in full", async () => {
476
+ const out = await lookup("swatch.00*");
477
+ expect(out.results).toHaveLength(10);
478
+ expect(out.truncated).toBeUndefined();
479
+ expect(formatters.notice?.(out, "cli")).toBeUndefined();
480
+ });
481
+
482
+ it("counts an entry ONCE across overlapping patterns", async () => {
483
+ // Twenty-seven matches over twenty-five entries: `swatch.001` and
484
+ // `swatch.011` are each matched twice and answered once.
485
+ const out = await lookup("swatch.00*", "swatch.0*1", "swatch.01*");
486
+ expect(out.truncated).toBeUndefined();
487
+ expect(out.results.map((entity) => entity.name)).toHaveLength(25);
488
+ expect(new Set(out.results.map((entity) => entity.uri)).size).toBe(25);
489
+ });
490
+
491
+ it("answers once for an entity a name pattern and an IRI pattern both reach", async () => {
492
+ const out = await lookup("swatch.001", "ds:swatch.001", "ds:swatch.00*");
493
+ expect(out.results).toHaveLength(10);
494
+ });
495
+
496
+ it("never cuts a literal — not even one a cut pattern also matched", async () => {
497
+ const last = `swatch.${pad(FAMILY - 1)}`;
498
+ const out = await lookup("swatch.*", last);
499
+ expect(out.results.map((entity) => entity.name)).toContain(last);
500
+ expect(out.results).toHaveLength(GLOB_EXPANSION_CAP + 1);
501
+ });
502
+ });