@reticlehq/server 2.6.0 → 2.8.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 (276) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +251 -33
  3. package/dist/bridge/bridge.d.ts +6 -1
  4. package/dist/bridge/bridge.js +27 -4
  5. package/dist/capsule/causal-summary.d.ts +20 -0
  6. package/dist/capsule/causal-summary.js +31 -6
  7. package/dist/capsule/divergence.js +7 -1
  8. package/dist/cli/chromium-hint.d.ts +68 -0
  9. package/dist/cli/chromium-hint.js +145 -0
  10. package/dist/cli/cli-doctor.js +31 -36
  11. package/dist/cli/cli-kill.d.ts +58 -0
  12. package/dist/cli/cli-kill.js +162 -0
  13. package/dist/cli/cli-launch.d.ts +18 -0
  14. package/dist/cli/cli-launch.js +32 -1
  15. package/dist/cli/cli-parse.d.ts +16 -1
  16. package/dist/cli/cli-parse.js +32 -9
  17. package/dist/cli/cli-port.d.ts +8 -0
  18. package/dist/cli/cli-port.js +36 -1
  19. package/dist/cli/cli-verify.js +2 -2
  20. package/dist/cli/doctor-daemon-line.js +10 -1
  21. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  22. package/dist/cli/doctor-sessions-line.js +66 -0
  23. package/dist/cli/port-holder.d.ts +10 -0
  24. package/dist/cli/port-holder.js +26 -2
  25. package/dist/cli.js +139 -23
  26. package/dist/crawl/crawl-tools.js +12 -2
  27. package/dist/crawl/crawl.d.ts +13 -0
  28. package/dist/crawl/crawl.js +50 -2
  29. package/dist/daemon/daemon-resilience.js +2 -2
  30. package/dist/daemon/loopback-alias.d.ts +32 -0
  31. package/dist/daemon/loopback-alias.js +80 -0
  32. package/dist/events/contradictions.d.ts +14 -0
  33. package/dist/events/contradictions.js +26 -17
  34. package/dist/events/predicate-eval.d.ts +26 -3
  35. package/dist/events/predicate-eval.js +284 -95
  36. package/dist/events/predicate-parse.d.ts +1 -1
  37. package/dist/events/predicate-parse.js +18 -4
  38. package/dist/events/predicate.js +170 -18
  39. package/dist/events/ring-buffer.d.ts +8 -0
  40. package/dist/events/ring-buffer.js +34 -1
  41. package/dist/flows/flow-classify.js +1 -1
  42. package/dist/flows/flow-tools.js +12 -4
  43. package/dist/flows/heal-run.js +1 -1
  44. package/dist/honesty/blind-spots.js +6 -2
  45. package/dist/honesty/honesty.d.ts +11 -0
  46. package/dist/honesty/honesty.js +9 -1
  47. package/dist/honesty/unsettled.d.ts +40 -0
  48. package/dist/honesty/unsettled.js +78 -0
  49. package/dist/honesty/verified.d.ts +7 -0
  50. package/dist/honesty/verified.js +27 -4
  51. package/dist/http-server.js +11 -1
  52. package/dist/index.js +29 -2
  53. package/dist/init/agent-rules.d.ts +16 -0
  54. package/dist/init/agent-rules.js +96 -17
  55. package/dist/init/astro-patch.d.ts +2 -1
  56. package/dist/init/astro-patch.js +11 -5
  57. package/dist/init/capabilities.d.ts +22 -0
  58. package/dist/init/capabilities.js +76 -0
  59. package/dist/init/confirm.d.ts +96 -0
  60. package/dist/init/confirm.js +138 -0
  61. package/dist/init/cra.js +3 -2
  62. package/dist/init/detect.d.ts +8 -0
  63. package/dist/init/detect.js +15 -0
  64. package/dist/init/non-js-project.d.ts +27 -0
  65. package/dist/init/non-js-project.js +84 -0
  66. package/dist/init/plan-framework.d.ts +9 -0
  67. package/dist/init/plan-framework.js +72 -16
  68. package/dist/init/plan.d.ts +40 -4
  69. package/dist/init/plan.js +183 -43
  70. package/dist/init/run.d.ts +24 -2
  71. package/dist/init/run.js +181 -46
  72. package/dist/init/slash-command.d.ts +1 -1
  73. package/dist/init/slash-command.js +46 -6
  74. package/dist/init/snippets.d.ts +64 -5
  75. package/dist/init/snippets.js +220 -31
  76. package/dist/init/vite-config.js +29 -3
  77. package/dist/input/network-mock-tools.d.ts +19 -0
  78. package/dist/input/network-mock-tools.js +29 -4
  79. package/dist/license/license.js +1 -1
  80. package/dist/mcp/mcp-proxy.d.ts +69 -3
  81. package/dist/mcp/mcp-proxy.js +215 -23
  82. package/dist/mcp/mcp.js +58 -7
  83. package/dist/mcp/proxy-lifecycle.d.ts +20 -1
  84. package/dist/mcp/proxy-lifecycle.js +22 -1
  85. package/dist/pool/browser-pool.d.ts +22 -2
  86. package/dist/pool/browser-pool.js +44 -4
  87. package/dist/pool/playwright-launcher.js +10 -2
  88. package/dist/project/learned-routes.d.ts +24 -0
  89. package/dist/project/learned-routes.js +63 -0
  90. package/dist/project/project-store.d.ts +6 -0
  91. package/dist/project/project-store.js +25 -1
  92. package/dist/session/live-control-tools.js +39 -2
  93. package/dist/session/live-control.d.ts +7 -9
  94. package/dist/session/live-control.js +34 -1
  95. package/dist/session/no-session-diagnosis.d.ts +23 -4
  96. package/dist/session/no-session-diagnosis.js +128 -44
  97. package/dist/session/no-session-watch.js +12 -0
  98. package/dist/session/ref-provenance.d.ts +54 -0
  99. package/dist/session/ref-provenance.js +61 -0
  100. package/dist/session/review-store.d.ts +19 -0
  101. package/dist/session/review-store.js +34 -5
  102. package/dist/session/session-lease.js +1 -1
  103. package/dist/session/session-manager.d.ts +9 -0
  104. package/dist/session/session-manager.js +11 -0
  105. package/dist/session/session.d.ts +8 -0
  106. package/dist/session/session.js +12 -0
  107. package/dist/status-payload.d.ts +13 -1
  108. package/dist/status-payload.js +6 -1
  109. package/dist/telemetry/app-instrumented.d.ts +37 -0
  110. package/dist/telemetry/app-instrumented.js +54 -0
  111. package/dist/telemetry/automation-hint.d.ts +17 -0
  112. package/dist/telemetry/automation-hint.js +71 -0
  113. package/dist/telemetry/bug-found.d.ts +0 -20
  114. package/dist/telemetry/bug-found.js +38 -21
  115. package/dist/telemetry/cli-telemetry.js +7 -1
  116. package/dist/telemetry/daemon-telemetry.d.ts +4 -4
  117. package/dist/telemetry/daemon-telemetry.js +8 -4
  118. package/dist/telemetry/feedback.js +1 -1
  119. package/dist/telemetry/init-telemetry.js +9 -1
  120. package/dist/telemetry/install-source.d.ts +36 -0
  121. package/dist/telemetry/install-source.js +40 -0
  122. package/dist/telemetry/run-telemetry.js +4 -1
  123. package/dist/telemetry/session-metrics.d.ts +3 -3
  124. package/dist/telemetry/session-metrics.js +9 -4
  125. package/dist/telemetry/telemetry.d.ts +7 -1
  126. package/dist/telemetry/telemetry.js +17 -2
  127. package/dist/telemetry/tool-refused.d.ts +19 -0
  128. package/dist/telemetry/tool-refused.js +55 -0
  129. package/dist/telemetry/verification-of.js +72 -8
  130. package/dist/temp-dir.d.ts +27 -0
  131. package/dist/temp-dir.js +46 -0
  132. package/dist/tools/act-tools.js +77 -11
  133. package/dist/tools/assert-grade.d.ts +1 -1
  134. package/dist/tools/assert-grade.js +27 -5
  135. package/dist/tools/assert-verdict.js +25 -1
  136. package/dist/tools/contract-tools.js +4 -0
  137. package/dist/tools/dynamic-tools.js +1 -1
  138. package/dist/tools/error-recovery.d.ts +10 -0
  139. package/dist/tools/error-recovery.js +70 -5
  140. package/dist/tools/feedback-invite.js +1 -1
  141. package/dist/tools/invoke-tool.js +149 -32
  142. package/dist/tools/lease-tools.d.ts +29 -1
  143. package/dist/tools/lease-tools.js +58 -5
  144. package/dist/tools/navigate-result.js +1 -1
  145. package/dist/tools/observe-tools.js +1 -1
  146. package/dist/tools/observed-nothing.js +1 -1
  147. package/dist/tools/react-grace.d.ts +48 -0
  148. package/dist/tools/react-grace.js +87 -0
  149. package/dist/tools/read-tools.js +20 -4
  150. package/dist/tools/settle-in-flight.d.ts +79 -0
  151. package/dist/tools/settle-in-flight.js +106 -0
  152. package/dist/tools/snapshot-delta.d.ts +7 -0
  153. package/dist/tools/snapshot-delta.js +130 -6
  154. package/dist/tools/tool-kit.d.ts +52 -5
  155. package/dist/tools/tool-kit.js +55 -24
  156. package/dist/tools/tool-surface.js +28 -9
  157. package/dist/tools/tools.js +38 -4
  158. package/dist/visual/capture-cleanup.d.ts +16 -0
  159. package/dist/visual/capture-cleanup.js +39 -0
  160. package/dist/visual/visual-tools.d.ts +18 -0
  161. package/dist/visual/visual-tools.js +24 -5
  162. package/docs/README.md +56 -11
  163. package/docs/actions.mdx +87 -0
  164. package/docs/agent-cheatsheet.md +102 -26
  165. package/docs/architecture.md +46 -35
  166. package/docs/benchmarks.md +76 -72
  167. package/docs/best-practices.mdx +211 -0
  168. package/docs/capabilities.mdx +387 -0
  169. package/docs/cli/affected.mdx +51 -0
  170. package/docs/cli/capsules.mdx +69 -0
  171. package/docs/cli/cloud.mdx +127 -0
  172. package/docs/cli/doctor.mdx +76 -0
  173. package/docs/cli/drive.mdx +55 -0
  174. package/docs/cli/feedback.mdx +67 -0
  175. package/docs/cli/gate.mdx +64 -0
  176. package/docs/cli/help.mdx +92 -0
  177. package/docs/cli/hunt.mdx +57 -0
  178. package/docs/cli/identify.mdx +62 -0
  179. package/docs/cli/init.mdx +94 -0
  180. package/docs/cli/kill.mdx +91 -0
  181. package/docs/cli/license.mdx +48 -0
  182. package/docs/cli/mcp.mdx +61 -0
  183. package/docs/cli/open.mdx +68 -0
  184. package/docs/cli/restart.mdx +60 -0
  185. package/docs/cli/rollback.mdx +35 -0
  186. package/docs/cli/serve.mdx +62 -0
  187. package/docs/cli/status.mdx +64 -0
  188. package/docs/cli/stop.mdx +56 -0
  189. package/docs/cli/telemetry.mdx +57 -0
  190. package/docs/cli/update.mdx +52 -0
  191. package/docs/cli/verify.mdx +57 -0
  192. package/docs/cli/version.mdx +39 -0
  193. package/docs/cli/watch.mdx +55 -0
  194. package/docs/cli.mdx +219 -0
  195. package/docs/debugging.md +13 -9
  196. package/docs/deploy-checks.md +15 -11
  197. package/docs/desktop-apps.md +49 -40
  198. package/docs/desktop.mdx +188 -0
  199. package/docs/docs.json +363 -0
  200. package/docs/enterprise.md +20 -16
  201. package/docs/faq.mdx +88 -0
  202. package/docs/fixtures.md +11 -7
  203. package/docs/flows.md +26 -22
  204. package/docs/for-agents.md +71 -0
  205. package/docs/frameworks.mdx +199 -0
  206. package/docs/gate-plan.md +45 -41
  207. package/docs/gates.md +23 -19
  208. package/docs/getting-started.md +76 -65
  209. package/docs/human-control.md +22 -18
  210. package/docs/index.mdx +101 -0
  211. package/docs/install-agentic.mdx +215 -0
  212. package/docs/install-manual.mdx +194 -0
  213. package/docs/instrumentation.mdx +232 -0
  214. package/docs/integration-patterns.md +38 -34
  215. package/docs/local-registry.md +16 -12
  216. package/docs/multi-agent-testing.md +16 -12
  217. package/docs/packages/babel-plugin.mdx +70 -0
  218. package/docs/packages/browser.mdx +111 -0
  219. package/docs/packages/core.mdx +106 -0
  220. package/docs/packages/electron.mdx +87 -0
  221. package/docs/packages/eslint-plugin.mdx +84 -0
  222. package/docs/packages/next.mdx +91 -0
  223. package/docs/packages/react.mdx +91 -0
  224. package/docs/packages/server.mdx +98 -0
  225. package/docs/packages/tauri.mdx +63 -0
  226. package/docs/packages/test.mdx +108 -0
  227. package/docs/packages/vite-plugin.mdx +96 -0
  228. package/docs/packages.mdx +217 -0
  229. package/docs/platform-integration.md +33 -26
  230. package/docs/predicates.mdx +179 -0
  231. package/docs/quickstart.mdx +211 -0
  232. package/docs/recipes.mdx +242 -0
  233. package/docs/skill-file.mdx +105 -0
  234. package/docs/state-management.mdx +197 -0
  235. package/docs/style.css +14 -0
  236. package/docs/system-map.md +47 -32
  237. package/docs/telemetry-contract.md +101 -46
  238. package/docs/telemetry-events.mdx +430 -0
  239. package/docs/telemetry.md +63 -50
  240. package/docs/testing.md +10 -6
  241. package/docs/token-efficiency.md +30 -26
  242. package/docs/tools-act-and-wait.mdx +123 -0
  243. package/docs/tools-act-sequence.mdx +120 -0
  244. package/docs/tools-act.mdx +82 -0
  245. package/docs/tools-assert.mdx +70 -0
  246. package/docs/tools-console.mdx +59 -0
  247. package/docs/tools-inspect.mdx +85 -0
  248. package/docs/tools-navigate.mdx +63 -0
  249. package/docs/tools-network.mdx +86 -0
  250. package/docs/tools-observe.mdx +89 -0
  251. package/docs/tools-overview.mdx +157 -0
  252. package/docs/tools-query.mdx +82 -0
  253. package/docs/tools-session-and-feedback.mdx +91 -0
  254. package/docs/tools-sessions.mdx +65 -0
  255. package/docs/tools-snapshot.mdx +75 -0
  256. package/docs/tools-state.mdx +81 -0
  257. package/docs/tools-tools-and-run.mdx +125 -0
  258. package/docs/tools-wait-for.mdx +66 -0
  259. package/docs/troubleshooting.mdx +193 -0
  260. package/docs/usage.md +175 -153
  261. package/docs/vs-chrome-devtools-mcp.mdx +82 -0
  262. package/docs/vs-playwright-mcp.mdx +85 -0
  263. package/docs/vs-screenshots.mdx +96 -0
  264. package/docs/what-is-reticle.mdx +63 -0
  265. package/docs/why-reticle.mdx +107 -0
  266. package/package.json +2 -2
  267. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  268. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  269. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  270. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  271. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  272. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  273. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  274. package/docs/matrix/MATRIX.md +0 -26
  275. package/docs/matrix/README.md +0 -59
  276. package/docs/mint.json +0 -61
package/dist/init/plan.js CHANGED
@@ -8,14 +8,18 @@ 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, 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, VITE_PLUGIN_DETAIL, } from './plan-framework.js';
13
+ import { join } from 'node:path';
13
14
  import { htmlManual, reticleConfigContent, unverifiedUiLibraryNote } from './snippets.js';
15
+ import { devServerPortWarning, isLikelyDevServerPort } from '../cli/cli-port.js';
14
16
  // An app dev installs exactly the audience-scoped browser-side dependencies — never the retired
15
17
  // `@reticlehq/core` umbrella (which dragged the Node MCP server + ws into every app). The kit is the
16
18
  // framework adapter (it re-exports the browser sensor), paired with that framework's dev-only build
17
19
  // plugin for source mapping + connect injection.
18
20
  const RETICLE_REACT_KIT = '@reticlehq/react';
21
+ /** The framework-neutral sensor, for stacks the React adapter has nothing to attach to. */
22
+ const RETICLE_BROWSER_SDK = '@reticlehq/browser';
19
23
  const RETICLE_VITE_PLUGIN = '@reticlehq/vite-plugin';
20
24
  const RETICLE_NEXT_PLUGIN = '@reticlehq/next';
21
25
  /**
@@ -32,26 +36,56 @@ export function pinnedPackages(packages, version) {
32
36
  return packages;
33
37
  return packages.map((p) => `${p}@${version}`);
34
38
  }
35
- /** The dev-dependencies `reticle init` installs for a given framework — kit first, build plugin next. */
36
- export function frameworkPackages(framework) {
39
+ /**
40
+ * Does this codebase want the React kit, or the framework-neutral sensor?
41
+ *
42
+ * The kit is what adds component identity — component names and stacks — and it is worth having
43
+ * wherever React or Preact is rendering (the adapter reaches Preact through `preact/compat`).
44
+ * Everywhere else it is a package named `@reticlehq/react`, carrying `react` in its peer
45
+ * dependencies, being installed into a codebase that has no React in it.
46
+ *
47
+ * UNKNOWN keeps the kit deliberately. Absence of evidence is not evidence of Vue, and guessing
48
+ * "sensor" on no information silently drops component identity from apps that should have it.
49
+ */
50
+ function wantsReactKit(ui) {
51
+ return ui !== UiLibrary.VUE && ui !== UiLibrary.SVELTE;
52
+ }
53
+ /**
54
+ * The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
55
+ *
56
+ * `uiLibrary` matters because the framework does not always name the renderer. The rule below was
57
+ * already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
58
+ * `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
59
+ * right after `init` had detected the real UI library and said so on screen.
60
+ */
61
+ export function frameworkPackages(framework, uiLibrary = UiLibrary.UNKNOWN) {
62
+ const kit = wantsReactKit(uiLibrary) ? RETICLE_REACT_KIT : RETICLE_BROWSER_SDK;
37
63
  switch (framework) {
38
64
  case Framework.NEXT:
65
+ // Next is React by construction, so the detection cannot disagree in a way worth honouring.
39
66
  return [RETICLE_REACT_KIT, RETICLE_NEXT_PLUGIN];
40
67
  case Framework.VITE:
41
68
  case Framework.SVELTEKIT:
42
69
  // SvelteKit builds on Vite; until a dedicated Svelte kit exists it uses the Vite build plugin.
43
- return [RETICLE_REACT_KIT, RETICLE_VITE_PLUGIN];
70
+ // The build plugin stamps `data-reticle-source` regardless of UI library, so a Vue or Svelte
71
+ // app still gets source pointers — it is only component identity that needs the React kit.
72
+ return [kit, RETICLE_VITE_PLUGIN];
73
+ case Framework.NUXT:
74
+ // The framework-neutral sensor, NOT the React kit. Nuxt renders Vue, and installing a package
75
+ // named @reticlehq/react — with `react` in its peer dependencies — into a Vue codebase is the
76
+ // single thing most likely to make someone abandon the setup, whether or not it works.
77
+ return [RETICLE_BROWSER_SDK];
44
78
  case Framework.ASTRO:
45
79
  // Astro owns its own Vite instance and renders its own HTML, so there is no config for the
46
80
  // plugin to attach to — the kit alone, connected from a page <script> (see astroManual).
47
- return [RETICLE_REACT_KIT];
81
+ return [kit];
48
82
  case Framework.CRA:
49
83
  // react-scripts owns its webpack config and cannot be extended without ejecting, so there is
50
84
  // no build plugin — the kit alone, imported from src/index.tsx (see cra.ts).
51
85
  return [RETICLE_REACT_KIT];
52
86
  case Framework.HTML:
53
87
  // No bundler plugin to install — just the kit; connect is wired by hand (see htmlManual).
54
- return [RETICLE_REACT_KIT];
88
+ return [kit];
55
89
  }
56
90
  }
57
91
  /** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
@@ -68,6 +102,7 @@ const CONNECT_STEP_TITLES = new Set([
68
102
  'Connect snippet',
69
103
  'Connect snippet (CRA)',
70
104
  'Connect snippet (Astro)',
105
+ 'Connect snippet (Nuxt)',
71
106
  'Reticle client hook',
72
107
  'Reticle connect module',
73
108
  'ReticleDev component',
@@ -93,6 +128,19 @@ export const StepStatus = {
93
128
  */
94
129
  NOTICE: 'notice',
95
130
  };
131
+ /**
132
+ * Where the agent-facing files go, which is not always where the app is.
133
+ *
134
+ * Reported from the field (#318): a repo with its app at `src/admin` got `.claude/commands/reticle.md`
135
+ * written into the app directory, while the human's agent session runs at the repo root — so
136
+ * `/reticle` did not exist for them at all until they copied it up by hand. The rule and command
137
+ * files are read by the AGENT, so they belong where the agent stands; the app files stay with the
138
+ * app. The two are the same directory in a single-package repo, which is why this went unnoticed.
139
+ */
140
+ function agentFile(input, relPath) {
141
+ const root = input.agentFileRoot;
142
+ return root === undefined || 0 === root.length ? relPath : join(root, relPath);
143
+ }
96
144
  const CLAUDE_MCP_TITLE = 'MCP server (Claude, global)';
97
145
  const CURSOR_MCP_TITLE = 'MCP server (Cursor, global)';
98
146
  function claudeMcpStep(input) {
@@ -259,32 +307,35 @@ function commandStepFor(path, present, content) {
259
307
  };
260
308
  }
261
309
  function claudeCommandStep(input) {
262
- return commandStepFor(CLAUDE_COMMAND_PATH, input.claudeCli, input.claudeCommandContent);
310
+ return commandStepFor(agentFile(input, CLAUDE_COMMAND_PATH), input.claudeCli, input.claudeCommandContent);
263
311
  }
264
312
  function cursorCommandStep(input) {
265
313
  const present = true === input.cursorProjectPresent || (input.cursorPresent && !input.claudeCli);
266
- return commandStepFor(CURSOR_COMMAND_PATH, present, input.cursorCommandContent);
314
+ return commandStepFor(agentFile(input, CURSOR_COMMAND_PATH), present, input.cursorCommandContent);
267
315
  }
268
316
  const AGENT_RULE_TITLE = 'Agent verification rule';
269
317
  const AGENT_RULE_DETAIL = 'teach the agent to verify features with Reticle after building them';
318
+ const RETICLE_MD_TITLE = 'Full agent rules';
319
+ const RETICLE_MD_DETAIL = 'the reference the always-loaded rule points at (when NOT to verify, recovery, feedback)';
270
320
  function claudeRuleStep(input) {
271
321
  if (!input.claudeCli)
272
322
  return null;
323
+ const path = agentFile(input, CLAUDE_MD_PATH);
273
324
  const r = mergeMarkedInstruction(input.claudeMdContent);
274
325
  if (r.status === AgentRuleStatus.ALREADY) {
275
326
  return {
276
327
  title: AGENT_RULE_TITLE,
277
- target: CLAUDE_MD_PATH,
328
+ target: path,
278
329
  status: StepStatus.ALREADY,
279
330
  detail: 'Reticle rule already in CLAUDE.md',
280
331
  };
281
332
  }
282
333
  return {
283
334
  title: AGENT_RULE_TITLE,
284
- target: CLAUDE_MD_PATH,
335
+ target: path,
285
336
  status: StepStatus.APPLY,
286
337
  detail: AGENT_RULE_DETAIL,
287
- write: { path: CLAUDE_MD_PATH, content: r.content },
338
+ write: { path, content: r.content },
288
339
  };
289
340
  }
290
341
  /**
@@ -300,20 +351,21 @@ function cursorRuleStep(input) {
300
351
  return null;
301
352
  // The whole file is Reticle's — init created it — so a stale one is REWRITTEN rather than merged.
302
353
  // Comparing content is what makes the rule updatable; comparing existence made it permanent.
354
+ const path = agentFile(input, CURSOR_RULE_PATH);
303
355
  if (input.cursorRuleContent === cursorRuleFile()) {
304
356
  return {
305
357
  title: AGENT_RULE_TITLE,
306
- target: CURSOR_RULE_PATH,
358
+ target: path,
307
359
  status: StepStatus.ALREADY,
308
360
  detail: 'Reticle rule already in .cursor/rules',
309
361
  };
310
362
  }
311
363
  return {
312
364
  title: AGENT_RULE_TITLE,
313
- target: CURSOR_RULE_PATH,
365
+ target: path,
314
366
  status: StepStatus.APPLY,
315
367
  detail: AGENT_RULE_DETAIL,
316
- write: { path: CURSOR_RULE_PATH, content: cursorRuleFile() },
368
+ write: { path, content: cursorRuleFile() },
317
369
  };
318
370
  }
319
371
  /**
@@ -357,33 +409,70 @@ function slashCommandSteps(input) {
357
409
  return stepsForAgents(input, (a) => a.commandStep);
358
410
  }
359
411
  /**
360
- * The behavioral rule that makes the agent actually USE Reticle. Written into the detected agent's
361
- * instruction file (Claude / Cursor, or both), falling back to the cross-agent AGENTS.md when neither
362
- * is detected. Rides with the MCP wiring — `--no-mcp` opts out of registering the tools AND the rule.
412
+ * `AGENTS.md`, always. It is the file every agent that is not Claude or Cursor reads.
413
+ *
414
+ * This used to be a FALLBACK, written only when no agent was detected. So a repo set up on a machine
415
+ * with Claude Code got `CLAUDE.md` and nothing else, and the next person to open it with Codex,
416
+ * Copilot, Amp, Gemini or any other agent found a fully instrumented app whose rules were addressed
417
+ * to somebody else's tool. The detection tells you which agent is running the install; it says
418
+ * nothing about which agents will open the repository afterwards.
419
+ */
420
+ function agentsMdStep(input) {
421
+ const r = mergeMarkedInstruction(input.agentsMdContent);
422
+ const path = agentFile(input, AGENTS_MD_PATH);
423
+ if (r.status === AgentRuleStatus.ALREADY) {
424
+ return {
425
+ title: AGENT_RULE_TITLE,
426
+ target: path,
427
+ status: StepStatus.ALREADY,
428
+ detail: 'Reticle rule already in AGENTS.md',
429
+ };
430
+ }
431
+ return {
432
+ title: AGENT_RULE_TITLE,
433
+ target: path,
434
+ status: StepStatus.APPLY,
435
+ detail: AGENT_RULE_DETAIL,
436
+ write: { path, content: r.content },
437
+ };
438
+ }
439
+ /**
440
+ * `RETICLE.md`, the reference the always-loaded blocks point at.
441
+ *
442
+ * Written whole rather than merged, because `init` owns the entire file: there is no user content to
443
+ * preserve, and the marker dance exists only for files somebody else already writes in.
444
+ */
445
+ function reticleMdStep(input) {
446
+ const path = agentFile(input, RETICLE_MD_PATH);
447
+ const content = reticleMdFile();
448
+ if (input.reticleMdContent === content) {
449
+ return {
450
+ title: RETICLE_MD_TITLE,
451
+ target: path,
452
+ status: StepStatus.ALREADY,
453
+ detail: 'full rules already current',
454
+ };
455
+ }
456
+ return {
457
+ title: RETICLE_MD_TITLE,
458
+ target: path,
459
+ status: StepStatus.APPLY,
460
+ detail: RETICLE_MD_DETAIL,
461
+ write: { path, content },
462
+ };
463
+ }
464
+ /**
465
+ * The behavioral rules that make the agent actually USE Reticle, and know when not to.
466
+ *
467
+ * Every detected agent's own file, plus `AGENTS.md` for the ones that will open this repo later, plus
468
+ * `RETICLE.md` holding the reference half. Rides with the MCP wiring: `--no-mcp` opts out of
469
+ * registering the tools AND of every rule file, because rules for tools the agent cannot reach are
470
+ * noise.
363
471
  */
364
472
  function agentRuleSteps(input) {
365
473
  if (!input.options.mcp)
366
474
  return [];
367
- const detected = stepsForAgents(input, (a) => a.ruleStep);
368
- if (detected.length > 0)
369
- return detected;
370
- const r = mergeMarkedInstruction(input.agentsMdContent);
371
- return [
372
- r.status === AgentRuleStatus.ALREADY
373
- ? {
374
- title: AGENT_RULE_TITLE,
375
- target: AGENTS_MD_PATH,
376
- status: StepStatus.ALREADY,
377
- detail: 'Reticle rule already in AGENTS.md',
378
- }
379
- : {
380
- title: AGENT_RULE_TITLE,
381
- target: AGENTS_MD_PATH,
382
- status: StepStatus.APPLY,
383
- detail: AGENT_RULE_DETAIL,
384
- write: { path: AGENTS_MD_PATH, content: r.content },
385
- },
386
- ];
475
+ return [...stepsForAgents(input, (a) => a.ruleStep), agentsMdStep(input), reticleMdStep(input)];
387
476
  }
388
477
  /**
389
478
  * What to say when the install command fails.
@@ -431,7 +520,7 @@ function installFailureHint(pm) {
431
520
  }
432
521
  function installStep(input) {
433
522
  const pm = input.detection.packageManager;
434
- const packages = pinnedPackages(frameworkPackages(input.detection.framework), input.options.sdkVersion);
523
+ const packages = pinnedPackages(frameworkPackages(input.detection.framework, input.detection.uiLibrary), input.options.sdkVersion);
435
524
  const command = installCommand(pm, packages);
436
525
  if (!input.options.install) {
437
526
  return {
@@ -455,13 +544,55 @@ function installStep(input) {
455
544
  // Unpinned. pnpm resolves the newest MATURE version there, which is how a project with a
456
545
  // release-age hold gets a working install instead of no install.
457
546
  retry: {
458
- ...installCommandParts(pm, frameworkPackages(input.detection.framework)),
547
+ ...installCommandParts(pm, frameworkPackages(input.detection.framework, input.detection.uiLibrary)),
459
548
  note: unpinnedRetryNote(input.options.sdkVersion, pm),
460
549
  },
461
550
  };
462
551
  }
552
+ /**
553
+ * What is wrong with the config that is already there, if anything.
554
+ *
555
+ * Reported from the field (#317): a project carried `"port": 3000` — its own dev-server port, which
556
+ * is the confusion SKILL.md names as the top setup failure — and `init` printed `.reticle.json
557
+ * already exists` on every re-run without reading a single field of it, so the file could never be
558
+ * repaired by the command that wrote it. It stayed invisible because a daemon on `127.0.0.1:3000`
559
+ * and Vite on `[::1]:3000` split the port by address family and neither reported a conflict.
560
+ *
561
+ * Narrower than the issue asked for on purpose: `init`'s IO surface is synchronous, so this cannot
562
+ * probe what is LISTENING right now. `isLikelyDevServerPort` covers the framework defaults, which is
563
+ * where the mistake is actually made.
564
+ */
565
+ function existingConfigProblem(source) {
566
+ if (null === source || source === undefined)
567
+ return undefined;
568
+ let parsed;
569
+ try {
570
+ parsed = JSON.parse(source);
571
+ }
572
+ catch {
573
+ return (`${RETICLE_CONFIG_FILE} is present but is not valid JSON, so nothing reads it — neither the ` +
574
+ 'daemon nor the SDK. Delete it and re-run `reticle init`.');
575
+ }
576
+ if ('object' !== typeof parsed || null === parsed)
577
+ return undefined;
578
+ const port = parsed['port'];
579
+ if ('number' !== typeof port || !isLikelyDevServerPort(port))
580
+ return undefined;
581
+ return `${devServerPortWarning(port)} Fix it by removing the "port" field from ${RETICLE_CONFIG_FILE}.`;
582
+ }
463
583
  function reticleConfigStep(input) {
464
584
  if (true === input.reticleConfigExists) {
585
+ const problem = existingConfigProblem(input.reticleConfigSource);
586
+ if (problem !== undefined) {
587
+ // `ℹ`, not `·`: the step is done and something about the result still stops things working,
588
+ // which is the one mark that says "there is something here to read".
589
+ return {
590
+ title: 'Reticle config',
591
+ target: RETICLE_CONFIG_FILE,
592
+ status: StepStatus.NOTICE,
593
+ detail: problem,
594
+ };
595
+ }
465
596
  return {
466
597
  title: 'Reticle config',
467
598
  target: RETICLE_CONFIG_FILE,
@@ -484,8 +615,14 @@ function reticleConfigStep(input) {
484
615
  */
485
616
  function uiLibraryStep(input) {
486
617
  const lib = input.detection.uiLibrary;
487
- if (lib === UiLibrary.REACT || input.detection.framework === Framework.SVELTEKIT)
618
+ // Nuxt and SvelteKit each carry their own unverified wording inside the recipe, so a second,
619
+ // more generic notice beside it would only argue with the specific one.
620
+ const framework = input.detection.framework;
621
+ if (lib === UiLibrary.REACT ||
622
+ framework === Framework.SVELTEKIT ||
623
+ framework === Framework.NUXT) {
488
624
  return [];
625
+ }
489
626
  if (lib === UiLibrary.UNKNOWN)
490
627
  return [];
491
628
  return [
@@ -518,6 +655,9 @@ export function buildPlan(input) {
518
655
  else if (input.detection.framework === Framework.CRA) {
519
656
  steps.push(...craSteps(input));
520
657
  }
658
+ else if (input.detection.framework === Framework.NUXT) {
659
+ steps.push(...nuxtSteps(input));
660
+ }
521
661
  else if (input.detection.framework === Framework.SVELTEKIT) {
522
662
  steps.push(...svelteKitSteps(input));
523
663
  // The Vite plugin as well as the client hook. `init` already INSTALLS @reticlehq/vite-plugin for
@@ -530,8 +670,8 @@ export function buildPlan(input) {
530
670
  title: 'Connect snippet',
531
671
  target: 'index.html',
532
672
  status: StepStatus.MANUAL,
533
- detail: htmlManual(input.options.port, input.options.projectId),
673
+ detail: htmlManual(input.options.port, input.options.projectId, input.pairingToken),
534
674
  });
535
675
  }
536
- return { framework: input.detection.framework, steps };
676
+ return { framework: input.detection.framework, uiLibrary: input.detection.uiLibrary, steps };
537
677
  }
@@ -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 {};