Imported from sunitghub/canon-skills (
skills/skill-eval/SKILL.md). Install upstream withnpx skills add sunitghub/canon-skills --skill skill-eval. Copyright stays with the author.
Skill Eval
Runs execution evals for the skill at skills/$ARGUMENTS/.
See example.md for a step-by-step walkthrough using the capture skill.
What this is not
Trigger eval (whether the skill fires for the right queries) and benchmark/improve/compare modes are out of scope. For those, see skill-creator.
For a plugin-vs-no-plugin baseline (Δ score), generate a throwaway plugin with tools/plugin-eval-gen <skill> --write, then run the claude plugin eval command it prints. Skills that only run inside a sprint (e.g. wrapup) stay on this skill: claude plugin eval can't invoke them directly.
Steps
-
Structural check. Analyse the SKILL.md body (all lines after the closing
---of the frontmatter) and the eval case count:Registration check (determines body threshold): Run
./tools/skills.sh list 2>/dev/nulland check whether$ARGUMENTSappears in the SKILL column.- Found → skill is registered and always-on → body threshold is 300 lines
- Not found → skill is standalone, a sub-skill, or external → body threshold is 500 lines
Body size:
- Count total body lines. If ≤ threshold → output
pass — body within threshold (N lines; threshold: T — always-on)orpass — body within threshold (N lines; threshold: T — standalone)and continue. - If > threshold: identify every
##section and its line span. If 2+ sections each exceed 30 lines → outputcandidate for ref/split — body N lines (threshold: T — always-on|standalone); sections <name> (X lines), <name> (Y lines) exceed 30-line threshold.
Eval coverage:
- Check whether
skills/$ARGUMENTS/evals/evals.jsonexists. This applies to every named target skill, includingskill-evalitself when runningskill-eval skill-eval. - If missing → output
missing evals — skills/$ARGUMENTS/evals/evals.json not found; fallback creation prompt required. - If present → read it and count the entries in the
evalsarray. If count ≥ 3 → outputpass — N eval cases. If count < 3 → outputtoo few evals — N case(s); minimum is 3.
Output both sub-checks under
### Structural checkin the eval report, before any case results. Both checks are advisory — they do not block execution evals.Thresholds: 300-line body for always-on skills (injected every session); 500-line body for standalone/sub-skill/external (Anthropic hard limit, on-demand only); 30-line section; 3 minimum eval cases.
-
Read the skill. Read
skills/$ARGUMENTS/SKILL.md. If missing, report the gap and stop.Check for
skills/$ARGUMENTS/evals/evals.json.- If present: proceed to Step 2 (executor+grader path).
- If missing: run the fallback evaluator (Step 1b) instead of normal eval cases.
1b. Fallback evaluator (no evals.json). Not when dispatched by the sprint gate (complete.md step 1): a skill with no evals/evals.json is skipped there, so report skipped — no evals.json and stop; never ask the user to write one mid-close. Otherwise spawn a fresh Agent subagent with a clean context. The prompt must:
- Include the skill's
SKILL.mdcontent verbatim under "Active skill:" - Instruct it to: (a) read the skill and identify 2–3 realistic user scenarios the skill is designed to handle, (b) execute each scenario as if in a fresh session with the skill active — reporting steps taken and output produced, (c) grade whether the skill's instructions were clear and complete enough to guide correct behaviour:
pass,partial, orfailwith a one-line reason per scenario - Ask it to recommend which scenarios should be formalised as
evals.jsoncases
Output fallback results under ### Fallback eval (no evals.json) in the report, before ### Summary.
Write offer. After outputting the fallback results, present the recommended scenarios to the user and ask:
"Write these as
skills/$ARGUMENTS/evals/evals.json? (yes/no)"
- Yes: write
skills/$ARGUMENTS/evals/evals.json. Each recommended scenario becomes one eval case with the fields:id: kebab-case slug of the scenario namecase_type: test technique —control(happy path),compliance(must-follow rule),boundary(edge of valid input),edge(unusual but valid),over-caution(should not refuse),self-check(skill evaluates itself)prompt: the scenario as a concrete user-facing prompt stringexpected_output: one sentence describing correct behaviourexpectations: array of 2–3 specific, assertable strings the grader can verifytype(optional):capability(can it do this new thing?) orregression(can it still do the old things?) Confirm the file was written, then read the new file and proceed to Step 2.
- No: stop. Do not write anything. Do not proceed to Step 3.
-
For each eval case, run two subagents in sequence (from the session that runs this skill, never from inside another subagent, which cannot spawn them):
Executor — spawn an Agent with a clean context. The prompt must:
- Include the skill's
SKILL.mdcontent verbatim under a heading "Active skill:" - Include the eval
promptunder "Your task:" - Instruct it to execute the task as if it were a fresh session with that skill active
- Ask it to report: what steps it took, what it would write or output, what tool calls it would make
Grader — spawn a second Agent with a clean context. The prompt must:
- Include the executor's full response
- Include the eval
expected_outputand theexpectationslist - For each expectation: grade
pass,fail, orpartialwith a one-line evidence citation - Return: pass count, total, and per-expectation breakdown
- Include the skill's
-
Aggregate and report. After all cases complete:
- Output the eval report inline (see Output format below).
- Record the pass rate. In canon (where
tools/skill-eval-history.shexists), runtools/skill-eval-history.sh $ARGUMENTS <model> <pass> <total>with the model that ran the executors and graders and the expectation counts from the Summary, and add the one line it prints (N/M pass (was K/M)) under### Summary. It appends toskills/$ARGUMENTS/evals/history.jsonl; skip it when execution evals did not run (missingevals.json, fallback path), because a count of zero is not a result. - Write the same report to
skills/$ARGUMENTS/skill-eval-result.md, replacing any prior contents. The file is always written — even if execution evals were skipped due to a missingevals.json. When dispatched by the sprint gate (complete.mdstep 1), write it to.tickets/<id>/skill-eval-$ARGUMENTS.mdinstead, so the tracked tree gains only the history line.
Output format
## Skill Eval: <skill-name>
Run: <ISO date>
### Structural check
Body: pass — body within threshold (N lines; threshold: T — always-on | standalone)
-or-
candidate for ref/split — body N lines (threshold: T — always-on | standalone); sections <name> (X lines), <name> (Y lines) exceed 30-line threshold
Evals: pass — N eval cases
-or-
too few evals — N case(s); minimum is 3
-or-
missing evals — skills/<skill-name>/evals/evals.json not found; fallback creation prompt required
### Case <id>: <prompt, truncated to 60 chars>
- "<expectation>" → pass | fail | partial
Evidence: <one line>
### Fallback eval (no evals.json)
Scenario <n>: <description>
Verdict: pass | partial | fail — <one-line reason>
...
Recommended evals: <scenario descriptions to formalise as evals.json cases>
### Summary
<n>/<total> expectations passed
Verdict: pass | fail | incomplete (fallback path — no authored evals)
### Issues
| Issue | Details | Reason |
|---|---|---|
| <issue title> | <specific finding> | <why it matters> |
Populate the Issues table with any candidate for ref/split, too few evals, failed expectations, or missing files. Leave the table empty (header only) if no issues were found.
Gotchas
- The executor runs in a clean context with only the skill content injected — it has no access to the repo. Expectations like "appended to HANDOFF.md" can only be graded
passif the executor explicitly describes taking that action. Grade conservatively;partialbeats an unsupportedpass. - Skills with heavy CLI dependencies (e.g., sprint, which calls
./tools/sprint) cannot be fully executed in a subagent — the executor will simulate the steps. This is still useful for catching missing steps, wrong output format, or logic errors. Note the limitation in findings when it applies. - If
evals.jsonexists but has zero test cases, report it as a finding and stop — the fallback evaluator only fires when the file is entirely absent, not when it exists but is empty. - Write expectations to assert outcomes and intent, not specific phrasing. A grader matching literal wording will false-fail a creative-but-correct executor response. Prefer "executor describes writing the file to the correct path" over "executor outputs the string 'Writing to HANDOFF.md'".
