@reticlehq/server 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +130 -778
  3. package/dist/bridge/bridge.d.ts +23 -0
  4. package/dist/bridge/bridge.js +80 -2
  5. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  6. package/dist/bridge/bridge.test-harness.js +6 -4
  7. package/dist/capsule/causal-summary.d.ts +36 -1
  8. package/dist/capsule/causal-summary.js +33 -7
  9. package/dist/capsule/divergence.js +7 -1
  10. package/dist/cli/chromium-hint.d.ts +33 -5
  11. package/dist/cli/chromium-hint.js +82 -12
  12. package/dist/cli/cli-doctor.js +65 -34
  13. package/dist/cli/cli-flow-commands.js +8 -8
  14. package/dist/cli/cli-kill.d.ts +58 -0
  15. package/dist/cli/cli-kill.js +162 -0
  16. package/dist/cli/cli-parse.d.ts +16 -1
  17. package/dist/cli/cli-parse.js +32 -9
  18. package/dist/cli/cli-port.d.ts +32 -0
  19. package/dist/cli/cli-port.js +82 -35
  20. package/dist/cli/cli-verify.d.ts +21 -0
  21. package/dist/cli/cli-verify.js +47 -3
  22. package/dist/cli/config-discovery.d.ts +35 -0
  23. package/dist/cli/config-discovery.js +163 -0
  24. package/dist/cli/doctor-daemon-line.js +4 -1
  25. package/dist/cli/doctor-project-line.d.ts +37 -0
  26. package/dist/cli/doctor-project-line.js +35 -0
  27. package/dist/cli/doctor-rows.d.ts +51 -0
  28. package/dist/cli/doctor-rows.js +54 -0
  29. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  30. package/dist/cli/doctor-sessions-line.js +69 -0
  31. package/dist/cli/drive-attach.d.ts +99 -0
  32. package/dist/cli/drive-attach.js +203 -0
  33. package/dist/cli/drive-command.d.ts +13 -0
  34. package/dist/cli/drive-command.js +103 -0
  35. package/dist/cli/open-note.d.ts +20 -0
  36. package/dist/cli/open-note.js +37 -0
  37. package/dist/cli/port-holder.d.ts +10 -0
  38. package/dist/cli/port-holder.js +26 -2
  39. package/dist/cli/status-next-action.d.ts +37 -0
  40. package/dist/cli/status-next-action.js +61 -0
  41. package/dist/cli.js +119 -39
  42. package/dist/crawl/crawl-tools.js +12 -2
  43. package/dist/crawl/crawl.d.ts +13 -0
  44. package/dist/crawl/crawl.js +50 -2
  45. package/dist/events/contradictions.d.ts +46 -0
  46. package/dist/events/contradictions.js +35 -22
  47. package/dist/events/declared.d.ts +45 -0
  48. package/dist/events/declared.js +73 -0
  49. package/dist/events/echo-mismatch.d.ts +8 -2
  50. package/dist/events/echo-mismatch.js +33 -5
  51. package/dist/events/event-filters.d.ts +1 -1
  52. package/dist/events/event-filters.js +7 -4
  53. package/dist/events/predicate-eval.d.ts +17 -72
  54. package/dist/events/predicate-eval.js +117 -236
  55. package/dist/events/predicate-parse.js +31 -3
  56. package/dist/events/predicate-route.d.ts +32 -0
  57. package/dist/events/predicate-route.js +102 -0
  58. package/dist/events/predicate-schema.d.ts +99 -0
  59. package/dist/events/predicate-schema.js +309 -0
  60. package/dist/events/predicate.d.ts +8 -0
  61. package/dist/events/predicate.js +296 -22
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-classify.js +1 -1
  69. package/dist/flows/flow-replay-run.js +8 -1
  70. package/dist/flows/flow-tools.js +21 -5
  71. package/dist/flows/heal-run.js +1 -1
  72. package/dist/flows/replay.js +3 -0
  73. package/dist/honesty/accepted-write.d.ts +3 -0
  74. package/dist/honesty/accepted-write.js +8 -3
  75. package/dist/honesty/already-true.js +15 -0
  76. package/dist/honesty/blind-spots.js +10 -2
  77. package/dist/honesty/honesty.d.ts +11 -0
  78. package/dist/honesty/honesty.js +1 -0
  79. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  80. package/dist/honesty/uncaptured-bodies.js +9 -2
  81. package/dist/honesty/unread-outcome.d.ts +8 -0
  82. package/dist/honesty/unread-outcome.js +36 -9
  83. package/dist/honesty/unsettled.d.ts +49 -0
  84. package/dist/honesty/unsettled.js +82 -0
  85. package/dist/honesty/verified.d.ts +36 -3
  86. package/dist/honesty/verified.js +54 -11
  87. package/dist/http-server.d.ts +19 -0
  88. package/dist/http-server.js +202 -4
  89. package/dist/impact/impact-recorder.d.ts +23 -0
  90. package/dist/impact/impact-recorder.js +76 -0
  91. package/dist/impact/impact-store.d.ts +47 -0
  92. package/dist/impact/impact-store.js +171 -0
  93. package/dist/index.d.ts +7 -0
  94. package/dist/index.js +47 -10
  95. package/dist/init/agent-rules.d.ts +36 -0
  96. package/dist/init/agent-rules.js +132 -22
  97. package/dist/init/astro-patch.d.ts +2 -1
  98. package/dist/init/astro-patch.js +38 -6
  99. package/dist/init/capabilities.d.ts +22 -0
  100. package/dist/init/capabilities.js +76 -0
  101. package/dist/init/confirm.d.ts +114 -0
  102. package/dist/init/confirm.js +165 -0
  103. package/dist/init/connect-steps.d.ts +2 -0
  104. package/dist/init/connect-steps.js +50 -0
  105. package/dist/init/cra.js +3 -2
  106. package/dist/init/csp-check.d.ts +38 -0
  107. package/dist/init/csp-check.js +92 -0
  108. package/dist/init/csp-doctor.d.ts +25 -0
  109. package/dist/init/csp-doctor.js +49 -0
  110. package/dist/init/cursor.js +6 -9
  111. package/dist/init/detect.d.ts +8 -0
  112. package/dist/init/detect.js +33 -3
  113. package/dist/init/mcp-clients.d.ts +1 -0
  114. package/dist/init/mcp-clients.js +51 -7
  115. package/dist/init/mcp.d.ts +15 -0
  116. package/dist/init/mcp.js +19 -0
  117. package/dist/init/non-js-project.js +29 -5
  118. package/dist/init/plan-framework.d.ts +10 -0
  119. package/dist/init/plan-framework.js +78 -16
  120. package/dist/init/plan.d.ts +47 -6
  121. package/dist/init/plan.js +228 -69
  122. package/dist/init/run.d.ts +24 -2
  123. package/dist/init/run.js +157 -45
  124. package/dist/init/slash-command.d.ts +1 -1
  125. package/dist/init/slash-command.js +54 -6
  126. package/dist/init/snippets.d.ts +45 -5
  127. package/dist/init/snippets.js +194 -42
  128. package/dist/init/vite-config.js +53 -5
  129. package/dist/input/network-mock-tools.d.ts +19 -0
  130. package/dist/input/network-mock-tools.js +37 -5
  131. package/dist/input/scroll-find.d.ts +6 -0
  132. package/dist/input/scroll-find.js +17 -2
  133. package/dist/input/scroll-tools.js +4 -0
  134. package/dist/journal/attach-journal.d.ts +9 -1
  135. package/dist/journal/attach-journal.js +13 -2
  136. package/dist/journal/journal-query.d.ts +13 -0
  137. package/dist/journal/journal-query.js +28 -3
  138. package/dist/journal/workspace-gitignore.d.ts +10 -0
  139. package/dist/journal/workspace-gitignore.js +59 -0
  140. package/dist/license/license.js +1 -1
  141. package/dist/mcp/mcp-proxy.d.ts +35 -30
  142. package/dist/mcp/mcp-proxy.js +306 -104
  143. package/dist/mcp/mcp.d.ts +10 -1
  144. package/dist/mcp/mcp.js +70 -34
  145. package/dist/mcp/proxy-handshake.d.ts +82 -1
  146. package/dist/mcp/proxy-handshake.js +109 -2
  147. package/dist/mcp/proxy-lifecycle.d.ts +11 -2
  148. package/dist/mcp/proxy-lifecycle.js +12 -3
  149. package/dist/mcp/proxy-log.d.ts +63 -0
  150. package/dist/mcp/proxy-log.js +132 -0
  151. package/dist/mcp/server-instructions.d.ts +17 -0
  152. package/dist/mcp/server-instructions.js +56 -0
  153. package/dist/mcp/shared-params.d.ts +27 -0
  154. package/dist/mcp/shared-params.js +36 -0
  155. package/dist/pool/browser-pool.d.ts +22 -2
  156. package/dist/pool/browser-pool.js +44 -4
  157. package/dist/project/learned-routes.d.ts +24 -0
  158. package/dist/project/learned-routes.js +63 -0
  159. package/dist/project/project-store.d.ts +6 -0
  160. package/dist/project/project-store.js +25 -1
  161. package/dist/project/reticle-dir.d.ts +2 -0
  162. package/dist/project/reticle-dir.js +1 -0
  163. package/dist/session/connection-memory.d.ts +59 -0
  164. package/dist/session/connection-memory.js +108 -0
  165. package/dist/session/dev-command.d.ts +26 -0
  166. package/dist/session/dev-command.js +89 -0
  167. package/dist/session/dev-server-probe.d.ts +68 -0
  168. package/dist/session/dev-server-probe.js +102 -7
  169. package/dist/session/last-act.d.ts +26 -0
  170. package/dist/session/last-act.js +35 -1
  171. package/dist/session/live-control-tools.js +42 -5
  172. package/dist/session/no-session-diagnosis.d.ts +67 -2
  173. package/dist/session/no-session-diagnosis.js +236 -27
  174. package/dist/session/no-session-next-action.d.ts +46 -0
  175. package/dist/session/no-session-next-action.js +85 -0
  176. package/dist/session/no-session-watch.d.ts +40 -1
  177. package/dist/session/no-session-watch.js +174 -8
  178. package/dist/session/pending-commands.d.ts +12 -0
  179. package/dist/session/pending-commands.js +13 -1
  180. package/dist/session/ref-provenance.d.ts +54 -0
  181. package/dist/session/ref-provenance.js +61 -0
  182. package/dist/session/session-lease.js +1 -1
  183. package/dist/session/session-manager.d.ts +4 -0
  184. package/dist/session/session-manager.js +35 -5
  185. package/dist/session/session.d.ts +10 -1
  186. package/dist/session/session.js +146 -5
  187. package/dist/telemetry/app-instrumented.d.ts +8 -0
  188. package/dist/telemetry/app-instrumented.js +10 -0
  189. package/dist/telemetry/automation-hint.d.ts +17 -0
  190. package/dist/telemetry/automation-hint.js +71 -0
  191. package/dist/telemetry/bug-found.d.ts +15 -17
  192. package/dist/telemetry/bug-found.js +52 -21
  193. package/dist/telemetry/cli-telemetry.js +7 -1
  194. package/dist/telemetry/daemon-telemetry.js +24 -0
  195. package/dist/telemetry/feedback.js +1 -1
  196. package/dist/telemetry/init-telemetry.js +9 -1
  197. package/dist/telemetry/install-source.d.ts +56 -0
  198. package/dist/telemetry/install-source.js +74 -0
  199. package/dist/telemetry/instrumentation-stall.d.ts +50 -0
  200. package/dist/telemetry/instrumentation-stall.js +90 -0
  201. package/dist/telemetry/run-telemetry.js +4 -1
  202. package/dist/telemetry/session-metrics.d.ts +8 -0
  203. package/dist/telemetry/session-metrics.js +15 -0
  204. package/dist/telemetry/telemetry.d.ts +7 -1
  205. package/dist/telemetry/telemetry.js +26 -2
  206. package/dist/telemetry/tool-refused.d.ts +19 -0
  207. package/dist/telemetry/tool-refused.js +55 -0
  208. package/dist/telemetry/verification-of.js +46 -7
  209. package/dist/temp-dir.d.ts +27 -0
  210. package/dist/temp-dir.js +46 -0
  211. package/dist/tools/act-tools.js +209 -28
  212. package/dist/tools/assert-grade.d.ts +1 -1
  213. package/dist/tools/assert-grade.js +27 -5
  214. package/dist/tools/assert-verdict.js +49 -7
  215. package/dist/tools/browser-tools.js +10 -0
  216. package/dist/tools/contract-tools.js +4 -0
  217. package/dist/tools/dynamic-tools.js +16 -7
  218. package/dist/tools/error-recovery.d.ts +10 -0
  219. package/dist/tools/error-recovery.js +73 -9
  220. package/dist/tools/feedback-invite.js +1 -1
  221. package/dist/tools/invoke-tool.js +159 -26
  222. package/dist/tools/lease-hint.d.ts +30 -8
  223. package/dist/tools/lease-hint.js +65 -14
  224. package/dist/tools/lease-tools.d.ts +40 -1
  225. package/dist/tools/lease-tools.js +181 -83
  226. package/dist/tools/navigate-result.js +1 -1
  227. package/dist/tools/observe-tools.js +10 -4
  228. package/dist/tools/observed-nothing.js +1 -1
  229. package/dist/tools/react-grace.d.ts +52 -0
  230. package/dist/tools/react-grace.js +90 -0
  231. package/dist/tools/read-tools.js +67 -5
  232. package/dist/tools/resolve-target.d.ts +33 -0
  233. package/dist/tools/resolve-target.js +53 -0
  234. package/dist/tools/settle-in-flight.d.ts +94 -0
  235. package/dist/tools/settle-in-flight.js +146 -0
  236. package/dist/tools/snapshot-delta.d.ts +10 -0
  237. package/dist/tools/snapshot-delta.js +194 -9
  238. package/dist/tools/tool-kit.d.ts +8 -0
  239. package/dist/tools/tool-surface.d.ts +60 -0
  240. package/dist/tools/tool-surface.js +86 -1
  241. package/dist/tools/tools.js +61 -8
  242. package/dist/tools/unadvertised-help.js +9 -6
  243. package/docs/README.md +55 -11
  244. package/docs/actions.mdx +120 -0
  245. package/docs/agent-cheatsheet.md +95 -29
  246. package/docs/architecture.md +52 -35
  247. package/docs/benchmarks.md +127 -75
  248. package/docs/best-practices.mdx +216 -0
  249. package/docs/capabilities.mdx +534 -0
  250. package/docs/cli/affected.mdx +53 -0
  251. package/docs/cli/capsules.mdx +70 -0
  252. package/docs/cli/cloud.mdx +153 -0
  253. package/docs/cli/doctor.mdx +109 -0
  254. package/docs/cli/drive.mdx +74 -0
  255. package/docs/cli/feedback.mdx +67 -0
  256. package/docs/cli/gate.mdx +64 -0
  257. package/docs/cli/help.mdx +94 -0
  258. package/docs/cli/hunt.mdx +73 -0
  259. package/docs/cli/identify.mdx +76 -0
  260. package/docs/cli/init.mdx +121 -0
  261. package/docs/cli/kill.mdx +91 -0
  262. package/docs/cli/license.mdx +48 -0
  263. package/docs/cli/mcp.mdx +61 -0
  264. package/docs/cli/open.mdx +74 -0
  265. package/docs/cli/restart.mdx +60 -0
  266. package/docs/cli/rollback.mdx +35 -0
  267. package/docs/cli/serve.mdx +69 -0
  268. package/docs/cli/status.mdx +88 -0
  269. package/docs/cli/stop.mdx +64 -0
  270. package/docs/cli/telemetry.mdx +57 -0
  271. package/docs/cli/update.mdx +52 -0
  272. package/docs/cli/verify.mdx +76 -0
  273. package/docs/cli/version.mdx +39 -0
  274. package/docs/cli/watch.mdx +62 -0
  275. package/docs/cli.mdx +241 -0
  276. package/docs/debugging.md +16 -10
  277. package/docs/deploy-checks.md +45 -12
  278. package/docs/desktop.mdx +384 -0
  279. package/docs/docs.json +364 -0
  280. package/docs/enterprise.md +22 -16
  281. package/docs/faq.mdx +102 -0
  282. package/docs/fixtures.md +13 -7
  283. package/docs/flows.md +29 -23
  284. package/docs/for-agents.md +71 -0
  285. package/docs/frameworks.mdx +238 -0
  286. package/docs/gate-plan.md +48 -42
  287. package/docs/gates.md +28 -22
  288. package/docs/getting-started.md +129 -77
  289. package/docs/human-control.md +24 -18
  290. package/docs/index.mdx +143 -0
  291. package/docs/install-agentic.mdx +224 -0
  292. package/docs/install-manual.mdx +196 -0
  293. package/docs/instrumentation.mdx +238 -0
  294. package/docs/integration-patterns.md +41 -35
  295. package/docs/local-registry.md +19 -13
  296. package/docs/multi-agent-testing.md +19 -13
  297. package/docs/packages/babel-plugin.mdx +79 -0
  298. package/docs/packages/browser.mdx +111 -0
  299. package/docs/packages/core.mdx +116 -0
  300. package/docs/packages/electron.mdx +85 -0
  301. package/docs/packages/eslint-plugin.mdx +86 -0
  302. package/docs/packages/next.mdx +89 -0
  303. package/docs/packages/react.mdx +91 -0
  304. package/docs/packages/server.mdx +98 -0
  305. package/docs/packages/tauri.mdx +63 -0
  306. package/docs/packages/test.mdx +119 -0
  307. package/docs/packages/vite-plugin.mdx +94 -0
  308. package/docs/packages.mdx +218 -0
  309. package/docs/platform-integration.md +35 -26
  310. package/docs/predicates.mdx +248 -0
  311. package/docs/quickstart.mdx +257 -0
  312. package/docs/recipes.mdx +282 -0
  313. package/docs/skill-file.mdx +107 -0
  314. package/docs/state-management.mdx +199 -0
  315. package/docs/style.css +14 -0
  316. package/docs/system-map.md +50 -33
  317. package/docs/telemetry-contract.md +106 -52
  318. package/docs/telemetry-events.mdx +475 -0
  319. package/docs/telemetry.md +63 -55
  320. package/docs/testing.md +44 -6
  321. package/docs/token-efficiency.md +37 -27
  322. package/docs/tools-act-and-wait.mdx +173 -0
  323. package/docs/tools-act-sequence.mdx +137 -0
  324. package/docs/tools-act.mdx +95 -0
  325. package/docs/tools-assert.mdx +129 -0
  326. package/docs/tools-console.mdx +63 -0
  327. package/docs/tools-inspect.mdx +88 -0
  328. package/docs/tools-navigate.mdx +99 -0
  329. package/docs/tools-network.mdx +119 -0
  330. package/docs/tools-observe.mdx +145 -0
  331. package/docs/tools-overview.mdx +160 -0
  332. package/docs/tools-query.mdx +126 -0
  333. package/docs/tools-session-and-feedback.mdx +112 -0
  334. package/docs/tools-sessions.mdx +94 -0
  335. package/docs/tools-snapshot.mdx +129 -0
  336. package/docs/tools-state.mdx +104 -0
  337. package/docs/tools-tools-and-run.mdx +156 -0
  338. package/docs/tools-wait-for.mdx +85 -0
  339. package/docs/troubleshooting.mdx +263 -0
  340. package/docs/usage.md +209 -165
  341. package/docs/vs-chrome-devtools-mcp.mdx +123 -0
  342. package/docs/vs-playwright-mcp.mdx +125 -0
  343. package/docs/vs-screenshots.mdx +137 -0
  344. package/docs/what-is-reticle.mdx +63 -0
  345. package/docs/why-reticle.mdx +109 -0
  346. package/package.json +5 -5
  347. package/docs/desktop-apps.md +0 -256
  348. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  349. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  350. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  351. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  352. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  353. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  354. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  355. package/docs/matrix/MATRIX.md +0 -26
  356. package/docs/matrix/README.md +0 -59
  357. package/docs/mint.json +0 -61
package/dist/init/plan.js CHANGED
@@ -8,9 +8,12 @@ import { claudeAddCommand, mcpManual } from './mcp.js';
8
8
  import { mergeCursorConfig, CursorMergeStatus, cursorServerEntry } from './cursor.js';
9
9
  import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, } from './mcp-clients.js';
10
10
  import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, SLASH_COMMAND_SIGNATURE, } from './slash-command.js';
11
- import { mergeMarkedInstruction, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
12
- import { viteSteps, nextSteps, craSteps, svelteKitSteps, nuxtSteps, astroSteps, VITE_PLUGIN_DETAIL, } from './plan-framework.js';
11
+ import { mergeMarkedInstruction, reticleMdFile, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, RETICLE_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
12
+ import { viteSteps, nextSteps, craSteps, svelteKitSteps, nuxtSteps, astroSteps, cspStep, VITE_PLUGIN_DETAIL, } from './plan-framework.js';
13
+ import { join } from 'node:path';
13
14
  import { htmlManual, reticleConfigContent, unverifiedUiLibraryNote } from './snippets.js';
15
+ import { declaredInstallSource } from '../telemetry/install-source.js';
16
+ import { devServerPortWarning, isLikelyDevServerPort } from '../cli/cli-port.js';
14
17
  // An app dev installs exactly the audience-scoped browser-side dependencies — never the retired
15
18
  // `@reticlehq/core` umbrella (which dragged the Node MCP server + ws into every app). The kit is the
16
19
  // framework adapter (it re-exports the browser sensor), paired with that framework's dev-only build
@@ -34,15 +37,40 @@ export function pinnedPackages(packages, version) {
34
37
  return packages;
35
38
  return packages.map((p) => `${p}@${version}`);
36
39
  }
37
- /** The dev-dependencies `reticle init` installs for a given framework — kit first, build plugin next. */
38
- export function frameworkPackages(framework) {
40
+ /**
41
+ * Does this codebase want the React kit, or the framework-neutral sensor?
42
+ *
43
+ * The kit is what adds component identity — component names and stacks — and it is worth having
44
+ * wherever React or Preact is rendering (the adapter reaches Preact through `preact/compat`).
45
+ * Everywhere else it is a package named `@reticlehq/react`, carrying `react` in its peer
46
+ * dependencies, being installed into a codebase that has no React in it.
47
+ *
48
+ * UNKNOWN keeps the kit deliberately. Absence of evidence is not evidence of Vue, and guessing
49
+ * "sensor" on no information silently drops component identity from apps that should have it.
50
+ */
51
+ function wantsReactKit(ui) {
52
+ return ui !== UiLibrary.VUE && ui !== UiLibrary.SVELTE;
53
+ }
54
+ /**
55
+ * The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
56
+ *
57
+ * `uiLibrary` matters because the framework does not always name the renderer. The rule below was
58
+ * already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
59
+ * `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
60
+ * right after `init` had detected the real UI library and said so on screen.
61
+ */
62
+ export function frameworkPackages(framework, uiLibrary = UiLibrary.UNKNOWN) {
63
+ const kit = wantsReactKit(uiLibrary) ? RETICLE_REACT_KIT : RETICLE_BROWSER_SDK;
39
64
  switch (framework) {
40
65
  case Framework.NEXT:
66
+ // Next is React by construction, so the detection cannot disagree in a way worth honouring.
41
67
  return [RETICLE_REACT_KIT, RETICLE_NEXT_PLUGIN];
42
68
  case Framework.VITE:
43
69
  case Framework.SVELTEKIT:
44
70
  // SvelteKit builds on Vite; until a dedicated Svelte kit exists it uses the Vite build plugin.
45
- return [RETICLE_REACT_KIT, RETICLE_VITE_PLUGIN];
71
+ // The build plugin stamps `data-reticle-source` regardless of UI library, so a Vue or Svelte
72
+ // app still gets source pointers — it is only component identity that needs the React kit.
73
+ return [kit, RETICLE_VITE_PLUGIN];
46
74
  case Framework.NUXT:
47
75
  // The framework-neutral sensor, NOT the React kit. Nuxt renders Vue, and installing a package
48
76
  // named @reticlehq/react — with `react` in its peer dependencies — into a Vue codebase is the
@@ -51,43 +79,21 @@ export function frameworkPackages(framework) {
51
79
  case Framework.ASTRO:
52
80
  // Astro owns its own Vite instance and renders its own HTML, so there is no config for the
53
81
  // plugin to attach to — the kit alone, connected from a page <script> (see astroManual).
54
- return [RETICLE_REACT_KIT];
82
+ return [kit];
55
83
  case Framework.CRA:
56
84
  // react-scripts owns its webpack config and cannot be extended without ejecting, so there is
57
85
  // no build plugin — the kit alone, imported from src/index.tsx (see cra.ts).
58
86
  return [RETICLE_REACT_KIT];
59
87
  case Framework.HTML:
60
88
  // No bundler plugin to install — just the kit; connect is wired by hand (see htmlManual).
61
- return [RETICLE_REACT_KIT];
89
+ return [kit];
62
90
  }
63
91
  }
64
92
  /** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
65
93
  export const MCP_TARGET = 'global (claude user scope)';
66
- /**
67
- * Titles of the steps WITHOUT which no session ever appears.
68
- *
69
- * A ⚠ on one of these is not a warning, it is a guaranteed failure: nothing performs the manual step,
70
- * so the app will not connect and every Reticle tool will answer "no browser session connected".
71
- * Reported from a field sweep, where the ⚠ count and "did it connect" were treated as independent
72
- * signals and are not.
73
- */
74
- const CONNECT_STEP_TITLES = new Set([
75
- 'Connect snippet',
76
- 'Connect snippet (CRA)',
77
- 'Connect snippet (Astro)',
78
- 'Connect snippet (Nuxt)',
79
- 'Reticle client hook',
80
- 'Reticle connect module',
81
- 'ReticleDev component',
82
- 'Vite plugin',
83
- ]);
84
- /** True when this step is what makes the app dial the daemon. */
85
- export function isConnectStep(title) {
86
- return CONNECT_STEP_TITLES.has(title);
87
- }
88
94
  /** The step that runs the package manager — the other thing that commonly fails on a user's machine. */
89
95
  export const DEPS_TARGET = 'package.json';
90
- const RETICLE_CONFIG_FILE = '.reticle.json';
96
+ export const RETICLE_CONFIG_FILE = '.reticle.json';
91
97
  export const StepStatus = {
92
98
  APPLY: 'apply',
93
99
  MANUAL: 'manual',
@@ -101,6 +107,19 @@ export const StepStatus = {
101
107
  */
102
108
  NOTICE: 'notice',
103
109
  };
110
+ /**
111
+ * Where the agent-facing files go, which is not always where the app is.
112
+ *
113
+ * Reported from the field (#318): a repo with its app at `src/admin` got `.claude/commands/reticle.md`
114
+ * written into the app directory, while the human's agent session runs at the repo root — so
115
+ * `/reticle` did not exist for them at all until they copied it up by hand. The rule and command
116
+ * files are read by the AGENT, so they belong where the agent stands; the app files stay with the
117
+ * app. The two are the same directory in a single-package repo, which is why this went unnoticed.
118
+ */
119
+ function agentFile(input, relPath) {
120
+ const root = input.agentFileRoot;
121
+ return root === undefined || 0 === root.length ? relPath : join(root, relPath);
122
+ }
104
123
  const CLAUDE_MCP_TITLE = 'MCP server (Claude, global)';
105
124
  const CURSOR_MCP_TITLE = 'MCP server (Cursor, global)';
106
125
  function claudeMcpStep(input) {
@@ -267,32 +286,35 @@ function commandStepFor(path, present, content) {
267
286
  };
268
287
  }
269
288
  function claudeCommandStep(input) {
270
- return commandStepFor(CLAUDE_COMMAND_PATH, input.claudeCli, input.claudeCommandContent);
289
+ return commandStepFor(agentFile(input, CLAUDE_COMMAND_PATH), input.claudeCli, input.claudeCommandContent);
271
290
  }
272
291
  function cursorCommandStep(input) {
273
292
  const present = true === input.cursorProjectPresent || (input.cursorPresent && !input.claudeCli);
274
- return commandStepFor(CURSOR_COMMAND_PATH, present, input.cursorCommandContent);
293
+ return commandStepFor(agentFile(input, CURSOR_COMMAND_PATH), present, input.cursorCommandContent);
275
294
  }
276
295
  const AGENT_RULE_TITLE = 'Agent verification rule';
277
296
  const AGENT_RULE_DETAIL = 'teach the agent to verify features with Reticle after building them';
297
+ const RETICLE_MD_TITLE = 'Full agent rules';
298
+ const RETICLE_MD_DETAIL = 'the reference the always-loaded rule points at (when NOT to verify, recovery, feedback)';
278
299
  function claudeRuleStep(input) {
279
300
  if (!input.claudeCli)
280
301
  return null;
302
+ const path = agentFile(input, CLAUDE_MD_PATH);
281
303
  const r = mergeMarkedInstruction(input.claudeMdContent);
282
304
  if (r.status === AgentRuleStatus.ALREADY) {
283
305
  return {
284
306
  title: AGENT_RULE_TITLE,
285
- target: CLAUDE_MD_PATH,
307
+ target: path,
286
308
  status: StepStatus.ALREADY,
287
309
  detail: 'Reticle rule already in CLAUDE.md',
288
310
  };
289
311
  }
290
312
  return {
291
313
  title: AGENT_RULE_TITLE,
292
- target: CLAUDE_MD_PATH,
314
+ target: path,
293
315
  status: StepStatus.APPLY,
294
316
  detail: AGENT_RULE_DETAIL,
295
- write: { path: CLAUDE_MD_PATH, content: r.content },
317
+ write: { path, content: r.content },
296
318
  };
297
319
  }
298
320
  /**
@@ -308,20 +330,21 @@ function cursorRuleStep(input) {
308
330
  return null;
309
331
  // The whole file is Reticle's — init created it — so a stale one is REWRITTEN rather than merged.
310
332
  // Comparing content is what makes the rule updatable; comparing existence made it permanent.
333
+ const path = agentFile(input, CURSOR_RULE_PATH);
311
334
  if (input.cursorRuleContent === cursorRuleFile()) {
312
335
  return {
313
336
  title: AGENT_RULE_TITLE,
314
- target: CURSOR_RULE_PATH,
337
+ target: path,
315
338
  status: StepStatus.ALREADY,
316
339
  detail: 'Reticle rule already in .cursor/rules',
317
340
  };
318
341
  }
319
342
  return {
320
343
  title: AGENT_RULE_TITLE,
321
- target: CURSOR_RULE_PATH,
344
+ target: path,
322
345
  status: StepStatus.APPLY,
323
346
  detail: AGENT_RULE_DETAIL,
324
- write: { path: CURSOR_RULE_PATH, content: cursorRuleFile() },
347
+ write: { path, content: cursorRuleFile() },
325
348
  };
326
349
  }
327
350
  /**
@@ -365,33 +388,70 @@ function slashCommandSteps(input) {
365
388
  return stepsForAgents(input, (a) => a.commandStep);
366
389
  }
367
390
  /**
368
- * The behavioral rule that makes the agent actually USE Reticle. Written into the detected agent's
369
- * instruction file (Claude / Cursor, or both), falling back to the cross-agent AGENTS.md when neither
370
- * is detected. Rides with the MCP wiring — `--no-mcp` opts out of registering the tools AND the rule.
391
+ * `AGENTS.md`, always. It is the file every agent that is not Claude or Cursor reads.
392
+ *
393
+ * This used to be a FALLBACK, written only when no agent was detected. So a repo set up on a machine
394
+ * with Claude Code got `CLAUDE.md` and nothing else, and the next person to open it with Codex,
395
+ * Copilot, Amp, Gemini or any other agent found a fully instrumented app whose rules were addressed
396
+ * to somebody else's tool. The detection tells you which agent is running the install; it says
397
+ * nothing about which agents will open the repository afterwards.
398
+ */
399
+ function agentsMdStep(input) {
400
+ const r = mergeMarkedInstruction(input.agentsMdContent);
401
+ const path = agentFile(input, AGENTS_MD_PATH);
402
+ if (r.status === AgentRuleStatus.ALREADY) {
403
+ return {
404
+ title: AGENT_RULE_TITLE,
405
+ target: path,
406
+ status: StepStatus.ALREADY,
407
+ detail: 'Reticle rule already in AGENTS.md',
408
+ };
409
+ }
410
+ return {
411
+ title: AGENT_RULE_TITLE,
412
+ target: path,
413
+ status: StepStatus.APPLY,
414
+ detail: AGENT_RULE_DETAIL,
415
+ write: { path, content: r.content },
416
+ };
417
+ }
418
+ /**
419
+ * `RETICLE.md`, the reference the always-loaded blocks point at.
420
+ *
421
+ * Written whole rather than merged, because `init` owns the entire file: there is no user content to
422
+ * preserve, and the marker dance exists only for files somebody else already writes in.
423
+ */
424
+ function reticleMdStep(input) {
425
+ const path = agentFile(input, RETICLE_MD_PATH);
426
+ const content = reticleMdFile();
427
+ if (input.reticleMdContent === content) {
428
+ return {
429
+ title: RETICLE_MD_TITLE,
430
+ target: path,
431
+ status: StepStatus.ALREADY,
432
+ detail: 'full rules already current',
433
+ };
434
+ }
435
+ return {
436
+ title: RETICLE_MD_TITLE,
437
+ target: path,
438
+ status: StepStatus.APPLY,
439
+ detail: RETICLE_MD_DETAIL,
440
+ write: { path, content },
441
+ };
442
+ }
443
+ /**
444
+ * The behavioral rules that make the agent actually USE Reticle, and know when not to.
445
+ *
446
+ * Every detected agent's own file, plus `AGENTS.md` for the ones that will open this repo later, plus
447
+ * `RETICLE.md` holding the reference half. Rides with the MCP wiring: `--no-mcp` opts out of
448
+ * registering the tools AND of every rule file, because rules for tools the agent cannot reach are
449
+ * noise.
371
450
  */
372
451
  function agentRuleSteps(input) {
373
452
  if (!input.options.mcp)
374
453
  return [];
375
- const detected = stepsForAgents(input, (a) => a.ruleStep);
376
- if (detected.length > 0)
377
- return detected;
378
- const r = mergeMarkedInstruction(input.agentsMdContent);
379
- return [
380
- r.status === AgentRuleStatus.ALREADY
381
- ? {
382
- title: AGENT_RULE_TITLE,
383
- target: AGENTS_MD_PATH,
384
- status: StepStatus.ALREADY,
385
- detail: 'Reticle rule already in AGENTS.md',
386
- }
387
- : {
388
- title: AGENT_RULE_TITLE,
389
- target: AGENTS_MD_PATH,
390
- status: StepStatus.APPLY,
391
- detail: AGENT_RULE_DETAIL,
392
- write: { path: AGENTS_MD_PATH, content: r.content },
393
- },
394
- ];
454
+ return [...stepsForAgents(input, (a) => a.ruleStep), agentsMdStep(input), reticleMdStep(input)];
395
455
  }
396
456
  /**
397
457
  * What to say when the install command fails.
@@ -439,7 +499,7 @@ function installFailureHint(pm) {
439
499
  }
440
500
  function installStep(input) {
441
501
  const pm = input.detection.packageManager;
442
- const packages = pinnedPackages(frameworkPackages(input.detection.framework), input.options.sdkVersion);
502
+ const packages = pinnedPackages(frameworkPackages(input.detection.framework, input.detection.uiLibrary), input.options.sdkVersion);
443
503
  const command = installCommand(pm, packages);
444
504
  if (!input.options.install) {
445
505
  return {
@@ -463,29 +523,127 @@ function installStep(input) {
463
523
  // Unpinned. pnpm resolves the newest MATURE version there, which is how a project with a
464
524
  // release-age hold gets a working install instead of no install.
465
525
  retry: {
466
- ...installCommandParts(pm, frameworkPackages(input.detection.framework)),
526
+ ...installCommandParts(pm, frameworkPackages(input.detection.framework, input.detection.uiLibrary)),
467
527
  note: unpinnedRetryNote(input.options.sdkVersion, pm),
468
528
  },
469
529
  };
470
530
  }
471
- function reticleConfigStep(input) {
531
+ /**
532
+ * What is wrong with the config that is already there, if anything.
533
+ *
534
+ * Reported from the field (#317): a project carried `"port": 3000` — its own dev-server port, which
535
+ * is the confusion SKILL.md names as the top setup failure — and `init` printed `.reticle.json
536
+ * already exists` on every re-run without reading a single field of it, so the file could never be
537
+ * repaired by the command that wrote it. It stayed invisible because a daemon on `127.0.0.1:3000`
538
+ * and Vite on `[::1]:3000` split the port by address family and neither reported a conflict.
539
+ *
540
+ * Narrower than the issue asked for on purpose: `init`'s IO surface is synchronous, so this cannot
541
+ * probe what is LISTENING right now. `isLikelyDevServerPort` covers the framework defaults, which is
542
+ * where the mistake is actually made.
543
+ */
544
+ function existingConfigProblem(source) {
545
+ if (null === source || source === undefined)
546
+ return undefined;
547
+ let parsed;
548
+ try {
549
+ parsed = JSON.parse(source);
550
+ }
551
+ catch {
552
+ return (`${RETICLE_CONFIG_FILE} is present but is not valid JSON, so nothing reads it — neither the ` +
553
+ 'daemon nor the SDK. Delete it and re-run `reticle init`.');
554
+ }
555
+ if ('object' !== typeof parsed || null === parsed)
556
+ return undefined;
557
+ const port = parsed['port'];
558
+ if ('number' !== typeof port || !isLikelyDevServerPort(port))
559
+ return undefined;
560
+ return `${devServerPortWarning(port)} Fix it by removing the "port" field from ${RETICLE_CONFIG_FILE}.`;
561
+ }
562
+ const RETICLE_CONFIG_TITLE = 'Reticle config';
563
+ const AGENT_ROOT_CONFIG_TITLE = 'Reticle config (agent root)';
564
+ /**
565
+ * The SECOND `.reticle.json`, in the directory the agent runs from.
566
+ *
567
+ * `.reticle.json` is not app wiring — nothing in the app reads it. The CLI and `reticle mcp` read it
568
+ * from their own CWD (see cli-port), and that CWD is the repo root, not the app directory a redirect
569
+ * wired. Reported from the field: an app in `frontend/` on a non-default bridge port left the root
570
+ * with no config at all, so `reticle mcp` fell back to 4400 — which on that machine was ANOTHER
571
+ * project's daemon, and `reticle_sessions` would have listed a different app's tabs while the wired
572
+ * app sat unseen. Nothing in the report said so; every step was green.
573
+ *
574
+ * Written in BOTH places rather than moved: a human standing in the app runs `reticle status` there,
575
+ * and the two files are byte-identical, so neither can disagree with the other about the port or the
576
+ * project identity.
577
+ */
578
+ function agentRootConfigStep(input, content) {
579
+ const root = input.agentFileRoot;
580
+ if (root === undefined || 0 === root.length)
581
+ return [];
582
+ const path = agentFile(input, RETICLE_CONFIG_FILE);
583
+ if (input.agentRootConfigSource === content) {
584
+ return [
585
+ {
586
+ title: AGENT_ROOT_CONFIG_TITLE,
587
+ target: path,
588
+ status: StepStatus.ALREADY,
589
+ detail: 'the agent already reads the same project config',
590
+ },
591
+ ];
592
+ }
593
+ return [
594
+ {
595
+ title: AGENT_ROOT_CONFIG_TITLE,
596
+ target: path,
597
+ status: StepStatus.APPLY,
598
+ detail: 'the same config where the agent runs — `reticle mcp` reads it from ITS cwd, and without ' +
599
+ 'it the agent talks to whatever daemon is on the default port',
600
+ write: { path, content },
601
+ },
602
+ ];
603
+ }
604
+ function reticleConfigStep(input, content) {
472
605
  if (true === input.reticleConfigExists) {
606
+ const problem = existingConfigProblem(input.reticleConfigSource);
607
+ if (problem !== undefined) {
608
+ // `ℹ`, not `·`: the step is done and something about the result still stops things working,
609
+ // which is the one mark that says "there is something here to read".
610
+ return {
611
+ title: RETICLE_CONFIG_TITLE,
612
+ target: RETICLE_CONFIG_FILE,
613
+ status: StepStatus.NOTICE,
614
+ detail: problem,
615
+ };
616
+ }
473
617
  return {
474
- title: 'Reticle config',
618
+ title: RETICLE_CONFIG_TITLE,
475
619
  target: RETICLE_CONFIG_FILE,
476
620
  status: StepStatus.ALREADY,
477
621
  detail: '.reticle.json already exists',
478
622
  };
479
623
  }
480
- const content = reticleConfigContent(input.detection.framework, input.options.port, input.options.projectId);
481
624
  return {
482
- title: 'Reticle config',
625
+ title: RETICLE_CONFIG_TITLE,
483
626
  target: RETICLE_CONFIG_FILE,
484
627
  status: StepStatus.APPLY,
485
628
  detail: 'write project config (framework + port)',
486
629
  write: { path: RETICLE_CONFIG_FILE, content },
487
630
  };
488
631
  }
632
+ /**
633
+ * The project config, in every directory that has to read it: the app's, and — after a redirect —
634
+ * the one the agent runs from. One content string for both, so they cannot disagree; an existing
635
+ * app-side config wins over a freshly derived one, or a re-run would copy a DIFFERENT identity up.
636
+ */
637
+ function reticleConfigSteps(input) {
638
+ const existing = input.reticleConfigSource;
639
+ const content = true === input.reticleConfigExists && null !== existing && existing !== undefined
640
+ ? existing
641
+ : reticleConfigContent(input.detection.framework, input.options.port, input.options.projectId,
642
+ // Only when it is actually known. Writing `unknown` would be indistinguishable from a
643
+ // config written before this field existed, and the two mean different things.
644
+ declaredInstallSource());
645
+ return [reticleConfigStep(input, content), ...agentRootConfigStep(input, content)];
646
+ }
489
647
  /**
490
648
  * A step that says out loud when the app isn't React. SvelteKit already carries its own unverified
491
649
  * note, so it isn't doubled up here.
@@ -513,12 +671,13 @@ function uiLibraryStep(input) {
513
671
  }
514
672
  export function buildPlan(input) {
515
673
  const steps = [
674
+ ...cspStep(input),
516
675
  ...mcpSteps(input),
517
676
  ...agentRuleSteps(input),
518
677
  ...slashCommandSteps(input),
519
678
  ...uiLibraryStep(input),
520
679
  installStep(input),
521
- reticleConfigStep(input),
680
+ ...reticleConfigSteps(input),
522
681
  ];
523
682
  if (input.detection.framework === Framework.VITE) {
524
683
  steps.push(...viteSteps(input));
@@ -550,5 +709,5 @@ export function buildPlan(input) {
550
709
  detail: htmlManual(input.options.port, input.options.projectId, input.pairingToken),
551
710
  });
552
711
  }
553
- return { framework: input.detection.framework, steps };
712
+ return { framework: input.detection.framework, uiLibrary: input.detection.uiLibrary, steps };
554
713
  }
@@ -3,6 +3,7 @@
3
3
  * plan (pure), optionally write the apply-steps, and print a human-readable report. All filesystem
4
4
  * access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
5
5
  */
6
+ import type { InitOutcome } from '@reticlehq/core';
6
7
  /**
7
8
  * Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
8
9
  * otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
@@ -29,6 +30,23 @@ export interface InitOptions {
29
30
  app?: string;
30
31
  /** Set on the recursive call after a workspace redirect, so the search happens at most once. */
31
32
  redirected?: boolean;
33
+ /**
34
+ * Where the human ran the command, carried across a workspace redirect.
35
+ *
36
+ * The agent rule and `/reticle` command files are read by the AGENT, whose session runs where the
37
+ * human started it — the repo root. Writing them beside the app instead is how a repo with its app
38
+ * at `src/admin` ended up with no `/reticle` at all (#318).
39
+ */
40
+ agentRoot?: string;
41
+ /**
42
+ * Hand the telemetry outcome back rather than emitting it here.
43
+ *
44
+ * `init` now stays to watch for an app to connect, and whether it saw one belongs on the SAME
45
+ * `init_completed` event — the funnel it exists to measure would be double-counted by a second
46
+ * emit and unjoinable as a second event kind. Only the CLI sets this; every other caller keeps
47
+ * today's fire-and-forget behaviour, so no path loses its event by forgetting to report.
48
+ */
49
+ deferOutcome?: boolean;
32
50
  }
33
51
  export interface InitIo {
34
52
  /** Returns file content or null if it does not exist. Path is project-relative or absolute. */
@@ -52,10 +70,15 @@ export interface InitIo {
52
70
  probe(command: string, args: readonly string[]): boolean;
53
71
  print(line: string): void;
54
72
  }
55
- interface InitResult {
73
+ export interface InitResult {
56
74
  ok: boolean;
57
75
  applied: number;
58
76
  manual: number;
77
+ /**
78
+ * The event body this run would report, handed to the caller instead of emitted, when
79
+ * `deferOutcome` is set. Absent on a dry run and on the exits that report for themselves.
80
+ */
81
+ outcome?: InitOutcome;
59
82
  }
60
83
  /**
61
84
  * App directories under a workspace root.
@@ -77,4 +100,3 @@ export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile'
77
100
  */
78
101
  export declare const FEEDBACK_HINT: string;
79
102
  export declare function runInit(options: InitOptions, io: InitIo): InitResult;
80
- export {};