@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.
- package/SKILL.md +73 -11
- package/dist/bridge/bridge.d.ts +0 -13
- package/dist/bridge/bridge.js +1 -1
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/capsule/capsule-store.d.ts +2 -1
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +8 -0
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -1
- package/dist/cli/cli-parse.d.ts +3 -1
- package/dist/cli/cli-parse.js +29 -7
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +1 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/cloud-cli.js +12 -6
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +3 -1
- package/dist/cli/doctor-rows.js +3 -1
- package/dist/cli/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +143 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli.js +37 -110
- package/dist/cloud/cloud-config.d.ts +2 -1
- package/dist/cloud/cloud-sync.d.ts +11 -0
- package/dist/cloud/cloud-sync.js +32 -0
- package/dist/crawl/crawl.d.ts +9 -13
- package/dist/crawl/crawl.js +20 -3
- package/dist/daemon/daemon-resolve.d.ts +79 -0
- package/dist/daemon/daemon-resolve.js +118 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/dev/stale-issue-guard.d.ts +41 -0
- package/dist/dev/stale-issue-guard.js +75 -0
- package/dist/domain/domain-tools.js +3 -2
- package/dist/events/contradictions.d.ts +41 -0
- package/dist/events/contradictions.js +125 -12
- package/dist/events/declared.d.ts +14 -0
- package/dist/events/declared.js +33 -0
- package/dist/events/echo-mismatch.js +14 -0
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +30 -0
- package/dist/events/predicate-asks.js +48 -0
- package/dist/events/predicate-eval.js +254 -25
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +51 -5
- package/dist/events/predicate-schema.d.ts +62 -0
- package/dist/events/predicate-schema.js +162 -4
- package/dist/events/predicate.d.ts +15 -0
- package/dist/events/predicate.js +41 -2
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +0 -2
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- package/dist/flows/flow-replay-run.js +27 -4
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +65 -8
- package/dist/flows/flows.d.ts +6 -1
- package/dist/flows/flows.js +34 -22
- package/dist/flows/predicate-to-expect.js +14 -3
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -2
- package/dist/honesty/blind-spots.d.ts +8 -16
- package/dist/honesty/blind-spots.js +18 -5
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/gap-ledger.d.ts +52 -0
- package/dist/honesty/gap-ledger.js +63 -0
- package/dist/honesty/instrumentation-gaps.d.ts +79 -0
- package/dist/honesty/instrumentation-gaps.js +77 -0
- package/dist/honesty/observability.d.ts +37 -0
- package/dist/honesty/observability.js +40 -0
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +15 -0
- package/dist/honesty/verified.js +68 -4
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +1 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-store.d.ts +0 -5
- package/dist/impact/impact-store.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +41 -0
- package/dist/init/agent-rules.d.ts +17 -0
- package/dist/init/agent-rules.js +31 -1
- package/dist/init/capabilities.d.ts +0 -2
- package/dist/init/capabilities.js +1 -1
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +0 -25
- package/dist/init/confirm.js +3 -3
- package/dist/init/cra.d.ts +14 -1
- package/dist/init/cra.js +43 -12
- package/dist/init/dev-script.d.ts +48 -0
- package/dist/init/dev-script.js +63 -0
- package/dist/init/mcp.d.ts +16 -5
- package/dist/init/mcp.js +26 -10
- package/dist/init/node-io.js +3 -0
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +83 -9
- package/dist/init/plan.d.ts +10 -30
- package/dist/init/plan.js +30 -15
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +2 -10
- package/dist/init/run.js +26 -68
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +33 -10
- package/dist/init/snippets.js +53 -10
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +3 -2
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/intent/inline-intent.d.ts +46 -0
- package/dist/intent/inline-intent.js +103 -0
- package/dist/intent/intent-store.d.ts +37 -0
- package/dist/intent/intent-store.js +112 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +80 -0
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +25 -0
- package/dist/journal/envelope.d.ts +2 -2
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license.d.ts +8 -2
- package/dist/license/license.js +10 -15
- package/dist/mcp/attach-memory.d.ts +67 -0
- package/dist/mcp/attach-memory.js +142 -0
- package/dist/mcp/mcp-proxy.d.ts +0 -7
- package/dist/mcp/mcp-proxy.js +14 -2
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +45 -23
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +10 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +12 -1
- package/dist/pool/browser-pool.js +31 -1
- package/dist/pool/playwright-launcher.js +3 -1
- package/dist/project/artifact-root.d.ts +83 -0
- package/dist/project/artifact-root.js +112 -0
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.d.ts +9 -0
- package/dist/project/memory-fs.js +93 -0
- package/dist/project/project-store.d.ts +17 -0
- package/dist/project/project-store.js +33 -0
- package/dist/project/project-tools.js +3 -32
- package/dist/project/remember-project.d.ts +24 -0
- package/dist/project/remember-project.js +40 -0
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/project/session-root.d.ts +15 -0
- package/dist/project/session-root.js +31 -0
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- package/dist/runs/run-tools.js +2 -1
- package/dist/runs/verification-sync.js +2 -2
- package/dist/session/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-availability.d.ts +40 -0
- package/dist/session/lease-availability.js +43 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +20 -0
- package/dist/session/no-session-diagnosis.js +41 -12
- package/dist/session/no-session-next-action.d.ts +5 -0
- package/dist/session/no-session-next-action.js +35 -3
- package/dist/session/no-session-watch.d.ts +8 -0
- package/dist/session/no-session-watch.js +83 -65
- package/dist/session/output-budget.js +4 -1
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-manager.js +50 -1
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +78 -0
- package/dist/session/session.d.ts +40 -1
- package/dist/session/session.js +84 -2
- package/dist/session/stall-clock.d.ts +18 -0
- package/dist/session/stall-clock.js +48 -0
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.js +12 -23
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/project-profile.d.ts +0 -8
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +12 -3
- package/dist/telemetry/telemetry.js +25 -5
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-tools.js +159 -24
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +21 -2
- package/dist/tools/assert-verdict.js +59 -4
- package/dist/tools/browser-tools.js +0 -20
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/contract-tools.js +5 -3
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +124 -37
- package/dist/tools/dynamic-tools.js +29 -8
- package/dist/tools/error-recovery.d.ts +24 -14
- package/dist/tools/error-recovery.js +70 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +62 -2
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +5 -1
- package/dist/tools/lease-tools.js +22 -8
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/observe-tools.js +70 -27
- package/dist/tools/read-tools.js +5 -3
- package/dist/tools/real-input-attempt.d.ts +22 -0
- package/dist/tools/real-input-attempt.js +153 -1
- package/dist/tools/reconcile-tools.js +18 -4
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +28 -1
- package/dist/tools/tool-kit.js +16 -0
- package/dist/tools/tool-names.d.ts +3 -0
- package/dist/tools/tool-names.js +3 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools.js +22 -4
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- package/dist/update/update-nudge.d.ts +7 -10
- package/dist/update/update-nudge.js +12 -3
- package/dist/update/updater.d.ts +1 -1
- package/docs/README.md +3 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/verify.mdx +4 -5
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +3 -3
- package/docs/enterprise.md +3 -1
- package/docs/gates.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +145 -0
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/telemetry-contract.md +1 -12
- package/docs/telemetry-events.mdx +1 -34
- package/docs/telemetry.md +0 -1
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +3 -2
- package/package.json +2 -2
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
- package/dist/telemetry/instrumentation-stall.d.ts +0 -57
- package/dist/telemetry/instrumentation-stall.js +0 -103
package/dist/init/mcp.js
CHANGED
|
@@ -19,11 +19,16 @@ import { MCP_SERVER_NAME } from '@reticlehq/core';
|
|
|
19
19
|
/**
|
|
20
20
|
* The registered command is STILL bare `npx` on every platform, deliberately.
|
|
21
21
|
*
|
|
22
|
-
* Windows is
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* Windows is most of Reticle's users and, when this was written, had no CI. There is Windows CI
|
|
23
|
+
* now, so the `cmd /c` fallback is testable. Defaulting the majority platform to that launch
|
|
24
|
+
* command is still the riskier change: `.cmd` has spawn caveats in some hosts, and a registration
|
|
25
|
+
* that works on the machines we can test can still fail in the hosts we cannot. So the fallback
|
|
26
|
+
* stays documented rather than defaulted.
|
|
27
|
+
*
|
|
28
|
+
* What DID change is when it is printed. `mcpManual` used to run only when the `claude` CLI was
|
|
29
|
+
* absent. The reported Windows install had `claude mcp add` succeed, so the one paragraph that
|
|
30
|
+
* would have unblocked zero tools never appeared. `mcpWindowsNote` is that paragraph, and init
|
|
31
|
+
* prints it on every Windows run — including the ones that look like a clean registration.
|
|
27
32
|
*/
|
|
28
33
|
export const NPX = 'npx';
|
|
29
34
|
// The `reticle` bin lives in the server package, so `npx <pkg> mcp` runs the bridge without the
|
|
@@ -31,6 +36,9 @@ export const NPX = 'npx';
|
|
|
31
36
|
const RETICLE_PACKAGE = RETICLE_NPM_PACKAGE;
|
|
32
37
|
const MCP_SUBCOMMAND = 'mcp';
|
|
33
38
|
const CLAUDE_CLI = 'claude';
|
|
39
|
+
/** Windows `cmd /c` — the spawn that PowerShell's execution policy does not gate. */
|
|
40
|
+
const WINDOWS_SHELL = 'cmd';
|
|
41
|
+
const WINDOWS_SHELL_C = '/c';
|
|
34
42
|
/**
|
|
35
43
|
* Args after `npx` that launch the bridge: `@reticlehq/server mcp`. Portless — the port comes from
|
|
36
44
|
* the project's `.reticle.json` at runtime, so one global entry works for every project.
|
|
@@ -94,6 +102,18 @@ export function claudeExistsProbe() {
|
|
|
94
102
|
export function claudeAvailableProbe() {
|
|
95
103
|
return { command: CLAUDE_CLI, args: ['--version'] };
|
|
96
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The Windows `cmd /c npx` registration. Printed on every Windows init, not only when the `claude`
|
|
107
|
+
* CLI is missing — a successful `claude mcp add` of bare `npx` is exactly the failure that reported
|
|
108
|
+
* as a clean install with zero tools.
|
|
109
|
+
*/
|
|
110
|
+
export function mcpWindowsNote() {
|
|
111
|
+
return `On Windows, if the agent cannot start Reticle because npx is blocked ("running scripts is disabled
|
|
112
|
+
on this system" — a PowerShell execution policy), register it through cmd instead, which that policy
|
|
113
|
+
does not gate:
|
|
114
|
+
|
|
115
|
+
"${MCP_SERVER_NAME}": { "command": "${WINDOWS_SHELL}", "args": ${JSON.stringify([WINDOWS_SHELL_C, NPX, ...serverInvocation().slice(1)])} }`;
|
|
116
|
+
}
|
|
97
117
|
/** Printed when the `claude` CLI isn't available — register Reticle globally once, by hand. */
|
|
98
118
|
export function mcpManual() {
|
|
99
119
|
const tail = serverInvocation().join(' ');
|
|
@@ -105,9 +125,5 @@ Or, for another agent, add this to its global MCP config (e.g. Cursor's ~/.curso
|
|
|
105
125
|
|
|
106
126
|
"${MCP_SERVER_NAME}": { "command": "${NPX}", "args": ${JSON.stringify(serverInvocation().slice(1))} }
|
|
107
127
|
|
|
108
|
-
|
|
109
|
-
on this system" — a PowerShell execution policy), register it through cmd instead, which that policy
|
|
110
|
-
does not gate:
|
|
111
|
-
|
|
112
|
-
"${MCP_SERVER_NAME}": { "command": "cmd", "args": ${JSON.stringify(['/c', NPX, ...serverInvocation().slice(1)])} }`;
|
|
128
|
+
${mcpWindowsNote()}`;
|
|
113
129
|
}
|
package/dist/init/node-io.js
CHANGED
|
@@ -17,11 +17,6 @@ export declare const VITE_PLUGIN_DETAIL: {
|
|
|
17
17
|
readonly SVELTEKIT: "add reticle() to plugins (stamps data-reticle-source in .svelte components)";
|
|
18
18
|
};
|
|
19
19
|
export declare function viteSteps(input: PlanInput, detail?: string): Step[];
|
|
20
|
-
/**
|
|
21
|
-
* Next used to be the ONLY stack with hand edits left over — and both of them fail silently when
|
|
22
|
-
* skipped, so a Next user's app booted, connected to nothing, and said nothing about why. Both are
|
|
23
|
-
* now patched by the same conservative rules the Vite config gets.
|
|
24
|
-
*/
|
|
25
20
|
export declare function nextSteps(input: PlanInput): Step[];
|
|
26
21
|
/**
|
|
27
22
|
* SvelteKit is WIRED but not SUPPORTED, and the plan says so out loud.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Split out of `plan.ts` — that file is the plan's SHAPE (statuses, ordering, the agent/MCP steps),
|
|
4
4
|
* this one is the per-framework detail, and they grow for different reasons.
|
|
5
5
|
*/
|
|
6
|
+
import { bridgeWsUrl } from '@reticlehq/core';
|
|
6
7
|
import { patchViteConfig, VitePatchKind } from './vite-config.js';
|
|
7
8
|
import { patchNextConfig, patchRootLayout, patchPagesApp } from './next-patch.js';
|
|
8
9
|
import { patchAstroConfig, patchAstroLayout } from './astro-patch.js';
|
|
@@ -62,6 +63,22 @@ function capabilitiesTodo(path, stores) {
|
|
|
62
63
|
* `__reticle_renders` — the state-truth read was unavailable on every app out of the box. Written
|
|
63
64
|
* only when absent, because it is the one generated file a user is expected to EDIT.
|
|
64
65
|
*/
|
|
66
|
+
/**
|
|
67
|
+
* Does the generated dev module actually REGISTER anything?
|
|
68
|
+
*
|
|
69
|
+
* Shared by the Vite and Next paths because it is one question about one generated call, and the
|
|
70
|
+
* two answered it differently for as long as only Vite asked: `capabilitiesStep` was called from
|
|
71
|
+
* `viteSteps` and nowhere else, so a Next app whose store can only be SUGGESTED wrote
|
|
72
|
+
* `registerCapabilities({ testids: [], stores: [] })`, registered nothing, and said nothing.
|
|
73
|
+
* Measured on a real Next + mobx app: five steps, every one a checkmark, no capabilities line.
|
|
74
|
+
*
|
|
75
|
+
* `wired` are stores init resolved and wrote a live `registerStore` call for. Hints are not
|
|
76
|
+
* registrations: they land in the file as a commented line, and counting one as a registration
|
|
77
|
+
* silences the notice whose whole purpose is to say "act on the hint".
|
|
78
|
+
*/
|
|
79
|
+
function registersNothing(testids, wired) {
|
|
80
|
+
return 0 === testids.length && 0 === wired.length;
|
|
81
|
+
}
|
|
65
82
|
function capabilitiesStep(input) {
|
|
66
83
|
if (true === input.viteDevModuleExists) {
|
|
67
84
|
return [
|
|
@@ -79,8 +96,21 @@ function capabilitiesStep(input) {
|
|
|
79
96
|
const found = testids.length > 0
|
|
80
97
|
? `${String(testids.length)} data-testid values`
|
|
81
98
|
: 'no data-testid values yet';
|
|
82
|
-
// A store we found and
|
|
83
|
-
|
|
99
|
+
// A store we found and WIRED is a registration, so the "registers nothing" notice must not fire.
|
|
100
|
+
// A store HINT is not: `stores` holds suggestions, written into the file as a commented line of
|
|
101
|
+
// the form `// import your store, then: registerStore(...)`. Counting a suggestion as a
|
|
102
|
+
// registration let the hint silence the notice whose entire job is to say "act on the hint".
|
|
103
|
+
//
|
|
104
|
+
// Measured against a real product UI (rowy — 70+ deps, jotai, a whole src/atoms tree): init
|
|
105
|
+
// detected jotai, offered one commented line, emitted NO notice, and wrote
|
|
106
|
+
// `registerCapabilities({ testids: [], signals: [], stores: [] })`. So `hasCapabilities` stayed
|
|
107
|
+
// false, `reticle_state` was empty forever, and the install gate reported "connected: 1,
|
|
108
|
+
// manual ⚠: none". Every check green, state observability zero.
|
|
109
|
+
//
|
|
110
|
+
// This is the common case rather than an edge one: a library whose registration needs an argument
|
|
111
|
+
// only reading the source supplies — jotai atoms, an XState actor, a TanStack queryClient — is
|
|
112
|
+
// exactly what init leaves commented.
|
|
113
|
+
const nothingToRegister = registersNothing(testids, wired);
|
|
84
114
|
return [
|
|
85
115
|
{
|
|
86
116
|
title: CAPABILITIES_TITLE,
|
|
@@ -197,16 +227,44 @@ function patchStep(title, path, patch, applyDetail, manualDetail) {
|
|
|
197
227
|
* skipped, so a Next user's app booted, connected to nothing, and said nothing about why. Both are
|
|
198
228
|
* now patched by the same conservative rules the Vite config gets.
|
|
199
229
|
*/
|
|
230
|
+
/** The env var withReticle sets from the discovered daemon, and the component must read. */
|
|
231
|
+
const NEXT_DAEMON_URL_ENV = 'NEXT_PUBLIC_RETICLE_URL';
|
|
232
|
+
/**
|
|
233
|
+
* The two-line edit that unfreezes an existing install's port.
|
|
234
|
+
*
|
|
235
|
+
* Stated as the edit rather than as a problem, because the reader is an agent that can apply it in
|
|
236
|
+
* one write and is otherwise going to ask what to do.
|
|
237
|
+
*/
|
|
238
|
+
const NEXT_DEV_FILE_STALE_DETAIL = 'predates daemon discovery, so it dials the port init saw when it ran. In this file add ' +
|
|
239
|
+
'`const url = process.env.NEXT_PUBLIC_RETICLE_URL;` and spread `...(url ? { url } : {})` into ' +
|
|
240
|
+
'reticle.connect(), after any url already there. Nothing else needs to change.';
|
|
200
241
|
export function nextSteps(input) {
|
|
201
242
|
const configFile = input.nextConfigFile ?? 'next.config.mjs';
|
|
202
243
|
const devPath = input.nextReticleDevPath ?? NEXT_RETICLE_DEV_PATH;
|
|
244
|
+
// An install that predates daemon discovery has a component that never reads the discovered URL,
|
|
245
|
+
// so it keeps dialling whatever port `init` saw on the day it ran. Re-running `init` used to call
|
|
246
|
+
// that "file exists" and move on, which is why the defect survives an upgrade. Named as work.
|
|
247
|
+
//
|
|
248
|
+
// Never overwritten: this file is the one an app owner edits — registered stores, capabilities,
|
|
249
|
+
// their own signals. Rewriting it to fix two lines would take the rest with it. Undefined source
|
|
250
|
+
// means it was not read, and an unread file stays ALREADY rather than becoming invented work.
|
|
251
|
+
const devStale = true === input.nextReticleDevExists &&
|
|
252
|
+
'string' === typeof input.nextReticleDevSource &&
|
|
253
|
+
!input.nextReticleDevSource.includes(NEXT_DAEMON_URL_ENV);
|
|
203
254
|
const devFile = input.nextReticleDevExists
|
|
204
|
-
?
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
255
|
+
? devStale
|
|
256
|
+
? {
|
|
257
|
+
title: 'ReticleDev component',
|
|
258
|
+
target: devPath,
|
|
259
|
+
status: StepStatus.MANUAL,
|
|
260
|
+
detail: NEXT_DEV_FILE_STALE_DETAIL,
|
|
261
|
+
}
|
|
262
|
+
: {
|
|
263
|
+
title: 'ReticleDev component',
|
|
264
|
+
target: devPath,
|
|
265
|
+
status: StepStatus.ALREADY,
|
|
266
|
+
detail: 'file exists',
|
|
267
|
+
}
|
|
210
268
|
: {
|
|
211
269
|
title: 'ReticleDev component',
|
|
212
270
|
target: devPath,
|
|
@@ -233,8 +291,22 @@ export function nextSteps(input) {
|
|
|
233
291
|
: isPagesRouter
|
|
234
292
|
? patchPagesApp(layout.source, input.nextReticleDevImport)
|
|
235
293
|
: patchRootLayout(layout.source);
|
|
294
|
+
// The same question the Vite path asks. Skipped when the module already exists: its contents are
|
|
295
|
+
// the user's, and re-nagging about a file we did not write is noise.
|
|
296
|
+
const nextTodo = true !== input.nextReticleDevExists &&
|
|
297
|
+
registersNothing(input.testids ?? [], input.nextFoundStores ?? [])
|
|
298
|
+
? [
|
|
299
|
+
{
|
|
300
|
+
title: CAPABILITIES_TODO_TITLE,
|
|
301
|
+
target: devPath,
|
|
302
|
+
status: StepStatus.NOTICE,
|
|
303
|
+
detail: capabilitiesTodo(devPath, input.storeHints ?? []),
|
|
304
|
+
},
|
|
305
|
+
]
|
|
306
|
+
: [];
|
|
236
307
|
return [
|
|
237
308
|
devFile,
|
|
309
|
+
...nextTodo,
|
|
238
310
|
patchStep('Next config (withReticle)', configFile, configPatch, 'wrap the export in withReticle (source mapping, dev-only)', nextConfigManual(configFile)),
|
|
239
311
|
patchStep('Mount ReticleDev', layout?.path ?? NEXT_LAYOUT_PATH, layoutPatch, 'mount <ReticleDev /> in the root layout (dev-only)', NEXT_LAYOUT_MANUAL),
|
|
240
312
|
];
|
|
@@ -269,7 +341,9 @@ export function craSteps(input) {
|
|
|
269
341
|
},
|
|
270
342
|
];
|
|
271
343
|
const token = input.pairingToken ?? '';
|
|
272
|
-
|
|
344
|
+
// The daemon that is live NOW, not the one baked into the module at first install. CRA has no
|
|
345
|
+
// build hook, so this is refreshed by re-running `init` rather than by starting the dev server.
|
|
346
|
+
const env = craEnvPatch(input.craEnv ?? null, token, input.options.port === undefined ? undefined : bridgeWsUrl(input.options.port));
|
|
273
347
|
if (env !== null) {
|
|
274
348
|
steps.push({
|
|
275
349
|
title: 'Pairing token',
|
package/dist/init/plan.d.ts
CHANGED
|
@@ -6,16 +6,6 @@
|
|
|
6
6
|
import { Framework, UiLibrary, type Detection } from './detect.js';
|
|
7
7
|
import type { FoundStore } from './capabilities.js';
|
|
8
8
|
import { type McpClient } from './mcp-clients.js';
|
|
9
|
-
/**
|
|
10
|
-
* Pin the SDK to the CLI's own version.
|
|
11
|
-
*
|
|
12
|
-
* `pnpm add -D @reticlehq/react` installed **2.2.1** in one project while npm and yarn took 2.3.0 in
|
|
13
|
-
* the next — a stale registry metadata cache, invisible to the user and to us. A version-skewed SDK
|
|
14
|
-
* talking to a newer daemon is the `-32000` failure path: the app connects, the protocol disagrees,
|
|
15
|
-
* and nothing on either side names a version. Asking for the CLI's exact version makes the cache
|
|
16
|
-
* irrelevant, and a skewed pair impossible to install by accident.
|
|
17
|
-
*/
|
|
18
|
-
export declare function pinnedPackages(packages: readonly string[], version: string | undefined): readonly string[];
|
|
19
9
|
/**
|
|
20
10
|
* The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
|
|
21
11
|
*
|
|
@@ -101,6 +91,8 @@ export interface PlanInput {
|
|
|
101
91
|
mcpExists: boolean;
|
|
102
92
|
/** Whether Cursor is installed for this user (its global config dir exists). */
|
|
103
93
|
cursorPresent: boolean;
|
|
94
|
+
/** `process.platform`. Injected so this module stays pure. Windows is the only branch. */
|
|
95
|
+
platform?: string;
|
|
104
96
|
/** Whether THIS project has a .cursor/ directory — the signal that Cursor works on this repo. */
|
|
105
97
|
cursorProjectPresent?: boolean | undefined;
|
|
106
98
|
/** Current ~/.cursor/mcp.json content, or null if absent. */
|
|
@@ -154,6 +146,14 @@ export interface PlanInput {
|
|
|
154
146
|
nextReticleDevImport?: string | undefined;
|
|
155
147
|
/** Whether the ReticleDev component file already exists. */
|
|
156
148
|
nextReticleDevExists: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The existing ReticleDev component's source, when there is one.
|
|
151
|
+
*
|
|
152
|
+
* Read so an install predating daemon discovery can be told apart from a current one. Undefined
|
|
153
|
+
* means NOT READ, and an unread file is reported as already-wired rather than as stale: inventing
|
|
154
|
+
* work from missing information is how a plan grows steps that can never be completed.
|
|
155
|
+
*/
|
|
156
|
+
nextReticleDevSource?: string | null | undefined;
|
|
157
157
|
/** `data-testid` values scanned from the app's source, for the generated capabilities block. */
|
|
158
158
|
testids?: readonly string[] | undefined;
|
|
159
159
|
/** Ready-to-uncomment `registerStore` lines for the state libraries the app actually depends on. */
|
|
@@ -226,24 +226,4 @@ export interface PlanInput {
|
|
|
226
226
|
sdkVersion?: string;
|
|
227
227
|
};
|
|
228
228
|
}
|
|
229
|
-
/**
|
|
230
|
-
* Every coding agent `reticle init` knows how to wire itself into, and the three surfaces it wires:
|
|
231
|
-
* the global MCP registration, the project rule file that makes the agent USE Reticle, and the
|
|
232
|
-
* `/reticle` command.
|
|
233
|
-
*
|
|
234
|
-
* One list, because those three surfaces used to enumerate Claude and Cursor separately in three
|
|
235
|
-
* places — so supporting a fourth agent meant finding all three and getting each right. Adding one
|
|
236
|
-
* is now an entry here plus its builders; each builder answers null when that agent is not present
|
|
237
|
-
* for this user or this project, which is also how "no agent detected" falls through to the
|
|
238
|
-
* cross-agent AGENTS.md.
|
|
239
|
-
*
|
|
240
|
-
* Deliberately functions rather than data: registering with Claude runs its CLI while Cursor merges
|
|
241
|
-
* a JSON file, and pretending those are the same shape would cost more than it saves.
|
|
242
|
-
*/
|
|
243
|
-
declare const AgentId: {
|
|
244
|
-
readonly CLAUDE: "claude";
|
|
245
|
-
readonly CURSOR: "cursor";
|
|
246
|
-
};
|
|
247
|
-
export type AgentId = (typeof AgentId)[keyof typeof AgentId];
|
|
248
229
|
export declare function buildPlan(input: PlanInput): Plan;
|
|
249
|
-
export {};
|
package/dist/init/plan.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* runner performs the `write` side-effects; this module decides *what* should happen.
|
|
5
5
|
*/
|
|
6
6
|
import { Framework, PackageManager, UiLibrary, installCommand, installCommandParts, } from './detect.js';
|
|
7
|
-
import { claudeAddCommand, mcpManual } from './mcp.js';
|
|
7
|
+
import { claudeAddCommand, mcpManual, mcpWindowsNote } from './mcp.js';
|
|
8
|
+
import { NodePlatform } from '../platform.js';
|
|
8
9
|
import { mergeCursorConfig, CursorMergeStatus, cursorServerEntry } from './cursor.js';
|
|
9
10
|
import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, } from './mcp-clients.js';
|
|
10
11
|
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, SLASH_COMMAND_SIGNATURE, } from './slash-command.js';
|
|
@@ -32,7 +33,7 @@ const RETICLE_NEXT_PLUGIN = '@reticlehq/next';
|
|
|
32
33
|
* and nothing on either side names a version. Asking for the CLI's exact version makes the cache
|
|
33
34
|
* irrelevant, and a skewed pair impossible to install by accident.
|
|
34
35
|
*/
|
|
35
|
-
|
|
36
|
+
function pinnedPackages(packages, version) {
|
|
36
37
|
if (version === undefined || 0 === version.length)
|
|
37
38
|
return packages;
|
|
38
39
|
return packages.map((p) => `${p}@${version}`);
|
|
@@ -232,19 +233,33 @@ function mcpSteps(input) {
|
|
|
232
233
|
// client. A machine with Cursor AND Windsurf gets both — registering only the first one found is
|
|
233
234
|
// how a user ends up with Reticle in the editor they were not using.
|
|
234
235
|
const steps = [...stepsForAgents(input, (a) => a.mcpStep), ...otherClientSteps(input)];
|
|
235
|
-
if (steps.length
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
236
|
+
if (0 === steps.length) {
|
|
237
|
+
// No agent detected. mcpManual already carries the Windows cmd fallback — do not append it again.
|
|
238
|
+
return [
|
|
239
|
+
{
|
|
240
|
+
title: 'MCP server (global)',
|
|
241
|
+
target: MCP_TARGET,
|
|
242
|
+
status: StepStatus.MANUAL,
|
|
243
|
+
detail: mcpManual(),
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
const windowsNote = windowsMcpNoteStep(input);
|
|
248
|
+
if (windowsNote !== null)
|
|
249
|
+
steps.push(windowsNote);
|
|
250
|
+
return steps;
|
|
251
|
+
}
|
|
252
|
+
const WINDOWS_MCP_TITLE = 'Windows MCP spawn';
|
|
253
|
+
/** Windows only. The reported install never reached mcpManual because `claude mcp add` succeeded. */
|
|
254
|
+
function windowsMcpNoteStep(input) {
|
|
255
|
+
if (input.platform !== NodePlatform.WINDOWS)
|
|
256
|
+
return null;
|
|
257
|
+
return {
|
|
258
|
+
title: WINDOWS_MCP_TITLE,
|
|
259
|
+
target: MCP_TARGET,
|
|
260
|
+
status: StepStatus.NOTICE,
|
|
261
|
+
detail: mcpWindowsNote(),
|
|
262
|
+
};
|
|
248
263
|
}
|
|
249
264
|
const SLASH_COMMAND_TITLE = 'The /reticle command';
|
|
250
265
|
/**
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bring an already-installed project's agent rules up to the version that is running.
|
|
3
|
+
*
|
|
4
|
+
* `mergeMarkedInstruction` has always been able to do this: the managed block is marker-delimited,
|
|
5
|
+
* idempotence is decided by comparing CONTENT rather than by seeing a marker, and everything
|
|
6
|
+
* outside the markers is left exactly as the human wrote it. What was missing is anything that
|
|
7
|
+
* CALLS it after the first install.
|
|
8
|
+
*
|
|
9
|
+
* It is reachable only from `buildPlan`, so the rules refresh when somebody re-runs `init` and
|
|
10
|
+
* never otherwise. A project set up on an older release keeps that release's instructions forever,
|
|
11
|
+
* which means every improvement to them — what to do when a capabilities file registers nothing,
|
|
12
|
+
* that an install is not finished until a verdict exists, that intent is captured while building —
|
|
13
|
+
* reaches new projects only.
|
|
14
|
+
*
|
|
15
|
+
* That is the wrong way round. The people who most need better instructions are the ones already
|
|
16
|
+
* installed and not getting value, and `reticle update` is the moment they are already touching the
|
|
17
|
+
* install.
|
|
18
|
+
*/
|
|
19
|
+
/** The instruction files the managed block lives in. Cursor's rule file is regenerated whole. */
|
|
20
|
+
export declare const RULE_FILES: readonly ["CLAUDE.md", "AGENTS.md"];
|
|
21
|
+
export interface RuleFileIo {
|
|
22
|
+
read: (path: string) => string | null;
|
|
23
|
+
write: (path: string, content: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface RefreshResult {
|
|
26
|
+
/** Files whose managed block was replaced with the current one. */
|
|
27
|
+
updated: string[];
|
|
28
|
+
/** Files that were already current, or that carry no Reticle block to update. */
|
|
29
|
+
unchanged: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Refresh every instruction file that already carries a Reticle block.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately does NOT create one where none exists. A project with no Reticle block in its
|
|
35
|
+
* CLAUDE.md either never ran `init` here or had the block removed on purpose, and `update` is not
|
|
36
|
+
* the place to make that decision for somebody — it is a version command, and quietly writing
|
|
37
|
+
* instructions into a file a user curates would be a surprise.
|
|
38
|
+
*/
|
|
39
|
+
export declare function refreshAgentRules(cwd: string, io: RuleFileIo): RefreshResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bring an already-installed project's agent rules up to the version that is running.
|
|
3
|
+
*
|
|
4
|
+
* `mergeMarkedInstruction` has always been able to do this: the managed block is marker-delimited,
|
|
5
|
+
* idempotence is decided by comparing CONTENT rather than by seeing a marker, and everything
|
|
6
|
+
* outside the markers is left exactly as the human wrote it. What was missing is anything that
|
|
7
|
+
* CALLS it after the first install.
|
|
8
|
+
*
|
|
9
|
+
* It is reachable only from `buildPlan`, so the rules refresh when somebody re-runs `init` and
|
|
10
|
+
* never otherwise. A project set up on an older release keeps that release's instructions forever,
|
|
11
|
+
* which means every improvement to them — what to do when a capabilities file registers nothing,
|
|
12
|
+
* that an install is not finished until a verdict exists, that intent is captured while building —
|
|
13
|
+
* reaches new projects only.
|
|
14
|
+
*
|
|
15
|
+
* That is the wrong way round. The people who most need better instructions are the ones already
|
|
16
|
+
* installed and not getting value, and `reticle update` is the moment they are already touching the
|
|
17
|
+
* install.
|
|
18
|
+
*/
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { mergeMarkedInstruction, AgentRuleStatus, hasManagedBlock } from './agent-rules.js';
|
|
21
|
+
/** The instruction files the managed block lives in. Cursor's rule file is regenerated whole. */
|
|
22
|
+
export const RULE_FILES = ['CLAUDE.md', 'AGENTS.md'];
|
|
23
|
+
/**
|
|
24
|
+
* Refresh every instruction file that already carries a Reticle block.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately does NOT create one where none exists. A project with no Reticle block in its
|
|
27
|
+
* CLAUDE.md either never ran `init` here or had the block removed on purpose, and `update` is not
|
|
28
|
+
* the place to make that decision for somebody — it is a version command, and quietly writing
|
|
29
|
+
* instructions into a file a user curates would be a surprise.
|
|
30
|
+
*/
|
|
31
|
+
export function refreshAgentRules(cwd, io) {
|
|
32
|
+
const updated = [];
|
|
33
|
+
const unchanged = [];
|
|
34
|
+
for (const name of RULE_FILES) {
|
|
35
|
+
const path = join(cwd, name);
|
|
36
|
+
const existing = io.read(path);
|
|
37
|
+
if (null === existing || !hasManagedBlock(existing)) {
|
|
38
|
+
unchanged.push(name);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const result = mergeMarkedInstruction(existing);
|
|
42
|
+
if (AgentRuleStatus.APPLY === result.status && result.content !== existing) {
|
|
43
|
+
io.write(path, result.content);
|
|
44
|
+
updated.push(name);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
unchanged.push(name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { updated, unchanged };
|
|
51
|
+
}
|
package/dist/init/run.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ export interface InitIo {
|
|
|
56
56
|
exists(relPath: string): boolean;
|
|
57
57
|
/** The user's home directory (for global agent config like ~/.cursor/mcp.json). */
|
|
58
58
|
homeDir(): string;
|
|
59
|
+
/** Absolute path of the directory `init` is running in — the project's own root. */
|
|
60
|
+
cwd(): string;
|
|
59
61
|
/** Basenames present in the project root. */
|
|
60
62
|
rootFiles(): readonly string[];
|
|
61
63
|
/** Subdirectory names inside a project-relative directory; empty when it isn't one. */
|
|
@@ -89,14 +91,4 @@ export interface InitResult {
|
|
|
89
91
|
* it knows it is in a workspace; this is the matching walk DOWN.
|
|
90
92
|
*/
|
|
91
93
|
export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
92
|
-
/**
|
|
93
|
-
* The standing ask, printed at the end of every `init`.
|
|
94
|
-
*
|
|
95
|
-
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
96
|
-
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
97
|
-
*
|
|
98
|
-
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
99
|
-
* the workspace redirect is kept from asking twice.
|
|
100
|
-
*/
|
|
101
|
-
export declare const FEEDBACK_HINT: string;
|
|
102
94
|
export declare function runInit(options: InitOptions, io: InitIo): InitResult;
|
package/dist/init/run.js
CHANGED
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { noPackageJsonMessage } from './non-js-project.js';
|
|
8
|
+
import { devCommandFrom } from './dev-script.js';
|
|
9
|
+
import { restartHint, FEEDBACK_HINT } from './closing-hint.js';
|
|
8
10
|
import { spanSync } from '../trace.js';
|
|
9
11
|
import { readFileSync } from 'node:fs';
|
|
10
12
|
import { homedir } from 'node:os';
|
|
13
|
+
import { projectIdOf, rememberProjectOnDisk } from '../project/remember-project.js';
|
|
11
14
|
import { detect, Framework, namesAPackageManager, UiLibrary } from './detect.js';
|
|
12
15
|
import { wasMcpRegistered } from './mcp-registered.js';
|
|
13
16
|
import { pickAstroHost } from './astro-host.js';
|
|
14
17
|
import { workspaceParents } from './workspace-apps.js';
|
|
15
18
|
import { chooseWorkspaceApp } from './app-choice.js';
|
|
16
19
|
import { isConnectStep } from './connect-steps.js';
|
|
17
|
-
import {
|
|
20
|
+
import { CURSOR_RULE_PATH, RETICLE_MD_PATH } from './agent-rules.js';
|
|
18
21
|
import { CRA_ENV_PATH } from './cra.js';
|
|
19
22
|
/** CRA's bundled entry, in the order create-react-app itself generates them. */
|
|
20
23
|
const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
|
|
@@ -351,6 +354,7 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
351
354
|
claudeCli,
|
|
352
355
|
mcpExists,
|
|
353
356
|
cursorPresent,
|
|
357
|
+
platform: process.platform,
|
|
354
358
|
detectedClients,
|
|
355
359
|
cursorProjectPresent: io.exists(CURSOR_DIR_RELPATH),
|
|
356
360
|
cursorConfig,
|
|
@@ -374,6 +378,7 @@ function gatherPlanInput(options, io, pkgRaw) {
|
|
|
374
378
|
nextReticleDevPath: devLocation.path,
|
|
375
379
|
nextReticleDevImport: devLocation.importSpecifier,
|
|
376
380
|
nextReticleDevExists: io.exists(devLocation.path),
|
|
381
|
+
nextReticleDevSource: io.readFile(devLocation.path),
|
|
377
382
|
svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
|
|
378
383
|
craEntry: craEntryOf(io),
|
|
379
384
|
craEnv: io.readFile(CRA_ENV_PATH),
|
|
@@ -467,72 +472,11 @@ export function findWorkspaceApps(io) {
|
|
|
467
472
|
}
|
|
468
473
|
return [...new Set(found)];
|
|
469
474
|
}
|
|
470
|
-
/**
|
|
471
|
-
* The standing ask, printed at the end of every `init`.
|
|
472
|
-
*
|
|
473
|
-
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
474
|
-
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
475
|
-
*
|
|
476
|
-
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
477
|
-
* the workspace redirect is kept from asking twice.
|
|
478
|
-
*/
|
|
479
|
-
export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
|
|
480
|
-
'one thing that decides what gets fixed:\n' +
|
|
481
|
-
` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
|
|
482
|
-
` ${CLI} feedback "what worked, what didn't" (humans)`;
|
|
483
|
-
function devServerRestart(framework) {
|
|
484
|
-
if (framework === Framework.NEXT)
|
|
485
|
-
return 'Restart `next dev`';
|
|
486
|
-
if (framework === Framework.VITE)
|
|
487
|
-
return 'Restart `vite`';
|
|
488
|
-
if (framework === Framework.ASTRO)
|
|
489
|
-
return 'Restart `astro dev`';
|
|
490
|
-
if (framework === Framework.SVELTEKIT)
|
|
491
|
-
return 'Restart your dev server (`npm run dev`)';
|
|
492
|
-
return 'Reload your app on localhost';
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* The last two lines a user reads, and the order matters.
|
|
496
|
-
*
|
|
497
|
-
* This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
|
|
498
|
-
* asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
|
|
499
|
-
* an agent client reads its tool list when it STARTS and never re-reads it, so the session that
|
|
500
|
-
* just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
|
|
501
|
-
* instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
|
|
502
|
-
* failed.
|
|
503
|
-
*
|
|
504
|
-
* So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
|
|
505
|
-
* run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
|
|
506
|
-
*/
|
|
507
|
-
function restartHint(framework, mcpRegistered) {
|
|
508
|
-
const dev = `${devServerRestart(framework)}.`;
|
|
509
|
-
// NAME THE COMMAND THAT PROVES IT, not one that merely asks.
|
|
510
|
-
//
|
|
511
|
-
// `init` writes files and stops; the install is not finished until an app carrying the SDK has
|
|
512
|
-
// actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
|
|
513
|
-
// people complete the agent half, never complete the app half, and keep a daemon running for
|
|
514
|
-
// weeks with nothing to drive — so this is the last instruction most of them read.
|
|
515
|
-
//
|
|
516
|
-
// "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
|
|
517
|
-
// status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
|
|
518
|
-
// running, an app running that never dialled us, a tab that closed — with the fix for each.
|
|
519
|
-
const prove = 'Then run `npx @reticlehq/server status` — it confirms the app connected, or says exactly why ' +
|
|
520
|
-
'it has not.';
|
|
521
|
-
if (!mcpRegistered)
|
|
522
|
-
return `${dev} ${prove}`;
|
|
523
|
-
return (`${dev}\n` +
|
|
524
|
-
'Then restart your agent so it picks up the new MCP server — restart Claude Code, reload the ' +
|
|
525
|
-
'window in Cursor, or hit Start in `.vscode/mcp.json` in VS Code.\n' +
|
|
526
|
-
'The tools only appear after that: your agent read its server list before Reticle existed, ' +
|
|
527
|
-
'and no slash command re-reads it (`/mcp` manages servers already loaded, so it cannot pick ' +
|
|
528
|
-
'up a new one). This is once per machine — Reticle is registered globally, so every later ' +
|
|
529
|
-
'project starts with the tools already there.\n' +
|
|
530
|
-
`${prove}\n` +
|
|
531
|
-
'Once it shows a session, ask your agent to drive a flow — that is the install finished.');
|
|
532
|
-
}
|
|
533
475
|
const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
|
|
534
476
|
'installed stops it booting. Run that install, then re-run `reticle init`.';
|
|
535
|
-
function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRoot
|
|
477
|
+
function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRoot,
|
|
478
|
+
/** The project's own dev command, so the closing line names what a human would type. */
|
|
479
|
+
devCommand) {
|
|
536
480
|
io.print(dryRun ? 'reticle init (dry run, no files written)' : 'reticle init');
|
|
537
481
|
// Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
|
|
538
482
|
// field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
|
|
@@ -592,7 +536,7 @@ function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRo
|
|
|
592
536
|
'"no browser session connected" until then. Everything else is already in place.');
|
|
593
537
|
io.print('');
|
|
594
538
|
}
|
|
595
|
-
io.print(restartHint(plan.framework,
|
|
539
|
+
io.print(restartHint(plan.framework, resolvedStatus(plan, MCP_TARGET, failed, skipped), devCommand));
|
|
596
540
|
return { ok: !connectPending, applied, manual };
|
|
597
541
|
}
|
|
598
542
|
/**
|
|
@@ -684,6 +628,17 @@ function applyEffects(plan, io) {
|
|
|
684
628
|
installFailed = true;
|
|
685
629
|
}
|
|
686
630
|
}
|
|
631
|
+
// Where this project lives, remembered for a daemon that will be started somewhere else.
|
|
632
|
+
//
|
|
633
|
+
// Deliberately AFTER the loop and unconditional on which steps ran: most init runs report "already
|
|
634
|
+
// wired", and a re-run in a re-cloned or moved checkout is exactly when the remembered path has
|
|
635
|
+
// gone stale — so the run that would otherwise be a no-op is the one that repairs the entry. The
|
|
636
|
+
// return value is ignored on purpose; this is a cache, and a read-only home directory must not
|
|
637
|
+
// turn a wired project into a failed init.
|
|
638
|
+
const projectId = projectIdOf(io.readFile(RETICLE_CONFIG_FILE));
|
|
639
|
+
if (projectId !== undefined) {
|
|
640
|
+
rememberProjectOnDisk(io, projectId, io.cwd(), Date.now());
|
|
641
|
+
}
|
|
687
642
|
return { failed, skipped, degraded };
|
|
688
643
|
}
|
|
689
644
|
/**
|
|
@@ -803,12 +758,15 @@ function runInitSteps(options, io) {
|
|
|
803
758
|
// Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
|
|
804
759
|
// 1–6s per app with no explanation of the spread. These three spans split that number into detect
|
|
805
760
|
// (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
|
|
806
|
-
const
|
|
761
|
+
const planInput = gatherPlanInput(options, io, pkgRaw);
|
|
762
|
+
const plan = spanSync('init.plan', {}, () => buildPlan(planInput));
|
|
807
763
|
const effects = options.dryRun
|
|
808
764
|
? { failed: new Set(), skipped: new Set(), degraded: new Map() }
|
|
809
765
|
: spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
|
|
810
766
|
const { failed, skipped, degraded } = effects;
|
|
811
|
-
|
|
767
|
+
// The project's own dev command, so the closing line names what a human would actually type.
|
|
768
|
+
const devCommand = devCommandFrom(pkgRaw, planInput.detection.packageManager);
|
|
769
|
+
const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd, agentRootOf(options), devCommand);
|
|
812
770
|
// A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
|
|
813
771
|
if (options.dryRun)
|
|
814
772
|
return result;
|