@tangle-network/agent-eval 0.84.0 → 0.86.0

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/dist/index.js CHANGED
@@ -230,6 +230,9 @@ import {
230
230
  describeTraceInsightScope,
231
231
  domainEvidencePattern,
232
232
  exportRunAsOtlp,
233
+ extractUsage,
234
+ extractUsageFromResponse,
235
+ extractUsageFromSse,
233
236
  flattenOtlpExportToNdjson,
234
237
  inferDomainKeywords,
235
238
  iterateRawCalls,
@@ -240,7 +243,7 @@ import {
240
243
  scoreTraceInsightReadiness,
241
244
  tokenizeDomainWords,
242
245
  traceAnalystOnRunComplete
243
- } from "./chunk-XQL22JDG.js";
246
+ } from "./chunk-P2J6SOXT.js";
244
247
  import {
245
248
  DEFAULT_REDACTION_RULES,
246
249
  REDACTION_VERSION,
@@ -1751,6 +1754,107 @@ function canonicalize2(value) {
1751
1754
  return out;
1752
1755
  }
1753
1756
 
1757
+ // src/integrity/preflight.ts
1758
+ function stripSlash(url) {
1759
+ return url.replace(/\/+$/, "");
1760
+ }
1761
+ function errorMessage(body) {
1762
+ if (body == null || typeof body !== "object") return null;
1763
+ const b = body;
1764
+ if (b.error && typeof b.error.message === "string") return b.error.message;
1765
+ if (typeof b.message === "string") return b.message;
1766
+ return null;
1767
+ }
1768
+ async function preflightModels(opts) {
1769
+ const fetchImpl = opts.fetchImpl ?? fetch;
1770
+ const baseUrl = stripSlash(opts.baseUrl);
1771
+ const authHeaders = { authorization: `Bearer ${opts.apiKey}` };
1772
+ let served;
1773
+ try {
1774
+ const res = await fetchImpl(`${baseUrl}/models`, { method: "GET", headers: authHeaders });
1775
+ if (!res.ok) {
1776
+ const text = await res.text().catch(() => "");
1777
+ return {
1778
+ succeeded: false,
1779
+ value: null,
1780
+ error: `preflightModels: GET ${baseUrl}/models \u2192 ${res.status} ${text.slice(0, 400)}`
1781
+ };
1782
+ }
1783
+ const body = await res.json();
1784
+ const ids = Array.isArray(body.data) ? body.data : [];
1785
+ served = new Set(ids.map((m) => m.id).filter((id) => typeof id === "string"));
1786
+ } catch (err) {
1787
+ return {
1788
+ succeeded: false,
1789
+ value: null,
1790
+ error: `preflightModels: GET ${baseUrl}/models failed \u2014 ${err instanceof Error ? err.message : String(err)}`
1791
+ };
1792
+ }
1793
+ const results = [];
1794
+ for (const model of opts.models) {
1795
+ const listed = served.has(model);
1796
+ if (!opts.probe) {
1797
+ results.push({ model, listed, served: null, status: null, detail: null });
1798
+ continue;
1799
+ }
1800
+ try {
1801
+ const res = await fetchImpl(`${baseUrl}/chat/completions`, {
1802
+ method: "POST",
1803
+ headers: { ...authHeaders, "content-type": "application/json" },
1804
+ body: JSON.stringify({
1805
+ model,
1806
+ messages: [{ role: "user", content: "ping" }],
1807
+ max_tokens: 5
1808
+ })
1809
+ });
1810
+ let detail = null;
1811
+ if (!res.ok) {
1812
+ const body = await res.json().catch(() => null);
1813
+ detail = errorMessage(body);
1814
+ }
1815
+ results.push({ model, listed, served: res.ok, status: res.status, detail });
1816
+ } catch (err) {
1817
+ return {
1818
+ succeeded: false,
1819
+ value: null,
1820
+ error: `preflightModels: probe POST ${baseUrl}/chat/completions (model ${model}) failed \u2014 ${err instanceof Error ? err.message : String(err)}`
1821
+ };
1822
+ }
1823
+ }
1824
+ return { succeeded: true, value: results, error: null };
1825
+ }
1826
+ var ModelsUnreachableError = class extends AgentEvalError {
1827
+ constructor(message, results) {
1828
+ super("config", message);
1829
+ this.results = results;
1830
+ this.name = "ModelsUnreachableError";
1831
+ }
1832
+ results;
1833
+ };
1834
+ function describeFailure(r) {
1835
+ if (!r.listed) {
1836
+ const probeNote = r.served === false ? ` (probe ${r.status}${r.detail ? `: ${r.detail}` : ""})` : "";
1837
+ return `${r.model}: not in /models${probeNote}`;
1838
+ }
1839
+ return `${r.model}: listed but probe ${r.status}${r.detail ? ` \u2014 ${r.detail}` : ""}`;
1840
+ }
1841
+ async function assertModelsServed(opts) {
1842
+ const outcome = await preflightModels(opts);
1843
+ if (!outcome.succeeded || outcome.value === null) {
1844
+ throw new ConfigError(
1845
+ outcome.error ?? "assertModelsServed: preflight failed without an error message"
1846
+ );
1847
+ }
1848
+ const dead = outcome.value.filter((r) => !r.listed || r.served === false);
1849
+ if (dead.length > 0) {
1850
+ throw new ModelsUnreachableError(
1851
+ `assertModelsServed: ${dead.length}/${outcome.value.length} model(s) unreachable on the router \u2014 ${dead.map(describeFailure).join("; ")}`,
1852
+ outcome.value
1853
+ );
1854
+ }
1855
+ return outcome.value;
1856
+ }
1857
+
1754
1858
  // src/integrity/single-backend.ts
1755
1859
  var SingleBackendError = class extends AgentEvalError {
1756
1860
  constructor(message, report) {
@@ -1760,7 +1864,7 @@ var SingleBackendError = class extends AgentEvalError {
1760
1864
  }
1761
1865
  report;
1762
1866
  };
1763
- function stripSlash(url) {
1867
+ function stripSlash2(url) {
1764
1868
  return url.replace(/\/+$/, "");
1765
1869
  }
1766
1870
  function assertSingleBackend(agent, judge, opts = {}) {
@@ -1768,7 +1872,7 @@ function assertSingleBackend(agent, judge, opts = {}) {
1768
1872
  if (agent.kind !== judge.kind) {
1769
1873
  divergences.push({ field: "kind", agent: agent.kind, judge: judge.kind });
1770
1874
  }
1771
- if (stripSlash(agent.baseUrl) !== stripSlash(judge.baseUrl)) {
1875
+ if (stripSlash2(agent.baseUrl) !== stripSlash2(judge.baseUrl)) {
1772
1876
  divergences.push({ field: "baseUrl", agent: agent.baseUrl, judge: judge.baseUrl });
1773
1877
  }
1774
1878
  if (agent.model !== judge.model) {
@@ -2290,16 +2394,16 @@ function aggregatePrReviewScore(dimensions, weights = {}) {
2290
2394
  return (merged.recall * clamp01(dimensions.recall) + merged.precision * clamp01(dimensions.precision) + merged.actionability * clamp01(dimensions.actionability) + merged.severityCalibration * clamp01(dimensions.severityCalibration) + merged.lowNoise * clamp01(dimensions.lowNoise)) / weightSum;
2291
2395
  }
2292
2396
  function matchReferenceFindings(references, comments) {
2293
- const matches = [];
2397
+ const matches2 = [];
2294
2398
  const usedCommentIds = /* @__PURE__ */ new Set();
2295
2399
  for (const reference of references) {
2296
2400
  const candidates = comments.filter((comment) => !usedCommentIds.has(comment.id)).map((comment) => ({ comment, score: matchScore(reference, comment) })).filter(({ score }) => score >= 0.55).sort((a, b) => b.score - a.score);
2297
2401
  const best = candidates[0];
2298
2402
  if (!best) continue;
2299
2403
  usedCommentIds.add(best.comment.id);
2300
- matches.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
2404
+ matches2.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
2301
2405
  }
2302
- return matches;
2406
+ return matches2;
2303
2407
  }
2304
2408
  function matchScore(reference, comment) {
2305
2409
  let score = 0;
@@ -2326,9 +2430,9 @@ function isActionableComment(comment) {
2326
2430
  body
2327
2431
  );
2328
2432
  }
2329
- function isSeverityAligned(comment, references, matches) {
2433
+ function isSeverityAligned(comment, references, matches2) {
2330
2434
  if (!comment.severity) return false;
2331
- const match = matches.find((candidate) => candidate.commentId === comment.id);
2435
+ const match = matches2.find((candidate) => candidate.commentId === comment.id);
2332
2436
  if (!match) return comment.severity === "nit" || comment.severity === "low";
2333
2437
  const reference = references.find((candidate) => candidate.id === match.referenceId);
2334
2438
  if (!reference) return false;
@@ -2624,28 +2728,28 @@ function analyzeAntiSlop(outputs, config) {
2624
2728
  }
2625
2729
  }
2626
2730
  for (const re of config.hedgingPatterns) {
2627
- const matches = output.match(
2731
+ const matches2 = output.match(
2628
2732
  new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
2629
2733
  );
2630
- if (matches) {
2631
- counts.hedging += matches.length;
2734
+ if (matches2) {
2735
+ counts.hedging += matches2.length;
2632
2736
  issues.push({
2633
2737
  category: "hedging",
2634
- detail: `${matches.length}x ${re.source}`,
2635
- example: matches[0]
2738
+ detail: `${matches2.length}x ${re.source}`,
2739
+ example: matches2[0]
2636
2740
  });
2637
2741
  }
2638
2742
  }
2639
2743
  for (const re of config.apologyPatterns) {
2640
- const matches = output.match(
2744
+ const matches2 = output.match(
2641
2745
  new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
2642
2746
  );
2643
- if (matches) {
2644
- counts.apology += matches.length;
2747
+ if (matches2) {
2748
+ counts.apology += matches2.length;
2645
2749
  issues.push({
2646
2750
  category: "apology",
2647
- detail: `${matches.length}x ${re.source}`,
2648
- example: matches[0]
2751
+ detail: `${matches2.length}x ${re.source}`,
2752
+ example: matches2[0]
2649
2753
  });
2650
2754
  }
2651
2755
  }
@@ -2915,14 +3019,14 @@ async function runHarnessExperiment(config) {
2915
3019
  const score = config.score ?? ((trace) => critic.scoreTrace(trace));
2916
3020
  const results = await mapLimit(jobs, config.parallelism ?? 1, async (request) => {
2917
3021
  const trace = await config.adapter.run(request);
2918
- const runScore3 = await score(trace, request);
3022
+ const runScore4 = await score(trace, request);
2919
3023
  const result = {
2920
3024
  variant: request.variant,
2921
3025
  scenario: request.scenario,
2922
3026
  trialIndex: request.trialIndex,
2923
3027
  trace,
2924
- score: runScore3,
2925
- aggregate: aggregateRunScore(runScore3, config.weights)
3028
+ score: runScore4,
3029
+ aggregate: aggregateRunScore(runScore4, config.weights)
2926
3030
  };
2927
3031
  await config.onResult?.(result);
2928
3032
  return result;
@@ -3568,13 +3672,118 @@ var BudgetGuard = class {
3568
3672
  }
3569
3673
  };
3570
3674
 
3675
+ // src/cost-ledger.ts
3676
+ function modelPriceKey(model) {
3677
+ return isModelPriced(model) ? model : null;
3678
+ }
3679
+ function costForUsage(model, usage) {
3680
+ assertNonNegative(usage.inputTokens, "inputTokens");
3681
+ assertNonNegative(usage.outputTokens, "outputTokens");
3682
+ if (usage.cachedTokens !== void 0) assertNonNegative(usage.cachedTokens, "cachedTokens");
3683
+ const pricing = resolveModelPricing(model);
3684
+ if (!pricing) return { costUsd: 0, costUnknown: true };
3685
+ const billedInput = usage.inputTokens + (usage.cachedTokens ?? 0);
3686
+ return { costUsd: estimateCost(billedInput, usage.outputTokens, model), costUnknown: false };
3687
+ }
3688
+ var CostLedger = class {
3689
+ entries = [];
3690
+ completedTasks = 0;
3691
+ /**
3692
+ * Record one LLM call. The cost is computed from pricing unless
3693
+ * `actualCostUsd` is supplied (a finite observed cost from the provider
3694
+ * response), in which case `costUnknown` is false regardless of pricing.
3695
+ */
3696
+ record(input) {
3697
+ const { costUsd, costUnknown } = costForUsage(input.model, input.usage);
3698
+ const hasActual = typeof input.actualCostUsd === "number" && Number.isFinite(input.actualCostUsd);
3699
+ if (hasActual) assertNonNegative(input.actualCostUsd, "actualCostUsd");
3700
+ const entry = {
3701
+ model: input.model,
3702
+ channel: input.channel,
3703
+ inputTokens: input.usage.inputTokens,
3704
+ outputTokens: input.usage.outputTokens,
3705
+ cachedTokens: input.usage.cachedTokens,
3706
+ costUsd: hasActual ? input.actualCostUsd : costUsd,
3707
+ costUnknown: hasActual ? false : costUnknown,
3708
+ actualCostUsd: hasActual ? input.actualCostUsd : void 0,
3709
+ tags: input.tags,
3710
+ timestamp: input.timestamp ?? Date.now()
3711
+ };
3712
+ this.entries.push(entry);
3713
+ return entry;
3714
+ }
3715
+ /** Increment the completed-task counter (used for cost-per-completed-task). */
3716
+ markCompleted(count = 1) {
3717
+ if (!Number.isInteger(count) || count < 0) {
3718
+ throw new ValidationError(
3719
+ `CostLedger.markCompleted: count must be a non-negative integer, got ${count}`
3720
+ );
3721
+ }
3722
+ this.completedTasks += count;
3723
+ }
3724
+ list() {
3725
+ return [...this.entries];
3726
+ }
3727
+ summary() {
3728
+ const byChannel = /* @__PURE__ */ new Map();
3729
+ const unpriced = /* @__PURE__ */ new Set();
3730
+ let totalCost = 0;
3731
+ let inputTokens = 0;
3732
+ let outputTokens = 0;
3733
+ let cachedTokens = 0;
3734
+ for (const e of this.entries) {
3735
+ totalCost += e.costUsd;
3736
+ inputTokens += e.inputTokens;
3737
+ outputTokens += e.outputTokens;
3738
+ cachedTokens += e.cachedTokens ?? 0;
3739
+ if (e.costUnknown) unpriced.add(e.model);
3740
+ const roll = byChannel.get(e.channel) ?? {
3741
+ channel: e.channel,
3742
+ calls: 0,
3743
+ inputTokens: 0,
3744
+ outputTokens: 0,
3745
+ cachedTokens: 0,
3746
+ costUsd: 0,
3747
+ unpricedCalls: 0
3748
+ };
3749
+ roll.calls += 1;
3750
+ roll.inputTokens += e.inputTokens;
3751
+ roll.outputTokens += e.outputTokens;
3752
+ roll.cachedTokens += e.cachedTokens ?? 0;
3753
+ roll.costUsd += e.costUsd;
3754
+ if (e.costUnknown) roll.unpricedCalls += 1;
3755
+ byChannel.set(e.channel, roll);
3756
+ }
3757
+ return {
3758
+ totalCalls: this.entries.length,
3759
+ inputTokens,
3760
+ outputTokens,
3761
+ cachedTokens,
3762
+ totalCostUsd: totalCost,
3763
+ byChannel: [...byChannel.values()].sort((a, b) => a.channel.localeCompare(b.channel)),
3764
+ unpricedModels: [...unpriced].sort(),
3765
+ fullyPriced: unpriced.size === 0
3766
+ };
3767
+ }
3768
+ /** Total spend divided by completed tasks; null when nothing completed. */
3769
+ costPerCompletedTask() {
3770
+ if (this.completedTasks === 0) return null;
3771
+ return this.summary().totalCostUsd / this.completedTasks;
3772
+ }
3773
+ };
3774
+ function assertNonNegative(n, name) {
3775
+ if (!Number.isFinite(n) || n < 0) {
3776
+ throw new ValidationError(`CostLedger: ${name} must be a non-negative finite number, got ${n}`);
3777
+ }
3778
+ }
3779
+
3571
3780
  // src/cost-tracker.ts
3572
3781
  var CostTracker = class {
3573
3782
  byScenario = /* @__PURE__ */ new Map();
3574
3783
  record(entry) {
3575
3784
  const full = { timestamp: entry.timestamp ?? Date.now(), ...entry };
3576
- assertNonNegative(full.inputTokens, "inputTokens");
3577
- assertNonNegative(full.outputTokens, "outputTokens");
3785
+ assertNonNegative2(full.inputTokens, "inputTokens");
3786
+ assertNonNegative2(full.outputTokens, "outputTokens");
3578
3787
  let bucket = this.byScenario.get(full.scenarioId);
3579
3788
  if (!bucket) {
3580
3789
  bucket = {
@@ -3653,12 +3862,433 @@ function costFor(entry) {
3653
3862
  }
3654
3863
  return estimateCost(entry.inputTokens, entry.outputTokens, entry.model);
3655
3864
  }
3656
- function assertNonNegative(n, name) {
3865
+ function assertNonNegative2(n, name) {
3657
3866
  if (!Number.isFinite(n) || n < 0) {
3658
3867
  throw new Error(`CostTracker: ${name} must be a non-negative finite number, got ${n}`);
3659
3868
  }
3660
3869
  }
3661
3870
 
3871
+ // src/eval-trace-store.ts
3872
+ function runScore(record) {
3873
+ const { holdoutScore, searchScore } = record.outcome;
3874
+ if (typeof holdoutScore === "number") return holdoutScore;
3875
+ if (typeof searchScore === "number") return searchScore;
3876
+ throw new ValidationError(
3877
+ `EvalTraceStore: run ${record.runId} has neither holdoutScore nor searchScore`
3878
+ );
3879
+ }
3880
+ function matches(record, f) {
3881
+ if (f.experimentId && record.experimentId !== f.experimentId) return false;
3882
+ if (f.candidateId && record.candidateId !== f.candidateId) return false;
3883
+ if (f.scenarioId && record.scenarioId !== f.scenarioId) return false;
3884
+ if (f.model && record.model !== f.model) return false;
3885
+ if (f.splitTag && record.splitTag !== f.splitTag) return false;
3886
+ if (f.minScore !== void 0 && runScore(record) < f.minScore) return false;
3887
+ if (f.maxScore !== void 0 && runScore(record) > f.maxScore) return false;
3888
+ if (f.rawEquals && record.outcome.raw[f.rawEquals.key] !== f.rawEquals.value) return false;
3889
+ if (f.where && !f.where(record)) return false;
3890
+ return true;
3891
+ }
3892
+ function inMemoryRunRecordBackend(initial = []) {
3893
+ const rows = initial.map((r) => validateRunRecord(r));
3894
+ return {
3895
+ async append(record) {
3896
+ rows.push(record);
3897
+ },
3898
+ async load() {
3899
+ return [...rows];
3900
+ }
3901
+ };
3902
+ }
3903
+ function jsonlRunRecordBackend(path, opts = {}) {
3904
+ return {
3905
+ async append(record) {
3906
+ const fs2 = await import("fs/promises");
3907
+ const pathMod = await import("path");
3908
+ await fs2.mkdir(pathMod.dirname(path), { recursive: true });
3909
+ await fs2.appendFile(path, `${JSON.stringify(record)}
3910
+ `, "utf8");
3911
+ },
3912
+ async load() {
3913
+ const fs2 = await import("fs/promises");
3914
+ let raw;
3915
+ try {
3916
+ raw = await fs2.readFile(path, "utf8");
3917
+ } catch (err) {
3918
+ if (err.code === "ENOENT") return [];
3919
+ throw err;
3920
+ }
3921
+ const out = [];
3922
+ let lineNo = 0;
3923
+ for (const line of raw.split("\n")) {
3924
+ lineNo++;
3925
+ if (!line.trim()) continue;
3926
+ let parsed;
3927
+ try {
3928
+ parsed = JSON.parse(line);
3929
+ } catch (err) {
3930
+ if (opts.skipInvalid) continue;
3931
+ throw new ValidationError(`EvalTraceStore: ${path}:${lineNo} is not valid JSON`);
3932
+ }
3933
+ if (opts.skipInvalid) {
3934
+ if (isRunRecord(parsed)) out.push(parsed);
3935
+ continue;
3936
+ }
3937
+ try {
3938
+ out.push(validateRunRecord(parsed));
3939
+ } catch (err) {
3940
+ throw new ValidationError(
3941
+ `EvalTraceStore: ${path}:${lineNo} is not a valid RunRecord \u2014 ${err.message}`
3942
+ );
3943
+ }
3944
+ }
3945
+ return out;
3946
+ }
3947
+ };
3948
+ }
3949
+ var EvalTraceStore = class {
3950
+ backend;
3951
+ constructor(backend = inMemoryRunRecordBackend()) {
3952
+ this.backend = backend;
3953
+ }
3954
+ /** Validate and append one run. Throws on an invalid record — the corpus
3955
+ * stays paper-grade. */
3956
+ async append(record) {
3957
+ await this.backend.append(validateRunRecord(record));
3958
+ }
3959
+ async all() {
3960
+ return this.backend.load();
3961
+ }
3962
+ async query(filter = {}) {
3963
+ const rows = await this.backend.load();
3964
+ return rows.filter((r) => matches(r, filter));
3965
+ }
3966
+ /**
3967
+ * Highest-scoring run for a scenario (optionally restricted to a candidate).
3968
+ * Returns null when no run matches. Ties resolve to the earliest-appended run
3969
+ * so the result is stable.
3970
+ */
3971
+ async getBest(scenarioId, opts = {}) {
3972
+ const rows = await this.query({
3973
+ scenarioId,
3974
+ candidateId: opts.candidateId,
3975
+ splitTag: opts.splitTag
3976
+ });
3977
+ if (rows.length === 0) return null;
3978
+ let best = rows[0];
3979
+ let bestScore = runScore(best);
3980
+ for (let i = 1; i < rows.length; i++) {
3981
+ const s = runScore(rows[i]);
3982
+ if (s > bestScore) {
3983
+ best = rows[i];
3984
+ bestScore = s;
3985
+ }
3986
+ }
3987
+ return best;
3988
+ }
3989
+ /**
3990
+ * Compare two candidates on the scenarios they BOTH ran. When a candidate
3991
+ * ran a scenario more than once, its best `runScore` for that scenario is
3992
+ * used. Throws when there is no paired scenario — an unpaired "comparison" is
3993
+ * not one.
3994
+ */
3995
+ async compareRuns(candidateA, candidateB) {
3996
+ if (candidateA === candidateB) {
3997
+ throw new ValidationError(
3998
+ `EvalTraceStore.compareRuns: candidates must differ ("${candidateA}")`
3999
+ );
4000
+ }
4001
+ const rows = await this.backend.load();
4002
+ const bestByScenario = (candidate) => {
4003
+ const m = /* @__PURE__ */ new Map();
4004
+ for (const r of rows) {
4005
+ if (r.candidateId !== candidate) continue;
4006
+ const sid = r.scenarioId;
4007
+ if (!sid) continue;
4008
+ const s = runScore(r);
4009
+ const prev = m.get(sid);
4010
+ if (prev === void 0 || s > prev) m.set(sid, s);
4011
+ }
4012
+ return m;
4013
+ };
4014
+ const aScores = bestByScenario(candidateA);
4015
+ const bScores = bestByScenario(candidateB);
4016
+ const paired = [...aScores.keys()].filter((sid) => bScores.has(sid)).sort();
4017
+ if (paired.length === 0) {
4018
+ throw new ValidationError(
4019
+ `EvalTraceStore.compareRuns: "${candidateA}" and "${candidateB}" share no scenario (need scenarioId on records)`
4020
+ );
4021
+ }
4022
+ let sumA = 0;
4023
+ let sumB = 0;
4024
+ let bWins = 0;
4025
+ let aWins = 0;
4026
+ let ties = 0;
4027
+ for (const sid of paired) {
4028
+ const sa = aScores.get(sid);
4029
+ const sb = bScores.get(sid);
4030
+ sumA += sa;
4031
+ sumB += sb;
4032
+ if (sb > sa) bWins++;
4033
+ else if (sb < sa) aWins++;
4034
+ else ties++;
4035
+ }
4036
+ const meanA = sumA / paired.length;
4037
+ const meanB = sumB / paired.length;
4038
+ return {
4039
+ a: candidateA,
4040
+ b: candidateB,
4041
+ pairedScenarioIds: paired,
4042
+ meanA,
4043
+ meanB,
4044
+ meanDelta: meanB - meanA,
4045
+ bWins,
4046
+ ties,
4047
+ aWins
4048
+ };
4049
+ }
4050
+ };
4051
+
4052
+ // src/experiment-tracker.ts
4053
+ import { execSync } from "child_process";
4054
+ var DEFAULTS = {
4055
+ keepThreshold: 5,
4056
+ regressionThreshold: 5,
4057
+ iqrUnstableAbove: 10,
4058
+ stddevUnstableAbove: Number.POSITIVE_INFINITY,
4059
+ minRepsForVerdict: 3
4060
+ };
4061
+ function resolveThresholds(t) {
4062
+ const r = { ...DEFAULTS, ...t ?? {} };
4063
+ if (r.keepThreshold < 0) {
4064
+ throw new ValidationError(
4065
+ `experiment-tracker: keepThreshold must be >= 0, got ${r.keepThreshold}`
4066
+ );
4067
+ }
4068
+ if (r.regressionThreshold < 0) {
4069
+ throw new ValidationError(
4070
+ `experiment-tracker: regressionThreshold must be >= 0, got ${r.regressionThreshold}`
4071
+ );
4072
+ }
4073
+ if (r.minRepsForVerdict < 1) {
4074
+ throw new ValidationError(
4075
+ `experiment-tracker: minRepsForVerdict must be >= 1, got ${r.minRepsForVerdict}`
4076
+ );
4077
+ }
4078
+ return r;
4079
+ }
4080
+ function median(sorted) {
4081
+ const n = sorted.length;
4082
+ if (n === 0) return 0;
4083
+ const mid = Math.floor(n / 2);
4084
+ return n % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
4085
+ }
4086
+ function stddev(values, mean5) {
4087
+ if (values.length < 2) return 0;
4088
+ const variance = values.reduce((acc, v) => acc + (v - mean5) ** 2, 0) / values.length;
4089
+ return Math.sqrt(variance);
4090
+ }
4091
+ function computeExperimentStats(reps, thresholds) {
4092
+ const t = resolveThresholds(thresholds);
4093
+ const n = reps.length;
4094
+ if (n === 0) {
4095
+ return {
4096
+ median: 0,
4097
+ mean: 0,
4098
+ min: 0,
4099
+ max: 0,
4100
+ iqr: 0,
4101
+ stddev: 0,
4102
+ passRate: null,
4103
+ n: 0,
4104
+ stable: false
4105
+ };
4106
+ }
4107
+ const scores2 = reps.map((r) => {
4108
+ if (!Number.isFinite(r.score)) {
4109
+ throw new ValidationError(`experiment-tracker: rep ${r.rep} has non-finite score ${r.score}`);
4110
+ }
4111
+ return r.score;
4112
+ });
4113
+ const sorted = [...scores2].sort((a, b) => a - b);
4114
+ const mean5 = scores2.reduce((s, v) => s + v, 0) / n;
4115
+ const sd = stddev(scores2, mean5);
4116
+ const spread = iqr(scores2);
4117
+ const rated = reps.filter((r) => typeof r.passed === "boolean");
4118
+ const passRate = rated.length === 0 ? null : rated.filter((r) => r.passed).length / rated.length;
4119
+ const stable = spread < t.iqrUnstableAbove && sd < t.stddevUnstableAbove;
4120
+ return {
4121
+ median: median(sorted),
4122
+ mean: mean5,
4123
+ min: sorted[0],
4124
+ max: sorted[n - 1],
4125
+ iqr: spread,
4126
+ stddev: sd,
4127
+ passRate,
4128
+ n,
4129
+ stable
4130
+ };
4131
+ }
4132
+ function improvementVerdict(candidate, parent, thresholds) {
4133
+ const t = resolveThresholds(thresholds);
4134
+ if (!parent) {
4135
+ return {
4136
+ verdict: "ITERATE",
4137
+ medianDelta: null,
4138
+ reason: "no parent experiment to compare against"
4139
+ };
4140
+ }
4141
+ if (candidate.n < t.minRepsForVerdict || parent.n < t.minRepsForVerdict) {
4142
+ return {
4143
+ verdict: "ITERATE",
4144
+ medianDelta: null,
4145
+ reason: `need >= ${t.minRepsForVerdict} reps on both sides (candidate n=${candidate.n}, parent n=${parent.n})`
4146
+ };
4147
+ }
4148
+ if (!candidate.stable) {
4149
+ return {
4150
+ verdict: "NOISE",
4151
+ medianDelta: candidate.median - parent.median,
4152
+ reason: `candidate unstable (iqr=${candidate.iqr}, stddev=${candidate.stddev.toFixed(2)})`
4153
+ };
4154
+ }
4155
+ const medianDelta2 = candidate.median - parent.median;
4156
+ if (medianDelta2 > t.keepThreshold) {
4157
+ return { verdict: "KEEP", medianDelta: medianDelta2, reason: `median +${medianDelta2} > +${t.keepThreshold}` };
4158
+ }
4159
+ if (medianDelta2 < -t.regressionThreshold) {
4160
+ return {
4161
+ verdict: "REGRESSION",
4162
+ medianDelta: medianDelta2,
4163
+ reason: `median ${medianDelta2} < -${t.regressionThreshold}`
4164
+ };
4165
+ }
4166
+ return {
4167
+ verdict: "NOISE",
4168
+ medianDelta: medianDelta2,
4169
+ reason: `median delta ${medianDelta2} inside noise band [-${t.regressionThreshold}, +${t.keepThreshold}]`
4170
+ };
4171
+ }
4172
+ var gitProvenanceReader = () => {
4173
+ const run = (cmd) => execSync(cmd, { encoding: "utf8" }).trim();
4174
+ const commit = run("git rev-parse --short HEAD");
4175
+ const message = run("git log -1 --format=%s");
4176
+ const changedRaw = run("git diff --name-only HEAD~1");
4177
+ const changedFiles = changedRaw.length === 0 ? [] : changedRaw.split("\n").filter(Boolean);
4178
+ return { commit, message, changedFiles };
4179
+ };
4180
+ function inMemoryExperimentStore(initial = []) {
4181
+ let state = initial.map((e) => structuredClone(e));
4182
+ return {
4183
+ async load() {
4184
+ return state.map((e) => structuredClone(e));
4185
+ },
4186
+ async save(experiments) {
4187
+ state = experiments.map((e) => structuredClone(e));
4188
+ }
4189
+ };
4190
+ }
4191
+ function fileExperimentStore(path) {
4192
+ return {
4193
+ async load() {
4194
+ const fs2 = await import("fs/promises");
4195
+ try {
4196
+ const raw = await fs2.readFile(path, "utf8");
4197
+ const parsed = JSON.parse(raw);
4198
+ if (!Array.isArray(parsed)) {
4199
+ throw new ValidationError(`experiment-tracker: store at ${path} is not a JSON array`);
4200
+ }
4201
+ return parsed;
4202
+ } catch (err) {
4203
+ if (err.code === "ENOENT") return [];
4204
+ throw err;
4205
+ }
4206
+ },
4207
+ async save(experiments) {
4208
+ const fs2 = await import("fs/promises");
4209
+ const pathMod = await import("path");
4210
+ await fs2.mkdir(pathMod.dirname(path), { recursive: true });
4211
+ await fs2.writeFile(path, JSON.stringify(experiments, null, 2), "utf8");
4212
+ }
4213
+ };
4214
+ }
4215
+ var ExperimentTracker = class {
4216
+ store;
4217
+ provenanceReader;
4218
+ thresholds;
4219
+ now;
4220
+ constructor(options = {}) {
4221
+ this.store = options.store ?? inMemoryExperimentStore();
4222
+ this.provenanceReader = options.provenanceReader ?? gitProvenanceReader;
4223
+ this.thresholds = resolveThresholds(options.thresholds);
4224
+ this.now = options.now ?? Date.now;
4225
+ }
4226
+ async create(input) {
4227
+ const experiments = await this.store.load();
4228
+ if (experiments.some((e) => e.id === input.id)) {
4229
+ throw new ValidationError(`experiment-tracker: experiment id "${input.id}" already exists`);
4230
+ }
4231
+ if (input.parentId && !experiments.some((e) => e.id === input.parentId)) {
4232
+ throw new ValidationError(
4233
+ `experiment-tracker: parent experiment "${input.parentId}" not found`
4234
+ );
4235
+ }
4236
+ const provenance = input.provenance ?? await this.provenanceReader();
4237
+ const experiment = {
4238
+ id: input.id,
4239
+ label: input.label,
4240
+ provenance,
4241
+ parentId: input.parentId,
4242
+ changeSummary: input.changeSummary,
4243
+ reps: [],
4244
+ stats: computeExperimentStats([], this.thresholds),
4245
+ verdict: "ITERATE",
4246
+ createdAt: new Date(this.now()).toISOString()
4247
+ };
4248
+ experiments.push(experiment);
4249
+ await this.store.save(experiments);
4250
+ return structuredClone(experiment);
4251
+ }
4252
+ /** Append a rep (its `rep` index defaults to the current rep count) and
4253
+ * recompute stats + verdict. Returns the updated experiment. */
4254
+ async addRep(experimentId, rep) {
4255
+ const experiments = await this.store.load();
4256
+ const exp = experiments.find((e) => e.id === experimentId);
4257
+ if (!exp)
4258
+ throw new ValidationError(`experiment-tracker: experiment "${experimentId}" not found`);
4259
+ const fullRep = {
4260
+ rep: rep.rep ?? exp.reps.length,
4261
+ score: rep.score,
4262
+ passed: rep.passed,
4263
+ metrics: rep.metrics,
4264
+ timestamp: rep.timestamp ?? new Date(this.now()).toISOString()
4265
+ };
4266
+ exp.reps.push(fullRep);
4267
+ exp.stats = computeExperimentStats(exp.reps, this.thresholds);
4268
+ const parent = exp.parentId ? experiments.find((e) => e.id === exp.parentId) : void 0;
4269
+ exp.verdict = improvementVerdict(exp.stats, parent?.stats ?? null, this.thresholds).verdict;
4270
+ await this.store.save(experiments);
4271
+ return structuredClone(exp);
4272
+ }
4273
+ async get(experimentId) {
4274
+ const experiments = await this.store.load();
4275
+ const found = experiments.find((e) => e.id === experimentId);
4276
+ return found ? structuredClone(found) : void 0;
4277
+ }
4278
+ async list() {
4279
+ return this.store.load();
4280
+ }
4281
+ /** Full verdict (not just the enum) for an experiment vs its parent. */
4282
+ async verdictFor(experimentId) {
4283
+ const experiments = await this.store.load();
4284
+ const exp = experiments.find((e) => e.id === experimentId);
4285
+ if (!exp)
4286
+ throw new ValidationError(`experiment-tracker: experiment "${experimentId}" not found`);
4287
+ const parent = exp.parentId ? experiments.find((e) => e.id === exp.parentId) : void 0;
4288
+ return improvementVerdict(exp.stats, parent?.stats ?? null, this.thresholds);
4289
+ }
4290
+ };
4291
+
3662
4292
  // src/muffled-gate-scanner.ts
3663
4293
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
3664
4294
  import { join } from "path";
@@ -3771,7 +4401,7 @@ var DEFAULT_FINDERS = [
3771
4401
  ];
3772
4402
  var UNIVERSAL_FINDERS = [findConstructorCwdDropped];
3773
4403
  function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
3774
- const matches = [];
4404
+ const matches2 = [];
3775
4405
  const walk = (rel) => {
3776
4406
  const abs = join(repoRoot, rel);
3777
4407
  if (!existsSync(abs)) return;
@@ -3797,12 +4427,12 @@ function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
3797
4427
  } catch {
3798
4428
  continue;
3799
4429
  }
3800
- if (text.includes(importsContain)) matches.push(sub);
4430
+ if (text.includes(importsContain)) matches2.push(sub);
3801
4431
  }
3802
4432
  }
3803
4433
  };
3804
4434
  for (const r of roots) walk(r);
3805
- return matches;
4435
+ return matches2;
3806
4436
  }
3807
4437
  function scanForMuffledGates(opts) {
3808
4438
  const findings = [];
@@ -3974,16 +4604,80 @@ function isObject(v) {
3974
4604
  return typeof v === "object" && v !== null && !Array.isArray(v);
3975
4605
  }
3976
4606
 
4607
+ // src/partition-held-out.ts
4608
+ function fnv1a32(input) {
4609
+ let h = 2166136261;
4610
+ for (let i = 0; i < input.length; i++) {
4611
+ h ^= input.charCodeAt(i) & 255;
4612
+ h = Math.imul(h, 16777619);
4613
+ }
4614
+ return h >>> 0;
4615
+ }
4616
+ function hashToUnit(id, seed) {
4617
+ return fnv1a32(`${id}
4618
+ ${seed}`) / 4294967296;
4619
+ }
4620
+ function assignHeldOutTag(id, options = {}) {
4621
+ const seed = options.seed ?? "held-out-v1";
4622
+ const holdoutFraction = options.holdoutFraction ?? 0.5;
4623
+ assertFraction(holdoutFraction);
4624
+ return hashToUnit(id, seed) < holdoutFraction ? "holdout" : "search";
4625
+ }
4626
+ function partitionHeldOut(ids, options = {}) {
4627
+ const seed = options.seed ?? "held-out-v1";
4628
+ const holdoutFraction = options.holdoutFraction ?? 0.5;
4629
+ const minHoldout = options.minHoldout ?? 1;
4630
+ const minSearch = options.minSearch ?? 1;
4631
+ assertFraction(holdoutFraction);
4632
+ if (ids.length === 0) {
4633
+ throw new ValidationError("partitionHeldOut: no ids supplied");
4634
+ }
4635
+ const seen = /* @__PURE__ */ new Set();
4636
+ const search = [];
4637
+ const holdout = [];
4638
+ for (const id of ids) {
4639
+ if (typeof id !== "string" || id.length === 0) {
4640
+ throw new ValidationError(
4641
+ `partitionHeldOut: ids must be non-empty strings, got ${JSON.stringify(id)}`
4642
+ );
4643
+ }
4644
+ if (seen.has(id)) {
4645
+ throw new ValidationError(
4646
+ `partitionHeldOut: duplicate id "${id}" \u2014 a paired comparison must not double-count`
4647
+ );
4648
+ }
4649
+ seen.add(id);
4650
+ if (hashToUnit(id, seed) < holdoutFraction) holdout.push(id);
4651
+ else search.push(id);
4652
+ }
4653
+ if (holdout.length < minHoldout) {
4654
+ throw new ValidationError(
4655
+ `partitionHeldOut: holdout set has ${holdout.length} id(s), below the floor of ${minHoldout} (n=${ids.length}, holdoutFraction=${holdoutFraction}) \u2014 too few for a significant comparison`
4656
+ );
4657
+ }
4658
+ if (search.length < minSearch) {
4659
+ throw new ValidationError(
4660
+ `partitionHeldOut: search set has ${search.length} id(s), below the floor of ${minSearch} (n=${ids.length}, holdoutFraction=${holdoutFraction})`
4661
+ );
4662
+ }
4663
+ return { search, holdout, seed, holdoutFraction };
4664
+ }
4665
+ function assertFraction(f) {
4666
+ if (!Number.isFinite(f) || f <= 0 || f >= 1) {
4667
+ throw new ValidationError(`partitionHeldOut: holdoutFraction must be in (0, 1), got ${f}`);
4668
+ }
4669
+ }
4670
+
3977
4671
  // src/scorecard.ts
3978
4672
  import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "fs";
3979
4673
  import { dirname } from "path";
3980
- function median(xs) {
4674
+ function median2(xs) {
3981
4675
  if (xs.length === 0) return 0;
3982
4676
  const sorted = [...xs].sort((a, b) => a - b);
3983
4677
  const mid = Math.floor(sorted.length / 2);
3984
4678
  return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
3985
4679
  }
3986
- function runScore(run) {
4680
+ function runScore2(run) {
3987
4681
  return run.outcome.holdoutScore ?? run.outcome.searchScore;
3988
4682
  }
3989
4683
  function aggregatePerDimension(runs) {
@@ -4017,14 +4711,14 @@ function recordRuns(runs, opts) {
4017
4711
  }
4018
4712
  const lines = [];
4019
4713
  for (const [scenarioId, scenarioRuns] of byScenario) {
4020
- const scored = scenarioRuns.map((run) => ({ run, score: runScore(run) })).filter((s) => s.score !== void 0);
4714
+ const scored = scenarioRuns.map((run) => ({ run, score: runScore2(run) })).filter((s) => s.score !== void 0);
4021
4715
  if (scored.length === 0) continue;
4022
4716
  const scores2 = scored.map((s) => s.score);
4023
4717
  const entry = {
4024
4718
  commitSha: opts.commitSha,
4025
4719
  timestamp,
4026
4720
  scores: scores2,
4027
- composite: median(scores2),
4721
+ composite: median2(scores2),
4028
4722
  runIds: scored.map((s) => s.run.runId)
4029
4723
  };
4030
4724
  const perDimension = aggregatePerDimension(scenarioRuns);
@@ -5772,13 +6466,13 @@ function extractErrorCount(text, opts = {}) {
5772
6466
  (p) => !opts.only || opts.only.includes(p.name)
5773
6467
  );
5774
6468
  for (const p of patterns) {
5775
- const matches = Array.from(text.matchAll(p.regex));
5776
- if (matches.length === 0) continue;
5777
- const count = p.transform ? matches.reduce((sum3, m) => sum3 + p.transform(m), 0) : matches.length;
6469
+ const matches2 = Array.from(text.matchAll(p.regex));
6470
+ if (matches2.length === 0) continue;
6471
+ const count = p.transform ? matches2.reduce((sum3, m) => sum3 + p.transform(m), 0) : matches2.length;
5778
6472
  return {
5779
6473
  count,
5780
6474
  matched: p.name,
5781
- samples: matches.slice(0, 5).map((m) => m[0])
6475
+ samples: matches2.slice(0, 5).map((m) => m[0])
5782
6476
  };
5783
6477
  }
5784
6478
  return { count: null, matched: null, samples: [] };
@@ -6719,7 +7413,7 @@ function scoreScenario(scenario, matcher, threshold, matchStrategy) {
6719
7413
  }
6720
7414
  function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6721
7415
  const candidatesLeft = scenario.candidates.map((candidate, index) => ({ candidate, index }));
6722
- const matches = [];
7416
+ const matches2 = [];
6723
7417
  for (const reference of scenario.references) {
6724
7418
  let best = null;
6725
7419
  for (const item of candidatesLeft) {
@@ -6732,7 +7426,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6732
7426
  if (best && best.score >= threshold) {
6733
7427
  const matchIndex = candidatesLeft.findIndex((item) => item.index === best.index);
6734
7428
  if (matchIndex >= 0) candidatesLeft.splice(matchIndex, 1);
6735
- matches.push({
7429
+ matches2.push({
6736
7430
  scenarioId: scenario.id,
6737
7431
  referenceId: reference.id,
6738
7432
  candidateId: best.candidate.id,
@@ -6742,7 +7436,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6742
7436
  reason: best.reason
6743
7437
  });
6744
7438
  } else {
6745
- matches.push({
7439
+ matches2.push({
6746
7440
  scenarioId: scenario.id,
6747
7441
  referenceId: reference.id,
6748
7442
  candidateId: best?.candidate.id ?? null,
@@ -6753,7 +7447,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
6753
7447
  });
6754
7448
  }
6755
7449
  }
6756
- return buildScenarioScore(scenario, matches, candidatesLeft.length);
7450
+ return buildScenarioScore(scenario, matches2, candidatesLeft.length);
6757
7451
  }
6758
7452
  function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6759
7453
  const pairs = [];
@@ -6780,7 +7474,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6780
7474
  selectedByReference.set(pair.referenceIndex, pair);
6781
7475
  selectedCandidates.add(pair.candidateIndex);
6782
7476
  }
6783
- const matches = scenario.references.map((reference, referenceIndex) => {
7477
+ const matches2 = scenario.references.map((reference, referenceIndex) => {
6784
7478
  const weight = reference.weight ?? 1;
6785
7479
  const selected = selectedByReference.get(referenceIndex);
6786
7480
  if (selected) {
@@ -6805,7 +7499,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
6805
7499
  reason: bestRejected?.reason ?? "no candidates"
6806
7500
  };
6807
7501
  });
6808
- return buildScenarioScore(scenario, matches, scenario.candidates.length - selectedCandidates.size);
7502
+ return buildScenarioScore(scenario, matches2, scenario.candidates.length - selectedCandidates.size);
6809
7503
  }
6810
7504
  function scorePair(scenario, matcher, reference, candidate) {
6811
7505
  const result = matcher(reference, candidate, scenario);
@@ -6816,11 +7510,11 @@ function scorePair(scenario, matcher, reference, candidate) {
6816
7510
  }
6817
7511
  return { score: clamp012(result.score), reason: result.reason ?? "" };
6818
7512
  }
6819
- function buildScenarioScore(scenario, matches, falsePositives) {
6820
- const matched = matches.filter((match) => match.matched).length;
7513
+ function buildScenarioScore(scenario, matches2, falsePositives) {
7514
+ const matched = matches2.filter((match) => match.matched).length;
6821
7515
  const total = scenario.references.length;
6822
- const matchedWeight = matches.filter((match) => match.matched).reduce((sum3, match) => sum3 + match.weight, 0);
6823
- const totalWeight = matches.reduce((sum3, match) => sum3 + match.weight, 0);
7516
+ const matchedWeight = matches2.filter((match) => match.matched).reduce((sum3, match) => sum3 + match.weight, 0);
7517
+ const totalWeight = matches2.reduce((sum3, match) => sum3 + match.weight, 0);
6824
7518
  const precision2 = ratio(matched, matched + falsePositives);
6825
7519
  const recall = ratio(matched, total);
6826
7520
  return {
@@ -6834,7 +7528,7 @@ function buildScenarioScore(scenario, matches, falsePositives) {
6834
7528
  precision: precision2,
6835
7529
  recall,
6836
7530
  f1: f1(precision2, recall),
6837
- matches
7531
+ matches: matches2
6838
7532
  };
6839
7533
  }
6840
7534
  function aggregateBySplit(scores2) {
@@ -7095,7 +7789,7 @@ function createDefaultReviewer(options) {
7095
7789
 
7096
7790
  // src/description-length-gate.ts
7097
7791
  import { gzipSync } from "zlib";
7098
- function runScore2(run) {
7792
+ function runScore3(run) {
7099
7793
  const o = run.outcome;
7100
7794
  const s = o.holdoutScore ?? o.searchScore ?? o.raw?.score;
7101
7795
  return typeof s === "number" && Number.isFinite(s) ? s : void 0;
@@ -7106,7 +7800,7 @@ function taskKey(run) {
7106
7800
  function perTaskMeanScore(runs) {
7107
7801
  const acc = /* @__PURE__ */ new Map();
7108
7802
  for (const run of runs) {
7109
- const s = runScore2(run);
7803
+ const s = runScore3(run);
7110
7804
  if (s === void 0) continue;
7111
7805
  const key = taskKey(run);
7112
7806
  const cur = acc.get(key) ?? { sum: 0, n: 0 };
@@ -7258,10 +7952,10 @@ async function discoverPersonas(dir, opts = {}) {
7258
7952
  function matchGoldens(goldens, candidates, options = {}) {
7259
7953
  const extract = options.text ?? defaultExtract2;
7260
7954
  const haystacks = candidates.map((c) => extract(c).toLowerCase());
7261
- const matches = goldens.map((golden) => goldenMatched(golden, haystacks));
7955
+ const matches2 = goldens.map((golden) => goldenMatched(golden, haystacks));
7262
7956
  return {
7263
- matches,
7264
- hits: matches.filter(Boolean).length,
7957
+ matches: matches2,
7958
+ hits: matches2.filter(Boolean).length,
7265
7959
  total: goldens.length
7266
7960
  };
7267
7961
  }
@@ -8416,6 +9110,7 @@ export {
8416
9110
  CaptureIntegrityError,
8417
9111
  ConfigError,
8418
9112
  ConvergenceTracker,
9113
+ CostLedger,
8419
9114
  CostTracker,
8420
9115
  CrossFamilyError,
8421
9116
  DEFAULT_AGENT_SLOS,
@@ -8437,6 +9132,8 @@ export {
8437
9132
  DockerSandboxDriver,
8438
9133
  DualAgentBench,
8439
9134
  ERROR_COUNT_PATTERNS,
9135
+ EvalTraceStore,
9136
+ ExperimentTracker,
8440
9137
  FAILURE_CLASSES,
8441
9138
  FAILURE_MODE_KIND_SPEC,
8442
9139
  FileSystemFeedbackTrajectoryStore,
@@ -8462,6 +9159,7 @@ export {
8462
9159
  LockedJsonlAppender,
8463
9160
  MODEL_PRICING,
8464
9161
  MetricsCollector,
9162
+ ModelsUnreachableError,
8465
9163
  MultiLayerVerifier,
8466
9164
  Mutex,
8467
9165
  NoopRawProviderSink,
@@ -8521,12 +9219,14 @@ export {
8521
9219
  asString,
8522
9220
  assertCrossFamily,
8523
9221
  assertLlmRoute,
9222
+ assertModelsServed,
8524
9223
  assertRealBackend,
8525
9224
  assertReleaseConfidence,
8526
9225
  assertRunAgentProfileCell,
8527
9226
  assertRunCaptured,
8528
9227
  assertSingleBackend,
8529
9228
  assignFeedbackSplit,
9229
+ assignHeldOutTag,
8530
9230
  attributeCounterfactuals,
8531
9231
  backoffMs,
8532
9232
  deterministicSplit as benchmarkDeterministicSplit,
@@ -8574,6 +9274,7 @@ export {
8574
9274
  compilerJudge,
8575
9275
  composeParsers,
8576
9276
  composeValidators,
9277
+ computeExperimentStats,
8577
9278
  computeFindingId,
8578
9279
  computeToolUseMetrics,
8579
9280
  computeTraceMetrics,
@@ -8586,6 +9287,7 @@ export {
8586
9287
  convertTraceStoresToOtlp,
8587
9288
  corpusInterRaterAgreement,
8588
9289
  corpusInterRaterAgreementFromJudgeScores,
9290
+ costForUsage,
8589
9291
  createAnalystAi,
8590
9292
  createAntiSlopJudge,
8591
9293
  createChatClient,
@@ -8641,6 +9343,9 @@ export {
8641
9343
  extractErrorCount,
8642
9344
  extractOtlpAttributes,
8643
9345
  extractProducedState,
9346
+ extractUsage,
9347
+ extractUsageFromResponse,
9348
+ extractUsageFromSse,
8644
9349
  feedbackTrajectoriesToDatasetScenarios,
8645
9350
  feedbackTrajectoriesToOptimizerRows,
8646
9351
  feedbackTrajectoryToDatasetScenario,
@@ -8648,6 +9353,7 @@ export {
8648
9353
  fieldAgreement,
8649
9354
  fileContains,
8650
9355
  fileExists,
9356
+ fileExperimentStore,
8651
9357
  findAutoMatchNoExpectation,
8652
9358
  findConstructorCwdDropped,
8653
9359
  findFallbackToPass,
@@ -8657,12 +9363,14 @@ export {
8657
9363
  firstStringAttr,
8658
9364
  flattenOtlpExportToNdjson,
8659
9365
  flowLayer,
9366
+ fnv1a32,
8660
9367
  formatBenchmarkReport,
8661
9368
  formatDriverReport,
8662
9369
  formatFindings,
8663
9370
  formatScorecardDiff,
8664
9371
  gainHistogram,
8665
9372
  ghCliClient,
9373
+ gitProvenanceReader,
8666
9374
  precision as goldenPrecision,
8667
9375
  gradeSemanticStatus,
8668
9376
  groupBy,
@@ -8670,10 +9378,14 @@ export {
8670
9378
  hashContent,
8671
9379
  hashJson,
8672
9380
  hashScenarios,
9381
+ hashToUnit,
8673
9382
  htmlContainsElement,
8674
9383
  httpGithubClient,
9384
+ improvementVerdict,
9385
+ inMemoryExperimentStore,
8675
9386
  inMemoryReferenceReplayStore,
8676
9387
  inMemoryReviewStore,
9388
+ inMemoryRunRecordBackend,
8677
9389
  inferDomainKeywords,
8678
9390
  inferOtlpKind,
8679
9391
  interRaterReliability,
@@ -8694,6 +9406,7 @@ export {
8694
9406
  jsonShape,
8695
9407
  jsonlReferenceReplayStore,
8696
9408
  jsonlReviewStore,
9409
+ jsonlRunRecordBackend,
8697
9410
  judgeFamily,
8698
9411
  judgeReplayGate,
8699
9412
  judgeSpans,
@@ -8713,6 +9426,7 @@ export {
8713
9426
  mergeLayerResults,
8714
9427
  mergeSteeringBundle,
8715
9428
  modelDescriptionBits,
9429
+ modelPriceKey,
8716
9430
  multiToolchainLayer,
8717
9431
  nistAiRmfReport,
8718
9432
  normalizeScores,
@@ -8737,11 +9451,13 @@ export {
8737
9451
  parseRunRecordSafe,
8738
9452
  parseRuntimeTrajectoryHookEvent,
8739
9453
  partialCredit,
9454
+ partitionHeldOut,
8740
9455
  passOrthogonality,
8741
9456
  pixelDeltaRatio,
8742
9457
  planTraceInsightQuestions,
8743
9458
  politenessPrefixMutator,
8744
9459
  positionalBias,
9460
+ preflightModels,
8745
9461
  printDriverSummary,
8746
9462
  probeLlm,
8747
9463
  profile_exports as profile,
@@ -8801,6 +9517,7 @@ export {
8801
9517
  runProposeReview,
8802
9518
  runProposeReviewAsControlLoop,
8803
9519
  runReferenceReplay,
9520
+ runScore,
8804
9521
  runSelfPlay,
8805
9522
  runSemanticConceptJudge,
8806
9523
  runTestGradedScenario,