@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -0,0 +1,123 @@
1
+ import { InstrumentationGapKind, instrumentationGap, } from '@reticlehq/core';
2
+ import { IntentStore } from '../intent/intent-store.js';
3
+ import { classifyFlowAssertions } from './flow-classify.js';
4
+ /**
5
+ * The link between a saved flow and the intent ledger — what the flow is FOR.
6
+ *
7
+ * A replay used to report only in the language of the DOM: step 3 drifted, this assertion failed.
8
+ * True, actionable, and illegible — the reader still had to reconstruct what stopped working. The
9
+ * ledger in `.reticle/intent.json` already holds the other half (prose statements, declared → bound
10
+ * → proved, and the verdict that discharged each one), so a flow does not need its own notion of
11
+ * intent. It needs an id.
12
+ *
13
+ * Nothing here derives an intent from step names. A flow that declares none reports that it declares
14
+ * none — a guessed intent puts words in the product owner's mouth and an agent will act on them,
15
+ * which is strictly worse than the honest absence.
16
+ */
17
+ /** Namespaced so a flow-declared intent never collides with one an agent declared by hand. */
18
+ const FLOW_INTENT_ID_PREFIX = 'flow:';
19
+ const FLOW_VERDICT_ID_PREFIX = 'flow_replay:';
20
+ const ID_SEPARATOR = ':';
21
+ /** The ledger id a flow's own prose goal is declared under. */
22
+ export function flowIntentId(flowName) {
23
+ return `${FLOW_INTENT_ID_PREFIX}${flowName}`;
24
+ }
25
+ /** The verdict that a replay of this flow, at this time, constitutes — what `provenBy` records. */
26
+ export function flowReplayVerdictId(flowName, at) {
27
+ return `${FLOW_VERDICT_ID_PREFIX}${flowName}${ID_SEPARATOR}${String(at)}`;
28
+ }
29
+ /**
30
+ * Which ledger row this flow answers to, or nothing.
31
+ *
32
+ * An explicit `intentId` wins, so a flow can discharge an intent that was declared before it existed.
33
+ * Otherwise the flow's own prose implies one. A flow with neither has no intent, and says so.
34
+ */
35
+ function intentIdOf(flow) {
36
+ if (flow.intentId !== undefined)
37
+ return flow.intentId;
38
+ // TRIMMED, not merely defined. `intent: " "` satisfied `!== undefined` and silenced the gap, so
39
+ // a flow whose stated goal is whitespace looked identical on disk to one somebody thought about.
40
+ //
41
+ // The worse direction of the two: this gap is the ONLY thing that would ever tell anyone the flow
42
+ // has no goal, so suppressing it wrongly means the flow replays for months and the day it goes red
43
+ // the report names the broken step and nothing else — the precise failure it was written to
44
+ // prevent. Found by probing the seam rather than reading it.
45
+ return undefined === flow.intent || '' === flow.intent.trim()
46
+ ? undefined
47
+ : flowIntentId(flow.name);
48
+ }
49
+ /**
50
+ * Promote a flow's prose goal into the ledger and stamp the id back onto the flow.
51
+ *
52
+ * The binding is attached only when the flow asserts an observable consequence, because that is the
53
+ * only case where replaying it green proves anything. A flow that asserts nothing leaves its intent
54
+ * `declared` — and `dischargeIntent` refuses to prove an unbound intent, so an assertion-free flow
55
+ * can never discharge one. That is the whole guard, and it is the ledger's own rule, not a new one.
56
+ *
57
+ * Returns the flow unchanged (and writes nothing) when there is no intent, so a flow file saved
58
+ * before any of this existed keeps its exact bytes.
59
+ */
60
+ export async function linkFlowIntent(store, flow) {
61
+ const id = intentIdOf(flow);
62
+ if (id === undefined)
63
+ return flow;
64
+ if (flow.intent !== undefined) {
65
+ await store.declare([{ id, statement: flow.intent, surface: { flow: flow.name } }]);
66
+ }
67
+ if (classifyFlowAssertions(flow).hasConsequenceAssertion) {
68
+ await store.bind(id, { flow: flow.name });
69
+ }
70
+ return { ...flow, intentId: id };
71
+ }
72
+ /**
73
+ * The nudge a flow saved without an intent earns, or nothing.
74
+ *
75
+ * Every save path routes through here, so the three of them cannot drift into three different ways
76
+ * of saying the same thing. It says it in the vocabulary the instrumentation gaps already use — what
77
+ * is missing, what it costs, and the one change that closes it — because "nobody said what this is
78
+ * for" is the same finding `undeclared-change` makes about an edit, one artifact later.
79
+ *
80
+ * Deliberately generic. It names no step, no assertion and not even the flow, for the reason this
81
+ * file already gives twice: anything specific enough to identify the goal is specific enough to be
82
+ * read AS the goal. It also never fails the save — a flow with no intent is still far better than no
83
+ * flow, and a verification tool that refuses to record work is one people route around.
84
+ */
85
+ export function flowIntentGap(flow) {
86
+ if (intentIdOf(flow) !== undefined)
87
+ return undefined;
88
+ return instrumentationGap(InstrumentationGapKind.NO_FLOW_INTENT, 'this flow declares no intent, so nothing on disk says what it is meant to prove', 'it will replay for months, and the day it goes red the report can only name the step that broke — not the thing that stopped being true — so whoever reads it has to reconstruct the goal from the steps before they can tell a real regression from a moved button');
89
+ }
90
+ /**
91
+ * What the ledger currently says this flow is for, or undefined when nothing declared it.
92
+ *
93
+ * Read from the ledger rather than from the flow file's own copy: the ledger is where an amendment
94
+ * lands, and a report that quotes the stale copy would be the two-vocabularies problem all over
95
+ * again — one sentence in review, a different one in the failure message.
96
+ */
97
+ export async function flowIntentStatement(store, flow) {
98
+ const id = intentIdOf(flow);
99
+ if (id === undefined)
100
+ return undefined;
101
+ const found = (await store.read()).find((intent) => intent.id === id);
102
+ return found?.statement ?? flow.intent;
103
+ }
104
+ /**
105
+ * Record that this replay proved the flow's intent. False when there was nothing to prove.
106
+ *
107
+ * Best-effort by construction: `IntentStore.discharge` returns false rather than throwing on an
108
+ * unknown or unbound id, because discharge runs off the back of a verdict and must never be the
109
+ * reason one fails to return.
110
+ */
111
+ export async function dischargeFlowIntent(store, flow, proof) {
112
+ const id = intentIdOf(flow);
113
+ if (id === undefined)
114
+ return false;
115
+ try {
116
+ return await store.discharge(id, proof);
117
+ }
118
+ catch {
119
+ // A ledger that cannot be written is a small problem; a verdict that never returns because of
120
+ // one is a large problem. The replay already succeeded — the proof is simply not recorded.
121
+ return false;
122
+ }
123
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * What the team already knows about the flow being verified.
3
+ *
4
+ * ## Why a replay should ask at all
5
+ *
6
+ * Shared memory is written on every verification and, measured across this project's whole corpus,
7
+ * read essentially never: every subject on the dashboard showed `fetches: 0` and `readers: []`. Not
8
+ * because the data is useless — because consulting it is a SEPARATE act an agent has to remember to
9
+ * perform. Knowledge that must be deliberately fetched is a wiki, and wikis are not read.
10
+ *
11
+ * So verification asks on the agent's behalf. Replaying `checkout` returns what the project knows
12
+ * about checkout beside the verdict, which is the difference between memory the platform stores and
13
+ * memory the platform USES — and it is the only way the read counts on the coverage map ever become
14
+ * a real signal rather than a number nobody moves.
15
+ *
16
+ * ## Why this file is pure
17
+ *
18
+ * The fetch is the caller's. What a replay is ENTITLED to ask for — which subject, how much of it —
19
+ * is a rule worth testing without a network, and the rule is where the mistakes live.
20
+ */
21
+ import type { FlowFile } from '@reticlehq/core';
22
+ /**
23
+ * How many records travel back with a verdict.
24
+ *
25
+ * Small on purpose. This rides on every replay, and a verdict that arrives with fifty statements
26
+ * attached is one whose actual result is now below the fold. The subject page is one call away for
27
+ * an agent that wants the rest.
28
+ */
29
+ export declare const MEMORY_CONSULT_LIMIT = 5;
30
+ /**
31
+ * The subject a replay should ask about.
32
+ *
33
+ * The FLOW NAME, because a flow is a feature and that is the axis the store shards on — replaying
34
+ * `checkout` should surface what the team knows about checkout, not about the route it happens to
35
+ * start on. A flow whose name says nothing gets nothing: asking for `unsorted` would return the
36
+ * project's junk drawer on every verdict, which is worse than silence because it looks like an
37
+ * answer.
38
+ */
39
+ export declare function consultSubjectFor(flow: FlowFile): string | undefined;
40
+ /** One thing the project knows, as it travels back with a verdict. */
41
+ export interface ConsultedMemory {
42
+ statement: string;
43
+ status: string;
44
+ }
45
+ /**
46
+ * Trim what came back to what is worth carrying.
47
+ *
48
+ * PROVED records first: a statement something has actually verified is stronger evidence about the
49
+ * feature than one somebody proposed and never checked. Beyond that the order is the server's, and
50
+ * anything without a statement is dropped rather than rendered as an empty line.
51
+ */
52
+ export declare function selectConsulted(entries: readonly {
53
+ statement?: unknown;
54
+ status?: unknown;
55
+ }[], limit?: number): ConsultedMemory[];
@@ -0,0 +1,38 @@
1
+ /**
2
+ * How many records travel back with a verdict.
3
+ *
4
+ * Small on purpose. This rides on every replay, and a verdict that arrives with fifty statements
5
+ * attached is one whose actual result is now below the fold. The subject page is one call away for
6
+ * an agent that wants the rest.
7
+ */
8
+ export const MEMORY_CONSULT_LIMIT = 5;
9
+ /**
10
+ * The subject a replay should ask about.
11
+ *
12
+ * The FLOW NAME, because a flow is a feature and that is the axis the store shards on — replaying
13
+ * `checkout` should surface what the team knows about checkout, not about the route it happens to
14
+ * start on. A flow whose name says nothing gets nothing: asking for `unsorted` would return the
15
+ * project's junk drawer on every verdict, which is worse than silence because it looks like an
16
+ * answer.
17
+ */
18
+ export function consultSubjectFor(flow) {
19
+ const name = flow.name;
20
+ if ('string' !== typeof name || 0 === name.trim().length)
21
+ return undefined;
22
+ return name.trim();
23
+ }
24
+ /**
25
+ * Trim what came back to what is worth carrying.
26
+ *
27
+ * PROVED records first: a statement something has actually verified is stronger evidence about the
28
+ * feature than one somebody proposed and never checked. Beyond that the order is the server's, and
29
+ * anything without a statement is dropped rather than rendered as an empty line.
30
+ */
31
+ export function selectConsulted(entries, limit = MEMORY_CONSULT_LIMIT) {
32
+ const usable = entries.flatMap((e) => 'string' === typeof e.statement && e.statement.trim().length > 0
33
+ ? [{ statement: e.statement, status: 'string' === typeof e.status ? e.status : 'unknown' }]
34
+ : []);
35
+ const proved = usable.filter((e) => 'proved' === e.status);
36
+ const rest = usable.filter((e) => 'proved' !== e.status);
37
+ return [...proved, ...rest].slice(0, limit);
38
+ }
@@ -1,4 +1,7 @@
1
- import { FlowErrorCode, type FlowFile, type FlowReplayResult, type ReticleEvent } from '@reticlehq/core';
1
+ import { FlowErrorCode, type CommandResult, type FlowFile, type FlowReplayResult, type ReticleEvent } from '@reticlehq/core';
2
+ import type { ArrivalClock } from '../tools/navigate-arrival.js';
3
+ import type { SessionManager } from '../session/session-manager.js';
4
+ import type { Session } from '../session/session.js';
2
5
  import type { ToolDeps } from '../tools/tools.js';
3
6
  export declare function latestRecordedFlow(events: ReticleEvent[]): {
4
7
  name: string;
@@ -6,18 +9,25 @@ export declare function latestRecordedFlow(events: ReticleEvent[]): {
6
9
  } | undefined;
7
10
  /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
8
11
  export declare function flowErrorMessage(code: FlowErrorCode): string;
12
+ /** The slice of a session the start-path logic reads: the live URL plus the event buffer. */
13
+ interface StartPathSession {
14
+ url?: string;
15
+ eventsSince(cursor: number): ReticleEvent[];
16
+ }
9
17
  /**
10
18
  * When a flow records the page its journey started on (`startPath`) and the tab is currently on a
11
19
  * different route, step 1 drifts for a reason that has nothing to do with the app regressing — the
12
20
  * anchor simply isn't on this page yet. Detect that so the decision says "navigate there first"
13
21
  * instead of a mystifying "a step no longer matches". Returns undefined when the routes agree or the
14
- * current route is unobservable (no route event) — never a false alarm. Replay itself does NOT
15
- * navigate: a full-page load mid-replay tears down the session socket; the agent navigates between
16
- * tool calls (reticle_navigate) where the session is re-resolved fresh.
22
+ * current route is unobservable — never a false alarm. Replay normally never gets here on a
23
+ * wrong page (arriveAtStartPath navigates before step 1); this is the fallback for when that
24
+ * navigation was refused or the SDK never reconnected in the window.
17
25
  */
18
- export declare function startPathMismatchHint(flow: FlowFile, session: {
19
- eventsSince(cursor: number): ReticleEvent[];
20
- }): string | undefined;
26
+ export declare function startPathMismatchHint(flow: FlowFile, session: StartPathSession): string | undefined;
27
+ export declare function arriveAtStartPath(sessions: SessionManager, session: StartPathSession & {
28
+ id: string;
29
+ command(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
30
+ }, flow: FlowFile, timeoutMs?: number, clock?: ArrivalClock): Promise<Session | undefined>;
21
31
  /**
22
32
  * Replay one named flow end to end: load → re-resolve+run each step → assert the success oracle →
23
33
  * status + decision. Shared by reticle_flow_replay (single flow) and reticle_flow_verify (whole suite) so
@@ -30,3 +40,4 @@ export declare function replayNamedFlow(deps: ToolDeps, args: Record<string, unk
30
40
  * documented to work headless), so a missing/unknown session degrades to the global/legacy store.
31
41
  */
32
42
  export declare function sessionProjectId(deps: ToolDeps, sessionId: string | undefined): string | undefined;
43
+ export {};
@@ -1,8 +1,15 @@
1
- import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, RunKind, RunStatus, } from '@reticlehq/core';
2
- import { asString } from '../tools/tools-helpers.js';
1
+ import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
2
+ import { asRecord, asString } from '../tools/tools-helpers.js';
3
+ import { carryReticleIdentity } from '../tools/lease-tools.js';
3
4
  import { replayFlow } from './flow-replay.js';
5
+ import { anchorQueryArgs } from './flow-step-runners.js';
6
+ import { queryRefs } from './replay.js';
4
7
  import { assertSuccess, dynamicTestids, successLabel, SUCCESS_STEP_TOOL } from './flow-success.js';
5
8
  import { buildDecision, unverifiableReason } from './decision.js';
9
+ import { classifyFlowAssertions } from './flow-classify.js';
10
+ import { dischargeFlowIntent, flowIntentStatement, flowReplayVerdictId } from './flow-intent.js';
11
+ import { IntentStore } from '../intent/intent-store.js';
12
+ import { sessionRoot } from '../project/session-root.js';
6
13
  import { waitForPredicate } from '../events/predicate.js';
7
14
  import { computeSegments } from '../journal/rollups.js';
8
15
  import { AssertionTiersStore } from './assertion-tiers-store.js';
@@ -10,9 +17,11 @@ import { toFlowSources } from './flow-sources.js';
10
17
  import { reportAndAccumulate } from '../journal/deviation-service.js';
11
18
  import { EnvelopeStore } from '../journal/envelope-store.js';
12
19
  import { homedir } from 'node:os';
13
- import { syncRunRecordToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
20
+ import { cloudFetch, syncRunRecordToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
14
21
  import { resolveProjectCloud } from '../cloud/cloud-config.js';
22
+ import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
15
23
  import { log } from '../log.js';
24
+ import { flowsForSession } from './flow-store-for-session.js';
16
25
  export function latestRecordedFlow(events) {
17
26
  for (let i = events.length - 1; i >= 0; i--) {
18
27
  const event = events[i];
@@ -50,10 +59,12 @@ function replayToRunStatus(status) {
50
59
  }
51
60
  /**
52
61
  * Append a flow-replay outcome to .reticle/project.json (never throws into replay) and, when logged in,
53
- * best-effort mirror it to Reticle Cloud so the team's server-side regression history stays current. The
62
+ * best-effort mirror it to Reticle so the team's server-side regression history stays current. The
54
63
  * cloud push is fire-and-forget: not logged in → skipped, a network failure is logged and swallowed.
55
64
  */
56
- async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId) {
65
+ async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId,
66
+ /** The APP's `.reticle`, resolved from the session — never the daemon's own. */
67
+ recordRoot) {
57
68
  const runStatus = replayToRunStatus(status);
58
69
  await deps.project.recordRun({
59
70
  kind: RunKind.FLOW_REPLAY,
@@ -63,35 +74,223 @@ async function recordReplayRun(deps, name, status, driftSteps, durationMs, proje
63
74
  durationMs,
64
75
  });
65
76
  // Per-project cloud: push memory outcomes only when cloud is attached AND memory sync is enabled.
66
- const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
77
+ // Rooted at the APP, not the daemon — see consultProjectMemory. This call had the same defect and
78
+ // it was worse here: a replay outcome that should have reached the dashboard silently did not,
79
+ // because the daemon's own directory has no link file.
80
+ const cloud = await resolveProjectCloud(deps.fs, recordRoot, homedir(), process.env);
67
81
  if (null === cloud.config || !cloud.policy.memory)
68
82
  return; // not attached / memory disabled → local only
69
- const result = await syncRunRecordToCloud({ kind: RunKind.FLOW_REPLAY, name, status: runStatus, at: deps.now(), durationMs }, projectId, cloud.config, (url, init) => fetch(url, init));
83
+ const result = await syncRunRecordToCloud({ kind: RunKind.FLOW_REPLAY, name, status: runStatus, at: deps.now(), durationMs }, projectId, cloud.config, cloudFetch);
70
84
  if (result.outcome !== SyncOutcome.SYNCED) {
71
85
  log('cloud-run-record-sync-failed', { flow: name, status: result.status, error: result.error });
72
86
  }
73
87
  }
88
+ /**
89
+ * The route the tab is on now: the last observed route.change, falling back to the pathname of the
90
+ * session's own URL. The fallback matters — a tab that hard-loaded its page emits no route event
91
+ * (the route observer only sees pushState/replaceState/popstate), but the HELLO url still names
92
+ * where it sits, and without it a wrong-page replay reported plain drift with no hint at all.
93
+ */
94
+ function currentPathOf(session) {
95
+ const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
96
+ const data = routes.at(-1)?.data ?? {};
97
+ // pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
98
+ // the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
99
+ // fired however far the tab had drifted, on the router desktop renderers use by default.
100
+ const observed = asString(data['pathname']) ?? asString(data['to']);
101
+ if (observed !== undefined)
102
+ return `${observed}${asString(data['hash']) ?? ''}`;
103
+ if (session.url === undefined)
104
+ return undefined;
105
+ try {
106
+ const parsed = new URL(session.url);
107
+ return `${parsed.pathname}${parsed.hash}`;
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ }
113
+ /** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
114
+ function samePath(a, b) {
115
+ return a.replace(/\/$/, '') === b.replace(/\/$/, '');
116
+ }
117
+ /**
118
+ * Ask the project what it already knows about this flow.
119
+ *
120
+ * Best-effort in the strongest sense: an unlinked project, a disabled memory policy, an offline
121
+ * laptop and a server that answers nonsense all reach the same place — the verdict returns without
122
+ * a memory block. A verification that FAILED because the knowledge lookup failed would be a worse
123
+ * product than one that never looked, and this is the path every replay takes.
124
+ *
125
+ * The read is also what makes the coverage map's fetch counts mean anything: they were zero across
126
+ * the entire corpus, not because memory is useless but because consulting it was a separate act
127
+ * nobody performed. Now the platform performs it.
128
+ */
129
+ async function consultProjectMemory(deps, flow, root) {
130
+ const subject = consultSubjectFor(flow);
131
+ if (subject === undefined)
132
+ return undefined;
133
+ try {
134
+ // The APP's root, not the daemon's. `deps.reticleRoot` is wherever the daemon was launched,
135
+ // which for a user-scoped MCP registration is almost never the project being verified — so the
136
+ // link file it reads is the wrong one, or absent, and every project silently reads as
137
+ // "not attached". Same class of bug as the flow store resolving per daemon instead of per
138
+ // session, and it is invisible: the feature simply never appears.
139
+ const cloud = await resolveProjectCloud(deps.fs, root, homedir(), process.env);
140
+ if (null === cloud.config || !cloud.policy.memory)
141
+ return undefined;
142
+ const url = `${cloud.config.url}/v1/memory?subject=${encodeURIComponent(subject)}`;
143
+ const res = await cloudFetch(url, {
144
+ method: 'GET',
145
+ headers: { authorization: `Bearer ${cloud.config.apiKey}` },
146
+ });
147
+ if (200 !== res.status)
148
+ return undefined;
149
+ // `cloudFetch` hands back a real `Response`, so the body is a METHOD. Reading `res.json` as a
150
+ // property yields the function itself, `.entries` on it is undefined, and the whole feature
151
+ // fails silently to "the project knows nothing" — which is indistinguishable from the honest
152
+ // empty case and is why this took a live drive to notice at all.
153
+ const body = (await res.json());
154
+ const entries = body?.entries;
155
+ if (!Array.isArray(entries))
156
+ return undefined;
157
+ const picked = selectConsulted(entries);
158
+ return 0 === picked.length ? undefined : picked;
159
+ }
160
+ catch {
161
+ // See the note above: never the reason a verdict fails to return.
162
+ return undefined;
163
+ }
164
+ }
74
165
  /**
75
166
  * When a flow records the page its journey started on (`startPath`) and the tab is currently on a
76
167
  * different route, step 1 drifts for a reason that has nothing to do with the app regressing — the
77
168
  * anchor simply isn't on this page yet. Detect that so the decision says "navigate there first"
78
169
  * instead of a mystifying "a step no longer matches". Returns undefined when the routes agree or the
79
- * current route is unobservable (no route event) — never a false alarm. Replay itself does NOT
80
- * navigate: a full-page load mid-replay tears down the session socket; the agent navigates between
81
- * tool calls (reticle_navigate) where the session is re-resolved fresh.
170
+ * current route is unobservable — never a false alarm. Replay normally never gets here on a
171
+ * wrong page (arriveAtStartPath navigates before step 1); this is the fallback for when that
172
+ * navigation was refused or the SDK never reconnected in the window.
82
173
  */
83
174
  export function startPathMismatchHint(flow, session) {
84
175
  const startPath = flow.startPath;
85
176
  if (startPath === undefined || 0 === startPath.length)
86
177
  return undefined;
87
- const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
88
- const last = routes.at(-1);
89
- const data = last?.data ?? {};
90
- const current = asString(data['pathname']) ?? asString(data['to']);
91
- if (current === undefined || current === startPath)
178
+ const current = currentPathOf(session);
179
+ if (current === undefined || samePath(current, startPath))
92
180
  return undefined;
93
181
  return `this flow's journey starts on ${startPath} but the tab is on ${current} — navigate there (reticle_navigate { url: "${startPath}" }), then replay`;
94
182
  }
183
+ /** How long replay waits for the SDK to reconnect on the start page before falling back to the hint. */
184
+ const START_PATH_ARRIVAL_TIMEOUT_MS = 5_000;
185
+ const START_PATH_POLL_MS = 100;
186
+ const REAL_ARRIVAL_CLOCK = {
187
+ now: () => Date.now(),
188
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
189
+ };
190
+ /**
191
+ * The session `oldId` denotes right now — itself, or the tombstone successor `resolve` rebinds to
192
+ * after a full-document navigation — but only once it actually sits on `target`. Undefined while
193
+ * the tab is still travelling (or in the gap between teardown and the successor's HELLO, when
194
+ * `resolve` throws). Keyed to the navigated tab's own identity on purpose: matching "any session at
195
+ * the target page" would happily hand replay an unrelated tab that was already sitting there.
196
+ */
197
+ function arrivedSuccessor(sessions, oldId, target) {
198
+ try {
199
+ const candidate = sessions.resolve(oldId);
200
+ const path = currentPathOf(candidate);
201
+ return path !== undefined && samePath(path, target) ? candidate : undefined;
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ }
207
+ /**
208
+ * Replay's half of the FlowFile contract: navigate to the flow's `startPath` before step 1.
209
+ *
210
+ * A full-page load tears down the session socket, so the navigation must happen here — before any
211
+ * step runs — and the replay continues on the session the SDK reconnects as (found via the same
212
+ * tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
213
+ * when the flow carries no startPath, the tab is already there, the current route is unobservable,
214
+ * the navigation is refused, or the SDK never reconnects in the window, it returns undefined and
215
+ * replay proceeds on the connected session as before — with startPathMismatchHint turning any
216
+ * resulting drift into an actionable next move rather than a mystifying one.
217
+ */
218
+ /**
219
+ * Can step 1 start from where the tab already is?
220
+ *
221
+ * `startPathMismatchHint` states the premise the navigation below exists to serve: on the wrong
222
+ * route "the anchor simply isn't on this page yet". That is the condition worth a page load — and
223
+ * it was never the condition checked. `arriveAtStartPath` fired on a route mismatch ALONE, so a
224
+ * flow whose first anchor lives somewhere persistent (a sidebar, a header, a nav rail — present on
225
+ * every route) was navigated for a problem it did not have.
226
+ *
227
+ * A navigation is a full page load, which tears the session down and brings the app back in its
228
+ * cold state. For the ordinary case of a flow recorded after signing in, that cold state is the
229
+ * LOGIN SCREEN: step 1 then reports its anchor missing and names the component that holds it, which
230
+ * is a correct sentence about a file that is completely fine. Found by the benchmark, on two flows
231
+ * with identical steps where the first replayed clean and the second — run after it, with the tab
232
+ * drifted off `/` — did not.
233
+ *
234
+ * So the question is asked directly. A best-effort step that can leave the caller WORSE off than
235
+ * skipping it is not best-effort; checking first is what makes the description true.
236
+ *
237
+ * Unresolvable either way (no anchor we can query, a failed query) reads as "cannot tell", and the
238
+ * navigation goes ahead — the pre-existing behaviour, and the safe direction for a check whose whole
239
+ * job is to avoid making things worse.
240
+ */
241
+ async function firstStepResolvesHere(session, flow) {
242
+ const first = flow.steps?.[0];
243
+ if (first === undefined)
244
+ return false;
245
+ const args = anchorQueryArgs(first.anchor);
246
+ if (null === args)
247
+ return false;
248
+ try {
249
+ return 0 < queryRefs(await session.command(ReticleCommand.QUERY, args)).length;
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ }
255
+ export async function arriveAtStartPath(sessions, session, flow, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
256
+ const target = flow.startPath;
257
+ if (target === undefined || 0 === target.length)
258
+ return undefined;
259
+ const current = currentPathOf(session);
260
+ if (current === undefined || samePath(current, target))
261
+ return undefined;
262
+ // The route differs — but that only matters if it stops the flow starting. See above.
263
+ if (await firstStepResolvesHere(session, flow))
264
+ return undefined;
265
+ let destination;
266
+ try {
267
+ // startPath is a pathname (a host belongs to the machine, not the journey) — resolve it
268
+ // against the tab's own URL to get something the browser can be sent to.
269
+ destination = new URL(target, session.url).toString();
270
+ }
271
+ catch {
272
+ return undefined; // no usable base URL — nowhere to navigate from
273
+ }
274
+ // A leased tab is addressed by URL params, so navigating without them would strand the lease.
275
+ const url = carryReticleIdentity(session.url, destination);
276
+ try {
277
+ const outcome = await session.command(ReticleCommand.NAVIGATE, { url });
278
+ if (!outcome.ok || true !== asRecord(outcome.result)['ok'])
279
+ return undefined;
280
+ }
281
+ catch {
282
+ return undefined;
283
+ }
284
+ const deadline = clock.now() + timeoutMs;
285
+ for (;;) {
286
+ const arrived = arrivedSuccessor(sessions, session.id, target);
287
+ if (arrived !== undefined)
288
+ return arrived;
289
+ if (clock.now() >= deadline)
290
+ return undefined;
291
+ await clock.sleep(START_PATH_POLL_MS);
292
+ }
293
+ }
95
294
  /**
96
295
  * Replay one named flow end to end: load → re-resolve+run each step → assert the success oracle →
97
296
  * status + decision. Shared by reticle_flow_replay (single flow) and reticle_flow_verify (whole suite) so
@@ -110,9 +309,11 @@ export async function replayNamedFlow(deps, args) {
110
309
  catch {
111
310
  projectId = undefined;
112
311
  }
113
- const loaded = await deps.flows.load(name, projectId);
312
+ // The app's store, not the daemon's: this load answering `flow_not_found` for a flow plainly on
313
+ // disk is what made replay unusable from a daemon started outside the project.
314
+ const loaded = await flowsForSession(deps, projectId).flows.load(name, projectId);
114
315
  if (!loaded.ok) {
115
- await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId);
316
+ await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId, sessionRoot(deps, asString(args['sessionId'])));
116
317
  return {
117
318
  name,
118
319
  status: ReplayStatus.ERROR,
@@ -120,9 +321,22 @@ export async function replayNamedFlow(deps, args) {
120
321
  error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
121
322
  };
122
323
  }
123
- const session = deps.sessions.resolve(asString(args['sessionId']));
124
- // Captured before replay: if the tab isn't on the flow's start page, a step-1 drift is a wrong-page
125
- // symptom, not a regression — surface that on the decision instead of a bare "a step no longer matches".
324
+ // What this flow is FOR, from the shared ledger — so a failure can report the business outcome
325
+ // that stopped being true before the step that stopped being green. Undefined when nothing
326
+ // declared one, which the decision then says plainly rather than inventing a goal from step names.
327
+ /*
328
+ * The APP's `.reticle`, resolved once. Everything below that reaches the project's own files —
329
+ * the intent ledger, the cloud link, the memory consultation — takes THIS, not `deps.reticleRoot`,
330
+ * which is wherever the daemon happened to be launched.
331
+ */
332
+ const replayRoot = sessionRoot(deps, asString(args['sessionId']));
333
+ const intents = new IntentStore(deps.fs, replayRoot, { now: deps.now });
334
+ const intentSaid = await flowIntentStatement(intents, loaded.value);
335
+ const connected = deps.sessions.resolve(asString(args['sessionId']));
336
+ // The FlowFile contract: replay navigates to the flow's startPath before step 1, and the steps run
337
+ // on the session the SDK reconnects as. When arrival can't be confirmed, replay proceeds on the
338
+ // connected session — and the hint below turns the wrong-page drift into an actionable next move.
339
+ const session = (await arriveAtStartPath(deps.sessions, connected, loaded.value)) ?? connected;
126
340
  const startPathHint = startPathMismatchHint(loaded.value, session);
127
341
  // Floor the success oracle at the start of THIS replay so a stale signal from a prior run
128
342
  // in the same session can't fake a pass.
@@ -146,7 +360,7 @@ export async function replayNamedFlow(deps, args) {
146
360
  const driftSteps = steps.filter((s) => s.drift !== undefined).length;
147
361
  const allOk = steps.every((s) => s.ok);
148
362
  const status = driftSteps > 0 ? ReplayStatus.DRIFT : allOk ? ReplayStatus.OK : ReplayStatus.ERROR;
149
- await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId);
363
+ await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId, replayRoot);
150
364
  // Anti-reward-hacking baseline: record what this flow asserted ONLY when it passed clean. A
151
365
  // failing run must never become the baseline a later weakening is measured against.
152
366
  if (status === ReplayStatus.OK) {
@@ -156,9 +370,31 @@ export async function replayNamedFlow(deps, args) {
156
370
  })),
157
371
  // Record what this flow COVERS so a later deletion can be scoped to the files that changed.
158
372
  toFlowSources([{ name, steps: loaded.value.steps }])[0]?.sources ?? []);
373
+ // The ledger learns what this run proved. Only a flow that COULD have gone red discharges: an
374
+ // assertion-free flow is never bound, and `dischargeIntent` refuses an unbound intent, so the
375
+ // guard here is the cheap half of a rule the ledger already enforces. `grade` is what makes a
376
+ // later weakening visible — an intent re-proved by a weaker flow says so in the git diff.
377
+ if (unverifiableReason(loaded.value) === undefined) {
378
+ const provedAt = deps.now();
379
+ await dischargeFlowIntent(intents, loaded.value, {
380
+ verdictId: flowReplayVerdictId(name, provedAt),
381
+ grade: classifyFlowAssertions(loaded.value).grade,
382
+ at: provedAt,
383
+ });
384
+ }
159
385
  }
160
386
  // Push-default: the deviation report over this drive's segments, learned across runs. Best-effort.
161
387
  const deviation = await computeReplayDeviation(deps, session, replayFloor);
388
+ /*
389
+ * What the team already knows about this flow, fetched on the agent's behalf.
390
+ *
391
+ * Attached to the FAILING path as well as the clean one, deliberately: a drift is exactly the
392
+ * moment somebody needs to know what this feature is supposed to do and who established it. A
393
+ * knowledge base you only see when everything is already fine is decoration.
394
+ */
395
+ // No projectId argument: the API key is already bound to one project server-side, so passing a
396
+ // second opinion about which project this is would only create a way for the two to disagree.
397
+ const knows = await consultProjectMemory(deps, loaded.value, replayRoot);
162
398
  const failed = steps.find((step) => !step.ok && step.drift === undefined);
163
399
  if (failed !== undefined) {
164
400
  const errored = {
@@ -167,13 +403,17 @@ export async function replayNamedFlow(deps, args) {
167
403
  steps,
168
404
  error: { code: ReplayStatus.ERROR, message: failed.error ?? 'flow action failed' },
169
405
  };
170
- errored.decision = buildDecision(errored, loaded.value);
406
+ errored.decision = buildDecision(errored, loaded.value, intentSaid);
171
407
  applyStartPathHint(errored, startPathHint);
172
408
  if (deviation !== undefined)
173
409
  errored.deviation = deviation;
410
+ if (knows !== undefined)
411
+ errored.knows = knows;
174
412
  return errored;
175
413
  }
176
414
  const result = { name, status, steps };
415
+ if (knows !== undefined)
416
+ result.knows = knows;
177
417
  // A green that cannot go red is not a pass. `reticle_flow_verify` already refuses to count these,
178
418
  // via this same function -- a single-flow caller saw a bare `ok` and had no way to learn the flow
179
419
  // asserts nothing. Derived from the same helper on purpose: two copies of this judgement would
@@ -182,7 +422,7 @@ export async function replayNamedFlow(deps, args) {
182
422
  if (cannotFail !== undefined)
183
423
  result.unverifiable = { reason: cannotFail };
184
424
  if (status !== ReplayStatus.OK)
185
- result.decision = buildDecision(result, loaded.value);
425
+ result.decision = buildDecision(result, loaded.value, intentSaid);
186
426
  applyStartPathHint(result, startPathHint);
187
427
  if (deviation !== undefined)
188
428
  result.deviation = deviation;