Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
2643d39
Merge pull request #277 from slowdini/main
slowdini Aug 16, 2026
a343cdb
feat(evals): declare the codebase a task environment is built from
slowdini Aug 16, 2026
84cdd6b
feat(source): resolve and materialize a declared source
slowdini Aug 16, 2026
a01edcb
test(run): compare baseline commit dates across git versions
slowdini Aug 16, 2026
89552ef
feat(run): build task environments from a sourced codebase
slowdini Aug 16, 2026
c621cd3
fix(source): hold the operator's git configuration off a sourced code…
slowdini Aug 17, 2026
68f0599
feat(run): record the resolved codebase in conditions and dispatch
slowdini Aug 17, 2026
6da7031
refactor(source): move the resolver's tests out of the module they ex…
slowdini Aug 17, 2026
87c0797
feat(pipeline): carry the resolved codebase to run, benchmark, and ba…
slowdini Aug 17, 2026
0dea570
docs: ship a guide for sourcing a codebase
slowdini Aug 17, 2026
05eedf0
fix(schema): keep the codebase additions to the schemas additive
slowdini Aug 17, 2026
1459a49
test(codebase): assert the cited origin in the host's own spelling
slowdini Aug 17, 2026
37eec5e
Merge pull request #278 from slowdini/feat/codebase-source
slowdini Aug 17, 2026
6e9aec9
refactor(source): name the subject a resolution is for
slowdini Aug 19, 2026
5759f28
refactor(core): name the shared record for what it records
slowdini Aug 19, 2026
c19936e
feat(core): default the eval home outside the skill's repository
slowdini Aug 19, 2026
1e251e9
feat(run): source the skill under test as a copied input
slowdini Aug 19, 2026
d38aa2e
refactor(pipeline): drop the relative-path live-source branch
slowdini Aug 19, 2026
9857372
feat(grade): read the iteration's own copy of the skill
slowdini Aug 19, 2026
c509462
test(run): hold Mode B to the same copied-input claim
slowdini Aug 19, 2026
4050703
docs: state the copied-input isolation claim
slowdini Aug 19, 2026
092adf2
refactor(workspace): tie the promoted commit to the baseline's own tree
slowdini Aug 19, 2026
e8983b0
test(pipeline): pin the skill source reaching each run record
slowdini Aug 19, 2026
3e24c77
fix(ci): fix classic windows path error
slowdini Aug 20, 2026
cd0c823
Merge pull request #279 from slowdini/feat/skill-source
slowdini Aug 20, 2026
5b4ea9c
feat(core): probe hard-link availability between two directories
slowdini Aug 20, 2026
5af21dd
feat(source): provision an environment from the cached checkout
slowdini Aug 20, 2026
d6d124e
feat(run): provision task environments from the per-iteration codebas…
slowdini Aug 20, 2026
db8ea64
docs(codebase): describe the per-iteration cache and its provisioning
slowdini Aug 20, 2026
0045b49
chore: comment cleanup
slowdini Aug 20, 2026
abcd77f
fix(run): read a codebase env link count through fsutil on Windows
slowdini Aug 20, 2026
79e993f
Merge pull request #280 from slowdini/feat/codebase-cache
slowdini Aug 20, 2026
f147395
refactor(core): share one isolated Git invocation helper
slowdini Aug 20, 2026
5f5c196
feat(diff-scope): measure and capture diffs with Git
slowdini Aug 20, 2026
ab2c66b
Merge pull request #281 from slowdini/feat/git-diff-capture
slowdini Aug 20, 2026
7d7918e
feat(run): drive every dispatch from the runner
slowdini Aug 20, 2026
f9ec57f
Merge pull request #282 from slowdini/feat/runner-driven-dispatch
slowdini Aug 20, 2026
387655e
refactor(platform): require WSL on Windows
slowdini Aug 21, 2026
e13bd5c
fix(release): allow Linux-only dist workflow
slowdini Aug 21, 2026
5e039cd
Merge pull request #283 from slowdini/feat/remove-native-windows
slowdini Aug 21, 2026
1ae8f23
feat(run): derive conversation turns from a heuristic responder
slowdini Aug 21, 2026
9e18a42
Merge pull request #284 from slowdini/feat/heuristic-responder
slowdini Aug 21, 2026
e9503d4
feat(run): answer the agent with a model, not a Markdown parser
slowdini Aug 22, 2026
1019c00
Merge pull request #285 from slowdini/feat/llm-responder
slowdini Aug 22, 2026
e6fb818
feat(realistic-env): add whitelist-based dev tool allowance
slowdini Aug 22, 2026
daf51cc
feat(guard): add configurable command policies
slowdini Aug 22, 2026
3347030
Merge pull request #286 from slowdini/feat/write-guard-real-dev
slowdini Aug 23, 2026
c56cb30
feat(run): preserve sourced harness config
slowdini Aug 23, 2026
5894b93
Merge pull request #287 from slowdini/feat/codebase-harness-config
slowdini Aug 23, 2026
dd28892
feat(judging): add bounded evidence bundles
slowdini Aug 23, 2026
6707dec
Merge pull request #288 from slowdini/feat/bounded-judge-evidence
slowdini Aug 24, 2026
26d9f99
feat(judging): add multi-sample pass^k grading
slowdini Aug 24, 2026
c4af136
Merge pull request #289 from slowdini/feat/multi-sample-judging
slowdini Aug 24, 2026
34e6661
feat(cli): add paired evidence comparison
slowdini Aug 24, 2026
5f2047b
Merge pull request #290 from slowdini/feat/compare-evidence
slowdini Aug 24, 2026
02d5769
feat(evals): support multi-skill treatments
slowdini Aug 24, 2026
e30a509
Merge pull request #291 from slowdini/feat/multi-skill-treatments
slowdini Aug 24, 2026
4778814
feat(init): scaffold pinned codebase fixtures
slowdini Aug 25, 2026
b892346
Merge pull request #292 from slowdini/feat/default-codebase-fixtures
slowdini Aug 25, 2026
927d758
feat(evals): require codebase-backed runs
slowdini Aug 26, 2026
f4a43d4
test(evals): cover unified run contracts
slowdini Aug 26, 2026
a34bed8
docs(evals): describe unified environment model
slowdini Aug 26, 2026
715b454
Merge pull request #293 from slowdini/issue-266-retire-fixture-only
slowdini Aug 26, 2026
b2574dc
fix(cli): re-emit --harness-file in generated commands, warn on descr…
slowdini Sep 1, 2026
4f2b1ed
docs(harness-file): state the drift check plainly in help and guides
slowdini Sep 1, 2026
8a34466
Merge pull request #299 from slowdini/issue-294-harness-file-selector
slowdini Sep 1, 2026
f1882d2
fix(grade): read assertions from the live evals.json (#295)
slowdini Sep 1, 2026
c435111
Merge pull request #300 from slowdini/issue-295-live-assertions
slowdini Sep 1, 2026
0a13e81
fix(run): hide framework-staged files from the codebase's own tooling…
slowdini Sep 1, 2026
308976f
Merge pull request #302 from slowdini/issue-296-framework-ignore-files
slowdini Sep 1, 2026
b73c497
fix(guard): name every denying layer in one Bash verdict (#297)
slowdini Sep 1, 2026
dee1294
feat(guard): allow dev and start scripts in language/javascript (#297)
slowdini Sep 1, 2026
cdb3a6b
Merge pull request #303 from slowdini/issue-297-guard-dev-server-verdict
slowdini Sep 2, 2026
d52e09d
fix(guard): reach every env guard from teardown-guard (#298)
slowdini Sep 2, 2026
bc883ce
fix(run): keep the task-local scratch directory out of diff scope (#298)
slowdini Sep 2, 2026
acd3b65
fix(cli): name the real workspace path in teardown's discard hint (#298)
slowdini Sep 2, 2026
66d0611
fix(guard): drop the unreachable iteration default in the sweep report
slowdini Sep 2, 2026
8bc5962
Merge pull request #304 from slowdini/issue-298-prerelease-fixes
slowdini Sep 2, 2026
1076f00
fix(grade): skip incomplete command checks
slowdini Sep 3, 2026
289900b
Merge pull request #312 from slowdini/issue-305-partial-dispatch-comm…
slowdini Sep 3, 2026
5bf8831
feat(run): record effective guard provenance
slowdini Sep 3, 2026
e349c7f
Merge pull request #313 from slowdini/issue-306-guard-provenance
slowdini Sep 3, 2026
68eb2bc
fix(grade): match transcript_check tool patterns by descriptor role
slowdini Sep 3, 2026
322d77a
Merge pull request #314 from slowdini/issue-308-descriptor-alias-tran…
slowdini Sep 3, 2026
44333d6
fix(codex): verify staged skill access
slowdini Sep 3, 2026
82d657d
Merge pull request #315 from slowdini/fix/codex-skill-access-evidence
slowdini Sep 3, 2026
ffc887a
feat(run): start plan-mode evals in the harness's native plan mode (#…
slowdini Sep 3, 2026
3027b02
fix(tests): compare recorded paths against their resolved spelling
slowdini Sep 3, 2026
2d70ab0
Merge pull request #316 from slowdini/issue-301-real-plan-mode
slowdini Sep 3, 2026
d28d0ed
fix(pipeline): match aliased spellings in live-source-read detection
slowdini Sep 4, 2026
9d062f3
refactor(pipeline): move the detect-stray-writes tests beside the module
slowdini Sep 4, 2026
7e02b63
Merge pull request #318 from slowdini/issue-317-resolve-live-source-a…
slowdini Sep 4, 2026
1b5474c
feat(timing): persist runner-measured duration
slowdini Sep 4, 2026
f9bc6be
Merge pull request #319 from slowdini/issue-310-runner-duration
slowdini Sep 4, 2026
714f677
feat(sandbox): contain the shell commands that write paths outright
slowdini Sep 4, 2026
e5c98d7
Merge pull request #320 from slowdini/issue-307-mutator-containment
slowdini Sep 4, 2026
76a1bd7
docs(codex): explain nested dispatch sandbox constraints
slowdini Sep 5, 2026
28c2b6f
Merge pull request #321 from slowdini/docs/311-nested-codex-sandbox
slowdini Sep 5, 2026
1a0e1cb
chore: bump version to 0.10.0
github-actions[bot] Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Check every text file out with LF on all platforms. Generated artifacts embed the bytes of
# descriptors, profiles, and docs verbatim, and the byte-exact tests compare against LF fixtures, so
# a CRLF working tree on Windows silently changes program output.
# line-ending conversion would silently change program output.
* text=auto eol=lf

# Golden fixtures are byte-exact — never EOL-normalize them.
Expand Down
33 changes: 4 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ env:
jobs:
test:
name: Test suite
strategy:
# Windows is the platform this matrix exists to cover, so a Linux failure
# must not cancel it — that is exactly when its result is worth having.
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
Expand All @@ -31,33 +25,14 @@ jobs:
components: rustfmt, clippy
- name: Cache cargo build
uses: Swatinem/rust-cache@v2
- name: Install jq
# Git for Windows supplies sh, xargs, tr, and wc, but not jq, and the
# judge-recipe tests execute the shipped pipeline text rather than a
# stand-in for it.
if: runner.os == 'Windows'
run: choco install jq --yes --no-progress
- name: Permit symlink creation
# Windows creates symlinks only under Developer Mode or elevation, and
# the core::fs round-trips need one. Asking for it explicitly beats
# depending on how the runner's token happens to be built.
if: runner.os == 'Windows'
run: >
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
/t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Test
# Capability-gated tests (the POSIX recipe pipelines, symlink
# round-trips, long-path staging) skip with a printed reason on a host
# that lacks the capability. This turns every such skip into a failure,
# so neither runner can quietly stop covering them. Ubuntu ships the
# recipe tools; the steps above provide them on Windows. Long paths need
# no provisioning — the runner passes core.longpaths to git itself.
# EVAL_MAGIC_SH stays unset deliberately: discovering the shell from the
# Git install root is what a Windows user hits, so CI should run it too.
# Capability-gated tests skip with a printed reason on a host that lacks
# the capability. CI turns every such skip into a failure so coverage
# cannot shrink silently.
env:
EVAL_MAGIC_REQUIRE_POSIX_TOOLS: 1
run: cargo test --all-targets
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v6
with:
persist-credentials: false
Expand Down Expand Up @@ -146,9 +143,7 @@ jobs:
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
# Force bash so every release runner writes GitHub outputs with one syntax.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
Expand Down
62 changes: 28 additions & 34 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
`eval-magic` is a Rust CLI crate. The binary entry point is `src/main.rs`; reusable logic lives in
`src/lib.rs` and submodules such as `cli/`, `pipeline/`, `sandbox/`, `validation/`, and
`workspace/`. JSON schemas are tracked in `schema/`, harness descriptors (plus embedded harness
assets such as the OpenCode write-guard plugin template) in `harnesses/`, shared
prompt profiles in `profiles/`, and docs in `docs/` — user-facing Markdown under `docs/guides/`
ships embedded in the binary as `eval-magic docs <topic>`; the other files are internal development
docs. `docs/developer_overview.md` maps the repository and holds the placement policy. Integration
tests are split by surface area under `tests/cli/` and `tests/run/`; unit tests usually live beside
the module they exercise.
assets such as the OpenCode write-guard plugin template) in `harnesses/`, the `RUNBOOK.md` prose
template in `profiles/`, and docs in `docs/` — user-facing Markdown under `docs/guides/` ships
embedded in the binary as `eval-magic docs <topic>`; the other files are internal development docs.
`docs/developer_overview.md` maps the repository and holds the placement policy. Integration tests
are split by surface area under `tests/cli/` and `tests/run/`; unit tests usually live beside the
module they exercise.

## Build, Test, and Development Commands

Expand Down Expand Up @@ -54,41 +54,35 @@ Extraction is a size decision, not a style preference; don't split a small inlin
**Spawning a child process from a test.** Use the hidden `__fixture` subcommand, never `sh`, `true`,
`printf`, or a `#!/bin/sh` stub. It exits with a chosen code, emits chosen bytes, writes a chosen
file, or checks a file or variable — see `FixtureArgs` in `src/cli/args.rs`. One invocation parses
the same under `sh -c` and `cmd /C`, which is what keeps `command_check` tests off per-OS command
strings. Build the command with the `fixture` helper (`tests/run/helpers.rs` for integration tests,
the one in `src/pipeline/grade/command_check/tests.rs` for unit tests). Because the fixture is the
binary, `cargo test --lib` alone does not build it — run `cargo test`, or `cargo build` first.

**Tests are gated on capabilities, not on the OS.** `#[cfg(unix)]` on a test hides it from
compilation and clippy on the other host and hides the coverage gap. Instead, probe for what the
test actually needs and call `report_skip` (`src/core/runtime.rs`), which prints the reason and
returns `true`. Setting `EVAL_MAGIC_REQUIRE_POSIX_TOOLS=1` turns every skip into a failure; CI sets
it on both runners, so neither can quietly stop covering something. Three capabilities are gated
today: the recipe tools beyond the shell itself (`require_posix_toolchain` — in practice `jq`),
symlink creation, which Windows allows only under Developer Mode, and creating a path past
Windows' 259-character limit (`deep_task_root`, `src/cli/run/orchestrate/git.rs`). The Windows
runner is provisioned for those rather than exempted from them, so a skip there is a red build. The
shell is not one of them; it is a hard requirement, per the section below.
`require_posix_toolchain` is not test-only either — the `run` preflight uses it to warn about the
same gap. Where a genuine per-OS difference is the behavior under test — signals, path separators —
branch on `cfg!(windows)` at runtime so both arms still compile everywhere.
predictably under `sh -c`, which keeps `command_check` tests focused on runner behavior instead of
the host's utility implementations. Build the command with the `fixture` helper
(`tests/run/helpers.rs` for integration tests, the one in
`src/pipeline/grade/command_check/tests.rs` for unit tests). Because the fixture is the binary,
`cargo test --lib` alone does not build it — run `cargo test`, or `cargo build` first.

**Tests are gated on capabilities, not on broad platform labels.** Probe for what the test actually
needs and call `report_skip` (`src/core/runtime.rs`), which prints the reason and returns `true`.
Setting `EVAL_MAGIC_REQUIRE_POSIX_TOOLS=1` turns every skip into a failure; CI sets it so coverage
cannot quietly shrink. Symlink creation is capability-gated because the backing filesystem may
forbid it. The shell is not capability-gated; it is a hard requirement, per the section below.

**A POSIX shell is required, for use and for development.** Harness `exec_template`s are POSIX
command lines, so the dispatch and probe paths spawn `sh` via `posix_shell()`
(`src/core/runtime.rs`) rather than a hardcoded `/bin/sh`: it searches `PATH`, then a Git for
Windows install. Set `EVAL_MAGIC_SH` to override it. `cargo test` inherits the requirement — the
scripted-turn tests spawn a `#!/bin/sh` harness stub through the resolved shell and do not skip —
so a host without `sh` fails the suite instead of quietly covering less. `jq` is required alongside
it for the parallel-dispatch and judge recipes; Git for Windows supplies the shell, `xargs`, `tr`,
and `wc`, but not `jq`. `POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is the one wording the
command lines, so the dispatch and probe paths spawn `sh` through `run_in_posix_shell` /
`posix_shell()` (`src/core/runtime.rs`) rather than a hardcoded `/bin/sh`: it searches `PATH`, then
checks `/bin/sh`. Set `EVAL_MAGIC_SH` to override it. `cargo test` inherits the requirement — the
dispatch tests spawn a `#!/bin/sh` harness stub through the resolved shell and do not skip — so a
host without `sh` fails the suite instead of quietly covering less. The shell is the whole
requirement: `jq` was needed only while operators pasted the generated dispatch and judge recipes,
and `eval-magic dispatch` drives both itself.
`POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is the one wording the
Markdown-carrying surfaces reuse: the shell-discovery errors, the `run` preflight warnings,
`RUNBOOK.md`, and `dispatch-manifest.md`. State the requirement from there rather than rephrasing
it. `--help` is the one deliberate restatement (`AFTER_HELP` in `src/cli/help.rs`), hard-wrapped and
backtick-free because clap renders into a terminal; keep the two in step by hand.

Which platforms that requirement is honored on — and why preparing on Windows but dispatching from
WSL is a correctness boundary rather than a preference — is stated once under "Platform support" in
`docs/developer_overview.md`.
eval-magic supports Linux and macOS. On Windows, use and develop eval-magic entirely inside WSL;
native Windows is unsupported. The complete boundary and the portable-data exception are stated
under "Platform support" in `docs/developer_overview.md`.

**Where user-facing warnings come from.** Library modules (`pipeline`, `workspace`, `sandbox`,
`adapters`) never print. They return warning strings on their result struct — `#[serde(skip)]` when
Expand Down
38 changes: 1 addition & 37 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "eval-magic"
version = "0.9.1"
version = "0.10.0"
edition = "2024"
description = "One-stop CLI for running skill evals — measure whether an agent skill actually shifts behavior."
license = "MIT"
Expand Down Expand Up @@ -38,20 +38,18 @@ clap = { version = "4.6.1", features = ["derive"] }
# schemas are embedded (include_str!) and use only internal #/definitions refs,
# so no resolver is needed at all.
jsonschema = { version = "0.46.5", default-features = false }
# Bash write-mutation patterns (sandbox policy). default-features off drops the
# Unicode tables: the shell-command patterns are ASCII, and ASCII `\b`/`\s` is
# the intended semantics, so `perf` (matching speed) is all we keep beyond `std`.
# Eval validation, transcript checks, dispatch parsing, and staging patterns.
# Default features are disabled to drop Unicode tables: these authored patterns
# use ASCII syntax, so `perf` (matching speed) is all we keep beyond `std`.
regex = { version = "1.12.3", default-features = false, features = ["std", "perf"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.150", features = ["preserve_order"] }
similar = { version = "3.1.1", default-features = false }
tempfile = "3.27.0"
thiserror = "2.0.18"
# Harness descriptor files (harnesses/*.toml). `display` serializes the
# resolved (layer-merged) descriptor back to authorable TOML for
# `harness show`.
toml = { version = "0.9", default-features = false, features = ["parse", "serde", "display"] }
walkdir = "2.5.0"

[dev-dependencies]
assert_cmd = "2.2.2"
Expand Down
Loading
Loading