@bonyadnouri/autoend 0.1.0 → 0.1.2
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/README.md +138 -38
- package/dist/cli.js +5 -3
- package/dist/cli.js.map +1 -1
- package/dist/explore/explorer.d.ts +4 -1
- package/dist/explore/explorer.js +62 -23
- package/dist/explore/explorer.js.map +1 -1
- package/dist/map/flow-map.d.ts +46 -0
- package/dist/map/flow-map.js +99 -6
- package/dist/map/flow-map.js.map +1 -1
- package/dist/replay/replay.d.ts +13 -2
- package/dist/replay/replay.js +92 -11
- package/dist/replay/replay.js.map +1 -1
- package/dist/report/artifact.d.ts +7 -1
- package/dist/report/artifact.js +10 -0
- package/dist/report/artifact.js.map +1 -1
- package/dist/report/types.d.ts +62 -0
- package/dist/run/run.d.ts +6 -1
- package/dist/run/run.js +64 -9
- package/dist/run/run.js.map +1 -1
- package/dist/run/sensitive-env.d.ts +24 -0
- package/dist/run/sensitive-env.js +45 -0
- package/dist/run/sensitive-env.js.map +1 -0
- package/dist/setup/wizard.js +5 -3
- package/dist/setup/wizard.js.map +1 -1
- package/dist/spa/assets/index-BYZk0rZl.js +264 -0
- package/dist/spa/assets/index-D8cAgArG.css +1 -0
- package/dist/spa/index.html +14 -0
- package/dist/spa/logo.svg +7 -0
- package/dist/viewer/server.d.ts +7 -6
- package/dist/viewer/server.js +168 -26
- package/dist/viewer/server.js.map +1 -1
- package/package.json +17 -4
- package/dist/viewer/html.d.ts +0 -7
- package/dist/viewer/html.js +0 -240
- package/dist/viewer/html.js.map +0 -1
package/dist/map/flow-map.js
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* The Flow Map (CONTEXT.md): the persistent record of every Flow agents have
|
|
5
|
+
* discovered and successfully executed. Committed to the user's repo like a
|
|
6
|
+
* lockfile — a branch carries its own baseline (ADR-0001).
|
|
7
|
+
*
|
|
8
|
+
* Layout: .autoend/flows/<flowId>/flow.json (metadata, this shape)
|
|
9
|
+
* .autoend/flows/<flowId>/flow.mts (the Playwright script, ADR-0002)
|
|
10
|
+
*
|
|
11
|
+
* .mts, deliberately: it is unambiguously ESM no matter what "type" the
|
|
12
|
+
* consuming repo's package.json declares, and it dodges test-runner globs
|
|
13
|
+
* (vitest/jest match *.spec.* / *.test.* by default).
|
|
14
|
+
*/
|
|
15
|
+
/** Bump when the persisted shape of flow.json changes; enables migrations. */
|
|
16
|
+
export const FLOW_SCHEMA_VERSION = 1;
|
|
3
17
|
export function flowMapDir(repoRoot) {
|
|
4
18
|
return join(repoRoot, '.autoend', 'flows');
|
|
5
19
|
}
|
|
20
|
+
function flowDir(repoRoot, id) {
|
|
21
|
+
return join(flowMapDir(repoRoot), id);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read every Flow's metadata. A single malformed or incomplete flow directory
|
|
25
|
+
* (missing/partial flow.json — e.g. an interrupted write, a merge conflict, a
|
|
26
|
+
* manually-created folder) is skipped with a warning rather than aborting the
|
|
27
|
+
* whole Run: the Flow Map is human-editable and lives in the user's repo, so it
|
|
28
|
+
* must never fail closed on one bad entry.
|
|
29
|
+
*/
|
|
6
30
|
export async function listFlows(repoRoot) {
|
|
7
31
|
let entries;
|
|
8
32
|
try {
|
|
@@ -15,20 +39,89 @@ export async function listFlows(repoRoot) {
|
|
|
15
39
|
for (const entry of entries) {
|
|
16
40
|
if (!entry.isDirectory())
|
|
17
41
|
continue;
|
|
18
|
-
const
|
|
19
|
-
|
|
42
|
+
const metaPath = join(flowMapDir(repoRoot), entry.name, 'flow.json');
|
|
43
|
+
try {
|
|
44
|
+
const meta = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
45
|
+
if (typeof meta?.id !== 'string' ||
|
|
46
|
+
typeof meta?.title !== 'string' ||
|
|
47
|
+
typeof meta?.discoveredAt !== 'string') {
|
|
48
|
+
console.warn(`skipping flow "${entry.name}": flow.json is missing required fields`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// The directory name is the flow's address — downstream code locates
|
|
52
|
+
// flow.mts via join(flowMapDir, flow.id, ...). A mismatch (from a manual
|
|
53
|
+
// edit or a botched merge) would silently read the wrong folder, so treat
|
|
54
|
+
// it as malformed rather than trusting the drifted id.
|
|
55
|
+
if (meta.id !== entry.name) {
|
|
56
|
+
console.warn(`skipping flow "${entry.name}": flow.json id "${meta.id}" does not match its directory`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
flows.push(meta);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
63
|
+
console.warn(`skipping flow "${entry.name}": could not read flow.json (${reason})`);
|
|
64
|
+
}
|
|
20
65
|
}
|
|
21
66
|
return flows;
|
|
22
67
|
}
|
|
68
|
+
async function writeMeta(repoRoot, meta) {
|
|
69
|
+
// Stamp AFTER the spread so the current version always wins — a stale
|
|
70
|
+
// meta.schemaVersion must not survive a write, or migrations can never
|
|
71
|
+
// upgrade persisted metadata.
|
|
72
|
+
const stamped = { ...meta, schemaVersion: FLOW_SCHEMA_VERSION };
|
|
73
|
+
await writeFile(join(flowDir(repoRoot, meta.id), 'flow.json'), JSON.stringify(stamped, null, 2));
|
|
74
|
+
}
|
|
23
75
|
/** Update a Flow's metadata in place (e.g. lastPassedAt after a green replay). */
|
|
24
76
|
export async function saveFlowMeta(repoRoot, meta) {
|
|
25
|
-
await
|
|
77
|
+
await writeMeta(repoRoot, meta);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Dismiss (CONTEXT.md): permanent map surgery — the Flow leaves the Map. A
|
|
81
|
+
* plain file edit performed by the viewer server (ADR-0004).
|
|
82
|
+
*/
|
|
83
|
+
export async function removeFlow(repoRoot, flowId) {
|
|
84
|
+
await rm(join(flowMapDir(repoRoot), flowId), { recursive: true, force: true });
|
|
26
85
|
}
|
|
27
86
|
/** Flows enter the map automatically on first successful execution (ADR-0001). */
|
|
28
87
|
export async function addFlow(repoRoot, meta, playwrightScript) {
|
|
29
|
-
const dir =
|
|
88
|
+
const dir = flowDir(repoRoot, meta.id);
|
|
30
89
|
await mkdir(dir, { recursive: true });
|
|
31
|
-
await writeFile(join(dir, 'flow.json'), JSON.stringify(meta, null, 2));
|
|
32
90
|
await writeFile(join(dir, 'flow.mts'), playwrightScript);
|
|
91
|
+
await writeMeta(repoRoot, meta);
|
|
92
|
+
}
|
|
93
|
+
/** Read a Flow's executable script (flow.mts). */
|
|
94
|
+
export async function readFlowScript(repoRoot, id) {
|
|
95
|
+
return readFile(join(flowDir(repoRoot, id), 'flow.mts'), 'utf8');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Record a Heal (CONTEXT.md): overwrite flow.mts with the rewritten script
|
|
99
|
+
* while retaining the pre-heal version on the Flow's metadata, so a later
|
|
100
|
+
* Reject can revert. Returns the updated metadata.
|
|
101
|
+
*/
|
|
102
|
+
export async function healFlow(repoRoot, meta, healedScript, healedInRun) {
|
|
103
|
+
const dir = flowDir(repoRoot, meta.id);
|
|
104
|
+
const previousScript = await readFlowScript(repoRoot, meta.id);
|
|
105
|
+
await writeFile(join(dir, 'flow.mts'), healedScript);
|
|
106
|
+
const healed = {
|
|
107
|
+
...meta,
|
|
108
|
+
heal: { previousScript, healedAt: new Date().toISOString(), healedInRun },
|
|
109
|
+
};
|
|
110
|
+
await writeMeta(repoRoot, healed);
|
|
111
|
+
return healed;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reject a Heal (CONTEXT.md): restore the pre-heal script and clear the heal
|
|
115
|
+
* record. Returns the reverted metadata. Throws if the Flow has no Heal to
|
|
116
|
+
* revert, so callers can surface a clear error.
|
|
117
|
+
*/
|
|
118
|
+
export async function revertHeal(repoRoot, meta) {
|
|
119
|
+
if (!meta.heal) {
|
|
120
|
+
throw new Error(`flow "${meta.id}" has no Heal to revert`);
|
|
121
|
+
}
|
|
122
|
+
await writeFile(join(flowDir(repoRoot, meta.id), 'flow.mts'), meta.heal.previousScript);
|
|
123
|
+
const { heal: _discarded, ...reverted } = meta;
|
|
124
|
+
await writeMeta(repoRoot, reverted);
|
|
125
|
+
return reverted;
|
|
33
126
|
}
|
|
34
127
|
//# sourceMappingURL=flow-map.js.map
|
package/dist/map/flow-map.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow-map.js","sourceRoot":"","sources":["../../src/map/flow-map.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"flow-map.js","sourceRoot":"","sources":["../../src/map/flow-map.ts"],"names":[],"mappings":"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"}
|
package/dist/replay/replay.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type Browser, type Page } from 'playwright';
|
|
2
2
|
import { type FlowMeta } from '../map/flow-map.js';
|
|
3
|
-
import type { Finding, Heal } from '../report/types.js';
|
|
3
|
+
import type { ConsoleEntry, Finding, FlowSnapshot, Heal, NetworkEntry, Screenshot, StepResult } from '../report/types.js';
|
|
4
4
|
export interface ReplayResult {
|
|
5
5
|
replayed: number;
|
|
6
6
|
findings: Finding[];
|
|
7
7
|
heals: Heal[];
|
|
8
|
+
/** One snapshot per replayed Flow — the Report's receipts (CONTEXT.md: Report). */
|
|
9
|
+
flows: FlowSnapshot[];
|
|
10
|
+
/** Chromium build string, present when a browser was actually launched. */
|
|
11
|
+
browserVersion?: string;
|
|
8
12
|
}
|
|
9
13
|
/**
|
|
10
14
|
* A Flow's executable steps (ADR-0002): a plain Playwright script, default-
|
|
@@ -21,11 +25,18 @@ export interface ScriptOutcome {
|
|
|
21
25
|
error?: string;
|
|
22
26
|
/** Evidence filename within evidenceDir (WebM). */
|
|
23
27
|
evidence?: string;
|
|
28
|
+
console: ConsoleEntry[];
|
|
29
|
+
network: NetworkEntry[];
|
|
30
|
+
timeline: StepResult[];
|
|
31
|
+
screenshots: Screenshot[];
|
|
32
|
+
durationMs: number;
|
|
24
33
|
}
|
|
25
34
|
/**
|
|
26
35
|
* Execute one Flow script in a fresh recording browser context. Shared by
|
|
27
36
|
* replay (map scripts) and exploration (verify-by-running a proposed script
|
|
28
|
-
* before it may enter the Flow Map, ADR-0002).
|
|
37
|
+
* before it may enter the Flow Map, ADR-0002). Alongside the WebM Evidence it
|
|
38
|
+
* captures the panels the viewer renders (ADR-0005): console/page errors,
|
|
39
|
+
* failed network, a navigation timeline, and before/after screenshots.
|
|
29
40
|
*/
|
|
30
41
|
export declare function runFlowScript(browser: Browser, scriptPath: string, target: URL, evidenceDir: string, videoBase: string): Promise<ScriptOutcome>;
|
|
31
42
|
/**
|
package/dist/replay/replay.js
CHANGED
|
@@ -3,17 +3,64 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { chromium } from 'playwright';
|
|
5
5
|
import { flowMapDir, saveFlowMeta } from '../map/flow-map.js';
|
|
6
|
+
import { withoutSensitiveEnv } from '../run/sensitive-env.js';
|
|
6
7
|
const FLOW_TIMEOUT_MS = 60_000;
|
|
7
8
|
const REPLAY_WORKERS = 4;
|
|
9
|
+
const VIEWPORT = { width: 1280, height: 720 };
|
|
10
|
+
const CAPTURE_CAP = 50; // per stream; drop beyond, note nothing — caps keep report.json bounded
|
|
8
11
|
/**
|
|
9
12
|
* Execute one Flow script in a fresh recording browser context. Shared by
|
|
10
13
|
* replay (map scripts) and exploration (verify-by-running a proposed script
|
|
11
|
-
* before it may enter the Flow Map, ADR-0002).
|
|
14
|
+
* before it may enter the Flow Map, ADR-0002). Alongside the WebM Evidence it
|
|
15
|
+
* captures the panels the viewer renders (ADR-0005): console/page errors,
|
|
16
|
+
* failed network, a navigation timeline, and before/after screenshots.
|
|
12
17
|
*/
|
|
13
18
|
export async function runFlowScript(browser, scriptPath, target, evidenceDir, videoBase) {
|
|
14
|
-
const context = await browser.newContext({ recordVideo: { dir: evidenceDir } });
|
|
19
|
+
const context = await browser.newContext({ recordVideo: { dir: evidenceDir }, viewport: VIEWPORT });
|
|
15
20
|
// TODO: inject shared storage state (src/auth/session.ts) once fleet auth exists.
|
|
16
21
|
const page = await context.newPage();
|
|
22
|
+
const startedMs = Date.now();
|
|
23
|
+
const consoleEntries = [];
|
|
24
|
+
const network = [];
|
|
25
|
+
const timeline = [];
|
|
26
|
+
const screenshots = [];
|
|
27
|
+
const screenshot = async (label, file) => {
|
|
28
|
+
try {
|
|
29
|
+
await page.screenshot({ path: join(evidenceDir, file) });
|
|
30
|
+
screenshots.push({ file, label, tMs: Date.now() - startedMs });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// A crashed or closed page must not mask the Flow's own error.
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
page.on('console', (msg) => {
|
|
37
|
+
const level = msg.type() === 'error' ? 'error' : msg.type() === 'warning' ? 'warning' : undefined;
|
|
38
|
+
if (level && consoleEntries.length < CAPTURE_CAP)
|
|
39
|
+
consoleEntries.push({ level, text: msg.text(), tMs: Date.now() - startedMs });
|
|
40
|
+
});
|
|
41
|
+
page.on('pageerror', (err) => {
|
|
42
|
+
if (consoleEntries.length < CAPTURE_CAP)
|
|
43
|
+
consoleEntries.push({ level: 'error', text: String(err), tMs: Date.now() - startedMs });
|
|
44
|
+
});
|
|
45
|
+
page.on('response', (res) => {
|
|
46
|
+
if (res.status() >= 400 && network.length < CAPTURE_CAP)
|
|
47
|
+
network.push({ method: res.request().method(), url: res.url(), status: res.status(), tMs: Date.now() - startedMs });
|
|
48
|
+
});
|
|
49
|
+
page.on('requestfailed', (req) => {
|
|
50
|
+
if (network.length < CAPTURE_CAP)
|
|
51
|
+
network.push({ method: req.method(), url: req.url(), status: 0, tMs: Date.now() - startedMs });
|
|
52
|
+
});
|
|
53
|
+
page.on('framenavigated', (frame) => {
|
|
54
|
+
if (frame !== page.mainFrame() || frame.url() === 'about:blank' || timeline.length >= CAPTURE_CAP)
|
|
55
|
+
return;
|
|
56
|
+
const path = new URL(frame.url()).pathname;
|
|
57
|
+
timeline.push({ label: `goto ${path}`, status: 'passed', tMs: Date.now() - startedMs });
|
|
58
|
+
});
|
|
59
|
+
// First main-frame load → the "before" screenshot; awaited below so it always precedes "after".
|
|
60
|
+
let beforeShot;
|
|
61
|
+
page.once('load', () => {
|
|
62
|
+
beforeShot = screenshot('before', `${videoBase}-before.png`);
|
|
63
|
+
});
|
|
17
64
|
let failure;
|
|
18
65
|
try {
|
|
19
66
|
const script = await loadFlowScript(scriptPath);
|
|
@@ -22,6 +69,16 @@ export async function runFlowScript(browser, scriptPath, target, evidenceDir, vi
|
|
|
22
69
|
catch (error) {
|
|
23
70
|
failure = error;
|
|
24
71
|
}
|
|
72
|
+
if (beforeShot)
|
|
73
|
+
await beforeShot;
|
|
74
|
+
if (failure !== undefined) {
|
|
75
|
+
const message = failure instanceof Error ? failure.message : String(failure);
|
|
76
|
+
timeline.push({ label: message.slice(0, 80), status: 'failed', tMs: Date.now() - startedMs });
|
|
77
|
+
await screenshot('at-failure', `${videoBase}-at-failure.png`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await screenshot('after', `${videoBase}-after.png`);
|
|
81
|
+
}
|
|
25
82
|
const video = page.video();
|
|
26
83
|
await context.close(); // finalizes the recording
|
|
27
84
|
let evidence;
|
|
@@ -29,10 +86,12 @@ export async function runFlowScript(browser, scriptPath, target, evidenceDir, vi
|
|
|
29
86
|
evidence = `${videoBase}.webm`;
|
|
30
87
|
await rename(await video.path(), join(evidenceDir, evidence));
|
|
31
88
|
}
|
|
89
|
+
const durationMs = Date.now() - startedMs;
|
|
90
|
+
const capture = { console: consoleEntries, network, timeline, screenshots, durationMs, evidence };
|
|
32
91
|
if (failure !== undefined) {
|
|
33
|
-
return { ok: false, error: failure instanceof Error ? failure.message : String(failure),
|
|
92
|
+
return { ok: false, error: failure instanceof Error ? failure.message : String(failure), ...capture };
|
|
34
93
|
}
|
|
35
|
-
return { ok: true,
|
|
94
|
+
return { ok: true, ...capture };
|
|
36
95
|
}
|
|
37
96
|
/**
|
|
38
97
|
* Phase 1 of every Run: replay the whole Flow Map — headless, parallel,
|
|
@@ -41,13 +100,27 @@ export async function runFlowScript(browser, scriptPath, target, evidenceDir, vi
|
|
|
41
100
|
*/
|
|
42
101
|
export async function replayFlowMap(repoRoot, target, flows, evidenceDir) {
|
|
43
102
|
if (flows.length === 0) {
|
|
44
|
-
return { replayed: 0, findings: [], heals: [] };
|
|
103
|
+
return { replayed: 0, findings: [], heals: [], flows: [] };
|
|
45
104
|
}
|
|
46
105
|
const browser = await chromium.launch();
|
|
47
106
|
try {
|
|
48
|
-
const
|
|
107
|
+
const browserVersion = browser.version();
|
|
108
|
+
// Map scripts are LLM-authored and imported in-process (ADR-0002); hide
|
|
109
|
+
// secrets from them while the whole pool runs (issue #3). Scrubbing wraps
|
|
110
|
+
// the batch, not each script, because process.env is process-global.
|
|
111
|
+
const results = await withoutSensitiveEnv(() => withPool(flows, REPLAY_WORKERS, async (flow) => {
|
|
49
112
|
const scriptPath = join(flowMapDir(repoRoot), flow.id, 'flow.mts');
|
|
50
113
|
const outcome = await runFlowScript(browser, scriptPath, target, evidenceDir, flow.id);
|
|
114
|
+
const snapshot = {
|
|
115
|
+
id: flow.id,
|
|
116
|
+
title: flow.title,
|
|
117
|
+
status: outcome.ok ? 'passed' : 'failed',
|
|
118
|
+
discoveredAt: flow.discoveredAt,
|
|
119
|
+
lastPassedAt: flow.lastPassedAt,
|
|
120
|
+
timeline: outcome.timeline,
|
|
121
|
+
evidence: outcome.evidence,
|
|
122
|
+
durationMs: outcome.durationMs,
|
|
123
|
+
};
|
|
51
124
|
if (!outcome.ok) {
|
|
52
125
|
// TODO(ADR-0001): attempt a Heal (re-achieve the Flow's goal via an agent)
|
|
53
126
|
// before reporting. Until healing exists, every failure is a Regression.
|
|
@@ -58,16 +131,24 @@ export async function replayFlowMap(repoRoot, target, flows, evidenceDir) {
|
|
|
58
131
|
title: `Flow "${flow.title}" failed on replay`,
|
|
59
132
|
detail: outcome.error ?? 'unknown failure',
|
|
60
133
|
evidence: outcome.evidence,
|
|
134
|
+
console: outcome.console,
|
|
135
|
+
network: outcome.network,
|
|
136
|
+
timeline: outcome.timeline,
|
|
137
|
+
screenshots: outcome.screenshots,
|
|
61
138
|
};
|
|
62
|
-
return finding;
|
|
139
|
+
return { snapshot, finding };
|
|
63
140
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
141
|
+
const lastPassedAt = new Date().toISOString();
|
|
142
|
+
await saveFlowMeta(repoRoot, { ...flow, lastPassedAt });
|
|
143
|
+
snapshot.lastPassedAt = lastPassedAt;
|
|
144
|
+
return { snapshot };
|
|
145
|
+
}));
|
|
67
146
|
return {
|
|
68
147
|
replayed: flows.length,
|
|
69
|
-
findings:
|
|
148
|
+
findings: results.map((r) => r.finding).filter((f) => f !== undefined),
|
|
70
149
|
heals: [],
|
|
150
|
+
flows: results.map((r) => r.snapshot),
|
|
151
|
+
browserVersion,
|
|
71
152
|
};
|
|
72
153
|
}
|
|
73
154
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/replay/replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAA2B,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAiB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/replay/replay.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunArtifact } from './types.js';
|
|
1
|
+
import type { Finding, RunArtifact } from './types.js';
|
|
2
2
|
/** Run artifacts live under .autoend/runs/<runId>/ — gitignored, kept until `autoend clean`. */
|
|
3
3
|
export declare function runsDir(repoRoot: string): string;
|
|
4
4
|
export interface RunDir {
|
|
@@ -8,3 +8,9 @@ export interface RunDir {
|
|
|
8
8
|
export declare function prepareRunDir(repoRoot: string, runId: string): Promise<RunDir>;
|
|
9
9
|
export declare function writeReport(runDir: string, artifact: RunArtifact): Promise<void>;
|
|
10
10
|
export declare function readRunArtifact(runDir: string): Promise<RunArtifact>;
|
|
11
|
+
/**
|
|
12
|
+
* Patch one Finding in report.json in place — how resolution actions
|
|
13
|
+
* (Dismiss/Suppress) become visible to a viewer reload (ADR-0004: the
|
|
14
|
+
* artifact is the state; the server just edits files).
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateFinding(runDir: string, findingId: string, patch: Partial<Finding>): Promise<void>;
|
package/dist/report/artifact.js
CHANGED
|
@@ -17,4 +17,14 @@ export async function readRunArtifact(runDir) {
|
|
|
17
17
|
const raw = await readFile(join(runDir, 'report.json'), 'utf8');
|
|
18
18
|
return JSON.parse(raw);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Patch one Finding in report.json in place — how resolution actions
|
|
22
|
+
* (Dismiss/Suppress) become visible to a viewer reload (ADR-0004: the
|
|
23
|
+
* artifact is the state; the server just edits files).
|
|
24
|
+
*/
|
|
25
|
+
export async function updateFinding(runDir, findingId, patch) {
|
|
26
|
+
const artifact = await readRunArtifact(runDir);
|
|
27
|
+
artifact.findings = artifact.findings.map((finding) => finding.id === findingId ? { ...finding, ...patch } : finding);
|
|
28
|
+
await writeReport(runDir, artifact);
|
|
29
|
+
}
|
|
20
30
|
//# sourceMappingURL=artifact.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact.js","sourceRoot":"","sources":["../../src/report/artifact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,gGAAgG;AAChG,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAa;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc,EAAE,QAAqB;IACrE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;AACxC,CAAC"}
|
|
1
|
+
{"version":3,"file":"artifact.js","sourceRoot":"","sources":["../../src/report/artifact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,gGAAgG;AAChG,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAa;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc,EAAE,QAAqB;IACrE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,SAAiB,EACjB,KAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACpD,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAC9D,CAAC;IACF,MAAM,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/report/types.d.ts
CHANGED
|
@@ -5,6 +5,41 @@ import type { Effort } from '../run/effort.js';
|
|
|
5
5
|
*/
|
|
6
6
|
/** The three Finding tiers (CONTEXT.md: Finding). */
|
|
7
7
|
export type FindingKind = 'hard-failure' | 'regression' | 'advisory';
|
|
8
|
+
/** Where a Finding's fault lives (CONTEXT.md: Diagnosis). A wrong Flow script is 'flow', never 'test'. */
|
|
9
|
+
export type FaultDomain = 'app' | 'flow' | 'environment';
|
|
10
|
+
/** CONTEXT.md: Diagnosis — the filing agent's judgment. Producers land per ADR-0006; schema-only for now. */
|
|
11
|
+
export interface Diagnosis {
|
|
12
|
+
rootCause: string;
|
|
13
|
+
faultDomain: FaultDomain;
|
|
14
|
+
/** 0–100. */
|
|
15
|
+
confidence: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ConsoleEntry {
|
|
18
|
+
level: 'error' | 'warning';
|
|
19
|
+
text: string;
|
|
20
|
+
/** Milliseconds since the Flow's execution started. */
|
|
21
|
+
tMs: number;
|
|
22
|
+
}
|
|
23
|
+
export interface NetworkEntry {
|
|
24
|
+
method: string;
|
|
25
|
+
url: string;
|
|
26
|
+
/** HTTP status; 0 = request failed/aborted before a response. */
|
|
27
|
+
status: number;
|
|
28
|
+
tMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface StepResult {
|
|
31
|
+
/** Navigation milestone or terminal outcome, e.g. "goto /pricing". */
|
|
32
|
+
label: string;
|
|
33
|
+
status: 'passed' | 'failed';
|
|
34
|
+
tMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface Screenshot {
|
|
37
|
+
/** Filename within the artifact's evidence/ dir. */
|
|
38
|
+
file: string;
|
|
39
|
+
label: 'before' | 'after' | 'at-failure';
|
|
40
|
+
tMs: number;
|
|
41
|
+
}
|
|
42
|
+
export type Resolution = 'dismissed' | 'rejected' | 'suppressed';
|
|
8
43
|
export interface Finding {
|
|
9
44
|
id: string;
|
|
10
45
|
kind: FindingKind;
|
|
@@ -14,6 +49,13 @@ export interface Finding {
|
|
|
14
49
|
detail: string;
|
|
15
50
|
/** Path to WebM Evidence, relative to the Run artifact's evidence/ dir. */
|
|
16
51
|
evidence?: string;
|
|
52
|
+
diagnosis?: Diagnosis;
|
|
53
|
+
console?: ConsoleEntry[];
|
|
54
|
+
network?: NetworkEntry[];
|
|
55
|
+
timeline?: StepResult[];
|
|
56
|
+
screenshots?: Screenshot[];
|
|
57
|
+
/** Written by the viewer server when the user resolves the Finding. */
|
|
58
|
+
resolution?: Resolution;
|
|
17
59
|
}
|
|
18
60
|
/** A Heal is reported alongside Findings but is not one (CONTEXT.md: Heal). */
|
|
19
61
|
export interface Heal {
|
|
@@ -21,6 +63,24 @@ export interface Heal {
|
|
|
21
63
|
summary: string;
|
|
22
64
|
evidence?: string;
|
|
23
65
|
}
|
|
66
|
+
/** One Flow as this Run touched it — the Report's receipts (CONTEXT.md: Report). */
|
|
67
|
+
export interface FlowSnapshot {
|
|
68
|
+
id: string;
|
|
69
|
+
title: string;
|
|
70
|
+
status: 'passed' | 'failed' | 'discovered';
|
|
71
|
+
discoveredAt: string;
|
|
72
|
+
lastPassedAt?: string;
|
|
73
|
+
timeline?: StepResult[];
|
|
74
|
+
evidence?: string;
|
|
75
|
+
durationMs?: number;
|
|
76
|
+
}
|
|
77
|
+
export interface Environment {
|
|
78
|
+
browser: string;
|
|
79
|
+
viewport: string;
|
|
80
|
+
os: string;
|
|
81
|
+
node: string;
|
|
82
|
+
autoendVersion: string;
|
|
83
|
+
}
|
|
24
84
|
/**
|
|
25
85
|
* The self-contained record of one Run (ADR-0004): serialized as report.json
|
|
26
86
|
* next to an evidence/ dir of WebM files. Portable — viewable anywhere;
|
|
@@ -34,6 +94,8 @@ export interface RunArtifact {
|
|
|
34
94
|
finishedAt?: string;
|
|
35
95
|
flowsReplayed: number;
|
|
36
96
|
flowsDiscovered: number;
|
|
97
|
+
flows: FlowSnapshot[];
|
|
98
|
+
environment: Environment;
|
|
37
99
|
findings: Finding[];
|
|
38
100
|
heals: Heal[];
|
|
39
101
|
}
|
package/dist/run/run.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunArtifact } from '../report/types.js';
|
|
1
|
+
import type { Finding, RunArtifact } from '../report/types.js';
|
|
2
2
|
import { type Effort } from './effort.js';
|
|
3
3
|
export interface RunOptions {
|
|
4
4
|
target: URL;
|
|
@@ -9,6 +9,11 @@ export interface RunOutcome {
|
|
|
9
9
|
artifactDir: string;
|
|
10
10
|
artifact: RunArtifact;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Suppress (CONTEXT.md): "future Runs stop re-reporting" — drop Advisories
|
|
14
|
+
* whose title the user suppressed. Other Finding kinds always pass through.
|
|
15
|
+
*/
|
|
16
|
+
export declare function filterSuppressed(findings: Finding[], suppressed: string[]): Finding[];
|
|
12
17
|
/**
|
|
13
18
|
* A Run (CONTEXT.md): two ordered phases. Phase 1 replays the whole Flow Map
|
|
14
19
|
* (always completes); phase 2 explores new surface within the Effort budget.
|
package/dist/run/run.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { release } from 'node:os';
|
|
1
3
|
import { listFlows } from '../map/flow-map.js';
|
|
2
4
|
import { explore } from '../explore/explorer.js';
|
|
3
5
|
import { replayFlowMap } from '../replay/replay.js';
|
|
6
|
+
import { join } from 'node:path';
|
|
4
7
|
import { prepareRunDir, writeReport } from '../report/artifact.js';
|
|
5
8
|
import { EFFORT_BUDGETS } from './effort.js';
|
|
9
|
+
/**
|
|
10
|
+
* Suppress (CONTEXT.md): "future Runs stop re-reporting" — drop Advisories
|
|
11
|
+
* whose title the user suppressed. Other Finding kinds always pass through.
|
|
12
|
+
*/
|
|
13
|
+
export function filterSuppressed(findings, suppressed) {
|
|
14
|
+
return findings.filter((f) => f.kind !== 'advisory' || !suppressed.includes(f.title));
|
|
15
|
+
}
|
|
16
|
+
/** Titles from .autoend/suppressed.json; missing or corrupt file means nothing suppressed. */
|
|
17
|
+
async function readSuppressedTitles(repoRoot) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = await readFile(join(repoRoot, '.autoend', 'suppressed.json'), 'utf8');
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
return Array.isArray(parsed.advisories)
|
|
22
|
+
? parsed.advisories.filter((t) => typeof t === 'string')
|
|
23
|
+
: [];
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
6
29
|
/**
|
|
7
30
|
* A Run (CONTEXT.md): two ordered phases. Phase 1 replays the whole Flow Map
|
|
8
31
|
* (always completes); phase 2 explores new surface within the Effort budget.
|
|
@@ -14,14 +37,44 @@ export async function executeRun(opts) {
|
|
|
14
37
|
const { dir, evidenceDir } = await prepareRunDir(opts.repoRoot, runId);
|
|
15
38
|
const flows = await listFlows(opts.repoRoot);
|
|
16
39
|
const replay = await replayFlowMap(opts.repoRoot, opts.target, flows, evidenceDir);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
// Exploration is best-effort (issue #1): replaying the whole map always
|
|
41
|
+
// completes, so an exploration crash must never swallow replay's regressions
|
|
42
|
+
// or skip the report. Degrade to an empty result plus an advisory instead.
|
|
43
|
+
let exploration;
|
|
44
|
+
try {
|
|
45
|
+
exploration = await explore({
|
|
46
|
+
repoRoot: opts.repoRoot,
|
|
47
|
+
target: opts.target,
|
|
48
|
+
budget: EFFORT_BUDGETS[opts.effort],
|
|
49
|
+
runDir: dir,
|
|
50
|
+
evidenceDir,
|
|
51
|
+
knownFlows: flows,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
console.warn(`exploration failed; reporting replay results only: ${message}`);
|
|
57
|
+
exploration = {
|
|
58
|
+
discovered: 0,
|
|
59
|
+
findings: [
|
|
60
|
+
{
|
|
61
|
+
id: 'exploration-failed',
|
|
62
|
+
kind: 'advisory',
|
|
63
|
+
title: 'Exploration phase did not complete',
|
|
64
|
+
detail: `Exploration failed and was skipped: ${message}. Replay results below are still complete.`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
flows: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const pkg = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
71
|
+
const environment = {
|
|
72
|
+
browser: replay.browserVersion ? `Chromium ${replay.browserVersion}` : 'Chromium (not launched)',
|
|
73
|
+
viewport: '1280×720',
|
|
74
|
+
os: `${process.platform} ${release()}`,
|
|
75
|
+
node: process.version,
|
|
76
|
+
autoendVersion: pkg.version,
|
|
77
|
+
};
|
|
25
78
|
const artifact = {
|
|
26
79
|
runId,
|
|
27
80
|
target: opts.target.href,
|
|
@@ -30,7 +83,9 @@ export async function executeRun(opts) {
|
|
|
30
83
|
finishedAt: new Date().toISOString(),
|
|
31
84
|
flowsReplayed: replay.replayed,
|
|
32
85
|
flowsDiscovered: exploration.discovered,
|
|
33
|
-
|
|
86
|
+
flows: [...replay.flows, ...exploration.flows],
|
|
87
|
+
environment,
|
|
88
|
+
findings: filterSuppressed([...replay.findings, ...exploration.findings], await readSuppressedTitles(opts.repoRoot)),
|
|
34
89
|
heals: replay.heals,
|
|
35
90
|
};
|
|
36
91
|
await writeReport(dir, artifact);
|
package/dist/run/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"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"}
|