@mutagent/evaluator 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (23) hide show
  1. package/.claude/skills/mutagent-evaluator/SKILL.md +19 -7
  2. package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +2 -2
  3. package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +65 -1
  4. package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +1 -0
  5. package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +20 -7
  6. package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +1 -1
  7. package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +16 -0
  8. package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +14 -7
  9. package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +18 -2
  10. package/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +285 -0
  11. package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +45 -9
  12. package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +63 -8
  13. package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +1 -1
  14. package/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
  15. package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1 -1
  16. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +21 -6
  17. package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +12 -2
  18. package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +114 -18
  19. package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +6 -2
  20. package/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
  21. package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +1 -1
  22. package/bin/mutagent-cli.mjs +698 -7
  23. package/package.json +3 -3
@@ -12,15 +12,18 @@ description: |
12
12
  framework-substrate choice (DEFAULT agent-dispatch — host-runtime subagents, no provider ·
13
13
  in-house AI-SDK judge optional · code-based · user's framework export).
14
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. ALSO surfaces the existing v1 4-tab static-auditor
16
- (eval-matrix · data-leak · variance · methodology) via *audit. Pinned judge model + temp=0
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
17
20
  for byte-identical reruns (C-PIN). Usable standalone OR borrowed by mutagent-skill-builder's
18
21
  SIMULATE phase as the SimVerifier.
19
22
  license: Proprietary. LICENSE.txt has complete terms.
20
23
  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.
21
24
  metadata:
22
25
  author: mutagent
23
- version: "0.1.0-alpha.2"
26
+ version: "0.1.0-alpha.4"
24
27
  # allowed-tools: OMITTED — agent uses all native tools per host runtime
25
28
  ---
26
29
 
@@ -126,9 +129,15 @@ When you encounter a *<name> token:
126
129
 
127
130
  ## §1 — Triggers
128
131
 
132
+ > **DEFAULT INTENT = `*evaluate` (the Judge).** "evaluate this skill/agent", "score/judge this",
133
+ > a bare `/mutagent-evaluator`, or any ambiguous evaluate ask → **`*evaluate`**. **Never** fall back to
134
+ > `*audit`. `*audit` (the 4-tab master-audit) is a SPECIALIZED, **explicit-only** surface — invoked by
135
+ > name (`*audit <subject>`), typically for complex agents/skills that emit HTML artifacts. If unsure
136
+ > between the two, choose `*evaluate`.
137
+
129
138
  Invoke me with:
130
- - `evaluate skill <name>` / `evaluate agent <name>` / `/mutagent-evaluator`
131
- - `*discover-evals` (mine criteria) · `build evals for <x>` · `validate judge <x>` · `*audit <subject>`
139
+ - `evaluate skill <name>` / `evaluate agent <name>` / `/mutagent-evaluator` → **`*evaluate`** (default)
140
+ - `*discover-evals` (mine criteria) · `build evals for <x>` · `validate judge <x>` · `*audit <subject>` (explicit only)
132
141
  - `*improve` (the ADL ③ EDD loop — variance-first then request-amend-reeval to full green, bounded)
133
142
  - `*self-audit` (the eval-of-the-eval — audit my OWN eval-dev; on-demand only)
134
143
  - borrowed by `mutagent-skill-builder` SIMULATE (SimVerifier role)
@@ -147,7 +156,9 @@ Minimal eval-dev spine (build order — do NOT reorder):
147
156
  amend agentspec|impl over SendMessage → re-eval) → full green OR bounded STOP
148
157
  ```
149
158
 
150
- `*validate` (calibrate the judge) and `*audit` (the v1 4-tab static report) layer on top.
159
+ `*validate` (calibrate the judge) layers on top. `*audit` (the v1 4-tab master-audit) is an
160
+ **explicit-only** specialized surface (complex / HTML-artifact subjects) — NOT part of the default
161
+ evaluate spine and never an automatic fallback.
151
162
  `*improve` is the IMPROVE stage — it runs AFTER an initial `*build` + `*evaluate` and is **bounded**
152
163
  (never infinite): full-green ⇒ DONE; else max-swings / max-wallclock / no-improvement ⇒ STOP + delta.
153
164
 
@@ -167,7 +178,7 @@ log-sink dependency up front, F7/F9/F14), streams wireframe progress + entity ca
167
178
  - **Curated Discovery handoff (P-B)** — when the traces arrive from the `discovery` SYSTEM AGENT under a PURPOSED intent (`*discover-evals` → `operationIntent: evals` · `*discover-dataset` → `operationIntent: dataset`), the handover is a **curated, classified** set: a `SelectionManifest` (`mutagent-tools` `selection.ts` — `byScenario` · `byWorthiness` · labeled/unlabeled counts) alongside `traces.jsonl` where each kept record carries `ext.signals` (deterministic worthiness from `trace select`) + `ext.classification` (`scenario` · `edgeCase` · `outcome` · `worthiness`, from the agent's LLM classify). BOTH evaluator commands READ this handoff (skills never fetch) and consume the labels: **`*discover-evals`** STRATIFIES criteria-mining by `ext.classification.scenario` and PRIORITIZES by `ext.signals.worthinessScore` (unlabeled/low-worthiness traces are kept for coverage but down-weighted); **`*discover-dataset`** distills a **scenario-balanced** regression set, **edge-cases first** (`ext.classification.edgeCase.is`), reusing the `derive-dataset.ts` selectors + `build-dataset.ts` merge. A generic `fetch` handoff (no `ext.signals`/`ext.classification`) degrades gracefully to the pre-P-B behavior. The judge/mining internals are unchanged — only the sampling/stratification READS the new curated fields.
168
179
  - **Code-before-judge (EX-2)** — the §5b `check_method` routes each metric: `deterministic`→a runnable code-eval the agent executes via Bash (zero judge tokens, byte-identical), `llm-judge`→the host judge, `hybrid`→code pre-filter that gates the judge (`check-method-router.ts` + `code-eval.ts`).
169
180
  - **`*evaluate`** — the real spine (`run-evaluate.ts`): **T1 tier-0** code-method pre-pass (`tier0Plan` — code rows decided deterministically, only residual judge rows dispatched) → PREP packet (**T5 `adaptivePacketPlan`** overload guard, default 1:1) → dispatch `#mode-judge-trajectory` (emits the **T2 DAG v2 walk**) → fail-loud readiness gate → fold code+judge → **T3 independent verifier** (downgrade-only on gating fails) → **T4 `consolidateByLocus` + `deriveWalkHealth`** → GATE + variance → EV-051 route-to-diagnostics; the masked scorecard is byte-identical across reruns (C-PIN).
170
- - **Report** — terminal eval-cards + the operator-APPROVED 5-tab HTML eval-report (`render-eval-report.ts`): §1 Overview (KPIs · coverage-contract · gating table · top-findings) · §2 Trajectory‖Judge per-trace ledger + click-row Target-Agent‖Judge side-by-side · §3 Eval Scorecard cohort heatmap + nested subcards + inline calibration · §4 Findings verbatim-evidence + judge chain + agree/revise/refute · §5 Self-Eval [INTERNAL, stripped on publish]. `run-evaluate.ts` renders `report.html` post-aggregate; consumes the §9.4 judge-walk (dense na-explicit map + `judge_steps[]`) when present, degrades to the per-trajectory scorecard otherwise.
181
+ - **Report** — terminal eval-cards + the operator-APPROVED 5-tab HTML eval-report (`render-eval-report.ts`): §1 Overview (KPIs · coverage-contract · gating table · top-findings) · §2 Trajectory‖Judge per-trace ledger + click-row Target-Agent‖Judge side-by-side · §3 Eval Scorecard cohort heatmap + nested subcards + inline calibration · §4 Findings verbatim-evidence + judge chain + agree/revise/refute · §5 Self-Eval [INTERNAL, stripped on publish]. `run-evaluate.ts` renders `evaluation-report.html` post-aggregate (client-audience by default; §5 shown only for `--audience internal`); consumes the §9.4 judge-walk (dense na-explicit map + `judge_steps[]`) when present, degrades to the per-trajectory scorecard otherwise.
171
182
 
172
183
  ## §3 — Architecture Overview
173
184
 
@@ -265,6 +276,7 @@ applies, EV-053/EQ5):
265
276
  | `scripts/aggregate-discover.ts` | P2 (§5a) + §9.4 (T6) | code | The real `*discover-evals` AGGREGATE half: parse the leaf's per-trace verdict files + per-batch mining report → `TraceAnnotation[]` (FAIL-LOUD via `missingVerdictKeys`; THROW on an undispatched-trace category ref) → `deriveMinedCriteria` (§5b metadata + §5c DR-2) → `growLivingSuite` (append-only, monotonic, saturation-stop). **T6:** `collectDatasetCandidates` (failure/uncertain → `DatasetCase[]` via the derive-dataset selectors) + `unmatchedDetectionCandidates` (the `*evaluate` judge's node-2.5 handoff) → consumable by `*build-dataset`. `DiscoverMiningReport` contract. No hand-rolled annotations. |
266
277
  | `scripts/code-eval.ts` | P3 (EX-2) | code | The v2 CODE-track primitive library — generic PURE `(trace) → {result, detail}` checks: presence · string-equality · format-validity · schema-conformance · ref-integrity (cross-stage). SV-1 dotted-path read incl. `obs:<name>.<path>`. Exhaustive `runCodeEval` (unknown primitive → THROW). NO LLM, byte-identical. Disjoint from the v1 `run-deterministic.ts` audit world. |
267
278
  | `scripts/check-method-router.ts` | P3 (EX-2) | hybrid | THE load-bearing §5b `check_method` router: `deterministic`→CODE-EXEC (judge NEVER called) · `llm-judge`→JUDGE seam · `hybrid`→code pre-filter that GATES the judge. Unknown → THROW (exhaustive). `RoutedCriterionVerdict` = `CriterionVerdict` + provenance `{producedBy, codeResult, judgeReached}` (structural superset → `evaluate.ts` rollup unchanged). `extractCodeEval(registry)` fail-loud (a code-class criterion with no spec THROWS — never silently judge). "Code before judge" lives INSIDE the agent. |
279
+ | `scripts/code-quality-verdict.ts` | W2I1 (⑤ IMPROVE) | hybrid | The **code-quality JUDGE mode** (`#mode-judge-code-quality`) — the (b) half of the ⑤ IMPROVE code-target BOTH-gate. Scores a `code` subject's QUALITY (spec-faithfulness · addresses-diagnosis · no-regression · error-handling · maintainability) BEYOND tests-green → severity-gated binary PASS/FAIL. `buildCodeQualityPrompt` (pure renderer, critique-before-verdict) + `codeQualityGatePass` (pure aggregate) + `runCodeQualityJudge` (C-PIN run wrapper on the injected JudgeInvoke seam). Judge-only (EV-051), host-runtime, pinned model + temp 0. **DISTINCT from `code-eval.ts`** (deterministic primitives — no LLM). `DEFAULT_CODE_QUALITY_CRITERIA` + its aggregation rule are FLAGGED-for-operator-sign-off. |
268
280
  | `scripts/run-evaluate.ts` | P5 + §9.4 (T1·T3·T4·T5) | hybrid | The real-engine `*evaluate` end-to-end COMPOSER: `tier0Plan` (T1 — code rows decided in the pre-pass, residual judge rows only in packets) → `prepMatrixPackets` (residual-only) → `adaptivePacketPlan` (T5 overload guard, default 1:1) → [dispatch #mode-judge-trajectory] → `aggregateEvaluate` (FAIL-LOUD readiness gate over DISPATCHED ids → `aggregateMatrixScorecard` folding code+judge + T3 `independentVerify` downgrade-only on gating fails → GATE + variance → T4 `consolidateByLocus` lociClusters → EV-051 `routeFailures`→validated HandoverBundle). `maskedScorecard` = the C-PIN byte-identity artifact. |
269
281
  | `scripts/render-eval-report.ts` | P4 (D-1/2/3) | code | The v2 reporting (v1 `render-report.ts` untouched): `renderEvalCards` (D-1 terminal per-criterion cards) + `buildEvalReportInput` (derives the rich tabs from real run data — ledger · coverage · cohorts · gating rolls · top-findings · judge-health) + `renderEvalReport` (the operator-APPROVED 5 tabs: ① Overview KPIs+coverage+gating-table+top-findings · ② Trajectory‖Judge ledger + click-row Target-Agent‖Judge side-by-side · ③ Eval Scorecard cohort heatmap + nested subcards + inline calibration · ④ Findings verbatim-evidence + judge chain + agree/revise/refute · ⑤ Self-Eval [INTERNAL, strip-marked]). Consumes the §9.4 judge-walk (`judge_steps[]` + dense map) when present, degrades to the per-trajectory scorecard otherwise. Tabs are `<button>` (no `<s>` strikethrough). Unified design-system tokens (`assets/brand/theme.css`). byte-identical after the `generatedAt` mask. `autoOpenCommand` cross-platform helper (orchestrator fires it). |
270
282
  | `scripts/contracts/eval-types.ts` *(extended)* | P2/P2b | code | + `MinedCriterion` (additive subtype of `DiscoveredCriterion`) carrying the §5b `MetricMetadata` (generality·dimension·level·check_method [3-value SF-2 router]·substrate·applies_to·severity·judge_inputs·flag) + the §5c `DiscoveryRationale` (DR-2: targets·why·evidence{grounding observed\|inferred·prevalence k/n·refs}·assumptions·expected_impact). `parseMinedCriterion` enforces the evidence-first gate (OBSERVED ⇒ refs + k>0). |
@@ -18,7 +18,7 @@ ACTIVATION-NOTICE: This file contains your full agent operating guidelines. Read
18
18
  class: pure_subagent_executor
19
19
  isolation: none
20
20
 
21
- # STATUS: SCOPED / FUTURE not dispatched today.
21
+ # STATUS: DISPATCHED standalone as the vendored delegate-or-artifact trace agent (diagnostics Step 3.6 · evaluator Step 0.5). The orchestrator `*collect-traces` alias route remains future.
22
22
  # Helix currently calls `mutagent-cli trace count` + `trace fetch --export` DIRECTLY (the
23
23
  # example workflows in trace-layer-plan.md §Example Workflows). This agent FORMALIZES that
24
24
  # pattern into a delegatable cell once the CLI surface is proven in production. The contracts
@@ -359,7 +359,7 @@ You are a **discovery** agent. You collect agent traces on behalf of Helix (or a
359
359
  orchestrator) and hand the result to a downstream skill (Diagnostics or Evaluator). You do
360
360
  NOT analyze traces — you only **collect, normalize, and validate** them.
361
361
 
362
- > **SCOPED / FUTURE.** This agent contract is written now so the contracts are locked and
362
+ > **STATUS.** The vendored agent IS dispatched standalone today (diagnostics Step 3.6 · evaluator Step 0.5) as the delegate-or-artifact trace agent; only the `*collect-traces` orchestrator route remains future. The contract below was written ahead so it was locked and
363
363
  > the handoff chain is fully specified. Helix currently calls `mutagent-cli` directly (the
364
364
  > example workflows in `trace-layer-plan.md`). This agent formalizes that into a delegatable
365
365
  > cell once the CLI surface is proven in production. Dispatch wiring (`*collect-traces` route
@@ -508,6 +508,7 @@ resolution_contract: |
508
508
  mode: judge, axis: criterion => run #mode-judge-criterion (preserves eval-judge); input is a .task.json.
509
509
  mode: verify => run #mode-verify (GA-5 result-verifier; reviewer≠judge, downgrade-only); input is a decided verdict + its situation.
510
510
  mode: improve => run #mode-improve (EDD ③ loop — F19 variance-first then F18 request-amend-reeval, bounded). JUDGE-ONLY: REQUEST, never patch.
511
+ mode: judge, axis: code-quality => run #mode-judge-code-quality (Wave-2 W2I1 — score a CODE subject's QUALITY for the ⑤ IMPROVE code-target BOTH-gate). JUDGE-ONLY; binary quality PASS/FAIL. Rubric mirrored in scripts/code-quality-verdict.ts.
511
512
  4. DISPATCH-INPUT DISAMBIGUATION — the parent hands you files:
512
513
  a *.packet.json (MatrixPacket) => judge/trajectory, ALWAYS (matrix-judge path; no unit field).
513
514
  a *.task.json (JudgeTaskSpec) => unit.kind: "discover" => discover; else unit.axis: "criterion" => judge/criterion.
@@ -870,7 +871,7 @@ this inline section is now its canonical home.
870
871
  the FAILURE + UNCERTAIN traces into DATASET CANDIDATES (`collectDatasetCandidates`, reusing the
871
872
  derive-dataset selectors) and folds in the `*evaluate` judge's node-2.5 unmatched-detection
872
873
  handoff — both consumable by `*build-dataset`. You mine + flag; the parent emits the candidates.
873
- 7. **Render the discover `report.html` via the SHIPPED `writeDiscoverRunReport` composer**
874
+ 7. **Render the discover `discovery-report.html` via the SHIPPED `writeDiscoverRunReport` composer**
874
875
  (`render-discover-report.ts`) — pass the run dir; it builds `triage-summary.json` from
875
876
  `triage.json` + wires every companion (verdicts → Proof-of-work, dataset-candidates → Dataset,
876
877
  triage census → coverage funnel, profile → entity hero) so the report is COMPLETE BY DEFAULT.
@@ -1029,6 +1030,69 @@ never re-implements, the runner. The interface this mode ASSUMES (documented ver
1029
1030
  C-PIN (pinned model + temperature 0).
1030
1031
  - **Additive to the runner:** consumes the assumed eval-runner interface; rebuilds nothing.
1031
1032
 
1033
+ ## Mode: judge — axis code-quality ← the ⑤ IMPROVE code-target quality verdict (Wave-2 W2I1)
1034
+
1035
+ The **(b) half of the ⑤ IMPROVE loop's code-target BOTH-gate.** For a `code`-kind subject, "converged"
1036
+ requires BOTH (a) the subject's OWN test suite green — a hard DETERMINISTIC gate the ai-engineer
1037
+ reports and the optimize oracle records as `testsGreen` — AND (b) THIS code-quality verdict passing.
1038
+ This mode is (b): score the QUALITY of the applied code change, BEYOND tests-green (a deterministic
1039
+ primitive cannot decide spec-faithfulness, scope, error-handling, maintainability — a judge must).
1040
+
1041
+ ### What it does (per dispatch)
1042
+
1043
+ Judge the CODE subject (the amend diff / scaffold against the agentspec definition it must realize)
1044
+ on the DRAFT criteria set — **critique-BEFORE-verdict, strictly BINARY** per criterion — and emit a
1045
+ severity-gated binary quality **PASS/FAIL**. The authoritative rubric + the drafted default criteria
1046
+ live in `scripts/code-quality-verdict.ts` (`DEFAULT_CODE_QUALITY_CRITERIA` — Q1 spec-faithful [crit] ·
1047
+ Q2 addresses-diagnosis [high] · Q3 no-regression [high] · Q4 error-handling [high] · Q5 maintainable
1048
+ [med]); this mode is that rubric's dispatched form (the provider-callable mirror is the same file,
1049
+ mirroring how the other judge prompts mirror their agent modes).
1050
+
1051
+ > **⚠️ FLAGGED-FOR-OPERATOR-SIGN-OFF.** The exact acceptance criteria + the severity-gated aggregation
1052
+ > rule (CRIT/HIGH `fail`|`uncertain` blocks; MEDIUM advisory) are a DRAFT default, implemented behind
1053
+ > a clearly-named constant so the operator can confirm/adjust WITHOUT a loop code change. Do not treat
1054
+ > the current set as final.
1055
+
1056
+ ### Boundaries
1057
+
1058
+ - **Judge-only (EV-051):** score the quality; NEVER propose or write a fix (the ⑤ loop's ai-engineer
1059
+ amends). A FAIL just keeps the loop iterating.
1060
+ - **Host-runtime, no provider key; C-PIN:** pinned model + temperature 0 — byte-identical reruns.
1061
+ - **Separate from tests-green:** you judge quality, NOT whether tests pass; the deterministic test
1062
+ gate is ai-engineer's report, ANDed by the oracle — never fold it into your quality verdict.
1063
+
1064
+ ## Eval-leg reconcile — criteria MAINTENANCE, not judging (Wave-2 W2I5 · KP-003)
1065
+
1066
+ The evaluator owns the **eval leg** of the def → impl → eval triad — the third leg of `#sync-spec`. When
1067
+ an impl amends (the ⑤ IMPROVE loop's ai-engineer, or brownfield drift), the eval criteria that ground the
1068
+ subject's evaluation can go stale. The reconcile REASONING — which criteria a changed impl makes stale,
1069
+ what new criterion it needs — is the **ai-architect's** (`#sync-spec`, session-dispatched, Model-B). The
1070
+ evaluator exposes only the deterministic CONTRACT the reconcile reads/writes, in
1071
+ `scripts/sync-eval-criteria.ts`:
1072
+
1073
+ - `evalLegForSubjectKind(kind)` — the shape resolver: agent/skill/composite → the **eval-suite criteria**
1074
+ (this cell's discovered/maintained criteria); code → the **code-quality criteria**
1075
+ (`DEFAULT_CODE_QUALITY_CRITERIA` / `#mode-judge-code-quality`, Wave-2 W2I1).
1076
+ - `flagEvalLegDrift(...)` / `assessEvalLeg(...)` — a pure freshness FLAG (mirrors the builder probe).
1077
+ - `reconcileEvalCriteria(request)` — the COMPUTE: upsert the ai-architect-proposed criteria delta by id,
1078
+ append the novel, and NEVER drop a criterion (EV-053, via `assertMonotonicGrowth`). It returns the grown
1079
+ set as DATA (`result.criteria`) — it does **not** touch disk; the reconcile hook COMPUTES, it is not the write.
1080
+ - `persistEvalCriteria(result, { cwd, updatedAt? })` (`scripts/persist-eval-criteria.ts`) — the WRITE: the
1081
+ evaluator session PERSISTS `result.criteria` back to the located criteria artifact
1082
+ (`.mutagent/evaluator/living-suite/<leg>.criteria.json`, per-leg — eval-suite for an agent/skill/composite
1083
+ subject, code-quality for a code subject). That write is what BUMPS the artifact's freshness (its mtime — and
1084
+ the optional `updatedAt` marker the builder probe reads), which returns the eval leg to `in-sync`: the loop
1085
+ only closes once the reconciled criteria are on disk, not when they are merely computed.
1086
+
1087
+ > **Judge-only PRESERVED (EV-051).** This is criteria MAINTENANCE — growing/revising the criteria SET to
1088
+ > match a changed impl — NOT judging a subject. `sync-eval-criteria.ts` scores nothing and decides no
1089
+ > pass/fail; it is pure set algebra + a freshness flag (no clock/random/network — C-PIN byte-identical). The
1090
+ > `persist-eval-criteria.ts` WRITER is the IO layer (it is the one place that touches disk, mirroring how
1091
+ > `artifact-paths.ts` does IO while `living-suite.ts` stays pure); its serialization is still deterministic
1092
+ > (stable key order, no clock unless the caller passes `updatedAt`), so a persisted artifact round-trips
1093
+ > byte-faithful. The evaluator never mutates the impl and never fixes the subject; it only maintains — and now
1094
+ > persists — its own criteria.
1095
+
1032
1096
  ## Boundaries (shared)
1033
1097
 
1034
1098
  judge-only (EV-051) · model-intent-sacred · critique-before-verdict · binary-not-Likert ·
@@ -76,6 +76,7 @@ makes NO pass/fail decision; the LLM/HITL halves are Type B (below).
76
76
  | `*build-dataset` expand (EV-046) | `scripts/build-dataset.ts` | `buildCase` · cartesian dimension×value expand · deterministic token-Jaccard near-dup removal · content-derived id · `mergeCases` MONOTONIC merge. The agent PROPOSES realism; the script ENFORCES non-redundancy. Code half of the 3-way `*build-dataset` Hybrid. |
77
77
  | `*discover-dataset` distill (EV-047) | `scripts/derive-dataset.ts` | Distill a living regression set from past ✓/✗ — reuses EV-052 `sample-traces.ts` selectors (failure/outlier/uncertainty) + EV-046 monotonic merge; trace→`DatasetCase` whose query IS the real input. Labels already on traces (from `*discover-evals`); makes NO new decision. Type A Code-only. |
78
78
  | Living-suite writer (EV-053) | `scripts/living-suite.ts` | `appendOnly` + `assertMonotonicGrowth` — generic-over-`T` append-only writer (a living artifact NEVER shrinks) shared by datasets + criteria. Pure-counter provenance (no clock → byte-identity, C-PIN). Pure set algebra w/ fail-loud monotonicity guard. |
79
+ | Eval-leg reconcile contract (W2I5 · KP-003) | `scripts/sync-eval-criteria.ts` | The eval leg of `#sync-spec` (def → impl → eval triad). `evalLegForSubjectKind` (agent/skill → eval-suite · code → code-quality) · `flagEvalLegDrift`/`assessEvalLeg` (pure freshness flag) · `reconcileEvalCriteria` (upsert-by-id criteria MAINTENANCE via `assertMonotonicGrowth` — never drops, EV-053). Criteria maintenance, NOT judging (EV-051): scores nothing, decides no pass/fail. The reconcile REASONING is `ai-architect #sync-spec`'s (Model-B); this holds only the deterministic contract. Pure (no clock/random/network, C-PIN). |
79
80
  | W2 dataset contract | `scripts/contracts/dataset.ts` | TypeBox `Dimension`/`DatasetTuple`/`DatasetCase`/`Dataset` (companion to `schemas/dataset.schema.yaml`) + `tupleKey`. NEW W2-OWN file — does NOT touch shared `contracts/eval-types.ts`. Data contract only. |
80
81
  | W2 trust contract | `scripts/contracts/validation.ts` | TypeBox `HumanLabel` (produced by `*review`, consumed by `*validate`) + the confusion-matrix / validation-result shapes. NEW W2-OWN file; disjoint from shared types. Data contract only. |
81
82
 
@@ -94,13 +94,26 @@ Resolve the pinned envelope ONCE for the whole run (C-PIN): `model` from
94
94
 
95
95
  ---
96
96
 
97
- ## Step 0.5 — Trace intake (UniTF handover — the skill NEVER fetches)
98
-
99
- Traces arrive as a **pre-produced UniTF JSONL handover**: one `UnifiedTrace`
100
- record per line, written by `mutagent-cli trace fetch --export <path>` BEFORE this
101
- skill is dispatched (Helix runs the fetch in its PRE-STAGE the evaluator owns no
102
- fetch/normalize code). Fetch + normalize live entirely in `mutagent-cli`; run
103
- `mutagent-cli trace --help` to discover the surface (count · fetch · export).
97
+ ## Step 0.5 — Trace intake (delegate-or-artifact — the evaluator ACQUIRES traces · reports-PRD R-6)
98
+
99
+ The evaluator owns NO fetch/normalize code (that lives in `mutagent-cli` + the `discovery`
100
+ agent) but **a real user will not hand you a trace file**. So, in **parity with Diagnostics
101
+ Step 3.6 (V3-9)**, traces are acquired by EXACTLY ONE of the 3 legal paths:
102
+
103
+ | # | Path | Who fetches | When |
104
+ |---|---|---|---|
105
+ | a | **Helix pre-fetch / handover** | Helix (pre-stage) or a pre-produced `mutagent-cli trace fetch --export`, handed in via `--traces <handover.jsonl>` | orchestrated runs |
106
+ | b | **`*discover` dispatch** | the `discovery` system agent | orchestrated collection |
107
+ | c | **Standalone: artifact intake OR vendored-Discovery spawn** | a pre-staged artifact, or the vendored `discovery` sub-agent | THIS step |
108
+
109
+ **Standalone flow (path c), in order:**
110
+ 1. **Handover / artifact intake first.** If `--traces <handover.jsonl>` (`.gz` ok) was handed in,
111
+ or a pre-staged `.mutagent/evaluator/<run>/ingest/traces.jsonl` exists → consume it; do NOT spawn Discovery.
112
+ 2. **Else DELEGATE to the vendored Discovery agent.** Spawn `assets/agents/discovery.md` (installed at
113
+ `.claude/agents/discovery.md`) via the Task tool (`subagent_type: "discovery"`), handing it the
114
+ resolved TraceQuery (source + filters + window + `--export .mutagent/evaluator/<run>/ingest/traces.jsonl`).
115
+ Discovery fetches + curates → a `SelectionManifest` + `traces.jsonl`; the PREP/AGGREGATE CLIs then
116
+ read it via `--traces` (P-B curated `ext.signals`/`ext.classification` consumed when present).
104
117
 
105
118
  The PREP/AGGREGATE CLIs (`scripts/cli/{prep,aggregate,dogfood}.ts`) take the
106
119
  handover file via `--traces <handover.jsonl>` (`.gz` also accepted) and read it
@@ -338,7 +338,7 @@ export function writeDiscoverReport(input: WriteDiscoverReportInput): string {
338
338
  ...(input.companions ?? {}),
339
339
  });
340
340
  mkdirSync(input.reportDir, { recursive: true });
341
- const outPath = join(input.reportDir, "report.html");
341
+ const outPath = join(input.reportDir, "discovery-report.html");
342
342
  writeFileSync(outPath, html);
343
343
  return outPath;
344
344
  }
@@ -91,6 +91,22 @@ export function scorecardDir(runId: string, cwd?: string): string {
91
91
  export function reportDir(runId: string, cwd?: string): string {
92
92
  return underRoot(cwd, "reports", runId);
93
93
  }
94
+
95
+ // ── the three named report artifacts (evaluator-reports PRD R-1/R-3) ──────────
96
+ // Distinct names under ONE run folder — no `report.html` collision, and *audit no longer
97
+ // writes under `.mutagent/diagnostics/`. Single resolver = single source of truth.
98
+ /** `*evaluate` → the 5-tab judge report. */
99
+ export function evaluationReportPath(runId: string, cwd?: string): string {
100
+ return underRoot(cwd, "reports", runId, "evaluation-report.html");
101
+ }
102
+ /** `*discover-evals` → the mined-criteria report. */
103
+ export function discoveryReportPath(runId: string, cwd?: string): string {
104
+ return underRoot(cwd, "reports", runId, "discovery-report.html");
105
+ }
106
+ /** `*audit` → the 4-tab master-audit (now under the evaluator root, not diagnostics). */
107
+ export function masterAuditPath(runId: string, cwd?: string): string {
108
+ return underRoot(cwd, "reports", runId, "master-audit.html");
109
+ }
94
110
  export function livingSuiteDir(cwd?: string): string {
95
111
  return underRoot(cwd, "living-suite");
96
112
  }
@@ -26,10 +26,11 @@ import {
26
26
  resolveProfilePaths,
27
27
  } from "../load-profile.ts";
28
28
  import { loadBundle } from "../load-bundle.ts";
29
- import { runDeterministic } from "../run-deterministic.ts";
29
+ import { runDeterministic, reexecDeterministicCheck } from "../run-deterministic.ts";
30
30
  import { assembleScorecard } from "../assemble-scorecard.ts";
31
31
  import { renderReport } from "../render-report.ts";
32
32
  import { maskedCanonicalJson } from "../mask.ts";
33
+ import { reportDir } from "../artifact-paths.ts";
33
34
 
34
35
  const HERE = dirname(fileURLToPath(import.meta.url));
35
36
  const PKG_ROOT = join(HERE, "..", "..");
@@ -106,8 +107,11 @@ export function main(argvIn?: string[]): number {
106
107
  // 2. Run-bundle
107
108
  const { bundle } = loadBundle(args.bundleDir, args.runId);
108
109
 
109
- // 3. Deterministic pass
110
- const det = runDeterministic(matrix, bundle);
110
+ // 3. Deterministic pass — WIRE the real live re-exec (Item #4): every mapped
111
+ // deterministic row runs an actual checkMethod-keyed check over the produced
112
+ // artifact instead of a presence proxy. In-cell, no sub-agents (PR-ORCH-01),
113
+ // judge-only (EV-051), C-PIN byte-stable.
114
+ const det = runDeterministic(matrix, bundle, { liveReexec: reexecDeterministicCheck });
111
115
 
112
116
  // 4. Two-track scorecard
113
117
  const scorecard = assembleScorecard({
@@ -120,10 +124,13 @@ export function main(argvIn?: string[]): number {
120
124
  // 5. Render
121
125
  const html = renderReport(scorecard);
122
126
 
123
- const outDir = args.outDir ?? args.bundleDir;
124
- mkdirSync(join(outDir, "audit"), { recursive: true });
125
- const scPath = join(outDir, "audit", "scorecard.json");
126
- const htmlPath = join(outDir, "audit", "master-audit.html");
127
+ // Output the UNIFIED evaluator reports root (PRD R-3): reports/<runId>/master-audit.html
128
+ // (was under .mutagent/diagnostics/<runId>/audit/ the wrong stage root). bundleDir stays the
129
+ // INPUT (the run-bundle being audited); the report is written to the evaluator namespace.
130
+ const outDir = args.outDir ?? reportDir(args.runId);
131
+ mkdirSync(outDir, { recursive: true });
132
+ const scPath = join(outDir, "master-audit.scorecard.json");
133
+ const htmlPath = join(outDir, "master-audit.html");
127
134
  const scJson = args.emitMasked
128
135
  ? maskedCanonicalJson(scorecard)
129
136
  : JSON.stringify(scorecard, null, 2);
@@ -16,8 +16,10 @@
16
16
  * SOURCE SELECTION (--source): when `global.sources[]` has >1 entry and none is
17
17
  * `default:true`, the run-start config surfaces a `needs-selection` state; the
18
18
  * PARENT session then ASKS the operator to pick one (AskUserQuestion / chat
19
- * multi-choice) and re-invokes with `--source <name>`. A single source, exactly
20
- * one `default:true`, or an explicit `--source` skips the prompt.
19
+ * multi-choice) and re-invokes with `--source <name>`. When >1 entry has exactly
20
+ * one `default:true`, the config surfaces `confirm-default` (F2) the PARENT
21
+ * CONFIRMS the preselected default (a yes/no ask; distinct from the open pick)
22
+ * before it is used. A single source or an explicit `--source` skips the prompt.
21
23
  *
22
24
  * MODEL INTENT SACRED: --model is the pinned host model (temperature pinned 0,
23
25
  * C-PIN); it is carried verbatim on every task spec for the subagent to honor —
@@ -58,6 +60,20 @@ function flag(argv: string[], name: string): string | undefined {
58
60
  */
59
61
  function surfaceSourceBinding(binding: SourceBinding): void {
60
62
  switch (binding.kind) {
63
+ case "confirm-default": {
64
+ // F2 — a preselected default among many: surface a CONFIRM ask (yes/no),
65
+ // distinct from the open needs-selection pick. NON-fatal: the default is usable
66
+ // as-is on accept / non-interactive / --source.
67
+ const names = binding.sources.map((s) => s.name).join(", ");
68
+ process.stdout.write(
69
+ `prep: source CONFIRM-DEFAULT — ${binding.sources.length} sources, default is ` +
70
+ `'${binding.source.name}'. PARENT: confirm the default via the platform ask ` +
71
+ `mechanism (AskUserQuestion / chat multi-choice; '${binding.source.name}' PRESELECTED, ` +
72
+ `operator may override), or accept it non-interactively (--source <name> / CI). ` +
73
+ `Candidates: ${names}\n`,
74
+ );
75
+ break;
76
+ }
61
77
  case "needs-selection": {
62
78
  const names = binding.sources.map((s) => s.name).join(", ");
63
79
  process.stdout.write(
@@ -0,0 +1,285 @@
1
+ /**
2
+ * scripts/code-quality-verdict.ts — the CODE-QUALITY judge mode (Wave-2 W2I1).
3
+ * ---------------------------------------------------------------------------
4
+ * A JUDGE mode that scores a CODE subject's QUALITY (the amended/scaffolded
5
+ * implementation of a `code`-kind subject) and emits a binary PASS/FAIL quality
6
+ * verdict with a critique-before-verdict per criterion. It is the (b) half of the
7
+ * ⑤ IMPROVE loop's code-target BOTH-gate: "converged" for a `code` subject = the
8
+ * subject's OWN test suite green (a deterministic gate the session records as
9
+ * `testsGreen`) AND this code-quality verdict PASSes. Neither alone converges.
10
+ *
11
+ * DISTINCT FROM `code-eval.ts`. That file is the DETERMINISTIC code-track primitive
12
+ * library (presence/string-equality/... over an EvalTrace — no LLM, byte-identical).
13
+ * This file is an LLM JUDGE mode: it scores the irreducibly-subjective quality of a
14
+ * code implementation (spec-faithfulness, maintainability, error-handling, ...) that
15
+ * a deterministic primitive cannot decide.
16
+ *
17
+ * INVARIANTS (consistent with the existing evaluator cell):
18
+ * - JUDGE-ONLY (EV-051). It scores; it NEVER fixes. Failures route to the ⑤ loop's
19
+ * ai-engineer amend (via the loop, not from here).
20
+ * - HOST-RUNTIME. Under the DEFAULT agent-dispatch substrate the authoritative
21
+ * rubric lives in the evaluator agent def (`#mode-judge-code-quality`), and the
22
+ * verdict comes from a dispatched subagent — this file's prompt is the
23
+ * provider-callable MIRROR for the OPTIONAL in-house / export substrate (same
24
+ * operator-named exception as judge-prompt-template.ts). No provider key on the
25
+ * default path.
26
+ * - PINNED model + temperature 0 (C-PIN). `runCodeQualityJudge` THROWS if the pin
27
+ * is not (modelId present AND temperature === 0) — reruns must be byte-identical.
28
+ * - PURE renderer + a thin injected-seam run wrapper. No clock, no random, no
29
+ * network; the LLM call is the injected `JudgeInvoke` seam so tests drive it.
30
+ */
31
+ import { parseCritiqueVerdict, type JudgeInvoke } from "./determine-outcome.ts";
32
+ import type { JudgePin } from "./build-evals.ts";
33
+ import type { CritiqueVerdict } from "./contracts/eval-types.ts";
34
+
35
+ /** Severity of a code-quality criterion — gates the aggregate (CRIT/HIGH block). */
36
+ export const CodeQualitySeverity = {
37
+ Critical: "critical",
38
+ High: "high",
39
+ Medium: "medium",
40
+ } as const;
41
+ export type CodeQualitySeverityValue =
42
+ (typeof CodeQualitySeverity)[keyof typeof CodeQualitySeverity];
43
+
44
+ /** One binary code-quality criterion the judge scores. */
45
+ export interface CodeQualityCriterion {
46
+ /** Stable id (byte-identity key + report anchor). */
47
+ id: string;
48
+ /** The one-line binary statement the judge decides pass/fail on. */
49
+ statement: string;
50
+ /** What a PASS looks like (concrete, so the judge is not guessing). */
51
+ passDefinition: string;
52
+ /** What a FAIL looks like. */
53
+ failDefinition: string;
54
+ /** Gating weight — a `fail`/`uncertain` on a CRIT/HIGH criterion blocks the gate. */
55
+ severity: CodeQualitySeverityValue;
56
+ }
57
+
58
+ /**
59
+ * ⚠️ FLAGGED-FOR-OPERATOR-SIGN-OFF (Wave-2 W2I1). This is the DRAFT default set of
60
+ * acceptance criteria that define "code quality-pass" BEYOND tests-green. It is
61
+ * implemented behind this clearly-named default so the operator can confirm/adjust
62
+ * the criteria + their severities WITHOUT a code change to the loop. The picks
63
+ * below are a conservative, subject-agnostic starting point — NOT a final contract.
64
+ *
65
+ * The five drafted criteria (each BINARY, critique-before-verdict):
66
+ * Q1 spec-faithful (critical) — the impl realizes the agentspec DEFINITION the
67
+ * amend targeted; no dropped tool / JTBD / silently
68
+ * altered system_prompt (def→impl cascade held).
69
+ * Q2 addresses-diagnosis (high) — the change actually targets the diagnosed
70
+ * root-cause locus, not an adjacent symptom.
71
+ * Q3 no-regression (high) — no unrelated behavior removed/broken; scope stayed
72
+ * on what the remedy named (scope discipline).
73
+ * Q4 error-handling (high) — failure paths are handled, not silently dropped
74
+ * (recovery/observability preserved).
75
+ * Q5 maintainable (medium) — readable, framework-idiomatic, no dead code /
76
+ * copy-paste / obvious footgun introduced.
77
+ *
78
+ * Aggregation (also FLAGGED): the gate is severity-gated — a `fail` OR `uncertain`
79
+ * on any CRITICAL/HIGH criterion blocks the quality-pass; MEDIUM is advisory. The
80
+ * operator may instead choose strict-all-pass or a different severity map.
81
+ */
82
+ export const DEFAULT_CODE_QUALITY_CRITERIA: readonly CodeQualityCriterion[] = [
83
+ {
84
+ id: "Q1-spec-faithful",
85
+ statement: "The implementation realizes the agentspec definition the amend targeted.",
86
+ passDefinition:
87
+ "PASS: every spec-declared tool / job-to-be-done is present and the operative system_prompt is unchanged except where the amend intended; the def→impl cascade held.",
88
+ failDefinition:
89
+ "FAIL: a spec-declared tool or JTBD is missing, or the system_prompt was altered beyond the amend intent (the impl forked from the SSoT).",
90
+ severity: CodeQualitySeverity.Critical,
91
+ },
92
+ {
93
+ id: "Q2-addresses-diagnosis",
94
+ statement: "The change targets the diagnosed root-cause locus, not an adjacent symptom.",
95
+ passDefinition:
96
+ "PASS: the edit is at (or directly causes a fix at) the RCA-named locus / the failing-case root.",
97
+ failDefinition:
98
+ "FAIL: the change edits an unrelated place or patches a symptom while the root cause remains.",
99
+ severity: CodeQualitySeverity.High,
100
+ },
101
+ {
102
+ id: "Q3-no-regression",
103
+ statement: "The change introduces no unrelated regression and stays in scope.",
104
+ passDefinition:
105
+ "PASS: only the remedy-named surface changed; no unrelated behavior was removed or broken.",
106
+ failDefinition:
107
+ "FAIL: unrelated code was changed/removed, or a previously-working path was broken.",
108
+ severity: CodeQualitySeverity.High,
109
+ },
110
+ {
111
+ id: "Q4-error-handling",
112
+ statement: "Failure paths are handled, not silently dropped.",
113
+ passDefinition:
114
+ "PASS: error/failure branches surface, recover, or log per the subject's contract; no new silent drop.",
115
+ failDefinition:
116
+ "FAIL: a failure is swallowed with no recovery/notify/observability (a silent-drop regression).",
117
+ severity: CodeQualitySeverity.High,
118
+ },
119
+ {
120
+ id: "Q5-maintainable",
121
+ statement: "The code is readable, framework-idiomatic, and free of obvious footguns.",
122
+ passDefinition:
123
+ "PASS: clear structure, idiomatic to the target framework, no dead code / copy-paste / obvious footgun introduced.",
124
+ failDefinition:
125
+ "FAIL: unreadable/duplicated/dead code or an obvious maintainability footgun was introduced.",
126
+ severity: CodeQualitySeverity.Medium,
127
+ },
128
+ ] as const;
129
+
130
+ /** The code subject under quality review (minimal, subject-agnostic). */
131
+ export interface CodeQualitySubject {
132
+ /** Subject id (spec_id / target name) — provenance only. */
133
+ subjectId: string;
134
+ /** The target framework/harness the impl was built into (context for the judge). */
135
+ framework: string;
136
+ /** A compact summary of the agentspec DEFINITION the impl must realize (the SSoT). */
137
+ specSummary: string;
138
+ /** The applied change under review — the amend diff, or the scaffold summary. */
139
+ appliedChange: string;
140
+ /**
141
+ * The diagnosed root-cause / remedy locus this change was meant to address (feeds
142
+ * Q2). Empty for a fresh scaffold with no prior diagnosis.
143
+ */
144
+ diagnosisLocus?: string;
145
+ }
146
+
147
+ /** One criterion's judged result (critique-before-verdict, binary). */
148
+ export interface CodeQualityCriterionVerdict {
149
+ criterionId: string;
150
+ severity: CodeQualitySeverityValue;
151
+ result: CritiqueVerdict["result"]; // pass | fail | uncertain
152
+ critique: string;
153
+ confidence: number;
154
+ }
155
+
156
+ /** The aggregated code-quality verdict for one subject. */
157
+ export interface CodeQualityVerdict {
158
+ subjectId: string;
159
+ /** The binary quality gate — the (b) half of the ⑤ code-target BOTH-gate. */
160
+ pass: boolean;
161
+ perCriterion: CodeQualityCriterionVerdict[];
162
+ /** C-PIN provenance stamped on the verdict (byte-identity guarantee). */
163
+ pinned: JudgePin;
164
+ }
165
+
166
+ /**
167
+ * Render the code-quality judge prompt for ONE criterion — the provider-callable
168
+ * mirror of the `#mode-judge-code-quality` rubric. Critique-BEFORE-verdict, strictly
169
+ * BINARY (no Likert), judge-only. No decision is made here.
170
+ */
171
+ export function buildCodeQualityPrompt(
172
+ criterion: CodeQualityCriterion,
173
+ subject: CodeQualitySubject,
174
+ ): { system: string; user: string } {
175
+ const system = [
176
+ "You are a BINARY Pass/Fail code-QUALITY judge for ONE criterion of a CODE",
177
+ "subject (an agent/tooling implementation). Judge exactly this criterion and",
178
+ "nothing else. You are a JUDGE ONLY — you NEVER propose or write a fix.",
179
+ "",
180
+ "Quality is assessed BEYOND the subject's test suite (tests-green is a separate",
181
+ "deterministic gate). Judge the code as written against the criterion.",
182
+ "",
183
+ `Criterion (${criterion.id}, severity ${criterion.severity}): ${criterion.statement}`,
184
+ criterion.passDefinition,
185
+ criterion.failDefinition,
186
+ "",
187
+ "Outcomes are strictly BINARY: pass or fail (use uncertain ONLY when the change",
188
+ "genuinely lacks the evidence to decide). NO Likert scales, NO 1-5 / letter",
189
+ "grades, NO partial credit.",
190
+ "",
191
+ "Output STRICT JSON with the critique BEFORE the verdict (reason first, then",
192
+ "commit):",
193
+ '{ "critique": "<evidence-citing assessment>", "result": "pass"|"fail"|"uncertain",',
194
+ ' "confidence": <0..1> }',
195
+ ].join("\n");
196
+
197
+ const user = [
198
+ `Subject: ${subject.subjectId} · target framework: ${subject.framework}`,
199
+ "",
200
+ "Agentspec definition the implementation must realize (the SSoT):",
201
+ subject.specSummary,
202
+ "",
203
+ subject.diagnosisLocus !== undefined && subject.diagnosisLocus.length > 0
204
+ ? `Diagnosed root-cause / remedy locus (for the addresses-diagnosis check):\n${subject.diagnosisLocus}\n`
205
+ : "",
206
+ "Applied change under review (diff or scaffold summary):",
207
+ subject.appliedChange,
208
+ ]
209
+ .filter((l) => l !== "")
210
+ .join("\n");
211
+
212
+ return { system, user };
213
+ }
214
+
215
+ /**
216
+ * The PURE aggregation: severity-gate the per-criterion verdicts into ONE binary
217
+ * quality-pass. FLAGGED default rule — a `fail` OR `uncertain` on any CRITICAL/HIGH
218
+ * criterion blocks the pass; MEDIUM is advisory (never blocks). Deterministic.
219
+ */
220
+ export function codeQualityGatePass(
221
+ perCriterion: readonly CodeQualityCriterionVerdict[],
222
+ ): boolean {
223
+ for (const v of perCriterion) {
224
+ const gating =
225
+ v.severity === CodeQualitySeverity.Critical || v.severity === CodeQualitySeverity.High;
226
+ if (gating && v.result !== "pass") return false;
227
+ }
228
+ return true;
229
+ }
230
+
231
+ /**
232
+ * Run the code-quality judge over the criteria set under a PINNED model. THROWS if
233
+ * the pin is not (modelId present AND temperature === 0) — C-PIN (byte-identical
234
+ * reruns). Renders each criterion → the injected `JudgeInvoke` seam → parses
235
+ * (critique-before-verdict) → severity-gated aggregate. Under the DEFAULT
236
+ * agent-dispatch substrate the verdicts instead come from a dispatched
237
+ * `#mode-judge-code-quality` subagent (verdict files) and this wrapper is not used.
238
+ * Deterministic given (subject, judge, pin, criteria).
239
+ */
240
+ export async function runCodeQualityJudge(
241
+ subject: CodeQualitySubject,
242
+ judge: JudgeInvoke,
243
+ pin: JudgePin,
244
+ criteria: readonly CodeQualityCriterion[] = DEFAULT_CODE_QUALITY_CRITERIA,
245
+ ): Promise<CodeQualityVerdict> {
246
+ if (typeof pin.modelId !== "string" || pin.modelId.length === 0) {
247
+ throw new Error(
248
+ "runCodeQualityJudge: judge is not pinned (missing modelId). MODEL INTENT IS " +
249
+ "SACRED — a non-pinned judge can never produce a verdict (C-PIN).",
250
+ );
251
+ }
252
+ if (pin.temperature !== 0) {
253
+ throw new Error(
254
+ `runCodeQualityJudge: judge temperature=${pin.temperature} (!= 0) — not pinned. ` +
255
+ "MODEL INTENT IS SACRED: reruns must be byte-identical (C-PIN); refusing.",
256
+ );
257
+ }
258
+ if (criteria.length === 0) {
259
+ throw new Error(
260
+ "runCodeQualityJudge: empty criteria set — a code-quality verdict with no " +
261
+ "criteria would vacuously pass (a false-green). Refusing.",
262
+ );
263
+ }
264
+
265
+ const perCriterion: CodeQualityCriterionVerdict[] = [];
266
+ for (const criterion of criteria) {
267
+ const { system, user } = buildCodeQualityPrompt(criterion, subject);
268
+ const raw = await judge(system, user);
269
+ const v = parseCritiqueVerdict(raw);
270
+ perCriterion.push({
271
+ criterionId: criterion.id,
272
+ severity: criterion.severity,
273
+ result: v.result,
274
+ critique: v.critique,
275
+ confidence: v.confidence,
276
+ });
277
+ }
278
+
279
+ return {
280
+ subjectId: subject.subjectId,
281
+ pass: codeQualityGatePass(perCriterion),
282
+ perCriterion,
283
+ pinned: { modelId: pin.modelId, temperature: pin.temperature },
284
+ };
285
+ }