@canonical/pragma-cli 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +1 -1
  2. package/dist/package.json +14 -14
  3. package/dist/pragma.conf.js +273 -176
  4. package/dist/pragma.conf.js.map +1 -1
  5. package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
  6. package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
  7. package/dist/src/capabilities/capabilities/catalog.js +47 -51
  8. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  9. package/dist/src/capabilities/capabilities/index.js +2 -0
  10. package/dist/src/capabilities/capabilities/index.js.map +1 -1
  11. package/dist/src/capabilities/capabilities/types.js +5 -5
  12. package/dist/src/capabilities/colophon/colophon.render.js +7 -4
  13. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  14. package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
  15. package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
  16. package/dist/src/capabilities/config/get.verb.js +2 -0
  17. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  18. package/dist/src/capabilities/config/runField.js +2 -2
  19. package/dist/src/capabilities/config/runField.js.map +1 -1
  20. package/dist/src/capabilities/config/set.verb.js +2 -0
  21. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  22. package/dist/src/capabilities/config/show.verb.js +1 -0
  23. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  24. package/dist/src/capabilities/config/unset.verb.js +2 -0
  25. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  26. package/dist/src/capabilities/create/constants.js +2 -2
  27. package/dist/src/capabilities/create/create.verb.js +14 -4
  28. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  29. package/dist/src/capabilities/distribution.js +3 -1
  30. package/dist/src/capabilities/distribution.js.map +1 -1
  31. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  32. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  33. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  34. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  35. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  37. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  38. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  39. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  40. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  41. package/dist/src/capabilities/graph/query.render.js +37 -5
  42. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  43. package/dist/src/capabilities/graph/query.verb.js +2 -0
  44. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/queryText.js +28 -0
  46. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  47. package/dist/src/capabilities/graph/runConnect.js +3 -3
  48. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  49. package/dist/src/capabilities/graph/runQuery.js +8 -6
  50. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  51. package/dist/src/capabilities/info/info.verb.js +2 -0
  52. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  53. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  54. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  55. package/dist/src/capabilities/ontology/verbs.js +7 -10
  56. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  57. package/dist/src/capabilities/prompt/verbs.js +5 -4
  58. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  59. package/dist/src/capabilities/resources/provider.js +6 -3
  60. package/dist/src/capabilities/resources/provider.js.map +1 -1
  61. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  62. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  63. package/dist/src/capabilities/shared/calls.js +16 -0
  64. package/dist/src/capabilities/shared/calls.js.map +1 -0
  65. package/dist/src/capabilities/skill/render.js +7 -5
  66. package/dist/src/capabilities/skill/render.js.map +1 -1
  67. package/dist/src/capabilities/skill/verbs.js +5 -4
  68. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  69. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  70. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  71. package/dist/src/capabilities/sources/index.js +4 -3
  72. package/dist/src/capabilities/sources/index.js.map +1 -1
  73. package/dist/src/capabilities/sources/reset.render.js +37 -0
  74. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  75. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  76. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  77. package/dist/src/capabilities/sources/runReset.js +59 -0
  78. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  79. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  80. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  81. package/dist/src/capabilities/sources/status.render.js +24 -8
  82. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  83. package/dist/src/capabilities/sources/status.verb.js +1 -0
  84. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  85. package/dist/src/capabilities/sources/update.verb.js +2 -0
  86. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  87. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  88. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  89. package/dist/src/capabilities/version/version.verb.js +1 -0
  90. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  91. package/dist/src/constants.js +4 -3
  92. package/dist/src/constants.js.map +1 -1
  93. package/dist/src/kernel/error/index.js +1 -1
  94. package/dist/src/kernel/error/index.js.map +1 -1
  95. package/dist/src/kernel/error/recovery.js +24 -42
  96. package/dist/src/kernel/error/recovery.js.map +1 -1
  97. package/dist/src/kernel/packs/collect.js +123 -9
  98. package/dist/src/kernel/packs/collect.js.map +1 -1
  99. package/dist/src/kernel/packs/compile.js +74 -15
  100. package/dist/src/kernel/packs/compile.js.map +1 -1
  101. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  102. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  103. package/dist/src/kernel/packs/iri.js +2 -4
  104. package/dist/src/kernel/packs/iri.js.map +1 -1
  105. package/dist/src/kernel/packs/renderPack.js +90 -50
  106. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  107. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  108. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  109. package/dist/src/kernel/packs/runBodies.js +89 -30
  110. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  111. package/dist/src/kernel/packs/schema.js +66 -12
  112. package/dist/src/kernel/packs/schema.js.map +1 -1
  113. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  114. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  115. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  116. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  117. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  118. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  119. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  120. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  121. package/dist/src/kernel/packs/storyRules.js +31 -4
  122. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  123. package/dist/src/kernel/packs/tierScope.js +3 -3
  124. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  125. package/dist/src/kernel/packs/types.js +4 -0
  126. package/dist/src/kernel/packs/types.js.map +1 -1
  127. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  128. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  129. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  130. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  131. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  132. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  133. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  134. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  135. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  136. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  137. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  138. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  139. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  140. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  141. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  142. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  143. package/dist/src/kernel/render/renderers.js +3 -3
  144. package/dist/src/kernel/render/turtle.js +21 -4
  145. package/dist/src/kernel/render/turtle.js.map +1 -1
  146. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  147. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  148. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  149. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  150. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  151. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  152. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  153. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  154. package/dist/src/kernel/runtime/loadSession.js +8 -1
  155. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  156. package/dist/src/kernel/runtime/packVersion.js +75 -0
  157. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  158. package/dist/src/kernel/runtime/readEntity.js +43 -4
  159. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  160. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  161. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  162. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  163. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  164. package/dist/src/kernel/spec/call.js +167 -0
  165. package/dist/src/kernel/spec/call.js.map +1 -0
  166. package/dist/src/kernel/spec/emitReference.js +9 -13
  167. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  168. package/dist/src/kernel/spec/emitSurface.js +8 -1
  169. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  170. package/dist/src/kernel/spec/guidance.js +63 -0
  171. package/dist/src/kernel/spec/guidance.js.map +1 -0
  172. package/dist/src/kernel/spec/wireType.js +36 -0
  173. package/dist/src/kernel/spec/wireType.js.map +1 -0
  174. package/package.json +14 -14
  175. package/pragma.conf.ts +294 -177
  176. package/src/capabilities/block.parity.test.ts +1 -1
  177. package/src/capabilities/callRule.test.ts +147 -0
  178. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  179. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  180. package/src/capabilities/capabilities/catalog.ts +60 -55
  181. package/src/capabilities/capabilities/index.ts +2 -0
  182. package/src/capabilities/capabilities/types.ts +16 -19
  183. package/src/capabilities/colophon/colophon.render.ts +9 -5
  184. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  185. package/src/capabilities/config/get.verb.ts +2 -0
  186. package/src/capabilities/config/runField.ts +3 -3
  187. package/src/capabilities/config/set.verb.ts +3 -0
  188. package/src/capabilities/config/show.verb.ts +2 -0
  189. package/src/capabilities/config/unset.verb.ts +2 -0
  190. package/src/capabilities/create/constants.ts +2 -2
  191. package/src/capabilities/create/create.verb.ts +16 -0
  192. package/src/capabilities/create/createGrammar.test.ts +1 -1
  193. package/src/capabilities/distribution.test.ts +6 -3
  194. package/src/capabilities/distribution.ts +6 -0
  195. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  196. package/src/capabilities/doctor/doctor.test.ts +38 -1
  197. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  198. package/src/capabilities/emptyRecovery.test.ts +5 -0
  199. package/src/capabilities/graph/connect.verb.ts +2 -0
  200. package/src/capabilities/graph/inspect.render.ts +9 -1
  201. package/src/capabilities/graph/inspect.test.ts +73 -0
  202. package/src/capabilities/graph/inspect.verb.ts +2 -0
  203. package/src/capabilities/graph/query.render.test.ts +35 -0
  204. package/src/capabilities/graph/query.render.ts +45 -7
  205. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  206. package/src/capabilities/graph/query.verb.ts +2 -0
  207. package/src/capabilities/graph/queryText.test.ts +55 -1
  208. package/src/capabilities/graph/queryText.ts +47 -0
  209. package/src/capabilities/graph/runConnect.ts +4 -5
  210. package/src/capabilities/graph/runQuery.ts +23 -9
  211. package/src/capabilities/info/info.verb.ts +2 -0
  212. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  213. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  214. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  215. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  216. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  217. package/src/capabilities/meta/mcp.verb.ts +2 -0
  218. package/src/capabilities/ontology/ontology.test.ts +24 -0
  219. package/src/capabilities/ontology/verbs.ts +16 -12
  220. package/src/capabilities/prompt/verbs.ts +10 -4
  221. package/src/capabilities/resources/provider.ts +7 -3
  222. package/src/capabilities/resources/resources.test.ts +2 -1
  223. package/src/capabilities/setup/setup.verb.ts +16 -0
  224. package/src/capabilities/shared/calls.ts +20 -0
  225. package/src/capabilities/skill/render.ts +9 -6
  226. package/src/capabilities/skill/verbs.ts +8 -4
  227. package/src/capabilities/sources/collectStatus.ts +6 -1
  228. package/src/capabilities/sources/index.ts +4 -3
  229. package/src/capabilities/sources/reset.render.test.ts +33 -0
  230. package/src/capabilities/sources/reset.render.ts +43 -0
  231. package/src/capabilities/sources/reset.verb.ts +59 -0
  232. package/src/capabilities/sources/runReset.ts +65 -0
  233. package/src/capabilities/sources/runUpdate.ts +12 -11
  234. package/src/capabilities/sources/sources.test.ts +158 -3
  235. package/src/capabilities/sources/status.render.test.ts +63 -1
  236. package/src/capabilities/sources/status.render.ts +25 -8
  237. package/src/capabilities/sources/status.verb.ts +2 -0
  238. package/src/capabilities/sources/types.ts +26 -0
  239. package/src/capabilities/sources/update.verb.ts +3 -0
  240. package/src/capabilities/surface.test.ts +2 -2
  241. package/src/capabilities/token.parity.test.ts +3 -2
  242. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  243. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  244. package/src/capabilities/variable.parity.test.ts +2 -1
  245. package/src/capabilities/version/version.verb.ts +1 -0
  246. package/src/capabilities/wireContract.test.ts +110 -0
  247. package/src/constants.ts +4 -3
  248. package/src/kernel/copy.test.ts +1 -1
  249. package/src/kernel/error/PragmaError.test.ts +7 -3
  250. package/src/kernel/error/index.ts +1 -1
  251. package/src/kernel/error/recovery.test.ts +52 -18
  252. package/src/kernel/error/recovery.ts +25 -46
  253. package/src/kernel/error/types.ts +1 -1
  254. package/src/kernel/packs/collect.test.ts +79 -0
  255. package/src/kernel/packs/collect.ts +151 -11
  256. package/src/kernel/packs/compile.test.ts +107 -10
  257. package/src/kernel/packs/compile.ts +100 -11
  258. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  259. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  260. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  261. package/src/kernel/packs/iri.ts +5 -4
  262. package/src/kernel/packs/listNotice.test.ts +1 -1
  263. package/src/kernel/packs/nounLink.test.ts +385 -0
  264. package/src/kernel/packs/renderPack.ts +137 -57
  265. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  266. package/src/kernel/packs/resolveEntity.ts +281 -101
  267. package/src/kernel/packs/runBodies.ts +133 -31
  268. package/src/kernel/packs/schema.ts +77 -12
  269. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  270. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  271. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  272. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  273. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  274. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  275. package/src/kernel/packs/storyRules.ts +37 -4
  276. package/src/kernel/packs/tierScope.test.ts +120 -6
  277. package/src/kernel/packs/tierScope.ts +5 -7
  278. package/src/kernel/packs/toolDescription.test.ts +59 -74
  279. package/src/kernel/packs/types.ts +93 -11
  280. package/src/kernel/project/cli/buildProgram.ts +4 -0
  281. package/src/kernel/project/cli/dispatch.ts +34 -8
  282. package/src/kernel/project/cli/suggest.test.ts +77 -0
  283. package/src/kernel/project/cli/suggestNames.ts +47 -1
  284. package/src/kernel/project/cli/verbHelp.ts +16 -19
  285. package/src/kernel/project/mcp/buildServer.ts +15 -4
  286. package/src/kernel/project/mcp/envelope.ts +3 -2
  287. package/src/kernel/project/mcp/instructions.ts +123 -35
  288. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  289. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  290. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  291. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  292. package/src/kernel/render/renderers.test.ts +2 -2
  293. package/src/kernel/render/renderers.ts +3 -3
  294. package/src/kernel/render/turtle.ts +24 -4
  295. package/src/kernel/runtime/graphpack/build.ts +58 -1
  296. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  297. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  298. package/src/kernel/runtime/graphpack/read.ts +4 -6
  299. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  300. package/src/kernel/runtime/graphpack/types.ts +13 -0
  301. package/src/kernel/runtime/loadSession.ts +8 -1
  302. package/src/kernel/runtime/packVersion.test.ts +61 -0
  303. package/src/kernel/runtime/packVersion.ts +84 -0
  304. package/src/kernel/runtime/readEntity.ts +61 -6
  305. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  306. package/src/kernel/runtime/resolveSources.ts +91 -10
  307. package/src/kernel/runtime/storeReadiness.ts +4 -4
  308. package/src/kernel/runtime/types.ts +12 -0
  309. package/src/kernel/spec/call.test.ts +95 -0
  310. package/src/kernel/spec/call.ts +186 -0
  311. package/src/kernel/spec/emitReference.ts +9 -13
  312. package/src/kernel/spec/emitSurface.ts +9 -1
  313. package/src/kernel/spec/guidance.test.ts +49 -0
  314. package/src/kernel/spec/guidance.ts +68 -0
  315. package/src/kernel/spec/index.ts +4 -0
  316. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  317. package/src/kernel/spec/types.ts +64 -7
  318. package/src/kernel/spec/wireType.ts +53 -0
  319. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  320. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  321. package/src/testing/behavioral/parity.test.ts +40 -0
  322. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  323. package/src/testing/eval/cases/assembly.ts +4 -3
  324. package/src/testing/eval/cases/stable.ts +2 -3
  325. package/src/testing/helpers/callSchema.ts +40 -0
  326. package/src/testing/helpers/parity.ts +35 -2
  327. package/src/testing/setupCallChecking.ts +30 -0
  328. package/dist/src/capabilities/capabilities/hints.js +0 -223
  329. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  330. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -20,8 +20,8 @@
20
20
  * (including the GraphQL path) stay off the storeless fast path.
21
21
  */
22
22
 
23
- import { cliRecovery, PragmaError } from "../error/index.js";
24
- import { suggestNames } from "../project/cli/suggestNames.js";
23
+ import { callRecovery, PragmaError, type Recovery } from "../error/index.js";
24
+ import { MAX_SUGGESTIONS, suggestNames } from "../project/cli/suggestNames.js";
25
25
  import { compactUri } from "../render/index.js";
26
26
  import type { PragmaRuntime } from "../runtime/index.js";
27
27
  import { activeExpands } from "./disclosure.js";
@@ -39,9 +39,9 @@ import {
39
39
  SCOPE_TIER_VARIABLE,
40
40
  } from "./sparql/buildLookupQuery.js";
41
41
  import { runSelect } from "./sparql/runSelect.js";
42
+ import { localName } from "./tierScope.js";
42
43
  import {
43
44
  expandIsSparql,
44
- type PackChildRow,
45
45
  type PackEntity,
46
46
  type PackLookup,
47
47
  type PackRow,
@@ -107,11 +107,39 @@ export interface LookupOutput {
107
107
  * case, and the case the payload speaks for itself in.
108
108
  */
109
109
  readonly outOfScope?: OutOfScopeAnswer[];
110
+ /** Set when the patterns matched more entities than {@link GLOB_EXPANSION_CAP}. */
111
+ readonly truncated?: true;
112
+ /**
113
+ * The matched entities the cut was made in: the in-scope ones when they alone
114
+ * exceed the cap, else every match. Present only when truncated.
115
+ */
116
+ readonly total?: number;
117
+ /** Out-of-scope matches not counted in `total`, when there are any. */
118
+ readonly elsewhere?: number;
119
+ /** The level the cut answer was built at, present only when truncated. */
120
+ readonly detail?: string;
110
121
  }
111
122
 
123
+ /**
124
+ * The most entities the patterns of ONE lookup may expand to (derivation in
125
+ * BUDGETS.md). Literal arguments are never counted or cut.
126
+ */
127
+ export const GLOB_EXPANSION_CAP = 50;
128
+
112
129
  /** What the resolver needs from the runtime: the store + the query facade. */
113
130
  type LookupRuntime = Pick<PragmaRuntime, "store" | "query">;
114
131
 
132
+ /**
133
+ * Point a missed name at the noun's list — when it has one. A story may declare
134
+ * a lookup alone, and a recovery naming a `list` that does not exist is a dead
135
+ * end dressed as a way out.
136
+ */
137
+ export function listRecovery(noun: string, hasList: boolean): Recovery {
138
+ return hasList
139
+ ? callRecovery({ verb: `${noun} list` }, `List available ${noun} entries.`)
140
+ : { message: `Check the name: ${noun} entries cannot be listed.` };
141
+ }
142
+
115
143
  /**
116
144
  * Resolve a batch of lookup queries, collecting per-query failures.
117
145
  *
@@ -125,41 +153,59 @@ export async function resolveLookup(
125
153
  source: StorySource,
126
154
  prefixes: Readonly<Record<string, string>>,
127
155
  level: string | undefined,
156
+ hasList: boolean,
128
157
  scope?: LookupScope,
129
158
  ): Promise<LookupOutput> {
130
159
  if (queries.length === 0) {
131
160
  throw PragmaError.invalidInput("names", "(empty)", {
132
- recovery: cliRecovery(`${noun} list`, `List available ${noun} entries.`, {
133
- tool: `${noun}_list`,
134
- // A concrete, valid argument bag rather than a bare tool name: an
135
- // agent can call the recovery as written instead of guessing one.
136
- params: {},
137
- }),
161
+ recovery: listRecovery(noun, hasList),
138
162
  });
139
163
  }
140
164
 
141
- const expanded = await expandQueries(
142
- rt,
143
- lookup,
144
- noun,
145
- source,
146
- queries,
147
- prefixes,
148
- );
165
+ // Read at most once per call, however many patterns and misses need it.
166
+ let pool: Promise<Addressable[]> | undefined;
167
+ const addressable = (): Promise<Addressable[]> => {
168
+ pool ??= listAddressable(rt, lookup, source, prefixes, scope?.via);
169
+ return pool;
170
+ };
171
+ const expanded = await expandQueries(noun, queries, addressable, scope);
149
172
  const results: PackEntity[] = [];
150
173
  const errors: LookupError[] = [...expanded.globErrors];
151
- const outOfScope: OutOfScopeAnswer[] = [];
174
+ const outOfScope: OutOfScopeAnswer[] = [...expanded.outOfScope];
175
+ // Two arguments may reach one entity by different routes — a name pattern
176
+ // and an IRI pattern, a name and that entity's own IRI. It is answered once.
177
+ const answered = new Set<string>();
152
178
  const settled = await Promise.allSettled(
153
179
  expanded.names.map((query) =>
154
- lookupOne(rt, lookup, noun, query, source, prefixes, level, scope),
180
+ lookupOne(
181
+ rt,
182
+ lookup,
183
+ noun,
184
+ query,
185
+ source,
186
+ prefixes,
187
+ level,
188
+ addressable,
189
+ scope,
190
+ ),
155
191
  ),
156
192
  );
157
193
  for (const [index, outcome] of settled.entries()) {
158
194
  const query = expanded.names[index];
159
195
  if (query === undefined) continue;
160
196
  if (outcome.status === "fulfilled") {
161
- results.push(...outcome.value.entities);
162
- if (outcome.value.outOfScope) outOfScope.push(outcome.value.outOfScope);
197
+ for (const entity of outcome.value.entities) {
198
+ const uri = String(entity.uri);
199
+ if (answered.has(uri)) continue;
200
+ answered.add(uri);
201
+ results.push(entity);
202
+ }
203
+ const fallback = outcome.value.outOfScope;
204
+ const key = (answer: OutOfScopeAnswer): string =>
205
+ `${answer.query.toLowerCase()}|${answer.tiers.join()}`;
206
+ if (fallback && !outOfScope.some((a) => key(a) === key(fallback))) {
207
+ outOfScope.push(fallback);
208
+ }
163
209
  continue;
164
210
  }
165
211
  const error = outcome.reason;
@@ -184,66 +230,190 @@ export async function resolveLookup(
184
230
  results,
185
231
  errors,
186
232
  ...(outOfScope.length > 0 ? { outOfScope } : {}),
233
+ ...(expanded.total === undefined
234
+ ? {}
235
+ : {
236
+ truncated: true as const,
237
+ total: expanded.total,
238
+ ...(expanded.elsewhere ? { elsewhere: expanded.elsewhere } : {}),
239
+ ...(level ? { detail: level } : {}),
240
+ }),
187
241
  };
188
242
  }
189
243
 
190
244
  /**
191
- * Expand glob queries against the population their own shape addresses;
192
- * literals pass through.
245
+ * The IRIs of every entity the given lookup arguments reach — the resolve
246
+ * alone, with nothing fetched. Each is read as {@link resolveLookup} reads a
247
+ * literal argument (a name, a prefixed IRI, an absolute IRI; never a pattern),
248
+ * under no tier scope: a name reaches its entity in every tier.
193
249
  *
194
- * A name glob expands over the `by` values, an IRI glob over the entity IRIs —
195
- * the same split {@link buildResolveQuery} makes, because a glob is just a
196
- * lookup argument with a `*` in it. Expanding an IRI pattern over names was why
197
- * `ds:global.component.but*` matched nothing on any pack while shell completion
198
- * offered nothing BUT those IRIs. Each population is fetched at most once, and
199
- * only when a glob of that shape is actually present.
250
+ * @returns The distinct IRIs; none when the store holds no entity of the noun.
251
+ * @throws PragmaError INVALID_INPUT naming every argument that reaches nothing,
252
+ * with the suggestions a lookup miss offers; when an argument is empty; or
253
+ * when an IRI-shaped argument cannot be embedded in a query.
200
254
  */
201
- async function expandQueries(
255
+ export async function resolveEntityIris(
202
256
  rt: LookupRuntime,
203
257
  lookup: PackLookup,
204
258
  noun: string,
205
- source: StorySource,
206
259
  queries: readonly string[],
260
+ source: StorySource,
207
261
  prefixes: Readonly<Record<string, string>>,
208
- ): Promise<{ names: string[]; globErrors: LookupError[] }> {
209
- if (!queries.some(isGlobPattern))
210
- return { names: [...queries], globErrors: [] };
262
+ ): Promise<string[]> {
263
+ const blank = queries.find((query) => query.trim() === "");
264
+ if (blank !== undefined) {
265
+ throw PragmaError.invalidInput(noun, "(empty)", {
266
+ recovery: { message: `Give a ${noun} name or IRI.` },
267
+ });
268
+ }
269
+ const iris = new Set<string>();
270
+ const missed: string[] = [];
271
+ // The graphql-sourced resolve is the bare `?uri ?name` pair: no field read.
272
+ const bare: PackLookup = { ...lookup, source: "graphql" };
273
+ for (const query of queries) {
274
+ const rows = await runSelect(
275
+ rt,
276
+ buildResolveQuery(bare, query, prefixes, undefined),
277
+ source,
278
+ );
279
+ if (rows.length === 0) missed.push(query);
280
+ for (const row of firstRowPerEntity(rows)) iris.add(String(row.uri));
281
+ }
282
+ if (missed.length === 0) return [...iris];
283
+ const pool = listAddressable(rt, lookup, source, prefixes, undefined);
284
+ if ((await pool).length === 0) return [];
285
+ const suggestions = await Promise.all(
286
+ missed.map((query) => suggestForMiss(() => pool, query)),
287
+ );
288
+ throw new PragmaError({
289
+ code: "INVALID_INPUT",
290
+ message: `No ${noun} is named "${missed.join('", "')}".`,
291
+ // Interleaved, so every miss gets a share of the slots.
292
+ suggestions: [
293
+ ...new Set(
294
+ suggestions
295
+ .flatMap((list, miss) =>
296
+ list.map((name, rank) => ({ name, rank, miss })),
297
+ )
298
+ .sort((a, b) => a.rank - b.rank || a.miss - b.miss)
299
+ .map((entry) => entry.name),
300
+ ),
301
+ ].slice(0, MAX_SUGGESTIONS),
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Expand glob queries to the ENTITIES they reach; literals pass through.
307
+ *
308
+ * An IRI-shaped glob is matched against both spellings of an IRI, a prefix-less
309
+ * one against names and IRI local names. Matches are keyed by entity, so two
310
+ * routes to one entity count once. Under a tier scope the in-scope matches come
311
+ * first and an out-of-scope namesake of one is dropped, as a name lookup would;
312
+ * the cap is applied after that, over a deterministic order.
313
+ */
314
+ async function expandQueries(
315
+ noun: string,
316
+ queries: readonly string[],
317
+ addressable: () => Promise<Addressable[]>,
318
+ scope: LookupScope | undefined,
319
+ ): Promise<{
320
+ names: string[];
321
+ globErrors: LookupError[];
322
+ outOfScope: OutOfScopeAnswer[];
323
+ total?: number;
324
+ elsewhere?: number;
325
+ }> {
326
+ const literals = [...new Set(queries.filter((q) => !isGlobPattern(q)))];
211
327
  const globs = queries.filter(isGlobPattern);
212
- const byIri = globs.some(looksLikeIri)
213
- ? await listEntityIriSpellings(rt, lookup, source, prefixes)
214
- : new Map<string, string>();
215
- const byName = globs.some((glob) => !looksLikeIri(glob))
216
- ? await listEntityNames(rt, lookup, source)
217
- : [];
218
- const names: string[] = [];
328
+ if (globs.length === 0) {
329
+ return { names: literals, globErrors: [], outOfScope: [] };
330
+ }
331
+ const pool = await addressable();
219
332
  const globErrors: LookupError[] = [];
220
- for (const query of queries) {
221
- if (!isGlobPattern(query)) {
222
- names.push(query);
223
- continue;
224
- }
225
- // An IRI glob expands over every spelling, then collapses to the entity:
226
- // matching `ds:button` and its absolute twin must not list it twice.
227
- const matches = looksLikeIri(query)
228
- ? [
229
- ...new Set(
230
- expandGlob(query, [...byIri.keys()]).map(
231
- (spelling) => byIri.get(spelling) ?? spelling,
232
- ),
233
- ),
234
- ]
235
- : expandGlob(query, byName);
236
- if (matches.length === 0) {
333
+ const matched = new Map<string, Addressable>();
334
+ const inScope = (entity: Addressable): boolean =>
335
+ !scope || entity.tier === undefined || scope.tiers.includes(entity.tier);
336
+ for (const glob of globs) {
337
+ const forms = (entity: Addressable): string[] =>
338
+ looksLikeIri(glob)
339
+ ? [entity.uri, entity.compact]
340
+ : [entity.name ?? "", entity.local];
341
+ const hits = new Set(expandGlob(glob, pool.flatMap(forms)));
342
+ const entities = pool.filter((entity) =>
343
+ forms(entity).some((form) => hits.has(form)),
344
+ );
345
+ if (entities.length === 0) {
237
346
  globErrors.push({
238
- query,
347
+ query: glob,
239
348
  code: "EMPTY_RESULTS",
240
- message: `No ${noun} entries matching "${query}".`,
349
+ message: `No ${noun} entries matching "${glob}".`,
241
350
  });
242
- } else {
243
- names.push(...matches);
244
351
  }
352
+ for (const entity of entities) {
353
+ // An entity in two tiers is in scope when either row is.
354
+ const known = matched.get(entity.uri);
355
+ if (!known || (!inScope(known) && inScope(entity))) {
356
+ matched.set(entity.uri, entity);
357
+ }
358
+ }
359
+ }
360
+ const label = (entity: Addressable): string => entity.name ?? entity.compact;
361
+ const scoped = new Set(
362
+ [...matched.values()].filter(inScope).map((e) => label(e).toLowerCase()),
363
+ );
364
+ const ordered = [...matched.values()]
365
+ .filter((e) => inScope(e) || !scoped.has(label(e).toLowerCase()))
366
+ .sort(
367
+ (a, b) =>
368
+ Number(!inScope(a)) - Number(!inScope(b)) ||
369
+ label(a).localeCompare(label(b)) ||
370
+ a.uri.localeCompare(b.uri),
371
+ );
372
+ const kept = ordered.slice(0, GLOB_EXPANSION_CAP);
373
+ const within = ordered.filter(inScope).length;
374
+ return {
375
+ names: [...literals, ...kept.map((entity) => entity.compact)],
376
+ globErrors,
377
+ outOfScope: kept
378
+ .filter((entity) => !inScope(entity))
379
+ .map((entity) => ({
380
+ query: label(entity),
381
+ tiers: [scopeTierName(entity.tier ?? "")],
382
+ scope: scope?.label ?? "",
383
+ })),
384
+ ...(ordered.length <= kept.length
385
+ ? {}
386
+ : within > kept.length
387
+ ? { total: within, elsewhere: ordered.length - within }
388
+ : { total: ordered.length }),
389
+ };
390
+ }
391
+
392
+ /**
393
+ * What to offer for an argument that reached nothing: near names, and the
394
+ * prefixed IRI of an entity whose local name is what was typed (exact match
395
+ * first). A bare local name is not an address; the miss names the form that is.
396
+ */
397
+ async function suggestForMiss(
398
+ addressable: () => Promise<Addressable[]>,
399
+ query: string,
400
+ ): Promise<string[]> {
401
+ const pool = await addressable();
402
+ const names = [...new Set(pool.flatMap((e) => (e.name ? [e.name] : [])))];
403
+ const taken = new Set(names.map((name) => name.trim().toLowerCase()));
404
+ const byLocal = new Map<string, string[]>();
405
+ for (const entity of pool) {
406
+ const key = entity.local.toLowerCase();
407
+ if (taken.has(key)) continue;
408
+ byLocal.set(key, [
409
+ ...new Set([...(byLocal.get(key) ?? []), entity.compact]),
410
+ ]);
245
411
  }
246
- return { names, globErrors };
412
+ const exact = byLocal.get(query.trim().toLowerCase()) ?? [];
413
+ const ranked = suggestNames(query, [...names, ...byLocal.keys()]).flatMap(
414
+ (candidate) => byLocal.get(candidate) ?? [candidate],
415
+ );
416
+ return [...new Set([...exact, ...ranked])].slice(0, MAX_SUGGESTIONS);
247
417
  }
248
418
 
249
419
  /**
@@ -283,6 +453,7 @@ async function lookupOne(
283
453
  source: StorySource,
284
454
  prefixes: Readonly<Record<string, string>>,
285
455
  level: string | undefined,
456
+ addressable: () => Promise<Addressable[]>,
286
457
  scope?: LookupScope,
287
458
  ): Promise<{ entities: PackEntity[]; outOfScope?: OutOfScopeAnswer }> {
288
459
  const graphqlSourced = lookup.source === "graphql";
@@ -293,15 +464,8 @@ async function lookupOne(
293
464
  );
294
465
  const resolved = firstRowPerEntity(rows);
295
466
  if (resolved.length === 0) {
296
- const candidates = await listEntityNames(rt, lookup, source);
297
467
  throw PragmaError.notFound(noun, query, {
298
- suggestions: suggestNames(query, candidates),
299
- recovery: cliRecovery(`${noun} list`, `List available ${noun} entries.`, {
300
- tool: `${noun}_list`,
301
- // A concrete, valid argument bag rather than a bare tool name: an
302
- // agent can call the recovery as written instead of guessing one.
303
- params: {},
304
- }),
468
+ suggestions: await suggestForMiss(addressable, query),
305
469
  });
306
470
  }
307
471
 
@@ -429,7 +593,10 @@ async function addSparqlExpands(
429
593
  ): Promise<PackEntity> {
430
594
  for (const expand of activeExpands(lookup, level)) {
431
595
  if (!expandIsSparql(lookup, expand)) continue;
432
- entity[expand.name] = (await runSelect(
596
+ const many = expand.select.flatMap((field) =>
597
+ "many" in field && field.many ? [field.name] : [],
598
+ );
599
+ const rows = (await runSelect(
433
600
  rt,
434
601
  buildExpandQuery(expand, String(entity.uri), lookup),
435
602
  source,
@@ -440,7 +607,16 @@ async function addSparqlExpands(
440
607
  // without that pack does not bind, and every other section of that
441
608
  // block is still perfectly answerable.
442
609
  { degradeOnUnboundPrefix: true },
443
- )) as readonly PackChildRow[];
610
+ )) as PackRow[];
611
+ // An aggregate has no order of its own; a `many` cell is sorted here.
612
+ entity[expand.name] = rows.map((row) => ({
613
+ ...row,
614
+ ...Object.fromEntries(
615
+ many
616
+ .filter((name) => row[name] !== undefined)
617
+ .map((name) => [name, (row[name] ?? "").split(" ").sort().join(" ")]),
618
+ ),
619
+ }));
444
620
  }
445
621
  return entity;
446
622
  }
@@ -509,38 +685,42 @@ function looksLikeIri(query: string): boolean {
509
685
  );
510
686
  }
511
687
 
512
- /**
513
- * Every SPELLING an IRI-shaped glob may be written against, mapped to the one
514
- * entity it addresses.
515
- *
516
- * An entity under a registered prefix has two legal spellings — the compact
517
- * `ds:button` shell completion offers, and the absolute
518
- * `https://ds.canonical.com/button` a user pastes from a browser. Both resolve
519
- * to the same entity in a literal lookup, so a glob must expand over both;
520
- * offering only the compact form made `https://ds.canonical.com/but*` return
521
- * EMPTY_RESULTS while the literal IRI it generalises succeeded.
522
- *
523
- * The map is spelling → CANONICAL IRI precisely so a pattern that matches an
524
- * entity under both spellings still yields it once. Matching over a flat list
525
- * of both would render the entity twice, which is why the previous shape
526
- * offered only one.
527
- */
528
- async function listEntityIriSpellings(
688
+ /** One way to address an entity: a row per name and per tier it carries. */
689
+ interface Addressable {
690
+ /** The canonical (absolute) IRI. */
691
+ readonly uri: string;
692
+ /** The prefixed form, or the IRI itself when no registered prefix matches. */
693
+ readonly compact: string;
694
+ /** The IRI's local name. */
695
+ readonly local: string;
696
+ /** The `by` value the entity carries, when it carries one. */
697
+ readonly name?: string;
698
+ /** The tier IRI, when the lookup is scoped and the entity is in one. */
699
+ readonly tier?: string;
700
+ }
701
+
702
+ /** List every entity the lookup can address by IRI. */
703
+ async function listAddressable(
529
704
  rt: LookupRuntime,
530
705
  lookup: PackLookup,
531
706
  source: StorySource,
532
707
  prefixes: Readonly<Record<string, string>>,
533
- ): Promise<Map<string, string>> {
534
- const rows = await runSelect(rt, buildLookupIrisQuery(lookup), source);
535
- const spellings = new Map<string, string>();
536
- for (const row of rows) {
537
- const uri = row.uri ?? "";
538
- if (uri === "") continue;
539
- spellings.set(uri, uri);
540
- const compact = compactUri(uri, prefixes);
541
- if (compact !== uri) spellings.set(compact, uri);
542
- }
543
- return spellings;
708
+ via: string | undefined,
709
+ ): Promise<Addressable[]> {
710
+ const rows = await runSelect(rt, buildLookupIrisQuery(lookup, via), source);
711
+ return rows
712
+ .filter((row) => (row.uri ?? "") !== "")
713
+ .map((row) => {
714
+ const uri = row.uri as string;
715
+ const tier = row[SCOPE_TIER_VARIABLE];
716
+ return {
717
+ uri,
718
+ compact: compactUri(uri, prefixes),
719
+ local: localName(uri),
720
+ ...(row.name ? { name: row.name } : {}),
721
+ ...(tier ? { tier } : {}),
722
+ };
723
+ });
544
724
  }
545
725
 
546
726
  /** List every entity name the lookup can address (miss suggestions + sample/glob). */