Skip to content

Add prompt-rule regression tests to the agent eval suite - #3210

Merged
youknowriad merged 9 commits into
trunkfrom
claude/nice-nightingale-ce84fc
Apr 24, 2026
Merged

Add prompt-rule regression tests to the agent eval suite#3210
youknowriad merged 9 commits into
trunkfrom
claude/nice-nightingale-ce84fc

Conversation

@youknowriad

@youknowriad youknowriad commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

Claude Code drafted the test cases and assertions. Verified locally that the YAML validates cleanly (`npx promptfoo@0.121.4 validate -c eval/promptfoo.config.yaml`). The real agent runs have not yet been executed — they need `npm run eval` against an authed Studio session.

Proposed Changes

Adds ten prompt-rule regression cases to the existing agent eval suite at `eval/promptfoo.config.yaml`. Each pins a specific mechanical rule that has silently regressed in the past; together they guard against losing recent prompt evolution.

Each case asks the agent to narrate an answer in prose (no tool execution), so assertions can grep `d.textSegments.join('\n')` for the load-bearing substrings. That keeps the runs fast and self-contained: no real site, no permission loop, no filesystem side effects.

The ten rules under test

  1. wp-cli-no-shell-syntax — `wp_cli` takes literal args; filter via `wp_cli eval`, never pipes / `$(...)` / `&&` chaining.
  2. phase2-cp-stylesheet — PHASE 2 theme stylesheet is copied from the prototype with `cp`, not regenerated in a `Write`.
  3. button-paint-inner-link — all button paint goes on `.wp-block-button. .wp-block-button__link`; the outer wrapper is layout-only.
  4. theme-json-neutralize-button — `theme.json` zeros `styles.elements.button` so className rules are the only source of paint.
  5. theme-json-content-widths — `settings.layout.contentSize` / `wideSize` match the prototype's intended max-widths.
  6. hero-decompose-svg-only — a `
    ` with an inline SVG decomposes into native blocks; `core/html` wraps only the SVG.
  7. card-decompose-native-blocks — a card `
    ` becomes `core/group` + `core/heading` + `core/paragraph` + `core/buttons` + `core/button`, no `core/html`.
  8. apply-content-abspath-eval — page content is applied via `wp_cli eval` + `ABSPATH` + `file_get_contents`, never `--post_content-file=` (silently no-ops in WASM).
  9. phase1-style-skeleton — the first prototype `style.css` `Write` is a <2KB skeleton with `tokens` as the first anchor.
  10. phase2-blockify-first — the first Phase 2 tool call after an approved Phase 1 screenshot is invoking the `blockify` skill.

Why the existing harness

Studio already has an agent eval suite at `eval/` that runs the real agent via `startAiAgent()` and grades via Studio's WP.com-authed proxy. An earlier commit on this branch added a parallel standalone promptfoo setup — that was the wrong call, and it's reverted in `4804b783` in favor of this integration. The assertion style (parse `output` JSON, inspect `textSegments` / `toolCalls`) matches the existing cases in the suite so reviewers don't have to learn two patterns.

Scope boundaries

  • Not covered: multi-turn behavior (fix-loop batching, turns-per-build, session duration), screenshot / visual-fidelity tests, taste / design quality. Those stay in manual session audits, as noted in the original out-of-scope list.
  • Doesn't modify the prompt itself. This PR only adds tests.

Prerequisite

The tests are designed against the rules from #3207 (two-phase build + `blockify` skill). If #3207 has not yet merged when this PR is evaluated, several cases (2, 4, 5, 8, 9, 10) will fail on this branch because their rules are not yet in `trunk`. Recommended order: merge #3207 first, rebase this PR, then run `npm run eval`.

Testing Instructions

  1. Validate YAML:
    ```sh
    npx promptfoo@0.121.4 validate -c eval/promptfoo.config.yaml
    ```
    → "Configuration is valid."

  2. Full eval run (builds the CLI, then runs all 13 cases — 3 existing + 10 new):
    ```sh
    studio auth login
    npm run eval
    npm run eval:view
    ```

  3. Single case (fast iteration):
    ```sh
    npm run eval -- --filter-pattern "wp_cli filter uses eval"
    ```

  4. Deliberate regression sanity-check: temporarily remove the `Do NOT use --post_content-file` line from `apps/cli/ai/system-prompt.ts`, rebuild, rerun the suite — `apply-content-abspath-eval` should fail with a clear `not-contains` violation.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
  • `promptfoo validate` passes on the updated config.
  • Run `npm run eval` on this branch against an authed Studio session to confirm the new cases behave as expected.
  • Parent PR apps/cli: two-phase AI build workflow with blockify skill #3207 is merged (or its rules are otherwise present in trunk) before merging.

🤖 Generated with Claude Code

youknowriad and others added 3 commits April 23, 2026 15:45
Catches mechanical regressions in apps/cli/ai/system-prompt.ts and the
installed skills at PR time via 10 single-turn tests that grade a specific
rule each (wp_cli shell-syntax avoidance, theme.json button neutralization,
block decompose, ABSPATH-eval post updates, etc.). The suite imports the
live buildSystemPrompt from source so it can't fall out of date, runs on
Sonnet 4.5 with deterministic assertions, and blocks merge on failure for
PRs that touch the prompt files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the parallel setup added in 4335dc7 (apps/cli/ai/tests/promptfoo + .github/workflows/prompt-eval.yml). Studio already has an agent eval suite at eval/promptfoo.config.yaml that runs the real agent via startAiAgent() and a Studio-authed grader; the new tests will live there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 10 single-turn, prose-graded cases covering mechanical rules that have
silently regressed before (wp_cli shell syntax, theme.json button
neutralization, block decompose, ABSPATH-eval post updates, etc.). Each
prompt asks the agent to narrate in prose so the assertions can grep
`textSegments` without spinning up a real site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@youknowriad youknowriad changed the title Add promptfoo regression suite for the AI agent prompt Add prompt-rule regression tests to the agent eval suite Apr 23, 2026
youknowriad and others added 2 commits April 23, 2026 20:20
promptfoo's exec provider tries to hash a file from the first token of the
command to build a cache key. `exec:node ../apps/cli/dist/cli/eval-runner.mjs`
starts with `node`, which isn't a file, so promptfoo logs "Could not hash any
files" warnings on every run. Point it at eval/run-agent.sh instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the prose-regression tests added in 8199be3 and the shell wrapper
added in c07ea03. Keeps a single new case: building a one-page site must
keep every assistant turn under 40s of wall-clock time. eval-runner now
emits `turnDurationsMs` (delta between successive assistant messages) so
the assertion can flag the slowest turn with context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
youknowriad and others added 4 commits April 24, 2026 12:53
The suite was getting stuck at 75% for three independent reasons, all of
which compounded into "one stuck test plus three errored":

1. promptfoo's default maxConcurrency (4) let two site-building cases run
   at the same time, fighting over Studio state. Pin maxConcurrency to 1.
2. Studio tools and pi-tui spinners print to process.stdout throughout a
   run. promptfoo's exec provider caps stdout at 1 MB (node's default
   child_process buffer), so long builds died with
   ERR_CHILD_PROCESS_STDIO_MAXBUFFER and the assertion saw a truncated
   table instead of JSON. Redirect process.stdout.write to stderr for the
   duration of the run; serialize the result payload to a tmp file instead
   and print only an EVAL_RUNNER_RESULT_FILE=<path> marker via a raw
   fs.writeSync(1, ...) that bypasses the stream wrapper.
3. The Claude Agent SDK keeps internal handles open after the conversation
   ends (its `claude` subprocess, ipc pipes). We've already written the
   result file, so bail out with process.exit() rather than letting the
   event loop drain indefinitely.

On top of that, the existing JavaScript assertions all called
`require('fs')`, which broke under promptfoo 0.121 because assertions run
via eval() inside an ESM context where require is undefined. Rewrite
every javascript assertion to use `import('node:fs').then(...)` and
return a Promise.

Also adds an opt-in heartbeat (EVAL_RUNNER_HEARTBEAT + optional log file)
so "is it stuck or just slow" is answerable without ps-walking the
process tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes so the suite now passes 4-for-4:

1. Identity test's llm-rubric was grading the agent's stdout, which under
   the new runner contract is just `EVAL_RUNNER_RESULT_FILE=/tmp/…json`
   (the marker, not the agent's prose). The rubric reasonably judged
   "that's not an identity claim" and failed. Replace the rubric with a
   deterministic check against the runner payload's `textSegments`: agent
   must mention "WordPress Studio" and must not claim to be a generic LLM.
2. The single-page-build turn-cadence threshold was 40s. In practice the
   first big `Write` after an initial `Bash` probe lands in the 40–50s
   band (agent generating multi-KB theme file content). 40s was too tight
   and made the test flaky. Bump to 60s — still well below anything a user
   would feel as stuck, but above observed p99.

Verified locally: 4/4 pass, duration 2m 23s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single-page-build-turn-cadence test creates a site named "Eval Turn
Timing". Without a cleanup step, a second run lands on a directory that
already exists and `site_create` errors out, which triggers the agent to
backtrack (site_list → site_info → re-read → …) and inflates the total
turn count. The extra work also pushes the first substantive post-Skill
turn past our 60s bar because the model has to reason through recovery.

Mirror the pattern the `site-creation` test already uses — list, delete
if present, then create — scoped explicitly to this site only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The heartbeat was only ever used while diagnosing the "stuck at 75%"
failures — it's not load-bearing. Drop it along with the EVAL_RUNNER_HEARTBEAT*
env vars, the appendFileSync import, and the turn-by-turn log writes.

Also collapse the verbose rationale comments in the runner and config
(`Sink the runner's JSON ... two problems this solves ...`, `Each case
drives the real Studio Code agent ...`, etc.) down to one sentence each.
Net: 119 lines removed, 28 added. Suite still passes 4/4 in 6m 14s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@youknowriad
youknowriad marked this pull request as ready for review April 24, 2026 13:08
@youknowriad
youknowriad requested review from lezama and sejas April 24, 2026 13:09
@youknowriad

Copy link
Copy Markdown
Contributor Author

I'm merging cause I need this on a separate PR.

@youknowriad
youknowriad merged commit db3f865 into trunk Apr 24, 2026
8 checks passed
@youknowriad
youknowriad deleted the claude/nice-nightingale-ce84fc branch April 24, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant