@reticlehq/server 2.10.0 → 2.12.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 (290) hide show
  1. package/SKILL.md +73 -11
  2. package/dist/bridge/bridge.d.ts +0 -13
  3. package/dist/bridge/bridge.js +1 -1
  4. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  5. package/dist/bridge/bridge.test-harness.js +8 -1
  6. package/dist/capsule/capsule-store.d.ts +2 -1
  7. package/dist/capsule/capsule-store.js +1 -1
  8. package/dist/capsule/causal-summary.d.ts +2 -2
  9. package/dist/chromium-launch-options.d.ts +28 -0
  10. package/dist/chromium-launch-options.js +29 -0
  11. package/dist/cli/cli-doctor.js +8 -0
  12. package/dist/cli/cli-flow-commands.d.ts +3 -1
  13. package/dist/cli/cli-flow-commands.js +34 -4
  14. package/dist/cli/cli-kill.d.ts +2 -1
  15. package/dist/cli/cli-parse.d.ts +3 -1
  16. package/dist/cli/cli-parse.js +29 -7
  17. package/dist/cli/cli-update-commands.js +20 -1
  18. package/dist/cli/cli-verify.d.ts +1 -1
  19. package/dist/cli/cli-verify.js +2 -2
  20. package/dist/cli/cloud-cli.js +12 -6
  21. package/dist/cli/config-discovery.d.ts +2 -1
  22. package/dist/cli/doctor-rows.d.ts +3 -1
  23. package/dist/cli/doctor-rows.js +3 -1
  24. package/dist/cli/gate-exit.d.ts +21 -0
  25. package/dist/cli/gate-exit.js +21 -0
  26. package/dist/cli/gate-hook-message.d.ts +33 -0
  27. package/dist/cli/gate-hook-message.js +46 -0
  28. package/dist/cli/mcp-command.d.ts +25 -0
  29. package/dist/cli/mcp-command.js +143 -0
  30. package/dist/cli/sibling-ports.d.ts +37 -0
  31. package/dist/cli/sibling-ports.js +56 -0
  32. package/dist/cli.js +37 -110
  33. package/dist/cloud/cloud-config.d.ts +2 -1
  34. package/dist/cloud/cloud-sync.d.ts +11 -0
  35. package/dist/cloud/cloud-sync.js +32 -0
  36. package/dist/crawl/crawl.d.ts +9 -13
  37. package/dist/crawl/crawl.js +20 -3
  38. package/dist/daemon/daemon-resolve.d.ts +79 -0
  39. package/dist/daemon/daemon-resolve.js +118 -0
  40. package/dist/daemon/free-port.d.ts +29 -0
  41. package/dist/daemon/free-port.js +55 -0
  42. package/dist/daemon/heartbeat.d.ts +0 -4
  43. package/dist/daemon/heartbeat.js +2 -2
  44. package/dist/dev/stale-issue-guard.d.ts +41 -0
  45. package/dist/dev/stale-issue-guard.js +75 -0
  46. package/dist/domain/domain-tools.js +3 -2
  47. package/dist/events/contradictions.d.ts +41 -0
  48. package/dist/events/contradictions.js +125 -12
  49. package/dist/events/declared.d.ts +14 -0
  50. package/dist/events/declared.js +33 -0
  51. package/dist/events/echo-mismatch.js +14 -0
  52. package/dist/events/observed-in-window.d.ts +12 -0
  53. package/dist/events/observed-in-window.js +17 -0
  54. package/dist/events/predicate-asks.d.ts +30 -0
  55. package/dist/events/predicate-asks.js +48 -0
  56. package/dist/events/predicate-eval.js +254 -25
  57. package/dist/events/predicate-parse.d.ts +0 -6
  58. package/dist/events/predicate-parse.js +51 -5
  59. package/dist/events/predicate-schema.d.ts +62 -0
  60. package/dist/events/predicate-schema.js +162 -4
  61. package/dist/events/predicate.d.ts +15 -0
  62. package/dist/events/predicate.js +41 -2
  63. package/dist/flows/coverage.d.ts +0 -8
  64. package/dist/flows/coverage.js +1 -1
  65. package/dist/flows/decision.d.ts +5 -1
  66. package/dist/flows/decision.js +33 -7
  67. package/dist/flows/flake.d.ts +0 -2
  68. package/dist/flows/flake.js +1 -1
  69. package/dist/flows/flow-intent.d.ts +52 -0
  70. package/dist/flows/flow-intent.js +123 -0
  71. package/dist/flows/flow-replay-run.js +27 -4
  72. package/dist/flows/flow-success.js +15 -3
  73. package/dist/flows/flow-tools.js +65 -8
  74. package/dist/flows/flows.d.ts +6 -1
  75. package/dist/flows/flows.js +34 -22
  76. package/dist/flows/predicate-to-expect.js +14 -3
  77. package/dist/flows/server-verify.js +16 -2
  78. package/dist/flows/verify-change-tools.js +9 -2
  79. package/dist/honesty/blind-spots.d.ts +8 -16
  80. package/dist/honesty/blind-spots.js +18 -5
  81. package/dist/honesty/feature-capture.d.ts +150 -0
  82. package/dist/honesty/feature-capture.js +233 -0
  83. package/dist/honesty/gap-ledger.d.ts +52 -0
  84. package/dist/honesty/gap-ledger.js +63 -0
  85. package/dist/honesty/instrumentation-gaps.d.ts +79 -0
  86. package/dist/honesty/instrumentation-gaps.js +77 -0
  87. package/dist/honesty/observability.d.ts +37 -0
  88. package/dist/honesty/observability.js +40 -0
  89. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  90. package/dist/honesty/tool-hit-rate.js +90 -0
  91. package/dist/honesty/undeclared-change.d.ts +51 -0
  92. package/dist/honesty/undeclared-change.js +56 -0
  93. package/dist/honesty/verified.d.ts +15 -0
  94. package/dist/honesty/verified.js +68 -4
  95. package/dist/http-server.d.ts +0 -1
  96. package/dist/http-server.js +1 -1
  97. package/dist/hunt/hunt-report.d.ts +0 -2
  98. package/dist/hunt/hunt-report.js +1 -1
  99. package/dist/impact/impact-store.d.ts +0 -5
  100. package/dist/impact/impact-store.js +1 -1
  101. package/dist/index.d.ts +4 -2
  102. package/dist/index.js +41 -0
  103. package/dist/init/agent-rules.d.ts +17 -0
  104. package/dist/init/agent-rules.js +31 -1
  105. package/dist/init/capabilities.d.ts +0 -2
  106. package/dist/init/capabilities.js +1 -1
  107. package/dist/init/closing-hint.d.ts +36 -0
  108. package/dist/init/closing-hint.js +120 -0
  109. package/dist/init/confirm.d.ts +0 -25
  110. package/dist/init/confirm.js +3 -3
  111. package/dist/init/cra.d.ts +14 -1
  112. package/dist/init/cra.js +43 -12
  113. package/dist/init/dev-script.d.ts +48 -0
  114. package/dist/init/dev-script.js +63 -0
  115. package/dist/init/mcp.d.ts +16 -5
  116. package/dist/init/mcp.js +26 -10
  117. package/dist/init/node-io.js +3 -0
  118. package/dist/init/plan-framework.d.ts +0 -5
  119. package/dist/init/plan-framework.js +83 -9
  120. package/dist/init/plan.d.ts +10 -30
  121. package/dist/init/plan.js +30 -15
  122. package/dist/init/refresh-rules.d.ts +39 -0
  123. package/dist/init/refresh-rules.js +51 -0
  124. package/dist/init/run.d.ts +2 -10
  125. package/dist/init/run.js +26 -68
  126. package/dist/init/slash-command.d.ts +1 -1
  127. package/dist/init/slash-command.js +33 -10
  128. package/dist/init/snippets.js +53 -10
  129. package/dist/input/real-input.d.ts +2 -0
  130. package/dist/input/real-input.js +3 -2
  131. package/dist/input/scroll-find.d.ts +7 -4
  132. package/dist/input/scroll-find.js +63 -25
  133. package/dist/intent/inline-intent.d.ts +46 -0
  134. package/dist/intent/inline-intent.js +103 -0
  135. package/dist/intent/intent-store.d.ts +37 -0
  136. package/dist/intent/intent-store.js +112 -0
  137. package/dist/intent/intent-tools.d.ts +2 -0
  138. package/dist/intent/intent-tools.js +80 -0
  139. package/dist/intent/open-intents.d.ts +21 -0
  140. package/dist/intent/open-intents.js +25 -0
  141. package/dist/journal/envelope.d.ts +2 -2
  142. package/dist/journal/journal-recorder.d.ts +23 -0
  143. package/dist/journal/journal-recorder.js +35 -0
  144. package/dist/license/license.d.ts +8 -2
  145. package/dist/license/license.js +10 -15
  146. package/dist/mcp/attach-memory.d.ts +67 -0
  147. package/dist/mcp/attach-memory.js +142 -0
  148. package/dist/mcp/mcp-proxy.d.ts +0 -7
  149. package/dist/mcp/mcp-proxy.js +14 -2
  150. package/dist/mcp/mcp.d.ts +2 -0
  151. package/dist/mcp/mcp.js +45 -23
  152. package/dist/mcp/server-instructions.js +45 -3
  153. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  154. package/dist/mcp/tool-catalog-cache.js +10 -0
  155. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  156. package/dist/oracles/flow-instrument-gaps.js +4 -5
  157. package/dist/oracles/self-instrument.d.ts +9 -9
  158. package/dist/oracles/self-instrument.js +19 -13
  159. package/dist/pool/browser-pool.d.ts +12 -1
  160. package/dist/pool/browser-pool.js +31 -1
  161. package/dist/pool/playwright-launcher.js +3 -1
  162. package/dist/project/artifact-root.d.ts +83 -0
  163. package/dist/project/artifact-root.js +112 -0
  164. package/dist/project/fs-port.d.ts +3 -0
  165. package/dist/project/fs-port.js +3 -2
  166. package/dist/project/memory-fs.d.ts +9 -0
  167. package/dist/project/memory-fs.js +93 -0
  168. package/dist/project/project-store.d.ts +17 -0
  169. package/dist/project/project-store.js +33 -0
  170. package/dist/project/project-tools.js +3 -32
  171. package/dist/project/remember-project.d.ts +24 -0
  172. package/dist/project/remember-project.js +40 -0
  173. package/dist/project/reticle-dir.d.ts +2 -0
  174. package/dist/project/reticle-dir.js +1 -0
  175. package/dist/project/session-root.d.ts +15 -0
  176. package/dist/project/session-root.js +31 -0
  177. package/dist/runs/context-tools.d.ts +2 -0
  178. package/dist/runs/context-tools.js +89 -0
  179. package/dist/runs/run-context.d.ts +40 -0
  180. package/dist/runs/run-context.js +158 -0
  181. package/dist/runs/run-diff.d.ts +2 -1
  182. package/dist/runs/run-tools.js +2 -1
  183. package/dist/runs/verification-sync.js +2 -2
  184. package/dist/session/dev-server-probe.d.ts +0 -8
  185. package/dist/session/dev-server-probe.js +1 -1
  186. package/dist/session/last-act.d.ts +7 -1
  187. package/dist/session/last-act.js +2 -2
  188. package/dist/session/lease-availability.d.ts +40 -0
  189. package/dist/session/lease-availability.js +43 -0
  190. package/dist/session/live-control-tools.js +1 -42
  191. package/dist/session/no-session-diagnosis.d.ts +20 -0
  192. package/dist/session/no-session-diagnosis.js +41 -12
  193. package/dist/session/no-session-next-action.d.ts +5 -0
  194. package/dist/session/no-session-next-action.js +35 -3
  195. package/dist/session/no-session-watch.d.ts +8 -0
  196. package/dist/session/no-session-watch.js +83 -65
  197. package/dist/session/output-budget.js +4 -1
  198. package/dist/session/session-health.d.ts +9 -0
  199. package/dist/session/session-health.js +23 -0
  200. package/dist/session/session-manager.js +50 -1
  201. package/dist/session/session-successor.d.ts +37 -0
  202. package/dist/session/session-successor.js +78 -0
  203. package/dist/session/session.d.ts +40 -1
  204. package/dist/session/session.js +84 -2
  205. package/dist/session/stall-clock.d.ts +18 -0
  206. package/dist/session/stall-clock.js +48 -0
  207. package/dist/telemetry/automation-hint.d.ts +2 -1
  208. package/dist/telemetry/daemon-telemetry.js +12 -23
  209. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  210. package/dist/telemetry/project-profile.d.ts +0 -8
  211. package/dist/telemetry/project-profile.js +1 -1
  212. package/dist/telemetry/run-telemetry.d.ts +0 -2
  213. package/dist/telemetry/run-telemetry.js +1 -1
  214. package/dist/telemetry/session-metrics.d.ts +1 -1
  215. package/dist/telemetry/session-metrics.js +1 -1
  216. package/dist/telemetry/telemetry.d.ts +12 -3
  217. package/dist/telemetry/telemetry.js +25 -5
  218. package/dist/telemetry/verification-of.js +2 -2
  219. package/dist/tools/act-tools.js +159 -24
  220. package/dist/tools/assert-source.d.ts +22 -0
  221. package/dist/tools/assert-source.js +53 -0
  222. package/dist/tools/assert-verdict.d.ts +21 -2
  223. package/dist/tools/assert-verdict.js +59 -4
  224. package/dist/tools/browser-tools.js +0 -20
  225. package/dist/tools/complete-snapshot.d.ts +48 -0
  226. package/dist/tools/complete-snapshot.js +75 -0
  227. package/dist/tools/contract-tools.js +5 -3
  228. package/dist/tools/coverage-tools.d.ts +10 -1
  229. package/dist/tools/coverage-tools.js +124 -37
  230. package/dist/tools/dynamic-tools.js +29 -8
  231. package/dist/tools/error-recovery.d.ts +24 -14
  232. package/dist/tools/error-recovery.js +70 -9
  233. package/dist/tools/feedback-tools.d.ts +1 -1
  234. package/dist/tools/feedback-tools.js +2 -2
  235. package/dist/tools/invoke-tool.js +62 -2
  236. package/dist/tools/lease-hint.d.ts +10 -0
  237. package/dist/tools/lease-hint.js +53 -4
  238. package/dist/tools/lease-tools.d.ts +5 -1
  239. package/dist/tools/lease-tools.js +22 -8
  240. package/dist/tools/navigate-arrival.d.ts +0 -2
  241. package/dist/tools/navigate-arrival.js +1 -1
  242. package/dist/tools/observe-tools.js +70 -27
  243. package/dist/tools/read-tools.js +5 -3
  244. package/dist/tools/real-input-attempt.d.ts +22 -0
  245. package/dist/tools/real-input-attempt.js +153 -1
  246. package/dist/tools/reconcile-tools.js +18 -4
  247. package/dist/tools/resolve-target.d.ts +0 -7
  248. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  249. package/dist/tools/sdk-marker-probe.js +57 -0
  250. package/dist/tools/snapshot-delta.d.ts +1 -1
  251. package/dist/tools/tool-kit.d.ts +28 -1
  252. package/dist/tools/tool-kit.js +16 -0
  253. package/dist/tools/tool-names.d.ts +3 -0
  254. package/dist/tools/tool-names.js +3 -0
  255. package/dist/tools/tool-surface.d.ts +85 -6
  256. package/dist/tools/tool-surface.js +122 -2
  257. package/dist/tools/tools.js +22 -4
  258. package/dist/tools/unadvertised-help.js +4 -4
  259. package/dist/tools/verify-next-baton.d.ts +53 -0
  260. package/dist/tools/verify-next-baton.js +52 -0
  261. package/dist/update/update-nudge.d.ts +7 -10
  262. package/dist/update/update-nudge.js +12 -3
  263. package/dist/update/updater.d.ts +1 -1
  264. package/docs/README.md +3 -1
  265. package/docs/cli/cloud.mdx +1 -1
  266. package/docs/cli/doctor.mdx +1 -0
  267. package/docs/cli/verify.mdx +4 -5
  268. package/docs/desktop.mdx +1 -1
  269. package/docs/docs.json +3 -3
  270. package/docs/enterprise.md +3 -1
  271. package/docs/gates.md +1 -1
  272. package/docs/install-manual.mdx +1 -1
  273. package/docs/license-activation.mdx +145 -0
  274. package/docs/packages/browser.mdx +2 -2
  275. package/docs/packages/server.mdx +1 -1
  276. package/docs/packages.mdx +8 -6
  277. package/docs/platform-integration.md +17 -0
  278. package/docs/predicates.mdx +8 -0
  279. package/docs/reticle-manual.md +106 -0
  280. package/docs/telemetry-contract.md +1 -12
  281. package/docs/telemetry-events.mdx +1 -34
  282. package/docs/telemetry.md +0 -1
  283. package/docs/tools-tools-and-run.mdx +2 -4
  284. package/docs/troubleshooting.mdx +9 -0
  285. package/docs/usage.md +3 -2
  286. package/package.json +2 -2
  287. package/dist/session/session-readiness.d.ts +0 -34
  288. package/dist/session/session-readiness.js +0 -36
  289. package/dist/telemetry/instrumentation-stall.d.ts +0 -57
  290. package/dist/telemetry/instrumentation-stall.js +0 -103
@@ -36,6 +36,15 @@ export declare const AGENTS_MD_PATH = "AGENTS.md";
36
36
  export declare const RETICLE_MD_PATH = "RETICLE.md";
37
37
  /** Cursor project rule; the frontmatter `alwaysApply` keeps it in context on every turn. */
38
38
  export declare const CURSOR_RULE_PATH = ".cursor/rules/reticle.mdc";
39
+ /**
40
+ * Does this file already carry the managed block?
41
+ *
42
+ * Exported so callers cannot invent their own looser test. The begin marker carries explanatory
43
+ * text inside the comment, so a substring check for `reticle:begin` matches files that
44
+ * `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
45
+ * silently appends a second copy instead of updating the first.
46
+ */
47
+ export declare function hasManagedBlock(text: string): boolean;
39
48
  /**
40
49
  * How the agent must INVOKE the CLI.
41
50
  *
@@ -67,6 +76,14 @@ export declare const CLI: string;
67
76
  * Exported because slash-command.ts renders the same rule; there must not be two versions of it.
68
77
  */
69
78
  export declare const DEV_SERVER_POLICY = "**If no dev server is listening, start one yourself.** Read the project's own dev script out of `package.json` (`dev`, `start`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.\n\nFive guards, none optional:\n\n1. **Never start a second one.** If something is already listening on the app's port, use it.\n2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.\n3. **Never kill anything.** Not a dev server, not a daemon, not a port holder \u2014 including one you started.\n4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.\n5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.";
79
+ /**
80
+ * The operative rules, carried by every file that loads on every turn.
81
+ *
82
+ * Short on purpose. This is the text that has to CHANGE BEHAVIOUR, so it states when to verify, when
83
+ * NOT to, how, and the guards against calling something proven when it is not. Everything an agent
84
+ * needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
85
+ */
86
+ export declare const RULE_BODY: string;
70
87
  /** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
71
88
  export declare function reticleMdFile(): string;
72
89
  export declare const AgentRuleStatus: {
@@ -41,6 +41,17 @@ export const CURSOR_RULE_PATH = '.cursor/rules/reticle.mdc';
41
41
  /** Markers delimiting the managed block so a re-run is idempotent and the block stays updatable. */
42
42
  const RULE_BEGIN = '<!-- reticle:begin (managed by `reticle init` — edit outside these markers) -->';
43
43
  const RULE_END = '<!-- reticle:end -->';
44
+ /**
45
+ * Does this file already carry the managed block?
46
+ *
47
+ * Exported so callers cannot invent their own looser test. The begin marker carries explanatory
48
+ * text inside the comment, so a substring check for `reticle:begin` matches files that
49
+ * `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
50
+ * silently appends a second copy instead of updating the first.
51
+ */
52
+ export function hasManagedBlock(text) {
53
+ return text.includes(RULE_BEGIN);
54
+ }
44
55
  /**
45
56
  * How the agent must INVOKE the CLI.
46
57
  *
@@ -87,7 +98,7 @@ Five guards, none optional:
87
98
  * NOT to, how, and the guards against calling something proven when it is not. Everything an agent
88
99
  * needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
89
100
  */
90
- const RULE_BODY = `## Verifying with Reticle
101
+ export const RULE_BODY = `## Verifying with Reticle
91
102
 
92
103
  This app is instrumented by **Reticle**, an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
93
104
 
@@ -103,21 +114,40 @@ This app is instrumented by **Reticle**, an in-app verification layer exposed as
103
114
  - **Only \`reticle_act_and_wait\` and \`reticle_assert\` produce a verdict.** \`reticle_act\` and everything else move or read the app and prove nothing, so a session ending without one of those two has no result however many tools it used.
104
115
  - Covered flows: \`${CLI} gate\` reports which recorded flows the changed files affect and whether they still pass.
105
116
 
117
+ **Setting Reticle up, or just restarted? You are mid-sequence — do not stop until a verdict exists.**
118
+ The whole of it is: instrument the app → get a dev server running → open the app in a browser →
119
+ drive one flow → report the verdict. Every step is yours to do, and none of them needs the user.
120
+ The ONE legitimate pause is a client restart, because a client reads its server list at startup;
121
+ when you come back, you are not finished, you are on the next step — resume it without being asked.
122
+ Stopping anywhere else leaves an app that looks installed and can verify nothing, which is the
123
+ single commonest way this goes wrong. \`/reticle\` carries the full recovery ladder when a step
124
+ does not work; use it rather than guessing, and never report the install as finished without a
125
+ verdict to point at.
126
+
106
127
  **Nothing connected? Get the app running.**
107
128
 
108
129
  ${DEV_SERVER_POLICY}
109
130
 
110
131
  A dev server that is already running does not pick up an edited build config or a newly created plugin file — restart it and hard-reload the tab. And if a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.
111
132
 
133
+ **Finish \`src/reticle-dev.ts\` before you claim setup is done.** \`init\` writes it and cannot always fill it in: a store that needs an argument only reading the code supplies (Jotai atoms, an XState actor, a TanStack \`queryClient\`) is left as a commented \`registerStore\` line. A file that registers nothing looks exactly like a finished one, and \`reticle_state\` then returns empty forever — which is indistinguishable from an app that has nothing to report, so it reads as success. Uncomment the line, complete it, and prove it by driving one flow and seeing your keys come back. If \`init\` told you to restart your client, this is the job waiting for you on the other side of that restart.
134
+
135
+ **Verify each feature as you finish it, not all of them at the end.** Asked for four, build one, drive it, get a verdict, then start the second. A red verdict after four builds has four suspects; after one it has none.
136
+
137
+ **Capture what a change is FOR while you are building it, not afterwards.** \`reticle_intent\` records the business outcome a change is meant to produce, and the only moment anybody knows it is while the change is being made. Pass \`intent\` when you save a flow, so the saved flow carries the reason it exists. A flow without one replays for months and then reports "step 3 failed" instead of what stopped being true for a user.
138
+
112
139
  **Honesty, which is the whole point:**
113
140
 
114
141
  - **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which clause decided that. Report it as unknown, never as working.
142
+ - **\`verified: "no-fault"\` is not a pass either.** It means nothing was DECLARED to prove: the page settled and no channel complained, but you asserted nothing, so there is no verification. You get it whenever \`until\` is omitted. Name a consequence the action changes — a signal, a request, a route, or store state — and call again.
115
143
  - **Never weaken a check to make it green.** Downgrading, skipping or deleting an assertion is a finding, not a fix.
116
144
  - **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.
117
145
  - **Setup is not finished until one real flow has been driven and produced a verdict.** \`init\` exiting 0, the tools appearing, and a session being listed are all things that happen before anything has been verified.
118
146
 
119
147
  **The \`/reticle\` skill runs this whole loop for you** — detect, connect, drive one flow, report. If your client does not have it, install it once: \`/plugin marketplace add reticlehq/reticle\` then \`/plugin install reticle@reticlehq\` in Claude Code, or \`npx skills add reticlehq/reticle\` anywhere the skills CLI works.
120
148
 
149
+ **A tool you need is missing? It exists.** The default surface advertises a subset; reach any other by name with \`reticle_run { tool, args }\`, and list them with \`reticle_tools\`. Two worth knowing: \`reticle_context\` returns this run's own memory — what is established, what is proven, what is still unverified — which is what you want after a compaction or when picking up work you did not start; \`reticle_intent\` records what a change was MEANT to do, while somebody still knows.
150
+
121
151
  **Report Reticle's own defects with \`reticle_feedback\` the moment you notice**, then carry on with your task. You are the user Reticle is built for and the only one who can say what it cost you, and that knowledge is gone when your context is.
122
152
 
123
153
  📄 **The rest is in [RETICLE.md](./${RETICLE_MD_PATH}): what to do when the tools are missing, when a result carries \`version_skew\` or \`update_available\`, when \`reticle_state\` comes back empty, and how to write a feedback report that can be acted on. Read it when you hit one of those, not before.**`;
@@ -24,8 +24,6 @@ export interface FoundStore {
24
24
  /** Module specifier relative to `src/reticle-dev.ts`. */
25
25
  importPath: string;
26
26
  }
27
- /** Where the generated dev module lives, for Vite. Next passes its own. */
28
- export declare const DEFAULT_DEV_MODULE_DIR = "src";
29
27
  /**
30
28
  * Store instances declared in the app's own source, ready to import and register.
31
29
  *
@@ -102,7 +102,7 @@ function importPathFor(storePath, fromDir) {
102
102
  return rel.startsWith('.') ? rel : `./${rel}`;
103
103
  }
104
104
  /** Where the generated dev module lives, for Vite. Next passes its own. */
105
- export const DEFAULT_DEV_MODULE_DIR = 'src';
105
+ const DEFAULT_DEV_MODULE_DIR = 'src';
106
106
  /**
107
107
  * Store instances declared in the app's own source, ready to import and register.
108
108
  *
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The last thing `init` prints, and the only part of it a user is asked to act on.
3
+ *
4
+ * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
+ * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
+ * what to say afterwards. The two failure modes are different too — a wrong write breaks an
7
+ * install, a wrong sentence loses a user quietly.
8
+ */
9
+ import { Framework } from './detect.js';
10
+ import { StepStatus } from './plan.js';
11
+ /**
12
+ * The standing ask, printed at the end of every `init`.
13
+ *
14
+ * Addressed to the agent as much as the human: whichever of them just ran this command is the one
15
+ * holding the experience, and neither has a Reticle tool surface to file through at this point.
16
+ *
17
+ * Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
18
+ * the workspace redirect is kept from asking twice.
19
+ */
20
+ export declare const FEEDBACK_HINT: string;
21
+ /**
22
+ * The last two lines a user reads, and the order matters.
23
+ *
24
+ * This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
25
+ * asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
26
+ * an agent client reads its tool list when it STARTS and never re-reads it, so the session that
27
+ * just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
28
+ * instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
29
+ * failed.
30
+ *
31
+ * So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
32
+ * run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
33
+ */
34
+ export declare function restartHint(framework: Framework, mcpStatus: StepStatus | undefined,
35
+ /** The project's own dev command, when package.json named one. */
36
+ devCommand?: string): string;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The last thing `init` prints, and the only part of it a user is asked to act on.
3
+ *
4
+ * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
+ * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
+ * what to say afterwards. The two failure modes are different too — a wrong write breaks an
7
+ * install, a wrong sentence loses a user quietly.
8
+ */
9
+ import { Framework } from './detect.js';
10
+ import { StepStatus } from './plan.js';
11
+ import { wasMcpRegistered } from './mcp-registered.js';
12
+ import { CLI } from './agent-rules.js';
13
+ /**
14
+ * The standing ask, printed at the end of every `init`.
15
+ *
16
+ * Addressed to the agent as much as the human: whichever of them just ran this command is the one
17
+ * holding the experience, and neither has a Reticle tool surface to file through at this point.
18
+ *
19
+ * Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
20
+ * the workspace redirect is kept from asking twice.
21
+ */
22
+ export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
23
+ 'one thing that decides what gets fixed:\n' +
24
+ ` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
25
+ ` ${CLI} feedback "what worked, what didn't" (humans)`;
26
+ /**
27
+ * What to say about the dev server.
28
+ *
29
+ * Names the project's own script when `package.json` has a recognisable one, and otherwise says the
30
+ * THING rather than guessing the command.
31
+ *
32
+ * The guesses that used to live here — `vite`, `next dev`, `astro dev` — were wrong often enough to
33
+ * be worth deleting. Measured on a real app: rowy has no `dev` script at all, so "Restart `vite`"
34
+ * sent the reader to work out what was meant instead of starting a server. Nobody needs the guess:
35
+ * an agent reading this can open package.json, and a human already knows how they start their own
36
+ * app. A wrong command costs more than a missing one.
37
+ */
38
+ function devServerRestart(_framework, devCommand) {
39
+ return devCommand === undefined
40
+ ? 'Restart your dev server'
41
+ : `Restart your dev server (\`${devCommand}\`)`;
42
+ }
43
+ /**
44
+ * The last two lines a user reads, and the order matters.
45
+ *
46
+ * This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
47
+ * asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
48
+ * an agent client reads its tool list when it STARTS and never re-reads it, so the session that
49
+ * just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
50
+ * instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
51
+ * failed.
52
+ *
53
+ * So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
54
+ * run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
55
+ */
56
+ export function restartHint(framework, mcpStatus,
57
+ /** The project's own dev command, when package.json named one. */
58
+ devCommand) {
59
+ const dev = `${devServerRestart(framework, devCommand)}.`;
60
+ // NAME THE COMMAND THAT PROVES IT, not one that merely asks.
61
+ //
62
+ // `init` writes files and stops; the install is not finished until an app carrying the SDK has
63
+ // actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
64
+ // people complete the agent half, never complete the app half, and keep a daemon running for
65
+ // weeks with nothing to drive — so this is the last instruction most of them read.
66
+ //
67
+ // "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
68
+ // status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
69
+ // running, an app running that never dialled us, a tab that closed — with the fix for each.
70
+ // The one thing that works BEFORE the restart, so it goes before it.
71
+ //
72
+ // What comes after the restart, named as a JOB rather than as a capability.
73
+ //
74
+ // This used to point at a `reticle demo` command that opened its own browser and drove one
75
+ // control. It was cut: a demo command is a side show. The thing a user needs to see is THEIR app,
76
+ // instrumented in THEIR codebase, driven by THEIR agent through the tools they just installed —
77
+ // because that is the workflow they are deciding whether to adopt, and a bespoke demo path proves
78
+ // nothing about it.
79
+ //
80
+ // So the close hands the agent the sequence instead. It is deliberately one drive of one real
81
+ // flow ending in a verdict, because an install that ends in "the tools are available" is an
82
+ // install that ends in nothing.
83
+ const demo = 'Then drive one real flow of your app end to end and report the verdict — that is the install ' +
84
+ "proving itself, and it is the agent's job, not a separate command.";
85
+ const prove = 'Then run `npx @reticlehq/server status` — it confirms the app connected, or says exactly why ' +
86
+ 'it has not.';
87
+ // Nothing registered this run (`--no-mcp`, or a client that needs a manual edit): a restart picks
88
+ // up nothing, so advice about one would be advice about something we did not do.
89
+ if (!wasMcpRegistered(mcpStatus))
90
+ return `${dev}\n${demo}\n${prove}`;
91
+ // ALREADY: the tools are reachable RIGHT NOW, and this is the branch that used to lie.
92
+ //
93
+ // `wasMcpRegistered` is true for both APPLY and ALREADY, which is correct for the funnel field it
94
+ // was written for and wrong here, because the two cases have opposite answers to "must anybody
95
+ // restart". Every second project on a machine, and every Claude Code plugin install, took the
96
+ // APPLY text: "restart your agent — the tools only appear after that". An agent reads this AFTER
97
+ // whatever its skill file said, and it describes the output in front of it, so it wins. The agent
98
+ // stops with the project wired and nothing driven, which is the exact failure the install path
99
+ // exists to prevent, caused by the install path itself.
100
+ if (mcpStatus === StepStatus.ALREADY) {
101
+ return (`${dev}\n` +
102
+ `${demo}\n` +
103
+ 'The Reticle MCP server was already registered on this machine, so there is no restart to do ' +
104
+ 'and the tools are available now.\n' +
105
+ `${prove}\n` +
106
+ 'Once it shows a session, drive a flow — that is the install finished. Carry straight on; ' +
107
+ 'nothing here is waiting on a human.');
108
+ }
109
+ return (`${dev}\n` +
110
+ `${demo}\n` +
111
+ 'Then restart your agent so it picks up the new MCP server — restart Claude Code, reload the ' +
112
+ 'window in Cursor, or hit Start in `.vscode/mcp.json` in VS Code.\n' +
113
+ 'The tools only appear after that: your agent read its server list before Reticle existed, ' +
114
+ 'and no slash command re-reads it (`/mcp` manages servers already loaded, so it cannot pick ' +
115
+ 'up a new one). This is once per machine — Reticle is registered globally, so every later ' +
116
+ 'project starts with the tools already there.\n' +
117
+ `${prove}\n` +
118
+ 'Once it shows a session, drive one flow and report the verdict — that is the install ' +
119
+ 'finished. Do it now; nothing here is waiting on a human.');
120
+ }
@@ -27,15 +27,6 @@
27
27
  */
28
28
  import { InitConfirmation } from '@reticlehq/core';
29
29
  import type { InitOutcome } from '@reticlehq/core';
30
- /**
31
- * How long a human is asked to wait.
32
- *
33
- * Long enough for a page that is already loading to finish dialling, short enough that somebody who
34
- * has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
35
- * have not typed the command that would make it succeed, and the message tells them which one.
36
- */
37
- export declare const CONFIRM_WINDOW_MS = 12000;
38
- export declare const CONFIRM_POLL_MS = 500;
39
30
  export interface ConfirmDeps {
40
31
  /** Session ids connected right now, or null when nothing is listening on the bridge port. */
41
32
  listSessionIds: () => Promise<readonly string[] | null>;
@@ -78,22 +69,6 @@ export declare function confirmAppConnected(deps: ConfirmDeps): Promise<InitConf
78
69
  * up when the agent loads the tools — so it is a notice.
79
70
  */
80
71
  export declare function confirmationMessage(confirmation: InitConfirmation, port: number): string;
81
- /**
82
- * What a non-TTY run says instead of waiting.
83
- *
84
- * `init` deliberately does not block when nobody is at the terminal — a 12-second wait in a script
85
- * is wrong, and an agent driving `init` through a shell is never a TTY. But silence was the wrong
86
- * other half of that decision: the agent path IS the prescribed path (every skill and every README
87
- * block tells an agent to run exactly this command), so the one message joining "init finished" to
88
- * "an app connected" was withheld from most of the people who run it. They were left with files on
89
- * disk, an exit code of 0, and nothing saying the page half had not happened.
90
- *
91
- * It claims nothing it did not check. It did not look for a session, so it does not say whether one
92
- * exists — a one-shot look would be worse than none, because a daemon is shared across every project
93
- * on the machine and "a session exists" is satisfied by somebody else's app, which is the false
94
- * green `confirmAppConnected` refuses by design.
95
- */
96
- export declare function unwatchedMessage(port: number): string;
97
72
  /**
98
73
  * Finish `init` on evidence rather than on a write.
99
74
  *
@@ -35,8 +35,8 @@ import { reportInitOutcome } from '../telemetry/init-telemetry.js';
35
35
  * has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
36
36
  * have not typed the command that would make it succeed, and the message tells them which one.
37
37
  */
38
- export const CONFIRM_WINDOW_MS = 12_000;
39
- export const CONFIRM_POLL_MS = 500;
38
+ const CONFIRM_WINDOW_MS = 12_000;
39
+ const CONFIRM_POLL_MS = 500;
40
40
  /**
41
41
  * Poll until an app connects or the window runs out.
42
42
  *
@@ -110,7 +110,7 @@ export function confirmationMessage(confirmation, port) {
110
110
  * on the machine and "a session exists" is satisfied by somebody else's app, which is the false
111
111
  * green `confirmAppConnected` refuses by design.
112
112
  */
113
- export function unwatchedMessage(port) {
113
+ function unwatchedMessage(port) {
114
114
  return (' [ℹ] the files are written. An app CONNECTING is what finishes the install, and this run did ' +
115
115
  'not wait to see one.\n' +
116
116
  ' Start the dev server, load the app in a browser, then ' +
@@ -17,6 +17,19 @@ export declare const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
17
17
  export declare const CRA_DEV_MODULE_PATH = "src/reticle-dev.ts";
18
18
  export declare const CRA_ENV_PATH = ".env.development.local";
19
19
  export declare const TOKEN_VAR = "REACT_APP_RETICLE_TOKEN";
20
+ /**
21
+ * The bridge URL, carried through the same channel as the token.
22
+ *
23
+ * CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
24
+ * `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
25
+ * `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
26
+ * is refreshed by the same `init` step that refreshes the token.
27
+ *
28
+ * That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
29
+ * plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
30
+ * the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
31
+ */
32
+ export declare const URL_VAR = "REACT_APP_RETICLE_URL";
20
33
  /**
21
34
  * What the app itself says when the token is not there.
22
35
  *
@@ -45,6 +58,6 @@ export declare const CRA_TOKEN_PER_MACHINE_NOTICE: string;
45
58
  /** Add the import after the last existing one, or null when it is already present. */
46
59
  export declare function craImportPatch(source: string): string | null;
47
60
  /** Set the token variable, or null when nothing needs to change. */
48
- export declare function craEnvPatch(existing: string | null, token: string): string | null;
61
+ export declare function craEnvPatch(existing: string | null, token: string, url?: string): string | null;
49
62
  /** The dev-only connect module imported from `src/index.tsx`. */
50
63
  export declare function craDevModuleFile(port: number | undefined, projectId?: string): string;
package/dist/init/cra.js CHANGED
@@ -12,12 +12,26 @@
12
12
  * `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
13
13
  * mechanism and is gitignored by CRA's own template.
14
14
  */
15
+ import { bridgeWsUrl } from '@reticlehq/core';
15
16
  import { CLI } from './agent-rules.js';
16
17
  /** The line added to `src/index.tsx`. Side-effect import: the module guards itself on NODE_ENV. */
17
18
  export const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
18
19
  export const CRA_DEV_MODULE_PATH = 'src/reticle-dev.ts';
19
20
  export const CRA_ENV_PATH = '.env.development.local';
20
21
  export const TOKEN_VAR = 'REACT_APP_RETICLE_TOKEN';
22
+ /**
23
+ * The bridge URL, carried through the same channel as the token.
24
+ *
25
+ * CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
26
+ * `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
27
+ * `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
28
+ * is refreshed by the same `init` step that refreshes the token.
29
+ *
30
+ * That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
31
+ * plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
32
+ * the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
33
+ */
34
+ export const URL_VAR = 'REACT_APP_RETICLE_URL';
21
35
  /**
22
36
  * What the app itself says when the token is not there.
23
37
  *
@@ -64,25 +78,38 @@ export function craImportPatch(source) {
64
78
  return lines.join('\n');
65
79
  }
66
80
  /** Set the token variable, or null when nothing needs to change. */
67
- export function craEnvPatch(existing, token) {
81
+ export function craEnvPatch(existing, token, url) {
68
82
  if ('' === token)
69
83
  return null;
70
- const line = `${TOKEN_VAR}=${token}`;
71
- if (null === existing || '' === existing.trim())
72
- return `${line}\n`;
73
- if (existing.includes(line))
74
- return null;
75
- // Replace rather than append: two assignments of one variable is a silent coin flip on which wins.
76
- if (new RegExp(`^${TOKEN_VAR}=`, 'm').test(existing)) {
77
- return existing.replace(new RegExp(`^${TOKEN_VAR}=.*$`, 'm'), line);
84
+ const pairs = [
85
+ [TOKEN_VAR, token],
86
+ ...(url !== undefined && url.length > 0 ? [[URL_VAR, url]] : []),
87
+ ];
88
+ let next = null === existing || '' === existing.trim() ? '' : existing;
89
+ for (const [name, value] of pairs) {
90
+ const line = `${name}=${value}`;
91
+ if (next.includes(line))
92
+ continue;
93
+ // Replace rather than append: two assignments of one variable is a silent coin flip on which
94
+ // wins, and for the URL that coin decides which daemon the app talks to.
95
+ if (new RegExp(`^${name}=`, 'm').test(next)) {
96
+ next = next.replace(new RegExp(`^${name}=.*$`, 'm'), line);
97
+ }
98
+ else {
99
+ next = `${'' === next || next.endsWith('\n') ? next : `${next}\n`}${line}\n`;
100
+ }
78
101
  }
79
- return `${existing.endsWith('\n') ? existing : `${existing}\n`}${line}\n`;
102
+ return next === (existing ?? '') ? null : next;
80
103
  }
81
104
  /** The dev-only connect module imported from `src/index.tsx`. */
82
105
  export function craDevModuleFile(port, projectId) {
83
106
  const fields = [];
107
+ // `bridgeWsUrl`, not a hand-written string. This was the only client URL in the product spelling
108
+ // the host as `127.0.0.1` while every other generator said `localhost` — same endpoint, but a
109
+ // difference with no reason behind it is the kind that becomes a real one after somebody edits half
110
+ // of it.
84
111
  if (port !== undefined)
85
- fields.push(`url: 'ws://127.0.0.1:${String(port)}/reticle'`);
112
+ fields.push(`url: '${bridgeWsUrl(port)}'`);
86
113
  if (projectId !== undefined && projectId.length > 0)
87
114
  fields.push(`projectId: '${projectId}'`);
88
115
  const inline = fields.length > 0 ? `${fields.join(', ')}, ` : '';
@@ -95,10 +122,14 @@ if (process.env.NODE_ENV === 'development') {
95
122
  void import('@reticlehq/react').then(({ reticle, install }) => {
96
123
  install();
97
124
  const token = process.env.${TOKEN_VAR} ?? '';
125
+ // Written by \`reticle init\` from the daemon that was live when it ran, and refreshed by
126
+ // re-running it. CRA gives us no hook to resolve this at dev-server start, so if the daemon
127
+ // moves, re-run \`reticle init\` rather than editing the url below.
128
+ const url = process.env.${URL_VAR} ?? '';
98
129
  // Loud on purpose: without this the only symptom is the bridge's generic auth failure.
99
130
  if (token.length === 0) console.error(${JSON.stringify(`[reticle] ${CRA_TOKEN_MISSING_NOTE}`)});
100
131
  // Still attempt it — a bridge running without a token pairs fine.
101
- reticle.connect({ ${inline}...(token.length > 0 ? { token } : {}) });
132
+ reticle.connect({ ${inline}...(url.length > 0 ? { url } : {}), ...(token.length > 0 ? { token } : {}) });
102
133
  });
103
134
  }
104
135
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Which command starts this project, and should we start it ourselves?
3
+ *
4
+ * `init` finishes by telling the human to start or restart their dev server, in a closing
5
+ * paragraph, in a terminal that is about to be cleared by the client restart it also asks for.
6
+ * That is the second unmarked hand-off in the install, after the capabilities file, and it lands
7
+ * on the same surface: prose nobody re-reads.
8
+ *
9
+ * Every fact needed to remove it is already in the project. `package.json` names the script; the
10
+ * framework tells us the conventional port; a probe tells us whether something already answers
11
+ * there. Nothing here needs a human.
12
+ *
13
+ * Kept as a PURE decision, separate from spawning, because the interesting part is the judgement —
14
+ * which script, and whether to touch anything at all — and that is what wants testing. Spawning a
15
+ * process is not.
16
+ */
17
+ export declare const DevScriptChoice: {
18
+ /** Something already answers on the port. Use it; never replace a server the user is running. */
19
+ readonly ALREADY_SERVING: "already-serving";
20
+ /** We know the command and nothing is listening. Start it. */
21
+ readonly START: "start";
22
+ /** No script we recognise. Say so rather than guess a command. */
23
+ readonly NO_SCRIPT: "no-script";
24
+ };
25
+ export type DevScriptChoice = (typeof DevScriptChoice)[keyof typeof DevScriptChoice];
26
+ export interface DevScriptPlan {
27
+ choice: DevScriptChoice;
28
+ /** The npm script name, when there is one. */
29
+ script?: string;
30
+ /** What a human would type, for the line we print. */
31
+ command?: string;
32
+ }
33
+ /**
34
+ * Pick the script and decide whether to run it.
35
+ *
36
+ * `serving` is the probe result, passed in rather than measured here so the decision stays pure.
37
+ * When something is already answering we do not care which script exists: the rule is never to
38
+ * start a second server, because the one that is running may be the user's, with their state in it.
39
+ */
40
+ export declare function planDevScript(scripts: Readonly<Record<string, string>>, packageManager: string, serving: boolean): DevScriptPlan;
41
+ /**
42
+ * The command to print for this project, straight from its `package.json`.
43
+ *
44
+ * Lives here rather than in `run.ts` because it is the same judgement as `planDevScript` with the
45
+ * parsing attached, and `run.ts` is at its cohesion limit. Returns undefined for anything it cannot
46
+ * read: this phrasing decides nothing, so it must never be able to break an install.
47
+ */
48
+ export declare function devCommandFrom(pkgRaw: string, packageManager: string): string | undefined;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Which command starts this project, and should we start it ourselves?
3
+ *
4
+ * `init` finishes by telling the human to start or restart their dev server, in a closing
5
+ * paragraph, in a terminal that is about to be cleared by the client restart it also asks for.
6
+ * That is the second unmarked hand-off in the install, after the capabilities file, and it lands
7
+ * on the same surface: prose nobody re-reads.
8
+ *
9
+ * Every fact needed to remove it is already in the project. `package.json` names the script; the
10
+ * framework tells us the conventional port; a probe tells us whether something already answers
11
+ * there. Nothing here needs a human.
12
+ *
13
+ * Kept as a PURE decision, separate from spawning, because the interesting part is the judgement —
14
+ * which script, and whether to touch anything at all — and that is what wants testing. Spawning a
15
+ * process is not.
16
+ */
17
+ /** Script names a JS project uses to run itself, in the order we would pick them. */
18
+ const CANDIDATES = ['dev', 'start', 'serve'];
19
+ export const DevScriptChoice = {
20
+ /** Something already answers on the port. Use it; never replace a server the user is running. */
21
+ ALREADY_SERVING: 'already-serving',
22
+ /** We know the command and nothing is listening. Start it. */
23
+ START: 'start',
24
+ /** No script we recognise. Say so rather than guess a command. */
25
+ NO_SCRIPT: 'no-script',
26
+ };
27
+ /**
28
+ * Pick the script and decide whether to run it.
29
+ *
30
+ * `serving` is the probe result, passed in rather than measured here so the decision stays pure.
31
+ * When something is already answering we do not care which script exists: the rule is never to
32
+ * start a second server, because the one that is running may be the user's, with their state in it.
33
+ */
34
+ export function planDevScript(scripts, packageManager, serving) {
35
+ if (serving)
36
+ return { choice: DevScriptChoice.ALREADY_SERVING };
37
+ const name = CANDIDATES.find((c) => 'string' === typeof scripts[c] && scripts[c] !== '');
38
+ if (name === undefined)
39
+ return { choice: DevScriptChoice.NO_SCRIPT };
40
+ // `npm` needs `run`; the others take the script name directly. Getting this wrong prints a
41
+ // command that fails, which is worse than printing nothing.
42
+ const command = 'npm' === packageManager ? `npm run ${name}` : `${packageManager} ${name}`;
43
+ return { choice: DevScriptChoice.START, script: name, command };
44
+ }
45
+ /**
46
+ * The command to print for this project, straight from its `package.json`.
47
+ *
48
+ * Lives here rather than in `run.ts` because it is the same judgement as `planDevScript` with the
49
+ * parsing attached, and `run.ts` is at its cohesion limit. Returns undefined for anything it cannot
50
+ * read: this phrasing decides nothing, so it must never be able to break an install.
51
+ */
52
+ export function devCommandFrom(pkgRaw, packageManager) {
53
+ try {
54
+ const parsed = JSON.parse(pkgRaw);
55
+ const scripts = 'object' === typeof parsed && null !== parsed
56
+ ? (parsed.scripts ?? {})
57
+ : {};
58
+ return planDevScript(scripts, packageManager, false).command;
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
@@ -15,11 +15,16 @@ export { MCP_SERVER_NAME } from '@reticlehq/core';
15
15
  /**
16
16
  * The registered command is STILL bare `npx` on every platform, deliberately.
17
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.
18
+ * Windows is most of Reticle's users and, when this was written, had no CI. There is Windows CI
19
+ * now, so the `cmd /c` fallback is testable. Defaulting the majority platform to that launch
20
+ * command is still the riskier change: `.cmd` has spawn caveats in some hosts, and a registration
21
+ * that works on the machines we can test can still fail in the hosts we cannot. So the fallback
22
+ * stays documented rather than defaulted.
23
+ *
24
+ * What DID change is when it is printed. `mcpManual` used to run only when the `claude` CLI was
25
+ * absent. The reported Windows install had `claude mcp add` succeed, so the one paragraph that
26
+ * would have unblocked zero tools never appeared. `mcpWindowsNote` is that paragraph, and init
27
+ * prints it on every Windows run — including the ones that look like a clean registration.
23
28
  */
24
29
  export declare const NPX = "npx";
25
30
  /**
@@ -74,5 +79,11 @@ export declare function claudeAvailableProbe(): {
74
79
  command: string;
75
80
  args: string[];
76
81
  };
82
+ /**
83
+ * The Windows `cmd /c npx` registration. Printed on every Windows init, not only when the `claude`
84
+ * CLI is missing — a successful `claude mcp add` of bare `npx` is exactly the failure that reported
85
+ * as a clean install with zero tools.
86
+ */
87
+ export declare function mcpWindowsNote(): string;
77
88
  /** Printed when the `claude` CLI isn't available — register Reticle globally once, by hand. */
78
89
  export declare function mcpManual(): string;