@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,5 +1,7 @@
1
- import { join } from 'node:path';
1
+ import { basename, join } from 'node:path';
2
+ import { ReticleDir } from '@reticlehq/core';
2
3
  import { reticleDirPaths, visualDir } from '../../project/dir/reticle-dir.js';
4
+ import { EVIDENCE_DIRS } from './workspace-tiers.js';
3
5
  /** Keep at most this many session journals on disk; older ones are pruned by recency. */
4
6
  export const DEFAULT_SESSION_RETENTION = 20;
5
7
  /**
@@ -13,8 +15,70 @@ export const DEFAULT_DIFF_RETENTION = 10;
13
15
  */
14
16
  export const DEFAULT_FEEDBACK_RETENTION = 20;
15
17
  const DIFF_SUFFIX = '.diff.png';
16
- /** Where refused feedback reports are copied. Named here because the writer lives in another area. */
17
- const FEEDBACK_SUBDIR = 'feedback';
18
+ const PNG_SUFFIX = '.png';
19
+ /**
20
+ * The TOTAL the evidence tier of one `.reticle/` may occupy, across sessions, runs, overlay diffs
21
+ * and feedback copies together.
22
+ *
23
+ * Every bound above this line counts things, and a count cannot see a size: twenty session
24
+ * directories is twenty UNBOUNDED directories. A workspace in the field grew to multiple gigabytes
25
+ * with all three counts being honoured the whole time, because one drive against a chatty app
26
+ * appends response bodies and DOM text for as long as the drive lasts, and nothing was looking at
27
+ * the total.
28
+ *
29
+ * Why this number and not one an order of magnitude either side. An ordinary session journal is
30
+ * hundreds of kilobytes to a few megabytes; a long drive against an app with large JSON responses
31
+ * runs to tens. Twenty of the ordinary ones fit inside this budget outright, which is the working
32
+ * set somebody plausibly wants — the evidence a person reads is from the drive they are looking at
33
+ * and the two before it, and nobody has ever opened the twentieth. A tenth of this (~50 MB) is
34
+ * smaller than a single bad session, so the budget would evict the newest evidence while it was
35
+ * still being written and the count bound could never be reached; ten times it (~5 GB) is not a
36
+ * bound a laptop notices, and 5 GB of untouched journals is the same abandoned-Reticle report with
37
+ * a different number in it. This is roughly one node_modules: large enough to be invisible in a
38
+ * project checkout, small enough that it can never be the reason a disk fills.
39
+ *
40
+ * Both bounds stay, and they are independent on purpose. The count is the cheap guard — it needs one
41
+ * stat per entry and it keeps the directory listing readable. The budget is the guard that holds
42
+ * when the counts are all satisfied and the bytes are not, which is the only situation anybody has
43
+ * actually been hurt by. Either one alone leaves the other's failure unguarded.
44
+ */
45
+ export const DEFAULT_EVIDENCE_BUDGET_BYTES = 512 * 1024 * 1024;
46
+ /** No session is open. The default everywhere, so an existing caller keeps its old behaviour. */
47
+ const NO_LIVE_SESSIONS = new Set();
48
+ /**
49
+ * Is this entry the journal directory of a session that is STILL BEING WRITTEN?
50
+ *
51
+ * A session directory is named for its session id, so the id IS the basename. Scoped to the
52
+ * sessions tier on purpose: a run artifact or a diff that happens to share a session's name is not
53
+ * a journal, and nothing is holding it open.
54
+ */
55
+ function isOpenSession(entry, live) {
56
+ return ReticleDir.SESSIONS_SUBDIR === entry.under && live.has(basename(entry.path));
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 function selectOverBudget(entries, budgetBytes, live = NO_LIVE_SESSIONS) {
64
+ const evidence = entries.filter((entry) => EVIDENCE_DIRS.includes(entry.under));
65
+ // Live sessions still COUNT — their bytes are on disk and the budget is about the disk. They are
66
+ // only excluded from the eviction list, so a session over budget on its own is left alone rather
67
+ // than deleted under its own writer.
68
+ let total = evidence.reduce((sum, entry) => sum + entry.sizeBytes, 0);
69
+ if (total <= budgetBytes)
70
+ return [];
71
+ const doomed = [];
72
+ for (const entry of [...evidence].sort((a, b) => a.mtimeMs - b.mtimeMs)) {
73
+ if (isOpenSession(entry, live))
74
+ continue;
75
+ if (total <= budgetBytes)
76
+ break;
77
+ doomed.push(entry.path);
78
+ total -= entry.sizeBytes;
79
+ }
80
+ return doomed;
81
+ }
18
82
  /**
19
83
  * Pure: given session dirs with their mtimes, the names to remove so only the `retention` most-recent
20
84
  * remain. Oldest-first. Exported for testing (no IO).
@@ -71,10 +135,23 @@ async function pruneByRecency(fs, dir, retention, keep = () => true) {
71
135
  * Bound the journal on disk: keep the `retention` most-recent session directories under
72
136
  * `.reticle/sessions/`, remove the rest by mtime. Never throws — retention is best-effort maintenance,
73
137
  * so a stat/rm failure on one dir (or a missing sessions/ dir) is swallowed, never crashing a session.
74
- * Mirrors RunStore's amortized pruning; run on daemon start.
138
+ * Mirrors RunStore's amortized pruning; run on daemon start and at the end of every session.
139
+ *
140
+ * The COUNT bound only. This used to sweep the tier total as well, from when it was the single
141
+ * sweep both call sites used; `pruneWorkspace` is that single place now and runs the budget last,
142
+ * on purpose, so the leftover call here was a second walk of every evidence entry on every teardown
143
+ * — and it ran FIRST, sizing everything the counts were about to delete anyway.
144
+ *
145
+ * An open session is excluded from BOTH bounds — see `RetentionOptions.live`.
146
+ *
147
+ * ponytail: excluded via `keep`, so an open session is not COUNTED toward the retention cap either.
148
+ * The directory can therefore hold `retention` + (concurrently open sessions) entries, which is a
149
+ * handful. Count them separately only if somebody runs enough parallel sessions for it to matter.
75
150
  */
76
- export async function pruneSessions(fs, root, retention = DEFAULT_SESSION_RETENTION) {
77
- await pruneByRecency(fs, reticleDirPaths(root).sessions, retention);
151
+ export async function pruneSessions(fs, root, options = {}) {
152
+ const live = options.live ?? NO_LIVE_SESSIONS;
153
+ const retention = options.retention ?? DEFAULT_SESSION_RETENTION;
154
+ await pruneByRecency(fs, reticleDirPaths(root).sessions, retention, (name) => !live.has(name));
78
155
  }
79
156
  /**
80
157
  * Bound `.reticle/visual/` — the largest thing in the workspace, and the only one with no delete
@@ -91,20 +168,113 @@ export async function pruneSessions(fs, root, retention = DEFAULT_SESSION_RETENT
91
168
  * `pruneSessions`: this is maintenance, and maintenance must never be the reason a daemon fails.
92
169
  */
93
170
  export async function pruneVisualDiffs(fs, root, retention = DEFAULT_DIFF_RETENTION) {
171
+ for (const dir of await visualDirs(fs, root)) {
172
+ await pruneByRecency(fs, dir, retention, isDiff);
173
+ }
174
+ }
175
+ /** `visual/` plus its per-runtime subdirectories. Empty when nothing has been captured yet. */
176
+ async function visualDirs(fs, root) {
94
177
  const top = visualDir(root);
95
178
  const dirs = [top];
96
179
  try {
97
180
  // A per-runtime subdirectory is anything here that is not itself a PNG.
98
181
  for (const entry of await fs.readdir(top)) {
99
- if (!entry.endsWith('.png'))
182
+ if (!entry.endsWith(PNG_SUFFIX))
100
183
  dirs.push(join(top, entry));
101
184
  }
102
185
  }
103
186
  catch {
104
- return; // no visual dir yet
187
+ return [];
105
188
  }
106
- for (const dir of dirs) {
107
- await pruneByRecency(fs, dir, retention, (name) => name.endsWith(DIFF_SUFFIX));
189
+ return dirs;
190
+ }
191
+ const isDiff = (name) => name.endsWith(DIFF_SUFFIX);
192
+ /**
193
+ * Bytes under `path`, summed recursively. A session journal is a DIRECTORY, and `stat` on one
194
+ * reports the dirent, not the megabytes of JSONL inside it.
195
+ *
196
+ * readdir-first because the port exposes no `isDirectory`: a readdir that fails is a file, and its
197
+ * own stat answers. Never throws — an unreadable entry contributes nothing, which can only make the
198
+ * sweep evict less than it should, never more.
199
+ */
200
+ async function sizeOf(fs, path) {
201
+ let names;
202
+ try {
203
+ names = await fs.readdir(path);
204
+ }
205
+ catch {
206
+ try {
207
+ return (await fs.stat(path)).size;
208
+ }
209
+ catch {
210
+ return 0;
211
+ }
212
+ }
213
+ let total = 0;
214
+ for (const name of names)
215
+ total += await sizeOf(fs, join(path, name));
216
+ return total;
217
+ }
218
+ /**
219
+ * Every evidence entry in this `.reticle/`, sized and dated. The IO half of the byte budget.
220
+ *
221
+ * `visual/` is the one evidence directory holding something that is not evidence — the PNG
222
+ * BASELINES a comparison means anything against — so the same `.diff.png` filter `pruneVisualDiffs`
223
+ * applies is applied here. The tier list is the coarse rule; that filter is the finer one, and
224
+ * deleting a baseline to save disk turns the next run's verdict into `baseline-missing`.
225
+ */
226
+ async function collectEvidence(fs, root) {
227
+ const entries = [];
228
+ for (const under of EVIDENCE_DIRS) {
229
+ const isVisual = ReticleDir.VISUAL_SUBDIR === under;
230
+ const dirs = isVisual ? await visualDirs(fs, root) : [join(root, under)];
231
+ for (const dir of dirs) {
232
+ let names;
233
+ try {
234
+ names = await fs.readdir(dir);
235
+ }
236
+ catch {
237
+ continue; // nothing written here yet
238
+ }
239
+ for (const name of names) {
240
+ if (isVisual && !isDiff(name))
241
+ continue;
242
+ const path = join(dir, name);
243
+ try {
244
+ const { mtimeMs } = await fs.stat(path);
245
+ entries.push({ path, under, sizeBytes: await sizeOf(fs, path), mtimeMs });
246
+ }
247
+ catch {
248
+ /* unreadable entry — skip */
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return entries;
254
+ }
255
+ /**
256
+ * Hold the evidence tier of one `.reticle/` under a TOTAL byte budget, evicting oldest-first.
257
+ *
258
+ * The memory tier is not read, not counted and not touched: the maintainer's constraint is a cap
259
+ * that never stops memory and flows being created, so a full disk costs somebody their session
260
+ * journals and never their flows.
261
+ *
262
+ * Never throws, like everything else here. Retention is maintenance, and maintenance must never be
263
+ * the reason a daemon fails to come up or a session dies.
264
+ */
265
+ export async function pruneEvidenceBudget(fs, root, budgetBytes = DEFAULT_EVIDENCE_BUDGET_BYTES, live = NO_LIVE_SESSIONS) {
266
+ try {
267
+ for (const path of selectOverBudget(await collectEvidence(fs, root), budgetBytes, live)) {
268
+ try {
269
+ await fs.rm(path);
270
+ }
271
+ catch {
272
+ /* best-effort */
273
+ }
274
+ }
275
+ }
276
+ catch {
277
+ /* maintenance never fails a daemon */
108
278
  }
109
279
  }
110
280
  /**
@@ -118,5 +288,5 @@ export async function pruneVisualDiffs(fs, root, retention = DEFAULT_DIFF_RETENT
118
288
  * retention rule is the same rule whether the directory holds journals, diffs or reports.
119
289
  */
120
290
  export async function pruneFeedback(fs, root, retention = DEFAULT_FEEDBACK_RETENTION) {
121
- await pruneByRecency(fs, join(root, FEEDBACK_SUBDIR), retention);
291
+ await pruneByRecency(fs, join(root, ReticleDir.FEEDBACK_SUBDIR), retention);
122
292
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Everything Reticle prunes in ONE `.reticle/` workspace, in one place.
3
+ *
4
+ * Called from two moments and both matter: daemon start (the daemon's own tree) and session end
5
+ * (the tree that SESSION wrote into). Those are usually different directories — a globally
6
+ * registered daemon stands in `$HOME` — and the per-project one is where the bytes actually
7
+ * accumulate, so sweeping only the first swept the wrong tree for the common case.
8
+ *
9
+ * The four bounds were inlined at the call site and one of them was simply missing:
10
+ * `pruneEvidenceBudget` had been written, tested, and never called by anything. The three COUNT
11
+ * bounds were wired and the BYTE bound was not, which is the combination the budget's own header
12
+ * warns about -- "twenty session directories is twenty UNBOUNDED directories", beside a field
13
+ * report of a multi-gigabyte `.reticle/sessions` with all three counts honoured throughout.
14
+ *
15
+ * Counts and bytes answer different questions and neither substitutes for the other. A count says
16
+ * how MANY sessions are kept; it says nothing about how large one is allowed to get. A chatty app
17
+ * writes one enormous journal and never trips a count.
18
+ *
19
+ * Gathered into a function rather than left as four statements so that "what maintenance runs at
20
+ * startup" has one answer that can be TESTED. Inlined, the only way to check the budget was wired
21
+ * was to grep the call site for its name -- and a grep passes on a comment that quotes the code,
22
+ * which has already happened in this repository.
23
+ *
24
+ * Order: counts first, budget last. The counts are cheap and bounded; the budget walks and sizes
25
+ * what the counts leave behind, so running it second means it sizes less.
26
+ *
27
+ * NEVER THROWS. Maintenance must not be the reason a daemon fails to come up, and each prune below
28
+ * already swallows its own errors -- this only has to avoid adding a new way to fail.
29
+ */
30
+ import type { FileSystemPort } from '../../project/fs/fs-port.js';
31
+ import { type RetainPolicy } from './retain-policy.js';
32
+ /**
33
+ * What this sweep is allowed to keep. Every field optional, defaulting to the built-in bound.
34
+ *
35
+ * Partial rather than a whole `RetainPolicy` for two reasons that happen to agree: a test states
36
+ * only the bound it is exercising instead of writing half a gigabyte to disk, and a project's
37
+ * `.reticle.json` states only the bounds it cares about. Same reason the clock is injected
38
+ * everywhere else here — a bound that can only be exercised by producing the real quantity is a
39
+ * bound nothing exercises.
40
+ */
41
+ export type PruneWorkspaceOptions = Partial<RetainPolicy>;
42
+ export declare function pruneWorkspace(fs: FileSystemPort, root: string, live: ReadonlySet<string>, options?: PruneWorkspaceOptions): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { pruneEvidenceBudget, pruneFeedback, pruneSessions, pruneVisualDiffs, } from './retention.js';
2
+ import { DEFAULT_RETAIN } from './retain-policy.js';
3
+ export async function pruneWorkspace(fs, root, live, options = {}) {
4
+ const retain = { ...DEFAULT_RETAIN, ...options };
5
+ // Empty in the ordinary case (nothing has connected yet); this path also runs on a daemon that is
6
+ // already serving sessions, which is why `live` is threaded rather than assumed empty.
7
+ await pruneSessions(fs, root, { live, retention: retain.sessions });
8
+ // The largest thing in the workspace, and for a long time the only one with no delete path.
9
+ await pruneVisualDiffs(fs, root, retain.visual);
10
+ // Write-only local copies of reports the outbox already carries.
11
+ await pruneFeedback(fs, root, retain.feedback);
12
+ // LAST, and the one that was missing: the only bound that limits SIZE rather than count.
13
+ await pruneEvidenceBudget(fs, root, retain.budgetBytes, live);
14
+ }
@@ -1,6 +1,4 @@
1
1
  import type { FileSystemPort } from '../../project/fs/fs-port.js';
2
- /** Every name Reticle treats as machine-local, however it is spelled in the ignore file. */
3
- export declare const TRANSIENT_NAMES: readonly string[];
4
2
  /**
5
3
  * Write `.reticle/.gitignore` if it is not already there.
6
4
  *
@@ -1,53 +1,21 @@
1
1
  import { join } from 'node:path';
2
2
  import { ReticleDir } from '@reticlehq/core';
3
+ import { LOCAL_DIRS, LOCAL_FILES } from './workspace-tiers.js';
3
4
  /**
4
5
  * The `.reticle/` entries that are LOCAL state, and must never be committed by accident.
5
6
  *
6
- * Split from the durable half deliberately, and the split is the whole design: `contract.json`,
7
- * `flows/`, `intent.json` and `capsules/` are meant to be checked in — a flow that cannot be shared
8
- * is not a regression check anybody else can run — while everything below is per-machine churn.
7
+ * Read from the tier table rather than restated here: which half a name belongs to is one decision,
8
+ * and a second copy of it is the list that silently disagrees. This file asks the table exactly one
9
+ * of its two questions — `sharing` — and has no opinion at all about what retention may delete.
9
10
  *
10
11
  * The reason this matters beyond tidiness: a session journal carries URLs, request and response
11
12
  * bodies, and DOM text from the app under test. `.reticle/` is created by the daemon rather than by
12
13
  * the user, so it arrives untracked and unexplained, and one `git add -A` puts an app's traffic into
13
14
  * a shared repository. Reticle created the directory; the ignore for it is Reticle's to write.
14
15
  */
15
- /**
16
- * The local feedback copies — `.reticle/feedback/`.
17
- *
18
- * Named here rather than imported: the directory is spelled inline where it is written, in files
19
- * another agent owns, and the ignore for a directory Reticle creates is Reticle's to write whether
20
- * or not that name has been hoisted yet.
21
- */
22
- const FEEDBACK_SUBDIR = 'feedback';
23
- /** Local directories, without the trailing slash the ignore file wants. */
24
- const TRANSIENT_DIRS = [
25
- ReticleDir.SESSIONS_SUBDIR,
26
- ReticleDir.RUNS_SUBDIR,
27
- ReticleDir.VISUAL_SUBDIR,
28
- // Write-only: a local copy of what was already sent. The outbox is the record.
29
- FEEDBACK_SUBDIR,
30
- ];
31
- /** Local files. Exported with the directories as `TRANSIENT_NAMES`, which the partition guard reads. */
32
- const TRANSIENT_FILES = [
33
- ReticleDir.PROJECT_FILE,
34
- ReticleDir.AMBIENT_FILE,
35
- ReticleDir.ENVELOPES_FILE,
36
- ReticleDir.FLAKE_FILE,
37
- ReticleDir.TIERS_FILE,
38
- // The user's own record of what Reticle did for them, on THIS machine.
39
- ReticleDir.IMPACT_FILE,
40
- // This machine's conversation with the server. Its own doc comment says why committing it is
41
- // harmful: one machine's pull cursor makes every other machine skip what it has not seen.
42
- ReticleDir.CLOUD_STATE_FILE,
43
- // Triage pulled back from the dashboard — a cache of somebody's decisions, re-pullable at will.
44
- ReticleDir.ISSUES_FILE,
45
- ];
46
- /** Every name Reticle treats as machine-local, however it is spelled in the ignore file. */
47
- export const TRANSIENT_NAMES = [...TRANSIENT_DIRS, ...TRANSIENT_FILES];
48
16
  const TRANSIENT = [
49
- ...TRANSIENT_DIRS.map((dir) => `${dir}/`),
50
- ...TRANSIENT_FILES,
17
+ ...LOCAL_DIRS.map((dir) => `${dir}/`),
18
+ ...LOCAL_FILES,
51
19
  '*.log',
52
20
  '*.tmp',
53
21
  ];
@@ -0,0 +1,52 @@
1
+ /**
2
+ * May retention delete this to reclaim disk?
3
+ *
4
+ * MEMORY is why `.reticle/` exists — what a flow replays from, what a deviation is measured
5
+ * against. It is small, it is never evicted, and it must keep being written however full the disk
6
+ * is. EVIDENCE is what one drive left behind: journals, run artifacts, pixel diffs. It is large, it
7
+ * ages out, and losing the oldest of it costs nothing anybody can name.
8
+ */
9
+ export declare const Durability: {
10
+ readonly MEMORY: "memory";
11
+ readonly EVIDENCE: "evidence";
12
+ };
13
+ export type Durability = (typeof Durability)[keyof typeof Durability];
14
+ /**
15
+ * Does this belong in the repository, or only on the machine that wrote it?
16
+ *
17
+ * COMMIT is the shareable half: a flow nobody else can replay is not a regression check. LOCAL is
18
+ * per-machine churn, and journals carry URLs, request and response bodies and page text from the
19
+ * app under test, so keeping them out of a shared repository is worth doing on its own account.
20
+ */
21
+ export declare const Sharing: {
22
+ readonly COMMIT: "commit";
23
+ readonly LOCAL: "local";
24
+ };
25
+ export type Sharing = (typeof Sharing)[keyof typeof Sharing];
26
+ /** What one `.reticle/` entry is, on both axes at once. */
27
+ export interface WorkspaceTier {
28
+ readonly durability: Durability;
29
+ readonly sharing: Sharing;
30
+ /** A directory is ignored as `name/` and swept entry by entry; a file is neither. */
31
+ readonly isDirectory: boolean;
32
+ }
33
+ /**
34
+ * Every top-level name `.reticle/` can hold, classified on both axes.
35
+ *
36
+ * One list used to answer both questions with one answer, and they are not the same question.
37
+ * `capsules/` is committed — a fail-to-pass capsule a teammate cannot replay is not a regression
38
+ * check — AND it is evidence a drive left behind, which grows without bound. That cell did not
39
+ * exist, so the byte budget, which read the gitignore's list, structurally could not see capsules
40
+ * however large they got. Splitting the axes is what makes that sayable; it does not by itself say
41
+ * it, and `capsules/` stays MEMORY here until the budget is measured against it.
42
+ *
43
+ * Declaration order is the order the ignore file lists them, so the file a user already has on disk
44
+ * and the one written today are the same file.
45
+ */
46
+ export declare const WORKSPACE_TIERS: Readonly<Record<string, WorkspaceTier>>;
47
+ /** Machine-local directories, without the trailing slash the ignore file wants. */
48
+ export declare const LOCAL_DIRS: readonly string[];
49
+ /** Machine-local files. */
50
+ export declare const LOCAL_FILES: readonly string[];
51
+ /** The directories retention sweeps, ignored or not. The byte budget and the count bounds read this. */
52
+ export declare const EVIDENCE_DIRS: readonly string[];
@@ -0,0 +1,88 @@
1
+ import { ReticleDir } from '@reticlehq/core';
2
+ /**
3
+ * May retention delete this to reclaim disk?
4
+ *
5
+ * MEMORY is why `.reticle/` exists — what a flow replays from, what a deviation is measured
6
+ * against. It is small, it is never evicted, and it must keep being written however full the disk
7
+ * is. EVIDENCE is what one drive left behind: journals, run artifacts, pixel diffs. It is large, it
8
+ * ages out, and losing the oldest of it costs nothing anybody can name.
9
+ */
10
+ export const Durability = {
11
+ MEMORY: 'memory',
12
+ EVIDENCE: 'evidence',
13
+ };
14
+ /**
15
+ * Does this belong in the repository, or only on the machine that wrote it?
16
+ *
17
+ * COMMIT is the shareable half: a flow nobody else can replay is not a regression check. LOCAL is
18
+ * per-machine churn, and journals carry URLs, request and response bodies and page text from the
19
+ * app under test, so keeping them out of a shared repository is worth doing on its own account.
20
+ */
21
+ export const Sharing = {
22
+ COMMIT: 'commit',
23
+ LOCAL: 'local',
24
+ };
25
+ const localDir = { durability: Durability.EVIDENCE, sharing: Sharing.LOCAL, isDirectory: true };
26
+ const localFile = { durability: Durability.MEMORY, sharing: Sharing.LOCAL, isDirectory: false };
27
+ const sharedDir = { durability: Durability.MEMORY, sharing: Sharing.COMMIT, isDirectory: true };
28
+ const sharedFile = { durability: Durability.MEMORY, sharing: Sharing.COMMIT, isDirectory: false };
29
+ /**
30
+ * Every top-level name `.reticle/` can hold, classified on both axes.
31
+ *
32
+ * One list used to answer both questions with one answer, and they are not the same question.
33
+ * `capsules/` is committed — a fail-to-pass capsule a teammate cannot replay is not a regression
34
+ * check — AND it is evidence a drive left behind, which grows without bound. That cell did not
35
+ * exist, so the byte budget, which read the gitignore's list, structurally could not see capsules
36
+ * however large they got. Splitting the axes is what makes that sayable; it does not by itself say
37
+ * it, and `capsules/` stays MEMORY here until the budget is measured against it.
38
+ *
39
+ * Declaration order is the order the ignore file lists them, so the file a user already has on disk
40
+ * and the one written today are the same file.
41
+ */
42
+ export const WORKSPACE_TIERS = {
43
+ [ReticleDir.SESSIONS_SUBDIR]: localDir,
44
+ [ReticleDir.RUNS_SUBDIR]: localDir,
45
+ [ReticleDir.VISUAL_SUBDIR]: localDir,
46
+ // Write-only: a local copy of what was already sent. The outbox is the record.
47
+ [ReticleDir.FEEDBACK_SUBDIR]: localDir,
48
+ [ReticleDir.PROJECT_FILE]: localFile,
49
+ [ReticleDir.AMBIENT_FILE]: localFile,
50
+ [ReticleDir.ENVELOPES_FILE]: localFile,
51
+ [ReticleDir.FLAKE_FILE]: localFile,
52
+ [ReticleDir.TIERS_FILE]: localFile,
53
+ // The user's own record of what Reticle did for them, on THIS machine.
54
+ [ReticleDir.IMPACT_FILE]: localFile,
55
+ // This machine's conversation with the server. Its own doc comment says why committing it is
56
+ // harmful: one machine's pull cursor makes every other machine skip what it has not seen.
57
+ [ReticleDir.CLOUD_STATE_FILE]: localFile,
58
+ // Triage pulled back from the dashboard — a cache of somebody's decisions, re-pullable at will.
59
+ [ReticleDir.ISSUES_FILE]: localFile,
60
+ [ReticleDir.CONTRACT_FILE]: sharedFile,
61
+ [ReticleDir.FLOWS_SUBDIR]: sharedDir,
62
+ [ReticleDir.BASELINES_SUBDIR]: sharedDir,
63
+ [ReticleDir.CAPSULES_SUBDIR]: sharedDir,
64
+ [ReticleDir.INTENT_FILE]: sharedFile,
65
+ // The sharded form of the same ledger, and shared for the same reason.
66
+ [ReticleDir.INTENT_SUBDIR]: sharedDir,
67
+ [ReticleDir.CLOUD_LINK_FILE]: sharedFile,
68
+ // A hook is a decision the whole team shares, exactly like a package script: one that exists only
69
+ // on the machine that wrote it is a rule nobody else is following. It names COMMANDS and never a
70
+ // credential, so it is safe to commit — the same reasoning that puts `cloud.json` here while its
71
+ // API key stays in ~/.reticle.
72
+ [ReticleDir.HOOKS_FILE]: sharedFile,
73
+ // What a drive types into each labelled field. Committed for two reasons: a replay must send
74
+ // exactly what the recording sent or it is not a replay, and a value a model wrote once should be
75
+ // reviewable and editable by the team rather than regenerated differently on every machine.
76
+ [ReticleDir.FILL_VALUES_FILE]: sharedFile,
77
+ };
78
+ function namesWhere(match) {
79
+ return Object.entries(WORKSPACE_TIERS)
80
+ .filter(([, tier]) => match(tier))
81
+ .map(([name]) => name);
82
+ }
83
+ /** Machine-local directories, without the trailing slash the ignore file wants. */
84
+ export const LOCAL_DIRS = namesWhere((tier) => Sharing.LOCAL === tier.sharing && tier.isDirectory);
85
+ /** Machine-local files. */
86
+ export const LOCAL_FILES = namesWhere((tier) => Sharing.LOCAL === tier.sharing && !tier.isDirectory);
87
+ /** The directories retention sweeps, ignored or not. The byte budget and the count bounds read this. */
88
+ export const EVIDENCE_DIRS = namesWhere((tier) => Durability.EVIDENCE === tier.durability && tier.isDirectory);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The key an envelope's statistics accumulate under.
3
+ *
4
+ * The envelope map was keyed on the raw `pathname`. On any app whose URLs carry ids — most of them —
5
+ * `/orders/1001` and `/orders/1002` are different keys, so every visit mints a fresh envelope
6
+ * holding one sample.
7
+ *
8
+ * The cost is not the file size. `deviation-report` skips any envelope below
9
+ * `MIN_ENVELOPE_SAMPLES`, so on such an app no envelope ever qualifies, every report answers
10
+ * "envelope too new", and the deviation feature never turns on at all — silently, permanently, with
11
+ * nothing anywhere saying why. The unbounded file and the dead feature are one defect.
12
+ *
13
+ * CONSERVATIVE on purpose. Over-collapsing is the worse error: merging two genuinely different
14
+ * routes builds a baseline from two different pages, and a confident comparison against the wrong
15
+ * population is more expensive than no comparison. So a segment is an id only when it could not
16
+ * plausibly be a route NAME.
17
+ *
18
+ * This is the statistics key alone. The observed path is untouched, because a report has to be able
19
+ * to say "/orders/1001 was slow" — the template tells you where to look, the path tells you what to
20
+ * open.
21
+ */
22
+ export declare function routeTemplate(pathname: string): string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The key an envelope's statistics accumulate under.
3
+ *
4
+ * The envelope map was keyed on the raw `pathname`. On any app whose URLs carry ids — most of them —
5
+ * `/orders/1001` and `/orders/1002` are different keys, so every visit mints a fresh envelope
6
+ * holding one sample.
7
+ *
8
+ * The cost is not the file size. `deviation-report` skips any envelope below
9
+ * `MIN_ENVELOPE_SAMPLES`, so on such an app no envelope ever qualifies, every report answers
10
+ * "envelope too new", and the deviation feature never turns on at all — silently, permanently, with
11
+ * nothing anywhere saying why. The unbounded file and the dead feature are one defect.
12
+ *
13
+ * CONSERVATIVE on purpose. Over-collapsing is the worse error: merging two genuinely different
14
+ * routes builds a baseline from two different pages, and a confident comparison against the wrong
15
+ * population is more expensive than no comparison. So a segment is an id only when it could not
16
+ * plausibly be a route NAME.
17
+ *
18
+ * This is the statistics key alone. The observed path is untouched, because a report has to be able
19
+ * to say "/orders/1001 was slow" — the template tells you where to look, the path tells you what to
20
+ * open.
21
+ */
22
+ /** Stands in for any id segment. Not a valid path segment itself, so it cannot collide with one. */
23
+ const ID = ':id';
24
+ /** 8+ hex characters: a token, a hash, a short uuid. Below that it is as likely to be a word. */
25
+ const LONG_HEX = /^[0-9a-f]{8,}$/i;
26
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27
+ const ALL_DIGITS = /^\d+$/;
28
+ /**
29
+ * A year collapses too, and that is a deliberate trade rather than an oversight: `/reports/2024`
30
+ * and `/reports/2025` are the same page with different data, which is exactly what one envelope
31
+ * should describe. Distinguishing a year from an id needs to know the app, and guessing would
32
+ * reintroduce the split this exists to remove.
33
+ */
34
+ function isIdSegment(segment) {
35
+ return ALL_DIGITS.test(segment) || UUID.test(segment) || LONG_HEX.test(segment);
36
+ }
37
+ export function routeTemplate(pathname) {
38
+ if (0 === pathname.length)
39
+ return pathname;
40
+ return pathname
41
+ .split('/')
42
+ .map((segment) => (isIdSegment(segment) ? ID : segment))
43
+ .join('/');
44
+ }
@@ -0,0 +1,3 @@
1
+ import { type ReticleEvent } from '@reticlehq/core';
2
+ /** True when this event is the page fetching Reticle's own code rather than doing anything. */
3
+ export declare function isSelfObservation(event: ReticleEvent): boolean;
@@ -0,0 +1,67 @@
1
+ import { EventType, urlForMatch } from '@reticlehq/core';
2
+ /**
3
+ * A request the page made for Reticle's OWN code, which the journal declines to keep.
4
+ *
5
+ * Measured over this repo's twenty recorded sessions: `net.request` is 64.6% of every journal byte
6
+ * and `net.detail` a further 17.6%, and the bulk of that is the browser fetching the SDK's module
7
+ * graph over and over as the dev server hot-reloads. Those bytes are Reticle watching itself arrive.
8
+ * They can never be evidence about the app under test, because the app did not ask for them — the
9
+ * instrumentation did, and it did so before the app had done anything worth verifying.
10
+ *
11
+ * The DOM side has drawn this line for a long time (`isReticleUi`), and the transport side draws it
12
+ * on the bridge socket's own path. This is the same line on the network channel.
13
+ *
14
+ * Identity, not resemblance. A request qualifies only when its PATH names our npm scope, which is
15
+ * the one thing about a URL that nobody else can be: `@reticlehq` is a registry scope we own, so a
16
+ * path segment under it is a file from one of our packages however it is being served — from
17
+ * `node_modules`, from a CDN, or through Vite's dep optimizer, which flattens the scope to
18
+ * `@reticlehq_browser.js`. A user's own file is never addressed that way.
19
+ *
20
+ * What this deliberately does NOT do is guess from shape. `/core/dist/index.js` and
21
+ * `/adapters/realm/browser/dist/index.js` are what this repo's own linked checkout serves, and
22
+ * matching those would collapse most of the bytes measured above — but they are ordinary paths that
23
+ * somebody else's app is entitled to serve, and a filter that drops a user's request to save our own
24
+ * bytes is the trade this project has already measured the cost of. So a source-linked checkout of
25
+ * Reticle itself keeps journaling its own SDK, and that is the correct side to be wrong on.
26
+ *
27
+ * Only the query string can carry a scope-shaped string that is really app data (a search for
28
+ * `@reticlehq/browser`), so the decision is taken on `pathname` alone.
29
+ */
30
+ /** The npm scope directory, as it appears when the package is served from a tree. */
31
+ const SCOPE_PATH = '/@reticlehq/';
32
+ /**
33
+ * The same scope after Vite's dependency optimizer flattens `@scope/name` to one filename. Anchored
34
+ * at a segment boundary so it cannot match in the middle of somebody's filename.
35
+ */
36
+ const SCOPE_FLATTENED = '/@reticlehq_';
37
+ /**
38
+ * Every event kind that carries a request URL — all of them, on purpose.
39
+ *
40
+ * `reconcileNet` pairs a NET_PENDING with its NET_REQUEST by id and reports an unpaired pending as a
41
+ * HUNG request. Dropping one half of a pair would therefore manufacture a finding out of a request
42
+ * that completed perfectly. The rule is a pure function of the URL, so both halves of a pair always
43
+ * get the same answer and no pair can be split.
44
+ */
45
+ const NET_TYPES = new Set([
46
+ EventType.NET_REQUEST,
47
+ EventType.NET_PENDING,
48
+ EventType.NET_DETAIL,
49
+ EventType.NET_STREAM,
50
+ ]);
51
+ /** A base for parsing relative URLs. Never dereferenced — only the path is read. */
52
+ const RELATIVE_BASE = 'http://reticle.invalid';
53
+ function pathOf(url) {
54
+ try {
55
+ return new URL(url, RELATIVE_BASE).pathname;
56
+ }
57
+ catch {
58
+ return url;
59
+ }
60
+ }
61
+ /** True when this event is the page fetching Reticle's own code rather than doing anything. */
62
+ export function isSelfObservation(event) {
63
+ if (!NET_TYPES.has(event.type))
64
+ return false;
65
+ const path = pathOf(urlForMatch(event.data));
66
+ return path.includes(SCOPE_PATH) || path.includes(SCOPE_FLATTENED);
67
+ }