Skip to content

Skill eval suite Phase 2: prose-register, choice-only gate, and CI - #67

Open
nonrational wants to merge 9 commits into
mainfrom
skill-eval-phase2
Open

nonrational wants to merge 9 commits into
mainfrom
skill-eval-phase2

Conversation

@nonrational

@nonrational nonrational commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Problem

Phase 1 (#58) left the eval suite unfit to gate anything. A keyword-overlap check failed a different correct discrimination answer every run. prose-register's rank and structural cases couldn't run at all. Nothing ran in CI, and the judge needed an ANTHROPIC_API_KEY that bills the API.

Motivation

A flaky or never-run check teaches everyone to ignore it. Phase 2 makes the suite something CI can run on every skill PR. Real keyed runs, not just unit tests, drove the design: they turned up four promptfoo behaviors the offline tests couldn't see.

Proposed Solution

Spec revisions (decisions 5–9) and the plan are in docs/superpowers/specs/2026-08-29-skill-eval-suite-design.md and docs/superpowers/plans/2026-09-19-skill-eval-suite-phase2.md. One commit per concern:

  • Judge through claude -p. Subject and judge share one spawn helper and one subscription credential, CLAUDE_CODE_OAUTH_TOKEN. The judge runs with tools and skills off, outside the repo, and not in --bare mode, which ignores OAuth. Spawned sessions drop TMUX/TMUX_PANE so they don't ring the developer's tmux bell through a Stop hook.
  • Only the discrimination choice gates hard. The choice assert is tagged metric: choice. The stated rule becomes a judged llm-rubric that sees the case's grading notes. check-gate reads per-assert results, so detection and judged checks count toward the 90% floor. make eval sets PROMPTFOO_FAILED_TEST_EXIT_CODE=0 so the gate actually runs after a failure.
  • prose-register fully supported. Rank and structural types, per-skill prompt framing, and rubric criteria read from each case. Prompts and rubrics are wrapped in {% raw %}, because promptfoo renders both as nunjucks and Hugo shortcodes broke that. The rank answer key is passed as a string, because promptfoo splits list-valued vars into one test per element. The old run-evals.mjs is deleted.
  • Offline checks in preflight. eval-validate and the unit tests (60) block in the existing CI job.
  • Lockfile refresh. An npm update within promptfoo's ranges (promptfoo stays pinned at 0.122.2) clears the transitive advisories behind Dependabot's Bump sharp and @huggingface/transformers in /evals #64Bump smol-toml and @openai/codex-security in /evals #66, plus fast-uri, qs, and fflate. What's left is extract-zip, which has no upstream fix and arrives through an optional promptfoo dependency the suite never calls. CI pins Node 24, because npm before 11.11 rejects this npm-11 lockfile as out of sync.
  • Advisory evals workflow. It selects the evaluated skills a PR touches (every skill when evals/ changes), links each skill into the runner's ~/.claude/skills (the repo has no root .claude/), runs make eval, and uploads results. Fork PRs and a missing secret skip with a notice. It's hardened with read-only permissions, persist-credentials: false and concurrency cancellation.

Feedback

  • The CI proof passed. This PR changes evals/, so its own evals run tested token auth and skill discovery on a bare runner. Neither skill had any subject errors. code-comment-register scored 15/16 and passed.
  • prose-register is red, on purpose. CI scored it 12/22. Local runs: code-comment-register 16/16. prose-register 15/22, with hard failures on disc-09 and disc-11. Those two cases (plus disc-04, which flipped once in four runs) have grading notes that call the alternative answers defensible. They stay hard until the cases are revised. Even then, prose sits below the 90% floor, because its detection cases are expected to fail. That's the main input for threshold tuning, recorded in the spec's After Phase 2.
  • Local runs aren't isolated from user config. They load user rules, plugins and Stop hooks. CI's bare $HOME doesn't. The spec records this as a follow-up. CI's prose score (12/22, with extra hard failures on disc-01 and disc-12) came in below the local 15/22, which fits that gap. One run isn't enough to tell.
  • Worth a close look: the gate rule in evals/bin/check-gate.mjs, and the judge's rule rubric in evals/lib/prompts.mjs.

nonrational and others added 8 commits September 19, 2026 13:34
Record three decisions from Phase 1's keyed runs and CI planning:
discrimination grades the choice deterministically and the stated rule
with a soft llm-rubric judgment; every model call, judge included, runs
through claude -p on one CLAUDE_CODE_OAUTH_TOKEN; the CI full-run job
starts advisory until real runs tune its thresholds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seven tasks: verify subscription auth in a bare HOME, route the judge
through claude -p, gate discrimination on the choice, grade
prose-register's rank and structural cases, retire its runner, move the
offline checks into preflight, and add the advisory PR workflow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The judge now runs as a second claude -p provider with tools and skills
off, outside the repo, so subject and judge share one subscription auth
and no ANTHROPIC_API_KEY is needed. The spawn-and-parse logic both
providers need moves to lib/claude-cli.mjs.

lib/claude-cli.mjs also strips TMUX and TMUX_PANE from the spawned
session's env, so a keyed run of dozens of sessions doesn't ring the
developer's tmux bell through their Stop hook. The judge asks for a
bare JSON reply, since claude -p sometimes wraps it in prose that
promptfoo can't parse. Both subject conditions and the judge pass
--no-session-persistence, so a keyed run leaves no transcript per case
under ~/.claude/projects. evals/package.json scopes node --test to
test/*.test.mjs so the executable echo stub under test/fixtures isn't
picked up as a test itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keyword overlap on the stated rule failed a different correct paraphrase
each run. The choice stays a deterministic assert tagged metric "choice"
and is the only one that gates; the rule moves to an llm-rubric judgment
counted toward the pass-rate floor. check-gate now reads per-assert
component results instead of case type, which also makes detection soft.

make eval also sets PROMPTFOO_FAILED_TEST_EXIT_CODE=0, so check-gate,
not promptfoo's exit code, decides pass/fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Structural cases grade as ordinary discrimination; rank cases grade the
full best-first ordering and judge the rule the worst version breaks.
Prompt framing moves into a per-skill table so prose cases read as text,
not code, and transformation rubrics list whatever criteria the case
defines (prose uses voice_match where code uses placement).

Prompts and rubric values are wrapped in {% raw %}, since promptfoo
renders vars and llm-rubric values as nunjucks and Hugo shortcodes in
prose-register's case text broke rendering; this applies to both
skills. The rank answer key passes as a comma-joined string because
promptfoo expands array vars into one test per element.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every prose-register case type now grades through promptfoo, so its
run-evals.mjs has no remaining job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
eval-validate and the suite's unit tests cost no model calls, so they
join preflight and block in the existing CI job. CI pins Node 24: the
evals lockfile was written by npm 11, and npm ci on older npm (Node 22
ships npm 10) rejects it as out of sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An advisory workflow diffs the PR, selects the evaluated skills it
touches (every skill when the suite itself changes), links each skill
into the runner's ~/.claude/skills, and runs make eval on the
CLAUDE_CODE_OAUTH_TOKEN secret. Fork PRs and a missing secret skip with
a notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
npm update within promptfoo's declared ranges (promptfoo itself stays
pinned at 0.122.2) clears the transitive advisories Dependabot flagged
in sharp, adm-zip, and smol-toml, plus fast-uri, qs, and fflate. The
remaining extract-zip advisory has no upstream fix and arrives through
@openai/codex-security, an optional promptfoo dependency the suite
never calls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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