@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
@@ -6,7 +6,8 @@
6
6
  * point: the action window must open and close identically no matter which anchor found the element,
7
7
  * or a step's events land in the wrong window depending on how it was addressed.
8
8
  */
9
- import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
9
+ import { expectedElementTestid } from '@reticlehq/core';
10
+ import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, PredicateKind, QueryBy, ReticleCommand, } from '@reticlehq/core';
10
11
  import { ReticleTool } from '@reticlehq/core';
11
12
  import { replayActionArgs } from './replay.js';
12
13
  import { isStaleRefError } from '../../surface/tools/act/act-sequence-retry.js';
@@ -102,7 +103,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
102
103
  * to recognise the raw refusal still finds it, plus the fix for this specific caller.
103
104
  */
104
105
  const DESTRUCTIVE_ACTION_PATTERN = /potentially destructive (?:\w+ )*(?:action|tool) blocked/i;
105
- export function replayDestructiveActionHint(rawError) {
106
+ function replayDestructiveActionHint(rawError) {
106
107
  if (!DESTRUCTIVE_ACTION_PATTERN.test(rawError))
107
108
  return rawError;
108
109
  return (`${rawError} — this is a flow REPLAY: a step's own args.confirmDangerous is stripped before ` +
@@ -118,7 +119,7 @@ export function replayDestructiveActionHint(rawError) {
118
119
  * Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
119
120
  * including the action window, whose open/close must not depend on which anchor found the element.
120
121
  */
121
- async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
122
+ export async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous,
122
123
  /**
123
124
  * The field name a redacted fill is supplied under, from `anchorFieldName` — the SAME function
124
125
  * redaction uses to decide what to hide. Without it a role-anchored secret is redacted at save
@@ -252,6 +253,21 @@ export function anchorQueryArgs(anchor) {
252
253
  return roleQueryArgs(anchor);
253
254
  return null;
254
255
  }
256
+ /**
257
+ * The anchor as a PRECONDITION a flow file can store: "this element is on the page".
258
+ *
259
+ * What `requires` means for a flow that starts from state a page load discards, in the shape the
260
+ * flow's own `requires` holds. No tool writes `requires`, so the reset's hint hands over this line.
261
+ */
262
+ export function anchorPrecondition(anchor) {
263
+ if (anchor.kind === AnchorKind.TESTID) {
264
+ return { kind: PredicateKind.ELEMENT, query: { testid: anchor.value } };
265
+ }
266
+ if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor)) {
267
+ return { kind: PredicateKind.ELEMENT, query: { role: anchor.role, name: anchor.name } };
268
+ }
269
+ return undefined;
270
+ }
255
271
  /**
256
272
  * Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
257
273
  * ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
@@ -321,7 +337,7 @@ dynamic = new Set()) {
321
337
  };
322
338
  /** The sub-step's own expectation, unless its testid is deliberately unasserted. */
323
339
  const declaredBy = (sub) => {
324
- const testid = sub.expect?.element?.testid;
340
+ const testid = expectedElementTestid(sub.expect);
325
341
  return testid === undefined || dynamic.has(testid) ? undefined : testid;
326
342
  };
327
343
  /**
@@ -24,8 +24,9 @@
24
24
  * returned unchanged.
25
25
  */
26
26
  import { FlowStore } from './flows.js';
27
+ import type { ProjectId } from '@reticlehq/core';
27
28
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
28
- export declare function flowsForSession(deps: ToolDeps, projectId: string | undefined): {
29
+ export declare function flowsForSession(deps: ToolDeps, projectId: ProjectId | undefined): {
29
30
  flows: FlowStore;
30
31
  root: string;
31
32
  };
@@ -12,21 +12,36 @@
12
12
  * tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
13
13
  * Pure: no IO, no clock.
14
14
  */
15
- import { type FlowExpect, type FlowFile } from '@reticlehq/core';
16
- import type { EvalResult, Predicate } from '@reticlehq/engine/question/predicate/predicate.js';
15
+ import { type FlowExpect, type FlowFile, type Predicate } from '@reticlehq/core';
16
+ import type { EvalResult } from '@reticlehq/engine/question/predicate/predicate.js';
17
17
  import type { FlowReplaySession, WaitForSignal } from './flow-replay-types.js';
18
18
  /** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
19
19
  export declare function dynamicTestids(flow: FlowFile): Set<string>;
20
20
  /** The synthetic step.tool a replay appends when it asserts the flow's success oracle. */
21
21
  export declare const SUCCESS_STEP_TOOL = "success";
22
22
  /** A short human label for the success end-condition, for result rows. */
23
- export declare function successLabel(success: FlowExpect): string;
24
- /** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
25
- export declare function successToPredicate(success: FlowExpect, dynamic: ReadonlySet<string>): Predicate | undefined;
23
+ export declare function successLabel(success: Predicate | undefined): string;
24
+ /**
25
+ * What a flow's success oracle actually asserts, once the file has been read and the dynamic
26
+ * (LLM-output) testids have been taken out of it.
27
+ *
28
+ * Most of what this function used to do is gone, and that is the migration working as intended: it
29
+ * compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
30
+ * by the schema when the file is read. It keeps the name and the signature because it is on
31
+ * `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
32
+ * v1 file still loads — a spec written against the old spelling is not wrong, it is old.
33
+ *
34
+ * What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
35
+ * asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
36
+ * assert exactly what the flow has already declared unassertable. Dropping every such clause can
37
+ * leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
38
+ * no success, because nothing here was checkable.
39
+ */
40
+ export declare function successToPredicate(success: Predicate | FlowExpect | undefined, dynamic: ReadonlySet<string>): Predicate | undefined;
26
41
  /**
27
42
  * Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
28
43
  * real waitForPredicate in production, a fake in unit tests). Passes when: no success was declared,
29
44
  * OR every success field was dynamic-skipped (vacuously met), OR the compiled predicate held within
30
45
  * the injected timeout. Never reads the wall clock.
31
46
  */
32
- export declare function assertSuccess(session: FlowReplaySession, success: FlowExpect | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
47
+ export declare function assertSuccess(session: FlowReplaySession, success: Predicate | undefined, dynamic: ReadonlySet<string>, waitForSignal: WaitForSignal, timeoutMs: number, since?: number): Promise<EvalResult>;
@@ -12,7 +12,7 @@
12
12
  * tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
13
13
  * Pure: no IO, no clock.
14
14
  */
15
- import { AnchorKind, PredicateKind } from '@reticlehq/core';
15
+ import { AnchorKind, PredicateKind, clauseOfKind, flowExpectToPredicate, withoutClauses, } from '@reticlehq/core';
16
16
  /** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
17
17
  export function dynamicTestids(flow) {
18
18
  return new Set((flow.dynamic ?? [])
@@ -23,145 +23,59 @@ export function dynamicTestids(flow) {
23
23
  export const SUCCESS_STEP_TOOL = 'success';
24
24
  /** A short human label for the success end-condition, for result rows. */
25
25
  export function successLabel(success) {
26
- if (success.signal !== undefined)
27
- return success.signal;
28
- if (success.net !== undefined)
29
- return success.net.urlContains ?? success.net.method ?? 'net';
30
- if (success.console !== undefined) {
31
- return `console:${true === success.console.absent ? 'clean' : (success.console.level ?? 'error')}`;
26
+ /*
27
+ * NOT `expectLabel`, and the difference is the reader.
28
+ *
29
+ * That one names a clause for a drift row, where `signal:` tells you which channel failed. This
30
+ * one is `mustHold` in the domain model an agent reads BEFORE testing — a list of what each flow
31
+ * guarantees — and there the prefix is noise on the commonest case. A signal's own name is the
32
+ * sentence: "order:placed" is what must hold.
33
+ */
34
+ const signal = clauseOfKind(success, PredicateKind.SIGNAL);
35
+ if (signal?.name !== undefined)
36
+ return signal.name;
37
+ const net = clauseOfKind(success, PredicateKind.NET);
38
+ if (net !== undefined)
39
+ return net.urlContains ?? net.method ?? 'net';
40
+ const console_ = clauseOfKind(success, PredicateKind.CONSOLE);
41
+ if (console_ !== undefined) {
42
+ return `console:${true === console_.absent ? 'clean' : (console_.level ?? 'error')}`;
32
43
  }
33
- if (success.state !== undefined)
34
- return `state:${success.state.path}`;
35
- if (success.text !== undefined)
36
- return `text:${success.text.contains}`;
37
- return success.element?.testid ?? success.element?.name ?? success.element?.role ?? 'success';
44
+ const state = clauseOfKind(success, PredicateKind.STATE);
45
+ if (state !== undefined)
46
+ return `state:${state.path}`;
47
+ const text = clauseOfKind(success, PredicateKind.TEXT);
48
+ if (text?.contains !== undefined)
49
+ return `text:${text.contains}`;
50
+ const element = clauseOfKind(success, PredicateKind.ELEMENT);
51
+ const q = element?.query;
52
+ return q?.testid ?? q?.name ?? q?.role ?? 'success';
38
53
  }
39
- /** Compile a success FlowExpect into a predicate. undefined → nothing assertable (vacuously met). */
54
+ /**
55
+ * What a flow's success oracle actually asserts, once the file has been read and the dynamic
56
+ * (LLM-output) testids have been taken out of it.
57
+ *
58
+ * Most of what this function used to do is gone, and that is the migration working as intended: it
59
+ * compiled a flat `FlowExpect` into a `Predicate`, and a step's `expect` IS a predicate now, lifted
60
+ * by the schema when the file is read. It keeps the name and the signature because it is on
61
+ * `@reticlehq/test`'s published surface, and it still accepts the flat shape for the same reason a
62
+ * v1 file still loads — a spec written against the old spelling is not wrong, it is old.
63
+ *
64
+ * What remains is the DYNAMIC SKIP, and it is not cosmetic. A testid whose content a model writes is
65
+ * asserted for PRESENCE by the step layer and never for content; a success oracle bound to one would
66
+ * assert exactly what the flow has already declared unassertable. Dropping every such clause can
67
+ * leave nothing at all, and `undefined` is the honest answer — vacuously met, the same as declaring
68
+ * no success, because nothing here was checkable.
69
+ */
40
70
  export function successToPredicate(success, dynamic) {
41
- const parts = [];
42
- if (success.signal !== undefined) {
43
- const signal = {
44
- kind: PredicateKind.SIGNAL,
45
- name: success.signal,
46
- };
47
- if (success.signalData !== undefined)
48
- signal.dataMatches = success.signalData;
49
- if (success.signalCount !== undefined) {
50
- signal.count = success.signalCount;
51
- // Same post-settle reasoning as net.count: the success waiter is wait-until-true, so an exact
52
- // count is transiently satisfied the instant the FIRST matching signal fires — before a
53
- // double-fire's duplicate arrives. Gating on `settled` forces the count to be read only after
54
- // the app goes quiet, by which point the duplicate IS counted and the over-count fails.
55
- parts.push({ kind: PredicateKind.SETTLED });
56
- }
57
- parts.push(signal);
58
- }
59
- if (success.route !== undefined) {
60
- // The mirror of `predicateToExpect`'s ROUTE case. Replay evaluates predicates, so carrying the
61
- // route back here is the whole of what makes a recorded navigation able to fail.
62
- const route = {
63
- kind: PredicateKind.ROUTE,
64
- };
65
- if (success.route.pathname !== undefined)
66
- route.pathname = success.route.pathname;
67
- if (success.route.contains !== undefined)
68
- route.contains = success.route.contains;
69
- return route;
70
- }
71
- if (success.net !== undefined) {
72
- const net = { kind: PredicateKind.NET };
73
- if (success.net.method !== undefined)
74
- net.method = success.net.method;
75
- if (success.net.urlContains !== undefined)
76
- net.urlContains = success.net.urlContains;
77
- if (success.net.status !== undefined)
78
- net.status = success.net.status;
79
- if (success.net.count !== undefined) {
80
- net.count = success.net.count;
81
- // A cardinality assertion is inherently POST-SETTLE. The success waiter is wait-until-true, so an
82
- // exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
83
- // before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
84
- // only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
85
- parts.push({ kind: PredicateKind.SETTLED });
86
- }
87
- parts.push(net);
88
- }
89
- if (success.console !== undefined) {
90
- const con = {
91
- kind: PredicateKind.CONSOLE,
92
- };
93
- if (success.console.level !== undefined)
94
- con.level = success.console.level;
95
- if (success.console.absent !== undefined) {
96
- con.absent = success.console.absent;
97
- // An `absent` assertion is post-settle, same as net.count: a wait-until-true waiter sees "no
98
- // error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
99
- // the console is read only after the page quiets, by which point any error is in the buffer.
100
- if (success.console.absent)
101
- parts.push({ kind: PredicateKind.SETTLED });
102
- }
103
- parts.push(con);
104
- }
105
- const state = success.state;
106
- if (state !== undefined) {
107
- const part = {
108
- kind: PredicateKind.STATE,
109
- path: state.path,
110
- };
111
- if (state.store !== undefined)
112
- part.store = state.store;
113
- if (state.equals !== undefined)
114
- part.equals = state.equals;
115
- // `hold` = an INVARIANT ("this state must still hold after the action settles"), vs the default
116
- // wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
117
- // value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
118
- // on `settled` so the read happens after the page quiets, by which point the leak has landed.
119
- if (true === state.hold)
120
- parts.push({ kind: PredicateKind.SETTLED });
121
- parts.push(part);
122
- }
123
- const element = success.element;
124
- if (element !== undefined) {
125
- const testid = element.testid;
126
- // A dynamic-marked testid is NOT asserted as a success condition (presence-only).
127
- if (testid === undefined || !dynamic.has(testid)) {
128
- const query = {};
129
- if (testid !== undefined)
130
- query['testid'] = testid;
131
- if (element.role !== undefined)
132
- query['role'] = element.role;
133
- if (element.name !== undefined)
134
- query['name'] = element.name;
135
- if (Object.keys(query).length > 0)
136
- parts.push({ kind: PredicateKind.ELEMENT, query });
137
- }
138
- }
139
- const text = success.text;
140
- if (text !== undefined) {
141
- const part = {
142
- kind: PredicateKind.TEXT,
143
- contains: text.contains,
144
- };
145
- if (text.scope !== undefined)
146
- part.scope = text.scope;
147
- if (text.visible !== undefined)
148
- part.visible = text.visible;
149
- if (true === text.absent) {
150
- part.absent = true;
151
- // Same post-settle reasoning as `console.absent` and `state.hold`: a wait-until-true waiter
152
- // reads "not there yet" on the first poll and passes BEFORE the text it is meant to see
153
- // disappear has even been rendered. Gate on `settled` so the read happens after the page
154
- // quiets, by which point a text that was going to appear has.
155
- parts.push({ kind: PredicateKind.SETTLED });
156
- }
157
- parts.push(part);
158
- }
159
- const [first] = parts;
160
- if (0 === parts.length)
71
+ if (success === undefined)
161
72
  return undefined;
162
- if (1 === parts.length && first !== undefined)
163
- return first;
164
- return { kind: PredicateKind.ALL_OF, predicates: parts };
73
+ const predicate = 'kind' in success ? success : flowExpectToPredicate(success);
74
+ if (predicate === undefined)
75
+ return undefined;
76
+ return withoutClauses(predicate, (clause) => PredicateKind.ELEMENT === clause.kind &&
77
+ clause.query.testid !== undefined &&
78
+ dynamic.has(clause.query.testid));
165
79
  }
166
80
  /**
167
81
  * Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
@@ -170,8 +84,6 @@ export function successToPredicate(success, dynamic) {
170
84
  * the injected timeout. Never reads the wall clock.
171
85
  */
172
86
  export async function assertSuccess(session, success, dynamic, waitForSignal, timeoutMs, since = 0) {
173
- if (success === undefined)
174
- return { pass: true };
175
87
  const predicate = successToPredicate(success, dynamic);
176
88
  if (predicate === undefined)
177
89
  return { pass: true };
@@ -20,6 +20,7 @@ import { recordingBacktrackWarning } from './recording/recording-backtrack.js';
20
20
  import { isValidFlowName, flowPath } from '../../memory/project/dir/reticle-dir.js';
21
21
  import {} from './flows.js';
22
22
  import { flowsForSession } from './flow-store-for-session.js';
23
+ import { rootForProjectId } from '../../memory/project/session-root.js';
23
24
  import { resolveSuiteSelection } from './suite-selection.js';
24
25
  import { replayNamedFlow, flowErrorMessage, latestRecordedFlow, sessionProjectId, } from './flow-replay-run.js';
25
26
  import { persistAndSyncVerificationRun, } from '../../judgement/runs/verification-sync.js';
@@ -34,7 +35,10 @@ import { replayAndLearn } from './flow-learning.js';
34
35
  */
35
36
  async function syncSavedFlowToCloud(deps, flow, projectId) {
36
37
  // Per-project cloud: sync a saved flow only when cloud is attached AND flow sync is enabled.
37
- const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
38
+ // The link file of the project this flow belongs to — the flow itself was already saved through
39
+ // `flowsForSession`, and reading the credential from a different tree is how one project's work
40
+ // reaches another project's dashboard.
41
+ const cloud = await resolveProjectCloud(deps.fs, rootForProjectId(deps, projectId), homedir(), process.env);
38
42
  if (null === cloud.config || !cloud.policy.flows)
39
43
  return; // not attached / flows disabled → local only
40
44
  const result = await syncFlowToCloud(flow, cloud.config, projectId, cloudFetch);
@@ -573,7 +577,7 @@ export const FLOW_TOOLS = [
573
577
  ...(selected.unsatisfied === undefined ? {} : { unsatisfied: selected.unsatisfied }),
574
578
  };
575
579
  // verify:server — hand the whole suite to the hosted runner; it records the verification itself.
576
- const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
580
+ const cloud = await resolveProjectCloud(deps.fs, rootForProjectId(deps, projectId), homedir(), process.env);
577
581
  const server = await runServerVerify(deps, cloud, sessionId, requested);
578
582
  if (server !== null)
579
583
  return server;
@@ -637,7 +641,7 @@ export const FLOW_TOOLS = [
637
641
  : leaseFailureReplay(requested[i] ?? '', o.error),
638
642
  durationMs: o.ok && o.value !== undefined ? o.value.durationMs : 0,
639
643
  }));
640
- const flaky = await recordSuiteFlakes(deps.fs, deps.reticleRoot, parallelRuns);
644
+ const flaky = await recordSuiteFlakes(deps.fs, rootForProjectId(deps, projectId), parallelRuns);
641
645
  await persistAndSyncVerificationRun(deps, timed, projectId);
642
646
  const verdict = buildSuiteVerdict(parallelRuns, selected.knownRoutes, SuiteIsolation.PER_FLOW_LEASE);
643
647
  return {
@@ -673,7 +677,7 @@ export const FLOW_TOOLS = [
673
677
  // `reticle flow` on the command line, so an AGENT running this tool a hundred times learned
674
678
  // nothing about which flows are intermittent. Same product, same ledger, two surfaces, and the
675
679
  // one an agent uses was the blind half.
676
- const flaky = await recordSuiteFlakes(deps.fs, deps.reticleRoot, runs);
680
+ const flaky = await recordSuiteFlakes(deps.fs, rootForProjectId(deps, projectId), runs);
677
681
  // Emit the consolidated run artifact (Runs tab) + best-effort cloud push. Never blocks the verdict.
678
682
  await persistAndSyncVerificationRun(deps, timed, projectId);
679
683
  const verdict = buildSuiteVerdict(runs, selected.knownRoutes, SuiteIsolation.SHARED_SESSION);
@@ -1,7 +1,8 @@
1
+ import { type Predicate, type ProjectId } from '@reticlehq/core';
1
2
  export { REDACTED_FILL } from './fields/flow-secret-field.js';
2
3
  import { type FlowResult } from './flow-result.js';
3
4
  export type { FlowResult } from './flow-result.js';
4
- import type { FlowAnchor, FlowExpect, FlowFile, FlowStep, HealChange, InstrumentationGap } from '@reticlehq/core';
5
+ import type { FlowAnchor, FlowFile, FlowStep, HealChange, InstrumentationGap } from '@reticlehq/core';
5
6
  import type { CompiledProgram, RecordedStep } from './recording/tape/recordings.js';
6
7
  import type { FileSystemPort } from '../../memory/project/fs/fs-port.js';
7
8
  import type { Clock } from '../../machine/clock.js';
@@ -48,9 +49,9 @@ interface SaveSummary {
48
49
  * the same bytes as before.
49
50
  */
50
51
  export interface FlowAnnotations {
51
- stepExpect: Map<number, FlowExpect>;
52
+ stepExpect: Map<number, Predicate>;
52
53
  dynamic: string[];
53
- success?: FlowExpect;
54
+ success?: Predicate;
54
55
  /** The flow's declared business goal (intent annotation). */
55
56
  intent?: string;
56
57
  }
@@ -63,15 +64,19 @@ export declare class FlowStore {
63
64
  * Optionally fold structured annotations (per-step expect, dynamic[], success) onto
64
65
  * the flow before writing. Omitting `annotations` reproduces the same bytes.
65
66
  */
66
- save(program: CompiledProgram, annotations?: FlowAnnotations, projectId?: string): Promise<FlowResult<SaveSummary>>;
67
+ save(program: CompiledProgram, annotations?: FlowAnnotations, projectId?: ProjectId): Promise<FlowResult<SaveSummary>>;
67
68
  /**
68
69
  * Persist an already-anchored FlowFile captured in-page (no recompilation). The
69
70
  * browser resolved every semantic anchor at capture time; here we only validate the name +
70
71
  * re-run FlowFileSchema before writing. save is left untouched.
71
72
  */
72
- saveFlow(flow: FlowFile, projectId?: string): Promise<FlowResult<SaveSummary>>;
73
+ saveFlow(flow: FlowFile, projectId?: ProjectId): Promise<FlowResult<SaveSummary>>;
73
74
  /** Write back only what a replay LEARNED. See `#changeInPlace` for why it is not `saveFlow`. */
74
- recordLearned(name: string, learned: NonNullable<FlowFile['learned']>, projectId?: string): Promise<FlowResult<{
75
+ recordLearned(name: string, learned: NonNullable<FlowFile['learned']>, projectId?: ProjectId): Promise<FlowResult<{
76
+ name: string;
77
+ }>>;
78
+ /** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
79
+ recordSources(name: string, sources: ReadonlyMap<string, NonNullable<FlowStep['source']>>, projectId?: ProjectId): Promise<FlowResult<{
75
80
  name: string;
76
81
  }>>;
77
82
  /**
@@ -83,7 +88,7 @@ export declare class FlowStore {
83
88
  * it with proposals that already cleared HEAL_CONFIDENCE_MIN. A change whose `from` no longer
84
89
  * matches the step's anchor is skipped — idempotent and defensive, never throwing.
85
90
  */
86
- heal(name: string, changes: HealChange[], projectId?: string): Promise<FlowResult<{
91
+ heal(name: string, changes: HealChange[], projectId?: ProjectId): Promise<FlowResult<{
87
92
  name: string;
88
93
  changed: HealChange[];
89
94
  }>>;
@@ -97,17 +102,17 @@ export declare class FlowStore {
97
102
  * the caller as a reportable error, never be silently filtered away. Callers that build a path from a
98
103
  * name must validate first; `flowPath` takes a branded FlowName precisely so the compiler insists.
99
104
  */
100
- list(projectId?: string): Promise<string[]>;
105
+ list(projectId?: ProjectId): Promise<string[]>;
101
106
  /**
102
107
  * Read + zod-validate a flow by name. With a `projectId`, prefers the per-project copy and falls
103
108
  * back to a legacy flat (untagged) flow of the same name — so pre-existing flows keep loading.
104
109
  */
105
- load(name: string, projectId?: string): Promise<FlowResult<FlowFile>>;
110
+ load(name: string, projectId?: ProjectId): Promise<FlowResult<FlowFile>>;
106
111
  /**
107
112
  * Delete a flow's file so a renamed/obsolete flow stops lingering in the replay list. Resolves the
108
113
  * same path `load` would (per-project copy, else legacy flat, else a subdir scan for the no-pid
109
114
  * caller), then removes it. NOT_FOUND when nothing resolves — deleting an absent flow is an error,
110
115
  * not a silent no-op, so a typo doesn't read as success.
111
116
  */
112
- remove(name: string, projectId?: string): Promise<FlowResult<void>>;
117
+ remove(name: string, projectId?: ProjectId): Promise<FlowResult<void>>;
113
118
  }
@@ -1,4 +1,4 @@
1
- import { asFlowName } from '@reticlehq/core';
1
+ import { asFlowName, asProjectId, } from '@reticlehq/core';
2
2
  import { REDACTED_FILL } from './fields/flow-secret-field.js';
3
3
  export { REDACTED_FILL } from './fields/flow-secret-field.js';
4
4
  import { safeProjectId } from './flow-result.js';
@@ -7,6 +7,7 @@ import { AnchorKind, DEGRADED_ANCHOR_ROLE, FLOW_FILE_VERSION, FlowErrorCode, Flo
7
7
  import { ReticleTool } from '@reticlehq/core';
8
8
  import { asRecord, asString } from '@reticlehq/core';
9
9
  import { applyHealChanges } from './heal.js';
10
+ import { withLearnedSources } from './learned-sources.js';
10
11
  import { flowIntentGap, linkFlowIntent } from './flow-intent.js';
11
12
  import { IntentStore } from '../../memory/intent/intent-store.js';
12
13
  import { flowDir, flowParentDir, flowPath, reticleDirPaths, isValidFlowName, } from '../../memory/project/dir/reticle-dir.js';
@@ -351,6 +352,13 @@ export class FlowStore {
351
352
  value: { name },
352
353
  }));
353
354
  }
355
+ /** Give sourceless steps the files a clean replay resolved them to, merged onto the file as it is now. */
356
+ async recordSources(name, sources, projectId) {
357
+ return await this.#changeInPlace(name, projectId, (flow) => ({
358
+ next: withLearnedSources(flow, sources) ?? flow,
359
+ value: { name },
360
+ }));
361
+ }
354
362
  /**
355
363
  * Apply confident testid rebinds to an on-disk flow (the `reticle_flow_heal` apply path),
356
364
  * rewriting ONLY the named steps' anchors and preserving every other field. Loading, the name
@@ -362,7 +370,8 @@ export class FlowStore {
362
370
  */
363
371
  async heal(name, changes, projectId) {
364
372
  return await this.#changeInPlace(name, projectId, (flow) => {
365
- const { flow: next, applied } = applyHealChanges(flow, changes);
373
+ // The store's injected clock, so a written flow records when the rebind actually happened.
374
+ const { flow: next, applied } = applyHealChanges(flow, changes, () => this.#clock.now());
366
375
  return { next, value: { name, changed: applied } };
367
376
  });
368
377
  }
@@ -408,8 +417,11 @@ export class FlowStore {
408
417
  catch {
409
418
  return legacy.sort();
410
419
  }
420
+ // Every subdirectory of `.reticle/flows/` is a project's, by construction: this class is the
421
+ // only thing that creates one, and it creates it from a projectId that already passed
422
+ // `safeProjectId`. Reading the name back is the third provenance boundary, not a cast.
411
423
  const subdirs = entries.filter((e) => !e.endsWith(FLOW_SUFFIX));
412
- const nested = (await Promise.all(subdirs.map((d) => this.#namesIn(flowDir(this.#root, d))))).flat();
424
+ const nested = (await Promise.all(subdirs.map((d) => this.#namesIn(flowDir(this.#root, asProjectId(d)))))).flat();
413
425
  return [...new Set([...legacy, ...nested])].sort();
414
426
  }
415
427
  /** The path a flow actually lives at: nested (per-project) if present, else legacy flat, else null. */
@@ -441,8 +453,9 @@ export class FlowStore {
441
453
  catch {
442
454
  return null;
443
455
  }
456
+ // Same provenance as `#namesIn`'s scan above: a subdir of `.reticle/flows/` is a projectId.
444
457
  for (const dir of entries.filter((e) => !e.endsWith(FLOW_SUFFIX))) {
445
- const nested = flowPath(this.#root, name, dir);
458
+ const nested = flowPath(this.#root, name, asProjectId(dir));
446
459
  if (await this.#fs.exists(nested))
447
460
  return nested;
448
461
  }
@@ -25,7 +25,15 @@ export declare function confidenceFor(from: string, to: string): number;
25
25
  * skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
26
26
  * and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
27
27
  */
28
- export declare function applyHealChanges(flow: FlowFile, changes: HealChange[]): {
28
+ export declare function applyHealChanges(flow: FlowFile, changes: HealChange[],
29
+ /**
30
+ * When the rebind happened. Injected rather than read here, because this function is pure and a
31
+ * clock inside it would make the flow it returns depend on when a test ran.
32
+ *
33
+ * Optional so every existing caller keeps working; without it the step still records WHICH anchor
34
+ * was replaced, which is the half a reader actually acts on.
35
+ */
36
+ now?: () => number): {
29
37
  flow: FlowFile;
30
38
  applied: HealChange[];
31
39
  };
@@ -38,7 +38,15 @@ export function confidenceFor(from, to) {
38
38
  * skipped (idempotent / defensive), never throwing. Shared by the on-disk writer (FlowStore.heal)
39
39
  * and the in-memory pre-write verification in reticle_flow_heal, so both rewrite identically.
40
40
  */
41
- export function applyHealChanges(flow, changes) {
41
+ export function applyHealChanges(flow, changes,
42
+ /**
43
+ * When the rebind happened. Injected rather than read here, because this function is pure and a
44
+ * clock inside it would make the flow it returns depend on when a test ran.
45
+ *
46
+ * Optional so every existing caller keeps working; without it the step still records WHICH anchor
47
+ * was replaced, which is the half a reader actually acts on.
48
+ */
49
+ now) {
42
50
  const byStep = new Map();
43
51
  for (const change of changes)
44
52
  byStep.set(change.step, change);
@@ -51,7 +59,21 @@ export function applyHealChanges(flow, changes) {
51
59
  return step;
52
60
  }
53
61
  applied.push(change);
54
- return { ...step, anchor: { kind: AnchorKind.TESTID, value: change.to } };
62
+ return {
63
+ ...step,
64
+ anchor: { kind: AnchorKind.TESTID, value: change.to },
65
+ /*
66
+ * `from` is the ORIGINAL recorded anchor, kept across later heals.
67
+ *
68
+ * A step healed twice has an intermediate name nobody ever chose, so overwriting with the
69
+ * newest would throw away the only anchor a reader wants — the one that was recorded by
70
+ * driving. The timestamp is the LATEST heal, because that is when this anchor arrived.
71
+ */
72
+ healed: {
73
+ from: step.healed?.from ?? change.from,
74
+ at: now?.() ?? step.healed?.at ?? 0,
75
+ },
76
+ };
55
77
  });
56
78
  return { flow: { ...flow, steps }, applied };
57
79
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Learn a flow step's source file from the element its anchor resolved to on a replay.
3
+ *
4
+ * A step recorded before the build stamped the app names no file, so a scoped re-verify cannot say
5
+ * which edits it covers. Every replay already asks the page for each anchor's element, and that
6
+ * element carries its stamp now: this records the answer at the one place every resolution passes
7
+ * through (the session's QUERY), rather than threading it through each step runner.
8
+ */
9
+ import { type FlowFile, type FlowStep } from '@reticlehq/core';
10
+ import type { FlowReplaySession } from './flow-replay-types.js';
11
+ type StepSource = NonNullable<FlowStep['source']>;
12
+ /** `src/Pay.tsx:12:4` or `src/Pay.tsx:12` → `{file, line}`; anything else is not a location. */
13
+ export declare function parseCompactSource(value: string): StepSource | undefined;
14
+ /** Wrap a replay session so every anchor QUERY that resolves to one stamped element is remembered. */
15
+ export declare function recordingSources(inner: FlowReplaySession): {
16
+ session: FlowReplaySession;
17
+ sources: ReadonlyMap<string, StepSource>;
18
+ };
19
+ /** The flow with every sourceless step given the file its anchor resolved to; undefined if none was. */
20
+ export declare function withLearnedSources(flow: FlowFile, sources: ReadonlyMap<string, StepSource>): FlowFile | undefined;
21
+ export {};