test: add agent evals - #229
Open
angeloashmore wants to merge 40 commits into
Open
Conversation
Plain vitest evals that drive a real Claude Code agent (via the Agent SDK) against the built CLI in a generic, hermetic config. Adds the `agent` fixture, an `toHaveRun` matcher, an LLM-judge matcher with a never-regress ratchet, and a per-run reporter that records efficiency metrics and the agent's prismic calls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge the agent fixture, matchers, judge, config isolation, and result recording into eval/it.ts; delete claude.ts, report.ts, and setup.ts. Move the judge ratchet from a read-back of results.jsonl into an inline threshold literal that vitest -u rewrites like a snapshot, so results.jsonl is write-only. Trim recorded fields to effectiveness and effort (turns, tokens, duration); drop cost, which swings with provider pricing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Judge uses the SDK's native structured output instead of hand-parsing JSON; score is range-checked to [0, 1] - Create a throwaway Claude config dir per agent/judge call and clean it up; set persistSession: false so runs stay ephemeral - Detect -u from vitest's update state; the worker's process.argv is empty - Fix raiseLiteral rewriting the wrong literal on the callsite line, and support inline bars - Fix the evals npm script so vitest also receives RUN_EVALS Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each eval now runs EVAL_TRIALS times (default 3) via it.for; a custom reporter prints per-eval pass rates with infra failures excluded and appends per-trial rows (pass, cost, tokens, turns, commands) to results.jsonl. The judge is binary like agent-eval's: prose criteria, pass/fail verdicts, no scores, no ratcheting bars. The full eval catalog is implemented as it.skip drafts; only the rich-text smoke eval runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`node --run evals:report` prints one block per eval with one line per run (pass rate, cost, mean tokens/turns/duration) and a latest-run footer with best-of and per-trial rates. Rows now carry a per-run id so the digest groups runs exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows record the git SHA that produced them, shown in the digest so rate changes point at a commit range. The file keeps the newest 100 runs; the digest shows the last 10 per eval. results.jsonl merges with the union driver so branch runs never conflict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The judge now calls the Messages API directly with structured outputs instead of spawning a Claude Code session. The agent fixture writes its own task.meta via the built-in task context, trial rows no longer carry a parsed trial number, and eval env vars are parsed with a zod schema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enable 26 validated evals with per-trial runs and skip 3 that fail consistently, with the reason noted on each. Trim duplicate evals and merge the two slice component evals into one. Each eval now gets its own repository via an isolatedRepo fixture, letting evals run 8-way concurrent without cross-test interference. Global teardown sweeps orphaned repositories created by agents (e.g. prismic init) that the harness cannot name in advance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add four evals from the Next.js happy-path docs: init with an existing repository, page types vs custom types, commit-then-push flow, and production previews after a deploy. The last two fail today and stay enabled: evals are measurements, and their pass rates track CLI gaps. Un-skip the other agent-capability evals for the same reason; only the field rename eval stays skipped since the CLI cannot rename fields. toHaveRun now evaluates each shell segment separately and ignores --help invocations, so reading help no longer counts as running a command in either direction. Scope the results digest to the last EVAL_HISTORY runs (default 10) with a per-run footer, and document the results.jsonl schema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts main's isolateRepo fixture API from #231: drops the serial test project split, moves the exec fixture into eval/it.ts, and switches the eval repo isolation to isolateRepo: true. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Evals only shell out to git; the generic runner's options plumbing was left over from the pre-#231 CLI fixture. throwOnError surfaces setup failures that previously passed silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… builds Evals now require PRISMIC_ALLOW_EVALS=true and an explicit --project evals; bare vitest runs only unit tests. Tests run against the prebuilt dist (no MODE=test build), sync's poll interval is a validated PRISMIC_SYNC_POLL_MS env var, agent-created repos are cleaned up per test instead of an account-wide sweep, and dead MODE/DEV env plumbing is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Take main's test-build infra from #232 (CLI built in test global setup, always-minified tsdown config) and keep the evals project config on top. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Export AgentRecord with field docs, build Row from it in the reporter, and reuse that type in the report instead of a duplicated schema comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n read Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vitest silently ignores `sequence` inside a `projects` entry, so every test in a file ran serially. Root-level `sequence.concurrent` propagates to projects and restores within-file concurrency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lihbr
marked this pull request as ready for review
July 24, 2026 01:28
lihbr
approved these changes
Jul 24, 2026
lihbr
left a comment
Member
There was a problem hiding this comment.
Look good to me, I like what you did with Vite and using a custom reporter, the failing evals are also really insightful :)
…ILL.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 tasks
lihbr
approved these changes
Jul 27, 2026
…ts.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
angeloashmore
marked this pull request as draft
July 27, 2026 23:51
Partial runs (file filters, -t, it.only) are detected by the reporter and never overwrite results.json, so it always holds one full run. Permanent disables must be it.todo, which vitest keeps distinguishable from filter skips. Also fixes the remaining bin/ mkdir race in the project fixture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every run rewrites most values, so a pretty-printed file only inflates PR diffs; one line keeps run-over-run changes to one changed line. Read it with jq or node --run evals:report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Aligns the eval fixtures with test/it.ts and drops the vitest 4.1 builder-pattern API, shrinking the lockfile diff to the agent SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nfig Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tests that assert non-zero exit codes need the result object back, not a thrown error. Restores main's behavior; the eval git setup surfaces failures through its own assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
angeloashmore
marked this pull request as ready for review
July 28, 2026 01:08
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c5fdf22. Configure here.
… agent A fixture failure before the prompt leaves a failed test with no recorded trial. Recording the rest as a full run would misstate pass rates, so such runs now count as partial. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every CLI invocation fired a fire-and-forget /profile request even with telemetry disabled, since the call sat outside the telemetry gates. In CI this put every spawned CLI process on user-service concurrently, which starts returning 500s at roughly eight concurrent same-user requests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Resolves:
Description
Adds an eval suite that measures how well AI agents (like Claude Code) use the CLI.
Each eval gives an agent a task in a temp project. The agent runs the real CLI against a real repo, and the test checks the outcome. A few notes on the design:
prismic init).evals/results.jsonthe reporter overwrites each run: trials grouped by eval, sorted by name, with each trial's pass/fail, cost, duration, and theprismiccommands it ran.node --run evals:report(a jq one-liner) prints the latest run.results.json, so git acts as the time-aware database.git log -- evals/results.jsonlists past runs,git show <rev>:evals/results.jsonreads any of them, and an analysis agent can walk those versions to compare pass rates, cost, and command usage across CLI commits — no run ids, timestamps, or history tooling in the harness itself.node --run evals -- sync-models,-t "adds a field",it.only) write only the gitignoredevals/results.local.json, never results.json — the reporter detects them, so a committed snapshot is always a full run. Agents can diff results.local.json against results.json to compare a working-tree run with the last recorded one. The one convention: permanently disabled evals useit.todo, since vitest reportsit.skipidentically to a filter.node --run evals) and refuse to run outside a container or CI, because the agent runs with permissions bypassed.Checklist
Preview
node --run evalsruns the suite as normal Vitest tests and writesevals/results.json. The file is stored on a single line so each recorded run diffs as one changed line; formatted excerpt:{ "model": "claude-sonnet-5", "evals": { "adds a field": [ { "pass": true, "costUsd": 0.35, "durationS": 20, "calls": [ "field add --help", "field add rich-text excerpt --to-type article --label Excerpt" ] } ] } }node --run evals:reportprints the latest run, one line per eval (excerpt):How to QA 1
Inside a disposable container with
.env.test.localavailable:🤖 Generated with Claude Code
Note
Medium Risk
Evals run a real agent with permission bypass against live Prismic accounts and billable Anthropic usage; risk is confined to isolated/CI runs, with a small CLI change to telemetry env overrides.
Overview
Adds an opt-in Vitest eval project that drives the real
prismicCLI through the Claude Agent SDK (with permissions bypassed) against disposable repos and temp Next.js projects, so agent behavior can be measured over multiple trials instead of gated as unit tests.The harness in
evals/it.tswires the built CLI into each project, appends the published Prismic skill, records everyprismicinvocation, and exposes matchers liketoHaveRunand LLM-basedtoSatisfyJudge. Scenarios live in new*.eval.tsfiles (modeling, routes, repo setup, sync/push, init, safety checks, slice components, etc.). A custom reporter writesevals/results.jsonon full runs andevals/results.local.jsonon filtered runs, with anevals:reportscript to summarize pass rates, cost, and duration.Tooling changes: Vitest is split into
testsvsevalsprojects;node --run evalsis guarded byPRISMIC_ALLOW_EVALS=true. E2E fixtures gain a genericexechelper;.env.test.exampledocumentsANTHROPIC_API_KEY. Telemetry/Sentry insrc/index.tsnow honor explicitPRISMIC_*_ENABLEDenv overrides (used to quiet eval runs). A baselineevals/results.jsonsnapshot is committed for git-based history.Reviewed by Cursor Bugbot for commit 70b0523. Bugbot is set up for automated code reviews on this repo. Configure here.
Footnotes
Please use these labels when submitting a review:
⚠️ #issue: Strongly suggest a change.
❓ #ask: Ask a question.
💡 #idea: Suggest an idea.
🎉 #nice: Share a compliment. ↩