Commit Graph
23 Commits
Author SHA1 Message Date
Addy Osmani 2b1ac61a4b Merge main: keep both sets of appended tests 2026-10-10 01:26:43 -07:00
Addy Osmani 279c1a8df9 Merge main: keep both sets of appended tests 2026-10-10 01:24:32 -07:00
Rudy Celekli efd49d48c3 Clean up incomplete evaluation workspaces
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-03 11:01:51 -04:00
Rudy Celekli 938a786076 Keep fixture commits independent of user signing settings
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-03 10:58:28 -04:00
Rudy Celekli aff59bccae Reject duplicate behavioral evaluation IDs
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-03 10:58:21 -04:00
mavericksea-aiandClaude Opus 4.6 4eb29c10e4 refactor(evals): extract clearGradingSlot and test the production path
Extract the two fs.rmSync calls into clearGradingSlot(base), export it,
and call it from runBehavioral. Rewrite both slot-clearing tests to call
clearGradingSlot instead of inlining fs.rmSync, so each test exercises
the production cleanup path and fails if the call is removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-21 02:52:39 +08:00
mavericksea-aiandClaude Opus 4.6 eb96f0c9fb fix(evals): clear result slot up front instead of on rejection
Move stale-file cleanup from persistGradingOutcome (which only ran on
rejected grading) to the top of each eval iteration, before the executor
is invoked.  Both ${base}.grading.json and ${base}.grading.raw.txt are
removed with fs.rmSync(p, { force: true }), which ignores a missing file
but still throws on a permissions error.

This closes three gaps at the source:
- The bare catch around unlinkSync swallowed EACCES alongside ENOENT.
- An executor or grader crash skipped persistGradingOutcome entirely,
  leaving the previous run's .grading.json intact.
- A successful grading left a stale .grading.raw.txt from a prior
  rejected run beside the new .grading.json.

persistGradingOutcome now only writes, never deletes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-21 02:42:48 +08:00
mavericksea-ai ee7fd58a93 fix(evals): record executor model and timestamp in grading.json
Each .grading.json now carries a run object with executor_model,
grader_model, and timestamp so results can be attributed to a
specific model version and point in time.

The executor model is read from the init event of the stream-json
output that the executor already produces (type "system",
subtype "init", top-level model field). The grader model is recorded
as "unknown": the grader is a bare `claude -p` whose output
parseGrading extracts by regex, so switching it to stream-json would
break parsing, and adding --model is a behaviour change that does
not belong in a recording fix.
2026-09-21 02:10:21 +08:00
mavericksea-ai f646694706 fix(evals): remove stale grading.json when grading is rejected
RESULTS_DIR is only ever created and never cleared, and the output
path is derived from skill name and eval id alone. When a run's
grading is rejected (parseGrading returns null), the runner writes
.grading.raw.txt but leaves any .grading.json from a previous
successful run in place. A collector reading that directory gets the
old score beside the raw output of the run that actually happened.

Extract persistGradingOutcome so the write-and-cleanup is a testable
unit, and unlink the stale .grading.json before writing the raw file.
2026-09-21 02:09:04 +08:00
mavericksea-aiandClaude Opus 5 0c497ba10f fix(evals): canonicalize expectation text and recompute pass_rate
Two hardening changes to parseGrading:

- Once a result's id is validated, overwrite its text with the declared
  expectation at that index. The id is the binding, so a grader that
  paraphrases an expectation no longer leaks its own wording into the
  saved grading report.
- Stop rejecting a grading whose summary.pass_rate disagrees with the
  counters. pass_rate is derived, not an independent observation, so a
  rounded or mis-divided value is now recomputed from the validated
  results instead of discarding the run. The integer counters
  (passed/failed/total) remain exact-match checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 01:19:15 +00:00
mavericksea-ai 13cd6a6d02 fix(evals): bind grader results to declared expectations by id
parseGrading accepted any result set whose length matched the expected count, without verifying that each result corresponded to a declared expectation. A grader could return duplicates, omit an expectation, or include results for undeclared expectations and still pass validation.

parseGrading now takes the full expectations array, requires each result to carry an integer id in 1..n matching the numbered expectations in the prompt, rejects duplicates and out-of-range ids, and derives pass_rate from the validated counters. The grader prompt requests the id field and the call site in runBehavioral is updated.

Five tests added: reordered-but-complete results, duplicate ids, out-of-range ids, omitted expectations, untrusted pass_rate.
2026-09-16 01:00:33 +00:00
Raghu :) e6a58d5fc3 fix(evals): reject null grader expectations without crashing 2026-09-06 23:20:26 -07:00
NgoQuocViet2001 c8483330f6 fix(evals): reject incomplete grader results 2026-08-09 00:08:12 +07:00
Addy Osmani d2478bf0c7 Merge #458: clean up throwaway eval workspaces
fix: clean up throwaway workspace directories after eval runs
2026-08-06 15:44:15 -07:00
Sunil 21fea4d528 fix: clean up throwaway workspace directories after eval runs
materializeWorkspace() creates directories in os.tmpdir() but
runBehavioral() never cleans them up on success or failure. If
execFileSync throws (timeout, signal, OOM), the workspace persists
in /tmp indefinitely with fixture data. The test file
run-evals-test.js uses try/finally cleanup (line 234), proving
the pattern was known but omitted in production code.

Fix: Wrap eval execution in try/finally with best-effort
fs.rmSync cleanup.
2026-08-05 22:37:48 +05:30
Sunil 4b3122e65f security: validate --behavioral skillName to prevent path traversal
The skillName argument from --behavioral has zero validation and is
used directly in path.join() for reading .json files, reading
SKILL.md, and WRITING .grading.json results. A value like
'../../etc/passwd' traverses outside the project directory for
both reads and writes.

Fix: Validate skillName against /^[a-z0-9]+(-[a-z0-9]+)*$/ before
any filesystem operations. This matches the existing KEBAB_CASE
validation in skill-lint.js.
2026-08-05 22:35:47 +05:30
ZhiyaoWen999 087033ddf0 feat: support trusted dialogue evals 2026-07-12 22:13:34 +08:00
Zhiyao 5168535ce4 feat: promote skill eval gates to trusted 2026-07-10 15:29:28 +08:00
Zhiyao 4290228323 test: ratchet deterministic skill eval gates 2026-07-10 14:53:37 +08:00
ShiroKSH df8eda8fbe fix: harden eval and command validation 2026-07-09 18:03:46 +03:00
Addy Osmani dd142f1da3 fix(evals): pipe grader prompt via stdin; grant executor tool permissions
Two latent Tier-3 bugs caught in review before the path was ever exercised:

- The grader prompt embeds the full stream-json trace (up to megabytes) and
  was passed as an argv entry, which would fail with E2BIG on any real run.
  It now goes to `claude -p` over stdin; the executor prompt moves to stdin
  for the same reason.
- The executor ran headless with no permission mode, so file edits and
  command runs could be denied, forcing the narrate-instead-of-perform
  failure mode that trace grading exists to catch. It now runs with
  --permission-mode acceptEdits and a pre-approved tool list
  (Read,Glob,Grep,Edit,Write,Bash), documented in the README.
2026-07-06 12:38:26 -07:00
Addy Osmani 194b2099c6 feat(evals): harden Tier 3 and make negatives pairwise routing tests
Per review from @federicobartoli and @nucliweb on #342:

Tier 3 (behavioral):
- Grade the execution trace, not the final output: executor runs with
  --output-format stream-json --verbose so the grader judges tool calls
  and file edits rather than the model's self-reporting.
- Run each eval in a throwaway workspace; files[] fixtures materialize
  from evals/fixtures/ so evals can operate on real code.
- Node-level timeouts on executor and grader calls; grader output parsed
  and shape-validated before writing (raw saved on failure); the trace is
  fenced as untrusted data in the grader prompt.
- All 24 behavioral evals flagged trust_level: "provisional" until they
  gain fixtures; the runner surfaces this and exits nonzero on failed
  expectations.

Tier 2 (deterministic):
- Negative triggers accept an "owner" skill that must outrank this one,
  turning them into pairwise routing tests that cannot pass vacuously;
  37 of 48 negatives now declare owners (the rest are tracked in #351).
- Warn when a case file is below the documented minimums (3 positive /
  2 negative / 1 behavioral); promotion to error tracked in #352.
- Stemmer: cluster trailing y/i ("simplify"/"simplifies").

Baseline holds: 120 checks, 0 errors, 85% trigger rank-1 rate.
2026-07-06 11:36:29 -07:00
Addy Osmani 45e1449138 feat(evals): add a three-tier skill eval framework
There was no way to measure whether skills trigger correctly, stay
distinct, or change agent behavior. This adds evals, aligned with what
the community has converged on, with a deterministic CI tier on top:

- evals/cases/<skill>.json for all 24 skills. The evals[] block uses
  Anthropic skill-creator's evals.json schema verbatim (id, prompt,
  expected_output, expectations[]) so its runner, benchmarks, and eval
  viewer work against our files unmodified. A trigger block (this
  repo's extension) adds positive/negative routing prompts per skill.
- scripts/run-evals.js, zero-dependency runner:
  Tier 2 (CI): trigger evals via stemmed TF-IDF ranking over skill
  descriptions (positive prompts must rank top-k, negative prompts
  must not rank first), catalog collision detection between skill
  descriptions, schema and coverage checks.
  Tier 3 (opt-in): --behavioral <skill> executes each eval through
  headless claude -p and grades the transcript against expectations[]
  (superpowers-style); --dry-run previews without spending tokens.
- CI: run the deterministic tier in the validate-skills job.
- Docs: evals/README.md defines the framework and prior art;
  CONTRIBUTING requires an eval file for new skills (warning-level in
  the runner until in-flight skill PRs clear); CLAUDE.md pointers.

Current baseline: 120 checks pass, 85% trigger rank-1 rate across 72
positive prompts, zero catalog collisions.
2026-07-03 23:46:30 -07:00