@reticlehq/server 3.3.0 → 3.5.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 (326) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  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 +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  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 +6 -2
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  17. package/dist/command/cli/cli-doctor.js +7 -5
  18. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  19. package/dist/command/cli/cli-flow-commands.js +67 -4
  20. package/dist/command/cli/cli-parse.d.ts +8 -1
  21. package/dist/command/cli/cli-parse.js +24 -4
  22. package/dist/command/cli/cli-update-commands.js +8 -1
  23. package/dist/command/cli/cli-verify.d.ts +24 -3
  24. package/dist/command/cli/cli-verify.js +54 -11
  25. package/dist/command/cli/cloud-cli.js +64 -2
  26. package/dist/command/cli/cloud-kit.js +3 -5
  27. package/dist/command/cli/cloud-login.d.ts +1 -1
  28. package/dist/command/cli/cloud-login.js +27 -8
  29. package/dist/command/cli/demo/demo-page.js +6 -3
  30. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  31. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  32. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  33. package/dist/command/cli/launch/cli-launch.js +21 -9
  34. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  35. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  36. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  37. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  38. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  39. package/dist/command/cli/setup-mcp-cli.js +21 -13
  40. package/dist/command/cli/tutorial.d.ts +2 -2
  41. package/dist/command/cli/tutorial.js +21 -7
  42. package/dist/command/cli.js +79 -11
  43. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  44. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  45. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  46. package/dist/command/daemon/daemon-resolve.js +1 -1
  47. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  48. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  49. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  50. package/dist/command/dev/stale-issue-guard.js +8 -8
  51. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  52. package/dist/command/setup/bringup/connect-lease.js +38 -0
  53. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  54. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  55. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  56. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  57. package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
  58. package/dist/command/setup/bringup/dev-server-wait.js +1 -1
  59. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  60. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  61. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  62. package/dist/command/setup/bringup/static-page-server.js +151 -0
  63. package/dist/command/setup/desktop-shape.d.ts +4 -0
  64. package/dist/command/setup/desktop-shape.js +18 -2
  65. package/dist/command/setup/init/init-port.d.ts +15 -0
  66. package/dist/command/setup/init/init-port.js +44 -0
  67. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  68. package/dist/command/setup/init/init-runtime.js +91 -9
  69. package/dist/command/setup/node-effects.d.ts +27 -14
  70. package/dist/command/setup/node-effects.js +86 -45
  71. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  72. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  73. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  74. package/dist/command/setup/probe/page-probe.js +18 -3
  75. package/dist/command/setup/run-setup.d.ts +28 -1
  76. package/dist/command/setup/run-setup.js +103 -15
  77. package/dist/command/setup/session-pick.d.ts +13 -0
  78. package/dist/command/setup/session-pick.js +20 -0
  79. package/dist/command/setup/setup-command.d.ts +2 -14
  80. package/dist/command/setup/setup-command.js +105 -31
  81. package/dist/command/setup/setup-mcp.d.ts +2 -0
  82. package/dist/command/setup/setup-mcp.js +17 -8
  83. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  84. package/dist/command/setup/terminal/wait-progress.js +17 -0
  85. package/dist/command/version/peer-announce.d.ts +6 -2
  86. package/dist/command/version/peer-announce.js +8 -6
  87. package/dist/command/version/version-nudge.d.ts +17 -3
  88. package/dist/command/version/version-nudge.js +25 -4
  89. package/dist/features/crawl/crawl-empty.js +1 -1
  90. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  91. package/dist/features/crawl/crawl-tools.js +57 -0
  92. package/dist/features/crawl/crawl.js +171 -157
  93. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  94. package/dist/features/exhaust/app-coverage.js +31 -0
  95. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  96. package/dist/features/exhaust/code-coverage.js +77 -0
  97. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  98. package/dist/features/exhaust/exhaust-run.js +82 -0
  99. package/dist/features/exhaust/explorer.d.ts +103 -0
  100. package/dist/features/exhaust/explorer.js +195 -0
  101. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  102. package/dist/features/exhaust/failure-branches.js +92 -0
  103. package/dist/features/exhaust/ledger.d.ts +202 -0
  104. package/dist/features/exhaust/ledger.js +252 -0
  105. package/dist/features/exhaust/session-fold.d.ts +31 -0
  106. package/dist/features/exhaust/session-fold.js +79 -0
  107. package/dist/features/exhaust/session-port.d.ts +36 -0
  108. package/dist/features/exhaust/session-port.js +102 -0
  109. package/dist/features/harness/drive-report.d.ts +0 -9
  110. package/dist/features/harness/drive-report.js +18 -0
  111. package/dist/features/harness/driver.js +4 -7
  112. package/dist/features/harness/fill-values.d.ts +10 -0
  113. package/dist/features/harness/fill-values.js +50 -11
  114. package/dist/features/harness/jev-driver.js +30 -13
  115. package/dist/features/harness/openai-driver.d.ts +12 -0
  116. package/dist/features/harness/openai-driver.js +21 -12
  117. package/dist/features/harness/platform-config.js +5 -6
  118. package/dist/features/license/license.d.ts +2 -2
  119. package/dist/index.d.ts +9 -0
  120. package/dist/index.js +56 -19
  121. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  122. package/dist/judgement/domain/domain-model.js +3 -1
  123. package/dist/judgement/oracles/self-instrument.js +4 -2
  124. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  125. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  126. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  127. package/dist/judgement/runs/verification-sync.js +1 -1
  128. package/dist/language/flows/change/affected.d.ts +9 -0
  129. package/dist/language/flows/change/affected.js +35 -0
  130. package/dist/language/flows/change/gate.d.ts +20 -0
  131. package/dist/language/flows/change/gate.js +12 -1
  132. package/dist/language/flows/flow-author.d.ts +7 -0
  133. package/dist/language/flows/flow-author.js +19 -0
  134. package/dist/language/flows/flow-expect-grammar.js +1 -1
  135. package/dist/language/flows/flow-journey.d.ts +54 -0
  136. package/dist/language/flows/flow-journey.js +119 -0
  137. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  138. package/dist/language/flows/flow-replay-run.js +13 -47
  139. package/dist/language/flows/flow-replay.d.ts +22 -8
  140. package/dist/language/flows/flow-replay.js +55 -9
  141. package/dist/language/flows/flow-result.d.ts +2 -0
  142. package/dist/language/flows/flow-result.js +25 -0
  143. package/dist/language/flows/flow-tools.js +17 -5
  144. package/dist/language/flows/flows.d.ts +10 -1
  145. package/dist/language/flows/flows.js +90 -14
  146. package/dist/language/flows/recording/replay-halt.js +3 -1
  147. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  148. package/dist/language/flows/recording/tape/recordings.js +55 -1
  149. package/dist/language/flows/replay.d.ts +19 -0
  150. package/dist/language/flows/replay.js +42 -6
  151. package/dist/language/flows/verify-change-tools.js +5 -2
  152. package/dist/launch-chromium.d.ts +80 -0
  153. package/dist/launch-chromium.js +218 -0
  154. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  155. package/dist/memory/cloud/cloud-config.js +47 -7
  156. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  157. package/dist/memory/cloud/cloud-sync.js +6 -9
  158. package/dist/memory/cloud/harness-offer.js +5 -6
  159. package/dist/memory/cloud/harness-switch.js +5 -6
  160. package/dist/memory/cloud/sync-cycle.d.ts +77 -3
  161. package/dist/memory/cloud/sync-cycle.js +325 -55
  162. package/dist/memory/cloud/sync-daemon.js +24 -20
  163. package/dist/memory/cloud/sync-disk.js +16 -2
  164. package/dist/memory/intent/intent-shard.d.ts +10 -32
  165. package/dist/memory/intent/intent-shard.js +0 -10
  166. package/dist/memory/intent/intent-tools.js +8 -3
  167. package/dist/memory/journal/drive-flow.d.ts +18 -13
  168. package/dist/memory/journal/drive-flow.js +45 -37
  169. package/dist/memory/journal/envelope.d.ts +8 -8
  170. package/dist/memory/journal/journal-query.d.ts +14 -0
  171. package/dist/memory/journal/journal-query.js +15 -0
  172. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  173. package/dist/memory/journal/session-end.d.ts +9 -1
  174. package/dist/memory/journal/session-end.js +24 -12
  175. package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
  176. package/dist/memory/project/artifact-root-resolver.js +46 -19
  177. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  178. package/dist/memory/project/dir/reticle-dir.js +1 -0
  179. package/dist/memory/project/project-tools.js +3 -1
  180. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  181. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  182. package/dist/portal/bridge/bridge.d.ts +12 -3
  183. package/dist/portal/bridge/bridge.js +49 -3
  184. package/dist/portal/input/js-coverage.d.ts +25 -0
  185. package/dist/portal/input/js-coverage.js +30 -0
  186. package/dist/portal/input/real-input.d.ts +11 -0
  187. package/dist/portal/input/real-input.js +29 -4
  188. package/dist/portal/input/viewport-tools.js +16 -6
  189. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  190. package/dist/portal/pool/playwright-launcher.js +20 -13
  191. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  192. package/dist/portal/session/dev-server/dev-command.js +30 -12
  193. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  194. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  195. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  196. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  197. package/dist/portal/session/facts/observed-state.js +34 -0
  198. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  199. package/dist/portal/session/no-session-diagnosis.js +62 -5
  200. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  201. package/dist/portal/session/no-session-next-action.js +48 -9
  202. package/dist/portal/session/no-session-watch.d.ts +18 -0
  203. package/dist/portal/session/no-session-watch.js +88 -2
  204. package/dist/portal/session/session-health.js +3 -2
  205. package/dist/portal/session/session-manager.d.ts +20 -6
  206. package/dist/portal/session/session-manager.js +19 -3
  207. package/dist/portal/session/session-reaper.d.ts +28 -0
  208. package/dist/portal/session/session-reaper.js +49 -1
  209. package/dist/portal/session/session.d.ts +10 -8
  210. package/dist/portal/session/session.js +24 -32
  211. package/dist/surface/http-server.d.ts +3 -2
  212. package/dist/surface/http-server.js +8 -5
  213. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  214. package/dist/surface/mcp/mcp-proxy.js +2 -2
  215. package/dist/surface/mcp/mcp.js +5 -3
  216. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  217. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  218. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  219. package/dist/surface/mcp/server-instructions.js +9 -7
  220. package/dist/surface/tools/act/after-match.d.ts +17 -0
  221. package/dist/surface/tools/act/after-match.js +30 -0
  222. package/dist/surface/tools/act/durable.d.ts +69 -0
  223. package/dist/surface/tools/act/durable.js +139 -0
  224. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  225. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  226. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  227. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  228. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  229. package/dist/surface/tools/act/proved-controls.js +11 -0
  230. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  231. package/dist/surface/tools/act/sequence-grade.js +32 -1
  232. package/dist/surface/tools/act-capsule.d.ts +7 -0
  233. package/dist/surface/tools/act-capsule.js +46 -1
  234. package/dist/surface/tools/act-sequence-tool.js +25 -8
  235. package/dist/surface/tools/act-tools.js +28 -52
  236. package/dist/surface/tools/assert/assert-grade.js +12 -1
  237. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  238. package/dist/surface/tools/browser-tools.js +14 -2
  239. package/dist/surface/tools/coverage-tools.js +27 -1
  240. package/dist/surface/tools/dynamic-tools.js +29 -2
  241. package/dist/surface/tools/error-recovery.js +5 -6
  242. package/dist/surface/tools/feedback-invite.js +6 -4
  243. package/dist/surface/tools/harness-explore.d.ts +3 -2
  244. package/dist/surface/tools/harness-explore.js +12 -7
  245. package/dist/surface/tools/invoke-tool.js +12 -8
  246. package/dist/surface/tools/lease-tools.js +15 -6
  247. package/dist/surface/tools/live-call-text.js +4 -1
  248. package/dist/surface/tools/observe-tools.js +9 -1
  249. package/dist/surface/tools/tool-kit.d.ts +10 -0
  250. package/dist/surface/tools/tools.js +4 -0
  251. package/dist/telemetry/argument-shape.d.ts +0 -2
  252. package/dist/telemetry/argument-shape.js +1 -5
  253. package/dist/telemetry/error-class.d.ts +2 -3
  254. package/dist/telemetry/error-class.js +2 -3
  255. package/dist/telemetry/harness-drive.d.ts +0 -2
  256. package/dist/telemetry/harness-drive.js +0 -4
  257. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  258. package/dist/telemetry/onboarding-firsts.js +2 -2
  259. package/dist/wire-journal.d.ts +3 -0
  260. package/dist/wire-journal.js +4 -0
  261. package/dist/workspace-packages.d.ts +0 -2
  262. package/dist/workspace-packages.js +1 -9
  263. package/docs/README.md +4 -2
  264. package/docs/actions.mdx +3 -4
  265. package/docs/agent-cheatsheet.md +4 -4
  266. package/docs/architecture.md +6 -6
  267. package/docs/best-practices.mdx +1 -1
  268. package/docs/capabilities.mdx +32 -1
  269. package/docs/cli/cloud.mdx +20 -8
  270. package/docs/cli/doctor.mdx +4 -0
  271. package/docs/cli/feedback.mdx +2 -2
  272. package/docs/cli/gate.mdx +9 -4
  273. package/docs/cli/update.mdx +10 -0
  274. package/docs/cli.mdx +22 -7
  275. package/docs/desktop.mdx +14 -10
  276. package/docs/environment.mdx +87 -0
  277. package/docs/faq.mdx +18 -16
  278. package/docs/first-drive.md +5 -13
  279. package/docs/flows.md +21 -2
  280. package/docs/frameworks.mdx +29 -10
  281. package/docs/getting-started.md +15 -5
  282. package/docs/http-transport.md +6 -3
  283. package/docs/human-control.md +2 -2
  284. package/docs/index.mdx +16 -6
  285. package/docs/install-agentic.mdx +2 -2
  286. package/docs/install-manual.mdx +1 -1
  287. package/docs/integration-patterns.md +3 -3
  288. package/docs/multi-agent-testing.md +1 -1
  289. package/docs/packages/babel-plugin.mdx +1 -1
  290. package/docs/packages/browser.mdx +2 -2
  291. package/docs/packages/next.mdx +3 -0
  292. package/docs/packages/react.mdx +2 -2
  293. package/docs/packages/tauri.mdx +1 -1
  294. package/docs/packages/vite-plugin.mdx +4 -2
  295. package/docs/packages.mdx +9 -9
  296. package/docs/predicates.mdx +50 -5
  297. package/docs/quickstart.mdx +12 -10
  298. package/docs/recipes.mdx +2 -2
  299. package/docs/state-management.mdx +2 -2
  300. package/docs/system-map.md +1 -1
  301. package/docs/telemetry.md +1 -1
  302. package/docs/token-efficiency.md +3 -3
  303. package/docs/tools/act-and-wait.mdx +1 -0
  304. package/docs/tools/act-sequence.mdx +1 -1
  305. package/docs/tools/act.mdx +1 -1
  306. package/docs/tools/console.mdx +1 -1
  307. package/docs/tools/inspect.mdx +1 -1
  308. package/docs/tools/network.mdx +1 -1
  309. package/docs/tools/query.mdx +1 -1
  310. package/docs/tools/sessions.mdx +1 -1
  311. package/docs/tools/snapshot.mdx +1 -1
  312. package/docs/tools/state.mdx +1 -1
  313. package/docs/tools/wait-for.mdx +1 -1
  314. package/docs/usage.md +7 -7
  315. package/docs/use-cases.mdx +157 -0
  316. package/docs/vs-screenshots.mdx +3 -3
  317. package/docs/what-is-recorded.md +2 -2
  318. package/docs/what-is-reticle.mdx +17 -20
  319. package/docs/why-reticle.mdx +14 -21
  320. package/package.json +10 -9
  321. package/dist/judgement/capsule/minimize.d.ts +0 -12
  322. package/dist/judgement/capsule/minimize.js +0 -22
  323. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  324. package/dist/judgement/phenomena/phenomena.js +0 -101
  325. package/dist/language/flows/flow-report.d.ts +0 -22
  326. package/dist/language/flows/flow-report.js +0 -106
@@ -80,11 +80,11 @@ Those are real bugs and structural reads can miss them. Reticle has `reticle_scr
80
80
 
81
81
  The argument is not "screenshots are bad". It is that a screenshot cannot answer "did the code work", and that is the question an AI coding agent needs answered on every change.
82
82
 
83
- ## The honest limits
83
+ ## What is measured, and what is not
84
84
 
85
- We have not proven that Reticle makes agents _fix_ more bugs. A controlled test did not show a fix-rate improvement; the benchmark was confounded and we are still working on measuring it properly.
85
+ What is measured is detection: 10 of 10 injected regressions caught with zero false alarms, against 9 and 8 for the alternatives, and one false green against a Playwright script's 29 over an 88-bug registry.
86
86
 
87
- What is measured is detection: 10 of 10 injected regressions caught with zero false alarms, against 9 and 8 for the alternatives.
87
+ What is not established is that Reticle makes agents _fix_ more bugs. A fix-loop test on a small fixture found the same fix rate with and without it; [Why Reticle](/why-reticle#what-the-fix-rate-test-measured) says what that test could and could not show.
88
88
 
89
89
  ## FAQ
90
90
 
@@ -5,7 +5,7 @@ description: What Reticle keeps on your machine, what is redacted, and what it t
5
5
 
6
6
  Reticle drives your app and reads what it did. That means it holds real content from real pages, so this page says exactly what it keeps, where it keeps it, and what it takes to make any of it leave.
7
7
 
8
- If you read one line: **a verdict is entirely local and needs no account.** Nothing is sent anywhere until you run `reticle login` and `reticle link`. Without them there is nowhere for anything to go.
8
+ If you read one line: **a verdict is entirely local and needs no account.** Nothing from your project is sent anywhere until you run `reticle login` and `reticle link`. Without them there is nowhere for it to go. (Anonymous usage counts, which carry nothing from your app, are separate and described in [Telemetry](telemetry.md).)
9
9
 
10
10
  ## On your machine
11
11
 
@@ -31,7 +31,7 @@ Network bodies are **not** captured by default. Page content that reaches a verd
31
31
 
32
32
  ## What leaves, only if you ask
33
33
 
34
- `reticle login` signs this machine in. `reticle link` binds one repo to one cloud project. Until both have happened, `reticle push` has nothing to talk to and the sync path is a no-op. The code calls this the no-phone-home default.
34
+ `reticle login` signs this machine in. `reticle link` binds one repo to one cloud project. Until both have happened, or `RETICLE_API_KEY` is set in the environment, `reticle push` has nothing to talk to and the sync path is a no-op. The code calls this the no-phone-home default. A key in the environment is how CI syncs with no login and no link, and it goes to the hosted service unless `RETICLE_CLOUD_URL` names another host.
35
35
 
36
36
  Once linked, you choose what syncs:
37
37
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: What is Reticle?
3
3
  sidebarTitle: What is Reticle
4
- description: Reticle is a verification layer for AI coding agents. It runs a dev-only SDK inside your running web app so an agent can prove a change works, instead of guessing. What it is, what it is not, and what it needs.
4
+ description: Reticle is a verification layer for AI coding agents. It runs a dev-only SDK inside your running web app so an agent can prove a change works, instead of guessing. What it is, where it fits beside other tools, and what it needs.
5
5
  icon: circle-question
6
6
  ---
7
7
 
@@ -12,37 +12,33 @@ It exists because an agent that writes code cannot otherwise tell whether that c
12
12
  ## How it works, in three parts
13
13
 
14
14
  1. **An SDK inside the app.** Dev-only, stripped from production builds, added by a build plugin. It instruments the DOM, network, routing, console, animations, and your state library.
15
- 2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine. The daemon never starts or manages your dev server. It attaches to the app that is running. If none is, the agent is the one told to start it, using your project's own dev script, in the background, and saying so.
16
- 3. **An MCP server.** The daemon exposes the app to your agent as tools. `reticle_snapshot` to look, `reticle_act_and_wait` to act and prove in one call, `reticle_state` and `reticle_network` to observe, `reticle_assert` to check.
15
+ 2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine, bound to `127.0.0.1`. The daemon never manages your dev server; it attaches to the app that is running. On first setup, `npx @reticlehq/server init` starts the dev server with your project's own dev script and opens the app.
16
+ 3. **An MCP server.** The daemon exposes the app to your agent as tools. `reticle_look` to look, `reticle_act_and_wait` to act and prove in one call, `reticle_observe` to read what the app did, `reticle_assert` to check.
17
17
 
18
- Everything stays on your machine. No application code, DOM, network traffic, or console output leaves it.
18
+ Your app's data stays on your machine: no application code, DOM, network traffic, state or console output leaves it. Anonymous usage counts are sent by default and can be [turned off](/telemetry).
19
19
 
20
- ## What Reticle is not
20
+ ## Where it fits
21
21
 
22
- This is the section most people need, because Reticle sits next to several familiar categories without being any of them.
22
+ Reticle sits next to several familiar categories, and it works alongside each of them.
23
23
 
24
- **Not a browser automation framework.** Playwright, Cypress and Selenium drive a browser from the outside and are excellent at it. Reticle runs inside the page you already have open. If you want to script a browser, use Playwright. Reticle is what an agent uses to answer "did the thing I just changed actually work?"
24
+ **Browser automation.** Playwright, Cypress and Selenium drive a browser from the outside. Reticle runs inside the page you already have open and answers "did the thing I just changed actually work?" Playwright remains the tool for sites you don't own and for a cross-browser matrix.
25
25
 
26
- **Not a replacement for your test suite.** Unit and integration tests still do their job. Reticle covers the layer they structurally cannot reach: behaviour that only exists when the whole application runs. Many teams use both, and Reticle is often the thing that catches a suite passing over a broken app.
26
+ **Your test suite.** Unit and integration tests keep doing their job. Reticle covers the layer they cannot reach: behaviour that only exists when the whole application runs, which is often where a suite passes over a broken app.
27
27
 
28
- **Not a screenshot or visual regression tool.** It can take screenshots, but the point is that it does not need to. Verdicts come from predicates over real events, a request with a status, a state path, a signal your code fired, so they do not break when a button moves four pixels. If pixel-perfect visual diffing is your goal, a dedicated visual testing service fits better.
28
+ **Visual testing.** Reticle can take screenshots, but its verdicts come from predicates over real events (a request with a status, a state path, a signal your code fired), so they do not break when a button moves four pixels. For pixel-level diffs, pair it with a visual testing tool.
29
29
 
30
- **Not a monitoring or observability product.** Reticle is a development-time tool aimed at localhost. It is not an APM, not error tracking, and it does not watch production.
30
+ **Security, accessibility, performance and SEO.** Reticle checks how your running app behaves in each: access control by role, calls that must never fire, focus and keyboard behaviour, request counts and long tasks, titles, links and redirects. It pairs with a vulnerability scanner, a full WCAG audit tool such as axe, and an SEO crawler. [Use cases](/use-cases) has what each check covers.
31
31
 
32
- **Not an accessibility auditor.** `reticle_snapshot` addresses elements by role and name, which is not the same as auditing contrast, focus order, or screen reader behaviour. Use a dedicated accessibility tool for that.
32
+ **Monitoring.** Reticle runs against environments you control (a dev server, a staging or preview deployment) and replays saved flows in CI. The SDK refuses to connect in a production build, so it complements an APM or error tracker rather than watching live traffic.
33
33
 
34
- **Not an AI model, and not an agent.** Reticle has no model of its own and makes no decisions. It gives an agent you already use a way to see the running app. Your agent, your model, your keys.
34
+ **Your agent.** Reticle has no model of its own for the verify loop. It gives the agent you already use a way to see the running app. Your agent, your model, your keys. (`explore` can drive a journey for you with a model inside the daemon; see [Autodrive](/autodrive).)
35
35
 
36
- **Not a hosted service that receives your application data.** The daemon runs on your machine and the SDK runs in your browser. Anonymous product analytics are sent unless you opt out, and they never include your code or your application's data. See the [Telemetry Policy](https://www.reticle.sh/legal/telemetry).
37
-
38
- **Not a way to make a failing check pass.** A verdict of `unknown` means Reticle drove the app and could not tell. It is deliberately not rounded up to a pass, because a verification tool that flatters you is worse than none.
36
+ **The verdict.** A verdict of `unknown` means Reticle drove the app and could not tell. It is deliberately not rounded up to a pass.
39
37
 
40
38
  ## What Reticle needs
41
39
 
42
- Honest constraints, so you can tell in a minute whether it fits:
43
-
44
- - **A web application you can run locally**, with its dev server. Reticle attaches; it does not start one.
45
- - **A build step it can hook**, or two lines added by hand. Vite, Next.js, Remix and Astro are proven by gates in the repository. The SDK is framework agnostic and usually connects elsewhere, with reduced source mapping.
40
+ - **A web application you can run**, with a dev build. `init` starts its dev server for you.
41
+ - **A build step it can hook**, or two lines added by hand. [Frameworks](/frameworks) lists what is proven for each.
46
42
  - **An agent that speaks MCP.** Claude Code, Cursor, Codex, Copilot, Windsurf, OpenCode and others.
47
43
  - **Node 20 or newer.**
48
44
 
@@ -59,5 +55,6 @@ Side by side with the tools people ask about most:
59
55
  ## Where to go next
60
56
 
61
57
  - [Quickstart](/quickstart) gets a real verdict out of your own app in about five minutes.
62
- - [Why Reticle](/why-reticle) is the argument, with the evidence and the cases where it loses.
58
+ - [Why Reticle](/why-reticle) is the argument, with the evidence and what pairs well with it.
59
+ - [Use cases](/use-cases) covers what people verify with it.
63
60
  - [For agents](/for-agents) is written for the agent rather than for you.
@@ -63,37 +63,30 @@ Observing one real dashboard once, with no bug involved:
63
63
  That last column is the one that matters more than the token counts. The other tools can _describe_ what a page looks like. Only Reticle can check whether the thing the app itself considers success actually happened.
64
64
 
65
65
  <Card title="How every number was measured" icon="chart-column" href="/benchmarks">
66
- The full method, the raw data, and the scenarios where Reticle comes second.
66
+ The full method, the raw data, and the scenarios where the tools tie.
67
67
  </Card>
68
68
 
69
- ## Where Reticle loses
69
+ On raw cost per glance, Chrome DevTools MCP is cheaper: 758 tokens against 815. It catches fewer bugs, which is why the efficiency column favours Reticle.
70
70
 
71
- A benchmark that only shows you the wins is an advertisement wearing a lab coat. Ours doesn't.
71
+ ## What the fix-rate test measured
72
72
 
73
- - **Sites you don't own.** Reticle needs a dev-only SDK inside the app. If you're driving a third party's website, use Playwright. That is what it's for, and it's excellent at it.
74
- - **Pixels.** Reticle reads the program, not the screen. If your bug is "the button is three pixels too far left", Reticle is the wrong instrument.
75
- - **Chrome DevTools MCP is cheaper per look.** 758 tokens against our 815. It catches fewer bugs, so the efficiency works out in our favour, but on raw cost per glance it wins.
73
+ We ran a fix-loop ablation: agents fixed 8 bugs injected into a small fixture app, with Reticle and without it, on a strong model and on a weaker one. **It did not show a fix-rate improvement at either tier.** The strong model fixed 8 of 8 in both conditions and the weaker one 7 of 8 in both, and the Reticle arm made roughly five to six times the tool calls, because driving an app costs more than reading a few short files.
76
74
 
77
- ## What we have not proven
75
+ The test cannot settle the question in either direction, and the reasons are specific: the fixture is small enough to read whole, several injected bugs were labelled by a comment on the changed line, and "fixed" was checked by a string disappearing rather than by the app behaving. One case pointed each way. The weaker model without Reticle claimed a fix it never applied, and the live re-check caught that with Reticle; with Reticle, the same model over-edited a stylesheet the string check scored as a pass. The full log is in [`bench/fix-loop/results.md`](https://github.com/reticlehq/reticle/blob/main/bench/fix-loop/results.md).
78
76
 
79
- Being honest here costs us something, and we'd rather pay it than have you find out later.
77
+ A separate, smaller measurement of the source pointer alone found the same fix rate with and without it (6 of 6) and fewer tool calls with it (12 against 22). The pointer removed the search; it did not make the agent more capable.
80
78
 
81
- We ran a controlled test of whether an agent equipped with Reticle _fixes_ more bugs than one without it. **It did not show a fix-rate improvement.** The benchmark was confounded in ways we can point at, and we don't consider the question settled. But we are not going to claim a number we didn't measure.
79
+ That is what Reticle is designed to improve: not the agent, but the verdict it reports. A broken build reads as broken, a failure arrives with the evidence and the `file:line`, and "I couldn't tell" is reported as that. In the 88-bug head-to-head against a Playwright script, Reticle reported one false green where the script reported 29.
82
80
 
83
- What Reticle demonstrably does is catch failures that are invisible to the alternatives, and hand back the file and line to go fix. Whether that translates into a better fix rate is a separate question, and we're still working on measuring it properly.
81
+ ## When to use it
84
82
 
85
- ## When you should use it
83
+ You own the app, an agent edits it, and you want the agent to prove its work rather than assure you of it. It needs a dev build it can instrument, and React (and any framework whose build plugin stamps source) adds source pointers on top. [Use cases](/use-cases) covers what people check with it beyond change verification, from access control to accessibility.
86
84
 
87
- <CardGroup cols={2}>
88
- <Card title="A good fit" icon="circle-check">
89
- You own the app. An agent edits it. You want the agent to prove its work rather than assure you
90
- of it. React gets you source pointers on top.
91
- </Card>
92
- <Card title="A bad fit" icon="circle-xmark">
93
- You're testing a site you don't control, you need pixel-level visual regression, or your app has
94
- no dev build to instrument.
95
- </Card>
96
- </CardGroup>
85
+ ## Pairs well with
86
+
87
+ - **Playwright** for driving sites you don't own and a cross-browser matrix. Reticle needs its dev-only SDK inside the app.
88
+ - **A visual testing tool** for pixel-level regression. Reticle reads the program, not the screen, so "the button is three pixels too far left" is a job for pixels.
89
+ - **axe** for full WCAG audits, and **a security scanner** for vulnerability discovery.
97
90
 
98
91
  ## The one-sentence version
99
92
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reticlehq/server",
3
- "version": "3.3.0",
4
- "description": "Reticle bridge + MCP server. Hosts the browser WS endpoint and exposes MCP tools to the coding agent.",
3
+ "version": "3.5.0",
4
+ "description": "Reticle CLI, bridge and MCP server: a coding agent drives your running web app from the inside and gets a verdict with evidence, for change verification, access control, accessibility, performance and SEO checks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -19,24 +19,24 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.30.0",
22
+ "@modelcontextprotocol/sdk": "^1.30.1",
23
23
  "ws": "^8.21.3",
24
24
  "zod": "^3.24.1",
25
25
  "zod-to-json-schema": "^3.24.0",
26
- "@reticlehq/engine": "3.3.0",
27
- "@reticlehq/browser": "3.3.0",
28
- "@reticlehq/init": "3.3.0",
26
+ "@reticlehq/browser": "3.5.0",
27
+ "@reticlehq/engine": "3.5.0",
28
+ "@reticlehq/core": "3.5.0",
29
29
  "open-verification": "1.0.0",
30
- "@reticlehq/core": "3.3.0"
30
+ "@reticlehq/init": "3.5.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/pngjs": "^6.0.5",
34
34
  "@types/ws": "^8.5.13",
35
- "yaml": "^2.9.0"
35
+ "yaml": "^2.9.1"
36
36
  },
37
37
  "optionalDependencies": {
38
38
  "pixelmatch": "^7.2.0",
39
- "playwright": "^1.62.1",
39
+ "playwright": "^1.63.0",
40
40
  "pngjs": "^7.0.0"
41
41
  },
42
42
  "license": "FSL-1.1-ALv2",
@@ -60,6 +60,7 @@
60
60
  "mcp",
61
61
  "ai-agents",
62
62
  "testing",
63
+ "verification",
63
64
  "cli"
64
65
  ],
65
66
  "reticle": {
@@ -1,12 +0,0 @@
1
- /**
2
- * Bug-capsule minimization — first cut: prefix-trim. A recorded flow that fails is often mostly setup;
3
- * the capsule should be the SMALLEST flow that still reproduces the failure, so a regression flow stays
4
- * fast and legible. Prefix-trim drops leading steps while the failure persists — the cheap, deterministic
5
- * v1 (delta-debugging over arbitrary subsets is a later refinement). The `stillFails` predicate is
6
- * injected (a real replay), so this stays pure and unit-testable.
7
- */
8
- /**
9
- * Return the shortest trailing sub-flow that still fails, by removing leading steps one at a time while
10
- * `stillFails` holds. Never trims below one step. Deterministic given a deterministic predicate.
11
- */
12
- export declare function prefixTrim<T>(steps: readonly T[], stillFails: (candidate: readonly T[]) => Promise<boolean>): Promise<T[]>;
@@ -1,22 +0,0 @@
1
- /**
2
- * Bug-capsule minimization — first cut: prefix-trim. A recorded flow that fails is often mostly setup;
3
- * the capsule should be the SMALLEST flow that still reproduces the failure, so a regression flow stays
4
- * fast and legible. Prefix-trim drops leading steps while the failure persists — the cheap, deterministic
5
- * v1 (delta-debugging over arbitrary subsets is a later refinement). The `stillFails` predicate is
6
- * injected (a real replay), so this stays pure and unit-testable.
7
- */
8
- /**
9
- * Return the shortest trailing sub-flow that still fails, by removing leading steps one at a time while
10
- * `stillFails` holds. Never trims below one step. Deterministic given a deterministic predicate.
11
- */
12
- export async function prefixTrim(steps, stillFails) {
13
- let kept = [...steps];
14
- while (kept.length > 1) {
15
- const trimmed = kept.slice(1);
16
- if (await stillFails(trimmed))
17
- kept = trimmed;
18
- else
19
- break;
20
- }
21
- return kept;
22
- }
@@ -1,27 +0,0 @@
1
- import { PhenomenonType, type JournalAction, type ReticleEvent } from '@reticlehq/core';
2
- /**
3
- * A detected phenomenon — a named, evidence-backed anomaly over the journal. Deterministic matchers, no
4
- * scanning by the agent: the layer names the fault, the agent acts on the name. `pre-hydration-click`
5
- * lands once the hydration signal (a later item) exists; the three here need only journal data.
6
- */
7
- interface Finding {
8
- phenomenon: PhenomenonType;
9
- evidence: Record<string, unknown>;
10
- }
11
- /** A request that started (NET_PENDING) but never completed (no NET_REQUEST with the same id). */
12
- export declare function detectHungRequests(events: readonly ReticleEvent[]): Finding[];
13
- /** A 5xx response observed while the page was hidden — an error the user never sees. */
14
- export declare function detectHidden500(events: readonly ReticleEvent[]): Finding[];
15
- /** An act that dispatched but attributed no events — the app did nothing. */
16
- export declare function detectDeadClicks(actions: readonly JournalAction[]): Finding[];
17
- /**
18
- * A click that landed BEFORE React hydration attached handlers — a silent no-op. Detectable only in-app:
19
- * the hydration-complete signal's time vs the click action's dispatch. No signal ⇒ can't tell (not a
20
- * React app, or hydration never completed), so we don't guess.
21
- */
22
- export declare function detectPreHydrationClicks(events: readonly ReticleEvent[], actions: readonly JournalAction[]): Finding[];
23
- /** A React error boundary that caught and swallowed — surfaced on the signal channel by the adapter. */
24
- export declare function detectSwallowedErrors(events: readonly ReticleEvent[]): Finding[];
25
- /** Run every journal-only matcher over a session's events + actions. */
26
- export declare function detectPhenomena(events: readonly ReticleEvent[], actions: readonly JournalAction[]): Finding[];
27
- export {};
@@ -1,101 +0,0 @@
1
- import { EventType, FlowStepTool, PhenomenonType, RETICLE_ERROR_BOUNDARY_SIGNAL, RETICLE_HYDRATION_SIGNAL, } from '@reticlehq/core';
2
- /** ACT tools that represent a user-style interaction (a "click"), for dead-click detection. */
3
- const CLICK_TOOLS = new Set([FlowStepTool.ACT, FlowStepTool.ACT_AND_WAIT]);
4
- /** A request that started (NET_PENDING) but never completed (no NET_REQUEST with the same id). */
5
- export function detectHungRequests(events) {
6
- const completed = new Set();
7
- for (const event of events) {
8
- if (event.type === EventType.NET_REQUEST)
9
- completed.add(event.data['id']);
10
- }
11
- const findings = [];
12
- for (const event of events) {
13
- if (event.type !== EventType.NET_PENDING)
14
- continue;
15
- if (completed.has(event.data['id']))
16
- continue;
17
- findings.push({
18
- phenomenon: PhenomenonType.HUNG_REQUEST,
19
- evidence: { id: event.data['id'], method: event.data['method'], url: event.data['url'] },
20
- });
21
- }
22
- return findings;
23
- }
24
- /** A 5xx response observed while the page was hidden — an error the user never sees. */
25
- export function detectHidden500(events) {
26
- const findings = [];
27
- let hidden = false;
28
- for (const event of events) {
29
- if (event.type === EventType.PAGE_HEALTH) {
30
- const h = event.data['hidden'];
31
- if ('boolean' === typeof h)
32
- hidden = h;
33
- continue;
34
- }
35
- if (event.type !== EventType.NET_REQUEST)
36
- continue;
37
- const status = event.data['status'];
38
- if (hidden && 'number' === typeof status && status >= 500) {
39
- findings.push({
40
- phenomenon: PhenomenonType.HIDDEN_500,
41
- evidence: { url: event.data['url'], status, method: event.data['method'] },
42
- });
43
- }
44
- }
45
- return findings;
46
- }
47
- /** An act that dispatched but attributed no events — the app did nothing. */
48
- export function detectDeadClicks(actions) {
49
- const findings = [];
50
- for (const action of actions) {
51
- if (!CLICK_TOOLS.has(action.tool))
52
- continue;
53
- if (action.seqRange !== undefined)
54
- continue; // it caused something → not dead
55
- findings.push({
56
- phenomenon: PhenomenonType.DEAD_CLICK,
57
- evidence: { actionId: action.actionId, tool: action.tool, args: action.args },
58
- });
59
- }
60
- return findings;
61
- }
62
- /**
63
- * A click that landed BEFORE React hydration attached handlers — a silent no-op. Detectable only in-app:
64
- * the hydration-complete signal's time vs the click action's dispatch. No signal ⇒ can't tell (not a
65
- * React app, or hydration never completed), so we don't guess.
66
- */
67
- export function detectPreHydrationClicks(events, actions) {
68
- const hydration = events.find((e) => e.type === EventType.SIGNAL && e.data['name'] === RETICLE_HYDRATION_SIGNAL);
69
- if (hydration === undefined)
70
- return [];
71
- return actions
72
- .filter((a) => CLICK_TOOLS.has(a.tool) && a.tRange.from < hydration.t)
73
- .map((a) => ({
74
- phenomenon: PhenomenonType.PRE_HYDRATION_CLICK,
75
- evidence: { actionId: a.actionId, clickedAt: a.tRange.from, hydratedAt: hydration.t },
76
- }));
77
- }
78
- /** A React error boundary that caught and swallowed — surfaced on the signal channel by the adapter. */
79
- export function detectSwallowedErrors(events) {
80
- const findings = [];
81
- for (const event of events) {
82
- if (event.type === EventType.SIGNAL && event.data['name'] === RETICLE_ERROR_BOUNDARY_SIGNAL) {
83
- const detail = event.data['data'];
84
- findings.push({
85
- phenomenon: PhenomenonType.SWALLOWED_ERROR,
86
- evidence: 'object' === typeof detail && detail !== null ? detail : {},
87
- });
88
- }
89
- }
90
- return findings;
91
- }
92
- /** Run every journal-only matcher over a session's events + actions. */
93
- export function detectPhenomena(events, actions) {
94
- return [
95
- ...detectHungRequests(events),
96
- ...detectHidden500(events),
97
- ...detectDeadClicks(actions),
98
- ...detectPreHydrationClicks(events, actions),
99
- ...detectSwallowedErrors(events),
100
- ];
101
- }
@@ -1,22 +0,0 @@
1
- import { type FlowFile, type FlowReplayResult } from '@reticlehq/core';
2
- /**
3
- * Render a HUMAN-facing confidence report for a replayed flow — the artifact a developer reads to
4
- * trust the test without re-running it. It answers the three confidence questions in one page:
5
- * WHY — the declared business intent (+ whether it is actually asserted).
6
- * WHAT — the journey: a mermaid flow diagram + a per-step page → action → consequence table.
7
- * PROOF — the verdict (pass/drift/fail), the observed evidence (signals/network), and the
8
- * measured cost (deterministic replay tokens vs an LLM re-drive).
9
- *
10
- * Pure: no IO, no clock. Markdown out (mermaid is the one place mermaid earns its tokens — a human
11
- * renders it). Token figures are passed in (measured by the caller), never faked.
12
- */
13
- interface FlowReportInput {
14
- flow: FlowFile;
15
- replay: FlowReplayResult;
16
- /** Measured deterministic replay cost (o200k proxy), for the "PROOF / cost" line. Optional. */
17
- replayTokens?: number;
18
- /** Measured per-run LLM re-drive cost of a step-driving tool, for the ratio. Optional. */
19
- competitorTokens?: number;
20
- }
21
- export declare function buildFlowReport(input: FlowReportInput): string;
22
- export {};
@@ -1,106 +0,0 @@
1
- import { ReplayStatus, } from '@reticlehq/core';
2
- import { classifyFlowAssertions } from './flow-classify.js';
3
- import { SUCCESS_STEP_TOOL } from './flow-success.js';
4
- const VERDICT_GLYPH = {
5
- [ReplayStatus.OK]: '✅ pass',
6
- [ReplayStatus.DRIFT]: '⚠️ drift',
7
- [ReplayStatus.ERROR]: '❌ fail',
8
- };
9
- /** A mermaid-safe node id (mermaid ids must be alnum/underscore). */
10
- function nodeId(index) {
11
- return `s${index}`;
12
- }
13
- /** Escape a label for a mermaid `["..."]` node (quotes/newlines break the parser). */
14
- function mermaidLabel(text) {
15
- return text.replace(/"/g, "'").replace(/\n/g, ' ').slice(0, 80);
16
- }
17
- /** A short action verb for a step row ("act" is the tool; the anchor carries the target). */
18
- function stepAction(step) {
19
- return step.tool === SUCCESS_STEP_TOOL ? 'assert outcome' : step.anchor;
20
- }
21
- /** The mermaid flowchart of the journey: one node per step, page-labelled, consequence on the edge. */
22
- function mermaidDiagram(steps) {
23
- const lines = ['```mermaid', 'flowchart TD'];
24
- steps.forEach((step, i) => {
25
- const page = step.page !== undefined ? `${step.page} — ` : '';
26
- const glyph = step.ok ? '' : ' ✗';
27
- lines.push(` ${nodeId(i)}["${mermaidLabel(`${page}${stepAction(step)}${glyph}`)}"]`);
28
- if (i > 0) {
29
- const edge = steps[i - 1]?.consequence;
30
- const label = edge !== undefined ? `|"${mermaidLabel(edge)}"|` : '';
31
- lines.push(` ${nodeId(i - 1)} -->${label} ${nodeId(i)}`);
32
- }
33
- });
34
- lines.push('```');
35
- return lines.join('\n');
36
- }
37
- /** The per-step journey table (page → action → consequence → result). */
38
- function journeyTable(steps) {
39
- const rows = [
40
- '| # | page | action | consequence | result |',
41
- '|---|------|--------|-------------|--------|',
42
- ];
43
- for (const step of steps) {
44
- const page = step.page ?? '—';
45
- const consequence = step.consequence ?? '—';
46
- const result = step.ok
47
- ? 'ok'
48
- : step.drift !== undefined
49
- ? `drift (${step.drift.anchor})`
50
- : 'fail';
51
- rows.push(`| ${step.step} | ${page} | ${stepAction(step)} | ${consequence} | ${result} |`);
52
- }
53
- return rows.join('\n');
54
- }
55
- /** Distinct observed consequences across the journey — the evidence the verdict rests on. */
56
- function evidenceList(steps) {
57
- const seen = new Set();
58
- for (const step of steps) {
59
- if (step.consequence !== undefined) {
60
- for (const part of step.consequence.split(';'))
61
- seen.add(part.trim());
62
- }
63
- }
64
- if (0 === seen.size)
65
- return '_No observable consequence captured._';
66
- return [...seen].map((e) => `- ${e}`).join('\n');
67
- }
68
- export function buildFlowReport(input) {
69
- const { flow, replay, replayTokens, competitorTokens } = input;
70
- const grade = classifyFlowAssertions(flow);
71
- const verdict = VERDICT_GLYPH[replay.status] ?? replay.status;
72
- const intent = flow.intent ?? '_(no intent declared)_';
73
- const intentLine = flow.intent !== undefined
74
- ? grade.intentVerified
75
- ? `**Intent:** ${flow.intent} — **verified** (an observable outcome is asserted).`
76
- : `**Intent:** ${flow.intent} — ⚠️ declared but NOT asserted (add a consequence oracle).`
77
- : `**Intent:** ${intent}`;
78
- const out = [];
79
- out.push(`# Flow report — \`${flow.name}\``);
80
- out.push('');
81
- out.push(intentLine);
82
- out.push(`**Verdict:** ${verdict} · **steps:** ${replay.steps.length}`);
83
- if (replayTokens !== undefined) {
84
- const ratio = competitorTokens !== undefined && replayTokens > 0
85
- ? ` · **${Math.round(competitorTokens / replayTokens)}×** cheaper than an LLM re-drive`
86
- : '';
87
- out.push(`**Cost:** ${replayTokens} tokens (deterministic replay, no LLM)${ratio}`);
88
- }
89
- if (replay.decision !== undefined) {
90
- out.push('');
91
- out.push(`> **Next action:** ${replay.decision.nextAction}`);
92
- if (replay.decision.whereInSource !== undefined) {
93
- out.push(`> **Where:** \`${replay.decision.whereInSource}\``);
94
- }
95
- }
96
- out.push('');
97
- out.push('## Journey');
98
- out.push(mermaidDiagram(replay.steps));
99
- out.push('');
100
- out.push(journeyTable(replay.steps));
101
- out.push('');
102
- out.push('## Evidence');
103
- out.push(evidenceList(replay.steps));
104
- out.push('');
105
- return out.join('\n');
106
- }