@post-print/agent-test 0.3.3 → 0.3.4

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 CHANGED
@@ -1,210 +1,100 @@
1
1
  # @post-print/agent-test
2
2
 
3
- **Source of truth for** agent-test package.
3
+ **Source of truth for** the direct-agent test package.
4
4
 
5
- <!-- doc-meta: owner=eng | last-reviewed=2026-07-29 -->
5
+ <!-- doc-meta: owner=eng | last-reviewed=2026-09-02 -->
6
6
 
7
- Jest-shaped agent scenario runner built on `@post-print/agent-harness`.
7
+ Scenario runner for real Cursor and Claude agents, built on `@post-print/agent-harness`.
8
8
 
9
- ## In-repo smoke (this monorepo)
9
+ > **Deprecated and removed:** stored-trace replay is not an agent test. `host: "replay"`, `replayTrace`, `--record-fixtures`, and the old `--live` mode flag are rejected with migration guidance. Every scenario execution now launches Cursor or Claude and can incur provider usage.
10
10
 
11
- After `bun run build` at the repo root:
11
+ ## Direct TypeScript API
12
12
 
13
- ```bash
14
- node packages/test/dist/cli.js --suites-dir packages/test/fixtures --suite smoke
15
- node packages/test/dist/cli.js --doctor
13
+ `runAgentTest` is the execution core. JSON suites load into this same function.
14
+
15
+ ```ts
16
+ import { runAgentTest } from "@post-print/agent-test";
17
+
18
+ const result = await runAgentTest({
19
+ cwd: process.cwd(),
20
+ scenario: {
21
+ name: "uses the project instructions",
22
+ prompt: "Review the current change.",
23
+ rubric: { mustReadPath: ["AGENTS.md"] },
24
+ },
25
+ });
16
26
  ```
17
27
 
18
- There is no top-level `agent-suites/` here — consumer examples below assume a consuming repo. Live `--live` needs an **exported** `CURSOR_API_KEY` for Cursor (default). `--host claude` needs an explicit `CLAUDE_AUTH_MODE=api-key` (with `ANTHROPIC_API_KEY`) or `CLAUDE_AUTH_MODE=subscription` (the Claude Code CLI login); nothing is inferred (copy repo-root `.env.example`; the CLI does not auto-load `.env`). Judge classifiers still need `CURSOR_API_KEY` unless you pass `--no-judge`.
28
+ The default host is Cursor. Set `scenario.host: "claude"`, `defaults.host`, or the top-level `host` option for Claude. The scenario host wins over the top-level override, which wins over suite defaults. The default suite name is `direct`; worktree isolation, judging, timeout enforcement, and announce-stop retry are enabled by default.
29
+
30
+ ## JSON suites and CLI
19
31
 
20
- ## CLI (consumers, Node >= 22)
32
+ JSON is an authoring adapter, not a stored answer. Consumer repositories can keep suites under `agent-suites/<suite>/scenarios.json`:
33
+
34
+ ```json
35
+ {
36
+ "name": "routing",
37
+ "defaults": { "host": "cursor", "profile": "cursor" },
38
+ "scenarios": [
39
+ {
40
+ "name": "reads instructions",
41
+ "prompt": "Review the current change.",
42
+ "rubric": { "mustReadPath": ["AGENTS.md"] }
43
+ }
44
+ ]
45
+ }
46
+ ```
21
47
 
22
- Works under **Node >= 22** (the published `agent-test` bin):
48
+ Run Cursor by default or select Claude explicitly:
23
49
 
24
50
  ```bash
25
51
  npx agent-test --suites-dir agent-suites
26
- npx agent-test --suites-dir agent-suites --suite ambient-routing
27
- npx agent-test --suites-dir agent-suites --live --suite ambient-routing # exported CURSOR_API_KEY required
28
- npx agent-test --suites-dir agent-suites --live --host claude --suite ambient-routing # CLAUDE_AUTH_MODE + claude CLI
29
- npx agent-test --live --compare-pairs skeleton-clean:skeleton-messy --out-dir "$TMPDIR/compare"
30
- npx agent-test compare --a clean.suite-report.json --b messy.suite-report.json --out-dir "$TMPDIR/compare"
31
- npx agent-test --suites-dir agent-suites --report-out "$TMPDIR/agent-test-report" # dir: html + suite JSON
32
- npx agent-test --suites-dir agent-suites --report-out "$TMPDIR/reports/run.html" # file: html only
52
+ npx agent-test --suites-dir agent-suites --suite routing
53
+ npx agent-test --suites-dir agent-suites --host claude
33
54
  npx agent-test --doctor
34
55
  ```
35
56
 
36
- ### Report output
57
+ Direct runs require an exported `CURSOR_API_KEY` for Cursor or `ANTHROPIC_API_KEY` plus the Claude Code CLI for Claude. Judge criteria use `CURSOR_API_KEY` unless `--no-judge` is set. The CLI does not load `.env`.
37
58
 
38
- By default the HTML report goes to a fresh temp directory. `--report-out <path>` puts it where you want:
59
+ The old `--live` flag is removed because direct execution is now the only execution mode. Direct runs capture transient traces automatically; use `--keep-recordings` or `--debug` to retain diagnostics.
39
60
 
40
- | `--report-out` value | Result |
41
- | --- | --- |
42
- | ends with `.html` | the HTML report is written to exactly that file (parent dirs created) |
43
- | any other path | treated as a directory: `report.html` plus all other report content — per-suite `<suite>.suite-report.json`, and the compare JSON / markdown / HTML when `--compare-pairs` is used |
61
+ ## Validation, rubrics, and comparison
44
62
 
45
- `--out-dir` still wins for compare output when both are given. `--no-html-report` skips the HTML report entirely.
46
-
47
- Bun is fine for local package development (`bun install` / `bun run build` in this monorepo), but consumers do not need Bun to run suites.
48
-
49
- Default suites root: `agent-suites/` (must exist, or pass `--suites-dir`). Absolute `--suites-dir` is supported. Optional `--rubrics-dir` loads harness-only answer keys from `<rubricsDir>/<suite>/rubrics.json` (preferred over a sibling file when present).
50
-
51
- ### Harness-only rubrics
52
-
53
- `scenarios.json` may omit `rubric` (or use `{}`) and keep only prompts / `seedPatch` / `compareId`. Put `must` / `mustNot` / `judge` / tool matchers in:
54
-
55
- - sibling `rubrics.json` or `scenarios.rubric.json`: `{ "scenarios": { "<scenario name>": { "must": […] } } }`
56
- - or `--rubrics-dir <path>` → `<path>/<suiteName>/rubrics.json`
57
-
58
- External entries **replace** the inline rubric for that scenario name. Unknown names in the rubrics file are an error. Keep rubrics off agent-visible roots (absolute `--suites-dir` / `--rubrics-dir` under `$TMPDIR`, or outside the IDE-open workspace).
59
-
60
- Live output always uses ANSI color (including under Cursor agent shells that set `NO_COLOR`).
61
-
62
- ## Environment variables
63
-
64
- See repo-root `.env.example`. Common knobs:
65
-
66
- | Variable | Purpose |
67
- | ------------------------------------------- | ------------------------------------------------------------- |
68
- | `CURSOR_API_KEY` | Required for `--live` Cursor and judge classifiers |
69
- | `CLAUDE_AUTH_MODE` | Required for `--live --host claude`: `api-key` or `subscription` (no default) |
70
- | `ANTHROPIC_API_KEY` | Required when `CLAUDE_AUTH_MODE=api-key` |
71
- | `CLAUDE_CODE_BIN` | Optional path to Claude Code CLI binary |
72
- | `CLAUDE_AGENT_MODEL` | Optional Claude model override |
73
- | `CLAUDE_CODE_ALLOWED_TOOLS` | Optional `--allowedTools` list for Claude live runs |
74
- | `AGENT_TEST_DEBUG` | Same as `--debug` when `1`/`true` |
75
- | `AGENT_TEST_VERBOSE` | Extra tips (e.g. OOM isolation) when `1` |
76
- | `AGENT_TEST_VERBOSE_PATHS` | Print full paths when `1` |
77
- | `AGENT_TEST_QUIET` | Suppress progress when `1` |
78
- | `AGENT_TEST_TIMEOUT_MS` | Live hard timeout (default 600000; `0` disables) |
79
- | `AGENT_TEST_LIVE_RETRIES` | Judge infra retry attempts (default 3) |
80
- | `AGENT_TEST_SCENARIO_RETRIES` | Live announce-stop scenario retries (default 1; `0` disables) |
81
- | `AGENT_TEST_ALLOW_IN_PLACE` | Allow `--no-worktree` live runs when `1` |
82
- | `AGENT_TEST_NO_WORKTREE` | Disable worktree isolation when `1`/`true` |
83
- | `AGENT_TEST_NO_ISOLATE` | Disable isolated subprocesses when `1` |
84
- | `AGENT_TEST_SCENARIO_SETTLE_MS` | Settle delay between live scenarios |
85
- | `CURSOR_AGENT_MODEL` / `CURSOR_JUDGE_MODEL` | Optional model overrides |
86
- | `CURSOR_JUDGE_TEMPERATURE` | Optional judge temperature |
87
-
88
- ## Debug mode
63
+ These commands inspect configuration or existing reports; they do not claim to execute an agent:
89
64
 
90
65
  ```bash
91
- npx agent-test --suites-dir agent-suites --suite smoke --debug
92
- npx agent-test --suites-dir agent-suites --live --debug
93
- npx agent-test --suites-dir agent-suites --live --debug --debug-dir "$TMPDIR/agent-test-debug"
66
+ npx agent-test --validate-only --validate-paths --suites-dir agent-suites
67
+ npx agent-test --validate-seeds --suites-dir agent-suites
68
+ npx agent-test --compare-pairs clean:changed --out-dir "$TMPDIR/compare"
69
+ npx agent-test compare --a clean.suite-report.json --b changed.suite-report.json --out-dir "$TMPDIR/compare"
94
70
  ```
95
71
 
96
- `--debug` (or `AGENT_TEST_DEBUG=1`) implies `--keep-recordings`, verbose failure detail, and full paths. Every non-skipped scenario writes a bundle under the session root:
97
-
98
- ```
99
- sessions/<id>/<suite>/<scenario>.debug/
100
- summary.md # verdict + Why (category hint + evidence + trace stats)
101
- transcript.md # Why, prompt, rubric, interleaved messages/tools (incl. results), failures
102
- scenario.json # prompt + rubric + seed metadata
103
- result.json # pass/fail, duration, failures, usage, skillsInvoked, routing, counts
104
- trace.json
105
- failures.json # includes category + evidence
106
- judge-debug.json # when judge criteria ran (SDK status/error, sizes, attempt)
107
- environment.json # versions/models/timeout/isolation; API keys only as booleans
108
- rerun.sh # shell-quoted exact re-run command (export API keys yourself)
109
- ```
72
+ `scenarios.json` may omit inline rubric keys when they are supplied by sibling `rubrics.json` / `scenarios.rubric.json`, or by `--rubrics-dir <path>` at `<path>/<suite>/rubrics.json`. External rubric entries replace the inline rubric for the same scenario.
110
73
 
111
- **Debug dir default:** omit `--debug-dir` to stage under `$TMPDIR/agent-spec/sessions/<id>/…` (outside the repo). Passing an in-repo `--debug-dir` (for example `./agent-test-debug`) is supported harness staging paths under that dir are excluded from worktree leak checks — but prefer `$TMPDIR` so debug artifacts never appear in `git status`.
74
+ Reports pair scenarios by `compareId` when present, otherwise by normalized scenario name. JSON, Markdown, and HTML reports include outcomes, token usage, duration, tools, and grounding signals.
112
75
 
113
- `--debug-dir <path>` replaces `$TMPDIR/agent-spec` as the sessions parent (`<path>/sessions/<id>/…`).
76
+ ## Isolation and diagnostics
114
77
 
115
- Failure categories printed on FAIL lines and in `failures.json`:
78
+ Each scenario uses a detached git worktree by default. This isolates edits, but it does not prevent a local host from reading the IDE-open caller checkout. Keep answer keys outside agent-visible roots when that distinction matters.
116
79
 
117
- | Category | Meaning |
118
- | ----------------- | -------------------------------------------------------------------------------------- |
119
- | `rubric_miss` | Assertion/judge criterion miss |
120
- | `judge_infra` | Judge SDK/API failure (not a criterion miss) |
121
- | `agent_runtime` | Agent session error, timeout, AskQuestion, subprocess exit |
122
- | `worktree_leak` | Live agent mutated the caller working tree (harness `--debug-dir` staging is excluded) |
123
- | `recording_error` | Failed to persist a staging/fixture trace |
80
+ `--debug` retains an evidence bundle under `$TMPDIR/agent-spec/sessions/<id>/` by default. It includes the transcript, scenario, result, trace, failures, environment metadata, judge details, and an exact direct-run rerun command. Use `--debug-dir` to override the parent directory.
124
81
 
125
- **Cancel:** `Ctrl+C` (SIGINT) kills in-flight isolated scenario subprocesses and best-effort cancels the active Cursor SDK run, then cleans scenario worktrees.
82
+ `Ctrl+C` cancels active Cursor/Claude work and cleans worktrees. `--no-worktree` requires `AGENT_TEST_ALLOW_IN_PLACE=1` because agent edits will persist in the caller checkout.
126
83
 
127
- ## Live dogfood
128
-
129
- Live runs need `CURSOR_API_KEY` and a suites directory that exists. Preflight fails when the resolved suites directory is missing (default `agent-suites/` if `--suites-dir` is omitted). `--suites-dir` may be relative to the repo cwd or an absolute path (for example a scrubbed suite tree under `$TMPDIR`).
130
-
131
- Passing live runs write staging traces under `$TMPDIR/agent-spec/sessions/<pid>-<timestamp>/` (removed on exit unless `--keep-recordings`). Use `--record-fixtures` to overwrite each scenario's committed `replayTrace` path. `--no-worktree` requires `AGENT_TEST_ALLOW_IN_PLACE=1`.
132
-
133
- ### Isolation model
134
-
135
- Live runs use a **detached git worktree** for agent file edits. That is not full filesystem isolation:
136
-
137
- - **Worktree does:** keep seed/apply edits and agent Write/Edit tools off the caller's working tree (leak checks catch escapes).
138
- - **Worktree does not:** stop context (skills/rules) from loading from the caller checkout by design, or stop Cursor **local** agents from Shell/Read against the IDE-open workspace instead of only `local.cwd`.
139
-
140
- Therefore: do **not** put answer keys, golden replays, or judge-bearing scenario text where a null-arm agent can forage them on the caller/IDE root. Prefer:
141
-
142
- - opaque prompts + `compareId`
143
- - seeds that only mutate fixtures (not skill bodies)
144
- - harness-only rubrics (sibling `rubrics.json` / `scenarios.rubric.json`, or `--rubrics-dir` outside the open workspace)
145
- - consumer orchestrators that park answer keys off the open workspace (toolbox pattern)
146
- - cloud runtime when true FS isolation is required
147
-
148
- Live agent runs have a **hard timeout** (default **10 minutes**, override with `--timeout-ms` or `AGENT_TEST_TIMEOUT_MS`; disable with `--no-timeout` or `AGENT_TEST_TIMEOUT_MS=0`). If the agent invokes `AskQuestion` or similar user-input tools, the harness fails fast with a clear error — live mode is single-shot and cannot supply follow-up turns. Use `--allow-user-input` only for intentional multi-turn dogfood (the run may still hang waiting for stdin).
149
-
150
- Announce-stop flakes (agent exits after Routing with no tools) are retried once by default on live runs (`AGENT_TEST_SCENARIO_RETRIES=1` or `--scenario-retries 1`; set `0` to disable). This is separate from `AGENT_TEST_LIVE_RETRIES` (judge infra only).
151
-
152
- ### Dialogue skills in live runs
153
-
154
- Skills that expect multi-turn Socratic dialogue (for example `crystallize`) will hang or fail in `--live` unless the scenario is written for one-shot completion:
155
-
156
- - **Replay-only** — commit a golden trace where the agent finishes without asking questions; use `host: "replay"` or `skip: true` with live skipped at suite level.
157
- - **One-shot live prompt** — instruct the agent to mirror intent and emit the final artifact in a single turn (`no AskQuestion; produce Crystallized idea now`).
158
- - **Ambient routing** — fuzzy-intent scenarios that only require mirroring + one assumption are naturally one-shot; full crystallize dialogue is not.
159
-
160
- Do not weaken dialogue-first product skills for CI; reshape the suite contract instead.
84
+ Dialogue-first skills must be tested with a one-shot prompt or an intentional `--allow-user-input` run. Do not weaken the production skill to make a headless test pass.
161
85
 
162
86
  ## MCP servers
163
87
 
164
- Live Cursor runs can attach **inline** MCP servers from suite/scenario JSON. Ambient project/user MCP (`.cursor/mcp.json`) is not loaded — tests stay hermetic.
165
-
166
- ```json
167
- {
168
- "defaults": {
169
- "mcpServers": {
170
- "docs": {
171
- "type": "http",
172
- "url": "https://example.com/mcp",
173
- "headers": { "Authorization": "Bearer ${DOCS_TOKEN}" }
174
- }
175
- }
176
- },
177
- "scenarios": [
178
- {
179
- "name": "use echo",
180
- "prompt": "Call the echo tool with text hello.",
181
- "mcpServers": {
182
- "echo": {
183
- "type": "stdio",
184
- "command": "node",
185
- "args": ["packages/test/fixtures/mcp-echo/server.mjs"]
186
- }
187
- },
188
- "rubric": {
189
- "mustCallTool": ["echo:hello"],
190
- "mustNotCallTool": ["shell"]
191
- }
192
- }
193
- ]
194
- }
195
- ```
88
+ Cursor and Claude scenarios can attach inline stdio or HTTP/SSE MCP servers through suite defaults or scenario overrides. Scenario server names replace matching defaults. `${ENV_VAR}` placeholders resolve at run time. Ambient project/user MCP configuration is not loaded.
196
89
 
197
- - Suite `defaults.mcpServers` merge with scenario `mcpServers` by server name (scenario wins).
198
- - `${ENV_VAR}` placeholders expand in `command`, `args`, `env`, `url`, `headers`, and OAuth fields at run time.
199
- - Rubric `mustCallTool` / `mustNotCallTool` match tool **name** substrings (works with MCP name prefixes). Use `name:argFragment` to also require a substring in JSON args.
200
- - Rubric `mustReadPath` / `mustNotReadPath` match substrings on **Read** tool JSON args (registry-first / avoid inventing paths). Keep hallucination scoring in live `judge` questions — no heavy factuality engine in v1.
201
- - Suite defaults may set `profile: "skeleton"` and/or `contextSources` (additive paths / `.skeleton/customize/` basenames). Shared/cursor/claude defaults stay backwards-compatible.
202
- - Live runs surface provider `usage` on traces/results; suite summary + HTML report include token sum / p50 / p95 when present.
203
- - `--compare-pairs A:B` (or `agent-test compare --a/--b`) pairs scenarios by **`compareId`** when present, else band-neutral scenario name (`outcome:` / `transfer:` stripped), and writes `compare-report.json` / `.md` / `.html` with pass/fail, tokens, toolCallCount, durationMs, and skill/registry hop proxies. Suite HTML also embeds an A/B table when two reports are present.
204
- - Replay hosts ignore `mcpServers` but still score recorded `toolCalls` against those matchers.
90
+ ## In-repo package checks
205
91
 
206
- ## Library
92
+ The repository does not run paid agents in its default CI package-integrity checks:
207
93
 
208
- ```ts
209
- import { runAllSuites, expectTrace } from "@post-print/agent-test";
94
+ ```bash
95
+ bun run build
96
+ node packages/test/dist/cli.js --validate-only --suites-dir packages/test/fixtures --suite smoke
97
+ node packages/test/dist/cli.js --doctor
210
98
  ```
99
+
100
+ Credentialed acceptance requires one TypeScript `runAgentTest` call and one JSON-suite CLI run against a real host.
package/dist/cli.d.ts CHANGED
@@ -10,9 +10,6 @@ export interface ParsedCliArgs {
10
10
  filter?: string;
11
11
  scenarioFilter?: string;
12
12
  stagingSessionId?: string;
13
- record: boolean;
14
- recordFixtures: boolean;
15
- live: boolean;
16
13
  judge?: boolean;
17
14
  worktree?: boolean;
18
15
  keepRecordings: boolean;
@@ -35,16 +32,13 @@ export interface ParsedCliArgs {
35
32
  compareMode: boolean;
36
33
  compareA?: string;
37
34
  compareB?: string;
38
- /** Live/replay A:B suite dirs or report JSON paths. */
35
+ /** Direct-run A:B suite dirs or report JSON paths. */
39
36
  comparePairs?: string;
40
37
  compareOutDir?: string;
41
38
  }
42
39
  /** Parse agent-test CLI argv (exported for unit tests). */
43
40
  export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
44
- /**
45
- * Split `--report-out` into an HTML file path and, when a directory was given,
46
- * the directory every other report artifact is written to.
47
- */
41
+ /** Resolve an explicit report target into the HTML path and optional artifact directory. */
48
42
  export declare function resolveReportOutput(reportOut?: string): {
49
43
  htmlPath?: string;
50
44
  outDir?: string;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,2BAA2B,CAAC;AAuBnC,OAAO,EACN,KAAK,UAAU,EAIf,MAAM,oBAAoB,CAAC;AAU5B,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAiL1D;AAoED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9F"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,2BAA2B,CAAC;AAsBnC,OAAO,EACN,KAAK,UAAU,EAIf,MAAM,oBAAoB,CAAC;AAU5B,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAmL1D;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9F"}
package/dist/cli.js CHANGED
@@ -8,9 +8,9 @@ import { compareSuiteReports, labelForCompareSide, loadSuiteRunReport, parseComp
8
8
  import { discoverSuites } from "./discover-suites.js";
9
9
  import { runDoctor } from "./doctor.js";
10
10
  import { writeHtmlReport } from "./html-report.js";
11
- import { assertLiveDogfoodPreflight } from "./preflight.js";
11
+ import { assertDirectAgentPreflight } from "./preflight.js";
12
12
  import { logProgress } from "./progress.js";
13
- import { cleanupLegacyRepoRecordings, cleanupStagingSession, createLiveStagingSessionId, getLiveStagingSessionRoot, setLiveStagingRootOverride, } from "./record-trace.js";
13
+ import { cleanupStagingSession, createLiveStagingSessionId, getLiveStagingSessionRoot, setLiveStagingRootOverride, } from "./record-trace.js";
14
14
  import { registerLiveRunHandlers, runAllSuites, runSuite } from "./run-suite.js";
15
15
  import { formatRunSummary, shouldFailScenario, summarizeReports, } from "./suite-summary.js";
16
16
  import { configureCliColor, theme } from "./theme.js";
@@ -28,9 +28,6 @@ export function parseCliArgs(argv) {
28
28
  let filter;
29
29
  let scenarioFilter;
30
30
  let stagingSessionId;
31
- let record = false;
32
- let recordFixtures = false;
33
- let live = false;
34
31
  let judge;
35
32
  let worktree;
36
33
  let keepRecordings = false;
@@ -59,7 +56,14 @@ export function parseCliArgs(argv) {
59
56
  for (let i = startIndex; i < argv.length; i++) {
60
57
  const token = argv[i];
61
58
  if (token === "--host" && argv[i + 1]) {
62
- host = argv[++i];
59
+ const value = argv[++i];
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;
63
67
  }
64
68
  else if (token === "--suites-dir" && argv[i + 1]) {
65
69
  suitesDir = argv[++i];
@@ -76,15 +80,14 @@ export function parseCliArgs(argv) {
76
80
  else if (token === "--staging-session-id" && argv[i + 1]) {
77
81
  stagingSessionId = argv[++i];
78
82
  }
83
+ else if (token === "--live") {
84
+ throw new Error("--live was removed because agent-test now always runs a real agent");
85
+ }
79
86
  else if (token === "--record") {
80
- record = true;
87
+ throw new Error("--record was removed; direct runs capture transient traces automatically (use --keep-recordings to retain them)");
81
88
  }
82
89
  else if (token === "--record-fixtures") {
83
- record = true;
84
- recordFixtures = true;
85
- }
86
- else if (token === "--live") {
87
- live = true;
90
+ throw new Error("--record-fixtures was removed because replay-based testing is deprecated and no longer supported");
88
91
  }
89
92
  else if (token === "--keep-recordings") {
90
93
  keepRecordings = true;
@@ -176,12 +179,8 @@ export function parseCliArgs(argv) {
176
179
  throw new Error("compare requires --a <report.json> and --b <report.json>");
177
180
  }
178
181
  }
179
- if (live) {
180
- host = host ?? "cursor";
181
- record = true;
182
- judge = judge ?? true;
183
- worktree = worktree ?? true;
184
- }
182
+ judge = judge ?? true;
183
+ worktree = worktree ?? true;
185
184
  if (debug) {
186
185
  keepRecordings = true;
187
186
  process.env.AGENT_TEST_DEBUG = "1";
@@ -196,9 +195,6 @@ export function parseCliArgs(argv) {
196
195
  filter,
197
196
  scenarioFilter,
198
197
  stagingSessionId,
199
- record,
200
- recordFixtures,
201
- live,
202
198
  judge,
203
199
  worktree,
204
200
  keepRecordings,
@@ -222,6 +218,16 @@ export function parseCliArgs(argv) {
222
218
  compareOutDir: compareOutDir ? resolve(cwd, compareOutDir) : undefined,
223
219
  };
224
220
  }
221
+ /** Resolve an explicit report target into the HTML path and optional artifact directory. */
222
+ export function resolveReportOutput(reportOut) {
223
+ if (!reportOut) {
224
+ return {};
225
+ }
226
+ if (reportOut.toLowerCase().endsWith(".html")) {
227
+ return { htmlPath: reportOut };
228
+ }
229
+ return { htmlPath: join(reportOut, "report.html"), outDir: reportOut };
230
+ }
225
231
  async function pathExists(path) {
226
232
  try {
227
233
  await access(path);
@@ -260,8 +266,6 @@ async function loadOrRunCompareSide(args, side, stagingSessionId) {
260
266
  suitePath,
261
267
  host: args.host,
262
268
  scenarioFilter: args.scenarioFilter,
263
- record: args.record,
264
- recordFixtures: args.recordFixtures,
265
269
  judge: args.judge,
266
270
  worktree: args.worktree,
267
271
  stagingSessionId,
@@ -275,26 +279,13 @@ async function loadOrRunCompareSide(args, side, stagingSessionId) {
275
279
  scenarioRetries: args.scenarioRetries,
276
280
  });
277
281
  }
278
- /**
279
- * Split `--report-out` into an HTML file path and, when a directory was given,
280
- * the directory every other report artifact is written to.
281
- */
282
- export function resolveReportOutput(reportOut) {
283
- if (!reportOut) {
284
- return {};
285
- }
286
- if (reportOut.toLowerCase().endsWith(".html")) {
287
- return { htmlPath: reportOut };
288
- }
289
- return { htmlPath: join(reportOut, "report.html"), outDir: reportOut };
290
- }
291
282
  async function writeSuiteReportDump(outDir, label, report) {
292
283
  await mkdir(outDir, { recursive: true });
293
284
  const path = join(outDir, `${label}.suite-report.json`);
294
285
  await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, "utf8");
295
286
  return path;
296
287
  }
297
- async function cleanupLiveRunArtifacts(cwd, stagingSessionRoot, keepRecordings) {
288
+ async function cleanupRunArtifacts(stagingSessionRoot, keepRecordings) {
298
289
  if (keepRecordings) {
299
290
  return;
300
291
  }
@@ -306,10 +297,6 @@ async function cleanupLiveRunArtifacts(cwd, stagingSessionRoot, keepRecordings)
306
297
  // best-effort
307
298
  }
308
299
  }
309
- const legacyRemoved = await cleanupLegacyRepoRecordings(cwd);
310
- if (legacyRemoved.length > 0) {
311
- console.log(`Removed legacy in-repo recording dir(s):\n ${legacyRemoved.join("\n ")}`);
312
- }
313
300
  }
314
301
  async function main() {
315
302
  let args;
@@ -332,9 +319,7 @@ async function main() {
332
319
  const bPath = resolve(args.cwd, args.compareB);
333
320
  const aReport = await loadSuiteRunReport(aPath);
334
321
  const bReport = await loadSuiteRunReport(bPath);
335
- const outDir = args.compareOutDir ??
336
- resolveReportOutput(args.reportOut).outDir ??
337
- resolve(args.cwd, "compare-out");
322
+ const outDir = args.compareOutDir ?? resolve(args.cwd, "compare-out");
338
323
  const compare = compareSuiteReports({
339
324
  aLabel: labelForCompareSide(aPath),
340
325
  bLabel: labelForCompareSide(bPath),
@@ -382,47 +367,41 @@ async function main() {
382
367
  }
383
368
  const isChild = process.env.AGENT_TEST_CHILD === "1";
384
369
  if (args.debugDir && isPathUnderRoot(args.debugDir, args.cwd) && !isChild) {
385
- console.warn(theme.warn(`--debug-dir is inside the repo (${args.debugDir}). Default is $TMPDIR/agent-spec — prefer that for live runs so debug output stays out of git status.`));
370
+ console.warn(theme.warn(`--debug-dir is inside the repo (${args.debugDir}). Default is $TMPDIR/agent-spec — prefer that for direct runs so debug output stays out of git status.`));
386
371
  }
387
372
  const verbose = args.debug || process.env.AGENT_TEST_VERBOSE === "1" || process.env.AGENT_TEST_DEBUG === "1";
388
373
  const stagingSessionId = args.stagingSessionId?.trim() ||
389
374
  process.env.AGENT_TEST_STAGING_SESSION_ID?.trim() ||
390
- (args.live || args.debug || (args.record && !args.recordFixtures)
391
- ? createLiveStagingSessionId()
392
- : undefined);
375
+ createLiveStagingSessionId();
393
376
  const stagingSessionRoot = stagingSessionId
394
377
  ? getLiveStagingSessionRoot(stagingSessionId)
395
378
  : undefined;
396
- const reportOutput = resolveReportOutput(args.reportOut);
397
379
  try {
398
- if (args.live) {
399
- const host = args.host ?? "cursor";
400
- if (host === "claude") {
401
- try {
402
- const authMode = parseClaudeAuthMode(process.env[CLAUDE_AUTH_MODE_ENV]);
403
- if (authMode === "api-key" && !process.env.ANTHROPIC_API_KEY?.trim()) {
404
- console.error(`${CLAUDE_AUTH_MODE_ENV}=api-key requires ANTHROPIC_API_KEY for --live --host claude`);
405
- return 1;
406
- }
407
- }
408
- catch (error) {
409
- console.error(error instanceof Error ? error.message : error);
380
+ if (args.host === "claude") {
381
+ try {
382
+ const authMode = parseClaudeAuthMode(process.env[CLAUDE_AUTH_MODE_ENV]);
383
+ if (authMode === "api-key" && !process.env.ANTHROPIC_API_KEY?.trim()) {
384
+ console.error(`${CLAUDE_AUTH_MODE_ENV}=api-key requires ANTHROPIC_API_KEY`);
410
385
  return 1;
411
386
  }
412
387
  }
413
- if (host === "cursor" && !process.env.CURSOR_API_KEY?.trim()) {
414
- console.error("CURSOR_API_KEY required for --live (Cursor SDK runs)");
415
- return 1;
416
- }
417
- // Judge classifiers still use the Cursor SDK.
418
- if (args.judge !== false && !process.env.CURSOR_API_KEY?.trim()) {
419
- console.error("CURSOR_API_KEY required for live judge classifiers (use --no-judge to skip)");
388
+ catch (error) {
389
+ console.error(error instanceof Error ? error.message : error);
420
390
  return 1;
421
391
  }
422
392
  }
423
- if (args.live) {
393
+ if (args.host === "cursor" && !process.env.CURSOR_API_KEY?.trim()) {
394
+ console.error("CURSOR_API_KEY required for Cursor agent runs");
395
+ return 1;
396
+ }
397
+ // Judge classifiers still use the Cursor SDK.
398
+ if (args.judge !== false && !process.env.CURSOR_API_KEY?.trim()) {
399
+ console.error("CURSOR_API_KEY required for judge classifiers (use --no-judge to skip)");
400
+ return 1;
401
+ }
402
+ {
424
403
  try {
425
- await assertLiveDogfoodPreflight(args.cwd, args.suitesDir);
404
+ await assertDirectAgentPreflight(args.cwd, args.suitesDir);
426
405
  }
427
406
  catch (error) {
428
407
  console.error(error instanceof Error ? error.message : error);
@@ -433,7 +412,7 @@ async function main() {
433
412
  process.env.AGENT_TEST_NO_WORKTREE === "1" ||
434
413
  process.env.AGENT_TEST_NO_WORKTREE === "true";
435
414
  if (worktreeDisabled && !inPlaceAllowed) {
436
- console.error("Live dogfood requires git worktree isolation. Set AGENT_TEST_ALLOW_IN_PLACE=1 to run in repo cwd (--no-worktree leaks agent edits into your working tree).");
415
+ console.error("Direct agent tests require git worktree isolation. Set AGENT_TEST_ALLOW_IN_PLACE=1 to run in repo cwd (--no-worktree leaks agent edits into your working tree).");
437
416
  return 1;
438
417
  }
439
418
  registerLiveRunHandlers();
@@ -442,7 +421,7 @@ async function main() {
442
421
  if (removed.length > 0) {
443
422
  console.log(theme.warn(`Cleaned ${removed.length} stale agent-test worktree(s) from a prior crash`));
444
423
  }
445
- console.log(theme.banner(args.debug ? "live debug" : "live"));
424
+ console.log(theme.banner(args.debug ? "direct debug" : "direct"));
446
425
  if (stagingSessionRoot) {
447
426
  console.log(theme.bannerSession(stagingSessionRoot));
448
427
  }
@@ -464,10 +443,6 @@ async function main() {
464
443
  }
465
444
  }
466
445
  }
467
- else if (args.debug && !isChild && stagingSessionRoot) {
468
- console.log(theme.banner("debug"));
469
- console.log(theme.bannerSession(stagingSessionRoot));
470
- }
471
446
  let reports;
472
447
  let comparePassRegressions = 0;
473
448
  if (args.comparePairs) {
@@ -477,7 +452,6 @@ async function main() {
477
452
  reports = [aReport, bReport];
478
453
  if (!isChild) {
479
454
  const outDir = args.compareOutDir ??
480
- reportOutput.outDir ??
481
455
  (stagingSessionRoot
482
456
  ? join(stagingSessionRoot, "compare")
483
457
  : resolve(args.cwd, "compare-out"));
@@ -547,15 +521,8 @@ async function main() {
547
521
  includeCompare: Boolean(args.comparePairs),
548
522
  compareALabel: pair ? labelForCompareSide(pair.a) : undefined,
549
523
  compareBLabel: pair ? labelForCompareSide(pair.b) : undefined,
550
- }, reportOutput.htmlPath);
524
+ });
551
525
  console.log(`\n${theme.fileTip("HTML report", reportPath)}`);
552
- // A directory collects everything, not just the HTML.
553
- if (reportOutput.outDir && !args.comparePairs) {
554
- for (const report of reports) {
555
- const dumpPath = await writeSuiteReportDump(reportOutput.outDir, report.suite, report);
556
- console.log(theme.tip(`suite JSON: ${dumpPath}`));
557
- }
558
- }
559
526
  }
560
527
  catch (error) {
561
528
  console.warn(theme.warn(`HTML report failed: ${error instanceof Error ? error.message : String(error)}`));
@@ -581,7 +548,7 @@ async function main() {
581
548
  }
582
549
  finally {
583
550
  if (stagingSessionId && !isChild) {
584
- await cleanupLiveRunArtifacts(args.cwd, stagingSessionRoot, args.keepRecordings);
551
+ await cleanupRunArtifacts(stagingSessionRoot, args.keepRecordings);
585
552
  }
586
553
  }
587
554
  }