@mutagent/evaluator 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +17 -6
- package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +2 -2
- package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1 -1
- 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 +8 -4
- package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +18 -2
- 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/render-discover-report.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +21 -6
- package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +6 -2
- package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +1 -1
- package/bin/mutagent-cli.mjs +48 -4
- package/package.json +2 -2
|
@@ -12,8 +12,11 @@ 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.
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -870,7 +870,7 @@ this inline section is now its canonical home.
|
|
|
870
870
|
the FAILURE + UNCERTAIN traces into DATASET CANDIDATES (`collectDatasetCandidates`, reusing the
|
|
871
871
|
derive-dataset selectors) and folds in the `*evaluate` judge's node-2.5 unmatched-detection
|
|
872
872
|
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**
|
|
873
|
+
7. **Render the discover `discovery-report.html` via the SHIPPED `writeDiscoverRunReport` composer**
|
|
874
874
|
(`render-discover-report.ts`) — pass the run dir; it builds `triage-summary.json` from
|
|
875
875
|
`triage.json` + wires every companion (verdicts → Proof-of-work, dataset-candidates → Dataset,
|
|
876
876
|
triage census → coverage funnel, profile → entity hero) so the report is COMPLETE BY DEFAULT.
|
|
@@ -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
|
}
|
|
@@ -30,6 +30,7 @@ import { runDeterministic } 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, "..", "..");
|
|
@@ -120,10 +121,13 @@ export function main(argvIn?: string[]): number {
|
|
|
120
121
|
// 5. Render
|
|
121
122
|
const html = renderReport(scorecard);
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
124
|
+
// Output → the UNIFIED evaluator reports root (PRD R-3): reports/<runId>/master-audit.html
|
|
125
|
+
// (was under .mutagent/diagnostics/<runId>/audit/ — the wrong stage root). bundleDir stays the
|
|
126
|
+
// INPUT (the run-bundle being audited); the report is written to the evaluator namespace.
|
|
127
|
+
const outDir = args.outDir ?? reportDir(args.runId);
|
|
128
|
+
mkdirSync(outDir, { recursive: true });
|
|
129
|
+
const scPath = join(outDir, "master-audit.scorecard.json");
|
|
130
|
+
const htmlPath = join(outDir, "master-audit.html");
|
|
127
131
|
const scJson = args.emitMasked
|
|
128
132
|
? maskedCanonicalJson(scorecard)
|
|
129
133
|
: 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(
|
|
@@ -93,16 +93,29 @@ export function detectLegacyConfig(obj: unknown): string[] {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* How the evaluator's source was bound (or why it couldn't be). The SELECTION
|
|
96
|
-
* contract (v0.2.
|
|
97
|
-
* bound — exactly one source resolved (single entry ·
|
|
96
|
+
* contract (v0.2.2 — parity with orchestrator + diagnostics; F2 disambiguation UX):
|
|
97
|
+
* bound — exactly one source resolved with NO ambiguity (single entry ·
|
|
98
|
+
* `--source` hit). No prompt.
|
|
99
|
+
* confirm-default — >1 source with exactly one `default:true` (F2). The default is
|
|
100
|
+
* PRESELECTED (`source`) but the caller ISSUES A CONFIRM ASK before
|
|
101
|
+
* using it (distinct from a silent bind — be explicit around ambiguity).
|
|
102
|
+
* NON-fatal + bindable: `source` is usable as-is if the operator accepts
|
|
103
|
+
* or the run is non-interactive (`--source` / CI). Carries the full
|
|
104
|
+
* candidate list so the confirm can offer an override.
|
|
98
105
|
* none — zero sources (DISCOVER needs a pick; code/dataset runs don't need a source).
|
|
99
106
|
* needs-selection — >1 source, none `default:true`, no `--source` ⇒ the caller must PROMPT the
|
|
100
|
-
* operator to pick (NON-fatal — the source EXISTS,
|
|
107
|
+
* operator to pick with NO preselection (NON-fatal — the source EXISTS,
|
|
108
|
+
* only the pick is missing).
|
|
101
109
|
* multiple-defaults— >1 source with `default:true` — an AMBIGUOUS config the operator must fix.
|
|
102
110
|
* unknown-name — an explicit `--source <name>` that matched no catalog entry.
|
|
111
|
+
*
|
|
112
|
+
* confirm-default vs needs-selection: BOTH defer a run-time ASK, but confirm-default
|
|
113
|
+
* carries a PRESELECTED default (a yes/no confirm) whereas needs-selection has none (an
|
|
114
|
+
* open pick). Both are bindable-in-principle; the distinction drives the ASK shape.
|
|
103
115
|
*/
|
|
104
116
|
export type SourceBinding =
|
|
105
117
|
| { kind: "bound"; source: GlobalSource }
|
|
118
|
+
| { kind: "confirm-default"; source: GlobalSource; sources: GlobalSource[] }
|
|
106
119
|
| { kind: "none" }
|
|
107
120
|
| { kind: "needs-selection"; sources: GlobalSource[] }
|
|
108
121
|
| { kind: "multiple-defaults"; sources: GlobalSource[] }
|
|
@@ -128,6 +141,17 @@ export interface EvaluatorConfigResolved {
|
|
|
128
141
|
evaluatorContext: ContextLink[];
|
|
129
142
|
/** present targets (evaluator is judge-only — carried for completeness only). */
|
|
130
143
|
targets: GlobalTarget[];
|
|
144
|
+
/** PRD R-4 — the admin default render audience for the evaluation report. Client-by-default
|
|
145
|
+
* (leak-safe); an admin sets `internal` in config.yaml to show the §5 Self-Eval tab. */
|
|
146
|
+
defaultAudience: "client" | "internal";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** PRD R-4 — resolve the effective render audience: explicit flag → config → "client". */
|
|
150
|
+
export function resolveEvalAudience(
|
|
151
|
+
explicit: "client" | "internal" | undefined,
|
|
152
|
+
config?: { defaultAudience?: "client" | "internal" } | undefined,
|
|
153
|
+
): "client" | "internal" {
|
|
154
|
+
return explicit ?? config?.defaultAudience ?? "client";
|
|
131
155
|
}
|
|
132
156
|
|
|
133
157
|
export type LoadEvaluatorConfigResult =
|
|
@@ -142,14 +166,16 @@ export type LoadEvaluatorConfigResult =
|
|
|
142
166
|
*
|
|
143
167
|
* 1. explicit `selectName` (`--source`) WINS — find by name; miss ⇒ `unknown-name`.
|
|
144
168
|
* 2. 0 sources ⇒ `none`.
|
|
145
|
-
* 3. 1 source ⇒ `bound` (auto-bind).
|
|
169
|
+
* 3. 1 source ⇒ `bound` (auto-bind — no ambiguity, no prompt).
|
|
146
170
|
* 4. >1 sources:
|
|
147
|
-
* exactly one `default:true` ⇒ `
|
|
171
|
+
* exactly one `default:true` ⇒ `confirm-default` (F2 — preselected default; caller
|
|
172
|
+
* CONFIRMS before use, not a silent bind).
|
|
148
173
|
* >1 `default:true` ⇒ `multiple-defaults` (ambiguous — operator fixes config).
|
|
149
|
-
* 0 `default:true` ⇒ `needs-selection` (caller PROMPTS the operator).
|
|
174
|
+
* 0 `default:true` ⇒ `needs-selection` (caller PROMPTS the operator — open pick).
|
|
150
175
|
*
|
|
151
|
-
* Pure; no clock/random/network. NON-fatal on `needs-selection` —
|
|
152
|
-
* exists
|
|
176
|
+
* Pure; no clock/random/network. NON-fatal on `confirm-default`/`needs-selection` —
|
|
177
|
+
* the source exists; only the disambiguation confirm/pick is deferred to the run-start ASK.
|
|
178
|
+
* `--source` (explicit) always short-circuits both, satisfying the non-interactive/CI escape.
|
|
153
179
|
*/
|
|
154
180
|
function bindSourceByRole(
|
|
155
181
|
sources: GlobalSource[],
|
|
@@ -167,7 +193,11 @@ function bindSourceByRole(
|
|
|
167
193
|
if (sources.length === 1) return { kind: "bound", source: sources[0] as GlobalSource };
|
|
168
194
|
// (4) — multiple entries; disambiguate by `default:true`.
|
|
169
195
|
const defaults = sources.filter((s) => s.default === true);
|
|
170
|
-
|
|
196
|
+
// F2: a lone default is PRESELECTED but CONFIRMED (be explicit around ambiguity) —
|
|
197
|
+
// it is NOT silently bound. The caller (parent session) issues a yes/no confirm ask.
|
|
198
|
+
if (defaults.length === 1) {
|
|
199
|
+
return { kind: "confirm-default", source: defaults[0] as GlobalSource, sources };
|
|
200
|
+
}
|
|
171
201
|
if (defaults.length > 1) return { kind: "multiple-defaults", sources };
|
|
172
202
|
return { kind: "needs-selection", sources };
|
|
173
203
|
}
|
|
@@ -266,6 +296,11 @@ export function loadEvaluatorConfig(
|
|
|
266
296
|
typeof evaluatorSection.judge_runtime === "string" && evaluatorSection.judge_runtime !== ""
|
|
267
297
|
? evaluatorSection.judge_runtime
|
|
268
298
|
: undefined;
|
|
299
|
+
// PRD R-4 — admin audience override. Precedence: lifecycle.evaluator.default_audience >
|
|
300
|
+
// global.default_audience > "client". Only "internal" flips it; anything else stays client.
|
|
301
|
+
const rawAudience =
|
|
302
|
+
evaluatorSection.default_audience ?? globalBlock.default_audience;
|
|
303
|
+
const defaultAudience: "client" | "internal" = rawAudience === "internal" ? "internal" : "client";
|
|
269
304
|
|
|
270
305
|
return {
|
|
271
306
|
status: "ok",
|
|
@@ -278,6 +313,7 @@ export function loadEvaluatorConfig(
|
|
|
278
313
|
globalContext,
|
|
279
314
|
evaluatorContext,
|
|
280
315
|
targets,
|
|
316
|
+
defaultAudience,
|
|
281
317
|
},
|
|
282
318
|
};
|
|
283
319
|
}
|
|
@@ -45,7 +45,12 @@ export const LEGACY_CONFIG_VERSIONS = ["0.1.0"] as const;
|
|
|
45
45
|
|
|
46
46
|
// ── Categorical constants (no magic strings — coding-rules) ──────────────────
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Source platforms a `global.sources[]` entry can pull traces from.
|
|
50
|
+
* CONSUMER: the trace-fetch layer (`mutagent-cli trace fetch`, run by Helix BEFORE
|
|
51
|
+
* dispatch — the evaluator never fetches) selects the adapter by this value.
|
|
52
|
+
* PURPOSE: names WHERE the subject's traces live so they can be normalized to UniTF.
|
|
53
|
+
*/
|
|
49
54
|
export const SourcePlatform = {
|
|
50
55
|
Langfuse: "langfuse",
|
|
51
56
|
Otel: "otel",
|
|
@@ -56,12 +61,21 @@ export const SourcePlatform = {
|
|
|
56
61
|
export type SourcePlatformValue =
|
|
57
62
|
(typeof SourcePlatform)[keyof typeof SourcePlatform];
|
|
58
63
|
|
|
59
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Normalizer format hint for a source (how to parse the records).
|
|
66
|
+
* CONSUMER: the trace-fetch/normalize layer (`@mutagent/tools` adapters) — picks the
|
|
67
|
+
* parser for a source's records. PURPOSE: disambiguates record shape within a platform.
|
|
68
|
+
* `unitf` (F1) — the records are ALREADY conformant UniTF jsonl (e.g. emitted by the
|
|
69
|
+
* code-run capture WRAP, `captureUniTF` in `@mutagent/tools`). The fetch layer then
|
|
70
|
+
* SKIPS per-platform normalization and reads the jsonl DIRECTLY. Pairs with
|
|
71
|
+
* `platform: local-jsonl` + `paths:[…]`. PARITY PORT of the orchestrator's SourceFormat.
|
|
72
|
+
*/
|
|
60
73
|
export const SourceFormat = {
|
|
61
74
|
LangfuseExport: "langfuse-export",
|
|
62
75
|
ClaudeCode: "claude-code",
|
|
63
76
|
Codex: "codex",
|
|
64
77
|
Raw: "raw",
|
|
78
|
+
Unitf: "unitf",
|
|
65
79
|
} as const;
|
|
66
80
|
export type SourceFormatValue = (typeof SourceFormat)[keyof typeof SourceFormat];
|
|
67
81
|
|
|
@@ -137,7 +151,16 @@ export const ContextLinkSchema = Type.Object(
|
|
|
137
151
|
);
|
|
138
152
|
export type ContextLink = Static<typeof ContextLinkSchema>;
|
|
139
153
|
|
|
140
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* The default model + the pinned judge model (`judge_model` — was `pinned_judge`).
|
|
156
|
+
* CONSUMER: config/load.ts `resolveJudgeModel` → prep.ts `--model` gap-fill.
|
|
157
|
+
* default — the project-wide fallback judge model. PURPOSE: used ONLY when
|
|
158
|
+
* `judge_model` is absent (`from: "default"`). ASYMMETRY: diagnostics
|
|
159
|
+
* IGNORES `models.default` for its judge (it pins its own) — the field
|
|
160
|
+
* is a shared config key that the two skills consume differently.
|
|
161
|
+
* judge_model — the C-PIN pinned judge (model-intent-sacred; temp pinned 0). PURPOSE:
|
|
162
|
+
* the authoritative judge model; wins over `default` (`from: "judge_model"`).
|
|
163
|
+
*/
|
|
141
164
|
export const ModelsSchema = Type.Object(
|
|
142
165
|
{
|
|
143
166
|
default: Type.Optional(Type.String({ minLength: 1 })),
|
|
@@ -154,7 +177,10 @@ export type Models = Static<typeof ModelsSchema>;
|
|
|
154
177
|
*/
|
|
155
178
|
export const GlobalSourceSchema = Type.Object(
|
|
156
179
|
{
|
|
180
|
+
// name — the catalog key. CONSUMER: config/load.ts `bindSourceByRole` (matched by
|
|
181
|
+
// `--source <name>`) + prep.ts surface messages. PURPOSE: stable id for selection.
|
|
157
182
|
name: Type.String({ minLength: 1 }),
|
|
183
|
+
// platform — see SourcePlatform. CONSUMER: the fetch/normalize adapter selector.
|
|
158
184
|
platform: Type.Union([
|
|
159
185
|
Type.Literal(SourcePlatform.Langfuse),
|
|
160
186
|
Type.Literal(SourcePlatform.Otel),
|
|
@@ -162,19 +188,34 @@ export const GlobalSourceSchema = Type.Object(
|
|
|
162
188
|
Type.Literal(SourcePlatform.ClaudeCode),
|
|
163
189
|
Type.Literal(SourcePlatform.Codex),
|
|
164
190
|
]),
|
|
191
|
+
// project — remote-source project/scope id. CONSUMER: the remote fetch adapter
|
|
192
|
+
// (e.g. Langfuse project). PURPOSE: scopes a pull on a multi-project backend.
|
|
165
193
|
project: Type.Optional(Type.String({ minLength: 1 })),
|
|
194
|
+
// endpoint — remote base URL. CONSUMER: the remote fetch adapter. PURPOSE: where to
|
|
195
|
+
// pull from (mutually exclusive with `paths`; onboarding enforces, schema tolerates).
|
|
166
196
|
endpoint: Type.Optional(Type.String({ minLength: 1 })),
|
|
197
|
+
// credential_ref — env-var NAME / {env,path} (NEVER a secret). CONSUMER: resolve-
|
|
198
|
+
// credential.ts at fetch-time. PURPOSE: names the key for a remote pull.
|
|
167
199
|
credential_ref: Type.Optional(CredentialRefSchema),
|
|
200
|
+
// paths — file-source globs. CONSUMER: the local-jsonl fetch adapter. PURPOSE: the
|
|
201
|
+
// on-disk jsonl location (pairs with `platform: local-jsonl`; see `format: unitf`).
|
|
168
202
|
paths: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
203
|
+
// format — see SourceFormat. CONSUMER: the normalize step (`unitf` ⇒ skip normalize,
|
|
204
|
+
// read jsonl direct). PURPOSE: record-shape hint within a platform.
|
|
169
205
|
format: Type.Optional(
|
|
170
206
|
Type.Union([
|
|
171
207
|
Type.Literal(SourceFormat.LangfuseExport),
|
|
172
208
|
Type.Literal(SourceFormat.ClaudeCode),
|
|
173
209
|
Type.Literal(SourceFormat.Codex),
|
|
174
210
|
Type.Literal(SourceFormat.Raw),
|
|
211
|
+
Type.Literal(SourceFormat.Unitf),
|
|
175
212
|
]),
|
|
176
213
|
),
|
|
214
|
+
// agent_field — record path naming the agent/subject. CONSUMER: the normalizer's
|
|
215
|
+
// subject-grouping. PURPOSE: which field identifies the agent when a trace is multi-agent.
|
|
177
216
|
agent_field: Type.Optional(Type.String({ minLength: 1 })),
|
|
217
|
+
// latency_unit — see LatencyUnit (`auto` = infer). CONSUMER: the normalizer's
|
|
218
|
+
// latency projection. PURPOSE: overrides ambiguous ms-vs-s latency records.
|
|
178
219
|
latency_unit: Type.Optional(
|
|
179
220
|
Type.Union([
|
|
180
221
|
Type.Literal(LatencyUnit.Auto),
|
|
@@ -182,16 +223,23 @@ export const GlobalSourceSchema = Type.Object(
|
|
|
182
223
|
Type.Literal(LatencyUnit.S),
|
|
183
224
|
]),
|
|
184
225
|
),
|
|
185
|
-
// PARITY (orchestrator SourceSchema): the catalog's DEFAULT pick when >1
|
|
186
|
-
// exists
|
|
187
|
-
// ⇒
|
|
226
|
+
// default — PARITY (orchestrator SourceSchema): the catalog's DEFAULT pick when >1
|
|
227
|
+
// entry exists. CONSUMER: config/load.ts `bindSourceByRole` — exactly one `default:true`
|
|
228
|
+
// ⇒ `confirm-default` (F2: preselected, operator confirms), not a silent bind. PURPOSE:
|
|
229
|
+
// marks the preferred source among many. Optional: absent ⇒ no default.
|
|
188
230
|
default: Type.Optional(Type.Boolean()),
|
|
189
231
|
},
|
|
190
232
|
{ additionalProperties: false },
|
|
191
233
|
);
|
|
192
234
|
export type GlobalSource = Static<typeof GlobalSourceSchema>;
|
|
193
235
|
|
|
194
|
-
/**
|
|
236
|
+
/**
|
|
237
|
+
* A precise link to an Agent / Tooling definition file in a code target.
|
|
238
|
+
* RESERVED — consumed by Build/Improve (future). The evaluator is JUDGE-ONLY (EV-051)
|
|
239
|
+
* and never writes a target; `targets[].code_refs` is ported for shape parity only and
|
|
240
|
+
* has NO evaluator consumer today. PURPOSE (future): tells BUILD/IMPROVE which files
|
|
241
|
+
* realize the agent so a fix lands on the right def.
|
|
242
|
+
*/
|
|
195
243
|
export const CodeRefSchema = Type.Object(
|
|
196
244
|
{
|
|
197
245
|
path: Type.String({ minLength: 1 }),
|
|
@@ -201,7 +249,14 @@ export const CodeRefSchema = Type.Object(
|
|
|
201
249
|
);
|
|
202
250
|
export type CodeRef = Static<typeof CodeRefSchema>;
|
|
203
251
|
|
|
204
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* HOW a fix is applied for a target. `kind` gates the report-only branch.
|
|
254
|
+
* CONSUMER: the diagnostics APPLY path (not the evaluator — judge-only). PURPOSE:
|
|
255
|
+
* selects the write mechanism for a fix.
|
|
256
|
+
* versioning / pr — RESERVED, consumed by Build/Improve (future). No evaluator
|
|
257
|
+
* consumer; ported for parity. PURPOSE (future): toggle spec-bump /
|
|
258
|
+
* open-a-PR on apply.
|
|
259
|
+
*/
|
|
205
260
|
export const ApplySchema = Type.Object(
|
|
206
261
|
{
|
|
207
262
|
kind: Type.Union([
|
|
@@ -31,7 +31,7 @@ export const WELL_KNOWN_ARTIFACTS: Readonly<Record<string, string>> = {
|
|
|
31
31
|
tracesMetadata: "traces-metadata.json",
|
|
32
32
|
entityContext: "entity-context.json",
|
|
33
33
|
renderInput: "render-input.json",
|
|
34
|
-
report: "report.html",
|
|
34
|
+
report: "evaluation-report.html",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/** Logical names whose VALUE is a sub-directory of evidence files. */
|
|
@@ -1566,7 +1566,7 @@ export function writeDiscoverRunReport(
|
|
|
1566
1566
|
return writeDiscoverReportFromFiles(
|
|
1567
1567
|
{
|
|
1568
1568
|
criteriaPath: join(dir, "criteria.json"),
|
|
1569
|
-
outPath: join(dir, "report.html"),
|
|
1569
|
+
outPath: join(dir, "discovery-report.html"),
|
|
1570
1570
|
groundingPath: join(dir, "grounding-check.json"),
|
|
1571
1571
|
triagePath: triageSummaryPath,
|
|
1572
1572
|
verdictsDir: join(dir, "verdicts"),
|
|
@@ -358,6 +358,9 @@ export interface CriterionResolution {
|
|
|
358
358
|
|
|
359
359
|
export interface EvalReportInput {
|
|
360
360
|
subject: { name: string; org?: string; source?: string; models?: string[] };
|
|
361
|
+
/** PRD R-4 — render audience. Default "client" (leak-safe): the §5 Self-Eval [INTERNAL]
|
|
362
|
+
* tab + nav button are NOT emitted. "internal" (admin opt-in) shows them. */
|
|
363
|
+
audience?: "client" | "internal";
|
|
361
364
|
/** the v2 *evaluate Scorecard (GATE + variance). */
|
|
362
365
|
scorecard: Scorecard;
|
|
363
366
|
/** the folded per-criterion verdicts. */
|
|
@@ -3479,6 +3482,16 @@ export function renderEvalReport(input: EvalReportInput): string {
|
|
|
3479
3482
|
const theme = readFileSync(join(BRAND_DIR, "theme.css"), "utf8");
|
|
3480
3483
|
const wordmark = readFileSync(join(BRAND_DIR, "wordmark.html"), "utf8");
|
|
3481
3484
|
|
|
3485
|
+
// PRD R-4 — audience gate. CLIENT is the DEFAULT (leak-safe): the §5 Self-Eval [INTERNAL]
|
|
3486
|
+
// tab + nav button are NOT emitted into the HTML at all (NODE-STRIP, not CSS-hidden).
|
|
3487
|
+
const internal = (input.audience ?? "client") === "internal";
|
|
3488
|
+
const selfEvalNav = internal
|
|
3489
|
+
? ` <button class="tab-btn internal" data-tab="t5">⑤ Self-Eval [INTERNAL]</button>\n`
|
|
3490
|
+
: "";
|
|
3491
|
+
const selfEvalPanel = internal
|
|
3492
|
+
? ` <section class="panel" id="t5">${selfEvalTab(input)}</section>\n`
|
|
3493
|
+
: "";
|
|
3494
|
+
|
|
3482
3495
|
const headerTitle = "evaluator · Eval Report";
|
|
3483
3496
|
const headerMeta =
|
|
3484
3497
|
`<span class="mk">subject</span> <span class="mv">${esc(input.subject.name)}</span>` +
|
|
@@ -3564,15 +3577,13 @@ ${header}
|
|
|
3564
3577
|
<button class="tab-btn" data-tab="t2">② Trajectory · Judge Behaviour</button>
|
|
3565
3578
|
<button class="tab-btn" data-tab="t3">③ Eval Scorecard</button>
|
|
3566
3579
|
<button class="tab-btn" data-tab="t4">④ Findings</button>
|
|
3567
|
-
|
|
3568
|
-
</nav>
|
|
3580
|
+
${selfEvalNav}</nav>
|
|
3569
3581
|
<main>
|
|
3570
3582
|
<section class="panel active" id="t1">${overviewTab(input)}</section>
|
|
3571
3583
|
<section class="panel" id="t2">${trajectoryTab(input)}</section>
|
|
3572
3584
|
<section class="panel" id="t3">${scorecardTab(input)}</section>
|
|
3573
3585
|
<section class="panel" id="t4">${findingsTab(input)}</section>
|
|
3574
|
-
|
|
3575
|
-
</main>
|
|
3586
|
+
${selfEvalPanel}</main>
|
|
3576
3587
|
<script data-pii="ledger">
|
|
3577
3588
|
${clientScript(ledgerJson, critJson, walksJson, resMetaJson)}
|
|
3578
3589
|
</script>
|
|
@@ -3635,6 +3646,8 @@ export function writeEvalReportFromFiles(
|
|
|
3635
3646
|
* one trace off the raw ndjson and passes it here; the system prompt is identical
|
|
3636
3647
|
* across the batch so one trace suffices. ABSENT ⇒ system prompt stays UNAVAILABLE. */
|
|
3637
3648
|
traces?: EvalTrace[];
|
|
3649
|
+
/** PRD R-4 — render audience (default "client", leak-safe). "internal" shows §5. */
|
|
3650
|
+
audience?: "client" | "internal";
|
|
3638
3651
|
},
|
|
3639
3652
|
io: EvalReportFilesIO,
|
|
3640
3653
|
): { report: string; completeness: EvalReportCompleteness } {
|
|
@@ -3746,6 +3759,8 @@ export function writeEvalReportFromFiles(
|
|
|
3746
3759
|
// WS-6 — forward the trace(s) so the system prompt is reconstructed for the hero.
|
|
3747
3760
|
...(params.traces !== undefined ? { traces: params.traces } : {}),
|
|
3748
3761
|
});
|
|
3762
|
+
// PRD R-4 — set audience on the built input (buildEvalReportInput does not forward it).
|
|
3763
|
+
if (params.audience !== undefined) input.audience = params.audience;
|
|
3749
3764
|
io.writeFile(params.outPath, renderEvalReport(input));
|
|
3750
3765
|
|
|
3751
3766
|
// 5) the data-completeness probe over the BOUND ledger (what the UI will render).
|
|
@@ -3803,9 +3818,9 @@ async function main(): Promise<void> {
|
|
|
3803
3818
|
const { join: pjoin } = await import("node:path");
|
|
3804
3819
|
const input = JSON.parse(rf(inputPath, "utf8")) as EvalReportInput;
|
|
3805
3820
|
// P8: a bare run DEFAULTS the report under the localized dot-root
|
|
3806
|
-
// `.mutagent/evaluator/reports/<runId>/report.html` (never /tmp or .memory).
|
|
3821
|
+
// `.mutagent/evaluator/reports/<runId>/evaluation-report.html` (never /tmp or .memory).
|
|
3807
3822
|
const runId = runIdArg ?? input.subject.name;
|
|
3808
|
-
const outPath = outArg ?? pjoin(reportDir(runId), "report.html");
|
|
3823
|
+
const outPath = outArg ?? pjoin(reportDir(runId), "evaluation-report.html");
|
|
3809
3824
|
mkdirSync(pjoin(outPath, ".."), { recursive: true });
|
|
3810
3825
|
console.info(renderEvalCards(input));
|
|
3811
3826
|
writeFileSync(outPath, renderEvalReport(input));
|
|
@@ -542,7 +542,7 @@ export function readRunVerdictFiles(input: EvaluateRunInput): MatrixVerdictFile[
|
|
|
542
542
|
* REPORT — build the rich `EvalReportInput` from the REAL run outputs (the folded
|
|
543
543
|
* scorecard + per-criterion verdicts + the per-trajectory Judge-Agent verdict
|
|
544
544
|
* files + the matrix criteria + the EV-051 handover) and render the 5-tab HTML
|
|
545
|
-
* eval-report into `<reportDir>/report.html`. This is the wiring D-2/D-3 had been
|
|
545
|
+
* eval-report into `<reportDir>/evaluation-report.html`. This is the wiring D-2/D-3 had been
|
|
546
546
|
* MISSING: `*evaluate` produced a scorecard but emitted no report. The report
|
|
547
547
|
* consumes the §9.4 judge-walk fields when present (side-by-side) and degrades to
|
|
548
548
|
* the per-trajectory scorecard otherwise. Returns the written report path.
|
|
@@ -555,6 +555,9 @@ export function writeRunReport(
|
|
|
555
555
|
result: EvaluateRunResult,
|
|
556
556
|
runId: string,
|
|
557
557
|
cwd?: string,
|
|
558
|
+
// PRD R-4 — render audience (resolved by the caller: explicit flag → config.default_audience →
|
|
559
|
+
// "client"). Omitted ⇒ the renderer defaults to "client" (leak-safe). "internal" shows §5.
|
|
560
|
+
audience?: "client" | "internal",
|
|
558
561
|
): string {
|
|
559
562
|
const matrixVerdictFiles = readRunVerdictFiles(input);
|
|
560
563
|
const reportInput = buildEvalReportInput({
|
|
@@ -586,9 +589,10 @@ export function writeRunReport(
|
|
|
586
589
|
cPin: true,
|
|
587
590
|
},
|
|
588
591
|
});
|
|
592
|
+
if (audience) reportInput.audience = audience; // else renderEvalReport defaults to "client"
|
|
589
593
|
const dir = reportDir(runId, cwd);
|
|
590
594
|
mkdirSync(dir, { recursive: true });
|
|
591
|
-
const outPath = join(dir, "report.html");
|
|
595
|
+
const outPath = join(dir, "evaluation-report.html");
|
|
592
596
|
writeFileSync(outPath, renderEvalReport(reportInput));
|
|
593
597
|
return outPath;
|
|
594
598
|
}
|
|
@@ -47,7 +47,7 @@ phase('Deterministic spine')
|
|
|
47
47
|
const spine = await agent(
|
|
48
48
|
`Run the DETERMINISTIC spine for the master audit. Use Bash:\n` +
|
|
49
49
|
` bash ${PKG}/scripts/cli/run.sh ${PKG}/scripts/cli/audit-run.ts ${RUN_ID} --subject ${SUBJECT} --bundle-dir ${BUNDLE_DIR} --subjects-root ${PKG}/subjects --generated-at ${GEN}\n` +
|
|
50
|
-
`This writes
|
|
50
|
+
`This writes the master-audit under the unified evaluator reports root: .mutagent/evaluator/reports/<runId>/master-audit.html + master-audit.scorecard.json (two-track GATE+TREND). Return the parsed JSON the script prints (subject, runId, criteria, deterministicRows, judgeRows, gateRunPass, scorecard, report).`,
|
|
51
51
|
{ label: 'spine:audit-run', phase: 'Deterministic spine' },
|
|
52
52
|
)
|
|
53
53
|
log(`spine: ${spine && spine.criteria} criteria, ${spine && spine.deterministicRows} deterministic + ${spine && spine.judgeRows} judge rows; gateRunPass=${spine && spine.gateRunPass}`)
|
package/bin/mutagent-cli.mjs
CHANGED
|
@@ -5170,11 +5170,15 @@ var OperationIntent = {
|
|
|
5170
5170
|
Evals: "evals",
|
|
5171
5171
|
Dataset: "dataset"
|
|
5172
5172
|
};
|
|
5173
|
+
var SourceFormat = {
|
|
5174
|
+
Unitf: "unitf"
|
|
5175
|
+
};
|
|
5173
5176
|
var QuerySourceSchema = Type.Object({
|
|
5174
5177
|
endpoint_ref: Type.Optional(Type.String()),
|
|
5175
5178
|
credential_ref: Type.Optional(Type.String()),
|
|
5176
5179
|
paths: Type.Optional(Type.Array(Type.String())),
|
|
5177
|
-
root: Type.Optional(Type.String())
|
|
5180
|
+
root: Type.Optional(Type.String()),
|
|
5181
|
+
format: Type.Optional(Type.Literal(SourceFormat.Unitf))
|
|
5178
5182
|
}, { additionalProperties: false });
|
|
5179
5183
|
var QueryFiltersSchema = Type.Object({
|
|
5180
5184
|
sessionId: Type.Optional(Type.String()),
|
|
@@ -5449,6 +5453,7 @@ function parseNdjson(text) {
|
|
|
5449
5453
|
// ../mutagent-tools/src/adapters/local-ndjson.ts
|
|
5450
5454
|
function collect(ctx) {
|
|
5451
5455
|
const src = ctx.query.source ?? {};
|
|
5456
|
+
const passthrough = src.format === "unitf";
|
|
5452
5457
|
const files = discoverFiles({ paths: src.paths, root: src.root });
|
|
5453
5458
|
const traces = [];
|
|
5454
5459
|
let malformed = 0;
|
|
@@ -5465,6 +5470,13 @@ function collect(ctx) {
|
|
|
5465
5470
|
const parsed = parseNdjson(text);
|
|
5466
5471
|
malformed += parsed.malformed;
|
|
5467
5472
|
for (const rec of parsed.records) {
|
|
5473
|
+
if (passthrough) {
|
|
5474
|
+
if (validateUnifiedTrace(rec).ok)
|
|
5475
|
+
traces.push(rec);
|
|
5476
|
+
else
|
|
5477
|
+
malformed++;
|
|
5478
|
+
continue;
|
|
5479
|
+
}
|
|
5468
5480
|
const t = mapGenericRecord(rec, "local-ndjson");
|
|
5469
5481
|
if (t)
|
|
5470
5482
|
traces.push(t);
|
|
@@ -7560,6 +7572,16 @@ function buildAttributeBag(hit) {
|
|
|
7560
7572
|
if (hit.error_message !== undefined && hit.error_message !== null && hit.error_message !== "") {
|
|
7561
7573
|
bag["error.message"] = hit.error_message;
|
|
7562
7574
|
}
|
|
7575
|
+
const CC_CONTENT_COLS = ["user_prompt", "user_prompt_length", "tool_name", "full_command", "file_path", "tool_use_id"];
|
|
7576
|
+
for (const c of CC_CONTENT_COLS) {
|
|
7577
|
+
const v = hit[c];
|
|
7578
|
+
if (v !== undefined && v !== null && v !== "" && bag[c] === undefined)
|
|
7579
|
+
bag[c] = v;
|
|
7580
|
+
}
|
|
7581
|
+
const userPrompt = hit["user_prompt"];
|
|
7582
|
+
if (typeof userPrompt === "string" && userPrompt !== "" && bag["gen_ai.prompt"] === undefined && bag["gen_ai.input.messages"] === undefined) {
|
|
7583
|
+
bag["gen_ai.prompt"] = userPrompt;
|
|
7584
|
+
}
|
|
7563
7585
|
return bag;
|
|
7564
7586
|
}
|
|
7565
7587
|
function toOtelAttribute(key, value) {
|
|
@@ -7689,7 +7711,9 @@ var ASSISTANT_RESPONSE_KEYS = [
|
|
|
7689
7711
|
"assistant_response",
|
|
7690
7712
|
"completion",
|
|
7691
7713
|
"output_text",
|
|
7692
|
-
"message"
|
|
7714
|
+
"message",
|
|
7715
|
+
"text",
|
|
7716
|
+
"content"
|
|
7693
7717
|
];
|
|
7694
7718
|
function eventTimeToIso(v) {
|
|
7695
7719
|
if (typeof v === "string" && v.length > 0) {
|
|
@@ -7816,6 +7840,23 @@ function eventToSpan(hit, sessionId, index) {
|
|
|
7816
7840
|
span.attributes = { model };
|
|
7817
7841
|
break;
|
|
7818
7842
|
}
|
|
7843
|
+
case "assistant_response": {
|
|
7844
|
+
span.kind = "llm";
|
|
7845
|
+
span.role = "assistant";
|
|
7846
|
+
const response = firstStr(hit, ASSISTANT_RESPONSE_KEYS);
|
|
7847
|
+
if (response !== undefined)
|
|
7848
|
+
span.output = response;
|
|
7849
|
+
const tok = eventTokens(hit);
|
|
7850
|
+
if (tok !== undefined)
|
|
7851
|
+
span.tokens = tok;
|
|
7852
|
+
const cost = num3(hit.cost_usd);
|
|
7853
|
+
if (cost !== undefined)
|
|
7854
|
+
span.costUsd = cost;
|
|
7855
|
+
const model = str3(hit.model);
|
|
7856
|
+
if (model !== undefined)
|
|
7857
|
+
span.attributes = { model };
|
|
7858
|
+
break;
|
|
7859
|
+
}
|
|
7819
7860
|
case "tool_result": {
|
|
7820
7861
|
span.kind = "tool";
|
|
7821
7862
|
span.role = "tool";
|
|
@@ -8924,12 +8965,14 @@ function buildQuery(sub, positionals, flags) {
|
|
|
8924
8965
|
const root = getStr(flags, "root");
|
|
8925
8966
|
const endpointRef = getStr(flags, "endpoint-ref");
|
|
8926
8967
|
const credRef = getStr(flags, "credential-ref");
|
|
8927
|
-
|
|
8968
|
+
const unitfPassthrough = getStr(flags, "format") === "unitf";
|
|
8969
|
+
if (paths || root || endpointRef || credRef || unitfPassthrough) {
|
|
8928
8970
|
q.source = {
|
|
8929
8971
|
...paths ? { paths } : {},
|
|
8930
8972
|
...root ? { root } : {},
|
|
8931
8973
|
...endpointRef ? { endpoint_ref: endpointRef } : {},
|
|
8932
|
-
...credRef ? { credential_ref: credRef } : {}
|
|
8974
|
+
...credRef ? { credential_ref: credRef } : {},
|
|
8975
|
+
...unitfPassthrough ? { format: "unitf" } : {}
|
|
8933
8976
|
};
|
|
8934
8977
|
}
|
|
8935
8978
|
const exportPath = getStr(flags, "export");
|
|
@@ -8988,6 +9031,7 @@ var FLAGS_HELP = [
|
|
|
8988
9031
|
["--limit <n>", "cap the result count"],
|
|
8989
9032
|
["--page-size <n> / --cursor <c>", "pagination"],
|
|
8990
9033
|
["--paths <file> / --root <dir>", "local source discovery (repeat --paths; --root pins a dir)"],
|
|
9034
|
+
["--format unitf", "local files are ALREADY UniTF \u2014 skip normalize, pass through (validate only)"],
|
|
8991
9035
|
["--endpoint-ref / --credential-ref", "env-var NAMES for a remote source (never raw secrets)"],
|
|
8992
9036
|
["--export <path.jsonl>", "write UniTF JSONL + <path>.manifest.json (the handover artifacts)"],
|
|
8993
9037
|
["--in <unitf.jsonl>", "`select`: UniTF JSONL input (repeatable; `--in -` reads stdin)"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutagent/evaluator",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "mutagent-evaluator: a generic, subject-agnostic AI-agent auditor — reviewer, never executor. Audits any skill/agent against a generated subject profile and emits a 4-tab master-audit report.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@sinclair/typebox": "^0.34.0",
|
|
32
|
-
"yaml": "^2.
|
|
32
|
+
"yaml": "^2.8.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
35
|
"@langchain/google-genai": "^2.1.27"
|