From 5db8d785c2f706d5948717c2f579172be76b0742 Mon Sep 17 00:00:00 2001 From: Stefano Furiosi <50787+stid@users.noreply.github.com> Date: Fri, 4 Sep 2026 12:06:35 -0700 Subject: [PATCH 1/4] chore: bump version to 0.19.0 (feat/eval-5-1) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01R6BjXKFWAvEtKDYifc2weC --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 71523b3..94aee49 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "lcd", "source": "./", "description": "Lean Context Development — adaptive-weight agentic dev methodology (Quick / Standard / Deep lanes).", - "version": "0.18.1" + "version": "0.19.0" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 104981d..4f423c4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "lcd", "displayName": "Lean Context Development", - "version": "0.18.1", + "version": "0.19.0", "description": "Context-economy agentic dev methodology: process weight adapts to the work (Quick / Standard / Deep lanes) and durable artifacts make a context reset cheap.", "author": { "name": "stid" From c9d31055e470bfb5a609c797c16683aa9b269456 Mon Sep 17 00:00:00 2001 From: Stefano Furiosi <50787+stid@users.noreply.github.com> Date: Fri, 4 Sep 2026 12:07:13 -0700 Subject: [PATCH 2/4] =?UTF-8?q?feat(evals):=20T1-5.1=20premise=20=E2=80=94?= =?UTF-8?q?=20fixture=20enables=20closeout-evaluator,=20default=20model=20?= =?UTF-8?q?claude-fable-5-1,=20persisted=20quality=20rubric?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - evals/fixture conventions: closeout-evaluator: on (its cost lands in the row) - run-eval.sh: default EVAL_CLAUDE_MODEL claude-fable-5-1 (rows stamp the id; D-015) - evals/rubric.md: the 5-dim two-lens rubric as an artifact; evals/quality/ for scoring files - tests: fixture asserts the key; runner dry-run asserts the default model Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01R6BjXKFWAvEtKDYifc2weC --- .../fixture/.claude/rules/lcd-conventions.md | 3 +- evals/rubric.md | 42 +++++++++++++++++++ evals/run-eval.sh | 7 ++-- tests/test-eval-fixture.sh | 2 + tests/test-eval-runner.sh | 1 + 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 evals/rubric.md diff --git a/evals/fixture/.claude/rules/lcd-conventions.md b/evals/fixture/.claude/rules/lcd-conventions.md index 426849d..52d5f2f 100644 --- a/evals/fixture/.claude/rules/lcd-conventions.md +++ b/evals/fixture/.claude/rules/lcd-conventions.md @@ -4,7 +4,7 @@ > benchmark runs are deterministic — do NOT re-run `/onboard` here. The machine block > below is read by the LCD suite; keep its keys intact. -**Generated:** 2026-06-11 (frozen fixture state) +**Generated:** 2026-06-11 (frozen fixture state) · **Revised:** 2026-09-04 (`closeout-evaluator: on` — T1-5.1: the opt-in evaluator's cost lands in the benchmark row instead of being priced at zero) ## Machine block (read by the LCD suite — do not rename keys) @@ -21,6 +21,7 @@ single-test: node --test --test-name-pattern {name} tests/ gate: node --test tests/ eval: n/a — deterministic node:test suite, no scored/LLM output maintenance-bundle: node --test tests/ +closeout-evaluator: on Key notes: plain Node.js, zero external dependencies — the runner is the built-in diff --git a/evals/rubric.md b/evals/rubric.md new file mode 100644 index 0000000..8b80519 --- /dev/null +++ b/evals/rubric.md @@ -0,0 +1,42 @@ +# Quality rubric — benchmark workspaces (persisted artifact, T1-5.1) + +> Every campaign's quality claim must point at a scoring file under `evals/quality/`, produced +> by applying THIS rubric to each workspace. Earlier campaigns (T1 2026-08-22/28, Standard +> 2026-08-30) reported rubric totals from reviewer transcripts only; those numbers are not +> reproducible and are superseded by the first scoring file that covers their workspaces. + +## The task under test (all arms) + +`stats` operation on the fixture, exposed via CLI **and** HTTP: record count, last-updated +indicator, checksum over the records; validation and error semantics identical on both +surfaces; shared logic in the core module both surfaces already use; full suite green. + +## Five dimensions, 0–5 each (25 per workspace) + +| # | Dimension | 5 | 3 | 1 | +|---|---|---|---|---| +| 1 | **Completeness** | count + last-updated + checksum on BOTH surfaces, suite green | one field or surface partial | one surface missing or suite red | +| 2 | **Cross-surface parity** | same validation, same error codes/messages/shape on CLI and HTTP, and a test asserts it | parity by inspection only, or one divergence | contradictory semantics | +| 3 | **Shared logic** | stats computed once in `core.js`; surfaces are thin adapters | duplication of a piece (e.g. checksum) in one surface | logic re-implemented per surface | +| 4 | **Test quality** (mutation lens) | tests fail on injected defects in count / last-updated / checksum / error path (≥3 of 4 caught) | 2 of 4 caught | ≤1 caught, or tests assert only existence/no-throw | +| 5 | **Code quality** | fits fixture conventions (node:test, zero deps, existing envelope), no dead code, no invented API surface | minor style drift or one unused helper | breaks fixture conventions or adds deps | + +## Two lenses per workspace (independent judges) + +- **Inspection** — read `core.js`, `cli.js`, `server.js`, `tests/*` and the diff since tag + `eval-baseline`; score 1–5 with `file:line` evidence per dimension; dimension 4 by reading + the assertions (would a stub pass?). +- **Mutation** — copy the workspace to a scratch dir, inject the four defects below one at a + time, run `node --test`, record which ones the suite catches; score dimension 4 from the + count and the others from what the mutation work exposed. + 1. count off by one · 2. last-updated frozen/constant · 3. checksum ignores one record · + 4. error path on one surface returns success (or a different code than the other surface) + +Judges never see each other's scores. A workspace's score is the mean of the two lenses; +a campaign's score is the sum over its workspaces (3 runs → /75). + +## Scoring file format (`evals/quality/-.md`) + +One table per lens: `workspace · arm · d1 · d2 · d3 · d4 · d5 · total · key evidence`, then a +`## Means` block (per arm, per lens, and combined) and a `## Mutation matrix` (workspace × +defect → caught/survived). Rows are comparable only within one model id (D-015). diff --git a/evals/run-eval.sh b/evals/run-eval.sh index 01f5ed8..fc53372 100755 --- a/evals/run-eval.sh +++ b/evals/run-eval.sh @@ -35,8 +35,9 @@ # is inert). The arms differ by exactly one flag: the plugin arm adds --plugin-dir. # Env: # EVAL_CLAUDE_BIN model CLI (default: claude) -# EVAL_CLAUDE_MODEL model for the run (default: claude-fable-5 — the experiment's premise -# is Fable 5 behavior; pin explicitly, never trust the CLI default) +# EVAL_CLAUDE_MODEL model for the run (default: claude-fable-5-1, the tier sessions run on +# since 2026-09; rows before that ran claude-fable-5 — pin explicitly, +# never trust the CLI default; rows compare only within one id, D-015) # EVAL_CLAUDE_FLAGS extra flags for the model call # (default: --output-format json --dangerously-skip-permissions) # EVAL_MAX_LEGS max model legs per run (default: 8). A single -p turn often ends @@ -78,7 +79,7 @@ done [[ -n "$arm" ]] || { usage; exit 2; } claude_bin="${EVAL_CLAUDE_BIN:-claude}" -claude_model="${EVAL_CLAUDE_MODEL:-claude-fable-5}" +claude_model="${EVAL_CLAUDE_MODEL:-claude-fable-5-1}" claude_flags="${EVAL_CLAUDE_FLAGS:---output-format json --dangerously-skip-permissions}" max_legs="${EVAL_MAX_LEGS:-8}" if [[ $baseline -eq 1 ]]; then diff --git a/tests/test-eval-fixture.sh b/tests/test-eval-fixture.sh index d19b71d..970ecf6 100644 --- a/tests/test-eval-fixture.sh +++ b/tests/test-eval-fixture.sh @@ -20,6 +20,8 @@ grep -q '"dependencies"' "$fixture/package.json" \ [[ -f "$fixture/.claude/rules/lcd-conventions.md" ]] || fail "fixture not onboarded (conventions missing)" grep -q 'artifact-root: docs/lcd' "$fixture/.claude/rules/lcd-conventions.md" \ || fail "fixture conventions must pin artifact-root docs/lcd" +grep -q '^closeout-evaluator: on' "$fixture/.claude/rules/lcd-conventions.md" \ + || fail "fixture must enable closeout-evaluator (T1-5.1 premise: the evaluator's cost lands in the row)" [[ -f "$fixture/docs/lcd/MAP.md" ]] || fail "fixture MAP.md missing" [[ -f "$fixture/docs/lcd/DECISIONS.md" ]] || fail "fixture DECISIONS.md missing" diff --git a/tests/test-eval-runner.sh b/tests/test-eval-runner.sh index 2845581..c5e261e 100644 --- a/tests/test-eval-runner.sh +++ b/tests/test-eval-runner.sh @@ -29,6 +29,7 @@ out="$(EVAL_CLAUDE_BIN=bash bash "$runner" --arm A --fixture "$fixture" \ --plugin-dir "$plugin" --results "$results" --workspace "$ws" --dry-run 2>&1)"; code=$? assert_exit 0 "$code" "AC-1 (CLI): dry-run exits 0" assert_contains "$out" 'DRY RUN' "AC-1 (CLI): dry-run banner printed" +assert_contains "$out" '--model claude-fable-5-1 ' "AC-1 (CLI): default model is the current tier (D-015: rows stamp it)" assert_contains "$out" "$fixture" "AC-1 (CLI): plan names the fixture" assert_contains "$out" 'arm: A' "AC-1 (CLI): plan names the arm" assert_contains "$out" 'stats-surfaces' "AC-1 (CLI): plan names the scripted slug" From bdd231b630e4b408a16ae9b7a94ab4c52d7c9113 Mon Sep 17 00:00:00 2001 From: Stefano Furiosi <50787+stid@users.noreply.github.com> Date: Fri, 4 Sep 2026 12:11:17 -0700 Subject: [PATCH 3/4] docs(evals): rubric scoring artifact for the 9 verified claude-fable-5 workspaces (18 judges, two lenses) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deep 68 / Standard 70 / bare 68 of 75 — the parity previously quoted from transcripts now has a file. Checksum-skips-a-record mutation survives in 5/9 workspaces across arms. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01R6BjXKFWAvEtKDYifc2weC --- evals/quality/2026-09-04-fable5-verified.md | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 evals/quality/2026-09-04-fable5-verified.md diff --git a/evals/quality/2026-09-04-fable5-verified.md b/evals/quality/2026-09-04-fable5-verified.md new file mode 100644 index 0000000..0be8edf --- /dev/null +++ b/evals/quality/2026-09-04-fable5-verified.md @@ -0,0 +1,80 @@ +# Quality scoring — verified claude-fable-5 workspaces (2026-09-04, rubric v1) + +> First application of `evals/rubric.md` as a persisted artifact. Covers the nine workspaces +> behind the T1 rerun (2026-08-28, `*-r2`: Deep `lcd` ×3, bare `none` ×3) and the Standard +> campaign (2026-08-30, `std-run1..3`). Supersedes the transcript-only totals quoted in +> `results.md` for those campaigns (T1's 69/75 vs 72/75 was scored on the INVALIDATED +> 2026-08-22 workspaces; Standard's 69–71/75 had no artifact). 18 independent judges (one +> inspection, one mutation per workspace); none saw another's score. Model of the runs: +> `claude-fable-5`. + +## Means + +| arm | inspection (mean/25) | mutation (mean/25) | combined (mean/25) | campaign /75 | +|---|---|---|---|---| +| deep | 22.0 (21, 23, 22) | 23.3 (22, 25, 23) | 22.7 | 68 | +| standard | 22.7 (22, 23, 23) | 23.7 (23, 24, 24) | 23.2 | 70 | +| none | 21.7 (21, 23, 21) | 23.3 (23, 23, 24) | 22.5 | 68 | + +## Inspection lens + +| workspace | arm | d1 | d2 | d3 | d4 | d5 | total | key evidence | +|---|---|---|---|---|---|---|---|---| +| 20260828-173311-lcd-r2 | deep | 5 | 4 | 5 | 3 | 4 | **21** | d1 cli.js:36-39 + server.js:43 emit count/lastUpdated/checksum; npm test 26/26 green · d2 shared STATS_INPUT_ERROR core.js:39; AC-4 tests stats.test.js:112-123 assert msg both sides; but GET /stats?x=1 passes while `stats x` errors · d3 getStats core.js:45-51 | +| 20260828-174601-lcd-r2 | deep | 5 | 5 | 5 | 4 | 4 | **23** | d1 core.js:43-52 count/lastUpdated/checksum; cli.js:31-42; server.js:38-46; 25/25 pass · d2 both forward input to core.getStats (cli.js:33, server.js:40); same msg asserted stats.test.js:137,143,153; HTTP==core :131 · d3 core.js:43-52 single impl; surfaces for | +| 20260828-175608-lcd-r2 | deep | 5 | 4 | 5 | 4 | 4 | **22** | d1 cli.js:36-39 + server.js:43 emit count/lastUpdated/checksum; suite 27/27 green · d2 stats.test.js:118-131 AC-4 both surfaces == core.getStats(); errors differ in text (cli.js:33 vs server.js:40) · d3 core.js:32-38 sole computation; surfaces format only · d4 | +| 20260828-174356-none-r2 | none | 5 | 3 | 5 | 4 | 4 | **21** | d1: cli.js:31-37 + server.js:38-41 expose count/lastUpdated/checksum; 25/25 pass. d2: no input so no validation; no test compares CLI vs HTTP output (cli.test.js:32-39 vs server.test.js:52-56) — parity by inspection. d3: core.js:31-37 single getStats; surfaces | +| 20260828-175344-none-r2 | none | 5 | 4 | 5 | 4 | 5 | **23** | d1: cli.js:31-37 + server.js:38-41 expose count/lastUpdated/checksum; suite 25 pass. d2: both call core.getStats; server.test.js:66 deepEquals core, cli.test.js:53-55 only format-checks, no CLI-vs-HTTP test. d3: core.js:31-40 single impl, surfaces thin. d4: co | +| 20260828-180846-none-r2 | none | 5 | 3 | 5 | 3 | 5 | **21** | d1: core.js:30-36 count/lastUpdated/checksum; cli.js:31-37; server.js:38-41; suite 25/25 green. d2: no error path or cross-surface parity test; CLI 'never' vs HTTP null (cli.js:34) is inspection-only. d3: getStats only in core.js:30; surfaces one-call adapters | +| std-run1 | standard | 5 | 4 | 5 | 4 | 4 | **22** | d1 core.js:41-47 count/lastUpdated/checksum; cli.js:31-37; server.js:38-41; 27/27 pass. d2 server.test.js:100-110 asserts CLI==HTTP values; no error/validation path defined or tested for stats. d3 getStats only in core.js:41; surfaces format only. d4 core.test | +| std-run2 | standard | 5 | 4 | 5 | 4 | 5 | **23** | d1 core.js:31-37 getStats; cli.js:31-37; server.js:38-41; suite 22/22 green. d2 server.test.js:70-82 asserts CLI text == HTTP values (empty store only); null->'never' shape diverges; no error-path test for stats. d3 core.js:31-37 sole impl, surfaces format onl | +| std-run3 | standard | 5 | 4 | 5 | 4 | 5 | **23** | d1: cli.js:31-37 + server.js:38-41 expose count/lastUpdated/checksum; suite 28/28 green · d2: both call core.getStats(); AC-4 tests (cli.test.js:88-98, server.test.js:83-88) equal core only transitively; null→'never' cli.js:34; no direct CLI↔HTTP test · d3: co | + +## Mutation lens + +| workspace | arm | d1 | d2 | d3 | d4 | d5 | total | key evidence | +|---|---|---|---|---|---|---|---|---| +| 20260828-173311-lcd-r2 | deep | 5 | 4 | 5 | 3 | 5 | **22** | d1: core.js:45-51 getStats; cli.js:31-41; server.js:38-45; 26/26 green. d2: shared STATS_INPUT_ERROR core.js:39, asserted stats.test.js:116,123; CLI exit1 vs HTTP 405 (semantic stretch), parity indirect. d3: single getStats core.js:45, surfaces format only. d4 | +| 20260828-174601-lcd-r2 | deep | 5 | 5 | 5 | 5 | 5 | **25** | d1: core.js:43-52 getStats; cli.js:31-42 + server.js:38-46 expose all 3 fields; 25/25 pass · d2: both surfaces forward input to core (cli.js:33, server.js:40); AC-7 CLI/HTTP assert same 'stats takes no input' msg + non-success codes (stats.test.js:137,153); AC | +| 20260828-175608-lcd-r2 | deep | 5 | 4 | 5 | 5 | 4 | **23** | d1: core.js:32-38 getStats; cli.js:31-41; server.js:38-45; 27/27 green · d2: stats.test.js:121-134 AC-4 both surfaces == core; error msgs differ cli.js:33 vs server.js:40, no cross-surface error test · d3: only core.js:36 computes checksum; surfaces format onl | +| 20260828-174356-none-r2 | none | 5 | 3 | 5 | 5 | 5 | **23** | d1 core.js:31-37 count/lastUpdated/checksum; cli.js:31-37; server.js:38-41; 25/25 green · d2 no stats validation on either surface; server.test.js:55 asserts HTTP==core, cli.test.js:32-39 checks format only, no CLI-vs-HTTP assertion · d3 single getStats core.j | +| 20260828-175344-none-r2 | none | 5 | 3 | 5 | 5 | 5 | **23** | d1 core.js:31-40 getStats; cli.js:31-37 stats; server.js:38-41 GET /stats; 25/25 green · d2 no CLI-vs-HTTP assertion; server.test.js:66 deepEq core only; cli.test.js:53-55 format regex only · d3 stats computed once core.js:31-40, surfaces pass-through · d4 ccs | +| 20260828-180846-none-r2 | none | 5 | 4 | 5 | 5 | 5 | **24** | d1 core.js:30-36 getStats; cli.js:31-37; server.js:38-41; 25/25 green · d2 server.test.js:51 HTTP==core.getStats(); cli.test.js:36-38 same fields; no CLI-vs-HTTP assert; stats has no input to validate · d3 single getStats in core.js:30; cli/server only format/ | +| std-run1 | standard | 5 | 4 | 5 | 5 | 4 | **23** | d1: core.js:41-47 count/lastUpdated/checksum; cli.js:31-37; server.js:38-41; 27/27 green · d2: server.test.js:100-110 asserts CLI==HTTP values; CLI prints 'never' for null (cli.js:34); no stats error path exists so no error-parity test · d3: getStats only in c | +| std-run2 | standard | 5 | 4 | 5 | 5 | 5 | **24** | d1: cli.js:31-37 + server.js:38-41 expose count/lastUpdated/checksum; suite 22/22 green. d2: parity test server.test.js:70-82 asserts CLI==HTTP values but only on empty store; stats has no error path. d3: core.js:31-37 getStats is sole impl; surfaces call core | +| std-run3 | standard | 5 | 4 | 5 | 5 | 5 | **24** | d1 core.js:31-37 count/lastUpdated/checksum; cli.js:31-37 + server.js:38-41 expose it; 28/28 green · d2 no direct CLI-vs-HTTP test; parity transitive via AC-4 cli.test.js:88-98 and server.test.js:83-88 vs core.getStats(); null->'never' cli.js:34 · d3 stats com | + +## Mutation matrix + +| workspace | arm | D1 count | D2 last-updated | D3 checksum | D4 error path | +|---|---|---|---|---|---| +| 20260828-173311-lcd-r2 | deep | caught | **survived** | **survived** | caught | +| 20260828-174601-lcd-r2 | deep | caught | caught | caught | caught | +| 20260828-175608-lcd-r2 | deep | caught | caught | **survived** | caught | +| 20260828-174356-none-r2 | none | caught | caught | caught | caught | +| 20260828-175344-none-r2 | none | caught | caught | **survived** | caught | +| 20260828-180846-none-r2 | none | caught | caught | **survived** | caught | +| std-run1 | standard | caught | caught | caught | caught | +| std-run2 | standard | caught | caught | caught | caught | +| std-run3 | standard | caught | caught | **survived** | caught | + +## Judge notes (what the score hides) + +- `20260828-173311-lcd-r2` (deep, inspection): No test asserts lastUpdated advances between adds or a non-empty exact checksum; defects 2 and 3 likely survive. Unrequested VERSION-from-package.json + 1.1.0 bump. +- `20260828-173311-lcd-r2` (deep, mutation): D2: cruder variant (constant returned in getStats) IS caught by AC-6; frozen-after-first-mutation survives since no test asserts the stamp advances. Checksum never asserted against a computed expected on non-empty store. +- `20260828-174601-lcd-r2` (deep, inspection): Scratch-copy mutation check: count+1, frozen lastUpdated, drop-last/middle checksum, HTTP 200-on-error, CLI exit 0 all caught; drop-first-record checksum survives (no non-empty checksum pinned). +- `20260828-174601-lcd-r2` (deep, mutation): M3 variant 'ignore first record' (slice(1)) survives: AC-2 only pins empty checksum + text/order diffs, never a multi-record literal. lastUpdated is a mutation seq (D-002), not a timestamp. +- `20260828-175608-lcd-r2` (deep, inspection): Checksum test never pins a value or compares empty-vs-one; a hash dropping first/last record passes. 405 on /stats is a new pattern (other routes 404 on wrong method). Version bump out of scope. +- `20260828-175608-lcd-r2` (deep, mutation): Checksum tests only assert hex64+determinism+changes-on-add; a checksum skipping any single record passes. Version bump/VERSION source change is scope drift outside the task. +- `20260828-174356-none-r2` (none, inspection): Checksum tests only use 0-vs-1 record; a defect hashing only the first record would survive. CLI stats always 0/never (per-process store) so CLI test is weak on values. +- `20260828-174356-none-r2` (none, mutation): M2 variant "frozen after first write" (lastUpdated ??= now) SURVIVED; M4 caught only by pre-existing baseline tests (stats has no error path). CLI stats never tested with count>0 (fresh process). +- `20260828-175344-none-r2` (none, inspection): Stats has no input so parity is trivially strong. Checksum-ignores-FIRST-record would survive (core.test.js:70 only detects last-record drop); CLI checksum never compared to core value. +- `20260828-175344-none-r2` (none, mutation): Checksum never pinned to a value on any surface. Alt variants also survive: lastUpdated frozen after first add; CLI error exit 1->2 (only non-zero asserted). Stats op has no input so no validation to diverge. +- `20260828-180846-none-r2` (none, inspection): Checksum test asserts only "changes after add" so a hash that drops one record still passes; stats has no input so error-parity is vacuous, not tested. Server test deepStrictEqual vs core.getStats() is self-referential. +- `20260828-180846-none-r2` (none, mutation): Checksum coverage gap: no test pins checksum to a known sha256 or detects a dropped record. D2 caught by only one test (core). cli.test.js:41 test name misleading (runs 'bogus'). +- `std-run1` (standard, inspection): Defect 4 not applicable: stats has no input so no error path exists on either surface; error parity only covered by pre-existing add tests. CLI prints 'never' for null lastUpdated (formatting only). +- `std-run1` (standard, mutation): D2 variant 'frozen after first add' SURVIVES (no test checks lastUpdated changes on 2nd add). D2/D3 caught only by core tests, not surface tests. D4 caught only by pre-existing fixture tests. +- `std-run2` (standard, inspection): No golden checksum value: sha256(count) stub passes all tests. No assertion that lastUpdated advances between adds. Parity test covers only empty state (CLI is process-per-run). Comma-op at core.test.js:70. +- `std-run2` (standard, mutation): Variant 'lastUpdated frozen at a constant ISO date' survived (tests only match ISO shape, never that it changes). Parity test covers empty store only, so a record-skipping checksum would not break parity. +- `std-run3` (standard, inspection): Checksum-ignores-first-record would survive (only last-record omission caught). Stats has no input so error parity rests on pre-existing usage/404 tests. runInProcess monkeypatches console.log. +- `std-run3` (standard, mutation): Checksum tests assert determinism only; dropping a record from the hash survives all 3 suites. D2 caught only by core test (timestamp bound), surface tests check format only. Stats has no input so no stats-specific error path exists. From d9abbe69cf023a78a8a8bf5355ba6121a3a82bf4 Mon Sep 17 00:00:00 2001 From: Stefano Furiosi <50787+stid@users.noreply.github.com> Date: Fri, 4 Sep 2026 12:49:14 -0700 Subject: [PATCH 4/4] =?UTF-8?q?docs(evals):=20T1-5.1=20conclusion=20?= =?UTF-8?q?=E2=80=94=20three=20arms=20on=20claude-fable-5-1,=20rubric=20sc?= =?UTF-8?q?oring=20artifact,=20evaluator=20record;=20README=20numbers;=20C?= =?UTF-8?q?HANGELOG=200.19.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01R6BjXKFWAvEtKDYifc2weC --- CHANGELOG.md | 21 +++++++ README.md | 10 +-- evals/quality/2026-09-04-fable51-t151.md | 78 ++++++++++++++++++++++++ evals/results.md | 72 ++++++++++++++++++++++ 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 evals/quality/2026-09-04-fable51-t151.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a1edc..f0a3413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ All notable changes to this project are documented here. Format loosely follows Development is tracked by decision IDs (`D-NNN`) in the project's internal decision log; entries cite them for traceability. +## [0.19.0] — 2026-09-04 + +T1-5.1: the benchmark re-baselined on the current model tier, with quality as an artifact. + +### Added +- **`evals/rubric.md`** — the five-dimension, two-lens (inspection + mutation) quality rubric as + a persisted artifact, and **`evals/quality/`** scoring files: one for the T1-5.1 campaign + (`claude-fable-5-1`) and one applied retroactively to the verified `claude-fable-5` + workspaces. Quality claims now point at a file, not a transcript. +- **T1-5.1 campaign conclusion** in `evals/results.md`: bare $1.41 / Standard $3.33 (2.4×, + evaluator on) / Deep $5.97 (4.2×) on 5.1; quality parity across arms on both tiers (72/72/71 + vs 68/70/68 of 75); the closeout evaluator challenged 3 of 6 plugin closeouts, twice on a + real cross-surface parity defect. + +### Changed +- **Eval fixture enables `closeout-evaluator: on`**, so the opt-in evaluator's cost lands in the + plugin rows instead of being priced at zero (test asserts the key). +- **`run-eval.sh` default model is `claude-fable-5-1`**; rows before 2026-09-04 ran + `claude-fable-5` and stay comparable only among themselves (D-015). Dry-run test pins the default. +- README "Self-measuring" quotes the 5.1 numbers. + ## [0.18.1] — 2026-09-04 Zero-weight release from the first Fable 5.1 review of the methodology (three-analyst council, diff --git a/README.md b/README.md index f376ad9..2e7ffe9 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,12 @@ plugin ships its own benchmark: [`evals/run-eval.sh`](evals/run-eval.sh) drives Deep-lane run against a frozen fixture and a chosen plugin variant, and a golden-locked grader emits one comparable result row per run (audit outcome, suite state, build-loop commits, token cost; rows compare only within one model id). Measured so far (`evals/results.md`, n=3 per arm, -one fixture, `claude-fable-5`): the Deep pipeline costs ~4.4× a bare agent and the median -Standard lane ~2.1×, at near-parity single-shot quality — process weight buys resumability, -not single-shot quality, and the cross-session half is the open measurement (longitudinal eval -planned). Prompt changes ship with an eval row as evidence, +one fixture, `claude-fable-5-1`, 2026-09): the Deep pipeline costs ~4.2× a bare agent and the +median Standard lane ~2.4× (independent closeout evaluator included), at parity single-shot +quality on a persisted rubric (`evals/quality/`) — process weight buys resumability, not +single-shot quality; the evaluator caught real defects the green suite missed in 3 of 6 plugin +runs; the cross-session half is the open measurement (longitudinal eval planned). Prompt +changes ship with an eval row as evidence, and A/B arms have decided real design questions — step enumeration was dropped from three phase commands and kept in `test-gen` on measured cost and quality, not taste. A 2026 survey of spec-driven frameworks (arXiv 2606.04967) names the absence of process benchmarks as a diff --git a/evals/quality/2026-09-04-fable51-t151.md b/evals/quality/2026-09-04-fable51-t151.md new file mode 100644 index 0000000..c321d45 --- /dev/null +++ b/evals/quality/2026-09-04-fable51-t151.md @@ -0,0 +1,78 @@ +# Quality scoring — T1-5.1 campaign, claude-fable-5-1 (2026-09-04, rubric v1) + +> Second application of `evals/rubric.md`; same 18-judge, two-lens protocol as +> `2026-09-04-fable5-verified.md`, so the two files are directly comparable — but the runs are +> NOT (different model id, D-015; compare the arms *within* each file). Workspaces +> `sandboxes/evals/t151-*` in the dev harness (gitignored). Plugin under test: 0.19.0 (=0.18.1 + +> fixture `closeout-evaluator: on`). Evaluator dispatched in 6/6 plugin runs; challenged 3. + +## Means + +| arm | inspection (mean/25) | mutation (mean/25) | combined (mean/25) | campaign /75 | +|---|---|---|---|---| +| deep | 23.7 (24, 24, 23) | 24.3 (25, 24, 24) | 24.0 | 72 | +| standard | 23.7 (24, 24, 23) | 24.3 (24, 24, 25) | 24.0 | 72 | +| none | 23.3 (24, 23, 23) | 24.0 (23, 25, 24) | 23.7 | 71 | + +## Inspection lens + +| workspace | arm | d1 | d2 | d3 | d4 | d5 | total | key evidence | +|---|---|---|---|---|---|---|---|---| +| t151-deep-1 | deep | 5 | 5 | 5 | 5 | 4 | **24** | d1 cli.js:31-42 + server.js:38-45 emit count/lastUpdated/checksum; suite 26/26 green · d2 both relay core msg: cli exit1 stderr (stats-cli.test.js:78-81), http 400 {error} (stats-server.test.js:64-67) · d3 core.js:44-51 single getStats incl. validation; surfac | +| t151-deep-2 | deep | 5 | 5 | 5 | 5 | 4 | **24** | d1 core.js:43-51 count/lastUpdated/sha256; cli.js:31-39; server.js:38-48; 28/28 pass · d2 cli.test.js AC-5 stderr =='error: stats takes no parameters' exit!=0; server.test.js AC-5 400 {error:same literal}; AC-2 both deepEqual core.getStats() · d3 validation+ha | +| t151-deep-3 | deep | 5 | 5 | 5 | 4 | 4 | **23** | d1 cli.js:31-42 + server.js:38-49 emit count/lastUpdated/checksum; suite 27/27 green. d2 core.js:42-45 one validation; cli.test.js:106-113 & server.test.js:104-110 assert identical msg (exit1/400); server.test.js:90-96 CLI==HTTP checksum. d3 core.js:42-48 sole | +| t151-none-1 | none | 5 | 5 | 5 | 5 | 4 | **24** | d1: cli.js:34-36 + server.js:42 emit count/lastUpdated/checksum; 30/30 pass. d2: validation only in core.js:49-53; CLI exit1+stderr cli.js:38-41, HTTP 400 {error} server.js:43-45; asserted cli.test.js:59-64, server.test.js:82-88 (87 cross-checks core msg). d3: | +| t151-none-2 | none | 5 | 5 | 5 | 5 | 3 | **23** | d1: count/lastUpdated/checksum on CLI cli.js:46-48 and HTTP server.js:44; 38/38 green. d2: both surfaces call core.getStats, same message; server.test.js:104-117 asserts CLI stderr == HTTP error body, both non-success. d3: all logic in core.js:40-52; adapters | +| t151-none-3 | none | 5 | 4 | 5 | 5 | 4 | **23** | d1 cli.js:52-54 + server.js:44 emit count/lastUpdated/checksum; 36/36 pass · d2 core.js:41-45 sole validation; cli.test.js:66-69 & server.test.js:85-88 assert same literal msg; no direct cross-test; cli.js:22 rejects extras, HTTP ignores · d3 core.js:38-47 sin | +| t151-standard-1 | standard | 5 | 5 | 5 | 5 | 4 | **24** | d1 cli.js:33-36 + server.js:38-45 emit count/lastUpdated/checksum; suite 26/26 green · d2 one check core.js:45-47; cli.test.js:82-84 exit1+'error: stats takes no arguments', server.test.js:79-80 400 {error: same msg} · d3 core.js:44-50 single impl; surfaces on | +| t151-standard-2 | standard | 5 | 5 | 5 | 5 | 4 | **24** | d1 cli.js:35-53 + server.js:38-49 emit count/lastUpdated/checksum; suite 31/31 green · d2 core throws (core.js:47-48); CLI exit1 'error: msg' / HTTP 400 {error} matches POST envelope; parity test server.test.js:112-128, same literal cli.test.js:108 & server.te | +| t151-standard-3 | standard | 5 | 5 | 5 | 4 | 4 | **23** | d1 core.js:44-50 getStats; cli.js:31-42; server.js:38-45; 27/27 green. d2 one core message, cli.test.js:85-90 exit1+exact stderr, server.test.js:83-91 400+exact body. d3 count/lastUpdated/sha256 only in core.js:44-50, surfaces format only. d4 count hardcoded ( | + +## Mutation lens + +| workspace | arm | d1 | d2 | d3 | d4 | d5 | total | key evidence | +|---|---|---|---|---|---|---|---|---| +| t151-deep-1 | deep | 5 | 5 | 5 | 5 | 5 | **25** | d1: core.js:44-51 count/lastUpdated/checksum; cli.js:31-42, server.js:38-45; 26/26 green. d2: one msg core.js:47; asserted stats-cli.test.js:81 + stats-server.test.js:67, exit1/400. d3: getStats only in core.js:44; surfaces relay. d4: M1 3 fails(AC-1x2,AC-5), | +| t151-deep-2 | deep | 5 | 5 | 5 | 5 | 4 | **24** | d1: core.js:43-51 count/lastUpdated/checksum; cli.js:31-39; server.js:38-48; suite 28/28 green. d2: same 'stats takes no parameters' from core.js:45; cli.test.js:105 + server.test.js:97-102 assert exact msg/code. d3: getStats only in core; cli passes rest (cli | +| t151-deep-3 | deep | 5 | 5 | 5 | 5 | 4 | **24** | d1: core.js:42-48 getStats; cli.js:31-42; server.js:38-49; 27/27 green · d2: both call core.getStats(extra); cli.test.js:109 & server.test.js:108 pin same msg; server.test.js:92-98 asserts CLI==HTTP checksum · d3: sha256+count+lastUpdated only in core.js:46-47 | +| t151-none-1 | none | 5 | 4 | 5 | 5 | 4 | **23** | d1 core.js:47-56 getStats; cli.js:31-42; server.js:38-47; 30/30 pass. d2 cli.js:39-40 exit1+error:, server.js:44 400 {error}; cli.test.js:63 + server.test.js:86-87 pin core msg, no single cross-surface test. d3 validation+hash only in core.js:49-54. d4 cccc (M | +| t151-none-2 | none | 5 | 5 | 5 | 5 | 5 | **25** | d1: core.js:40-52 count/lastUpdated/checksum; cli.js:31-54, server.js:38-49; 38/38 green · d2: tests/server.test.js:104-117 asserts CLI stderr msg == HTTP error body, both non-success; cli.js:37 maps missing value to '' to match ?algorithm= · d3: getStats only | +| t151-none-3 | none | 5 | 5 | 5 | 5 | 4 | **24** | d1: core.js:39-48 count/lastUpdated/checksum; cli.js:49-60 + server.js:38-49 expose both; 36/36 green · d2: one core error msg; cli.test.js:67-70 and server.test.js:83-89 assert identical literal; cli exit1/stderr, http 400 {error} · d3: getStats only in core. | +| t151-standard-1 | standard | 5 | 5 | 5 | 5 | 4 | **24** | d1 core.js:44-50 stats{count,lastUpdated,checksum}; cli.js:31-42; server.js:38-45; suite 26/26 green · d2 both call core.stats(raw input); cli.test.js:82-87 + server.test.js:77-81 assert same 'stats takes no arguments' (exit1/400) · d3 core.js:45-49 single com | +| t151-standard-2 | standard | 5 | 5 | 5 | 5 | 4 | **24** | d1: core.js:46-54 count/lastUpdated/checksum; cli.js:35-53; server.js:38-49; 31/31 green · d2: cli.js:50-51 exit1 'error: msg' vs server.js:46 400 {error}; AC-4 tests cli.test.js:105-110, server.test.js:102-106 same literal; parity test server.test.js:108-125 | +| t151-standard-3 | standard | 5 | 5 | 5 | 5 | 5 | **25** | d1: core.js:45-51 count/lastUpdated/checksum; cli.js:31-42; server.js:38-45; 27/27 green. d2: both call core.getStats(unexpected) core.js:46-47; cli.test.js:87-89 exit1+msg, server.test.js:86-89 400+same msg. d3: sha256+count only in core.js:49-50, surfaces fo | + +## Mutation matrix + +| workspace | arm | D1 count | D2 last-updated | D3 checksum | D4 error path | +|---|---|---|---|---|---| +| t151-deep-1 | deep | caught | caught | caught | caught | +| t151-deep-2 | deep | caught | caught | caught | caught | +| t151-deep-3 | deep | caught | caught | **survived** | caught | +| t151-none-1 | none | caught | caught | caught | caught | +| t151-none-2 | none | caught | caught | caught | caught | +| t151-none-3 | none | caught | caught | caught | caught | +| t151-standard-1 | standard | caught | caught | caught | caught | +| t151-standard-2 | standard | caught | caught | caught | caught | +| t151-standard-3 | standard | caught | caught | **survived** | caught | + +## Judge notes (what the score hides) + +- `t151-deep-1` (deep, inspection): lastUpdated variant "set once, never updated on later adds" is not caught (no two-add change test); CLI prints 'never' vs HTTP null (presentation only). Version-source change is out-of-scope drift. +- `t151-deep-1` (deep, mutation): core.getStats(extra) accepts argv array or query object (core.js:45), so core knows surface input shapes; parity is asserted per-surface with the same literal, not by one cross-surface test. +- `t151-deep-2` (deep, inspection): No single test cross-compares CLI vs HTTP, but both assert the identical error literal. A "set lastUpdated only if null" mutation would survive (clear() resets null). /stats body is unwrapped unlike /version,/records. +- `t151-deep-2` (deep, mutation): Checksum-drops-a-record defect caught only by core.test.js AC-6/AC-7; surface AC-4 tests pass under it. Variants tried: lastUpdated const vs load-frozen, HTTP 200/500, CLI exit 0 - all caught. +- `t151-deep-3` (deep, inspection): Checksum tests blind to a slice(1) mutation (drop-first survives; drop-last caught). core.test.js:92-97 greps surface source text for 'crypto|createHash' — brittle. VERSION bumped to 1.1.0. +- `t151-deep-3` (deep, mutation): D3 survives only when the FIRST record is dropped (slice(1)); dropping the last (slice(0,-1)) is caught by AC-6. CLI-side D4 variant (return 0) also caught. run-1.json/run.stderr are untracked harness leftovers. +- `t151-none-1` (none, inspection): No test directly compares CLI vs HTTP output; parity is asserted via identical core-message regex on each surface. server.test.js:61 deepStrictEqual vs core.getStats() is self-referential (would not catch core defects alone). +- `t151-none-1` (none, mutation): M2 caught only by timestamp-range assert (core.test.js:61), M3 only by core.test.js:68; surface tests alone would not catch them. Algorithm-selection option is beyond the ask but tested and documented. +- `t151-none-2` (none, inspection): Strong implementation and tests; d5 docked for scope creep (algorithm selection nobody asked for) rather than any correctness issue. CLI rejects unknown options, HTTP ignores unknown query params (minor asymmetry). +- `t151-none-2` (none, mutation): D2 caught by only one core test (timestamp range check); no surface-level lastUpdated-freshness test. D3 caught in core only. Cross-surface checksum parity test (server.test.js:119) compares empty stores only. +- `t151-none-3` (none, inspection): Checksum never checked vs known digest; hash-of-count stub would pass. Algorithm selector is invented but supplies the only validation path the rubric demands. +- `t151-none-3` (none, mutation): D2 and D3 each caught by a single test. --algorithm/?algorithm= selector + exported CHECKSUM_ALGORITHMS (unused outside core) is API surface beyond the ask; CLI argv errors have no HTTP analogue. +- `t151-standard-1` (standard, inspection): No test compares CLI vs HTTP directly, but both assert the identical core message/code. d5 -1: core.stats(input) accepts raw argv/query to validate "no args" — core aware of surface shapes. +- `t151-standard-1` (standard, mutation): D2/D3 caught only by core tests; surface tests compare against core.stats() so a core-level defect would pass them. core.stats(input) accepts array-or-object raw surface input, mild surface-shape leak into core. +- `t151-standard-2` (standard, inspection): Error-path parity is locked by two independent identical literal assertions, not one cross-surface test. Algorithm selection is scope beyond the task but well-tested. +- `t151-standard-2` (standard, mutation): Every mutation caught in core AND on each surface (M1/M2: 3 tests each). Parity test covers success output only; error parity is asserted via matching literals in the two AC-4 tests, not one test. +- `t151-standard-3` (standard, inspection): Checksum-ignores-one-record would survive: surface tests use core.getStats() as oracle and core test only checks determinism/change, never an independent sha256 value. Off-by-one, frozen timestamp, error-code divergence all caught. +- `t151-standard-3` (standard, mutation): M2 caught only by 1s-tolerance check; a frozen-at-module-load variant survives. Checksum never pinned/compared per-record, so any slice() mutant survives. CLI/HTTP parity asserted per surface, not in one test. diff --git a/evals/results.md b/evals/results.md index ee92993..151f0d0 100644 --- a/evals/results.md +++ b/evals/results.md @@ -136,3 +136,75 @@ is parked in the harness ROADMAP. (not 4.4×) at the same near-parity quality, and 3/3 runs produced the durable spine (JOURNAL, closeout telemetry) whose value T1b prices. The keep/slim/pivot decision now has the median lane's number. +2026-09-04 · none-51 · t151-none-1 · audit: n/a (n/a non-OK) · suite: green · commits: 4 (redgreen: 0) · tokens: 598345 · cost: $1.49 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · none-51 · t151-none-2 · audit: n/a (n/a non-OK) · suite: green · commits: 4 (redgreen: 0) · tokens: 514855 · cost: $1.43 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · standard-51 · t151-standard-1 · audit: INCOMPLETE (n/a non-OK) · suite: green · commits: 6 (redgreen: 0) · tokens: 1254448 · cost: $3.15 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · none-51 · t151-none-3 · audit: n/a (n/a non-OK) · suite: green · commits: 3 (redgreen: 0) · tokens: 495900 · cost: $1.32 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · deep-51 · t151-deep-1 · audit: PASS (0 non-OK) · suite: green · commits: 14 (redgreen: 6) · tokens: 3064562 · cost: $5.63 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · standard-51 · t151-standard-2 · audit: INCOMPLETE (n/a non-OK) · suite: green · commits: 7 (redgreen: 0) · tokens: 1177874 · cost: $3.68 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · standard-51 · t151-standard-3 · audit: INCOMPLETE (n/a non-OK) · suite: green · commits: 6 (redgreen: 0) · tokens: 1189916 · cost: $3.15 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · deep-51 · t151-deep-2 · audit: PASS (0 non-OK) · suite: green · commits: 13 (redgreen: 4) · tokens: 4081585 · cost: $6.43 · interventions: n/a · model: claude-fable-5-1 +2026-09-04 · deep-51 · t151-deep-3 · audit: PASS (0 non-OK) · suite: green · commits: 10 (redgreen: 3) · tokens: 2905503 · cost: $5.84 · interventions: n/a · model: claude-fable-5-1 + +## T1-5.1 campaign conclusion (2026-09-04) — three arms re-baselined on claude-fable-5-1 + +Context: the first Fable 5.1 review (harness D-027) found every row above stamped +`claude-fable-5` while sessions had moved to 5.1 — under D-015 the 2.1×/4.4×/parity numbers +described a tier nobody was running. This campaign re-measured all three arms on 5.1, with two +protocol changes: the fixture enables `closeout-evaluator: on` (so the opt-in evaluator's cost +lands in the plugin rows instead of being priced at zero), and quality is scored by the +persisted rubric (`evals/rubric.md`) into a scoring file — `evals/quality/2026-09-04-fable51-t151.md`; +the same rubric was applied retroactively to the verified fable-5 workspaces in +`evals/quality/2026-09-04-fable5-verified.md`, which supersedes the transcript-only quality +totals quoted in the two conclusions above. Plugin under test: 0.19.0 (= 0.18.1 + fixture key). +Arms ran in parallel (one process per arm, three sequential runs each), settings-only isolation +via the D-025 wrapper. n=3 per arm, one fixture, one task, one model. + +**Cost / tokens / time (means, ranges):** + +| arm | tokens | cost | wall-clock | vs bare (cost / tokens) | +|---|---|---|---|---| +| bare (`none-51`) | 0.54M (0.50–0.60) | $1.41 ($1.32–$1.49) | 2.3 min | 1× | +| Standard, triage-routed, evaluator ON (`standard-51`) | 1.21M (1.18–1.25) | $3.33 ($3.15–$3.68) | 6.2 min | **2.4× / 2.3×** | +| Deep, evaluator ON (`deep-51`) | 3.35M (2.91–4.08) | $5.97 ($5.63–$6.43) | 11.4 min | **4.2× / 6.2×** | + +No range overlap between arms. Against the fable-5 rows: absolute costs fell in every arm (bare +$2.01→$1.41, Standard $4.18→$3.33, Deep $8.88→$5.97 — 5.1 prices cache reads lower and every +Deep run finished in ONE leg where fable-5 needed several), while the ratios held (Standard +2.1×→2.4× now *including* the evaluator dispatch; Deep 4.4×→4.2×). The per-token bloat argument +is weaker on 5.1; the ratio structure is unchanged. + +**Routing:** 3/3 triage-routed runs → Standard (4–5 signals, hard:no, risk:no), all completing +the Standard contract unprompted (JOURNAL, TDD, test-presence audit, closeout line). 3/3 Deep +runs: audit PASS, one leg each, 1 subagent (the evaluator). + +**Quality (rubric v1, two lenses × 3 judges per arm, `/25` per workspace):** Deep 24.0 · +Standard 24.0 · bare 23.7 (campaign /75: 72 / 72 / 71). On the same rubric the verified fable-5 +workspaces score Deep 22.7 · Standard 23.2 · bare 22.5 (68 / 70 / 68). **Parity across arms holds +on both tiers** — process weight still buys no measurable single-shot quality — and the whole +band moved up ~1.3 points on 5.1. Mutation matrix: 5.1 suites caught 34/36 injected defects +(the two survivors are both "checksum ignores the FIRST record", Deep-3 and Standard-3); +fable-5 suites caught 31/36 (5 checksum survivors + 1 frozen-timestamp). The checksum defect is +the fixture task's blind spot at any tier. + +**Evaluator (first campaign with it priced and on):** dispatched 6/6 plugin runs; `stands` 3, +`challenged` 3 — Standard-2 (1 finding: no test asserted `lastUpdated` advances on a second add; +a first-write-only stub passed 30/30), Deep-2 (4 findings: count-only checksum mutant passed +28/28; HTTP `GET /stats?` and `/stats?&` answered 200 while the CLI refused the empty arg — +a real cross-surface parity defect; audit CLI cell hit the USAGE string, not the handler; +boundary under-declared), Deep-3 (4 findings, incl. the same `/stats?` parity defect and +degenerate timestamp brackets). Every finding was fixed and re-audited before closeout; the +closeout lines record them as `interventions: 1`. Two of six closeouts shipped a code defect +the green suite did not see — the maker≠checker failure mode D-022 named, observed on the +current tier. Cost of the dispatch is inside the rows above (Standard 2.1×→2.4× is the upper +bound of its price, confounded with the lean-loop cuts that pulled the other way). + +**What this does NOT show:** single-shot only; cold-pickup, multi-session drift and decision +durability remain unmeasured (→ T1b). Whether native `/code-review` would have produced the +same evaluator findings is untested. One fixture whose task cannot separate the arms on +quality; T1b needs a non-toy fixture. + +**Honest verdict:** on the current tier the median lane costs ~2.4× bare with an independent +checker that caught real defects in half its runs; the Deep pipeline costs ~4.2× and buys no +single-shot quality over either. LCD's single-shot case is the evaluator, not the process +weight; its multi-session case is still the open measurement.