@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
package/dist/patch/snippets.js
CHANGED
|
@@ -675,9 +675,9 @@ export function unverifiedUiLibraryNote(library, installGated = false) {
|
|
|
675
675
|
const identity = 'preact' === library
|
|
676
676
|
? 'React component identity — component names and stacks — comes from `@reticlehq/react`, which reaches Preact through `preact/compat`. That path is not covered by a CI gate here, so treat it as expected-to-work rather than proven.'
|
|
677
677
|
: 'What `@reticlehq/react` adds and you will NOT get is React component identity: component names and component stacks.';
|
|
678
|
-
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${installGated ? `The install gate scaffolds this ${library} stack from scratch
|
|
678
|
+
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${installGated ? `The install gate scaffolds this ${library} stack from scratch whenever the install path changes, so this SETUP is proven; no gate drives it to a verdict, so the drive is not.` : `No CI gate covers ${library}.`} Driven to a verdict in CI: Vite + React and Next.js; Astro is checked for a connected session. If something doesn't work, please open an issue.`;
|
|
679
679
|
}
|
|
680
|
-
export const SVELTEKIT_SETUP_GATED_NOTE = 'The install gate scaffolds a SvelteKit app
|
|
680
|
+
export 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.';
|
|
681
681
|
export const UNVERIFIED_TANSTACK_START_NOTE = 'Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested — 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.';
|
|
682
682
|
/**
|
|
683
683
|
* Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
export declare const HTML_INDEX_PATH = "index.html";
|
|
23
23
|
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
24
24
|
export declare const STATIC_TOKEN_MODULE = "reticle.local.js";
|
|
25
|
+
export declare const STATIC_GITIGNORE_PATH = ".gitignore";
|
|
25
26
|
/** Opens the block `init` writes; a re-run looks for it. */
|
|
26
27
|
export declare const STATIC_SNIPPET_MARKER = "<!-- reticle:connect (dev only, written by reticle init) -->";
|
|
27
28
|
/** Whether this page already carries a Reticle connect. */
|
|
@@ -23,7 +23,7 @@ import { CDN_SDK_URL } from './snippets.js';
|
|
|
23
23
|
export const HTML_INDEX_PATH = 'index.html';
|
|
24
24
|
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
25
25
|
export const STATIC_TOKEN_MODULE = 'reticle.local.js';
|
|
26
|
-
const
|
|
26
|
+
export const STATIC_GITIGNORE_PATH = '.gitignore';
|
|
27
27
|
/** Opens the block `init` writes; a re-run looks for it. */
|
|
28
28
|
export const STATIC_SNIPPET_MARKER = '<!-- reticle:connect (dev only, written by reticle init) -->';
|
|
29
29
|
const STATIC_SNIPPET_END = '<!-- /reticle:connect -->';
|
|
@@ -71,12 +71,12 @@ export function staticTokenFiles(pairingToken, readFile) {
|
|
|
71
71
|
const module = staticTokenModule(pairingToken);
|
|
72
72
|
if (readFile(STATIC_TOKEN_MODULE) !== module)
|
|
73
73
|
files[STATIC_TOKEN_MODULE] = module;
|
|
74
|
-
const ignore = readFile(
|
|
74
|
+
const ignore = readFile(STATIC_GITIGNORE_PATH) ?? '';
|
|
75
75
|
const covered = ignore
|
|
76
76
|
.split('\n')
|
|
77
77
|
.some((l) => [STATIC_TOKEN_MODULE, `/${STATIC_TOKEN_MODULE}`].includes(l.trim()));
|
|
78
78
|
if (!covered) {
|
|
79
|
-
files[
|
|
79
|
+
files[STATIC_GITIGNORE_PATH] =
|
|
80
80
|
`${'' === ignore ? '' : ignore.replace(/\n*$/, '\n')}${STATIC_TOKEN_MODULE}\n`;
|
|
81
81
|
}
|
|
82
82
|
return files;
|
|
@@ -21,6 +21,7 @@ export declare const StepTitle: {
|
|
|
21
21
|
readonly CONNECT_MODULE: "Reticle connect module";
|
|
22
22
|
readonly PAIRING_TOKEN: "Pairing token";
|
|
23
23
|
readonly PAIRING_TOKEN_PER_MACHINE: "Pairing token is per-machine";
|
|
24
|
+
readonly STATIC_PAIRING_TOKEN: "Pairing token (static page)";
|
|
24
25
|
readonly CONNECT_SNIPPET_CRA: "Connect snippet (CRA)";
|
|
25
26
|
readonly CONNECT_SNIPPET_NUXT: "Connect snippet (Nuxt)";
|
|
26
27
|
readonly NUXT_CONFIG: "Nuxt config (token + watcher)";
|
|
@@ -22,6 +22,7 @@ export const StepTitle = {
|
|
|
22
22
|
CONNECT_MODULE: 'Reticle connect module',
|
|
23
23
|
PAIRING_TOKEN: 'Pairing token',
|
|
24
24
|
PAIRING_TOKEN_PER_MACHINE: 'Pairing token is per-machine',
|
|
25
|
+
STATIC_PAIRING_TOKEN: 'Pairing token (static page)',
|
|
25
26
|
CONNECT_SNIPPET_CRA: 'Connect snippet (CRA)',
|
|
26
27
|
CONNECT_SNIPPET_NUXT: 'Connect snippet (Nuxt)',
|
|
27
28
|
NUXT_CONFIG: 'Nuxt config (token + watcher)',
|
|
@@ -98,6 +99,10 @@ export const CONNECT_STEP_TITLES = new Set([
|
|
|
98
99
|
// not, and `gate:install` scaffolds no CRA app, so that change would ship with no coverage of the
|
|
99
100
|
// path it changes. It is worth doing, and worth doing with a scaffold behind it.
|
|
100
101
|
StepTitle.VITE_PLUGIN,
|
|
102
|
+
// A plain page imports its token from a gitignored sibling module. Unlike CRA's, this step never
|
|
103
|
+
// goes MANUAL by plan: it is ⚠ only when the write itself failed — no token module means the page
|
|
104
|
+
// is refused, and no ignore entry means the token is on disk without the protection we promised.
|
|
105
|
+
StepTitle.STATIC_PAIRING_TOKEN,
|
|
101
106
|
// electron-vite's connect IS the renderer plugin. Without it the SDK never injects, and the
|
|
102
107
|
// preload/capture steps are not enough to produce a session.
|
|
103
108
|
StepTitle.ELECTRON_VITE_PLUGIN,
|
|
@@ -223,10 +223,10 @@ export const FRAMEWORK_ADAPTERS = {
|
|
|
223
223
|
carriesOwnUnverifiedNote: false,
|
|
224
224
|
},
|
|
225
225
|
[Framework.HTML]: {
|
|
226
|
-
// No bundler plugin to install —
|
|
226
|
+
// No bundler plugin to install — write the static connect into index.html when it is present.
|
|
227
227
|
packages: (kit) => [kit],
|
|
228
228
|
steps: htmlSteps,
|
|
229
|
-
connectStepTitles: [StepTitle.CONNECT_SNIPPET],
|
|
229
|
+
connectStepTitles: [StepTitle.CONNECT_SNIPPET, StepTitle.STATIC_PAIRING_TOKEN],
|
|
230
230
|
carriesOwnUnverifiedNote: false,
|
|
231
231
|
},
|
|
232
232
|
};
|
|
@@ -9,7 +9,7 @@ import { StepTitle } from './connect-steps.js';
|
|
|
9
9
|
import { patchStep } from './plan-framework.js';
|
|
10
10
|
import { alreadyOrMovedPort, retargetPort } from './port-steps.js';
|
|
11
11
|
import { StepStatus } from './plan-types.js';
|
|
12
|
-
const ANGULAR_SETUP_GATED_NOTE = 'The install gate scaffolds an Angular app (Angular 21, no SSR)
|
|
12
|
+
const ANGULAR_SETUP_GATED_NOTE = 'The install gate scaffolds an Angular app (Angular 21, no SSR) whenever the install path changes and requires a ' +
|
|
13
13
|
'session, and this wiring was proven by hand on Angular 22 with and without SSR; nothing yet ' +
|
|
14
14
|
'drives an Angular app to a verdict on each change. DOM, network, console and state ' +
|
|
15
15
|
'tools work here; component identity does not (that is the React adapter). If no session ' +
|
|
@@ -66,7 +66,7 @@ export declare function svelteKitSteps(input: PlanInput): Step[];
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function astroSteps(input: PlanInput): Step[];
|
|
68
68
|
/**
|
|
69
|
-
* The plain-HTML path:
|
|
69
|
+
* The plain-HTML path: write the static connect when the page exists, otherwise print the recipe.
|
|
70
70
|
*
|
|
71
71
|
* A function of its own, and NOT a fallthrough. Every framework is sent here on purpose or not at
|
|
72
72
|
* all — a member of `Framework` with no adapter entry is a compile error in `FRAMEWORK_ADAPTERS`,
|
|
@@ -9,14 +9,25 @@ import { patchNextConfig, patchRootLayout, patchPagesApp } from '../patch/next-p
|
|
|
9
9
|
import { ASTRO_ENV_DTS_PATH, ASTRO_RETICLE_DEV_PATH, astroReticleDevFile, patchAstroConfig, patchAstroEnvDts, patchAstroLayout, } from '../patch/astro-patch.js';
|
|
10
10
|
import { CRA_DEV_MODULE_IMPORT, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craDevModulePath, craEnvPatch, craImportPatch, } from '../patch/cra.js';
|
|
11
11
|
import { PatchKind } from '../patch/patch-kind.js';
|
|
12
|
-
import { NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, SVELTEKIT_SETUP_GATED_NOTE, astroManual, nuxtManual, nuxtPluginFile, nuxtPluginPath, NUXT_PLUGIN_NOTICE, webpack4TranspileNote, WEBPACK4_REACT_SCRIPTS_MAJOR, reactRouterEntryFile, reactRouterEntryPatch, REACT_ROUTER_ENTRY_PATH, UNVERIFIED_TANSTACK_START_NOTE, htmlManual, } from '../patch/snippets.js';
|
|
12
|
+
import { NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, SVELTEKIT_SETUP_GATED_NOTE, astroManual, nuxtManual, nuxtPluginFile, nuxtPluginPath, NUXT_PLUGIN_NOTICE, webpack4TranspileNote, WEBPACK4_REACT_SCRIPTS_MAJOR, reactRouterEntryFile, reactRouterEntryPatch, REACT_ROUTER_ENTRY_PATH, UNVERIFIED_TANSTACK_START_NOTE, htmlManual, connectArg, } from '../patch/snippets.js';
|
|
13
13
|
import { StepStatus } from './plan-types.js';
|
|
14
14
|
import { StepTitle } from './connect-steps.js';
|
|
15
15
|
import { patchTanstackStartRoot, tanstackStartConnectFile, tanstackStartConnectPath, tanstackStartManual, TANSTACK_START_ROOT_PATH, } from '../patch/tanstack-start.js';
|
|
16
16
|
import { patchNuxtConfig } from '../patch/nuxt-patch.js';
|
|
17
17
|
import { alreadyOrMovedPort } from './port-steps.js';
|
|
18
18
|
import { capabilitiesTodo, needsManualStore } from './plan-vite.js';
|
|
19
|
-
import { HTML_INDEX_PATH,
|
|
19
|
+
import { HTML_INDEX_PATH, STATIC_GITIGNORE_PATH, STATIC_SNIPPET_MARKER, STATIC_TOKEN_MODULE, staticTokenFiles, withStaticSnippet, } from '../patch/static-page.js';
|
|
20
|
+
/*
|
|
21
|
+
* Threat model for the static page's token module. `reticle.local.js` sits beside `index.html`, so
|
|
22
|
+
* whatever serves the page serves it too, to any client that can reach that server. That is the same
|
|
23
|
+
* exposure every other dev path already has: the Vite plugin bakes the token into the connect module
|
|
24
|
+
* it serves, and the no-package.json path writes this very file. The dev server's bind address is
|
|
25
|
+
* the boundary; the module is gitignored so it never reaches a commit or a published build.
|
|
26
|
+
*/
|
|
27
|
+
const STATIC_TOKEN_DETAIL = `write this machine’s pairing token to ${STATIC_TOKEN_MODULE} beside the page`;
|
|
28
|
+
const STATIC_IGNORE_DETAIL = `add ${STATIC_TOKEN_MODULE} to ${STATIC_GITIGNORE_PATH} — it holds this machine’s pairing token`;
|
|
29
|
+
const STATIC_CONNECT_DETAIL = 'write the dev-only connect snippet (loopback hosts only)';
|
|
30
|
+
const STATIC_CONNECT_ALREADY_DETAIL = `${HTML_INDEX_PATH} already loads the Reticle SDK`;
|
|
20
31
|
/**
|
|
21
32
|
* Turn a conservative source patch into a step: applied when it patched, already when the wiring is
|
|
22
33
|
* there, and the hand-edit instructions when the file shape wasn't one we recognise.
|
|
@@ -106,8 +117,14 @@ export function nextSteps(input) {
|
|
|
106
117
|
// Pages Router mounts through pages/_app, App Router through the root layout — different edits,
|
|
107
118
|
// and picking by path is what stops a Pages app being handed the layout patch that cannot apply.
|
|
108
119
|
const isPagesRouter = layout !== null && /(^|\/)pages\/_app\.[jt]sx?$/.test(layout.path);
|
|
120
|
+
const candidates = input.nextLayoutCandidates ?? [];
|
|
109
121
|
const layoutPatch = null === layout
|
|
110
|
-
? {
|
|
122
|
+
? {
|
|
123
|
+
kind: PatchKind.MANUAL,
|
|
124
|
+
reason: 0 === candidates.length
|
|
125
|
+
? 'no root layout (app/layout.tsx) or pages/_app found'
|
|
126
|
+
: `no app/layout.tsx; route-group root layouts ${candidates.join(', ')}, and none of them alone renders /`,
|
|
127
|
+
}
|
|
111
128
|
: isPagesRouter
|
|
112
129
|
? patchPagesApp(layout.source, input.nextReticleDevImport)
|
|
113
130
|
: patchRootLayout(layout.source);
|
|
@@ -513,24 +530,41 @@ export function astroSteps(input) {
|
|
|
513
530
|
];
|
|
514
531
|
}
|
|
515
532
|
/**
|
|
516
|
-
* The plain-HTML path:
|
|
533
|
+
* The plain-HTML path: write the static connect when the page exists, otherwise print the recipe.
|
|
517
534
|
*
|
|
518
535
|
* A function of its own, and NOT a fallthrough. Every framework is sent here on purpose or not at
|
|
519
536
|
* all — a member of `Framework` with no adapter entry is a compile error in `FRAMEWORK_ADAPTERS`,
|
|
520
537
|
* where the if/else chain this replaced used to hand it these instructions silently.
|
|
521
538
|
*/
|
|
522
539
|
export function htmlSteps(input) {
|
|
523
|
-
// Read the page before calling the step undone. It used to be MANUAL unconditionally, so a re-run
|
|
524
|
-
// over a page that already connects said "This app will NOT connect until the ⚠ step".
|
|
525
540
|
const index = input.htmlIndexSource ?? null;
|
|
526
|
-
if (null !== index
|
|
541
|
+
if (null !== index) {
|
|
542
|
+
// The page is publishable; the pairing token belongs in its gitignored sibling module instead.
|
|
543
|
+
// Refresh that module even when the connect block itself is already in place.
|
|
544
|
+
const steps = Object.entries(staticTokenFiles(input.pairingToken, (path) => input.htmlLocalSources?.[path] ?? null)).map(([path, content]) => ({
|
|
545
|
+
title: StepTitle.STATIC_PAIRING_TOKEN,
|
|
546
|
+
target: path,
|
|
547
|
+
status: StepStatus.APPLY,
|
|
548
|
+
detail: STATIC_GITIGNORE_PATH === path ? STATIC_IGNORE_DETAIL : STATIC_TOKEN_DETAIL,
|
|
549
|
+
write: { path, content },
|
|
550
|
+
}));
|
|
551
|
+
const written = withStaticSnippet(index, connectArg(input.options.port, input.options.projectId));
|
|
527
552
|
return [
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
553
|
+
...steps,
|
|
554
|
+
null !== written
|
|
555
|
+
? {
|
|
556
|
+
title: StepTitle.CONNECT_SNIPPET,
|
|
557
|
+
target: HTML_INDEX_PATH,
|
|
558
|
+
status: StepStatus.APPLY,
|
|
559
|
+
detail: STATIC_CONNECT_DETAIL,
|
|
560
|
+
write: { path: HTML_INDEX_PATH, content: written, expect: [STATIC_SNIPPET_MARKER] },
|
|
561
|
+
}
|
|
562
|
+
: alreadyOrMovedPort({
|
|
563
|
+
title: StepTitle.CONNECT_SNIPPET,
|
|
564
|
+
target: HTML_INDEX_PATH,
|
|
565
|
+
status: StepStatus.ALREADY,
|
|
566
|
+
detail: STATIC_CONNECT_ALREADY_DETAIL,
|
|
567
|
+
}, index, input.options.port),
|
|
534
568
|
];
|
|
535
569
|
}
|
|
536
570
|
return [
|
|
@@ -96,6 +96,11 @@ export interface Plan {
|
|
|
96
96
|
}
|
|
97
97
|
export interface PlanInput {
|
|
98
98
|
detection: Detection;
|
|
99
|
+
/**
|
|
100
|
+
* `node_modules` is a link to another tree (a workspace's, a shared cache). A package manager run
|
|
101
|
+
* here replaces the link with a fresh tree, so the install is skipped and named instead.
|
|
102
|
+
*/
|
|
103
|
+
nodeModulesLinked?: boolean | undefined;
|
|
99
104
|
/**
|
|
100
105
|
* Write `captureNetworkBodies: true` into the app's config. Off unless the caller asked (#705).
|
|
101
106
|
*
|
|
@@ -145,11 +150,18 @@ export interface PlanInput {
|
|
|
145
150
|
configPath: string;
|
|
146
151
|
existing: string | null;
|
|
147
152
|
}[] | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the `codex` CLI is on PATH. Only probed when Codex was detected, and absent otherwise.
|
|
155
|
+
* With it, Codex registers through `codex mcp add` instead of a block to paste.
|
|
156
|
+
*/
|
|
157
|
+
codexCli?: boolean | undefined;
|
|
148
158
|
/** Discovered Vite config: its path + source, or null if none found. */
|
|
149
159
|
viteConfig: {
|
|
150
160
|
path: string;
|
|
151
161
|
source: string;
|
|
152
162
|
} | null;
|
|
163
|
+
/** Config paths found but not safely attributable to the selected dev command. */
|
|
164
|
+
viteConfigCandidates?: readonly string[] | undefined;
|
|
153
165
|
/** Discovered electron-vite config: its path + source, or null if none found. */
|
|
154
166
|
electronViteConfig?: {
|
|
155
167
|
path: string;
|
|
@@ -200,6 +212,11 @@ export interface PlanInput {
|
|
|
200
212
|
path: string;
|
|
201
213
|
source: string;
|
|
202
214
|
} | null | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Route-group root layouts `init` could not choose between (`app/(a)/layout.tsx`, ...), set only
|
|
217
|
+
* when there is no top-level layout and no single group renders `/` (#1538).
|
|
218
|
+
*/
|
|
219
|
+
nextLayoutCandidates?: readonly string[] | undefined;
|
|
203
220
|
/** Where the dev-only connect component goes — never inside `pages/`, which routes on presence. */
|
|
204
221
|
nextReticleDevPath?: string | undefined;
|
|
205
222
|
/** What the mount file should import — a sibling for App Router, `../components/…` for Pages. */
|
|
@@ -277,10 +294,12 @@ export interface PlanInput {
|
|
|
277
294
|
/** The generated `reticle.proxy.mjs`, when a previous run left one (idempotency). */
|
|
278
295
|
angularProxySource?: string | null | undefined;
|
|
279
296
|
/**
|
|
280
|
-
* The plain-HTML app's `index.html`, when it has one —
|
|
281
|
-
*
|
|
297
|
+
* The plain-HTML app's `index.html`, when it has one — patched on the first run, recognised on
|
|
298
|
+
* later runs, and left manual only when there is no page to write into.
|
|
282
299
|
*/
|
|
283
300
|
htmlIndexSource?: string | null | undefined;
|
|
301
|
+
/** Current token module and .gitignore beside the HTML page, for idempotent token-file writes. */
|
|
302
|
+
htmlLocalSources?: Record<string, string | null> | undefined;
|
|
284
303
|
/** CRA's bundled entry (src/index.tsx or .js) — where the connect import has to go. */
|
|
285
304
|
craEntry?: {
|
|
286
305
|
path: string;
|
|
@@ -342,6 +361,8 @@ export interface PlanInput {
|
|
|
342
361
|
* that disagrees with the app's is worse than no root config, and only reading it can tell.
|
|
343
362
|
*/
|
|
344
363
|
agentRootConfigSource?: string | null | undefined;
|
|
364
|
+
/** The agent root holds several apps, so its config would name one and mislead about the rest. */
|
|
365
|
+
agentRootSeveralApps?: boolean | undefined;
|
|
345
366
|
options: {
|
|
346
367
|
port: number | undefined;
|
|
347
368
|
mcp: boolean;
|
package/dist/plan/plan-vite.js
CHANGED
|
@@ -194,6 +194,21 @@ function viteConfigSteps(input, detail, inject = true) {
|
|
|
194
194
|
// plugin, and a file carrying ours alone would not boot the app.
|
|
195
195
|
const plainVite = Framework.VITE === input.detection.framework;
|
|
196
196
|
if (null === cfg) {
|
|
197
|
+
const candidates = input.viteConfigCandidates ?? [];
|
|
198
|
+
if (candidates.length > 0) {
|
|
199
|
+
const named = candidates.map((path) => `\`${path}\``).join(', ');
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
title: StepTitle.VITE_PLUGIN,
|
|
203
|
+
target: candidates[0] ?? 'vite.config',
|
|
204
|
+
status: StepStatus.MANUAL,
|
|
205
|
+
detail: `Reticle could not safely patch the Vite config used by the dev server. ` +
|
|
206
|
+
`Candidate config(s): ${named}. ` +
|
|
207
|
+
`Add the Reticle plugin to the config passed to createServer().\n\n` +
|
|
208
|
+
viteManual(port, input.detection.uiLibrary, inject, stampSource),
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
}
|
|
197
212
|
if (!plainVite) {
|
|
198
213
|
return [
|
|
199
214
|
{
|
package/dist/plan/plan.js
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
import { Framework, UiLibrary, installCommand, installCommandParts } from '../detect/detect.js';
|
|
7
7
|
import { installFailureHint } from '../diagnose/install-hint.js';
|
|
8
8
|
import { installRetries } from '../diagnose/install-retries.js';
|
|
9
|
-
import { claudeAddCommand, claudeProjectMcpJson, mcpManual } from '../register/mcp.js';
|
|
9
|
+
import { claudeAddCommand, claudeProjectMcpJson, codexAddCommand, mcpManual, } from '../register/mcp.js';
|
|
10
|
+
import { codexNamesOurServer } from '../register/codex-toml.js';
|
|
10
11
|
import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, CLAUDE_PROJECT_CONFIG, CLAUDE_PROJECT_SPEC, McpClient, } from '../register/mcp-clients.js';
|
|
11
12
|
import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, SLASH_COMMAND_SIGNATURE, } from '../register/slash-command.js';
|
|
12
13
|
import { mergeMarkedInstruction, reticleMdFile, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, markedBlock, AGENTS_MD_PATH, RETICLE_MD_PATH, CURSOR_RULE_PATH, } from '../project/agent-rules.js';
|
|
13
14
|
import { cspStep } from '../diagnose/csp-step.js';
|
|
15
|
+
import { HTML_INDEX_PATH } from '../patch/static-page.js';
|
|
14
16
|
import { frameworkSteps } from './framework-adapter.js';
|
|
15
17
|
import { FRAMEWORK_ADAPTERS, RETICLE_BROWSER_SDK, RETICLE_REACT_KIT } from './framework-adapter.js';
|
|
16
18
|
import { MCP_TARGET, StepStatus } from './plan-types.js';
|
|
@@ -21,7 +23,7 @@ import { configWithInstallSource } from '../project/install-source-config.js';
|
|
|
21
23
|
import { portMoveDetail, retargetConfigPort } from './port-steps.js';
|
|
22
24
|
import { containerisedStep, uiLibraryStep, webGlCanvasStep, windowsMcpNoteStep, } from './notices.js';
|
|
23
25
|
import { existingConfigProblem, projectIdOf, RETICLE_CONFIG_FILE, } from '../detect/existing-config.js';
|
|
24
|
-
import { CLAUDE_SETTINGS_PATH,
|
|
26
|
+
import { CLAUDE_SETTINGS_PATH, claudeSettingsSteps } from './stop-hook-step.js';
|
|
25
27
|
// Re-exported: it moved to the module that reads it, and every existing importer says `plan.js`.
|
|
26
28
|
export { RETICLE_CONFIG_FILE };
|
|
27
29
|
/**
|
|
@@ -92,7 +94,34 @@ function claudeMcpStep(input) {
|
|
|
92
94
|
* parse is reported with a paste-able block rather than overwritten, and everything else is written.
|
|
93
95
|
*/
|
|
94
96
|
function otherClientSteps(input) {
|
|
95
|
-
return (input.detectedClients ?? []).map((detected) =>
|
|
97
|
+
return (input.detectedClients ?? []).map((detected) => {
|
|
98
|
+
const spec = clientSpec(detected.id);
|
|
99
|
+
return (codexCliStep(spec, detected.configPath, detected.existing, input) ??
|
|
100
|
+
clientStep(spec, detected.configPath, detected.existing));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Codex, registered through its own CLI instead of a printed block (#1238).
|
|
105
|
+
*
|
|
106
|
+
* `mergeClientConfig` answers MANUAL for TOML, since we never edit it blind. `codex mcp add` does
|
|
107
|
+
* the editing, so with the binary on PATH nothing is left for a human. It REPLACES an entry of the
|
|
108
|
+
* same name and exits 0, so this answers only when the config names no `reticle` server at all, in
|
|
109
|
+
* any shape. An entry somebody wrote — a local build, a `url` — falls through to `clientStep`, which
|
|
110
|
+
* leaves it alone (ALREADY) or hands over the block (MANUAL), exactly as before.
|
|
111
|
+
*/
|
|
112
|
+
function codexCliStep(spec, configPath, existing, input) {
|
|
113
|
+
if (McpClient.CODEX !== spec.id || true !== input.codexCli)
|
|
114
|
+
return null;
|
|
115
|
+
if (codexNamesOurServer(existing, spec.serversKey))
|
|
116
|
+
return null;
|
|
117
|
+
const cmd = codexAddCommand();
|
|
118
|
+
return {
|
|
119
|
+
title: `MCP server (${spec.label})`,
|
|
120
|
+
target: configPath,
|
|
121
|
+
status: StepStatus.APPLY,
|
|
122
|
+
detail: `register reticle with ${spec.label} (${cmd.display})`,
|
|
123
|
+
exec: { command: cmd.command, args: cmd.args, fallback: cmd.display },
|
|
124
|
+
};
|
|
96
125
|
}
|
|
97
126
|
/** One client's step: already-correct is left alone, an unparseable file gets a paste-able block. */
|
|
98
127
|
function clientStep(spec, configPath, existing) {
|
|
@@ -488,12 +517,22 @@ function installStep(input) {
|
|
|
488
517
|
detail: `${names.join(', ')} already declared${version === undefined || 0 === version.length ? '' : ` at ${version}`}`,
|
|
489
518
|
};
|
|
490
519
|
}
|
|
520
|
+
// Never install into a linked tree: the package manager replaces the link (a workspace's tree).
|
|
521
|
+
if (true === input.nodeModulesLinked) {
|
|
522
|
+
return {
|
|
523
|
+
title: 'Install dependencies',
|
|
524
|
+
target: 'package.json',
|
|
525
|
+
status: StepStatus.SKIP,
|
|
526
|
+
detail: `skipped: node_modules is a link to another tree, which an install here would replace; install them where it points: ${command}`,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
// The person said `--no-install`: a choice, not a step left undone, so not a ⚠.
|
|
491
530
|
if (!input.options.install) {
|
|
492
531
|
return {
|
|
493
532
|
title: 'Install dependencies',
|
|
494
533
|
target: 'package.json',
|
|
495
|
-
status: StepStatus.
|
|
496
|
-
detail: command
|
|
534
|
+
status: StepStatus.SKIP,
|
|
535
|
+
detail: `skipped (--no-install); install them yourself: ${command}`,
|
|
497
536
|
};
|
|
498
537
|
}
|
|
499
538
|
const parts = installCommandParts(pm, packages, [], pmCommand);
|
|
@@ -561,6 +600,19 @@ function agentRootConfigStep(input, content) {
|
|
|
561
600
|
},
|
|
562
601
|
];
|
|
563
602
|
}
|
|
603
|
+
// Several apps: a root config names one and is wrong for the rest. Not written; an agent at the
|
|
604
|
+
// root is served whichever app's daemon is live, and one started in an app reads that app's.
|
|
605
|
+
if (true === input.agentRootSeveralApps && existingProject === undefined) {
|
|
606
|
+
return [
|
|
607
|
+
{
|
|
608
|
+
title: AGENT_ROOT_CONFIG_TITLE,
|
|
609
|
+
target: path,
|
|
610
|
+
status: StepStatus.NOTICE,
|
|
611
|
+
detail: 'not written: this workspace has several apps and the root can name only one. Run the ' +
|
|
612
|
+
"agent from an app's directory to pin it to that app.",
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
}
|
|
564
616
|
if (input.agentRootConfigSource === content) {
|
|
565
617
|
return [
|
|
566
618
|
{
|
|
@@ -652,20 +704,26 @@ function reticleConfigSteps(input) {
|
|
|
652
704
|
];
|
|
653
705
|
}
|
|
654
706
|
export function buildPlan(input) {
|
|
707
|
+
const cspSteps = cspStep(input);
|
|
655
708
|
const steps = [
|
|
656
|
-
...
|
|
709
|
+
...cspSteps,
|
|
657
710
|
...mcpSteps(input),
|
|
658
711
|
...agentRuleSteps(input),
|
|
659
712
|
...slashCommandSteps(input),
|
|
660
|
-
...(
|
|
661
|
-
|
|
662
|
-
:
|
|
713
|
+
...claudeSettingsSteps(agentFile(input, CLAUDE_SETTINGS_PATH), input.claudeSettingsContent, {
|
|
714
|
+
stop: true === input.hooks,
|
|
715
|
+
// Beside the CLAUDE.md rule: the same agent, the same consent to write its instruction files.
|
|
716
|
+
compact: input.options.mcp && input.claudeCli,
|
|
717
|
+
}),
|
|
663
718
|
...uiLibraryStep(input),
|
|
664
719
|
...webGlCanvasStep(input),
|
|
665
720
|
installStep(input),
|
|
666
721
|
...reticleConfigSteps(input),
|
|
667
722
|
];
|
|
668
|
-
steps.
|
|
723
|
+
// Both steps can patch index.html: the connect write must keep the CSP edit that runs first.
|
|
724
|
+
const patchedIndex = cspSteps.find((step) => step.write?.path === HTML_INDEX_PATH)?.write
|
|
725
|
+
?.content;
|
|
726
|
+
steps.push(...frameworkSteps(patchedIndex === undefined ? input : { ...input, htmlIndexSource: patchedIndex }));
|
|
669
727
|
// LAST, and a notice rather than an action: it is a statement about this machine's shape, and it
|
|
670
728
|
// only matters once every file above has been written. See containerised-dev-server.ts.
|
|
671
729
|
const container = containerisedStep(input);
|
|
@@ -2,3 +2,22 @@ import { type Step } from './plan-types.js';
|
|
|
2
2
|
export declare const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
|
|
3
3
|
export declare const STOP_HOOK_COMMAND = "npx @reticlehq/server report --hook";
|
|
4
4
|
export declare function stopHookStep(path: string, existing: string | null | undefined): Step;
|
|
5
|
+
/**
|
|
6
|
+
* The Claude Code `SessionStart` hook with matcher `compact`: right after a compaction it prints
|
|
7
|
+
* `reticle context --compact` — what this session proved, what failed, what is still owed — back
|
|
8
|
+
* into the agent's fresh context. Reticle holds the memory; this is how it is handed back without
|
|
9
|
+
* the agent having to remember to ask.
|
|
10
|
+
*
|
|
11
|
+
* Written by default wherever the CLAUDE.md rule is (it only reads the project's own `.reticle/`, and
|
|
12
|
+
* prints nothing where there is none). Removable like any hook: delete the entry.
|
|
13
|
+
*/
|
|
14
|
+
export declare const COMPACT_HOOK_COMMAND = "npx @reticlehq/server context --compact";
|
|
15
|
+
export declare function compactHookStep(path: string, existing: string | null | undefined): Step;
|
|
16
|
+
/**
|
|
17
|
+
* Every hook this run writes into `.claude/settings.json`, each merged on top of the one before, so
|
|
18
|
+
* two steps writing the same file cannot undo each other.
|
|
19
|
+
*/
|
|
20
|
+
export declare function claudeSettingsSteps(path: string, existing: string | null | undefined, options: {
|
|
21
|
+
stop: boolean;
|
|
22
|
+
compact: boolean;
|
|
23
|
+
}): Step[];
|
|
@@ -36,7 +36,12 @@ export function stopHookStep(path, existing) {
|
|
|
36
36
|
const hooks = isObject(settings['hooks']) ? settings['hooks'] : {};
|
|
37
37
|
const stop = Array.isArray(hooks[STOP_EVENT]) ? hooks[STOP_EVENT] : [];
|
|
38
38
|
if (JSON.stringify(stop).includes(STOP_HOOK_COMMAND)) {
|
|
39
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
title: TITLE,
|
|
41
|
+
target: path,
|
|
42
|
+
status: StepStatus.ALREADY,
|
|
43
|
+
detail: 'hook already installed',
|
|
44
|
+
};
|
|
40
45
|
}
|
|
41
46
|
const merged = { ...settings, hooks: { ...hooks, [STOP_EVENT]: [...stop, HOOK_ENTRY] } };
|
|
42
47
|
return {
|
|
@@ -47,3 +52,71 @@ export function stopHookStep(path, existing) {
|
|
|
47
52
|
write: { path, content: `${JSON.stringify(merged, null, 2)}\n`, expect: [STOP_HOOK_COMMAND] },
|
|
48
53
|
};
|
|
49
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The Claude Code `SessionStart` hook with matcher `compact`: right after a compaction it prints
|
|
57
|
+
* `reticle context --compact` — what this session proved, what failed, what is still owed — back
|
|
58
|
+
* into the agent's fresh context. Reticle holds the memory; this is how it is handed back without
|
|
59
|
+
* the agent having to remember to ask.
|
|
60
|
+
*
|
|
61
|
+
* Written by default wherever the CLAUDE.md rule is (it only reads the project's own `.reticle/`, and
|
|
62
|
+
* prints nothing where there is none). Removable like any hook: delete the entry.
|
|
63
|
+
*/
|
|
64
|
+
export const COMPACT_HOOK_COMMAND = `npx ${RETICLE_NPM_PACKAGE} context --compact`;
|
|
65
|
+
const COMPACT_TITLE = 'Claude Code resume-after-compaction hook';
|
|
66
|
+
const SESSION_START_EVENT = 'SessionStart';
|
|
67
|
+
const COMPACT_MATCHER = 'compact';
|
|
68
|
+
export function compactHookStep(path, existing) {
|
|
69
|
+
const settings = null === existing || undefined === existing ? {} : parseSettings(existing);
|
|
70
|
+
if (settings === undefined) {
|
|
71
|
+
return {
|
|
72
|
+
title: COMPACT_TITLE,
|
|
73
|
+
target: path,
|
|
74
|
+
status: StepStatus.MANUAL,
|
|
75
|
+
detail: `${CLAUDE_SETTINGS_PATH} is not a JSON object, so it was left alone. Add a SessionStart hook (matcher "${COMPACT_MATCHER}") running \`${COMPACT_HOOK_COMMAND}\``,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const hooks = isObject(settings['hooks']) ? settings['hooks'] : {};
|
|
79
|
+
const start = Array.isArray(hooks[SESSION_START_EVENT])
|
|
80
|
+
? hooks[SESSION_START_EVENT]
|
|
81
|
+
: [];
|
|
82
|
+
if (JSON.stringify(start).includes(COMPACT_HOOK_COMMAND)) {
|
|
83
|
+
return {
|
|
84
|
+
title: COMPACT_TITLE,
|
|
85
|
+
target: path,
|
|
86
|
+
status: StepStatus.ALREADY,
|
|
87
|
+
detail: 'hook already installed',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const entry = {
|
|
91
|
+
matcher: COMPACT_MATCHER,
|
|
92
|
+
hooks: [{ type: 'command', command: COMPACT_HOOK_COMMAND }],
|
|
93
|
+
};
|
|
94
|
+
const merged = { ...settings, hooks: { ...hooks, [SESSION_START_EVENT]: [...start, entry] } };
|
|
95
|
+
return {
|
|
96
|
+
title: COMPACT_TITLE,
|
|
97
|
+
target: path,
|
|
98
|
+
status: StepStatus.APPLY,
|
|
99
|
+
detail: 'after a context compaction, hand the agent back what this session proved and still owes',
|
|
100
|
+
write: {
|
|
101
|
+
path,
|
|
102
|
+
content: `${JSON.stringify(merged, null, 2)}\n`,
|
|
103
|
+
expect: [COMPACT_HOOK_COMMAND],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Every hook this run writes into `.claude/settings.json`, each merged on top of the one before, so
|
|
109
|
+
* two steps writing the same file cannot undo each other.
|
|
110
|
+
*/
|
|
111
|
+
export function claudeSettingsSteps(path, existing, options) {
|
|
112
|
+
const steps = [];
|
|
113
|
+
let current = existing;
|
|
114
|
+
if (options.stop) {
|
|
115
|
+
const stop = stopHookStep(path, current);
|
|
116
|
+
steps.push(stop);
|
|
117
|
+
current = stop.write?.content ?? current;
|
|
118
|
+
}
|
|
119
|
+
if (options.compact)
|
|
120
|
+
steps.push(compactHookStep(path, current));
|
|
121
|
+
return steps;
|
|
122
|
+
}
|
|
@@ -90,7 +90,7 @@ export declare const DEV_SERVER_POLICY = "**A dev server already running when `r
|
|
|
90
90
|
* NOT to, how, and the guards against calling something proven when it is not. Everything an agent
|
|
91
91
|
* needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
|
|
92
92
|
*/
|
|
93
|
-
export declare const RULE_BODY = "## Verifying with Reticle\n\nThis app is instrumented by **Reticle**, an in-app verification layer exposed as `reticle_*` MCP tools and the `npx @reticlehq/server` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of \"done\", not an optional extra.\n\n**Verify when you have changed something a user can see or do.** A component, a form, a route, a request, a piece of state that reaches the screen. Do it BEFORE telling the user it is complete. Reading the diff proves nothing and unit tests do not run the app.\n\n**Do not reach for Reticle when the change cannot show up in the running app.** It costs tool calls and the user's patience, and a verdict over an unrelated flow proves nothing about what you changed. Skip it for: documentation, comments, tests, build config, CI, dependency bumps with no user-facing effect, backend or CLI work with no UI surface, and any change to a project that is not a running web app. Say in one line that you skipped verification and why, rather than silently not doing it.\n\n**How to verify:**\n\
|
|
93
|
+
export declare const RULE_BODY = "## Verifying with Reticle\n\nThis app is instrumented by **Reticle**, an in-app verification layer exposed as `reticle_*` MCP tools and the `npx @reticlehq/server` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of \"done\", not an optional extra.\n\n**Verify when you have changed something a user can see or do.** A component, a form, a route, a request, a piece of state that reaches the screen. Do it BEFORE telling the user it is complete. Reading the diff proves nothing and unit tests do not run the app.\n\n**Do not reach for Reticle when the change cannot show up in the running app.** It costs tool calls and the user's patience, and a verdict over an unrelated flow proves nothing about what you changed. Skip it for: documentation, comments, tests, build config, CI, dependency bumps with no user-facing effect, backend or CLI work with no UI surface, and any change to a project that is not a running web app. Say in one line that you skipped verification and why, rather than silently not doing it.\n\n**How to verify \u2014 the rules to keep in mind on every call:**\n\n1. **Only `verified: \"yes\"` is a pass.** `\"no-fault\"` (nothing was declared \u2014 you omitted `until`) and `\"unknown\"` (Reticle could not tell; `verifiedReason` says why) are not a pass: report them as not proved, never as working.\n2. **Name the consequence BEFORE acting:** `reticle_act_and_wait({ target, action, until })` (add `durable: true` if it must survive a reload). Only it, `reticle_assert` and `reticle_act { steps }` produce a verdict; everything else proves nothing.\n3. **A journey is one call:** `reticle_act { steps: [{ target, action, expect }] }` runs every step and stops at the first `expect` that fails. The last step's `expect` is the end state you were asked to deliver.\n4. **Text checks are exact-case.** Add `ignoreCase: true` only when case truly does not matter.\n5. **Target by `{ testid }` or `{ role, name }`.** An ambiguous target is refused, never guessed.\n6. **On a failure, read `expected` / `observed` / the nearest candidates before retrying.** Never weaken a check to make it green.\n7. **Open `screenshot.path` only when the look matters;** `reticle_observe { since }` has the rest.\n8. **Predicates:** `{ kind: \"signal\", name }` \u00B7 `{ kind: \"net\", method, urlContains, status }` \u00B7 `{ kind: \"route\", pathname }` \u00B7 `{ kind: \"state\", path, equals }` \u00B7 `{ kind: \"element\", query: { role, name } }` \u00B7 `{ kind: \"text\", contains }` \u00B7 `allOf` / `anyOf` / `not`.\n9. **No session?** `reticle_session { action: \"list\" }` and do its `next_action`. **No app connected at all?** Run `npx @reticlehq/server init` in the app's own folder yourself. **A version-skew warning?** `npx @reticlehq/server stop`, then retry.\n10. **End with the Reticle result block the server gives you**, verbatim.\n11. **If your context was compacted or you lost track, run `npx @reticlehq/server context --compact` first.** It is the daemon's own record of what was proved, what failed and what is still owed.\n\nCovered flows: `npx @reticlehq/server gate` reports which recorded flows the changed files affect and whether they still pass.\n\n**Setting Reticle up? You are mid-sequence \u2014 do not stop until a verdict exists.** The whole of it\nis: instrument the app \u2192 get a dev server running \u2192 open the app in a browser \u2192 drive one flow \u2192\nreport the verdict. Every step is yours to do, and none of them needs the user. Stopping short leaves an app that\nlooks installed and can verify nothing, which is the commonest way this goes wrong. `/reticle`\ncarries the recovery ladder; never report the install as finished without a verdict to point at.\n\n**Nothing connected? Get the app running.**\n\n**A dev server already running when `reticle init` ran does not have Reticle in its bundle.** It read the build config at boot; `init` edited it afterwards. It serves the old bundle and no session appears. In order:\n\n1. **A dev server was already running?** Restart it, then hard-reload the tab. \"Something is listening\" does not mean the right bundle is served.\n2. **Nothing was running?** Start it in the BACKGROUND and say so in one line. `reticle_session { action: \"list\" }` gives you this project's own dev command in `next_action`; use that, never compose one. Started after `init`, it needs no restart.\n\nStopping to ask is how a verification turn ends with nothing verified.\n\nFour guards, none optional:\n\n1. **Never run two at once.** One dev server on the app's port. Restarting a stale one means stopping it first, not starting a second alongside it.\n2. **Never guess the command.** It comes from `package.json` scripts. No recognisable dev script means say so and stop, not invent one.\n3. **Never kill anything you did not start**, and never a daemon or a port holder. The one exception is the restart above, and say in one line that you did it.\n4. **The permission prompt belongs to your host.** Never bypass, suppress or auto-approve it, and take a refusal as the answer.\n\nIf a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.\n\n**Finish `src/reticle-dev.ts` before you claim setup is done.** `init` writes it and cannot always fill it in: a store that needs an argument only reading the code supplies (Jotai atoms, an XState actor, a TanStack `queryClient`) is left as a commented `registerStore` line. A file that registers nothing looks exactly like a finished one, and `reticle_look { action: \"state\" }` then returns empty forever \u2014 which is indistinguishable from an app that has nothing to report, so it reads as success. Uncomment the line, complete it, and prove it by driving one flow and seeing your keys come back. If `init` told you to restart your client, this is the job waiting for you on the other side of that restart.\n\n**Verify each feature as you finish it, not all of them at the end.** Asked for four, build one, drive it, get a verdict, then start the second. A red verdict after four builds has four suspects; after one it has none.\n\n**Capture what a change is FOR while you are building it, not afterwards.** The business outcome a change is meant to produce is known only while the change is being made. Pass `intent` when a flow is saved, so the saved flow carries the reason it exists. A flow without one replays for months and then reports \"step 3 failed\" instead of what stopped being true for a user.\n\n**Honesty, which is the whole point:**\n\n- **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.\n- **Setup is not finished until one real flow has been driven and produced a verdict.** `init` exiting 0, the tools appearing, and a session being listed are all things that happen before anything has been verified.\n\n**The `/reticle` skill runs this whole loop for you** \u2014 detect, connect, drive one flow, report. If your client does not have it, install it once: `/plugin marketplace add reticlehq/reticle` then `/plugin install reticle@reticlehq` in Claude Code, or `npx skills add reticlehq/reticle` anywhere the skills CLI works.\n\n**A tool you need is missing?** Call `reticle_tools` before assuming it: this surface merges several families behind an `action`, so what looks absent is usually one argument away. It is the verify loop and nothing else on purpose, and a daemon started with `RETICLE_ADVERTISE_ALL_TOOLS=1` advertises the wider set \u2014 it reads that at startup, so it takes effect on the next one.\n\n**Report Reticle's own defects with `reticle_session { action: \"feedback\" }` the moment you notice**, then carry on with your task. You are the user Reticle is built for and the only one who can say what it cost you, and that knowledge is gone when your context is.\n\n\uD83D\uDCC4 **The rest is in [RETICLE.md](./RETICLE.md): what to do when the tools are missing, when a result carries `version_skew` or `update_available`, when `reticle_look { action: \"state\" }` comes back empty, and how to write a feedback report that can be acted on. Read it when you hit one of those, not before.**";
|
|
94
94
|
/** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
|
|
95
95
|
export declare function reticleMdFile(): string;
|
|
96
96
|
export declare const AgentRuleStatus: {
|