@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.
Files changed (126) hide show
  1. package/.claude/skills/mutagent-evaluator/SKILL.md +538 -0
  2. package/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
  3. package/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
  4. package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
  5. package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1044 -0
  6. package/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
  7. package/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
  8. package/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
  9. package/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
  10. package/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
  11. package/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
  12. package/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
  13. package/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
  14. package/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
  15. package/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
  16. package/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
  17. package/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
  18. package/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
  19. package/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
  20. package/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
  21. package/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
  22. package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +196 -0
  23. package/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
  24. package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +287 -0
  25. package/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
  26. package/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
  27. package/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
  28. package/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
  29. package/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
  30. package/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
  31. package/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
  32. package/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
  33. package/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
  34. package/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
  35. package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
  36. package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +99 -0
  37. package/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
  38. package/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
  39. package/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
  40. package/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
  41. package/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
  42. package/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
  43. package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +175 -0
  44. package/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
  45. package/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
  46. package/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
  47. package/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
  48. package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +279 -0
  49. package/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
  50. package/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
  51. package/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
  52. package/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
  53. package/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
  54. package/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
  55. package/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
  56. package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +307 -0
  57. package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +325 -0
  58. package/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
  59. package/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
  60. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
  61. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
  62. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
  63. package/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
  64. package/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
  65. package/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
  66. package/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
  67. package/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
  68. package/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
  69. package/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
  70. package/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
  71. package/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
  72. package/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
  73. package/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
  74. package/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
  75. package/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
  76. package/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
  77. package/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
  78. package/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
  79. package/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
  80. package/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
  81. package/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
  82. package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
  83. package/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
  84. package/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
  85. package/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
  86. package/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
  87. package/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
  88. package/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
  89. package/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
  90. package/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
  91. package/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
  92. package/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
  93. package/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
  94. package/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
  95. package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
  96. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3823 -0
  97. package/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
  98. package/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
  99. package/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
  100. package/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
  101. package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +320 -0
  102. package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +271 -0
  103. package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +715 -0
  104. package/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
  105. package/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
  106. package/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
  107. package/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
  108. package/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
  109. package/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
  110. package/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
  111. package/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
  112. package/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
  113. package/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
  114. package/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
  115. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
  116. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
  117. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
  118. package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
  119. package/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
  120. package/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
  121. package/LICENSE +201 -0
  122. package/NOTICE +23 -0
  123. package/README.md +90 -0
  124. package/bin/mutagent-cli.mjs +9263 -0
  125. package/bin/mutagent-evaluator.mjs +96 -0
  126. 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
+ }