@reticlehq/init 3.6.0 → 3.7.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/dist/detect/dev-script.d.ts +17 -0
- package/dist/detect/dev-script.js +128 -0
- package/dist/detect/workspace-apps.d.ts +11 -0
- package/dist/detect/workspace-apps.js +63 -0
- package/dist/detect/workspace-redirect.js +49 -5
- package/dist/diagnose/init-failure.d.ts +2 -0
- package/dist/diagnose/init-failure.js +2 -0
- package/dist/gather-plan-input.d.ts +11 -0
- package/dist/gather-plan-input.js +416 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -2
- package/dist/node-io.d.ts +1 -1
- package/dist/node-io.js +11 -2
- package/dist/patch/next-patch.d.ts +28 -0
- package/dist/patch/next-patch.js +66 -0
- package/dist/patch/nuxt-snippets.js +1 -1
- package/dist/patch/snippets.d.ts +1 -1
- package/dist/patch/snippets.js +2 -2
- package/dist/patch/static-page.d.ts +1 -0
- package/dist/patch/static-page.js +3 -3
- package/dist/plan/connect-steps.d.ts +1 -0
- package/dist/plan/connect-steps.js +5 -0
- package/dist/plan/framework-adapter.js +2 -2
- package/dist/plan/plan-angular.js +1 -1
- package/dist/plan/plan-framework.d.ts +1 -1
- package/dist/plan/plan-framework.js +47 -13
- package/dist/plan/plan-types.d.ts +23 -2
- package/dist/plan/plan-vite.js +15 -0
- package/dist/plan/plan.js +68 -10
- package/dist/plan/stop-hook-step.d.ts +19 -0
- package/dist/plan/stop-hook-step.js +74 -1
- package/dist/project/agent-rules.d.ts +1 -1
- package/dist/project/agent-rules.js +16 -11
- package/dist/register/codex-toml.d.ts +12 -0
- package/dist/register/codex-toml.js +44 -0
- package/dist/register/mcp.d.ts +15 -2
- package/dist/register/mcp.js +16 -0
- package/dist/run-types.d.ts +6 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.js +11 -389
- package/package.json +2 -2
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `reticle init` reads before it plans: every project file, probe and host answer the pure
|
|
3
|
+
* planner takes, gathered through the injected `InitIo`.
|
|
4
|
+
*
|
|
5
|
+
* Split from `run.ts`, which orchestrates the run (plan, apply, report). This is only the read side,
|
|
6
|
+
* and it writes nothing.
|
|
7
|
+
*/
|
|
8
|
+
import { detectMcpClients } from './register/detect-clients.js';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { CONTAINER_MARKERS, runsDevServer } from './diagnose/containerised-dev-server.js';
|
|
11
|
+
import { CSP_FILES } from './diagnose/csp-doctor.js';
|
|
12
|
+
import { devScriptBody, discoverPlainViteConfig } from './detect/dev-script.js';
|
|
13
|
+
import { ANGULAR_WORKSPACE_FILE, detect, FORGE_RENDERER_CONFIGS, Framework, } from './detect/detect.js';
|
|
14
|
+
import { ANGULAR_PROXY_PATH, angularEntry } from './patch/angular.js';
|
|
15
|
+
import { HTML_INDEX_PATH, STATIC_GITIGNORE_PATH, STATIC_TOKEN_MODULE, } from './patch/static-page.js';
|
|
16
|
+
import { pickAstroHost } from './patch/astro-host.js';
|
|
17
|
+
import { ELECTRON_VITE_CONFIG_CANDIDATES, NEXT_CONFIG_CANDIDATES, VITE_CONFIG_CANDIDATES, } from './detect/workspace-apps.js';
|
|
18
|
+
import { CURSOR_RULE_PATH, RETICLE_MD_PATH } from './project/agent-rules.js';
|
|
19
|
+
import { CLAUDE_SETTINGS_PATH } from './plan/stop-hook-step.js';
|
|
20
|
+
import { CRA_ENV_PATH } from './patch/cra.js';
|
|
21
|
+
import { findNextMountFile } from './patch/next-patch.js';
|
|
22
|
+
import { tanstackStartConnectPath } from './patch/tanstack-start.js';
|
|
23
|
+
import { installedViteMajor } from './patch/vite-owning-config.js';
|
|
24
|
+
import { RETICLE_CONFIG_FILE } from './plan/plan.js';
|
|
25
|
+
import { claudeAvailableProbe, claudeHasReticle, codexAvailableProbe } from './register/mcp.js';
|
|
26
|
+
import { reticleDevLocation } from './patch/next-patch.js';
|
|
27
|
+
import { scanTestids, storeHints, scanStores } from './detect/capabilities.js';
|
|
28
|
+
import { CLAUDE_PROJECT_CONFIG, CURSOR_PROJECT_MARKER, McpClient } from './register/mcp-clients.js';
|
|
29
|
+
import { deriveProjectId, packageName } from './project/project-id.js';
|
|
30
|
+
import { VITE_DEV_MODULE_PATH, ELECTRON_VITE_DEV_MODULE_PATH, nuxtPluginPath, } from './patch/snippets.js';
|
|
31
|
+
import { NUXT_CONFIG_CANDIDATES } from './patch/nuxt-patch.js';
|
|
32
|
+
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './register/slash-command.js';
|
|
33
|
+
import { RETICLE_VERSION } from './version.js';
|
|
34
|
+
import { resolveLockfiles } from './detect/lockfiles.js';
|
|
35
|
+
/** CRA's bundled entry, in the order create-react-app itself generates them. */
|
|
36
|
+
const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
|
|
37
|
+
/** Preview snippets must not mint a real machine credential or imply this token is usable. */
|
|
38
|
+
const DRY_RUN_PAIRING_TOKEN = 'DRY_RUN_TOKEN_NOT_FOR_USE';
|
|
39
|
+
function craEntryOf(io) {
|
|
40
|
+
for (const path of CRA_ENTRY_CANDIDATES) {
|
|
41
|
+
const source = io.readFile(path);
|
|
42
|
+
if (source !== null)
|
|
43
|
+
return { path, source };
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const SVELTEKIT_HOOKS = 'src/hooks.client.ts';
|
|
48
|
+
const REACT_ROUTER_ENTRY = 'app/entry.client.tsx';
|
|
49
|
+
const TANSTACK_START_ROOT_CANDIDATES = [
|
|
50
|
+
'src/routes/__root.tsx',
|
|
51
|
+
'src/routes/__root.jsx',
|
|
52
|
+
'app/routes/__root.tsx',
|
|
53
|
+
'app/routes/__root.jsx',
|
|
54
|
+
];
|
|
55
|
+
const SOURCE_FILE = /\.(tsx|jsx|ts|js|svelte|vue|astro)$/;
|
|
56
|
+
/** Files read for the testid scan. A capabilities block is a hint; reading a whole repo for it is not. */
|
|
57
|
+
const MAX_SCANNED_FILES = 200;
|
|
58
|
+
/** Directories that never hold the app's own source, and are expensive or misleading to read. */
|
|
59
|
+
const NOT_SOURCE_DIRS = new Set([
|
|
60
|
+
'node_modules',
|
|
61
|
+
'dist',
|
|
62
|
+
'build',
|
|
63
|
+
'out',
|
|
64
|
+
'coverage',
|
|
65
|
+
'target',
|
|
66
|
+
'public',
|
|
67
|
+
'static',
|
|
68
|
+
'.next',
|
|
69
|
+
'.svelte-kit',
|
|
70
|
+
'.nuxt',
|
|
71
|
+
]);
|
|
72
|
+
/** How far below the app root to look. Deep enough for `modules/users/UserList.tsx`, not a repo crawl. */
|
|
73
|
+
const MAX_SCAN_DEPTH = 5;
|
|
74
|
+
/**
|
|
75
|
+
* Read a bounded set of the app's source files, for the `data-testid` scan.
|
|
76
|
+
*
|
|
77
|
+
* This used to walk the fixed list `src, src/components, src/pages, app, components`. Reported from
|
|
78
|
+
* the field (#318) by a repo whose app is at `src/admin`: the scan read directories that were not
|
|
79
|
+
* the app's and correctly reported finding nothing in them, so `init` said "no data-testid values
|
|
80
|
+
* yet" about an app with several — which makes an agent go and write the ones already there. That
|
|
81
|
+
* list had already grown once for a `frontend/` app, and a third report of the same shape is what
|
|
82
|
+
* says the answer is not another name in a list. So: walk the app root, bounded by depth and by a
|
|
83
|
+
* file count.
|
|
84
|
+
*/
|
|
85
|
+
function readSourceFiles(io) {
|
|
86
|
+
const out = [];
|
|
87
|
+
const read = (path) => {
|
|
88
|
+
const content = io.readFile(path);
|
|
89
|
+
if (content !== null)
|
|
90
|
+
out.push({ path, source: content });
|
|
91
|
+
};
|
|
92
|
+
for (const name of io.rootFiles()) {
|
|
93
|
+
if (out.length >= MAX_SCANNED_FILES)
|
|
94
|
+
return out;
|
|
95
|
+
if (SOURCE_FILE.test(name))
|
|
96
|
+
read(name);
|
|
97
|
+
}
|
|
98
|
+
const walk = (dir, depth) => {
|
|
99
|
+
for (const name of io.listFiles(dir)) {
|
|
100
|
+
if (out.length >= MAX_SCANNED_FILES)
|
|
101
|
+
return;
|
|
102
|
+
if (SOURCE_FILE.test(name))
|
|
103
|
+
read(`${dir}/${name}`);
|
|
104
|
+
}
|
|
105
|
+
if (depth >= MAX_SCAN_DEPTH)
|
|
106
|
+
return;
|
|
107
|
+
for (const sub of io.listDirs(dir)) {
|
|
108
|
+
if (out.length >= MAX_SCANNED_FILES)
|
|
109
|
+
return;
|
|
110
|
+
if (sub.startsWith('.') || NOT_SOURCE_DIRS.has(sub))
|
|
111
|
+
continue;
|
|
112
|
+
walk(`${dir}/${sub}`, depth + 1);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
for (const dir of io.listDirs('.')) {
|
|
116
|
+
if (out.length >= MAX_SCANNED_FILES)
|
|
117
|
+
return out;
|
|
118
|
+
if (dir.startsWith('.') || NOT_SOURCE_DIRS.has(dir))
|
|
119
|
+
continue;
|
|
120
|
+
walk(dir, 1);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/** Direct dependency names, for naming the state libraries an app actually has. */
|
|
125
|
+
function dependencyNames(pkg) {
|
|
126
|
+
const p = (pkg ?? {});
|
|
127
|
+
return new Set([
|
|
128
|
+
...Object.keys(p['dependencies'] ?? {}),
|
|
129
|
+
...Object.keys(p['devDependencies'] ?? {}),
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
/** electron-vite and the unbundled Electron layouts this repo already ships. */
|
|
133
|
+
const ELECTRON_MAIN_SOURCES = [
|
|
134
|
+
'src/main/index.ts',
|
|
135
|
+
'src/main/index.js',
|
|
136
|
+
'src/main/index.mts',
|
|
137
|
+
'src/main/index.mjs',
|
|
138
|
+
'electron/main.cjs',
|
|
139
|
+
'electron/main.js',
|
|
140
|
+
'electron/main.ts',
|
|
141
|
+
'src/main.ts',
|
|
142
|
+
'src/main.js',
|
|
143
|
+
];
|
|
144
|
+
const ELECTRON_PRELOAD_SOURCES = [
|
|
145
|
+
'src/preload/index.ts',
|
|
146
|
+
'src/preload/index.js',
|
|
147
|
+
'src/preload/index.mts',
|
|
148
|
+
'src/preload/index.mjs',
|
|
149
|
+
'electron/preload.cjs',
|
|
150
|
+
'electron/preload.js',
|
|
151
|
+
'electron/preload.ts',
|
|
152
|
+
'src/preload.ts',
|
|
153
|
+
'src/preload.js',
|
|
154
|
+
];
|
|
155
|
+
const ASTRO_CONFIG_CANDIDATES = [
|
|
156
|
+
'astro.config.mjs',
|
|
157
|
+
'astro.config.js',
|
|
158
|
+
'astro.config.ts',
|
|
159
|
+
'astro.config.cjs',
|
|
160
|
+
];
|
|
161
|
+
/** Where a conventional Astro app keeps the layout every page wraps itself in. */
|
|
162
|
+
const ASTRO_LAYOUTS_DIR = 'src/layouts';
|
|
163
|
+
/** Also searched: an app with no layouts directory renders the document straight from a page. */
|
|
164
|
+
const ASTRO_PAGES_DIR = 'src/pages';
|
|
165
|
+
function firstPresent(files, candidates) {
|
|
166
|
+
for (const c of candidates)
|
|
167
|
+
if (files.has(c))
|
|
168
|
+
return c;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
function firstReadable(io, candidates) {
|
|
172
|
+
for (const path of candidates) {
|
|
173
|
+
const source = io.readFile(path);
|
|
174
|
+
if (source !== null)
|
|
175
|
+
return { path, source };
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The directory the human's agent runs in, when it is not the app's directory.
|
|
181
|
+
*
|
|
182
|
+
* `undefined` for a single-package repo, which keeps every agent-file path project-relative exactly
|
|
183
|
+
* as it was — the two roots are the same there, which is why writing them beside the app went
|
|
184
|
+
* unnoticed until a repo with its app at `src/admin` reported it.
|
|
185
|
+
*/
|
|
186
|
+
export function agentRootOf(options) {
|
|
187
|
+
const root = options.agentRoot;
|
|
188
|
+
return root === undefined || root === options.cwd ? undefined : root;
|
|
189
|
+
}
|
|
190
|
+
/** What the Angular steps read: the workspace file, the entry it names, and our proxy module. */
|
|
191
|
+
function angularInputs(io) {
|
|
192
|
+
const workspace = io.readFile(ANGULAR_WORKSPACE_FILE);
|
|
193
|
+
const entryPath = angularEntry(workspace);
|
|
194
|
+
const entry = io.readFile(entryPath);
|
|
195
|
+
return {
|
|
196
|
+
angularWorkspace: null === workspace ? null : { path: ANGULAR_WORKSPACE_FILE, source: workspace },
|
|
197
|
+
angularEntry: null === entry ? null : { path: entryPath, source: entry },
|
|
198
|
+
angularProxySource: io.readFile(ANGULAR_PROXY_PATH),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export function gatherPlanInput(options, io, pkg) {
|
|
202
|
+
// Stable identity derived from the app's package.json name + root, so it survives port changes.
|
|
203
|
+
const projectId = deriveProjectId(packageName(pkg), options.cwd);
|
|
204
|
+
const rootFiles = new Set(io.rootFiles());
|
|
205
|
+
const nodeModulesMarkers = new Set(io.listFiles('node_modules'));
|
|
206
|
+
const detectInput = {
|
|
207
|
+
pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
|
|
208
|
+
configFiles: rootFiles,
|
|
209
|
+
// Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager —
|
|
210
|
+
// unless this package's own installed tree already answers it, which outranks an inherited one.
|
|
211
|
+
lockfiles: resolveLockfiles(rootFiles, options.cwd, io, nodeModulesMarkers),
|
|
212
|
+
// An already-installed tree names its own manager, which matters when no lockfile is committed.
|
|
213
|
+
nodeModulesMarkers,
|
|
214
|
+
};
|
|
215
|
+
const detection = detect(detectInput);
|
|
216
|
+
const nodeModulesLinked = true === io.isSymlink?.('node_modules');
|
|
217
|
+
// Forge has no `vite.config.*`; its renderer config IS the Vite config the plugin belongs in, and
|
|
218
|
+
// handing it over under that name is what lets the ordinary Vite patcher do the work.
|
|
219
|
+
const plainViteConfig = Framework.VITE === detection.framework
|
|
220
|
+
? discoverPlainViteConfig(io, pkg, rootFiles, VITE_CONFIG_CANDIDATES)
|
|
221
|
+
: undefined;
|
|
222
|
+
const vitePath = firstPresent(rootFiles, Framework.ELECTRON_FORGE === detection.framework
|
|
223
|
+
? FORGE_RENDERER_CONFIGS
|
|
224
|
+
: VITE_CONFIG_CANDIDATES);
|
|
225
|
+
const viteSource = null === vitePath ? null : io.readFile(vitePath);
|
|
226
|
+
const viteConfig = plainViteConfig === undefined
|
|
227
|
+
? vitePath !== null && viteSource !== null
|
|
228
|
+
? { path: vitePath, source: viteSource }
|
|
229
|
+
: null
|
|
230
|
+
: plainViteConfig.config;
|
|
231
|
+
const electronVitePath = firstPresent(rootFiles, ELECTRON_VITE_CONFIG_CANDIDATES);
|
|
232
|
+
const electronViteSource = null === electronVitePath ? null : io.readFile(electronVitePath);
|
|
233
|
+
const electronViteConfig = electronVitePath !== null && electronViteSource !== null
|
|
234
|
+
? { path: electronVitePath, source: electronViteSource }
|
|
235
|
+
: null;
|
|
236
|
+
const electronMain = firstReadable(io, ELECTRON_MAIN_SOURCES);
|
|
237
|
+
const electronPreload = firstReadable(io, ELECTRON_PRELOAD_SOURCES);
|
|
238
|
+
// Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
|
|
239
|
+
// global config file. Only probe when the MCP step is in play.
|
|
240
|
+
const availableProbe = claudeAvailableProbe();
|
|
241
|
+
const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
|
|
242
|
+
// Read from Claude's config, never `claude mcp get`: that launches the server — see claudeHasReticle.
|
|
243
|
+
const mcpExists = claudeCli ? claudeHasReticle(io, io.cwd()) : false;
|
|
244
|
+
// Every MCP client this machine shows evidence of. Conservative and one-directional: we write
|
|
245
|
+
// into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody who
|
|
246
|
+
// does not use them.
|
|
247
|
+
const detectedClients = options.mcp ? detectMcpClients(io) : [];
|
|
248
|
+
// Codex registers through its own CLI when there is one. Asked only after Codex was FOUND: the
|
|
249
|
+
// probe runs a binary, and a machine with no Codex has no business running one.
|
|
250
|
+
const codexProbe = codexAvailableProbe();
|
|
251
|
+
const codexCli = detectedClients.some((client) => McpClient.CODEX === client.id)
|
|
252
|
+
? io.probe(codexProbe.command, codexProbe.args)
|
|
253
|
+
: false;
|
|
254
|
+
const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
|
|
255
|
+
const astroSource = null === astroPath ? null : io.readFile(astroPath);
|
|
256
|
+
// Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
|
|
257
|
+
// .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
|
|
258
|
+
// renders from src/pages/index.astro, the other has three files there of which two are partials.
|
|
259
|
+
// `</body>` is the discriminator — see astro-host.
|
|
260
|
+
const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
|
|
261
|
+
.listFiles(dir)
|
|
262
|
+
.filter((f) => f.endsWith('.astro'))
|
|
263
|
+
.map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
|
|
264
|
+
.filter((c) => c.source !== null));
|
|
265
|
+
const astroHost = pickAstroHost(astroCandidates);
|
|
266
|
+
const layoutRelPath = astroHost?.path ?? null;
|
|
267
|
+
const astroLayoutSource = astroHost?.source ?? null;
|
|
268
|
+
const nuxtConfigPath = firstPresent(rootFiles, NUXT_CONFIG_CANDIDATES);
|
|
269
|
+
const nuxtConfigSource = null === nuxtConfigPath ? null : io.readFile(nuxtConfigPath);
|
|
270
|
+
// Nuxt 4's default srcDir is `app/`, Nuxt 3's is the root — and a plugin written into the
|
|
271
|
+
// directory Nuxt does not scan is never registered, silently.
|
|
272
|
+
const nuxtHasAppDir = io.listDirs('.').includes('app');
|
|
273
|
+
const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
|
|
274
|
+
// App Router first, route groups included (#1538); a Pages Router app mounts in pages/_app.
|
|
275
|
+
const nextMount = findNextMountFile(io);
|
|
276
|
+
const layoutPath = nextMount.path;
|
|
277
|
+
const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
|
|
278
|
+
// Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
|
|
279
|
+
// component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
|
|
280
|
+
const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
|
|
281
|
+
// Read once: both the testid scan and the store scan want the same bounded set of files.
|
|
282
|
+
const sourceFiles = readSourceFiles(io);
|
|
283
|
+
const agentRoot = agentRootOf(options);
|
|
284
|
+
const agentFile = (relPath) => agentRoot === undefined ? relPath : join(agentRoot, relPath);
|
|
285
|
+
const cspSources = {};
|
|
286
|
+
for (const file of CSP_FILES) {
|
|
287
|
+
const source = io.readFile(file);
|
|
288
|
+
if (null !== source)
|
|
289
|
+
cspSources[file] = source;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
detection,
|
|
293
|
+
...(nodeModulesLinked ? { nodeModulesLinked } : {}),
|
|
294
|
+
captureBodies: options.captureBodies,
|
|
295
|
+
hooks: options.hooks,
|
|
296
|
+
cspSources,
|
|
297
|
+
claudeCli,
|
|
298
|
+
mcpExists,
|
|
299
|
+
insideClaudeCode: io.host.insideClaudeCode(),
|
|
300
|
+
claudeProjectConfig: options.mcp ? io.readFile(agentFile(CLAUDE_PROJECT_CONFIG)) : undefined,
|
|
301
|
+
platform: process.platform,
|
|
302
|
+
detectedClients,
|
|
303
|
+
codexCli,
|
|
304
|
+
cursorProjectPresent: io.exists(CURSOR_PROJECT_MARKER),
|
|
305
|
+
// Looked for beside the app AND one level up, because the app is routinely a subdirectory of the
|
|
306
|
+
// repo that containerises it — `frontend/` under a root `docker-compose.yml` is the shape this
|
|
307
|
+
// came from. Only the first match is reported; the note is the same whichever file found it.
|
|
308
|
+
...(() => {
|
|
309
|
+
// Only a file that RUNS the dev server: a production-only image is the common case, and
|
|
310
|
+
// React Router's template ships one. See runsDevServer.
|
|
311
|
+
const devScript = devScriptBody(pkg);
|
|
312
|
+
const marker = CONTAINER_MARKERS.find((name) => [name, join('..', name)].some((path) => {
|
|
313
|
+
const content = io.readFile(path);
|
|
314
|
+
return null !== content && runsDevServer(name, content, devScript);
|
|
315
|
+
}));
|
|
316
|
+
return marker === undefined ? {} : { containerMarker: marker };
|
|
317
|
+
})(),
|
|
318
|
+
viteConfig,
|
|
319
|
+
viteConfigCandidates: plainViteConfig?.candidates,
|
|
320
|
+
electronViteConfig,
|
|
321
|
+
electronPreload,
|
|
322
|
+
electronMain,
|
|
323
|
+
astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
|
|
324
|
+
astroLayout: layoutRelPath !== null && astroLayoutSource !== null
|
|
325
|
+
? { path: layoutRelPath, source: astroLayoutSource }
|
|
326
|
+
: null,
|
|
327
|
+
astroEnvDts: io.readFile('src/env.d.ts'),
|
|
328
|
+
astroReticleDev: io.readFile('src/components/ReticleDev.ts'),
|
|
329
|
+
nextConfigFile,
|
|
330
|
+
nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
|
|
331
|
+
nextLayout: layoutPath !== null && layoutSource !== null
|
|
332
|
+
? { path: layoutPath, source: layoutSource }
|
|
333
|
+
: null,
|
|
334
|
+
// Capabilities: scanned, never asked for. Bounded — a hint for the agent, not a repo index.
|
|
335
|
+
nextLayoutCandidates: nextMount.candidates,
|
|
336
|
+
testids: scanTestids(sourceFiles.map((f) => f.source)),
|
|
337
|
+
storeHints: storeHints(dependencyNames(pkg), sourceFiles),
|
|
338
|
+
foundStores: scanStores(sourceFiles, dependencyNames(pkg)),
|
|
339
|
+
nextFoundStores: scanStores(sourceFiles, dependencyNames(pkg), dirname(devLocation.path)),
|
|
340
|
+
viteDevModuleExists: io.exists(detection.framework === Framework.ELECTRON_VITE
|
|
341
|
+
? ELECTRON_VITE_DEV_MODULE_PATH
|
|
342
|
+
: VITE_DEV_MODULE_PATH),
|
|
343
|
+
nextReticleDevPath: devLocation.path,
|
|
344
|
+
nextReticleDevImport: devLocation.importSpecifier,
|
|
345
|
+
nextReticleDevExists: io.exists(devLocation.path),
|
|
346
|
+
nextReticleDevSource: io.readFile(devLocation.path),
|
|
347
|
+
svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
|
|
348
|
+
svelteKitHooksSource: io.readFile(SVELTEKIT_HOOKS),
|
|
349
|
+
reactRouterEntryExists: io.exists(REACT_ROUTER_ENTRY),
|
|
350
|
+
// The SOURCE, so an entry the app already owns is ADDED TO rather than replaced — it is an
|
|
351
|
+
// override of React Router's default, and everything in it is load-bearing.
|
|
352
|
+
reactRouterEntrySource: io.readFile(REACT_ROUTER_ENTRY),
|
|
353
|
+
nuxtConfig: nuxtConfigPath !== null && nuxtConfigSource !== null
|
|
354
|
+
? { path: nuxtConfigPath, source: nuxtConfigSource }
|
|
355
|
+
: null,
|
|
356
|
+
nuxtHasAppDir,
|
|
357
|
+
viteMajor: installedViteMajor(io, detectInput.pkg),
|
|
358
|
+
nuxtPluginExists: io.exists(nuxtPluginPath(nuxtHasAppDir)),
|
|
359
|
+
nuxtPluginSource: io.readFile(nuxtPluginPath(nuxtHasAppDir)),
|
|
360
|
+
...(() => {
|
|
361
|
+
const root = TANSTACK_START_ROOT_CANDIDATES.find((file) => io.exists(file));
|
|
362
|
+
return root === undefined
|
|
363
|
+
? {}
|
|
364
|
+
: {
|
|
365
|
+
tanstackStartRoot: root,
|
|
366
|
+
tanstackStartRootSource: io.readFile(root),
|
|
367
|
+
tanstackStartConnectExists: io.exists(tanstackStartConnectPath(root)),
|
|
368
|
+
tanstackStartConnectSource: io.readFile(tanstackStartConnectPath(root)),
|
|
369
|
+
};
|
|
370
|
+
})(),
|
|
371
|
+
...(Framework.ANGULAR === detection.framework ? angularInputs(io) : {}),
|
|
372
|
+
...(Framework.HTML === detection.framework
|
|
373
|
+
? {
|
|
374
|
+
htmlIndexSource: io.readFile(HTML_INDEX_PATH),
|
|
375
|
+
htmlLocalSources: {
|
|
376
|
+
[STATIC_TOKEN_MODULE]: io.readFile(STATIC_TOKEN_MODULE),
|
|
377
|
+
[STATIC_GITIGNORE_PATH]: io.readFile(STATIC_GITIGNORE_PATH),
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
: {}),
|
|
381
|
+
craEntry: craEntryOf(io),
|
|
382
|
+
craEnv: io.readFile(CRA_ENV_PATH),
|
|
383
|
+
pairingToken: options.dryRun ? DRY_RUN_PAIRING_TOKEN : io.host.pairingToken(),
|
|
384
|
+
installSource: io.host.installSource(),
|
|
385
|
+
reticleConfigExists: io.exists(RETICLE_CONFIG_FILE),
|
|
386
|
+
// The CONTENT, so a config that exists can be checked rather than trusted — a `"port"` set to
|
|
387
|
+
// the app's own dev-server port used to survive every re-run of `init`.
|
|
388
|
+
reticleConfigSource: io.readFile(RETICLE_CONFIG_FILE),
|
|
389
|
+
// Read the agent instruction files so the rule merge stays idempotent across re-runs — from the
|
|
390
|
+
// agent's own root, or the merge would idempotently check a file it is not going to write.
|
|
391
|
+
claudeMdContent: io.readFile(agentFile('CLAUDE.md')),
|
|
392
|
+
agentsMdContent: io.readFile(agentFile('AGENTS.md')),
|
|
393
|
+
reticleMdContent: io.readFile(agentFile(RETICLE_MD_PATH)),
|
|
394
|
+
cursorRuleContent: io.readFile(agentFile(CURSOR_RULE_PATH)),
|
|
395
|
+
claudeCommandContent: io.readFile(agentFile(CLAUDE_COMMAND_PATH)),
|
|
396
|
+
cursorCommandContent: io.readFile(agentFile(CURSOR_COMMAND_PATH)),
|
|
397
|
+
// Read only to MERGE into: the settings file is the user's, and other tools write in it too.
|
|
398
|
+
claudeSettingsContent: io.readFile(agentFile(CLAUDE_SETTINGS_PATH)),
|
|
399
|
+
...(agentRoot === undefined
|
|
400
|
+
? {}
|
|
401
|
+
: {
|
|
402
|
+
agentFileRoot: agentRoot,
|
|
403
|
+
// The config the AGENT's cwd already has, if any — see agentRootConfigStep.
|
|
404
|
+
agentRootConfigSource: io.readFile(agentFile(RETICLE_CONFIG_FILE)),
|
|
405
|
+
agentRootSeveralApps: true === options.severalApps,
|
|
406
|
+
}),
|
|
407
|
+
options: {
|
|
408
|
+
port: options.port,
|
|
409
|
+
mcp: options.mcp,
|
|
410
|
+
install: options.install,
|
|
411
|
+
projectId,
|
|
412
|
+
// The SDK must match the CLI asking for it — see pinnedPackages.
|
|
413
|
+
sdkVersion: RETICLE_VERSION,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from '
|
|
|
29
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
30
30
|
export { projectIdOf, rememberProjectOnDisk, type RegistryIo } from './project/remember-project.js';
|
|
31
31
|
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
32
|
-
export { findWorkspaceApps, isWebApp, workspacePackageDirs } from './detect/workspace-apps.js';
|
|
32
|
+
export { enclosingWorkspaceRoot, findWorkspaceApps, isWebApp, workspacePackageDirs, } from './detect/workspace-apps.js';
|
|
33
33
|
export { desktopLaunch, type DesktopLaunch } from './detect/dev-script.js';
|
|
34
34
|
export { detectNonJsEcosystem, noPackageJsonMessage } from './detect/non-js-project.js';
|
|
35
35
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
@@ -47,5 +47,6 @@ export { reticleConfigContent } from './patch/snippets.js';
|
|
|
47
47
|
* here", and the two would disagree the first time a client was added to one of them.
|
|
48
48
|
*/
|
|
49
49
|
export { McpClient, ConfigScope, MCP_CLIENTS, ClientMergeStatus, clientSpec, mergeClientConfig, } from './register/mcp-clients.js';
|
|
50
|
-
export { claudeAddCommand, claudeAvailableProbe, claudeHasReticle, } from './register/mcp.js';
|
|
50
|
+
export { claudeAddCommand, claudeAvailableProbe, claudeHasReticle, codexAddCommand, codexAvailableProbe, } from './register/mcp.js';
|
|
51
|
+
export { codexNamesOurServer } from './register/codex-toml.js';
|
|
51
52
|
export { detectMcpClients, type DetectedClient } from './register/detect-clients.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from '
|
|
|
29
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
30
30
|
export { projectIdOf, rememberProjectOnDisk } from './project/remember-project.js';
|
|
31
31
|
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
32
|
-
export { findWorkspaceApps, isWebApp, workspacePackageDirs } from './detect/workspace-apps.js';
|
|
32
|
+
export { enclosingWorkspaceRoot, findWorkspaceApps, isWebApp, workspacePackageDirs, } from './detect/workspace-apps.js';
|
|
33
33
|
export { desktopLaunch } from './detect/dev-script.js';
|
|
34
34
|
export { detectNonJsEcosystem, noPackageJsonMessage } from './detect/non-js-project.js';
|
|
35
35
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
@@ -51,5 +51,9 @@ export { claudeAddCommand,
|
|
|
51
51
|
// The two probes `run.ts` uses to decide about Claude Code. Exported because `reticle setup mcp`
|
|
52
52
|
// asks the same question and had no way to ask it: Claude Code keeps no config file, so the
|
|
53
53
|
// file-reading detector cannot see it, and the installer silently skipped the commonest client.
|
|
54
|
-
claudeAvailableProbe, claudeHasReticle,
|
|
54
|
+
claudeAvailableProbe, claudeHasReticle,
|
|
55
|
+
// Codex keeps its registration behind `codex mcp add`, like Claude. `reticle setup mcp` needs the
|
|
56
|
+
// command, the probe and the "is the entry already there, in any shape" check to do the same.
|
|
57
|
+
codexAddCommand, codexAvailableProbe, } from './register/mcp.js';
|
|
58
|
+
export { codexNamesOurServer } from './register/codex-toml.js';
|
|
55
59
|
export { detectMcpClients } from './register/detect-clients.js';
|
package/dist/node-io.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { InitHost } from './host.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Every program `init` is ever allowed to run.
|
|
10
10
|
*
|
|
11
|
-
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude
|
|
11
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude and Codex CLIs —
|
|
12
12
|
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
13
13
|
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
14
14
|
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
package/dist/node-io.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* command, not the MCP stdio transport.
|
|
5
5
|
*/
|
|
6
6
|
import { terminalWidth, wrapForTerminal } from './diagnose/terminal-wrap.js';
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, } from 'node:fs';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, lstatSync, } from 'node:fs';
|
|
8
8
|
import { NodePlatform } from './detect/platform.js';
|
|
9
9
|
import { join, dirname, isAbsolute } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
@@ -24,7 +24,7 @@ function shellSafe(args) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Every program `init` is ever allowed to run.
|
|
26
26
|
*
|
|
27
|
-
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude
|
|
27
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude and Codex CLIs —
|
|
28
28
|
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
29
29
|
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
30
30
|
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
|
@@ -46,6 +46,7 @@ export const RUNNABLE_COMMANDS = [
|
|
|
46
46
|
'npx',
|
|
47
47
|
'corepack',
|
|
48
48
|
'claude',
|
|
49
|
+
'codex',
|
|
49
50
|
];
|
|
50
51
|
/** The command, proven to be one of `RUNNABLE_COMMANDS` — never the caller's string. */
|
|
51
52
|
function runnable(command) {
|
|
@@ -158,6 +159,14 @@ export function buildNodeIo(cwd, host, output = {}) {
|
|
|
158
159
|
return [];
|
|
159
160
|
}
|
|
160
161
|
},
|
|
162
|
+
isSymlink(rel) {
|
|
163
|
+
try {
|
|
164
|
+
return lstatSync(abs(rel)).isSymbolicLink();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
161
170
|
scoped(rel) {
|
|
162
171
|
// The host travels with the re-rooted IO: a monorepo redirect re-enters `runInit` through
|
|
163
172
|
// this, and an untraced, unreported inner run is the half of init that actually did the work.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* half-edit. These must not be left manual: both edits fail silently when skipped, and one of them
|
|
6
6
|
* is JSX.
|
|
7
7
|
*/
|
|
8
|
+
import type { InitIo } from '../run-types.js';
|
|
8
9
|
import { type SourcePatch } from './patch-kind.js';
|
|
9
10
|
/** Where the generated connect component goes, and how the mount file should import it. */
|
|
10
11
|
interface ReticleDevLocation {
|
|
@@ -41,9 +42,36 @@ export declare function patchRootLayout(source: string): SourcePatch;
|
|
|
41
42
|
* ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
|
|
42
43
|
*/
|
|
43
44
|
export declare const NEXT_LAYOUT_CANDIDATES: string[];
|
|
45
|
+
/** What `findRouteGroupRootLayout` found: the layout to mount into, or the ones it could not choose between. */
|
|
46
|
+
export type RouteGroupLayouts = {
|
|
47
|
+
path: string;
|
|
48
|
+
} | {
|
|
49
|
+
candidates: string[];
|
|
50
|
+
};
|
|
51
|
+
type ListingIo = Pick<InitIo, 'listDirs' | 'listFiles' | 'readFile'>;
|
|
52
|
+
/**
|
|
53
|
+
* The root layout of an App Router app with no top-level `app/layout.*`: its root layouts then live
|
|
54
|
+
* in route groups (`app/(site)/layout.tsx`), one per group, which Next documents as "multiple root
|
|
55
|
+
* layouts" (#1538). `null` when the app has none, so the Pages Router candidates are still asked.
|
|
56
|
+
*
|
|
57
|
+
* One layout is chosen, because a step patches one file: a group layout that already mounts
|
|
58
|
+
* `<ReticleDev />` (so the patch reports it already wired), else the one group that renders `/`,
|
|
59
|
+
* else the only group layout there is. Several and none of those is returned as candidates, so the
|
|
60
|
+
* manual step can name them rather than say there is no root layout.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findRouteGroupRootLayout(io: ListingIo): RouteGroupLayouts | null;
|
|
44
63
|
/**
|
|
45
64
|
* Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
|
|
46
65
|
* `app/` directory at all, so writing the component there produced a file nothing imported.
|
|
47
66
|
*/
|
|
48
67
|
export declare const NEXT_PAGES_APP_CANDIDATES: string[];
|
|
68
|
+
/**
|
|
69
|
+
* The file `<ReticleDev />` is mounted in: the App Router's root layout, then a route-group root
|
|
70
|
+
* layout (#1538), then the Pages Router's `_app`. `candidates` is set when route groups hold several
|
|
71
|
+
* root layouts and none could be chosen, so the manual step can name them.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findNextMountFile(io: ListingIo & Pick<InitIo, 'exists'>): {
|
|
74
|
+
path: string | null;
|
|
75
|
+
candidates?: string[];
|
|
76
|
+
};
|
|
49
77
|
export {};
|
package/dist/patch/next-patch.js
CHANGED
|
@@ -223,6 +223,57 @@ export const NEXT_LAYOUT_CANDIDATES = [
|
|
|
223
223
|
'src/app/layout.jsx',
|
|
224
224
|
'src/app/layout.js',
|
|
225
225
|
];
|
|
226
|
+
/** The App Router roots, in the order `NEXT_LAYOUT_CANDIDATES` checks them. */
|
|
227
|
+
const NEXT_APP_DIRS = ['app', 'src/app'];
|
|
228
|
+
const LAYOUT_BASENAMES = ['layout.tsx', 'layout.jsx', 'layout.js'];
|
|
229
|
+
const PAGE_FILE = /^page\.(tsx|ts|jsx|js|mdx)$/;
|
|
230
|
+
/** `(marketing)`: a route group, which organises routes without adding a URL segment. */
|
|
231
|
+
const ROUTE_GROUP = /^\(.+\)$/;
|
|
232
|
+
/** Whether `dir` renders `/`: a page of its own, or one in a route group nested under it. */
|
|
233
|
+
function servesRoot(io, dir) {
|
|
234
|
+
if (io.listFiles(dir).some((name) => PAGE_FILE.test(name)))
|
|
235
|
+
return true;
|
|
236
|
+
return io
|
|
237
|
+
.listDirs(dir)
|
|
238
|
+
.filter((name) => ROUTE_GROUP.test(name))
|
|
239
|
+
.some((name) => servesRoot(io, `${dir}/${name}`));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The root layout of an App Router app with no top-level `app/layout.*`: its root layouts then live
|
|
243
|
+
* in route groups (`app/(site)/layout.tsx`), one per group, which Next documents as "multiple root
|
|
244
|
+
* layouts" (#1538). `null` when the app has none, so the Pages Router candidates are still asked.
|
|
245
|
+
*
|
|
246
|
+
* One layout is chosen, because a step patches one file: a group layout that already mounts
|
|
247
|
+
* `<ReticleDev />` (so the patch reports it already wired), else the one group that renders `/`,
|
|
248
|
+
* else the only group layout there is. Several and none of those is returned as candidates, so the
|
|
249
|
+
* manual step can name them rather than say there is no root layout.
|
|
250
|
+
*/
|
|
251
|
+
export function findRouteGroupRootLayout(io) {
|
|
252
|
+
for (const appDir of NEXT_APP_DIRS) {
|
|
253
|
+
const layouts = io
|
|
254
|
+
.listDirs(appDir)
|
|
255
|
+
.filter((name) => ROUTE_GROUP.test(name))
|
|
256
|
+
.sort()
|
|
257
|
+
.flatMap((group) => {
|
|
258
|
+
const dir = `${appDir}/${group}`;
|
|
259
|
+
const files = io.listFiles(dir);
|
|
260
|
+
const layout = LAYOUT_BASENAMES.find((name) => files.includes(name));
|
|
261
|
+
return layout === undefined ? [] : [{ dir, path: `${dir}/${layout}` }];
|
|
262
|
+
});
|
|
263
|
+
if (0 === layouts.length)
|
|
264
|
+
continue;
|
|
265
|
+
const wired = layouts.find((l) => true === io.readFile(l.path)?.includes(RETICLE_DEV_COMPONENT));
|
|
266
|
+
if (wired !== undefined)
|
|
267
|
+
return { path: wired.path };
|
|
268
|
+
const root = layouts.filter((l) => servesRoot(io, l.dir));
|
|
269
|
+
if (1 === root.length && root[0] !== undefined)
|
|
270
|
+
return { path: root[0].path };
|
|
271
|
+
if (1 === layouts.length && layouts[0] !== undefined)
|
|
272
|
+
return { path: layouts[0].path };
|
|
273
|
+
return { candidates: layouts.map((l) => l.path) };
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
226
277
|
/**
|
|
227
278
|
* Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
|
|
228
279
|
* `app/` directory at all, so writing the component there produced a file nothing imported.
|
|
@@ -235,3 +286,18 @@ export const NEXT_PAGES_APP_CANDIDATES = [
|
|
|
235
286
|
'src/pages/_app.jsx',
|
|
236
287
|
'src/pages/_app.js',
|
|
237
288
|
];
|
|
289
|
+
/**
|
|
290
|
+
* The file `<ReticleDev />` is mounted in: the App Router's root layout, then a route-group root
|
|
291
|
+
* layout (#1538), then the Pages Router's `_app`. `candidates` is set when route groups hold several
|
|
292
|
+
* root layouts and none could be chosen, so the manual step can name them.
|
|
293
|
+
*/
|
|
294
|
+
export function findNextMountFile(io) {
|
|
295
|
+
const top = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p));
|
|
296
|
+
if (top !== undefined)
|
|
297
|
+
return { path: top };
|
|
298
|
+
const groups = findRouteGroupRootLayout(io);
|
|
299
|
+
if (groups !== null && 'path' in groups)
|
|
300
|
+
return { path: groups.path };
|
|
301
|
+
const pagesApp = NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ?? null;
|
|
302
|
+
return null === groups ? { path: pagesApp } : { path: pagesApp, candidates: groups.candidates };
|
|
303
|
+
}
|
|
@@ -130,6 +130,6 @@ and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which i
|
|
|
130
130
|
|
|
131
131
|
The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
|
|
132
132
|
source file:line all work in Vue. What you do not get is React component identity, which is the only
|
|
133
|
-
thing the React adapter adds. The install gate scaffolds a Nuxt app
|
|
133
|
+
thing the React adapter adds. The install gate scaffolds a Nuxt app whenever the install path changes and requires a session, so this wiring
|
|
134
134
|
is proven; if something does not work, please open an issue.`;
|
|
135
135
|
}
|
package/dist/patch/snippets.d.ts
CHANGED
|
@@ -182,7 +182,7 @@ export declare const SVELTEKIT_HOOKS_PATH = "src/hooks.client.ts";
|
|
|
182
182
|
* reporting all-green.
|
|
183
183
|
*/
|
|
184
184
|
export declare function unverifiedUiLibraryNote(library: string, installGated?: boolean): string;
|
|
185
|
-
export declare const SVELTEKIT_SETUP_GATED_NOTE = "The install gate scaffolds a SvelteKit app
|
|
185
|
+
export declare const SVELTEKIT_SETUP_GATED_NOTE = "The install gate scaffolds a SvelteKit app whenever the install path changes and requires this hook to connect a session, so the wiring is proven; driving flows on SvelteKit to a verdict is not. If the hook does not register a session, please open an issue.";
|
|
186
186
|
export declare const UNVERIFIED_TANSTACK_START_NOTE = "Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested \u2014 it may work, but nothing proves it and nothing will tell us if it breaks. Gated today: Vite (React, Vue), Next.js, Nuxt, React Router, Remix, Astro, SvelteKit, Create React App and Angular. If the client effect does not register a session, please open an issue.";
|
|
187
187
|
/**
|
|
188
188
|
* Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
|