fast-replay 0.1.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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/api.d.ts +65 -0
- package/dist/api.js +78 -0
- package/dist/api.js.map +1 -0
- package/dist/browser.d.ts +67 -0
- package/dist/browser.js +77 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli/format.d.ts +12 -0
- package/dist/cli/format.js +55 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +199 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compiler/compile.d.ts +18 -0
- package/dist/compiler/compile.js +226 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/normalize.d.ts +16 -0
- package/dist/compiler/normalize.js +75 -0
- package/dist/compiler/normalize.js.map +1 -0
- package/dist/compiler/waits.d.ts +31 -0
- package/dist/compiler/waits.js +69 -0
- package/dist/compiler/waits.js.map +1 -0
- package/dist/ir/io.d.ts +47 -0
- package/dist/ir/io.js +122 -0
- package/dist/ir/io.js.map +1 -0
- package/dist/ir/schema.d.ts +236 -0
- package/dist/ir/schema.js +153 -0
- package/dist/ir/schema.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +22 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +268 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/noise.d.ts +20 -0
- package/dist/noise.js +70 -0
- package/dist/noise.js.map +1 -0
- package/dist/recorder/agent/capture.d.ts +15 -0
- package/dist/recorder/agent/capture.js +310 -0
- package/dist/recorder/agent/capture.js.map +1 -0
- package/dist/recorder/agent/config.d.ts +25 -0
- package/dist/recorder/agent/config.js +23 -0
- package/dist/recorder/agent/config.js.map +1 -0
- package/dist/recorder/agent/dom-reaction.d.ts +13 -0
- package/dist/recorder/agent/dom-reaction.js +166 -0
- package/dist/recorder/agent/dom-reaction.js.map +1 -0
- package/dist/recorder/agent/main.d.ts +1 -0
- package/dist/recorder/agent/main.js +14 -0
- package/dist/recorder/agent/main.js.map +1 -0
- package/dist/recorder/agent/page-agent.d.ts +12 -0
- package/dist/recorder/agent/page-agent.js +40 -0
- package/dist/recorder/agent/page-agent.js.map +1 -0
- package/dist/recorder/agent/reveal-tracker.d.ts +26 -0
- package/dist/recorder/agent/reveal-tracker.js +41 -0
- package/dist/recorder/agent/reveal-tracker.js.map +1 -0
- package/dist/recorder/agent/roles.d.ts +11 -0
- package/dist/recorder/agent/roles.js +148 -0
- package/dist/recorder/agent/roles.js.map +1 -0
- package/dist/recorder/agent/selectors.d.ts +26 -0
- package/dist/recorder/agent/selectors.js +256 -0
- package/dist/recorder/agent/selectors.js.map +1 -0
- package/dist/recorder/agent/text.d.ts +19 -0
- package/dist/recorder/agent/text.js +82 -0
- package/dist/recorder/agent/text.js.map +1 -0
- package/dist/recorder/agent/transport.d.ts +14 -0
- package/dist/recorder/agent/transport.js +43 -0
- package/dist/recorder/agent/transport.js.map +1 -0
- package/dist/recorder/agent/visibility.d.ts +8 -0
- package/dist/recorder/agent/visibility.js +20 -0
- package/dist/recorder/agent/visibility.js.map +1 -0
- package/dist/recorder/agent-bundle.generated.d.ts +2 -0
- package/dist/recorder/agent-bundle.generated.js +6 -0
- package/dist/recorder/agent-bundle.generated.js.map +1 -0
- package/dist/recorder/attach.d.ts +52 -0
- package/dist/recorder/attach.js +159 -0
- package/dist/recorder/attach.js.map +1 -0
- package/dist/recorder/instrument.d.ts +17 -0
- package/dist/recorder/instrument.js +24 -0
- package/dist/recorder/instrument.js.map +1 -0
- package/dist/recorder/launch.d.ts +38 -0
- package/dist/recorder/launch.js +72 -0
- package/dist/recorder/launch.js.map +1 -0
- package/dist/recorder/reaction.d.ts +13 -0
- package/dist/recorder/reaction.js +65 -0
- package/dist/recorder/reaction.js.map +1 -0
- package/dist/recorder/types.d.ts +62 -0
- package/dist/recorder/types.js +2 -0
- package/dist/recorder/types.js.map +1 -0
- package/dist/replayer/artifacts.d.ts +25 -0
- package/dist/replayer/artifacts.js +58 -0
- package/dist/replayer/artifacts.js.map +1 -0
- package/dist/replayer/invariants.d.ts +26 -0
- package/dist/replayer/invariants.js +109 -0
- package/dist/replayer/invariants.js.map +1 -0
- package/dist/replayer/resolve.d.ts +35 -0
- package/dist/replayer/resolve.js +50 -0
- package/dist/replayer/resolve.js.map +1 -0
- package/dist/replayer/run.d.ts +104 -0
- package/dist/replayer/run.js +467 -0
- package/dist/replayer/run.js.map +1 -0
- package/dist/replayer/values.d.ts +13 -0
- package/dist/replayer/values.js +76 -0
- package/dist/replayer/values.js.map +1 -0
- package/dist/replayer/waits.d.ts +25 -0
- package/dist/replayer/waits.js +90 -0
- package/dist/replayer/waits.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isIncidentalRequest } from '../noise.js';
|
|
2
|
+
import { normalizeUrlPattern } from './normalize.js';
|
|
3
|
+
export const DEFAULT_WAIT_RULES = {
|
|
4
|
+
triggerWindowMs: 500,
|
|
5
|
+
domWindowMs: 5_000,
|
|
6
|
+
emptyTimeoutMs: 2_000,
|
|
7
|
+
minTimeoutMs: 3_000,
|
|
8
|
+
maxTimeoutMs: 15_000,
|
|
9
|
+
timeoutSlack: 3,
|
|
10
|
+
maxSelectors: 5,
|
|
11
|
+
};
|
|
12
|
+
function uniq(values, max) {
|
|
13
|
+
return Array.from(new Set(values)).slice(0, max);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Turn one action's observed reaction into the wait the replayer will perform.
|
|
17
|
+
*
|
|
18
|
+
* The whole point is that replay waits on *signals*, never on sleeps: if the
|
|
19
|
+
* recording saw a DELETE settle and a row vanish, replay waits for exactly
|
|
20
|
+
* that, and proceeds the instant it happens.
|
|
21
|
+
*/
|
|
22
|
+
export function buildWaitAfter(window, rules = DEFAULT_WAIT_RULES) {
|
|
23
|
+
const { actionAt, windowEnd, baseUrl } = window;
|
|
24
|
+
const triggered = window.network.filter((n) => !isIncidentalRequest(n.url) &&
|
|
25
|
+
n.startedAt >= actionAt &&
|
|
26
|
+
n.startedAt <= actionAt + rules.triggerWindowMs &&
|
|
27
|
+
n.settledAt !== null &&
|
|
28
|
+
n.settledAt <= windowEnd);
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const network = [];
|
|
31
|
+
for (const n of triggered) {
|
|
32
|
+
const urlPattern = normalizeUrlPattern(n.url, baseUrl);
|
|
33
|
+
const key = `${n.method} ${urlPattern}`;
|
|
34
|
+
if (seen.has(key))
|
|
35
|
+
continue;
|
|
36
|
+
seen.add(key);
|
|
37
|
+
network.push({ urlPattern, method: n.method });
|
|
38
|
+
}
|
|
39
|
+
const domEnd = Math.min(windowEnd, actionAt + rules.domWindowMs);
|
|
40
|
+
const domEvents = window.dom.filter((d) => d.t >= actionAt && d.t <= domEnd);
|
|
41
|
+
const appearedAll = domEvents.flatMap((d) => d.appeared);
|
|
42
|
+
const goneAll = domEvents.flatMap((d) => d.gone);
|
|
43
|
+
// An element that appeared and vanished inside one window is a flicker
|
|
44
|
+
// (spinner, transient re-render) and is not a reliable signal either way.
|
|
45
|
+
const flicker = new Set(appearedAll.filter((s) => goneAll.includes(s)));
|
|
46
|
+
const domAppeared = uniq(appearedAll.filter((s) => !flicker.has(s)), rules.maxSelectors);
|
|
47
|
+
const domGone = uniq(goneAll.filter((s) => !flicker.has(s)), rules.maxSelectors);
|
|
48
|
+
const hasSignal = network.length > 0 || domAppeared.length > 0 || domGone.length > 0;
|
|
49
|
+
if (!hasSignal) {
|
|
50
|
+
return { timeoutMs: rules.emptyTimeoutMs, networkIdle: true };
|
|
51
|
+
}
|
|
52
|
+
// Budget from what actually happened: a 1.5s endpoint earns a generous
|
|
53
|
+
// ceiling, an instant one fails fast instead of hanging on a fixed default.
|
|
54
|
+
const settleTimes = [
|
|
55
|
+
...triggered.map((n) => (n.settledAt ?? actionAt) - actionAt),
|
|
56
|
+
...domEvents.map((d) => d.t - actionAt),
|
|
57
|
+
];
|
|
58
|
+
const observed = settleTimes.length ? Math.max(...settleTimes) : 0;
|
|
59
|
+
const timeoutMs = Math.min(rules.maxTimeoutMs, Math.max(rules.minTimeoutMs, Math.ceil((observed * rules.timeoutSlack) / 500) * 500));
|
|
60
|
+
const wait = { timeoutMs };
|
|
61
|
+
if (network.length)
|
|
62
|
+
wait.network = network;
|
|
63
|
+
if (domAppeared.length)
|
|
64
|
+
wait.domAppeared = domAppeared;
|
|
65
|
+
if (domGone.length)
|
|
66
|
+
wait.domGone = domGone;
|
|
67
|
+
return wait;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=waits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waits.js","sourceRoot":"","sources":["../../src/compiler/waits.ts"],"names":[],"mappings":"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"}
|
package/dist/ir/io.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Repro } from './schema.js';
|
|
2
|
+
export declare const REPROS_DIR = ".repros";
|
|
3
|
+
/** Filesystem layout for one repro, all derived from the project root. */
|
|
4
|
+
export interface ReproPaths {
|
|
5
|
+
root: string;
|
|
6
|
+
reprosDir: string;
|
|
7
|
+
/** The IR itself: .repros/<name>.json */
|
|
8
|
+
ir: string;
|
|
9
|
+
/** Sidecar dir for everything that isn't the IR: .repros/<name>/ */
|
|
10
|
+
dir: string;
|
|
11
|
+
storageState: string;
|
|
12
|
+
artifactsDir: string;
|
|
13
|
+
lastResult: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Repro names become path segments, so reject anything that could escape the
|
|
17
|
+
* .repros directory or collide with the sidecar-dir convention.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertValidName(name: string): void;
|
|
20
|
+
export declare function reproPaths(name: string, root?: string): ReproPaths;
|
|
21
|
+
/**
|
|
22
|
+
* Write via a temp file in the destination directory then rename. A rename
|
|
23
|
+
* within one filesystem is atomic, so a crashed or concurrent write can never
|
|
24
|
+
* leave a half-parsed IR on disk — which matters because Phase 1's self-healer
|
|
25
|
+
* rewrites selectors in place while a replay is running.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeFileAtomic(file: string, contents: string): Promise<void>;
|
|
28
|
+
export declare function writeRepro(repro: Repro, paths: ReproPaths): Promise<void>;
|
|
29
|
+
export declare function readRepro(name: string, root?: string): Promise<Repro>;
|
|
30
|
+
export interface LastResult {
|
|
31
|
+
status: 'pass' | 'fail';
|
|
32
|
+
at: string;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
failedStepId?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function writeLastResult(paths: ReproPaths, result: LastResult): Promise<void>;
|
|
37
|
+
export declare function readLastResult(paths: ReproPaths): Promise<LastResult | null>;
|
|
38
|
+
export interface ReproSummary {
|
|
39
|
+
name: string;
|
|
40
|
+
createdAt: string | null;
|
|
41
|
+
steps: number | null;
|
|
42
|
+
lastResult: LastResult | null;
|
|
43
|
+
/** Set when the IR exists but does not parse, so `list` never hard-fails. */
|
|
44
|
+
error: string | null;
|
|
45
|
+
}
|
|
46
|
+
export declare function listRepros(root?: string): Promise<ReproSummary[]>;
|
|
47
|
+
export declare function deleteRepro(name: string, root?: string): Promise<boolean>;
|
package/dist/ir/io.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parseRepro } from './schema.js';
|
|
5
|
+
export const REPROS_DIR = '.repros';
|
|
6
|
+
const NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
7
|
+
/**
|
|
8
|
+
* Repro names become path segments, so reject anything that could escape the
|
|
9
|
+
* .repros directory or collide with the sidecar-dir convention.
|
|
10
|
+
*/
|
|
11
|
+
export function assertValidName(name) {
|
|
12
|
+
if (!NAME_RE.test(name) || name.includes('..')) {
|
|
13
|
+
throw new Error(`Invalid repro name "${name}". Use letters, digits, dot, dash and underscore; must start alphanumeric.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function reproPaths(name, root = process.cwd()) {
|
|
17
|
+
assertValidName(name);
|
|
18
|
+
const reprosDir = path.join(root, REPROS_DIR);
|
|
19
|
+
const dir = path.join(reprosDir, name);
|
|
20
|
+
return {
|
|
21
|
+
root,
|
|
22
|
+
reprosDir,
|
|
23
|
+
ir: path.join(reprosDir, `${name}.json`),
|
|
24
|
+
dir,
|
|
25
|
+
storageState: path.join(dir, 'state.json'),
|
|
26
|
+
artifactsDir: path.join(dir, 'artifacts'),
|
|
27
|
+
lastResult: path.join(dir, 'last-result.json'),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write via a temp file in the destination directory then rename. A rename
|
|
32
|
+
* within one filesystem is atomic, so a crashed or concurrent write can never
|
|
33
|
+
* leave a half-parsed IR on disk — which matters because Phase 1's self-healer
|
|
34
|
+
* rewrites selectors in place while a replay is running.
|
|
35
|
+
*/
|
|
36
|
+
export async function writeFileAtomic(file, contents) {
|
|
37
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
38
|
+
const tmp = `${file}.${randomBytes(6).toString('hex')}.tmp`;
|
|
39
|
+
try {
|
|
40
|
+
await writeFile(tmp, contents, 'utf8');
|
|
41
|
+
await rename(tmp, file);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
await rm(tmp, { force: true });
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function writeRepro(repro, paths) {
|
|
49
|
+
await writeFileAtomic(paths.ir, `${JSON.stringify(repro, null, 2)}\n`);
|
|
50
|
+
}
|
|
51
|
+
export async function readRepro(name, root = process.cwd()) {
|
|
52
|
+
const paths = reproPaths(name, root);
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = await readFile(paths.ir, 'utf8');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new Error(`No repro named "${name}". Looked for ${path.relative(root, paths.ir)}`);
|
|
59
|
+
}
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new Error(`Repro "${name}" is not valid JSON (${err.message}). File: ${path.relative(root, paths.ir)}`);
|
|
66
|
+
}
|
|
67
|
+
return parseRepro(data, path.relative(root, paths.ir));
|
|
68
|
+
}
|
|
69
|
+
export async function writeLastResult(paths, result) {
|
|
70
|
+
await writeFileAtomic(paths.lastResult, `${JSON.stringify(result, null, 2)}\n`);
|
|
71
|
+
}
|
|
72
|
+
export async function readLastResult(paths) {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(await readFile(paths.lastResult, 'utf8'));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export async function listRepros(root = process.cwd()) {
|
|
81
|
+
const reprosDir = path.join(root, REPROS_DIR);
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = await readdir(reprosDir);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const names = entries.filter((e) => e.endsWith('.json')).map((e) => e.slice(0, -'.json'.length));
|
|
90
|
+
const summaries = await Promise.all(names.map(async (name) => {
|
|
91
|
+
const paths = reproPaths(name, root);
|
|
92
|
+
const lastResult = await readLastResult(paths);
|
|
93
|
+
try {
|
|
94
|
+
const repro = await readRepro(name, root);
|
|
95
|
+
return {
|
|
96
|
+
name,
|
|
97
|
+
createdAt: repro.createdAt,
|
|
98
|
+
steps: repro.steps.length,
|
|
99
|
+
lastResult,
|
|
100
|
+
error: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
createdAt: null,
|
|
107
|
+
steps: null,
|
|
108
|
+
lastResult,
|
|
109
|
+
error: err.message.split('\n')[0] ?? 'unreadable',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
return summaries.sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''));
|
|
114
|
+
}
|
|
115
|
+
export async function deleteRepro(name, root = process.cwd()) {
|
|
116
|
+
const paths = reproPaths(name, root);
|
|
117
|
+
const existed = await stat(paths.ir).then(() => true, () => false);
|
|
118
|
+
await rm(paths.ir, { force: true });
|
|
119
|
+
await rm(paths.dir, { recursive: true, force: true });
|
|
120
|
+
return existed;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/ir/io.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Bump when a change to the IR shape is not backward-readable. */
|
|
3
|
+
export declare const IR_VERSION = 1;
|
|
4
|
+
export declare const ActionSchema: z.ZodEnum<{
|
|
5
|
+
click: "click";
|
|
6
|
+
dblclick: "dblclick";
|
|
7
|
+
fill: "fill";
|
|
8
|
+
press: "press";
|
|
9
|
+
select: "select";
|
|
10
|
+
goto: "goto";
|
|
11
|
+
scroll: "scroll";
|
|
12
|
+
hover: "hover";
|
|
13
|
+
}>;
|
|
14
|
+
export type Action = z.infer<typeof ActionSchema>;
|
|
15
|
+
/**
|
|
16
|
+
* Who produced this step. Phase 0 only ever writes "human", but the field exists
|
|
17
|
+
* now so an agent-authored step (Phase 1 `repro auto`) is the same shape, and a
|
|
18
|
+
* hybrid recording can interleave both in one stream.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AuthorSchema: z.ZodEnum<{
|
|
21
|
+
human: "human";
|
|
22
|
+
agent: "agent";
|
|
23
|
+
}>;
|
|
24
|
+
export type Author = z.infer<typeof AuthorSchema>;
|
|
25
|
+
export declare const TargetSchema: z.ZodObject<{
|
|
26
|
+
candidates: z.ZodArray<z.ZodString>;
|
|
27
|
+
semantic: z.ZodString;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export type Target = z.infer<typeof TargetSchema>;
|
|
30
|
+
export declare const NetworkWaitSchema: z.ZodObject<{
|
|
31
|
+
urlPattern: z.ZodString;
|
|
32
|
+
method: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type NetworkWait = z.infer<typeof NetworkWaitSchema>;
|
|
35
|
+
export declare const WaitAfterSchema: z.ZodObject<{
|
|
36
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
37
|
+
urlPattern: z.ZodString;
|
|
38
|
+
method: z.ZodString;
|
|
39
|
+
}, z.core.$strip>>>;
|
|
40
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
networkIdle: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
timeoutMs: z.ZodNumber;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type WaitAfter = z.infer<typeof WaitAfterSchema>;
|
|
46
|
+
export declare const StepSchema: z.ZodObject<{
|
|
47
|
+
id: z.ZodString;
|
|
48
|
+
action: z.ZodEnum<{
|
|
49
|
+
click: "click";
|
|
50
|
+
dblclick: "dblclick";
|
|
51
|
+
fill: "fill";
|
|
52
|
+
press: "press";
|
|
53
|
+
select: "select";
|
|
54
|
+
goto: "goto";
|
|
55
|
+
scroll: "scroll";
|
|
56
|
+
hover: "hover";
|
|
57
|
+
}>;
|
|
58
|
+
value: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
target: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
candidates: z.ZodArray<z.ZodString>;
|
|
61
|
+
semantic: z.ZodString;
|
|
62
|
+
}, z.core.$strip>>;
|
|
63
|
+
waitAfter: z.ZodObject<{
|
|
64
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
65
|
+
urlPattern: z.ZodString;
|
|
66
|
+
method: z.ZodString;
|
|
67
|
+
}, z.core.$strip>>>;
|
|
68
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
69
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
70
|
+
networkIdle: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
timeoutMs: z.ZodNumber;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
author: z.ZodDefault<z.ZodEnum<{
|
|
74
|
+
human: "human";
|
|
75
|
+
agent: "agent";
|
|
76
|
+
}>>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
export type Step = z.infer<typeof StepSchema>;
|
|
79
|
+
/**
|
|
80
|
+
* What the environment looked like at authoring time. Phase 0 uses this to
|
|
81
|
+
* decide whether an invariant is even checkable; Phase 1's `--expect-fixed`
|
|
82
|
+
* uses it as the definition of "the bug" that must no longer occur.
|
|
83
|
+
*/
|
|
84
|
+
export declare const ObservedAtRecordSchema: z.ZodObject<{
|
|
85
|
+
consoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
86
|
+
ambientConsoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
87
|
+
failedRequests: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
88
|
+
urlPattern: z.ZodString;
|
|
89
|
+
method: z.ZodString;
|
|
90
|
+
status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
91
|
+
}, z.core.$strip>>>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type ObservedAtRecord = z.infer<typeof ObservedAtRecordSchema>;
|
|
94
|
+
export declare const InvariantsSchema: z.ZodObject<{
|
|
95
|
+
noConsoleErrors: z.ZodDefault<z.ZodBoolean>;
|
|
96
|
+
noFailedRequests: z.ZodDefault<z.ZodBoolean>;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
export type Invariants = z.infer<typeof InvariantsSchema>;
|
|
99
|
+
export declare const FinalStateSchema: z.ZodObject<{
|
|
100
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
101
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
102
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
103
|
+
urlPattern: z.ZodString;
|
|
104
|
+
method: z.ZodString;
|
|
105
|
+
}, z.core.$strip>>>;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
export type FinalState = z.infer<typeof FinalStateSchema>;
|
|
108
|
+
export declare const AssertionSchema: z.ZodObject<{
|
|
109
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
110
|
+
"expect-bug": "expect-bug";
|
|
111
|
+
"expect-fixed": "expect-fixed";
|
|
112
|
+
}>>;
|
|
113
|
+
finalState: z.ZodObject<{
|
|
114
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
116
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
117
|
+
urlPattern: z.ZodString;
|
|
118
|
+
method: z.ZodString;
|
|
119
|
+
}, z.core.$strip>>>;
|
|
120
|
+
}, z.core.$strip>;
|
|
121
|
+
expectedWhenFixed: z.ZodOptional<z.ZodObject<{
|
|
122
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
123
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
124
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
125
|
+
urlPattern: z.ZodString;
|
|
126
|
+
method: z.ZodString;
|
|
127
|
+
}, z.core.$strip>>>;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
invariants: z.ZodObject<{
|
|
130
|
+
noConsoleErrors: z.ZodDefault<z.ZodBoolean>;
|
|
131
|
+
noFailedRequests: z.ZodDefault<z.ZodBoolean>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
observedAtRecord: z.ZodOptional<z.ZodObject<{
|
|
134
|
+
consoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
135
|
+
ambientConsoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
136
|
+
failedRequests: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
137
|
+
urlPattern: z.ZodString;
|
|
138
|
+
method: z.ZodString;
|
|
139
|
+
status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
140
|
+
}, z.core.$strip>>>;
|
|
141
|
+
}, z.core.$strip>>;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
export type Assertion = z.infer<typeof AssertionSchema>;
|
|
144
|
+
export declare const ViewportSchema: z.ZodObject<{
|
|
145
|
+
width: z.ZodNumber;
|
|
146
|
+
height: z.ZodNumber;
|
|
147
|
+
}, z.core.$strip>;
|
|
148
|
+
export type Viewport = z.infer<typeof ViewportSchema>;
|
|
149
|
+
export declare const ReproSchema: z.ZodObject<{
|
|
150
|
+
version: z.ZodLiteral<1>;
|
|
151
|
+
name: z.ZodString;
|
|
152
|
+
createdAt: z.ZodString;
|
|
153
|
+
baseUrl: z.ZodString;
|
|
154
|
+
startPath: z.ZodDefault<z.ZodString>;
|
|
155
|
+
viewport: z.ZodObject<{
|
|
156
|
+
width: z.ZodNumber;
|
|
157
|
+
height: z.ZodNumber;
|
|
158
|
+
}, z.core.$strip>;
|
|
159
|
+
storageStatePath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
160
|
+
steps: z.ZodArray<z.ZodObject<{
|
|
161
|
+
id: z.ZodString;
|
|
162
|
+
action: z.ZodEnum<{
|
|
163
|
+
click: "click";
|
|
164
|
+
dblclick: "dblclick";
|
|
165
|
+
fill: "fill";
|
|
166
|
+
press: "press";
|
|
167
|
+
select: "select";
|
|
168
|
+
goto: "goto";
|
|
169
|
+
scroll: "scroll";
|
|
170
|
+
hover: "hover";
|
|
171
|
+
}>;
|
|
172
|
+
value: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
173
|
+
target: z.ZodOptional<z.ZodObject<{
|
|
174
|
+
candidates: z.ZodArray<z.ZodString>;
|
|
175
|
+
semantic: z.ZodString;
|
|
176
|
+
}, z.core.$strip>>;
|
|
177
|
+
waitAfter: z.ZodObject<{
|
|
178
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
179
|
+
urlPattern: z.ZodString;
|
|
180
|
+
method: z.ZodString;
|
|
181
|
+
}, z.core.$strip>>>;
|
|
182
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
183
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
184
|
+
networkIdle: z.ZodOptional<z.ZodBoolean>;
|
|
185
|
+
timeoutMs: z.ZodNumber;
|
|
186
|
+
}, z.core.$strip>;
|
|
187
|
+
author: z.ZodDefault<z.ZodEnum<{
|
|
188
|
+
human: "human";
|
|
189
|
+
agent: "agent";
|
|
190
|
+
}>>;
|
|
191
|
+
}, z.core.$strip>>;
|
|
192
|
+
assertion: z.ZodObject<{
|
|
193
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
194
|
+
"expect-bug": "expect-bug";
|
|
195
|
+
"expect-fixed": "expect-fixed";
|
|
196
|
+
}>>;
|
|
197
|
+
finalState: z.ZodObject<{
|
|
198
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
199
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
200
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
201
|
+
urlPattern: z.ZodString;
|
|
202
|
+
method: z.ZodString;
|
|
203
|
+
}, z.core.$strip>>>;
|
|
204
|
+
}, z.core.$strip>;
|
|
205
|
+
expectedWhenFixed: z.ZodOptional<z.ZodObject<{
|
|
206
|
+
domAppeared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
207
|
+
domGone: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
208
|
+
network: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
209
|
+
urlPattern: z.ZodString;
|
|
210
|
+
method: z.ZodString;
|
|
211
|
+
}, z.core.$strip>>>;
|
|
212
|
+
}, z.core.$strip>>;
|
|
213
|
+
invariants: z.ZodObject<{
|
|
214
|
+
noConsoleErrors: z.ZodDefault<z.ZodBoolean>;
|
|
215
|
+
noFailedRequests: z.ZodDefault<z.ZodBoolean>;
|
|
216
|
+
}, z.core.$strip>;
|
|
217
|
+
observedAtRecord: z.ZodOptional<z.ZodObject<{
|
|
218
|
+
consoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
219
|
+
ambientConsoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
220
|
+
failedRequests: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
221
|
+
urlPattern: z.ZodString;
|
|
222
|
+
method: z.ZodString;
|
|
223
|
+
status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
224
|
+
}, z.core.$strip>>>;
|
|
225
|
+
}, z.core.$strip>>;
|
|
226
|
+
}, z.core.$strip>;
|
|
227
|
+
}, z.core.$strip>;
|
|
228
|
+
export type Repro = z.infer<typeof ReproSchema>;
|
|
229
|
+
export declare class IRValidationError extends Error {
|
|
230
|
+
readonly file: string;
|
|
231
|
+
readonly issues: z.core.$ZodIssue[];
|
|
232
|
+
constructor(file: string, issues: z.core.$ZodIssue[]);
|
|
233
|
+
private static format;
|
|
234
|
+
}
|
|
235
|
+
/** Parse and validate an unknown value as a Repro, or throw IRValidationError. */
|
|
236
|
+
export declare function parseRepro(data: unknown, file: string): Repro;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Bump when a change to the IR shape is not backward-readable. */
|
|
3
|
+
export const IR_VERSION = 1;
|
|
4
|
+
export const ActionSchema = z.enum([
|
|
5
|
+
'click',
|
|
6
|
+
'dblclick',
|
|
7
|
+
'fill',
|
|
8
|
+
'press',
|
|
9
|
+
'select',
|
|
10
|
+
'goto',
|
|
11
|
+
'scroll',
|
|
12
|
+
'hover',
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Who produced this step. Phase 0 only ever writes "human", but the field exists
|
|
16
|
+
* now so an agent-authored step (Phase 1 `repro auto`) is the same shape, and a
|
|
17
|
+
* hybrid recording can interleave both in one stream.
|
|
18
|
+
*/
|
|
19
|
+
export const AuthorSchema = z.enum(['human', 'agent']);
|
|
20
|
+
export const TargetSchema = z.object({
|
|
21
|
+
/**
|
|
22
|
+
* Selector strings in priority order, highest-confidence first. Every entry is
|
|
23
|
+
* a valid Playwright selector so the replayer stays dumb: it tries them in
|
|
24
|
+
* order and never rewrites them. A future self-healer patches this array.
|
|
25
|
+
*/
|
|
26
|
+
candidates: z.array(z.string().min(1)).min(1),
|
|
27
|
+
/**
|
|
28
|
+
* Rule-based, human-readable description of the element, derived from role,
|
|
29
|
+
* accessible name and nearest row/section label. No LLM involved. Used in
|
|
30
|
+
* failure messages, and as the re-grounding prompt for Phase 1 self-heal.
|
|
31
|
+
*/
|
|
32
|
+
semantic: z.string(),
|
|
33
|
+
});
|
|
34
|
+
export const NetworkWaitSchema = z.object({
|
|
35
|
+
/** Recorded URL with volatile path/query IDs normalized to `*`. */
|
|
36
|
+
urlPattern: z.string().min(1),
|
|
37
|
+
method: z.string().min(1),
|
|
38
|
+
});
|
|
39
|
+
export const WaitAfterSchema = z.object({
|
|
40
|
+
network: z.array(NetworkWaitSchema).optional(),
|
|
41
|
+
domAppeared: z.array(z.string()).optional(),
|
|
42
|
+
domGone: z.array(z.string()).optional(),
|
|
43
|
+
/** Set when the action produced no observable reaction at all. */
|
|
44
|
+
networkIdle: z.boolean().optional(),
|
|
45
|
+
/** Ceiling for the whole wait, not a per-signal budget. */
|
|
46
|
+
timeoutMs: z.number().int().positive(),
|
|
47
|
+
});
|
|
48
|
+
export const StepSchema = z.object({
|
|
49
|
+
id: z.string().min(1),
|
|
50
|
+
action: ActionSchema,
|
|
51
|
+
/** Payload for fill/press/select/goto/scroll. Null for click-likes. */
|
|
52
|
+
value: z.string().nullable().default(null),
|
|
53
|
+
/** Absent for `goto`, which addresses a URL rather than an element. */
|
|
54
|
+
target: TargetSchema.optional(),
|
|
55
|
+
waitAfter: WaitAfterSchema,
|
|
56
|
+
author: AuthorSchema.default('human'),
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* What the environment looked like at authoring time. Phase 0 uses this to
|
|
60
|
+
* decide whether an invariant is even checkable; Phase 1's `--expect-fixed`
|
|
61
|
+
* uses it as the definition of "the bug" that must no longer occur.
|
|
62
|
+
*/
|
|
63
|
+
export const ObservedAtRecordSchema = z.object({
|
|
64
|
+
consoleErrors: z.array(z.string()).default([]),
|
|
65
|
+
/**
|
|
66
|
+
* Console output seen while the app booted, before the user acted.
|
|
67
|
+
*
|
|
68
|
+
* This app's own background noise, captured per repro rather than guessed at
|
|
69
|
+
* by a global pattern list. Replay subtracts it, so an error the app always
|
|
70
|
+
* (or intermittently) logs cannot be mistaken for the bug.
|
|
71
|
+
*/
|
|
72
|
+
ambientConsoleErrors: z.array(z.string()).default([]),
|
|
73
|
+
failedRequests: z
|
|
74
|
+
.array(z.object({
|
|
75
|
+
urlPattern: z.string(),
|
|
76
|
+
method: z.string(),
|
|
77
|
+
status: z.number().int().nullable().default(null),
|
|
78
|
+
}))
|
|
79
|
+
.default([]),
|
|
80
|
+
});
|
|
81
|
+
export const InvariantsSchema = z.object({
|
|
82
|
+
noConsoleErrors: z.boolean().default(true),
|
|
83
|
+
noFailedRequests: z.boolean().default(true),
|
|
84
|
+
});
|
|
85
|
+
export const FinalStateSchema = z.object({
|
|
86
|
+
domAppeared: z.array(z.string()).optional(),
|
|
87
|
+
domGone: z.array(z.string()).optional(),
|
|
88
|
+
network: z.array(NetworkWaitSchema).optional(),
|
|
89
|
+
});
|
|
90
|
+
export const AssertionSchema = z.object({
|
|
91
|
+
/**
|
|
92
|
+
* Explicit rather than implied, so both polarities are expressible:
|
|
93
|
+
* expect-bug — the recorded final state must occur (Phase 0 `repro run`)
|
|
94
|
+
* expect-fixed — the flow completes, the bug does NOT occur, invariants hold
|
|
95
|
+
* Phase 0 always records "expect-bug"; the run mode is selected at replay time.
|
|
96
|
+
*/
|
|
97
|
+
mode: z.enum(['expect-bug', 'expect-fixed']).default('expect-bug'),
|
|
98
|
+
finalState: FinalStateSchema,
|
|
99
|
+
/**
|
|
100
|
+
* What must be TRUE once the bug is fixed. Hand-written; nothing derives it.
|
|
101
|
+
*
|
|
102
|
+
* `finalState` describes the buggy end state, so it cannot double as a fix
|
|
103
|
+
* criterion. Without this, a bug that leaves no console error and no failed
|
|
104
|
+
* request — a missing element, a wrong number, a broken layout — gives
|
|
105
|
+
* `--expect-fixed` nothing to check, and a tool that reports success after
|
|
106
|
+
* checking nothing is worse than one that refuses to answer.
|
|
107
|
+
*/
|
|
108
|
+
expectedWhenFixed: FinalStateSchema.optional(),
|
|
109
|
+
invariants: InvariantsSchema,
|
|
110
|
+
observedAtRecord: ObservedAtRecordSchema.optional(),
|
|
111
|
+
});
|
|
112
|
+
export const ViewportSchema = z.object({
|
|
113
|
+
width: z.number().int().positive(),
|
|
114
|
+
height: z.number().int().positive(),
|
|
115
|
+
});
|
|
116
|
+
export const ReproSchema = z.object({
|
|
117
|
+
version: z.literal(IR_VERSION),
|
|
118
|
+
name: z.string().min(1),
|
|
119
|
+
createdAt: z.string(),
|
|
120
|
+
baseUrl: z.string().url(),
|
|
121
|
+
/** Path recorded at start, appended to baseUrl when replay begins. */
|
|
122
|
+
startPath: z.string().default('/'),
|
|
123
|
+
viewport: ViewportSchema,
|
|
124
|
+
/** Project-root-relative, e.g. ".repros/<name>/state.json". Null if nothing worth persisting. */
|
|
125
|
+
storageStatePath: z.string().nullable().default(null),
|
|
126
|
+
steps: z.array(StepSchema),
|
|
127
|
+
assertion: AssertionSchema,
|
|
128
|
+
});
|
|
129
|
+
export class IRValidationError extends Error {
|
|
130
|
+
file;
|
|
131
|
+
issues;
|
|
132
|
+
constructor(file, issues) {
|
|
133
|
+
super(IRValidationError.format(file, issues));
|
|
134
|
+
this.file = file;
|
|
135
|
+
this.issues = issues;
|
|
136
|
+
this.name = 'IRValidationError';
|
|
137
|
+
}
|
|
138
|
+
static format(file, issues) {
|
|
139
|
+
const lines = issues.map((i) => {
|
|
140
|
+
const path = i.path.length ? i.path.join('.') : '(root)';
|
|
141
|
+
return ` ${path}: ${i.message}`;
|
|
142
|
+
});
|
|
143
|
+
return `Invalid repro IR in ${file}\n${lines.join('\n')}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Parse and validate an unknown value as a Repro, or throw IRValidationError. */
|
|
147
|
+
export function parseRepro(data, file) {
|
|
148
|
+
const result = ReproSchema.safeParse(data);
|
|
149
|
+
if (!result.success)
|
|
150
|
+
throw new IRValidationError(file, result.error.issues);
|
|
151
|
+
return result.data;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/ir/schema.ts"],"names":[],"mappings":"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"}
|