@reticlehq/server 3.9.0 → 3.10.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 (350) hide show
  1. package/SKILL.md +13 -12
  2. package/dist/command/cli/adhoc-suite.js +2 -0
  3. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  4. package/dist/command/cli/adhoc-verdict.js +50 -187
  5. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  6. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  7. package/dist/command/cli/cli-doctor.js +1 -1
  8. package/dist/command/cli/cli-flow-commands.js +2 -9
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  11. package/dist/command/cli/cli-parse-compare.js +75 -0
  12. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  13. package/dist/command/cli/cli-parse-grade.js +75 -0
  14. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  15. package/dist/command/cli/cli-parse-grammar.js +18 -20
  16. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  17. package/dist/command/cli/cli-parse-verify.js +63 -14
  18. package/dist/command/cli/cli-parse.d.ts +17 -15
  19. package/dist/command/cli/cli-parse.js +44 -27
  20. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  21. package/dist/command/cli/cli-update-commands.js +29 -20
  22. package/dist/command/cli/cli-usage.js +58 -29
  23. package/dist/command/cli/cli-verify.d.ts +11 -2
  24. package/dist/command/cli/cli-verify.js +27 -8
  25. package/dist/command/cli/cloud-cli.js +2 -119
  26. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  27. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  28. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  29. package/dist/command/cli/doctor-repairs.js +1 -1
  30. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  31. package/dist/command/cli/ensure-daemon.js +57 -0
  32. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  33. package/dist/command/cli/finished-run/grade-command.js +180 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  35. package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
  36. package/dist/command/cli/ports/port-holder.d.ts +9 -0
  37. package/dist/command/cli/ports/port-holder.js +27 -3
  38. package/dist/command/cli/status/status-lines.d.ts +1 -0
  39. package/dist/command/cli/status/status-lines.js +22 -0
  40. package/dist/command/cli/status-fields.d.ts +5 -1
  41. package/dist/command/cli/status-fields.js +7 -3
  42. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  43. package/dist/command/cli/two-builds/compare-command.js +314 -0
  44. package/dist/command/cli/verify-against.d.ts +61 -0
  45. package/dist/command/cli/verify-against.js +187 -0
  46. package/dist/command/cli/verify-no-session.d.ts +2 -0
  47. package/dist/command/cli/verify-no-session.js +13 -0
  48. package/dist/command/cli/verify-results.d.ts +35 -2
  49. package/dist/command/cli/verify-results.js +76 -10
  50. package/dist/command/cli.js +46 -41
  51. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  52. package/dist/command/daemon/daemon-resolve.js +27 -1
  53. package/dist/command/daemon/daemon.d.ts +12 -1
  54. package/dist/command/daemon/daemon.js +35 -4
  55. package/dist/command/daemon/lifetime/death-cause.js +21 -2
  56. package/dist/command/daemon/lifetime/heartbeat.js +7 -2
  57. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  58. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  59. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  60. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  61. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  62. package/dist/command/setup/bringup/static-page-server.js +43 -9
  63. package/dist/command/setup/init/init-runtime.js +6 -0
  64. package/dist/command/setup/node-effects.d.ts +3 -2
  65. package/dist/command/setup/node-effects.js +9 -3
  66. package/dist/command/setup/remaining-steps.d.ts +8 -0
  67. package/dist/command/setup/remaining-steps.js +10 -0
  68. package/dist/command/setup/run-setup.d.ts +2 -0
  69. package/dist/command/setup/run-setup.js +9 -1
  70. package/dist/command/setup/setup-command.js +6 -3
  71. package/dist/command/update/updater.js +6 -0
  72. package/dist/command/version/version-skew.js +1 -1
  73. package/dist/features/compare/action-plan.d.ts +260 -0
  74. package/dist/features/compare/action-plan.js +543 -0
  75. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  76. package/dist/features/compare/behaviour-compare.js +85 -0
  77. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  78. package/dist/features/compare/behaviour-diff.js +202 -0
  79. package/dist/features/compare/behaviour-report.d.ts +40 -0
  80. package/dist/features/compare/behaviour-report.js +128 -0
  81. package/dist/features/compare/build-readings.d.ts +106 -0
  82. package/dist/features/compare/build-readings.js +250 -0
  83. package/dist/features/compare/compare-diff.d.ts +62 -0
  84. package/dist/features/compare/compare-diff.js +177 -0
  85. package/dist/features/compare/compare-report-html.d.ts +6 -0
  86. package/dist/features/compare/compare-report-html.js +102 -0
  87. package/dist/features/compare/compare-report.d.ts +15 -0
  88. package/dist/features/compare/compare-report.js +73 -0
  89. package/dist/features/compare/compare-run.d.ts +60 -0
  90. package/dist/features/compare/compare-run.js +123 -0
  91. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  92. package/dist/features/compare/outside/effect-lines.js +103 -0
  93. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  94. package/dist/features/compare/outside/state-sources.js +164 -0
  95. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  96. package/dist/features/compare/outside/value-dictionary.js +227 -0
  97. package/dist/features/compare/visible-content.d.ts +20 -0
  98. package/dist/features/compare/visible-content.js +125 -0
  99. package/dist/features/crawl/crawl-empty.js +2 -1
  100. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  101. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  102. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  103. package/dist/features/crawl/crawl-tools.js +28 -12
  104. package/dist/features/crawl/crawl.d.ts +8 -9
  105. package/dist/features/crawl/crawl.js +61 -65
  106. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  107. package/dist/features/exhaust/app-coverage.js +3 -1
  108. package/dist/features/exhaust/code-coverage.js +8 -4
  109. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  110. package/dist/features/exhaust/edge-cases.js +310 -0
  111. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  112. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  113. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  114. package/dist/features/exhaust/edges/edge-run.js +169 -0
  115. package/dist/features/exhaust/explorer.d.ts +87 -14
  116. package/dist/features/exhaust/explorer.js +159 -25
  117. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  118. package/dist/features/exhaust/failure-branches.js +29 -11
  119. package/dist/features/exhaust/invariants.d.ts +112 -0
  120. package/dist/features/exhaust/invariants.js +188 -0
  121. package/dist/features/exhaust/ledger.d.ts +68 -0
  122. package/dist/features/exhaust/ledger.js +121 -13
  123. package/dist/features/exhaust/session-fold.d.ts +10 -0
  124. package/dist/features/exhaust/session-fold.js +46 -3
  125. package/dist/features/exhaust/session-port.d.ts +3 -1
  126. package/dist/features/exhaust/session-port.js +97 -28
  127. package/dist/features/grade/grade-answer.d.ts +99 -0
  128. package/dist/features/grade/grade-answer.js +112 -0
  129. package/dist/features/grade/grade-noise.d.ts +27 -0
  130. package/dist/features/grade/grade-noise.js +55 -0
  131. package/dist/features/grade/grade-renumber.d.ts +12 -0
  132. package/dist/features/grade/grade-renumber.js +86 -0
  133. package/dist/features/grade/grade-state.d.ts +50 -0
  134. package/dist/features/grade/grade-state.js +93 -0
  135. package/dist/features/grade/grade-suggest.d.ts +33 -0
  136. package/dist/features/grade/grade-suggest.js +98 -0
  137. package/dist/features/visual/visual-tools.js +66 -19
  138. package/dist/index.js +7 -4
  139. package/dist/judgement/capsule/causal-summary.js +9 -2
  140. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  141. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  142. package/dist/judgement/runs/reticle-runner.js +16 -2
  143. package/dist/judgement/runs/runner-port.d.ts +2 -0
  144. package/dist/judgement/runs/runner-port.js +23 -5
  145. package/dist/language/flows/affected-tools.d.ts +1 -1
  146. package/dist/language/flows/affected-tools.js +49 -30
  147. package/dist/language/flows/change/git-changed.d.ts +2 -1
  148. package/dist/language/flows/change/git-changed.js +19 -2
  149. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  150. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  151. package/dist/language/flows/flow-anchor.d.ts +60 -2
  152. package/dist/language/flows/flow-anchor.js +125 -20
  153. package/dist/language/flows/flow-classify.d.ts +20 -0
  154. package/dist/language/flows/flow-classify.js +90 -12
  155. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  156. package/dist/language/flows/flow-cross-step.js +25 -1
  157. package/dist/language/flows/flow-journey.d.ts +38 -4
  158. package/dist/language/flows/flow-journey.js +138 -8
  159. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  160. package/dist/language/flows/flow-replay-run.js +76 -14
  161. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  162. package/dist/language/flows/flow-replay.d.ts +19 -1
  163. package/dist/language/flows/flow-replay.js +169 -52
  164. package/dist/language/flows/flow-replayable.js +7 -1
  165. package/dist/language/flows/flow-start-path.d.ts +34 -5
  166. package/dist/language/flows/flow-start-path.js +202 -15
  167. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  168. package/dist/language/flows/flow-step-runners.js +184 -109
  169. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  170. package/dist/language/flows/flow-store-for-session.js +44 -0
  171. package/dist/language/flows/flow-tools.js +46 -33
  172. package/dist/language/flows/flows.js +176 -76
  173. package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
  174. package/dist/language/flows/recording/tape/recordings.js +71 -5
  175. package/dist/language/flows/replay.d.ts +19 -5
  176. package/dist/language/flows/replay.js +99 -7
  177. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  178. package/dist/language/flows/steps/page-steps.js +198 -0
  179. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  180. package/dist/language/flows/suite/sequential-run.js +96 -0
  181. package/dist/language/flows/verify-change-tools.js +26 -15
  182. package/dist/memory/cloud/background-sync.d.ts +13 -0
  183. package/dist/memory/cloud/background-sync.js +24 -3
  184. package/dist/memory/cloud/sync-daemon.js +26 -27
  185. package/dist/memory/journal/attach-journal.js +2 -1
  186. package/dist/memory/journal/drive-flow.d.ts +7 -0
  187. package/dist/memory/journal/drive-flow.js +44 -13
  188. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  189. package/dist/memory/journal/session-journal.d.ts +7 -0
  190. package/dist/memory/journal/session-journal.js +58 -10
  191. package/dist/memory/project/root-claims.d.ts +9 -0
  192. package/dist/memory/project/root-claims.js +9 -4
  193. package/dist/portal/input/download-capture.d.ts +7 -0
  194. package/dist/portal/input/download-capture.js +92 -0
  195. package/dist/portal/input/network-detail.d.ts +6 -0
  196. package/dist/portal/input/network-detail.js +12 -1
  197. package/dist/portal/input/network-mock-tools.js +49 -13
  198. package/dist/portal/input/network-mock.d.ts +8 -0
  199. package/dist/portal/input/network-mock.js +28 -9
  200. package/dist/portal/input/page-capture.d.ts +24 -0
  201. package/dist/portal/input/page-capture.js +33 -0
  202. package/dist/portal/input/real-input.d.ts +22 -20
  203. package/dist/portal/input/real-input.js +41 -31
  204. package/dist/portal/input/viewport-tools.d.ts +8 -0
  205. package/dist/portal/input/viewport-tools.js +75 -18
  206. package/dist/portal/pool/browser-pool.d.ts +11 -29
  207. package/dist/portal/pool/browser-pool.js +35 -17
  208. package/dist/portal/pool/context-permissions.d.ts +17 -3
  209. package/dist/portal/pool/context-permissions.js +36 -5
  210. package/dist/portal/pool/determinism.d.ts +52 -0
  211. package/dist/portal/pool/determinism.js +259 -0
  212. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  213. package/dist/portal/pool/lease-emulation.js +18 -0
  214. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  215. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  216. package/dist/portal/pool/lease-page-error.d.ts +44 -17
  217. package/dist/portal/pool/lease-page-error.js +147 -5
  218. package/dist/portal/pool/page-error-journal.d.ts +7 -1
  219. package/dist/portal/pool/page-error-journal.js +27 -0
  220. package/dist/portal/pool/playwright-launcher.js +51 -8
  221. package/dist/portal/pool/pool-contract.d.ts +27 -1
  222. package/dist/portal/pool/pool-lease.d.ts +36 -1
  223. package/dist/portal/pool/zero-install.d.ts +8 -0
  224. package/dist/portal/pool/zero-install.js +23 -1
  225. package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
  226. package/dist/portal/session/dev-server/dev-command.js +61 -15
  227. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  228. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  229. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  230. package/dist/portal/session/dev-server/project-ports.js +62 -0
  231. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  232. package/dist/portal/session/facts/departed-network.js +142 -0
  233. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  234. package/dist/portal/session/no-session-diagnosis.js +67 -25
  235. package/dist/portal/session/no-session-next-action.d.ts +2 -0
  236. package/dist/portal/session/no-session-next-action.js +12 -3
  237. package/dist/portal/session/no-session-watch.d.ts +6 -0
  238. package/dist/portal/session/no-session-watch.js +38 -3
  239. package/dist/portal/session/serving-directory.d.ts +10 -0
  240. package/dist/portal/session/serving-directory.js +39 -8
  241. package/dist/portal/session/session-manager.d.ts +10 -0
  242. package/dist/portal/session/session-manager.js +174 -63
  243. package/dist/start-wiring.d.ts +2 -1
  244. package/dist/start-wiring.js +2 -1
  245. package/dist/surface/mcp/mcp.js +3 -2
  246. package/dist/surface/mcp/shared-params.d.ts +2 -0
  247. package/dist/surface/mcp/shared-params.js +31 -0
  248. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  249. package/dist/surface/tools/act/act-observation.js +118 -12
  250. package/dist/surface/tools/act/act-preflight.js +1 -1
  251. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  252. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  253. package/dist/surface/tools/act/after-match.d.ts +4 -1
  254. package/dist/surface/tools/act/after-match.js +8 -1
  255. package/dist/surface/tools/act/already-true.js +1 -1
  256. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  257. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  258. package/dist/surface/tools/act/resolve-target.d.ts +15 -1
  259. package/dist/surface/tools/act/resolve-target.js +24 -1
  260. package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
  261. package/dist/surface/tools/act/sequence-grade.js +37 -26
  262. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  263. package/dist/surface/tools/act-command.d.ts +23 -0
  264. package/dist/surface/tools/act-command.js +50 -0
  265. package/dist/surface/tools/act-sequence-tool.js +78 -50
  266. package/dist/surface/tools/act-tools.d.ts +1 -13
  267. package/dist/surface/tools/act-tools.js +74 -86
  268. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  269. package/dist/surface/tools/agent/build-staleness.js +1 -1
  270. package/dist/surface/tools/assert/assert-verdict.js +16 -37
  271. package/dist/surface/tools/browser-tools.js +12 -6
  272. package/dist/surface/tools/compare-tools.d.ts +11 -0
  273. package/dist/surface/tools/compare-tools.js +94 -0
  274. package/dist/surface/tools/coverage-tools.js +1 -1
  275. package/dist/surface/tools/dynamic-tools.js +9 -2
  276. package/dist/surface/tools/error-recovery.d.ts +12 -1
  277. package/dist/surface/tools/error-recovery.js +91 -21
  278. package/dist/surface/tools/explore-tools.js +1 -1
  279. package/dist/surface/tools/invoke-tool.js +3 -2
  280. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  281. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  282. package/dist/surface/tools/lease-hint.d.ts +35 -1
  283. package/dist/surface/tools/lease-hint.js +42 -0
  284. package/dist/surface/tools/lease-readiness.d.ts +26 -0
  285. package/dist/surface/tools/lease-readiness.js +79 -0
  286. package/dist/surface/tools/lease-seed.d.ts +14 -1
  287. package/dist/surface/tools/lease-seed.js +35 -3
  288. package/dist/surface/tools/lease-tools.d.ts +3 -41
  289. package/dist/surface/tools/lease-tools.js +62 -110
  290. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  291. package/dist/surface/tools/lease-url-identity.js +97 -0
  292. package/dist/surface/tools/merge-tools.js +99 -2
  293. package/dist/surface/tools/merged-name-redirect.js +5 -1
  294. package/dist/surface/tools/observe-tools.js +15 -9
  295. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  296. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  297. package/dist/surface/tools/read-tools.js +33 -30
  298. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  299. package/dist/surface/tools/real-input-attempt.js +143 -28
  300. package/dist/surface/tools/session-ports.d.ts +14 -0
  301. package/dist/surface/tools/session-ports.js +53 -0
  302. package/dist/surface/tools/tool-kit.d.ts +6 -0
  303. package/dist/surface/tools/tool-kit.js +2 -3
  304. package/dist/surface/tools/tool-surface.js +0 -1
  305. package/dist/surface/tools/tools.js +28 -5
  306. package/dist/surface/tools/type-spelling.d.ts +13 -0
  307. package/dist/surface/tools/type-spelling.js +36 -0
  308. package/dist/telemetry/agent-client.d.ts +6 -0
  309. package/dist/telemetry/agent-client.js +10 -0
  310. package/dist/telemetry/argument-shape.js +1 -0
  311. package/dist/telemetry/tool-refused.d.ts +6 -3
  312. package/dist/telemetry/tool-refused.js +8 -3
  313. package/docs/actions.mdx +3 -3
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/capabilities.mdx +61 -1
  316. package/docs/cli/affected.mdx +9 -7
  317. package/docs/cli/cloud.mdx +3 -3
  318. package/docs/cli/compare.mdx +94 -0
  319. package/docs/cli/doctor.mdx +1 -1
  320. package/docs/cli/grade.mdx +97 -0
  321. package/docs/cli/license.mdx +9 -7
  322. package/docs/cli/restart.mdx +7 -5
  323. package/docs/cli/rollback.mdx +5 -5
  324. package/docs/cli/stop.mdx +3 -3
  325. package/docs/cli/update.mdx +2 -2
  326. package/docs/cli/verify.mdx +23 -1
  327. package/docs/cli.mdx +33 -23
  328. package/docs/deploy-checks.md +1 -1
  329. package/docs/desktop.mdx +1 -1
  330. package/docs/enterprise.md +2 -2
  331. package/docs/environment.mdx +1 -1
  332. package/docs/flows.md +11 -1
  333. package/docs/frameworks.mdx +9 -9
  334. package/docs/getting-started.md +1 -1
  335. package/docs/install-manual.mdx +1 -1
  336. package/docs/license-activation.mdx +3 -3
  337. package/docs/packages/tauri.mdx +1 -1
  338. package/docs/packages.mdx +1 -1
  339. package/docs/predicates.mdx +1 -1
  340. package/docs/quickstart.mdx +68 -49
  341. package/docs/skill-setup.md +1 -1
  342. package/docs/skill.md +14 -13
  343. package/docs/telemetry.md +2 -2
  344. package/docs/tools/navigate.mdx +1 -1
  345. package/docs/tools/verify.mdx +4 -1
  346. package/docs/troubleshooting.mdx +1 -1
  347. package/docs/usage.md +17 -14
  348. package/package.json +13 -13
  349. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  350. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -1,4 +1,4 @@
1
- import type { ToolDef } from '../../surface/tools/tool-kit.js';
1
+ import { type ToolDef } from '../../surface/tools/tool-kit.js';
2
2
  /**
3
3
  * `reticle_affected` — which saved flows must re-verify for a set of changed files.
4
4
  *
@@ -1,10 +1,40 @@
1
1
  import { z } from 'zod';
2
- import { ReticleTool } from '@reticlehq/core';
3
- import { loadNamedFlows, resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
4
- import { projectDirFor } from '../../memory/project/session-root.js';
5
- import { sessionRoot, sessionProjectId } from '../../memory/project/session-root.js';
2
+ import { asString, ReticleTool } from '@reticlehq/core';
3
+ import { sessionIdShape } from '../../surface/tools/tool-kit.js';
4
+ import { resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
5
+ import { namedFlowsForSession } from './flow-store-for-session.js';
6
6
  import { affectedSavedFlows } from './change/flow-sources.js';
7
7
  import { RECORD_A_FLOW } from './suite/coverage.js';
8
+ /**
9
+ * Why the answer is empty, when that needs saying.
10
+ *
11
+ * Several situations produced the same empty answer: no files changed, no flows saved, and a git
12
+ * ref that resolved to nothing. Only the first means "nothing to re-verify". The others mean the
13
+ * question could not be answered, and re-running nothing on that basis is how a regression ships.
14
+ */
15
+ function emptyAnswerReason(facts) {
16
+ const { changed, since, fileCount, flowCount, changedCount, unmatchedRoot } = facts;
17
+ // No folder declares this project, so nothing here is about a checkout (#1550).
18
+ if (unmatchedRoot !== undefined) {
19
+ return `no flows here: no folder on disk declares this project, so its evidence goes to ${unmatchedRoot}. Run \`reticle init\` in the app's folder, or lease the tab from there.`;
20
+ }
21
+ // A git failure OUTRANKS every other explanation: the others describe a question that was
22
+ // answered, and this one is a question that was not. Reporting "nothing changed" over a mistyped
23
+ // ref or a shallow clone is how a regression ships past the tool meant to catch it.
24
+ if (true === changed.failed) {
25
+ return `could not read the diff for \`${String(since)}\`, so NOTHING was compared and this result proves nothing: ${changed.reason ?? 'git failed'}`;
26
+ }
27
+ if (0 === flowCount) {
28
+ return `no saved flows exist yet, so nothing COULD be affected. ${RECORD_A_FLOW}`;
29
+ }
30
+ if (0 !== changedCount) {
31
+ return undefined;
32
+ }
33
+ if (since === undefined && 0 === fileCount) {
34
+ return 'no files were given and no `since` ref was passed, so nothing was compared — pass the files you edited, or a git ref';
35
+ }
36
+ return 'nothing changed against that input, so no saved flow needs re-verifying';
37
+ }
8
38
  /**
9
39
  * `reticle_affected` — which saved flows must re-verify for a set of changed files.
10
40
  *
@@ -27,6 +57,7 @@ export const AFFECTED_TOOLS = [
27
57
  description: 'Which saved flows must re-verify for a set of changed files. Pass the files you edited (and/or `since` for a git ref to diff against). Returns { changedFiles, affected, unknownProvenance } — `affected` is what to re-run, and `unknownProvenance` are flows included only because Reticle cannot tell which sources they cover, so they are re-verified by default rather than silently skipped.',
28
58
  example: { files: ['src/App.tsx'] },
29
59
  inputSchema: {
60
+ ...sessionIdShape,
30
61
  files: z
31
62
  .array(z.string())
32
63
  .optional()
@@ -46,34 +77,22 @@ export const AFFECTED_TOOLS = [
46
77
  ? args['files'].filter((f) => 'string' === typeof f)
47
78
  : [];
48
79
  const since = 'string' === typeof args['since'] ? args['since'] : undefined;
49
- // Same address as verify_change, resolved the same way. `deps.reticleRoot` is NOT the project
50
- // directory this line used to claim it was — it is the daemon's `.reticle` subdirectory, in
51
- // whichever tree the MCP host was started in. `undefined` here resolves the single connected
52
- // session, which is the same rule the flow lookup below already follows.
53
- const changed = await resolveChangedFiles(files, since, projectDirFor(deps, undefined));
80
+ // The project of the tab being driven, not the folder the daemon started in (#1550). The flows
81
+ // come from the store `flows` replays from, so a flow named here can be replayed by that name
82
+ // (#1996). `git diff` runs in the project directory that holds that store.
83
+ const { flows, projectDir, unmatchedRoot } = await namedFlowsForSession(deps, asString(args['sessionId']));
84
+ // A bucket of evidence is not a tree, so a `since` has nothing to diff against.
85
+ const changed = await resolveChangedFiles(files, unmatchedRoot === undefined ? since : undefined, projectDir);
54
86
  const changedFiles = changed.files;
55
- // No `sessionId` on this tool's surface, and it does not need one: passing `undefined`
56
- // resolves the single connected session, and falls back exactly as before when it cannot.
57
- // Adding an argument here would be a surface change to fix an addressing bug.
58
- const flows = await loadNamedFlows(deps.fs, sessionRoot(deps, undefined), sessionProjectId(deps, undefined));
59
87
  const result = affectedSavedFlows(flows, changedFiles);
60
- // Three different situations produced the same empty answer: no files changed, no flows saved,
61
- // and a git ref that resolved to nothing. Only the first means "nothing to re-verify"; the
62
- // others mean the question could not be answered, and re-running nothing on that basis is how a
63
- // regression ships. Say which one it is.
64
- const why =
65
- // A git failure OUTRANKS every other explanation: the others describe a question that was
66
- // answered, and this one is a question that was not. Reporting "nothing changed" over a
67
- // mistyped ref or a shallow clone is how a regression ships past the tool meant to catch it.
68
- changed.failed
69
- ? `could not read the diff for \`${String(since)}\`, so NOTHING was compared and this result proves nothing: ${changed.reason ?? 'git failed'}`
70
- : 0 === flows.length
71
- ? `no saved flows exist yet, so nothing COULD be affected. ${RECORD_A_FLOW}`
72
- : 0 === changedFiles.length
73
- ? since === undefined && 0 === files.length
74
- ? 'no files were given and no `since` ref was passed, so nothing was compared — pass the files you edited, or a git ref'
75
- : 'nothing changed against that input, so no saved flow needs re-verifying'
76
- : undefined;
88
+ const why = emptyAnswerReason({
89
+ changed,
90
+ since,
91
+ fileCount: files.length,
92
+ flowCount: flows.length,
93
+ changedCount: changedFiles.length,
94
+ unmatchedRoot,
95
+ });
77
96
  return {
78
97
  changedFiles,
79
98
  ...result,
@@ -17,7 +17,8 @@ export interface ChangedFiles {
17
17
  }
18
18
  /**
19
19
  * Files changed since a git ref (`git diff --name-only <ref>`), so `reticle gate --since main` works
20
- * from the real diff instead of hand-listed files.
20
+ * from the real diff instead of hand-listed files. Files under Reticle's own directory are left
21
+ * out: a replay writes them, and they are not a change to the app's code.
21
22
  *
22
23
  * Never throws, and never pretends. The CLI gate may still degrade to "no changes" and pass rather
23
24
  * than crash CI — that reasoning was right and is unchanged — but it now has to opt into doing so by
@@ -1,5 +1,6 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
+ import { ReticleDir } from '@reticlehq/core';
3
4
  const run = promisify(execFile);
4
5
  /** Parse `git diff --name-only` output into a clean file list. Pure; exported for testing. */
5
6
  export function parseGitFiles(output) {
@@ -19,9 +20,22 @@ export function parseGitFiles(output) {
19
20
  function refIsOptionLike(ref) {
20
21
  return ref.startsWith('-');
21
22
  }
23
+ /**
24
+ * True when a diff path sits under Reticle's own project directory.
25
+ *
26
+ * A replay writes intent, impact and episode files into that directory. Those writes are
27
+ * Reticle's output, not a change to the app's code. A flow with no source stamps reads any
28
+ * change as "affected", so counting them marked flows the gate had just proved as uncovered
29
+ * (#2050). The directory can sit below the repo root, so any path segment counts. `git diff`
30
+ * prints paths with `/` on every platform.
31
+ */
32
+ function isReticleOwnPath(path) {
33
+ return path.split('/').includes(ReticleDir.ROOT);
34
+ }
22
35
  /**
23
36
  * Files changed since a git ref (`git diff --name-only <ref>`), so `reticle gate --since main` works
24
- * from the real diff instead of hand-listed files.
37
+ * from the real diff instead of hand-listed files. Files under Reticle's own directory are left
38
+ * out: a replay writes them, and they are not a change to the app's code.
25
39
  *
26
40
  * Never throws, and never pretends. The CLI gate may still degrade to "no changes" and pass rather
27
41
  * than crash CI — that reasoning was right and is unchanged — but it now has to opt into doing so by
@@ -38,7 +52,10 @@ export async function changedFilesSince(ref, cwd) {
38
52
  }
39
53
  try {
40
54
  const { stdout } = await run('git', ['diff', '--name-only', ref], { cwd });
41
- return { files: parseGitFiles(stdout), failed: false };
55
+ return {
56
+ files: parseGitFiles(stdout).filter((file) => !isReticleOwnPath(file)),
57
+ failed: false,
58
+ };
42
59
  }
43
60
  catch (error) {
44
61
  return {
@@ -41,7 +41,7 @@ export declare const SECRET_FIELD_ARG = "secretField";
41
41
  * rules must not be able to drift. It used to sit in `replay.ts`, one import away from the sentence
42
42
  * saying it had to stay together.
43
43
  */
44
- export declare const secretEnvKey: (field: string) => string;
44
+ export declare function secretEnvKey(field: string): string;
45
45
  /** The schema of that argument, shared by every tool that replays a flow. */
46
46
  export { CALL_SECRETS_ARG };
47
47
  export declare const callSecretsArg: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -59,16 +59,4 @@ export interface UnsuppliedSecret {
59
59
  field: string;
60
60
  envKey: string;
61
61
  }
62
- /**
63
- * Every credential this flow will replay as the literal placeholder.
64
- *
65
- * Checked BEFORE a step runs, because the alternative is what shipped: the placeholder is typed
66
- * into the password box, sign-in fails, and the flow reports that an element further down the
67
- * journey has gone missing -- a `drift` verdict naming a component nobody touched, with an
68
- * invitation to update a flow that was correct.
69
- *
70
- * The emptiness rule matches `replayActionArgs` exactly: it substitutes only for a value of
71
- * non-zero length, so an empty variable leaves the placeholder and must count as unsupplied here.
72
- * Two different answers to "is this supplied" is how the gate would pass and the replay still fail.
73
- */
74
62
  export declare function unsuppliedSecrets(flow: FlowFile, env: Record<string, string | undefined>): UnsuppliedSecret[];
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { z } from 'zod';
2
3
  import { AnchorKind } from '@reticlehq/core';
3
4
  import { CALL_SECRETS_ARG, callSecrets } from '../../../surface/tools/tool-kit.js';
@@ -14,12 +15,15 @@ import { CALL_SECRETS_ARG, callSecrets } from '../../../surface/tools/tool-kit.j
14
15
  * to find the env var because the runner passed a role or a label instead.
15
16
  */
16
17
  export function anchorFieldName(anchor) {
17
- if (AnchorKind.TESTID === anchor.kind)
18
+ if (AnchorKind.TESTID === anchor.kind) {
18
19
  return anchor.value;
19
- if (AnchorKind.ROLE === anchor.kind)
20
+ }
21
+ if (AnchorKind.ROLE === anchor.kind) {
20
22
  return anchor.name;
21
- if (AnchorKind.SIGNAL === anchor.kind)
23
+ }
24
+ if (AnchorKind.SIGNAL === anchor.kind) {
22
25
  return anchor.name;
26
+ }
23
27
  return undefined;
24
28
  }
25
29
  /**
@@ -39,6 +43,16 @@ export const SECRET_NAME_ARG = 'secret';
39
43
  export const SECRET_FIELD_ARG = 'secretField';
40
44
  /** The prefix every secret's variable carries. */
41
45
  const SECRET_ENV_PREFIX = 'RETICLE_SECRET_';
46
+ /** Any character outside ASCII. */
47
+ const NON_ASCII = /[^\p{ASCII}]/u;
48
+ /** The word in the variable of a field whose name is not ASCII. */
49
+ const NON_ASCII_FIELD = 'FIELD';
50
+ const NON_ASCII_DIGEST_LENGTH = 8;
51
+ /**
52
+ * The variable name an ASCII field gets. Up to 3.9.0 a non-ASCII field got this name too, and a
53
+ * flow saved then still names it.
54
+ */
55
+ const asciiSecretEnvKey = (field) => `${SECRET_ENV_PREFIX}${field.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}`;
42
56
  /**
43
57
  * The environment variable that supplies one redacted field.
44
58
  *
@@ -51,7 +65,16 @@ const SECRET_ENV_PREFIX = 'RETICLE_SECRET_';
51
65
  * rules must not be able to drift. It used to sit in `replay.ts`, one import away from the sentence
52
66
  * saying it had to stay together.
53
67
  */
54
- export const secretEnvKey = (field) => `${SECRET_ENV_PREFIX}${field.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}`;
68
+ export function secretEnvKey(field) {
69
+ if (!NON_ASCII.test(field)) {
70
+ return asciiSecretEnvKey(field);
71
+ }
72
+ // A name in another script has no ASCII letters to keep: "Пароль" and "Гасло" both became
73
+ // `RETICLE_SECRET__`, so one field's value was typed into the other. A short hash of the name
74
+ // keeps each field's variable distinct and the same on every machine.
75
+ const digest = createHash('sha256').update(field.normalize('NFC').toLowerCase()).digest('hex');
76
+ return `${SECRET_ENV_PREFIX}${NON_ASCII_FIELD}_${digest.slice(0, NON_ASCII_DIGEST_LENGTH).toUpperCase()}`;
77
+ }
55
78
  /** The schema of that argument, shared by every tool that replays a flow. */
56
79
  export { CALL_SECRETS_ARG };
57
80
  export const callSecretsArg = z
@@ -64,30 +87,35 @@ export const callSecretsArg = z
64
87
  */
65
88
  export function secretValue(envKey, env) {
66
89
  for (const [key, value] of Object.entries(callSecrets() ?? {})) {
67
- if (envKey === (key.startsWith(SECRET_ENV_PREFIX) ? key : secretEnvKey(key)))
90
+ if (envKey === (key.startsWith(SECRET_ENV_PREFIX) ? key : secretEnvKey(key))) {
68
91
  return value;
92
+ }
69
93
  }
70
94
  return env[envKey];
71
95
  }
72
96
  const SECRET_OCCURRENCE = /^(?:[2-9]|[1-9][0-9]+)$/;
73
97
  /** The field's own saved variable name, or its original key for legacy and invalid metadata. */
74
98
  export function secretKeyForFill(args, field) {
75
- if (args?.['value'] !== REDACTED_FILL)
99
+ if (args?.['value'] !== REDACTED_FILL) {
76
100
  return undefined;
101
+ }
77
102
  const named = args[SECRET_NAME_ARG];
78
103
  const base = secretEnvKey(field);
79
- // A flow can supply this field's occurrence, never an unrelated environment variable.
80
- return 'string' === typeof named &&
81
- named.startsWith(`${base}_`) &&
82
- SECRET_OCCURRENCE.test(named.slice(base.length + 1))
83
- ? named
84
- : base;
104
+ // A flow can supply this field's occurrence, never an unrelated environment variable. The name
105
+ // an earlier version gave a non-ASCII field is still this field's.
106
+ if ('string' !== typeof named) {
107
+ return base;
108
+ }
109
+ const own = (key) => named === key ||
110
+ (named.startsWith(`${key}_`) && SECRET_OCCURRENCE.test(named.slice(key.length + 1)));
111
+ return own(base) || own(asciiSecretEnvKey(field)) ? named : base;
85
112
  }
86
113
  function* secretSteps(steps) {
87
114
  for (const step of steps) {
88
115
  yield step;
89
- if (step.steps !== undefined)
116
+ if (step.steps !== undefined) {
90
117
  yield* secretSteps(step.steps);
118
+ }
91
119
  }
92
120
  }
93
121
  /** Allocate recorded fills' names while preserving legacy placeholders and existing suffixes. */
@@ -96,14 +124,17 @@ export function withDistinctSecretKeys(flow) {
96
124
  const used = new Set();
97
125
  for (const step of secretSteps(flow.steps)) {
98
126
  const field = anchorFieldName(step.anchor);
99
- if (field === undefined)
127
+ if (field === undefined) {
100
128
  continue;
129
+ }
101
130
  const key = secretKeyForFill(step.args, field);
102
- if (key === undefined)
131
+ if (key === undefined) {
103
132
  continue;
133
+ }
104
134
  reserved.add(key);
105
- if (key !== secretEnvKey(field) || step.args?.[SECRET_NAME_ARG] === undefined)
135
+ if (key !== secretEnvKey(field) || step.args?.[SECRET_NAME_ARG] === undefined) {
106
136
  used.add(key);
137
+ }
107
138
  }
108
139
  const assign = (step) => {
109
140
  let out = step;
@@ -119,8 +150,9 @@ export function withDistinctSecretKeys(flow) {
119
150
  occurrence += 1;
120
151
  }
121
152
  used.add(key);
122
- if (key !== base)
153
+ if (key !== base) {
123
154
  out = { ...step, args: { ...step.args, [SECRET_NAME_ARG]: key } };
155
+ }
124
156
  }
125
157
  return step.steps === undefined ? out : { ...out, steps: step.steps.map(assign) };
126
158
  };
@@ -138,23 +170,47 @@ export function withDistinctSecretKeys(flow) {
138
170
  * non-zero length, so an empty variable leaves the placeholder and must count as unsupplied here.
139
171
  * Two different answers to "is this supplied" is how the gate would pass and the replay still fail.
140
172
  */
173
+ function* flattenAll(steps) {
174
+ for (const step of steps) {
175
+ yield step;
176
+ yield* flattenAll(step.steps ?? []);
177
+ }
178
+ }
141
179
  export function unsuppliedSecrets(flow, env) {
142
180
  const out = [];
143
181
  const seen = new Set();
144
182
  for (const step of secretSteps(flow.steps ?? [])) {
145
183
  const field = anchorFieldName(step.anchor);
146
- if (field === undefined)
184
+ if (field === undefined) {
147
185
  continue;
186
+ }
148
187
  const envKey = secretKeyForFill(step.args, field);
149
- if (envKey === undefined)
188
+ if (envKey === undefined) {
150
189
  continue;
190
+ }
151
191
  const supplied = secretValue(envKey, env);
152
- if (supplied !== undefined && supplied.length > 0)
192
+ if (supplied !== undefined && supplied.length > 0) {
153
193
  continue;
154
- if (seen.has(envKey))
194
+ }
195
+ if (seen.has(envKey)) {
155
196
  continue;
197
+ }
156
198
  seen.add(envKey);
157
199
  out.push({ field, envKey });
158
200
  }
201
+ // A request step that acts as another account needs that account's session.
202
+ for (const step of flattenAll(flow.steps ?? [])) {
203
+ const account = step.request?.as;
204
+ if (account === undefined) {
205
+ continue;
206
+ }
207
+ const envKey = secretEnvKey(account);
208
+ const supplied = secretValue(envKey, env);
209
+ if ((supplied !== undefined && supplied.length > 0) || seen.has(envKey)) {
210
+ continue;
211
+ }
212
+ seen.add(envKey);
213
+ out.push({ field: account, envKey });
214
+ }
159
215
  return out;
160
216
  }
@@ -1,4 +1,4 @@
1
- import { AnchorKind, type Drift, type FlowAnchor, type QueryEmptyHint } from '@reticlehq/core';
1
+ import { AnchorKind, type CommandResult, type Drift, type FlowAnchor, type QueryEmptyHint } from '@reticlehq/core';
2
2
  import type { FlowReplaySession, Sleep } from './flow-replay-types.js';
3
3
  /**
4
4
  * Finding a step's anchor again, and saying legibly why it could not be found.
@@ -33,6 +33,29 @@ export declare function nearestTestid(missing: string, present: string[]): strin
33
33
  export declare function nearestIsAmbiguous(missing: string, present: string[]): boolean;
34
34
  /** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
35
35
  export declare function testidDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
36
+ /**
37
+ * How an anchor is ADDRESSED — the sentence that names it, and the label a result row carries.
38
+ *
39
+ * Two different strings, and neither can be derived from the other: `value` is a raw testid for one
40
+ * anchor kind and a human-readable label for the others, so one sentence cannot name both. Calling a
41
+ * role anchor `testid "button "View result""` sends the reader to edit a locator that does not exist
42
+ * on that step, which is the same misdirection the drift reason exists to remove.
43
+ *
44
+ * Derived from the anchor rather than passed in by the caller. The first version of the role fix took
45
+ * a `described` argument and wrote `ambiguousAnchorDrift(label, n, \`role anchor ${label}\`)` — the
46
+ * same string twice, so the `anchor` field and the sentence could drift apart with nothing to catch
47
+ * it, and the next anchor kind would have had to remember the convention on its own.
48
+ */
49
+ /**
50
+ * How a role anchor reads in a message: `button "View result"`, or for a control with no accessible
51
+ * name, `button (no accessible name, #2 of 3)` — never `button "undefined"`.
52
+ */
53
+ export declare function roleAnchorLabel(anchor: {
54
+ role: string;
55
+ name?: string | undefined;
56
+ nth?: number | undefined;
57
+ of?: number | undefined;
58
+ }): string;
36
59
  /**
37
60
  * What the stale-ref retry found when it ran the locator again.
38
61
  *
@@ -106,10 +129,28 @@ export declare function queryOnce(session: FlowReplaySession, queryArgs: Record<
106
129
  refs: string[];
107
130
  hint?: QueryEmptyHint;
108
131
  }>;
109
- export declare function resolveQuery(session: FlowReplaySession, queryArgs: Record<string, unknown>, sleep: Sleep, now?: () => number): Promise<{
132
+ export declare function resolveQuery(session: FlowReplaySession, queryArgs: Record<string, unknown>, sleep: Sleep, now?: () => number, read?: (result: CommandResult) => {
133
+ refs: string[];
134
+ hint?: QueryEmptyHint;
135
+ }): Promise<{
110
136
  refs: string[];
111
137
  hint?: QueryEmptyHint;
112
138
  }>;
139
+ /**
140
+ * Several queries for one control, pooled: each attempt runs every query and joins the refs, so
141
+ * the settle budget is spent once, not once for each query. A role anchor with `alsoRoles` uses it.
142
+ */
143
+ export declare function resolveAnyQuery(session: FlowReplaySession, queries: readonly Record<string, unknown>[], sleep: Sleep, now?: () => number, read?: (result: CommandResult) => {
144
+ refs: string[];
145
+ hint?: QueryEmptyHint;
146
+ }): Promise<{
147
+ refs: string[];
148
+ hint?: QueryEmptyHint;
149
+ }>;
150
+ /** `resolveQuery`'s reader for a nameless role anchor: only the matches with no accessible name. */
151
+ export declare function readNamelessQuery(result: CommandResult): {
152
+ refs: string[];
153
+ };
113
154
  /** Re-resolve a testid anchor. */
114
155
  export declare function resolveTestid(session: FlowReplaySession, value: string, sleep: Sleep): Promise<{
115
156
  refs: string[];
@@ -125,3 +166,20 @@ export declare function anchorLabel(anchor: FlowAnchor): string;
125
166
  export declare function componentQueryArgs(anchor: Extract<FlowAnchor, {
126
167
  kind: typeof AnchorKind.COMPONENT;
127
168
  }>): Record<string, unknown>;
169
+ /** Query args for a NAMED role anchor — the handle that identifies an instance, not a JSX site. */
170
+ export declare function roleQueryArgs(anchor: Extract<FlowAnchor, {
171
+ kind: typeof AnchorKind.ROLE;
172
+ }>): Record<string, unknown>;
173
+ /**
174
+ * Resolve a role anchor. ONE function for the first look and the re-resolve after a stale ref: the
175
+ * re-resolve used to count every control of the role, so a nameless anchor's position came back
176
+ * ambiguous on a working page.
177
+ *
178
+ * A nameless anchor's position counts only the nameless controls of its role (an icon-only edit
179
+ * button is the 2nd nameless button of its row, not the 5th button), so it reads only those.
180
+ */
181
+ export declare function resolveRoleAnchor(session: FlowReplaySession, anchor: Extract<FlowAnchor, {
182
+ kind: typeof AnchorKind.ROLE;
183
+ }>, sleep: Sleep): Promise<{
184
+ refs: string[];
185
+ }>;