@reticlehq/server 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -6,10 +6,11 @@
6
6
  * point: the action window must open and close identically no matter which anchor found the element,
7
7
  * or a step's events land in the wrong window depending on how it was addressed.
8
8
  */
9
- import { AnchorKind, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
9
+ import { AnchorKind, DEGRADED_ANCHOR_ROLE, DriftReason, QueryBy, ReticleCommand, } from '@reticlehq/core';
10
10
  import { ReticleTool } from '../tools/tool-names.js';
11
11
  import { replayActionArgs } from './replay.js';
12
- import { componentLabel, componentQueryArgs, resolveQuery } from './flow-replay.js';
12
+ import { anchorLabel, componentLabel, componentQueryArgs, resolveQuery, testidDrift, } from './flow-replay.js';
13
+ import { nearestRoleName } from './role-anchor-nearest.js';
13
14
  /** Query args for a NAMED role anchor — the handle that identifies an instance, not a JSX site. */
14
15
  function roleQueryArgs(anchor) {
15
16
  return {
@@ -19,11 +20,53 @@ function roleQueryArgs(anchor) {
19
20
  };
20
21
  }
21
22
  /** Run one role+name-anchored step: re-resolve via QUERY by:'role', ACT on the live ref, else drift. */
23
+ /**
24
+ * The controls actually on the page, as role + name, for the nearest-match scan.
25
+ *
26
+ * Reads the interactive snapshot rather than a second query per candidate: one round trip, and it is
27
+ * the same view the coverage tool uses, so "what is on this page" has one definition.
28
+ */
29
+ async function nearestRoleNameOnPage(session, anchor) {
30
+ const wanted = 'string' === typeof anchor.name ? anchor.name : undefined;
31
+ if (wanted === undefined)
32
+ return null;
33
+ try {
34
+ // Straight off the QUERY result: resolveQuery reduces to refs, and the NAMES are what this needs.
35
+ const result = await session.command(ReticleCommand.QUERY, {
36
+ by: QueryBy.ROLE,
37
+ value: anchor.role,
38
+ });
39
+ const payload = result.result;
40
+ const elements = 'object' === typeof payload && payload !== null
41
+ ? payload.elements
42
+ : undefined;
43
+ if (!Array.isArray(elements))
44
+ return null;
45
+ const candidates = elements
46
+ .map((element) => 'object' === typeof element && element !== null
47
+ ? element.name
48
+ : undefined)
49
+ .filter((name) => 'string' === typeof name && name.length > 0)
50
+ .map((name) => ({ role: anchor.role, name }));
51
+ return nearestRoleName(anchor.role, wanted, candidates);
52
+ }
53
+ catch {
54
+ // A drift report must never fail because the suggestion lookup did.
55
+ return null;
56
+ }
57
+ }
22
58
  export async function runRoleStep(session, step, index, anchor, confirmDangerous, sleep) {
23
59
  const label = `${anchor.role} "${String(anchor.name)}"`;
24
60
  const { refs } = await resolveQuery(session, roleQueryArgs(anchor), sleep);
25
61
  const ref = refs[0];
26
62
  if (ref === undefined) {
63
+ // `nearest` used to be the literal null, so heal answered "no nearest match cleared the
64
+ // confidence floor" for EVERY role-anchored drift — a structural limit reported as a judgement
65
+ // about candidates, when nothing had looked. It looks now, and it is deliberately stricter than
66
+ // the testid path: a role name is user-visible text, so a near-match is far more likely to be a
67
+ // different control than a renamed one. A candidate here is INFORMATION for the agent; heal
68
+ // still refuses to rebind a role anchor on its own.
69
+ const nearest = await nearestRoleNameOnPage(session, anchor);
27
70
  return {
28
71
  step: index,
29
72
  tool: step.tool,
@@ -31,9 +74,11 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
31
74
  ok: false,
32
75
  drift: {
33
76
  reasonKind: DriftReason.COMPONENT_NOT_FOUND,
34
- reason: `role anchor ${label} not found`,
77
+ reason: null === nearest
78
+ ? `role anchor ${label} not found, and no surviving ${anchor.role} has a similar name — anchor this step to a data-testid`
79
+ : `role anchor ${label} not found; the closest surviving ${anchor.role} is "${nearest}" — confirm it is the same control before rebinding, or add a data-testid`,
35
80
  anchor: label,
36
- nearest: null,
81
+ nearest,
37
82
  },
38
83
  };
39
84
  }
@@ -43,7 +88,7 @@ export async function runRoleStep(session, step, index, anchor, confirmDangerous
43
88
  * Dispatch one already-resolved step. Shared so every anchor kind runs the action the same way —
44
89
  * including the action window, whose open/close must not depend on which anchor found the element.
45
90
  */
46
- export async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous) {
91
+ async function actOnResolvedRef(session, step, index, label, ref, confirmDangerous) {
47
92
  session.beginAction?.(ReticleTool.FLOW_REPLAY, { ref, action: step.action ?? '' });
48
93
  let act;
49
94
  try {
@@ -65,7 +110,7 @@ export async function actOnResolvedRef(session, step, index, label, ref, confirm
65
110
  export async function runComponentStep(session, step, index, anchor, confirmDangerous, sleep) {
66
111
  const label = componentLabel(anchor);
67
112
  const { refs } = await resolveQuery(session, componentQueryArgs(anchor), sleep);
68
- if (refs.length === 0) {
113
+ if (0 === refs.length) {
69
114
  return {
70
115
  step: index,
71
116
  tool: step.tool,
@@ -100,3 +145,109 @@ export async function runComponentStep(session, step, index, anchor, confirmDang
100
145
  result.error = act.error ?? 'command failed';
101
146
  return result;
102
147
  }
148
+ /**
149
+ * DEGRADED_ANCHOR_ROLE is a MARKER ("no anchor could be determined"), never a locator.
150
+ *
151
+ * Recognising it is most of the fix: a nameless-ROLE anchor used to fall through to the testid
152
+ * runner, which asked the DOM eight times for a testid literally named "unresolved", found none (it
153
+ * never exists), and reported a MISSING ELEMENT. It then ran edit-distance against the word
154
+ * "unresolved" and offered the nearest testid as a rebind target — which flow_verify printed while
155
+ * flow_heal refused it on confidence. Two tools contradicting each other over a candidate that never
156
+ * meant anything.
157
+ *
158
+ * Both recorders emit this sentinel, so the check belongs here on the replay side, where all of them
159
+ * land.
160
+ */
161
+ export function isDegradedAnchor(anchor) {
162
+ return (anchor.kind === AnchorKind.ROLE &&
163
+ anchor.role === DEGRADED_ANCHOR_ROLE &&
164
+ anchor.name === undefined);
165
+ }
166
+ /** What a step with no resolvable anchor reports, instead of a missing-element story. */
167
+ const DEGRADED_STEP_REASON = 'recorded without a resolvable anchor (no data-testid, no accessible role+name), so it can never ' +
168
+ 'resolve on replay — add a data-testid to the element and record this flow again';
169
+ export function degradedStepResult(step, index, label) {
170
+ return {
171
+ step: index,
172
+ tool: step.tool,
173
+ anchor: label,
174
+ ok: false,
175
+ drift: {
176
+ reasonKind: DriftReason.ANCHOR_DEGRADED,
177
+ reason: DEGRADED_STEP_REASON,
178
+ anchor: label,
179
+ // Deliberately null: the old path produced a nearest match to the WORD "unresolved", which is
180
+ // not a candidate for anything.
181
+ nearest: null,
182
+ },
183
+ };
184
+ }
185
+ /** QUERY args for an element anchor — null when the anchor addresses no element. */
186
+ function anchorQueryArgs(anchor) {
187
+ if (anchor.kind === AnchorKind.TESTID)
188
+ return { by: QueryBy.TESTID, value: anchor.value };
189
+ if (anchor.kind === AnchorKind.COMPONENT)
190
+ return componentQueryArgs(anchor);
191
+ if (anchor.kind === AnchorKind.ROLE && !isDegradedAnchor(anchor))
192
+ return roleQueryArgs(anchor);
193
+ return null;
194
+ }
195
+ /**
196
+ * Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
197
+ * ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
198
+ * execute identically.
199
+ *
200
+ * `replayFlow` had no sequence branch at all. It dispatches on `anchor.kind`, so a saved sequence fell
201
+ * to the testid runner and ran ONE act with `action: ''` (a saved sequence carries no top-level
202
+ * action) — sub-steps 2..n never executed. Fixing the anchor without this would have turned a visible
203
+ * drift into a silent partial replay reporting ok.
204
+ */
205
+ export async function runSequenceStep(session, step, index, subs, confirmDangerous, sleep) {
206
+ const live = [];
207
+ for (const [subIndex, sub] of subs.entries()) {
208
+ const label = `${anchorLabel(sub.anchor)} (sub-step ${String(subIndex)})`;
209
+ const queryArgs = anchorQueryArgs(sub.anchor);
210
+ if (null === queryArgs)
211
+ return degradedStepResult(step, index, label);
212
+ const { refs, hint } = await resolveQuery(session, queryArgs, sleep);
213
+ const ref = refs[0];
214
+ if (ref === undefined) {
215
+ return {
216
+ step: index,
217
+ tool: step.tool,
218
+ anchor: label,
219
+ ok: false,
220
+ drift: sub.anchor.kind === AnchorKind.TESTID
221
+ ? testidDrift(sub.anchor.value, hint)
222
+ : {
223
+ reasonKind: DriftReason.COMPONENT_NOT_FOUND,
224
+ reason: `anchor ${label} not found`,
225
+ anchor: label,
226
+ nearest: null,
227
+ },
228
+ };
229
+ }
230
+ live.push({
231
+ ref,
232
+ action: sub.action ?? '',
233
+ args: replayActionArgs(sub.args, confirmDangerous),
234
+ });
235
+ }
236
+ session.beginAction?.(ReticleTool.FLOW_REPLAY, { steps: live.length });
237
+ let act;
238
+ try {
239
+ act = await session.command(ReticleCommand.ACT_SEQUENCE, { steps: live });
240
+ }
241
+ finally {
242
+ session.finishAction?.();
243
+ }
244
+ const result = {
245
+ step: index,
246
+ tool: step.tool,
247
+ anchor: anchorLabel(step.anchor),
248
+ ok: act.ok,
249
+ };
250
+ if (!act.ok)
251
+ result.error = act.error ?? 'command failed';
252
+ return result;
253
+ }
@@ -12,7 +12,7 @@
12
12
  * tool and the `@reticlehq/test` spec runner share one implementation — no divergent oracle.
13
13
  * Pure: no IO, no clock.
14
14
  */
15
- import { AnchorKind } from '@reticlehq/core';
15
+ import { AnchorKind, PredicateKind } from '@reticlehq/core';
16
16
  /** The dynamic (LLM-output) testids whose presence is never asserted — same rule replay uses. */
17
17
  export function dynamicTestids(flow) {
18
18
  return new Set((flow.dynamic ?? [])
@@ -28,7 +28,7 @@ export function successLabel(success) {
28
28
  if (success.net !== undefined)
29
29
  return success.net.urlContains ?? success.net.method ?? 'net';
30
30
  if (success.console !== undefined) {
31
- return `console:${success.console.absent === true ? 'clean' : (success.console.level ?? 'error')}`;
31
+ return `console:${true === success.console.absent ? 'clean' : (success.console.level ?? 'error')}`;
32
32
  }
33
33
  if (success.state !== undefined)
34
34
  return `state:${success.state.path}`;
@@ -39,11 +39,11 @@ export function successToPredicate(success, dynamic) {
39
39
  const parts = [];
40
40
  if (success.signal !== undefined) {
41
41
  parts.push(success.signalData !== undefined
42
- ? { kind: 'signal', name: success.signal, dataMatches: success.signalData }
43
- : { kind: 'signal', name: success.signal });
42
+ ? { kind: PredicateKind.SIGNAL, name: success.signal, dataMatches: success.signalData }
43
+ : { kind: PredicateKind.SIGNAL, name: success.signal });
44
44
  }
45
45
  if (success.net !== undefined) {
46
- const net = { kind: 'net' };
46
+ const net = { kind: PredicateKind.NET };
47
47
  if (success.net.method !== undefined)
48
48
  net.method = success.net.method;
49
49
  if (success.net.urlContains !== undefined)
@@ -56,12 +56,14 @@ export function successToPredicate(success, dynamic) {
56
56
  // exact count (e.g. 1) is transiently satisfied the instant the FIRST matching request lands —
57
57
  // before a double-submit's duplicate arrives. Gating on `settled` forces the count to be read
58
58
  // only after the network has gone quiet, so the duplicate IS counted and the over-count fails.
59
- parts.push({ kind: 'settled' });
59
+ parts.push({ kind: PredicateKind.SETTLED });
60
60
  }
61
61
  parts.push(net);
62
62
  }
63
63
  if (success.console !== undefined) {
64
- const con = { kind: 'console' };
64
+ const con = {
65
+ kind: PredicateKind.CONSOLE,
66
+ };
65
67
  if (success.console.level !== undefined)
66
68
  con.level = success.console.level;
67
69
  if (success.console.absent !== undefined) {
@@ -70,13 +72,16 @@ export function successToPredicate(success, dynamic) {
70
72
  // error yet" at the first poll and passes BEFORE the action's error fires. Gate on `settled` so
71
73
  // the console is read only after the page quiets, by which point any error is in the buffer.
72
74
  if (success.console.absent)
73
- parts.push({ kind: 'settled' });
75
+ parts.push({ kind: PredicateKind.SETTLED });
74
76
  }
75
77
  parts.push(con);
76
78
  }
77
79
  const state = success.state;
78
80
  if (state !== undefined) {
79
- const part = { kind: 'state', path: state.path };
81
+ const part = {
82
+ kind: PredicateKind.STATE,
83
+ path: state.path,
84
+ };
80
85
  if (state.store !== undefined)
81
86
  part.store = state.store;
82
87
  if (state.equals !== undefined)
@@ -85,8 +90,8 @@ export function successToPredicate(success, dynamic) {
85
90
  // wait-for-change. A wait-until-true waiter would pass the instant the path already equals the
86
91
  // value — which, for "an unrelated path stayed put", is true BEFORE a side-effect leak fires. Gate
87
92
  // on `settled` so the read happens after the page quiets, by which point the leak has landed.
88
- if (state.hold === true)
89
- parts.push({ kind: 'settled' });
93
+ if (true === state.hold)
94
+ parts.push({ kind: PredicateKind.SETTLED });
90
95
  parts.push(part);
91
96
  }
92
97
  const element = success.element;
@@ -102,15 +107,15 @@ export function successToPredicate(success, dynamic) {
102
107
  if (element.name !== undefined)
103
108
  query['name'] = element.name;
104
109
  if (Object.keys(query).length > 0)
105
- parts.push({ kind: 'element', query });
110
+ parts.push({ kind: PredicateKind.ELEMENT, query });
106
111
  }
107
112
  }
108
113
  const [first] = parts;
109
- if (parts.length === 0)
114
+ if (0 === parts.length)
110
115
  return undefined;
111
- if (parts.length === 1 && first !== undefined)
116
+ if (1 === parts.length && first !== undefined)
112
117
  return first;
113
- return { kind: 'allOf', predicates: parts };
118
+ return { kind: PredicateKind.ALL_OF, predicates: parts };
114
119
  }
115
120
  /**
116
121
  * Assert a flow's success end-condition after replay. Delegates to the injected waitForSignal (the
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ import { emptyFlowRefusal } from './empty-flow.js';
3
+ import { aliasParam } from '../tools/alias-args.js';
2
4
  import { FlowErrorCode, RecordedSaveError, ReplayStatus, } from '@reticlehq/core';
3
5
  import { FlakeStore } from './flake-store.js';
4
6
  import { ReticleTool } from '../tools/tool-names.js';
@@ -25,7 +27,7 @@ export { replayNamedFlow } from './flow-replay-run.js';
25
27
  async function syncSavedFlowToCloud(deps, flow, projectId) {
26
28
  // Per-project cloud: sync a saved flow only when cloud is attached AND flow sync is enabled.
27
29
  const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
28
- if (cloud.config === null || !cloud.policy.flows)
30
+ if (null === cloud.config || !cloud.policy.flows)
29
31
  return; // not attached / flows disabled → local only
30
32
  const result = await syncFlowToCloud(flow, cloud.config, projectId, (url, init) => fetch(url, init));
31
33
  if (result.outcome !== SyncOutcome.SYNCED) {
@@ -43,7 +45,7 @@ async function syncSavedFlowToCloud(deps, flow, projectId) {
43
45
  export function leasableAppUrl(deps, sessionId) {
44
46
  try {
45
47
  const url = deps.sessions.resolve(sessionId).url;
46
- if (typeof url !== 'string' || url.length === 0)
48
+ if (typeof url !== 'string' || 0 === url.length)
47
49
  return undefined;
48
50
  return new URL(url).origin;
49
51
  }
@@ -70,6 +72,10 @@ export const FLOW_TOOLS = [
70
72
  name: ReticleTool.FLOW_SAVE,
71
73
  description: 'Persist the last/active recording (by name) as a git-checked, anchor-resolved flow at .reticle/flows/<name>.json. Each step is bound to a SEMANTIC anchor (testid/role/signal), never a volatile ref; steps without a resolvable testid are kept with degraded:true (a "add a data-testid here" marker) rather than dropped. Returns { name, stepCount, degraded, empty, assertions } — `assertions.grade` is asserted | presence-only | assertion-free: a flow that only acts (or only checks element presence) will pass even if the feature breaks, so when grade is not "asserted" follow assertions.warning and add a consequence assertion via reticle_annotate (assert-signal / assert-net / success-state).',
72
74
  inputSchema: {
75
+ flow: z
76
+ .string()
77
+ .optional()
78
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
73
79
  flowName: z
74
80
  .string()
75
81
  .describe('Name for the flow file (saved to .reticle/flows/<flowName>.json). Use again in reticle_flow{action:"load"} / reticle_flow_replay.'),
@@ -103,7 +109,7 @@ export const FLOW_TOOLS = [
103
109
  code: z.string().optional(),
104
110
  },
105
111
  handler: (deps, args) => {
106
- const name = asString(args['flowName']) ?? '';
112
+ const name = asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '';
107
113
  const program = deps.recordings.getCompiled(name);
108
114
  if (program === undefined) {
109
115
  return Promise.resolve({
@@ -121,6 +127,11 @@ export const FLOW_TOOLS = [
121
127
  ...(intent !== undefined ? { intent } : {}),
122
128
  };
123
129
  const projectId = sessionProjectId(deps, asString(args['sessionId']));
130
+ // A zero-step recording is not a flow. Saving it wrote a file the suite reports "unverifiable"
131
+ // forever while telling the agent it had saved a regression test — see empty-flow.
132
+ const emptyRefusal = emptyFlowRefusal(program.steps.length, name);
133
+ if (emptyRefusal !== undefined)
134
+ return Promise.resolve(emptyRefusal);
124
135
  return deps.flows.save(program, annotations, projectId).then(async (res) => {
125
136
  if (!res.ok)
126
137
  return { error: flowErrorMessage(res.code), code: res.code };
@@ -170,6 +181,10 @@ export const FLOW_TOOLS = [
170
181
  name: ReticleTool.FLOW_LOAD,
171
182
  description: 'Read + validate a saved flow by flowName from .reticle/flows/<flowName>.json. Returns the FlowFile (version, flowName, createdAt, anchored steps) or a structured { error, code }.',
172
183
  inputSchema: {
184
+ flow: z
185
+ .string()
186
+ .optional()
187
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
173
188
  flowName: z
174
189
  .string()
175
190
  .describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
@@ -179,13 +194,19 @@ export const FLOW_TOOLS = [
179
194
  .describe('Active session ID — resolves the flow within that app’s scope.'),
180
195
  },
181
196
  outputSchema: {
197
+ flow: z
198
+ .string()
199
+ .optional()
200
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
182
201
  flowName: z.string(),
183
202
  steps: z.array(z.unknown()),
184
203
  createdAt: z.number().optional(),
185
204
  },
186
205
  handler: (deps, args) => {
187
206
  const projectId = sessionProjectId(deps, asString(args['sessionId']));
188
- return deps.flows.load(asString(args['flowName']) ?? '', projectId).then((res) => {
207
+ return deps.flows
208
+ .load(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
209
+ .then((res) => {
189
210
  if (!res.ok)
190
211
  return { error: flowErrorMessage(res.code), code: res.code };
191
212
  const { name, ...rest } = res.value;
@@ -199,6 +220,10 @@ export const FLOW_TOOLS = [
199
220
  'to the connected app. Returns { deleted: true } or a structured { error, code } (code not_found ' +
200
221
  'when no such flow — deleting an absent flow is an error, not a silent no-op).',
201
222
  inputSchema: {
223
+ flow: z
224
+ .string()
225
+ .optional()
226
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
202
227
  flowName: z
203
228
  .string()
204
229
  .describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
@@ -214,7 +239,9 @@ export const FLOW_TOOLS = [
214
239
  },
215
240
  handler: (deps, args) => {
216
241
  const projectId = sessionProjectId(deps, asString(args['sessionId']));
217
- return deps.flows.remove(asString(args['flowName']) ?? '', projectId).then((res) => {
242
+ return deps.flows
243
+ .remove(asString(aliasParam(args, 'flowName', ['flow'])['flowName']) ?? '', projectId)
244
+ .then((res) => {
218
245
  if (!res.ok)
219
246
  return { error: flowErrorMessage(res.code), code: res.code };
220
247
  return { deleted: true };
@@ -230,6 +257,10 @@ export const FLOW_TOOLS = [
230
257
  'Returns { name, status: ok|drift|error, steps:[...] }; missing/malformed files and action ' +
231
258
  'failures are status:error with a structured code (distinct from contract-changed drift).',
232
259
  inputSchema: {
260
+ flow: z
261
+ .string()
262
+ .optional()
263
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
233
264
  flowName: z
234
265
  .string()
235
266
  .describe('Flow file name (without .json extension) from reticle_flow{action:"list"}.'),
@@ -311,7 +342,7 @@ export const FLOW_TOOLS = [
311
342
  // "Replay all" means all of THIS app's flows (+ legacy), not every project's on a shared daemon.
312
343
  const projectId = sessionProjectId(deps, sessionId);
313
344
  const requested = Array.isArray(args['names'])
314
- ? args['names'].filter((n) => typeof n === 'string')
345
+ ? args['names'].filter((n) => 'string' === typeof n)
315
346
  : await deps.flows.list(projectId);
316
347
  // verify:server — hand the whole suite to the hosted runner; it records the verification itself.
317
348
  const cloud = await resolveProjectCloud(deps.fs, deps.reticleRoot, homedir(), process.env);
@@ -345,12 +376,18 @@ export const FLOW_TOOLS = [
345
376
  await lease.release().catch(() => undefined);
346
377
  }
347
378
  });
348
- return buildSuiteVerdict(outcomes.map((o, i) => ({
349
- replay: o.ok && o.value !== undefined
350
- ? o.value
351
- : leaseFailureReplay(requested[i] ?? '', o.error),
352
- })));
379
+ const parallelRuns = await Promise.all(outcomes.map(async (o, i) => {
380
+ const name = requested[i] ?? '';
381
+ const replay = o.ok && o.value !== undefined ? o.value : leaseFailureReplay(name, o.error);
382
+ const loaded = await deps.flows.load(name, projectId).catch(() => null);
383
+ const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
384
+ return flow === undefined ? { replay } : { replay, flow };
385
+ }));
386
+ return buildSuiteVerdict(parallelRuns);
353
387
  }
388
+ // The flow FILE travels with each replay so the verdict can tell a green that verified
389
+ // something from a green that could never have gone red. Without it the suite reported "all 1
390
+ // flow pass" for a flow with no steps at all.
354
391
  const runs = [];
355
392
  const timed = [];
356
393
  // The flake ledger the CLI gate already keeps — see FlakeStore. It was only ever written by
@@ -362,7 +399,9 @@ export const FLOW_TOOLS = [
362
399
  for (const flowName of requested) {
363
400
  const start = deps.now();
364
401
  const replay = await replayNamedFlow(deps, { flowName, sessionId });
365
- runs.push({ replay });
402
+ const loaded = await deps.flows.load(flowName, projectId).catch(() => null);
403
+ const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
404
+ runs.push(flow === undefined ? { replay } : { replay, flow });
366
405
  timed.push({ replay, durationMs: deps.now() - start });
367
406
  }
368
407
  // Accrue this run's outcomes, then report what the ledger knows. Best-effort on both halves: a
@@ -389,6 +428,10 @@ export const FLOW_TOOLS = [
389
428
  'recorded name. Returns { name, stepCount, degraded, empty } or { error, code } (code ' +
390
429
  'flow_no_recorded when no recording is present).',
391
430
  inputSchema: {
431
+ flow: z
432
+ .string()
433
+ .optional()
434
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
392
435
  flowName: z
393
436
  .string()
394
437
  .optional()
@@ -419,10 +462,13 @@ export const FLOW_TOOLS = [
419
462
  code: RecordedSaveError.NO_RECORDED_FLOW,
420
463
  };
421
464
  }
422
- const override = asString(args['flowName']);
465
+ const override = asString(aliasParam(args, 'flowName', ['flow'])['flowName']);
423
466
  const flow = override !== undefined ? { ...recorded.flow, name: override } : recorded.flow;
424
467
  // The store stamps the project into the file AND routes it to the per-project subdir (a shared
425
468
  // daemon serves many apps), so location and content agree from one source of truth.
469
+ const emptyRecorded = emptyFlowRefusal(flow.steps.length, flow.name);
470
+ if (emptyRecorded !== undefined)
471
+ return emptyRecorded;
426
472
  const res = await deps.flows.saveFlow(flow, session.projectId);
427
473
  if (!res.ok)
428
474
  return { error: flowErrorMessage(res.code), code: res.code };
@@ -446,6 +492,10 @@ export const FLOW_TOOLS = [
446
492
  'untouched). Returns { name, status: healed|drift|unhealable|consequence_broken|' +
447
493
  'nothing_to_heal|error, applied, proposals[], changed[], message }.',
448
494
  inputSchema: {
495
+ flow: z
496
+ .string()
497
+ .optional()
498
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
449
499
  flowName: z.string().describe('Flow file name to heal (from reticle_flow{action:"list"}).'),
450
500
  apply: z.boolean().optional(),
451
501
  confirmDangerous: z
@@ -458,6 +508,10 @@ export const FLOW_TOOLS = [
458
508
  .describe('Active session ID from reticle_sessions. Omit when only one browser session is open.'),
459
509
  },
460
510
  outputSchema: {
511
+ flow: z
512
+ .string()
513
+ .optional()
514
+ .describe('Alias for `flowName` (the name reticle_annotate uses).'),
461
515
  flowName: z.string(),
462
516
  status: z.string(),
463
517
  applied: z.boolean(),
@@ -28,10 +28,10 @@ function sourceArg(src) {
28
28
  const source = asRecord(src['source']);
29
29
  const file = source['file'];
30
30
  const line = source['line'];
31
- if (typeof file !== 'string' || file.length === 0 || typeof line !== 'number')
31
+ if (typeof file !== 'string' || 0 === file.length || typeof line !== 'number')
32
32
  return undefined;
33
33
  const out = { file, line };
34
- if (typeof source['column'] === 'number')
34
+ if ('number' === typeof source['column'])
35
35
  out.column = source['column'];
36
36
  return out;
37
37
  }
@@ -109,8 +109,12 @@ export function recordedStepToFlowStep(step) {
109
109
  if (step.tool === ReticleTool.ACT_SEQUENCE) {
110
110
  const rawSubs = Array.isArray(step.args['steps']) ? step.args['steps'] : [];
111
111
  const subs = rawSubs.map(subStepToFlowStep);
112
- const degraded = subs.some((s) => s.degraded === true);
113
- const anchor = subs[0]?.anchor ?? degradedAnchor();
112
+ const degraded = subs.some((s) => true === s.degraded);
113
+ // The first sub-step that HAS an anchor, not blindly the first. Taking subs[0] handed the whole
114
+ // sequence the degraded sentinel whenever sub-step 0 lacked a testid — even with every later
115
+ // sub-step perfectly anchored — and replay then queried the DOM for a testid literally named
116
+ // "unresolved", so the step drifted on all 5 apps, every replay.
117
+ const anchor = subs.find((s) => s.degraded !== true)?.anchor ?? subs[0]?.anchor ?? degradedAnchor();
114
118
  const out = { tool: ReticleTool.ACT_SEQUENCE, anchor, steps: subs };
115
119
  if (degraded)
116
120
  out.degraded = true;
@@ -185,14 +189,14 @@ export class FlowStore {
185
189
  const flow = withAnnotations(base, annotations);
186
190
  await this.#fs.mkdir(flowDir(this.#root, pid));
187
191
  await this.#fs.writeFile(flowPath(this.#root, program.name, pid), this.#serialize(flow));
188
- const degraded = flow.steps.filter((s) => s.degraded === true).length;
192
+ const degraded = flow.steps.filter((s) => true === s.degraded).length;
189
193
  return {
190
194
  ok: true,
191
195
  value: {
192
196
  name: program.name,
193
197
  stepCount: flow.steps.length,
194
198
  degraded,
195
- empty: flow.steps.length === 0,
199
+ empty: 0 === flow.steps.length,
196
200
  },
197
201
  };
198
202
  }
@@ -214,14 +218,14 @@ export class FlowStore {
214
218
  const valid = parsed.data;
215
219
  await this.#fs.mkdir(flowDir(this.#root, pid));
216
220
  await this.#fs.writeFile(flowPath(this.#root, asFlowName(valid.name), pid), this.#serialize(valid));
217
- const degraded = valid.steps.filter((s) => s.degraded === true).length;
221
+ const degraded = valid.steps.filter((s) => true === s.degraded).length;
218
222
  return {
219
223
  ok: true,
220
224
  value: {
221
225
  name: valid.name,
222
226
  stepCount: valid.steps.length,
223
227
  degraded,
224
- empty: valid.steps.length === 0,
228
+ empty: 0 === valid.steps.length,
225
229
  },
226
230
  };
227
231
  }
@@ -247,7 +251,7 @@ export class FlowStore {
247
251
  // Write back to the SAME file load resolved (nested if it lives there, else legacy flat), so a
248
252
  // heal never forks a second copy and byte-stability holds regardless of where the flow lives.
249
253
  const path = await this.#resolveReadPath(name, pid);
250
- if (path === null)
254
+ if (null === path)
251
255
  return { ok: false, code: FlowErrorCode.NOT_FOUND };
252
256
  const { flow: next, applied } = applyHealChanges(flow, changes);
253
257
  await this.#fs.writeFile(path, this.#serialize(next));
@@ -343,7 +347,7 @@ export class FlowStore {
343
347
  if (!isValidFlowName(name))
344
348
  return { ok: false, code: FlowErrorCode.INVALID_NAME };
345
349
  const path = await this.#resolveReadPath(name, safeProjectId(projectId));
346
- if (path === null)
350
+ if (null === path)
347
351
  return { ok: false, code: FlowErrorCode.NOT_FOUND };
348
352
  let text;
349
353
  try {
@@ -377,7 +381,7 @@ export class FlowStore {
377
381
  if (!isValidFlowName(name))
378
382
  return { ok: false, code: FlowErrorCode.INVALID_NAME };
379
383
  const path = await this.#resolveReadPath(name, safeProjectId(projectId));
380
- if (path === null)
384
+ if (null === path)
381
385
  return { ok: false, code: FlowErrorCode.NOT_FOUND };
382
386
  await this.#fs.rm(path);
383
387
  return { ok: true, value: undefined };
@@ -6,12 +6,12 @@
6
6
  * inputs (git diff → affected → run artifacts + flake ledger) and maps `pass` to the exit code.
7
7
  */
8
8
  /** A flow whose assertions were WEAKENED since its last passing run. */
9
- export interface DowngradedFlow {
9
+ interface DowngradedFlow {
10
10
  flow: string;
11
11
  /** Step indices whose mustHold dropped from a consequence to presence-only. */
12
12
  steps: number[];
13
13
  }
14
- export interface GateInput {
14
+ interface GateInput {
15
15
  /** Flows that must be covered (from the affected index over the changed files). */
16
16
  affected: readonly string[];
17
17
  /** Flows that have a passing verification artifact. */
@@ -23,7 +23,7 @@ export interface GateInput {
23
23
  /** Flows that covered a changed file but no longer exist — coverage deleted rather than satisfied. */
24
24
  deleted?: readonly string[];
25
25
  }
26
- export interface GateResult {
26
+ interface GateResult {
27
27
  /** True when every affected, non-flaky flow has a passing artifact AND nothing was weakened/deleted. */
28
28
  pass: boolean;
29
29
  /** Affected flows with no passing artifact and not flaky — these block the gate. */
@@ -36,3 +36,4 @@ export interface GateResult {
36
36
  deleted: string[];
37
37
  }
38
38
  export declare function gateDecision(input: GateInput): GateResult;
39
+ export {};
@@ -24,7 +24,7 @@ export function gateDecision(input) {
24
24
  const downgraded = [...(input.downgraded ?? [])];
25
25
  const deleted = [...(input.deleted ?? [])];
26
26
  return {
27
- pass: uncovered.length === 0 && downgraded.length === 0 && deleted.length === 0,
27
+ pass: 0 === uncovered.length && 0 === downgraded.length && 0 === deleted.length,
28
28
  uncovered,
29
29
  quarantined,
30
30
  downgraded,
@@ -7,12 +7,4 @@
7
7
  */
8
8
  import { type FlowHealResult } from '@reticlehq/core';
9
9
  import type { ToolDeps } from '../tools/tools.js';
10
- export declare const HEAL_MESSAGES: {
11
- readonly NOTHING: "nothing to heal — every anchor resolved on replay";
12
- readonly HEALED: "rewrote drifted testid anchors to their nearest surviving match and re-verified the flow's success consequence still fires";
13
- readonly DRIFT_DRY: "confident rebind(s) proposed — re-run with apply:true to write them to disk";
14
- readonly UNHEALABLE: "drift found, but no nearest match cleared the confidence floor (HEAL_CONFIDENCE_MIN=0.5); file left untouched — add a data-testid or fix the flow by hand";
15
- readonly HEALED_UNVERIFIED: "rewrote drifted testid anchors — but this flow declares no success consequence, so the rebind resolves a locator without proving the intent still holds. Add a success-state assertion (reticle_annotate) so future heals can be verified.";
16
- readonly CONSEQUENCE_BROKEN: "rebind resolves the drifted locator to a surviving element, but the healed flow no longer satisfies its success consequence — refusing to write (a heal that loses the intent would ship a green-but-dead test). Fix by hand and verify";
17
- };
18
10
  export declare function healFlow(deps: ToolDeps, args: Record<string, unknown>): Promise<FlowHealResult>;