@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.
- package/.claude/skills/mutagent-evaluator/SKILL.md +19 -7
- package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +2 -2
- package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +65 -1
- package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +1 -0
- package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +20 -7
- package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +16 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +14 -7
- package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +18 -2
- package/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +285 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +45 -9
- package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +63 -8
- package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +21 -6
- package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +12 -2
- package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +114 -18
- package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +6 -2
- package/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
- package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +1 -1
- package/bin/mutagent-cli.mjs +698 -7
- 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.
|
|
16
|
-
|
|
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.
|
|
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)
|
|
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:
|
|
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
|
-
> **
|
|
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 (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
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>`.
|
|
20
|
-
* one `default:true`,
|
|
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
|
+
}
|