@canonical/pragma-cli 0.38.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +3 -3
  2. package/dist/package.json +16 -16
  3. package/dist/pragma.conf.js +273 -176
  4. package/dist/pragma.conf.js.map +1 -1
  5. package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
  6. package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
  7. package/dist/src/capabilities/capabilities/catalog.js +47 -51
  8. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  9. package/dist/src/capabilities/capabilities/index.js +2 -0
  10. package/dist/src/capabilities/capabilities/index.js.map +1 -1
  11. package/dist/src/capabilities/capabilities/types.js +5 -5
  12. package/dist/src/capabilities/colophon/colophon.render.js +7 -4
  13. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  14. package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
  15. package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
  16. package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -1
  17. package/dist/src/capabilities/config/get.verb.js +2 -0
  18. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  19. package/dist/src/capabilities/config/runField.js +2 -2
  20. package/dist/src/capabilities/config/runField.js.map +1 -1
  21. package/dist/src/capabilities/config/set.verb.js +2 -0
  22. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  23. package/dist/src/capabilities/config/show.render.js.map +1 -1
  24. package/dist/src/capabilities/config/show.verb.js +1 -0
  25. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  26. package/dist/src/capabilities/config/unset.verb.js +2 -0
  27. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  28. package/dist/src/capabilities/create/constants.js +8 -5
  29. package/dist/src/capabilities/create/constants.js.map +1 -1
  30. package/dist/src/capabilities/create/create.render.js.map +1 -1
  31. package/dist/src/capabilities/create/create.verb.js +36 -7
  32. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  33. package/dist/src/capabilities/create/createSurface.generated.js +22 -0
  34. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  35. package/dist/src/capabilities/create/index.js +1 -1
  36. package/dist/src/capabilities/create/pickGenerator.js +1 -0
  37. package/dist/src/capabilities/create/pickGenerator.js.map +1 -1
  38. package/dist/src/capabilities/distribution.js +3 -1
  39. package/dist/src/capabilities/distribution.js.map +1 -1
  40. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  41. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  42. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  43. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  44. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  46. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  47. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  48. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  49. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  50. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  51. package/dist/src/capabilities/graph/query.render.js +37 -5
  52. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  53. package/dist/src/capabilities/graph/query.verb.js +2 -0
  54. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  55. package/dist/src/capabilities/graph/queryText.js +28 -0
  56. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  57. package/dist/src/capabilities/graph/runConnect.js +3 -3
  58. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  59. package/dist/src/capabilities/graph/runQuery.js +8 -6
  60. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  61. package/dist/src/capabilities/info/info.verb.js +2 -0
  62. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  63. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  64. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  65. package/dist/src/capabilities/ontology/verbs.js +7 -10
  66. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  67. package/dist/src/capabilities/prompt/verbs.js +5 -4
  68. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  69. package/dist/src/capabilities/resources/provider.js +6 -3
  70. package/dist/src/capabilities/resources/provider.js.map +1 -1
  71. package/dist/src/capabilities/setup/operations/writability.js.map +1 -1
  72. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  73. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  74. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  75. package/dist/src/capabilities/shared/calls.js +16 -0
  76. package/dist/src/capabilities/shared/calls.js.map +1 -0
  77. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  78. package/dist/src/capabilities/skill/discover.js.map +1 -1
  79. package/dist/src/capabilities/skill/render.js +7 -5
  80. package/dist/src/capabilities/skill/render.js.map +1 -1
  81. package/dist/src/capabilities/skill/verbs.js +5 -4
  82. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  83. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  84. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  85. package/dist/src/capabilities/sources/index.js +4 -3
  86. package/dist/src/capabilities/sources/index.js.map +1 -1
  87. package/dist/src/capabilities/sources/reset.render.js +37 -0
  88. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  89. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  90. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  91. package/dist/src/capabilities/sources/runReset.js +59 -0
  92. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  93. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  94. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  95. package/dist/src/capabilities/sources/status.render.js +24 -8
  96. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  97. package/dist/src/capabilities/sources/status.verb.js +1 -0
  98. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  99. package/dist/src/capabilities/sources/update.verb.js +2 -0
  100. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  101. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  102. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  103. package/dist/src/capabilities/version/version.verb.js +1 -0
  104. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  105. package/dist/src/constants.js +4 -3
  106. package/dist/src/constants.js.map +1 -1
  107. package/dist/src/kernel/completion/complete.js.map +1 -1
  108. package/dist/src/kernel/completion/emitScripts.js.map +1 -1
  109. package/dist/src/kernel/completion/parse.js.map +1 -1
  110. package/dist/src/kernel/completion/rank.js.map +1 -1
  111. package/dist/src/kernel/completion/resolve.js.map +1 -1
  112. package/dist/src/kernel/config/readConfig.js.map +1 -1
  113. package/dist/src/kernel/error/PragmaError.js.map +1 -1
  114. package/dist/src/kernel/error/index.js +1 -1
  115. package/dist/src/kernel/error/index.js.map +1 -1
  116. package/dist/src/kernel/error/recovery.js +24 -42
  117. package/dist/src/kernel/error/recovery.js.map +1 -1
  118. package/dist/src/kernel/packs/collect.js +123 -9
  119. package/dist/src/kernel/packs/collect.js.map +1 -1
  120. package/dist/src/kernel/packs/compile.js +74 -15
  121. package/dist/src/kernel/packs/compile.js.map +1 -1
  122. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  123. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  124. package/dist/src/kernel/packs/iri.js +2 -4
  125. package/dist/src/kernel/packs/iri.js.map +1 -1
  126. package/dist/src/kernel/packs/renderPack.js +90 -50
  127. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  128. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  129. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  130. package/dist/src/kernel/packs/runBodies.js +89 -30
  131. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  132. package/dist/src/kernel/packs/schema.js +66 -12
  133. package/dist/src/kernel/packs/schema.js.map +1 -1
  134. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  135. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  136. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  137. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  138. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  139. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  140. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  141. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  142. package/dist/src/kernel/packs/storyRules.js +31 -4
  143. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  144. package/dist/src/kernel/packs/tierScope.js +3 -3
  145. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  146. package/dist/src/kernel/packs/types.js +4 -0
  147. package/dist/src/kernel/packs/types.js.map +1 -1
  148. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  149. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  150. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  151. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  152. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  153. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  154. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  155. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  156. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  157. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  158. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  159. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  160. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  161. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  162. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  163. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  164. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  165. package/dist/src/kernel/render/envelope.js.map +1 -1
  166. package/dist/src/kernel/render/renderers.js +3 -3
  167. package/dist/src/kernel/render/renderers.js.map +1 -1
  168. package/dist/src/kernel/render/turtle.js +21 -4
  169. package/dist/src/kernel/render/turtle.js.map +1 -1
  170. package/dist/src/kernel/runtime/boot.js.map +1 -1
  171. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  172. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  173. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  174. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  175. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  176. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  177. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  178. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  179. package/dist/src/kernel/runtime/loadSession.js +8 -1
  180. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  181. package/dist/src/kernel/runtime/packVersion.js +75 -0
  182. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  183. package/dist/src/kernel/runtime/readEntity.js +43 -4
  184. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  185. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  186. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  187. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  188. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  189. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  190. package/dist/src/kernel/spec/call.js +167 -0
  191. package/dist/src/kernel/spec/call.js.map +1 -0
  192. package/dist/src/kernel/spec/emitReference.js +9 -13
  193. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  194. package/dist/src/kernel/spec/emitSurface.js +8 -1
  195. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  196. package/dist/src/kernel/spec/guidance.js +63 -0
  197. package/dist/src/kernel/spec/guidance.js.map +1 -0
  198. package/dist/src/kernel/spec/wireType.js +36 -0
  199. package/dist/src/kernel/spec/wireType.js.map +1 -0
  200. package/package.json +16 -16
  201. package/pragma.conf.ts +294 -177
  202. package/src/capabilities/block.parity.test.ts +1 -1
  203. package/src/capabilities/callRule.test.ts +147 -0
  204. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  205. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  206. package/src/capabilities/capabilities/catalog.ts +60 -55
  207. package/src/capabilities/capabilities/index.ts +2 -0
  208. package/src/capabilities/capabilities/types.ts +16 -19
  209. package/src/capabilities/colophon/colophon.render.ts +9 -5
  210. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  211. package/src/capabilities/config/get.verb.ts +2 -0
  212. package/src/capabilities/config/runField.ts +3 -3
  213. package/src/capabilities/config/set.verb.ts +3 -0
  214. package/src/capabilities/config/show.verb.ts +2 -0
  215. package/src/capabilities/config/unset.verb.ts +2 -0
  216. package/src/capabilities/create/constants.ts +8 -5
  217. package/src/capabilities/create/create.mcp.test.ts +173 -1
  218. package/src/capabilities/create/create.test.ts +4 -2
  219. package/src/capabilities/create/create.verb.ts +45 -3
  220. package/src/capabilities/create/createGrammar.test.ts +41 -5
  221. package/src/capabilities/create/createSurface.generated.ts +22 -0
  222. package/src/capabilities/create/crossCli.subprocess.test.ts +53 -14
  223. package/src/capabilities/create/index.ts +1 -1
  224. package/src/capabilities/create/pickGenerator.ts +1 -0
  225. package/src/capabilities/create/shippedCreate.subprocess.test.ts +28 -7
  226. package/src/capabilities/distribution.test.ts +6 -3
  227. package/src/capabilities/distribution.ts +6 -0
  228. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  229. package/src/capabilities/doctor/doctor.test.ts +38 -1
  230. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  231. package/src/capabilities/emptyRecovery.test.ts +5 -0
  232. package/src/capabilities/graph/connect.verb.ts +2 -0
  233. package/src/capabilities/graph/inspect.render.ts +9 -1
  234. package/src/capabilities/graph/inspect.test.ts +73 -0
  235. package/src/capabilities/graph/inspect.verb.ts +2 -0
  236. package/src/capabilities/graph/query.render.test.ts +35 -0
  237. package/src/capabilities/graph/query.render.ts +45 -7
  238. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  239. package/src/capabilities/graph/query.verb.ts +2 -0
  240. package/src/capabilities/graph/queryText.test.ts +55 -1
  241. package/src/capabilities/graph/queryText.ts +47 -0
  242. package/src/capabilities/graph/runConnect.ts +4 -5
  243. package/src/capabilities/graph/runQuery.ts +23 -9
  244. package/src/capabilities/info/info.verb.ts +2 -0
  245. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  246. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  247. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  248. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  249. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  250. package/src/capabilities/meta/mcp.verb.ts +2 -0
  251. package/src/capabilities/ontology/ontology.test.ts +24 -0
  252. package/src/capabilities/ontology/verbs.ts +16 -12
  253. package/src/capabilities/prompt/verbs.ts +10 -4
  254. package/src/capabilities/resources/provider.ts +7 -3
  255. package/src/capabilities/resources/resources.test.ts +2 -1
  256. package/src/capabilities/setup/setup.verb.ts +16 -0
  257. package/src/capabilities/shared/calls.ts +20 -0
  258. package/src/capabilities/skill/render.ts +9 -6
  259. package/src/capabilities/skill/verbs.ts +8 -4
  260. package/src/capabilities/sources/collectStatus.ts +6 -1
  261. package/src/capabilities/sources/index.ts +4 -3
  262. package/src/capabilities/sources/reset.render.test.ts +33 -0
  263. package/src/capabilities/sources/reset.render.ts +43 -0
  264. package/src/capabilities/sources/reset.verb.ts +59 -0
  265. package/src/capabilities/sources/runReset.ts +65 -0
  266. package/src/capabilities/sources/runUpdate.ts +12 -11
  267. package/src/capabilities/sources/sources.test.ts +158 -3
  268. package/src/capabilities/sources/status.render.test.ts +63 -1
  269. package/src/capabilities/sources/status.render.ts +25 -8
  270. package/src/capabilities/sources/status.verb.ts +2 -0
  271. package/src/capabilities/sources/types.ts +26 -0
  272. package/src/capabilities/sources/update.verb.ts +3 -0
  273. package/src/capabilities/surface.test.ts +29 -4
  274. package/src/capabilities/token.parity.test.ts +3 -2
  275. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  276. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  277. package/src/capabilities/variable.parity.test.ts +2 -1
  278. package/src/capabilities/version/version.verb.ts +1 -0
  279. package/src/capabilities/wireContract.test.ts +110 -0
  280. package/src/constants.ts +4 -3
  281. package/src/kernel/completion/shellDrive.test.ts +2 -2
  282. package/src/kernel/copy.test.ts +1 -1
  283. package/src/kernel/error/PragmaError.test.ts +7 -3
  284. package/src/kernel/error/index.ts +1 -1
  285. package/src/kernel/error/recovery.test.ts +52 -18
  286. package/src/kernel/error/recovery.ts +25 -46
  287. package/src/kernel/error/types.ts +1 -1
  288. package/src/kernel/packs/collect.test.ts +79 -0
  289. package/src/kernel/packs/collect.ts +151 -11
  290. package/src/kernel/packs/compile.test.ts +107 -10
  291. package/src/kernel/packs/compile.ts +100 -11
  292. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  293. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  294. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  295. package/src/kernel/packs/iri.ts +5 -4
  296. package/src/kernel/packs/listNotice.test.ts +1 -1
  297. package/src/kernel/packs/nounLink.test.ts +385 -0
  298. package/src/kernel/packs/renderPack.ts +137 -57
  299. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  300. package/src/kernel/packs/resolveEntity.ts +281 -101
  301. package/src/kernel/packs/runBodies.ts +133 -31
  302. package/src/kernel/packs/schema.ts +77 -12
  303. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  304. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  305. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  306. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  307. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  308. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  309. package/src/kernel/packs/storyRules.ts +37 -4
  310. package/src/kernel/packs/tierScope.test.ts +120 -6
  311. package/src/kernel/packs/tierScope.ts +5 -7
  312. package/src/kernel/packs/toolDescription.test.ts +59 -74
  313. package/src/kernel/packs/types.ts +93 -11
  314. package/src/kernel/project/cli/buildProgram.ts +4 -0
  315. package/src/kernel/project/cli/dispatch.ts +34 -8
  316. package/src/kernel/project/cli/suggest.test.ts +77 -0
  317. package/src/kernel/project/cli/suggestNames.ts +47 -1
  318. package/src/kernel/project/cli/verbHelp.ts +16 -19
  319. package/src/kernel/project/mcp/buildServer.ts +15 -4
  320. package/src/kernel/project/mcp/envelope.ts +3 -2
  321. package/src/kernel/project/mcp/instructions.ts +123 -35
  322. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  323. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  324. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  325. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  326. package/src/kernel/render/renderers.test.ts +2 -2
  327. package/src/kernel/render/renderers.ts +3 -3
  328. package/src/kernel/render/turtle.ts +24 -4
  329. package/src/kernel/runtime/graphpack/build.ts +58 -1
  330. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  331. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  332. package/src/kernel/runtime/graphpack/read.ts +4 -6
  333. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  334. package/src/kernel/runtime/graphpack/types.ts +13 -0
  335. package/src/kernel/runtime/loadSession.ts +8 -1
  336. package/src/kernel/runtime/packVersion.test.ts +61 -0
  337. package/src/kernel/runtime/packVersion.ts +84 -0
  338. package/src/kernel/runtime/readEntity.ts +61 -6
  339. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  340. package/src/kernel/runtime/resolveSources.ts +91 -10
  341. package/src/kernel/runtime/storeReadiness.ts +4 -4
  342. package/src/kernel/runtime/types.ts +12 -0
  343. package/src/kernel/spec/call.test.ts +95 -0
  344. package/src/kernel/spec/call.ts +186 -0
  345. package/src/kernel/spec/emitReference.ts +9 -13
  346. package/src/kernel/spec/emitSurface.ts +9 -1
  347. package/src/kernel/spec/guidance.test.ts +49 -0
  348. package/src/kernel/spec/guidance.ts +68 -0
  349. package/src/kernel/spec/index.ts +4 -0
  350. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  351. package/src/kernel/spec/types.ts +64 -7
  352. package/src/kernel/spec/wireType.ts +53 -0
  353. package/src/testing/behavioral/README.md +1 -1
  354. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  355. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  356. package/src/testing/behavioral/parity.test.ts +40 -0
  357. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  358. package/src/testing/eval/cases/assembly.ts +4 -3
  359. package/src/testing/eval/cases/stable.ts +2 -3
  360. package/src/testing/helpers/callSchema.ts +40 -0
  361. package/src/testing/helpers/parity.ts +35 -2
  362. package/src/testing/setupCallChecking.ts +30 -0
  363. package/dist/src/capabilities/capabilities/hints.js +0 -223
  364. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  365. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -51,6 +51,7 @@ export const packIndexSchema: z.ZodType<PackIndex> = z.object({
51
51
  /** zod schema validating a persisted `manifest.json`. */
52
52
  export const manifestSchema: z.ZodType<Manifest> = z.object({
53
53
  name: z.string(),
54
+ /** The CLI version that built the pack — the boot decision compares it. */
54
55
  version: z.string(),
55
56
  /** The config `packs` ref this pack was built from (verbatim), or a label. */
56
57
  sourceRef: z.string(),
@@ -115,6 +115,19 @@ export interface PackIndex {
115
115
  */
116
116
  export interface Manifest {
117
117
  readonly name: string;
118
+ /**
119
+ * The version of the CLI that built this pack — `VERSION`, written by
120
+ * `sources update` and by the bundler for the embedded snapshot.
121
+ *
122
+ * Load-bearing, not just provenance: the boot decision compares it with the
123
+ * running CLI's version, so a pack an older CLI built in a project that
124
+ * declares no packs of its own yields to the newer CLI's embedded snapshot
125
+ * (see `resolveSources`). Every pack any released CLI has ever built already
126
+ * carries it, which is why that rule needed no new field and heals packs built
127
+ * before it existed. A REUSED pack has it refreshed (see
128
+ * `build.stampBuilderVersion`) — otherwise `sources update` could not get a
129
+ * project out of that state.
130
+ */
118
131
  readonly version: string;
119
132
  /** The config `packs` ref this pack was built from (verbatim), or a label. */
120
133
  readonly sourceRef: string;
@@ -46,5 +46,12 @@ export async function loadStoreSession(
46
46
  decision.kind === "embedded"
47
47
  ? await materializeEmbeddedPack()
48
48
  : decision.dir;
49
- return readPack(dir);
49
+ const session = await readPack(dir);
50
+ // Carry the passed-over pack (the upgrade row of the decision table) onto the
51
+ // session, so a read can report which pack it did NOT answer from without
52
+ // re-deriving the decision — the same reason the session already carries the
53
+ // loaded pack's own manifest.
54
+ return decision.kind === "embedded" && decision.ignoredPack !== undefined
55
+ ? { ...session, ignoredPack: decision.ignoredPack }
56
+ : session;
50
57
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The builder-version comparison behind the boot decision's upgrade row.
3
+ *
4
+ * It decides whether a pack a user built on purpose is passed over, so the
5
+ * cases that matter most here are the ones where it must answer NO.
6
+ */
7
+
8
+ import { describe, expect, it } from "vitest";
9
+ import { packIsOlderThanCli, parseSemver } from "./packVersion.js";
10
+
11
+ describe("parseSemver", () => {
12
+ it("parses a release triple", () => {
13
+ expect(parseSemver("0.38.0")).toEqual([0, 38, 0]);
14
+ expect(parseSemver("10.2.137")).toEqual([10, 2, 137]);
15
+ });
16
+
17
+ it("parses a pre-release/build version to its release triple", () => {
18
+ // The suffix orders two builds of the same release; this comparison only
19
+ // ever asks which RELEASE is older.
20
+ expect(parseSemver("1.2.3-rc.1")).toEqual([1, 2, 3]);
21
+ expect(parseSemver("1.2.3+sha.abc")).toEqual([1, 2, 3]);
22
+ });
23
+
24
+ it("refuses anything that is not a version", () => {
25
+ for (const value of ["0", "1.2", "v1.2.3", "", "next", "1.2.3.4"]) {
26
+ expect(parseSemver(value)).toBeUndefined();
27
+ }
28
+ });
29
+ });
30
+
31
+ describe("packIsOlderThanCli", () => {
32
+ it("is true only for a pack built by a strictly older CLI", () => {
33
+ expect(packIsOlderThanCli("0.37.0", "0.38.0")).toBe(true);
34
+ expect(packIsOlderThanCli("0.38.0", "1.0.0")).toBe(true);
35
+ expect(packIsOlderThanCli("1.2.3", "1.2.4")).toBe(true);
36
+ });
37
+
38
+ it("keeps the pack when the versions are equal", () => {
39
+ expect(packIsOlderThanCli("0.38.0", "0.38.0")).toBe(false);
40
+ });
41
+
42
+ it("keeps the pack when a NEWER CLI built it", () => {
43
+ // A downgrade, or two CLIs sharing one cache. The user's own build wins.
44
+ expect(packIsOlderThanCli("0.39.0", "0.38.0")).toBe(false);
45
+ expect(packIsOlderThanCli("1.0.0", "0.38.0")).toBe(false);
46
+ });
47
+
48
+ it("keeps the pack under a dev build (0.0.0) or an unreadable CLI version", () => {
49
+ // A local checkout must not start reporting every pack on the machine as
50
+ // stale — its version says nothing about how its snapshot compares.
51
+ expect(packIsOlderThanCli("0.37.0", "0.0.0")).toBe(false);
52
+ expect(packIsOlderThanCli("0.37.0", "workspace")).toBe(false);
53
+ });
54
+
55
+ it("keeps a pack whose recorded version cannot be read", () => {
56
+ // The notice names the version that built the pack, so a pack we cannot
57
+ // name is one we cannot honestly call old.
58
+ expect(packIsOlderThanCli("0", "0.38.0")).toBe(false);
59
+ expect(packIsOlderThanCli("", "0.38.0")).toBe(false);
60
+ });
61
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Is a built pack older than the CLI that is now reading it?
3
+ *
4
+ * A pack's `manifest.json` records the version of the CLI that built it (the
5
+ * `version` field — see `graphpack/types.Manifest`). When a user upgrades, the
6
+ * new CLI ships a new embedded snapshot, and a pack built by the OLD CLI weeks
7
+ * ago is no longer the better of the two: it answered every read from a graph
8
+ * the upgrade was meant to replace, silently, with nothing in `sources status`
9
+ * to say so. This module is the comparison that ends that — `resolveSources`
10
+ * uses it to let the embedded snapshot answer instead (see its decision table).
11
+ *
12
+ * A LEAF on the storeless fast path: string arithmetic, no imports at all. It
13
+ * deliberately does NOT depend on a semver library — the comparison is
14
+ * major/minor/patch on two strings that both come from a `package.json`
15
+ * `version`, and the fast path pays for every value import it makes.
16
+ */
17
+
18
+ /** A parsed `major.minor.patch` triple. */
19
+ type Semver = readonly [major: number, minor: number, patch: number];
20
+
21
+ /**
22
+ * Parse the `major.minor.patch` prefix of a version string.
23
+ *
24
+ * A pre-release or build suffix (`1.2.3-rc.1`, `1.2.3+sha`) parses to its
25
+ * release triple: the suffix orders two builds of the SAME release, and this
26
+ * comparison only ever asks which release is older.
27
+ *
28
+ * @param value - The recorded version string.
29
+ * @returns The triple, or `undefined` when the string is not a version.
30
+ */
31
+ export function parseSemver(value: string): Semver | undefined {
32
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value.trim());
33
+ if (match === null) return undefined;
34
+ const [, major, minor, patch] = match as unknown as [
35
+ string,
36
+ string,
37
+ string,
38
+ string,
39
+ ];
40
+ return [Number(major), Number(minor), Number(patch)];
41
+ }
42
+
43
+ /**
44
+ * Whether a pack built by `packVersion` predates the CLI at `cliVersion`.
45
+ *
46
+ * Deliberately conservative — it answers `true` only when the pack is PROVABLY
47
+ * older, because answering `true` means overriding a store the user built on
48
+ * purpose:
49
+ *
50
+ * - Equal, or a pack built by a NEWER CLI (a downgrade, or two CLIs sharing one
51
+ * cache): keep the built pack. The user's own build wins, exactly as today.
52
+ * - `cliVersion` unparseable, or `0.0.0` (a dev build, whose version says
53
+ * nothing about how its snapshot compares): keep the built pack, and say
54
+ * nothing about it — a local checkout must not start reporting every pack on
55
+ * the machine as stale.
56
+ * - `packVersion` unparseable: keep the built pack. The notice a user reads
57
+ * names the version that built the pack, so a pack we cannot name is one we
58
+ * cannot honestly call old. (No released CLI writes such a manifest —
59
+ * `version` comes from `package.json` — and a manifest MISSING the field is
60
+ * already not a readable pack at all: `validateManifest` requires it, so the
61
+ * directory fails the completeness gate and boot reports the ordinary
62
+ * "not built" recovery.)
63
+ *
64
+ * @param packVersion - The manifest's `version` (the CLI that built the pack).
65
+ * @param cliVersion - The running CLI's version.
66
+ * @returns Whether the pack should yield to the running CLI's snapshot.
67
+ */
68
+ export function packIsOlderThanCli(
69
+ packVersion: string,
70
+ cliVersion: string,
71
+ ): boolean {
72
+ const cli = parseSemver(cliVersion);
73
+ if (cli === undefined || (cli[0] === 0 && cli[1] === 0 && cli[2] === 0)) {
74
+ return false;
75
+ }
76
+ const pack = parseSemver(packVersion);
77
+ if (pack === undefined) return false;
78
+ for (let index = 0; index < 3; index++) {
79
+ const packPart = pack[index] as number;
80
+ const cliPart = cli[index] as number;
81
+ if (packPart !== cliPart) return packPart < cliPart;
82
+ }
83
+ return false;
84
+ }
@@ -54,7 +54,7 @@
54
54
  */
55
55
 
56
56
  import type { DetailLevel } from "../../constants.js";
57
- import { cliRecovery, PragmaError } from "../error/index.js";
57
+ import { callRecovery, PragmaError } from "../error/index.js";
58
58
  import { resolveUri } from "../packs/iri.js";
59
59
  import { compactUri, resolveDetail } from "../render/index.js";
60
60
  import type { PackIndex } from "./graphpack/types.js";
@@ -110,6 +110,18 @@ const SAMPLE_CAP: Readonly<Record<DetailLevel, number>> = {
110
110
  detailed: 5,
111
111
  };
112
112
 
113
+ /**
114
+ * How many OBJECTS of one outbound predicate each level lists; the true total
115
+ * rides {@link PredicateGroup.count}. `summary` keeps a few rather than none —
116
+ * a predicate's first objects are the entity's type, name and tier. Derivation
117
+ * in BUDGETS.md.
118
+ */
119
+ const OUTBOUND_CAP: Readonly<Record<DetailLevel, number>> = {
120
+ summary: 3,
121
+ standard: 10,
122
+ detailed: Number.POSITIVE_INFINITY,
123
+ };
124
+
113
125
  /**
114
126
  * How many characters of a LITERAL each level carries.
115
127
  *
@@ -182,7 +194,12 @@ export type NestedRecord = Record<string, ReadTerm>;
182
194
  /** All objects asserted for one predicate on the subject. */
183
195
  export interface PredicateGroup {
184
196
  readonly predicate: ReadTerm;
197
+ /** The objects — all of them, or the head when {@link truncated}. */
185
198
  readonly objects: ReadTerm[];
199
+ /** Set when `objects` is shorter than the predicate's true fan-out. */
200
+ readonly truncated?: true;
201
+ /** The TRUE number of objects, present only when truncated. */
202
+ readonly count?: number;
186
203
  }
187
204
 
188
205
  /** Everything asserting one predicate ABOUT the subject. */
@@ -348,10 +365,12 @@ export async function readEntity(
348
365
 
349
366
  if (outbound.type !== "select" || outbound.termBindings.length === 0) {
350
367
  throw PragmaError.notFound("entity", uri, {
351
- recovery: cliRecovery(
352
- `graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'`,
368
+ recovery: callRecovery(
369
+ {
370
+ verb: "graph query",
371
+ params: { sparql: "SELECT ?s WHERE { ?s ?p ?o } LIMIT 10" },
372
+ },
353
373
  "Check the URI, or list known entities.",
354
- { tool: "graph_query" },
355
374
  ),
356
375
  });
357
376
  }
@@ -381,11 +400,17 @@ export async function readEntity(
381
400
  objects.push(term(object));
382
401
  groupMap.set(predicate.value, objects);
383
402
  }
403
+ // Cut from the rows already in hand: the count is the group's own length, so
404
+ // bounding the answer costs no second query.
405
+ const cap = OUTBOUND_CAP[detail];
384
406
  const groups: PredicateGroup[] = [...groupMap.entries()].map(
385
407
  ([predicate, objects]) => ({
386
408
  // Every key was written from a binding above, so the lookup cannot miss.
387
409
  predicate: predicates.get(predicate) as ReadTerm,
388
- objects,
410
+ objects: objects.slice(0, cap),
411
+ ...(objects.length > cap
412
+ ? { truncated: true as const, count: objects.length }
413
+ : {}),
389
414
  }),
390
415
  );
391
416
 
@@ -393,7 +418,10 @@ export async function readEntity(
393
418
  const nested =
394
419
  detail === "summary" || !hasBlankObject
395
420
  ? {}
396
- : await readNested(rt, resolved, session.prefixes, term);
421
+ : capNested(
422
+ await readNested(rt, resolved, session.prefixes, term),
423
+ groups,
424
+ );
397
425
 
398
426
  const prefixed = compactUri(resolved, session.prefixes);
399
427
  const label = session.index.entities.find((e) => e.uri === resolved)?.label;
@@ -409,6 +437,33 @@ export async function readEntity(
409
437
  };
410
438
  }
411
439
 
440
+ /**
441
+ * Hold the inlined records to the objects their predicate kept.
442
+ *
443
+ * A record IS one of its predicate's objects, written out. Listing ten of a
444
+ * predicate's 24 blank objects beside all 24 of their records would make the
445
+ * cap a claim about the group and not about the answer.
446
+ */
447
+ function capNested(
448
+ nested: Record<string, NestedRecord[]>,
449
+ groups: readonly PredicateGroup[],
450
+ ): Record<string, NestedRecord[]> {
451
+ const kept = new Map(
452
+ groups
453
+ .filter((group) => group.truncated)
454
+ .map((group) => [
455
+ group.predicate.prefixed ?? group.predicate.value,
456
+ group.objects.filter((object) => object.termType === "BlankNode")
457
+ .length,
458
+ ]),
459
+ );
460
+ return Object.fromEntries(
461
+ Object.entries(nested)
462
+ .map(([via, rows]) => [via, rows.slice(0, kept.get(via))] as const)
463
+ .filter(([, rows]) => rows.length > 0),
464
+ );
465
+ }
466
+
412
467
  /**
413
468
  * Everything asserting a predicate about the subject, grouped and capped.
414
469
  *
@@ -2,10 +2,12 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { VERSION } from "../../constants.js";
5
6
  import type { ConfigLayers } from "../config/types.js";
6
7
  import { loadStoreSession } from "./loadSession.js";
8
+ import { parseSemver } from "./packVersion.js";
7
9
  import { activePackPath, packDir } from "./paths.js";
8
- import { resolveSources } from "./resolveSources.js";
10
+ import { describeIgnoredPack, resolveSources } from "./resolveSources.js";
9
11
 
10
12
  /**
11
13
  * Pin the store-boot decision table (`resolveSources`) — especially the two
@@ -41,33 +43,52 @@ function layersWith(packsOrigin: "default" | "project"): ConfigLayers {
41
43
  };
42
44
  }
43
45
 
44
- /** Write a pack's manifest.json at the given hash (the completeness marker). */
45
- function writeManifest(dir: string, hash: string): void {
46
+ /**
47
+ * Write a pack's manifest.json at the given hash (the completeness marker).
48
+ *
49
+ * `version` is the CLI version that built the pack — the field the upgrade row
50
+ * of the decision table compares. The default is deliberately NOT a version:
51
+ * every case that predates the upgrade row keeps the built pack whatever CLI is
52
+ * running, so those cases stay about what they were about.
53
+ */
54
+ function writeManifest(dir: string, hash: string, version = "0"): void {
46
55
  writeFileSync(
47
56
  join(dir, "manifest.json"),
48
57
  JSON.stringify({
49
58
  name: "t",
50
- version: "0",
59
+ version,
51
60
  sourceRef: "t",
52
61
  contentHash: hash,
53
62
  prefixes: {},
54
- createdAt: new Date().toISOString(),
63
+ createdAt: BUILT_AT,
55
64
  }),
56
65
  );
57
66
  }
58
67
 
68
+ /** The `createdAt` every fixture pack records — a fixed day, for the sentence. */
69
+ const BUILT_AT = "2026-09-10T21:49:00.411Z";
70
+
59
71
  /** Materialize a COMPLETE pack — manifest + non-empty dump, schema, and index. */
60
- function writeCompletePack(hash: string): string {
72
+ function writeCompletePack(hash: string, version?: string): string {
61
73
  const dir = packDir(hash);
62
74
  mkdirSync(dir, { recursive: true });
63
75
  writeFileSync(join(dir, "data.nq"), "<urn:s> <urn:p> <urn:o> .\n");
64
76
  writeFileSync(join(dir, "schema.json"), "{}");
65
77
  writeFileSync(join(dir, "index.json"), "{}");
66
78
  writeFileSync(join(dir, "stories.json"), "[]");
67
- writeManifest(dir, hash);
79
+ writeManifest(dir, hash, version);
68
80
  return dir;
69
81
  }
70
82
 
83
+ /** A version one minor release below the running CLI, and one above it. */
84
+ function neighbouringVersions(): { older: string; newer: string } {
85
+ const [major, minor] = parseSemver(VERSION) as [number, number, number];
86
+ return {
87
+ older: minor > 0 ? `${major}.${minor - 1}.0` : `${major - 1}.0.0`,
88
+ newer: `${major}.${minor + 1}.0`,
89
+ };
90
+ }
91
+
71
92
  /** Plant the active-pack pointer a `sources update` would have written. */
72
93
  function writePointer(cwd: string, content: string): void {
73
94
  const path = activePackPath(cwd);
@@ -175,6 +196,68 @@ describe("resolveSources decision table", () => {
175
196
  });
176
197
  });
177
198
 
199
+ describe("resolveSources — a pack an older CLI built (the upgrade row)", () => {
200
+ it("yields to the embedded snapshot, and names the pack it passed over", () => {
201
+ // The reported bug: a pack built weeks ago by an older CLI answered every
202
+ // read from the graph the upgrade replaced, and nothing said so.
203
+ const cwd = tmp();
204
+ const hash = "e".repeat(64);
205
+ const { older } = neighbouringVersions();
206
+ writeCompletePack(hash, older);
207
+ writePointer(cwd, hash);
208
+
209
+ expect(resolveSources(layersWith("default"), cwd)).toEqual({
210
+ kind: "embedded",
211
+ ignoredPack: { contentHash: hash, builtBy: older, builtAt: BUILT_AT },
212
+ });
213
+ });
214
+
215
+ it("keeps the built pack when the project declares its OWN packs", () => {
216
+ // A project with its own packs has its own graph, and a snapshot of the
217
+ // distribution's is not a substitute for it, stale or not. Byte-identical
218
+ // to the pre-existing pack row.
219
+ const cwd = tmp();
220
+ const hash = "f".repeat(64);
221
+ const { older } = neighbouringVersions();
222
+ const dir = writeCompletePack(hash, older);
223
+ writePointer(cwd, hash);
224
+
225
+ expect(resolveSources(layersWith("project"), cwd)).toEqual({
226
+ kind: "pack",
227
+ dir,
228
+ contentHash: hash,
229
+ });
230
+ });
231
+
232
+ it("keeps a pack this CLI, or a newer one, built", () => {
233
+ const { newer } = neighbouringVersions();
234
+ for (const version of [VERSION, newer]) {
235
+ const cwd = tmp();
236
+ const hash = version === VERSION ? "1".repeat(64) : "2".repeat(64);
237
+ const dir = writeCompletePack(hash, version);
238
+ writePointer(cwd, hash);
239
+
240
+ expect(resolveSources(layersWith("default"), cwd)).toEqual({
241
+ kind: "pack",
242
+ dir,
243
+ contentHash: hash,
244
+ });
245
+ }
246
+ });
247
+
248
+ it("names the version, the day, and both ways out", () => {
249
+ expect(
250
+ describeIgnoredPack({
251
+ contentHash: "0e82d35c6668",
252
+ builtBy: "0.37.0",
253
+ builtAt: BUILT_AT,
254
+ }),
255
+ ).toBe(
256
+ "a pack built by pragma 0.37.0 on 2026-09-10 is ignored — run `pragma sources update` to rebuild it or `pragma sources reset` to remove it",
257
+ );
258
+ });
259
+ });
260
+
178
261
  describe("loadStoreSession recovery", () => {
179
262
  it("surfaces the `pragma sources update` recovery (CLI + MCP tool) when cold", async () => {
180
263
  const cwd = tmp();
@@ -8,27 +8,59 @@
8
8
  * says so, and it answers with completion candidates, never content.
9
9
  *
10
10
  * Boot never touches the network. From the project's active-pack pointer (the
11
- * content hash `sources update` last built here) and the resolved config:
11
+ * content hash `sources update` last built here), the pack's recorded builder
12
+ * version, and the resolved config:
12
13
  *
13
- * | pointer | pack complete | packs origin | → decision |
14
- * |---------|---------------|--------------|-----------------------|
15
- * | yes | yes | — | load the built pack |
16
- * | yes | no | — | STORE_UNAVAILABLE |
17
- * | no | — | default | embedded fallback |
18
- * | no | — | configured | STORE_UNAVAILABLE |
14
+ * | pointer | pack complete | built by | packs origin | → decision |
15
+ * |---------|---------------|----------------|--------------|---------------------|
16
+ * | yes | yes | this CLI/newer | — | load the built pack |
17
+ * | yes | yes | an older CLI | configured | load the built pack |
18
+ * | yes | yes | an older CLI | default | embedded, pack named|
19
+ * | yes | no | — | — | STORE_UNAVAILABLE |
20
+ * | no | — | — | default | embedded fallback |
21
+ * | no | — | — | configured | STORE_UNAVAILABLE |
19
22
  *
20
23
  * A configured-but-unbuilt store (or a pointer whose pack the cache lost)
21
24
  * surfaces STORE_UNAVAILABLE with a single recovery: `pragma sources update`.
22
25
  * "packs origin default" means the user has not pinned their own packs —
23
26
  * a fresh install — so the embedded pack answers reads offline. A project that
24
27
  * DID configure its own packs is never quietly served the distribution's graph.
28
+ *
29
+ * The third row is the UPGRADE row, and it is why the pack's builder version is
30
+ * part of the decision. A CLI upgrade ships a NEW embedded snapshot; a pack some
31
+ * older CLI built in this directory weeks ago then answered every read from the
32
+ * graph the upgrade replaced — silently, with `sources status` reporting a
33
+ * healthy `built` store. So a pack built by an older CLI, in a project that
34
+ * declares no packs of its own, YIELDS: the snapshot answers and the pack is
35
+ * named as ignored ({@link IgnoredPack}) on every surface that reports on the
36
+ * store, until `sources update` rebuilds it or `sources reset` removes it. The
37
+ * second row is the other half of the same rule: a project with its own packs
38
+ * has its own graph, and a snapshot of the distribution's is not a substitute
39
+ * for it, stale or not — that project keeps today's behaviour exactly.
25
40
  */
26
41
 
27
42
  import { existsSync } from "node:fs";
43
+ import { BIN_NAME, VERSION } from "../../constants.js";
28
44
  import type { ConfigLayers } from "../config/types.js";
29
- import { packIsComplete } from "./graphpack/manifest.js";
45
+ import { packArtifactsPresent, readManifest } from "./graphpack/manifest.js";
46
+ import { packIsOlderThanCli } from "./packVersion.js";
30
47
  import { packDir, readActivePack } from "./paths.js";
31
48
 
49
+ /**
50
+ * A built pack the boot decided to pass over: it was built by an older CLI, and
51
+ * the project declares no packs of its own, so the CLI's own snapshot answers
52
+ * instead. Carried on the decision (and onward on the booted session) so every
53
+ * surface that reports on the store can name the pack it is NOT reading.
54
+ */
55
+ export interface IgnoredPack {
56
+ /** The passed-over pack's content hash (its cache directory name). */
57
+ readonly contentHash: string;
58
+ /** The CLI version that built it (its manifest `version`). */
59
+ readonly builtBy: string;
60
+ /** When it was built (its manifest `createdAt`). */
61
+ readonly builtAt: string;
62
+ }
63
+
32
64
  /** The resolved boot strategy. */
33
65
  export type SourcesDecision =
34
66
  | {
@@ -36,9 +68,37 @@ export type SourcesDecision =
36
68
  readonly dir: string;
37
69
  readonly contentHash: string;
38
70
  }
39
- | { readonly kind: "embedded" }
71
+ | {
72
+ readonly kind: "embedded";
73
+ /**
74
+ * Present only on the upgrade row: the built pack this project has, which
75
+ * an older CLI built and which the snapshot is answering in place of.
76
+ * Absent for an ordinary fresh install, which has no pack at all.
77
+ */
78
+ readonly ignoredPack?: IgnoredPack;
79
+ }
40
80
  | { readonly kind: "unavailable"; readonly reason: string };
41
81
 
82
+ /**
83
+ * The one sentence every surface uses for a passed-over pack.
84
+ *
85
+ * ONE wording, in one place, because the three surfaces that say it
86
+ * (`sources status`, `doctor`'s pack refs row, and the recovery text a reader
87
+ * lands on) must not each invent their own account of the same state. It names
88
+ * the version and the day — the two facts that make the situation legible — and
89
+ * both ways out, because they are genuinely different choices: rebuild if you
90
+ * want your own packs back, reset if you want the shipped snapshot.
91
+ *
92
+ * @param ignored - The passed-over pack.
93
+ * @returns The sentence, without a trailing full stop.
94
+ */
95
+ export function describeIgnoredPack(ignored: IgnoredPack): string {
96
+ return (
97
+ `a pack built by ${BIN_NAME} ${ignored.builtBy} on ${ignored.builtAt.slice(0, 10)} is ignored — ` +
98
+ `run \`${BIN_NAME} sources update\` to rebuild it or \`${BIN_NAME} sources reset\` to remove it`
99
+ );
100
+ }
101
+
42
102
  /**
43
103
  * Decide how (or whether) to boot the store — without any network or store I/O.
44
104
  *
@@ -54,7 +114,28 @@ export function resolveSources(
54
114
  const contentHash = readActivePack(cwd);
55
115
  if (contentHash !== undefined) {
56
116
  const dir = packDir(contentHash);
57
- if (packIsComplete(dir)) return { kind: "pack", dir, contentHash };
117
+ // The manifest is read ONCE and used twice — for completeness (the half of
118
+ // `packIsComplete` that is not the manifest itself) and for the builder
119
+ // version. The boot decision is evaluated on the storeless fast path, so
120
+ // asking the same ~1 KB file for the version separately would be a second
121
+ // read for a field the first read already had in hand.
122
+ const manifest = readManifest(dir);
123
+ if (manifest !== undefined && packArtifactsPresent(dir)) {
124
+ if (
125
+ layers.origins.packs === "default" &&
126
+ packIsOlderThanCli(manifest.version, VERSION)
127
+ ) {
128
+ return {
129
+ kind: "embedded",
130
+ ignoredPack: {
131
+ contentHash,
132
+ builtBy: manifest.version,
133
+ builtAt: manifest.createdAt,
134
+ },
135
+ };
136
+ }
137
+ return { kind: "pack", dir, contentHash };
138
+ }
58
139
  // Two different things, and the recovery text is the only thing a user has
59
140
  // to go on: the cache evicted the pack, or the directory is there but does
60
141
  // not hold a whole pack — the state EVERY install upgraded past the
@@ -18,7 +18,8 @@
18
18
  */
19
19
 
20
20
  import type { ConfigLayers } from "../config/types.js";
21
- import { cliRecovery, PragmaError } from "../error/index.js";
21
+ import { callRecovery, PragmaError } from "../error/index.js";
22
+ import { BUILD_STORE_CALL } from "../spec/call.js";
22
23
  import { resolveSources } from "./resolveSources.js";
23
24
 
24
25
  /**
@@ -32,10 +33,9 @@ import { resolveSources } from "./resolveSources.js";
32
33
  */
33
34
  export function storeUnavailable(reason: string): PragmaError {
34
35
  return PragmaError.storeUnavailable(`${reason}.`, {
35
- recovery: cliRecovery(
36
- "sources update",
36
+ recovery: callRecovery(
37
+ BUILD_STORE_CALL,
37
38
  "Build the local store from the configured packs.",
38
- { tool: "sources_update" },
39
39
  ),
40
40
  });
41
41
  }
@@ -67,6 +67,18 @@ export interface StoreSession {
67
67
  * have changed since.
68
68
  */
69
69
  readonly manifest: import("./graphpack/types.js").Manifest;
70
+ /**
71
+ * The project's OWN built pack, when the boot passed over it because an older
72
+ * CLI built it (see `resolveSources`' upgrade row). Present only on a session
73
+ * booted from the embedded snapshot in a project that has a pack; absent on
74
+ * every ordinary boot.
75
+ *
76
+ * Carried for the same reason `manifest` is: a read that was answered from
77
+ * the snapshot instead of the pack this directory built has to be able to say
78
+ * so, from the session it was answered from, without re-resolving a decision
79
+ * the caller could have changed since.
80
+ */
81
+ readonly ignoredPack?: import("./resolveSources.js").IgnoredPack;
70
82
  }
71
83
 
72
84
  /**