@mutagent/evaluator 0.1.0-alpha.2 → 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.
@@ -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. ALSO surfaces the existing v1 4-tab static-auditor
16
- (eval-matrix · data-leak · variance · methodology) via *audit. Pinned judge model + temp=0
15
+ sub-agents (one cell, discover / judge modes; host runtime, harness-capped) against the subject's traces.
16
+ DEFAULT INTENT: any "evaluate this skill/agent" request runs *evaluate the JUDGE which is what an
17
+ end user wants. *audit is a SEPARATE, explicit-only 4-tab static-auditor (eval-matrix · data-leak ·
18
+ variance · methodology) used mainly for complex agents/skills that emit HTML artifacts; it is NEVER the
19
+ fallback for a plain evaluate. Pinned judge model + temp=0
17
20
  for byte-identical reruns (C-PIN). Usable standalone OR borrowed by mutagent-skill-builder's
18
21
  SIMULATE phase as the SimVerifier.
19
22
  license: Proprietary. LICENSE.txt has complete terms.
@@ -29,8 +32,6 @@ metadata:
29
32
  Evaluation-development-on-Tap for AI agents/skills. Invoke this skill to build a trustworthy
30
33
  eval suite for a subject and run it to a GATE verdict.
31
34
 
32
- > **DRAFT — NOT YET PUBLISHED.** Operator tags `evaluator-v*` only after real-run verification.
33
-
34
35
  ## §0 — Setup Detection (ALWAYS runs first)
35
36
 
36
37
  > **CWD matters.** Every `scripts/cli/run.sh`-dispatched script MUST be invoked from the
@@ -128,9 +129,15 @@ When you encounter a *<name> token:
128
129
 
129
130
  ## §1 — Triggers
130
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
+
131
138
  Invoke me with:
132
- - `evaluate skill <name>` / `evaluate agent <name>` / `/mutagent-evaluator`
133
- - `*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)
134
141
  - `*improve` (the ADL ③ EDD loop — variance-first then request-amend-reeval to full green, bounded)
135
142
  - `*self-audit` (the eval-of-the-eval — audit my OWN eval-dev; on-demand only)
136
143
  - borrowed by `mutagent-skill-builder` SIMULATE (SimVerifier role)
@@ -149,7 +156,9 @@ Minimal eval-dev spine (build order — do NOT reorder):
149
156
  amend agentspec|impl over SendMessage → re-eval) → full green OR bounded STOP
150
157
  ```
151
158
 
152
- `*validate` (calibrate the judge) and `*audit` (the v1 4-tab static report) layer on top.
159
+ `*validate` (calibrate the judge) layers on top. `*audit` (the v1 4-tab master-audit) is an
160
+ **explicit-only** specialized surface (complex / HTML-artifact subjects) — NOT part of the default
161
+ evaluate spine and never an automatic fallback.
153
162
  `*improve` is the IMPROVE stage — it runs AFTER an initial `*build` + `*evaluate` and is **bounded**
154
163
  (never infinite): full-green ⇒ DONE; else max-swings / max-wallclock / no-improvement ⇒ STOP + delta.
155
164
 
@@ -169,7 +178,7 @@ log-sink dependency up front, F7/F9/F14), streams wireframe progress + entity ca
169
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.
170
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`).
171
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).
172
- - **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.
173
182
 
174
183
  ## §3 — Architecture Overview
175
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: SCOPED / FUTURE not dispatched today.
21
+ # STATUS: DISPATCHED standalone as the vendored delegate-or-artifact trace agent (diagnostics Step 3.6 · evaluator Step 0.5). The orchestrator `*collect-traces` alias route remains future.
22
22
  # Helix currently calls `mutagent-cli trace count` + `trace fetch --export` DIRECTLY (the
23
23
  # example workflows in trace-layer-plan.md §Example Workflows). This agent FORMALIZES that
24
24
  # pattern into a delegatable cell once the CLI surface is proven in production. The contracts
@@ -359,7 +359,7 @@ You are a **discovery** agent. You collect agent traces on behalf of Helix (or a
359
359
  orchestrator) and hand the result to a downstream skill (Diagnostics or Evaluator). You do
360
360
  NOT analyze traces — you only **collect, normalize, and validate** them.
361
361
 
362
- > **SCOPED / FUTURE.** This agent contract is written now so the contracts are locked and
362
+ > **STATUS.** The vendored agent IS dispatched standalone today (diagnostics Step 3.6 · evaluator Step 0.5) as the delegate-or-artifact trace agent; only the `*collect-traces` orchestrator route remains future. The contract below was written ahead so it was locked and
363
363
  > the handoff chain is fully specified. Helix currently calls `mutagent-cli` directly (the
364
364
  > example workflows in `trace-layer-plan.md`). This agent formalizes that into a delegatable
365
365
  > cell once the CLI surface is proven in production. Dispatch wiring (`*collect-traces` route
@@ -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 (UniTF handover — the skill NEVER fetches)
98
-
99
- Traces arrive as a **pre-produced UniTF JSONL handover**: one `UnifiedTrace`
100
- record per line, written by `mutagent-cli trace fetch --export <path>` BEFORE this
101
- skill is dispatched (Helix runs the fetch in its PRE-STAGE the evaluator owns no
102
- fetch/normalize code). Fetch + normalize live entirely in `mutagent-cli`; run
103
- `mutagent-cli trace --help` to discover the surface (count · fetch · export).
97
+ ## Step 0.5 — Trace intake (delegate-or-artifact — the evaluator ACQUIRES traces · reports-PRD R-6)
98
+
99
+ The evaluator owns NO fetch/normalize code (that lives in `mutagent-cli` + the `discovery`
100
+ agent) but **a real user will not hand you a trace file**. So, in **parity with Diagnostics
101
+ Step 3.6 (V3-9)**, traces are acquired by EXACTLY ONE of the 3 legal paths:
102
+
103
+ | # | Path | Who fetches | When |
104
+ |---|---|---|---|
105
+ | a | **Helix pre-fetch / handover** | Helix (pre-stage) or a pre-produced `mutagent-cli trace fetch --export`, handed in via `--traces <handover.jsonl>` | orchestrated runs |
106
+ | b | **`*discover` dispatch** | the `discovery` system agent | orchestrated collection |
107
+ | c | **Standalone: artifact intake OR vendored-Discovery spawn** | a pre-staged artifact, or the vendored `discovery` sub-agent | THIS step |
108
+
109
+ **Standalone flow (path c), in order:**
110
+ 1. **Handover / artifact intake first.** If `--traces <handover.jsonl>` (`.gz` ok) was handed in,
111
+ or a pre-staged `.mutagent/evaluator/<run>/ingest/traces.jsonl` exists → consume it; do NOT spawn Discovery.
112
+ 2. **Else DELEGATE to the vendored Discovery agent.** Spawn `assets/agents/discovery.md` (installed at
113
+ `.claude/agents/discovery.md`) via the Task tool (`subagent_type: "discovery"`), handing it the
114
+ resolved TraceQuery (source + filters + window + `--export .mutagent/evaluator/<run>/ingest/traces.jsonl`).
115
+ Discovery fetches + curates → a `SelectionManifest` + `traces.jsonl`; the PREP/AGGREGATE CLIs then
116
+ read it via `--traces` (P-B curated `ext.signals`/`ext.classification` consumed when present).
104
117
 
105
118
  The PREP/AGGREGATE CLIs (`scripts/cli/{prep,aggregate,dogfood}.ts`) take the
106
119
  handover file via `--traces <handover.jsonl>` (`.gz` also accepted) and read it
@@ -338,7 +338,7 @@ export function writeDiscoverReport(input: WriteDiscoverReportInput): string {
338
338
  ...(input.companions ?? {}),
339
339
  });
340
340
  mkdirSync(input.reportDir, { recursive: true });
341
- const outPath = join(input.reportDir, "report.html");
341
+ const outPath = join(input.reportDir, "discovery-report.html");
342
342
  writeFileSync(outPath, html);
343
343
  return outPath;
344
344
  }
@@ -91,6 +91,22 @@ export function scorecardDir(runId: string, cwd?: string): string {
91
91
  export function reportDir(runId: string, cwd?: string): string {
92
92
  return underRoot(cwd, "reports", runId);
93
93
  }
94
+
95
+ // ── the three named report artifacts (evaluator-reports PRD R-1/R-3) ──────────
96
+ // Distinct names under ONE run folder — no `report.html` collision, and *audit no longer
97
+ // writes under `.mutagent/diagnostics/`. Single resolver = single source of truth.
98
+ /** `*evaluate` → the 5-tab judge report. */
99
+ export function evaluationReportPath(runId: string, cwd?: string): string {
100
+ return underRoot(cwd, "reports", runId, "evaluation-report.html");
101
+ }
102
+ /** `*discover-evals` → the mined-criteria report. */
103
+ export function discoveryReportPath(runId: string, cwd?: string): string {
104
+ return underRoot(cwd, "reports", runId, "discovery-report.html");
105
+ }
106
+ /** `*audit` → the 4-tab master-audit (now under the evaluator root, not diagnostics). */
107
+ export function masterAuditPath(runId: string, cwd?: string): string {
108
+ return underRoot(cwd, "reports", runId, "master-audit.html");
109
+ }
94
110
  export function livingSuiteDir(cwd?: string): string {
95
111
  return underRoot(cwd, "living-suite");
96
112
  }
@@ -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
- const outDir = args.outDir ?? args.bundleDir;
124
- mkdirSync(join(outDir, "audit"), { recursive: true });
125
- const scPath = join(outDir, "audit", "scorecard.json");
126
- const htmlPath = join(outDir, "audit", "master-audit.html");
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>`. A single source, exactly
20
- * one `default:true`, or an explicit `--source` skips the prompt.
19
+ * multi-choice) and re-invokes with `--source <name>`. When >1 entry has exactly
20
+ * one `default:true`, the config surfaces `confirm-default` (F2) the PARENT
21
+ * CONFIRMS the preselected default (a yes/no ask; distinct from the open pick)
22
+ * before it is used. A single source or an explicit `--source` skips the prompt.
21
23
  *
22
24
  * MODEL INTENT SACRED: --model is the pinned host model (temperature pinned 0,
23
25
  * C-PIN); it is carried verbatim on every task spec for the subagent to honor —
@@ -58,6 +60,20 @@ function flag(argv: string[], name: string): string | undefined {
58
60
  */
59
61
  function surfaceSourceBinding(binding: SourceBinding): void {
60
62
  switch (binding.kind) {
63
+ case "confirm-default": {
64
+ // F2 — a preselected default among many: surface a CONFIRM ask (yes/no),
65
+ // distinct from the open needs-selection pick. NON-fatal: the default is usable
66
+ // as-is on accept / non-interactive / --source.
67
+ const names = binding.sources.map((s) => s.name).join(", ");
68
+ process.stdout.write(
69
+ `prep: source CONFIRM-DEFAULT — ${binding.sources.length} sources, default is ` +
70
+ `'${binding.source.name}'. PARENT: confirm the default via the platform ask ` +
71
+ `mechanism (AskUserQuestion / chat multi-choice; '${binding.source.name}' PRESELECTED, ` +
72
+ `operator may override), or accept it non-interactively (--source <name> / CI). ` +
73
+ `Candidates: ${names}\n`,
74
+ );
75
+ break;
76
+ }
61
77
  case "needs-selection": {
62
78
  const names = binding.sources.map((s) => s.name).join(", ");
63
79
  process.stdout.write(
@@ -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.1 — parity with orchestrator + diagnostics):
97
- * bound — exactly one source resolved (single entry · `default:true` · `--source` hit).
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, only the pick is missing).
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` ⇒ `bound` (silent default).
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` — the source
152
- * exists, only the disambiguation pick is deferred to the run-start ASK.
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
- if (defaults.length === 1) return { kind: "bound", source: defaults[0] as GlobalSource };
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
- /** Source platforms a `global.sources[]` entry can pull traces from. */
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
- /** Normalizer format hint for a source (how to parse the records). */
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
- /** The default model + the pinned judge model (`judge_model` — was `pinned_judge`). */
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 entry
186
- // exists the selection contract's silent-bind marker (exactly one `default:true`
187
- // ⇒ auto-bind, skipping the disambiguation prompt). Optional: absent no default.
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
- /** A precise link to an Agent / Tooling definition file in a code target. */
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
- /** HOW a fix is applied for a target. `kind` gates the report-only branch. */
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
- <button class="tab-btn internal" data-tab="t5">⑤ Self-Eval [INTERNAL]</button>
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
- <section class="panel" id="t5">${selfEvalTab(input)}</section>
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 <bundle>/audit/scorecard.json (two-track GATE+TREND) + <bundle>/audit/master-audit.html (the base report). Return the parsed JSON the script prints (subject, runId, criteria, deterministicRows, judgeRows, gateRunPass, scorecard, report).`,
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/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # @mutagent/evaluator
2
2
 
3
- > **DRAFT — NOT YET PUBLISHED.** Operator tags `evaluator-v*` only after real-run verification.
4
-
5
3
  A **generic, subject-agnostic** AI-agent **auditor** — a **reviewer, never the
6
4
  executor**. It verifies that a skill or agent did what it should, **repeatably**,
7
5
  operator-out-of-loop, and emits a **4-tab master-audit report**.
@@ -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
- if (paths || root || endpointRef || credRef) {
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.2",
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.4.0"
32
+ "yaml": "^2.8.0"
33
33
  },
34
34
  "optionalDependencies": {
35
35
  "@langchain/google-genai": "^2.1.27"