Evaluate a single retort experiment run. Score the generated code against the task's TASK.md requirements, run its build and tests, compute metrics, and emit a structured evaluation report plus a machine-readable findings file.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add adrianco/retort --skill evaluate-run --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Evaluate Run?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adrianco-evaluate-run)More formats (shields.io, HTML) on the badges page.
---
name: evaluate-run
description: Evaluate a single retort experiment run. Score the generated code against the task's TASK.md requirements, run its build and tests, compute metrics, and emit a structured evaluation report plus a machine-readable findings file.
type: harness-agnostic
version: "1.0"
---
# Evaluate Retort Run
## Overview
A retort run produces a workspace directory — generated source code for one factor-level combination — archived under `<experiment>/runs/<cell>/rep<N>/`. This skill evaluates that workspace against the task spec it was asked to implement, captures quantitative and qualitative findings, and writes results in a format comparable across runs.
This is the per-run counterpart to pourpoise's `evaluate-attempt`, adapted for retort's DoE structure: instead of ad-hoc attempts, each run is a point in a design matrix.
## Parameters
- **run_dir** (required): Path to the archived run workspace, e.g. `experiment-1/runs/language=rust_model=opus_tooling=beads/rep2/`
- **output_file** (optional, default: `{run_dir}/evaluation.md`): Where to write the human-readable report
- **findings_file** (optional, default: `{run_dir}/findings.jsonl`): Where to write structured findings (one JSON object per line) suitable for `file-run-issues`
## Inputs You Can Rely On
Each `run_dir` is laid out by retort's LocalRunner and contains:
| File | Purpose |
|------|---------|
| `TASK.md` | Task spec — the prompt the agent received. This is the "requirements" source of truth. |
| `stack.json` | `{"language": ..., "agent": ..., "framework": ...}` — the factor levels for this run |
| All generated source files | Exactly as the agent left them |
| Possibly `.beads/` | Only if `tooling=beads` was in effect — the agent used bd for tracking |
| Possibly build artifacts | `node_modules/`, `target/`, `__pycache__/`, etc. |
The retort database (`experiment-<N>/retort.db`) also holds this run's `ExperimentRun` + `RunResult` rows. You MAY query it read-only for cross-checking scores; you MUST NOT write to it.
## Steps
### 1. Verify the run workspace
```bash
test -d "{run_dir}" || { echo "run_dir missing"; exit 1; }
test -f "{run_dir}/TASK.md" || { echo "TASK.md missing — not a retort workspace"; exit 1; }
test -f "{run_dir}/stack.json" || { echo "stack.json missing"; exit 1; }
```
Constraints:
- You MUST NOT modify any file in `run_dir`. Run all commands read-only or in a temp copy.
- You MUST handle the case where the run was marked failed (suffix `-failed` on the directory). Evaluate what exists; note the failure up front.
### 2. Read the already-computed build/test/lint scores from `retort.db`
**Do NOT re-run the build, tests, or linter.** retort's scorers already ran them
for this run during scoring and stored the results — re-running the toolchain
(especially compiled/JVM languages) is the slowest part of evaluation and is
pure duplication. Read the stored scores instead.
**Fastest source — `{run_dir}/scores.json`.** When the eval runs inline as a
gate during `retort run`, the run isn't in `retort.db` yet, so the runner drops
the just-computed mechanical scores into `scores.json` in the archive. If it
exists, read it and skip the DB query:
```bash
[ -f "{run_dir}/scores.json" ] && cat "{run_dir}/scores.json" # {"test_coverage": 1.0, "code_quality": 0.83, ...}
```
If `scores.json` is absent (e.g. retroactive `retort evaluate`), fall back to the
database.
The database is at `<experiment>/retort.db`. `run_dir` is `runs/<cell>/<rep>`,
so walk *up* until you find `retort.db` (don't hard-code a level count — the
nesting can vary). Match this run by the factors in `stack.json` plus the
replicate (the trailing `repN` of `run_dir`, also in `_meta.json`):
```bash
db=""; d="{run_dir}"
for _ in 1 2 3 4 5; do d="$(cd "$d/.." && pwd)"; [ -f "$d/retort.db" ] && { db="$d/retort.db"; break; }; done
lang=$(python3 -c "import json;print(json.load(open('{run_dir}/stack.json'))['language'])")
model=$(python3 -c "import json;print(json.load(open('{run_dir}/stack.json')).get('model',''))")
tooling=$(python3 -c "import json;print(json.load(open('{run_dir}/stack.json')).get('tooling',''))")
rep=$(basename "{run_dir}" | sed -E 's/rep([0-9]+).*/\1/')
# A resumed/retried cell can have BOTH a stale `failed` row (test_coverage=0)
# and the real `completed` row for the same (factors, replicate). Pull scores
# from the single most-recent matching run, preferring the archive's own state:
# a `-failed` run_dir -> the failed row, otherwise the completed row.
want_status=completed
case "{run_dir}" in *-failed) want_status=failed;; esac
sqlite3 -readonly "$db" "
SELECT rr.metric_name, rr.value
FROM run_results rr
WHERE rr.run_id = (
SELECT er.id FROM experiment_runs er
WHERE json_extract(er.run_config_json,'\$.language')='$lang'
AND json_extract(er.run_config_json,'\$.model')='$model'
AND json_extract(er.run_config_json,'\$.tooling')='$tooling'
AND er.replicate=$rep AND er.status='$want_status'
ORDER BY er.finished_at DESC LIMIT 1)
AND rr.metric_name IN ('test_coverage','code_quality','defect_rate',
'maintainability','idiomatic','token_efficiency');"
```
Interpret the stored scores (all 0–1) — these stand in for re-running:
- **`test_coverage`** — coverage / pass-rate. **1.0 ⇒ build + all tests passed; 0.0 ⇒ tests did not execute** (build or import failure — the test gate). Use this as the build+test signal.
- **`code_quality`** — lint/quality score. Use it for the Lint line.
- **`defect_rate`** — `1.0` ⇒ build+test succeeded.
Constraints:
- You MUST NOT re-run build/test/lint when these scores exist. Cite the score (e.g. "test_coverage=1.0 from retort.db") as evidence.
- **Fallback** — only if the DB or this run's row is absent (e.g. evaluating an un-scored archive): run the language's **test command once** (it builds too); skip the separate build and lint runs. Mark build/lint as derived. Use a 180s timeout; if a toolchain is missing, mark `unavailable`, not `failed`.
### 3. Extract requirements from TASK.md AND the agent's prompt
**First: prefer a pinned requirement list.** Per-run requirement extraction is
non-deterministic (the same task yields different counts on different runs,
which makes `requirement_coverage` non-comparable). So if the experiment ships a
fixed list, you MUST use it verbatim. Walk *up* from `run_dir` (as you did for
`retort.db`) to find `REQUIREMENTS.json`:
```bash
req=""; d="{run_dir}"
for _ in 1 2 3 4 5; do d="$(cd "$d/.." && pwd)"; [ -f "$d/REQUIREMENTS.json" ] && { req="$d/REQUIREMENTS.json"; break; }; done
```
If `REQUIREMENTS.json` exists, its `requirements[]` array IS the checklist —
use those exact `id`s and `requirement` texts, in that order, as the **complete
and only** list. Do NOT add, drop, merge, or re-number any. The denominator
(`total`) is fixed at `len(requirements)` for **every** run of this task. Skip
the extraction below entirely; go straight to step 4. (`how_to_verify` on each
entry tells you what evidence to look for.)
**Otherwise (no pinned list), extract requirements** as below.
The run must conform to the full prompt the agent was actually given. retort
assembles that prompt as: *"Read TASK.md … implement everything it asks for"* +
(a tooling instruction) + (only when a `prompt` factor was set) the contents of
`prompts/<level>.md`. So there are up to two requirement sources:
1. **`TASK.md`** — the task spec, always present. Parse into a checklist (`R1`, `R2`, …). Typical patterns:
- Numbered lists (`1. Implement ...`), "must"/"should" bullets, code-fenced API signatures.
2. **The prompt-factor file** — *only if* `stack.json` has `prompt` set to something other than `none`/absent. Then read `prompts/<prompt>.md` from the experiment dir (where `workspace.yaml` lives — walk up from `run_dir` like you did for `retort.db`). Extract its additional, checkable instructions as prompt requirements (`P1`, `P2`, …) and verify the code/output followed them.
**Ignore `prompts.txt`** — it is a benchmark-template placeholder (it literally begins with `#ignore this file`), NOT the prompt retort gave the agent. Do not derive requirements from it.
Constraints:
- You MUST produce a deterministic list with stable IDs (`R<N>` for TASK.md, `P<N>` for prompt-factor instructions) so comparisons across runs align.
- You MUST NOT invent requirements not present in TASK.md or the prompt-factor file — these are the spec, not your expectations.
- You SHOULD group related bullets into a single requirement when the source is clearly a single ask.
- Most runs have no `prompt` factor, so the `P*` list is usually empty — that's fine; TASK.md is then the whole spec.
### 4. Assess each requirement and prompt instruction
This is the conformance gate: a run that doesn't implement the spec (and follow
the prompt) is a failure, so be accurate — cite evidence, don't guess.
For each `R<N>` (TASK.md) and each `P<N>` (prompt), classify as one of:
- `implemented` — code clearly satisfies it, tests exercise it
- `partial` — code attempts it but is incomplete or untested
- `missing` — no evidence in the codebase
- `cannot-verify` — you genuinely can't tell from the code (rare). Use sparingly with evidence.
**Tests are non-negotiable:** if `test_coverage == 0` (tests did not run), the run already FAILS the test gate — that is always a failure, full stop. Note it up front and don't dress it up as `cannot-verify`.
Base the assessment on:
- The generated source (read key files)
- The stored `test_coverage` from Step 2 (1.0 ⇒ build + all tests pass; 0.0 ⇒ tests did not execute, so treat unverified requirements as `cannot-verify`)
- The grepped test/skip counts from Step 5
Constraints:
- You MUST cite concrete evidence for each classification: file path, symbol name, or test name.
- You MUST NOT score a requirement as `implemented` solely because it has a stub function.
- You SHOULD note "enhancement beyond spec" separately — these aren't deductions but are worth surfacing.
### 5. Detect skipped / disabled tests
Skips inflate pass rates without verifying behavior. Count them:
```bash
# Python
grep -rE "pytest\.skip|@pytest\.mark\.skip|xfail" tests/ --include="*.py" 2>/dev/null | wc -l
# Go
grep -rE "t\.Skip\(|t\.Skipf\(" . --include="*.go" 2>/dev/null | wc -l
# Rust
grep -rE "#\[ignore\]|#\[cfg\(ignore\)\]" . --include="*.rs" 2>/dev/null | wc -l
# TypeScript (jest/vitest)
grep -rE "\.skip\(|xit\(|xdescribe\(|it\.todo\(" . --include="*.ts" --include="*.js" 2>/dev/null | wc -l
```
Constraints:
- You MUST report `effective_tests = passed + failed` (skipped excluded).
- You MUST flag a `skipped_test` finding for each skip, even if the skip looks "reasonable" — the signal matters for cross-run comparison.
### 6. Compute run metrics
```bash
# Lines of code (exclude build artifacts)
cloc . --exclude-dir=node_modules,target,__pycache__,.git,dist,build 2>/dev/null | tail -20
# File count
find . -type f \
-not -path "*/node_modules/*" -not -path "*/target/*" \
-not -path "*/__pycache__/*" -not -path "*/.git/*" \
| wc -l
# Dependency count (language-appropriate)
case $lang in
python) wc -l requirements.txt pyproject.toml 2>/dev/null ;;
typescript) node -e "const p=require('./package.json');console.log(Object.keys({...p.dependencies,...p.devDependencies}).length)" 2>/dev/null ;;
go) grep -c "^\s*\S" go.sum 2>/dev/null ;;
rust) grep -cE "^\S+ = " Cargo.toml 2>/dev/null ;;
esac
```
If `cloc` isn't available, fall back to a simple `wc -l` loop over source files for the language's extensions only. Never include `node_modules`, `target`, etc.
### 7. Invoke run-summary
Delegate architecture analysis to the `run-summary` skill:
```
summarize codebase {run_dir} to {run_dir}/summary/
```
This produces structured markdown under `{run_dir}/summary/` covering modules, interfaces, and flow. Reference it from the final report rather than duplicating its content.
### 8. Write findings.jsonl
One JSON object per line, one object per finding. Schema:
```json
{"id": "R3", "kind": "requirement_missing", "severity": "high", "title": "No pagination support on GET /books", "evidence": "src/app.py:42 returns full list unconditionally", "suggestion": "Add ?limit and ?offset query params"}
{"id": "test-skip-1", "kind": "skipped_test", "severity": "medium", "title": "test_concurrent_writes is skipped", "evidence": "tests/test_app.py:87 @pytest.mark.skip", "suggestion": "Implement the concurrency check or delete the test"}
{"id": "build-fail", "kind": "build_failure", "severity": "critical", "title": "cargo build fails with E0308", "evidence": "src/main.rs:23 — mismatched types", "suggestion": "Fix the type signature before this run can be scored"}
```
Allowed `kind` values:
- `requirement_missing`, `requirement_partial`
- `build_failure`, `test_failure`
- `skipped_test`, `disabled_test`
- `lint_warning`, `security_concern`
- `doc_missing`, `enhancement`
Allowed `severity`: `critical`, `high`, `medium`, `low`, `info`.
Constraints:
- You MUST produce valid JSON on every line (newline-delimited).
- You MUST NOT emit findings that duplicate each other; collapse similar items.
- Each finding MUST have non-empty `evidence` — the file + line or command + output snippet that backs the claim.
### 9. Write evaluation.md
Use the template in Output Format below. The human-readable report links to `findings.jsonl` and `summary/index.md` rather than inlining them.
## Output Format
```markdown
# Evaluation: {cell_name} · rep {replicate}
## Summary
- **Factors:** language={lang}, model={model}, tooling={tooling} (plus any extras)
- **Status:** ok | failed ({reason}) | cannot-verify ({reason})
- **Requirements:** {implemented}/{total} implemented, {partial} partial, {missing} missing
- **Tests:** {passed} passed / {failed} failed / {skipped} skipped ({effective} effective)
- **Build:** {pass|fail|unavailable} — {duration}s
- **Lint:** {pass|fail|unavailable} — {warning_count} warnings
- **Architecture:** see `summary/index.md`
- **Findings:** {n} items in `findings.jsonl` ({critical} critical, {high} high, ...)
## Requirements
| ID | Requirement (short) | Status | Evidence |
|----|----|----|----|
| R1 | ... | ✓ implemented | `src/app.py:Book` |
| R2 | ... | ~ partial | `src/app.py:list_books` — no pagination |
| R3 | ... | ✗ missing | no search endpoint found |
## Build & Test
```text
{build command}
{first 40 lines of output, elided if long}
```
```text
{test command}
{test summary + failures}
```
## Metrics
| Metric | Value |
|--------|-------|
| Lines of code (source only) | {n} |
| Files | {n} |
| Dependencies | {n} |
| Tests total | {n} |
| Tests effective | {n} |
| Skip ratio | {pct}% |
| Build duration | {s}s |
## Findings
Top 5 by severity (full list in `findings.jsonl`):
1. [critical] ...
2. [high] ...
...
## Reproduce
```bash
cd {run_dir}
{exact commands used above, in order}
```
```
## Interaction with retort
- The retort CLI invokes this skill after each successful run (see `cli.py:_evaluate_run`). You SHOULD assume the archive already exists when this skill is called.
- Evaluation failures MUST NOT abort the experiment — the skill exits with stderr written but always exit code 0 so the run loop continues.
- Results are cached per run — if `evaluation.md` already exists and is newer than all source files in `run_dir`, the skill MAY exit early (idempotent re-invocation).
## Constraints Summary
- You MUST NOT modify files in `run_dir` except under `summary/`, and MUST create `evaluation.md` and `findings.jsonl` inside `run_dir`.
- You MUST NOT write to `retort.db` or any file outside `run_dir`.
- You MUST finish in under 5 minutes wall-clock. If you can't, emit whatever you have and return.
- You MUST cite file:line evidence for every finding.
- You MUST keep the output deterministic enough that re-running against the same workspace produces the same requirement IDs and the same findings (order may differ).
## Troubleshooting
**Toolchain missing (e.g. `cargo: command not found`)**
- Mark build/test as `unavailable`.
- Add a finding `toolchain_missing` (severity: info) so cross-run comparison knows why this run wasn't verified.
**TASK.md looks generic / doesn't list discrete requirements**
- Extract one requirement per imperative sentence in the prompt.
- Emit a `doc_missing` info finding noting that the task spec is under-specified.
**`run-summary` skill fails**
- Continue without it. Note in evaluation.md under Architecture: "summary skill unavailable".
- Do not let summary failure prevent the evaluation report from being written.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!