@reticlehq/server 2.12.0 → 2.13.1

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 (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +68 -0
  253. package/dist/setup/dev-server-wait.js +67 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +203 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Reading and writing the sharded intent store, and migrating the flat one into it.
3
+ *
4
+ * The operations an agent actually performs, and what each is allowed to cost:
5
+ *
6
+ * index() every id + one line, always affordable — the session-start read
7
+ * subject(name) the full records for one subject, when it is about to work on that
8
+ * get(id) one record, via the index rather than by scanning shards
9
+ * record()/update() a write that touches ONE shard file plus the index
10
+ *
11
+ * The last one is the reason for the whole design. Under the flat file, changing one intent rewrote
12
+ * all 141, so two sessions touching unrelated subjects raced each other and the loser's write
13
+ * vanished. Sharded, they only collide when they genuinely edit the same subject.
14
+ *
15
+ * ## Migration is automatic, and it does not delete
16
+ *
17
+ * `.reticle/intent.json` is read on every load and folded in when the sharded copy has nothing for
18
+ * an id. That makes the move invisible: an agent on the new store sees old intents immediately, and
19
+ * an older build still reading the flat file keeps working, because nothing removes it. Deleting it
20
+ * is a separate decision somebody can take once they believe the migration.
21
+ */
22
+ import { parseIntentFile } from '@reticlehq/core';
23
+ import { reticleDirPaths } from '../project/reticle-dir.js';
24
+ import { withFileLock } from '../project/file-lock.js';
25
+ import { subjectFor } from './intent-subject.js';
26
+ import { emptyShard, indexFrom, IntentShardSchema, IntentStatus, recordFromIntent, serialise, shardsFrom, readJsonFile, } from './intent-shard.js';
27
+ const INTENT_DIR = 'intent';
28
+ const INDEX_FILE = 'index.json';
29
+ const SHARD_SUFFIX = '.json';
30
+ export class IntentShardStore {
31
+ #fs;
32
+ #root;
33
+ #clock;
34
+ constructor(fs, root, clock) {
35
+ this.#fs = fs;
36
+ this.#root = root;
37
+ this.#clock = clock;
38
+ }
39
+ #dir() {
40
+ return `${reticleDirPaths(this.#root).root}/${INTENT_DIR}`;
41
+ }
42
+ #indexPath() {
43
+ return `${this.#dir()}/${INDEX_FILE}`;
44
+ }
45
+ #shardPath(subject) {
46
+ return `${this.#dir()}/${subject}${SHARD_SUFFIX}`;
47
+ }
48
+ /** Legacy records, keyed by id. Empty when there is no flat file, which is the eventual case. */
49
+ async #legacy() {
50
+ const file = await readJsonFile(this.#fs, reticleDirPaths(this.#root).intent, (raw) => parseIntentFile(raw), { version: 1, intents: {} });
51
+ return new Map(Object.values(file.intents).map((i) => [i.id, recordFromIntent(i)]));
52
+ }
53
+ async #readShard(subject) {
54
+ return readJsonFile(this.#fs, this.#shardPath(subject), (raw) => IntentShardSchema.parse(raw), emptyShard(subject));
55
+ }
56
+ /**
57
+ * Which subjects exist, read from the DIRECTORY rather than the index.
58
+ *
59
+ * The index cannot be the source here. It is derived from the shards, so discovering shards
60
+ * through it is circular — and the circle is not theoretical: a brand-new subject was invisible to
61
+ * the very write that created it, because its shard existed on disk while the index did not
62
+ * mention it yet. Listing the directory also makes the index self-healing: delete it and the next
63
+ * read rebuilds it from what is actually there.
64
+ */
65
+ async #subjectsOnDisk() {
66
+ try {
67
+ const entries = await this.#fs.readdir(this.#dir());
68
+ return entries
69
+ .filter((e) => e.endsWith(SHARD_SUFFIX) && INDEX_FILE !== e)
70
+ .map((e) => e.slice(0, -SHARD_SUFFIX.length));
71
+ }
72
+ catch {
73
+ // No directory yet — an unmigrated project, which the legacy fold below still answers for.
74
+ return [];
75
+ }
76
+ }
77
+ /**
78
+ * Every record: the shards, plus any legacy intent the shards have not absorbed.
79
+ *
80
+ * Sharded wins on a collision. An id present in both has been migrated and possibly edited since,
81
+ * and letting the flat copy overwrite that would silently revert the edit.
82
+ */
83
+ async #all() {
84
+ const shards = await Promise.all((await this.#subjectsOnDisk()).map((s) => this.#readShard(s)));
85
+ const merged = new Map();
86
+ for (const [id, record] of await this.#legacy())
87
+ merged.set(id, record);
88
+ for (const shard of shards) {
89
+ for (const record of Object.values(shard.intents))
90
+ merged.set(record.id, record);
91
+ }
92
+ return [...merged.values()];
93
+ }
94
+ /**
95
+ * The cheap read: one line per intent, and the call an agent makes at the start of a session.
96
+ *
97
+ * Includes anything still only in the legacy flat file, so the very first call on an unmigrated
98
+ * project already answers completely — which is what makes the migration invisible rather than a
99
+ * step somebody has to remember to run.
100
+ */
101
+ async index() {
102
+ // Derived from what is on disk, never from the stored copy. The stored index is a cache for
103
+ // readers outside this process; inside it, trusting it would let a stale one hide a real record.
104
+ return indexFrom(shardsFrom(await this.#all()));
105
+ }
106
+ /** Records already in shards, ignoring the legacy file — what migration diffs against. */
107
+ async #storedRecords() {
108
+ const shards = await Promise.all((await this.#subjectsOnDisk()).map((s) => this.#readShard(s)));
109
+ return shards.flatMap((s) => Object.values(s.intents));
110
+ }
111
+ /** Every record for one subject — the working read, once an agent knows what it is touching. */
112
+ async subject(name) {
113
+ const all = await this.#all();
114
+ return all.filter((r) => name === r.subject);
115
+ }
116
+ /** One record by id, or null. */
117
+ async get(id) {
118
+ const all = await this.#all();
119
+ return all.find((r) => id === r.id) ?? null;
120
+ }
121
+ /**
122
+ * Write one intent, creating or updating it.
123
+ *
124
+ * Merges onto whatever is already stored, so a caller adding a `why` to a migrated record does not
125
+ * have to restate its binding — and cannot silently drop it by omission, which is the failure mode
126
+ * that makes agents afraid to touch a store.
127
+ */
128
+ async record(input) {
129
+ return withFileLock(this.#indexPath(), async () => {
130
+ const existing = await this.get(input.id);
131
+ const now = this.#clock.now();
132
+ const subject = input.subject ??
133
+ existing?.subject ??
134
+ subjectFor({ surface: input.surface, binding: input.binding });
135
+ const next = {
136
+ ...(existing ?? {
137
+ id: input.id,
138
+ statement: input.statement,
139
+ state: 'declared',
140
+ declaredAt: now,
141
+ subject,
142
+ status: IntentStatus.PROPOSED,
143
+ }),
144
+ statement: input.statement,
145
+ subject,
146
+ status: input.status ?? existing?.status ?? IntentStatus.PROPOSED,
147
+ updatedAt: now,
148
+ ...(input.why === undefined ? {} : { why: input.why }),
149
+ ...(input.source === undefined ? {} : { source: input.source }),
150
+ ...(input.surface === undefined ? {} : { surface: input.surface }),
151
+ ...(input.binding === undefined ? {} : { binding: input.binding }),
152
+ };
153
+ await this.#write(next, existing?.subject);
154
+ return next;
155
+ });
156
+ }
157
+ /** Persist one record, rewriting its shard and the index — and the OLD shard if it moved. */
158
+ async #write(record, previousSubject) {
159
+ await this.#fs.mkdir(this.#dir());
160
+ if (previousSubject !== undefined && record.subject !== previousSubject) {
161
+ const old = await this.#readShard(previousSubject);
162
+ if (old.intents[record.id] !== undefined) {
163
+ const { [record.id]: _moved, ...rest } = old.intents;
164
+ await this.#fs.writeFile(this.#shardPath(previousSubject), serialise({ ...old, intents: rest }));
165
+ }
166
+ }
167
+ const shard = await this.#readShard(record.subject);
168
+ shard.intents[record.id] = record;
169
+ await this.#fs.writeFile(this.#shardPath(record.subject), serialise(shard));
170
+ // The index is DERIVED, never edited in place: two sources of truth would disagree, and the one
171
+ // that is cheap to read is the one people would trust.
172
+ const all = await this.#all();
173
+ await this.#fs.writeFile(this.#indexPath(), serialise(indexFrom(shardsFrom(all))));
174
+ }
175
+ /**
176
+ * Fold the flat file into shards on disk, so the move is visible in a diff rather than implicit.
177
+ *
178
+ * Idempotent, and it does not delete `.reticle/intent.json`: an older build still reads it, and a
179
+ * migration that removes its own source cannot be checked afterwards.
180
+ */
181
+ async migrate() {
182
+ return withFileLock(this.#indexPath(), async () => {
183
+ const legacy = await this.#legacy();
184
+ if (0 === legacy.size)
185
+ return { migrated: 0, subjects: [] };
186
+ const stored = await this.#storedRecords();
187
+ const known = new Set(stored.map((r) => r.id));
188
+ const incoming = [...legacy.values()].filter((r) => !known.has(r.id));
189
+ if (0 === incoming.length)
190
+ return { migrated: 0, subjects: [] };
191
+ const shards = shardsFrom([...stored, ...incoming]);
192
+ await this.#fs.mkdir(this.#dir());
193
+ for (const shard of shards) {
194
+ await this.#fs.writeFile(this.#shardPath(shard.subject), serialise(shard));
195
+ }
196
+ await this.#fs.writeFile(this.#indexPath(), serialise(indexFrom(shards)));
197
+ return {
198
+ migrated: incoming.length,
199
+ subjects: [...new Set(incoming.map((r) => r.subject))].sort(),
200
+ };
201
+ });
202
+ }
203
+ }
@@ -0,0 +1,411 @@
1
+ /**
2
+ * The sharded intent store: one small file per subject, plus an index cheap enough to always load.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * `.reticle/intent.json` was a single object that reached 141 entries and 109KB. Parsing that is
7
+ * nothing; READING it is the cost, and reading is what this store is for. An agent wanting the two
8
+ * rules about checkout pulled 139 irrelevant ones through its context to find them, and an agent
9
+ * changing one rewrote the whole file — which is both slow to review and a lost write whenever two
10
+ * sessions touch different subjects at once.
11
+ *
12
+ * ## The shape
13
+ *
14
+ * .reticle/intent/index.json every id, its subject and one line of prose
15
+ * .reticle/intent/<subject>.json the full records for that subject
16
+ *
17
+ * The index is the whole point: it answers "what do we know, and where does it live?" without
18
+ * opening anything, and detail is then fetched for the one subject in play. Measured on the real
19
+ * corpus, that is 109KB all-or-nothing becoming an index plus one shard — and the largest shard is
20
+ * under 10KB once the unsorted bucket is set aside.
21
+ *
22
+ * The index is NOT tiny, and measuring beat guessing twice over. With full statements it came to
23
+ * 36KB for 141 entries; summarising them saved 7%, because the bulk is not the prose but the ids —
24
+ * `inline:signing-in-with-the-email-password-created-earli-6ca804e9` is 63 characters before the
25
+ * statement starts. So the honest numbers are: 109KB all-or-nothing becomes a 33KB index plus one
26
+ * ~10KB shard, and the real win is that a READ and a WRITE are both bounded to one subject rather
27
+ * than the whole corpus.
28
+ *
29
+ * The index is therefore the part that does not scale forever. At ten times this size it needs a
30
+ * second tier — subjects and counts, with ids fetched per subject — rather than a longer single
31
+ * read. Recorded here because the next person will hit it, and the shape of the fix is already
32
+ * visible.
33
+ *
34
+ * ## What the records carry that the old ones did not
35
+ *
36
+ * The legacy file was written BY verification, AT verification time, in verification's vocabulary: a
37
+ * statement, a predicate, and whether a verdict discharged it. Of 141 entries, four named a surface.
38
+ * There was nowhere to put the reason a thing must be true, who decided it, or when — so the
39
+ * business case somebody states once and then forgets had no home and was never captured.
40
+ *
41
+ * `why`, `source` and `subject` are that home. They are optional because a migrated record cannot
42
+ * invent them, and a store that rejected incomplete records would simply not be written to.
43
+ */
44
+ import { z } from 'zod';
45
+ import { type Intent } from '@reticlehq/core';
46
+ import type { FileSystemPort } from '../project/fs-port.js';
47
+ export declare const INTENT_SHARD_VERSION = 1;
48
+ /**
49
+ * How settled a record is.
50
+ *
51
+ * Separate from the legacy `state`, which only ever described the VERIFICATION lifecycle
52
+ * (declared → bound → proved). A rule can be agreed with the customer months before anything can
53
+ * prove it, and collapsing those two axes is what made the old file an assertion log rather than a
54
+ * memory.
55
+ */
56
+ export declare const IntentStatus: {
57
+ /** Captured, not yet confirmed by anyone. What an agent writes while building. */
58
+ readonly PROPOSED: "proposed";
59
+ /** Confirmed as something that must hold. May have no way to prove it yet. */
60
+ readonly AGREED: "agreed";
61
+ /** A verdict has actually shown it holds. */
62
+ readonly PROVED: "proved";
63
+ /** Believed out of date — kept, because deleting the record loses the fact it was ever true. */
64
+ readonly STALE: "stale";
65
+ };
66
+ export type IntentStatus = (typeof IntentStatus)[keyof typeof IntentStatus];
67
+ export declare const IntentRecordSchema: z.ZodObject<{
68
+ id: z.ZodString;
69
+ statement: z.ZodString;
70
+ state: z.ZodEnum<["declared", "bound", "proved"]>;
71
+ declaredAt: z.ZodNumber;
72
+ binding: z.ZodOptional<z.ZodUnknown>;
73
+ surface: z.ZodOptional<z.ZodObject<{
74
+ route: z.ZodOptional<z.ZodString>;
75
+ flow: z.ZodOptional<z.ZodString>;
76
+ files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
77
+ }, "strip", z.ZodTypeAny, {
78
+ flow?: string | undefined;
79
+ route?: string | undefined;
80
+ files?: string[] | undefined;
81
+ }, {
82
+ flow?: string | undefined;
83
+ route?: string | undefined;
84
+ files?: string[] | undefined;
85
+ }>>;
86
+ provenBy: z.ZodOptional<z.ZodObject<{
87
+ verdictId: z.ZodString;
88
+ grade: z.ZodString;
89
+ at: z.ZodNumber;
90
+ }, "strip", z.ZodTypeAny, {
91
+ at: number;
92
+ verdictId: string;
93
+ grade: string;
94
+ }, {
95
+ at: number;
96
+ verdictId: string;
97
+ grade: string;
98
+ }>>;
99
+ amended: z.ZodOptional<z.ZodArray<z.ZodObject<{
100
+ statement: z.ZodString;
101
+ at: z.ZodNumber;
102
+ }, "strip", z.ZodTypeAny, {
103
+ at: number;
104
+ statement: string;
105
+ }, {
106
+ at: number;
107
+ statement: string;
108
+ }>, "many">>;
109
+ } & {
110
+ /** Which shard this lives in. Stored as well as implied, so a file read alone is self-describing. */
111
+ subject: z.ZodString;
112
+ status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
113
+ /** WHY it must be true. The half the old file had no room for. */
114
+ why: z.ZodOptional<z.ZodString>;
115
+ /** Where it came from — "the user, in conversation" beats an anonymous assertion in six months. */
116
+ source: z.ZodOptional<z.ZodString>;
117
+ /** When it was last touched, so a stale-looking record can be told from an untouched one. */
118
+ updatedAt: z.ZodOptional<z.ZodNumber>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ status: "proved" | "stale" | "proposed" | "agreed";
121
+ state: "proved" | "declared" | "bound";
122
+ subject: string;
123
+ id: string;
124
+ statement: string;
125
+ declaredAt: number;
126
+ surface?: {
127
+ flow?: string | undefined;
128
+ route?: string | undefined;
129
+ files?: string[] | undefined;
130
+ } | undefined;
131
+ source?: string | undefined;
132
+ why?: string | undefined;
133
+ updatedAt?: number | undefined;
134
+ binding?: unknown;
135
+ provenBy?: {
136
+ at: number;
137
+ verdictId: string;
138
+ grade: string;
139
+ } | undefined;
140
+ amended?: {
141
+ at: number;
142
+ statement: string;
143
+ }[] | undefined;
144
+ }, {
145
+ status: "proved" | "stale" | "proposed" | "agreed";
146
+ state: "proved" | "declared" | "bound";
147
+ subject: string;
148
+ id: string;
149
+ statement: string;
150
+ declaredAt: number;
151
+ surface?: {
152
+ flow?: string | undefined;
153
+ route?: string | undefined;
154
+ files?: string[] | undefined;
155
+ } | undefined;
156
+ source?: string | undefined;
157
+ why?: string | undefined;
158
+ updatedAt?: number | undefined;
159
+ binding?: unknown;
160
+ provenBy?: {
161
+ at: number;
162
+ verdictId: string;
163
+ grade: string;
164
+ } | undefined;
165
+ amended?: {
166
+ at: number;
167
+ statement: string;
168
+ }[] | undefined;
169
+ }>;
170
+ export type IntentRecord = z.infer<typeof IntentRecordSchema>;
171
+ export declare const IntentShardSchema: z.ZodObject<{
172
+ version: z.ZodLiteral<1>;
173
+ subject: z.ZodString;
174
+ intents: z.ZodRecord<z.ZodString, z.ZodObject<{
175
+ id: z.ZodString;
176
+ statement: z.ZodString;
177
+ state: z.ZodEnum<["declared", "bound", "proved"]>;
178
+ declaredAt: z.ZodNumber;
179
+ binding: z.ZodOptional<z.ZodUnknown>;
180
+ surface: z.ZodOptional<z.ZodObject<{
181
+ route: z.ZodOptional<z.ZodString>;
182
+ flow: z.ZodOptional<z.ZodString>;
183
+ files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
184
+ }, "strip", z.ZodTypeAny, {
185
+ flow?: string | undefined;
186
+ route?: string | undefined;
187
+ files?: string[] | undefined;
188
+ }, {
189
+ flow?: string | undefined;
190
+ route?: string | undefined;
191
+ files?: string[] | undefined;
192
+ }>>;
193
+ provenBy: z.ZodOptional<z.ZodObject<{
194
+ verdictId: z.ZodString;
195
+ grade: z.ZodString;
196
+ at: z.ZodNumber;
197
+ }, "strip", z.ZodTypeAny, {
198
+ at: number;
199
+ verdictId: string;
200
+ grade: string;
201
+ }, {
202
+ at: number;
203
+ verdictId: string;
204
+ grade: string;
205
+ }>>;
206
+ amended: z.ZodOptional<z.ZodArray<z.ZodObject<{
207
+ statement: z.ZodString;
208
+ at: z.ZodNumber;
209
+ }, "strip", z.ZodTypeAny, {
210
+ at: number;
211
+ statement: string;
212
+ }, {
213
+ at: number;
214
+ statement: string;
215
+ }>, "many">>;
216
+ } & {
217
+ /** Which shard this lives in. Stored as well as implied, so a file read alone is self-describing. */
218
+ subject: z.ZodString;
219
+ status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
220
+ /** WHY it must be true. The half the old file had no room for. */
221
+ why: z.ZodOptional<z.ZodString>;
222
+ /** Where it came from — "the user, in conversation" beats an anonymous assertion in six months. */
223
+ source: z.ZodOptional<z.ZodString>;
224
+ /** When it was last touched, so a stale-looking record can be told from an untouched one. */
225
+ updatedAt: z.ZodOptional<z.ZodNumber>;
226
+ }, "strip", z.ZodTypeAny, {
227
+ status: "proved" | "stale" | "proposed" | "agreed";
228
+ state: "proved" | "declared" | "bound";
229
+ subject: string;
230
+ id: string;
231
+ statement: string;
232
+ declaredAt: number;
233
+ surface?: {
234
+ flow?: string | undefined;
235
+ route?: string | undefined;
236
+ files?: string[] | undefined;
237
+ } | undefined;
238
+ source?: string | undefined;
239
+ why?: string | undefined;
240
+ updatedAt?: number | undefined;
241
+ binding?: unknown;
242
+ provenBy?: {
243
+ at: number;
244
+ verdictId: string;
245
+ grade: string;
246
+ } | undefined;
247
+ amended?: {
248
+ at: number;
249
+ statement: string;
250
+ }[] | undefined;
251
+ }, {
252
+ status: "proved" | "stale" | "proposed" | "agreed";
253
+ state: "proved" | "declared" | "bound";
254
+ subject: string;
255
+ id: string;
256
+ statement: string;
257
+ declaredAt: number;
258
+ surface?: {
259
+ flow?: string | undefined;
260
+ route?: string | undefined;
261
+ files?: string[] | undefined;
262
+ } | undefined;
263
+ source?: string | undefined;
264
+ why?: string | undefined;
265
+ updatedAt?: number | undefined;
266
+ binding?: unknown;
267
+ provenBy?: {
268
+ at: number;
269
+ verdictId: string;
270
+ grade: string;
271
+ } | undefined;
272
+ amended?: {
273
+ at: number;
274
+ statement: string;
275
+ }[] | undefined;
276
+ }>>;
277
+ }, "strip", z.ZodTypeAny, {
278
+ subject: string;
279
+ intents: Record<string, {
280
+ status: "proved" | "stale" | "proposed" | "agreed";
281
+ state: "proved" | "declared" | "bound";
282
+ subject: string;
283
+ id: string;
284
+ statement: string;
285
+ declaredAt: number;
286
+ surface?: {
287
+ flow?: string | undefined;
288
+ route?: string | undefined;
289
+ files?: string[] | undefined;
290
+ } | undefined;
291
+ source?: string | undefined;
292
+ why?: string | undefined;
293
+ updatedAt?: number | undefined;
294
+ binding?: unknown;
295
+ provenBy?: {
296
+ at: number;
297
+ verdictId: string;
298
+ grade: string;
299
+ } | undefined;
300
+ amended?: {
301
+ at: number;
302
+ statement: string;
303
+ }[] | undefined;
304
+ }>;
305
+ version: 1;
306
+ }, {
307
+ subject: string;
308
+ intents: Record<string, {
309
+ status: "proved" | "stale" | "proposed" | "agreed";
310
+ state: "proved" | "declared" | "bound";
311
+ subject: string;
312
+ id: string;
313
+ statement: string;
314
+ declaredAt: number;
315
+ surface?: {
316
+ flow?: string | undefined;
317
+ route?: string | undefined;
318
+ files?: string[] | undefined;
319
+ } | undefined;
320
+ source?: string | undefined;
321
+ why?: string | undefined;
322
+ updatedAt?: number | undefined;
323
+ binding?: unknown;
324
+ provenBy?: {
325
+ at: number;
326
+ verdictId: string;
327
+ grade: string;
328
+ } | undefined;
329
+ amended?: {
330
+ at: number;
331
+ statement: string;
332
+ }[] | undefined;
333
+ }>;
334
+ version: 1;
335
+ }>;
336
+ export type IntentShard = z.infer<typeof IntentShardSchema>;
337
+ export declare const summarise: (statement: string) => string;
338
+ /** One line per intent: enough to decide whether to open the shard, and no more. */
339
+ export declare const IntentIndexEntrySchema: z.ZodObject<{
340
+ id: z.ZodString;
341
+ subject: z.ZodString;
342
+ statement: z.ZodString;
343
+ status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
344
+ }, "strip", z.ZodTypeAny, {
345
+ status: "proved" | "stale" | "proposed" | "agreed";
346
+ subject: string;
347
+ id: string;
348
+ statement: string;
349
+ }, {
350
+ status: "proved" | "stale" | "proposed" | "agreed";
351
+ subject: string;
352
+ id: string;
353
+ statement: string;
354
+ }>;
355
+ export type IntentIndexEntry = z.infer<typeof IntentIndexEntrySchema>;
356
+ export declare const IntentIndexSchema: z.ZodObject<{
357
+ version: z.ZodLiteral<1>;
358
+ entries: z.ZodArray<z.ZodObject<{
359
+ id: z.ZodString;
360
+ subject: z.ZodString;
361
+ statement: z.ZodString;
362
+ status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
363
+ }, "strip", z.ZodTypeAny, {
364
+ status: "proved" | "stale" | "proposed" | "agreed";
365
+ subject: string;
366
+ id: string;
367
+ statement: string;
368
+ }, {
369
+ status: "proved" | "stale" | "proposed" | "agreed";
370
+ subject: string;
371
+ id: string;
372
+ statement: string;
373
+ }>, "many">;
374
+ }, "strip", z.ZodTypeAny, {
375
+ entries: {
376
+ status: "proved" | "stale" | "proposed" | "agreed";
377
+ subject: string;
378
+ id: string;
379
+ statement: string;
380
+ }[];
381
+ version: 1;
382
+ }, {
383
+ entries: {
384
+ status: "proved" | "stale" | "proposed" | "agreed";
385
+ subject: string;
386
+ id: string;
387
+ statement: string;
388
+ }[];
389
+ version: 1;
390
+ }>;
391
+ export type IntentIndex = z.infer<typeof IntentIndexSchema>;
392
+ export declare const emptyShard: (subject: string) => IntentShard;
393
+ export declare const emptyIndex: () => IntentIndex;
394
+ /** The verification lifecycle a legacy record carried, mapped onto the settledness axis. */
395
+ export declare const statusFromState: (state: Intent["state"]) => IntentStatus;
396
+ /**
397
+ * Turn a legacy intent into a record, inferring only what can be inferred.
398
+ *
399
+ * `why` and `source` are left ABSENT rather than filled with the statement or a placeholder. A
400
+ * record that claims to carry a reason it does not have is worse than one that admits the gap: the
401
+ * gap is a prompt to write the reason down, and a placeholder is a reason to stop looking.
402
+ */
403
+ export declare const recordFromIntent: (intent: Intent) => IntentRecord;
404
+ /** Build the index from the shards. Derived, never hand-maintained — two sources would disagree. */
405
+ export declare const indexFrom: (shards: readonly IntentShard[]) => IntentIndex;
406
+ /** Group records into shards by their own subject. */
407
+ export declare const shardsFrom: (records: readonly IntentRecord[]) => IntentShard[];
408
+ /** Serialise byte-stably, with keys sorted so a re-save of unchanged data is a no-op diff. */
409
+ export declare const serialise: (value: unknown) => string;
410
+ /** Read + validate one JSON file, failing soft: a hand-merged file with a conflict marker is real. */
411
+ export declare const readJsonFile: <T>(fs: FileSystemPort, path: string, parse: (raw: unknown) => T, fallback: T) => Promise<T>;