@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
|
@@ -118,13 +118,21 @@ This app is instrumented by **Reticle**, an in-app verification layer exposed as
|
|
|
118
118
|
|
|
119
119
|
**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.
|
|
120
120
|
|
|
121
|
-
**How to verify:**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
**How to verify — the rules to keep in mind on every call:**
|
|
122
|
+
|
|
123
|
+
1. **Only \`verified: "yes"\` is a pass.** \`"no-fault"\` (nothing was declared — you omitted \`until\`) and \`"unknown"\` (Reticle could not tell; \`verifiedReason\` says why) are not a pass: report them as not proved, never as working.
|
|
124
|
+
2. **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.
|
|
125
|
+
3. **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.
|
|
126
|
+
4. **Text checks are exact-case.** Add \`ignoreCase: true\` only when case truly does not matter.
|
|
127
|
+
5. **Target by \`{ testid }\` or \`{ role, name }\`.** An ambiguous target is refused, never guessed.
|
|
128
|
+
6. **On a failure, read \`expected\` / \`observed\` / the nearest candidates before retrying.** Never weaken a check to make it green.
|
|
129
|
+
7. **Open \`screenshot.path\` only when the look matters;** \`reticle_observe { since }\` has the rest.
|
|
130
|
+
8. **Predicates:** \`{ kind: "signal", name }\` · \`{ kind: "net", method, urlContains, status }\` · \`{ kind: "route", pathname }\` · \`{ kind: "state", path, equals }\` · \`{ kind: "element", query: { role, name } }\` · \`{ kind: "text", contains }\` · \`allOf\` / \`anyOf\` / \`not\`.
|
|
131
|
+
9. **No session?** \`reticle_session { action: "list" }\` and do its \`next_action\`. **No app connected at all?** Run \`${CLI} init\` in the app's own folder yourself. **A version-skew warning?** \`${CLI} stop\`, then retry.
|
|
132
|
+
10. **End with the Reticle result block the server gives you**, verbatim.
|
|
133
|
+
11. **If your context was compacted or you lost track, run \`${CLI} context --compact\` first.** It is the daemon's own record of what was proved, what failed and what is still owed.
|
|
134
|
+
|
|
135
|
+
Covered flows: \`${CLI} gate\` reports which recorded flows the changed files affect and whether they still pass.
|
|
128
136
|
|
|
129
137
|
**Setting Reticle up? You are mid-sequence — do not stop until a verdict exists.** The whole of it
|
|
130
138
|
is: instrument the app → get a dev server running → open the app in a browser → drive one flow →
|
|
@@ -136,7 +144,7 @@ carries the recovery ladder; never report the install as finished without a verd
|
|
|
136
144
|
|
|
137
145
|
${DEV_SERVER_POLICY}
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
If a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.
|
|
140
148
|
|
|
141
149
|
**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 — 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.
|
|
142
150
|
|
|
@@ -146,9 +154,6 @@ A dev server that is already running does not pick up an edited build config or
|
|
|
146
154
|
|
|
147
155
|
**Honesty, which is the whole point:**
|
|
148
156
|
|
|
149
|
-
- **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which clause decided that. Report it as unknown, never as working.
|
|
150
|
-
- **\`verified: "no-fault"\` is not a pass either.** It means nothing was DECLARED to prove: the page settled and no channel complained, but you asserted nothing, so there is no verification. You get it whenever \`until\` is omitted. Name a consequence the action changes — a signal, a request, a route, or store state — and call again.
|
|
151
|
-
- **Never weaken a check to make it green.** Downgrading, skipping or deleting an assertion is a finding, not a fix.
|
|
152
157
|
- **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.
|
|
153
158
|
- **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.
|
|
154
159
|
|
|
@@ -8,3 +8,15 @@
|
|
|
8
8
|
export declare function codexServerTokens(existing: string | null, serversKey: string): string[];
|
|
9
9
|
/** Does this config already declare a Reticle server under our name? */
|
|
10
10
|
export declare function codexDeclaresOurServer(existing: string | null, serversKey: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Does this config NAME a Reticle server at all, in any shape?
|
|
13
|
+
*
|
|
14
|
+
* Wider than {@link codexDeclaresOurServer}, which asks whether the entry holds a command we can
|
|
15
|
+
* read. A `url` entry, one that only has an `env` table, or an inline `mcp_servers = { … }` has no
|
|
16
|
+
* command and args, so it reads as absent there. `codex mcp add reticle` REPLACES such an entry
|
|
17
|
+
* without a word and exits 0, so anything that registers through that command has to ask this.
|
|
18
|
+
*
|
|
19
|
+
* Structural rather than a substring match: a project called `reticle`, under
|
|
20
|
+
* `[projects."/home/u/reticle"]`, does not name a server.
|
|
21
|
+
*/
|
|
22
|
+
export declare function codexNamesOurServer(existing: string | null, serversKey: string): boolean;
|
|
@@ -142,3 +142,47 @@ export function codexServerTokens(existing, serversKey) {
|
|
|
142
142
|
export function codexDeclaresOurServer(existing, serversKey) {
|
|
143
143
|
return 0 < codexServerTokens(existing, serversKey).length;
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Our key, as a KEY, inside an inline table's text: `reticle = …`, `"reticle" = …`, or the dotted
|
|
147
|
+
* `reticle.command = …`. A key follows the `{` or a `,`, and its quotes match, so a value that merely
|
|
148
|
+
* starts with the name (`command = "reticle.js"`) is not one.
|
|
149
|
+
*/
|
|
150
|
+
const OUR_KEY_INLINE = new RegExp(`(?:^|[{,])\\s*(?:${MCP_SERVER_NAME}|"${MCP_SERVER_NAME}"|'${MCP_SERVER_NAME}')\\s*[.=]`);
|
|
151
|
+
/**
|
|
152
|
+
* Does this config NAME a Reticle server at all, in any shape?
|
|
153
|
+
*
|
|
154
|
+
* Wider than {@link codexDeclaresOurServer}, which asks whether the entry holds a command we can
|
|
155
|
+
* read. A `url` entry, one that only has an `env` table, or an inline `mcp_servers = { … }` has no
|
|
156
|
+
* command and args, so it reads as absent there. `codex mcp add reticle` REPLACES such an entry
|
|
157
|
+
* without a word and exits 0, so anything that registers through that command has to ask this.
|
|
158
|
+
*
|
|
159
|
+
* Structural rather than a substring match: a project called `reticle`, under
|
|
160
|
+
* `[projects."/home/u/reticle"]`, does not name a server.
|
|
161
|
+
*/
|
|
162
|
+
export function codexNamesOurServer(existing, serversKey) {
|
|
163
|
+
if (null === existing)
|
|
164
|
+
return false;
|
|
165
|
+
const want = ourTablePath(serversKey);
|
|
166
|
+
const isOurs = (path) => want.every((segment, i) => path[i] === segment);
|
|
167
|
+
let table = [];
|
|
168
|
+
for (const rawLine of existing.split(/\r?\n/)) {
|
|
169
|
+
const line = withoutComment(rawLine).trim();
|
|
170
|
+
const header = /^\[{1,2}([^\]]+)\]{1,2}$/.exec(line);
|
|
171
|
+
if (null !== header) {
|
|
172
|
+
table = keyPath(header[1] ?? '');
|
|
173
|
+
if (isOurs(table))
|
|
174
|
+
return true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const assignment = /^([^=]+)=(.*)$/.exec(line);
|
|
178
|
+
if (null === assignment)
|
|
179
|
+
continue;
|
|
180
|
+
const path = [...table, ...keyPath(assignment[1] ?? '')];
|
|
181
|
+
if (isOurs(path))
|
|
182
|
+
return true;
|
|
183
|
+
// `mcp_servers = { reticle = { … } }`: the whole table is one inline value, our key inside it.
|
|
184
|
+
if (samePath(path, [serversKey]) && OUR_KEY_INLINE.test(assignment[2] ?? ''))
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
package/dist/register/mcp.d.ts
CHANGED
|
@@ -58,14 +58,14 @@ export declare function npxServerArgs(): string[];
|
|
|
58
58
|
* @param tokens every string in the entry's command + args, in any of the client shapes.
|
|
59
59
|
*/
|
|
60
60
|
export declare function isReticleRegistration(tokens: readonly string[]): boolean;
|
|
61
|
-
interface
|
|
61
|
+
interface CliAddCommand {
|
|
62
62
|
command: string;
|
|
63
63
|
args: string[];
|
|
64
64
|
/** Human-readable form of the same command, for reports and manual fallback. */
|
|
65
65
|
display: string;
|
|
66
66
|
}
|
|
67
67
|
/** `claude mcp add reticle -s user -- npx @reticlehq/server mcp` — registers globally for all projects (portless). */
|
|
68
|
-
export declare function claudeAddCommand():
|
|
68
|
+
export declare function claudeAddCommand(): CliAddCommand;
|
|
69
69
|
/**
|
|
70
70
|
* Is a `reticle` server already registered with Claude Code, in any scope this project sees?
|
|
71
71
|
*
|
|
@@ -89,6 +89,19 @@ export declare function claudeAvailableProbe(): {
|
|
|
89
89
|
command: string;
|
|
90
90
|
args: string[];
|
|
91
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* `codex mcp add reticle -- npx @reticlehq/server mcp` — Codex keeps the entry in its own
|
|
94
|
+
* `~/.codex/config.toml`, shared by the desktop app and the CLI, so one registration covers both.
|
|
95
|
+
*
|
|
96
|
+
* It REPLACES an existing entry of the same name and still exits 0, so callers run it only when the
|
|
97
|
+
* config names no `reticle` server at all (see `codexNamesOurServer`).
|
|
98
|
+
*/
|
|
99
|
+
export declare function codexAddCommand(): CliAddCommand;
|
|
100
|
+
/** Probe args for whether the `codex` CLI is installed at all. */
|
|
101
|
+
export declare function codexAvailableProbe(): {
|
|
102
|
+
command: string;
|
|
103
|
+
args: string[];
|
|
104
|
+
};
|
|
92
105
|
/**
|
|
93
106
|
* The Windows `cmd /c npx` registration. Printed on every Windows init, not only when the `claude`
|
|
94
107
|
* CLI is missing — a successful `claude mcp add` of bare `npx` is exactly the failure that reported
|
package/dist/register/mcp.js
CHANGED
|
@@ -36,6 +36,7 @@ export const NPX = 'npx';
|
|
|
36
36
|
const RETICLE_PACKAGE = RETICLE_NPM_PACKAGE;
|
|
37
37
|
const MCP_SUBCOMMAND = 'mcp';
|
|
38
38
|
const CLAUDE_CLI = 'claude';
|
|
39
|
+
const CODEX_CLI = 'codex';
|
|
39
40
|
/** Windows `cmd /c` — the spawn that PowerShell's execution policy does not gate. */
|
|
40
41
|
const WINDOWS_SHELL = 'cmd';
|
|
41
42
|
const WINDOWS_SHELL_C = '/c';
|
|
@@ -140,6 +141,21 @@ export function claudeHasReticle(io, projectDir, configDir = process.env['CLAUDE
|
|
|
140
141
|
export function claudeAvailableProbe() {
|
|
141
142
|
return { command: CLAUDE_CLI, args: ['--version'] };
|
|
142
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* `codex mcp add reticle -- npx @reticlehq/server mcp` — Codex keeps the entry in its own
|
|
146
|
+
* `~/.codex/config.toml`, shared by the desktop app and the CLI, so one registration covers both.
|
|
147
|
+
*
|
|
148
|
+
* It REPLACES an existing entry of the same name and still exits 0, so callers run it only when the
|
|
149
|
+
* config names no `reticle` server at all (see `codexNamesOurServer`).
|
|
150
|
+
*/
|
|
151
|
+
export function codexAddCommand() {
|
|
152
|
+
const args = [MCP_SUBCOMMAND, 'add', MCP_SERVER_NAME, '--', ...serverInvocation()];
|
|
153
|
+
return { command: CODEX_CLI, args, display: `${CODEX_CLI} ${args.join(' ')}` };
|
|
154
|
+
}
|
|
155
|
+
/** Probe args for whether the `codex` CLI is installed at all. */
|
|
156
|
+
export function codexAvailableProbe() {
|
|
157
|
+
return { command: CODEX_CLI, args: ['--version'] };
|
|
158
|
+
}
|
|
143
159
|
/**
|
|
144
160
|
* The Windows `cmd /c npx` registration. Printed on every Windows init, not only when the `claude`
|
|
145
161
|
* CLI is missing — a successful `claude mcp add` of bare `npx` is exactly the failure that reported
|
package/dist/run-types.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface InitOptions {
|
|
|
41
41
|
* at `src/admin` ended up with no `/reticle` at all (#318).
|
|
42
42
|
*/
|
|
43
43
|
agentRoot?: string;
|
|
44
|
+
/** The agent root is a workspace with several apps: it is pinned to none of them. */
|
|
45
|
+
severalApps?: boolean;
|
|
44
46
|
/**
|
|
45
47
|
* Hand the telemetry outcome back rather than emitting it here.
|
|
46
48
|
*
|
|
@@ -74,6 +76,8 @@ export interface InitIo {
|
|
|
74
76
|
listDirs(relPath: string): readonly string[];
|
|
75
77
|
/** File (non-directory) basenames inside a project-relative directory, including dotfiles. */
|
|
76
78
|
listFiles(relPath: string): readonly string[];
|
|
79
|
+
/** Whether a project-relative path is a symbolic link. Optional: an IO without it reads none. */
|
|
80
|
+
isSymlink?(relPath: string): boolean;
|
|
77
81
|
/** The same IO re-rooted at a project-relative subdirectory (used for the workspace redirect). */
|
|
78
82
|
scoped(relPath: string): InitIo;
|
|
79
83
|
/** Runs a subprocess to completion (inherits stdio); returns true on exit code 0. */
|
|
@@ -138,4 +142,6 @@ export interface InitResult {
|
|
|
138
142
|
* `deferOutcome` is set. Absent on a dry run and on the exits that report for themselves.
|
|
139
143
|
*/
|
|
140
144
|
outcome?: InitOutcome;
|
|
145
|
+
/** No app where init ran; it listed the apps below instead. Reported as `no_package_json`. */
|
|
146
|
+
listedAppsBelow?: true;
|
|
141
147
|
}
|
package/dist/run.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InitOptions, InitIo, InitResult } from './run-types.js';
|
|
2
|
-
export type { InitOptions, InitIo, InitResult } from './run-types.js';
|
|
3
2
|
import { resolveLockfiles } from './detect/lockfiles.js';
|
|
3
|
+
export type { InitOptions, InitIo, InitResult } from './run-types.js';
|
|
4
4
|
export { resolveLockfiles };
|
|
5
5
|
export declare function runInit(options: InitOptions, io: InitIo): InitResult;
|