@mutagent/evaluator 0.1.0-alpha.2
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 +538 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1044 -0
- package/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
- package/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
- package/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
- package/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
- package/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
- package/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
- package/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
- package/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
- package/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
- package/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
- package/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
- package/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
- package/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
- package/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
- package/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
- package/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
- package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +196 -0
- package/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
- package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +287 -0
- package/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
- package/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
- package/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
- package/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
- package/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
- package/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
- package/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
- package/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
- package/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
- package/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
- package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
- package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +99 -0
- package/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
- package/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +175 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +279 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
- package/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
- package/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
- package/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
- package/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +307 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +325 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
- package/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
- package/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
- package/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
- package/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
- package/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
- package/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
- package/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
- package/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
- package/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
- package/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
- package/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
- package/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
- package/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
- package/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
- package/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
- package/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
- package/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
- package/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
- package/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
- package/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
- package/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
- package/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3823 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
- package/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
- package/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
- package/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
- package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +320 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +271 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +715 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
- package/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
- package/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
- package/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
- package/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
- package/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
- package/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
- package/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
- package/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
- package/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
- package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
- package/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
- package/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
- package/LICENSE +201 -0
- package/NOTICE +23 -0
- package/README.md +90 -0
- package/bin/mutagent-cli.mjs +9263 -0
- package/bin/mutagent-evaluator.mjs +96 -0
- package/package.json +52 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/substrate.ts — EV-050 judge-runtime selection.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* NOTE (v0.2.0 rename): `substrate` → `judge_runtime` in config + the public API
|
|
5
|
+
* (`resolveSubstrate`→`resolveJudgeRuntime`, `judgeForSubstrate`→`judgeForRuntime`).
|
|
6
|
+
* The enum VALUES are UNCHANGED (`agent-dispatch`/`in-house`/`code-based`/
|
|
7
|
+
* `user-framework`) and the EV-050 `Substrate` enum symbol (contracts/eval-types.ts)
|
|
8
|
+
* is kept — only the config KEY + the two selector functions were renamed.
|
|
9
|
+
*
|
|
10
|
+
* The W1 DEFAULT judge-runtime is **agent-dispatch** (scripts/agent-dispatch.ts):
|
|
11
|
+
* verdicts are produced by parent-session-dispatched eval-judge / error-analyst
|
|
12
|
+
* leaf subagents reasoning on the HOST runtime (Claude Code, diagnostics-style
|
|
13
|
+
* mass-dispatch) and read back from verdict FILES — the default path calls NO
|
|
14
|
+
* provider SDK. The onboarding choice (PRD §7.9) stays a real fork; this picks
|
|
15
|
+
* the DEFAULT the spine + dogfood build against, and STRUCTURES the rest as
|
|
16
|
+
* seams rather than hardcoding any single transport:
|
|
17
|
+
*
|
|
18
|
+
* - agent-dispatch (DEFAULT) → a verdict-file-backed JudgeInvoke; the LLM
|
|
19
|
+
* reasoning happens in dispatched subagents on the host,
|
|
20
|
+
* NOT in a script. Requires `verdictDir` (where the
|
|
21
|
+
* dispatched subagents wrote their verdict files).
|
|
22
|
+
* - in-house (OPTIONAL) → a live LLM JudgeInvoke (google-genai shape);
|
|
23
|
+
* kept, not deleted — DEMOTED from default. For the CI /
|
|
24
|
+
* code-based export path that wants a provider call.
|
|
25
|
+
* - code-based → objective checks over tool outputs; runs WITHOUT an LLM
|
|
26
|
+
* judge → judgeForRuntime THROWS (deterministic code).
|
|
27
|
+
* - user-framework → an EXPORT target (Vitest/promptfoo/Braintrust/…); the
|
|
28
|
+
* evaluator emits to it at onboarding → not a live judge.
|
|
29
|
+
*
|
|
30
|
+
* Why agent-dispatch is the default (operator correction 2026-06-19): the
|
|
31
|
+
* Evaluator's functions must be performed BY an Evaluator agent on Claude Code
|
|
32
|
+
* itself, mass-dispatched for throughput (mirror diagnostics), NOT by a script
|
|
33
|
+
* calling Gemini. In-house Gemini remains a real onboarding option (EV-050) but
|
|
34
|
+
* is no longer the spine default. Model intent stays sacred either way — the
|
|
35
|
+
* host's pinned model for agent-dispatch, the asserted provider model in-house.
|
|
36
|
+
*/
|
|
37
|
+
import { Substrate, type SubstrateValue } from "./contracts/eval-types.ts";
|
|
38
|
+
import { createInHouseJudge } from "./judge-provider.ts";
|
|
39
|
+
import { createAgentDispatchJudge } from "./agent-dispatch.ts";
|
|
40
|
+
import type { JudgeInvoke } from "./determine-outcome.ts";
|
|
41
|
+
|
|
42
|
+
const KNOWN_SUBSTRATES: ReadonlySet<string> = new Set([
|
|
43
|
+
Substrate.AgentDispatch,
|
|
44
|
+
Substrate.InHouse,
|
|
45
|
+
Substrate.UserFramework,
|
|
46
|
+
Substrate.CodeBased,
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** Resolve the judge-runtime choice; default = agent-dispatch. Unknown → THROW (no silent default). */
|
|
50
|
+
export function resolveJudgeRuntime(choice?: string): SubstrateValue {
|
|
51
|
+
if (choice === undefined || choice === "") return Substrate.AgentDispatch;
|
|
52
|
+
if (!KNOWN_SUBSTRATES.has(choice)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`resolveJudgeRuntime: unknown judge_runtime '${choice}'. Choose one of ` +
|
|
55
|
+
`{${[...KNOWN_SUBSTRATES].join(", ")}}.`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return choice as SubstrateValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SubstrateDescription {
|
|
62
|
+
kind: SubstrateValue;
|
|
63
|
+
/** true for transports that yield a live JudgeInvoke (agent-dispatch · in-house);
|
|
64
|
+
* false for the seams (code-based · user-framework). */
|
|
65
|
+
liveJudge: boolean;
|
|
66
|
+
/** the verdict transport: where the judge text comes from. */
|
|
67
|
+
transport: "agent-dispatch" | "provider-sdk" | "code" | "export";
|
|
68
|
+
/** true ONLY for the in-house provider-SDK path (the others never call a provider). */
|
|
69
|
+
callsProvider: boolean;
|
|
70
|
+
note: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Describe a substrate (which one provides a live judge, and via what transport). */
|
|
74
|
+
export function describeSubstrate(s: SubstrateValue): SubstrateDescription {
|
|
75
|
+
switch (s) {
|
|
76
|
+
case Substrate.AgentDispatch:
|
|
77
|
+
return {
|
|
78
|
+
kind: s,
|
|
79
|
+
liveJudge: true,
|
|
80
|
+
transport: "agent-dispatch",
|
|
81
|
+
callsProvider: false,
|
|
82
|
+
note: "Claude Code agent-dispatch (DEFAULT) — verdicts from host-runtime subagents (verdict files), no provider",
|
|
83
|
+
};
|
|
84
|
+
case Substrate.InHouse:
|
|
85
|
+
return {
|
|
86
|
+
kind: s,
|
|
87
|
+
liveJudge: true,
|
|
88
|
+
transport: "provider-sdk",
|
|
89
|
+
callsProvider: true,
|
|
90
|
+
note: "in-house AI-SDK judge (OPTIONAL — demoted from default; provider-SDK call)",
|
|
91
|
+
};
|
|
92
|
+
case Substrate.CodeBased:
|
|
93
|
+
return {
|
|
94
|
+
kind: s,
|
|
95
|
+
liveJudge: false,
|
|
96
|
+
transport: "code",
|
|
97
|
+
callsProvider: false,
|
|
98
|
+
note: "objective code-checks over tool outputs — no LLM judge",
|
|
99
|
+
};
|
|
100
|
+
case Substrate.UserFramework:
|
|
101
|
+
default:
|
|
102
|
+
return {
|
|
103
|
+
kind: s,
|
|
104
|
+
liveJudge: false,
|
|
105
|
+
transport: "export",
|
|
106
|
+
callsProvider: false,
|
|
107
|
+
note: "export target (user's eval framework) — emit, not a live judge",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface SubstrateJudgeConfig {
|
|
113
|
+
substrate: SubstrateValue;
|
|
114
|
+
/** agent-dispatch: the dir the dispatched subagents wrote their verdict files into. */
|
|
115
|
+
verdictDir?: string;
|
|
116
|
+
/** in-house: the resolved (pinned) provider model. */
|
|
117
|
+
model?: string;
|
|
118
|
+
apiKey?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Return a live `JudgeInvoke` for the judge-runtime.
|
|
123
|
+
* - agent-dispatch (DEFAULT) → a verdict-file-backed judge (requires verdictDir);
|
|
124
|
+
* NO provider is loaded.
|
|
125
|
+
* - in-house (OPTIONAL) → the google-genai judge (requires a resolved model —
|
|
126
|
+
* model intent sacred; createInHouseJudge asserts it).
|
|
127
|
+
* - code-based / user-framework → SEAMS; THROW (no live judge to return).
|
|
128
|
+
*/
|
|
129
|
+
export function judgeForRuntime(cfg: SubstrateJudgeConfig): JudgeInvoke {
|
|
130
|
+
if (cfg.substrate === Substrate.AgentDispatch) {
|
|
131
|
+
if (cfg.verdictDir === undefined || cfg.verdictDir === "") {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"judgeForRuntime(agent-dispatch): a verdictDir is required — it is the " +
|
|
134
|
+
"directory the parent session's dispatched eval-judge / error-analyst " +
|
|
135
|
+
"subagents wrote their verdict files into. PREP → dispatch → collect, " +
|
|
136
|
+
"then aggregate (references/workflows/orchestrator-protocol.md).",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return createAgentDispatchJudge({ verdictDir: cfg.verdictDir });
|
|
140
|
+
}
|
|
141
|
+
if (cfg.substrate === Substrate.InHouse) {
|
|
142
|
+
if (cfg.model === undefined || cfg.model === "") {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"judgeForRuntime(in-house): a model is required (model intent sacred). " +
|
|
145
|
+
"Resolve it via resolveJudgeModel() and pass it through.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return createInHouseJudge({ model: cfg.model, ...(cfg.apiKey !== undefined ? { apiKey: cfg.apiKey } : {}) });
|
|
149
|
+
}
|
|
150
|
+
if (cfg.substrate === Substrate.CodeBased) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"judgeForRuntime(code-based): code-based evals run WITHOUT an LLM judge — " +
|
|
153
|
+
"there is no live judge to return. Run the deterministic code-checks " +
|
|
154
|
+
"instead (this is a seam, not a live-judge path).",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
throw new Error(
|
|
158
|
+
"judgeForRuntime(user-framework): the user's framework is an EXPORT seam, " +
|
|
159
|
+
"not a live judge. Emit the suite to the framework at onboarding; do not " +
|
|
160
|
+
"expect a JudgeInvoke here.",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/ui-slots.ts — EV-039/040 HTML-artifact missing-data audit (Code-only).
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* The operator's first-class case: *"if a skill/agent produces an HTML artifact,
|
|
5
|
+
* data-leak is concerned with MISSING DATA in the UI representation."* A value
|
|
6
|
+
* the agent COMPUTED but did NOT render is a missing-data leak.
|
|
7
|
+
*
|
|
8
|
+
* This is the deterministic, SUBJECT-AGNOSTIC half of the EV-039/040 Hybrid: it
|
|
9
|
+
* cross-references a PROFILE-SUPPLIED expected-UI-slot list (EV-037
|
|
10
|
+
* `expectedUiSlots`, NOT the v1 diagnostics-specific hardcoded slot names) against
|
|
11
|
+
* the agent's computed values + the published HTML, and classifies each slot:
|
|
12
|
+
* - computed-but-not-rendered (EV-039) — a value computed yet ABSENT from the
|
|
13
|
+
* HTML → missing-data leak (locus UI, cls B). The operator's exact case;
|
|
14
|
+
* works on the HTML-only path (the v1 OUTSIDER FALLBACK promoted to
|
|
15
|
+
* first-class — no intermediate render-input file required).
|
|
16
|
+
* - orphan slot (EV-039) — the HTML references the slot but NO producer
|
|
17
|
+
* computed it → drawn-but-empty.
|
|
18
|
+
* - faithful — computed AND rendered verbatim → explicitly NOT a leak.
|
|
19
|
+
* Nuanced faithfulness (altered/truncated, EV-040) is left to the judge — this
|
|
20
|
+
* helper flags only verbatim presence/absence (it decides no severity).
|
|
21
|
+
*
|
|
22
|
+
* PURE + deterministic: no clock / random / network; same inputs → same audit.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Classification of one expected UI slot. */
|
|
26
|
+
export const UiSlotKind = {
|
|
27
|
+
/** Computed by the agent but ABSENT from the rendered HTML (EV-039 leak). */
|
|
28
|
+
ComputedNotRendered: "computed-not-rendered",
|
|
29
|
+
/** Referenced in the HTML but NO producer computed it (EV-039 orphan). */
|
|
30
|
+
OrphanSlot: "orphan-slot",
|
|
31
|
+
/** Computed AND rendered verbatim — NOT a leak. */
|
|
32
|
+
Faithful: "faithful",
|
|
33
|
+
/** Neither computed nor referenced in the HTML — out of scope this run. */
|
|
34
|
+
Absent: "absent",
|
|
35
|
+
} as const;
|
|
36
|
+
export type UiSlotKindValue = (typeof UiSlotKind)[keyof typeof UiSlotKind];
|
|
37
|
+
|
|
38
|
+
export interface UiSlotFinding {
|
|
39
|
+
slot: string;
|
|
40
|
+
kind: UiSlotKindValue;
|
|
41
|
+
/** the computed value string (when the agent produced one). */
|
|
42
|
+
computedValue?: string;
|
|
43
|
+
/** whether the slot is referenced (by name) anywhere in the HTML. */
|
|
44
|
+
slotReferenced: boolean;
|
|
45
|
+
/** whether the computed value appears verbatim in the HTML. */
|
|
46
|
+
valueRendered: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface UiSlotAuditInput {
|
|
50
|
+
/** EV-037 profile-supplied expected UI slots (subject-agnostic). */
|
|
51
|
+
expectedSlots: string[];
|
|
52
|
+
/** slot -> the value the agent COMPUTED (from trace / runMeta / intermediate). */
|
|
53
|
+
computedSlots: Record<string, string>;
|
|
54
|
+
/** the published HTML artifact text (the HTML-only path is supported). */
|
|
55
|
+
renderedHtml: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UiSlotAudit {
|
|
59
|
+
findings: UiSlotFinding[];
|
|
60
|
+
/** EV-039 leaks: computed but not in the HTML. */
|
|
61
|
+
computedNotRendered: string[];
|
|
62
|
+
/** EV-039 orphans: referenced in the HTML but never computed. */
|
|
63
|
+
orphanSlots: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Does the HTML reference this slot by name (a token match)? */
|
|
67
|
+
function slotReferenced(html: string, slot: string): boolean {
|
|
68
|
+
if (slot.length === 0) return false;
|
|
69
|
+
return html.includes(slot);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Does the computed value appear verbatim in the HTML? */
|
|
73
|
+
function valueRendered(html: string, value: string): boolean {
|
|
74
|
+
const v = value.trim();
|
|
75
|
+
if (v.length === 0) return false;
|
|
76
|
+
return html.includes(v);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Audit the expected UI slots against the computed values + the rendered HTML.
|
|
81
|
+
* PURE + deterministic. SUBJECT-AGNOSTIC — the slot list is supplied, never a
|
|
82
|
+
* module constant. Findings are emitted in `expectedSlots` order (stable).
|
|
83
|
+
*/
|
|
84
|
+
export function auditUiSlots(input: UiSlotAuditInput): UiSlotAudit {
|
|
85
|
+
const findings: UiSlotFinding[] = [];
|
|
86
|
+
const computedNotRendered: string[] = [];
|
|
87
|
+
const orphanSlots: string[] = [];
|
|
88
|
+
|
|
89
|
+
for (const slot of input.expectedSlots) {
|
|
90
|
+
const hasComputed = Object.prototype.hasOwnProperty.call(input.computedSlots, slot);
|
|
91
|
+
const computedValue = hasComputed ? input.computedSlots[slot] : undefined;
|
|
92
|
+
const referenced = slotReferenced(input.renderedHtml, slot);
|
|
93
|
+
const rendered =
|
|
94
|
+
computedValue !== undefined && valueRendered(input.renderedHtml, computedValue);
|
|
95
|
+
|
|
96
|
+
let kind: UiSlotKindValue;
|
|
97
|
+
if (hasComputed && !rendered) {
|
|
98
|
+
kind = UiSlotKind.ComputedNotRendered; // EV-039: computed but missing from HTML
|
|
99
|
+
computedNotRendered.push(slot);
|
|
100
|
+
} else if (!hasComputed && referenced) {
|
|
101
|
+
kind = UiSlotKind.OrphanSlot; // EV-039: drawn but no producer
|
|
102
|
+
orphanSlots.push(slot);
|
|
103
|
+
} else if (hasComputed && rendered) {
|
|
104
|
+
kind = UiSlotKind.Faithful;
|
|
105
|
+
} else {
|
|
106
|
+
kind = UiSlotKind.Absent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
findings.push({
|
|
110
|
+
slot,
|
|
111
|
+
kind,
|
|
112
|
+
...(computedValue !== undefined ? { computedValue } : {}),
|
|
113
|
+
slotReferenced: referenced,
|
|
114
|
+
valueRendered: rendered,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { findings, computedNotRendered, orphanSlots };
|
|
119
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/unitf-to-evaltrace.ts — the STRANGLER projection adapter.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Projects one UniTF `UnifiedTrace` record → the evaluator's in-package
|
|
5
|
+
* `EvalTrace` shape (MIGRATION-diagnostics-evaluator.md §3.2). This REPLACED (and
|
|
6
|
+
* the flip DELETED) `load-traces.ts` `mapRecord` (raw Langfuse → EvalTrace):
|
|
7
|
+
* instead of mapping a raw platform record, the evaluator reads a PRE-NORMALIZED
|
|
8
|
+
* UniTF JSONL handed over by `mutagent-cli` and projects each record here. All
|
|
9
|
+
* downstream (sample-traces / profile-subject / discover / judge / scorecard)
|
|
10
|
+
* consumes the resulting `EvalTrace[]` UNCHANGED.
|
|
11
|
+
*
|
|
12
|
+
* STANDALONE — the UniTF type shape is PORTED (a minimal structural subset), NOT
|
|
13
|
+
* imported from `@mutagent/tools`. The boundary between the CLI and this skill is
|
|
14
|
+
* the JSONL file on disk, never a source import (avoids a cross-package cycle;
|
|
15
|
+
* see the migration doc §6.1 "Circular import" risk row). Only the fields the
|
|
16
|
+
* projection actually reads are ported.
|
|
17
|
+
*
|
|
18
|
+
* PURE + deterministic: no clock, no random, no I/O. (The effectful JSONL read
|
|
19
|
+
* lives in `read-unitf-traces.ts`, which calls this.)
|
|
20
|
+
*/
|
|
21
|
+
import type { EvalTrace, TraceObservation } from "./contracts/eval-types.ts";
|
|
22
|
+
|
|
23
|
+
// ── Ported minimal UniTF shape (structural subset of @mutagent/tools unitf.ts) ──
|
|
24
|
+
//
|
|
25
|
+
// Intentionally a LOOSE structural mirror — a `UnifiedTrace` handed over on disk
|
|
26
|
+
// carries the full frozen schema, but the projection reads only these fields.
|
|
27
|
+
// Kept in-package so the skill never source-imports the tools package.
|
|
28
|
+
|
|
29
|
+
/** The message-style role a UniTF span may carry (transcript projection). */
|
|
30
|
+
export type UnitfRole = "user" | "assistant" | "system" | "tool" | "toolResult";
|
|
31
|
+
|
|
32
|
+
/** The OTel-aligned span kind. Kept as the literal union the projection branches on. */
|
|
33
|
+
export type UnitfSpanKind =
|
|
34
|
+
| "llm"
|
|
35
|
+
| "tool"
|
|
36
|
+
| "retrieval"
|
|
37
|
+
| "agent"
|
|
38
|
+
| "event"
|
|
39
|
+
| "span";
|
|
40
|
+
|
|
41
|
+
/** The OTel-aligned status a UniTF span (and the trace) may carry. */
|
|
42
|
+
export type UnitfStatus = "ok" | "error" | "unknown";
|
|
43
|
+
|
|
44
|
+
/** Token usage split (structural subset of @mutagent/tools `TokensSchema`). */
|
|
45
|
+
export interface UnitfTokens {
|
|
46
|
+
input?: number;
|
|
47
|
+
output?: number;
|
|
48
|
+
total?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One UniTF span (unifies a message AND an observation). Structural subset. */
|
|
52
|
+
export interface UnitfSpan {
|
|
53
|
+
name: string;
|
|
54
|
+
kind: UnitfSpanKind;
|
|
55
|
+
role?: UnitfRole;
|
|
56
|
+
/** XF-FIX Finding C — span-level status, used to derive the trace `errored` flag. */
|
|
57
|
+
status?: UnitfStatus;
|
|
58
|
+
input?: unknown;
|
|
59
|
+
output?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A raw platform score, carried through verbatim (Langfuse-style). */
|
|
63
|
+
export interface UnitfScore {
|
|
64
|
+
name: string;
|
|
65
|
+
value?: number | string | boolean;
|
|
66
|
+
comment?: string;
|
|
67
|
+
dataType?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The `ext.eval` fidelity block the fidelity gate reads. Structural subset. */
|
|
71
|
+
export interface UnitfEvalExt {
|
|
72
|
+
incomplete?: boolean;
|
|
73
|
+
incompleteReason?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The derived `computed` convenience block (structural subset). NEVER authoritative —
|
|
77
|
+
* read only as a fallback, exactly as the diagnostics adapter does. */
|
|
78
|
+
export interface UnitfComputed {
|
|
79
|
+
hasError?: boolean;
|
|
80
|
+
totalTokens?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** One UniTF record. Structural subset of the frozen `UnifiedTrace` contract. */
|
|
84
|
+
export interface UnifiedTraceLike {
|
|
85
|
+
traceId: string;
|
|
86
|
+
agentName?: string;
|
|
87
|
+
latencyMs?: number;
|
|
88
|
+
costUsd?: number;
|
|
89
|
+
tags?: string[];
|
|
90
|
+
scores?: UnitfScore[];
|
|
91
|
+
spans: UnitfSpan[];
|
|
92
|
+
/** XF-FIX Finding C — top-level trace status (`ok|error|unknown`). */
|
|
93
|
+
status?: UnitfStatus;
|
|
94
|
+
/** XF-FIX Finding D — trace-level token usage. */
|
|
95
|
+
tokens?: UnitfTokens;
|
|
96
|
+
/** XF-FIX Finding D — derived convenience block (hasError / totalTokens fallbacks). */
|
|
97
|
+
computed?: UnitfComputed;
|
|
98
|
+
ext?: {
|
|
99
|
+
eval?: UnitfEvalExt;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Text derivation (ported from @mutagent/tools derive.ts `asText`) ─────────
|
|
104
|
+
//
|
|
105
|
+
// A span's input/output is `unknown` — real exports carry nested payloads. The
|
|
106
|
+
// evaluator's `EvalTrace.input.prompt` / `output.response` are strings, so a
|
|
107
|
+
// deterministic best-effort stringify is needed. Mirrors the tools-package
|
|
108
|
+
// `asText` (pull a known text-ish field, else stable JSON) so a value projected
|
|
109
|
+
// here matches what a diagnostics `messagesView` would render.
|
|
110
|
+
|
|
111
|
+
/** Best-effort deterministic stringify of a span payload. PURE. */
|
|
112
|
+
function asText(v: unknown): string {
|
|
113
|
+
if (v === undefined || v === null) return "";
|
|
114
|
+
if (typeof v === "string") return v;
|
|
115
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
116
|
+
if (typeof v === "object") {
|
|
117
|
+
const o = v as Record<string, unknown>;
|
|
118
|
+
for (const k of ["text", "content", "response", "prompt", "message"]) {
|
|
119
|
+
if (typeof o[k] === "string") return o[k] as string;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return JSON.stringify(v);
|
|
123
|
+
} catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Role kind-fallback (XF-FIX Finding A) ────────────────────────────────────
|
|
131
|
+
//
|
|
132
|
+
// ROOT CAUSE of the Langfuse loss: `langfuse.ts obsToSpan` never sets `span.role`
|
|
133
|
+
// (a Langfuse observation carries no message role). The old projection derived
|
|
134
|
+
// the prompt from `role∈{user,system}` and the response from `role==="assistant"
|
|
135
|
+
// && kind==="llm"` — BOTH miss on a role-less span ⇒ `input.prompt` / `output.
|
|
136
|
+
// response` were empty for EVERY Langfuse trace. Diagnostics never had this bug:
|
|
137
|
+
// its `unitf-adapter.ts` `spanMessageRole` falls back to `kind` when `role` is
|
|
138
|
+
// absent. This mirrors that fallback so the evaluator lane survives the identical
|
|
139
|
+
// role-less input Diagnostics already reads fine (reader-side fix — the shared
|
|
140
|
+
// `langfuse.ts` producer is deliberately NOT touched, keeping the clean
|
|
141
|
+
// Diagnostics lane untouched).
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The span's effective message role. When an explicit `role` is present it wins;
|
|
145
|
+
* otherwise it is inferred from `kind` (a role-less `llm`/`agent` span — the
|
|
146
|
+
* Langfuse GENERATION shape — reads as `assistant`; a `tool` span as `tool`).
|
|
147
|
+
* MIRRORS diagnostics `unitf-adapter.ts` `spanMessageRole`. PURE.
|
|
148
|
+
*
|
|
149
|
+
* NOTE: inference never yields `user`/`system` (a role-less span is never a user
|
|
150
|
+
* turn), so prompt detection still requires an EXPLICIT user/system role — the
|
|
151
|
+
* Langfuse prompt is recovered from the GENERATION span's own `input` instead
|
|
152
|
+
* (see the response-span input fallback in the projection).
|
|
153
|
+
*/
|
|
154
|
+
function effectiveRole(span: UnitfSpan): UnitfRole {
|
|
155
|
+
if (span.role !== undefined) return span.role;
|
|
156
|
+
switch (span.kind) {
|
|
157
|
+
case "llm":
|
|
158
|
+
case "agent":
|
|
159
|
+
return "assistant";
|
|
160
|
+
case "tool":
|
|
161
|
+
return "tool";
|
|
162
|
+
default:
|
|
163
|
+
return "assistant";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Token total fallback (XF-FIX Finding D) ──────────────────────────────────
|
|
168
|
+
//
|
|
169
|
+
// MIRRORS diagnostics `unitf-adapter.ts` `fallbackTotalTokens` so both lanes
|
|
170
|
+
// report the SAME total: prefer `tokens.total`, else `input + output` when either
|
|
171
|
+
// is present, else undefined (NEVER 0 — an absent count stays absent).
|
|
172
|
+
|
|
173
|
+
/** Best-effort trace total from a `UnitfTokens` split. PURE; undefined-safe. */
|
|
174
|
+
function fallbackTotalTokens(t: UnitfTokens | undefined): number | undefined {
|
|
175
|
+
if (t === undefined) return undefined;
|
|
176
|
+
if (t.total !== undefined) return t.total;
|
|
177
|
+
if (t.input !== undefined || t.output !== undefined) {
|
|
178
|
+
return (t.input ?? 0) + (t.output ?? 0);
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Project a UniTF record → EvalTrace (migration doc §3.2 mapping table).
|
|
185
|
+
*
|
|
186
|
+
* id ← ut.traceId
|
|
187
|
+
* name ← ut.agentName
|
|
188
|
+
* output ← first EFFECTIVE-assistant llm span → { response: <text of span.output> }
|
|
189
|
+
* (XF-FIX A: role-less GENERATION spans read as assistant via kind)
|
|
190
|
+
* input ← first user|system span's input, ELSE the response span's own
|
|
191
|
+
* input → { prompt } (XF-FIX A: recovers the Langfuse prompt)
|
|
192
|
+
* observations ← ut.spans → { type: kind.toUpperCase(), name, input, output }
|
|
193
|
+
* errored/status ← computed.hasError ?? (status==="error" || any span error) / ut.status (XF-FIX C)
|
|
194
|
+
* tokens/totalTokens← ut.tokens split / computed.totalTokens ?? tokens.total ?? in+out (XF-FIX D)
|
|
195
|
+
* scores ← ut.scores (verbatim)
|
|
196
|
+
* tags ← ut.tags
|
|
197
|
+
* latencyMs/costUsd← ut.latencyMs / ut.costUsd
|
|
198
|
+
* incomplete ← ut.ext.eval.incomplete ?? false (the §9.4.2 fidelity marker)
|
|
199
|
+
* incompleteReason ← ut.ext.eval.incompleteReason
|
|
200
|
+
*
|
|
201
|
+
* PURE + deterministic — same record ⇒ same EvalTrace.
|
|
202
|
+
*/
|
|
203
|
+
export function projectUnitfToEvalTrace(ut: UnifiedTraceLike): EvalTrace {
|
|
204
|
+
const observations: TraceObservation[] = ut.spans.map((span) => {
|
|
205
|
+
const obs: TraceObservation = { type: span.kind.toUpperCase() };
|
|
206
|
+
if (span.name !== undefined) obs.name = span.name;
|
|
207
|
+
if (span.input !== undefined) obs.input = span.input;
|
|
208
|
+
if (span.output !== undefined) obs.output = span.output;
|
|
209
|
+
return obs;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const out: EvalTrace = {
|
|
213
|
+
id: ut.traceId,
|
|
214
|
+
observations,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (ut.agentName !== undefined) out.name = ut.agentName;
|
|
218
|
+
|
|
219
|
+
// output ← first assistant LLM span's output, wrapped to { response }. The
|
|
220
|
+
// assistant role is now EFFECTIVE (XF-FIX Finding A): a role-less GENERATION
|
|
221
|
+
// span (kind==="llm", the Langfuse shape) reads as assistant via `effectiveRole`,
|
|
222
|
+
// so its output is picked up instead of being silently dropped.
|
|
223
|
+
const responseSpan = ut.spans.find(
|
|
224
|
+
(s) => effectiveRole(s) === "assistant" && s.kind === "llm",
|
|
225
|
+
);
|
|
226
|
+
if (responseSpan !== undefined && responseSpan.output !== undefined) {
|
|
227
|
+
out.output = { response: asText(responseSpan.output) };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// input ← first user|system span's input, wrapped to { prompt }. A role-less
|
|
231
|
+
// span never infers to user/system, so when there is no explicit user/system
|
|
232
|
+
// turn (again the Langfuse shape — one GENERATION span carrying prompt in its
|
|
233
|
+
// OWN `input` and response in `output`) the prompt is recovered from the
|
|
234
|
+
// response span's `input`. Without this fallback `input.prompt` was empty for
|
|
235
|
+
// EVERY Langfuse trace (Finding A).
|
|
236
|
+
const promptSpan = ut.spans.find(
|
|
237
|
+
(s) => effectiveRole(s) === "user" || effectiveRole(s) === "system",
|
|
238
|
+
);
|
|
239
|
+
if (promptSpan !== undefined && promptSpan.input !== undefined) {
|
|
240
|
+
out.input = { prompt: asText(promptSpan.input) };
|
|
241
|
+
} else if (responseSpan !== undefined && responseSpan.input !== undefined) {
|
|
242
|
+
out.input = { prompt: asText(responseSpan.input) };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Finding C — carry structured error/status so an error-KIND criterion has a
|
|
246
|
+
// field to read (previously error state survived only as textual span output).
|
|
247
|
+
// `errored` mirrors diagnostics `computed.hasError ?? status==="error"`, extended
|
|
248
|
+
// to also honor any span-level error. Always set (false = grounded not-errored).
|
|
249
|
+
if (ut.status !== undefined) out.status = ut.status;
|
|
250
|
+
out.errored =
|
|
251
|
+
ut.computed?.hasError ??
|
|
252
|
+
(ut.status === "error" || ut.spans.some((s) => s.status === "error"));
|
|
253
|
+
|
|
254
|
+
// Finding D — carry token usage (undefined-safe; never coerced to 0) so a
|
|
255
|
+
// token-budget criterion is supported at intake, alongside the already-carried
|
|
256
|
+
// costUsd + latencyMs. `totalTokens` uses the diagnostics fallback chain.
|
|
257
|
+
if (ut.tokens !== undefined) {
|
|
258
|
+
const tk: { input?: number; output?: number; total?: number } = {};
|
|
259
|
+
if (typeof ut.tokens.input === "number") tk.input = ut.tokens.input;
|
|
260
|
+
if (typeof ut.tokens.output === "number") tk.output = ut.tokens.output;
|
|
261
|
+
if (typeof ut.tokens.total === "number") tk.total = ut.tokens.total;
|
|
262
|
+
if (tk.input !== undefined || tk.output !== undefined || tk.total !== undefined) {
|
|
263
|
+
out.tokens = tk;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const totalTokens = ut.computed?.totalTokens ?? fallbackTotalTokens(ut.tokens);
|
|
267
|
+
if (totalTokens !== undefined) out.totalTokens = totalTokens;
|
|
268
|
+
|
|
269
|
+
if (ut.scores !== undefined) out.scores = ut.scores;
|
|
270
|
+
if (ut.tags !== undefined) {
|
|
271
|
+
out.tags = ut.tags.filter((t): t is string => typeof t === "string");
|
|
272
|
+
}
|
|
273
|
+
if (typeof ut.latencyMs === "number") out.latencyMs = ut.latencyMs;
|
|
274
|
+
if (typeof ut.costUsd === "number") out.costUsd = ut.costUsd;
|
|
275
|
+
|
|
276
|
+
// §9.4.2 fidelity marker — projected DIRECTLY from ext.eval so an incomplete
|
|
277
|
+
// trace never reaches the criteria walk unflagged.
|
|
278
|
+
const incomplete = ut.ext?.eval?.incomplete ?? false;
|
|
279
|
+
if (incomplete) out.incomplete = true;
|
|
280
|
+
const reason = ut.ext?.eval?.incompleteReason;
|
|
281
|
+
if (typeof reason === "string" && reason.length > 0) out.incompleteReason = reason;
|
|
282
|
+
|
|
283
|
+
return out;
|
|
284
|
+
}
|