@reticlehq/server 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/SKILL.md +64 -29
- package/dist/capsule/causal-summary.d.ts +20 -0
- package/dist/capsule/causal-summary.js +31 -6
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +28 -25
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +17 -0
- package/dist/cli/cli-launch.js +28 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +8 -0
- package/dist/cli/cli-port.js +16 -0
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +66 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli.js +99 -14
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +20 -1
- package/dist/events/predicate-eval.js +125 -3
- package/dist/events/predicate.js +170 -18
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-tools.js +12 -4
- package/dist/flows/heal-run.js +1 -1
- package/dist/honesty/blind-spots.js +6 -2
- package/dist/honesty/unsettled.d.ts +40 -0
- package/dist/honesty/unsettled.js +78 -0
- package/dist/honesty/verified.d.ts +7 -0
- package/dist/honesty/verified.js +3 -3
- package/dist/index.js +3 -0
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +11 -5
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +96 -0
- package/dist/init/confirm.js +138 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.js +53 -15
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +163 -40
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +148 -42
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +44 -4
- package/dist/init/snippets.js +161 -37
- package/dist/init/vite-config.js +29 -3
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +29 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +69 -3
- package/dist/mcp/mcp-proxy.js +205 -20
- package/dist/mcp/mcp.js +23 -7
- package/dist/mcp/proxy-lifecycle.d.ts +1 -1
- package/dist/mcp/proxy-lifecycle.js +1 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/session/no-session-diagnosis.d.ts +19 -0
- package/dist/session/no-session-diagnosis.js +71 -18
- package/dist/session/no-session-watch.js +12 -0
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +0 -20
- package/dist/telemetry/bug-found.js +38 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +36 -0
- package/dist/telemetry/install-source.js +40 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.js +5 -0
- package/dist/telemetry/telemetry.d.ts +5 -1
- package/dist/telemetry/telemetry.js +15 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +45 -1
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +18 -1
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +64 -4
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +139 -23
- package/dist/tools/lease-tools.d.ts +29 -1
- package/dist/tools/lease-tools.js +58 -5
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +1 -1
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +48 -0
- package/dist/tools/react-grace.js +87 -0
- package/dist/tools/read-tools.js +20 -4
- package/dist/tools/settle-in-flight.d.ts +79 -0
- package/dist/tools/settle-in-flight.js +106 -0
- package/dist/tools/snapshot-delta.d.ts +7 -0
- package/dist/tools/snapshot-delta.js +130 -6
- package/dist/tools/tool-surface.js +19 -0
- package/dist/tools/tools.js +16 -4
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +93 -29
- package/docs/architecture.md +46 -35
- package/docs/benchmarks.md +76 -72
- package/docs/best-practices.mdx +211 -0
- package/docs/capabilities.mdx +387 -0
- package/docs/cli/affected.mdx +51 -0
- package/docs/cli/capsules.mdx +69 -0
- package/docs/cli/cloud.mdx +127 -0
- package/docs/cli/doctor.mdx +76 -0
- package/docs/cli/drive.mdx +55 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +92 -0
- package/docs/cli/hunt.mdx +57 -0
- package/docs/cli/identify.mdx +62 -0
- package/docs/cli/init.mdx +94 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +68 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +62 -0
- package/docs/cli/status.mdx +64 -0
- package/docs/cli/stop.mdx +56 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +57 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +55 -0
- package/docs/cli.mdx +219 -0
- package/docs/debugging.md +13 -9
- package/docs/deploy-checks.md +15 -11
- package/docs/desktop-apps.md +49 -40
- package/docs/desktop.mdx +188 -0
- package/docs/docs.json +363 -0
- package/docs/enterprise.md +20 -16
- package/docs/faq.mdx +88 -0
- package/docs/fixtures.md +11 -7
- package/docs/flows.md +26 -22
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +199 -0
- package/docs/gate-plan.md +45 -41
- package/docs/gates.md +22 -18
- package/docs/getting-started.md +76 -65
- package/docs/human-control.md +22 -18
- package/docs/index.mdx +101 -0
- package/docs/install-agentic.mdx +215 -0
- package/docs/install-manual.mdx +194 -0
- package/docs/instrumentation.mdx +232 -0
- package/docs/integration-patterns.md +38 -34
- package/docs/local-registry.md +16 -12
- package/docs/multi-agent-testing.md +16 -12
- package/docs/packages/babel-plugin.mdx +70 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +106 -0
- package/docs/packages/electron.mdx +87 -0
- package/docs/packages/eslint-plugin.mdx +84 -0
- package/docs/packages/next.mdx +91 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +108 -0
- package/docs/packages/vite-plugin.mdx +96 -0
- package/docs/packages.mdx +217 -0
- package/docs/platform-integration.md +33 -26
- package/docs/predicates.mdx +179 -0
- package/docs/quickstart.mdx +211 -0
- package/docs/recipes.mdx +242 -0
- package/docs/skill-file.mdx +105 -0
- package/docs/state-management.mdx +197 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +47 -32
- package/docs/telemetry-contract.md +93 -50
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +57 -52
- package/docs/testing.md +10 -6
- package/docs/token-efficiency.md +30 -26
- package/docs/tools-act-and-wait.mdx +123 -0
- package/docs/tools-act-sequence.mdx +120 -0
- package/docs/tools-act.mdx +82 -0
- package/docs/tools-assert.mdx +70 -0
- package/docs/tools-console.mdx +59 -0
- package/docs/tools-inspect.mdx +85 -0
- package/docs/tools-navigate.mdx +63 -0
- package/docs/tools-network.mdx +86 -0
- package/docs/tools-observe.mdx +89 -0
- package/docs/tools-overview.mdx +157 -0
- package/docs/tools-query.mdx +82 -0
- package/docs/tools-session-and-feedback.mdx +91 -0
- package/docs/tools-sessions.mdx +65 -0
- package/docs/tools-snapshot.mdx +75 -0
- package/docs/tools-state.mdx +81 -0
- package/docs/tools-tools-and-run.mdx +125 -0
- package/docs/tools-wait-for.mdx +66 -0
- package/docs/troubleshooting.mdx +193 -0
- package/docs/usage.md +175 -153
- package/docs/vs-chrome-devtools-mcp.mdx +82 -0
- package/docs/vs-playwright-mcp.mdx +85 -0
- package/docs/vs-screenshots.mdx +96 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +107 -0
- package/package.json +2 -2
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
|
@@ -29,7 +29,13 @@ const ECOSYSTEM_MARKERS = [
|
|
|
29
29
|
['pom.xml', 'Java'],
|
|
30
30
|
['build.gradle', 'Java'],
|
|
31
31
|
['mix.exs', 'Elixir'],
|
|
32
|
+
// Not like the others. Every ecosystem above can grow a JS front end that Reticle instruments
|
|
33
|
+
// perfectly well, so their message ends with a lead. Flutter web paints into a canvas, and there
|
|
34
|
+
// is no DOM behind it at any path, so the honest answer is a named refusal.
|
|
35
|
+
['pubspec.yaml', 'Flutter'],
|
|
32
36
|
];
|
|
37
|
+
/** The one ecosystem where no directory, front end or flag could ever help. */
|
|
38
|
+
const FLUTTER = 'Flutter';
|
|
33
39
|
/** The ecosystem this directory looks like, or undefined when nothing says. */
|
|
34
40
|
export function detectNonJsEcosystem(exists) {
|
|
35
41
|
for (const [marker, name] of ECOSYSTEM_MARKERS) {
|
|
@@ -51,10 +57,28 @@ export function noPackageJsonMessage(exists) {
|
|
|
51
57
|
return ('No package.json found here, and no app directory beneath it either. Run `reticle init` from ' +
|
|
52
58
|
"your app's directory, or from a repo root that contains it.");
|
|
53
59
|
}
|
|
60
|
+
if (ecosystem === FLUTTER) {
|
|
61
|
+
// An agent on a Flutter workspace asked us to "support Flutter, or clearly identify it as
|
|
62
|
+
// unsupported during init". The second is the truthful one, and saying it by name in one line
|
|
63
|
+
// is the difference between an agent that stops and an agent that spends an hour looking for
|
|
64
|
+
// the package.json we told it was missing.
|
|
65
|
+
return ('This is a Flutter project. Reticle cannot instrument it, and no directory or flag will ' +
|
|
66
|
+
'change that: Reticle reads the DOM of a running page, and Flutter web paints its entire UI ' +
|
|
67
|
+
'into a single canvas element with no DOM behind it. Nothing here is missing or misconfigured. ' +
|
|
68
|
+
'If this repo also contains an ordinary web app with its own package.json, run `reticle init` ' +
|
|
69
|
+
'inside that directory instead.');
|
|
70
|
+
}
|
|
54
71
|
return (`This looks like a ${ecosystem} project, and there is no package.json here or in any app ` +
|
|
55
|
-
'directory beneath it.
|
|
56
|
-
'
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
'directory beneath it. That is not a blocker: Reticle needs its SDK loaded by the page, not ' +
|
|
73
|
+
'built by npm, and a page with no build step can load it from a URL. ' +
|
|
74
|
+
// The old message ended at "there is no directory you could run this from that would change
|
|
75
|
+
// that", which read as a refusal and was acted on as one — agents went looking for a bundler,
|
|
76
|
+
// or for a standalone build to inject by hand, or gave up and used a different tool. The
|
|
77
|
+
// capability was there the whole time; only the sentence was missing. Proven end to end on a
|
|
78
|
+
// page served by `python3 -m http.server` before this line was written.
|
|
79
|
+
'Add the script-tag snippet below to a template you only serve in development, then reload the ' +
|
|
80
|
+
'page. ' +
|
|
81
|
+
'If this project ALSO has a JS front end (a `frontend/`, `client/` or `assets/` directory with ' +
|
|
82
|
+
'its own package.json), running `reticle init` there instead gives you source mapping and ' +
|
|
83
|
+
'framework state as well: point at it with `--app <dir>`.');
|
|
60
84
|
}
|
|
@@ -23,6 +23,36 @@ export const VITE_PLUGIN_DETAIL = {
|
|
|
23
23
|
SVELTEKIT: 'add reticle() to plugins (stamps data-reticle-source in .svelte components)',
|
|
24
24
|
};
|
|
25
25
|
const CAPABILITIES_TITLE = 'Capabilities + store';
|
|
26
|
+
/**
|
|
27
|
+
* The one step `init` genuinely cannot finish, addressed to the AGENT reading this report.
|
|
28
|
+
*
|
|
29
|
+
* It used to read "Capabilities are empty until you edit that file" and describe the consequence to
|
|
30
|
+
* a human — homework, in a report a human mostly does not read, naming no file to open and no thing
|
|
31
|
+
* to look for. The agent that ran `init` is right there, has the codebase, and can finish this in
|
|
32
|
+
* the same minute; what it needed was the specific instruction, not the rationale.
|
|
33
|
+
*
|
|
34
|
+
* What is left is exactly what could not be PROVEN from source: which atoms/actor/queryClient an
|
|
35
|
+
* adapter-wrapped store should be built from, and which elements matter enough to name. `init`
|
|
36
|
+
* emits nothing it cannot prove, because a wrong import here throws on every dev page load — worse
|
|
37
|
+
* than an empty file.
|
|
38
|
+
*/
|
|
39
|
+
const CAPABILITIES_TODO_TITLE = 'AGENT: finish the capabilities file';
|
|
40
|
+
function capabilitiesTodo(path, stores) {
|
|
41
|
+
const store = stores.length > 0
|
|
42
|
+
? `This app depends on a state library init cannot wire on its own (it needs an argument only ` +
|
|
43
|
+
`reading the code can supply). Uncomment the suggested line in that file and complete it: ` +
|
|
44
|
+
`${stores.join('; ')}.`
|
|
45
|
+
: `No state library was detected. If the app holds state somewhere init cannot see, register ` +
|
|
46
|
+
`it — see node_modules/@reticlehq/server/docs/usage.md.`;
|
|
47
|
+
return (`${path} was written but registers nothing, so \`hasCapabilities\` stays false and reticle_state ` +
|
|
48
|
+
`has nothing to read.\n` +
|
|
49
|
+
`Do this now, before driving anything:\n` +
|
|
50
|
+
` 1. ${store}\n` +
|
|
51
|
+
` 2. Open the app's main flow and add \`testids\` for the elements it touches (or add ` +
|
|
52
|
+
`data-testid attributes where there are none).\n` +
|
|
53
|
+
` 3. Prove it: drive one flow and check reticle_state returns your keys. An empty state read ` +
|
|
54
|
+
`means this file is still not doing anything, whatever else went green.`);
|
|
55
|
+
}
|
|
26
56
|
/**
|
|
27
57
|
* The dev module carrying `registerCapabilities` / `registerStore`.
|
|
28
58
|
*
|
|
@@ -37,23 +67,32 @@ function capabilitiesStep(input) {
|
|
|
37
67
|
title: CAPABILITIES_TITLE,
|
|
38
68
|
target: VITE_DEV_MODULE_PATH,
|
|
39
69
|
status: StepStatus.ALREADY,
|
|
40
|
-
detail: 'file exists
|
|
70
|
+
detail: 'file exists, left alone, it is yours to edit',
|
|
41
71
|
},
|
|
42
72
|
];
|
|
43
73
|
}
|
|
44
74
|
const testids = input.testids ?? [];
|
|
45
75
|
const stores = input.storeHints ?? [];
|
|
76
|
+
const wired = input.foundStores ?? [];
|
|
46
77
|
const found = testids.length > 0
|
|
47
78
|
? `${String(testids.length)} data-testid values`
|
|
48
79
|
: 'no data-testid values yet';
|
|
49
|
-
|
|
80
|
+
// A store we found and wired IS a registration, so the "registers nothing" notice must not fire.
|
|
81
|
+
const nothingToRegister = 0 === testids.length && 0 === stores.length && 0 === wired.length;
|
|
50
82
|
return [
|
|
51
83
|
{
|
|
52
84
|
title: CAPABILITIES_TITLE,
|
|
53
85
|
target: VITE_DEV_MODULE_PATH,
|
|
54
86
|
status: StepStatus.APPLY,
|
|
55
|
-
detail: `${found}; ${
|
|
56
|
-
|
|
87
|
+
detail: `${found}; ${wired.length > 0
|
|
88
|
+
? `registered ${wired.map((s) => `'${s.key}'`).join(', ')} from your source`
|
|
89
|
+
: stores.length > 0
|
|
90
|
+
? `store: uncomment the ${String(stores.length)} suggested line(s)`
|
|
91
|
+
: 'no state library detected'}`,
|
|
92
|
+
write: {
|
|
93
|
+
path: VITE_DEV_MODULE_PATH,
|
|
94
|
+
content: viteDevModuleFile(testids, stores, wired, input.detection.uiLibrary),
|
|
95
|
+
},
|
|
57
96
|
dependsOnInstall: true,
|
|
58
97
|
},
|
|
59
98
|
// The write is real; what it registers is not. `registerCapabilities({ testids: [], signals: [],
|
|
@@ -67,14 +106,10 @@ function capabilitiesStep(input) {
|
|
|
67
106
|
...(nothingToRegister
|
|
68
107
|
? [
|
|
69
108
|
{
|
|
70
|
-
title:
|
|
109
|
+
title: CAPABILITIES_TODO_TITLE,
|
|
71
110
|
target: VITE_DEV_MODULE_PATH,
|
|
72
111
|
status: StepStatus.NOTICE,
|
|
73
|
-
detail:
|
|
74
|
-
'no state library was detected, so `hasCapabilities` stays false and reticle_state ' +
|
|
75
|
-
'has nothing to read. Add data-testid to the elements your main flow touches, or ' +
|
|
76
|
-
'register a store in that file, and the agent gains what the app BELIEVES rather than ' +
|
|
77
|
-
'only what it rendered.',
|
|
112
|
+
detail: capabilitiesTodo(VITE_DEV_MODULE_PATH, stores),
|
|
78
113
|
},
|
|
79
114
|
]
|
|
80
115
|
: []),
|
|
@@ -94,7 +129,7 @@ function viteConfigSteps(input, detail) {
|
|
|
94
129
|
title: 'Vite plugin',
|
|
95
130
|
target: 'vite.config',
|
|
96
131
|
status: StepStatus.MANUAL,
|
|
97
|
-
detail: viteManual(port),
|
|
132
|
+
detail: viteManual(port, input.detection.uiLibrary),
|
|
98
133
|
},
|
|
99
134
|
];
|
|
100
135
|
}
|
|
@@ -115,7 +150,7 @@ function viteConfigSteps(input, detail) {
|
|
|
115
150
|
title: 'Vite plugin',
|
|
116
151
|
target: cfg.path,
|
|
117
152
|
status: StepStatus.MANUAL,
|
|
118
|
-
detail: `${patch.reason}\n\n${viteManual(port)}`,
|
|
153
|
+
detail: `${patch.reason}\n\n${viteManual(port, input.detection.uiLibrary)}`,
|
|
119
154
|
},
|
|
120
155
|
];
|
|
121
156
|
}
|
|
@@ -177,7 +212,10 @@ export function nextSteps(input) {
|
|
|
177
212
|
detail: 'create dev-only connect component',
|
|
178
213
|
write: {
|
|
179
214
|
path: devPath,
|
|
180
|
-
content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? []
|
|
215
|
+
content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? [],
|
|
216
|
+
// Next's dev module is not a sibling of `src/` — its imports are resolved from wherever
|
|
217
|
+
// the component actually lands (`app/`, or `src/app/` in a --src-dir app).
|
|
218
|
+
input.nextFoundStores ?? []),
|
|
181
219
|
},
|
|
182
220
|
dependsOnInstall: true,
|
|
183
221
|
};
|
|
@@ -338,7 +376,7 @@ export function svelteKitSteps(input) {
|
|
|
338
376
|
detail: 'create dev-only client connect (SvelteKit renders via app.html)',
|
|
339
377
|
write: {
|
|
340
378
|
path: SVELTEKIT_HOOKS_PATH,
|
|
341
|
-
content: svelteKitHooksFile(input.options.port, input.options.projectId),
|
|
379
|
+
content: svelteKitHooksFile(input.options.port, input.options.projectId, input.detection.uiLibrary),
|
|
342
380
|
},
|
|
343
381
|
dependsOnInstall: true,
|
|
344
382
|
},
|
|
@@ -375,7 +413,7 @@ export function astroSteps(input) {
|
|
|
375
413
|
// single recipe that does the whole job.
|
|
376
414
|
const manualWithLayout = astroManual(input.options.port, input.options.projectId, layout.path);
|
|
377
415
|
const configPatch = patchAstroConfig(config.source);
|
|
378
|
-
const layoutPatch = patchAstroLayout(layout.source, input.options.port, input.options.projectId);
|
|
416
|
+
const layoutPatch = patchAstroLayout(layout.source, input.options.port, input.options.projectId, input.detection.uiLibrary);
|
|
379
417
|
if (configPatch.kind === PatchKind.MANUAL || layoutPatch.kind === PatchKind.MANUAL) {
|
|
380
418
|
return [
|
|
381
419
|
{
|
package/dist/init/plan.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* contents, produce an ordered list of steps — each marked apply / manual / already / skip. The
|
|
4
4
|
* runner performs the `write` side-effects; this module decides *what* should happen.
|
|
5
5
|
*/
|
|
6
|
-
import { Framework, type Detection } from './detect.js';
|
|
6
|
+
import { Framework, UiLibrary, type Detection } from './detect.js';
|
|
7
|
+
import type { FoundStore } from './capabilities.js';
|
|
7
8
|
import { type McpClient } from './mcp-clients.js';
|
|
8
9
|
/**
|
|
9
10
|
* Pin the SDK to the CLI's own version.
|
|
@@ -15,8 +16,15 @@ import { type McpClient } from './mcp-clients.js';
|
|
|
15
16
|
* irrelevant, and a skewed pair impossible to install by accident.
|
|
16
17
|
*/
|
|
17
18
|
export declare function pinnedPackages(packages: readonly string[], version: string | undefined): readonly string[];
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
|
|
21
|
+
*
|
|
22
|
+
* `uiLibrary` matters because the framework does not always name the renderer. The rule below was
|
|
23
|
+
* already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
|
|
24
|
+
* `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
|
|
25
|
+
* right after `init` had detected the real UI library and said so on screen.
|
|
26
|
+
*/
|
|
27
|
+
export declare function frameworkPackages(framework: Framework, uiLibrary?: UiLibrary): readonly string[];
|
|
20
28
|
/** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
|
|
21
29
|
export declare const MCP_TARGET = "global (claude user scope)";
|
|
22
30
|
/** True when this step is what makes the app dial the daemon. */
|
|
@@ -77,6 +85,13 @@ export interface Step {
|
|
|
77
85
|
}
|
|
78
86
|
export interface Plan {
|
|
79
87
|
framework: Framework;
|
|
88
|
+
/**
|
|
89
|
+
* The renderer this app actually uses, carried so every consumer agrees on WHICH packages the
|
|
90
|
+
* plan installed. The retry guard checks node_modules for exactly those names; without this it
|
|
91
|
+
* looked for `@reticlehq/react` in a Vue app that had correctly been given the sensor, decided the
|
|
92
|
+
* install had failed, and skipped every wiring step on the retry.
|
|
93
|
+
*/
|
|
94
|
+
uiLibrary: UiLibrary;
|
|
80
95
|
steps: Step[];
|
|
81
96
|
}
|
|
82
97
|
export interface PlanInput {
|
|
@@ -144,6 +159,10 @@ export interface PlanInput {
|
|
|
144
159
|
testids?: readonly string[] | undefined;
|
|
145
160
|
/** Ready-to-uncomment `registerStore` lines for the state libraries the app actually depends on. */
|
|
146
161
|
storeHints?: readonly string[] | undefined;
|
|
162
|
+
/** Store instances found in the app's own source — imported and registered outright, not hinted. */
|
|
163
|
+
foundStores?: readonly FoundStore[] | undefined;
|
|
164
|
+
/** The same stores, with specifiers resolved from Next's dev-module directory instead of `src/`. */
|
|
165
|
+
nextFoundStores?: readonly FoundStore[] | undefined;
|
|
147
166
|
/** Whether src/reticle-dev.ts already exists — it is the one generated file users are meant to edit. */
|
|
148
167
|
viteDevModuleExists?: boolean | undefined;
|
|
149
168
|
/** Whether src/hooks.client.ts already exists (SvelteKit idempotency). */
|
|
@@ -159,10 +178,19 @@ export interface PlanInput {
|
|
|
159
178
|
pairingToken?: string;
|
|
160
179
|
/** Whether .reticle.json already exists in the project root (idempotency). */
|
|
161
180
|
reticleConfigExists?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Its CONTENT, so an existing config can be checked instead of trusted.
|
|
183
|
+
*
|
|
184
|
+
* Existence alone said "a file is present" and was reported as "this is configured correctly" —
|
|
185
|
+
* see reticleConfigStep for the port that survived every re-run because of it.
|
|
186
|
+
*/
|
|
187
|
+
reticleConfigSource?: string | null | undefined;
|
|
162
188
|
/** Current project-root CLAUDE.md content (for the idempotent agent-rule merge), or null/undefined. */
|
|
163
189
|
claudeMdContent?: string | null | undefined;
|
|
164
|
-
/** Current project-root AGENTS.md content (cross-agent
|
|
190
|
+
/** Current project-root AGENTS.md content (the cross-agent rule), or null/undefined. */
|
|
165
191
|
agentsMdContent?: string | null | undefined;
|
|
192
|
+
/** Current project-root RETICLE.md content (the full rules), or null when absent. */
|
|
193
|
+
reticleMdContent?: string | null | undefined;
|
|
166
194
|
/**
|
|
167
195
|
* Current .cursor/rules/reticle.mdc content, or null when absent.
|
|
168
196
|
*
|
|
@@ -175,6 +203,14 @@ export interface PlanInput {
|
|
|
175
203
|
claudeCommandContent?: string | null | undefined;
|
|
176
204
|
/** Current .cursor/commands/reticle.md content, or null when absent. */
|
|
177
205
|
cursorCommandContent?: string | null | undefined;
|
|
206
|
+
/**
|
|
207
|
+
* Absolute directory the AGENT runs in, when that is not the app's directory.
|
|
208
|
+
*
|
|
209
|
+
* The rule and command files are read by the agent, not by the app, so a monorepo whose app is a
|
|
210
|
+
* directory down must still get its `/reticle` where the human's session actually stands. Absent
|
|
211
|
+
* (the single-package case) leaves every path project-relative exactly as before.
|
|
212
|
+
*/
|
|
213
|
+
agentFileRoot?: string | undefined;
|
|
178
214
|
options: {
|
|
179
215
|
port: number | undefined;
|
|
180
216
|
mcp: boolean;
|
package/dist/init/plan.js
CHANGED
|
@@ -8,9 +8,11 @@ import { claudeAddCommand, mcpManual } from './mcp.js';
|
|
|
8
8
|
import { mergeCursorConfig, CursorMergeStatus, cursorServerEntry } from './cursor.js';
|
|
9
9
|
import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, } from './mcp-clients.js';
|
|
10
10
|
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, SLASH_COMMAND_SIGNATURE, } from './slash-command.js';
|
|
11
|
-
import { mergeMarkedInstruction, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
|
|
11
|
+
import { mergeMarkedInstruction, reticleMdFile, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, RETICLE_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
|
|
12
12
|
import { viteSteps, nextSteps, craSteps, svelteKitSteps, nuxtSteps, astroSteps, VITE_PLUGIN_DETAIL, } from './plan-framework.js';
|
|
13
|
+
import { join } from 'node:path';
|
|
13
14
|
import { htmlManual, reticleConfigContent, unverifiedUiLibraryNote } from './snippets.js';
|
|
15
|
+
import { devServerPortWarning, isLikelyDevServerPort } from '../cli/cli-port.js';
|
|
14
16
|
// An app dev installs exactly the audience-scoped browser-side dependencies — never the retired
|
|
15
17
|
// `@reticlehq/core` umbrella (which dragged the Node MCP server + ws into every app). The kit is the
|
|
16
18
|
// framework adapter (it re-exports the browser sensor), paired with that framework's dev-only build
|
|
@@ -34,15 +36,40 @@ export function pinnedPackages(packages, version) {
|
|
|
34
36
|
return packages;
|
|
35
37
|
return packages.map((p) => `${p}@${version}`);
|
|
36
38
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Does this codebase want the React kit, or the framework-neutral sensor?
|
|
41
|
+
*
|
|
42
|
+
* The kit is what adds component identity — component names and stacks — and it is worth having
|
|
43
|
+
* wherever React or Preact is rendering (the adapter reaches Preact through `preact/compat`).
|
|
44
|
+
* Everywhere else it is a package named `@reticlehq/react`, carrying `react` in its peer
|
|
45
|
+
* dependencies, being installed into a codebase that has no React in it.
|
|
46
|
+
*
|
|
47
|
+
* UNKNOWN keeps the kit deliberately. Absence of evidence is not evidence of Vue, and guessing
|
|
48
|
+
* "sensor" on no information silently drops component identity from apps that should have it.
|
|
49
|
+
*/
|
|
50
|
+
function wantsReactKit(ui) {
|
|
51
|
+
return ui !== UiLibrary.VUE && ui !== UiLibrary.SVELTE;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
|
|
55
|
+
*
|
|
56
|
+
* `uiLibrary` matters because the framework does not always name the renderer. The rule below was
|
|
57
|
+
* already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
|
|
58
|
+
* `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
|
|
59
|
+
* right after `init` had detected the real UI library and said so on screen.
|
|
60
|
+
*/
|
|
61
|
+
export function frameworkPackages(framework, uiLibrary = UiLibrary.UNKNOWN) {
|
|
62
|
+
const kit = wantsReactKit(uiLibrary) ? RETICLE_REACT_KIT : RETICLE_BROWSER_SDK;
|
|
39
63
|
switch (framework) {
|
|
40
64
|
case Framework.NEXT:
|
|
65
|
+
// Next is React by construction, so the detection cannot disagree in a way worth honouring.
|
|
41
66
|
return [RETICLE_REACT_KIT, RETICLE_NEXT_PLUGIN];
|
|
42
67
|
case Framework.VITE:
|
|
43
68
|
case Framework.SVELTEKIT:
|
|
44
69
|
// SvelteKit builds on Vite; until a dedicated Svelte kit exists it uses the Vite build plugin.
|
|
45
|
-
|
|
70
|
+
// The build plugin stamps `data-reticle-source` regardless of UI library, so a Vue or Svelte
|
|
71
|
+
// app still gets source pointers — it is only component identity that needs the React kit.
|
|
72
|
+
return [kit, RETICLE_VITE_PLUGIN];
|
|
46
73
|
case Framework.NUXT:
|
|
47
74
|
// The framework-neutral sensor, NOT the React kit. Nuxt renders Vue, and installing a package
|
|
48
75
|
// named @reticlehq/react — with `react` in its peer dependencies — into a Vue codebase is the
|
|
@@ -51,14 +78,14 @@ export function frameworkPackages(framework) {
|
|
|
51
78
|
case Framework.ASTRO:
|
|
52
79
|
// Astro owns its own Vite instance and renders its own HTML, so there is no config for the
|
|
53
80
|
// plugin to attach to — the kit alone, connected from a page <script> (see astroManual).
|
|
54
|
-
return [
|
|
81
|
+
return [kit];
|
|
55
82
|
case Framework.CRA:
|
|
56
83
|
// react-scripts owns its webpack config and cannot be extended without ejecting, so there is
|
|
57
84
|
// no build plugin — the kit alone, imported from src/index.tsx (see cra.ts).
|
|
58
85
|
return [RETICLE_REACT_KIT];
|
|
59
86
|
case Framework.HTML:
|
|
60
87
|
// No bundler plugin to install — just the kit; connect is wired by hand (see htmlManual).
|
|
61
|
-
return [
|
|
88
|
+
return [kit];
|
|
62
89
|
}
|
|
63
90
|
}
|
|
64
91
|
/** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
|
|
@@ -101,6 +128,19 @@ export const StepStatus = {
|
|
|
101
128
|
*/
|
|
102
129
|
NOTICE: 'notice',
|
|
103
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Where the agent-facing files go, which is not always where the app is.
|
|
133
|
+
*
|
|
134
|
+
* Reported from the field (#318): a repo with its app at `src/admin` got `.claude/commands/reticle.md`
|
|
135
|
+
* written into the app directory, while the human's agent session runs at the repo root — so
|
|
136
|
+
* `/reticle` did not exist for them at all until they copied it up by hand. The rule and command
|
|
137
|
+
* files are read by the AGENT, so they belong where the agent stands; the app files stay with the
|
|
138
|
+
* app. The two are the same directory in a single-package repo, which is why this went unnoticed.
|
|
139
|
+
*/
|
|
140
|
+
function agentFile(input, relPath) {
|
|
141
|
+
const root = input.agentFileRoot;
|
|
142
|
+
return root === undefined || 0 === root.length ? relPath : join(root, relPath);
|
|
143
|
+
}
|
|
104
144
|
const CLAUDE_MCP_TITLE = 'MCP server (Claude, global)';
|
|
105
145
|
const CURSOR_MCP_TITLE = 'MCP server (Cursor, global)';
|
|
106
146
|
function claudeMcpStep(input) {
|
|
@@ -267,32 +307,35 @@ function commandStepFor(path, present, content) {
|
|
|
267
307
|
};
|
|
268
308
|
}
|
|
269
309
|
function claudeCommandStep(input) {
|
|
270
|
-
return commandStepFor(CLAUDE_COMMAND_PATH, input.claudeCli, input.claudeCommandContent);
|
|
310
|
+
return commandStepFor(agentFile(input, CLAUDE_COMMAND_PATH), input.claudeCli, input.claudeCommandContent);
|
|
271
311
|
}
|
|
272
312
|
function cursorCommandStep(input) {
|
|
273
313
|
const present = true === input.cursorProjectPresent || (input.cursorPresent && !input.claudeCli);
|
|
274
|
-
return commandStepFor(CURSOR_COMMAND_PATH, present, input.cursorCommandContent);
|
|
314
|
+
return commandStepFor(agentFile(input, CURSOR_COMMAND_PATH), present, input.cursorCommandContent);
|
|
275
315
|
}
|
|
276
316
|
const AGENT_RULE_TITLE = 'Agent verification rule';
|
|
277
317
|
const AGENT_RULE_DETAIL = 'teach the agent to verify features with Reticle after building them';
|
|
318
|
+
const RETICLE_MD_TITLE = 'Full agent rules';
|
|
319
|
+
const RETICLE_MD_DETAIL = 'the reference the always-loaded rule points at (when NOT to verify, recovery, feedback)';
|
|
278
320
|
function claudeRuleStep(input) {
|
|
279
321
|
if (!input.claudeCli)
|
|
280
322
|
return null;
|
|
323
|
+
const path = agentFile(input, CLAUDE_MD_PATH);
|
|
281
324
|
const r = mergeMarkedInstruction(input.claudeMdContent);
|
|
282
325
|
if (r.status === AgentRuleStatus.ALREADY) {
|
|
283
326
|
return {
|
|
284
327
|
title: AGENT_RULE_TITLE,
|
|
285
|
-
target:
|
|
328
|
+
target: path,
|
|
286
329
|
status: StepStatus.ALREADY,
|
|
287
330
|
detail: 'Reticle rule already in CLAUDE.md',
|
|
288
331
|
};
|
|
289
332
|
}
|
|
290
333
|
return {
|
|
291
334
|
title: AGENT_RULE_TITLE,
|
|
292
|
-
target:
|
|
335
|
+
target: path,
|
|
293
336
|
status: StepStatus.APPLY,
|
|
294
337
|
detail: AGENT_RULE_DETAIL,
|
|
295
|
-
write: { path
|
|
338
|
+
write: { path, content: r.content },
|
|
296
339
|
};
|
|
297
340
|
}
|
|
298
341
|
/**
|
|
@@ -308,20 +351,21 @@ function cursorRuleStep(input) {
|
|
|
308
351
|
return null;
|
|
309
352
|
// The whole file is Reticle's — init created it — so a stale one is REWRITTEN rather than merged.
|
|
310
353
|
// Comparing content is what makes the rule updatable; comparing existence made it permanent.
|
|
354
|
+
const path = agentFile(input, CURSOR_RULE_PATH);
|
|
311
355
|
if (input.cursorRuleContent === cursorRuleFile()) {
|
|
312
356
|
return {
|
|
313
357
|
title: AGENT_RULE_TITLE,
|
|
314
|
-
target:
|
|
358
|
+
target: path,
|
|
315
359
|
status: StepStatus.ALREADY,
|
|
316
360
|
detail: 'Reticle rule already in .cursor/rules',
|
|
317
361
|
};
|
|
318
362
|
}
|
|
319
363
|
return {
|
|
320
364
|
title: AGENT_RULE_TITLE,
|
|
321
|
-
target:
|
|
365
|
+
target: path,
|
|
322
366
|
status: StepStatus.APPLY,
|
|
323
367
|
detail: AGENT_RULE_DETAIL,
|
|
324
|
-
write: { path
|
|
368
|
+
write: { path, content: cursorRuleFile() },
|
|
325
369
|
};
|
|
326
370
|
}
|
|
327
371
|
/**
|
|
@@ -365,33 +409,70 @@ function slashCommandSteps(input) {
|
|
|
365
409
|
return stepsForAgents(input, (a) => a.commandStep);
|
|
366
410
|
}
|
|
367
411
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
412
|
+
* `AGENTS.md`, always. It is the file every agent that is not Claude or Cursor reads.
|
|
413
|
+
*
|
|
414
|
+
* This used to be a FALLBACK, written only when no agent was detected. So a repo set up on a machine
|
|
415
|
+
* with Claude Code got `CLAUDE.md` and nothing else, and the next person to open it with Codex,
|
|
416
|
+
* Copilot, Amp, Gemini or any other agent found a fully instrumented app whose rules were addressed
|
|
417
|
+
* to somebody else's tool. The detection tells you which agent is running the install; it says
|
|
418
|
+
* nothing about which agents will open the repository afterwards.
|
|
419
|
+
*/
|
|
420
|
+
function agentsMdStep(input) {
|
|
421
|
+
const r = mergeMarkedInstruction(input.agentsMdContent);
|
|
422
|
+
const path = agentFile(input, AGENTS_MD_PATH);
|
|
423
|
+
if (r.status === AgentRuleStatus.ALREADY) {
|
|
424
|
+
return {
|
|
425
|
+
title: AGENT_RULE_TITLE,
|
|
426
|
+
target: path,
|
|
427
|
+
status: StepStatus.ALREADY,
|
|
428
|
+
detail: 'Reticle rule already in AGENTS.md',
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
title: AGENT_RULE_TITLE,
|
|
433
|
+
target: path,
|
|
434
|
+
status: StepStatus.APPLY,
|
|
435
|
+
detail: AGENT_RULE_DETAIL,
|
|
436
|
+
write: { path, content: r.content },
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* `RETICLE.md`, the reference the always-loaded blocks point at.
|
|
441
|
+
*
|
|
442
|
+
* Written whole rather than merged, because `init` owns the entire file: there is no user content to
|
|
443
|
+
* preserve, and the marker dance exists only for files somebody else already writes in.
|
|
444
|
+
*/
|
|
445
|
+
function reticleMdStep(input) {
|
|
446
|
+
const path = agentFile(input, RETICLE_MD_PATH);
|
|
447
|
+
const content = reticleMdFile();
|
|
448
|
+
if (input.reticleMdContent === content) {
|
|
449
|
+
return {
|
|
450
|
+
title: RETICLE_MD_TITLE,
|
|
451
|
+
target: path,
|
|
452
|
+
status: StepStatus.ALREADY,
|
|
453
|
+
detail: 'full rules already current',
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
title: RETICLE_MD_TITLE,
|
|
458
|
+
target: path,
|
|
459
|
+
status: StepStatus.APPLY,
|
|
460
|
+
detail: RETICLE_MD_DETAIL,
|
|
461
|
+
write: { path, content },
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The behavioral rules that make the agent actually USE Reticle, and know when not to.
|
|
466
|
+
*
|
|
467
|
+
* Every detected agent's own file, plus `AGENTS.md` for the ones that will open this repo later, plus
|
|
468
|
+
* `RETICLE.md` holding the reference half. Rides with the MCP wiring: `--no-mcp` opts out of
|
|
469
|
+
* registering the tools AND of every rule file, because rules for tools the agent cannot reach are
|
|
470
|
+
* noise.
|
|
371
471
|
*/
|
|
372
472
|
function agentRuleSteps(input) {
|
|
373
473
|
if (!input.options.mcp)
|
|
374
474
|
return [];
|
|
375
|
-
|
|
376
|
-
if (detected.length > 0)
|
|
377
|
-
return detected;
|
|
378
|
-
const r = mergeMarkedInstruction(input.agentsMdContent);
|
|
379
|
-
return [
|
|
380
|
-
r.status === AgentRuleStatus.ALREADY
|
|
381
|
-
? {
|
|
382
|
-
title: AGENT_RULE_TITLE,
|
|
383
|
-
target: AGENTS_MD_PATH,
|
|
384
|
-
status: StepStatus.ALREADY,
|
|
385
|
-
detail: 'Reticle rule already in AGENTS.md',
|
|
386
|
-
}
|
|
387
|
-
: {
|
|
388
|
-
title: AGENT_RULE_TITLE,
|
|
389
|
-
target: AGENTS_MD_PATH,
|
|
390
|
-
status: StepStatus.APPLY,
|
|
391
|
-
detail: AGENT_RULE_DETAIL,
|
|
392
|
-
write: { path: AGENTS_MD_PATH, content: r.content },
|
|
393
|
-
},
|
|
394
|
-
];
|
|
475
|
+
return [...stepsForAgents(input, (a) => a.ruleStep), agentsMdStep(input), reticleMdStep(input)];
|
|
395
476
|
}
|
|
396
477
|
/**
|
|
397
478
|
* What to say when the install command fails.
|
|
@@ -439,7 +520,7 @@ function installFailureHint(pm) {
|
|
|
439
520
|
}
|
|
440
521
|
function installStep(input) {
|
|
441
522
|
const pm = input.detection.packageManager;
|
|
442
|
-
const packages = pinnedPackages(frameworkPackages(input.detection.framework), input.options.sdkVersion);
|
|
523
|
+
const packages = pinnedPackages(frameworkPackages(input.detection.framework, input.detection.uiLibrary), input.options.sdkVersion);
|
|
443
524
|
const command = installCommand(pm, packages);
|
|
444
525
|
if (!input.options.install) {
|
|
445
526
|
return {
|
|
@@ -463,13 +544,55 @@ function installStep(input) {
|
|
|
463
544
|
// Unpinned. pnpm resolves the newest MATURE version there, which is how a project with a
|
|
464
545
|
// release-age hold gets a working install instead of no install.
|
|
465
546
|
retry: {
|
|
466
|
-
...installCommandParts(pm, frameworkPackages(input.detection.framework)),
|
|
547
|
+
...installCommandParts(pm, frameworkPackages(input.detection.framework, input.detection.uiLibrary)),
|
|
467
548
|
note: unpinnedRetryNote(input.options.sdkVersion, pm),
|
|
468
549
|
},
|
|
469
550
|
};
|
|
470
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* What is wrong with the config that is already there, if anything.
|
|
554
|
+
*
|
|
555
|
+
* Reported from the field (#317): a project carried `"port": 3000` — its own dev-server port, which
|
|
556
|
+
* is the confusion SKILL.md names as the top setup failure — and `init` printed `.reticle.json
|
|
557
|
+
* already exists` on every re-run without reading a single field of it, so the file could never be
|
|
558
|
+
* repaired by the command that wrote it. It stayed invisible because a daemon on `127.0.0.1:3000`
|
|
559
|
+
* and Vite on `[::1]:3000` split the port by address family and neither reported a conflict.
|
|
560
|
+
*
|
|
561
|
+
* Narrower than the issue asked for on purpose: `init`'s IO surface is synchronous, so this cannot
|
|
562
|
+
* probe what is LISTENING right now. `isLikelyDevServerPort` covers the framework defaults, which is
|
|
563
|
+
* where the mistake is actually made.
|
|
564
|
+
*/
|
|
565
|
+
function existingConfigProblem(source) {
|
|
566
|
+
if (null === source || source === undefined)
|
|
567
|
+
return undefined;
|
|
568
|
+
let parsed;
|
|
569
|
+
try {
|
|
570
|
+
parsed = JSON.parse(source);
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return (`${RETICLE_CONFIG_FILE} is present but is not valid JSON, so nothing reads it — neither the ` +
|
|
574
|
+
'daemon nor the SDK. Delete it and re-run `reticle init`.');
|
|
575
|
+
}
|
|
576
|
+
if ('object' !== typeof parsed || null === parsed)
|
|
577
|
+
return undefined;
|
|
578
|
+
const port = parsed['port'];
|
|
579
|
+
if ('number' !== typeof port || !isLikelyDevServerPort(port))
|
|
580
|
+
return undefined;
|
|
581
|
+
return `${devServerPortWarning(port)} Fix it by removing the "port" field from ${RETICLE_CONFIG_FILE}.`;
|
|
582
|
+
}
|
|
471
583
|
function reticleConfigStep(input) {
|
|
472
584
|
if (true === input.reticleConfigExists) {
|
|
585
|
+
const problem = existingConfigProblem(input.reticleConfigSource);
|
|
586
|
+
if (problem !== undefined) {
|
|
587
|
+
// `ℹ`, not `·`: the step is done and something about the result still stops things working,
|
|
588
|
+
// which is the one mark that says "there is something here to read".
|
|
589
|
+
return {
|
|
590
|
+
title: 'Reticle config',
|
|
591
|
+
target: RETICLE_CONFIG_FILE,
|
|
592
|
+
status: StepStatus.NOTICE,
|
|
593
|
+
detail: problem,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
473
596
|
return {
|
|
474
597
|
title: 'Reticle config',
|
|
475
598
|
target: RETICLE_CONFIG_FILE,
|
|
@@ -550,5 +673,5 @@ export function buildPlan(input) {
|
|
|
550
673
|
detail: htmlManual(input.options.port, input.options.projectId, input.pairingToken),
|
|
551
674
|
});
|
|
552
675
|
}
|
|
553
|
-
return { framework: input.detection.framework, steps };
|
|
676
|
+
return { framework: input.detection.framework, uiLibrary: input.detection.uiLibrary, steps };
|
|
554
677
|
}
|
package/dist/init/run.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* plan (pure), optionally write the apply-steps, and print a human-readable report. All filesystem
|
|
4
4
|
* access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
|
|
5
5
|
*/
|
|
6
|
+
import type { InitOutcome } from '@reticlehq/core';
|
|
6
7
|
/**
|
|
7
8
|
* Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
|
|
8
9
|
* otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
|
|
@@ -29,6 +30,23 @@ export interface InitOptions {
|
|
|
29
30
|
app?: string;
|
|
30
31
|
/** Set on the recursive call after a workspace redirect, so the search happens at most once. */
|
|
31
32
|
redirected?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Where the human ran the command, carried across a workspace redirect.
|
|
35
|
+
*
|
|
36
|
+
* The agent rule and `/reticle` command files are read by the AGENT, whose session runs where the
|
|
37
|
+
* human started it — the repo root. Writing them beside the app instead is how a repo with its app
|
|
38
|
+
* at `src/admin` ended up with no `/reticle` at all (#318).
|
|
39
|
+
*/
|
|
40
|
+
agentRoot?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Hand the telemetry outcome back rather than emitting it here.
|
|
43
|
+
*
|
|
44
|
+
* `init` now stays to watch for an app to connect, and whether it saw one belongs on the SAME
|
|
45
|
+
* `init_completed` event — the funnel it exists to measure would be double-counted by a second
|
|
46
|
+
* emit and unjoinable as a second event kind. Only the CLI sets this; every other caller keeps
|
|
47
|
+
* today's fire-and-forget behaviour, so no path loses its event by forgetting to report.
|
|
48
|
+
*/
|
|
49
|
+
deferOutcome?: boolean;
|
|
32
50
|
}
|
|
33
51
|
export interface InitIo {
|
|
34
52
|
/** Returns file content or null if it does not exist. Path is project-relative or absolute. */
|
|
@@ -52,10 +70,15 @@ export interface InitIo {
|
|
|
52
70
|
probe(command: string, args: readonly string[]): boolean;
|
|
53
71
|
print(line: string): void;
|
|
54
72
|
}
|
|
55
|
-
interface InitResult {
|
|
73
|
+
export interface InitResult {
|
|
56
74
|
ok: boolean;
|
|
57
75
|
applied: number;
|
|
58
76
|
manual: number;
|
|
77
|
+
/**
|
|
78
|
+
* The event body this run would report, handed to the caller instead of emitted, when
|
|
79
|
+
* `deferOutcome` is set. Absent on a dry run and on the exits that report for themselves.
|
|
80
|
+
*/
|
|
81
|
+
outcome?: InitOutcome;
|
|
59
82
|
}
|
|
60
83
|
/**
|
|
61
84
|
* App directories under a workspace root.
|
|
@@ -77,4 +100,3 @@ export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile'
|
|
|
77
100
|
*/
|
|
78
101
|
export declare const FEEDBACK_HINT: string;
|
|
79
102
|
export declare function runInit(options: InitOptions, io: InitIo): InitResult;
|
|
80
|
-
export {};
|