sitelooper 0.3.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 +625 -0
- package/bin/sitelooper.js +6 -0
- package/dist/agent/llm.js +460 -0
- package/dist/agent/llm.js.map +1 -0
- package/dist/agent/loop.js +870 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/prompt.js +40 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/report.js +545 -0
- package/dist/agent/report.js.map +1 -0
- package/dist/agent/tools.js +1147 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/cli.js +1692 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon/browser.js +218 -0
- package/dist/daemon/browser.js.map +1 -0
- package/dist/daemon/codegen.js +241 -0
- package/dist/daemon/codegen.js.map +1 -0
- package/dist/daemon/dialogs.js +57 -0
- package/dist/daemon/dialogs.js.map +1 -0
- package/dist/daemon/diff.js +198 -0
- package/dist/daemon/diff.js.map +1 -0
- package/dist/daemon/fingerprint.js +98 -0
- package/dist/daemon/fingerprint.js.map +1 -0
- package/dist/daemon/inputs.js +134 -0
- package/dist/daemon/inputs.js.map +1 -0
- package/dist/daemon/recorder.js +1232 -0
- package/dist/daemon/recorder.js.map +1 -0
- package/dist/daemon/refs.js +194 -0
- package/dist/daemon/refs.js.map +1 -0
- package/dist/daemon/server.js +1724 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/state.js +239 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/doctor.js +90 -0
- package/dist/doctor.js.map +1 -0
- package/dist/shared/paths.js +80 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/protocol.js +28 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/shared/secrets.js +92 -0
- package/dist/shared/secrets.js.map +1 -0
- package/dist/shared/text.js +39 -0
- package/dist/shared/text.js.map +1 -0
- package/dist/skills/compile.js +1420 -0
- package/dist/skills/compile.js.map +1 -0
- package/dist/skills/components.js +456 -0
- package/dist/skills/components.js.map +1 -0
- package/dist/skills/flow.js +1041 -0
- package/dist/skills/flow.js.map +1 -0
- package/dist/skills/learn.js +406 -0
- package/dist/skills/learn.js.map +1 -0
- package/dist/skills/ledger.js +304 -0
- package/dist/skills/ledger.js.map +1 -0
- package/dist/skills/relabel.js +206 -0
- package/dist/skills/relabel.js.map +1 -0
- package/dist/skills/repair.js +570 -0
- package/dist/skills/repair.js.map +1 -0
- package/dist/skills/replay.js +1281 -0
- package/dist/skills/replay.js.map +1 -0
- package/dist/skills/store.js +147 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/spec/check.js +428 -0
- package/dist/spec/check.js.map +1 -0
- package/dist/spec/diagnostics.js +58 -0
- package/dist/spec/diagnostics.js.map +1 -0
- package/dist/spec/emit.js +2084 -0
- package/dist/spec/emit.js.map +1 -0
- package/dist/spec/index.js +62 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/spec/ir.js +216 -0
- package/dist/spec/ir.js.map +1 -0
- package/dist/spec/lift.js +162 -0
- package/dist/spec/lift.js.map +1 -0
- package/dist/spec/locators.js +270 -0
- package/dist/spec/locators.js.map +1 -0
- package/dist/spec/lower.js +124 -0
- package/dist/spec/lower.js.map +1 -0
- package/dist/spec/repair.js +657 -0
- package/dist/spec/repair.js.map +1 -0
- package/dist/spec/rerecord.js +169 -0
- package/dist/spec/rerecord.js.map +1 -0
- package/dist/spec/rethread.js +120 -0
- package/dist/spec/rethread.js.map +1 -0
- package/package.json +50 -0
- package/skills/sitelooper/SKILL.md +228 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// Does the file we just wrote actually WORK?
|
|
2
|
+
//
|
|
3
|
+
// `sitelooper repair` answers a narrower question than it looks like it does.
|
|
4
|
+
// It lifts the owned `.flow.ts` back to IR, replays that IR through the daemon,
|
|
5
|
+
// folds what the recovery ladder learned back in, and re-emits the file. Every
|
|
6
|
+
// one of those steps runs on the IR — so a defect in the EMITTER (a locator
|
|
7
|
+
// expression that lowers correctly for replay but transpiles to a Playwright
|
|
8
|
+
// call that never resolves, a step body emitted in the wrong order, a var that
|
|
9
|
+
// reaches the scaffold under a name nothing sets) is invisible to it. Cloud set
|
|
10
|
+
// 2 is the proof: repair reported "converged, 5/5, no changes" on kanboard and
|
|
11
|
+
// wrote the file, while the emitted spec failed deterministically under plain
|
|
12
|
+
// Playwright on the very first assertion.
|
|
13
|
+
//
|
|
14
|
+
// This module closes that blind spot the only way it can be closed: by running
|
|
15
|
+
// the emitted spec as a user would — the Playwright test runner over the two files,
|
|
16
|
+
// with no sitelooper runtime anywhere in the process — and reporting what came
|
|
17
|
+
// back. It is a CHECK, not a gate on the write: the diff is still the
|
|
18
|
+
// reviewer's, and a spec that fails here is information they need, not a reason
|
|
19
|
+
// to throw the repair away. What it does change is the exit code (4) and the
|
|
20
|
+
// sentence the reviewer reads.
|
|
21
|
+
//
|
|
22
|
+
// The split below is the usual one: everything that parses (the Playwright JSON
|
|
23
|
+
// report, an error's stack, the `@step` anchor above a source line) is pure and
|
|
24
|
+
// unit-tested against fixture strings; the one impure function spawns the
|
|
25
|
+
// runner. `bench/spec-replay.mjs` is the source of the config/reporter shape —
|
|
26
|
+
// this deliberately mirrors it rather than inventing a second convention.
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { spawnSync } from 'node:child_process';
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
|
+
/** Strip the ANSI colouring Playwright puts in `error.message`. */
|
|
32
|
+
function plain(s) {
|
|
33
|
+
// eslint-disable-next-line no-control-regex
|
|
34
|
+
return s.replace(/\u001b\[[0-9;]*m/g, '');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One stdout/stderr chunk from the JSON reporter as text. Playwright records
|
|
38
|
+
* each console write as `{ text }` or, for binary writes, `{ buffer }`
|
|
39
|
+
* (base64) — a compiled spec never writes binary, but decode it anyway rather
|
|
40
|
+
* than dropping a chunk that happens to take that shape. (Same rule as
|
|
41
|
+
* bench/spec-replay.mjs; the two must agree or a drift line counted there
|
|
42
|
+
* would go missing here.)
|
|
43
|
+
*/
|
|
44
|
+
function chunkText(c) {
|
|
45
|
+
if (typeof c === 'string')
|
|
46
|
+
return c;
|
|
47
|
+
const o = c;
|
|
48
|
+
if (typeof o?.text === 'string')
|
|
49
|
+
return o.text;
|
|
50
|
+
if (typeof o?.buffer === 'string') {
|
|
51
|
+
try {
|
|
52
|
+
return Buffer.from(o.buffer, 'base64').toString('utf8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
/** Every `[sitelooper <tag>] …` line one test result logged, on either stream. */
|
|
61
|
+
function taggedLines(result, tag) {
|
|
62
|
+
const chunks = [...(result?.stdout ?? []), ...(result?.stderr ?? [])];
|
|
63
|
+
return chunks
|
|
64
|
+
.map(chunkText)
|
|
65
|
+
.join('')
|
|
66
|
+
.split(/\r?\n/)
|
|
67
|
+
.map((l) => plain(l).trim())
|
|
68
|
+
.filter((l) => l.startsWith(`[sitelooper ${tag}]`));
|
|
69
|
+
}
|
|
70
|
+
/** A filename, as a regex literal. */
|
|
71
|
+
function escapeRe(s) {
|
|
72
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse a stack frame (or a Playwright `error.location`) out of one error.
|
|
76
|
+
*
|
|
77
|
+
* Only frames in the flow/spec files themselves are of interest: the top of a
|
|
78
|
+
* Playwright failure stack is usually inside the runner, and the line a reader
|
|
79
|
+
* needs is the emitted gesture that asked for the locator.
|
|
80
|
+
*/
|
|
81
|
+
export function errorSites(error, files) {
|
|
82
|
+
const wanted = files.map((f) => path.basename(f));
|
|
83
|
+
const out = [];
|
|
84
|
+
const add = (file, line) => {
|
|
85
|
+
if (!out.some((s) => s.file === file && s.line === line))
|
|
86
|
+
out.push({ file, line });
|
|
87
|
+
};
|
|
88
|
+
const loc = error?.location;
|
|
89
|
+
const locFile = typeof loc?.file === 'string' ? loc.file : null;
|
|
90
|
+
if (locFile && typeof loc?.line === 'number' && wanted.some((w) => locFile.endsWith(w)))
|
|
91
|
+
add(locFile, loc.line);
|
|
92
|
+
const text = plain(`${typeof error?.stack === 'string' ? error.stack : ''}\n${typeof error?.message === 'string' ? error.message : ''}`);
|
|
93
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
94
|
+
const line = raw.trim();
|
|
95
|
+
for (const w of wanted) {
|
|
96
|
+
// Matches both "at fn (C:\…\x.flow.ts:41:22)" and a bare "…/x.flow.ts:41:22".
|
|
97
|
+
const m = line.match(new RegExp(`([^\\s()]*${escapeRe(w)}):(\\d+)(?::(\\d+))?`));
|
|
98
|
+
if (m)
|
|
99
|
+
add(m[1], Number(m[2]));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/** The topmost frame of the failure that lands in the emitted files. */
|
|
105
|
+
export function errorSite(error, files) {
|
|
106
|
+
return errorSites(error, files)[0] ?? null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The nearest `// @step <id> <segment>/<index>` marker at or above `line`.
|
|
110
|
+
*
|
|
111
|
+
* The emitter writes one of these above every gesture precisely so a stack
|
|
112
|
+
* line can be turned back into something a reviewer recognises — see
|
|
113
|
+
* src/spec/emit.ts. `line` is 1-based, the way stacks and Playwright count.
|
|
114
|
+
*/
|
|
115
|
+
export function findStepAnchor(source, line) {
|
|
116
|
+
const lines = source.split(/\r?\n/);
|
|
117
|
+
for (let i = Math.min(line, lines.length) - 1; i >= 0; i--) {
|
|
118
|
+
const m = lines[i]?.match(/^\s*\/\/\s*@step\s+(.+?)\s*$/);
|
|
119
|
+
if (m)
|
|
120
|
+
return m[1];
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/** Walk the Playwright JSON reporter's suite tree, flattening to one row per test. */
|
|
125
|
+
function flattenTests(suite, files, acc = []) {
|
|
126
|
+
for (const s of suite.suites ?? [])
|
|
127
|
+
flattenTests(s, files, acc);
|
|
128
|
+
for (const spec of suite.specs ?? []) {
|
|
129
|
+
for (const t of spec.tests ?? []) {
|
|
130
|
+
const r = (t.results ?? [])[0];
|
|
131
|
+
const err = (r?.error ?? (r?.errors ?? [])[0]);
|
|
132
|
+
const sites = errorSites(err, files);
|
|
133
|
+
const message = typeof err?.message === 'string' ? plain(err.message).trim() : null;
|
|
134
|
+
acc.push({
|
|
135
|
+
title: String(spec.title ?? ''),
|
|
136
|
+
status: String(t.status ?? ''),
|
|
137
|
+
ok: t.status === 'expected',
|
|
138
|
+
durationMs: typeof r?.duration === 'number' ? r.duration : null,
|
|
139
|
+
error: message,
|
|
140
|
+
errorStack: typeof err?.stack === 'string' ? plain(err.stack) : null,
|
|
141
|
+
errorFile: sites[0]?.file ?? null,
|
|
142
|
+
errorLine: sites[0]?.line ?? null,
|
|
143
|
+
errorSites: sites,
|
|
144
|
+
drift: taggedLines(r, 'drift'),
|
|
145
|
+
satisfied: taggedLines(r, 'satisfied'),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return acc;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The JSON reporter's output, reduced to the handful of facts a verdict needs.
|
|
153
|
+
*
|
|
154
|
+
* An empty report is deliberately NOT a pass: a spec whose module failed to
|
|
155
|
+
* load (the emitter's most likely way to be wrong) produces a report with no
|
|
156
|
+
* test results at all, and calling that "0 failures" is the exact blind spot
|
|
157
|
+
* this module exists to close.
|
|
158
|
+
*/
|
|
159
|
+
export function parseSpecReport(report, files = []) {
|
|
160
|
+
const root = (report ?? {});
|
|
161
|
+
const tests = flattenTests(root, files);
|
|
162
|
+
const failing = tests.find((t) => !t.ok && t.status !== 'skipped') ?? null;
|
|
163
|
+
const stats = root.stats;
|
|
164
|
+
const durationMs = typeof stats?.duration === 'number' ? stats.duration : tests.reduce((n, t) => n + (t.durationMs ?? 0), 0);
|
|
165
|
+
return {
|
|
166
|
+
tests,
|
|
167
|
+
passed: tests.length > 0 && tests.every((t) => t.ok),
|
|
168
|
+
durationMs,
|
|
169
|
+
drift: tests.flatMap((t) => t.drift),
|
|
170
|
+
satisfied: tests.flatMap((t) => t.satisfied),
|
|
171
|
+
error: failing?.error ?? null,
|
|
172
|
+
errorFile: failing?.errorFile ?? null,
|
|
173
|
+
errorLine: failing?.errorLine ?? null,
|
|
174
|
+
errorSites: failing?.errorSites ?? [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** One line of the failure, short enough to read in a terminal. */
|
|
178
|
+
function shortError(message) {
|
|
179
|
+
if (!message)
|
|
180
|
+
return 'the spec failed with no error message';
|
|
181
|
+
const first = message
|
|
182
|
+
.split(/\r?\n/)
|
|
183
|
+
.map((l) => l.trim())
|
|
184
|
+
.filter(Boolean)
|
|
185
|
+
.slice(0, 2)
|
|
186
|
+
.join(' ');
|
|
187
|
+
return first.length > 200 ? `${first.slice(0, 197)}…` : first;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The diagnostic for the step a failing anchor names, if it has one.
|
|
191
|
+
*
|
|
192
|
+
* An anchor is `"08-open s_c86522/1"` — step id, pinned skill, step index —
|
|
193
|
+
* so the step id is its first token, and that is what the flagged map is
|
|
194
|
+
* keyed by.
|
|
195
|
+
*/
|
|
196
|
+
function flaggedStep(anchor, flagged) {
|
|
197
|
+
if (!anchor || !flagged?.size)
|
|
198
|
+
return null;
|
|
199
|
+
return flagged.get(anchor.split(/\s+/)[0]) ?? null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The verdict, in one paragraph.
|
|
203
|
+
*
|
|
204
|
+
* The "emitter defect, not drift" clause is the whole point of the sentence
|
|
205
|
+
* and is only earned in the repair path: there, the same procedure has just
|
|
206
|
+
* replayed cleanly through the daemon N times, so a failure HERE cannot be the
|
|
207
|
+
* app moving — it is the compilation of that procedure into Playwright. The
|
|
208
|
+
* standalone `check` command has no such evidence and does not claim it.
|
|
209
|
+
*
|
|
210
|
+
* And it is not claimed AT ALL for a step repair has already flagged as
|
|
211
|
+
* needing a re-record. sp8od is why: 08-open's demoted pin never ran, a
|
|
212
|
+
* read-only skill covered it, the live replay went 9/9 tier A — and this
|
|
213
|
+
* sentence then told the reviewer to go and fix the emitter. When `flagged`
|
|
214
|
+
* carries the failing anchor's step, the verdict names the recording instead.
|
|
215
|
+
*/
|
|
216
|
+
export function verdictFor(r, liveReplayPassed, flagged) {
|
|
217
|
+
if (!r.ran)
|
|
218
|
+
return `spec check: skipped — ${r.skipped ?? 'the spec was not run'}`;
|
|
219
|
+
const secs = Math.max(1, Math.round(r.durationMs / 1000));
|
|
220
|
+
// A pass that skipped a step because its work was already done is not the
|
|
221
|
+
// same pass as one that ran everything, and the reader has to be told which
|
|
222
|
+
// it was: the procedure under test never executed for that step.
|
|
223
|
+
const already = r.satisfied?.length ? `, ${r.satisfied.length} already satisfied` : '';
|
|
224
|
+
if (r.passed)
|
|
225
|
+
return `spec check: passed in ${secs} s, ${r.driftCount} drift${already}`;
|
|
226
|
+
const where = r.anchor ? ` at @step ${r.anchor}` : r.errorLine ? ` at ${path.basename(r.errorFile ?? '')}:${r.errorLine}` : '';
|
|
227
|
+
const why = flaggedStep(r.anchor, flagged)
|
|
228
|
+
? ` — the step's recording is the problem, not the emitter: ${flaggedStep(r.anchor, flagged).what}`
|
|
229
|
+
: liveReplayPassed
|
|
230
|
+
? ' — this is an emitter defect, not drift: the live replay passed this step'
|
|
231
|
+
: ' — run it yourself with the config in the workspace below to see the full trace';
|
|
232
|
+
const timeout = r.timedOut ? ' (the runner was killed on timeout)' : '';
|
|
233
|
+
return `spec check: FAILED${where} — ${shortError(r.error)}${why}${timeout}`;
|
|
234
|
+
}
|
|
235
|
+
/** The env var a run var reaches the scaffold under, per emitSpecFile. */
|
|
236
|
+
export function envName(name) {
|
|
237
|
+
return name.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Is `@playwright/test` resolvable from where the spec will run, and where is
|
|
241
|
+
* its runner?
|
|
242
|
+
*
|
|
243
|
+
* Both halves matter. The emitted files `import { expect } from
|
|
244
|
+
* '@playwright/test'`, so Node has to resolve that package from the directory
|
|
245
|
+
* the spec sits in — which is why the check runs beside the owned file rather
|
|
246
|
+
* than in the system temp dir, where nothing resolves. And the runner is
|
|
247
|
+
* spawned as `node <pkg>/cli.js` rather than `npx playwright`: npx run from a
|
|
248
|
+
* directory that cannot see the package DOWNLOADS a fresh copy of playwright
|
|
249
|
+
* and then fails anyway on the missing `@playwright/test`, which reads as a
|
|
250
|
+
* spec failure and is not one.
|
|
251
|
+
*/
|
|
252
|
+
export function resolvePlaywrightTest(fromDir) {
|
|
253
|
+
for (const base of [fromDir, process.cwd()]) {
|
|
254
|
+
try {
|
|
255
|
+
const main = createRequire(path.join(base, 'noop.cjs')).resolve('@playwright/test');
|
|
256
|
+
const cli = path.join(path.dirname(main), 'cli.js');
|
|
257
|
+
return { main, cli: fs.existsSync(cli) ? cli : null };
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
/* try the next root */
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Run the emitted spec once under plain `@playwright/test`, in a temp dir with
|
|
267
|
+
* a minimal config, and report what happened.
|
|
268
|
+
*
|
|
269
|
+
* Copies rather than running in place, for the same reason spec-replay.mjs
|
|
270
|
+
* does: the config names its own `testDir`/`testMatch`, so nothing in the
|
|
271
|
+
* user's project (a root playwright.config, another spec, a global setup) can
|
|
272
|
+
* change what this measures. Reset-command failure is fatal to the CHECK only
|
|
273
|
+
* — it comes back as a skip, not a spec failure, because a spec that never ran
|
|
274
|
+
* has said nothing about the emitter.
|
|
275
|
+
*/
|
|
276
|
+
export function runSpecCheck(o) {
|
|
277
|
+
const flowFile = path.resolve(o.flowFile);
|
|
278
|
+
const say = o.onProgress ?? (() => { });
|
|
279
|
+
const base = path.basename(flowFile).replace(/\.flow\.ts$/, '');
|
|
280
|
+
const dir = path.dirname(flowFile);
|
|
281
|
+
const specSrc = path.join(dir, `${base}.spec.ts`);
|
|
282
|
+
const empty = {
|
|
283
|
+
ran: false,
|
|
284
|
+
skipped: null,
|
|
285
|
+
passed: false,
|
|
286
|
+
durationMs: 0,
|
|
287
|
+
exitCode: null,
|
|
288
|
+
timedOut: false,
|
|
289
|
+
error: null,
|
|
290
|
+
anchor: null,
|
|
291
|
+
errorFile: null,
|
|
292
|
+
errorLine: null,
|
|
293
|
+
drift: [],
|
|
294
|
+
driftCount: 0,
|
|
295
|
+
satisfied: [],
|
|
296
|
+
workspace: null,
|
|
297
|
+
specFile: null,
|
|
298
|
+
};
|
|
299
|
+
const skip = (why) => {
|
|
300
|
+
const r = { ...empty, skipped: why };
|
|
301
|
+
return { ...r, verdict: verdictFor(r, o.liveReplayPassed ?? false, o.flagged) };
|
|
302
|
+
};
|
|
303
|
+
if (!fs.existsSync(specSrc))
|
|
304
|
+
return skip(`no ${base}.spec.ts beside ${path.basename(flowFile)} — compile it first`);
|
|
305
|
+
const pw = resolvePlaywrightTest(dir);
|
|
306
|
+
if (!pw) {
|
|
307
|
+
return skip('@playwright/test could not be resolved from this project (`npm install -D @playwright/test`); the emitted spec imports it directly, so it was not run');
|
|
308
|
+
}
|
|
309
|
+
// Beside the owned file, NOT in os.tmpdir(): the copies have to keep the same
|
|
310
|
+
// node_modules ancestry as the original, or the spec's own first import fails
|
|
311
|
+
// and the check reports a module error as if it were an emitter defect. The
|
|
312
|
+
// directory is removed again when the spec passes, and kept when it does not
|
|
313
|
+
// — a failure is exactly when someone wants the config, the log and the trace.
|
|
314
|
+
const work = fs.mkdtempSync(path.join(dir, '.sitelooper-check-'));
|
|
315
|
+
const flowCopy = path.join(work, `${base}.flow.ts`);
|
|
316
|
+
const specCopy = path.join(work, `${base}.spec.ts`);
|
|
317
|
+
fs.copyFileSync(flowFile, flowCopy);
|
|
318
|
+
fs.copyFileSync(specSrc, specCopy);
|
|
319
|
+
const reportFile = path.join(work, 'pw-report.json');
|
|
320
|
+
const configFile = path.join(work, 'playwright.config.mjs');
|
|
321
|
+
// No `defineConfig` import: a plain object is a valid config module, and
|
|
322
|
+
// this way the config itself never depends on resolving @playwright/test
|
|
323
|
+
// from the temp dir. Same shape as bench/spec-replay.mjs.
|
|
324
|
+
fs.writeFileSync(configFile, `// GENERATED by sitelooper --check-spec — safe to delete.
|
|
325
|
+
export default {
|
|
326
|
+
testDir: ${JSON.stringify(work)},
|
|
327
|
+
testMatch: ${JSON.stringify(`${base}.spec.ts`)},
|
|
328
|
+
timeout: 600_000,
|
|
329
|
+
retries: 0,
|
|
330
|
+
workers: 1,
|
|
331
|
+
reporter: [['json', { outputFile: ${JSON.stringify(reportFile)} }], ['list']],
|
|
332
|
+
use: { headless: true },
|
|
333
|
+
};
|
|
334
|
+
`);
|
|
335
|
+
if (o.resetCmd) {
|
|
336
|
+
say(` spec check reset: ${o.resetCmd}`);
|
|
337
|
+
const reset = spawnSync(o.resetCmd, { shell: true, encoding: 'utf8', timeout: 120_000 });
|
|
338
|
+
if (reset.error || reset.status !== 0) {
|
|
339
|
+
return skip(`the --reset-cmd exited ${reset.status ?? reset.error?.message ?? 'by signal'} before the spec check could run`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// The scaffold reads every run var from process.env under its uppercased
|
|
343
|
+
// name (emitSpecFile), so that is the only channel the values have.
|
|
344
|
+
const env = { ...process.env };
|
|
345
|
+
for (const [k, v] of Object.entries(o.vars ?? {}))
|
|
346
|
+
env[envName(k)] = v;
|
|
347
|
+
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
348
|
+
const cmd = pw.cli ? [process.execPath, pw.cli] : [npx, 'playwright'];
|
|
349
|
+
const argv = [...cmd.slice(1), 'test', '--config', configFile];
|
|
350
|
+
say(` spec check: ${cmd[0]} ${argv.join(' ')}`);
|
|
351
|
+
const started = Date.now();
|
|
352
|
+
const run = spawnSync(cmd[0], argv, {
|
|
353
|
+
encoding: 'utf8',
|
|
354
|
+
cwd: work,
|
|
355
|
+
env,
|
|
356
|
+
timeout: o.timeoutMs ?? 900_000,
|
|
357
|
+
shell: !pw.cli && process.platform === 'win32',
|
|
358
|
+
});
|
|
359
|
+
const wallMs = Date.now() - started;
|
|
360
|
+
fs.writeFileSync(path.join(work, 'run.log'), `${run.stdout ?? ''}${run.stderr ?? ''}`);
|
|
361
|
+
let report = null;
|
|
362
|
+
try {
|
|
363
|
+
report = JSON.parse(fs.readFileSync(reportFile, 'utf8'));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
/* the spec crashed before the reporter could write — handled below */
|
|
367
|
+
}
|
|
368
|
+
const parsed = parseSpecReport(report, [flowCopy, specCopy]);
|
|
369
|
+
const timedOut = run.status === null;
|
|
370
|
+
// No report at all is a failure with the runner's own output as the message:
|
|
371
|
+
// a module that would not load never reaches the reporter.
|
|
372
|
+
const error = parsed.error ??
|
|
373
|
+
(parsed.tests.length
|
|
374
|
+
? null
|
|
375
|
+
: shortError(plain(`${run.stderr ?? ''}${run.stdout ?? ''}`).trim() || 'playwright wrote no JSON report'));
|
|
376
|
+
// The frame worth naming is the first with a `// @step` above it, not the
|
|
377
|
+
// topmost: pick() throws from the helper block at the top of the flow file,
|
|
378
|
+
// and "fwrd42.flow.ts:3981" tells a reviewer nothing.
|
|
379
|
+
let anchor = null;
|
|
380
|
+
let site = parsed.errorSites[0] ?? null;
|
|
381
|
+
const sources = new Map();
|
|
382
|
+
const read = (f) => {
|
|
383
|
+
if (!sources.has(f)) {
|
|
384
|
+
try {
|
|
385
|
+
sources.set(f, fs.readFileSync(f, 'utf8'));
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
sources.set(f, '');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return sources.get(f) ?? '';
|
|
392
|
+
};
|
|
393
|
+
for (const frame of parsed.errorSites) {
|
|
394
|
+
const found = findStepAnchor(read(frame.file.endsWith('.spec.ts') ? specCopy : flowCopy), frame.line);
|
|
395
|
+
if (found) {
|
|
396
|
+
anchor = found;
|
|
397
|
+
site = frame;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const r = {
|
|
402
|
+
ran: true,
|
|
403
|
+
skipped: null,
|
|
404
|
+
passed: parsed.passed && run.status === 0,
|
|
405
|
+
durationMs: parsed.durationMs || wallMs,
|
|
406
|
+
exitCode: run.status,
|
|
407
|
+
timedOut,
|
|
408
|
+
error: parsed.passed && run.status === 0 ? null : error,
|
|
409
|
+
anchor,
|
|
410
|
+
errorFile: site?.file ?? null,
|
|
411
|
+
errorLine: site?.line ?? null,
|
|
412
|
+
drift: parsed.drift,
|
|
413
|
+
driftCount: parsed.drift.length,
|
|
414
|
+
satisfied: parsed.satisfied,
|
|
415
|
+
workspace: work,
|
|
416
|
+
specFile: specSrc,
|
|
417
|
+
};
|
|
418
|
+
if (r.passed) {
|
|
419
|
+
try {
|
|
420
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
/* left behind is harmless; it is dot-prefixed and named */
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return { ...r, verdict: verdictFor(r, o.liveReplayPassed ?? false, o.flagged), workspace: r.passed ? null : work };
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/spec/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One problem, said once, in the same shape everywhere the tool reports one.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The odoo flow fwod34 pinned step 08-open to a DEMOTED
|
|
5
|
+
* skill: the recording orchestrator asked it to cancel an order step 06 had
|
|
6
|
+
* already cancelled, so the skill's first step clicks a Cancel button that is
|
|
7
|
+
* never there on replay. Every fact needed to say "re-record 08-open" was in
|
|
8
|
+
* hand, and four different surfaces said four different partial things —
|
|
9
|
+
* compile a quiet one-liner with no reason and no fix, the emitted spec a bare
|
|
10
|
+
* locator error that reads as drift, repair "9/9 tier A, no change" (because
|
|
11
|
+
* the engine silently replayed a DIFFERENT, read-only skill), and
|
|
12
|
+
* `repair --check-spec` "this is an emitter defect", which was simply wrong.
|
|
13
|
+
*
|
|
14
|
+
* A Diagnostic is the fix: what is wrong, the evidence for it, and the exact
|
|
15
|
+
* command that repairs it — carried as data, printed identically by compile,
|
|
16
|
+
* repair, check and rerecord, and included verbatim in every `--json`.
|
|
17
|
+
*
|
|
18
|
+
* Every surface prints its diagnostics FIRST (before counts, file lists and
|
|
19
|
+
* change lists) as `formatDiagnostic` blocks, and puts them in its JSON under
|
|
20
|
+
* `diagnostics`. `diagnosticLine` is the one-line legacy form, so a caller
|
|
21
|
+
* that still prints `warning: ...` lines — and every test that asserts on
|
|
22
|
+
* those exact strings — keeps working.
|
|
23
|
+
*/
|
|
24
|
+
/** One diagnostic as terminal text: `<severity> <step>: <what>\n why: ...\n fix: ...` */
|
|
25
|
+
export function formatDiagnostic(d) {
|
|
26
|
+
const head = `${d.severity} ${d.step ? `${d.step}: ` : ''}${d.what}`;
|
|
27
|
+
const lines = [head, ` why: ${d.why}`];
|
|
28
|
+
if (d.fix)
|
|
29
|
+
lines.push(` fix: ${d.fix}`);
|
|
30
|
+
return lines.join('\n');
|
|
31
|
+
}
|
|
32
|
+
/** The legacy one-line warning string for callers that still print `warning: ...` lines. */
|
|
33
|
+
export function diagnosticLine(d) {
|
|
34
|
+
if (d.line)
|
|
35
|
+
return d.line;
|
|
36
|
+
return `${d.step ? `step ${d.step} ` : ''}${d.what}`;
|
|
37
|
+
}
|
|
38
|
+
/** True when any diagnostic is severe enough to stop a surface from writing. */
|
|
39
|
+
export function hasError(diagnostics) {
|
|
40
|
+
return diagnostics.some((d) => d.severity === 'error');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A diagnostic as ONE line, for places with no room for a block: the note a
|
|
44
|
+
* flagged step's thrown error carries, or a comment in generated source.
|
|
45
|
+
*/
|
|
46
|
+
export function diagnosticNote(d) {
|
|
47
|
+
return `${d.what}${d.fix ? ` — fix: ${d.fix}` : ''}`;
|
|
48
|
+
}
|
|
49
|
+
/** The `sitelooper rerecord ...` command that repairs a step (agent C's command). */
|
|
50
|
+
export function rerecordFix(flowFile, stepId, instruction) {
|
|
51
|
+
const tail = instruction ? ` --instruction ${JSON.stringify(instruction)}` : '';
|
|
52
|
+
return `sitelooper rerecord ${quoteArg(flowFile)} ${stepId}${tail}`;
|
|
53
|
+
}
|
|
54
|
+
/** A shell-safe-enough argument: quoted only when it needs it. */
|
|
55
|
+
function quoteArg(text) {
|
|
56
|
+
return /[\s"']/.test(text) ? JSON.stringify(text) : text;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/spec/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAiCH,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAAC,CAAa;IAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,cAAc,CAAC,CAAa;IAC1C,IAAI,CAAC,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC;IAC1B,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,WAAyB;IAChD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,CAAa;IAC1C,OAAO,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACvD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,MAAc,EAAE,WAAoB;IAChF,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,OAAO,uBAAuB,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;AACtE,CAAC;AAED,kEAAkE;AAClE,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC"}
|