@post-print/agent-test 0.3.4 → 0.3.6
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 +17 -61
- package/dist/check.d.ts +38 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +169 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +11 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +327 -318
- package/dist/cli.js.map +1 -1
- package/dist/compare-scenario.d.ts +41 -0
- package/dist/compare-scenario.d.ts.map +1 -0
- package/dist/compare-scenario.js +194 -0
- package/dist/compare-scenario.js.map +1 -0
- package/dist/debug-bundle.d.ts +2 -1
- package/dist/debug-bundle.d.ts.map +1 -1
- package/dist/debug-bundle.js +12 -5
- package/dist/debug-bundle.js.map +1 -1
- package/dist/doctor.d.ts +6 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +133 -11
- package/dist/doctor.js.map +1 -1
- package/dist/expect.d.ts +6 -5
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +67 -57
- package/dist/expect.js.map +1 -1
- package/dist/host-log.d.ts +17 -0
- package/dist/host-log.d.ts.map +1 -0
- package/dist/host-log.js +154 -0
- package/dist/host-log.js.map +1 -0
- package/dist/hosts.d.ts +22 -0
- package/dist/hosts.d.ts.map +1 -0
- package/dist/hosts.js +84 -0
- package/dist/hosts.js.map +1 -0
- package/dist/html-report.d.ts +0 -13
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +504 -323
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/live-isolation.d.ts +11 -4
- package/dist/live-isolation.d.ts.map +1 -1
- package/dist/live-isolation.js +31 -10
- package/dist/live-isolation.js.map +1 -1
- package/dist/load-adapters.d.ts +13 -0
- package/dist/load-adapters.d.ts.map +1 -0
- package/dist/load-adapters.js +76 -0
- package/dist/load-adapters.js.map +1 -0
- package/dist/load-suite.d.ts.map +1 -1
- package/dist/load-suite.js +6 -2
- package/dist/load-suite.js.map +1 -1
- package/dist/mcp-config.d.ts +8 -0
- package/dist/mcp-config.d.ts.map +1 -1
- package/dist/mcp-config.js +46 -0
- package/dist/mcp-config.js.map +1 -1
- package/dist/progress.d.ts +7 -0
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +39 -9
- package/dist/progress.js.map +1 -1
- package/dist/record-trace.d.ts +9 -7
- package/dist/record-trace.d.ts.map +1 -1
- package/dist/record-trace.js +5 -16
- package/dist/record-trace.js.map +1 -1
- package/dist/report-preview-server.d.ts +2 -0
- package/dist/report-preview-server.d.ts.map +1 -0
- package/dist/report-preview-server.js +24 -0
- package/dist/report-preview-server.js.map +1 -0
- package/dist/report-preview.d.ts +11 -0
- package/dist/report-preview.d.ts.map +1 -0
- package/dist/report-preview.js +161 -0
- package/dist/report-preview.js.map +1 -0
- package/dist/run-suite.d.ts +34 -1
- package/dist/run-suite.d.ts.map +1 -1
- package/dist/run-suite.js +588 -82
- package/dist/run-suite.js.map +1 -1
- package/dist/scenario-story.d.ts +43 -0
- package/dist/scenario-story.d.ts.map +1 -0
- package/dist/scenario-story.js +215 -0
- package/dist/scenario-story.js.map +1 -0
- package/dist/suite-summary.d.ts +2 -0
- package/dist/suite-summary.d.ts.map +1 -1
- package/dist/suite-summary.js +70 -22
- package/dist/suite-summary.js.map +1 -1
- package/dist/theme.d.ts +30 -5
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +207 -45
- package/dist/theme.js.map +1 -1
- package/dist/types.d.ts +105 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-seeds.d.ts.map +1 -1
- package/dist/validate-seeds.js +21 -6
- package/dist/validate-seeds.js.map +1 -1
- package/dist/validate-suite.d.ts +3 -1
- package/dist/validate-suite.d.ts.map +1 -1
- package/dist/validate-suite.js +268 -23
- package/dist/validate-suite.js.map +1 -1
- package/package.json +4 -4
- package/dist/compare.d.ts +0 -79
- package/dist/compare.d.ts.map +0 -1
- package/dist/compare.js +0 -232
- package/dist/compare.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,30 +1,96 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
|
-
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
4
|
+
import { cleanupStaleScenarioWorktrees, isPathUnderRoot, knownAgentHosts, missingClassifierAuth, } from "@post-print/agent-harness";
|
|
5
|
+
import { formatCheckReport, formatCheckSummary, missingHostsAuth, runCheck } from "./check.js";
|
|
6
6
|
import { isCliMain } from "./cli-entry.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { missingAgentAuth } from "./doctor.js";
|
|
8
|
+
import { installHostLogFilter } from "./host-log.js";
|
|
9
|
+
import { parseHostList, uniqueHosts } from "./hosts.js";
|
|
10
10
|
import { writeHtmlReport } from "./html-report.js";
|
|
11
|
+
import { loadHostAdapters } from "./load-adapters.js";
|
|
11
12
|
import { assertDirectAgentPreflight } from "./preflight.js";
|
|
12
13
|
import { logProgress } from "./progress.js";
|
|
13
14
|
import { cleanupStagingSession, createLiveStagingSessionId, getLiveStagingSessionRoot, setLiveStagingRootOverride, } from "./record-trace.js";
|
|
14
|
-
import {
|
|
15
|
+
import { shouldStartReportPreview, startDetachedReportPreview } from "./report-preview.js";
|
|
16
|
+
import { registerLiveRunHandlers, runAllSuites, selectedRunNeedsJudge } from "./run-suite.js";
|
|
15
17
|
import { formatRunSummary, shouldFailScenario, summarizeReports, } from "./suite-summary.js";
|
|
16
18
|
import { configureCliColor, theme } from "./theme.js";
|
|
17
|
-
import { formatSeedValidationReport, validateSeedPatches } from "./validate-seeds.js";
|
|
18
|
-
import { formatValidationReport, validateSuitePaths } from "./validate-suite.js";
|
|
19
19
|
import { suppressNoisyRuntimeWarnings } from "./warnings.js";
|
|
20
20
|
suppressNoisyRuntimeWarnings();
|
|
21
21
|
configureCliColor();
|
|
22
|
+
function splitArgvFlag(token) {
|
|
23
|
+
if (!token.startsWith("-") || token === "-") {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
if (token.startsWith("--")) {
|
|
27
|
+
const eq = token.indexOf("=");
|
|
28
|
+
if (eq === -1) {
|
|
29
|
+
return { flag: token };
|
|
30
|
+
}
|
|
31
|
+
return { flag: token.slice(0, eq), inline: token.slice(eq + 1) };
|
|
32
|
+
}
|
|
33
|
+
return { flag: token };
|
|
34
|
+
}
|
|
35
|
+
function readFlagValue(flag, inline, argv, index) {
|
|
36
|
+
if (inline !== undefined) {
|
|
37
|
+
if (inline.length === 0) {
|
|
38
|
+
throw new Error(`${flag} requires a value`);
|
|
39
|
+
}
|
|
40
|
+
return { value: inline, nextIndex: index };
|
|
41
|
+
}
|
|
42
|
+
const next = argv[index + 1];
|
|
43
|
+
if (!next || next.startsWith("--") || /^-[A-Za-z]/.test(next)) {
|
|
44
|
+
throw new Error(`${flag} requires a value`);
|
|
45
|
+
}
|
|
46
|
+
return { value: next, nextIndex: index + 1 };
|
|
47
|
+
}
|
|
48
|
+
function enableCheckMode(target) {
|
|
49
|
+
target.check = true;
|
|
50
|
+
target.validateOnly = true;
|
|
51
|
+
target.validatePaths = true;
|
|
52
|
+
target.validateSeeds = true;
|
|
53
|
+
}
|
|
54
|
+
/** Usage text for `--help`. */
|
|
55
|
+
export function formatHelp() {
|
|
56
|
+
return [
|
|
57
|
+
"agent-test [options] [suite]",
|
|
58
|
+
"",
|
|
59
|
+
"Launch a host agent and score the transcript.",
|
|
60
|
+
"A run checks the suite and host first.",
|
|
61
|
+
"",
|
|
62
|
+
" --check Check the suite and host. Do not launch an agent.",
|
|
63
|
+
" --help, -h Print this help.",
|
|
64
|
+
"",
|
|
65
|
+
" --host cursor|claude|openai|all Host adapter (default: suite or cursor).",
|
|
66
|
+
" Repeat or comma-separate for a matrix.",
|
|
67
|
+
" --adapter <module> Load a consumer host adapter module.",
|
|
68
|
+
" --suites-dir <path> Suite root (default: agent-suites)",
|
|
69
|
+
" --suite <name> Run one suite",
|
|
70
|
+
" --scenario <name> Run one scenario",
|
|
71
|
+
" --no-judge Skip the judge",
|
|
72
|
+
" --fail-on all|behavior|infra-only",
|
|
73
|
+
" --allow-user-input Let a user agent answer AskQuestion tools",
|
|
74
|
+
" --timeout-ms <n> Agent deadline",
|
|
75
|
+
" --no-timeout Disable the deadline",
|
|
76
|
+
" --scenario-retries <n> Announce-stop retries",
|
|
77
|
+
" --debug Keep recordings and write a debug bundle",
|
|
78
|
+
" --debug-dir <path> Debug session parent directory",
|
|
79
|
+
" --report-out <path> HTML report file or directory",
|
|
80
|
+
" --no-html-report Skip the HTML report",
|
|
81
|
+
" --no-worktree Run in the caller checkout (needs AGENT_TEST_ALLOW_IN_PLACE=1)",
|
|
82
|
+
"",
|
|
83
|
+
"--doctor, --validate-only, --validate-paths, and --validate-seeds run --check.",
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
22
86
|
/** Parse agent-test CLI argv (exported for unit tests). */
|
|
23
87
|
export function parseCliArgs(argv) {
|
|
24
88
|
const cwd = process.cwd();
|
|
25
89
|
let suitesDir = "agent-suites";
|
|
26
90
|
let rubricsDir;
|
|
27
|
-
|
|
91
|
+
const parsedHosts = [];
|
|
92
|
+
let hostAll = false;
|
|
93
|
+
const adapterModules = [];
|
|
28
94
|
let filter;
|
|
29
95
|
let scenarioFilter;
|
|
30
96
|
let stagingSessionId;
|
|
@@ -34,6 +100,8 @@ export function parseCliArgs(argv) {
|
|
|
34
100
|
let timeoutMs;
|
|
35
101
|
let noTimeout = false;
|
|
36
102
|
let allowUserInput = false;
|
|
103
|
+
let check = false;
|
|
104
|
+
let help = false;
|
|
37
105
|
let doctor = false;
|
|
38
106
|
let htmlReport = true;
|
|
39
107
|
let reportOut;
|
|
@@ -44,141 +112,162 @@ export function parseCliArgs(argv) {
|
|
|
44
112
|
let validatePaths = false;
|
|
45
113
|
let failOn = "all";
|
|
46
114
|
let scenarioRetries;
|
|
47
|
-
let compareMode = false;
|
|
48
|
-
let compareA;
|
|
49
|
-
let compareB;
|
|
50
|
-
let comparePairs;
|
|
51
|
-
let compareOutDir;
|
|
52
|
-
const startIndex = argv[2] === "compare" ? 3 : 2;
|
|
53
115
|
if (argv[2] === "compare") {
|
|
54
|
-
|
|
116
|
+
throw new Error("The compare subcommand is removed. Add compare.a and compare.b on one scenario. Set compare.faster or compare.cheaper when one arm must win on time or tokens. Add rubric.judge only when you want a pairwise judge.");
|
|
55
117
|
}
|
|
118
|
+
const startIndex = 2;
|
|
119
|
+
const checkMode = {
|
|
120
|
+
check: false,
|
|
121
|
+
validateOnly: false,
|
|
122
|
+
validatePaths: false,
|
|
123
|
+
validateSeeds: false,
|
|
124
|
+
};
|
|
56
125
|
for (let i = startIndex; i < argv.length; i++) {
|
|
57
126
|
const token = argv[i];
|
|
58
|
-
if (token
|
|
59
|
-
|
|
60
|
-
if (value === "replay") {
|
|
61
|
-
throw new Error("Replay-based testing is deprecated and no longer supported; use --host cursor or --host claude.");
|
|
62
|
-
}
|
|
63
|
-
if (value !== "cursor" && value !== "claude") {
|
|
64
|
-
throw new Error("--host must be cursor|claude");
|
|
65
|
-
}
|
|
66
|
-
host = value;
|
|
67
|
-
}
|
|
68
|
-
else if (token === "--suites-dir" && argv[i + 1]) {
|
|
69
|
-
suitesDir = argv[++i];
|
|
70
|
-
}
|
|
71
|
-
else if (token === "--rubrics-dir" && argv[i + 1]) {
|
|
72
|
-
rubricsDir = argv[++i];
|
|
73
|
-
}
|
|
74
|
-
else if (token === "--suite" && argv[i + 1]) {
|
|
75
|
-
filter = argv[++i];
|
|
76
|
-
}
|
|
77
|
-
else if (token === "--scenario" && argv[i + 1]) {
|
|
78
|
-
scenarioFilter = argv[++i];
|
|
127
|
+
if (!token) {
|
|
128
|
+
continue;
|
|
79
129
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
const parsed = splitArgvFlag(token);
|
|
131
|
+
if (!parsed) {
|
|
132
|
+
filter = token;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const { flag, inline } = parsed;
|
|
136
|
+
const read = () => {
|
|
137
|
+
const result = readFlagValue(flag, inline, argv, i);
|
|
138
|
+
i = result.nextIndex;
|
|
139
|
+
return result.value;
|
|
140
|
+
};
|
|
141
|
+
switch (flag) {
|
|
142
|
+
case "--help":
|
|
143
|
+
case "-h":
|
|
144
|
+
help = true;
|
|
145
|
+
break;
|
|
146
|
+
case "--check":
|
|
147
|
+
enableCheckMode(checkMode);
|
|
148
|
+
break;
|
|
149
|
+
case "--doctor":
|
|
150
|
+
doctor = true;
|
|
151
|
+
enableCheckMode(checkMode);
|
|
152
|
+
break;
|
|
153
|
+
case "--validate-only":
|
|
154
|
+
enableCheckMode(checkMode);
|
|
155
|
+
break;
|
|
156
|
+
case "--validate-seeds":
|
|
157
|
+
enableCheckMode(checkMode);
|
|
158
|
+
break;
|
|
159
|
+
case "--validate-paths":
|
|
160
|
+
enableCheckMode(checkMode);
|
|
161
|
+
break;
|
|
162
|
+
case "--host": {
|
|
163
|
+
const value = read();
|
|
164
|
+
if (value
|
|
165
|
+
.split(",")
|
|
166
|
+
.map((token) => token.trim().toLowerCase())
|
|
167
|
+
.includes("all")) {
|
|
168
|
+
hostAll = true;
|
|
169
|
+
}
|
|
170
|
+
parsedHosts.push(...parseHostList(value));
|
|
171
|
+
break;
|
|
108
172
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
case "--adapter":
|
|
174
|
+
adapterModules.push(resolve(cwd, read()));
|
|
175
|
+
break;
|
|
176
|
+
case "--suites-dir":
|
|
177
|
+
suitesDir = read();
|
|
178
|
+
break;
|
|
179
|
+
case "--rubrics-dir":
|
|
180
|
+
rubricsDir = read();
|
|
181
|
+
break;
|
|
182
|
+
case "--suite":
|
|
183
|
+
filter = read();
|
|
184
|
+
break;
|
|
185
|
+
case "--scenario":
|
|
186
|
+
scenarioFilter = read();
|
|
187
|
+
break;
|
|
188
|
+
case "--staging-session-id":
|
|
189
|
+
stagingSessionId = read();
|
|
190
|
+
break;
|
|
191
|
+
case "--live":
|
|
192
|
+
throw new Error("--live was removed because agent-test now always runs a real agent");
|
|
193
|
+
case "--record":
|
|
194
|
+
throw new Error("--record was removed; direct runs capture transient traces automatically (use --keep-recordings to retain them)");
|
|
195
|
+
case "--record-fixtures":
|
|
196
|
+
throw new Error("--record-fixtures was removed because replay-based testing is deprecated and no longer supported");
|
|
197
|
+
case "--keep-recordings":
|
|
198
|
+
keepRecordings = true;
|
|
199
|
+
break;
|
|
200
|
+
case "--judge":
|
|
201
|
+
judge = true;
|
|
202
|
+
break;
|
|
203
|
+
case "--no-judge":
|
|
204
|
+
judge = false;
|
|
205
|
+
break;
|
|
206
|
+
case "--no-worktree":
|
|
207
|
+
worktree = false;
|
|
208
|
+
break;
|
|
209
|
+
case "--timeout-ms": {
|
|
210
|
+
const parsedTimeout = Number(read());
|
|
211
|
+
if (Number.isFinite(parsedTimeout) && parsedTimeout > 0) {
|
|
212
|
+
timeoutMs = parsedTimeout;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
132
215
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
216
|
+
case "--no-timeout":
|
|
217
|
+
noTimeout = true;
|
|
218
|
+
break;
|
|
219
|
+
case "--allow-user-input":
|
|
220
|
+
allowUserInput = true;
|
|
221
|
+
break;
|
|
222
|
+
case "--fail-on": {
|
|
223
|
+
const mode = read();
|
|
224
|
+
if (mode !== "all" && mode !== "behavior" && mode !== "infra-only") {
|
|
225
|
+
throw new Error("--fail-on must be all|behavior|infra-only");
|
|
226
|
+
}
|
|
227
|
+
failOn = mode;
|
|
228
|
+
break;
|
|
139
229
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
compareMode = true;
|
|
148
|
-
}
|
|
149
|
-
else if ((token === "--b" || token === "--compare-b") && argv[i + 1]) {
|
|
150
|
-
compareB = argv[++i];
|
|
151
|
-
compareMode = true;
|
|
152
|
-
}
|
|
153
|
-
else if ((token === "--out-dir" || token === "--compare-out") && argv[i + 1]) {
|
|
154
|
-
compareOutDir = argv[++i];
|
|
155
|
-
}
|
|
156
|
-
else if (token === "--report-out" && argv[i + 1]) {
|
|
157
|
-
reportOut = argv[++i];
|
|
158
|
-
}
|
|
159
|
-
else if (token === "--no-html-report") {
|
|
160
|
-
htmlReport = false;
|
|
161
|
-
}
|
|
162
|
-
else if (token === "--debug") {
|
|
163
|
-
debug = true;
|
|
164
|
-
}
|
|
165
|
-
else if (token === "--debug-dir") {
|
|
166
|
-
const value = argv[++i];
|
|
167
|
-
if (!value || value.startsWith("-")) {
|
|
168
|
-
throw new Error("--debug-dir requires a non-empty path argument");
|
|
230
|
+
case "--scenario-retries": {
|
|
231
|
+
const parsedRetries = Number(read());
|
|
232
|
+
if (!Number.isInteger(parsedRetries) || parsedRetries < 0) {
|
|
233
|
+
throw new Error("--scenario-retries must be an integer >= 0");
|
|
234
|
+
}
|
|
235
|
+
scenarioRetries = parsedRetries;
|
|
236
|
+
break;
|
|
169
237
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
238
|
+
case "--compare-pairs":
|
|
239
|
+
case "--a":
|
|
240
|
+
case "--b":
|
|
241
|
+
case "--compare-a":
|
|
242
|
+
case "--compare-b":
|
|
243
|
+
case "--out-dir":
|
|
244
|
+
case "--compare-out":
|
|
245
|
+
throw new Error(`${flag} is removed. Add compare.a and compare.b on one scenario. Set compare.faster or compare.cheaper when one arm must win on time or tokens. Add rubric.judge only when you want a pairwise judge.`);
|
|
246
|
+
case "--report-out":
|
|
247
|
+
reportOut = read();
|
|
248
|
+
break;
|
|
249
|
+
case "--no-html-report":
|
|
250
|
+
htmlReport = false;
|
|
251
|
+
break;
|
|
252
|
+
case "--debug":
|
|
253
|
+
debug = true;
|
|
254
|
+
break;
|
|
255
|
+
case "--debug-dir": {
|
|
256
|
+
const value = read();
|
|
257
|
+
debugDir = value;
|
|
258
|
+
debug = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unknown flag: ${flag}`);
|
|
180
263
|
}
|
|
181
264
|
}
|
|
265
|
+
check = checkMode.check;
|
|
266
|
+
validateOnly = checkMode.validateOnly;
|
|
267
|
+
validatePaths = checkMode.validatePaths;
|
|
268
|
+
validateSeeds = checkMode.validateSeeds;
|
|
269
|
+
const hosts = uniqueHosts(parsedHosts);
|
|
270
|
+
const host = hosts?.[0];
|
|
182
271
|
judge = judge ?? true;
|
|
183
272
|
worktree = worktree ?? true;
|
|
184
273
|
if (debug) {
|
|
@@ -192,6 +281,9 @@ export function parseCliArgs(argv) {
|
|
|
192
281
|
suitesDir,
|
|
193
282
|
rubricsDir,
|
|
194
283
|
host,
|
|
284
|
+
hosts,
|
|
285
|
+
hostAll,
|
|
286
|
+
adapterModules: adapterModules.length > 0 ? adapterModules : undefined,
|
|
195
287
|
filter,
|
|
196
288
|
scenarioFilter,
|
|
197
289
|
stagingSessionId,
|
|
@@ -201,6 +293,8 @@ export function parseCliArgs(argv) {
|
|
|
201
293
|
timeoutMs: noTimeout ? 0 : timeoutMs,
|
|
202
294
|
noTimeout,
|
|
203
295
|
allowUserInput,
|
|
296
|
+
check,
|
|
297
|
+
help,
|
|
204
298
|
doctor,
|
|
205
299
|
htmlReport,
|
|
206
300
|
reportOut: reportOut ? resolve(cwd, reportOut) : undefined,
|
|
@@ -211,11 +305,6 @@ export function parseCliArgs(argv) {
|
|
|
211
305
|
validatePaths,
|
|
212
306
|
failOn,
|
|
213
307
|
scenarioRetries,
|
|
214
|
-
compareMode,
|
|
215
|
-
compareA,
|
|
216
|
-
compareB,
|
|
217
|
-
comparePairs,
|
|
218
|
-
compareOutDir: compareOutDir ? resolve(cwd, compareOutDir) : undefined,
|
|
219
308
|
};
|
|
220
309
|
}
|
|
221
310
|
/** Resolve an explicit report target into the HTML path and optional artifact directory. */
|
|
@@ -228,62 +317,14 @@ export function resolveReportOutput(reportOut) {
|
|
|
228
317
|
}
|
|
229
318
|
return { htmlPath: join(reportOut, "report.html"), outDir: reportOut };
|
|
230
319
|
}
|
|
231
|
-
async function
|
|
232
|
-
|
|
233
|
-
await
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
catch {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
async function resolveSuiteScenariosPath(cwd, suitesDir, side) {
|
|
241
|
-
const candidates = [
|
|
242
|
-
resolve(cwd, side, "scenarios.json"),
|
|
243
|
-
resolve(cwd, suitesDir, side, "scenarios.json"),
|
|
244
|
-
resolve(side, "scenarios.json"),
|
|
245
|
-
];
|
|
246
|
-
for (const candidate of candidates) {
|
|
247
|
-
if (await pathExists(candidate)) {
|
|
248
|
-
return candidate;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
throw new Error(`compare side "${side}" is not a suite report JSON or suite dir (looked under ${suitesDir}/ and cwd)`);
|
|
252
|
-
}
|
|
253
|
-
async function loadOrRunCompareSide(args, side, stagingSessionId) {
|
|
254
|
-
const resolved = resolve(args.cwd, side);
|
|
255
|
-
if (side.endsWith(".json") || resolved.endsWith(".json")) {
|
|
256
|
-
const jsonPath = side.endsWith(".json") ? resolved : resolve(args.cwd, `${side}.json`);
|
|
257
|
-
const path = (await pathExists(resolved)) ? resolved : jsonPath;
|
|
258
|
-
if (!(await pathExists(path))) {
|
|
259
|
-
throw new Error(`compare report not found: ${side}`);
|
|
320
|
+
async function htmlReportTip(label, reportPath) {
|
|
321
|
+
if (shouldStartReportPreview()) {
|
|
322
|
+
const url = await startDetachedReportPreview(reportPath);
|
|
323
|
+
if (url) {
|
|
324
|
+
return theme.fileTip(label, url);
|
|
260
325
|
}
|
|
261
|
-
return loadSuiteRunReport(path);
|
|
262
326
|
}
|
|
263
|
-
|
|
264
|
-
return runSuite({
|
|
265
|
-
cwd: args.cwd,
|
|
266
|
-
suitePath,
|
|
267
|
-
host: args.host,
|
|
268
|
-
scenarioFilter: args.scenarioFilter,
|
|
269
|
-
judge: args.judge,
|
|
270
|
-
worktree: args.worktree,
|
|
271
|
-
stagingSessionId,
|
|
272
|
-
keepRecordings: args.keepRecordings,
|
|
273
|
-
suitesDir: args.suitesDir,
|
|
274
|
-
rubricsDir: args.rubricsDir,
|
|
275
|
-
timeoutMs: args.timeoutMs,
|
|
276
|
-
allowUserInput: args.allowUserInput,
|
|
277
|
-
debug: args.debug,
|
|
278
|
-
debugDir: args.debugDir,
|
|
279
|
-
scenarioRetries: args.scenarioRetries,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
async function writeSuiteReportDump(outDir, label, report) {
|
|
283
|
-
await mkdir(outDir, { recursive: true });
|
|
284
|
-
const path = join(outDir, `${label}.suite-report.json`);
|
|
285
|
-
await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
286
|
-
return path;
|
|
327
|
+
return theme.fileTip(label, reportPath);
|
|
287
328
|
}
|
|
288
329
|
async function cleanupRunArtifacts(stagingSessionRoot, keepRecordings) {
|
|
289
330
|
if (keepRecordings) {
|
|
@@ -302,65 +343,38 @@ async function main() {
|
|
|
302
343
|
let args;
|
|
303
344
|
try {
|
|
304
345
|
args = parseCliArgs(process.argv);
|
|
346
|
+
installHostLogFilter();
|
|
305
347
|
}
|
|
306
348
|
catch (error) {
|
|
307
349
|
console.error(error instanceof Error ? error.message : error);
|
|
308
350
|
return 1;
|
|
309
351
|
}
|
|
310
|
-
if (args.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
352
|
+
if (args.help) {
|
|
353
|
+
console.log(formatHelp());
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await loadHostAdapters({ cwd: args.cwd, adapterModules: args.adapterModules });
|
|
358
|
+
if (args.hostAll) {
|
|
359
|
+
args.hosts = uniqueHosts(knownAgentHosts());
|
|
360
|
+
args.host = args.hosts?.[0];
|
|
314
361
|
}
|
|
315
|
-
return report.ok ? 0 : 1;
|
|
316
362
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const aReport = await loadSuiteRunReport(aPath);
|
|
321
|
-
const bReport = await loadSuiteRunReport(bPath);
|
|
322
|
-
const outDir = args.compareOutDir ?? resolve(args.cwd, "compare-out");
|
|
323
|
-
const compare = compareSuiteReports({
|
|
324
|
-
aLabel: labelForCompareSide(aPath),
|
|
325
|
-
bLabel: labelForCompareSide(bPath),
|
|
326
|
-
a: aReport,
|
|
327
|
-
b: bReport,
|
|
328
|
-
});
|
|
329
|
-
const written = await writeCompareReport({ outDir, report: compare });
|
|
330
|
-
console.log(theme.tip(`compare JSON: ${written.jsonPath}`));
|
|
331
|
-
console.log(theme.tip(`compare markdown: ${written.markdownPath}`));
|
|
332
|
-
console.log(theme.fileTip("compare HTML", written.htmlPath));
|
|
333
|
-
return compare.summary.passRegressions > 0 ? 1 : 0;
|
|
363
|
+
catch (error) {
|
|
364
|
+
console.error(error instanceof Error ? error.message : error);
|
|
365
|
+
return 1;
|
|
334
366
|
}
|
|
335
|
-
if (args.validateOnly || args.validateSeeds) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
console.log(formatValidationReport(report));
|
|
347
|
-
if (!report.ok) {
|
|
348
|
-
return 1;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
if (args.validateSeeds) {
|
|
352
|
-
const report = await validateSeedPatches({
|
|
353
|
-
cwd: args.cwd,
|
|
354
|
-
suitesDir: args.suitesDir,
|
|
355
|
-
rubricsDir: args.rubricsDir,
|
|
356
|
-
filter: args.filter,
|
|
357
|
-
});
|
|
358
|
-
console.log(formatSeedValidationReport(report));
|
|
359
|
-
if (!report.ok) {
|
|
360
|
-
return 1;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return 0;
|
|
367
|
+
if (args.check || args.doctor || args.validateOnly || args.validateSeeds) {
|
|
368
|
+
const report = await runCheck({
|
|
369
|
+
cwd: args.cwd,
|
|
370
|
+
suitesDir: args.suitesDir,
|
|
371
|
+
filter: args.filter,
|
|
372
|
+
rubricsDir: args.rubricsDir,
|
|
373
|
+
host: args.host,
|
|
374
|
+
hosts: args.hosts,
|
|
375
|
+
});
|
|
376
|
+
console.log(formatCheckReport(report));
|
|
377
|
+
return report.ok ? 0 : 1;
|
|
364
378
|
}
|
|
365
379
|
if (args.debugDir) {
|
|
366
380
|
setLiveStagingRootOverride(args.debugDir);
|
|
@@ -377,27 +391,59 @@ async function main() {
|
|
|
377
391
|
? getLiveStagingSessionRoot(stagingSessionId)
|
|
378
392
|
: undefined;
|
|
379
393
|
try {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
394
|
+
let checkHosts = args.hosts ?? (args.host ? [args.host] : ["cursor"]);
|
|
395
|
+
if (!isChild) {
|
|
396
|
+
const report = await runCheck({
|
|
397
|
+
cwd: args.cwd,
|
|
398
|
+
suitesDir: args.suitesDir,
|
|
399
|
+
filter: args.filter,
|
|
400
|
+
rubricsDir: args.rubricsDir,
|
|
401
|
+
host: args.host,
|
|
402
|
+
hosts: args.hosts,
|
|
403
|
+
});
|
|
404
|
+
console.log(theme.tip(formatCheckSummary(report)));
|
|
405
|
+
if (!report.ok) {
|
|
406
|
+
console.error(formatCheckReport(report));
|
|
407
|
+
return 1;
|
|
387
408
|
}
|
|
388
|
-
|
|
389
|
-
|
|
409
|
+
checkHosts = report.hosts;
|
|
410
|
+
const missingHost = missingHostsAuth(report.hosts);
|
|
411
|
+
if (missingHost) {
|
|
412
|
+
console.error(missingHost);
|
|
390
413
|
return 1;
|
|
391
414
|
}
|
|
392
415
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
416
|
+
else {
|
|
417
|
+
const missingHost = missingAgentAuth(args.host ?? "cursor");
|
|
418
|
+
if (missingHost) {
|
|
419
|
+
console.error(missingHost);
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
396
422
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
423
|
+
if (args.judge !== false) {
|
|
424
|
+
let needsJudge = false;
|
|
425
|
+
try {
|
|
426
|
+
needsJudge = await selectedRunNeedsJudge({
|
|
427
|
+
cwd: args.cwd,
|
|
428
|
+
suitesDir: args.suitesDir,
|
|
429
|
+
filter: args.filter,
|
|
430
|
+
scenarioFilter: args.scenarioFilter,
|
|
431
|
+
rubricsDir: args.rubricsDir,
|
|
432
|
+
judge: args.judge,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
needsJudge = false;
|
|
437
|
+
}
|
|
438
|
+
if (needsJudge) {
|
|
439
|
+
for (const host of checkHosts) {
|
|
440
|
+
const missingJudge = missingClassifierAuth(host);
|
|
441
|
+
if (missingJudge) {
|
|
442
|
+
console.error(`${missingJudge} (use --no-judge to skip)`);
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
401
447
|
}
|
|
402
448
|
{
|
|
403
449
|
try {
|
|
@@ -412,7 +458,7 @@ async function main() {
|
|
|
412
458
|
process.env.AGENT_TEST_NO_WORKTREE === "1" ||
|
|
413
459
|
process.env.AGENT_TEST_NO_WORKTREE === "true";
|
|
414
460
|
if (worktreeDisabled && !inPlaceAllowed) {
|
|
415
|
-
console.error("Direct agent tests require
|
|
461
|
+
console.error("Direct agent tests require a sealed temp workspace. Set AGENT_TEST_ALLOW_IN_PLACE=1 to run in repo cwd (--no-worktree leaks agent edits into your working tree).");
|
|
416
462
|
return 1;
|
|
417
463
|
}
|
|
418
464
|
registerLiveRunHandlers();
|
|
@@ -425,61 +471,28 @@ async function main() {
|
|
|
425
471
|
if (stagingSessionRoot) {
|
|
426
472
|
console.log(theme.bannerSession(stagingSessionRoot));
|
|
427
473
|
}
|
|
428
|
-
|
|
429
|
-
if (worktreeDisabled) {
|
|
430
|
-
console.warn(theme.warn("running in repo cwd — agent file edits will persist in your working tree"));
|
|
431
|
-
}
|
|
474
|
+
const hints = ["Ctrl+C cancels"];
|
|
432
475
|
if (args.debug || process.env.AGENT_TEST_VERBOSE === "1") {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
console.log(` ${theme.tip("debug: recordings kept")}`);
|
|
438
|
-
}
|
|
439
|
-
console.log(` ${theme.tip("exit 137 = macOS OOM — isolated subprocesses (AGENT_TEST_NO_ISOLATE=1 to disable)")}`);
|
|
476
|
+
hints.push(args.keepRecordings ? "recordings kept" : "traces removed on exit");
|
|
477
|
+
hints.push("exit 137 = macOS OOM");
|
|
440
478
|
}
|
|
441
479
|
else if (!args.keepRecordings) {
|
|
442
|
-
|
|
480
|
+
hints.push("traces removed on exit");
|
|
481
|
+
}
|
|
482
|
+
console.log(theme.bannerHints(hints));
|
|
483
|
+
if (worktreeDisabled) {
|
|
484
|
+
console.warn(theme.warn("running in repo cwd — agent file edits will persist in your working tree"));
|
|
443
485
|
}
|
|
444
486
|
}
|
|
445
487
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const outDir = args.compareOutDir ??
|
|
455
|
-
(stagingSessionRoot
|
|
456
|
-
? join(stagingSessionRoot, "compare")
|
|
457
|
-
: resolve(args.cwd, "compare-out"));
|
|
458
|
-
await writeSuiteReportDump(outDir, labelForCompareSide(a), aReport);
|
|
459
|
-
await writeSuiteReportDump(outDir, labelForCompareSide(b), bReport);
|
|
460
|
-
const compare = compareSuiteReports({
|
|
461
|
-
aLabel: labelForCompareSide(a),
|
|
462
|
-
bLabel: labelForCompareSide(b),
|
|
463
|
-
a: aReport,
|
|
464
|
-
b: bReport,
|
|
465
|
-
});
|
|
466
|
-
comparePassRegressions = compare.summary.passRegressions;
|
|
467
|
-
const written = await writeCompareReport({ outDir, report: compare });
|
|
468
|
-
console.log(`\n${theme.tip(`compare JSON: ${written.jsonPath}`)}`);
|
|
469
|
-
console.log(theme.tip(`compare markdown: ${written.markdownPath}`));
|
|
470
|
-
console.log(theme.fileTip("compare HTML", written.htmlPath));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
reports = await runAllSuites({
|
|
475
|
-
...args,
|
|
476
|
-
stagingSessionId,
|
|
477
|
-
timeoutMs: args.timeoutMs,
|
|
478
|
-
allowUserInput: args.allowUserInput,
|
|
479
|
-
debug: args.debug,
|
|
480
|
-
debugDir: args.debugDir,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
488
|
+
const reports = await runAllSuites({
|
|
489
|
+
...args,
|
|
490
|
+
stagingSessionId,
|
|
491
|
+
timeoutMs: args.timeoutMs,
|
|
492
|
+
allowUserInput: args.allowUserInput,
|
|
493
|
+
debug: args.debug,
|
|
494
|
+
debugDir: args.debugDir,
|
|
495
|
+
});
|
|
483
496
|
let exitCode = 0;
|
|
484
497
|
if (!isChild) {
|
|
485
498
|
for (const report of reports) {
|
|
@@ -505,7 +518,10 @@ async function main() {
|
|
|
505
518
|
console.log(theme.summary(report.suite, report.passed, report.failed, report.skipped));
|
|
506
519
|
}
|
|
507
520
|
const runSummary = summarizeReports(reports);
|
|
508
|
-
|
|
521
|
+
const runSummaryText = formatRunSummary(runSummary);
|
|
522
|
+
if (runSummaryText) {
|
|
523
|
+
console.log(`\n${theme.runSummary(runSummaryText)}`);
|
|
524
|
+
}
|
|
509
525
|
if (args.failOn === "behavior" && runSummary.infraFailures > 0 && exitCode === 0) {
|
|
510
526
|
console.log(theme.tip(`${runSummary.infraFailures} infra failure(s) ignored (--fail-on=behavior)`));
|
|
511
527
|
}
|
|
@@ -514,23 +530,16 @@ async function main() {
|
|
|
514
530
|
}
|
|
515
531
|
if (args.htmlReport && reports.length > 0) {
|
|
516
532
|
try {
|
|
517
|
-
const pair = args.comparePairs ? parseComparePairToken(args.comparePairs) : undefined;
|
|
518
533
|
const reportPath = await writeHtmlReport(reports, {
|
|
519
534
|
host: args.host,
|
|
520
535
|
suitesDir: args.suitesDir,
|
|
521
|
-
includeCompare: Boolean(args.comparePairs),
|
|
522
|
-
compareALabel: pair ? labelForCompareSide(pair.a) : undefined,
|
|
523
|
-
compareBLabel: pair ? labelForCompareSide(pair.b) : undefined,
|
|
524
536
|
});
|
|
525
|
-
console.log(`\n${
|
|
537
|
+
console.log(`\n${await htmlReportTip("HTML report", reportPath)}`);
|
|
526
538
|
}
|
|
527
539
|
catch (error) {
|
|
528
540
|
console.warn(theme.warn(`HTML report failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
529
541
|
}
|
|
530
542
|
}
|
|
531
|
-
if (comparePassRegressions > 0) {
|
|
532
|
-
exitCode = 1;
|
|
533
|
-
}
|
|
534
543
|
}
|
|
535
544
|
else {
|
|
536
545
|
for (const report of reports) {
|