@reticlehq/server 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * What the app can be driven by: its `data-testid` values, and which state library holds its truth.
3
+ *
4
+ * `init` wired none of this, so every app came up with `hasCapabilities: false`, empty capabilities,
5
+ * and a `reticle_state` holding nothing but `__reticle_renders` — the state-truth read that SKILL.md
6
+ * calls the highest-value line was unavailable on every app out of the box. Measured across six.
7
+ *
8
+ * Testids are scanned rather than asked for. Stores are NAMED but not wired: detecting that an app
9
+ * depends on zustand is easy, knowing which module exports the store instance is not, and generating
10
+ * a wrong import would break the dev module that everything else now hangs off.
11
+ */
12
+ /** `data-testid="foo"` / `data-testid='foo'` / `data-testid={"foo"}` — the forms people actually write. */
13
+ const TESTID_ATTR = /data-testid\s*=\s*[{]?\s*["'`]([^"'`]+)["'`]/g;
14
+ /** Cap the generated list. A capabilities block is a hint for an agent, not an inventory. */
15
+ export const MAX_TESTIDS = 60;
16
+ /** Every distinct `data-testid` literal in the given sources, in first-seen order. */
17
+ export function scanTestids(sources) {
18
+ const found = new Set();
19
+ for (const src of sources) {
20
+ for (const m of src.matchAll(TESTID_ATTR)) {
21
+ const id = m[1];
22
+ if (id !== undefined && id.length > 0)
23
+ found.add(id);
24
+ if (found.size >= MAX_TESTIDS)
25
+ return [...found];
26
+ }
27
+ }
28
+ return [...found];
29
+ }
30
+ /**
31
+ * State libraries the browser SDK has an adapter for, in the order we would recommend registering
32
+ * them. TanStack Query is first on purpose: a stale cache served as fresh fires NO network request,
33
+ * so the network log shows silence and the DOM shows a plausible number — the cache is the only
34
+ * witness, which makes it the highest-value store in the list and the least obvious one to add.
35
+ */
36
+ const STORE_LIBRARIES = [
37
+ ['@tanstack/react-query', "registerStore('queries', tanstackQueryStore(queryClient))"],
38
+ [
39
+ 'zustand',
40
+ "registerStore('app', useStore) // pass the store itself, not () => store.getState()",
41
+ ],
42
+ ['@reduxjs/toolkit', "registerStore('app', store)"],
43
+ ['redux', "registerStore('app', store)"],
44
+ ['jotai', "registerStore('app', jotaiStore(getDefaultStore(), { cart, user }))"],
45
+ ['valtio', "registerStore('app', valtioStore(state))"],
46
+ ['mobx', "registerStore('app', mobxStore(state))"],
47
+ ['xstate', "registerStore('machine', xstateStore(actor))"],
48
+ ['pinia', "registerStore('cart', piniaStore(useCartStore()))"],
49
+ ['svelte', "registerStore('cart', svelteStore(cartStore))"],
50
+ ];
51
+ /** The store libraries this app depends on, as ready-to-uncomment registration lines. */
52
+ export function storeHints(deps) {
53
+ return STORE_LIBRARIES.filter(([dep]) => deps.has(dep)).map(([, hint]) => hint);
54
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Wiring Reticle into a Create React App project.
3
+ *
4
+ * CRA had no automated path: init reported `⚠ Connect snippet → index.html`, a target that cannot
5
+ * work. CRA's `public/index.html` is a static template the bundler never processes for modules, so a
6
+ * bare import in it resolves to nothing. `src/index.tsx` is what gets bundled, and that is where the
7
+ * connect has to arrive.
8
+ *
9
+ * The token is the second half of the problem. Every other stack has somewhere to inline it —
10
+ * Vite defines `__RETICLE_TOKEN__`, the Astro and HTML snippets read the file in Node. `src/index.tsx`
11
+ * is browser code inside a bundler that inlines exactly one thing: environment variables prefixed
12
+ * `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
13
+ * mechanism and is gitignored by CRA's own template.
14
+ */
15
+ /** The line added to `src/index.tsx`. Side-effect import: the module guards itself on NODE_ENV. */
16
+ export declare const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
17
+ export declare const CRA_DEV_MODULE_PATH = "src/reticle-dev.ts";
18
+ export declare const CRA_ENV_PATH = ".env.development.local";
19
+ export declare const TOKEN_VAR = "REACT_APP_RETICLE_TOKEN";
20
+ /**
21
+ * What the app itself says when the token is not there.
22
+ *
23
+ * The token is a per-machine secret and CRA's own template gitignores the file it lives in, so it
24
+ * CANNOT travel with the repo — that part is correct and must stay. What was wrong is that it failed
25
+ * silently: a teammate clones, runs `npm start`, and the app connects with an empty token, so the
26
+ * only signal is the bridge's generic `authentication failed` in a console nobody had open. Naming
27
+ * the variable, the gitignored file and the one command that fixes it turns a dead app into a
28
+ * one-line repair. Every other stack reads the token in Node at dev-server start (the Vite plugin,
29
+ * withReticle); CRA has no such hook without ejecting, so this message is the fix.
30
+ */
31
+ export declare const CRA_TOKEN_MISSING_NOTE: string;
32
+ /** Add the import after the last existing one, or null when it is already present. */
33
+ export declare function craImportPatch(source: string): string | null;
34
+ /** Set the token variable, or null when nothing needs to change. */
35
+ export declare function craEnvPatch(existing: string | null, token: string): string | null;
36
+ /** The dev-only connect module imported from `src/index.tsx`. */
37
+ export declare function craDevModuleFile(port: number | undefined, projectId?: string): string;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Wiring Reticle into a Create React App project.
3
+ *
4
+ * CRA had no automated path: init reported `⚠ Connect snippet → index.html`, a target that cannot
5
+ * work. CRA's `public/index.html` is a static template the bundler never processes for modules, so a
6
+ * bare import in it resolves to nothing. `src/index.tsx` is what gets bundled, and that is where the
7
+ * connect has to arrive.
8
+ *
9
+ * The token is the second half of the problem. Every other stack has somewhere to inline it —
10
+ * Vite defines `__RETICLE_TOKEN__`, the Astro and HTML snippets read the file in Node. `src/index.tsx`
11
+ * is browser code inside a bundler that inlines exactly one thing: environment variables prefixed
12
+ * `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
13
+ * mechanism and is gitignored by CRA's own template.
14
+ */
15
+ /** The line added to `src/index.tsx`. Side-effect import: the module guards itself on NODE_ENV. */
16
+ export const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
17
+ export const CRA_DEV_MODULE_PATH = 'src/reticle-dev.ts';
18
+ export const CRA_ENV_PATH = '.env.development.local';
19
+ export const TOKEN_VAR = 'REACT_APP_RETICLE_TOKEN';
20
+ /**
21
+ * What the app itself says when the token is not there.
22
+ *
23
+ * The token is a per-machine secret and CRA's own template gitignores the file it lives in, so it
24
+ * CANNOT travel with the repo — that part is correct and must stay. What was wrong is that it failed
25
+ * silently: a teammate clones, runs `npm start`, and the app connects with an empty token, so the
26
+ * only signal is the bridge's generic `authentication failed` in a console nobody had open. Naming
27
+ * the variable, the gitignored file and the one command that fixes it turns a dead app into a
28
+ * one-line repair. Every other stack reads the token in Node at dev-server start (the Vite plugin,
29
+ * withReticle); CRA has no such hook without ejecting, so this message is the fix.
30
+ */
31
+ export const CRA_TOKEN_MISSING_NOTE = `${TOKEN_VAR} is not set, so Reticle cannot pair with the daemon. ` +
32
+ `The pairing token is per-machine and ${CRA_ENV_PATH} is gitignored by CRA's template, ` +
33
+ 'so it does not survive a clone. Run `npx reticle init` in this project to write it for this machine.';
34
+ /** Add the import after the last existing one, or null when it is already present. */
35
+ export function craImportPatch(source) {
36
+ if (source.includes(CRA_DEV_MODULE_IMPORT))
37
+ return null;
38
+ const lines = source.split('\n');
39
+ // After the LAST import: React's own imports must still run first, and a side-effect import placed
40
+ // above them would connect before the app exists.
41
+ let lastImport = -1;
42
+ for (const [index, line] of lines.entries()) {
43
+ if (/^\s*import\s/.test(line))
44
+ lastImport = index;
45
+ }
46
+ lines.splice(lastImport + 1, 0, CRA_DEV_MODULE_IMPORT);
47
+ return lines.join('\n');
48
+ }
49
+ /** Set the token variable, or null when nothing needs to change. */
50
+ export function craEnvPatch(existing, token) {
51
+ if ('' === token)
52
+ return null;
53
+ const line = `${TOKEN_VAR}=${token}`;
54
+ if (null === existing || '' === existing.trim())
55
+ return `${line}\n`;
56
+ if (existing.includes(line))
57
+ return null;
58
+ // Replace rather than append: two assignments of one variable is a silent coin flip on which wins.
59
+ if (new RegExp(`^${TOKEN_VAR}=`, 'm').test(existing)) {
60
+ return existing.replace(new RegExp(`^${TOKEN_VAR}=.*$`, 'm'), line);
61
+ }
62
+ return `${existing.endsWith('\n') ? existing : `${existing}\n`}${line}\n`;
63
+ }
64
+ /** The dev-only connect module imported from `src/index.tsx`. */
65
+ export function craDevModuleFile(port, projectId) {
66
+ const fields = [];
67
+ if (port !== undefined)
68
+ fields.push(`url: 'ws://127.0.0.1:${String(port)}/reticle'`);
69
+ if (projectId !== undefined && projectId.length > 0)
70
+ fields.push(`projectId: '${projectId}'`);
71
+ const inline = fields.length > 0 ? `${fields.join(', ')}, ` : '';
72
+ return `// Dev-only: connect Reticle. Imported for its side effect from src/index.tsx.
73
+ //
74
+ // CRA's public/index.html is a static template the bundler never processes for modules, so the
75
+ // connect cannot live there. The pairing token arrives through REACT_APP_RETICLE_TOKEN because
76
+ // REACT_APP_* is the only thing CRA inlines into browser code.
77
+ if (process.env.NODE_ENV === 'development') {
78
+ void import('@reticlehq/react').then(({ reticle, install }) => {
79
+ install();
80
+ const token = process.env.${TOKEN_VAR} ?? '';
81
+ // Loud on purpose: without this the only symptom is the bridge's generic auth failure.
82
+ if (token.length === 0) console.error(${JSON.stringify(`[reticle] ${CRA_TOKEN_MISSING_NOTE}`)});
83
+ // Still attempt it — a bridge running without a token pairs fine.
84
+ reticle.connect({ ${inline}...(token.length > 0 ? { token } : {}) });
85
+ });
86
+ }
87
+
88
+ export {};
89
+ `;
90
+ }
@@ -5,6 +5,7 @@
5
5
  * (jsonc/comment) files bail to manual rather than being rewritten.
6
6
  */
7
7
  import { NPX, MCP_SERVER_NAME, npxServerArgs } from './mcp.js';
8
+ import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
8
9
  /** Path of Cursor's global MCP config, relative to the user's home directory. */
9
10
  export const CURSOR_MCP_RELPATH = '.cursor/mcp.json';
10
11
  /** The directory whose presence signals Cursor is installed for this user. */
@@ -18,18 +19,47 @@ export function cursorServerEntry() {
18
19
  return { command: NPX, args: npxServerArgs() };
19
20
  }
20
21
  function parseConfig(existing) {
21
- if (existing === null || existing.trim().length === 0)
22
+ if (null === existing || 0 === existing.trim().length)
22
23
  return { ok: true, config: {} };
23
24
  try {
24
25
  const parsed = JSON.parse(existing);
25
- if (typeof parsed !== 'object' || parsed === null)
26
- return { ok: true, config: {} };
26
+ // Valid JSON that is not a PLAIN OBJECT — `[]`, `3`, `"x"`, `null` — used to fall through to an
27
+ // empty config, and the file was then rewritten wholesale. Unparseable JSON was already handled
28
+ // conservatively; this adjacent case destroyed the file instead. Whatever is in there is the
29
+ // user's, and we do not understand it, so we do not touch it.
30
+ if (typeof parsed !== 'object' || null === parsed || Array.isArray(parsed))
31
+ return { ok: false };
27
32
  return { ok: true, config: parsed };
28
33
  }
29
34
  catch {
30
35
  return { ok: false };
31
36
  }
32
37
  }
38
+ /**
39
+ * Should this entry be left exactly as it is?
40
+ *
41
+ * Two different reasons to leave one alone, and only one of them is "it is correct":
42
+ * - it already matches what we would write; or
43
+ * - it is not an entry WE wrote. Somebody pointing `reticle` at their own local build is a
44
+ * deliberate choice, and silently replacing it with the published npx command would break their
45
+ * setup to fix a problem they do not have.
46
+ *
47
+ * What IS repaired is a stale entry of our own shape — an old pin, a command that has moved — which
48
+ * presence-only idempotency reported as "already registered" and never fixed, so an upgrade could
49
+ * not repair the thing an upgrade exists to repair.
50
+ */
51
+ function leaveEntryAlone(existing) {
52
+ if (JSON.stringify(existing) === JSON.stringify(cursorServerEntry()))
53
+ return true;
54
+ if (typeof existing !== 'object' || null === existing)
55
+ return true;
56
+ const command = existing.command;
57
+ const args = existing.args;
58
+ const ours = command === NPX &&
59
+ Array.isArray(args) &&
60
+ args.some((arg) => 'string' === typeof arg && arg.includes(RETICLE_NPM_PACKAGE));
61
+ return !ours;
62
+ }
33
63
  export function mergeCursorConfig(existing) {
34
64
  const parsed = parseConfig(existing);
35
65
  if (!parsed.ok) {
@@ -37,7 +67,12 @@ export function mergeCursorConfig(existing) {
37
67
  }
38
68
  const config = parsed.config;
39
69
  const servers = config.mcpServers ?? {};
40
- if (Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_NAME)) {
70
+ // Presence alone used to mean "already registered", so a `reticle` entry left by an older release —
71
+ // a command that no longer exists, or a stale pin — was reported done and never repaired. An
72
+ // upgrade could not fix a bad entry, which is exactly when it needs to. Only OUR key is replaced;
73
+ // every other server and top-level key is carried through untouched by the spread below.
74
+ if (Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_NAME) &&
75
+ leaveEntryAlone(servers[MCP_SERVER_NAME])) {
41
76
  return { status: CursorMergeStatus.ALREADY, content: existing ?? '' };
42
77
  }
43
78
  const merged = {
@@ -23,7 +23,7 @@ export declare const DesktopFinding: {
23
23
  readonly ELECTRON_CAPTURE_MISSING: "electron-capture-missing";
24
24
  };
25
25
  export type DesktopFinding = (typeof DesktopFinding)[keyof typeof DesktopFinding];
26
- export interface DesktopDiagnosis {
26
+ interface DesktopDiagnosis {
27
27
  code: DesktopFinding;
28
28
  /** Which file to look at. */
29
29
  file: string;
@@ -33,8 +33,9 @@ export interface DesktopDiagnosis {
33
33
  fix: string;
34
34
  }
35
35
  /** Reads a project-relative path. Undefined means the file does not exist. */
36
- export type ReadFile = (path: string) => string | undefined;
36
+ type ReadFile = (path: string) => string | undefined;
37
37
  /** Is this project a desktop shell at all? Used to print a positive result instead of silence. */
38
38
  export declare function isDesktopProject(read: ReadFile): boolean;
39
39
  /** Which desktop shell (if any) this project is, and what is missing from its Reticle wiring. */
40
40
  export declare function diagnoseDesktop(read: ReadFile, port: number): DesktopDiagnosis[];
41
+ export {};
@@ -31,7 +31,7 @@ function parseJson(text) {
31
31
  return undefined;
32
32
  try {
33
33
  const parsed = JSON.parse(text);
34
- return typeof parsed === 'object' && parsed !== null
34
+ return 'object' === typeof parsed && parsed !== null
35
35
  ? parsed
36
36
  : undefined;
37
37
  }
@@ -40,7 +40,7 @@ function parseJson(text) {
40
40
  }
41
41
  }
42
42
  function record(value) {
43
- return typeof value === 'object' && value !== null ? value : {};
43
+ return 'object' === typeof value && value !== null ? value : {};
44
44
  }
45
45
  /**
46
46
  * Does this CSP permit the bridge WebSocket on `port`?
@@ -72,7 +72,7 @@ function diagnoseTauri(read, port) {
72
72
  if (conf === undefined)
73
73
  return [];
74
74
  const security = record(record(conf['app'])['security']);
75
- const csp = typeof security['csp'] === 'string' ? security['csp'] : undefined;
75
+ const csp = 'string' === typeof security['csp'] ? security['csp'] : undefined;
76
76
  const findings = [];
77
77
  if (!cspAllowsBridge(csp, port)) {
78
78
  findings.push({
@@ -193,6 +193,6 @@ export function diagnoseDesktop(read, port) {
193
193
  const deps = { ...record(pkg['dependencies']), ...record(pkg['devDependencies']) };
194
194
  if (deps['electron'] === undefined)
195
195
  return [];
196
- const main = typeof pkg['main'] === 'string' ? pkg['main'] : undefined;
196
+ const main = 'string' === typeof pkg['main'] ? pkg['main'] : undefined;
197
197
  return main === undefined ? [] : diagnoseElectron(read, main);
198
198
  }
@@ -6,6 +6,9 @@ export declare const Framework: {
6
6
  readonly NEXT: "next";
7
7
  readonly VITE: "vite";
8
8
  readonly SVELTEKIT: "sveltekit";
9
+ readonly ASTRO: "astro";
10
+ /** Create React App. No config file exists, so `react-scripts` in the dependencies is the signal. */
11
+ readonly CRA: "cra";
9
12
  readonly HTML: "html";
10
13
  };
11
14
  export type Framework = (typeof Framework)[keyof typeof Framework];
@@ -27,9 +30,31 @@ export interface DetectInput {
27
30
  configFiles: ReadonlySet<string>;
28
31
  /** Lockfile basenames present in the project root. */
29
32
  lockfiles: ReadonlySet<string>;
33
+ /** Marker basenames inside `node_modules` (`.modules.yaml`, `.yarn-state.yml`, …), if installed. */
34
+ nodeModulesMarkers?: ReadonlySet<string>;
30
35
  }
36
+ /**
37
+ * The UI library the app renders with. Detection used to key on "vite is in package.json" and stop
38
+ * there, so a Vue or Preact app got `@reticlehq/react` installed and an all-green report with no
39
+ * mention that the React adapter has nothing to attach to.
40
+ */
41
+ export declare const UiLibrary: {
42
+ readonly REACT: "react";
43
+ readonly PREACT: "preact";
44
+ readonly VUE: "vue";
45
+ readonly SVELTE: "svelte";
46
+ readonly UNKNOWN: "unknown";
47
+ };
48
+ export type UiLibrary = (typeof UiLibrary)[keyof typeof UiLibrary];
31
49
  export interface Detection {
32
50
  framework: Framework;
51
+ uiLibrary: UiLibrary;
52
+ /**
53
+ * Whether the project is TypeScript. Not cosmetic: generating a `.tsx` file into a JavaScript
54
+ * project makes Next auto-install TypeScript on the next `next dev`, which on Next 13 takes its
55
+ * require-hook down with it and the dev server never starts.
56
+ */
57
+ typescript: boolean;
33
58
  reactMajor: number | undefined;
34
59
  /** React 19 dropped _debugSource, so it needs the build-time source-map stamp. */
35
60
  needsSourceMapping: boolean;
@@ -37,6 +62,7 @@ export interface Detection {
37
62
  }
38
63
  /** Extract the leading major version from a semver range like "^19.0.0" or "19.1.1". */
39
64
  export declare function parseMajor(range: string | undefined): number | undefined;
65
+ export declare function detectPackageManager(lockfiles: ReadonlySet<string>, nodeModulesMarkers: ReadonlySet<string>): PackageManager;
40
66
  export declare function detect(input: DetectInput): Detection;
41
67
  interface InstallCommand {
42
68
  command: string;
@@ -6,6 +6,9 @@ export const Framework = {
6
6
  NEXT: 'next',
7
7
  VITE: 'vite',
8
8
  SVELTEKIT: 'sveltekit',
9
+ ASTRO: 'astro',
10
+ /** Create React App. No config file exists, so `react-scripts` in the dependencies is the signal. */
11
+ CRA: 'cra',
9
12
  HTML: 'html',
10
13
  };
11
14
  export const PackageManager = {
@@ -14,9 +17,27 @@ export const PackageManager = {
14
17
  BUN: 'bun',
15
18
  NPM: 'npm',
16
19
  };
20
+ /**
21
+ * The UI library the app renders with. Detection used to key on "vite is in package.json" and stop
22
+ * there, so a Vue or Preact app got `@reticlehq/react` installed and an all-green report with no
23
+ * mention that the React adapter has nothing to attach to.
24
+ */
25
+ export const UiLibrary = {
26
+ REACT: 'react',
27
+ PREACT: 'preact',
28
+ VUE: 'vue',
29
+ SVELTE: 'svelte',
30
+ UNKNOWN: 'unknown',
31
+ };
17
32
  const NEXT_CONFIGS = ['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.cjs'];
18
33
  const VITE_CONFIGS = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'vite.config.mts'];
19
34
  const SVELTE_CONFIGS = ['svelte.config.js', 'svelte.config.ts', 'svelte.config.mjs'];
35
+ const ASTRO_CONFIGS = [
36
+ 'astro.config.mjs',
37
+ 'astro.config.js',
38
+ 'astro.config.ts',
39
+ 'astro.config.cjs',
40
+ ];
20
41
  function depVersion(pkg, name) {
21
42
  return pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? pkg.peerDependencies?.[name];
22
43
  }
@@ -28,19 +49,41 @@ export function parseMajor(range) {
28
49
  if (range === undefined)
29
50
  return undefined;
30
51
  const match = range.match(/(\d+)/);
31
- if (match === null || match[1] === undefined)
52
+ if (null === match || match[1] === undefined)
32
53
  return undefined;
33
54
  const major = parseInt(match[1], 10);
34
55
  return isNaN(major) ? undefined : major;
35
56
  }
36
- function detectPackageManager(lockfiles) {
57
+ /**
58
+ * Markers each package manager leaves INSIDE `node_modules`. An installed tree is the strongest
59
+ * evidence there is — stronger than a lockfile, which may simply not be committed.
60
+ *
61
+ * Without this, a pnpm-installed project with no committed lockfile was read as npm, and `npm i -D`
62
+ * then died on pnpm's symlink layout with `Cannot read properties of null (reading 'matches')`. Worse,
63
+ * it left the package present in `node_modules` but absent from `package.json`, so every later run
64
+ * reported the same failure — a setup that could not be retried into working.
65
+ */
66
+ const NODE_MODULES_MARKERS = [
67
+ ['.modules.yaml', PackageManager.PNPM],
68
+ ['.yarn-state.yml', PackageManager.YARN],
69
+ ['.package-lock.json', PackageManager.NPM],
70
+ ];
71
+ /** Marker basenames present inside the project's `node_modules`, if it has one. */
72
+ function packageManagerFromNodeModules(markers) {
73
+ for (const [name, pm] of NODE_MODULES_MARKERS)
74
+ if (markers.has(name))
75
+ return pm;
76
+ return undefined;
77
+ }
78
+ export function detectPackageManager(lockfiles, nodeModulesMarkers) {
37
79
  if (lockfiles.has('pnpm-lock.yaml'))
38
80
  return PackageManager.PNPM;
39
81
  if (lockfiles.has('yarn.lock'))
40
82
  return PackageManager.YARN;
41
83
  if (lockfiles.has('bun.lockb') || lockfiles.has('bun.lock'))
42
84
  return PackageManager.BUN;
43
- return PackageManager.NPM;
85
+ // No lockfile is not the same as "npm". An already-installed tree says which manager built it.
86
+ return packageManagerFromNodeModules(nodeModulesMarkers) ?? PackageManager.NPM;
44
87
  }
45
88
  function detectFramework(input) {
46
89
  const { pkg, configFiles } = input;
@@ -52,18 +95,48 @@ function detectFramework(input) {
52
95
  if (depVersion(pkg, '@sveltejs/kit') !== undefined || hasAnyConfig(configFiles, SVELTE_CONFIGS)) {
53
96
  return Framework.SVELTEKIT;
54
97
  }
98
+ // Astro is Vite-based but SSRs its own HTML, so the plugin's index.html injection never fires and
99
+ // `vite` is not a direct dependency — it used to fall all the way through to HTML and be handed
100
+ // connect instructions for a bundler it does not have. Check BEFORE the generic Vite branch.
101
+ if (depVersion(pkg, 'astro') !== undefined || hasAnyConfig(configFiles, ASTRO_CONFIGS)) {
102
+ return Framework.ASTRO;
103
+ }
55
104
  if (depVersion(pkg, 'vite') !== undefined || hasAnyConfig(configFiles, VITE_CONFIGS)) {
56
105
  return Framework.VITE;
57
106
  }
107
+ // Checked after Vite, never before: a project migrating off CRA can carry both, and the Vite path
108
+ // is the one that works. CRA has no config file at all, so the dependency is the only signal.
109
+ if (depVersion(pkg, 'react-scripts') !== undefined)
110
+ return Framework.CRA;
58
111
  return Framework.HTML;
59
112
  }
113
+ /**
114
+ * React first: a Preact app using preact/compat aliases React, and Next/Remix apps list both. The
115
+ * order is the precedence — whichever the app actually renders through is the one it depends on
116
+ * directly.
117
+ */
118
+ function detectUiLibrary(pkg) {
119
+ if (depVersion(pkg, 'react') !== undefined)
120
+ return UiLibrary.REACT;
121
+ if (depVersion(pkg, 'preact') !== undefined)
122
+ return UiLibrary.PREACT;
123
+ if (depVersion(pkg, 'vue') !== undefined)
124
+ return UiLibrary.VUE;
125
+ if (depVersion(pkg, 'svelte') !== undefined)
126
+ return UiLibrary.SVELTE;
127
+ return UiLibrary.UNKNOWN;
128
+ }
129
+ const TS_CONFIGS = ['tsconfig.json'];
60
130
  export function detect(input) {
61
131
  const reactMajor = parseMajor(depVersion(input.pkg, 'react'));
62
132
  return {
63
133
  framework: detectFramework(input),
134
+ uiLibrary: detectUiLibrary(input.pkg),
135
+ typescript: hasAnyConfig(input.configFiles, TS_CONFIGS) ||
136
+ depVersion(input.pkg, 'typescript') !== undefined,
64
137
  reactMajor,
65
138
  needsSourceMapping: reactMajor !== undefined && reactMajor >= 19,
66
- packageManager: detectPackageManager(input.lockfiles),
139
+ packageManager: detectPackageManager(input.lockfiles, input.nodeModulesMarkers ?? new Set()),
67
140
  };
68
141
  }
69
142
  const INSTALL_ARGS = {
@@ -74,7 +147,7 @@ const INSTALL_ARGS = {
74
147
  };
75
148
  /** Build a dev-dependency install command for one or more packages (e.g. the kit + its build plugin). */
76
149
  export function installCommandParts(pm, pkgs) {
77
- const list = typeof pkgs === 'string' ? [pkgs] : pkgs;
150
+ const list = 'string' === typeof pkgs ? [pkgs] : pkgs;
78
151
  return { command: pm, args: [...INSTALL_ARGS[pm], ...list] };
79
152
  }
80
153
  export function installCommand(pm, pkgs) {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Did `reticle init` actually register the MCP server?
3
+ *
4
+ * This was `!failed.has(MCP_TARGET)` — the negation of one narrow failure set — so a step that was
5
+ * SKIPPED (`--no-mcp`) or left MANUAL (the user has to run a command themselves) reported success,
6
+ * because neither is a failure. Under `--no-mcp` every run claimed registration.
7
+ *
8
+ * It is the onboarding funnel's most important field, and it was wrong in the flattering direction
9
+ * on exactly the runs least likely to have a working install.
10
+ */
11
+ import { StepStatus } from './plan.js';
12
+ export declare function wasMcpRegistered(status: StepStatus | undefined): boolean;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Did `reticle init` actually register the MCP server?
3
+ *
4
+ * This was `!failed.has(MCP_TARGET)` — the negation of one narrow failure set — so a step that was
5
+ * SKIPPED (`--no-mcp`) or left MANUAL (the user has to run a command themselves) reported success,
6
+ * because neither is a failure. Under `--no-mcp` every run claimed registration.
7
+ *
8
+ * It is the onboarding funnel's most important field, and it was wrong in the flattering direction
9
+ * on exactly the runs least likely to have a working install.
10
+ */
11
+ import { StepStatus } from './plan.js';
12
+ export function wasMcpRegistered(status) {
13
+ return status === StepStatus.APPLY || status === StepStatus.ALREADY;
14
+ }
@@ -12,9 +12,34 @@
12
12
  * global agent config.
13
13
  */
14
14
  export declare const MCP_SERVER_NAME = "reticle";
15
+ /**
16
+ * The registered command is STILL bare `npx` on every platform, deliberately.
17
+ *
18
+ * Windows is 66% of Reticle's users (65 of 99 in a day of telemetry) and has NO CI, no fixture, and
19
+ * nothing in this repo has ever run there. Switching the majority platform to a launch command
20
+ * nobody can test — where `.cmd` also has its own spawn caveats in some hosts — risks breaking the
21
+ * users it is meant to help. So the fallback is DOCUMENTED (see mcpManual) rather than defaulted,
22
+ * until somebody can run it on Windows.
23
+ */
15
24
  export declare const NPX = "npx";
16
- /** Args after `npx` that launch the bridge: `@reticlehq/server mcp`. Portless — the port comes from
17
- * the project's `.reticle.json` at runtime, so one global entry works for every project. */
25
+ /**
26
+ * Args after `npx` that launch the bridge: `@reticlehq/server mcp`. Portless — the port comes from
27
+ * the project's `.reticle.json` at runtime, so one global entry works for every project.
28
+ *
29
+ * DELIBERATELY UNPINNED, and the trade is worth stating. `reticle init` pins the SDK to the CLI's
30
+ * exact version, so on release day the app can be on the new one while npx serves a cached older
31
+ * build — a real skew, reported from the field. Pinning this entry would remove that window.
32
+ *
33
+ * It would also freeze the agent's MCP server at whatever version was installed the day `init` ran,
34
+ * for as long as that entry survives — and `reticle update` upgrades the CLI, not a global agent
35
+ * config. Reticle's biggest measured problem is fixes not reaching people (2.4.0 reached zero users
36
+ * before its nudge existed), and a permanent pin makes that worse for every install, to close a
37
+ * window that lasts until the next npx cache miss.
38
+ *
39
+ * So it stays unpinned, and the skew is handled where it actually shows up: the contract fingerprint
40
+ * makes a real mismatch loud on the next tool result (see version-skew), and a stale entry of our own
41
+ * shape is now repaired on re-run rather than reported "already registered" (see cursor.ts).
42
+ */
18
43
  export declare function npxServerArgs(): string[];
19
44
  interface ClaudeAddCommand {
20
45
  command: string;
package/dist/init/mcp.js CHANGED
@@ -11,16 +11,41 @@
11
11
  * project's `.reticle.json` in its CWD (see cli-port.ts). The port belongs to the project, not the
12
12
  * global agent config.
13
13
  */
14
- import { RETICLE_NPM_PACKAGE } from '../server-version.js';
14
+ import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
15
15
  export const MCP_SERVER_NAME = 'reticle';
16
+ /**
17
+ * The registered command is STILL bare `npx` on every platform, deliberately.
18
+ *
19
+ * Windows is 66% of Reticle's users (65 of 99 in a day of telemetry) and has NO CI, no fixture, and
20
+ * nothing in this repo has ever run there. Switching the majority platform to a launch command
21
+ * nobody can test — where `.cmd` also has its own spawn caveats in some hosts — risks breaking the
22
+ * users it is meant to help. So the fallback is DOCUMENTED (see mcpManual) rather than defaulted,
23
+ * until somebody can run it on Windows.
24
+ */
16
25
  export const NPX = 'npx';
17
26
  // The `reticle` bin lives in the server package, so `npx <pkg> mcp` runs the bridge without the
18
27
  // retired `core` umbrella. Sourced from the single derived identity so it can never drift.
19
28
  const RETICLE_PACKAGE = RETICLE_NPM_PACKAGE;
20
29
  const MCP_SUBCOMMAND = 'mcp';
21
30
  const CLAUDE_CLI = 'claude';
22
- /** Args after `npx` that launch the bridge: `@reticlehq/server mcp`. Portless — the port comes from
23
- * the project's `.reticle.json` at runtime, so one global entry works for every project. */
31
+ /**
32
+ * Args after `npx` that launch the bridge: `@reticlehq/server mcp`. Portless — the port comes from
33
+ * the project's `.reticle.json` at runtime, so one global entry works for every project.
34
+ *
35
+ * DELIBERATELY UNPINNED, and the trade is worth stating. `reticle init` pins the SDK to the CLI's
36
+ * exact version, so on release day the app can be on the new one while npx serves a cached older
37
+ * build — a real skew, reported from the field. Pinning this entry would remove that window.
38
+ *
39
+ * It would also freeze the agent's MCP server at whatever version was installed the day `init` ran,
40
+ * for as long as that entry survives — and `reticle update` upgrades the CLI, not a global agent
41
+ * config. Reticle's biggest measured problem is fixes not reaching people (2.4.0 reached zero users
42
+ * before its nudge existed), and a permanent pin makes that worse for every install, to close a
43
+ * window that lasts until the next npx cache miss.
44
+ *
45
+ * So it stays unpinned, and the skew is handled where it actually shows up: the contract fingerprint
46
+ * makes a real mismatch loud on the next tool result (see version-skew), and a stale entry of our own
47
+ * shape is now repaired on re-run rather than reported "already registered" (see cursor.ts).
48
+ */
24
49
  export function npxServerArgs() {
25
50
  return [RETICLE_PACKAGE, MCP_SUBCOMMAND];
26
51
  }
@@ -36,6 +61,11 @@ export function claudeAddCommand() {
36
61
  }
37
62
  /** Probe args that tell us whether an `reticle` server already exists in any scope (exit 0 = exists). */
38
63
  export function claudeExistsProbe() {
64
+ // NO `-s`: `claude mcp get` takes no options at all, so passing one exits 1 with "unknown option
65
+ // '-s'" — the probe answered "not registered" on EVERY machine, init then ran `claude mcp add`,
66
+ // which exits 1 with "already exists", and a re-run reported `[⚠] step failed` plus a manual
67
+ // command that fails the same way. A false positive from a project-scoped entry costs one skipped
68
+ // registration; the flag cost every re-run a failed step.
39
69
  return { command: CLAUDE_CLI, args: [MCP_SUBCOMMAND, 'get', MCP_SERVER_NAME] };
40
70
  }
41
71
  /** Probe args for whether the `claude` CLI is installed at all. */
@@ -51,5 +81,11 @@ export function mcpManual() {
51
81
 
52
82
  Or, for another agent, add this to its global MCP config (e.g. Cursor's ~/.cursor/mcp.json):
53
83
 
54
- "${MCP_SERVER_NAME}": { "command": "${NPX}", "args": ${JSON.stringify(serverInvocation().slice(1))} }`;
84
+ "${MCP_SERVER_NAME}": { "command": "${NPX}", "args": ${JSON.stringify(serverInvocation().slice(1))} }
85
+
86
+ On Windows, if the agent cannot start Reticle because npx is blocked ("running scripts is disabled
87
+ on this system" — a PowerShell execution policy), register it through cmd instead, which that policy
88
+ does not gate:
89
+
90
+ "${MCP_SERVER_NAME}": { "command": "cmd", "args": ${JSON.stringify(['/c', NPX, ...serverInvocation().slice(1)])} }`;
55
91
  }