@reticlehq/server 3.2.0 → 3.4.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 (437) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +23 -22
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +41 -0
  7. package/dist/command/cli/adhoc-suite.js +159 -0
  8. package/dist/command/cli/adhoc-verdict.d.ts +58 -2
  9. package/dist/command/cli/adhoc-verdict.js +108 -13
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +30 -8
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  20. package/dist/command/cli/cli-parse-grammar.js +7 -0
  21. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  22. package/dist/command/cli/cli-parse-verify.js +86 -13
  23. package/dist/command/cli/cli-parse.d.ts +14 -1
  24. package/dist/command/cli/cli-parse.js +54 -8
  25. package/dist/command/cli/cli-update-commands.js +8 -1
  26. package/dist/command/cli/cli-verify.d.ts +80 -0
  27. package/dist/command/cli/cli-verify.js +181 -26
  28. package/dist/command/cli/cloud-cli.js +5 -2
  29. package/dist/command/cli/cloud-kit.d.ts +0 -2
  30. package/dist/command/cli/cloud-kit.js +3 -5
  31. package/dist/command/cli/cloud-login.js +21 -3
  32. package/dist/command/cli/demo/demo-page.js +6 -3
  33. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  34. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  35. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  36. package/dist/command/cli/launch/cli-launch.d.ts +9 -29
  37. package/dist/command/cli/launch/cli-launch.js +27 -49
  38. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  39. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  40. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  41. package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
  42. package/dist/command/cli/ports/resolve/cli-port.js +169 -15
  43. package/dist/command/cli/report-command.d.ts +16 -0
  44. package/dist/command/cli/report-command.js +97 -0
  45. package/dist/command/cli/setup-mcp-cli.js +13 -13
  46. package/dist/command/cli/tutorial.d.ts +2 -2
  47. package/dist/command/cli/tutorial.js +21 -7
  48. package/dist/command/cli.js +67 -10
  49. package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
  50. package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
  51. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  52. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  53. package/dist/command/daemon/daemon-resolve.js +1 -1
  54. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  55. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  56. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  57. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  58. package/dist/command/dev/stale-issue-guard.js +8 -8
  59. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  60. package/dist/command/setup/bringup/connect-lease.js +38 -0
  61. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  62. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  63. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  64. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  65. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  66. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  67. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  68. package/dist/command/setup/bringup/static-page-server.js +151 -0
  69. package/dist/command/setup/desktop-shape.d.ts +4 -0
  70. package/dist/command/setup/desktop-shape.js +18 -2
  71. package/dist/command/setup/init/init-host.js +8 -0
  72. package/dist/command/setup/init/init-port.d.ts +15 -0
  73. package/dist/command/setup/init/init-port.js +44 -0
  74. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  75. package/dist/command/setup/init/init-runtime.js +39 -2
  76. package/dist/command/setup/node-effects.d.ts +27 -14
  77. package/dist/command/setup/node-effects.js +80 -44
  78. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  79. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  80. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  81. package/dist/command/setup/probe/page-probe.js +18 -3
  82. package/dist/command/setup/run-setup.d.ts +26 -1
  83. package/dist/command/setup/run-setup.js +96 -13
  84. package/dist/command/setup/session-pick.d.ts +13 -0
  85. package/dist/command/setup/session-pick.js +20 -0
  86. package/dist/command/setup/setup-command.d.ts +2 -14
  87. package/dist/command/setup/setup-command.js +105 -31
  88. package/dist/command/setup/setup-mcp.js +3 -3
  89. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  90. package/dist/command/setup/terminal/wait-progress.js +17 -0
  91. package/dist/features/crawl/crawl-empty.js +1 -1
  92. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  93. package/dist/features/crawl/crawl-tools.js +57 -0
  94. package/dist/features/crawl/crawl.d.ts +2 -0
  95. package/dist/features/crawl/crawl.js +171 -156
  96. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  97. package/dist/features/exhaust/app-coverage.js +31 -0
  98. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  99. package/dist/features/exhaust/code-coverage.js +77 -0
  100. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  101. package/dist/features/exhaust/exhaust-run.js +82 -0
  102. package/dist/features/exhaust/explorer.d.ts +103 -0
  103. package/dist/features/exhaust/explorer.js +195 -0
  104. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  105. package/dist/features/exhaust/failure-branches.js +92 -0
  106. package/dist/features/exhaust/ledger.d.ts +202 -0
  107. package/dist/features/exhaust/ledger.js +252 -0
  108. package/dist/features/exhaust/session-fold.d.ts +31 -0
  109. package/dist/features/exhaust/session-fold.js +79 -0
  110. package/dist/features/exhaust/session-port.d.ts +36 -0
  111. package/dist/features/exhaust/session-port.js +102 -0
  112. package/dist/features/harness/drive-report.d.ts +0 -9
  113. package/dist/features/harness/drive-report.js +18 -0
  114. package/dist/features/harness/driver.js +4 -7
  115. package/dist/features/harness/fill-values.d.ts +6 -0
  116. package/dist/features/harness/fill-values.js +3 -0
  117. package/dist/features/harness/jev-driver.js +36 -22
  118. package/dist/features/harness/openai-driver.js +4 -7
  119. package/dist/features/harness/platform-config.d.ts +10 -0
  120. package/dist/features/harness/platform-config.js +13 -6
  121. package/dist/index.d.ts +10 -1
  122. package/dist/index.js +78 -104
  123. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  124. package/dist/judgement/capsule/capsule-store.js +58 -6
  125. package/dist/judgement/capsule/capsule.js +5 -0
  126. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  127. package/dist/judgement/capsule/causal-summary.js +7 -0
  128. package/dist/judgement/domain/domain-model.js +40 -9
  129. package/dist/judgement/oracles/self-instrument.js +4 -2
  130. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  131. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  132. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  133. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  134. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  135. package/dist/judgement/runs/artifact/render-report.js +4 -1
  136. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  137. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  138. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  139. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  140. package/dist/judgement/runs/context-tools.js +21 -3
  141. package/dist/judgement/runs/drive-run.js +7 -2
  142. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  143. package/dist/judgement/runs/verification-sync.js +1 -1
  144. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  145. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  146. package/dist/language/flows/change/affected.d.ts +9 -0
  147. package/dist/language/flows/change/affected.js +35 -0
  148. package/dist/language/flows/change/gate.d.ts +20 -0
  149. package/dist/language/flows/change/gate.js +12 -1
  150. package/dist/language/flows/flow-anchor.d.ts +12 -0
  151. package/dist/language/flows/flow-anchor.js +20 -0
  152. package/dist/language/flows/flow-author.d.ts +7 -0
  153. package/dist/language/flows/flow-author.js +19 -0
  154. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  155. package/dist/language/flows/flow-expect-grammar.js +92 -11
  156. package/dist/language/flows/flow-intent.js +1 -1
  157. package/dist/language/flows/flow-journey.d.ts +54 -0
  158. package/dist/language/flows/flow-journey.js +119 -0
  159. package/dist/language/flows/flow-learning.d.ts +3 -0
  160. package/dist/language/flows/flow-learning.js +39 -8
  161. package/dist/language/flows/flow-replay-run.d.ts +33 -12
  162. package/dist/language/flows/flow-replay-run.js +131 -52
  163. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  164. package/dist/language/flows/flow-replay.d.ts +27 -12
  165. package/dist/language/flows/flow-replay.js +127 -111
  166. package/dist/language/flows/flow-result.d.ts +2 -0
  167. package/dist/language/flows/flow-result.js +25 -0
  168. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  169. package/dist/language/flows/flow-step-runners.js +20 -4
  170. package/dist/language/flows/flow-success.d.ts +21 -6
  171. package/dist/language/flows/flow-success.js +50 -138
  172. package/dist/language/flows/flow-tools.js +17 -5
  173. package/dist/language/flows/flows.d.ts +18 -5
  174. package/dist/language/flows/flows.js +101 -16
  175. package/dist/language/flows/heal.d.ts +9 -1
  176. package/dist/language/flows/heal.js +24 -2
  177. package/dist/language/flows/learned-sources.d.ts +21 -0
  178. package/dist/language/flows/learned-sources.js +84 -0
  179. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  180. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  181. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  182. package/dist/language/flows/recording/replay-halt.js +3 -1
  183. package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
  184. package/dist/language/flows/recording/tape/recordings.js +55 -1
  185. package/dist/language/flows/replay.d.ts +19 -0
  186. package/dist/language/flows/replay.js +53 -13
  187. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  188. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  189. package/dist/language/flows/stores/flake-store.js +2 -3
  190. package/dist/language/flows/suite/coverage.d.ts +1 -1
  191. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  192. package/dist/language/flows/verify-change-tools.js +19 -2
  193. package/dist/launch-chromium.d.ts +80 -0
  194. package/dist/launch-chromium.js +218 -0
  195. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  196. package/dist/memory/cloud/cloud-config.js +47 -7
  197. package/dist/memory/cloud/cloud-sync.d.ts +4 -3
  198. package/dist/memory/cloud/cloud-sync.js +7 -10
  199. package/dist/memory/cloud/harness-config.d.ts +2 -0
  200. package/dist/memory/cloud/harness-offer.js +5 -6
  201. package/dist/memory/cloud/harness-switch.js +5 -6
  202. package/dist/memory/cloud/sync-cycle.d.ts +78 -1
  203. package/dist/memory/cloud/sync-cycle.js +321 -53
  204. package/dist/memory/cloud/sync-daemon.d.ts +13 -0
  205. package/dist/memory/cloud/sync-daemon.js +45 -20
  206. package/dist/memory/cloud/sync-disk.js +27 -8
  207. package/dist/memory/impact/impact-store.js +6 -0
  208. package/dist/memory/intent/inline-intent.js +8 -3
  209. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  210. package/dist/memory/intent/intent-shard-store.js +252 -140
  211. package/dist/memory/intent/intent-shard.d.ts +0 -23
  212. package/dist/memory/intent/intent-shard.js +1 -11
  213. package/dist/memory/intent/intent-store.d.ts +13 -8
  214. package/dist/memory/intent/intent-store.js +46 -90
  215. package/dist/memory/intent/intent-tools.js +34 -23
  216. package/dist/memory/journal/ambient-store.js +2 -3
  217. package/dist/memory/journal/deviation-report.js +4 -1
  218. package/dist/memory/journal/deviation-service.js +10 -2
  219. package/dist/memory/journal/drive-flow.d.ts +19 -14
  220. package/dist/memory/journal/drive-flow.js +65 -48
  221. package/dist/memory/journal/envelope-store.js +2 -3
  222. package/dist/memory/journal/journal-recorder.d.ts +21 -0
  223. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  224. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  225. package/dist/memory/journal/on-disk/retention.d.ts +14 -10
  226. package/dist/memory/journal/on-disk/retention.js +13 -12
  227. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  228. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  229. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
  230. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
  231. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  232. package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
  233. package/dist/memory/journal/route-template.d.ts +22 -0
  234. package/dist/memory/journal/route-template.js +44 -0
  235. package/dist/memory/journal/session-end.d.ts +13 -2
  236. package/dist/memory/journal/session-end.js +88 -18
  237. package/dist/memory/journal/session-journal.d.ts +8 -1
  238. package/dist/memory/journal/session-journal.js +10 -0
  239. package/dist/memory/project/baselines.d.ts +1 -1
  240. package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
  241. package/dist/memory/project/dir/reticle-dir.js +1 -0
  242. package/dist/memory/project/fs/fs-port.js +44 -5
  243. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  244. package/dist/memory/project/fs/write-atomic.js +48 -0
  245. package/dist/memory/project/project-tools.js +3 -1
  246. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  247. package/dist/memory/recall/prior/connection-memory.js +8 -0
  248. package/dist/memory/recall/registered-projects.d.ts +2 -1
  249. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  250. package/dist/portal/bridge/auth-failure-reason.js +9 -4
  251. package/dist/portal/bridge/bridge.d.ts +8 -0
  252. package/dist/portal/bridge/bridge.js +43 -2
  253. package/dist/portal/input/js-coverage.d.ts +25 -0
  254. package/dist/portal/input/js-coverage.js +30 -0
  255. package/dist/portal/input/real-input.d.ts +11 -0
  256. package/dist/portal/input/real-input.js +31 -5
  257. package/dist/portal/pool/browser-pool.d.ts +10 -0
  258. package/dist/portal/pool/browser-pool.js +10 -0
  259. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  260. package/dist/portal/pool/playwright-launcher.js +21 -13
  261. package/dist/portal/pool/pool-contract.d.ts +2 -0
  262. package/dist/portal/pool/storage-seed.d.ts +2 -1
  263. package/dist/portal/pool/zero-install.d.ts +27 -0
  264. package/dist/portal/pool/zero-install.js +54 -0
  265. package/dist/portal/realm/web-realm.js +6 -0
  266. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  267. package/dist/portal/session/dev-server/dev-command.js +30 -12
  268. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  269. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  270. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  271. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  272. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  273. package/dist/portal/session/dev-server/served-document.js +57 -0
  274. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  275. package/dist/portal/session/facts/background-traffic.js +25 -0
  276. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  277. package/dist/portal/session/facts/observed-state.js +34 -0
  278. package/dist/portal/session/live-control-tools.js +48 -3
  279. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  280. package/dist/portal/session/no-session-diagnosis.js +62 -5
  281. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  282. package/dist/portal/session/no-session-next-action.js +48 -9
  283. package/dist/portal/session/no-session-watch.d.ts +18 -0
  284. package/dist/portal/session/no-session-watch.js +88 -2
  285. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  286. package/dist/portal/session/session-health.d.ts +61 -0
  287. package/dist/portal/session/session-health.js +43 -3
  288. package/dist/portal/session/session-info.d.ts +6 -0
  289. package/dist/portal/session/session-manager.d.ts +29 -6
  290. package/dist/portal/session/session-manager.js +42 -5
  291. package/dist/portal/session/session-reaper.d.ts +28 -0
  292. package/dist/portal/session/session-reaper.js +49 -1
  293. package/dist/portal/session/session-successor.d.ts +11 -0
  294. package/dist/portal/session/session-successor.js +14 -1
  295. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  296. package/dist/portal/session/session-verdict-facts.js +15 -0
  297. package/dist/portal/session/session.d.ts +10 -0
  298. package/dist/portal/session/session.js +34 -26
  299. package/dist/surface/http-server.d.ts +2 -1
  300. package/dist/surface/http-server.js +4 -2
  301. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  302. package/dist/surface/mcp/mcp-proxy.js +5 -5
  303. package/dist/surface/mcp/mcp.js +4 -2
  304. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  305. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  306. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  307. package/dist/surface/mcp/server-instructions.js +21 -8
  308. package/dist/surface/tools/act/after-match.d.ts +17 -0
  309. package/dist/surface/tools/act/after-match.js +30 -0
  310. package/dist/surface/tools/act/already-true.d.ts +40 -0
  311. package/dist/surface/tools/act/already-true.js +36 -0
  312. package/dist/surface/tools/act/durable.d.ts +69 -0
  313. package/dist/surface/tools/act/durable.js +139 -0
  314. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
  315. package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
  316. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  317. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  318. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  319. package/dist/surface/tools/act/proved-controls.js +11 -0
  320. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  321. package/dist/surface/tools/act/sequence-grade.js +32 -1
  322. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  323. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  324. package/dist/surface/tools/act-capsule.d.ts +7 -0
  325. package/dist/surface/tools/act-capsule.js +68 -8
  326. package/dist/surface/tools/act-sequence-tool.js +25 -8
  327. package/dist/surface/tools/act-tools.js +51 -74
  328. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  329. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  330. package/dist/surface/tools/assert/assert-grade.js +26 -3
  331. package/dist/surface/tools/assert/assert-verdict.js +32 -6
  332. package/dist/surface/tools/browser-tools.js +17 -4
  333. package/dist/surface/tools/coverage-tools.js +27 -1
  334. package/dist/surface/tools/dynamic-tools.js +6 -0
  335. package/dist/surface/tools/error-recovery.js +4 -4
  336. package/dist/surface/tools/feedback-invite.js +6 -4
  337. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  338. package/dist/surface/tools/feedback-tools.js +1 -1
  339. package/dist/surface/tools/harness-explore.d.ts +3 -2
  340. package/dist/surface/tools/harness-explore.js +7 -6
  341. package/dist/surface/tools/invoke-tool.js +17 -4
  342. package/dist/surface/tools/lease-hint.d.ts +8 -0
  343. package/dist/surface/tools/lease-hint.js +10 -0
  344. package/dist/surface/tools/lease-tools.d.ts +16 -28
  345. package/dist/surface/tools/lease-tools.js +68 -11
  346. package/dist/surface/tools/lineage-tools.js +1 -1
  347. package/dist/surface/tools/live-call-text.js +4 -1
  348. package/dist/surface/tools/observe-tools.js +23 -7
  349. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  350. package/dist/surface/tools/real-input-attempt.js +1 -1
  351. package/dist/surface/tools/tool-kit.d.ts +5 -0
  352. package/dist/surface/tools/tool-kit.js +6 -1
  353. package/dist/surface/tools/tool-surface.js +17 -5
  354. package/dist/surface/tools/tools.js +68 -29
  355. package/dist/telemetry/argument-shape.d.ts +0 -2
  356. package/dist/telemetry/argument-shape.js +0 -4
  357. package/dist/telemetry/error-class.d.ts +2 -3
  358. package/dist/telemetry/error-class.js +2 -3
  359. package/dist/telemetry/feedback-local.d.ts +1 -0
  360. package/dist/telemetry/feedback-local.js +1 -3
  361. package/dist/telemetry/feedback.d.ts +1 -1
  362. package/dist/telemetry/harness-drive.d.ts +0 -2
  363. package/dist/telemetry/harness-drive.js +0 -4
  364. package/dist/telemetry/hud-metrics.d.ts +32 -0
  365. package/dist/telemetry/hud-metrics.js +95 -0
  366. package/dist/telemetry/session-metrics.d.ts +5 -0
  367. package/dist/telemetry/session-metrics.js +15 -1
  368. package/dist/wire-journal.d.ts +34 -0
  369. package/dist/wire-journal.js +104 -0
  370. package/dist/workspace-packages.d.ts +0 -2
  371. package/dist/workspace-packages.js +1 -9
  372. package/docs/README.md +5 -2
  373. package/docs/actions.mdx +3 -3
  374. package/docs/agent-cheatsheet.md +4 -4
  375. package/docs/architecture.md +6 -6
  376. package/docs/best-practices.mdx +1 -1
  377. package/docs/capabilities.mdx +32 -1
  378. package/docs/cli/cloud.mdx +21 -9
  379. package/docs/cli/doctor.mdx +4 -0
  380. package/docs/cli/feedback.mdx +2 -2
  381. package/docs/cli/gate.mdx +9 -4
  382. package/docs/cli/init.mdx +1 -0
  383. package/docs/cli/report.mdx +44 -0
  384. package/docs/cli/update.mdx +10 -0
  385. package/docs/cli.mdx +9 -0
  386. package/docs/desktop.mdx +14 -10
  387. package/docs/environment.mdx +87 -0
  388. package/docs/faq.mdx +18 -16
  389. package/docs/first-drive.md +5 -13
  390. package/docs/flows.md +32 -2
  391. package/docs/frameworks.mdx +140 -9
  392. package/docs/getting-started.md +3 -3
  393. package/docs/http-transport.md +6 -3
  394. package/docs/human-control.md +2 -2
  395. package/docs/index.mdx +16 -6
  396. package/docs/install-agentic.mdx +37 -1
  397. package/docs/install-manual.mdx +1 -1
  398. package/docs/integration-patterns.md +3 -3
  399. package/docs/multi-agent-testing.md +1 -1
  400. package/docs/packages/babel-plugin.mdx +1 -1
  401. package/docs/packages/browser.mdx +2 -2
  402. package/docs/packages/react.mdx +2 -2
  403. package/docs/packages/tauri.mdx +1 -1
  404. package/docs/packages/vite-plugin.mdx +2 -2
  405. package/docs/packages.mdx +9 -9
  406. package/docs/predicates.mdx +176 -7
  407. package/docs/quickstart.mdx +2 -2
  408. package/docs/recipes.mdx +2 -2
  409. package/docs/state-management.mdx +2 -2
  410. package/docs/system-map.md +1 -1
  411. package/docs/telemetry.md +1 -1
  412. package/docs/token-efficiency.md +3 -3
  413. package/docs/tools/act-and-wait.mdx +1 -0
  414. package/docs/tools/act-sequence.mdx +1 -1
  415. package/docs/tools/console.mdx +1 -1
  416. package/docs/tools/inspect.mdx +1 -1
  417. package/docs/tools/network.mdx +1 -1
  418. package/docs/tools/query.mdx +11 -11
  419. package/docs/tools/sessions.mdx +1 -1
  420. package/docs/tools/snapshot.mdx +1 -1
  421. package/docs/tools/state.mdx +1 -1
  422. package/docs/tools/wait-for.mdx +1 -1
  423. package/docs/usage.md +8 -7
  424. package/docs/use-cases.mdx +157 -0
  425. package/docs/vs-screenshots.mdx +3 -3
  426. package/docs/what-is-recorded.md +62 -0
  427. package/docs/what-is-reticle.mdx +17 -20
  428. package/docs/why-reticle.mdx +14 -21
  429. package/package.json +11 -10
  430. package/dist/judgement/capsule/minimize.d.ts +0 -12
  431. package/dist/judgement/capsule/minimize.js +0 -22
  432. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  433. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
  434. package/dist/judgement/phenomena/phenomena.d.ts +0 -26
  435. package/dist/judgement/phenomena/phenomena.js +0 -101
  436. package/dist/language/flows/flow-report.d.ts +0 -22
  437. package/dist/language/flows/flow-report.js +0 -106
@@ -1,4 +1,4 @@
1
- import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICATE_PREFIX, flowExpectHasConsequence, flowExpectIsPresenceOnly, } from '@reticlehq/core';
1
+ import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICATE_PREFIX, flowExpectHasConsequence, flowExpectIsPresenceOnly, PredicateKind, } from '@reticlehq/core';
2
2
  /**
3
3
  * The PURE compiler at the heart of the annotation facet. A structured annotation +
4
4
  * the current captured-step count → an AnnotateOutcome (a result envelope + the patch to apply to
@@ -14,35 +14,71 @@ import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICA
14
14
  * compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
15
15
  * rejects it upstream; the tool maps that to UNKNOWN_KIND). No NL parser exists or is faked.
16
16
  */
17
+ /**
18
+ * A network annotation as a predicate, `count` included.
19
+ *
20
+ * Written once because it is compiled on two paths — a step gate and a flow's success oracle — and
21
+ * the first version of this migration wrote it twice and dropped `count` from both. The schema's own
22
+ * comment says why that matters: presence says the request fired, cardinality catches the
23
+ * double-submit that fired it twice, and `count` is the point of the annotation.
24
+ *
25
+ * A counted assertion also gates on `settled`. A wait-until-true evaluator satisfies `count: 1` the
26
+ * instant the FIRST request lands, which is before a duplicate arrives — so without the gate the
27
+ * guard passes on exactly the bug it exists to catch.
28
+ */
29
+ function netPredicate(net) {
30
+ const clause = {
31
+ kind: PredicateKind.NET,
32
+ };
33
+ if (net.method !== undefined)
34
+ clause.method = net.method;
35
+ if (net.urlContains !== undefined)
36
+ clause.urlContains = net.urlContains;
37
+ if (net.status !== undefined)
38
+ clause.status = net.status;
39
+ if (net.count === undefined)
40
+ return clause;
41
+ clause.count = net.count;
42
+ return { kind: PredicateKind.ALL_OF, predicates: [{ kind: PredicateKind.SETTLED }, clause] };
43
+ }
17
44
  export function compileAnnotation(a, stepCount) {
18
45
  switch (a.kind) {
19
46
  case AnnotationKind.ASSERT_SIGNAL: {
20
47
  if (0 === stepCount)
21
48
  return noStep();
22
- const expect = { signal: a.name };
49
+ const signal = {
50
+ kind: PredicateKind.SIGNAL,
51
+ name: a.name,
52
+ };
23
53
  if (a.dataMatches !== undefined)
24
- expect.signalData = a.dataMatches;
25
- return stepPatch(a, stepCount, expect);
54
+ signal.dataMatches = a.dataMatches;
55
+ return stepPatch(a, stepCount, signal);
26
56
  }
27
57
  case AnnotationKind.ASSERT_VISIBLE: {
28
58
  if (0 === stepCount)
29
59
  return noStep();
30
- return stepPatch(a, stepCount, { element: { testid: a.testid } });
60
+ return stepPatch(a, stepCount, {
61
+ kind: PredicateKind.ELEMENT,
62
+ query: { testid: a.testid },
63
+ });
31
64
  }
32
65
  case AnnotationKind.ASSERT_STATE: {
33
66
  if (0 === stepCount)
34
67
  return noStep();
35
- const state = { path: a.statePath };
68
+ const state = {
69
+ kind: PredicateKind.STATE,
70
+ path: a.statePath,
71
+ };
36
72
  if (a.store !== undefined)
37
73
  state.store = a.store;
38
74
  if (a.equals !== undefined)
39
75
  state.equals = a.equals;
40
- return stepPatch(a, stepCount, { state });
76
+ return stepPatch(a, stepCount, state);
41
77
  }
42
78
  case AnnotationKind.ASSERT_NET: {
43
79
  if (0 === stepCount)
44
80
  return noStep();
45
- return stepPatch(a, stepCount, { net: a.net });
81
+ return stepPatch(a, stepCount, netPredicate(a.net));
46
82
  }
47
83
  case AnnotationKind.MARK_DYNAMIC:
48
84
  return {
@@ -53,39 +89,51 @@ export function compileAnnotation(a, stepCount) {
53
89
  // Precedence: signal > state > net > console > testid (a consequence end-condition beats a
54
90
  // presence check). None of them → MISSING_FIELD.
55
91
  if (a.signal !== undefined) {
56
- return flowSuccess(a, { signal: a.signal });
92
+ return flowSuccess(a, { kind: PredicateKind.SIGNAL, name: a.signal });
57
93
  }
58
94
  if (a.statePath !== undefined) {
59
- const state = { path: a.statePath };
95
+ const state = {
96
+ kind: PredicateKind.STATE,
97
+ path: a.statePath,
98
+ };
60
99
  if (a.store !== undefined)
61
100
  state.store = a.store;
62
101
  if (a.equals !== undefined)
63
102
  state.equals = a.equals;
64
- if (a.hold !== undefined)
65
- state.hold = a.hold;
66
- return flowSuccess(a, { state });
103
+ // `hold` was a v1 flag meaning "this must STILL be true after the page settles". A predicate
104
+ // says that by composing: the settle gate is a clause, not a boolean on another clause.
105
+ return flowSuccess(a, true === a.hold
106
+ ? { kind: PredicateKind.ALL_OF, predicates: [{ kind: PredicateKind.SETTLED }, state] }
107
+ : state);
67
108
  }
68
109
  if (a.net !== undefined) {
69
- return flowSuccess(a, { net: a.net });
110
+ return flowSuccess(a, netPredicate(a.net));
70
111
  }
71
112
  if (a.console !== undefined) {
72
- return flowSuccess(a, { console: a.console });
113
+ return flowSuccess(a, {
114
+ kind: PredicateKind.CONSOLE,
115
+ ...(a.console.level === undefined ? {} : { level: a.console.level }),
116
+ ...(a.console.absent === undefined ? {} : { absent: a.console.absent }),
117
+ });
73
118
  }
74
119
  if (a.testid !== undefined) {
75
- return flowSuccess(a, { element: { testid: a.testid } });
120
+ return flowSuccess(a, { kind: PredicateKind.ELEMENT, query: { testid: a.testid } });
76
121
  }
77
122
  // Last, and above only MISSING_FIELD. An element is a locator and text is content; where a
78
123
  // caller offered both, the locator is the more stable anchor. But text beats nothing at all,
79
124
  // which is what an app whose only observable is a rendered value used to get (#811).
80
125
  if (a.text !== undefined) {
81
- const text = { contains: a.text.contains };
126
+ const text = {
127
+ kind: PredicateKind.TEXT,
128
+ contains: a.text.contains,
129
+ };
82
130
  if (a.text.scope !== undefined)
83
131
  text.scope = a.text.scope;
84
132
  if (a.text.absent !== undefined)
85
133
  text.absent = a.text.absent;
86
134
  if (a.text.visible !== undefined)
87
135
  text.visible = a.text.visible;
88
- return flowSuccess(a, { text });
136
+ return flowSuccess(a, text);
89
137
  }
90
138
  return { result: { ok: false, code: AnnotationErrorCode.MISSING_FIELD } };
91
139
  }
@@ -22,3 +22,12 @@ export interface AffectedResult {
22
22
  * a repo-relative change matches an absolute stamp and vice-versa.
23
23
  */
24
24
  export declare function affectedFlows(flows: readonly FlowSources[], changedFiles: readonly string[]): AffectedResult;
25
+ export declare function isInteractiveSource(text: string): boolean;
26
+ /**
27
+ * Changed interactive components that no saved flow's manifest names.
28
+ *
29
+ * `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
30
+ * built only on it passed a brand-new feature that had never been driven. This starts from the
31
+ * change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
32
+ */
33
+ export declare function unflowedFiles(flows: readonly FlowSources[], changedFiles: readonly string[], isInteractive: (file: string) => boolean): string[];
@@ -33,3 +33,38 @@ export function affectedFlows(flows, changedFiles) {
33
33
  }
34
34
  return { affected, unknownProvenance };
35
35
  }
36
+ /** A file a source stamper can put on an element — the only files a flow's manifest can ever name. */
37
+ const COMPONENT_FILE = /\.(?:jsx|tsx|vue|svelte|astro)$/;
38
+ /** Same test-file rule the stampers use, so a test is never demanded a flow. */
39
+ const TEST_FILE = /(?:\.(?:test|spec)\.[^./]+$)|(?:^|\/)__tests__\//;
40
+ /**
41
+ * Something a step could act on: a handler prop (`onClick=`, `@click`, `v-on:`, `on:submit`) or a
42
+ * native control.
43
+ *
44
+ * ponytail: a text heuristic, not a parse. A flow's manifest only names the file of the element a
45
+ * step ACTED on, so a display-only component can never be attributed and demanding a flow for one
46
+ * would block forever. A handler passed in from a parent under another name escapes it; upgrade to
47
+ * recording the source of the element an `expect` matched if that proves common.
48
+ */
49
+ const INTERACTIVE = /\bon[A-Z]\w*=|@[a-z]+[=.]|\bv-on:|\bon:[a-z]+|<(?:button|input|select|textarea|form|a|summary)[\s>/]/;
50
+ export function isInteractiveSource(text) {
51
+ return INTERACTIVE.test(text);
52
+ }
53
+ /**
54
+ * Changed interactive components that no saved flow's manifest names.
55
+ *
56
+ * `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
57
+ * built only on it passed a brand-new feature that had never been driven. This starts from the
58
+ * change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
59
+ */
60
+ export function unflowedFiles(flows, changedFiles, isInteractive) {
61
+ const sources = flows.flatMap((flow) => (flow.sources ?? []).map(normalize));
62
+ return changedFiles.filter((file) => {
63
+ const c = normalize(file);
64
+ if (!COMPONENT_FILE.test(c) || TEST_FILE.test(c))
65
+ return false;
66
+ if (sources.some((s) => c === s || c.endsWith(`/${s}`) || s.endsWith(`/${c}`)))
67
+ return false;
68
+ return isInteractive(file);
69
+ });
70
+ }
@@ -22,6 +22,16 @@ interface GateInput {
22
22
  downgraded?: readonly DowngradedFlow[];
23
23
  /** Flows that covered a changed file but no longer exist — coverage deleted rather than satisfied. */
24
24
  deleted?: readonly string[];
25
+ /** Changed interactive components no saved flow touches — a change nothing has driven. */
26
+ unflowed?: readonly string[];
27
+ /** Coverage levels below the best they ever reached — something new appeared, nothing covered it. */
28
+ coverageRegressed?: readonly {
29
+ level: string;
30
+ was: number;
31
+ now: number;
32
+ }[];
33
+ /** Changed files the browser loaded and never ran a function of. */
34
+ unexecuted?: readonly string[];
25
35
  }
26
36
  interface GateResult {
27
37
  /** True when every affected, non-flaky flow has a passing artifact AND nothing was weakened/deleted. */
@@ -34,6 +44,16 @@ interface GateResult {
34
44
  downgraded: DowngradedFlow[];
35
45
  /** Deleted flows that covered changed files — BLOCKING for the same reason. */
36
46
  deleted: string[];
47
+ /** Changed interactive components no flow covers — BLOCKING: the change was never driven. */
48
+ unflowed: string[];
49
+ /** BLOCKING: the app grew something nothing has covered. */
50
+ coverageRegressed: {
51
+ level: string;
52
+ was: number;
53
+ now: number;
54
+ }[];
55
+ /** BLOCKING: changed code that never ran in any drive. */
56
+ unexecuted: string[];
37
57
  }
38
58
  export declare function gateDecision(input: GateInput): GateResult;
39
59
  export {};
@@ -23,11 +23,22 @@ export function gateDecision(input) {
23
23
  // trivially gameable, which is the exact failure this gate exists to prevent.
24
24
  const downgraded = [...(input.downgraded ?? [])];
25
25
  const deleted = [...(input.deleted ?? [])];
26
+ const unflowed = [...(input.unflowed ?? [])];
27
+ const coverageRegressed = [...(input.coverageRegressed ?? [])];
28
+ const unexecuted = [...(input.unexecuted ?? [])];
26
29
  return {
27
- pass: 0 === uncovered.length && 0 === downgraded.length && 0 === deleted.length,
30
+ pass: 0 === uncovered.length &&
31
+ 0 === downgraded.length &&
32
+ 0 === deleted.length &&
33
+ 0 === unflowed.length &&
34
+ 0 === coverageRegressed.length &&
35
+ 0 === unexecuted.length,
28
36
  uncovered,
29
37
  quarantined,
30
38
  downgraded,
31
39
  deleted,
40
+ unflowed,
41
+ coverageRegressed,
42
+ unexecuted,
32
43
  };
33
44
  }
@@ -28,6 +28,18 @@ export declare function nearestTestid(missing: string, present: string[]): strin
28
28
  export declare function nearestIsAmbiguous(missing: string, present: string[]): boolean;
29
29
  /** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
30
30
  export declare function testidDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
31
+ /**
32
+ * Build the drift for a step whose anchor matched MORE THAN ONE live element.
33
+ *
34
+ * `ambiguous: true` is set because that is exactly what it is, and it is the field heal already
35
+ * reads to refuse an auto-rebind -- an ambiguous drift must never be healed to an arbitrary pick,
36
+ * which is the same reasoning that made this a drift instead of a note in the first place.
37
+ *
38
+ * `nearest` is null on purpose. There is no nearest: the anchor was found, several times over, and
39
+ * proposing one of the matches as "the" match would re-make the guess this reason exists to stop.
40
+ * The fix is a NARROWER anchor, and only a human or an agent looking at the page can choose it.
41
+ */
42
+ export declare function ambiguousAnchorDrift(value: string, matches: number): Drift;
31
43
  /**
32
44
  * Build the drift for a step whose `expect.element` testid was absent after the action ran.
33
45
  *
@@ -139,6 +139,26 @@ export function testidDrift(value, hint) {
139
139
  drift.ambiguous = true;
140
140
  return drift;
141
141
  }
142
+ /**
143
+ * Build the drift for a step whose anchor matched MORE THAN ONE live element.
144
+ *
145
+ * `ambiguous: true` is set because that is exactly what it is, and it is the field heal already
146
+ * reads to refuse an auto-rebind -- an ambiguous drift must never be healed to an arbitrary pick,
147
+ * which is the same reasoning that made this a drift instead of a note in the first place.
148
+ *
149
+ * `nearest` is null on purpose. There is no nearest: the anchor was found, several times over, and
150
+ * proposing one of the matches as "the" match would re-make the guess this reason exists to stop.
151
+ * The fix is a NARROWER anchor, and only a human or an agent looking at the page can choose it.
152
+ */
153
+ export function ambiguousAnchorDrift(value, matches) {
154
+ return {
155
+ reasonKind: DriftReason.ANCHOR_AMBIGUOUS,
156
+ reason: `testid "${value}" matched ${String(matches)} live elements — which one the recording meant is not decidable; anchor this step to something unique`,
157
+ anchor: value,
158
+ nearest: null,
159
+ ambiguous: true,
160
+ };
161
+ }
142
162
  /**
143
163
  * Build the drift for a step whose `expect.element` testid was absent after the action ran.
144
164
  *
@@ -0,0 +1,7 @@
1
+ import type { FlowFile } from '@reticlehq/core';
2
+ /**
3
+ * Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
4
+ * to Reticle on this machine. A key handed over in the environment names no person, so none is
5
+ * written — the cloud can still attribute the push by the key that sent it.
6
+ */
7
+ export declare function flowAuthor(env?: NodeJS.ProcessEnv, home?: string): FlowFile['author'];
@@ -0,0 +1,19 @@
1
+ import { homedir } from 'node:os';
2
+ import { mcpClientIdentity } from '../../surface/mcp/peer/client-identity.js';
3
+ import { readAccountState } from '../../memory/cloud/account-state.js';
4
+ /**
5
+ * Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
6
+ * to Reticle on this machine. A key handed over in the environment names no person, so none is
7
+ * written — the cloud can still attribute the push by the key that sent it.
8
+ */
9
+ export function flowAuthor(env = process.env, home = homedir()) {
10
+ const agent = mcpClientIdentity().name;
11
+ const account = readAccountState(home, env);
12
+ const person = account.signedIn ? account.email : undefined;
13
+ if (agent === undefined && person === undefined)
14
+ return undefined;
15
+ return {
16
+ ...(agent === undefined ? {} : { agent }),
17
+ ...(person === undefined ? {} : { person }),
18
+ };
19
+ }
@@ -18,9 +18,8 @@ export declare const FlowParseNote: {
18
18
  readonly NOT_JSON: "flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save";
19
19
  readonly MALFORMED: "flow file is malformed — fix or regenerate it with reticle_flow_save";
20
20
  readonly UNSUPPORTED_SHAPE: "valid JSON, unsupported expect shape";
21
- readonly UNENFORCED: "valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)";
22
21
  };
23
- export type CoerceExpectResult = {
22
+ type CoerceExpectResult = {
24
23
  ok: true;
25
24
  value: unknown;
26
25
  } | {
@@ -31,3 +30,4 @@ export declare function coerceFlowExpect(raw: unknown): CoerceExpectResult;
31
30
  export declare function coerceFlowFileExpects(raw: unknown): CoerceExpectResult;
32
31
  export declare function describeFlowZodFailure(error: ZodError): string;
33
32
  export declare function parseFlowFileText(text: string): FlowResult<FlowFile>;
33
+ export {};
@@ -11,14 +11,11 @@
11
11
  * re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
12
12
  * to an empty expect that would grade asserted-while-unchecked.
13
13
  */
14
- import { FlowErrorCode, FlowFileSchema } from '@reticlehq/core';
15
- import { PredicateSchema } from '@reticlehq/engine/question/predicate/predicate.js';
16
- import { predicateToExpect } from '../../judgement/outcome/predicate-to-expect.js';
14
+ import { FlowExpectSchema, FlowPredicateSchema, READABLE_FLOW_VERSIONS, FlowErrorCode, FlowFileSchema, } from '@reticlehq/core';
17
15
  export const FlowParseNote = {
18
16
  NOT_JSON: 'flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save',
19
17
  MALFORMED: 'flow file is malformed — fix or regenerate it with reticle_flow_save',
20
18
  UNSUPPORTED_SHAPE: 'valid JSON, unsupported expect shape',
21
- UNENFORCED: 'valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)',
22
19
  };
23
20
  function isRecord(value) {
24
21
  return 'object' === typeof value && null !== value && !Array.isArray(value);
@@ -43,20 +40,44 @@ function flattenSignalObject(raw) {
43
40
  }
44
41
  export function coerceFlowExpect(raw) {
45
42
  if (isRecord(raw) && 'kind' in raw) {
46
- const parsed = PredicateSchema.safeParse(raw);
43
+ const parsed = FlowPredicateSchema.safeParse(raw);
47
44
  if (!parsed.success) {
48
45
  return { ok: false, detail: describePredicateExpectFailure(parsed.error) };
49
46
  }
50
- const expect = predicateToExpect(parsed.data);
51
- if (undefined === expect)
52
- return { ok: false, detail: FlowParseNote.UNENFORCED };
53
- return { ok: true, value: expect };
47
+ // Kept AS the predicate. There is nothing to convert to any more, so nothing to refuse for
48
+ // being inexpressible — the shape an agent wrote is the shape the file holds.
49
+ return { ok: true, value: parsed.data };
54
50
  }
55
51
  if (!isRecord(raw))
56
52
  return { ok: true, value: raw };
57
- return { ok: true, value: flattenSignalObject(raw) };
53
+ const flattened = flattenSignalObject(raw);
54
+ /*
55
+ * A v1 expect is validated HERE so a misspelling is named.
56
+ *
57
+ * The schema accepts both formats as a union, and a union rejection reports every branch's
58
+ * complaint at once — so `{ signal: 'x', signl: 'typo' }` came back as "flow file is malformed"
59
+ * without the word `signl` in it. The file was still refused, which is the half that matters, but
60
+ * the reader was left to find a one-character typo by eye. Checking the v1 shape on its own, on
61
+ * the path that already knows this is a v1 expect, puts the key back in the message.
62
+ */
63
+ const asV1 = FlowExpectSchema.safeParse(flattened);
64
+ if (!asV1.success) {
65
+ return { ok: false, detail: describePredicateExpectFailure(asV1.error) };
66
+ }
67
+ return { ok: true, value: flattened };
58
68
  }
59
69
  function describePredicateExpectFailure(error) {
70
+ const refused = error.issues.find((one) => 'custom' === one.code);
71
+ if (refused !== undefined)
72
+ return `${FlowParseNote.UNSUPPORTED_SHAPE}: ${refused.message}`;
73
+ // An unrecognized key FIRST: a strict object reports it on the object's own path with the names
74
+ // in `keys`, so reading `path[0]` misses the one thing worth saying. A misspelling is the failure
75
+ // this whole check exists to catch, and naming it is the difference between a fix and a hunt.
76
+ const unknownKey = error.issues.find((one) => 'unrecognized_keys' === one.code);
77
+ if (unknownKey !== undefined && 'unrecognized_keys' === unknownKey.code) {
78
+ const named = unknownKey.keys.map((k) => `"${k}"`).join(', ');
79
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} key ${named}`;
80
+ }
60
81
  const issue = error.issues[0];
61
82
  if (undefined === issue)
62
83
  return FlowParseNote.UNSUPPORTED_SHAPE;
@@ -102,8 +123,48 @@ export function coerceFlowFileExpects(raw) {
102
123
  }
103
124
  return { ok: true, value: next };
104
125
  }
126
+ /**
127
+ * The issues a union rejection buries.
128
+ *
129
+ * `expect` accepts a predicate OR a v1 expect, and zod reports a union failure as one
130
+ * `invalid_union` issue whose branch errors live inside `unionErrors`. The one that matters is the
131
+ * v1 branch's `unrecognized_keys` — the misspelled field. Without this the file was still correctly
132
+ * REFUSED and the message said only "malformed", which sends the author back to hunt a
133
+ * one-character typo we had already identified.
134
+ */
135
+ function flatten(issues) {
136
+ return issues.flatMap((issue) => 'invalid_union' === issue.code
137
+ ? [issue, ...flatten(issue.unionErrors.flatMap((e) => e.issues))]
138
+ : [issue]);
139
+ }
105
140
  export function describeFlowZodFailure(error) {
106
- const issue = error.issues[0];
141
+ const all = flatten(error.issues);
142
+ /*
143
+ * The most SPECIFIC complaint wins, wherever in the union it was reported.
144
+ *
145
+ * An unrecognized key names the typo. Failing that, an issue whose path reaches INTO an expect
146
+ * names the field that was the wrong type — `{ signal: { count: 1 } }`, a signal object with no
147
+ * name, is reported on `steps.0.expect.signal` and that path is the whole answer. The union's own
148
+ * top-level `invalid_union` says only "malformed", which is true and useless.
149
+ */
150
+ const named = (one) => {
151
+ // NOT the discriminator complaint. `invalid_union_discriminator` on `expect.kind` is the
152
+ // PREDICATE branch objecting that a v1 expect has no `kind` — which is true of every v1 expect
153
+ // ever written and tells the author nothing. The useful issue is the one from the branch they
154
+ // were actually writing.
155
+ if ('invalid_union_discriminator' === one.code)
156
+ return false;
157
+ return (('steps' === one.path[0] && 'expect' === one.path[2] && one.path.length > 3) ||
158
+ ('success' === one.path[0] && one.path.length > 1));
159
+ };
160
+ // A refinement carries its own reason (a session ref, named by field), and that reason IS the fix.
161
+ const refused = all.find((one) => 'custom' === one.code);
162
+ if (refused !== undefined && 'steps' === refused.path[0] && 'number' === typeof refused.path[1]) {
163
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(refused.path[1])}: ${refused.message}`;
164
+ }
165
+ if (refused !== undefined)
166
+ return `${FlowParseNote.UNSUPPORTED_SHAPE}: ${refused.message}`;
167
+ const issue = all.find((one) => 'unrecognized_keys' === one.code) ?? all.find(named) ?? error.issues[0];
107
168
  if (undefined === issue)
108
169
  return FlowParseNote.MALFORMED;
109
170
  const path = issue.path;
@@ -139,6 +200,26 @@ export function parseFlowFileText(text) {
139
200
  catch {
140
201
  return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: FlowParseNote.NOT_JSON };
141
202
  }
203
+ /*
204
+ * Version BEFORE schema. When the format does not match, nothing else in the file can be trusted
205
+ * to mean what this reader thinks it means, so the first unrelated field that happens to fail
206
+ * would be noise about the wrong thing -- and `version` is a `z.literal`, so the schema's own
207
+ * complaint about it is indistinguishable from a typo.
208
+ *
209
+ * Only a NUMBER that is not ours counts. A missing or non-numeric `version` is a damaged field
210
+ * rather than a different format, and stays PARSE_FAILED.
211
+ */
212
+ const declared = isRecord(parsed) ? parsed['version'] : undefined;
213
+ if ('number' === typeof declared && !READABLE_FLOW_VERSIONS.has(declared)) {
214
+ return {
215
+ ok: false,
216
+ code: FlowErrorCode.WRONG_VERSION,
217
+ detail: `this flow file is version ${String(declared)} and this Reticle reads versions ` +
218
+ `${[...READABLE_FLOW_VERSIONS].join(', ')}. The file is not damaged, the reader is the ` +
219
+ 'wrong one. ' +
220
+ 'Upgrade or downgrade Reticle rather than editing the flow.',
221
+ };
222
+ }
142
223
  const coerced = coerceFlowFileExpects(parsed);
143
224
  if (!coerced.ok) {
144
225
  return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: coerced.detail };
@@ -6,7 +6,7 @@ import { classifyFlowAssertions } from './flow-classify.js';
6
6
  *
7
7
  * A replay used to report only in the language of the DOM: step 3 drifted, this assertion failed.
8
8
  * True, actionable, and illegible — the reader still had to reconstruct what stopped working. The
9
- * ledger in `.reticle/intent.json` already holds the other half (prose statements, declared → bound
9
+ * ledger in `.reticle/intent/` already holds the other half (prose statements, declared → bound
10
10
  * → proved, and the verdict that discharged each one), so a flow does not need its own notion of
11
11
  * intent. It needs an id.
12
12
  *
@@ -0,0 +1,54 @@
1
+ import { type FlowFile, type FlowStep, type Predicate } from '@reticlehq/core';
2
+ /** A short, stable identity for a journey's shape: its start page and its steps. */
3
+ export declare function journeyFingerprint(flow: Pick<FlowFile, 'startPath' | 'steps'>): string;
4
+ /**
5
+ * Fold two copies of one journey into one file.
6
+ *
7
+ * Gap-filling, never overwriting: whatever the kept flow already says — its name, its intent, a
8
+ * step's declared consequence, a quarantine a person wrote — survives, and the other copy only
9
+ * supplies what is missing. So a merge can add an assertion or an intent and can never remove one.
10
+ */
11
+ export declare function mergeJourney(kept: FlowFile, other: FlowFile): FlowFile;
12
+ /**
13
+ * The name a new journey saves under.
14
+ *
15
+ * Its readable name when that is free or already this journey's; otherwise the same name with the
16
+ * fingerprint's head, so two different journeys that share an intent never overwrite each other.
17
+ */
18
+ export declare function journeyName(base: string, fingerprint: string, taken: ReadonlyMap<string, string>): string;
19
+ /**
20
+ * What must hold before a journey and what it leaves behind, read from where it started and ended.
21
+ *
22
+ * Routes only: the page is the one piece of state every recorded step knows for certain, and a
23
+ * claim written from a guess about the rest would be a precondition nobody met. Written in the
24
+ * exact form `canFollow` compares by value, so A-then-B is answerable from the two files alone.
25
+ */
26
+ export declare function routeClaims(startPath: string | undefined, steps: readonly FlowStep[]): {
27
+ requires?: Predicate[];
28
+ ensures?: Predicate[];
29
+ };
30
+ /**
31
+ * The preconditions only another flow can establish.
32
+ *
33
+ * A route claim on the page the flow starts on is not one of them: replay navigates there itself, so
34
+ * it neither opts the flow out of the reset nor gates the run. That is the claim a saved drive writes
35
+ * for the map (see routeClaims) — reading it as "starts from leftover state" would stop every
36
+ * auto-saved flow from ever being reset.
37
+ */
38
+ export declare function establishedState(flow: FlowFile): readonly Predicate[];
39
+ /**
40
+ * Is the tab where the flow asked to start? Up to a trailing slash, and up to the query the flow
41
+ * did not ask about.
42
+ *
43
+ * `startPath` is the SPECIFICATION, so it decides what counts. A query it recorded is compared:
44
+ * `?tab=wrap` and `?tab=summary` are different pages, and a replay that starts on the wrong one
45
+ * proves nothing about the right one. A query it did NOT record is ignored: the tab carrying
46
+ * `?next=%2F` on a login page, or the identity params Reticle puts on a leased tab, are not the flow
47
+ * being elsewhere, and navigating to strip them costs a session for nothing.
48
+ *
49
+ * The asymmetry is the whole point and the reason `observed` and `expected` are named rather than
50
+ * `a` and `b`. Comparing with the query on both sides always re-navigated a query-bearing
51
+ * `startPath` (#1059, which killed the session mid-flow); comparing with it on neither side reads a
52
+ * tab on `?tab=summary` as already at `?tab=wrap`.
53
+ */
54
+ export declare function samePath(observed: string, expected: string): boolean;