@reticlehq/server 3.1.0 → 3.3.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 (384) hide show
  1. package/SKILL.md +30 -28
  2. package/dist/command/cli/adhoc-suite.d.ts +19 -0
  3. package/dist/command/cli/adhoc-suite.js +88 -0
  4. package/dist/command/cli/adhoc-verdict.d.ts +23 -2
  5. package/dist/command/cli/adhoc-verdict.js +18 -10
  6. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  7. package/dist/command/cli/cli-doctor.js +37 -5
  8. package/dist/command/cli/cli-flow-commands.d.ts +2 -1
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  11. package/dist/command/cli/cli-parse-grammar.js +7 -0
  12. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  13. package/dist/command/cli/cli-parse-verify.js +86 -13
  14. package/dist/command/cli/cli-parse.d.ts +8 -1
  15. package/dist/command/cli/cli-parse.js +38 -7
  16. package/dist/command/cli/cli-verify.d.ts +59 -0
  17. package/dist/command/cli/cli-verify.js +131 -19
  18. package/dist/command/cli/cloud-cli.js +7 -6
  19. package/dist/command/cli/cloud-kit.d.ts +0 -2
  20. package/dist/command/cli/cloud-kit.js +3 -7
  21. package/dist/command/cli/config/config-discovery.d.ts +12 -0
  22. package/dist/command/cli/config/config-discovery.js +19 -0
  23. package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
  24. package/dist/command/cli/doctor/doctor-rows.js +12 -0
  25. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  26. package/dist/command/cli/drive/drive-command.js +1 -1
  27. package/dist/command/cli/launch/cli-launch.d.ts +3 -23
  28. package/dist/command/cli/launch/cli-launch.js +29 -70
  29. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
  30. package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
  31. package/dist/command/cli/mcp-command.js +5 -3
  32. package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
  33. package/dist/command/cli/ports/resolve/cli-port.js +50 -2
  34. package/dist/command/cli/report-command.d.ts +16 -0
  35. package/dist/command/cli/report-command.js +97 -0
  36. package/dist/command/cli/status/status-lines.d.ts +28 -0
  37. package/dist/command/cli/status/status-lines.js +117 -0
  38. package/dist/command/cli.d.ts +1 -1
  39. package/dist/command/cli.js +29 -177
  40. package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
  41. package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
  42. package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
  43. package/dist/command/daemon/binding/loopback-reach.js +63 -0
  44. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  45. package/dist/command/daemon/daemon-resilience.d.ts +13 -1
  46. package/dist/command/daemon/daemon-resilience.js +12 -1
  47. package/dist/command/daemon/daemon.d.ts +2 -11
  48. package/dist/command/daemon/daemon.js +8 -11
  49. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  50. package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
  51. package/dist/command/daemon/lifetime/heartbeat.js +10 -0
  52. package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
  53. package/dist/command/daemon/lifetime/log-cap.js +115 -0
  54. package/dist/command/setup/agent-notice-memory.d.ts +16 -0
  55. package/dist/command/setup/agent-notice-memory.js +65 -0
  56. package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
  57. package/dist/command/setup/bringup/dev-server-wait.js +16 -0
  58. package/dist/command/setup/bringup/ensure-daemon.js +1 -1
  59. package/dist/command/setup/init/init-host.js +8 -0
  60. package/dist/command/setup/init/init-runtime.js +13 -1
  61. package/dist/command/setup/node-effects.js +1 -1
  62. package/dist/command/setup/probe/page-probe.js +9 -2
  63. package/dist/command/setup/run-setup.d.ts +25 -7
  64. package/dist/command/setup/run-setup.js +51 -9
  65. package/dist/command/setup/setup-command.js +31 -3
  66. package/dist/command/setup/terminal/confirm.js +2 -1
  67. package/dist/command/update/updater.d.ts +27 -0
  68. package/dist/command/update/updater.js +35 -2
  69. package/dist/features/crawl/crawl-tools.js +3 -1
  70. package/dist/features/crawl/crawl.d.ts +2 -0
  71. package/dist/features/crawl/crawl.js +3 -1
  72. package/dist/features/harness/drive-report.d.ts +73 -0
  73. package/dist/features/harness/drive-report.js +196 -0
  74. package/dist/features/harness/driver.js +29 -8
  75. package/dist/features/harness/drivers.d.ts +19 -0
  76. package/dist/features/harness/drivers.js +19 -0
  77. package/dist/features/harness/fill-values.d.ts +67 -0
  78. package/dist/features/harness/fill-values.js +153 -0
  79. package/dist/features/harness/jev-driver.d.ts +188 -0
  80. package/dist/features/harness/jev-driver.js +661 -0
  81. package/dist/features/harness/openai-driver.d.ts +46 -0
  82. package/dist/features/harness/openai-driver.js +197 -0
  83. package/dist/features/harness/platform-config.d.ts +62 -0
  84. package/dist/features/harness/platform-config.js +97 -0
  85. package/dist/features/visual/visual-tools.js +26 -2
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +80 -130
  88. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  89. package/dist/judgement/capsule/capsule-store.js +58 -6
  90. package/dist/judgement/capsule/capsule.js +5 -0
  91. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  92. package/dist/judgement/capsule/causal-summary.js +7 -0
  93. package/dist/judgement/domain/domain-model.js +38 -9
  94. package/dist/judgement/domain/domain-tools.js +4 -2
  95. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  96. package/dist/judgement/phenomena/phenomena.d.ts +2 -1
  97. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  98. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  99. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  100. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  101. package/dist/judgement/runs/artifact/render-report.js +4 -1
  102. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  103. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  104. package/dist/judgement/runs/context-tools.js +21 -3
  105. package/dist/judgement/runs/drive-run.js +7 -2
  106. package/dist/judgement/runs/verification-sync.d.ts +2 -2
  107. package/dist/judgement/runs/verification-sync.js +9 -2
  108. package/dist/language/flows/affected-tools.js +6 -3
  109. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  110. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  111. package/dist/language/flows/flow-anchor.d.ts +12 -0
  112. package/dist/language/flows/flow-anchor.js +20 -0
  113. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  114. package/dist/language/flows/flow-expect-grammar.js +92 -11
  115. package/dist/language/flows/flow-intent.js +1 -1
  116. package/dist/language/flows/flow-learning.d.ts +3 -0
  117. package/dist/language/flows/flow-learning.js +39 -8
  118. package/dist/language/flows/flow-replay-run.d.ts +55 -10
  119. package/dist/language/flows/flow-replay-run.js +217 -58
  120. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  121. package/dist/language/flows/flow-replay.d.ts +29 -11
  122. package/dist/language/flows/flow-replay.js +221 -203
  123. package/dist/language/flows/flow-result.d.ts +18 -1
  124. package/dist/language/flows/flow-result.js +18 -2
  125. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  126. package/dist/language/flows/flow-step-runners.js +20 -4
  127. package/dist/language/flows/flow-store-for-session.d.ts +2 -1
  128. package/dist/language/flows/flow-success.d.ts +21 -6
  129. package/dist/language/flows/flow-success.js +50 -138
  130. package/dist/language/flows/flow-tools.js +8 -4
  131. package/dist/language/flows/flows.d.ts +15 -10
  132. package/dist/language/flows/flows.js +17 -4
  133. package/dist/language/flows/heal.d.ts +9 -1
  134. package/dist/language/flows/heal.js +24 -2
  135. package/dist/language/flows/learned-sources.d.ts +21 -0
  136. package/dist/language/flows/learned-sources.js +84 -0
  137. package/dist/language/flows/narrow-write.d.ts +4 -4
  138. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  139. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  140. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  141. package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
  142. package/dist/language/flows/replay.js +13 -9
  143. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  144. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  145. package/dist/language/flows/stores/flake-store.js +2 -3
  146. package/dist/language/flows/suite/coverage.d.ts +1 -1
  147. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  148. package/dist/language/flows/suite-selection.d.ts +15 -1
  149. package/dist/language/flows/suite-selection.js +1 -1
  150. package/dist/language/flows/verify-change-tools.js +21 -4
  151. package/dist/memory/cloud/account-state.d.ts +1 -1
  152. package/dist/memory/cloud/account-state.js +5 -4
  153. package/dist/memory/cloud/cloud-config.d.ts +9 -0
  154. package/dist/memory/cloud/cloud-config.js +9 -0
  155. package/dist/memory/cloud/cloud-sync.d.ts +7 -4
  156. package/dist/memory/cloud/cloud-sync.js +11 -6
  157. package/dist/memory/cloud/harness-config.d.ts +12 -0
  158. package/dist/memory/cloud/harness-config.js +58 -0
  159. package/dist/memory/cloud/harness-offer.d.ts +44 -0
  160. package/dist/memory/cloud/harness-offer.js +146 -0
  161. package/dist/memory/cloud/harness-switch.d.ts +11 -0
  162. package/dist/memory/cloud/harness-switch.js +53 -0
  163. package/dist/memory/cloud/sync-cycle.d.ts +12 -0
  164. package/dist/memory/cloud/sync-cycle.js +29 -4
  165. package/dist/memory/cloud/sync-daemon.d.ts +35 -2
  166. package/dist/memory/cloud/sync-daemon.js +45 -0
  167. package/dist/memory/cloud/sync-disk.js +11 -6
  168. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  169. package/dist/memory/impact/impact-recorder.js +2 -1
  170. package/dist/memory/impact/impact-store.d.ts +5 -0
  171. package/dist/memory/impact/impact-store.js +25 -0
  172. package/dist/memory/intent/inline-intent.js +8 -3
  173. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  174. package/dist/memory/intent/intent-shard-store.js +253 -141
  175. package/dist/memory/intent/intent-shard.d.ts +22 -23
  176. package/dist/memory/intent/intent-shard.js +1 -1
  177. package/dist/memory/intent/intent-store.d.ts +13 -8
  178. package/dist/memory/intent/intent-store.js +46 -90
  179. package/dist/memory/intent/intent-tools.js +34 -23
  180. package/dist/memory/journal/ambient-store.js +2 -3
  181. package/dist/memory/journal/attach-journal.d.ts +11 -0
  182. package/dist/memory/journal/attach-journal.js +1 -1
  183. package/dist/memory/journal/deviation-report.js +4 -1
  184. package/dist/memory/journal/deviation-service.js +10 -2
  185. package/dist/memory/journal/drive-flow.d.ts +2 -2
  186. package/dist/memory/journal/drive-flow.js +21 -12
  187. package/dist/memory/journal/envelope-store.js +2 -3
  188. package/dist/memory/journal/journal-query.d.ts +35 -1
  189. package/dist/memory/journal/journal-query.js +46 -0
  190. package/dist/memory/journal/journal-recorder.d.ts +71 -2
  191. package/dist/memory/journal/journal-recorder.js +45 -7
  192. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  193. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  194. package/dist/memory/journal/on-disk/retention.d.ts +91 -2
  195. package/dist/memory/journal/on-disk/retention.js +181 -11
  196. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  197. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  198. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
  199. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
  200. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  201. package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
  202. package/dist/memory/journal/route-template.d.ts +22 -0
  203. package/dist/memory/journal/route-template.js +44 -0
  204. package/dist/memory/journal/self-observation.d.ts +3 -0
  205. package/dist/memory/journal/self-observation.js +67 -0
  206. package/dist/memory/journal/session-end.d.ts +17 -4
  207. package/dist/memory/journal/session-end.js +85 -7
  208. package/dist/memory/journal/session-journal.d.ts +122 -5
  209. package/dist/memory/journal/session-journal.js +489 -32
  210. package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
  211. package/dist/memory/project/artifact-root-resolver.js +89 -0
  212. package/dist/memory/project/artifact-root.d.ts +39 -2
  213. package/dist/memory/project/artifact-root.js +58 -1
  214. package/dist/memory/project/baselines.d.ts +1 -1
  215. package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
  216. package/dist/memory/project/dir/fill-value-store.js +88 -0
  217. package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
  218. package/dist/memory/project/dir/reticle-dir.js +11 -0
  219. package/dist/memory/project/fs/fs-port.d.ts +22 -1
  220. package/dist/memory/project/fs/fs-port.js +72 -5
  221. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  222. package/dist/memory/project/fs/write-atomic.js +48 -0
  223. package/dist/memory/project/learned-routes.d.ts +16 -2
  224. package/dist/memory/project/learned-routes.js +26 -10
  225. package/dist/memory/project/project-for-root.d.ts +30 -0
  226. package/dist/memory/project/project-for-root.js +43 -0
  227. package/dist/memory/project/project-tools.js +10 -5
  228. package/dist/memory/project/session-root.d.ts +35 -6
  229. package/dist/memory/project/session-root.js +70 -8
  230. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  231. package/dist/memory/recall/prior/connection-memory.js +8 -0
  232. package/dist/memory/recall/registered-projects.d.ts +2 -1
  233. package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
  234. package/dist/portal/bridge/auth-failure-reason.js +20 -3
  235. package/dist/portal/bridge/bridge.d.ts +6 -1
  236. package/dist/portal/bridge/bridge.js +36 -2
  237. package/dist/portal/input/real-input.js +2 -1
  238. package/dist/portal/pool/browser-pool.d.ts +10 -0
  239. package/dist/portal/pool/browser-pool.js +10 -0
  240. package/dist/portal/pool/playwright-launcher.js +1 -0
  241. package/dist/portal/pool/pool-contract.d.ts +2 -0
  242. package/dist/portal/pool/storage-seed.d.ts +2 -1
  243. package/dist/portal/pool/zero-install.d.ts +27 -0
  244. package/dist/portal/pool/zero-install.js +54 -0
  245. package/dist/portal/realm/web-realm.js +6 -0
  246. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  247. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  248. package/dist/portal/session/dev-server/served-document.js +57 -0
  249. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  250. package/dist/portal/session/facts/background-traffic.js +25 -0
  251. package/dist/portal/session/facts/session-replaced.d.ts +19 -0
  252. package/dist/portal/session/facts/session-replaced.js +22 -0
  253. package/dist/portal/session/live-control-tools.js +48 -3
  254. package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
  255. package/dist/portal/session/no-session-diagnosis.js +67 -60
  256. package/dist/portal/session/no-session-next-action.js +3 -2
  257. package/dist/portal/session/no-session-watch.js +14 -10
  258. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  259. package/dist/portal/session/rebind.d.ts +18 -0
  260. package/dist/portal/session/rebind.js +38 -0
  261. package/dist/portal/session/session-health.d.ts +61 -0
  262. package/dist/portal/session/session-health.js +40 -1
  263. package/dist/portal/session/session-info.d.ts +6 -0
  264. package/dist/portal/session/session-manager.d.ts +13 -0
  265. package/dist/portal/session/session-manager.js +42 -3
  266. package/dist/portal/session/session-successor.d.ts +11 -0
  267. package/dist/portal/session/session-successor.js +14 -1
  268. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  269. package/dist/portal/session/session-verdict-facts.js +15 -0
  270. package/dist/portal/session/session.d.ts +26 -10
  271. package/dist/portal/session/session.js +48 -49
  272. package/dist/status-payload.d.ts +12 -1
  273. package/dist/status-payload.js +4 -1
  274. package/dist/surface/http-server.js +3 -5
  275. package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
  276. package/dist/surface/mcp/faults/mcp-outage.js +20 -4
  277. package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
  278. package/dist/surface/mcp/mcp-proxy.js +23 -12
  279. package/dist/surface/mcp/mcp.d.ts +10 -1
  280. package/dist/surface/mcp/mcp.js +59 -29
  281. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
  282. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
  283. package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
  284. package/dist/surface/mcp/proxy/proxy-log.js +7 -22
  285. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  286. package/dist/surface/mcp/server-instructions.js +12 -1
  287. package/dist/surface/tools/act/act-preflight.js +6 -1
  288. package/dist/surface/tools/act/already-true.d.ts +40 -0
  289. package/dist/surface/tools/act/already-true.js +36 -0
  290. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
  291. package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
  292. package/dist/surface/tools/act/sequence-grade.js +13 -0
  293. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  294. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  295. package/dist/surface/tools/act-capsule.js +49 -8
  296. package/dist/surface/tools/act-tools.js +25 -24
  297. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  298. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  299. package/dist/surface/tools/assert/assert-grade.js +14 -2
  300. package/dist/surface/tools/assert/assert-verdict.js +74 -21
  301. package/dist/surface/tools/browser-tools.js +4 -3
  302. package/dist/surface/tools/coverage-tools.js +5 -2
  303. package/dist/surface/tools/dynamic-tools.js +43 -46
  304. package/dist/surface/tools/error-recovery.d.ts +6 -0
  305. package/dist/surface/tools/error-recovery.js +15 -0
  306. package/dist/surface/tools/explore-tools.js +80 -7
  307. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  308. package/dist/surface/tools/feedback-tools.js +50 -3
  309. package/dist/surface/tools/first-sentence.d.ts +24 -0
  310. package/dist/surface/tools/first-sentence.js +0 -0
  311. package/dist/surface/tools/harness-explore.d.ts +125 -1
  312. package/dist/surface/tools/harness-explore.js +401 -15
  313. package/dist/surface/tools/harness-plan.d.ts +76 -0
  314. package/dist/surface/tools/harness-plan.js +156 -0
  315. package/dist/surface/tools/harness-toolset.js +12 -1
  316. package/dist/surface/tools/invoke-tool.js +82 -9
  317. package/dist/surface/tools/lease-hint.d.ts +8 -0
  318. package/dist/surface/tools/lease-hint.js +10 -0
  319. package/dist/surface/tools/lease-tools.d.ts +16 -28
  320. package/dist/surface/tools/lease-tools.js +53 -5
  321. package/dist/surface/tools/lineage-tools.js +1 -1
  322. package/dist/surface/tools/live-call-text.d.ts +6 -0
  323. package/dist/surface/tools/live-call-text.js +36 -2
  324. package/dist/surface/tools/observe-tools.js +69 -15
  325. package/dist/surface/tools/read/query-windowed.d.ts +1 -1
  326. package/dist/surface/tools/read/query-windowed.js +1 -1
  327. package/dist/surface/tools/read-tools.js +1 -2
  328. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  329. package/dist/surface/tools/real-input-attempt.js +1 -1
  330. package/dist/surface/tools/tool-kit.d.ts +23 -3
  331. package/dist/surface/tools/tool-kit.js +6 -1
  332. package/dist/surface/tools/tool-surface.js +17 -5
  333. package/dist/surface/tools/tools-helpers.d.ts +8 -7
  334. package/dist/surface/tools/tools-helpers.js +11 -13
  335. package/dist/surface/tools/tools.js +70 -30
  336. package/dist/telemetry/bug-found.js +32 -2
  337. package/dist/telemetry/feedback-context.js +3 -3
  338. package/dist/telemetry/feedback-local.d.ts +1 -0
  339. package/dist/telemetry/feedback-local.js +1 -3
  340. package/dist/telemetry/feedback.d.ts +1 -1
  341. package/dist/telemetry/harness-drive.d.ts +6 -0
  342. package/dist/telemetry/harness-drive.js +39 -0
  343. package/dist/telemetry/hud-metrics.d.ts +32 -0
  344. package/dist/telemetry/hud-metrics.js +95 -0
  345. package/dist/telemetry/mcp-connection.d.ts +14 -1
  346. package/dist/telemetry/mcp-connection.js +17 -2
  347. package/dist/telemetry/onboarding-firsts.d.ts +30 -1
  348. package/dist/telemetry/onboarding-firsts.js +54 -12
  349. package/dist/telemetry/session-metrics.d.ts +5 -0
  350. package/dist/telemetry/session-metrics.js +15 -1
  351. package/dist/telemetry/verification-of.js +31 -3
  352. package/dist/wire-journal.d.ts +31 -0
  353. package/dist/wire-journal.js +100 -0
  354. package/docs/README.md +1 -0
  355. package/docs/agent-cheatsheet.md +7 -3
  356. package/docs/autodrive.md +42 -2
  357. package/docs/benchmarks.md +1 -1
  358. package/docs/capabilities.mdx +1 -1
  359. package/docs/cli/cloud.mdx +11 -9
  360. package/docs/cli/doctor.mdx +1 -0
  361. package/docs/cli/init.mdx +1 -0
  362. package/docs/cli/report.mdx +44 -0
  363. package/docs/cli/status.mdx +35 -20
  364. package/docs/cli.mdx +8 -18
  365. package/docs/desktop.mdx +1 -1
  366. package/docs/faq.mdx +1 -1
  367. package/docs/flows.md +11 -0
  368. package/docs/frameworks.mdx +113 -1
  369. package/docs/install-agentic.mdx +36 -0
  370. package/docs/install-manual.mdx +1 -1
  371. package/docs/packages/tauri.mdx +1 -1
  372. package/docs/packages.mdx +1 -1
  373. package/docs/predicates.mdx +144 -4
  374. package/docs/system-map.md +6 -4
  375. package/docs/telemetry.md +2 -1
  376. package/docs/token-efficiency.md +2 -2
  377. package/docs/tools/overview.mdx +1 -1
  378. package/docs/tools/query.mdx +10 -10
  379. package/docs/troubleshooting.mdx +8 -2
  380. package/docs/usage.md +2 -2
  381. package/docs/what-is-recorded.md +62 -0
  382. package/package.json +5 -5
  383. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  384. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
@@ -1,4 +1,5 @@
1
- import { REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
1
+ import { PredicateKind, REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
2
+ import { clauseOfKind } from '@reticlehq/core';
2
3
  /**
3
4
  * What a session drove, saved as a replayable flow — without anybody asking for it.
4
5
  *
@@ -36,21 +37,29 @@ function provedSlug(steps) {
36
37
  const expect = step.expect;
37
38
  if (expect === undefined)
38
39
  continue;
39
- if (expect.signal !== undefined)
40
- return segment(expect.signal, 32);
41
- if (expect.net !== undefined) {
42
- const method = segment(expect.net.method ?? '', 6);
43
- const where = segment(expect.net.urlContains ?? '', 24);
40
+ // A predicate names its kind, so the slug reads the tree rather than probing a struct. The
41
+ // signal wins over the request for the same reason it always did: it is the app saying what it
42
+ // did, which is the strongest thing a name can carry.
43
+ const signal = clauseOfKind(expect, PredicateKind.SIGNAL);
44
+ if (signal?.name !== undefined)
45
+ return segment(signal.name, 32);
46
+ const netClause = clauseOfKind(expect, PredicateKind.NET);
47
+ if (netClause !== undefined) {
48
+ const method = segment(netClause.method ?? '', 6);
49
+ const where = segment(netClause.urlContains ?? '', 24);
44
50
  const joined = [method, where].filter((part) => '' !== part).join('-');
45
51
  if ('' !== joined)
46
52
  return joined;
47
53
  }
48
- if (expect.state !== undefined)
49
- return segment(expect.state.path, 32);
50
- if (expect.route?.pathname !== undefined)
51
- return segment(expect.route.pathname, 24);
52
- if (expect.text?.contains !== undefined)
53
- return segment(expect.text.contains, 24);
54
+ const state = clauseOfKind(expect, PredicateKind.STATE);
55
+ if (state !== undefined)
56
+ return segment(state.path, 32);
57
+ const route = clauseOfKind(expect, PredicateKind.ROUTE);
58
+ if (route?.pathname !== undefined)
59
+ return segment(route.pathname, 24);
60
+ const text = clauseOfKind(expect, PredicateKind.TEXT);
61
+ if (text?.contains !== undefined)
62
+ return segment(text.contains, 24);
54
63
  }
55
64
  return '';
56
65
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { writeFileAtomic } from '../project/fs/write-atomic.js';
2
3
  import { dirname } from 'node:path';
3
4
  import { reticleDirPaths } from '../project/dir/reticle-dir.js';
4
5
  import { RouteEnvelopeSchema } from './envelope.js';
@@ -54,8 +55,6 @@ export class EnvelopeStore {
54
55
  routes[route] = envelope;
55
56
  const body = `${JSON.stringify({ version: ENVELOPE_FILE_VERSION, routes }, null, 2)}\n`;
56
57
  // Atomic publish: write a temp sibling then rename, so a crash never leaves a half-written baseline.
57
- const tmp = `${this.#path}.tmp`;
58
- await this.#fs.writeFile(tmp, body);
59
- await this.#fs.rename(tmp, this.#path);
58
+ await writeFileAtomic(this.#fs, this.#path, body);
60
59
  }
61
60
  }
@@ -1,4 +1,13 @@
1
- import type { ReticleEvent } from '@reticlehq/core';
1
+ import type { JournalWriteLoss, ReticleEvent } from '@reticlehq/core';
2
+ import type { JournalReader } from './journal-recorder.js';
3
+ /** The slice of the ring buffer a journal-backed query reads. `RingBuffer` satisfies it structurally. */
4
+ export interface QueryBuffer {
5
+ since(cursor: number): ReticleEvent[];
6
+ bufferHealth(): {
7
+ total: number;
8
+ dropped: number;
9
+ };
10
+ }
2
11
  /** Filter bounds for a journal-backed event query. All optional; omitted = unbounded on that axis. */
3
12
  export interface EventQueryOptions {
4
13
  /** Lower time bound (inclusive), elapsed ms — the act cursor semantics of `eventsSince`. */
@@ -28,5 +37,30 @@ export interface EventQueryOptions {
28
37
  * Overlapping seqs need no rule — appends are ordered, so the newest write already wins the dedupe.
29
38
  */
30
39
  export declare function mergeEventsBySeq(journal: readonly ReticleEvent[], buffer: readonly ReticleEvent[]): ReticleEvent[];
40
+ /**
41
+ * The events a query answers with: the ring buffer's, merged with the durable journal's **only when
42
+ * the buffer has evicted** (so a healthy session pays no disk cost), then filtered by
43
+ * since/until/actionId. This is how "what did action N cause" is answered after the buffer has
44
+ * dropped the evidence — the substrate's whole point.
45
+ */
46
+ export declare function readQueryEvents(reader: JournalReader | undefined, buffer: QueryBuffer, options: EventQueryOptions): Promise<ReticleEvent[]>;
47
+ /**
48
+ * What the durable ledger could not WRITE, when a query would read from it.
49
+ *
50
+ * The event ledger stops at a byte ceiling, so a journal-backed window can be missing exactly the
51
+ * evidence that would have refuted an absence claim. The in-band `truncated` record cannot carry
52
+ * this on its own: it is stamped with the `t` of the last refused event, and `filterEvents` above
53
+ * bounds on `t` — so a window opened after the ceiling was reached holds no marker, no events, and
54
+ * nothing to distinguish it from a window in which nothing happened.
55
+ *
56
+ * Reported ONLY while the buffer has evicted, which is the same condition `readQueryEvents` uses to
57
+ * fall through to disk. While the buffer still holds the window, the ledger's state says nothing
58
+ * about that window's completeness, and saying it anyway would impeach healthy verdicts for the
59
+ * rest of every session that ever hit its cap. Live ring data is never discarded over this.
60
+ *
61
+ * A reader with no opinion answers `undefined`, which means NOT MEASURED. It must never be read as
62
+ * "nothing was lost" — see `JournalReader.readWriteLoss`.
63
+ */
64
+ export declare function readJournalWriteLoss(reader: JournalReader | undefined, buffer: QueryBuffer): Promise<JournalWriteLoss | undefined>;
31
65
  /** Apply since/until/actionId bounds to an event list. */
32
66
  export declare function filterEvents(events: readonly ReticleEvent[], options: EventQueryOptions): ReticleEvent[];
@@ -44,6 +44,52 @@ export function mergeEventsBySeq(journal, buffer) {
44
44
  return a.t - b.t;
45
45
  });
46
46
  }
47
+ /**
48
+ * Does a query read the durable journal at all, or is the buffer still authoritative?
49
+ *
50
+ * One expression, three callers, because the two public functions below are only honest while this
51
+ * is true. Kept here rather than on `Session` so the rule sits beside the merge and filter rules it
52
+ * governs; two copies of it would drift into a verdict that impeached itself over a file it never
53
+ * opened.
54
+ */
55
+ function readsJournal(reader, buffer) {
56
+ return reader !== undefined && buffer.bufferHealth().dropped > 0;
57
+ }
58
+ /**
59
+ * The events a query answers with: the ring buffer's, merged with the durable journal's **only when
60
+ * the buffer has evicted** (so a healthy session pays no disk cost), then filtered by
61
+ * since/until/actionId. This is how "what did action N cause" is answered after the buffer has
62
+ * dropped the evidence — the substrate's whole point.
63
+ */
64
+ export async function readQueryEvents(reader, buffer, options) {
65
+ if (!readsJournal(reader, buffer)) {
66
+ return filterEvents(buffer.since(options.since ?? 0), options);
67
+ }
68
+ const durable = (await reader?.readEvents()) ?? [];
69
+ return filterEvents(mergeEventsBySeq(durable, buffer.since(0)), options);
70
+ }
71
+ /**
72
+ * What the durable ledger could not WRITE, when a query would read from it.
73
+ *
74
+ * The event ledger stops at a byte ceiling, so a journal-backed window can be missing exactly the
75
+ * evidence that would have refuted an absence claim. The in-band `truncated` record cannot carry
76
+ * this on its own: it is stamped with the `t` of the last refused event, and `filterEvents` above
77
+ * bounds on `t` — so a window opened after the ceiling was reached holds no marker, no events, and
78
+ * nothing to distinguish it from a window in which nothing happened.
79
+ *
80
+ * Reported ONLY while the buffer has evicted, which is the same condition `readQueryEvents` uses to
81
+ * fall through to disk. While the buffer still holds the window, the ledger's state says nothing
82
+ * about that window's completeness, and saying it anyway would impeach healthy verdicts for the
83
+ * rest of every session that ever hit its cap. Live ring data is never discarded over this.
84
+ *
85
+ * A reader with no opinion answers `undefined`, which means NOT MEASURED. It must never be read as
86
+ * "nothing was lost" — see `JournalReader.readWriteLoss`.
87
+ */
88
+ export async function readJournalWriteLoss(reader, buffer) {
89
+ if (!readsJournal(reader, buffer))
90
+ return undefined;
91
+ return (await reader?.readWriteLoss?.()) ?? undefined;
92
+ }
47
93
  /** Apply since/until/actionId bounds to an event list. */
48
94
  export function filterEvents(events, options) {
49
95
  return events.filter((event) => (options.since === undefined || event.t >= options.since) &&
@@ -1,9 +1,35 @@
1
- import { type JournalAction, type ReticleEvent } from '@reticlehq/core';
1
+ import { type JournalAction, type JournalWriteLoss, type ReticleEvent } from '@reticlehq/core';
2
2
  /** Where a recorder persists. `SessionJournal` satisfies this structurally. */
3
3
  export interface JournalSink {
4
4
  appendEvents(events: readonly ReticleEvent[]): Promise<void>;
5
5
  appendAction(action: JournalAction): Promise<void>;
6
6
  }
7
+ /**
8
+ * What a durable read could NOT produce — the report that travels beside the events.
9
+ *
10
+ * The journal is append-only and one session's file is never pruned, so a long-lived session's
11
+ * ledger outgrows what a JavaScript string can hold and the read has to stop somewhere; and what a
12
+ * reader keeps across many reads has to stop somewhere too. Where it stopped is not a detail: an
13
+ * agent reading a short answer it cannot tell apart from a complete one is the false green this
14
+ * whole layer exists to prevent. Absent means nothing was lost.
15
+ */
16
+ export interface JournalReadLoss {
17
+ /**
18
+ * Journal bytes not represented in the answer — skipped past by a read that would have exceeded
19
+ * its ceiling, plus the bytes of records evicted after parsing. A floor, not a total: the record
20
+ * straddling a cut is discarded with them and is not counted here.
21
+ */
22
+ droppedBytes: number;
23
+ /** Parsed records evicted from the retained set. Exact, unlike `droppedBytes`. */
24
+ droppedEvents: number;
25
+ /**
26
+ * Latest timestamp of parsed cache evictions, inclusive. Omitted after a byte cut skips
27
+ * unparsed records: their time range is unknown even when newer bytes survive.
28
+ */
29
+ lostThroughT?: number;
30
+ /** Human-and-agent readable summary. Present so a consumer never has to compose one. */
31
+ note: string;
32
+ }
7
33
  /** Read side of the durable journal — the fall-through source when the ring buffer has evicted. */
8
34
  export interface JournalReader {
9
35
  readEvents(): Promise<ReticleEvent[]>;
@@ -12,6 +38,42 @@ export interface JournalReader {
12
38
  * needs events should be forced to grow a second method to satisfy the type.
13
39
  */
14
40
  readActions?(): Promise<JournalAction[]>;
41
+ /**
42
+ * What the reads so far could not reach. Optional on the same reasoning as `readActions`: a reader
43
+ * that omits it declares no loss, which is the behaviour every partial double already had.
44
+ */
45
+ readLoss?(): JournalReadLoss | undefined;
46
+ /**
47
+ * What the WRITER could not put on disk: the ledger's closure report, when its byte ceiling
48
+ * refused a batch. `undefined` means nothing was refused.
49
+ *
50
+ * Optional for the same reason `readActions` is, and absence means NOT MEASURED rather than "no
51
+ * loss" — a reader that cannot answer must not be made to answer "clean". The separate question
52
+ * of what a bounded READ declined to hand back belongs to the reader that bounded it; this one is
53
+ * only ever about writes that never happened.
54
+ */
55
+ readWriteLoss?(): Promise<JournalWriteLoss | undefined>;
56
+ /**
57
+ * How big the event ledger is right now, and the ceiling it is measured against.
58
+ *
59
+ * Synchronous and cached: the size is already tracked for the cap, so reporting it costs a field
60
+ * read. Optional on the same reasoning as `readActions` -- a partial double declares no size,
61
+ * and a caller reads that as "not measured" rather than "empty".
62
+ *
63
+ * It is reported at all because nothing reported it. A repeating uncaught error wrote one
64
+ * session's ledger without limit, and neither the daemon nor
65
+ * `doctor` nor any health counter said a word; the user found out when the disk was full (#986).
66
+ * The cap that now bounds this is only half an answer -- a ledger silently approaching it is
67
+ * still a dev tool writing hard to somebody's repo directory.
68
+ */
69
+ ledger?(): JournalLedgerSize | undefined;
70
+ }
71
+ /** The event ledger's size on disk against the ceiling that will refuse it. */
72
+ export interface JournalLedgerSize {
73
+ /** Bytes of `events.jsonl` on disk. */
74
+ bytes: number;
75
+ /** The ceiling that refuses the next batch past it. */
76
+ capBytes: number;
15
77
  }
16
78
  interface JournalRecorderOptions {
17
79
  /** Injected elapsed-ms clock (never read from Date here — the clock-injection rule). */
@@ -31,7 +93,14 @@ interface JournalRecorderOptions {
31
93
  export declare class JournalRecorder {
32
94
  #private;
33
95
  constructor(sink: JournalSink, options: JournalRecorderOptions);
34
- /** Attribute (if an action is active), enqueue for journaling, and return the possibly-stamped event. */
96
+ /**
97
+ * Attribute (if an action is active), enqueue for journaling, and return the possibly-stamped event.
98
+ *
99
+ * Reticle fetching its own SDK is attributed and returned like anything else but never written to
100
+ * disk. That is a decision about what is worth KEEPING, so it is taken here, where the journal is
101
+ * written, and not in a reader: a reader that hid these would leave the bytes on disk and the
102
+ * cost unpaid, and a second reader would still find them and disagree about what happened.
103
+ */
35
104
  observe(event: ReticleEvent): ReticleEvent;
36
105
  /** Open an attribution window. One action is active at a time (the agent drives sequentially). */
37
106
  beginAction(actionId: string, tool: string, args: Record<string, unknown>): void;
@@ -1,4 +1,17 @@
1
1
  import { EventAttribution, JOURNAL_FILE_VERSION, } from '@reticlehq/core';
2
+ import { isSelfObservation } from './self-observation.js';
3
+ import { log } from '../../log.js';
4
+ /**
5
+ * Logged the FIRST time a journal write is lost, and never again for that recorder.
6
+ *
7
+ * The swallow stays — a failed local journal write must never break a live session, and a tool call
8
+ * is not the place to learn the disk is full. But a loss that says nothing is how a journal wrote
9
+ * nothing for a whole session while the session reported healthy and reads kept answering from the
10
+ * in-memory cache. Once per recorder, on the existing daemon-log channel: enough to find in the
11
+ * daemon log when somebody asks why a journal file is short, quiet enough that a full disk does not
12
+ * produce one line per batch.
13
+ */
14
+ const JOURNAL_WRITE_LOST = 'journal_write_lost';
2
15
  const DEFAULT_FLUSH_AT = 64;
3
16
  /**
4
17
  * Owns action-window attribution and batched, order-preserving journaling — the durable half of the
@@ -16,12 +29,20 @@ export class JournalRecorder {
16
29
  #pending = [];
17
30
  #active;
18
31
  #chain = Promise.resolve();
32
+ #lossReported = false;
19
33
  constructor(sink, options) {
20
34
  this.#sink = sink;
21
35
  this.#now = options.now;
22
36
  this.#flushAt = options.flushAt ?? DEFAULT_FLUSH_AT;
23
37
  }
24
- /** Attribute (if an action is active), enqueue for journaling, and return the possibly-stamped event. */
38
+ /**
39
+ * Attribute (if an action is active), enqueue for journaling, and return the possibly-stamped event.
40
+ *
41
+ * Reticle fetching its own SDK is attributed and returned like anything else but never written to
42
+ * disk. That is a decision about what is worth KEEPING, so it is taken here, where the journal is
43
+ * written, and not in a reader: a reader that hid these would leave the bytes on disk and the
44
+ * cost unpaid, and a second reader would still find them and disagree about what happened.
45
+ */
25
46
  observe(event) {
26
47
  let out = event;
27
48
  const active = this.#active;
@@ -33,9 +54,13 @@ export class JournalRecorder {
33
54
  active.seqTo = active.seqTo === undefined ? event.seq : Math.max(active.seqTo, event.seq);
34
55
  }
35
56
  }
36
- this.#pending.push(out);
37
- if (this.#pending.length >= this.#flushAt)
38
- this.#enqueueFlush();
57
+ // Attribution above still runs, and the return value is what the live ring buffer stores — the
58
+ // session keeps seeing this event. What is declined is the DURABLE copy: see isSelfObservation.
59
+ if (!isSelfObservation(out)) {
60
+ this.#pending.push(out);
61
+ if (this.#pending.length >= this.#flushAt)
62
+ this.#enqueueFlush();
63
+ }
39
64
  return out;
40
65
  }
41
66
  /** Open an attribution window. One action is active at a time (the agent drives sequentially). */
@@ -63,7 +88,9 @@ export class JournalRecorder {
63
88
  at: active.tStart,
64
89
  };
65
90
  this.#enqueueFlush();
66
- this.#chain = this.#chain.then(() => this.#sink.appendAction(action)).catch(() => undefined);
91
+ this.#chain = this.#chain
92
+ .then(() => this.#sink.appendAction(action))
93
+ .catch((error) => this.#reportLoss(error));
67
94
  }
68
95
  /**
69
96
  * Append one action record for a tool that PROVED something without driving the page.
@@ -98,7 +125,9 @@ export class JournalRecorder {
98
125
  at,
99
126
  };
100
127
  this.#enqueueFlush();
101
- this.#chain = this.#chain.then(() => this.#sink.appendAction(action)).catch(() => undefined);
128
+ this.#chain = this.#chain
129
+ .then(() => this.#sink.appendAction(action))
130
+ .catch((error) => this.#reportLoss(error));
102
131
  }
103
132
  /** Persist any buffered events now (call on session end). Awaits the write chain to settle. */
104
133
  async flush() {
@@ -110,6 +139,15 @@ export class JournalRecorder {
110
139
  return;
111
140
  const batch = this.#pending;
112
141
  this.#pending = [];
113
- this.#chain = this.#chain.then(() => this.#sink.appendEvents(batch)).catch(() => undefined);
142
+ this.#chain = this.#chain
143
+ .then(() => this.#sink.appendEvents(batch))
144
+ .catch((error) => this.#reportLoss(error));
145
+ }
146
+ /** Swallow a lost write, but say so once. See JOURNAL_WRITE_LOST. */
147
+ #reportLoss(error) {
148
+ if (this.#lossReported)
149
+ return;
150
+ this.#lossReported = true;
151
+ log(JOURNAL_WRITE_LOST, { error: error instanceof Error ? error.message : String(error) });
114
152
  }
115
153
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * What a project is willing to keep.
3
+ *
4
+ * The only answer `.reticle.json` used to offer a full directory was `journal: false` — turning off
5
+ * the thing the product is FOR, and, until the sweep was ungated, the one setting under which
6
+ * nothing was deleted either. The bounds themselves were four constants no user could reach.
7
+ *
8
+ * `0` is a real answer and means keep none, which the count bounds already do for free: a sweep that
9
+ * keeps zero directories deletes all of them. It is not the same as "unset", which is why every
10
+ * field is read explicitly rather than with `??` over a falsy value.
11
+ */
12
+ export interface RetainPolicy {
13
+ /** Session journal directories under `sessions/`. */
14
+ sessions: number;
15
+ /** Overlay diffs under `visual/`. Baselines are memory and are never counted here. */
16
+ visual: number;
17
+ /** Local copies of reports the outbox already carries. */
18
+ feedback: number;
19
+ /** The TOTAL the evidence tier may occupy, in bytes. Stated in the file as `budgetMb`. */
20
+ budgetBytes: number;
21
+ }
22
+ export declare const DEFAULT_RETAIN: RetainPolicy;
23
+ /**
24
+ * Read the `retain` block out of an already-parsed `.reticle.json`.
25
+ *
26
+ * Takes the record rather than a path: the config search belongs to the caller that already does it
27
+ * once at startup, and reaching for it from here would point the retention domain at the
28
+ * command-line surface for a single lookup.
29
+ *
30
+ * Never throws, and falls back PER FIELD. A project that mistypes one bound keeps the other three —
31
+ * the alternative silently reverts every bound the user did set, which is the same class of quiet
32
+ * disagreement the whole partition of `.reticle/` exists to prevent.
33
+ */
34
+ export declare function readRetainPolicy(config: Record<string, unknown> | undefined): RetainPolicy;
@@ -0,0 +1,44 @@
1
+ import { DEFAULT_DIFF_RETENTION, DEFAULT_EVIDENCE_BUDGET_BYTES, DEFAULT_FEEDBACK_RETENTION, DEFAULT_SESSION_RETENTION, } from './retention.js';
2
+ /** The key a project writes in `.reticle.json`. */
3
+ const RETAIN_KEY = 'retain';
4
+ const BYTES_PER_MB = 1024 * 1024;
5
+ export const DEFAULT_RETAIN = {
6
+ sessions: DEFAULT_SESSION_RETENTION,
7
+ visual: DEFAULT_DIFF_RETENTION,
8
+ feedback: DEFAULT_FEEDBACK_RETENTION,
9
+ budgetBytes: DEFAULT_EVIDENCE_BUDGET_BYTES,
10
+ };
11
+ /**
12
+ * A count a sweep can act on, or `undefined` for anything else.
13
+ *
14
+ * Negative and fractional are refused rather than coerced. "Keep -1 sessions" and "keep 1.5" are
15
+ * both a user saying something they did not mean, and a sweep that guesses at what they meant is
16
+ * deleting files on a guess.
17
+ */
18
+ function count(value) {
19
+ return 'number' === typeof value && Number.isInteger(value) && value >= 0 ? value : undefined;
20
+ }
21
+ /**
22
+ * Read the `retain` block out of an already-parsed `.reticle.json`.
23
+ *
24
+ * Takes the record rather than a path: the config search belongs to the caller that already does it
25
+ * once at startup, and reaching for it from here would point the retention domain at the
26
+ * command-line surface for a single lookup.
27
+ *
28
+ * Never throws, and falls back PER FIELD. A project that mistypes one bound keeps the other three —
29
+ * the alternative silently reverts every bound the user did set, which is the same class of quiet
30
+ * disagreement the whole partition of `.reticle/` exists to prevent.
31
+ */
32
+ export function readRetainPolicy(config) {
33
+ const raw = config?.[RETAIN_KEY];
34
+ if ('object' !== typeof raw || null === raw || Array.isArray(raw))
35
+ return DEFAULT_RETAIN;
36
+ const retain = raw;
37
+ const mb = count(retain['budgetMb']);
38
+ return {
39
+ sessions: count(retain['sessions']) ?? DEFAULT_RETAIN.sessions,
40
+ visual: count(retain['visual']) ?? DEFAULT_RETAIN.visual,
41
+ feedback: count(retain['feedback']) ?? DEFAULT_RETAIN.feedback,
42
+ budgetBytes: mb === undefined ? DEFAULT_RETAIN.budgetBytes : mb * BYTES_PER_MB,
43
+ };
44
+ }
@@ -11,6 +11,56 @@ export declare const DEFAULT_DIFF_RETENTION = 10;
11
11
  * for the one case where delivery was refused and somebody has to file the report by hand.
12
12
  */
13
13
  export declare const DEFAULT_FEEDBACK_RETENTION = 20;
14
+ /**
15
+ * The TOTAL the evidence tier of one `.reticle/` may occupy, across sessions, runs, overlay diffs
16
+ * and feedback copies together.
17
+ *
18
+ * Every bound above this line counts things, and a count cannot see a size: twenty session
19
+ * directories is twenty UNBOUNDED directories. A workspace in the field grew to multiple gigabytes
20
+ * with all three counts being honoured the whole time, because one drive against a chatty app
21
+ * appends response bodies and DOM text for as long as the drive lasts, and nothing was looking at
22
+ * the total.
23
+ *
24
+ * Why this number and not one an order of magnitude either side. An ordinary session journal is
25
+ * hundreds of kilobytes to a few megabytes; a long drive against an app with large JSON responses
26
+ * runs to tens. Twenty of the ordinary ones fit inside this budget outright, which is the working
27
+ * set somebody plausibly wants — the evidence a person reads is from the drive they are looking at
28
+ * and the two before it, and nobody has ever opened the twentieth. A tenth of this (~50 MB) is
29
+ * smaller than a single bad session, so the budget would evict the newest evidence while it was
30
+ * still being written and the count bound could never be reached; ten times it (~5 GB) is not a
31
+ * bound a laptop notices, and 5 GB of untouched journals is the same abandoned-Reticle report with
32
+ * a different number in it. This is roughly one node_modules: large enough to be invisible in a
33
+ * project checkout, small enough that it can never be the reason a disk fills.
34
+ *
35
+ * Both bounds stay, and they are independent on purpose. The count is the cheap guard — it needs one
36
+ * stat per entry and it keeps the directory listing readable. The budget is the guard that holds
37
+ * when the counts are all satisfied and the bytes are not, which is the only situation anybody has
38
+ * actually been hurt by. Either one alone leaves the other's failure unguarded.
39
+ */
40
+ export declare const DEFAULT_EVIDENCE_BUDGET_BYTES: number;
41
+ /**
42
+ * One candidate entry inside a `.reticle/`, classified by the top-level directory it lives under.
43
+ *
44
+ * `under` is what decides the tier, and it is looked up in the workspace tier table's `durability`
45
+ * axis — never in the gitignore's, which answers a different question. Anything not EVIDENCE is
46
+ * MEMORY: flows, capsules, baselines, the contract, the intent ledger. Those are small, durable
47
+ * and the reason the directory exists, so they are never evicted, never counted, and must keep
48
+ * being written however full the disk is.
49
+ */
50
+ export interface TierEntry {
51
+ /** Absolute path, and what is removed. */
52
+ path: string;
53
+ /** The `.reticle/` top-level entry this lives under. */
54
+ under: string;
55
+ sizeBytes: number;
56
+ mtimeMs: number;
57
+ }
58
+ /**
59
+ * Pure: the paths to remove so the EVIDENCE tier fits in `budgetBytes`. Oldest first, memory-tier
60
+ * entries never selected and never counted. No IO — the call site stats and removes, exactly as
61
+ * `selectPrunable` and `pruneByRecency` already split it.
62
+ */
63
+ export declare function selectOverBudget(entries: readonly TierEntry[], budgetBytes: number, live?: ReadonlySet<string>): string[];
14
64
  interface DatedDir {
15
65
  name: string;
16
66
  mtimeMs: number;
@@ -20,13 +70,41 @@ interface DatedDir {
20
70
  * remain. Oldest-first. Exported for testing (no IO).
21
71
  */
22
72
  export declare function selectPrunable(dirs: readonly DatedDir[], retention: number): string[];
73
+ /** What a sweep is allowed to delete. Every field optional: omitting one keeps the old behaviour. */
74
+ export interface RetentionOptions {
75
+ /** How many session directories survive the count bound. */
76
+ retention?: number;
77
+ /**
78
+ * The ids of sessions that are STILL OPEN. Neither bound may evict one.
79
+ *
80
+ * Without this both bounds could delete the journal of the session currently being written, and
81
+ * the long-running session was the FIRST candidate rather than the last: a directory's mtime is
82
+ * stamped when it is created and never moves again — appending to a file inside it advances the
83
+ * file's mtime, not the directory's — so an hour-long drive looks older than every short session
84
+ * that started after it. The journal then latched its directory as ensured, every later append
85
+ * failed ENOENT into a swallowed catch, and reads kept answering from the in-memory cache, so the
86
+ * session reported healthy with nothing reaching disk.
87
+ */
88
+ live?: ReadonlySet<string> | undefined;
89
+ }
23
90
  /**
24
91
  * Bound the journal on disk: keep the `retention` most-recent session directories under
25
92
  * `.reticle/sessions/`, remove the rest by mtime. Never throws — retention is best-effort maintenance,
26
93
  * so a stat/rm failure on one dir (or a missing sessions/ dir) is swallowed, never crashing a session.
27
- * Mirrors RunStore's amortized pruning; run on daemon start.
94
+ * Mirrors RunStore's amortized pruning; run on daemon start and at the end of every session.
95
+ *
96
+ * The COUNT bound only. This used to sweep the tier total as well, from when it was the single
97
+ * sweep both call sites used; `pruneWorkspace` is that single place now and runs the budget last,
98
+ * on purpose, so the leftover call here was a second walk of every evidence entry on every teardown
99
+ * — and it ran FIRST, sizing everything the counts were about to delete anyway.
100
+ *
101
+ * An open session is excluded from BOTH bounds — see `RetentionOptions.live`.
102
+ *
103
+ * ponytail: excluded via `keep`, so an open session is not COUNTED toward the retention cap either.
104
+ * The directory can therefore hold `retention` + (concurrently open sessions) entries, which is a
105
+ * handful. Count them separately only if somebody runs enough parallel sessions for it to matter.
28
106
  */
29
- export declare function pruneSessions(fs: FileSystemPort, root: string, retention?: number): Promise<void>;
107
+ export declare function pruneSessions(fs: FileSystemPort, root: string, options?: RetentionOptions): Promise<void>;
30
108
  /**
31
109
  * Bound `.reticle/visual/` — the largest thing in the workspace, and the only one with no delete
32
110
  * path at all.
@@ -42,6 +120,17 @@ export declare function pruneSessions(fs: FileSystemPort, root: string, retentio
42
120
  * `pruneSessions`: this is maintenance, and maintenance must never be the reason a daemon fails.
43
121
  */
44
122
  export declare function pruneVisualDiffs(fs: FileSystemPort, root: string, retention?: number): Promise<void>;
123
+ /**
124
+ * Hold the evidence tier of one `.reticle/` under a TOTAL byte budget, evicting oldest-first.
125
+ *
126
+ * The memory tier is not read, not counted and not touched: the maintainer's constraint is a cap
127
+ * that never stops memory and flows being created, so a full disk costs somebody their session
128
+ * journals and never their flows.
129
+ *
130
+ * Never throws, like everything else here. Retention is maintenance, and maintenance must never be
131
+ * the reason a daemon fails to come up or a session dies.
132
+ */
133
+ export declare function pruneEvidenceBudget(fs: FileSystemPort, root: string, budgetBytes?: number, live?: ReadonlySet<string>): Promise<void>;
45
134
  /**
46
135
  * Bound `.reticle/feedback/` — local copies of reports that were already sent.
47
136
  *