@reticlehq/server 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +63 -13
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +174 -92
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.js +24 -9
  88. package/dist/flows/empty-flow.d.ts +6 -0
  89. package/dist/flows/empty-flow.js +24 -0
  90. package/dist/flows/flake-store.d.ts +0 -1
  91. package/dist/flows/flake-store.js +1 -1
  92. package/dist/flows/flake.d.ts +2 -1
  93. package/dist/flows/flake.js +2 -2
  94. package/dist/flows/flow-classify.js +12 -0
  95. package/dist/flows/flow-replay-run.js +4 -4
  96. package/dist/flows/flow-replay.d.ts +5 -7
  97. package/dist/flows/flow-replay.js +114 -47
  98. package/dist/flows/flow-report.js +1 -1
  99. package/dist/flows/flow-scope.d.ts +1 -6
  100. package/dist/flows/flow-step-runners.d.ts +26 -6
  101. package/dist/flows/flow-step-runners.js +157 -6
  102. package/dist/flows/flow-success.js +20 -15
  103. package/dist/flows/flow-tools.js +53 -7
  104. package/dist/flows/flows.js +15 -11
  105. package/dist/flows/gate.d.ts +4 -3
  106. package/dist/flows/gate.js +1 -1
  107. package/dist/flows/heal-run.d.ts +0 -8
  108. package/dist/flows/heal-run.js +5 -5
  109. package/dist/flows/heal.js +3 -3
  110. package/dist/flows/predicate-to-expect.d.ts +38 -0
  111. package/dist/flows/predicate-to-expect.js +95 -0
  112. package/dist/flows/replay.d.ts +16 -1
  113. package/dist/flows/replay.js +58 -22
  114. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  115. package/dist/flows/role-anchor-nearest.js +45 -0
  116. package/dist/flows/server-verify.d.ts +1 -1
  117. package/dist/flows/server-verify.js +5 -5
  118. package/dist/flows/verify-change-tools.js +32 -10
  119. package/dist/flows/verify-change-verdict.d.ts +15 -0
  120. package/dist/flows/verify-change-verdict.js +25 -0
  121. package/dist/flows/watch-batcher.d.ts +3 -2
  122. package/dist/honesty/already-true.d.ts +2 -0
  123. package/dist/honesty/already-true.js +31 -0
  124. package/dist/honesty/blind-spots.d.ts +16 -3
  125. package/dist/honesty/blind-spots.js +24 -12
  126. package/dist/honesty/honesty.d.ts +3 -2
  127. package/dist/honesty/honesty.js +4 -4
  128. package/dist/honesty/uncaptured-bodies.js +1 -1
  129. package/dist/honesty/unread-outcome.js +2 -2
  130. package/dist/honesty/verified.d.ts +22 -3
  131. package/dist/honesty/verified.js +48 -8
  132. package/dist/http-server.js +14 -7
  133. package/dist/hunt/hunt-report.d.ts +2 -1
  134. package/dist/hunt/hunt-report.js +2 -2
  135. package/dist/index.d.ts +8 -6
  136. package/dist/index.js +32 -25
  137. package/dist/init/agent-rules.d.ts +2 -1
  138. package/dist/init/agent-rules.js +45 -7
  139. package/dist/init/app-choice.d.ts +20 -0
  140. package/dist/init/app-choice.js +25 -0
  141. package/dist/init/astro-host.d.ts +21 -0
  142. package/dist/init/astro-host.js +27 -0
  143. package/dist/init/astro-patch.d.ts +16 -0
  144. package/dist/init/astro-patch.js +240 -0
  145. package/dist/init/capabilities.js +4 -1
  146. package/dist/init/cra.d.ts +37 -0
  147. package/dist/init/cra.js +90 -0
  148. package/dist/init/cursor.js +39 -4
  149. package/dist/init/desktop-doctor.d.ts +3 -2
  150. package/dist/init/desktop-doctor.js +4 -4
  151. package/dist/init/detect.d.ts +3 -2
  152. package/dist/init/detect.js +16 -5
  153. package/dist/init/mcp-registered.d.ts +12 -0
  154. package/dist/init/mcp-registered.js +14 -0
  155. package/dist/init/mcp.d.ts +27 -2
  156. package/dist/init/mcp.js +40 -4
  157. package/dist/init/next-patch.d.ts +2 -4
  158. package/dist/init/next-patch.js +109 -19
  159. package/dist/init/node-io.js +23 -6
  160. package/dist/init/plan-framework.d.ts +16 -1
  161. package/dist/init/plan-framework.js +132 -8
  162. package/dist/init/plan.d.ts +73 -8
  163. package/dist/init/plan.js +133 -34
  164. package/dist/init/project-id.d.ts +5 -9
  165. package/dist/init/project-id.js +10 -22
  166. package/dist/init/run.d.ts +5 -0
  167. package/dist/init/run.js +210 -36
  168. package/dist/init/slash-command.d.ts +9 -0
  169. package/dist/init/slash-command.js +10 -1
  170. package/dist/init/snippets.d.ts +7 -10
  171. package/dist/init/snippets.js +44 -15
  172. package/dist/init/vite-config.js +1 -1
  173. package/dist/init/workspace-apps.d.ts +23 -0
  174. package/dist/init/workspace-apps.js +68 -0
  175. package/dist/input/driven-redaction.js +1 -1
  176. package/dist/input/network-detail.js +8 -8
  177. package/dist/input/network-mock-tools.js +2 -2
  178. package/dist/input/network-mock.js +4 -4
  179. package/dist/input/real-input.js +8 -3
  180. package/dist/input/scroll-find.js +2 -2
  181. package/dist/input/scroll-tools.d.ts +0 -5
  182. package/dist/input/scroll-tools.js +11 -4
  183. package/dist/input/viewport-tools.js +2 -2
  184. package/dist/journal/ambient-store.d.ts +0 -2
  185. package/dist/journal/ambient-store.js +1 -1
  186. package/dist/journal/ambient.js +1 -1
  187. package/dist/journal/attach-journal.d.ts +2 -1
  188. package/dist/journal/deviation-report.js +6 -6
  189. package/dist/journal/deviation-service.js +1 -1
  190. package/dist/journal/envelope-store.d.ts +0 -2
  191. package/dist/journal/envelope-store.js +1 -1
  192. package/dist/journal/envelope.d.ts +2 -3
  193. package/dist/journal/envelope.js +3 -3
  194. package/dist/journal/journal-query.js +1 -1
  195. package/dist/journal/journal-recorder.d.ts +2 -1
  196. package/dist/journal/journal-recorder.js +2 -2
  197. package/dist/journal/rollups.js +9 -9
  198. package/dist/journal/session-end.d.ts +2 -1
  199. package/dist/journal/session-journal.js +4 -4
  200. package/dist/license/license.d.ts +0 -14
  201. package/dist/license/license.js +8 -8
  202. package/dist/log.d.ts +10 -0
  203. package/dist/log.js +11 -1
  204. package/dist/mcp/mcp-is-error.d.ts +13 -0
  205. package/dist/mcp/mcp-is-error.js +18 -0
  206. package/dist/mcp/mcp-outage.d.ts +31 -0
  207. package/dist/mcp/mcp-outage.js +48 -0
  208. package/dist/mcp/mcp-proxy.d.ts +124 -0
  209. package/dist/mcp/mcp-proxy.js +697 -0
  210. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  211. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  212. package/dist/mcp/proxy-handshake.d.ts +3 -0
  213. package/dist/mcp/proxy-handshake.js +54 -0
  214. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  215. package/dist/mcp/proxy-lifecycle.js +70 -0
  216. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  217. package/dist/mcp/tool-catalog-cache.js +55 -0
  218. package/dist/oracles/propose-consequences.d.ts +2 -1
  219. package/dist/oracles/propose-consequences.js +16 -11
  220. package/dist/oracles/self-instrument.d.ts +2 -1
  221. package/dist/oracles/self-instrument.js +1 -1
  222. package/dist/phenomena/phenomena.js +3 -3
  223. package/dist/platform.d.ts +16 -0
  224. package/dist/platform.js +16 -0
  225. package/dist/pool/playwright-launcher.d.ts +15 -2
  226. package/dist/pool/playwright-launcher.js +21 -2
  227. package/dist/project/fs-port.js +1 -1
  228. package/dist/project/project-tools.js +2 -2
  229. package/dist/runs/build-verification-run.js +1 -1
  230. package/dist/runs/repair-prompt.d.ts +0 -6
  231. package/dist/runs/repair-prompt.js +3 -3
  232. package/dist/runs/risk-classify.js +1 -1
  233. package/dist/runs/run-diff.d.ts +0 -2
  234. package/dist/runs/run-diff.js +3 -3
  235. package/dist/runs/run-tools.js +2 -2
  236. package/dist/runs/verification-sync.js +2 -2
  237. package/dist/runs/verify-http.js +1 -1
  238. package/dist/runs/verify-server.js +1 -1
  239. package/dist/session/command-timeout.d.ts +11 -3
  240. package/dist/session/command-timeout.js +2 -2
  241. package/dist/session/control-envelope.d.ts +6 -0
  242. package/dist/session/control-envelope.js +7 -1
  243. package/dist/session/dev-server-probe.d.ts +13 -0
  244. package/dist/session/dev-server-probe.js +52 -0
  245. package/dist/session/last-act.d.ts +17 -3
  246. package/dist/session/last-act.js +16 -4
  247. package/dist/session/live-control-tools.js +7 -2
  248. package/dist/session/live-control.js +1 -1
  249. package/dist/session/looks-like-dev-server.d.ts +14 -0
  250. package/dist/session/looks-like-dev-server.js +25 -0
  251. package/dist/session/no-session-diagnosis.d.ts +38 -0
  252. package/dist/session/no-session-diagnosis.js +70 -0
  253. package/dist/session/no-session-watch.d.ts +19 -0
  254. package/dist/session/no-session-watch.js +61 -0
  255. package/dist/session/observed-state.d.ts +4 -0
  256. package/dist/session/observed-state.js +19 -1
  257. package/dist/session/output-budget.js +1 -1
  258. package/dist/session/session-health.d.ts +8 -1
  259. package/dist/session/session-health.js +8 -7
  260. package/dist/session/session-info.d.ts +26 -7
  261. package/dist/session/session-info.js +42 -1
  262. package/dist/session/session-lease.d.ts +0 -11
  263. package/dist/session/session-lease.js +1 -1
  264. package/dist/session/session-manager.d.ts +3 -0
  265. package/dist/session/session-manager.js +52 -9
  266. package/dist/session/session-readiness.d.ts +1 -1
  267. package/dist/session/session-readiness.js +6 -1
  268. package/dist/session/session-reaper.js +1 -1
  269. package/dist/session/session-reconnect.d.ts +32 -0
  270. package/dist/session/session-reconnect.js +42 -0
  271. package/dist/session/session.d.ts +14 -2
  272. package/dist/session/session.js +69 -26
  273. package/dist/session/state-select.d.ts +1 -1
  274. package/dist/session/state-select.js +1 -1
  275. package/dist/status-payload.d.ts +19 -0
  276. package/dist/status-payload.js +11 -0
  277. package/dist/telemetry/argument-shape.js +10 -1
  278. package/dist/telemetry/browser-mode.d.ts +27 -0
  279. package/dist/telemetry/browser-mode.js +33 -0
  280. package/dist/telemetry/bug-found.d.ts +17 -21
  281. package/dist/telemetry/bug-found.js +120 -8
  282. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  283. package/dist/telemetry/cli-telemetry.js +27 -4
  284. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  285. package/dist/telemetry/daemon-telemetry.js +12 -2
  286. package/dist/telemetry/dev-repo.js +4 -4
  287. package/dist/telemetry/error-fingerprint.js +21 -2
  288. package/dist/telemetry/feedback-cli.d.ts +1 -1
  289. package/dist/telemetry/feedback-cli.js +4 -4
  290. package/dist/telemetry/feedback-context.d.ts +22 -0
  291. package/dist/telemetry/feedback-context.js +41 -1
  292. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  293. package/dist/telemetry/feedback-delivery.js +30 -0
  294. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  295. package/dist/telemetry/feedback-outbox.js +87 -0
  296. package/dist/telemetry/feedback.d.ts +22 -1
  297. package/dist/telemetry/feedback.js +67 -8
  298. package/dist/telemetry/git-facts.js +1 -1
  299. package/dist/telemetry/session-metrics.d.ts +2 -0
  300. package/dist/telemetry/session-metrics.js +68 -6
  301. package/dist/telemetry/telemetry.d.ts +27 -2
  302. package/dist/telemetry/telemetry.js +119 -29
  303. package/dist/telemetry/verification-of.d.ts +22 -0
  304. package/dist/telemetry/verification-of.js +84 -0
  305. package/dist/tools/act-capsule.js +2 -2
  306. package/dist/tools/act-danger.d.ts +12 -0
  307. package/dist/tools/act-danger.js +20 -3
  308. package/dist/tools/act-helpers.js +4 -4
  309. package/dist/tools/act-tools.d.ts +0 -8
  310. package/dist/tools/act-tools.js +84 -115
  311. package/dist/tools/act-view.js +7 -7
  312. package/dist/tools/alias-args.d.ts +17 -0
  313. package/dist/tools/alias-args.js +25 -0
  314. package/dist/tools/assert-grade.js +9 -9
  315. package/dist/tools/assert-verdict.d.ts +3 -1
  316. package/dist/tools/assert-verdict.js +8 -5
  317. package/dist/tools/browser-tools.js +30 -11
  318. package/dist/tools/contract-tools.js +1 -1
  319. package/dist/tools/coverage-identity.d.ts +34 -0
  320. package/dist/tools/coverage-identity.js +46 -0
  321. package/dist/tools/coverage-tools.js +10 -14
  322. package/dist/tools/dynamic-tools.d.ts +2 -1
  323. package/dist/tools/dynamic-tools.js +90 -18
  324. package/dist/tools/error-recovery.d.ts +20 -2
  325. package/dist/tools/error-recovery.js +179 -4
  326. package/dist/tools/feedback-tools.d.ts +12 -1
  327. package/dist/tools/feedback-tools.js +28 -5
  328. package/dist/tools/invoke-tool.d.ts +0 -6
  329. package/dist/tools/invoke-tool.js +57 -32
  330. package/dist/tools/lease-hint.d.ts +17 -0
  331. package/dist/tools/lease-hint.js +24 -0
  332. package/dist/tools/lease-tools.js +6 -5
  333. package/dist/tools/merge-tools.d.ts +10 -1
  334. package/dist/tools/merge-tools.js +9 -3
  335. package/dist/tools/merged-name-redirect.d.ts +13 -0
  336. package/dist/tools/merged-name-redirect.js +55 -0
  337. package/dist/tools/navigate-result.d.ts +17 -0
  338. package/dist/tools/navigate-result.js +31 -0
  339. package/dist/tools/observe-tools.js +54 -20
  340. package/dist/tools/observed-nothing.d.ts +25 -0
  341. package/dist/tools/observed-nothing.js +33 -0
  342. package/dist/tools/query-paginate.js +2 -2
  343. package/dist/tools/query-shape.d.ts +18 -0
  344. package/dist/tools/query-shape.js +39 -0
  345. package/dist/tools/read-tools.js +81 -20
  346. package/dist/tools/real-input-attempt.d.ts +28 -0
  347. package/dist/tools/real-input-attempt.js +88 -0
  348. package/dist/tools/reconcile-tools.js +7 -7
  349. package/dist/tools/reload-result.d.ts +14 -0
  350. package/dist/tools/reload-result.js +29 -0
  351. package/dist/tools/snapshot-delta.d.ts +4 -5
  352. package/dist/tools/snapshot-delta.js +0 -0
  353. package/dist/tools/tool-kit.d.ts +8 -0
  354. package/dist/tools/tool-surface.d.ts +83 -0
  355. package/dist/tools/tool-surface.js +204 -0
  356. package/dist/tools/tools-helpers.js +6 -6
  357. package/dist/tools/tools.d.ts +15 -0
  358. package/dist/tools/tools.js +53 -17
  359. package/dist/tools/unadvertised-help.d.ts +5 -0
  360. package/dist/tools/unadvertised-help.js +39 -0
  361. package/dist/trace.d.ts +69 -0
  362. package/dist/trace.js +150 -0
  363. package/dist/update/nudge-credit.d.ts +4 -0
  364. package/dist/update/nudge-credit.js +53 -0
  365. package/dist/update/sdk-sync.d.ts +21 -0
  366. package/dist/update/sdk-sync.js +42 -0
  367. package/dist/update/update-checker.js +3 -3
  368. package/dist/update/update-nudge.d.ts +63 -6
  369. package/dist/update/update-nudge.js +128 -15
  370. package/dist/update/updater.d.ts +1 -9
  371. package/dist/update/updater.js +13 -8
  372. package/dist/version/peer-announce.d.ts +4 -0
  373. package/dist/version/peer-announce.js +30 -0
  374. package/dist/{server-version.js → version/server-version.js} +5 -1
  375. package/dist/version/version-nudge.d.ts +28 -0
  376. package/dist/version/version-nudge.js +40 -0
  377. package/dist/version/version-skew.d.ts +66 -0
  378. package/dist/version/version-skew.js +71 -0
  379. package/dist/visual/visual-diff.js +2 -2
  380. package/dist/visual/visual-tools.js +4 -4
  381. package/docs/agent-cheatsheet.md +85 -0
  382. package/docs/architecture.md +113 -0
  383. package/docs/benchmarks.md +255 -0
  384. package/docs/debugging.md +102 -0
  385. package/docs/deploy-checks.md +69 -0
  386. package/docs/desktop-apps.md +256 -0
  387. package/docs/enterprise.md +62 -0
  388. package/docs/fixtures.md +28 -0
  389. package/docs/flows.md +166 -0
  390. package/docs/getting-started.md +469 -0
  391. package/docs/human-control.md +63 -0
  392. package/docs/integration-patterns.md +171 -0
  393. package/docs/local-registry.md +126 -0
  394. package/docs/mint.json +61 -0
  395. package/docs/multi-agent-testing.md +55 -0
  396. package/docs/platform-integration.md +122 -0
  397. package/docs/telemetry-contract.md +214 -0
  398. package/docs/telemetry.md +184 -0
  399. package/docs/testing.md +82 -0
  400. package/docs/token-efficiency.md +80 -0
  401. package/docs/usage.md +1046 -0
  402. package/package.json +8 -5
  403. package/dist/cli-launch.js +0 -112
  404. package/dist/daemon-resilience.d.ts +0 -22
  405. package/dist/daemon-resilience.js +0 -85
  406. package/dist/mcp-proxy.d.ts +0 -67
  407. package/dist/mcp-proxy.js +0 -337
  408. package/dist/tools/profiles.d.ts +0 -37
  409. package/dist/tools/profiles.js +0 -139
  410. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  411. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  412. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  413. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  414. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  415. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  416. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/dist/init/run.js CHANGED
@@ -4,7 +4,42 @@
4
4
  * access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
5
5
  */
6
6
  import { dirname, join } from 'node:path';
7
+ import { spanSync } from '../trace.js';
8
+ import { readFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
7
10
  import { detect, Framework } from './detect.js';
11
+ import { wasMcpRegistered } from './mcp-registered.js';
12
+ import { pickAstroHost } from './astro-host.js';
13
+ import { workspaceParents } from './workspace-apps.js';
14
+ import { chooseWorkspaceApp } from './app-choice.js';
15
+ import { isConnectStep } from './plan.js';
16
+ import { CURSOR_RULE_PATH } from './agent-rules.js';
17
+ import { CRA_ENV_PATH } from './cra.js';
18
+ /** CRA's bundled entry, in the order create-react-app itself generates them. */
19
+ const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
20
+ function craEntryOf(io) {
21
+ for (const path of CRA_ENTRY_CANDIDATES) {
22
+ const source = io.readFile(path);
23
+ if (source !== null)
24
+ return { path, source };
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * The daemon's pairing token, for the one stack that cannot read it at build time.
30
+ *
31
+ * Every other framework's snippet reads this file in Node when the dev server starts, so the token
32
+ * is never written into the project. CRA bundles browser code and inlines only REACT_APP_*, so it
33
+ * has to be materialised into .env.development.local — which CRA's own template gitignores.
34
+ */
35
+ function readPairingToken() {
36
+ try {
37
+ return readFileSync(join(homedir(), '.reticle', 'pairing-token'), 'utf8').trim();
38
+ }
39
+ catch {
40
+ return '';
41
+ }
42
+ }
8
43
  import { DEPS_TARGET, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
9
44
  import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
10
45
  import { reticleDevLocation } from './next-patch.js';
@@ -13,7 +48,7 @@ import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
13
48
  import { deriveProjectId, packageName } from './project-id.js';
14
49
  import { VITE_DEV_MODULE_PATH } from './snippets.js';
15
50
  import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
16
- import { SERVER_VERSION } from '../server-version.js';
51
+ import { SERVER_VERSION } from '../version/server-version.js';
17
52
  import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
18
53
  /** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
19
54
  const LOCKFILE_NAMES = [
@@ -108,6 +143,16 @@ const VITE_CONFIG_CANDIDATES = [
108
143
  'vite.config.mjs',
109
144
  'vite.config.mts',
110
145
  ];
146
+ const ASTRO_CONFIG_CANDIDATES = [
147
+ 'astro.config.mjs',
148
+ 'astro.config.js',
149
+ 'astro.config.ts',
150
+ 'astro.config.cjs',
151
+ ];
152
+ /** Where a conventional Astro app keeps the layout every page wraps itself in. */
153
+ const ASTRO_LAYOUTS_DIR = 'src/layouts';
154
+ /** Also searched: an app with no layouts directory renders the document straight from a page. */
155
+ const ASTRO_PAGES_DIR = 'src/pages';
111
156
  const NEXT_CONFIG_CANDIDATES = [
112
157
  'next.config.mjs',
113
158
  'next.config.js',
@@ -119,6 +164,21 @@ const NEXT_CONFIG_CANDIDATES = [
119
164
  * decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
120
165
  * on a working install that happens to be on an ungated stack.
121
166
  */
167
+ /**
168
+ * A step's status AFTER the run, which is what actually happened to it.
169
+ *
170
+ * `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
171
+ * MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
172
+ * the run had already given up on it.
173
+ */
174
+ function resolvedStatus(plan, target, failed, skipped) {
175
+ const step = plan.steps.find((s) => s.target === target);
176
+ if (step === undefined)
177
+ return undefined;
178
+ if (failed.has(target) || skipped.has(target))
179
+ return StepStatus.MANUAL;
180
+ return step.status;
181
+ }
122
182
  const STATUS_SYMBOL = {
123
183
  [StepStatus.APPLY]: '✓',
124
184
  [StepStatus.MANUAL]: '⚠',
@@ -138,7 +198,7 @@ function gatherPlanInput(options, io, pkgRaw) {
138
198
  const projectId = deriveProjectId(packageName(pkg), options.cwd);
139
199
  const rootFiles = new Set(io.rootFiles());
140
200
  const detectInput = {
141
- pkg: typeof pkg === 'object' && pkg !== null ? pkg : {},
201
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
142
202
  configFiles: rootFiles,
143
203
  // Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager.
144
204
  lockfiles: resolveLockfiles(rootFiles, options.cwd, io),
@@ -147,7 +207,7 @@ function gatherPlanInput(options, io, pkgRaw) {
147
207
  };
148
208
  const detection = detect(detectInput);
149
209
  const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
150
- const viteSource = vitePath === null ? null : io.readFile(vitePath);
210
+ const viteSource = null === vitePath ? null : io.readFile(vitePath);
151
211
  const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
152
212
  // Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
153
213
  // global config file. Only probe when the MCP step is in play.
@@ -155,16 +215,36 @@ function gatherPlanInput(options, io, pkgRaw) {
155
215
  const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
156
216
  const existsProbe = claudeExistsProbe();
157
217
  const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
158
- const cursorDir = `${io.homeDir()}/${CURSOR_DIR_RELPATH}`;
159
- const cursorConfigPath = `${io.homeDir()}/${CURSOR_MCP_RELPATH}`;
160
- const cursorPresent = options.mcp && io.exists(cursorDir);
218
+ // join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
219
+ // backslash platform silently misses the config it is meant to find.
220
+ const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
221
+ const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
222
+ // Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
223
+ // The directory alone missed a real case: Cursor installed with a FRESH profile has not written
224
+ // ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
225
+ // project-level .cursor/ is unambiguous evidence somebody uses it here.
226
+ const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
161
227
  const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
228
+ const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
229
+ const astroSource = null === astroPath ? null : io.readFile(astroPath);
230
+ // Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
231
+ // .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
232
+ // renders from src/pages/index.astro, the other has three files there of which two are partials.
233
+ // `</body>` is the discriminator — see astro-host.
234
+ const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
235
+ .listFiles(dir)
236
+ .filter((f) => f.endsWith('.astro'))
237
+ .map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
238
+ .filter((c) => c.source !== null));
239
+ const astroHost = pickAstroHost(astroCandidates);
240
+ const layoutRelPath = astroHost?.path ?? null;
241
+ const astroLayoutSource = astroHost?.source ?? null;
162
242
  const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
163
243
  // App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
164
- const layoutPath = (NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
165
- NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p))) ??
244
+ const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
245
+ NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
166
246
  null;
167
- const layoutSource = layoutPath === null ? null : io.readFile(layoutPath);
247
+ const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
168
248
  // Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
169
249
  // component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
170
250
  const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
@@ -177,8 +257,12 @@ function gatherPlanInput(options, io, pkgRaw) {
177
257
  cursorConfig,
178
258
  cursorConfigPath,
179
259
  viteConfig,
260
+ astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
261
+ astroLayout: layoutRelPath !== null && astroLayoutSource !== null
262
+ ? { path: layoutRelPath, source: astroLayoutSource }
263
+ : null,
180
264
  nextConfigFile,
181
- nextConfigSource: nextConfigFile === null ? null : io.readFile(nextConfigFile),
265
+ nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
182
266
  nextLayout: layoutPath !== null && layoutSource !== null
183
267
  ? { path: layoutPath, source: layoutSource }
184
268
  : null,
@@ -190,13 +274,16 @@ function gatherPlanInput(options, io, pkgRaw) {
190
274
  nextReticleDevImport: devLocation.importSpecifier,
191
275
  nextReticleDevExists: io.exists(devLocation.path),
192
276
  svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
277
+ craEntry: craEntryOf(io),
278
+ craEnv: io.readFile(CRA_ENV_PATH),
279
+ pairingToken: readPairingToken(),
193
280
  reticleConfigExists: io.exists('.reticle.json'),
194
281
  // Read the agent instruction files so the rule merge stays idempotent across re-runs.
195
282
  claudeMdContent: io.readFile('CLAUDE.md'),
196
283
  agentsMdContent: io.readFile('AGENTS.md'),
197
- cursorRuleExists: io.exists('.cursor/rules/reticle.mdc'),
198
- claudeCommandExists: io.exists(CLAUDE_COMMAND_PATH),
199
- cursorCommandExists: io.exists(CURSOR_COMMAND_PATH),
284
+ cursorRuleContent: io.readFile(CURSOR_RULE_PATH),
285
+ claudeCommandContent: io.readFile(CLAUDE_COMMAND_PATH),
286
+ cursorCommandContent: io.readFile(CURSOR_COMMAND_PATH),
200
287
  options: {
201
288
  port: options.port,
202
289
  mcp: options.mcp,
@@ -208,12 +295,13 @@ function gatherPlanInput(options, io, pkgRaw) {
208
295
  };
209
296
  }
210
297
  /** Where workspace tooling conventionally puts packages. */
211
- const WORKSPACE_PARENTS = ['apps', 'packages'];
298
+ /** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
299
+ const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
212
300
  /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
213
301
  const APP_DEPS = ['next', 'vite'];
214
302
  function looksLikeApp(dir, io) {
215
303
  const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
216
- if (pkgRaw === null)
304
+ if (null === pkgRaw)
217
305
  return false;
218
306
  const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
219
307
  if (configs.some((c) => io.exists(`${dir}/${c}`)))
@@ -231,14 +319,40 @@ function looksLikeApp(dir, io) {
231
319
  */
232
320
  export function findWorkspaceApps(io) {
233
321
  const found = [];
234
- for (const parent of WORKSPACE_PARENTS) {
322
+ // A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
323
+ // with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
324
+ // root and reported ✓ for a file Next never compiles. See workspace-apps.
325
+ const pkgRaw = io.readFile(PACKAGE_JSON);
326
+ let pkgWorkspaces;
327
+ try {
328
+ const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
329
+ pkgWorkspaces =
330
+ 'object' === typeof parsed && parsed !== null
331
+ ? parsed.workspaces
332
+ : undefined;
333
+ }
334
+ catch {
335
+ pkgWorkspaces = undefined;
336
+ }
337
+ const parents = workspaceParents({
338
+ ...(null === io.readFile(PNPM_WORKSPACE)
339
+ ? {}
340
+ : { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
341
+ ...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
342
+ topLevelDirs: io.listDirs('.'),
343
+ });
344
+ // A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
345
+ // its children rather than assuming one level of nesting.
346
+ for (const parent of parents) {
347
+ if (looksLikeApp(parent, io))
348
+ found.push(parent);
235
349
  for (const name of io.listDirs(parent)) {
236
350
  const dir = `${parent}/${name}`;
237
351
  if (looksLikeApp(dir, io))
238
352
  found.push(dir);
239
353
  }
240
354
  }
241
- return found;
355
+ return [...new Set(found)];
242
356
  }
243
357
  function restartHint(framework) {
244
358
  if (framework === Framework.NEXT)
@@ -253,13 +367,26 @@ function restartHint(framework) {
253
367
  }
254
368
  const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
255
369
  'installed stops it booting. Run that install, then re-run `reticle init`.';
256
- function report(plan, dryRun, failed, skipped, io) {
370
+ function report(plan, dryRun, failed, skipped, degraded, io) {
257
371
  io.print(dryRun ? 'reticle init (dry run — no files written)' : 'reticle init');
258
372
  io.print('');
259
373
  let applied = 0;
260
374
  let manual = 0;
375
+ // A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
376
+ // the app never dials the daemon and every tool answers "no browser session connected". `ok` was
377
+ // hardcoded true, so a run that could not possibly work reported success.
378
+ let connectPending = false;
261
379
  for (const s of plan.steps) {
262
380
  // A side effect that failed to apply is reported as a manual step with its fallback command.
381
+ const note = degraded.get(s.target);
382
+ if (note !== undefined) {
383
+ // Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
384
+ io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
385
+ for (const line of note.split('\n'))
386
+ io.print(` ${line}`);
387
+ applied++;
388
+ continue;
389
+ }
263
390
  const downgraded = failed.has(s.target) || skipped.has(s.target);
264
391
  const status = downgraded ? StepStatus.MANUAL : s.status;
265
392
  const detail = skipped.has(s.target)
@@ -272,8 +399,11 @@ function report(plan, dryRun, failed, skipped, io) {
272
399
  applied++;
273
400
  if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
274
401
  // A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
275
- if (status === StepStatus.MANUAL)
402
+ if (status === StepStatus.MANUAL) {
276
403
  manual++;
404
+ if (isConnectStep(s.title))
405
+ connectPending = true;
406
+ }
277
407
  for (const line of detail.split('\n'))
278
408
  io.print(` ${line}`);
279
409
  }
@@ -282,8 +412,13 @@ function report(plan, dryRun, failed, skipped, io) {
282
412
  }
283
413
  }
284
414
  io.print('');
415
+ if (connectPending) {
416
+ io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
417
+ '"no browser session connected" until then. Everything else is already in place.');
418
+ io.print('');
419
+ }
285
420
  io.print(restartHint(plan.framework));
286
- return { ok: true, applied, manual };
421
+ return { ok: !connectPending, applied, manual };
287
422
  }
288
423
  /**
289
424
  * Perform the apply-step side effects; return the targets whose side effect failed.
@@ -297,23 +432,48 @@ function report(plan, dryRun, failed, skipped, io) {
297
432
  function applyEffects(plan, io) {
298
433
  const failed = new Set();
299
434
  const skipped = new Set();
435
+ const degraded = new Map();
300
436
  let installFailed = false;
301
437
  for (const s of plan.steps) {
302
438
  if (s.status !== StepStatus.APPLY)
303
439
  continue;
304
- if (installFailed && s.dependsOnInstall === true) {
440
+ if (installFailed && true === s.dependsOnInstall) {
305
441
  skipped.add(s.target);
306
442
  continue;
307
443
  }
308
- if (s.write !== undefined)
309
- io.writeFile(s.write.path, s.write.content);
310
- if (s.exec !== undefined && !io.exec(s.exec.command, s.exec.args)) {
444
+ const write = s.write;
445
+ if (write !== undefined) {
446
+ spanSync('init.write', { target: s.target, path: write.path }, () => {
447
+ io.writeFile(write.path, write.content);
448
+ });
449
+ }
450
+ // Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
451
+ // narrowing mistake into an empty command that silently "succeeds".
452
+ const exec = s.exec;
453
+ // Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
454
+ // package-manager install and a `claude mcp add` are both subprocesses, and one of them being
455
+ // slow is a completely different problem from the other.
456
+ if (exec !== undefined &&
457
+ !spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
458
+ // A weaker second attempt beats no install at all — but only when it is REPORTED, because the
459
+ // thing it gives up is the version pin that keeps SDK and daemon in step.
460
+ //
461
+ // Traced separately, and it is the reason init can take twice as long as it looks like it
462
+ // should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
463
+ // init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
464
+ // nothing accounted for this one.
465
+ const retry = s.retry;
466
+ if (retry !== undefined &&
467
+ spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
468
+ degraded.set(s.target, retry.note);
469
+ continue;
470
+ }
311
471
  failed.add(s.target);
312
472
  if (s.target === DEPS_TARGET)
313
473
  installFailed = true;
314
474
  }
315
475
  }
316
- return { failed, skipped };
476
+ return { failed, skipped, degraded };
317
477
  }
318
478
  /**
319
479
  * Which STEP failed, from the step targets — not from an error string, which would carry paths.
@@ -338,25 +498,35 @@ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle
338
498
  * Returns null when there is nothing to redirect to — the caller then proceeds here as before.
339
499
  */
340
500
  function redirectToWorkspaceApp(options, io, pkgRaw) {
341
- if (options.redirected === true)
501
+ if (true === options.redirected)
342
502
  return null;
343
503
  const pkg = JSON.parse(pkgRaw);
344
504
  const rootFiles = new Set(io.rootFiles());
345
505
  const here = detect({
346
- pkg: typeof pkg === 'object' && pkg !== null ? pkg : {},
506
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
347
507
  configFiles: rootFiles,
348
508
  lockfiles: new Set(),
349
509
  });
350
510
  if (here.framework !== Framework.HTML)
351
511
  return null; // this directory IS the app
352
512
  const apps = findWorkspaceApps(io);
353
- const target = apps.length === 1 ? apps[0] : undefined;
513
+ // An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
514
+ // one you want" is not something a script, a CI step, or an agent that cannot change directory can
515
+ // act on — so the refusal was a dead end for exactly the callers most likely to hit it.
516
+ const chosen = chooseWorkspaceApp(options.app, apps);
517
+ if (!chosen.ok) {
518
+ io.print(chosen.message);
519
+ return { ok: false, applied: 0, manual: 1 };
520
+ }
521
+ const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
354
522
  if (target === undefined) {
355
- if (apps.length === 0)
523
+ if (0 === apps.length)
356
524
  return null; // not a workspace — fall through to the normal HTML plan
357
525
  io.print(AMBIGUOUS_HEADER);
358
526
  for (const a of apps)
359
527
  io.print(` ${a}`);
528
+ io.print('');
529
+ io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
360
530
  return { ok: false, applied: 0, manual: apps.length };
361
531
  }
362
532
  io.print(`No app in this directory — wiring ${target} instead.`);
@@ -365,7 +535,7 @@ function redirectToWorkspaceApp(options, io, pkgRaw) {
365
535
  }
366
536
  export function runInit(options, io) {
367
537
  const pkgRaw = io.readFile(PACKAGE_JSON);
368
- if (pkgRaw === null) {
538
+ if (null === pkgRaw) {
369
539
  io.print('No package.json found. Run `reticle init` from your project root.');
370
540
  // The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
371
541
  // from someone who never ran the command — the two failure modes with the most different fixes.
@@ -375,19 +545,23 @@ export function runInit(options, io) {
375
545
  const redirected = redirectToWorkspaceApp(options, io, pkgRaw);
376
546
  if (redirected !== null)
377
547
  return redirected;
378
- const plan = buildPlan(gatherPlanInput(options, io, pkgRaw));
548
+ // Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
549
+ // 1–6s per app with no explanation of the spread. These three spans split that number into detect
550
+ // (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
551
+ const plan = spanSync('init.plan', {}, () => buildPlan(gatherPlanInput(options, io, pkgRaw)));
379
552
  const effects = options.dryRun
380
- ? { failed: new Set(), skipped: new Set() }
381
- : applyEffects(plan, io);
382
- const { failed, skipped } = effects;
383
- const result = report(plan, options.dryRun, failed, skipped, io);
553
+ ? { failed: new Set(), skipped: new Set(), degraded: new Map() }
554
+ : spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
555
+ const { failed, skipped, degraded } = effects;
556
+ const result = report(plan, options.dryRun, failed, skipped, degraded, io);
384
557
  // A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
385
558
  if (!options.dryRun) {
386
559
  reportInitOutcome({
387
560
  ok: result.ok,
388
561
  ...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
389
562
  stack: plan.framework,
390
- mcpRegistered: !failed.has(MCP_TARGET),
563
+ // The step's REAL final status, not the absence of a failure — see mcp-registered.
564
+ mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
391
565
  });
392
566
  }
393
567
  return result;
@@ -15,4 +15,13 @@
15
15
  export declare const CLAUDE_COMMAND_PATH = ".claude/commands/reticle.md";
16
16
  /** Cursor reads them from `.cursor/commands/<name>.md`. */
17
17
  export declare const CURSOR_COMMAND_PATH = ".cursor/commands/reticle.md";
18
+ /**
19
+ * The frontmatter line that marks a command file as OURS.
20
+ *
21
+ * A stale command has to be refreshable — a file frozen at whatever release created it can never be
22
+ * improved for anyone who already ran init — but the path is one a human may have claimed for their
23
+ * own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
24
+ * are told apart.
25
+ */
26
+ export declare const SLASH_COMMAND_SIGNATURE = "description: Verify this app in the browser with Reticle";
18
27
  export declare const SLASH_COMMAND_BODY = "---\ndescription: Verify this app in the browser with Reticle \u2014 drive one real flow and report what happened.\n---\n\nVerify this running app with Reticle. Drive it; do not read the code and guess.\n\n## Pick ONE flow\n\nNot the whole app. Pick the single most important flow you can complete in a handful of steps \u2014 the\none a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or\njust changed some code, use that one instead.\n\nState which flow you picked in one line before you start.\n\n## Drive it where the human can see\n\n1. `reticle_sessions` \u2014 find the connected tab. None? Tell the user to run their dev server and open\n the app, then stop. Reticle never starts a dev server.\n2. `reticle_snapshot` \u2014 read the accessibility tree and locate the elements the flow needs. Elements\n are addressable by role and name; you do **not** need `data-testid` to drive them.\n3. Walk the flow with `reticle_act_and_wait`, one step at a time. Narrate each step first \u2014 the human\n is watching the page, and the HUD shows what you say.\n4. After each step, check the effect with `reticle_assert` \u2014 not just that the click dispatched, but\n that the thing it was supposed to do actually happened.\n5. Finish with `reticle_console` and `reticle_network` to catch errors the DOM does not show.\n\n## Report\n\n- What you drove, step by step, and what each step actually produced.\n- Anything broken, with the `file:line` Reticle gave you.\n- If a step could not be verified, say so plainly. \"Unknown\" is a real answer; a green verdict you\n cannot back is not.\n\nDo not weaken or skip an assertion to make the run pass \u2014 that is a finding, not a fix.\n";
@@ -15,8 +15,17 @@
15
15
  export const CLAUDE_COMMAND_PATH = '.claude/commands/reticle.md';
16
16
  /** Cursor reads them from `.cursor/commands/<name>.md`. */
17
17
  export const CURSOR_COMMAND_PATH = '.cursor/commands/reticle.md';
18
+ /**
19
+ * The frontmatter line that marks a command file as OURS.
20
+ *
21
+ * A stale command has to be refreshable — a file frozen at whatever release created it can never be
22
+ * improved for anyone who already ran init — but the path is one a human may have claimed for their
23
+ * own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
24
+ * are told apart.
25
+ */
26
+ export const SLASH_COMMAND_SIGNATURE = 'description: Verify this app in the browser with Reticle';
18
27
  export const SLASH_COMMAND_BODY = `---
19
- description: Verify this app in the browser with Reticle — drive one real flow and report what happened.
28
+ ${SLASH_COMMAND_SIGNATURE} — drive one real flow and report what happened.
20
29
  ---
21
30
 
22
31
  Verify this running app with Reticle. Drive it; do not read the code and guess.
@@ -15,19 +15,16 @@ export declare function nextConfigManual(configFile: string): string;
15
15
  * setup ended at `bridge refused the connection: authentication failed` and no session ever appeared.
16
16
  */
17
17
  export declare function nextReticleDevFile(port: number | undefined, projectId?: string, testids?: readonly string[], stores?: readonly string[]): string;
18
+ export declare function astroManual(port: number | undefined, projectId?: string,
18
19
  /**
19
- * Astro connect instructions.
20
+ * A layout file that actually exists, when one does.
20
21
  *
21
- * Astro is Vite-based but SSRs its own HTML, so the plugin's index.html injection never fires, and
22
- * `vite` is not a direct dependency — so this used to fall through to the generic HTML advice, which
23
- * tells you to add a connect to an "entry module" Astro does not have, or to bundle the SDK with
24
- * esbuild. Both are wrong for Astro, and following either gets you nothing.
25
- *
26
- * Astro bundles a page `<script>`, so the bare import resolves there. The token has to be inlined by
27
- * the config because there is no plugin in the page's path to inject it, and `build.target` has to be
28
- * raised or Astro down-levels the modern SDK bundle and dies on a destructuring transform.
22
+ * Reported from the field: on `examples/framework-react` — which has no layout at all, only
23
+ * `src/pages/index.astro` — init printed thirty lines telling the user to paste into "your
24
+ * layout". Instructions that name a file the project does not have read as a mistake by the
25
+ * reader, and cost them the time it takes to go and confirm it is missing.
29
26
  */
30
- export declare function astroManual(port: number | undefined, projectId?: string): string;
27
+ layoutPath?: string): string;
31
28
  /**
32
29
  * The app-side dev module the Vite plugin imports by convention.
33
30
  *
@@ -47,9 +47,9 @@ export function nextConfigManual(configFile) {
47
47
  */
48
48
  export function nextReticleDevFile(port, projectId, testids = [], stores = []) {
49
49
  const base = connectArg(port, projectId);
50
- const fields = base === '' ? '' : `${base.slice(1, -1).trim()}, `;
50
+ const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
51
51
  const ids = testids.map((t) => `'${t}'`).join(', ');
52
- const storeBlock = stores.length === 0
52
+ const storeBlock = 0 === stores.length
53
53
  ? ' // No state library detected. If you add one, register it here — see docs/usage.md.'
54
54
  : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
55
55
  return `'use client';
@@ -73,7 +73,7 @@ export function ReticleDev() {
73
73
  // form wires \`subscribe\` too, so every mutation emits a diff; the getter form is read-only.
74
74
  ${storeBlock}
75
75
  registerCapabilities({
76
- testids: [${ids}],${testids.length === 0 ? ' // none found — add data-testid to your key elements' : ''}
76
+ testids: [${ids}],${0 === testids.length ? ' // none found — add data-testid to your key elements' : ''}
77
77
  signals: [], // names you pass to reticle.signal()
78
78
  stores: [], // the keys you registered above
79
79
  });
@@ -95,8 +95,28 @@ ${storeBlock}
95
95
  * the config because there is no plugin in the page's path to inject it, and `build.target` has to be
96
96
  * raised or Astro down-levels the modern SDK bundle and dies on a destructuring transform.
97
97
  */
98
- export function astroManual(port, projectId) {
99
- const extra = port !== undefined && port !== RETICLE_DEFAULT_PORT ? `\n url: '${bridgeWsUrl(port)}',` : '';
98
+ /**
99
+ * Name the file the connect <script> should go in — the layout when one exists, otherwise the page,
100
+ * because a project with no layout has nowhere else to put it and should not be told otherwise.
101
+ */
102
+ function layoutHost(layoutPath) {
103
+ return layoutPath === undefined
104
+ ? '2. This project has no layout, so put it in the page you want instrumented (e.g.\n src/pages/index.astro) — every page you want a session from needs it — inside <body>:'
105
+ : `2. In ${layoutPath} (or any other page you want instrumented), inside <body>:`;
106
+ }
107
+ export function astroManual(port, projectId,
108
+ /**
109
+ * A layout file that actually exists, when one does.
110
+ *
111
+ * Reported from the field: on `examples/framework-react` — which has no layout at all, only
112
+ * `src/pages/index.astro` — init printed thirty lines telling the user to paste into "your
113
+ * layout". Instructions that name a file the project does not have read as a mistake by the
114
+ * reader, and cost them the time it takes to go and confirm it is missing.
115
+ */
116
+ layoutPath) {
117
+ const extra = port !== undefined && port !== RETICLE_DEFAULT_PORT
118
+ ? `\n url: '${bridgeWsUrl(port)}',`
119
+ : '';
100
120
  const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
101
121
  return `Astro renders its own HTML, so the connect goes in a page <script> and the pairing token is inlined by the config.
102
122
 
@@ -125,7 +145,7 @@ export function astroManual(port, projectId) {
125
145
  },
126
146
  });
127
147
 
128
- 2. In your layout (or the page you want instrumented), inside <body>:
148
+ ${layoutHost(layoutPath)}
129
149
 
130
150
  <script>
131
151
  if (import.meta.env.DEV) {
@@ -153,7 +173,7 @@ Start the daemon BEFORE \`astro dev\`, so the token file exists when the config
153
173
  */
154
174
  export function viteDevModuleFile(testids, stores) {
155
175
  const ids = testids.map((t) => `'${t}'`).join(', ');
156
- const storeBlock = stores.length === 0
176
+ const storeBlock = 0 === stores.length
157
177
  ? ' // No state library detected. If you add one, register it here — see docs/usage.md.'
158
178
  : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
159
179
  return `// Dev-only. Imported automatically by @reticlehq/vite-plugin — you do not need to import it.
@@ -173,7 +193,7 @@ if (import.meta.env.DEV) {
173
193
  ${storeBlock}
174
194
 
175
195
  registerCapabilities({
176
- testids: [${ids}],${testids.length === 0 ? ' // none found — add data-testid to your key elements' : ''}
196
+ testids: [${ids}],${0 === testids.length ? ' // none found — add data-testid to your key elements' : ''}
177
197
  signals: [], // names you pass to reticle.signal()
178
198
  stores: [], // the keys you registered above
179
199
  });
@@ -203,10 +223,11 @@ export function htmlManual(port, projectId) {
203
223
  • Bundled app (Create React App, webpack, Parcel, Vue/Svelte CLI, etc.) — add to your ENTRY module
204
224
  (e.g. src/index.js or src/main.js), where '@reticlehq/react' resolves through your bundler:
205
225
 
206
- if (location.hostname === 'localhost') {
207
- const { reticle, install } = await import('@reticlehq/react');
208
- install();
209
- reticle.connect(${arg});
226
+ if (window.location.hostname === 'localhost') {
227
+ void import('@reticlehq/react').then(({ reticle, install }) => {
228
+ install();
229
+ reticle.connect(${arg});
230
+ });
210
231
  }
211
232
 
212
233
  • Plain static HTML with no build step — the browser can't resolve the bare '@reticlehq/react' import, so
@@ -226,7 +247,7 @@ export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
226
247
  * here is the one thing this project exists not to do.
227
248
  */
228
249
  export function unverifiedUiLibraryNote(library) {
229
- return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here, but @reticlehq/react is a React adapter: component names and data-reticle-source file:line will be missing, and no CI gate covers ${library}. Gated today: Vite + React, Next.js, Remix, Astro. If something doesn't work, please open an issue.`;
250
+ return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here, and source file:line does too — the build plugin stamps data-reticle-source regardless of UI library (measured on preact and svelte). What @reticlehq/react adds and you will NOT get is React component identity: component names and component stacks. No CI gate covers ${library}. Gated today: Vite + React, Next.js, Remix, Astro. If something doesn't work, please open an issue.`;
230
251
  }
231
252
  export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the hook does not register a session, please open an issue.';
232
253
  /**
@@ -236,7 +257,7 @@ export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI
236
257
  */
237
258
  export function svelteKitHooksFile(port, projectId) {
238
259
  const base = connectArg(port, projectId);
239
- const fields = base === '' ? '' : `${base.slice(1, -1).trim()}, `;
260
+ const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
240
261
  return `// Dev-only: connect Reticle on the client. SvelteKit renders via app.html, so the Vite-plugin
241
262
  // index.html injection doesn't fire — connect from this client hook instead.
242
263
  if (import.meta.env.DEV) {
@@ -246,11 +267,19 @@ if (import.meta.env.DEV) {
246
267
  // file it lives in, so @reticlehq/vite-plugin inlines it here at build time. Without it the
247
268
  // console reads "bridge refused the connection: authentication failed" and no session appears.
248
269
  const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
249
- reticle.connect({ ${fields}...(token.length > 0 ? { token } : {}) });
270
+ const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
271
+ const sdkVersion = typeof __RETICLE_SDK_VERSION__ !== 'undefined' ? __RETICLE_SDK_VERSION__ : '';
272
+ reticle.connect({
273
+ ${fields}...(token.length > 0 ? { token } : {}),
274
+ ...(root.length > 0 ? { root } : {}),
275
+ ...(sdkVersion.length > 0 ? { sdkVersion } : {}),
276
+ });
250
277
  });
251
278
  }
252
279
 
253
280
  declare const __RETICLE_TOKEN__: string | undefined;
281
+ declare const __RETICLE_ROOT__: string | undefined;
282
+ declare const __RETICLE_SDK_VERSION__: string | undefined;
254
283
  `;
255
284
  }
256
285
  /**
@@ -37,7 +37,7 @@ function insertImport(source) {
37
37
  function insertPlugin(source, port) {
38
38
  return source.replace(PLUGINS_ARRAY, (match, _g, offset) => {
39
39
  const next = source[offset + match.length] ?? '';
40
- const separator = next === '' || /\s/.test(next) ? '' : ' ';
40
+ const separator = '' === next || /\s/.test(next) ? '' : ' ';
41
41
  return `${match}${reticlePluginCall(port)},${separator}`;
42
42
  });
43
43
  }