@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,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/cli/variance-check.ts — coordinator entrypoint.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* The COORDINATOR role (executor != reviewer): compares TWO run bundles on the
|
|
5
|
+
* fixed 15-dim determinism scorecard and emits the delta + trend. This is fully
|
|
6
|
+
* deterministic — no judge, no model — so it runs as a complete wired script.
|
|
7
|
+
*
|
|
8
|
+
* variance-check.ts <bundleDirA> <bundleDirB> [--out <scorecard.json>]
|
|
9
|
+
* [--generated-at <iso>] [--emit-masked]
|
|
10
|
+
*/
|
|
11
|
+
import { writeFileSync } from "node:fs";
|
|
12
|
+
import { loadBundle } from "../load-bundle.ts";
|
|
13
|
+
import { varianceCompare } from "../variance-compare.ts";
|
|
14
|
+
import { assembleScorecard } from "../assemble-scorecard.ts";
|
|
15
|
+
import { maskedCanonicalJson } from "../mask.ts";
|
|
16
|
+
import { DEFAULT_GENERATED_AT } from "./audit-run.ts";
|
|
17
|
+
|
|
18
|
+
interface Args {
|
|
19
|
+
a: string;
|
|
20
|
+
b: string;
|
|
21
|
+
out?: string;
|
|
22
|
+
generatedAt: string;
|
|
23
|
+
emitMasked: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv: string[]): Args {
|
|
27
|
+
const positional: string[] = [];
|
|
28
|
+
const flags: Record<string, string | boolean> = {};
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const a = argv[i];
|
|
31
|
+
if (a.startsWith("--")) {
|
|
32
|
+
const key = a.slice(2);
|
|
33
|
+
const next = argv[i + 1];
|
|
34
|
+
if (next && !next.startsWith("--")) {
|
|
35
|
+
flags[key] = next;
|
|
36
|
+
i++;
|
|
37
|
+
} else flags[key] = true;
|
|
38
|
+
} else positional.push(a);
|
|
39
|
+
}
|
|
40
|
+
if (positional.length < 2) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"usage: variance-check.ts <bundleDirA> <bundleDirB> [--out <file>] [--generated-at <iso>] [--emit-masked]",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
a: positional[0],
|
|
47
|
+
b: positional[1],
|
|
48
|
+
out: (flags.out as string) || undefined,
|
|
49
|
+
generatedAt: (flags["generated-at"] as string) || DEFAULT_GENERATED_AT,
|
|
50
|
+
emitMasked: flags["emit-masked"] === true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function main(argvIn?: string[]): number {
|
|
55
|
+
const argv =
|
|
56
|
+
argvIn ??
|
|
57
|
+
(typeof Bun !== "undefined" ? Bun.argv.slice(2) : process.argv.slice(2));
|
|
58
|
+
const args = parseArgs(argv);
|
|
59
|
+
|
|
60
|
+
const { bundle: a } = loadBundle(args.a);
|
|
61
|
+
const { bundle: b } = loadBundle(args.b);
|
|
62
|
+
const trend = varianceCompare(a, b);
|
|
63
|
+
|
|
64
|
+
// Carry the trend into a TREND-only scorecard frame (GATE empty — this is the
|
|
65
|
+
// coordinator's variance comparison, not a conformance gate).
|
|
66
|
+
const scorecard = assembleScorecard({
|
|
67
|
+
subject: a.runId,
|
|
68
|
+
runId: `${a.runId}__vs__${b.runId}`,
|
|
69
|
+
generatedAt: args.generatedAt,
|
|
70
|
+
rows: [],
|
|
71
|
+
trendDimensions: trend.dimensions,
|
|
72
|
+
trendRunPair: trend.runPair,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const json = args.emitMasked
|
|
76
|
+
? maskedCanonicalJson(scorecard)
|
|
77
|
+
: JSON.stringify(scorecard, null, 2);
|
|
78
|
+
if (args.out) writeFileSync(args.out, json + "\n");
|
|
79
|
+
|
|
80
|
+
console.info(
|
|
81
|
+
JSON.stringify(
|
|
82
|
+
{
|
|
83
|
+
runPair: trend.runPair,
|
|
84
|
+
varianceScore: trend.varianceScore,
|
|
85
|
+
dimensions: trend.dimensions.length,
|
|
86
|
+
diverged: trend.dimensions
|
|
87
|
+
.filter((d) => d.divergence === "diverged")
|
|
88
|
+
.map((d) => d.name),
|
|
89
|
+
out: args.out ?? "(stdout only)",
|
|
90
|
+
},
|
|
91
|
+
null,
|
|
92
|
+
2,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
// exit 0 when byte-identical (varianceScore 0), 1 otherwise.
|
|
96
|
+
return trend.varianceScore === 0 ? 0 : 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const Bun: { argv: string[] } | undefined;
|
|
100
|
+
const isMain =
|
|
101
|
+
typeof import.meta !== "undefined" &&
|
|
102
|
+
(import.meta as unknown as { main?: boolean }).main === true;
|
|
103
|
+
if (isMain) {
|
|
104
|
+
process.exit(main());
|
|
105
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/code-eval.ts — P3 (EX-2): the v2 CODE-track primitive library.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Code-class criteria (§5b `check_method: deterministic`, and the pre-filter
|
|
5
|
+
* half of `hybrid`) are evaluated by DETERMINISTIC code-eval scripts the
|
|
6
|
+
* evaluator agent runs via its Bash tool — NO LLM, ZERO judge tokens,
|
|
7
|
+
* byte-identical across reruns (restores full C-PIN on code rows; closes M1/M2).
|
|
8
|
+
*
|
|
9
|
+
* This is a generic, SUBJECT-AGNOSTIC primitive set over the `EvalTrace` shape:
|
|
10
|
+
* - presence — a field is present + non-empty
|
|
11
|
+
* - string-equality — exact (optionally case-insensitive) match
|
|
12
|
+
* - format-validity — a regex/format conformance check
|
|
13
|
+
* - schema-conformance — an object carries the required keys
|
|
14
|
+
* - ref-integrity — cross-stage: every value produced in stage A is
|
|
15
|
+
* present in stage B's output (referential integrity)
|
|
16
|
+
* - recovery-after-failure — TEMPORAL: a failure marker on an observation MUST
|
|
17
|
+
* be followed by a recovery tool LATER in the trace; an
|
|
18
|
+
* un-recovered failure is a "silent drop" (FAIL).
|
|
19
|
+
* - tool-output-failure — a named tool's success flag is false on any call (FAIL).
|
|
20
|
+
* A per-subject extraction (check-method-router.ts) maps a code-class
|
|
21
|
+
* MinedCriterion → one of these specs; the primitives themselves hold NO subject
|
|
22
|
+
* literal — the field names, fail values, and tool names are all PARAMETERS, so
|
|
23
|
+
* the temporal/tool primitives are reusable on ANY agent's failure modes (the
|
|
24
|
+
* sample `send-failure-silent-drop` mode is just one parameterization).
|
|
25
|
+
*
|
|
26
|
+
* TWO-WORLDS BOUNDARY: this is the v2 code-track over `EvalTrace` +
|
|
27
|
+
* `MinedCriterion`. It deliberately does NOT import the v1 audit world
|
|
28
|
+
* (`run-deterministic.ts` / `contracts/types.ts` 6-value Track over RunBundle) —
|
|
29
|
+
* the two stay disjoint.
|
|
30
|
+
*
|
|
31
|
+
* PURE + deterministic: no clock / random / network. Reading a trace field is
|
|
32
|
+
* the only effect-free input; the verdict is a pure function of (spec, trace).
|
|
33
|
+
*/
|
|
34
|
+
import type { EvalTrace, TraceObservation } from "./contracts/eval-types.ts";
|
|
35
|
+
|
|
36
|
+
/** A single code-eval's binary, deterministic outcome. `detail` → verdict critique. */
|
|
37
|
+
export interface CodeEvalResult {
|
|
38
|
+
result: "pass" | "fail";
|
|
39
|
+
detail: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read a dotted path off an `EvalTrace`. Supports:
|
|
44
|
+
* - `output.response`, `input.prompt` → navigate the trace object
|
|
45
|
+
* - `obs:<name>.<path>` → the named GENERATION observation's
|
|
46
|
+
* output, then navigate (SV-1: per-stage
|
|
47
|
+
* outputs live in the observations)
|
|
48
|
+
* Returns undefined for any missing segment (tolerant — never throws). PURE.
|
|
49
|
+
*/
|
|
50
|
+
export function readTracePath(trace: EvalTrace, path: string): unknown {
|
|
51
|
+
if (path.startsWith("obs:")) {
|
|
52
|
+
const rest = path.slice(4);
|
|
53
|
+
const dot = rest.indexOf(".");
|
|
54
|
+
const name = dot === -1 ? rest : rest.slice(0, dot);
|
|
55
|
+
const sub = dot === -1 ? "" : rest.slice(dot + 1);
|
|
56
|
+
const obs = trace.observations.find(
|
|
57
|
+
(o: TraceObservation) => o.name === name && typeof o.type === "string" && o.type.toUpperCase() === "GENERATION",
|
|
58
|
+
);
|
|
59
|
+
if (obs === undefined) return undefined;
|
|
60
|
+
return sub.length === 0 ? obs.output : navigate(obs.output, sub);
|
|
61
|
+
}
|
|
62
|
+
return navigate(trace as unknown, path);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Navigate a dotted path through a plain object graph. Undefined on any gap. */
|
|
66
|
+
function navigate(root: unknown, path: string): unknown {
|
|
67
|
+
let cur: unknown = root;
|
|
68
|
+
for (const seg of path.split(".")) {
|
|
69
|
+
if (cur === null || typeof cur !== "object") return undefined;
|
|
70
|
+
cur = (cur as Record<string, unknown>)[seg];
|
|
71
|
+
if (cur === undefined) return undefined;
|
|
72
|
+
}
|
|
73
|
+
return cur;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Coerce a value to a search "haystack" string for ref-integrity. */
|
|
77
|
+
function haystack(value: unknown): string {
|
|
78
|
+
if (typeof value === "string") return value;
|
|
79
|
+
if (value === undefined || value === null) return "";
|
|
80
|
+
return JSON.stringify(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Coerce a producer value to its set of string tokens (array → items; scalar → [it]). */
|
|
84
|
+
function tokens(value: unknown): string[] {
|
|
85
|
+
if (Array.isArray(value)) return value.map((v) => (typeof v === "string" ? v : JSON.stringify(v)));
|
|
86
|
+
if (value === undefined || value === null) return [];
|
|
87
|
+
return [typeof value === "string" ? value : JSON.stringify(value)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Coerce a scalar trace value to a canonical string for EXACT equality compare
|
|
92
|
+
* (used by the temporal/tool primitives). Booleans/numbers stringify (`false` →
|
|
93
|
+
* "false", `0` → "0"); strings pass through; null/undefined → "" (so a missing
|
|
94
|
+
* field never spuriously equals a non-empty `failEquals`); objects JSON-serialize.
|
|
95
|
+
*/
|
|
96
|
+
function coerce(value: unknown): string {
|
|
97
|
+
if (typeof value === "string") return value;
|
|
98
|
+
if (typeof value === "boolean" || typeof value === "number" || typeof value === "bigint") {
|
|
99
|
+
return String(value);
|
|
100
|
+
}
|
|
101
|
+
if (value === undefined || value === null) return "";
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The `name` of an observation, or "" when unnamed. */
|
|
106
|
+
function obsName(o: TraceObservation): string {
|
|
107
|
+
return typeof o.name === "string" ? o.name : "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── The CodeEvalSpec union (one per primitive) ──────────────────────────────
|
|
111
|
+
|
|
112
|
+
export type CodeEvalSpec =
|
|
113
|
+
| { primitive: "presence"; field: string }
|
|
114
|
+
| { primitive: "string-equality"; field: string; expected: string; caseInsensitive?: boolean }
|
|
115
|
+
| { primitive: "format-validity"; field: string; pattern: string }
|
|
116
|
+
| { primitive: "schema-conformance"; field: string; requiredKeys: string[] }
|
|
117
|
+
| { primitive: "ref-integrity"; producer: string; consumer: string }
|
|
118
|
+
| {
|
|
119
|
+
primitive: "recovery-after-failure";
|
|
120
|
+
/** path read RELATIVE TO each observation (e.g. "output.status"). */
|
|
121
|
+
failField: string;
|
|
122
|
+
/** the coerced value at `failField` that marks an observation as a failure. */
|
|
123
|
+
failEquals: string;
|
|
124
|
+
/** observation `name`s that count as a recovery reaction AFTER the failure. */
|
|
125
|
+
recoveryTools: string[];
|
|
126
|
+
}
|
|
127
|
+
| {
|
|
128
|
+
primitive: "tool-output-failure";
|
|
129
|
+
/** the observation `name` of the tool to inspect. */
|
|
130
|
+
tool: string;
|
|
131
|
+
/** path read RELATIVE TO the tool observation; `false` (bool/string) ⇒ failure. */
|
|
132
|
+
successPath: string;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function pass(detail: string): CodeEvalResult {
|
|
136
|
+
return { result: "pass", detail };
|
|
137
|
+
}
|
|
138
|
+
function fail(detail: string): CodeEvalResult {
|
|
139
|
+
return { result: "fail", detail };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Run one code-eval over a trace → a deterministic binary verdict. Exhaustive on
|
|
144
|
+
* the primitive union; an UNKNOWN primitive THROWS (no silent pass — a
|
|
145
|
+
* misconfigured code-eval is a fail-loud error, never a fabricated success). PURE.
|
|
146
|
+
*/
|
|
147
|
+
export function runCodeEval(spec: CodeEvalSpec, trace: EvalTrace): CodeEvalResult {
|
|
148
|
+
switch (spec.primitive) {
|
|
149
|
+
case "presence": {
|
|
150
|
+
const v = readTracePath(trace, spec.field);
|
|
151
|
+
const empty =
|
|
152
|
+
v === undefined ||
|
|
153
|
+
v === null ||
|
|
154
|
+
(typeof v === "string" && v.length === 0) ||
|
|
155
|
+
(Array.isArray(v) && v.length === 0);
|
|
156
|
+
return empty ? fail(`'${spec.field}' is absent/empty`) : pass(`'${spec.field}' is present`);
|
|
157
|
+
}
|
|
158
|
+
case "string-equality": {
|
|
159
|
+
const v = readTracePath(trace, spec.field);
|
|
160
|
+
const s = typeof v === "string" ? v : haystack(v);
|
|
161
|
+
const a = spec.caseInsensitive === true ? s.toLowerCase() : s;
|
|
162
|
+
const b = spec.caseInsensitive === true ? spec.expected.toLowerCase() : spec.expected;
|
|
163
|
+
return a === b
|
|
164
|
+
? pass(`'${spec.field}' equals expected`)
|
|
165
|
+
: fail(`'${spec.field}'='${s}' != expected '${spec.expected}'`);
|
|
166
|
+
}
|
|
167
|
+
case "format-validity": {
|
|
168
|
+
const v = readTracePath(trace, spec.field);
|
|
169
|
+
const s = typeof v === "string" ? v : haystack(v);
|
|
170
|
+
return new RegExp(spec.pattern).test(s)
|
|
171
|
+
? pass(`'${spec.field}' matches /${spec.pattern}/`)
|
|
172
|
+
: fail(`'${spec.field}'='${s}' does not match /${spec.pattern}/`);
|
|
173
|
+
}
|
|
174
|
+
case "schema-conformance": {
|
|
175
|
+
const v = readTracePath(trace, spec.field);
|
|
176
|
+
if (v === null || typeof v !== "object") {
|
|
177
|
+
return fail(`'${spec.field}' is not an object`);
|
|
178
|
+
}
|
|
179
|
+
const obj = v as Record<string, unknown>;
|
|
180
|
+
const missing = spec.requiredKeys.filter((k) => obj[k] === undefined);
|
|
181
|
+
return missing.length === 0
|
|
182
|
+
? pass(`'${spec.field}' has all required keys`)
|
|
183
|
+
: fail(`'${spec.field}' missing keys: ${missing.join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
case "ref-integrity": {
|
|
186
|
+
const producerTokens = tokens(readTracePath(trace, spec.producer));
|
|
187
|
+
const hay = haystack(readTracePath(trace, spec.consumer));
|
|
188
|
+
const missing = producerTokens.filter((t) => !hay.includes(t));
|
|
189
|
+
return missing.length === 0
|
|
190
|
+
? pass(`all ${producerTokens.length} '${spec.producer}' value(s) present in '${spec.consumer}'`)
|
|
191
|
+
: fail(`'${spec.consumer}' is missing producer value(s): ${missing.join(", ")}`);
|
|
192
|
+
}
|
|
193
|
+
case "recovery-after-failure": {
|
|
194
|
+
// TEMPORAL: find the FIRST observation whose `failField` marks a failure, then
|
|
195
|
+
// require a recovery-tool observation AFTER it. No failure ⇒ vacuously pass.
|
|
196
|
+
let failIdx = -1;
|
|
197
|
+
for (let i = 0; i < trace.observations.length; i++) {
|
|
198
|
+
const v = navigate(trace.observations[i], spec.failField);
|
|
199
|
+
if (v !== undefined && coerce(v) === spec.failEquals) {
|
|
200
|
+
failIdx = i;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (failIdx === -1) {
|
|
205
|
+
return pass(
|
|
206
|
+
`no observation has '${spec.failField}'=='${spec.failEquals}' — no failure to recover from`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const recoverySet = new Set(spec.recoveryTools);
|
|
210
|
+
const after = trace.observations.slice(failIdx + 1);
|
|
211
|
+
const recovered = after.find((o) => recoverySet.has(obsName(o)));
|
|
212
|
+
return recovered !== undefined
|
|
213
|
+
? pass(
|
|
214
|
+
`failure at obs#${failIdx} ('${spec.failField}'=='${spec.failEquals}') was followed by ` +
|
|
215
|
+
`recovery tool '${obsName(recovered)}'`,
|
|
216
|
+
)
|
|
217
|
+
: fail(
|
|
218
|
+
`failure at obs#${failIdx} ('${spec.failField}'=='${spec.failEquals}') with NO recovery ` +
|
|
219
|
+
`tool (${spec.recoveryTools.join(" | ")}) AFTER it — silent drop`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
case "tool-output-failure": {
|
|
223
|
+
const calls = trace.observations.filter((o) => obsName(o) === spec.tool);
|
|
224
|
+
if (calls.length === 0) {
|
|
225
|
+
return pass(`no '${spec.tool}' observation present — nothing to check`);
|
|
226
|
+
}
|
|
227
|
+
const failed = calls.filter((o) => {
|
|
228
|
+
const v = navigate(o, spec.successPath);
|
|
229
|
+
return v === false || coerce(v) === "false";
|
|
230
|
+
});
|
|
231
|
+
return failed.length === 0
|
|
232
|
+
? pass(`all ${calls.length} '${spec.tool}' call(s) report '${spec.successPath}' != false`)
|
|
233
|
+
: fail(
|
|
234
|
+
`'${spec.tool}' reports failure ('${spec.successPath}'==false) in ` +
|
|
235
|
+
`${failed.length}/${calls.length} call(s)`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
default: {
|
|
239
|
+
// exhaustiveness: an unknown primitive is a fail-loud error.
|
|
240
|
+
const unknown = spec as { primitive?: unknown };
|
|
241
|
+
throw new Error(
|
|
242
|
+
`runCodeEval: unknown code-eval primitive '${String(unknown.primitive)}'. ` +
|
|
243
|
+
"Known: presence | string-equality | format-validity | schema-conformance | " +
|
|
244
|
+
"ref-integrity | recovery-after-failure | tool-output-failure.",
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/codegen-evals.ts — Path B (F14): emit CODE-WRITTEN evals in the target's
|
|
3
|
+
* own language (Type A — PURE string codegen).
|
|
4
|
+
* ---------------------------------------------------------------------------
|
|
5
|
+
* Path B of the EVAL-stage engine fork (eval-engine.ts). For a code-FRAMEWORK
|
|
6
|
+
* target (Mastra/TS, runtime=bun) this emits a SELF-CONTAINED eval-suite source
|
|
7
|
+
* file the user runs DIRECTLY — no Claude-Code sub-agents anywhere (F14):
|
|
8
|
+
*
|
|
9
|
+
* - deterministic CODE-CHECKS (the `codeCheck` helper — objective, no LLM)
|
|
10
|
+
* - an LLM-JUDGE call VIA SDK (the `judgeViaSdk` seam — provider SDK, NOT a
|
|
11
|
+
* CC sub-agent; the user wires their own key)
|
|
12
|
+
* - eval-criteria checks → a per-criterion pass/fail + a gate (runVerdict)
|
|
13
|
+
* - the scorecard JSON written to a DISCOVERABLE output sink.
|
|
14
|
+
*
|
|
15
|
+
* The emitted code reaches back into NOTHING in `.claude/skills/` (it ships INTO
|
|
16
|
+
* the target's repo). This module is PURE string assembly — same input →
|
|
17
|
+
* byte-identical source (no clock / random / network), so the generated suite is
|
|
18
|
+
* itself reproducible. Subject-agnostic: criteria are DATA.
|
|
19
|
+
*
|
|
20
|
+
* Austerity note: this is the Path-B analog of the EV-050 in-house/export seam —
|
|
21
|
+
* it EMITS a provider-calling artifact for the user to run; the DEFAULT
|
|
22
|
+
* agent-dispatch (Path A) path never imports it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** A criterion to codegen — the minimal Path-B slice (maps from agentspec success_criteria). */
|
|
26
|
+
export interface CodegenCriterion {
|
|
27
|
+
id: string;
|
|
28
|
+
statement: string;
|
|
29
|
+
/** `code-check` → a deterministic check; `llm-judge` → an SDK judge call. */
|
|
30
|
+
type: "code-check" | "llm-judge";
|
|
31
|
+
goal: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CodegenInput {
|
|
35
|
+
subject: string;
|
|
36
|
+
/** the target runtime ("bun" | "node" | "deno" …) — drives the emitted module style. */
|
|
37
|
+
runtime: string;
|
|
38
|
+
targetFramework: string;
|
|
39
|
+
criteria: CodegenCriterion[];
|
|
40
|
+
/** the discoverable sink the suite writes its scorecard to. */
|
|
41
|
+
outputSink: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** JSON-encode a value for safe embedding as a source literal. PURE. */
|
|
45
|
+
function lit(value: unknown): string {
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emit a portable, self-contained eval-suite SOURCE file (TS) for the target.
|
|
51
|
+
* Deterministic. THROWS on an empty criteria set or an unknown criterion type
|
|
52
|
+
* (fail-loud — never silently emit a no-op suite or skip a criterion). PURE.
|
|
53
|
+
*/
|
|
54
|
+
export function codegenEvalSuite(input: CodegenInput): string {
|
|
55
|
+
if (input.criteria.length === 0) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"codegenEvalSuite: no criteria — there is nothing to write evals for. " +
|
|
58
|
+
"Materialize criteria from the agentspec.evals.success_criteria first.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
for (const c of input.criteria) {
|
|
62
|
+
if (c.type !== "code-check" && c.type !== "llm-judge") {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`codegenEvalSuite: unknown criterion type '${String((c as { type: unknown }).type)}' ` +
|
|
65
|
+
`on criterion '${c.id}'. Expected 'code-check' | 'llm-judge'.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The criteria are embedded as data; the runner dispatches by `type` at runtime.
|
|
71
|
+
const criteriaLiteral = lit(
|
|
72
|
+
input.criteria.map((c) => ({ id: c.id, statement: c.statement, type: c.type, goal: c.goal })),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// NOTE: keep this a single template literal so the codegen is PURE + deterministic.
|
|
76
|
+
// It deliberately contains NO Claude-Code dispatch — the only LLM path is the
|
|
77
|
+
// user-supplied `judgeViaSdk` SDK seam (Path B portability, F14).
|
|
78
|
+
return `/**
|
|
79
|
+
* code-evals.ts — PORTABLE code-written eval suite for ${input.subject}
|
|
80
|
+
* Target: ${input.targetFramework} (runtime: ${input.runtime})
|
|
81
|
+
* GENERATED by mutagent-evaluator (Path B — code-written). Runs WITHOUT Claude Code.
|
|
82
|
+
*
|
|
83
|
+
* Wire two seams to your target:
|
|
84
|
+
* - runAgent(query): call YOUR built ${input.targetFramework} agent → its output.
|
|
85
|
+
* - judgeViaSdk(prompt): call YOUR provider SDK (the LLM judge). NOT a Claude-Code
|
|
86
|
+
* sub-agent — your own key, your own SDK. Returns {result:"pass"|"fail", critique}.
|
|
87
|
+
*
|
|
88
|
+
* Then: \`${input.runtime} run code-evals.ts <dataset.json>\` → writes the scorecard to
|
|
89
|
+
* ${input.outputSink}scorecard.json (the discoverable sink).
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
export interface DatasetCase { id: string; query: string; tuple?: Record<string, string>; }
|
|
93
|
+
export interface CriterionVerdict { criterionId: string; result: "pass" | "fail"; critique: string; }
|
|
94
|
+
export interface Scorecard {
|
|
95
|
+
subject: string;
|
|
96
|
+
perCriterion: Record<string, { pass: number; fail: number }>;
|
|
97
|
+
runVerdict: "pass" | "fail";
|
|
98
|
+
rows: { caseId: string; verdicts: CriterionVerdict[] }[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The agentspec-derived criteria (embedded as DATA — subject-agnostic). */
|
|
102
|
+
const CRITERIA: { id: string; statement: string; type: "code-check" | "llm-judge"; goal: string }[] =
|
|
103
|
+
${criteriaLiteral};
|
|
104
|
+
|
|
105
|
+
// ── seam 1: run YOUR built agent (wire this to your ${input.targetFramework} entrypoint) ──
|
|
106
|
+
export type RunAgent = (query: string) => Promise<{ output: string; trace?: unknown }>;
|
|
107
|
+
|
|
108
|
+
// ── seam 2: your provider-SDK LLM judge (NOT a Claude-Code sub-agent — portable) ──
|
|
109
|
+
export type JudgeViaSdk = (prompt: string) => Promise<{ result: "pass" | "fail"; critique: string }>;
|
|
110
|
+
|
|
111
|
+
/** Deterministic CODE check for an objective criterion (no LLM). Extend per criterion id. */
|
|
112
|
+
export function codeCheck(criterionId: string, output: string): CriterionVerdict {
|
|
113
|
+
// Default code-check: non-empty, well-formed output. Specialize by criterionId as needed.
|
|
114
|
+
const ok = typeof output === "string" && output.trim().length > 0;
|
|
115
|
+
return { criterionId, result: ok ? "pass" : "fail", critique: ok ? "output present" : "empty output" };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** LLM-judge a subjective criterion VIA the user's SDK seam (critique-before-verdict, binary). */
|
|
119
|
+
export async function judgeCriterion(
|
|
120
|
+
judgeViaSdk: JudgeViaSdk,
|
|
121
|
+
criterion: { id: string; statement: string },
|
|
122
|
+
query: string,
|
|
123
|
+
output: string,
|
|
124
|
+
): Promise<CriterionVerdict> {
|
|
125
|
+
const prompt =
|
|
126
|
+
"Critique FIRST, then a BINARY verdict. Criterion: " + criterion.statement +
|
|
127
|
+
"\\nUser query: " + query + "\\nAgent output: " + output +
|
|
128
|
+
"\\nRespond {critique, result: 'pass'|'fail'}.";
|
|
129
|
+
const { result, critique } = await judgeViaSdk(prompt);
|
|
130
|
+
return { criterionId: criterion.id, result, critique };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Run the full suite over a dataset → a gate-bearing scorecard. Portable. */
|
|
134
|
+
export async function runEvalSuite(
|
|
135
|
+
dataset: DatasetCase[],
|
|
136
|
+
runAgent: RunAgent,
|
|
137
|
+
judgeViaSdk: JudgeViaSdk,
|
|
138
|
+
): Promise<Scorecard> {
|
|
139
|
+
const perCriterion: Record<string, { pass: number; fail: number }> = {};
|
|
140
|
+
for (const c of CRITERIA) perCriterion[c.id] = { pass: 0, fail: 0 };
|
|
141
|
+
const rows: { caseId: string; verdicts: CriterionVerdict[] }[] = [];
|
|
142
|
+
for (const dc of dataset) {
|
|
143
|
+
const { output } = await runAgent(dc.query);
|
|
144
|
+
const verdicts: CriterionVerdict[] = [];
|
|
145
|
+
for (const c of CRITERIA) {
|
|
146
|
+
const v =
|
|
147
|
+
c.type === "code-check"
|
|
148
|
+
? codeCheck(c.id, output)
|
|
149
|
+
: await judgeCriterion(judgeViaSdk, c, dc.query, output);
|
|
150
|
+
perCriterion[c.id][v.result] += 1;
|
|
151
|
+
verdicts.push(v);
|
|
152
|
+
}
|
|
153
|
+
rows.push({ caseId: dc.id, verdicts });
|
|
154
|
+
}
|
|
155
|
+
const anyFail = Object.values(perCriterion).some((p) => p.fail > 0);
|
|
156
|
+
return { subject: ${lit(input.subject)}, perCriterion, runVerdict: anyFail ? "fail" : "pass", rows };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** CLI: \`${input.runtime} run code-evals.ts <dataset.json>\` → write the scorecard to the sink. */
|
|
160
|
+
export async function main(
|
|
161
|
+
datasetPath: string,
|
|
162
|
+
runAgent: RunAgent,
|
|
163
|
+
judgeViaSdk: JudgeViaSdk,
|
|
164
|
+
writeFile: (path: string, body: string) => Promise<void>,
|
|
165
|
+
readFile: (path: string) => Promise<string>,
|
|
166
|
+
): Promise<Scorecard> {
|
|
167
|
+
const dataset = JSON.parse(await readFile(datasetPath)) as DatasetCase[];
|
|
168
|
+
const scorecard = await runEvalSuite(dataset, runAgent, judgeViaSdk);
|
|
169
|
+
await writeFile(${lit(input.outputSink)} + "scorecard.json", JSON.stringify(scorecard, null, 2));
|
|
170
|
+
return scorecard;
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/cold-start-project.ts — EV-5.3 the EvalTrace → ColdTrace projection.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* The COLD front-door (`cold-start-sampler.ts`) balances a bootstrap ✓/✗ suite
|
|
5
|
+
* from MECHANICAL labels alone (no LLM judge yet). Its input is a `ColdTrace`
|
|
6
|
+
* (id + `MechanicalSignals` + dedup keys). The determiner PREP stage, however,
|
|
7
|
+
* works over `EvalTrace[]` (the projection of the handed-over UniTF export). This
|
|
8
|
+
* module is the thin, PURE adapter between the two — and the SINGLE place the
|
|
9
|
+
* cold sampler is wired into the determiner/discover path.
|
|
10
|
+
*
|
|
11
|
+
* ── HONEST-NULL projection (why the mapping looks the way it does) ────────────
|
|
12
|
+
* The `EvalTrace` contract does NOT carry a generic `ext.signals` /
|
|
13
|
+
* `ext.classification` block — the UniTF→EvalTrace projection (`unitf-to-
|
|
14
|
+
* evaltrace.ts`) FLATTENS only the fields it can ground onto the top level
|
|
15
|
+
* (`errored` · `status` · `incomplete` · `scores`) and the UniTF source `ext`
|
|
16
|
+
* itself carries just `ext.eval` (the §9.4.2 fidelity marker). So the mechanical
|
|
17
|
+
* signals are read from what ACTUALLY survives to `EvalTrace`, and every signal
|
|
18
|
+
* with no grounded source stays UNKNOWN (absent), never a fabricated `false`:
|
|
19
|
+
*
|
|
20
|
+
* MechanicalSignals.hasError ← EvalTrace.errored (ERROR family)
|
|
21
|
+
* MechanicalSignals.hasApiErrors ← EvalTrace.status === "error" (ERROR family)
|
|
22
|
+
* MechanicalSignals.incomplete ← EvalTrace.incomplete (TERMINAL family)
|
|
23
|
+
* MechanicalSignals.hasScore/minScore ← numeric EvalTrace.scores (SCORE family)
|
|
24
|
+
* MechanicalSignals.hasChatFeedback/negativeReaction ← UNKNOWN — the FEEDBACK
|
|
25
|
+
* family is not carried on EvalTrace today; omitted (never coerced false).
|
|
26
|
+
* ColdTrace.scenario ← UNKNOWN — no classification field on EvalTrace, so a
|
|
27
|
+
* trace has no dedup key and is kept (never a false collapse).
|
|
28
|
+
* ColdTrace.worthiness ← UNKNOWN — no worthiness field on EvalTrace (dedup is a
|
|
29
|
+
* no-op without a scenario key anyway).
|
|
30
|
+
*
|
|
31
|
+
* PURE + deterministic: no clock, no random, no network. A given `EvalTrace[]`
|
|
32
|
+
* always yields the identical selection (reproducible cold suites — C-PIN-adjacent).
|
|
33
|
+
*/
|
|
34
|
+
import {
|
|
35
|
+
coldStartSample,
|
|
36
|
+
type ColdStartSuite,
|
|
37
|
+
type ColdTrace,
|
|
38
|
+
type MechanicalSignals,
|
|
39
|
+
} from "./cold-start-sampler.ts";
|
|
40
|
+
import type { EvalTrace } from "./contracts/eval-types.ts";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read the SCORE family off `EvalTrace.scores`. Scores are carried verbatim
|
|
44
|
+
* (`unknown[]`, Langfuse-style `{ name, value }`). Only FINITE NUMERIC `value`s
|
|
45
|
+
* count toward the cold SCORE signal; a non-numeric or absent value contributes
|
|
46
|
+
* nothing. Returns `{}` (both UNKNOWN) when there is no numeric score — honest-
|
|
47
|
+
* null, never a fabricated `hasScore:false`.
|
|
48
|
+
*/
|
|
49
|
+
function scoreSignals(scores: unknown[] | undefined): Pick<MechanicalSignals, "hasScore" | "minScore"> {
|
|
50
|
+
if (scores === undefined || scores.length === 0) return {};
|
|
51
|
+
let min = Number.POSITIVE_INFINITY;
|
|
52
|
+
let seen = false;
|
|
53
|
+
for (const s of scores) {
|
|
54
|
+
if (s === null || typeof s !== "object") continue;
|
|
55
|
+
const value = (s as { value?: unknown }).value;
|
|
56
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
57
|
+
seen = true;
|
|
58
|
+
if (value < min) min = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return seen ? { hasScore: true, minScore: min } : {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Project one `EvalTrace` → the cold sampler's `ColdTrace`. Honest-null: only
|
|
66
|
+
* grounded signals are set; everything else is left absent (UNKNOWN). Explicit
|
|
67
|
+
* about the ERROR/TERMINAL/SCORE families it can ground and the FEEDBACK family
|
|
68
|
+
* it cannot (see the module header).
|
|
69
|
+
*/
|
|
70
|
+
export function projectEvalTraceToCold(trace: EvalTrace): ColdTrace {
|
|
71
|
+
const signals: MechanicalSignals = {};
|
|
72
|
+
// ERROR family — `errored` is the always-set structured flag (false = a grounded
|
|
73
|
+
// not-errored). `status === "error"` is the raw carried tri-state; surface it as
|
|
74
|
+
// the API-error signal when the source supplied it.
|
|
75
|
+
if (trace.errored !== undefined) signals.hasError = trace.errored;
|
|
76
|
+
if (trace.status === "error") signals.hasApiErrors = true;
|
|
77
|
+
// TERMINAL family — the §9.4.2 truncation marker (absent ⇒ UNKNOWN, never false).
|
|
78
|
+
if (trace.incomplete !== undefined) signals.incomplete = trace.incomplete;
|
|
79
|
+
// SCORE family — derived from any numeric platform score.
|
|
80
|
+
Object.assign(signals, scoreSignals(trace.scores));
|
|
81
|
+
// FEEDBACK family (hasChatFeedback/negativeReaction), scenario, worthiness: NOT
|
|
82
|
+
// carried on EvalTrace — deliberately omitted so they read UNKNOWN downstream.
|
|
83
|
+
return { id: trace.id, signals };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Project a batch of `EvalTrace` → `ColdTrace[]`, order-preserving + PURE. */
|
|
87
|
+
export function projectColdTraces(traces: EvalTrace[]): ColdTrace[] {
|
|
88
|
+
return traces.map(projectEvalTraceToCold);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The cold-start selection metadata surfaced into the discover/determiner report
|
|
93
|
+
* (the determiner-stage `manifest.json`). `lowConfidence` is the load-bearing
|
|
94
|
+
* flag: a thin-negative cold suite MUST have its first judge pass treated as
|
|
95
|
+
* LOW-CONFIDENCE and re-balanced once real verdicts exist.
|
|
96
|
+
*/
|
|
97
|
+
export interface ColdStartMeta {
|
|
98
|
+
/** the sampler's target suite size (default = incoming trace count → pass-through). */
|
|
99
|
+
targetSize: number;
|
|
100
|
+
/** how many traces the batch handed to the sampler. */
|
|
101
|
+
input: number;
|
|
102
|
+
/** how many traces the cold suite selected (≤ input; = input on pass-through). */
|
|
103
|
+
selected: number;
|
|
104
|
+
passCount: number;
|
|
105
|
+
failCount: number;
|
|
106
|
+
/** the ✗-pool was too thin — treat the first cold judge pass as LOW-CONFIDENCE. */
|
|
107
|
+
lowConfidence: boolean;
|
|
108
|
+
/** the ✗-pool was rarer than its balanced half → every available fail was taken. */
|
|
109
|
+
oversampledFailPool: boolean;
|
|
110
|
+
/** BOTH non-empty pools are represented (never a success-only bootstrap suite). */
|
|
111
|
+
minBothHeld: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Wire the cold-start sampler over an `EvalTrace[]` batch and map the balanced
|
|
116
|
+
* suite BACK to the concrete `EvalTrace[]` the determiner PREP consumes. This is
|
|
117
|
+
* the SAFE call-site helper: it subsamples FIRST, so the untouched
|
|
118
|
+
* `prepDeterminerTasks` still emits exactly one determiner task per SELECTED
|
|
119
|
+
* trace. `size` defaults to the incoming count — a pass-through that only
|
|
120
|
+
* subsamples when an explicit smaller `size` is given (or the batch's own
|
|
121
|
+
* ✓/✗ imbalance triggers oversample flagging). PURE + deterministic.
|
|
122
|
+
*/
|
|
123
|
+
export function selectColdStartSuite(
|
|
124
|
+
traces: EvalTrace[],
|
|
125
|
+
opts: { size?: number; minBoth?: number; prevalenceFloor?: number; scoreThreshold?: number } = {},
|
|
126
|
+
): { selected: EvalTrace[]; meta: ColdStartMeta; suite: ColdStartSuite } {
|
|
127
|
+
const size = opts.size ?? traces.length;
|
|
128
|
+
const suite = coldStartSample(projectColdTraces(traces), {
|
|
129
|
+
size,
|
|
130
|
+
minBoth: opts.minBoth,
|
|
131
|
+
prevalenceFloor: opts.prevalenceFloor,
|
|
132
|
+
scoreThreshold: opts.scoreThreshold,
|
|
133
|
+
});
|
|
134
|
+
// Map the selected cold reps back to their source EvalTrace, preserving the
|
|
135
|
+
// suite's (✓-then-✗) order. `filter` guards the impossible missing-id case.
|
|
136
|
+
const byId = new Map(traces.map((t) => [t.id, t] as const));
|
|
137
|
+
const selected = suite.selected
|
|
138
|
+
.map((c) => byId.get(c.id))
|
|
139
|
+
.filter((t): t is EvalTrace => t !== undefined);
|
|
140
|
+
const meta: ColdStartMeta = {
|
|
141
|
+
targetSize: size,
|
|
142
|
+
input: traces.length,
|
|
143
|
+
selected: selected.length,
|
|
144
|
+
passCount: suite.passCount,
|
|
145
|
+
failCount: suite.failCount,
|
|
146
|
+
lowConfidence: suite.lowConfidence,
|
|
147
|
+
oversampledFailPool: suite.oversampledFailPool,
|
|
148
|
+
minBothHeld: suite.minBothHeld,
|
|
149
|
+
};
|
|
150
|
+
return { selected, meta, suite };
|
|
151
|
+
}
|