@reticlehq/server 2.7.0 → 2.9.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 (357) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +130 -778
  3. package/dist/bridge/bridge.d.ts +23 -0
  4. package/dist/bridge/bridge.js +80 -2
  5. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  6. package/dist/bridge/bridge.test-harness.js +6 -4
  7. package/dist/capsule/causal-summary.d.ts +36 -1
  8. package/dist/capsule/causal-summary.js +33 -7
  9. package/dist/capsule/divergence.js +7 -1
  10. package/dist/cli/chromium-hint.d.ts +33 -5
  11. package/dist/cli/chromium-hint.js +82 -12
  12. package/dist/cli/cli-doctor.js +65 -34
  13. package/dist/cli/cli-flow-commands.js +8 -8
  14. package/dist/cli/cli-kill.d.ts +58 -0
  15. package/dist/cli/cli-kill.js +162 -0
  16. package/dist/cli/cli-parse.d.ts +16 -1
  17. package/dist/cli/cli-parse.js +32 -9
  18. package/dist/cli/cli-port.d.ts +32 -0
  19. package/dist/cli/cli-port.js +82 -35
  20. package/dist/cli/cli-verify.d.ts +21 -0
  21. package/dist/cli/cli-verify.js +47 -3
  22. package/dist/cli/config-discovery.d.ts +35 -0
  23. package/dist/cli/config-discovery.js +163 -0
  24. package/dist/cli/doctor-daemon-line.js +4 -1
  25. package/dist/cli/doctor-project-line.d.ts +37 -0
  26. package/dist/cli/doctor-project-line.js +35 -0
  27. package/dist/cli/doctor-rows.d.ts +51 -0
  28. package/dist/cli/doctor-rows.js +54 -0
  29. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  30. package/dist/cli/doctor-sessions-line.js +69 -0
  31. package/dist/cli/drive-attach.d.ts +99 -0
  32. package/dist/cli/drive-attach.js +203 -0
  33. package/dist/cli/drive-command.d.ts +13 -0
  34. package/dist/cli/drive-command.js +103 -0
  35. package/dist/cli/open-note.d.ts +20 -0
  36. package/dist/cli/open-note.js +37 -0
  37. package/dist/cli/port-holder.d.ts +10 -0
  38. package/dist/cli/port-holder.js +26 -2
  39. package/dist/cli/status-next-action.d.ts +37 -0
  40. package/dist/cli/status-next-action.js +61 -0
  41. package/dist/cli.js +119 -39
  42. package/dist/crawl/crawl-tools.js +12 -2
  43. package/dist/crawl/crawl.d.ts +13 -0
  44. package/dist/crawl/crawl.js +50 -2
  45. package/dist/events/contradictions.d.ts +46 -0
  46. package/dist/events/contradictions.js +35 -22
  47. package/dist/events/declared.d.ts +45 -0
  48. package/dist/events/declared.js +73 -0
  49. package/dist/events/echo-mismatch.d.ts +8 -2
  50. package/dist/events/echo-mismatch.js +33 -5
  51. package/dist/events/event-filters.d.ts +1 -1
  52. package/dist/events/event-filters.js +7 -4
  53. package/dist/events/predicate-eval.d.ts +17 -72
  54. package/dist/events/predicate-eval.js +117 -236
  55. package/dist/events/predicate-parse.js +31 -3
  56. package/dist/events/predicate-route.d.ts +32 -0
  57. package/dist/events/predicate-route.js +102 -0
  58. package/dist/events/predicate-schema.d.ts +99 -0
  59. package/dist/events/predicate-schema.js +309 -0
  60. package/dist/events/predicate.d.ts +8 -0
  61. package/dist/events/predicate.js +296 -22
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-classify.js +1 -1
  69. package/dist/flows/flow-replay-run.js +8 -1
  70. package/dist/flows/flow-tools.js +21 -5
  71. package/dist/flows/heal-run.js +1 -1
  72. package/dist/flows/replay.js +3 -0
  73. package/dist/honesty/accepted-write.d.ts +3 -0
  74. package/dist/honesty/accepted-write.js +8 -3
  75. package/dist/honesty/already-true.js +15 -0
  76. package/dist/honesty/blind-spots.js +10 -2
  77. package/dist/honesty/honesty.d.ts +11 -0
  78. package/dist/honesty/honesty.js +1 -0
  79. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  80. package/dist/honesty/uncaptured-bodies.js +9 -2
  81. package/dist/honesty/unread-outcome.d.ts +8 -0
  82. package/dist/honesty/unread-outcome.js +36 -9
  83. package/dist/honesty/unsettled.d.ts +49 -0
  84. package/dist/honesty/unsettled.js +82 -0
  85. package/dist/honesty/verified.d.ts +36 -3
  86. package/dist/honesty/verified.js +54 -11
  87. package/dist/http-server.d.ts +19 -0
  88. package/dist/http-server.js +202 -4
  89. package/dist/impact/impact-recorder.d.ts +23 -0
  90. package/dist/impact/impact-recorder.js +76 -0
  91. package/dist/impact/impact-store.d.ts +47 -0
  92. package/dist/impact/impact-store.js +171 -0
  93. package/dist/index.d.ts +7 -0
  94. package/dist/index.js +47 -10
  95. package/dist/init/agent-rules.d.ts +36 -0
  96. package/dist/init/agent-rules.js +132 -22
  97. package/dist/init/astro-patch.d.ts +2 -1
  98. package/dist/init/astro-patch.js +38 -6
  99. package/dist/init/capabilities.d.ts +22 -0
  100. package/dist/init/capabilities.js +76 -0
  101. package/dist/init/confirm.d.ts +114 -0
  102. package/dist/init/confirm.js +165 -0
  103. package/dist/init/connect-steps.d.ts +2 -0
  104. package/dist/init/connect-steps.js +50 -0
  105. package/dist/init/cra.js +3 -2
  106. package/dist/init/csp-check.d.ts +38 -0
  107. package/dist/init/csp-check.js +92 -0
  108. package/dist/init/csp-doctor.d.ts +25 -0
  109. package/dist/init/csp-doctor.js +49 -0
  110. package/dist/init/cursor.js +6 -9
  111. package/dist/init/detect.d.ts +8 -0
  112. package/dist/init/detect.js +33 -3
  113. package/dist/init/mcp-clients.d.ts +1 -0
  114. package/dist/init/mcp-clients.js +51 -7
  115. package/dist/init/mcp.d.ts +15 -0
  116. package/dist/init/mcp.js +19 -0
  117. package/dist/init/non-js-project.js +29 -5
  118. package/dist/init/plan-framework.d.ts +10 -0
  119. package/dist/init/plan-framework.js +78 -16
  120. package/dist/init/plan.d.ts +47 -6
  121. package/dist/init/plan.js +228 -69
  122. package/dist/init/run.d.ts +24 -2
  123. package/dist/init/run.js +157 -45
  124. package/dist/init/slash-command.d.ts +1 -1
  125. package/dist/init/slash-command.js +54 -6
  126. package/dist/init/snippets.d.ts +45 -5
  127. package/dist/init/snippets.js +194 -42
  128. package/dist/init/vite-config.js +53 -5
  129. package/dist/input/network-mock-tools.d.ts +19 -0
  130. package/dist/input/network-mock-tools.js +37 -5
  131. package/dist/input/scroll-find.d.ts +6 -0
  132. package/dist/input/scroll-find.js +17 -2
  133. package/dist/input/scroll-tools.js +4 -0
  134. package/dist/journal/attach-journal.d.ts +9 -1
  135. package/dist/journal/attach-journal.js +13 -2
  136. package/dist/journal/journal-query.d.ts +13 -0
  137. package/dist/journal/journal-query.js +28 -3
  138. package/dist/journal/workspace-gitignore.d.ts +10 -0
  139. package/dist/journal/workspace-gitignore.js +59 -0
  140. package/dist/license/license.js +1 -1
  141. package/dist/mcp/mcp-proxy.d.ts +35 -30
  142. package/dist/mcp/mcp-proxy.js +306 -104
  143. package/dist/mcp/mcp.d.ts +10 -1
  144. package/dist/mcp/mcp.js +70 -34
  145. package/dist/mcp/proxy-handshake.d.ts +82 -1
  146. package/dist/mcp/proxy-handshake.js +109 -2
  147. package/dist/mcp/proxy-lifecycle.d.ts +11 -2
  148. package/dist/mcp/proxy-lifecycle.js +12 -3
  149. package/dist/mcp/proxy-log.d.ts +63 -0
  150. package/dist/mcp/proxy-log.js +132 -0
  151. package/dist/mcp/server-instructions.d.ts +17 -0
  152. package/dist/mcp/server-instructions.js +56 -0
  153. package/dist/mcp/shared-params.d.ts +27 -0
  154. package/dist/mcp/shared-params.js +36 -0
  155. package/dist/pool/browser-pool.d.ts +22 -2
  156. package/dist/pool/browser-pool.js +44 -4
  157. package/dist/project/learned-routes.d.ts +24 -0
  158. package/dist/project/learned-routes.js +63 -0
  159. package/dist/project/project-store.d.ts +6 -0
  160. package/dist/project/project-store.js +25 -1
  161. package/dist/project/reticle-dir.d.ts +2 -0
  162. package/dist/project/reticle-dir.js +1 -0
  163. package/dist/session/connection-memory.d.ts +59 -0
  164. package/dist/session/connection-memory.js +108 -0
  165. package/dist/session/dev-command.d.ts +26 -0
  166. package/dist/session/dev-command.js +89 -0
  167. package/dist/session/dev-server-probe.d.ts +68 -0
  168. package/dist/session/dev-server-probe.js +102 -7
  169. package/dist/session/last-act.d.ts +26 -0
  170. package/dist/session/last-act.js +35 -1
  171. package/dist/session/live-control-tools.js +42 -5
  172. package/dist/session/no-session-diagnosis.d.ts +67 -2
  173. package/dist/session/no-session-diagnosis.js +236 -27
  174. package/dist/session/no-session-next-action.d.ts +46 -0
  175. package/dist/session/no-session-next-action.js +85 -0
  176. package/dist/session/no-session-watch.d.ts +40 -1
  177. package/dist/session/no-session-watch.js +174 -8
  178. package/dist/session/pending-commands.d.ts +12 -0
  179. package/dist/session/pending-commands.js +13 -1
  180. package/dist/session/ref-provenance.d.ts +54 -0
  181. package/dist/session/ref-provenance.js +61 -0
  182. package/dist/session/session-lease.js +1 -1
  183. package/dist/session/session-manager.d.ts +4 -0
  184. package/dist/session/session-manager.js +35 -5
  185. package/dist/session/session.d.ts +10 -1
  186. package/dist/session/session.js +146 -5
  187. package/dist/telemetry/app-instrumented.d.ts +8 -0
  188. package/dist/telemetry/app-instrumented.js +10 -0
  189. package/dist/telemetry/automation-hint.d.ts +17 -0
  190. package/dist/telemetry/automation-hint.js +71 -0
  191. package/dist/telemetry/bug-found.d.ts +15 -17
  192. package/dist/telemetry/bug-found.js +52 -21
  193. package/dist/telemetry/cli-telemetry.js +7 -1
  194. package/dist/telemetry/daemon-telemetry.js +24 -0
  195. package/dist/telemetry/feedback.js +1 -1
  196. package/dist/telemetry/init-telemetry.js +9 -1
  197. package/dist/telemetry/install-source.d.ts +56 -0
  198. package/dist/telemetry/install-source.js +74 -0
  199. package/dist/telemetry/instrumentation-stall.d.ts +50 -0
  200. package/dist/telemetry/instrumentation-stall.js +90 -0
  201. package/dist/telemetry/run-telemetry.js +4 -1
  202. package/dist/telemetry/session-metrics.d.ts +8 -0
  203. package/dist/telemetry/session-metrics.js +15 -0
  204. package/dist/telemetry/telemetry.d.ts +7 -1
  205. package/dist/telemetry/telemetry.js +26 -2
  206. package/dist/telemetry/tool-refused.d.ts +19 -0
  207. package/dist/telemetry/tool-refused.js +55 -0
  208. package/dist/telemetry/verification-of.js +46 -7
  209. package/dist/temp-dir.d.ts +27 -0
  210. package/dist/temp-dir.js +46 -0
  211. package/dist/tools/act-tools.js +209 -28
  212. package/dist/tools/assert-grade.d.ts +1 -1
  213. package/dist/tools/assert-grade.js +27 -5
  214. package/dist/tools/assert-verdict.js +49 -7
  215. package/dist/tools/browser-tools.js +10 -0
  216. package/dist/tools/contract-tools.js +4 -0
  217. package/dist/tools/dynamic-tools.js +16 -7
  218. package/dist/tools/error-recovery.d.ts +10 -0
  219. package/dist/tools/error-recovery.js +73 -9
  220. package/dist/tools/feedback-invite.js +1 -1
  221. package/dist/tools/invoke-tool.js +159 -26
  222. package/dist/tools/lease-hint.d.ts +30 -8
  223. package/dist/tools/lease-hint.js +65 -14
  224. package/dist/tools/lease-tools.d.ts +40 -1
  225. package/dist/tools/lease-tools.js +181 -83
  226. package/dist/tools/navigate-result.js +1 -1
  227. package/dist/tools/observe-tools.js +10 -4
  228. package/dist/tools/observed-nothing.js +1 -1
  229. package/dist/tools/react-grace.d.ts +52 -0
  230. package/dist/tools/react-grace.js +90 -0
  231. package/dist/tools/read-tools.js +67 -5
  232. package/dist/tools/resolve-target.d.ts +33 -0
  233. package/dist/tools/resolve-target.js +53 -0
  234. package/dist/tools/settle-in-flight.d.ts +94 -0
  235. package/dist/tools/settle-in-flight.js +146 -0
  236. package/dist/tools/snapshot-delta.d.ts +10 -0
  237. package/dist/tools/snapshot-delta.js +194 -9
  238. package/dist/tools/tool-kit.d.ts +8 -0
  239. package/dist/tools/tool-surface.d.ts +60 -0
  240. package/dist/tools/tool-surface.js +86 -1
  241. package/dist/tools/tools.js +61 -8
  242. package/dist/tools/unadvertised-help.js +9 -6
  243. package/docs/README.md +55 -11
  244. package/docs/actions.mdx +120 -0
  245. package/docs/agent-cheatsheet.md +95 -29
  246. package/docs/architecture.md +52 -35
  247. package/docs/benchmarks.md +127 -75
  248. package/docs/best-practices.mdx +216 -0
  249. package/docs/capabilities.mdx +534 -0
  250. package/docs/cli/affected.mdx +53 -0
  251. package/docs/cli/capsules.mdx +70 -0
  252. package/docs/cli/cloud.mdx +153 -0
  253. package/docs/cli/doctor.mdx +109 -0
  254. package/docs/cli/drive.mdx +74 -0
  255. package/docs/cli/feedback.mdx +67 -0
  256. package/docs/cli/gate.mdx +64 -0
  257. package/docs/cli/help.mdx +94 -0
  258. package/docs/cli/hunt.mdx +73 -0
  259. package/docs/cli/identify.mdx +76 -0
  260. package/docs/cli/init.mdx +121 -0
  261. package/docs/cli/kill.mdx +91 -0
  262. package/docs/cli/license.mdx +48 -0
  263. package/docs/cli/mcp.mdx +61 -0
  264. package/docs/cli/open.mdx +74 -0
  265. package/docs/cli/restart.mdx +60 -0
  266. package/docs/cli/rollback.mdx +35 -0
  267. package/docs/cli/serve.mdx +69 -0
  268. package/docs/cli/status.mdx +88 -0
  269. package/docs/cli/stop.mdx +64 -0
  270. package/docs/cli/telemetry.mdx +57 -0
  271. package/docs/cli/update.mdx +52 -0
  272. package/docs/cli/verify.mdx +76 -0
  273. package/docs/cli/version.mdx +39 -0
  274. package/docs/cli/watch.mdx +62 -0
  275. package/docs/cli.mdx +241 -0
  276. package/docs/debugging.md +16 -10
  277. package/docs/deploy-checks.md +45 -12
  278. package/docs/desktop.mdx +384 -0
  279. package/docs/docs.json +364 -0
  280. package/docs/enterprise.md +22 -16
  281. package/docs/faq.mdx +102 -0
  282. package/docs/fixtures.md +13 -7
  283. package/docs/flows.md +29 -23
  284. package/docs/for-agents.md +71 -0
  285. package/docs/frameworks.mdx +238 -0
  286. package/docs/gate-plan.md +48 -42
  287. package/docs/gates.md +28 -22
  288. package/docs/getting-started.md +129 -77
  289. package/docs/human-control.md +24 -18
  290. package/docs/index.mdx +143 -0
  291. package/docs/install-agentic.mdx +224 -0
  292. package/docs/install-manual.mdx +196 -0
  293. package/docs/instrumentation.mdx +238 -0
  294. package/docs/integration-patterns.md +41 -35
  295. package/docs/local-registry.md +19 -13
  296. package/docs/multi-agent-testing.md +19 -13
  297. package/docs/packages/babel-plugin.mdx +79 -0
  298. package/docs/packages/browser.mdx +111 -0
  299. package/docs/packages/core.mdx +116 -0
  300. package/docs/packages/electron.mdx +85 -0
  301. package/docs/packages/eslint-plugin.mdx +86 -0
  302. package/docs/packages/next.mdx +89 -0
  303. package/docs/packages/react.mdx +91 -0
  304. package/docs/packages/server.mdx +98 -0
  305. package/docs/packages/tauri.mdx +63 -0
  306. package/docs/packages/test.mdx +119 -0
  307. package/docs/packages/vite-plugin.mdx +94 -0
  308. package/docs/packages.mdx +218 -0
  309. package/docs/platform-integration.md +35 -26
  310. package/docs/predicates.mdx +248 -0
  311. package/docs/quickstart.mdx +257 -0
  312. package/docs/recipes.mdx +282 -0
  313. package/docs/skill-file.mdx +107 -0
  314. package/docs/state-management.mdx +199 -0
  315. package/docs/style.css +14 -0
  316. package/docs/system-map.md +50 -33
  317. package/docs/telemetry-contract.md +106 -52
  318. package/docs/telemetry-events.mdx +475 -0
  319. package/docs/telemetry.md +63 -55
  320. package/docs/testing.md +44 -6
  321. package/docs/token-efficiency.md +37 -27
  322. package/docs/tools-act-and-wait.mdx +173 -0
  323. package/docs/tools-act-sequence.mdx +137 -0
  324. package/docs/tools-act.mdx +95 -0
  325. package/docs/tools-assert.mdx +129 -0
  326. package/docs/tools-console.mdx +63 -0
  327. package/docs/tools-inspect.mdx +88 -0
  328. package/docs/tools-navigate.mdx +99 -0
  329. package/docs/tools-network.mdx +119 -0
  330. package/docs/tools-observe.mdx +145 -0
  331. package/docs/tools-overview.mdx +160 -0
  332. package/docs/tools-query.mdx +126 -0
  333. package/docs/tools-session-and-feedback.mdx +112 -0
  334. package/docs/tools-sessions.mdx +94 -0
  335. package/docs/tools-snapshot.mdx +129 -0
  336. package/docs/tools-state.mdx +104 -0
  337. package/docs/tools-tools-and-run.mdx +156 -0
  338. package/docs/tools-wait-for.mdx +85 -0
  339. package/docs/troubleshooting.mdx +263 -0
  340. package/docs/usage.md +209 -165
  341. package/docs/vs-chrome-devtools-mcp.mdx +123 -0
  342. package/docs/vs-playwright-mcp.mdx +125 -0
  343. package/docs/vs-screenshots.mdx +137 -0
  344. package/docs/what-is-reticle.mdx +63 -0
  345. package/docs/why-reticle.mdx +109 -0
  346. package/package.json +5 -5
  347. package/docs/desktop-apps.md +0 -256
  348. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  349. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  350. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  351. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  352. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  353. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  354. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  355. package/docs/matrix/MATRIX.md +0 -26
  356. package/docs/matrix/README.md +0 -59
  357. package/docs/mint.json +0 -61
package/dist/index.js CHANGED
@@ -11,9 +11,11 @@ import { resolveBridgeSecurityWithAutoToken } from './bridge/bridge-security.js'
11
11
  import { Bridge } from './bridge/bridge.js';
12
12
  import { BaselineStore } from './project/baselines.js';
13
13
  import { RecordingStore } from './flows/recordings.js';
14
+ import { initImpact } from './impact/impact-recorder.js';
14
15
  import { FlowStore } from './flows/flows.js';
15
16
  import { buildFlowChips } from './flows/flow-scope.js';
16
17
  import { ProjectStore } from './project/project-store.js';
18
+ import { attachRouteLearning } from './project/learned-routes.js';
17
19
  import { AnnotationStore } from './flows/annotation-store.js';
18
20
  import { createNodeFileSystem } from './project/fs-port.js';
19
21
  import { cleanupCaptureDirectories } from './visual/capture-cleanup.js';
@@ -22,6 +24,8 @@ import { createRunnerPort } from './runs/runner-port.js';
22
24
  import { RunStore } from './runs/run-store.js';
23
25
  import { startVerifyServer } from './runs/verify-server.js';
24
26
  import { createMcpServer } from './mcp/mcp.js';
27
+ import { LEASE_ACQUIRE_TOOL } from './tools/lease-tools.js';
28
+ import { runTool } from './tools/invoke-tool.js';
25
29
  import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY } from './session/session-reaper.js';
26
30
  import { wireSessionScope } from './session/no-session-watch.js';
27
31
  import { buildIdlePredicate } from './daemon/daemon-usefulness.js';
@@ -33,9 +37,13 @@ import { BrowserPool } from './pool/browser-pool.js';
33
37
  import { playwrightLauncher, resolveMaxContexts } from './pool/playwright-launcher.js';
34
38
  import { LeaseReaper } from './pool/lease-reaper.js';
35
39
  import { readJournalEnabled, readProjectId } from './cli/cli-port.js';
40
+ import { hasProjectConnectedBefore } from './session/connection-memory.js';
41
+ import { reticleStateHome } from './daemon/daemon.js';
42
+ import { probeChromium } from './cli/chromium-hint.js';
36
43
  import { makeJournalAttach } from './journal/attach-journal.js';
37
44
  import { makeSessionEnd } from './journal/session-end.js';
38
45
  import { AmbientStore } from './journal/ambient-store.js';
46
+ import { ensureWorkspaceGitignore } from './journal/workspace-gitignore.js';
39
47
  import { pruneSessions } from './journal/retention.js';
40
48
  import { log } from './log.js';
41
49
  /** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored. */
@@ -171,8 +179,14 @@ function attachJournal(bridge, deps) {
171
179
  });
172
180
  // Teardown: flush the journal tail to disk + persist what this session learned.
173
181
  bridge.attachSessionEnd(makeSessionEnd(deps));
174
- if (deps.enabled)
182
+ if (deps.enabled) {
175
183
  void pruneSessions(deps.fs, deps.reticleRoot);
184
+ // Here rather than in `init`, because this is the moment we are actually about to write into
185
+ // somebody's repository — and the paths that reach it without ever running `init` (a plugin
186
+ // install, a hand-added client config) are exactly the ones that would otherwise leave an
187
+ // unexplained pile of untracked files behind. Best-effort and write-once; see the helper.
188
+ void ensureWorkspaceGitignore(deps.fs, deps.reticleRoot);
189
+ }
176
190
  }
177
191
  /**
178
192
  * Resolve the drive/real-input provider from options — shared by start and startDaemon so the
@@ -223,6 +237,10 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
223
237
  /** Start the Reticle bridge (browser WS endpoint) and, by default, the MCP stdio server. */
224
238
  export async function start(options = {}) {
225
239
  const port = options.port ?? RETICLE_DEFAULT_PORT;
240
+ // Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
241
+ // serving a browser with no agent attached still has a HUD to answer, and a report that reads
242
+ // "nothing recorded yet" over a month of history on disk is the worst version of this feature.
243
+ initImpact({ reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT) });
226
244
  const security = await resolveBridgeSecurityWithAutoToken(options);
227
245
  const bridge = new Bridge({ port, ...security });
228
246
  // Server-authoritative liveness: a Node-side reaper (immune to browser throttling) ends sessions
@@ -253,6 +271,7 @@ export async function start(options = {}) {
253
271
  attachJournal(bridge, { fs, reticleRoot, enabled: journalEnabled });
254
272
  const flows = new FlowStore(fs, reticleRoot, { now });
255
273
  const project = new ProjectStore(fs, reticleRoot, { now });
274
+ attachRouteLearning(bridge, project);
256
275
  const annotations = new AnnotationStore();
257
276
  pool = createBrowserPool(options.headless ?? true);
258
277
  leaseReaper = new LeaseReaper(pool);
@@ -269,12 +288,13 @@ export async function start(options = {}) {
269
288
  reticleRoot,
270
289
  now,
271
290
  bridgePort: port,
291
+ browserProbe: probeChromium,
272
292
  // The daemon's OWN project, so a tool can tell "this session is mine" from "this session
273
293
  // belongs to a sibling app under the same daemon". contract_save refuses on the second.
274
294
  ...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
275
295
  };
276
296
  const profile = resolveToolSurface(options.toolProfile);
277
- const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile);
297
+ const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile, hasProjectConnectedBefore(reticleStateHome(), port, activeProjectId));
278
298
  // When the agent (the MCP client) disconnects cleanly, end every active session at once so the
279
299
  // HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
280
300
  // self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
@@ -306,6 +326,12 @@ export async function start(options = {}) {
306
326
  */
307
327
  export async function startDaemon(options = {}) {
308
328
  const port = options.port ?? RETICLE_DEFAULT_PORT;
329
+ // The SAME line as in `start`, because these are two entry points that each wire their own world
330
+ // and the daemon is the one that actually serves people. Wired only in `start`, the impact record
331
+ // was never opened in the process the HUD talks to: tool calls still recorded (the dispatch
332
+ // chokepoint opens it lazily), but a tab that connected before the first tool call was pushed
333
+ // nothing, so the report read "nothing recorded yet" over a file with history in it.
334
+ initImpact({ reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT) });
309
335
  const security = await resolveBridgeSecurityWithAutoToken(options);
310
336
  const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
311
337
  const bridge = new Bridge({ port, server: shared.httpServer, ...security });
@@ -330,13 +356,6 @@ export async function startDaemon(options = {}) {
330
356
  });
331
357
  const reaper = new SessionReaper(bridge.sessions);
332
358
  reaper.start();
333
- // Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
334
- // never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
335
- // Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
336
- // sessions, and the agent is told to check two things it cannot see. See no-session-diagnosis.ts.
337
- // The pool reader is passed lazily: `pool` is assigned above but may be undefined when this
338
- // daemon runs without one, and the diagnosis must degrade to the tab message rather than lie.
339
- wireSessionScope(bridge.sessions, readProjectId(process.cwd()), port, () => pool?.reapedLeaseCount() ?? 0);
340
359
  const { realInput, owned } = await resolveRealInput(options, () => shared.close(), makeNetworkDetailRouter(bridge, options.driveUrl));
341
360
  const fs = createNodeFileSystem();
342
361
  const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
@@ -345,10 +364,19 @@ export async function startDaemon(options = {}) {
345
364
  attachJournal(bridge, { fs, reticleRoot, enabled: journalEnabled });
346
365
  const flows = new FlowStore(fs, reticleRoot, { now });
347
366
  const project = new ProjectStore(fs, reticleRoot, { now });
367
+ attachRouteLearning(bridge, project);
348
368
  const annotations = new AnnotationStore();
349
369
  const pool = createBrowserPool(options.headless ?? true);
350
370
  const leaseReaper = new LeaseReaper(pool);
351
371
  leaseReaper.start();
372
+ // Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
373
+ // never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
374
+ // Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
375
+ // sessions, and the agent is told to check two things it cannot see. See no-session-diagnosis.ts.
376
+ // Ordered AFTER the pool: the watch may open a browser itself the moment it finds a listening dev
377
+ // server for a wired project (see no-session-watch.ts), and it opens it through the pool — the same
378
+ // path reticle_lease takes, in this process, binding nothing.
379
+ wireSessionScope(bridge.sessions, readProjectId(process.cwd()), port, () => pool.reapedLeaseCount(), (url) => pool.acquire(url));
352
380
  const deps = {
353
381
  sessions: bridge.sessions,
354
382
  pool,
@@ -361,10 +389,18 @@ export async function startDaemon(options = {}) {
361
389
  reticleRoot,
362
390
  now,
363
391
  bridgePort: port,
392
+ browserProbe: probeChromium,
364
393
  };
365
394
  const profile = resolveToolSurface(options.toolProfile);
366
395
  const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
367
- shared.attachMcp(() => createMcpServer(effectiveDeps, profile));
396
+ // Read per attach, not once: a project that gets wired while this daemon is alive should stop
397
+ // being told to wire itself on the next agent that connects.
398
+ shared.attachMcp(() => createMcpServer(effectiveDeps, profile, hasProjectConnectedBefore(reticleStateHome(), port, readProjectId(process.cwd()))));
399
+ // `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
400
+ // bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
401
+ // through runTool, so it is counted and reported like any other call rather than being a second,
402
+ // invisible dispatch path. See cli/drive-attach.ts for why attaching beats refereeing the race.
403
+ shared.attachDrive((url) => runTool(LEASE_ACQUIRE_TOOL, effectiveDeps, { url }));
368
404
  // Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
369
405
  // driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
370
406
  // persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
@@ -451,6 +487,7 @@ export async function startDaemon(options = {}) {
451
487
  // Exposed so the shutdown watcher can give an ATTACHED daemon a longer grace. The predicate above
452
488
  // says WHETHER it is idle; this says how long that quiet should be tolerated — see idle-grace.
453
489
  agentAttached: () => agentConnected,
490
+ announceShutdown: () => shared.announceShutdown(),
454
491
  close: async () => {
455
492
  reaper.stop();
456
493
  await cleanupCaptureDirectories();
@@ -20,6 +20,20 @@
20
20
  /** Project-root instruction files the agent re-reads every session. */
21
21
  export declare const CLAUDE_MD_PATH = "CLAUDE.md";
22
22
  export declare const AGENTS_MD_PATH = "AGENTS.md";
23
+ /**
24
+ * The full rules, written once and pointed at from every always-loaded file.
25
+ *
26
+ * Two problems it solves at the same time. The rule text is dense and most of it is REFERENCE (what
27
+ * to do when the tools are missing, what a `version_skew` field means, how to file feedback), which
28
+ * an agent needs on the turn it hits that situation and never before — so carrying all of it in
29
+ * `CLAUDE.md` taxed every session for text almost every session did not read. And `AGENTS.md` was
30
+ * only written when NO agent was detected, so a repo set up on a Claude Code machine told Codex,
31
+ * Copilot, Amp and every other agent nothing at all.
32
+ *
33
+ * So: the operative rules (when to verify, WHEN NOT TO, how, and the honesty guards) go in the files
34
+ * that load every turn, and this file holds the rest, one hop away and named in that block.
35
+ */
36
+ export declare const RETICLE_MD_PATH = "RETICLE.md";
23
37
  /** Cursor project rule; the frontmatter `alwaysApply` keeps it in context on every turn. */
24
38
  export declare const CURSOR_RULE_PATH = ".cursor/rules/reticle.mdc";
25
39
  /**
@@ -33,6 +47,28 @@ export declare const CURSOR_RULE_PATH = ".cursor/rules/reticle.mdc";
33
47
  * the package's own name so the two can never drift apart.
34
48
  */
35
49
  export declare const CLI: string;
50
+ /**
51
+ * Who starts the dev server, stated once and rendered everywhere it is needed.
52
+ *
53
+ * The old rule was "Reticle never starts the dev server for you — that's your job", and agents obeyed
54
+ * it exactly: they read it, declined to start anything, and ended the turn with nothing verified. An
55
+ * instruction that reliably produces no verification is a defect in the instruction.
56
+ *
57
+ * The AGENT starts it, not the daemon. A daemon that spawns a build process is invisible to the
58
+ * person whose machine it is running on, was never consented to, and orphans when the daemon exits.
59
+ * The agent already has shell access in this repo, already runs install and build commands, and runs
60
+ * inside a host that asks the human for permission — so a dev server it starts is visible in the
61
+ * transcript, attributable, and stoppable.
62
+ *
63
+ * The guards are the whole reason this is safe, so no renderer may drop them: adopt what is already
64
+ * listening, read the command rather than invent one, never kill, background it and SAY so, and leave
65
+ * the permission prompt to the host.
66
+ *
67
+ * Exported because slash-command.ts renders the same rule; there must not be two versions of it.
68
+ */
69
+ export declare const DEV_SERVER_POLICY = "**If no dev server is listening, start one yourself.** Read the project's own dev script out of `package.json` (`dev`, `start`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.\n\nFive guards, none optional:\n\n1. **Never start a second one.** If something is already listening on the app's port, use it.\n2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.\n3. **Never kill anything.** Not a dev server, not a daemon, not a port holder \u2014 including one you started.\n4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.\n5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.";
70
+ /** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
71
+ export declare function reticleMdFile(): string;
36
72
  export declare const AgentRuleStatus: {
37
73
  readonly APPLY: "apply";
38
74
  readonly ALREADY: "already";
@@ -22,6 +22,20 @@ import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
22
22
  /** Project-root instruction files the agent re-reads every session. */
23
23
  export const CLAUDE_MD_PATH = 'CLAUDE.md';
24
24
  export const AGENTS_MD_PATH = 'AGENTS.md';
25
+ /**
26
+ * The full rules, written once and pointed at from every always-loaded file.
27
+ *
28
+ * Two problems it solves at the same time. The rule text is dense and most of it is REFERENCE (what
29
+ * to do when the tools are missing, what a `version_skew` field means, how to file feedback), which
30
+ * an agent needs on the turn it hits that situation and never before — so carrying all of it in
31
+ * `CLAUDE.md` taxed every session for text almost every session did not read. And `AGENTS.md` was
32
+ * only written when NO agent was detected, so a repo set up on a Claude Code machine told Codex,
33
+ * Copilot, Amp and every other agent nothing at all.
34
+ *
35
+ * So: the operative rules (when to verify, WHEN NOT TO, how, and the honesty guards) go in the files
36
+ * that load every turn, and this file holds the rest, one hop away and named in that block.
37
+ */
38
+ export const RETICLE_MD_PATH = 'RETICLE.md';
25
39
  /** Cursor project rule; the frontmatter `alwaysApply` keeps it in context on every turn. */
26
40
  export const CURSOR_RULE_PATH = '.cursor/rules/reticle.mdc';
27
41
  /** Markers delimiting the managed block so a re-run is idempotent and the block stays updatable. */
@@ -38,36 +52,132 @@ const RULE_END = '<!-- reticle:end -->';
38
52
  * the package's own name so the two can never drift apart.
39
53
  */
40
54
  export const CLI = `${NPX} ${RETICLE_NPM_PACKAGE}`;
41
- /** The rule body (markdown). States WHEN to verify, HOW, and never-weaken-a-check. */
55
+ /**
56
+ * Who starts the dev server, stated once and rendered everywhere it is needed.
57
+ *
58
+ * The old rule was "Reticle never starts the dev server for you — that's your job", and agents obeyed
59
+ * it exactly: they read it, declined to start anything, and ended the turn with nothing verified. An
60
+ * instruction that reliably produces no verification is a defect in the instruction.
61
+ *
62
+ * The AGENT starts it, not the daemon. A daemon that spawns a build process is invisible to the
63
+ * person whose machine it is running on, was never consented to, and orphans when the daemon exits.
64
+ * The agent already has shell access in this repo, already runs install and build commands, and runs
65
+ * inside a host that asks the human for permission — so a dev server it starts is visible in the
66
+ * transcript, attributable, and stoppable.
67
+ *
68
+ * The guards are the whole reason this is safe, so no renderer may drop them: adopt what is already
69
+ * listening, read the command rather than invent one, never kill, background it and SAY so, and leave
70
+ * the permission prompt to the host.
71
+ *
72
+ * Exported because slash-command.ts renders the same rule; there must not be two versions of it.
73
+ */
74
+ export const DEV_SERVER_POLICY = `**If no dev server is listening, start one yourself.** Read the project's own dev script out of \`package.json\` (\`dev\`, \`start\`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.
75
+
76
+ Five guards, none optional:
77
+
78
+ 1. **Never start a second one.** If something is already listening on the app's port, use it.
79
+ 2. **Never guess the command.** It comes from \`package.json\` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.
80
+ 3. **Never kill anything.** Not a dev server, not a daemon, not a port holder — including one you started.
81
+ 4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.
82
+ 5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.`;
83
+ /**
84
+ * The operative rules, carried by every file that loads on every turn.
85
+ *
86
+ * Short on purpose. This is the text that has to CHANGE BEHAVIOUR, so it states when to verify, when
87
+ * NOT to, how, and the guards against calling something proven when it is not. Everything an agent
88
+ * needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
89
+ */
42
90
  const RULE_BODY = `## Verifying with Reticle
43
91
 
44
- This app is instrumented by **Reticle** — an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (run it through npx — Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
92
+ This app is instrumented by **Reticle**, an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
93
+
94
+ **Verify when you have changed something a user can see or do.** A component, a form, a route, a request, a piece of state that reaches the screen. Do it BEFORE telling the user it is complete. Reading the diff proves nothing and unit tests do not run the app.
95
+
96
+ **Do not reach for Reticle when the change cannot show up in the running app.** It costs tool calls and the user's patience, and a verdict over an unrelated flow proves nothing about what you changed. Skip it for: documentation, comments, tests, build config, CI, dependency bumps with no user-facing effect, backend or CLI work with no UI surface, and any change to a project that is not a running web app. Say in one line that you skipped verification and why, rather than silently not doing it.
97
+
98
+ **How to verify:**
99
+
100
+ - Drive the flow with \`reticle_act_and_wait({ ref, action, until })\`. It names the consequence you expect BEFORE the action, which is the difference between a check and a rationalisation.
101
+ - Batch a multi-step journey (a login, a form) into one \`reticle_act_sequence\` rather than one round trip per field.
102
+ - Read the surrounding evidence with \`reticle_snapshot\`, \`reticle_state\`, \`reticle_network\`, \`reticle_console\`.
103
+ - **Only \`reticle_act_and_wait\` and \`reticle_assert\` produce a verdict.** \`reticle_act\` and everything else move or read the app and prove nothing, so a session ending without one of those two has no result however many tools it used.
104
+ - Covered flows: \`${CLI} gate\` reports which recorded flows the changed files affect and whether they still pass.
105
+
106
+ **Nothing connected? Get the app running.**
107
+
108
+ ${DEV_SERVER_POLICY}
109
+
110
+ A dev server that is already running does not pick up an edited build config or a newly created plugin file — restart it and hard-reload the tab. And if a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.
111
+
112
+ **Honesty, which is the whole point:**
45
113
 
46
- - **When:** after you build or change any user-facing feature, flow, or UI, verify it with Reticle **before** telling the user it is complete. Reading the code or passing unit tests is not enough — drive the real running app.
47
- - **How:** drive the flow with \`reticle_act_and_wait({ ref, action, until })\` — it names the consequence you expect BEFORE the action, which is the difference between a check and a rationalisation. Batch a multi-step flow (a login, a form) into one \`reticle_act_sequence\` rather than one round trip per field. Read the surrounding evidence with \`reticle_snapshot\`, \`reticle_state\`, \`reticle_network\`, \`reticle_console\`.
48
- - **Only \`reticle_act_and_wait\` and \`reticle_assert\` produce a verdict.** \`reticle_act\` and everything else move or read the app and prove nothing, so a session that ends without one of those two has no result however many tools it used. In practice \`reticle_act\` is reached for far more often and proves nothing; \`act_and_wait\` is where the defects actually get caught.
49
- - **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which of eleven clauses decided that and what to do about it. Report it as unknown — never as working.
50
- - **If a tool result carries \`verify_next\`, do what it says next.** It means you have driven this page several times without asking for a verdict, so nothing you have done so far is proven.
51
- - **Covered flows:** run \`${CLI} gate\` — it reports which recorded flows the changed files affect and whether they still pass.
52
- - **Never weaken a check to make it green.** Downgrading, skipping, or deleting an assertion is a finding, not a fix.
53
- - **If Reticle can't run** (no daemon, or this is not a running web app), say so — do not skip verification silently.
54
- - **If the \`reticle_*\` tools are missing entirely, they have not been loaded yet — not broken.** \`init\` registers the MCP server, but a client reads its tool list at startup and does not re-read it. Ask the human to reload their MCP tools (\`/mcp\` in Claude Code, or reload the window in Cursor/VS Code), wait, then carry on. \`npx ${RETICLE_NPM_PACKAGE} status\` works meanwhile and says whether the daemon is up and whether the app has connected.
55
- - **Setup is not finished until one real flow has been driven and produced a verdict.** \`init\` exiting 0 means files were written; the tools appearing means your client can reach a daemon; a listed session means the app dialled in. None of those is evidence that anything works. If you are blocked before a verdict, say which step and what you tried — never report a clean install you did not verify.
56
- - **If a tool result carries \`version_skew\`, act on it before trusting anything else.** Two pieces of Reticle are on different wire contracts, so tools can misbehave in ways neither side reports — a green here is not evidence. The field says which pair disagrees and names the exact fix: run \`${CLI} stop\` and retry when the DAEMON is stale, or tell the human to install the matching SDK and restart their dev server when the PAGE is. Do that first, then redo whatever you were verifying.
57
- - **If a tool result carries \`update_available\`, say so.** Reticle checked npm and a newer version exists. Tell the human the version numbers and offer to run \`${CLI} update\` — between tasks, never mid-verification, because it restarts the daemon and drops the session. Finish what you are doing first.
114
+ - **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which clause decided that. Report it as unknown, never as working.
115
+ - **Never weaken a check to make it green.** Downgrading, skipping or deleting an assertion is a finding, not a fix.
116
+ - **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.
117
+ - **Setup is not finished until one real flow has been driven and produced a verdict.** \`init\` exiting 0, the tools appearing, and a session being listed are all things that happen before anything has been verified.
58
118
 
59
- ## Sending feedback to the Reticle team (first-class)
119
+ **The \`/reticle\` skill runs this whole loop for you** — detect, connect, drive one flow, report. If your client does not have it, install it once: \`/plugin marketplace add reticlehq/reticle\` then \`/plugin install reticle@reticlehq\` in Claude Code, or \`npx skills add reticlehq/reticle\` anywhere the skills CLI works.
60
120
 
61
- **Reporting your experience with Reticle is part of using Reticle, from the moment you start wiring it in — not something to do at the end if anything went wrong.** Reticle is built FOR agents, which makes you its actual user and the only one who can see what it costs you. That knowledge is gone when your context is, so it has to leave the turn it happened in.
121
+ **Report Reticle's own defects with \`reticle_feedback\` the moment you notice**, then carry on with your task. You are the user Reticle is built for and the only one who can say what it cost you, and that knowledge is gone when your context is.
62
122
 
63
- - **Send it with \`reticle_feedback\`, the moment you notice.** One call, then carry straight on with your task. Do not batch reports, do not wait until the task is done, and do not decide something is too minor — the small awkward things are what nobody ever reports, so they never get fixed.
64
- - **Everything counts, in both directions.** A tool that returned something wrong (\`bug\`), something Reticle could not observe at all (\`gap\`), a verdict you could not act on (\`ambiguity\`), something you wished existed (\`feature_request\`), something that works but costs too many calls or has an awkward shape (\`improvement\`).
65
- - **Setup and installation count too, and are the easiest to lose.** Anything that went wrong while installing, wiring, or starting Reticle — a step in the docs that did not match reality, a plugin that had to be added by hand, a daemon that would not start, an OS-specific detour. You hit these before any verification runs, which is exactly why they usually go unreported.
123
+ 📄 **The rest is in [RETICLE.md](./${RETICLE_MD_PATH}): what to do when the tools are missing, when a result carries \`version_skew\` or \`update_available\`, when \`reticle_state\` comes back empty, and how to write a feedback report that can be acted on. Read it when you hit one of those, not before.**`;
124
+ /**
125
+ * The reference half. Fetched when a situation calls for it rather than carried every turn.
126
+ *
127
+ * Every section here answers a question an agent only has once it is already stuck, and each one was
128
+ * written because the absence of it cost somebody a session: tools that were never loaded read as a
129
+ * broken install, a `version_skew` field went unread while its green verdicts were trusted, an empty
130
+ * `reticle_state` was reported as a clean setup, and a defect in Reticle was worked around in silence.
131
+ */
132
+ const RETICLE_MD_BODY = `# Reticle: the full rules
133
+
134
+ The short version lives in your agent instruction file (\`CLAUDE.md\` / \`AGENTS.md\` / \`.cursor/rules/reticle.mdc\`) and loads every turn. This file is the reference: read the section that matches the situation you are in.
135
+
136
+ ## The tools are missing entirely
137
+
138
+ They have not been loaded yet, which is not the same as broken. \`init\` registers the MCP server, but a client reads its server list at startup and does not re-read it, and no slash command reloads it (\`/mcp\` only manages servers already loaded).
139
+
140
+ Ask the human to RESTART their client: restart Claude Code, reload the window in Cursor, or hit Start in \`.vscode/mcp.json\` in VS Code. Then wait and carry on. This is once per machine, because Reticle is registered globally and later projects already have the tools. \`${CLI} status\` works meanwhile and says whether the daemon is up and whether the app has connected.
141
+
142
+ ## A result carries \`verify_next\`
143
+
144
+ You have driven this page several times without asking for a verdict, so nothing you have done so far is proven. Do what the field says next.
145
+
146
+ ## A result carries \`version_skew\`
147
+
148
+ Act on it before trusting anything else. Two pieces of Reticle are on different wire contracts, so tools can misbehave in ways neither side reports, and a green is not evidence. The field names the pair that disagrees and the exact fix: run \`${CLI} stop\` and retry when the DAEMON is stale, or tell the human to install the matching SDK and restart their dev server when the PAGE is. Then redo whatever you were verifying.
149
+
150
+ ## A result carries \`update_available\`
151
+
152
+ Reticle checked npm and a newer version exists. Tell the human the version numbers and offer to run \`${CLI} update\`, between tasks and never mid-verification, because it restarts the daemon and drops the session. Finish what you are doing first.
153
+
154
+ ## \`reticle_state\` comes back empty
155
+
156
+ The generated \`reticle-dev\` file registers nothing. Say so. \`init\` registers every store it could prove from your source; the ones needing an argument only reading the code supplies (Jotai atoms, an XState actor, a TanStack \`queryClient\`) are left commented, and a file that registers nothing is indistinguishable from a working one until you read the state back.
157
+
158
+ Finish that file when you see it. It is what lets you check what the app BELIEVES, not only what it rendered, which is the class of bug a screenshot cannot show. Never report a clean setup over an empty state read. It blocks nothing else: DOM, network, console and source pointers all work without it.
159
+
160
+ ## Sending feedback to the Reticle team
161
+
162
+ **Reporting your experience is part of using Reticle, from the moment you start wiring it in.** Reticle is built FOR agents, which makes you its actual user and the only one who can see what it costs you.
163
+
164
+ - **Send it with \`reticle_feedback\` the moment you notice.** One call, then carry straight on. Do not batch reports, do not wait until the task is done, and do not decide something is too minor. The small awkward things are what nobody ever reports, so they never get fixed.
165
+ - **Everything counts, in both directions.** A tool that returned something wrong (\`bug\`), something Reticle could not observe at all (\`gap\`), a verdict you could not act on (\`ambiguity\`), something you wished existed (\`feature_request\`), something that works but costs too many calls (\`improvement\`).
166
+ - **Setup and installation count too, and are the easiest to lose.** Anything that went wrong while installing, wiring, or starting Reticle: a step in the docs that did not match reality, a plugin that had to be added by hand, a daemon that would not start, an OS-specific detour. You hit these before any verification runs, which is exactly why they go unreported.
66
167
  - **When the tools are not reachable yet**, file the same report from the shell: \`${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened"\`. It needs no daemon and no working install, so a Reticle that broke before it started can still be reported.
67
- - **Write it so it can be acted on.** For a failure: what you called, what you expected, what you got, and the call trail in \`trace\`. For a request: the GOAL in \`need\` (not your guess at the solution), what improves in \`impact\`, and how you work around it today in \`currentApproach\` — that workaround is usually the most useful line in the report. Set \`model\` to the model you are running.
168
+ - **Write it so it can be acted on.** For a failure: what you called, what you expected, what you got, and the call trail in \`trace\`. For a request: the GOAL in \`need\` (not your guess at the solution), what improves in \`impact\`, and how you work around it today in \`currentApproach\`. That workaround is usually the most useful line in the report. Set \`model\` to the model you are running.
68
169
  - **Working around a Reticle defect in silence is the one thing not to do.** A silent workaround is how the same defect survives to the next user.
69
- - **Report defects in *Reticle*, not in the app under test.** A bug you find in this app is Reticle working as intended; that belongs in your report to the user.
70
- - **Never include app source, secrets, user data, or customer records** in a report.`;
170
+ - **Report defects in *Reticle*, not in the app under test.** A bug you find in this app is Reticle working as intended; that one belongs in your report to the user.
171
+ - **Never include app source, secrets, user data, or customer records** in a report.
172
+
173
+ ## More
174
+
175
+ The docs are built to be fetched rather than browsed: \`https://docs.reticle.sh/llms.txt\` lists every page, and appending \`.md\` to any page URL returns its source with no site chrome.
176
+ `;
177
+ /** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
178
+ export function reticleMdFile() {
179
+ return RETICLE_MD_BODY;
180
+ }
71
181
  export const AgentRuleStatus = {
72
182
  APPLY: 'apply',
73
183
  ALREADY: 'already',
@@ -12,5 +12,6 @@
12
12
  * Half-editing a build config is worse than a documented manual step.
13
13
  */
14
14
  import { type SourcePatch } from './patch-kind.js';
15
+ import { UiLibrary } from './detect.js';
15
16
  export declare function patchAstroConfig(source: string): SourcePatch;
16
- export declare function patchAstroLayout(source: string, port: number | undefined, projectId: string | undefined): SourcePatch;
17
+ export declare function patchAstroLayout(source: string, port: number | undefined, projectId: string | undefined, uiLibrary?: UiLibrary): SourcePatch;
@@ -11,8 +11,10 @@
11
11
  * Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
12
12
  * Half-editing a build config is worse than a documented manual step.
13
13
  */
14
- import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
14
+ import { RETICLE_DEFAULT_PORT, ReticleDir, bridgeWsUrl } from '@reticlehq/core';
15
15
  import { PatchKind } from './patch-kind.js';
16
+ import { UiLibrary } from './detect.js';
17
+ import { sdkImport } from './snippets.js';
16
18
  /** Present in a patched config AND in a hand-followed recipe — so both count as already wired. */
17
19
  const CONFIG_MARKER = '__RETICLE_TOKEN__';
18
20
  /** Present in a patched layout and in the printed recipe's script. */
@@ -29,6 +31,26 @@ const LAYOUT_MARKER = 'reticle.connect';
29
31
  * never got it.
30
32
  */
31
33
  const SDK_INCLUDE_LITERAL = `'@reticlehq/react'`;
34
+ /**
35
+ * The daemon's journal directory, kept out of the dev server's watcher.
36
+ *
37
+ * The daemon writes `.reticle/` into the project root and rewrites `ambient.json` atomically
38
+ * (`.tmp` + rename) for as long as a session is live. Astro dev is a Vite dev server watching that
39
+ * root, and `.reticle/` is not in Vite's default ignore list — so every journal write read as a
40
+ * project file changing and Vite answered with a full page reload. That closes a loop with no exit:
41
+ * the page loads, the SDK connects and streams events, the daemon journals them, Vite reloads the
42
+ * page, the SDK reconnects. It ran several times a second for as long as the dev server was up,
43
+ * and the symptom looked nothing like the cause — stale refs, actions dying mid-flight, and a log
44
+ * full of connect/disconnect pairs that read as a flapping SDK.
45
+ *
46
+ * The Vite plugin sets the same ignore in its `config` hook. Astro is hand-patched and never loads
47
+ * that plugin, so it needs its own.
48
+ *
49
+ * A RegExp, not a glob. chokidar dropped glob support in v4 and Vite 7+ ships v4/v5, where a
50
+ * double-star pattern is accepted and matches nothing — the ignore would be visibly present in the
51
+ * config and do nothing at all. Measured against the chokidar this repo resolves.
52
+ */
53
+ const WATCH_IGNORE_LITERAL = `/(^|[\\\\/])\\.${ReticleDir.ROOT.slice(1)}([\\\\/]|$)/`;
32
54
  /**
33
55
  * An `include:` the app already declared — ours joins that array instead of adding a second key.
34
56
  * Deliberately not anchored to a newline: `optimizeDeps: { include: ['x'] }` on one line is the
@@ -70,12 +92,17 @@ const VITE_KEYS = [
70
92
  key: 'define',
71
93
  inner: `\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),`,
72
94
  },
95
+ // Merged in first, so a `server: { port }` the app already set keeps its port. An app that
96
+ // already sets `server.watch` itself is the one shape this loses to — the inner `watch` key would
97
+ // be duplicated and the app's would win — which is the same nested-merge ceiling `build` has.
98
+ { key: 'server', inner: `\n watch: { ignored: [${WATCH_IGNORE_LITERAL}] },` },
73
99
  ];
74
100
  /** Whole-key form, for a `vite:` block that does not have the key at all. */
75
101
  const WHOLE = {
76
102
  build: `\n build: { target: 'es2022' },`,
77
103
  optimizeDeps: `\n optimizeDeps: { include: [${SDK_INCLUDE_LITERAL}], esbuildOptions: { target: 'es2022' } },`,
78
104
  define: `\n define: {\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),\n },`,
105
+ server: `\n server: { watch: { ignored: [${WATCH_IGNORE_LITERAL}] } },`,
79
106
  };
80
107
  /**
81
108
  * Add our keys to an existing `vite: { ... }` block, merging into any the app already set.
@@ -155,6 +182,7 @@ const VITE_BLOCK = ` vite: {
155
182
  __RETICLE_TOKEN__: JSON.stringify(reticleToken()),
156
183
  __RETICLE_ROOT__: JSON.stringify(process.cwd()),
157
184
  },
185
+ server: { watch: { ignored: [${WATCH_IGNORE_LITERAL}] } },
158
186
  },
159
187
  `;
160
188
  export function patchAstroConfig(source) {
@@ -204,7 +232,11 @@ export function patchAstroConfig(source) {
204
232
  };
205
233
  }
206
234
  /** The dev-only connect that goes inside the layout's `<body>`. */
207
- function astroConnectScript(port, projectId) {
235
+ function astroConnectScript(port, projectId, uiLibrary) {
236
+ // Must match what `frameworkPackages` installed. Astro hosts islands from any framework, so an
237
+ // Astro app whose islands are Vue or Svelte gets the sensor, and a script importing the React
238
+ // adapter would load a package that is not in node_modules. See sdkImport.
239
+ const sdk = sdkImport(uiLibrary);
208
240
  const url = port !== undefined && port !== RETICLE_DEFAULT_PORT
209
241
  ? `\n url: '${bridgeWsUrl(port)}',`
210
242
  : '';
@@ -213,8 +245,8 @@ function astroConnectScript(port, projectId) {
213
245
  if (import.meta.env.DEV) {
214
246
  const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
215
247
  const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
216
- const { reticle, install } = await import('@reticlehq/react');
217
- install();
248
+ const { reticle${sdk.usesInstall ? ', install' : ''} } = await import('${sdk.specifier}');
249
+ ${sdk.usesInstall ? 'install();' : '// The sensor has no install(); that is the React adapter.'}
218
250
  reticle.connect({${id}${url}
219
251
  ...(token.length > 0 ? { token } : {}),
220
252
  ...(root.length > 0 ? { root } : {}),
@@ -223,7 +255,7 @@ function astroConnectScript(port, projectId) {
223
255
  </script>
224
256
  `;
225
257
  }
226
- export function patchAstroLayout(source, port, projectId) {
258
+ export function patchAstroLayout(source, port, projectId, uiLibrary = UiLibrary.REACT) {
227
259
  if (source.includes(LAYOUT_MARKER))
228
260
  return { kind: PatchKind.ALREADY };
229
261
  const at = source.lastIndexOf(BODY_CLOSE);
@@ -235,6 +267,6 @@ export function patchAstroLayout(source, port, projectId) {
235
267
  }
236
268
  return {
237
269
  kind: PatchKind.APPLY,
238
- code: `${source.slice(0, at)}${astroConnectScript(port, projectId)}${source.slice(at)}`,
270
+ code: `${source.slice(0, at)}${astroConnectScript(port, projectId, uiLibrary)}${source.slice(at)}`,
239
271
  };
240
272
  }
@@ -15,3 +15,25 @@ export declare const MAX_TESTIDS = 60;
15
15
  export declare function scanTestids(sources: readonly string[]): string[];
16
16
  /** The store libraries this app depends on, as ready-to-uncomment registration lines. */
17
17
  export declare function storeHints(deps: ReadonlySet<string>): string[];
18
+ /** A store instance we found in the app's own source, with everything needed to import and register it. */
19
+ export interface FoundStore {
20
+ /** The name it registers under — what `reticle_state` keys its output by. */
21
+ key: string;
22
+ /** The exported identifier. */
23
+ ident: string;
24
+ /** Module specifier relative to `src/reticle-dev.ts`. */
25
+ importPath: string;
26
+ }
27
+ /** Where the generated dev module lives, for Vite. Next passes its own. */
28
+ export declare const DEFAULT_DEV_MODULE_DIR = "src";
29
+ /**
30
+ * Store instances declared in the app's own source, ready to import and register.
31
+ *
32
+ * Only exported declarations of a factory the app actually depends on: anything less certain would
33
+ * generate an import that breaks the dev module everything else hangs off, and a broken module is
34
+ * worse than the commented hint it replaces.
35
+ */
36
+ export declare function scanStores(files: readonly {
37
+ path: string;
38
+ source: string;
39
+ }[], deps: ReadonlySet<string>, fromDir?: string): FoundStore[];
@@ -9,6 +9,7 @@
9
9
  * depends on zustand is easy, knowing which module exports the store instance is not, and generating
10
10
  * a wrong import would break the dev module that everything else now hangs off.
11
11
  */
12
+ import { posix } from 'node:path';
12
13
  /** `data-testid="foo"` / `data-testid='foo'` / `data-testid={"foo"}` — the forms people actually write. */
13
14
  const TESTID_ATTR = /data-testid\s*=\s*[{]?\s*["'`]([^"'`]+)["'`]/g;
14
15
  /** Cap the generated list. A capabilities block is a hint for an agent, not an inventory. */
@@ -52,3 +53,78 @@ const STORE_LIBRARIES = [
52
53
  export function storeHints(deps) {
53
54
  return STORE_LIBRARIES.filter(([dep]) => deps.has(dep)).map(([, hint]) => hint);
54
55
  }
56
+ /**
57
+ * Store factories whose EXPORTED result is the store instance itself — the form `registerStore`
58
+ * takes directly. Each is gated on the app actually depending on that library, so a local helper
59
+ * called `create` in an app with no store library is never mistaken for one.
60
+ *
61
+ * Deliberately short. The adapter-wrapped libraries (Jotai, XState, Valtio, MobX, TanStack Query)
62
+ * need an argument we cannot infer — which atoms, which actor, which queryClient — so they keep the
63
+ * commented hint. Guessing there produces a module that throws on import, which is strictly worse
64
+ * than a comment.
65
+ */
66
+ const STORE_FACTORIES = [
67
+ ['zustand', 'create'],
68
+ ['@reduxjs/toolkit', 'configureStore'],
69
+ ['redux', 'createStore'],
70
+ ];
71
+ /** `export const useCart = create<S>()(...)` / `export const store = configureStore({...})`. */
72
+ function storeDeclaration(factory) {
73
+ return new RegExp(`export\\s+const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${factory}\\s*[<(]`, 'g');
74
+ }
75
+ /**
76
+ * `src/cart-store.ts` → `cart`; `src/store.ts` → `app`. The key is what the agent reads state under,
77
+ * so it has to be stable and distinct — two stores sharing one key would silently overwrite.
78
+ */
79
+ function storeKey(path, taken) {
80
+ const file = (path.split('/').pop() ?? '').replace(/\.[^.]+$/, '');
81
+ const base = file.replace(/[-_.]?stores?$/i, '').replace(/[^a-z0-9]+/gi, '-') || 'app';
82
+ const key = base.toLowerCase();
83
+ if (!taken.has(key))
84
+ return key;
85
+ for (let n = 2;; n += 1) {
86
+ const candidate = `${key}-${String(n)}`;
87
+ if (!taken.has(candidate))
88
+ return candidate;
89
+ }
90
+ }
91
+ /**
92
+ * Module specifier for `store.ts` as seen FROM the generated dev module.
93
+ *
94
+ * The dev module is not always a sibling: Vite puts it at `src/reticle-dev.ts`, Next at
95
+ * `app/reticle-dev.tsx` or `src/app/reticle-dev.tsx`. A specifier computed for one is dead in the
96
+ * other, and a dead import in this file breaks the module every other capability hangs off — so the
97
+ * relative path is computed, not assumed.
98
+ */
99
+ function importPathFor(storePath, fromDir) {
100
+ const noExt = storePath.replace(/\.[^./]+$/, '');
101
+ const rel = posix.relative(fromDir, noExt);
102
+ return rel.startsWith('.') ? rel : `./${rel}`;
103
+ }
104
+ /** Where the generated dev module lives, for Vite. Next passes its own. */
105
+ export const DEFAULT_DEV_MODULE_DIR = 'src';
106
+ /**
107
+ * Store instances declared in the app's own source, ready to import and register.
108
+ *
109
+ * Only exported declarations of a factory the app actually depends on: anything less certain would
110
+ * generate an import that breaks the dev module everything else hangs off, and a broken module is
111
+ * worse than the commented hint it replaces.
112
+ */
113
+ export function scanStores(files, deps, fromDir = DEFAULT_DEV_MODULE_DIR) {
114
+ const factories = STORE_FACTORIES.filter(([dep]) => deps.has(dep)).map(([, f]) => f);
115
+ const found = [];
116
+ const keys = new Set();
117
+ for (const { path, source } of files) {
118
+ for (const factory of factories) {
119
+ for (const m of source.matchAll(storeDeclaration(factory))) {
120
+ const ident = m[1];
121
+ if (ident === undefined)
122
+ continue;
123
+ const key = storeKey(path, keys);
124
+ keys.add(key);
125
+ found.push({ key, ident, importPath: importPathFor(path, fromDir) });
126
+ }
127
+ }
128
+ }
129
+ return found;
130
+ }