d6c4faa3c0
The #11 vacuous-green shape on the main path: `outcome = DONE` was set purely from the absence of a BLOCKED task, with no check that the iteration actually wrote anything. `files_touched` was even computed by the finalize agent but never asserted > 0, `e2e.status` was never read, and in mini mode the handed-off RED test was never independently re-run (GREEN was asserted from the implementer's self-report, never observed). Add a positive-evidence precondition on DONE, taken from git ground truth — never a self-report, which must not be able to fail a run that actually did the work: - Standard mode: a dedicated `tree-check` agent runs `git status --porcelain | wc -l` after the per-task loop but BEFORE E2E/finalize. Zero (or a non-returning agent) downgrades DONE to BLOCKED. `git status --porcelain` (not `git diff HEAD`) so a brand-new untracked file — the implementer leaves edits unstaged — still counts; running pre-E2E/finalize keeps fixtures and the stats/BLOCKED.md artefacts from inflating it. - Mini mode: an independent `mini-verify` agent re-runs the RED test by name and the suite and checks the tree is dirty; a still-red test, a regression, or a clean tree is BLOCKED (route back to debug). - Per-task `applied_changes` (OR-ed across the initial dispatch and every repair) feeds only a neutral concern, never the outcome. - `e2e.status` is now read: a non-DONE status or a zero-fixture run surfaces as a concern. A clean-tree no-op is BLOCKED with no BLOCKED.md (nothing to clean up); the status + reason ride the end-report. SKILL.md documents the precondition, the no-BLOCKED.md carve-out, and the Step-4 handling. closes #12