@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
@@ -21,6 +21,12 @@ interface GateHookInput {
21
21
  downgraded: readonly string[];
22
22
  /** Flows whose coverage was deleted while their files changed. */
23
23
  deleted: readonly string[];
24
+ /** Changed interactive components no saved flow has ever driven. */
25
+ unflowed?: readonly string[];
26
+ /** Coverage levels below their best, already worded (`branched 100% -> 50%`). */
27
+ coverageRegressed?: readonly string[];
28
+ /** Changed files the browser loaded and never ran a function of. */
29
+ unexecuted?: readonly string[];
24
30
  }
25
31
  /**
26
32
  * The message, or undefined when there is nothing to say.
@@ -37,6 +37,12 @@ export function gateHookMessage(exit, input) {
37
37
  lines.push(` assertions weakened to something fakeable: ${name(input.downgraded)} — a check that cannot fail is not a check`);
38
38
  if (input.deleted.length > 0)
39
39
  lines.push(` coverage deleted while its files changed: ${name(input.deleted)}`);
40
+ if (input.unflowed !== undefined && input.unflowed.length > 0)
41
+ lines.push(` changed, and no saved flow drives it: ${name(input.unflowed)} — drive it with reticle_act_and_wait and a declared \`until\``);
42
+ if (input.coverageRegressed !== undefined && input.coverageRegressed.length > 0)
43
+ lines.push(` coverage fell: ${name(input.coverageRegressed)} — something new is uncovered; reticle_verify { action: "crawl", exhaustive: true } names it. If the drop was intended, \`reticle gate --accept-coverage\` records it`);
44
+ if (input.unexecuted !== undefined && input.unexecuted.length > 0)
45
+ lines.push(` changed, and no drive ever ran it: ${name(input.unexecuted)}`);
40
46
  if (input.quarantined.length > 0)
41
47
  lines.push(` held back as flaky, so proving nothing: ${name(input.quarantined)}`);
42
48
  lines.push('');
@@ -33,6 +33,4 @@ interface CredentialWrite {
33
33
  * re-link, and refusing it would strand the repo.
34
34
  */
35
35
  export declare const applyCredential: (store: Record<string, unknown>, write: CredentialWrite) => Record<string, unknown>;
36
- /** Where the store lives, given the reticle home directory. */
37
- export declare const credentialsPath: (homeDir: string, file: string) => string;
38
36
  export {};
@@ -14,7 +14,6 @@
14
14
  * two are kept readable (a repo linked by an older CLI must not lose its key) and written only when
15
15
  * doing so cannot mislead somebody else.
16
16
  */
17
- import { join } from 'node:path';
18
17
  import { credentialSlot } from '../../../memory/cloud/cloud-config.js';
19
18
  /** Trailing slashes are not identity: `https://x/` and `https://x` are one cloud. */
20
19
  const normalizeUrl = (url) => url.replace(/\/+$/, '');
@@ -93,5 +92,3 @@ export const applyCredential = (store, write) => {
93
92
  store[projectId] = { key, url };
94
93
  return store;
95
94
  };
96
- /** Where the store lives, given the reticle home directory. */
97
- export const credentialsPath = (homeDir, file) => join(homeDir, file);
@@ -18,15 +18,19 @@ declare const SessionSchema: z.ZodObject<{
18
18
  * is ours" and mints, which is the safe direction.
19
19
  */
20
20
  orgId: z.ZodOptional<z.ZodString>;
21
+ /** Who signed in. Absent in sessions written before it was recorded. */
22
+ email: z.ZodOptional<z.ZodString>;
21
23
  }, "strip", z.ZodTypeAny, {
22
24
  url: string;
23
25
  token: string;
24
26
  orgName: string;
27
+ email?: string | undefined;
25
28
  orgId?: string | undefined;
26
29
  }, {
27
30
  url: string;
28
31
  token: string;
29
32
  orgName: string;
33
+ email?: string | undefined;
30
34
  orgId?: string | undefined;
31
35
  }>;
32
36
  export type Session = z.infer<typeof SessionSchema>;
@@ -19,6 +19,8 @@ const SessionSchema = z.object({
19
19
  * is ours" and mints, which is the safe direction.
20
20
  */
21
21
  orgId: z.string().optional(),
22
+ /** Who signed in. Absent in sessions written before it was recorded. */
23
+ email: z.string().optional(),
22
24
  });
23
25
  /** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
24
26
  export const normalizeUrl = (url) => url.replace(/\/+$/, '');
@@ -1,26 +1,29 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { ReticleEnv } from '@reticlehq/core';
3
+ import { devServersForProject, ReticleEnv } from '@reticlehq/core';
4
4
  import { readPid, reticleStateHome } from '../daemon/daemon.js';
5
5
  import { PortPresence, probePresence } from '../daemon/binding/port-presence.js';
6
6
  import { isLocalhostSplit, probeLoopbackReach } from '../daemon/binding/loopback-reach.js';
7
7
  import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
8
- import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
8
+ import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
9
9
  import { daemonLine } from './doctor/doctor-daemon-line.js';
10
10
  import { projectWiringLine } from './doctor/doctor-project-line.js';
11
11
  import { hasProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
12
12
  import { sessionsLine } from './doctor/doctor-sessions-line.js';
13
13
  import { captureLookup, describeForeignHolder, findPortHolder } from './ports/port-holder.js';
14
- import { chromiumHint, probeChromium } from './doctor/browser/chromium-hint.js';
14
+ import { chromiumHint } from './doctor/browser/chromium-hint.js';
15
+ import { probeChromiumWithFallback } from '../../launch-chromium.js';
15
16
  import { SERVER_VERSION } from '../version/identity/server-version.js';
16
17
  import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
17
- import { diagnoseDesktop, isDesktopProject, diagnoseWebCsp } from '@reticlehq/init';
18
- import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectId, readProjectPort, } from './ports/resolve/cli-port.js';
18
+ import { CspBasis, diagnoseDesktop, diagnoseObservedWebCsp, diagnoseWebCsp, isDesktopProject, resolveWebCspFindings, webCspOptionsFor, } from '@reticlehq/init';
19
+ import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectFramework, readProjectId, readProjectPort, } from './ports/resolve/cli-port.js';
19
20
  import { DoctorRow, doctorRow } from './doctor/doctor-rows.js';
20
21
  import { attachState, describeAttachState } from '../../surface/mcp/attach-memory.js';
21
22
  import { findOccupiedSiblings, siblingListenerNote } from './ports/sibling-ports.js';
22
23
  import { daemonsServingProjectElsewhere, resolveDaemonForProject, splitBrainNote, wrongDaemonNote, } from '../daemon/daemon-resolve.js';
23
24
  import { isAlive } from '../daemon/daemon.js';
25
+ import { readDevServers } from '../daemon/dev-servers.js';
26
+ import { hasConnectedDocument, observeWebDocument, } from '../../portal/session/dev-server/served-document.js';
24
27
  /**
25
28
  * `reticle doctor` — collapse the ~6 independent first-run failure modes into one command. Checks the
26
29
  * Chromium install (the #1 silent failure), whether a daemon is up on the resolved bridge port, and
@@ -65,7 +68,8 @@ export async function handleDoctor(port) {
65
68
  // Filled in only on the daemon branch — there is nothing to ask when no daemon is answering, and
66
69
  // the branches below already say so in their own terms.
67
70
  let sessions;
68
- line(doctorRow(DoctorRow.CHROMIUM, chromiumHint(await probeChromium())));
71
+ let daemonStatus;
72
+ line(doctorRow(DoctorRow.CHROMIUM, chromiumHint(await probeChromiumWithFallback())));
69
73
  // Ask the PORT, not just the pid file. "not running on :4400" has been printed about a port that
70
74
  // was demonstrably occupied, which sends the reader to start a daemon that cannot bind. The three
71
75
  // states are genuinely different problems with different fixes, so doctor names which one it is.
@@ -76,6 +80,7 @@ export async function handleDoctor(port) {
76
80
  // throwing both away — while skew is invisible everywhere else, reaching the agent as a bare
77
81
  // -32000 naming no version. This is the command a human runs at exactly that moment.
78
82
  const payload = await fetchStatus(port);
83
+ daemonStatus = payload;
79
84
  const status = asIdentity(payload);
80
85
  const built = daemonLine(port, pid, status, {
81
86
  version: SERVER_VERSION,
@@ -187,10 +192,27 @@ export async function handleDoctor(port) {
187
192
  // The web sibling of the desktop findings below: a `connect-src` that excludes the bridge makes
188
193
  // the browser refuse the WebSocket and report it in ITS console only, so every check above passes
189
194
  // at an app that can never connect. Named, with the exact text to paste.
190
- const csp = diagnoseWebCsp(readProjectFile, port);
195
+ const devServers = devServersForProject(readDevServers(reticleStateHome()), {
196
+ projectId,
197
+ root: process.cwd(),
198
+ });
199
+ const observedDocuments = (await Promise.all(devServers.map((server) => observeWebDocument(server.url)))).filter((document) => document !== undefined);
200
+ const observedCsp = 0 === observedDocuments.length
201
+ ? undefined
202
+ : observedDocuments.flatMap((document) => diagnoseObservedWebCsp(document, port));
203
+ const connected = hasConnectedDocument(summarizeStatus(daemonStatus).sessions, devServers.map((server) => server.url), projectId);
204
+ const csp = resolveWebCspFindings({
205
+ // With the wiring `init` recorded, so the two commands name the same rule for one policy.
206
+ predicted: diagnoseWebCsp(readProjectFile, port, [], webCspOptionsFor(readProjectFramework(process.cwd()))),
207
+ ...(observedCsp === undefined ? {} : { observed: observedCsp }),
208
+ connected,
209
+ });
191
210
  if (csp.length > 0) {
192
211
  line('');
193
- line(` csp ✗ a Content-Security-Policy is blocking the Reticle bridge:`);
212
+ const observed = csp.some((finding) => finding.basis === CspBasis.OBSERVED);
213
+ line(observed
214
+ ? ` csp ✗ a Content-Security-Policy is blocking the Reticle bridge:`
215
+ : ` csp ? a configuration may block the Reticle bridge (predicted, not observed):`);
194
216
  for (const finding of csp) {
195
217
  line(` ${finding.file}`);
196
218
  line(` ${finding.problem}`);
@@ -37,11 +37,8 @@ export declare function handleWatch(): void;
37
37
  * failing reproduction plus the consequence that should have held; replay one with reticle_flow_replay.
38
38
  */
39
39
  export declare function handleCapsules(): Promise<void>;
40
- /**
41
- * `reticle gate <file...>` — exit non-zero unless passing artifacts cover the flows affected by the
42
- * changed files. Flaky flows are quarantined (surfaced, not blocking). The environment-side enforcement
43
- * that makes verification unavoidable. Never throws; a fault fails closed (exit 1).
44
- */
45
40
  export declare function handleGate(files: string[], since: string | undefined,
46
41
  /** Hook mode: prose for a human, and silence when there was simply nothing to check. */
47
- hook?: boolean): Promise<void>;
42
+ hook?: boolean,
43
+ /** Accept today's coverage levels as the best before judging, for a drop that was intended. */
44
+ acceptCoverage?: boolean): Promise<void>;
@@ -14,7 +14,9 @@ import { ReticleDir, RunFlowStatus } from '@reticlehq/core';
14
14
  import { FlowStore } from '../../language/flows/flows.js';
15
15
  import { RunStore } from '../../judgement/runs/artifact/run-store.js';
16
16
  import { createNodeFileSystem } from '../../memory/project/fs/fs-port.js';
17
- import { affectedSavedFlows } from '../../language/flows/change/flow-sources.js';
17
+ import { affectedSavedFlows, toFlowSources, } from '../../language/flows/change/flow-sources.js';
18
+ import { isInteractiveSource, unflowedFiles } from '../../language/flows/change/affected.js';
19
+ import { LedgerStore, regressions, staleChanged, unexecutedChanged, } from '../../features/exhaust/ledger.js';
18
20
  import { gateDecision } from '../../language/flows/change/gate.js';
19
21
  import { FlakeStore } from '../../language/flows/stores/flake-store.js';
20
22
  import { formatBuddyStatus } from '../../language/flows/buddy-status.js';
@@ -23,7 +25,7 @@ import { AssertionTiersStore } from '../../language/flows/stores/assertion-tiers
23
25
  import { detectDowngrades } from '../../judgement/outcome/assertion-integrity.js';
24
26
  import { computeCoverage, flowCoverageReport } from '../../language/flows/suite/coverage.js';
25
27
  import { createWatchBatcher } from '../../language/flows/change/watch-batcher.js';
26
- import { watch } from 'node:fs';
28
+ import { readFileSync, statSync, watch } from 'node:fs';
27
29
  import { log } from '../../log.js';
28
30
  /** Load the {name, steps} of every saved flow for the active project. */
29
31
  /** Explicit files plus, when --since is given, the git-changed files since that ref. */
@@ -163,9 +165,35 @@ export async function handleCapsules() {
163
165
  * changed files. Flaky flows are quarantined (surfaced, not blocking). The environment-side enforcement
164
166
  * that makes verification unavoidable. Never throws; a fault fails closed (exit 1).
165
167
  */
168
+ /**
169
+ * A changed file's text, or '' when it is gone. `git diff --name-only` answers from the repository
170
+ * root while the gate may run in a package below it, so leading segments are dropped until the path
171
+ * resolves from `cwd`.
172
+ */
173
+ function atChangedPath(cwd, file, read) {
174
+ const parts = file.split('/');
175
+ for (let i = 0; i < parts.length; i += 1) {
176
+ try {
177
+ return read(join(cwd, ...parts.slice(i)));
178
+ }
179
+ catch {
180
+ // not at this depth; try the path one segment shorter
181
+ }
182
+ }
183
+ return undefined;
184
+ }
185
+ function readChangedFile(cwd, file) {
186
+ return atChangedPath(cwd, file, (path) => readFileSync(path, 'utf8')) ?? '';
187
+ }
188
+ /** When a changed file was last modified, or undefined when it is gone. Same lookup as its text. */
189
+ function changedFileModifiedAt(cwd, file) {
190
+ return atChangedPath(cwd, file, (path) => statSync(path).mtimeMs);
191
+ }
166
192
  export async function handleGate(files, since,
167
193
  /** Hook mode: prose for a human, and silence when there was simply nothing to check. */
168
- hook = false) {
194
+ hook = false,
195
+ /** Accept today's coverage levels as the best before judging, for a drop that was intended. */
196
+ acceptCoverage = false) {
169
197
  try {
170
198
  const fs = createNodeFileSystem();
171
199
  const reticleRoot = join(process.cwd(), ReticleDir.ROOT);
@@ -204,7 +232,32 @@ hook = false) {
204
232
  const deleted = Object.entries(baseline)
205
233
  .filter(([name, entry]) => !byName.has(name) && entry.sources.some((f) => changedSet.has(f)))
206
234
  .map(([name]) => name);
207
- const result = gateDecision({ affected, passing, flaky, downgraded, deleted });
235
+ // Only against a suite that exists: a project with no flows yet is NOTHING_TO_CHECK, and
236
+ // demanding a flow per edited component on its first day would block every stop.
237
+ const unflowed = 0 === allFlows.length
238
+ ? []
239
+ : unflowedFiles(toFlowSources(allFlows), changed, (file) => isInteractiveSource(readChangedFile(process.cwd(), file)));
240
+ // The coverage ledger, when one exists: a level that fell below its best, and changed code the
241
+ // browser loaded and never ran. A project that never folded coverage has an empty ledger and
242
+ // neither can fire.
243
+ const ledgerStore = new LedgerStore(fs, reticleRoot);
244
+ const coverageAccepted = acceptCoverage ? await ledgerStore.acceptCurrent() : [];
245
+ const ledger = await ledgerStore.load();
246
+ const unexecuted = unexecutedChanged(ledger.code, changed);
247
+ // Changed files whose coverage predates the edit: their counts describe the old code, so
248
+ // `unexecuted` cannot judge them either way. Named rather than silently read as covered; not
249
+ // blocking, because a destructive exhaustive crawl after every edit is not a price a gate may set.
250
+ const coverageStale = staleChanged(ledger, changed, (file) => changedFileModifiedAt(process.cwd(), file)).filter((file) => !unexecuted.includes(file));
251
+ const result = gateDecision({
252
+ affected,
253
+ passing,
254
+ flaky,
255
+ downgraded,
256
+ deleted,
257
+ unflowed,
258
+ coverageRegressed: regressions(ledger),
259
+ unexecuted,
260
+ });
208
261
  // Verified-surface coverage over flows: how much of the saved suite this run actually exercised.
209
262
  const coverage = computeCoverage({ testids: [], signals: [], flows: allFlows.map((f) => f.name) }, { testids: [], signals: [], flows: passing });
210
263
  // A gate over an empty suite has not passed — it had nothing to check. Same rule the suite
@@ -217,6 +270,13 @@ hook = false) {
217
270
  quarantined: result.quarantined,
218
271
  ...(result.downgraded.length > 0 ? { downgraded: result.downgraded } : {}),
219
272
  ...(result.deleted.length > 0 ? { deletedCoverage: result.deleted } : {}),
273
+ ...(result.unflowed.length > 0 ? { unflowed: result.unflowed } : {}),
274
+ ...(result.coverageRegressed.length > 0
275
+ ? { coverageRegressed: result.coverageRegressed }
276
+ : {}),
277
+ ...(result.unexecuted.length > 0 ? { unexecuted: result.unexecuted } : {}),
278
+ ...(coverageStale.length > 0 ? { coverageStale } : {}),
279
+ ...(coverageAccepted.length > 0 ? { coverageAccepted } : {}),
220
280
  coverage: flowCoverage,
221
281
  });
222
282
  // Two non-zero codes, because two callers want opposite things from the same run. CI wants any
@@ -242,6 +302,9 @@ hook = false) {
242
302
  // A downgrade is reported per flow with its step indices; the hook names the flow.
243
303
  downgraded: result.downgraded.map((d) => d.flow),
244
304
  deleted: result.deleted,
305
+ unflowed: result.unflowed,
306
+ coverageRegressed: result.coverageRegressed.map((r) => `${r.level} ${String(r.was)}% -> ${String(r.now)}%`),
307
+ unexecuted: result.unexecuted,
245
308
  });
246
309
  if (message !== undefined)
247
310
  process.stderr.write(`${message}\n`);
@@ -27,3 +27,10 @@ export declare const missingOperand: (command: string, what: string) => ParseErr
27
27
  export declare const HEADED_FLAG = "--headed";
28
28
  export declare const PORT_FLAG = "--port";
29
29
  export declare const VERIFY_COMMAND = "verify";
30
+ /**
31
+ * The predicate flag, shared for the same reason the rest of this file is: two places have to name
32
+ * it. The grammar reads it, and `verify` itself has to say IN PROSE which flag it could not honour
33
+ * when the port is not serving a daemon — a refusal that never names the flag is how this one was
34
+ * missed for a release.
35
+ */
36
+ export declare const EXPECT_FLAG = "--expect";
@@ -37,3 +37,10 @@ export const missingOperand = (command, what) => ({
37
37
  export const HEADED_FLAG = '--headed';
38
38
  export const PORT_FLAG = '--port';
39
39
  export const VERIFY_COMMAND = 'verify';
40
+ /**
41
+ * The predicate flag, shared for the same reason the rest of this file is: two places have to name
42
+ * it. The grammar reads it, and `verify` itself has to say IN PROSE which flag it could not honour
43
+ * when the port is not serving a daemon — a refusal that never names the flag is how this one was
44
+ * missed for a release.
45
+ */
46
+ export const EXPECT_FLAG = '--expect';
@@ -7,6 +7,8 @@ export type VerifySuffix = {
7
7
  storageState?: string;
8
8
  sessionId?: string;
9
9
  expect?: unknown;
10
+ /** A path to read the predicate from, when `--expect-file` was used instead of `--expect`. */
11
+ expectFile?: string;
10
12
  explore?: boolean;
11
13
  persona?: string;
12
14
  select?: string[];
@@ -14,9 +16,4 @@ export type VerifySuffix = {
14
16
  kind: 'error';
15
17
  message: string;
16
18
  };
17
- /**
18
- * Parse `verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>]
19
- * [--session-id <id>]`. The first non-flag token is the preview URL. `defaultPort` is already
20
- * env + `.reticle.json` + 4400.
21
- */
22
19
  export declare function parseVerifySuffix(args: string[], defaultPort: number): VerifySuffix;
@@ -6,11 +6,28 @@
6
6
  * pin, a predicate to assert AND a persona to drive as — so it carries more grammar than the rest of
7
7
  * the CLI put together.
8
8
  */
9
- import { HEADED_FLAG, PORT_FLAG, VERIFY_COMMAND, missingOperand, missingValue, notANumber, unknownArgument, } from './cli-parse-grammar.js';
9
+ import { EXPECT_FLAG, HEADED_FLAG, PORT_FLAG, VERIFY_COMMAND, missingOperand, missingValue, notANumber, unknownArgument, } from './cli-parse-grammar.js';
10
10
  const TIMEOUT_FLAG = '--timeout';
11
11
  const STORAGE_STATE_FLAG = '--storage-state';
12
- const EXPECT_FLAG = '--expect';
12
+ /** The same predicate, read from a file, which no shell can mangle. See `parseExpect`. */
13
+ const EXPECT_FILE_FLAG = '--expect-file';
13
14
  const SESSION_ID_FLAG = '--session-id';
15
+ /**
16
+ * `--expect` and `--storage-state` cannot both be honoured, so the pair is refused rather than
17
+ * half-applied.
18
+ *
19
+ * `--expect` takes its verdict from the tab a running daemon already owns — it binds nothing and
20
+ * launches nothing, which is exactly what makes it work while the port is busy. There is nowhere in
21
+ * that path to load a storage state, and the flag was not refused, it was dropped: a restricted-user
22
+ * absence assertion was graded against a logged-in admin tab and PASSED. A false green is worse than
23
+ * a missing answer, and the only honest reply to a question this command cannot ask is to say so.
24
+ */
25
+ const MSG_EXPECT_WITH_STORAGE_STATE = `${EXPECT_FLAG} cannot be combined with ${STORAGE_STATE_FLAG}: the predicate is graded against ` +
26
+ 'the tab the running daemon already owns, so the storage state would never be loaded and the ' +
27
+ 'verdict would be about whoever is signed in there.\n' +
28
+ ` Use one of them: ${EXPECT_FLAG} on its own asserts against the session you have, and ` +
29
+ `${STORAGE_STATE_FLAG} without ${EXPECT_FLAG} makes Reticle drive the url with that state and ` +
30
+ 'replay the saved flows.';
14
31
  /** Let Reticle drive the app itself and record what it drove, when nothing is saved yet. */
15
32
  const EXPLORE_FLAG = '--explore';
16
33
  /** Who to be while exploring — a persona, or the business outcome to reach. Implies --explore. */
@@ -22,6 +39,46 @@ const SELECT_FLAG = '--select';
22
39
  * [--session-id <id>]`. The first non-flag token is the preview URL. `defaultPort` is already
23
40
  * env + `.reticle.json` + 4400.
24
41
  */
42
+ /**
43
+ * Read a predicate off the command line, allowing for what a Windows shell did to it on the way.
44
+ *
45
+ * `cmd.exe` does not treat `'` as a quote character, so the command every doc and README shows
46
+ * arrives with LITERAL single quotes around the JSON. That is unambiguous, so it is undone: a
47
+ * matching pair, and only when what is inside then parses.
48
+ *
49
+ * PowerShell calling a native executable strips the INNER double quotes instead, and `{kind:signal}`
50
+ * is not JSON and cannot be repaired without guessing which bare words were keys and which were
51
+ * string values. Guessing there would assert something the caller did not write, so it is named
52
+ * instead — with the two forms that do work, because a predicate that does not parse produces NO
53
+ * verdict at all, which is the worst of the three possible outcomes.
54
+ */
55
+ function parseExpect(raw) {
56
+ const unwrapped = raw.length > 1 && raw.startsWith("'") && raw.endsWith("'") ? raw.slice(1, -1) : raw;
57
+ try {
58
+ return { kind: 'ok', value: JSON.parse(unwrapped) };
59
+ }
60
+ catch {
61
+ if (looksQuoteStripped(unwrapped)) {
62
+ return {
63
+ kind: 'error',
64
+ message: `${EXPECT_FLAG} received ${unwrapped}, which has had its double quotes removed — that is ` +
65
+ 'PowerShell passing an argument to a native program, not a problem with the predicate. ' +
66
+ `Use ${EXPECT_FILE_FLAG} <path> (no quoting at all), or escape them: ` +
67
+ `${EXPECT_FLAG} "{\\"kind\\":\\"signal\\",\\"name\\":\\"order:placed\\"}"`,
68
+ };
69
+ }
70
+ // Named as a JSON problem rather than an unknown argument: the value IS the predicate, and
71
+ // "unknown argument" would send the reader looking at the flag instead of at their quoting.
72
+ return {
73
+ kind: 'error',
74
+ message: `${EXPECT_FLAG} needs a JSON predicate; could not parse: ${unwrapped}`,
75
+ };
76
+ }
77
+ }
78
+ /** A JSON object with every double quote gone: braces, a colon, and not one `"` anywhere. */
79
+ function looksQuoteStripped(value) {
80
+ return (value.startsWith('{') && value.endsWith('}') && value.includes(':') && !value.includes('"'));
81
+ }
25
82
  export function parseVerifySuffix(args, defaultPort) {
26
83
  let headless = true;
27
84
  let url;
@@ -29,6 +86,7 @@ export function parseVerifySuffix(args, defaultPort) {
29
86
  let storageState;
30
87
  let sessionId;
31
88
  let expect;
89
+ let expectFile;
32
90
  let explore = false;
33
91
  let persona;
34
92
  const select = [];
@@ -102,17 +160,17 @@ export function parseVerifySuffix(args, defaultPort) {
102
160
  const v = args[i];
103
161
  if (v === undefined)
104
162
  return missingValue(EXPECT_FLAG);
105
- try {
106
- expect = JSON.parse(v);
107
- }
108
- catch {
109
- // Named as a JSON problem rather than an unknown argument: the value IS the predicate, and
110
- // "unknown argument" would send the reader looking at the flag instead of at their quoting.
111
- return {
112
- kind: 'error',
113
- message: `${EXPECT_FLAG} needs a JSON predicate; could not parse: ${v}`,
114
- };
115
- }
163
+ const read = parseExpect(v);
164
+ if ('error' === read.kind)
165
+ return read;
166
+ expect = read.value;
167
+ }
168
+ else if (arg === EXPECT_FILE_FLAG) {
169
+ i++;
170
+ const v = args[i];
171
+ if (v === undefined)
172
+ return missingValue(EXPECT_FILE_FLAG);
173
+ expectFile = v;
116
174
  }
117
175
  else if (arg.startsWith('--')) {
118
176
  return unknownArgument(arg);
@@ -127,12 +185,27 @@ export function parseVerifySuffix(args, defaultPort) {
127
185
  }
128
186
  if (url === undefined)
129
187
  return missingOperand(VERIFY_COMMAND, 'a url');
188
+ // Two ways to say the same thing, and no way to say two different things. Preferring one
189
+ // silently is how a run asserts something the person did not write.
190
+ if (expect !== undefined && expectFile !== undefined) {
191
+ return {
192
+ kind: 'error',
193
+ message: `${EXPECT_FLAG} and ${EXPECT_FILE_FLAG} both name the predicate; pass one`,
194
+ };
195
+ }
196
+ // Refused here, where nothing has been bound and no browser exists yet, because the damage this
197
+ // pair does is a verdict taken against the wrong user. A predicate from a file is the same
198
+ // predicate, so it is refused the same way.
199
+ if ((expect !== undefined || expectFile !== undefined) && storageState !== undefined) {
200
+ return { kind: 'error', message: MSG_EXPECT_WITH_STORAGE_STATE };
201
+ }
130
202
  return {
131
203
  kind: 'ok',
132
204
  url,
133
205
  headless,
134
206
  port,
135
207
  ...(expect !== undefined ? { expect } : {}),
208
+ ...(expectFile !== undefined ? { expectFile } : {}),
136
209
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
137
210
  ...(storageState !== undefined ? { storageState } : {}),
138
211
  ...(sessionId !== undefined ? { sessionId } : {}),
@@ -15,7 +15,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
15
15
  * telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
16
16
  * the usage block rather than being a footnote somewhere else.
17
17
  */
18
- export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n reticle verify <url> --explore --persona \"<who does what>\", or the same thing\n from an agent as reticle_verify { action: \"explore\", persona: \"\u2026\" }\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N] [--json] (a readable block; --json for the event)\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>'] (one verdict, no saved flows needed \u2014 asks\n the daemon that is already running, so nothing is bound and nothing is stopped. This\n is the path when your client never loaded the reticle_* tools. exit 0 ONLY on\n verified:\"yes\" \u2014 \"unknown\" is not a pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
18
+ export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n reticle verify <url> --explore --persona \"<who does what>\", or the same thing\n from an agent as reticle_verify { action: \"explore\", persona: \"\u2026\" }\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N] [--json] (a readable block; --json for the event)\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved\n flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the\n single quotes the docs show, and PowerShell strips the inner double quotes, and\n a predicate that does not parse produces no verdict at all \u2014 asks the daemon that\n is already running, so nothing is bound and nothing is stopped. It NEEDS that\n daemon: with none on the port it refuses and names the flag, rather than reporting\n on your saved flows instead. Cannot be combined with --storage-state, which this\n path has nowhere to load. This is the path when your client never loaded the\n reticle_* tools. exit 0 ONLY on verified:\"yes\" \u2014 \"unknown\" is not a pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)\n reticle setup mcp (register the MCP server with your agents; the installer's registration half)\n reticle capsules (list the saved fail-to-pass bug capsules in .reticle/capsules)\n reticle hunt <dir> (aggregate a directory of crawl reports into one false-green rate)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
19
19
  /** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
20
20
  export declare const TelemetryAction: {
21
21
  readonly STATUS: "status";
@@ -58,6 +58,7 @@ export type CliResult = {
58
58
  env: string[];
59
59
  filesOnly: boolean;
60
60
  captureBodies: boolean;
61
+ hooks: boolean;
61
62
  json: boolean;
62
63
  relaunch: boolean;
63
64
  open: boolean;
@@ -148,6 +149,7 @@ export type CliResult = {
148
149
  storageState?: string;
149
150
  sessionId?: string;
150
151
  expect?: unknown;
152
+ expectFile?: string;
151
153
  explore?: boolean;
152
154
  persona?: string;
153
155
  select?: string[];
@@ -165,6 +167,11 @@ export type CliResult = {
165
167
  files: string[];
166
168
  since?: string;
167
169
  hook?: boolean;
170
+ acceptCoverage?: boolean;
171
+ } | {
172
+ kind: 'report';
173
+ session?: string;
174
+ hook: boolean;
168
175
  } | {
169
176
  kind: 'watch';
170
177
  url?: string;
@@ -184,6 +191,12 @@ export type CliResult = {
184
191
  kind: 'error';
185
192
  message: string;
186
193
  };
194
+ /**
195
+ * Whether this command talks to a daemon on the resolved port, and so must not run when
196
+ * the workspace port conflict (cli-port) has said the port is a guess. `init` carries a port but resolves the
197
+ * workspace itself, and a tutorial only dials one when asked to run.
198
+ */
199
+ export declare function dialsTheDaemon(parsed: CliResult): boolean;
187
200
  /**
188
201
  * @param defaultHeadless Whether a browser Reticle launches should be hidden when no flag says
189
202
  * otherwise. Injected rather than read from the environment here, because this module is pure —