@canonical/pragma-cli 0.36.0 → 0.38.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 (312) hide show
  1. package/README.md +3 -2
  2. package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
  3. package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
  4. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
  5. package/bundled-skills/anatomy-author/SKILL.md +840 -137
  6. package/bundled-skills/design-auditor/SKILL.md +133 -9
  7. package/bundled-skills/implement-component/SKILL.md +192 -23
  8. package/bundled-skills/specify-component/SKILL.md +124 -12
  9. package/bundled-skills/specify-pattern/SKILL.md +47 -8
  10. package/dist/package.json +23 -20
  11. package/dist/pragma.conf.js +963 -40
  12. package/dist/pragma.conf.js.map +1 -1
  13. package/dist/src/capabilities/capabilities/catalog.js +14 -5
  14. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  15. package/dist/src/capabilities/capabilities/hints.js +33 -5
  16. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  17. package/dist/src/capabilities/config/show.render.js +30 -2
  18. package/dist/src/capabilities/config/show.render.js.map +1 -1
  19. package/dist/src/capabilities/create/create.verb.js +2 -1
  20. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  21. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  22. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  23. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  24. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  25. package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
  26. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  28. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  29. package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
  30. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  31. package/dist/src/capabilities/graph/connect.render.js +101 -0
  32. package/dist/src/capabilities/graph/connect.render.js.map +1 -0
  33. package/dist/src/capabilities/graph/connect.types.js +21 -0
  34. package/dist/src/capabilities/graph/connect.types.js.map +1 -0
  35. package/dist/src/capabilities/graph/connect.verb.js +147 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
  37. package/dist/src/capabilities/graph/index.js +5 -3
  38. package/dist/src/capabilities/graph/index.js.map +1 -1
  39. package/dist/src/capabilities/graph/queryText.js +206 -0
  40. package/dist/src/capabilities/graph/queryText.js.map +1 -0
  41. package/dist/src/capabilities/graph/relationIndex.js +302 -0
  42. package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
  43. package/dist/src/capabilities/graph/runConnect.js +456 -0
  44. package/dist/src/capabilities/graph/runConnect.js.map +1 -0
  45. package/dist/src/capabilities/graph/runQuery.js +21 -4
  46. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  47. package/dist/src/capabilities/setup/buildPlan.js +6 -2
  48. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  49. package/dist/src/capabilities/setup/operations/index.js +7 -2
  50. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  51. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  52. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  53. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  54. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  55. package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
  56. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  57. package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
  58. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
  60. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/writability.js +114 -0
  64. package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
  65. package/dist/src/capabilities/setup/plan.js.map +1 -1
  66. package/dist/src/capabilities/setup/setup.verb.js +80 -31
  67. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  68. package/dist/src/capabilities/setup/targets.js +124 -42
  69. package/dist/src/capabilities/setup/targets.js.map +1 -1
  70. package/dist/src/capabilities/shared/index.js +1 -1
  71. package/dist/src/capabilities/shared/index.js.map +1 -1
  72. package/dist/src/constants.js +7 -1
  73. package/dist/src/constants.js.map +1 -1
  74. package/dist/src/kernel/error/renderError.js +3 -2
  75. package/dist/src/kernel/error/renderError.js.map +1 -1
  76. package/dist/src/kernel/error/validOptions.js +41 -0
  77. package/dist/src/kernel/error/validOptions.js.map +1 -0
  78. package/dist/src/kernel/packs/compile.js +90 -6
  79. package/dist/src/kernel/packs/compile.js.map +1 -1
  80. package/dist/src/kernel/packs/cursor.js +135 -0
  81. package/dist/src/kernel/packs/cursor.js.map +1 -0
  82. package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
  83. package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
  84. package/dist/src/kernel/packs/paging.js +108 -0
  85. package/dist/src/kernel/packs/paging.js.map +1 -0
  86. package/dist/src/kernel/packs/renderPack.js +154 -5
  87. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  88. package/dist/src/kernel/packs/resolveEntity.js +118 -19
  89. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  90. package/dist/src/kernel/packs/runBodies.js +156 -18
  91. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  92. package/dist/src/kernel/packs/schema.js +160 -12
  93. package/dist/src/kernel/packs/schema.js.map +1 -1
  94. package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
  95. package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
  96. package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
  97. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
  98. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
  99. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  100. package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
  101. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
  102. package/dist/src/kernel/packs/sparql/index.js +24 -14
  103. package/dist/src/kernel/packs/sparql/index.js.map +1 -1
  104. package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
  105. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  106. package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
  107. package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
  108. package/dist/src/kernel/packs/storyRules.js +151 -0
  109. package/dist/src/kernel/packs/storyRules.js.map +1 -0
  110. package/dist/src/kernel/packs/tierScope.js +310 -0
  111. package/dist/src/kernel/packs/tierScope.js.map +1 -0
  112. package/dist/src/kernel/packs/types.js +80 -0
  113. package/dist/src/kernel/packs/types.js.map +1 -1
  114. package/dist/src/kernel/project/cli/buildProgram.js +10 -1
  115. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  116. package/dist/src/kernel/project/cli/dispatch.js +104 -3
  117. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  118. package/dist/src/kernel/project/cli/rootHelp.js +8 -5
  119. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  120. package/dist/src/kernel/project/cli/suggestNames.js +31 -7
  121. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  122. package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
  123. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  124. package/dist/src/kernel/render/renderers.js +34 -2
  125. package/dist/src/kernel/render/renderers.js.map +1 -1
  126. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  127. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  128. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  129. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  130. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  131. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  132. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  133. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  134. package/dist/src/kernel/runtime/graphpack/read.js +17 -10
  135. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  136. package/dist/src/kernel/runtime/loadSession.js +3 -1
  137. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  138. package/dist/src/kernel/runtime/probe.js +1 -1
  139. package/dist/src/kernel/runtime/probe.js.map +1 -1
  140. package/dist/src/kernel/runtime/readEntity.js +19 -4
  141. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  142. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  143. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  144. package/dist/src/kernel/runtime/refs/resolve.js +38 -5
  145. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  146. package/dist/src/kernel/spec/emitReference.js +1 -1
  147. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  148. package/package.json +23 -20
  149. package/pragma.conf.ts +983 -40
  150. package/src/capabilities/block.parity.test.ts +207 -27
  151. package/src/capabilities/block.shipped.exec.test.ts +156 -14
  152. package/src/capabilities/block.tierRank.exec.test.ts +46 -9
  153. package/src/capabilities/capabilities/catalog.ts +14 -5
  154. package/src/capabilities/capabilities/hints.ts +40 -5
  155. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  156. package/src/capabilities/config/set.test.ts +24 -25
  157. package/src/capabilities/config/show.render.test.ts +9 -4
  158. package/src/capabilities/config/show.render.ts +30 -2
  159. package/src/capabilities/create/byteEquality.test.ts +28 -1
  160. package/src/capabilities/create/create.test.ts +1 -0
  161. package/src/capabilities/create/create.verb.ts +2 -1
  162. package/src/capabilities/create/createGrammar.test.ts +4 -1
  163. package/src/capabilities/create/createSurface.generated.ts +23 -1
  164. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  165. package/src/capabilities/distribution.parity.test.ts +12 -11
  166. package/src/capabilities/distribution.test.ts +77 -7
  167. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  168. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  169. package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
  170. package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
  171. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  172. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  173. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  174. package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
  175. package/src/capabilities/doctor/doctor.test.ts +327 -36
  176. package/src/capabilities/emptyRecovery.test.ts +84 -28
  177. package/src/capabilities/graph/connect.render.ts +141 -0
  178. package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
  179. package/src/capabilities/graph/connect.test.ts +361 -0
  180. package/src/capabilities/graph/connect.types.ts +136 -0
  181. package/src/capabilities/graph/connect.verb.ts +165 -0
  182. package/src/capabilities/graph/index.ts +5 -3
  183. package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
  184. package/src/capabilities/graph/query.test.ts +17 -0
  185. package/src/capabilities/graph/queryText.test.ts +121 -0
  186. package/src/capabilities/graph/queryText.ts +240 -0
  187. package/src/capabilities/graph/relationIndex.ts +372 -0
  188. package/src/capabilities/graph/runConnect.ts +577 -0
  189. package/src/capabilities/graph/runQuery.ts +21 -6
  190. package/src/capabilities/lazy.test.ts +32 -2
  191. package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
  192. package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
  193. package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
  194. package/src/capabilities/resources/resources.test.ts +38 -2
  195. package/src/capabilities/setup/buildPlan.ts +7 -1
  196. package/src/capabilities/setup/operations/index.ts +14 -0
  197. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  198. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  199. package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
  200. package/src/capabilities/setup/operations/setupLsp.ts +544 -85
  201. package/src/capabilities/setup/operations/setupMcp.ts +212 -4
  202. package/src/capabilities/setup/operations/setupSkills.ts +7 -2
  203. package/src/capabilities/setup/operations/writability.test.ts +151 -0
  204. package/src/capabilities/setup/operations/writability.ts +135 -0
  205. package/src/capabilities/setup/plan.ts +11 -0
  206. package/src/capabilities/setup/setup.test.ts +992 -16
  207. package/src/capabilities/setup/setup.verb.ts +84 -36
  208. package/src/capabilities/setup/targets.ts +152 -48
  209. package/src/capabilities/setup/types.ts +41 -5
  210. package/src/capabilities/shared/index.ts +1 -0
  211. package/src/capabilities/sources/sources.test.ts +55 -0
  212. package/src/capabilities/surface.test.ts +59 -11
  213. package/src/capabilities/tier.parity.test.ts +2 -2
  214. package/src/capabilities/token.parity.test.ts +784 -0
  215. package/src/capabilities/tokenGraph.cli.test.ts +279 -0
  216. package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
  217. package/src/capabilities/variable.parity.test.ts +448 -0
  218. package/src/constants.ts +8 -0
  219. package/src/kernel/completion/shellDrive.test.ts +32 -30
  220. package/src/kernel/config/defaults.test.ts +21 -2
  221. package/src/kernel/config/readConfig.test.ts +1 -1
  222. package/src/kernel/error/renderError.ts +3 -2
  223. package/src/kernel/error/validOptions.test.ts +57 -0
  224. package/src/kernel/error/validOptions.ts +45 -0
  225. package/src/kernel/packs/collect.test.ts +2 -0
  226. package/src/kernel/packs/compile.test.ts +188 -15
  227. package/src/kernel/packs/compile.ts +109 -14
  228. package/src/kernel/packs/cursor.ts +135 -0
  229. package/src/kernel/packs/filteredEmpty.test.ts +213 -0
  230. package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
  231. package/src/kernel/packs/graphqlSource.test.ts +152 -0
  232. package/src/kernel/packs/listNotice.test.ts +156 -0
  233. package/src/kernel/packs/paging.ts +110 -0
  234. package/src/kernel/packs/renderPack.ts +186 -13
  235. package/src/kernel/packs/resolveEntity.test.ts +62 -3
  236. package/src/kernel/packs/resolveEntity.ts +186 -26
  237. package/src/kernel/packs/runBodies.ts +196 -26
  238. package/src/kernel/packs/schema.ts +180 -13
  239. package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
  240. package/src/kernel/packs/sparql/authorQuery.ts +257 -0
  241. package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
  242. package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
  243. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
  244. package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
  245. package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
  246. package/src/kernel/packs/sparql/filterValues.ts +182 -0
  247. package/src/kernel/packs/sparql/index.ts +30 -14
  248. package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
  249. package/src/kernel/packs/sparql/runSelect.ts +42 -2
  250. package/src/kernel/packs/sparql/searchTerm.ts +29 -0
  251. package/src/kernel/packs/storyRules.ts +180 -0
  252. package/src/kernel/packs/tierScope.test.ts +487 -0
  253. package/src/kernel/packs/tierScope.ts +409 -0
  254. package/src/kernel/packs/toolDescription.test.ts +13 -10
  255. package/src/kernel/packs/types.ts +311 -9
  256. package/src/kernel/project/cli/buildProgram.ts +17 -1
  257. package/src/kernel/project/cli/dispatch.test.ts +87 -0
  258. package/src/kernel/project/cli/dispatch.ts +114 -4
  259. package/src/kernel/project/cli/rootHelp.ts +8 -5
  260. package/src/kernel/project/cli/suggest.test.ts +35 -0
  261. package/src/kernel/project/cli/suggestNames.ts +34 -7
  262. package/src/kernel/project/mcp/registerVerb.ts +5 -1
  263. package/src/kernel/render/contracts.ts +2 -0
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.test.ts +43 -0
  266. package/src/kernel/render/renderers.ts +34 -1
  267. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  268. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  269. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  270. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  271. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  272. package/src/kernel/runtime/graphpack/read.ts +17 -13
  273. package/src/kernel/runtime/loadSession.ts +3 -1
  274. package/src/kernel/runtime/probe.ts +1 -1
  275. package/src/kernel/runtime/readEntity.ts +19 -4
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +39 -5
  279. package/src/kernel/runtime/types.ts +30 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  282. package/src/kernel/spec/types.ts +17 -0
  283. package/src/testing/PARITY_GAPS.ts +4 -4
  284. package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
  285. package/src/testing/behavioral/completion.test.ts +7 -6
  286. package/src/testing/behavioral/disclosure.test.ts +39 -37
  287. package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
  288. package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
  289. package/src/testing/behavioral/journeys.cli.test.ts +71 -32
  290. package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
  291. package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
  292. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
  293. package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
  294. package/src/testing/behavioral/mcpSurface.test.ts +28 -25
  295. package/src/testing/behavioral/parity.test.ts +25 -23
  296. package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
  297. package/src/testing/eval/cases/effectful.ts +7 -1
  298. package/src/testing/eval/cases/readNouns.ts +100 -11
  299. package/src/testing/fixtures/blockGraph.ts +89 -5
  300. package/src/testing/fixtures/connectGraph.ts +159 -0
  301. package/src/testing/fixtures/graph/canonical.ts +128 -32
  302. package/src/testing/helpers/fsProbe.ts +88 -0
  303. package/src/testing/helpers/runCli.test.ts +44 -0
  304. package/src/testing/helpers/runCli.ts +27 -3
  305. package/src/testing/perf/budgets.test.ts +3 -1
  306. package/src/testing/perf/budgets.ts +138 -54
  307. package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
  308. package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
  309. package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
  310. package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
  311. package/src/kernel/packs/sparql/applyFilters.ts +0 -203
  312. package/src/kernel/packs/sparql/applySearch.ts +0 -33
@@ -19,83 +19,167 @@
19
19
  * p95, and it lands TIGHTER than 2× its median); its arithmetic is written out
20
20
  * in full in BUDGETS.md, as are the measurements and environment for all of
21
21
  * them.
22
+ *
23
+ * A CEILING IS ALSO RELATIVE TO WHAT THE SURFACE HAS GROWN INTO. Re-derived
24
+ * again 2026-09-10, because three of the five had drifted red for two causes,
25
+ * both of them real work rather than noise: the eagerly-imported capability
26
+ * barrel grew 37.6 → 68.0 ms as the surface reached 22 capability modules and
27
+ * 42 MCP tools, and the embedded pack grew 8 479 → 49 630 triples. Every
28
+ * constant below names its cause, because a raised ceiling with no cause is
29
+ * just a lower standard. The two in-process ceilings were re-measured and
30
+ * DELIBERATELY LEFT ALONE — they have three orders of magnitude of headroom.
31
+ *
32
+ * THIS PASS IS NOT RUN BY CI — owner ruling 2026-08-30, restated 2026-09-10.
33
+ * Nothing reaches it indirectly either: `test` does not chain it, no Nx target
34
+ * or workflow names it, and the repo has no git hooks. See
35
+ * `vitest.perf.config.ts`, `docs/CI.md` and BUDGETS.md.
36
+ *
37
+ * EVERY CEILING HERE IS LATENCY. The one SIZE budget the package enforces on a
38
+ * verb — a list-shaped answer's payload — deliberately does not live here: it
39
+ * is a property of the payload rather than of wall-clock time, so it needs
40
+ * neither the serial pass nor the spawned binary, — and a budget nothing
41
+ * runs is not a gate. It sits where the payload is built, in
42
+ * `capabilities/listBudget.shipped.exec.test.ts`, as the MCP resource
43
+ * listing's own ceiling does in `capabilities/resources/resources.test.ts`.
22
44
  */
23
45
 
24
46
  /**
25
- * `pragma --help` ceiling (ms). Designed 50; the rule is 2× the measured median.
26
- *
27
- * Down from the provisional 220, which covered the create surface's eager
28
- * registration imports on the capabilities barrel. Those are gone — the
29
- * registered flag spellings are baked into `createSurface.generated.ts` at
30
- * build time, the mount's adapter loads behind `CliProjection.prepare()`, and
31
- * the bare-help path no longer loads Commander at all — and the lazy-graph
32
- * guard in `lazy.test.ts` pins all three so the cost cannot creep back
33
- * silently.
34
- *
35
- * MEASURED, paired: the pre-refactor tree and this one were each built and
36
- * spawned alternately, 40 kept samples per cell, so drift on a shared box hits
37
- * both arms. Median 74.6 → 64.7 ms; net of each arm's own `--version` control,
38
- * the work this path does went 49.3 → 35.3 ms (−28%). 2 × 64.7 = 129.5, so
39
- * 130 is the rule's own number rather than a number the rule tolerates. It is
40
- * also where this path sat before the regression. BUDGETS.md carries the full
41
- * table, the reference-box projection, and why this is the floor.
47
+ * `pragma --help` ceiling (ms). Designed 50 — **not met, and recorded as such**
48
+ * for the same reason as {@link BUDGET_COMPLETE_MS}: node's own start is most
49
+ * of that number before pragma runs a line. The rule is 2× the measured median.
50
+ *
51
+ * Up from 130, re-derived 2026-09-10. 130 *was* the rule's own number, back
52
+ * when this path's work was 35.3 ms net of process start; it is not any more.
53
+ * The eagerly-imported capability barrel (`capabilities/index`) now costs
54
+ * **68.0 ms** to import where the recovery A/B measured 37.6 — the surface
55
+ * grew to 22 capability modules and 42 MCP tools, and each one puts its spec +
56
+ * formatter modules on this path. `lazy.test.ts` is green, so this is SURFACE
57
+ * GROWTH and not a lazy-boundary leak: summon-core's projection, its Commander
58
+ * adapter, `commander`, zod and oxigraph are all still absent from the
59
+ * fast-path graph. The barrel import is now essentially the whole of the cost
60
+ * (68.0 of 69.1 ms), which is where any future cut has to come from.
61
+ *
62
+ * MEASURED (2026-09-10; five load-gated repetitions × 40 kept samples,
63
+ * interleaved with an in-run `--version` control of 34.8 ms): median
64
+ * **103.3 ms**, p95 134.0 ms, work **69.1 ms** net of the control.
65
+ * `2 × 103.3 = 206.6`, so **210**. The reference-box projection cross-checks
66
+ * LOOSER, not tighter — `45.5 + 69.1 = 114.6` median → 2× = 229 — so 210 is
67
+ * the tight side of the rule, exactly as 130 was.
68
+ *
69
+ * What this can and cannot separate: at 210 against a 103 ms median it catches
70
+ * a doubling of the path and no longer catches the ~30 ms the barrel just
71
+ * gained. It is asserted on median AND raw p95 over 12 kept samples, where a
72
+ * nearest-rank p95 is effectively the maximum — the worst of 12 protocol
73
+ * replays here was 200.2 ms, so it holds but without much room. BUDGETS.md
74
+ * carries the table, the environment, and that flake characterisation.
42
75
  */
43
- export const BUDGET_HELP_MS = 130;
76
+ export const BUDGET_HELP_MS = 210;
44
77
 
45
78
  /**
46
79
  * `pragma __complete …` ceiling (ms). Designed 50 — **not met, and recorded as
47
80
  * such**: the shipped entry cannot reach it, because node's own start is most
48
81
  * of that number before pragma runs a line.
49
82
  *
50
- * Down from the provisional 220 for the same reason as {@link BUDGET_HELP_MS}:
51
- * the eager create-surface imports both fast paths paid for are deferred, and
52
- * completion additionally sheds Commander — nothing on the `__complete`
53
- * closure imports it any more.
54
- *
55
- * MEASURED in the same paired run: median 79.1 → 69.2 ms for the noun case and
56
- * 74.2 → 69.3 ms for the name-source case; net of the control, 53.7 → 39.7 ms
57
- * and 48.9 → 39.8 ms.
58
- *
59
- * 2× the slower median is 138.5, BELOW this 150 — and it stays 150 anyway,
60
- * because a ceiling is relative to the box as well as the artifact. This box's
61
- * cold start is 25–30 ms against the reference box's 45.5; projecting the
62
- * measured work onto the reference box gives a ~85 ms median, whose 2× is
63
- * ~170. CI has already run this path at a ~100 ms trimmed mean. Cutting to 140
64
- * on a local median would be deriving a ceiling on hardware the suite does not
65
- * run on. Completion is typed interactively, so this stays the budget most
66
- * worth defending — see BUDGETS.md for the arithmetic.
83
+ * Up from 150, re-derived 2026-09-10, and for the same single cause as
84
+ * {@link BUDGET_HELP_MS} — completion pays for the same grown capability
85
+ * barrel. It still sheds Commander (nothing on the `__complete` closure
86
+ * imports it), and it still costs ~12 ms more than `--help` because it walks
87
+ * the grammar and, in the name-source case, the skills directory.
88
+ *
89
+ * MEASURED in the same runs, on the SLOWER of the two cases
90
+ * (`__complete skill lookup do`): median **115.7 ms**, 10%-trimmed mean
91
+ * 118.6 ms, p95 145.0 ms, work 81.4 ms net of the control. The noun case
92
+ * (`__complete config`) is median 116.4 / trimmed mean 118.5 ms — the two are
93
+ * now within a millisecond of each other. `2 × 115.7 = 231.4`, so **240**;
94
+ * 2× the trimmed mean gives 237.2, the same 240. The reference-box projection
95
+ * again cross-checks looser (`45.5 + 81.4 = 126.9` → 2× = 254), so 240 is the
96
+ * tight side of the rule.
97
+ *
98
+ * The ceiling is enforced on the trimmed mean, with p95 kept as a SOFT signal
99
+ * at 1.5× — the statistic change made in the p95-stabilization work, which is
100
+ * unaffected by this re-derivation. Worst trimmed mean across 8 protocol
101
+ * replays here: 136.7 ms, so 240 is not a marginal ceiling.
102
+ *
103
+ * Completion is typed interactively, so this stays the budget most worth
104
+ * defending, and it has now lost 90 ms of standard across two re-derivations
105
+ * with nothing bought back. See BUDGETS.md.
67
106
  */
68
- export const BUDGET_COMPLETE_MS = 150;
107
+ export const BUDGET_COMPLETE_MS = 240;
69
108
 
70
- /** Warm project-config (`pragma.config.ts`) load ceiling (ms). Cache hit is sub-ms. */
109
+ /**
110
+ * Warm project-config (`pragma.config.ts`) load ceiling (ms). Cache hit is
111
+ * sub-ms.
112
+ *
113
+ * RE-MEASURED 2026-09-10 and deliberately UNCHANGED: 40 warm
114
+ * `evaluateProjectConfig` calls after priming the content-hash cache give a
115
+ * median of **0.011 ms** and a p95 of 0.034 ms — roughly 900× of headroom. The
116
+ * 2×-median rule would put this at 0.02 ms, which would assert nothing but
117
+ * scheduler jitter. 10 ms is a gross-regression guard (a cache that stopped
118
+ * hitting, an import that stopped being cached) and that is all it is for.
119
+ */
71
120
  export const BUDGET_PROJECT_CONFIG_MS = 10;
72
121
 
73
122
  /**
74
123
  * Warm store-backed verb ceiling (ms) — a store boot from the cached n-quads
75
124
  * dump plus a query, through the shipped entry.
76
125
  *
77
- * Re-derived when the embed became the distribution's real 8 479-triple graph
78
- * instead of a 23-triple sample. Netted against a `--version` control from the
79
- * SAME binary in the SAME run (this box's process start swings 60–287 ms with
80
- * page-cache state alone), the real pack's store work is 2.83× the sample's —
81
- * the median of five repetitions across two protocols. Projected onto the
82
- * reference box, whose own toy store work is 147 − 45.5 = 101.5 ms: 45.5 +
83
- * 101.5 × 2.83 ≈ 333 ms median, and 333 × (176/147) ≈ 398 ms p95 using the
84
- * reference box's own dispersion for this command. `ceil(398 × 1.25 / 50) × 50`
85
- * = 500 — which is 1.6× the projected median, i.e. tighter than the
86
- * 2×-of-median rule the ceilings above use. Every input, every step, and the
87
- * raw measurements are in BUDGETS.md; the designed `<300ms` target stays in the
88
- * surface covenant, exactly as the 50 ms `help` target survived its 130 ms
89
- * ceiling.
126
+ * Up from 500, re-derived 2026-09-10. The cause is the pack, not the code: the
127
+ * embed went from the 8 479-triple graph the 500 was derived against to
128
+ * **49 630 triples / 4 167 entities** (5.85× the triples). Boot still loads
129
+ * the n-quads dump rather than parsing TTL and still rebuilds the schema from
130
+ * the extraction artifact rather than running a live 7-pass compile, which is
131
+ * why the cost grew 1.75× and not 5.85×.
132
+ *
133
+ * The arithmetic, on the same route that produced 500, with one new input —
134
+ * the within-box growth multiplier. This box's netted real store work was
135
+ * +285.4 ms against the 8 479-triple pack (five repetitions, recorded in
136
+ * BUDGETS.md) and is +499.9 ms against this one (four load-gated repetitions
137
+ * × 40 kept samples, 2026-09-10), so:
138
+ *
139
+ * ```
140
+ * within-box growth multiplier = 499.9 / 285.4 = 1.752
141
+ * reference store work (old pack) = 101.5 × 2.83 = 287.2 ms
142
+ * reference store work (new pack) = 287.2 × 1.752 = 503.2 ms
143
+ * projected reference median = 45.5 + 503.2 = 548.7 ms
144
+ * reference p95/median, this command = 176 / 147 = 1.197
145
+ * projected p95 = 548.7 × 1.197 = 656.9 ms
146
+ * ceiling = ceil(656.9 × 1.25 / 50) × 50 = 850 ms
147
+ * ```
148
+ *
149
+ * The local-only route agrees to within 6%: this box's own measured p95 is
150
+ * 608.9 ms and `ceil(608.9 × 1.25 / 50) × 50` = 800. The reference-box number
151
+ * is taken, because that is the route the standing ceiling came from.
152
+ *
153
+ * 850 is still **1.55×** the projected median, i.e. tighter than the
154
+ * 2×-of-median rule the fast paths use. But it is now **2.83×** the designed
155
+ * `<300ms` target, where 500 was 1.67× — and that gap is the honest headline:
156
+ * the designed target was set against a 23-triple sample and then held against
157
+ * 8 479, and a 49 630-triple pack whose store work alone is ~500 ms cannot
158
+ * reach 300 ms at all. `warmStoreVerb: "<300ms"` stays in the surface covenant
159
+ * as the aspiration, and it is now an aspiration that needs a different boot
160
+ * strategy rather than a tuning pass. Every input and every step is in
161
+ * BUDGETS.md.
162
+ *
163
+ * What this can and cannot separate: asserted on median AND p95 over 9 kept
164
+ * samples, where a nearest-rank p95 IS the maximum — kept deliberately,
165
+ * because this excess is real work rather than contention noise, and hiding
166
+ * real cost behind a robust statistic would make the budget lie.
90
167
  */
91
- export const BUDGET_WARM_STORE_MS = 500;
168
+ export const BUDGET_WARM_STORE_MS = 850;
92
169
 
93
170
  /**
94
171
  * Warm in-process MCP tool-call ceiling (ms) — PR7 graduates this from seeded to
95
172
  * ENFORCED. Measured over a warm, storeless tool (`capabilities`): pure envelope
96
173
  * + dispatch, no store boot, no network, so it isolates the per-call overhead of
97
- * the grown 38-tool catalog. Measured p95 is ~0.4 ms here (huge headroom), so
98
- * 100 ms guards against a gross regression without flaking. `info` is
99
- * deliberately NOT used — its network update-check makes it ~55 ms (see BUDGETS.md).
174
+ * the grown tool catalog. `info` is deliberately NOT used — its network
175
+ * update-check makes it ~55 ms (see BUDGETS.md).
176
+ *
177
+ * RE-MEASURED 2026-09-10 and deliberately UNCHANGED. The catalog has grown
178
+ * 38 → 42 tools and the warm call grew with it — p95 **0.732 ms**, trimmed
179
+ * mean 0.530 ms over 40 calls, against ~0.4 ms p95 at 38 tools — but that is
180
+ * still ~137× of headroom under 100 ms. This is a gross-regression guard (a
181
+ * per-call store boot, a network read, a catalog rebuilt per call), and 100 ms
182
+ * is what lets it be one without flaking. Enforced on the trimmed mean with
183
+ * p95 as a second check (both ≤ 100).
100
184
  */
101
185
  export const BUDGET_MCP_P95_WARM_MS = 100;
@@ -1,167 +0,0 @@
1
- /**
2
- * Apply declared pack filters to resolved list rows.
3
- *
4
- * Filters are row predicates on projected SELECT variables — the author query is
5
- * never modified, so user input cannot inject SPARQL and the query's ordering is
6
- * preserved. With declared `values` the input is canonicalized against the set
7
- * (NFC, case-insensitive) and rows are matched case-insensitively; without them
8
- * the input is a free term matched the same way. A row lacking the variable never
9
- * matches. Several filters combine conjunctively; several values for one
10
- * filter (a repeated flag) combine as a union.
11
- *
12
- * A filter declaring `match: "set"` reads its cell as a space-separated SET and
13
- * matches any member — the shape a query produces for a dimension a row belongs
14
- * to several values of at once (a category and its ancestors, say). The
15
- * comparison is otherwise identical, so exactly one code path decides what a
16
- * match is.
17
- *
18
- * A value-free filter still REJECTS a value the data does not carry, with the
19
- * admissible values as `validOptions` — the same courtesy a declared-`values`
20
- * filter has always extended, without copying the vocabulary into the consumer.
21
- * The vocabulary is read from the graph, because the graph IS the vocabulary; a
22
- * story that hard-coded the slugs would go stale the first time the data grew a
23
- * category. Before this, a typo'd `--category` returned
24
- * `{"ok":true,"data":[],"meta":{}}` — indistinguishable, over MCP, from a
25
- * genuinely empty category.
26
- *
27
- * WHICH part of the graph is the vocabulary matters, and it is NOT the rows.
28
- * Rows are a population: a value can be perfectly real while no row carries it —
29
- * `standard categories` lists a category with count 0, `ds:ConceptType` declares
30
- * "Decision guide" before any concept uses it. Validating against rows called
31
- * those `INVALID_INPUT`, when the documented answer is a calm empty list. So a
32
- * filter DECLARES where its vocabulary lives ({@link PackFilter.vocabulary},
33
- * resolved by the caller and handed in here), and the observed rows are the
34
- * fallback for a filter that declares none — the only evidence there is, and
35
- * knowingly narrower than the truth.
36
- */
37
- import { PragmaError } from "../../error/index.js";
38
- /**
39
- * @param rows - Rows produced by the pack's list query.
40
- * @param filters - Declared filters (absent means no filtering).
41
- * @param params - Story parameters as provided by the surface.
42
- * @param vocabularies - Authoritative values per filter param, resolved from
43
- * each filter's declared `vocabulary` (see {@link FilterVocabularies}).
44
- * @returns Rows matching every provided filter.
45
- * @throws PragmaError INVALID_INPUT when a value is not in a filter's declared
46
- * set, or when a value-free filter receives a non-string value.
47
- */
48
- export function applyPackFilters(rows, filters, params, vocabularies) {
49
- let result = rows;
50
- for (const filter of filters ?? []) {
51
- const provided = params[filter.param];
52
- if (provided === undefined)
53
- continue;
54
- // A repeated CLI flag accumulates into an array; MCP args stay scalar.
55
- // Several values for ONE filter are a union (a row matches any of them);
56
- // several filters still combine conjunctively.
57
- const occurrences = Array.isArray(provided) ? provided : [provided];
58
- if (occurrences.length === 0)
59
- continue;
60
- const values = filter.values;
61
- const terms = occurrences.map((occurrence) => values === undefined
62
- ? requireStringValue(occurrence, filter).toLowerCase()
63
- : canonicalizeFilterValue(occurrence, filter, values).toLowerCase());
64
- // The fallback vocabulary is read from the UNFILTERED rows: a value that
65
- // exists but is excluded by a conjunction is a legitimately empty answer,
66
- // not a bad argument.
67
- if (values === undefined)
68
- rejectUnknownValue(vocabularies?.get(filter.param) ?? observedValues(rows, filter), filter, occurrences, terms);
69
- result = result.filter((row) => cellValues(row, filter).some((value) => terms.includes(value.toLowerCase())));
70
- }
71
- return result;
72
- }
73
- /**
74
- * The values the rows themselves carry — the fallback vocabulary for a filter
75
- * that declares no authoritative one, in the data's own DISPLAY spelling.
76
- */
77
- function observedValues(rows, filter) {
78
- const observed = new Map();
79
- for (const row of rows) {
80
- for (const value of cellValues(row, filter)) {
81
- const key = value.toLowerCase();
82
- if (!observed.has(key))
83
- observed.set(key, value);
84
- }
85
- }
86
- return [...observed.values()];
87
- }
88
- /**
89
- * Reject a value-free filter value the vocabulary does not admit, naming the
90
- * ones it does.
91
- *
92
- * Silent when the vocabulary is EMPTY: an unbuilt or genuinely-empty store is
93
- * not the caller's mistake, and the list's own `emptyRecovery` is the right
94
- * voice for it.
95
- *
96
- * A value the vocabulary admits is never rejected here even when no row carries
97
- * it — a real slug with zero standards is a calm empty list, which is what the
98
- * tool descriptions document and what `standard categories` reporting count 0
99
- * promises.
100
- *
101
- * @throws PragmaError INVALID_INPUT when a term matches none of the admissible
102
- * values, carrying them as `validOptions`.
103
- */
104
- function rejectUnknownValue(vocabulary, filter, occurrences, terms) {
105
- const admissible = new Map();
106
- for (const value of vocabulary) {
107
- const key = value.normalize("NFC").toLowerCase();
108
- if (!admissible.has(key))
109
- admissible.set(key, value.normalize("NFC"));
110
- }
111
- if (admissible.size === 0)
112
- return;
113
- const index = terms.findIndex((term) => !admissible.has(term));
114
- if (index === -1)
115
- return;
116
- const validOptions = [...admissible.values()].sort();
117
- throw PragmaError.invalidInput(filter.param, String(occurrences[index]), {
118
- validOptions,
119
- recovery: {
120
- message: `Values allowed for --${filter.param}: ${validOptions.join(", ")}.`,
121
- },
122
- });
123
- }
124
- /**
125
- * The values a row offers for one filter, in their DISPLAY spelling (NFC).
126
- *
127
- * One value for an ordinary filter; every space-separated member for a `"set"`
128
- * one. A row lacking the variable offers none, so it never matches. Kept in the
129
- * data's own casing because these are also what an INVALID_INPUT lists back as
130
- * `validOptions` — lowercasing here would hand the caller a value the graph does
131
- * not spell that way. Comparison lowercases at the point of comparison instead.
132
- */
133
- function cellValues(row, filter) {
134
- const cell = row[filter.variable];
135
- if (cell === undefined)
136
- return [];
137
- const normalized = cell.normalize("NFC");
138
- if (filter.match !== "set") {
139
- return normalized === "" ? [] : [normalized];
140
- }
141
- return normalized.split(/\s+/).filter((value) => value !== "");
142
- }
143
- /** @throws PragmaError INVALID_INPUT when a value-free filter value is not a string. */
144
- function requireStringValue(provided, filter) {
145
- if (typeof provided !== "string") {
146
- throw PragmaError.invalidInput(filter.param, String(provided), {
147
- recovery: { message: `Provide a string value for --${filter.param}.` },
148
- });
149
- }
150
- return provided.trim().normalize("NFC");
151
- }
152
- /** @throws PragmaError INVALID_INPUT when the value is not in the declared set. */
153
- function canonicalizeFilterValue(provided, filter, values) {
154
- if (typeof provided === "string") {
155
- const normalized = provided.trim().normalize("NFC");
156
- const match = values.find((value) => value.normalize("NFC").toLowerCase() === normalized.toLowerCase());
157
- if (match !== undefined)
158
- return match.normalize("NFC");
159
- }
160
- throw PragmaError.invalidInput(filter.param, String(provided), {
161
- validOptions: [...values],
162
- recovery: {
163
- message: `Allowed values for --${filter.param}: ${values.join(", ")}.`,
164
- },
165
- });
166
- }
167
- //# sourceMappingURL=applyFilters.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"applyFilters.js","sourceRoot":"","sources":["../../../../../src/kernel/packs/sparql/applyFilters.ts"],"names":[],"mappings":"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"}
@@ -1,25 +0,0 @@
1
- /**
2
- * Apply a pack list's free-text search to resolved rows.
3
- *
4
- * Like filters, search runs AFTER the author query, so user input never touches
5
- * the query text (injection-safe) and the author's ORDER BY is preserved. The
6
- * term is NFC-normalized and matched case-insensitively; a row passes when ANY
7
- * declared variable contains it as a substring. A non-string or empty term is a
8
- * no-op.
9
- */
10
- /**
11
- * @param rows - Rows produced by the pack's list query (post-filter).
12
- * @param search - The declared search (absent means no searching).
13
- * @param params - Story parameters as provided by the surface.
14
- * @returns Rows containing the term in at least one searched variable.
15
- */
16
- export function applyPackSearch(rows, search, params) {
17
- const provided = params.search;
18
- if (search === undefined || typeof provided !== "string")
19
- return rows;
20
- const term = provided.trim().normalize("NFC").toLowerCase();
21
- if (term === "")
22
- return rows;
23
- return rows.filter((row) => search.variables.some((variable) => row[variable]?.normalize("NFC").toLowerCase().includes(term)));
24
- }
25
- //# sourceMappingURL=applySearch.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"applySearch.js","sourceRoot":"","sources":["../../../../../src/kernel/packs/sparql/applySearch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAe,EACf,MAA8B,EAC9B,MAA+B;IAE/B,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CACzB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CACjC,GAAG,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC7D,CACF,CAAC;AACJ,CAAC"}
@@ -1,203 +0,0 @@
1
- /**
2
- * Apply declared pack filters to resolved list rows.
3
- *
4
- * Filters are row predicates on projected SELECT variables — the author query is
5
- * never modified, so user input cannot inject SPARQL and the query's ordering is
6
- * preserved. With declared `values` the input is canonicalized against the set
7
- * (NFC, case-insensitive) and rows are matched case-insensitively; without them
8
- * the input is a free term matched the same way. A row lacking the variable never
9
- * matches. Several filters combine conjunctively; several values for one
10
- * filter (a repeated flag) combine as a union.
11
- *
12
- * A filter declaring `match: "set"` reads its cell as a space-separated SET and
13
- * matches any member — the shape a query produces for a dimension a row belongs
14
- * to several values of at once (a category and its ancestors, say). The
15
- * comparison is otherwise identical, so exactly one code path decides what a
16
- * match is.
17
- *
18
- * A value-free filter still REJECTS a value the data does not carry, with the
19
- * admissible values as `validOptions` — the same courtesy a declared-`values`
20
- * filter has always extended, without copying the vocabulary into the consumer.
21
- * The vocabulary is read from the graph, because the graph IS the vocabulary; a
22
- * story that hard-coded the slugs would go stale the first time the data grew a
23
- * category. Before this, a typo'd `--category` returned
24
- * `{"ok":true,"data":[],"meta":{}}` — indistinguishable, over MCP, from a
25
- * genuinely empty category.
26
- *
27
- * WHICH part of the graph is the vocabulary matters, and it is NOT the rows.
28
- * Rows are a population: a value can be perfectly real while no row carries it —
29
- * `standard categories` lists a category with count 0, `ds:ConceptType` declares
30
- * "Decision guide" before any concept uses it. Validating against rows called
31
- * those `INVALID_INPUT`, when the documented answer is a calm empty list. So a
32
- * filter DECLARES where its vocabulary lives ({@link PackFilter.vocabulary},
33
- * resolved by the caller and handed in here), and the observed rows are the
34
- * fallback for a filter that declares none — the only evidence there is, and
35
- * knowingly narrower than the truth.
36
- */
37
-
38
- import { PragmaError } from "../../error/index.js";
39
- import type { PackFilter, PackRow } from "../types.js";
40
-
41
- /**
42
- * Authoritative values per filter `param`, read from each filter's declared
43
- * `vocabulary`. A param absent from the map falls back to the observed rows.
44
- */
45
- export type FilterVocabularies = ReadonlyMap<string, readonly string[]>;
46
-
47
- /**
48
- * @param rows - Rows produced by the pack's list query.
49
- * @param filters - Declared filters (absent means no filtering).
50
- * @param params - Story parameters as provided by the surface.
51
- * @param vocabularies - Authoritative values per filter param, resolved from
52
- * each filter's declared `vocabulary` (see {@link FilterVocabularies}).
53
- * @returns Rows matching every provided filter.
54
- * @throws PragmaError INVALID_INPUT when a value is not in a filter's declared
55
- * set, or when a value-free filter receives a non-string value.
56
- */
57
- export function applyPackFilters(
58
- rows: PackRow[],
59
- filters: readonly PackFilter[] | undefined,
60
- params: Record<string, unknown>,
61
- vocabularies?: FilterVocabularies,
62
- ): PackRow[] {
63
- let result = rows;
64
- for (const filter of filters ?? []) {
65
- const provided = params[filter.param];
66
- if (provided === undefined) continue;
67
- // A repeated CLI flag accumulates into an array; MCP args stay scalar.
68
- // Several values for ONE filter are a union (a row matches any of them);
69
- // several filters still combine conjunctively.
70
- const occurrences = Array.isArray(provided) ? provided : [provided];
71
- if (occurrences.length === 0) continue;
72
- const values = filter.values;
73
- const terms = occurrences.map((occurrence) =>
74
- values === undefined
75
- ? requireStringValue(occurrence, filter).toLowerCase()
76
- : canonicalizeFilterValue(occurrence, filter, values).toLowerCase(),
77
- );
78
- // The fallback vocabulary is read from the UNFILTERED rows: a value that
79
- // exists but is excluded by a conjunction is a legitimately empty answer,
80
- // not a bad argument.
81
- if (values === undefined)
82
- rejectUnknownValue(
83
- vocabularies?.get(filter.param) ?? observedValues(rows, filter),
84
- filter,
85
- occurrences,
86
- terms,
87
- );
88
- result = result.filter((row) =>
89
- cellValues(row, filter).some((value) =>
90
- terms.includes(value.toLowerCase()),
91
- ),
92
- );
93
- }
94
- return result;
95
- }
96
-
97
- /**
98
- * The values the rows themselves carry — the fallback vocabulary for a filter
99
- * that declares no authoritative one, in the data's own DISPLAY spelling.
100
- */
101
- function observedValues(
102
- rows: readonly PackRow[],
103
- filter: PackFilter,
104
- ): string[] {
105
- const observed = new Map<string, string>();
106
- for (const row of rows) {
107
- for (const value of cellValues(row, filter)) {
108
- const key = value.toLowerCase();
109
- if (!observed.has(key)) observed.set(key, value);
110
- }
111
- }
112
- return [...observed.values()];
113
- }
114
-
115
- /**
116
- * Reject a value-free filter value the vocabulary does not admit, naming the
117
- * ones it does.
118
- *
119
- * Silent when the vocabulary is EMPTY: an unbuilt or genuinely-empty store is
120
- * not the caller's mistake, and the list's own `emptyRecovery` is the right
121
- * voice for it.
122
- *
123
- * A value the vocabulary admits is never rejected here even when no row carries
124
- * it — a real slug with zero standards is a calm empty list, which is what the
125
- * tool descriptions document and what `standard categories` reporting count 0
126
- * promises.
127
- *
128
- * @throws PragmaError INVALID_INPUT when a term matches none of the admissible
129
- * values, carrying them as `validOptions`.
130
- */
131
- function rejectUnknownValue(
132
- vocabulary: readonly string[],
133
- filter: PackFilter,
134
- occurrences: readonly unknown[],
135
- terms: readonly string[],
136
- ): void {
137
- const admissible = new Map<string, string>();
138
- for (const value of vocabulary) {
139
- const key = value.normalize("NFC").toLowerCase();
140
- if (!admissible.has(key)) admissible.set(key, value.normalize("NFC"));
141
- }
142
- if (admissible.size === 0) return;
143
- const index = terms.findIndex((term) => !admissible.has(term));
144
- if (index === -1) return;
145
- const validOptions = [...admissible.values()].sort();
146
- throw PragmaError.invalidInput(filter.param, String(occurrences[index]), {
147
- validOptions,
148
- recovery: {
149
- message: `Values allowed for --${filter.param}: ${validOptions.join(", ")}.`,
150
- },
151
- });
152
- }
153
-
154
- /**
155
- * The values a row offers for one filter, in their DISPLAY spelling (NFC).
156
- *
157
- * One value for an ordinary filter; every space-separated member for a `"set"`
158
- * one. A row lacking the variable offers none, so it never matches. Kept in the
159
- * data's own casing because these are also what an INVALID_INPUT lists back as
160
- * `validOptions` — lowercasing here would hand the caller a value the graph does
161
- * not spell that way. Comparison lowercases at the point of comparison instead.
162
- */
163
- function cellValues(row: PackRow, filter: PackFilter): string[] {
164
- const cell = row[filter.variable];
165
- if (cell === undefined) return [];
166
- const normalized = cell.normalize("NFC");
167
- if (filter.match !== "set") {
168
- return normalized === "" ? [] : [normalized];
169
- }
170
- return normalized.split(/\s+/).filter((value) => value !== "");
171
- }
172
-
173
- /** @throws PragmaError INVALID_INPUT when a value-free filter value is not a string. */
174
- function requireStringValue(provided: unknown, filter: PackFilter): string {
175
- if (typeof provided !== "string") {
176
- throw PragmaError.invalidInput(filter.param, String(provided), {
177
- recovery: { message: `Provide a string value for --${filter.param}.` },
178
- });
179
- }
180
- return provided.trim().normalize("NFC");
181
- }
182
-
183
- /** @throws PragmaError INVALID_INPUT when the value is not in the declared set. */
184
- function canonicalizeFilterValue(
185
- provided: unknown,
186
- filter: PackFilter,
187
- values: readonly string[],
188
- ): string {
189
- if (typeof provided === "string") {
190
- const normalized = provided.trim().normalize("NFC");
191
- const match = values.find(
192
- (value) =>
193
- value.normalize("NFC").toLowerCase() === normalized.toLowerCase(),
194
- );
195
- if (match !== undefined) return match.normalize("NFC");
196
- }
197
- throw PragmaError.invalidInput(filter.param, String(provided), {
198
- validOptions: [...values],
199
- recovery: {
200
- message: `Allowed values for --${filter.param}: ${values.join(", ")}.`,
201
- },
202
- });
203
- }