@mutagent/evaluator 0.1.0-alpha.6 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.claude/skills/mutagent-evaluator/SKILL.md +23 -25
  2. package/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +6 -5
  3. package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +77 -14
  4. package/.claude/skills/mutagent-evaluator/assets/templates/discover-report.template.html +397 -0
  5. package/.claude/skills/mutagent-evaluator/assets/templates/eval-report.template.html +594 -0
  6. package/.claude/skills/mutagent-evaluator/assets/templates/review-report.template.html +560 -0
  7. package/.claude/skills/mutagent-evaluator/golden/judge-trajectory.prose.md +69 -0
  8. package/.claude/skills/mutagent-evaluator/references/data-registry.md +43 -0
  9. package/.claude/skills/mutagent-evaluator/references/eval-layers.md +52 -0
  10. package/.claude/skills/mutagent-evaluator/references/eval-stage.md +1 -1
  11. package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +254 -9
  12. package/.claude/skills/mutagent-evaluator/scripts/audience.ts +84 -0
  13. package/.claude/skills/mutagent-evaluator/scripts/code-eval-library.ts +201 -0
  14. package/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +101 -39
  15. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +287 -3
  16. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +30 -4
  17. package/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +130 -68
  18. package/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +219 -1
  19. package/.claude/skills/mutagent-evaluator/scripts/merge-criteria.ts +849 -0
  20. package/.claude/skills/mutagent-evaluator/scripts/render-discover-report-v3.ts +1155 -0
  21. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report-v3.ts +610 -0
  22. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +2 -1
  23. package/.claude/skills/mutagent-evaluator/scripts/render-review-report-v3.ts +691 -0
  24. package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +10 -7
  25. package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +46 -1
  26. package/.claude/skills/mutagent-evaluator/scripts/run-review.ts +266 -0
  27. package/.claude/skills/mutagent-evaluator/scripts/verify-render.ts +728 -0
  28. package/package.json +2 -2
@@ -1,24 +1,19 @@
1
1
  ---
2
2
  name: mutagent-evaluator
3
3
  description: |
4
- Evaluation-development engine for AI agents and skills. Turns a subject (a skill/agent +
5
- its traces) into a TRUSTWORTHY eval suite: deep-reads traces to determine success/failure
6
- (even when nothing is user-marked), mines emergent BINARY ACTIONABLE criteria from the
7
- ✓/✗ split, builds one critique-before-verdict LLM-judge (or code-check) per criterion,
8
- validates each judge against human labels (TPR/TNR · Rogan-Gladen · bootstrap CI), then
9
- runs the suite against a target per-criterion binary+confidence severity-gated GATE
10
- verdict + agent-variance view. A judge is ONLY a judge: failures are flagged and ROUTED to
11
- diagnostics, never fixed here (EV-051). First invocation auto-detects the subject + the
12
- framework-substrate choice (DEFAULT agent-dispatch host-runtime subagents, no provider ·
13
- in-house AI-SDK judge optional · code-based · user's framework export).
14
- Subsequent invocations dispatch the parent session + MASS-PARALLEL evaluator
15
- sub-agents (one cell, discover / judge modes; host runtime, harness-capped) against the subject's traces.
16
- DEFAULT INTENT: any "evaluate this skill/agent" request runs *evaluate — the JUDGE — which is what an
17
- end user wants. *audit is a SEPARATE, explicit-only 4-tab static-auditor (eval-matrix · data-leak ·
18
- variance · methodology) used mainly for complex agents/skills that emit HTML artifacts; it is NEVER the
19
- fallback for a plain evaluate. Pinned judge model + temp=0
20
- for byte-identical reruns (C-PIN). Usable standalone OR borrowed by mutagent-skill-builder's
21
- SIMULATE phase as the SimVerifier.
4
+ The EVALUATE stage of the agentic development lifecycle (ADL ③). Builds and runs
5
+ trustworthy evaluations for AI agents and skills: from a subject's real traces it
6
+ learns what success and failure look like, turns that into binary eval criteria,
7
+ and scores the subject to a clear gate verdict. It only judges failures are
8
+ flagged and routed to mutagent-diagnostics, never fixed here. Pick it whenever the
9
+ ask is to evaluate, judge, score, or build and validate evals for an agent or
10
+ skill: any plain "evaluate this" runs *evaluate one judge per trajectory walks
11
+ the evidence layers (code checks outcome trajectory tool outputs → context),
12
+ criteria binding evidence across all of them; *evaluate-<layer> scopes the same
13
+ pipeline to one layer; *discover mines new criteria, never re-recommending one it
14
+ already has; *audit is a separate, explicit-only static auditor and never the
15
+ fallback for a plain evaluate. Also serves as the verifier inside
16
+ mutagent-skill-builder's SIMULATE.
22
17
  license: Proprietary. LICENSE.txt has complete terms.
23
18
  compatibility: Designed for Claude Code, Codex, Cursor, OpenCode and similar coding-agent runtimes; works with git, gh CLI, jq, curl, and Bun/pnpm/npm runtimes.
24
19
  metadata:
@@ -102,11 +97,12 @@ reason for the rest. NEVER improvise.
102
97
  |---------|------|-------------------|---------|
103
98
  | `*discover-evals` | hybrid | `references/workflows/orchestrator-protocol.md` + `scripts/prep-tasks.ts` + `scripts/determine-outcome.ts` + `scripts/discover-criteria.ts` + `assets/agents/evaluator.md` (`#mode-discover`) | PREP determiner tasks → fan out `evaluator` (`#mode-discover`, mass-parallel) → AGGREGATE ✓/✗ → mine emergent BINARY ACTIONABLE criteria (EV-041/042/052) **+ T6 failure/uncertain DATASET CANDIDATES** (`collectDatasetCandidates`, reuses the derive-dataset selectors → `*build-dataset`). **GA split** — **code:** sample (broken+healthy) · aggregate · `diff-discriminate` · ground-gate · dataset-candidates; **LLM leaf:** determine + 3 detectors + cite refs + typed assumptions (root-not-symptom). |
104
99
  | `*build-evals` | hybrid | `references/workflows/orchestrator-protocol.md` + `scripts/eval-engine.ts` + `scripts/codegen-evals.ts` + `scripts/prep-tasks.ts` + `scripts/build-evals.ts` + `assets/agents/evaluator.md` (`#mode-judge-criterion`) + `scripts/render-build-cards.ts` | **ENGINE-FORK FIRST (ADL F7/F9/F14).** ASKS the eval implementation mode via `chooseEvalEngineOptions(target)` — Path A `native-matrix` (eval-matrix + LLM-judge SUB-AGENTS; SURFACES the Claude-Code + log-sink dependency up front) vs Path B `code-written` (`codegen-evals.ts` emits a portable bun/TS suite — runs WITHOUT Claude Code, F14). Target-conditional: a code framework offers BOTH, a `harness:*` target is native-only. **Path A** then PREPs judge tasks (criterion × trace-slice) → fan out `evaluator` (`#mode-judge-criterion`, mass-parallel) → one binary+confidence judge per criterion (EV-043). **GA split** — **code:** engine-resolve · spec render · `lint-grounding` · `resolve-ref` (BIND) · codegen; **LLM leaf:** judge + VERIFY (cite refs · note assumptions · abstain). Streams progress + a verbose evals entity card (F13/F16/F22). |
105
- | `*evaluate` | hybrid | `references/workflows/orchestrator-protocol.md` + `scripts/matrix-judge.ts` + `scripts/contracts/eval-matrix.ts` + `assets/agents/evaluator.md` (`#mode-judge-trajectory`) + `scripts/evaluate.ts` | **DEFAULT (headline):** **T1 TIER-0 deterministic pre-pass** (code-method rows run first, zero judge tokens) → PREP one matrix packet per RESIDUAL judge trajectory (**T5 adaptive-K guard, default 1:1**) → fan out `evaluator` (`#mode-judge-trajectory` — one judge/trajectory scoring the WHOLE matrix, emitting the **T2 Judge DAG v2 walk** = `judge_steps[]` + dense na-explicit map + confidence band + early-INCOMPLETE + node-2.5 candidates) → AGGREGATE: fold code+judge verdicts → **T3 independent verifier** refutes GATING fails (downgrade-only) → **T4 consolidate-by-locus + walk-derived health** → GATE verdict + variance view (EV-048). **GA split** — **code:** tier-0 · prep · `lint-grounding` · `resolve-ref` · `assemble-scorecard` (gate `fail ▸ incomplete ▸ pass`) · consolidate-by-locus · route-failures; **LLM leaf:** trajectory judge (DAG v2 walk: BIND · GROUND[absence-split] · cite · abstain) + independent VERIFY. **MAY now return `incomplete`** (indeterminate → calibrate). **ADL F20:** the rollup ALSO renders a SCORECARD DASHBOARD wireframe (`renderScorecardDashboard` — per-criterion pass/fail bar + variance + samples), not a flat terminal dump. Under a **Path B** engine, `*evaluate` instead runs the portable `codegen-evals.ts` suite (no Claude Code) and reads back its scorecard JSON from the discoverable sink. |
100
+ | `*evaluate` | hybrid | `references/workflows/orchestrator-protocol.md` + `scripts/matrix-judge.ts` + `scripts/contracts/eval-matrix.ts` + `assets/agents/evaluator.md` (`#mode-judge-trajectory`) + `scripts/evaluate.ts` | **DEFAULT (headline):** **T1 TIER-0 deterministic pre-pass** (code-method rows run first, zero judge tokens) → PREP one matrix packet per RESIDUAL judge trajectory (**T5 adaptive-K guard, default 1:1**) → fan out `evaluator` (`#mode-judge-trajectory` — one judge/trajectory scoring the WHOLE matrix, emitting the **T2 Judge DAG v2 walk** = `judge_steps[]` + dense na-explicit map + confidence band + early-INCOMPLETE + node-2.5 candidates) → AGGREGATE: fold code+judge verdicts → **T3 independent verifier** refutes GATING fails (downgrade-only) → **T4 consolidate-by-locus + walk-derived health** → GATE verdict + variance view (EV-048). **GA split** — **code:** tier-0 · prep · `lint-grounding` · `resolve-ref` · `assemble-scorecard` (gate `fail ▸ incomplete ▸ pass`) · consolidate-by-locus · route-failures; **LLM leaf:** trajectory judge (DAG v2 walk: BIND · GROUND[absence-split] · cite · abstain) + independent VERIFY. **MAY now return `incomplete`** (indeterminate → calibrate). **ADL F20:** the rollup ALSO renders a SCORECARD DASHBOARD wireframe (`renderScorecardDashboard` — per-criterion pass/fail bar + variance + samples), not a flat terminal dump. Under a **Path B** engine, `*evaluate` instead runs the portable `codegen-evals.ts` suite (no Claude Code) and reads back its scorecard JSON from the discoverable sink. **v3 LAYERED WALK (D1=B+):** the trajectory judge performs the whole evidence-layer sequence INSIDE its one dispatch — L0 code checks → L1 outcome (scenario + expectedExit, cheap, first) → per stopping rules L2 trajectory → L3 tool outputs → L4 context — emitting `layerVerdicts[]` + `earlyExit` + `codeEvalHits[]` + an emissions self-manifest (E-NEW contract, `contracts/eval-matrix.ts`); AGGREGATE folds layers (`foldLayerVerdicts`), DETECTS cross-layer conflicts (`detectLayerConflicts` — surfaced for the human calibration queue, NEVER auto-resolved, D2-A/OT-1), enforces the COMPLETENESS LAW (`assertNoUnverdictedCriterion` THROWS on a silent skip), and folds MR-5 data-completeness (`foldEmissionsScorecard`). |
101
+ | `*evaluate-outcome` · `*evaluate-trajectory` · `*evaluate-outputs` · `*evaluate-context` · `*evaluate-code` · `*evaluate-criteria` | hybrid | the SAME binds as `*evaluate` | **v3 PIPEABLE LAYER FILTERS (D3=A):** each runs the identical `*evaluate` pipeline with `layerScope` set in every packet (`outcome`→`[L1]` · `trajectory`→`[L2]` · `outputs`→`[L3]` · `context`→`[L4]` · `code`→`[L0]`); the judge runs ONLY those walk phases — **criteria are the SUPER-LAYER and are still verdicted over whatever those phases gather (never left unverdicted — the completeness law holds per scope)**. `*evaluate-criteria` = the criterion-axis pass (`#mode-judge-criterion`, single criterion × slice — the suite-build/validation path). Standalone-testable AND composable: a standalone layer verdict must equal the same layer's verdict inside the full walk (V9). |
106
102
  | `*optimize` | hybrid | `references/edd-loop.md` + `scripts/edd/variance-gate.ts` + `scripts/edd/change-request.ts` + `scripts/edd/edd-types.ts` + `schemas/edd-change-request.schema.yaml` + `assets/agents/evaluator.md` (`#mode-optimize`) | **ADL ③ OPTIMIZE / EDD loop (F18+F19).** **F19 VARIANCE-FIRST:** repeat-N (default 5) the SAME cases → `evaluateVarianceGate` → accuracy is entered ONLY when the variance gate passes (`assertVarianceStableBeforeAccuracy` THROWS otherwise — "accuracy over big samples is wasted on a flapping verdict"). **F18 CLOSURE:** still JUDGE-ONLY (EV-051), the evaluator emits a grounded `EddChangeRequest` (failing cases + `ref{obs,path,value}` + remedy target `agentspec`\|`impl`) to the `mutagent-builder ai-engineer` over **SendMessage**, consumes the `ChangeRequestResponse`, and re-evals on `amended`. **Bounded terminator** (`decideEddLoop`, afkloop-legal): `full-green` ▸ DONE \| `max-swings`\|`max-wallclock`\|`no-improvement-streak` ▸ STOPPED + convergence delta. **GA split** — **code:** variance gate · request/response validate · loop terminator (all PURE, injected wall-clock); **LLM leaf:** localize the flap/fail to its root + author the grounded request + decide remedy target. |
107
103
  | `*validate` | hybrid | `scripts/validate-judge.ts` + `references/validate-evaluator.md` + `assets/agents/evaluator.md` (`#mode-judge-criterion`) | **ENGINED (EV-044)** — calibrate a judge vs `*review` labels: confusion matrix → TPR/TNR · split-disjointness + test-once · Rogan-Gladen θ · deterministic bootstrap CI; `<MIN_LABELS` stays `unvalidated`+bias-corrected. **GA split** — **code:** entire path is deterministic (filter EXCLUDES indeterminate · confusion · RG · assumption-agreement). |
108
- | `*review` | hybrid | `scripts/build-review-ui.ts` + `references/build-review-interface.md` | **ENGINED (EV-045)** — CODE renders a browser annotation UI (one trace/screen · Pass/Fail/Defer · keyboard · auto-save · labels export); **HITL**: a human labels `mergeLabels` persists feeds `*validate`. |
109
- | `*eval` | agent-chain | `references/eval-stage.md` + `scripts/eval-engine.ts` + `scripts/materialize-dataset.ts` + `scripts/render-build-cards.ts` | **THE ADL EVAL-STAGE ENTRY (F15).** Called after `*build` hands a freshly-built agent + its agentspec. INTERACTIVELY offers `*build-dataset` / `*build-evals` derived from `agentspec.definition.evals`, and lets the user PICK THE EVAL ENGINE (mutagent native eval-matrix [Path A] vs code-written-in-target-lang [Path B]) via the target-conditional menu (`chooseEvalEngineOptions`). Surfaces Path A's Claude-Code + log-sink dependency UP FRONT (F7/F9). Streams wireframe progress + entity cards (F13/F16/F22). Parent-session only (AskUserQuestion). |
104
+ | `*review` | hybrid | `scripts/run-review.ts` (run composer) + `scripts/render-review-report-v3.ts` (surface) + `scripts/build-review-ui.ts` (v2 fallback) + `references/build-review-interface.md` | **ENGINED (EV-045)** — CODE renders a browser review surface; **HITL**: a human labels → `mergeLabels` persists → feeds `*validate`. **Entry point:** `bun scripts/run-review.ts <runId> [--cwd <repoRoot>]` — reads an EXISTING `*evaluate` run dir (`run-input.full.json` + `verdicts/*.verdict.json`; joins on the parsed `trajectoryId`, never the packet filename) and writes `review-report.html` beside `evaluation-report.html`, plus `review-report.v2.html` from the previous annotation UI (module untouched). Dispatches NO judge — it costs nothing and invents no artifacts. **Surface:** navigator matrix (criteria rows × trace columns) → single-trace drill, right-rail verdict cards with Agree/Revise/Refute + why-note, keys `1`/`2`/`D`, >48 traces → window-cluster, submit **calibration handoff markdown** (diagnostics remedy-bundle shape) server-generated from the run's own artifacts. Ingested-but-unjudged trajectories are NAMED in a scope note, never dropped. **Does NOT do W4:** no feedback store, no ruling persistence, no propagation into `*validate` — producing the artifact is the scope; consuming a reviewer's answer is task #9. |
105
+ | `*eval` | agent-chain | `references/eval-stage.md` + `scripts/eval-engine.ts` + `scripts/materialize-dataset.ts` + `scripts/render-build-cards.ts` | **THE ADL EVAL-STAGE ENTRY (F15).** Called after `*build` hands a freshly-built agent + its agentspec. INTERACTIVELY offers `*build-dataset` / `*build-evals` derived from `agentspec.spec.evaluation`, and lets the user PICK THE EVAL ENGINE (mutagent native eval-matrix [Path A] vs code-written-in-target-lang [Path B]) via the target-conditional menu (`chooseEvalEngineOptions`). Surfaces Path A's Claude-Code + log-sink dependency UP FRONT (F7/F9). Streams wireframe progress + entity cards (F13/F16/F22). Parent-session only (AskUserQuestion). |
110
106
  | `*build-dataset` | hybrid | `scripts/materialize-dataset.ts` + `scripts/build-dataset.ts` + `assets/agents/dataset-builder.md` + `schemas/dataset.schema.yaml` + `references/generate-synthetic-data.md` + `scripts/render-build-cards.ts` | **ENGINED (EV-046) + ADL F8.** Now MATERIALIZES real items first: `materializeFromAgentspec` seeds ≥1 REAL DatasetCase per `dataset_category` + one per `edge_case` (seed → actual items, not definitions) → then the 3-way: HITL seed interview (~10) → `dataset-builder` agent (tuples → NL queries → realism filter) → CODE cartesian-expand + near-dup drop + monotonic merge. Streams wireframe progress cards (F13/F16) + a verbose dataset entity card (F22). |
111
107
  | `*discover-dataset` | script | `scripts/derive-dataset.ts` | **ENGINED (EV-047) · re-fronts the old `*derive-dataset`** — distill a living regression set from labeled ✓/✗ traces (reuses `sample-traces.ts` EV-052 selectors + `build-dataset.ts` merge). Code-only. **P-B:** consumes the `discovery` agent's curated `SelectionManifest` handoff — **scenario-balanced** (`ext.classification.scenario`), **edge-cases first** (`ext.classification.edgeCase.is`), worthiness-prioritized (`ext.signals.worthinessScore`). |
112
108
  | `*audit` | agent-chain | `assets/agents/audit-executor.md` + `workflows/{audit,data-leak,variance}.workflow.js` + `scripts/{flow-graph,ui-slots}.ts` + `lenses/context-flow-lens.md` | SURFACES the v1 4-tab static-auditor (EV-001..027, KEEP) **+ now the agent context-flow audit**: tool-result threading + sub-agent handoff completeness (EV-028/029, over the `flow-graph` EV-032 + expected-flow EV-037) and the first-class **HTML-artifact missing-data** dimension (computed-but-not-rendered / orphan / faithfulness, EV-039/040, subject-agnostic). |
@@ -164,8 +160,8 @@ evaluate spine and never an automatic fallback.
164
160
 
165
161
  **ADL EVAL-stage entry (`*eval`).** When `*build` hands over a freshly-built agent +
166
162
  its agentspec, `*eval` is the entry: it interactively offers `*build-dataset` /
167
- `*build-evals` derived from `agentspec.definition.evals` (F15), MATERIALIZES real
168
- dataset items per category + edge-case (F8), lets the user PICK THE EVAL ENGINE
163
+ `*build-evals` derived from `agentspec.spec.evaluation` (F15), MATERIALIZES real
164
+ dataset cases by projecting the 0.3.0 `datasets[].items[]` (F8), lets the user PICK THE EVAL ENGINE
169
165
  (Path A native eval-matrix vs Path B code-written — surfacing Path A's Claude-Code +
170
166
  log-sink dependency up front, F7/F9/F14), streams wireframe progress + entity cards
171
167
  (F13/F16/F22), and renders the scorecard as a dashboard (F20). See
@@ -287,7 +283,7 @@ applies, EV-053/EQ5):
287
283
  |--------|---------|------|---------|
288
284
  | `scripts/contracts/eval-engine.ts` | F7/F9/F14 | code | The ENGINE FORK contract — `EvalEngine` (`native-matrix` Path A · `code-written` Path B) + `EngineTargetInput` (the minimal agentspec.build slice) + `EvalEngineOption` (the menu, with `requiresClaudeCode`/`requiresLogSink`/`portable` SURFACED) + `EvalEnginePlan` (the resolved choice incl. discoverable `outputSink`). |
289
285
  | `scripts/eval-engine.ts` | F7/F9/F14 | code | The target-conditional resolver — `isFrameworkTarget` · `chooseEvalEngineOptions` (framework→BOTH · harness→native-only) · `resolveEvalEngine` (surfaces CC + log-sink deps) · `assertEngineMatchesTarget` (code-written⊥harness, fail-loud) · `defaultOutputSink`. PURE. |
290
- | `scripts/contracts/agentspec-evals.ts` | F8 | code | The MINIMAL `agentspec.definition.evals` slice (`success_criteria[]` · `scenarios[]` · `dataset_categories[]`) — local re-declaration (standalone: NEVER imports the agentspec skill). `parseAgentspecEvals` guarded. |
286
+ | `scripts/contracts/agentspec-evals.ts` | F8 | code | The MINIMAL `agentspec.spec.evaluation` slice (0.3.0 D18/D19 — `criteria[]` · `scenarios[]` · `datasets[]` with dataset-local `categories[]` + `caseDimensions` + real `items[]`) — local re-declaration (standalone: NEVER imports the agentspec skill). `parseSpecEvaluation` guarded. |
291
287
  | `scripts/materialize-dataset.ts` | F8 | code | MATERIALIZE real dataset items from the agentspec — `materializeFromAgentspec` (≥1 real item/category + one/edge_case, seed→actual) · `dimensionsFromAgentspec` · `materializeToDataset` (monotonic). Reuses `build-dataset.ts` ids/dedup/merge. PURE. |
292
288
  | `scripts/codegen-evals.ts` | F14 (Path B) | code | Emit a PORTABLE code-written eval suite source (bun/TS) — `codegenEvalSuite` (code-checks + SDK judge + criteria → gate-bearing scorecard to a discoverable sink). NO CC dispatch. Fail-loud, deterministic. PURE. |
293
289
  | `scripts/render-build-cards.ts` | F13/F16/F20/F22 | code | The wireframe terminal surface — progress cards (`renderBuildDatasetProgressCard`/`renderBuildEvalsProgressCard`, F13/F16) · entity cards (`renderDatasetEntityCard`/`renderEvalsEntityCard`, F22) · the scorecard DASHBOARD (`renderScorecardDashboard` — pass/fail bar + variance + samples, F20, not a flat dump). PURE. |
@@ -296,7 +292,9 @@ applies, EV-053/EQ5):
296
292
 
297
293
  | Script | Req | Kind | Purpose |
298
294
  |--------|-----|------|---------|
299
- | `scripts/build-review-ui.ts` | EV-045 | code | The `*review` Code half — `renderReviewUi` emits a DETERMINISTIC browser annotation UI (one trace/screen, native render, Pass/Fail/Defer, notes, keyboard, localStorage auto-save, labels export) + `mergeLabels` (dedup by traceId). Holds no judge prompt; the HUMAN decides. |
295
+ | `scripts/build-review-ui.ts` | EV-045 | code | The `*review` Code half — `renderReviewUi` emits a DETERMINISTIC browser annotation UI (one trace/screen, native render, Pass/Fail/Defer, notes, keyboard, localStorage auto-save, labels export) + `mergeLabels` (dedup by traceId). Holds no judge prompt; the HUMAN decides. Retained as the `review-report.v2.html` transition fallback under the W4 surface. |
296
+ | `scripts/run-review.ts` | EV-045 | code | **The `*review` RUN COMPOSER** — the analogue of run-evaluate's `writeRunReport`. Reads an existing run dir (run-input + `verdicts/*.verdict.json`, excluding the `*.verify.json` ledger) and writes `review-report.html` + the v2 fallback. Joins verdicts to traces on the parsed `trajectoryId`, NOT the packet filename; treats the lean run-input's `"<N EvalTraces>"` placeholder as ABSENT rather than data; NAMES ingested-but-unjudged trajectories in a scope note. Fail-loud on a missing run dir / run-input / verdicts / criteria, and a malformed verdict names its FILE. Dispatches no judge. |
297
+ | `scripts/render-review-report-v3.ts` | EV-045 | code | The W4 frozen-contract review surface (navigator + drill · rail verdict cards · rulings · calibration handoff MD) + `REVIEW_REPORT_SPEC`, the surface's verification spec, exported here so the unit test and the composer assert the same shape. |
300
298
  | `scripts/build-dataset.ts` | EV-046 | code | The `*build-dataset` Code half — cartesian dimension×value expand · deterministic token-Jaccard near-dup removal · content-derived id · `mergeCases` MONOTONIC merge. Agent proposes realism; script enforces non-redundancy. |
301
299
  | `scripts/derive-dataset.ts` | EV-047 | code | `*discover-dataset` — distill a living regression set from past ✓/✗ (reuses `sample-traces.ts` selectors + `build-dataset.ts` merge); trace→`DatasetCase`. Append-only, no new decision. |
302
300
  | `scripts/living-suite.ts` | EV-053 | code | Generic append-only writer (`appendOnly` + `assertMonotonicGrowth`) shared by datasets + criteria — a living artifact NEVER shrinks. Pure-counter provenance (no clock → C-PIN byte-identity). |
@@ -131,11 +131,12 @@ not run the subject and does not decide pass/fail.
131
131
  1. **Pre-read** `references/generate-synthetic-data.md` — the dimension → tuple → query → filter
132
132
  process is the lens. Read the passed `dimensions` + `seed_tuples`.
133
133
  - **ADL F8 — agentspec-materialized seeds.** In the ADL EVAL stage the parent ALSO PREPs real
134
- items MATERIALIZED from `agentspec.definition.evals` (`scripts/materialize-dataset.ts`): ≥1 real
135
- `DatasetCase` per `dataset_category` + one per `edge_case`. When present, treat these
136
- materialized cases as ADDITIONAL realism anchors (alongside the HITL `seed_tuples`) they are
137
- real queries from the spec, NOT definitions. EXPAND around them; do not re-derive them. The
138
- deterministic dedup/merge (`build-dataset.ts`) drops any overlap.
134
+ items PROJECTED from `agentspec.spec.evaluation` (0.3.0, `scripts/materialize-dataset.ts`): one
135
+ real `DatasetCase` per `datasets[].items[]` entry tuple = the item's `category` + `case` axis
136
+ assignment, query = the item's opaque `input` payload. When present, treat these materialized
137
+ cases as ADDITIONAL realism anchors (alongside the HITL `seed_tuples`) they are real queries
138
+ from the spec, NOT definitions. EXPAND around them; do not re-derive them. The deterministic
139
+ dedup/merge (`build-dataset.ts`) drops any overlap.
139
140
  2. **Generate more tuples (Step 3).** Produce `target_count` new `(dim1, dim2, dim3, …)` combinations
140
141
  for the subject. Avoid duplicates of the seed + each other; vary values across dimensions. This is
141
142
  ONE step — tuples only, no queries yet.
@@ -224,11 +224,15 @@ optional_verdict_fields:
224
224
  # structured refs + typed assumptions). A LIGHTWEIGHT / FLATTENED emit that drops discovery.evidence.refs
225
225
  # or discovery.assumptions is a DEFECT — the gate (lint-grounding) + diff-discriminate operate on those
226
226
  # fields; stripping them silently downgrades every criterion to inferred.
227
- required_label_fields: [traceId, verdict, rootCause, refs, evidencePointer] # discover — `rootCause` REPLACES firstThingWrong (root-not-symptom); `refs` = structured grounding
227
+ required_label_fields: [traceId, verdict, firstThingWrong, evidencePointer, refs] # discover — the EXACT keys DiscoverLabelSchema accepts (closed object); `firstThingWrong` HOLDS THE ROOT, not the first visible symptom (root_not_symptom); `refs` = structured grounding
228
228
  required_category_fields: [name, definition, class, fixOrEval, exampleTraceIds] # discover
229
229
  # optional_category_fields (P2/P2b, ADDITIVE — the leaf MAY emit these §5b metric-metadata
230
- # PROPOSALS; AGGREGATE (scripts/aggregate-discover.ts) applies GENERIC defaults when absent,
231
- # so emitting them is OPTIONAL and backward-compatible): [dimension, level, generality, severity, judgeInputs, codeEval]
230
+ # PROPOSALS; AGGREGATE (scripts/aggregate-discover.ts) HONORS what is present and applies GENERIC
231
+ # defaults ONLY where absent, so emitting them is OPTIONAL and backward-compatible):
232
+ # [dimension, level, generality, severity, judgeInputs, codeEval]
233
+ # Each is a CLOSED union (below) — a value outside it fails LOUD at parse time rather than being
234
+ # silently ignored. (Until the P2 passthrough fix the JOIN dropped all four, so the defaults applied
235
+ # unconditionally and every mined criterion read `level: output` no matter what the leaf proposed.)
232
236
  # dimension ∈ operation-correctness | data-correctness | operational-deviation
233
237
  # level ∈ context | output | cross-stage
234
238
  # generality ∈ general-structural | specific-semantic (else derived from class: code→general-structural)
@@ -310,6 +314,7 @@ commands:
310
314
  - "references/grounded-adjudication.md" # GA doctrine: bind · gather refs · typed assumptions · abstain
311
315
  - "subjects/<name>/" # the auto-generated subject profile / event taxonomy
312
316
  - "references/workflows/orchestrator-protocol.md" # Step 1 dispatch FSM
317
+ - "<run_dir>/existing-criteria.json" # SUITE-AWARENESS — the criteria that ALREADY exist (see suite_awareness)
313
318
  workflow:
314
319
  - "Pre-read references/error-analysis.md + references/grounded-adjudication.md + the subjects/<name>/ profile + your assigned <key>.task.json determiner specs"
315
320
  - "Determine outcome per trace (EV-042) — event→intended goal, trajectory→what happened, terminal→verdict; a guard-hold is a PASS"
@@ -319,14 +324,35 @@ commands:
319
324
  - "Trace each Fail to its ROOT (root-not-symptom): one criterion per root; multiple INDEPENDENT roots ⇒ multiple criteria; a causal edge must be GROUNDED (cite it) or surfaced as a typed assumption; deep recursive-why → route to diagnostics"
320
325
  - "Surface TYPED assumptions (factual-intent · normative · scope) for any premise the trace did not establish"
321
326
  - "Cluster notes into 5-10 emergent categories; tag class (objective→code/subjective→judge/hybrid) + fixOrEval"
327
+ - "NAME each cluster suite-aware (AFTER clustering, never before — see suite_awareness): if an EXISTING criterion in existing-criteria.json already states this behaviour, REUSE its exact id as the category name and attach your traces as REINFORCING evidence instead of minting a restatement under a new name"
328
+ - "Emit the §5b PROPOSALS you actually judged — level · severity · dimension · generality. AGGREGATE now HONORS them (they are no longer silently replaced by generic defaults), so an omitted field means 'derive it', not 'it does not matter'"
322
329
  - "Flag fixable-vs-eval-worthy (EV-051) — route fixable + infra-class to diagnostics; keep behavioral criteria"
323
330
  - "Write <key>.verdict.json (critique-before-verdict) + discover/<batch_id>.json (labels + FULL MinedCriterion per category, incl. discovery.evidence.refs + discovery.assumptions)"
331
+ # suite_awareness — the leaf SEES the criteria that already exist, so a behaviour
332
+ # already covered gains EVIDENCE instead of a second name for the same check.
333
+ # The parent writes `existing-criteria.json` (scripts/aggregate-discover.ts
334
+ # writeExistingSuiteBrief) into the run dir before dispatch: {id, statement,
335
+ # severity, level, checkedBy} per existing criterion — NO evidence, deliberately —
336
+ # PLUS `openQuestions[]`: pairs already awaiting a ruling on whether they are one
337
+ # check, each with the guard reason. Those carry only the pair + reason; both
338
+ # sides' statements are already in `criteria[]`, which is what keeps the whole
339
+ # brief evidence-free by construction.
340
+ suite_awareness:
341
+ - "WHEN: consult it AFTER you have clustered, to NAME a cluster. NEVER before."
342
+ - "WHY THAT ORDER IS LOAD-BEARING: reading the existing suite BEFORE the traces would seed a pre-defined failure list and re-introduce exactly the confirmation bias `emergent_categories_only` forbids — you would go hunting for the criteria you were shown. Clustering FIRST keeps the categories emergent; the suite is consulted only to decide what an already-emerged cluster should be CALLED. Emergence is preserved because the brief cannot influence what you found, only what you name it."
343
+ - "IT CARRIES NO EVIDENCE ON PURPOSE: the brief ships statements, not the traces or refs behind them, so it cannot tempt you into re-citing a failure you did not read this batch."
344
+ - "REINFORCE: if an existing criterion already states the behaviour your cluster found, emit the cluster under THAT criterion's exact id. Seeing the behaviour again is legitimate REINFORCING evidence — the parent unions your traces onto the existing criterion and the report marks it REINFORCED."
345
+ - "OPEN QUESTIONS: the brief's `openQuestions[]` lists pairs of EXISTING criteria already awaiting a human ruling on whether they are one check, each with the reason they were kept apart (e.g. `marker parity: [BEFORE] vs [AFTER]`). BOTH sides are live criteria and both statements are in `criteria[]`. If your cluster matches one side, emit it under THAT side's id — do NOT mint a third name for a behaviour that is already an open question, and do NOT try to settle the question yourself (you are not the ruling authority; that is the calibration loop's). If your cluster genuinely matches NEITHER side, mint it as new."
346
+ - "INFORMS, NEVER BLOCKS: a full-looking suite is not a reason to suppress a real finding. If the traces show a behaviour no existing criterion states, MINT IT — a genuinely new criterion is always correct, and the parent's deterministic merge is the safety net for a restatement you mint anyway."
347
+ - "NEVER FABRICATE: do not carry an existing criterion into your mining report because it is in the brief. You may only emit clusters backed by traces YOU actually read this batch — an id reused with no trace of your own behind it is an invented finding."
348
+ - "ABSENT/EMPTY brief ⇒ proceed exactly as before (a first run has no suite; that is normal, not a gap)."
324
349
  compresses:
325
350
  - "deep-read trace → goal-attainment Pass/Fail"
326
351
  - "DETECT across 3 lenses (drift · tool-output-failure · missing-context)"
327
352
  - "GATHER structured refs (claim + absence) · BIND criterion terms"
328
353
  - "root-not-symptom localization + typed-assumption surfacing"
329
354
  - "emergent category clustering"
355
+ - "suite-aware NAMING (reuse an existing id ⇒ REINFORCE, never restate)"
330
356
  - "fixable-vs-eval-worthy flag"
331
357
  - "emit FULL-MinedCriterion verdict + mining files"
332
358
  preserves: "the error-analyst discipline — folded INLINE as 'Mode: discover' (below); the standalone assets/agents/error-analyst.md was RETIRED in the 5→3 consolidation (Phase 3a, df6a6e8c8). This file is its canonical home."
@@ -403,13 +429,15 @@ commands:
403
429
  - "Pre-read references/write-judge-prompt.md (the 4-component judging contract — your lens) + references/grounded-adjudication.md"
404
430
  - "Read your assigned <trajectory_key>.packet.json (MatrixPacket: subject · trajectoryId · WHOLE matrix · trajectory · transcript · pinned envelope) — judge exactly this, never re-derive the data"
405
431
  - "Frame the trajectory in its ROUTE/intended-outcome (CONTEXT) before scoring"
406
- - "Score EVERY criterion in the matrix for THIS trajectory: read only what the row needs (judgeInputs); compare against statement + passCondition"
432
+ - "v3 LAYERED WALK (D1=B+, operator-frozen): the walk IS a fixed layer sequence you run INSIDE this one dispatch. Honor `packet.layerScope` when present (absent/empty = FULL walk). The phases: (L0 code checks) if the code-eval library is present at scripts/code-eval-library.ts, RUN it yourself via Bash over the packet's trajectory JSON and record each hit as codeEvalHits[{pattern,anchor,detail}] — a fired pattern is evidence, not a verdict; (L1 outcome) classify the SCENARIO from the inbound intent + tool inventory, derive its expectedExit[] terminal actions, glance ONLY at end-state evidence and emit layerVerdicts[{layer:'L1',verdict,scenario,expectedExit,refs,note}]; (STOPPING RULES) if L1 passes AND every criterion is answerable from evidence gathered so far → you may set earlyExit:'L1' and mark L2-L4 as {verdict:'skipped'} — but a criterion may NEVER go unverdicted because the agent did the right thing; if ANY criterion is unanswerable OR L1 fails/undecidable → descend; (L2 trajectory) actual-vs-expected decision path, read from the END, emit layerVerdicts L2 (+ divergence in localize); (L3 tool outputs) per-observation structural validity + completeness — external-data correctness is UNVERIFIABLE (fence), a failed tool makes everything downstream suspect; emit layerVerdicts L3 (+firstProblemOrigin); (L4 context) for each critical operational send: was required context retrieved AND present in history AT THAT TIME (chronology-sorted!) — distinguish gapKind retrieval (never fetched) vs attention (present, ignored); emit layerVerdicts L4. Record layersEngaged. CONFLICT DISCIPLINE (OT-1): when your own layers disagree (e.g. L1 pass vs L2 fail) emit BOTH verdicts honestly with a note — NEVER reconcile them yourself; the aggregate surfaces the conflict for a human."
433
+ - "Score EVERY criterion in the matrix for THIS trajectory: read only what the row needs (judgeInputs); compare against statement + passCondition — criteria are the SUPER-LAYER: they may bind evidence from ANY walk phase"
407
434
  - "BIND (L1) per row: each criterion TERM must resolve in THIS trajectory; an unbound term ⇒ uncertain + blockedBy:{kind:factual-intent} (INDETERMINATE), ABSTAIN — never fail"
408
- - "GATHER: cite a structured ref {obs,path,value} for the claim AND any absence; surface TYPED assumptions for any ungroundable premise → uncertain(blockedBy)"
435
+ - "GATHER: cite a structured ref {obs,path,value} for the claim AND any absence (ref.obs = the packet's trajectoryId, NEVER an observation name; ref.path = packet-shaped trajectory.N/transcript.N OR trace-shaped observations.N — dot form canonical, brackets tolerated); surface TYPED assumptions for any ungroundable premise → uncertain(blockedBy)"
409
436
  - "Critique BEFORE verdict; commit to result ∈ {pass,fail} (uncertain when the INPUTS can't decide — abstain-on-silence); binary, severity is the row's"
410
437
  - "Inaction-can-be-success: a correct HOLD (no send during a non-critical outbound_guard) is a PASS even with zero tool calls"
411
438
  - "Write <trajectory_key>.verdict.json (a MatrixVerdictFile {trajectoryId, judgeModel, temperature:0, verdicts[]} — each verdict may carry refs?/assumptions?/blockedBy?)"
412
439
  - "EMIT-CONTRACT (HARD, WS-1): on every COMPLETE-fidelity trajectory you MUST also persist the §9.4 walk — `understanding` (M2), `expectedTrajectory` (M3), `agentSteps` (the target step lane), and `judgeSteps` (your anchored reasoning). These are NOT optional: the parent runs a machine `assessEmitCompleteness` gate (scripts/emit-completeness.ts) that LOUDLY flags any wholly-dropped field, and a verdict missing them STARVES the report's Trajectory (§2) + Self-Eval (§5) tabs. `agentSteps` is factual trace data (reconstruct it from the ordered tool steps you were given — order by startTime, the observations array is reverse-chronological); M2/M3/judgeSteps are YOUR reasoning. Only an INCOMPLETE (node-1) trajectory is exempt (it emits verdicts:[] and no walk)."
440
+ - "v3 EMIT-CONTRACT ADDITIONS (E-NEW): alongside the §9.4 walk, persist layerVerdicts[] (one per engaged layer, skipped ones marked), earlyExit?, layersEngaged, codeEvalHits[] (when the library ran), and the emissions SELF-MANIFEST {emitted[], missing[{key,reason}]} — list honestly what you gathered vs what this contract expects; a missing emission with a stated reason is a NAMED degrade, a silent drop is a defect (MR-5 catches it same-run)."
413
441
  - "Verdicts are independently VERIFIED (#mode-verify · ≠ judge · downgrade-only); a CRIT/HIGH uncertain rolls the run up to INCOMPLETE at the gate (no false-green)"
414
442
  compresses:
415
443
  - "read MatrixPacket DATA; frame in ROUTE (CONTEXT)"
@@ -615,8 +643,13 @@ a new eval mid-judging. Your focus is judging what IS, not pondering what could
615
643
  - Compare the trajectory/transcript against the row's `statement` + `passCondition`.
616
644
  - **GROUND (node 4 — ABSENCE-SPLIT) — REFS ARE MANDATORY, NOT DECORATIVE:** every **decided**
617
645
  verdict (`result ∈ {pass, fail}`) MUST carry **≥1 structured `ref {obs, path, value}`** that
618
- grounds it — `obs` = the trace/observation id you read, `path` = the field path within it
619
- (e.g. `output.response`, `observations[3].output.success`), `value` = the EXACT cited string
646
+ grounds it — **`obs` = the packet's `trajectoryId` (the trace id), NEVER an observation
647
+ name** (in a multi-trajectory batch an observation-name `obs` can locate the WRONG
648
+ trajectory — the resolver takes the FIRST match in batch order), `path` = the field path
649
+ within the situation you were handed — packet-shaped (`trajectory.0.name`,
650
+ `transcript.0.content`) and trace-shaped (`observations.3.output.success`) BOTH re-resolve
651
+ (the verifier reconstructs the packet views deterministically); dot form is canonical,
652
+ bracket indices (`observations[3]`) are tolerated — `value` = the EXACT cited string
620
653
  (re-resolved by whitespace-normalized exact match by `#mode-verify`). **The string your prose
621
654
  critique already quotes IS the ref's `value`** — you are not inventing evidence, you are making
622
655
  the evidence the critique cites MACHINE-CHECKABLE. A decided verdict with a critique that cites
@@ -690,10 +723,12 @@ a new eval mid-judging. Your focus is judging what IS, not pondering what could
690
723
  "refs": [
691
724
  { "obs": "ef30a271", "path": "input.prompt", "value": "<outbound_guard consecutive_outbound=\"7\">" },
692
725
  { "obs": "ef30a271", "path": "output.response", "value": "held" },
693
- { "obs": "ef30a271", "path": "observations[0].name", "value": "ai.generateText" }
726
+ { "obs": "ef30a271", "path": "observations.0.name", "value": "ai.generateText" }
694
727
  ]
695
728
  }
696
729
  ```
730
+ (`"ef30a271"` is the packet's `trajectoryId`. Note the dot-form index `observations.0.name` —
731
+ canonical; `observations[0].name` also resolves.)
697
732
  Each `ref.value` is a VERBATIM string from the trace the critique quotes — `#mode-verify`
698
733
  re-resolves it by exact match; a value the trace doesn't contain is a dead ref → downgrade.
699
734
  **Counter-example (DEFECT — do NOT emit):** the same verdict with `"refs": []` (or `refs`
@@ -860,6 +895,29 @@ this inline section is now its canonical home.
860
895
  pick a primitive + field/params); else keep `class: judge` and emit NO codeEval. A code/hybrid
861
896
  category WITHOUT a `codeEval` is a HARD error (tier-0 inert). The `statement` stays the human
862
897
  "Pass = …"; the `codeEval` is its runnable twin (run by the tier-0 pre-pass, zero judge tokens).
898
+ **§5b PROPOSALS ARE NOW HONORED:** the `level` / `severity` / `dimension` / `generality` you emit
899
+ are carried onto the mined criterion and the generic heuristics are the FALLBACK, not the
900
+ override (they used to be dropped in the JOIN, which pinned every criterion to `level: output`).
901
+ Emit what you actually judged; omit a field only when you want it derived.
902
+ 4b. **NAME the cluster suite-aware — `reinforce_not_restate`.** Only now (AFTER clustering, never
903
+ before) open `existing-criteria.json`: the criteria the suite ALREADY holds, as
904
+ `{id, statement, severity, level, checkedBy}`. If one of them already states the behaviour your
905
+ cluster found, emit your cluster under **that criterion's exact id** — your traces then attach to
906
+ it as REINFORCING evidence rather than standing up a second name for one check. The ORDER is
907
+ load-bearing: consulting the suite before reading would hand you a pre-defined failure list and
908
+ re-create the confirmation bias `emergent_categories_only` exists to prevent. Clustering first
909
+ keeps what you FIND emergent; the brief only informs what you CALL it. It ships no evidence, so
910
+ it cannot tempt you into re-citing a failure you did not read. It **informs, it never blocks** —
911
+ if the traces show something no existing criterion states, MINT IT; and never emit an existing id
912
+ with no trace of your own behind it (that is an invented finding). An absent or empty brief is
913
+ normal on a first run — proceed unchanged.
914
+ The brief also lists **`openQuestions[]`** — pairs of EXISTING criteria already awaiting a human
915
+ ruling on whether they are really one check, each with the reason they were kept apart (e.g.
916
+ `marker parity: [BEFORE] vs [AFTER]`). Both sides are live and both statements appear in
917
+ `criteria[]`. If your cluster matches one side, emit it under **that side's id**: minting a third
918
+ name for a behaviour that is already an open question makes the question harder to answer, not
919
+ easier. Do **not** attempt to settle the question yourself — you are not the ruling authority.
920
+ If your cluster genuinely matches neither side, it is new; mint it.
863
921
  5. **Flag fixable-vs-eval-worthy (S4 / EV-051).** Route the fixable + infra-class (e.g. sample C4
864
922
  dead-channel `account_number_unavailable`) to diagnostics; keep the genuinely behavioral criteria
865
923
  for `*build-evals`. NEVER fix.
@@ -871,14 +929,18 @@ this inline section is now its canonical home.
871
929
  the FAILURE + UNCERTAIN traces into DATASET CANDIDATES (`collectDatasetCandidates`, reusing the
872
930
  derive-dataset selectors) and folds in the `*evaluate` judge's node-2.5 unmatched-detection
873
931
  handoff — both consumable by `*build-dataset`. You mine + flag; the parent emits the candidates.
874
- 7. **Render the discover `discovery-report.html` via the SHIPPED `writeDiscoverRunReport` composer**
875
- (`render-discover-report.ts`) — pass the run dir; it builds `triage-summary.json` from
876
- `triage.json` + wires every companion (verdicts Proof-of-work, dataset-candidates Dataset,
877
- triage census coverage funnel, profile entity hero) so the report is COMPLETE BY DEFAULT.
932
+ 7. **Render the discover `discovery-report.html` via the SHIPPED `writeDiscoverRunReportV3` composer**
933
+ (`render-discover-report-v3.ts`) — pass the run dir PLUS the AGGREGATE result you already hold in
934
+ memory (`criteria` · `annotations` · the leaf `miningReport` · the ingested `traces` ·
935
+ `datasetCandidates` · the grown `suite` provenance). It writes BOTH surfaces: the PRODUCTION
936
+ W3 frozen-contract report at `discovery-report.html` and the previous renderer's output at
937
+ `discovery-report.v2.html` (transition fallback) — mirroring eval's `writeRunReport`. Anything you
938
+ do not pass is read from the run dir; a genuinely absent companion renders as a **NAMED absence**
939
+ (e.g. "sampling strategy census: not recorded by this run"), NEVER a fabricated number.
878
940
  **Do NOT render via bare `writeDiscoverReport(criteria, grounding)`** — without companions it
879
941
  degrades the funnel to em-dash and leaves Proof-of-work + Dataset empty (the A4 thin-report gap;
880
- the data exists, it just isn't forwarded). Eval's `writeRunReport` is already complete-by-default;
881
- discover MUST use its composer to match.
942
+ the data exists, it just isn't forwarded). The v2 composer `writeDiscoverRunReport`
943
+ (`render-discover-report.ts`) stays available unchanged for the legacy surface.
882
944
 
883
945
  ### Governing invariants (verbatim — must survive the merge)
884
946
 
@@ -890,6 +952,7 @@ this inline section is now its canonical home.
890
952
  - `gather_structured_refs`: "Every observed claim AND every absence claim cites a structured ref {obs,path,value} (ground-absence = a positive field check, never inferred from silence)."
891
953
  - `typed_assumptions`: "Every surfaced assumption is TYPED — factual-intent · normative · scope — so its lifecycle/blocking routes correctly (GA-3)."
892
954
  - `emergent_categories_only`: "Categories EMERGE from what the traces show. Never start from a pre-defined failure list (confirmation bias). No generic scores as categories."
955
+ - `reinforce_not_restate`: "The existing suite (existing-criteria.json) is consulted AFTER clustering, to NAME an already-emerged cluster — never before, which would seed a pre-defined list and violate emergent_categories_only. A cluster whose behaviour an existing criterion already states is emitted under THAT id (reinforcing evidence), not as a second name for one check. INFORMS, never blocks: a genuinely new behaviour is always minted. An existing id emitted with no trace of your own behind it is a fabricated finding."
893
956
  - `judge_only_never_fix`: "EV-051 — FLAGS fixable-vs-eval-worthy and routes the fixable + infra-class to diagnostics. NEVER fixes the subject."
894
957
  - `binary_actionable`: "Each emergent category is one binary criterion whose verdict points at a concrete fix locus."
895
958
  - `full_mined_criterion_emit`: "Emit the FULL MinedCriterion (base + §5b metadata + §5c discovery incl. structured refs + typed assumptions). A lightweight/flattened emit that drops refs/assumptions is a DEFECT."