@tangle-network/agent-eval 0.85.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/CHANGELOG.md +14 -0
- package/dist/{chunk-XQL22JDG.js → chunk-P2J6SOXT.js} +97 -5
- package/dist/chunk-P2J6SOXT.js.map +1 -0
- package/dist/index.d.ts +471 -4
- package/dist/index.js +662 -49
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/traces.d.ts +55 -1
- package/dist/traces.js +7 -1
- package/package.json +26 -13
- package/dist/chunk-XQL22JDG.js.map +0 -1
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-
|
|
246
|
+
} from "./chunk-P2J6SOXT.js";
|
|
244
247
|
import {
|
|
245
248
|
DEFAULT_REDACTION_RULES,
|
|
246
249
|
REDACTION_VERSION,
|
|
@@ -2391,16 +2394,16 @@ function aggregatePrReviewScore(dimensions, weights = {}) {
|
|
|
2391
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;
|
|
2392
2395
|
}
|
|
2393
2396
|
function matchReferenceFindings(references, comments) {
|
|
2394
|
-
const
|
|
2397
|
+
const matches2 = [];
|
|
2395
2398
|
const usedCommentIds = /* @__PURE__ */ new Set();
|
|
2396
2399
|
for (const reference of references) {
|
|
2397
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);
|
|
2398
2401
|
const best = candidates[0];
|
|
2399
2402
|
if (!best) continue;
|
|
2400
2403
|
usedCommentIds.add(best.comment.id);
|
|
2401
|
-
|
|
2404
|
+
matches2.push({ referenceId: reference.id, commentId: best.comment.id, score: best.score });
|
|
2402
2405
|
}
|
|
2403
|
-
return
|
|
2406
|
+
return matches2;
|
|
2404
2407
|
}
|
|
2405
2408
|
function matchScore(reference, comment) {
|
|
2406
2409
|
let score = 0;
|
|
@@ -2427,9 +2430,9 @@ function isActionableComment(comment) {
|
|
|
2427
2430
|
body
|
|
2428
2431
|
);
|
|
2429
2432
|
}
|
|
2430
|
-
function isSeverityAligned(comment, references,
|
|
2433
|
+
function isSeverityAligned(comment, references, matches2) {
|
|
2431
2434
|
if (!comment.severity) return false;
|
|
2432
|
-
const match =
|
|
2435
|
+
const match = matches2.find((candidate) => candidate.commentId === comment.id);
|
|
2433
2436
|
if (!match) return comment.severity === "nit" || comment.severity === "low";
|
|
2434
2437
|
const reference = references.find((candidate) => candidate.id === match.referenceId);
|
|
2435
2438
|
if (!reference) return false;
|
|
@@ -2725,28 +2728,28 @@ function analyzeAntiSlop(outputs, config) {
|
|
|
2725
2728
|
}
|
|
2726
2729
|
}
|
|
2727
2730
|
for (const re of config.hedgingPatterns) {
|
|
2728
|
-
const
|
|
2731
|
+
const matches2 = output.match(
|
|
2729
2732
|
new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
|
|
2730
2733
|
);
|
|
2731
|
-
if (
|
|
2732
|
-
counts.hedging +=
|
|
2734
|
+
if (matches2) {
|
|
2735
|
+
counts.hedging += matches2.length;
|
|
2733
2736
|
issues.push({
|
|
2734
2737
|
category: "hedging",
|
|
2735
|
-
detail: `${
|
|
2736
|
-
example:
|
|
2738
|
+
detail: `${matches2.length}x ${re.source}`,
|
|
2739
|
+
example: matches2[0]
|
|
2737
2740
|
});
|
|
2738
2741
|
}
|
|
2739
2742
|
}
|
|
2740
2743
|
for (const re of config.apologyPatterns) {
|
|
2741
|
-
const
|
|
2744
|
+
const matches2 = output.match(
|
|
2742
2745
|
new RegExp(re, re.flags.includes("g") ? re.flags : `${re.flags}g`)
|
|
2743
2746
|
);
|
|
2744
|
-
if (
|
|
2745
|
-
counts.apology +=
|
|
2747
|
+
if (matches2) {
|
|
2748
|
+
counts.apology += matches2.length;
|
|
2746
2749
|
issues.push({
|
|
2747
2750
|
category: "apology",
|
|
2748
|
-
detail: `${
|
|
2749
|
-
example:
|
|
2751
|
+
detail: `${matches2.length}x ${re.source}`,
|
|
2752
|
+
example: matches2[0]
|
|
2750
2753
|
});
|
|
2751
2754
|
}
|
|
2752
2755
|
}
|
|
@@ -3016,14 +3019,14 @@ async function runHarnessExperiment(config) {
|
|
|
3016
3019
|
const score = config.score ?? ((trace) => critic.scoreTrace(trace));
|
|
3017
3020
|
const results = await mapLimit(jobs, config.parallelism ?? 1, async (request) => {
|
|
3018
3021
|
const trace = await config.adapter.run(request);
|
|
3019
|
-
const
|
|
3022
|
+
const runScore4 = await score(trace, request);
|
|
3020
3023
|
const result = {
|
|
3021
3024
|
variant: request.variant,
|
|
3022
3025
|
scenario: request.scenario,
|
|
3023
3026
|
trialIndex: request.trialIndex,
|
|
3024
3027
|
trace,
|
|
3025
|
-
score:
|
|
3026
|
-
aggregate: aggregateRunScore(
|
|
3028
|
+
score: runScore4,
|
|
3029
|
+
aggregate: aggregateRunScore(runScore4, config.weights)
|
|
3027
3030
|
};
|
|
3028
3031
|
await config.onResult?.(result);
|
|
3029
3032
|
return result;
|
|
@@ -3669,13 +3672,118 @@ var BudgetGuard = class {
|
|
|
3669
3672
|
}
|
|
3670
3673
|
};
|
|
3671
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
|
+
|
|
3672
3780
|
// src/cost-tracker.ts
|
|
3673
3781
|
var CostTracker = class {
|
|
3674
3782
|
byScenario = /* @__PURE__ */ new Map();
|
|
3675
3783
|
record(entry) {
|
|
3676
3784
|
const full = { timestamp: entry.timestamp ?? Date.now(), ...entry };
|
|
3677
|
-
|
|
3678
|
-
|
|
3785
|
+
assertNonNegative2(full.inputTokens, "inputTokens");
|
|
3786
|
+
assertNonNegative2(full.outputTokens, "outputTokens");
|
|
3679
3787
|
let bucket = this.byScenario.get(full.scenarioId);
|
|
3680
3788
|
if (!bucket) {
|
|
3681
3789
|
bucket = {
|
|
@@ -3754,12 +3862,433 @@ function costFor(entry) {
|
|
|
3754
3862
|
}
|
|
3755
3863
|
return estimateCost(entry.inputTokens, entry.outputTokens, entry.model);
|
|
3756
3864
|
}
|
|
3757
|
-
function
|
|
3865
|
+
function assertNonNegative2(n, name) {
|
|
3758
3866
|
if (!Number.isFinite(n) || n < 0) {
|
|
3759
3867
|
throw new Error(`CostTracker: ${name} must be a non-negative finite number, got ${n}`);
|
|
3760
3868
|
}
|
|
3761
3869
|
}
|
|
3762
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
|
+
|
|
3763
4292
|
// src/muffled-gate-scanner.ts
|
|
3764
4293
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
3765
4294
|
import { join } from "path";
|
|
@@ -3872,7 +4401,7 @@ var DEFAULT_FINDERS = [
|
|
|
3872
4401
|
];
|
|
3873
4402
|
var UNIVERSAL_FINDERS = [findConstructorCwdDropped];
|
|
3874
4403
|
function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
|
|
3875
|
-
const
|
|
4404
|
+
const matches2 = [];
|
|
3876
4405
|
const walk = (rel) => {
|
|
3877
4406
|
const abs = join(repoRoot, rel);
|
|
3878
4407
|
if (!existsSync(abs)) return;
|
|
@@ -3898,12 +4427,12 @@ function autoDeriveImporters(repoRoot, roots, extensions, importsContain) {
|
|
|
3898
4427
|
} catch {
|
|
3899
4428
|
continue;
|
|
3900
4429
|
}
|
|
3901
|
-
if (text.includes(importsContain))
|
|
4430
|
+
if (text.includes(importsContain)) matches2.push(sub);
|
|
3902
4431
|
}
|
|
3903
4432
|
}
|
|
3904
4433
|
};
|
|
3905
4434
|
for (const r of roots) walk(r);
|
|
3906
|
-
return
|
|
4435
|
+
return matches2;
|
|
3907
4436
|
}
|
|
3908
4437
|
function scanForMuffledGates(opts) {
|
|
3909
4438
|
const findings = [];
|
|
@@ -4075,16 +4604,80 @@ function isObject(v) {
|
|
|
4075
4604
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4076
4605
|
}
|
|
4077
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
|
+
|
|
4078
4671
|
// src/scorecard.ts
|
|
4079
4672
|
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "fs";
|
|
4080
4673
|
import { dirname } from "path";
|
|
4081
|
-
function
|
|
4674
|
+
function median2(xs) {
|
|
4082
4675
|
if (xs.length === 0) return 0;
|
|
4083
4676
|
const sorted = [...xs].sort((a, b) => a - b);
|
|
4084
4677
|
const mid = Math.floor(sorted.length / 2);
|
|
4085
4678
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
4086
4679
|
}
|
|
4087
|
-
function
|
|
4680
|
+
function runScore2(run) {
|
|
4088
4681
|
return run.outcome.holdoutScore ?? run.outcome.searchScore;
|
|
4089
4682
|
}
|
|
4090
4683
|
function aggregatePerDimension(runs) {
|
|
@@ -4118,14 +4711,14 @@ function recordRuns(runs, opts) {
|
|
|
4118
4711
|
}
|
|
4119
4712
|
const lines = [];
|
|
4120
4713
|
for (const [scenarioId, scenarioRuns] of byScenario) {
|
|
4121
|
-
const scored = scenarioRuns.map((run) => ({ run, score:
|
|
4714
|
+
const scored = scenarioRuns.map((run) => ({ run, score: runScore2(run) })).filter((s) => s.score !== void 0);
|
|
4122
4715
|
if (scored.length === 0) continue;
|
|
4123
4716
|
const scores2 = scored.map((s) => s.score);
|
|
4124
4717
|
const entry = {
|
|
4125
4718
|
commitSha: opts.commitSha,
|
|
4126
4719
|
timestamp,
|
|
4127
4720
|
scores: scores2,
|
|
4128
|
-
composite:
|
|
4721
|
+
composite: median2(scores2),
|
|
4129
4722
|
runIds: scored.map((s) => s.run.runId)
|
|
4130
4723
|
};
|
|
4131
4724
|
const perDimension = aggregatePerDimension(scenarioRuns);
|
|
@@ -5873,13 +6466,13 @@ function extractErrorCount(text, opts = {}) {
|
|
|
5873
6466
|
(p) => !opts.only || opts.only.includes(p.name)
|
|
5874
6467
|
);
|
|
5875
6468
|
for (const p of patterns) {
|
|
5876
|
-
const
|
|
5877
|
-
if (
|
|
5878
|
-
const count = p.transform ?
|
|
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;
|
|
5879
6472
|
return {
|
|
5880
6473
|
count,
|
|
5881
6474
|
matched: p.name,
|
|
5882
|
-
samples:
|
|
6475
|
+
samples: matches2.slice(0, 5).map((m) => m[0])
|
|
5883
6476
|
};
|
|
5884
6477
|
}
|
|
5885
6478
|
return { count: null, matched: null, samples: [] };
|
|
@@ -6820,7 +7413,7 @@ function scoreScenario(scenario, matcher, threshold, matchStrategy) {
|
|
|
6820
7413
|
}
|
|
6821
7414
|
function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
|
|
6822
7415
|
const candidatesLeft = scenario.candidates.map((candidate, index) => ({ candidate, index }));
|
|
6823
|
-
const
|
|
7416
|
+
const matches2 = [];
|
|
6824
7417
|
for (const reference of scenario.references) {
|
|
6825
7418
|
let best = null;
|
|
6826
7419
|
for (const item of candidatesLeft) {
|
|
@@ -6833,7 +7426,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
|
|
|
6833
7426
|
if (best && best.score >= threshold) {
|
|
6834
7427
|
const matchIndex = candidatesLeft.findIndex((item) => item.index === best.index);
|
|
6835
7428
|
if (matchIndex >= 0) candidatesLeft.splice(matchIndex, 1);
|
|
6836
|
-
|
|
7429
|
+
matches2.push({
|
|
6837
7430
|
scenarioId: scenario.id,
|
|
6838
7431
|
referenceId: reference.id,
|
|
6839
7432
|
candidateId: best.candidate.id,
|
|
@@ -6843,7 +7436,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
|
|
|
6843
7436
|
reason: best.reason
|
|
6844
7437
|
});
|
|
6845
7438
|
} else {
|
|
6846
|
-
|
|
7439
|
+
matches2.push({
|
|
6847
7440
|
scenarioId: scenario.id,
|
|
6848
7441
|
referenceId: reference.id,
|
|
6849
7442
|
candidateId: best?.candidate.id ?? null,
|
|
@@ -6854,7 +7447,7 @@ function scoreScenarioReferenceOrder(scenario, matcher, threshold) {
|
|
|
6854
7447
|
});
|
|
6855
7448
|
}
|
|
6856
7449
|
}
|
|
6857
|
-
return buildScenarioScore(scenario,
|
|
7450
|
+
return buildScenarioScore(scenario, matches2, candidatesLeft.length);
|
|
6858
7451
|
}
|
|
6859
7452
|
function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
|
|
6860
7453
|
const pairs = [];
|
|
@@ -6881,7 +7474,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
|
|
|
6881
7474
|
selectedByReference.set(pair.referenceIndex, pair);
|
|
6882
7475
|
selectedCandidates.add(pair.candidateIndex);
|
|
6883
7476
|
}
|
|
6884
|
-
const
|
|
7477
|
+
const matches2 = scenario.references.map((reference, referenceIndex) => {
|
|
6885
7478
|
const weight = reference.weight ?? 1;
|
|
6886
7479
|
const selected = selectedByReference.get(referenceIndex);
|
|
6887
7480
|
if (selected) {
|
|
@@ -6906,7 +7499,7 @@ function scoreScenarioGlobalGreedy(scenario, matcher, threshold) {
|
|
|
6906
7499
|
reason: bestRejected?.reason ?? "no candidates"
|
|
6907
7500
|
};
|
|
6908
7501
|
});
|
|
6909
|
-
return buildScenarioScore(scenario,
|
|
7502
|
+
return buildScenarioScore(scenario, matches2, scenario.candidates.length - selectedCandidates.size);
|
|
6910
7503
|
}
|
|
6911
7504
|
function scorePair(scenario, matcher, reference, candidate) {
|
|
6912
7505
|
const result = matcher(reference, candidate, scenario);
|
|
@@ -6917,11 +7510,11 @@ function scorePair(scenario, matcher, reference, candidate) {
|
|
|
6917
7510
|
}
|
|
6918
7511
|
return { score: clamp012(result.score), reason: result.reason ?? "" };
|
|
6919
7512
|
}
|
|
6920
|
-
function buildScenarioScore(scenario,
|
|
6921
|
-
const matched =
|
|
7513
|
+
function buildScenarioScore(scenario, matches2, falsePositives) {
|
|
7514
|
+
const matched = matches2.filter((match) => match.matched).length;
|
|
6922
7515
|
const total = scenario.references.length;
|
|
6923
|
-
const matchedWeight =
|
|
6924
|
-
const totalWeight =
|
|
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);
|
|
6925
7518
|
const precision2 = ratio(matched, matched + falsePositives);
|
|
6926
7519
|
const recall = ratio(matched, total);
|
|
6927
7520
|
return {
|
|
@@ -6935,7 +7528,7 @@ function buildScenarioScore(scenario, matches, falsePositives) {
|
|
|
6935
7528
|
precision: precision2,
|
|
6936
7529
|
recall,
|
|
6937
7530
|
f1: f1(precision2, recall),
|
|
6938
|
-
matches
|
|
7531
|
+
matches: matches2
|
|
6939
7532
|
};
|
|
6940
7533
|
}
|
|
6941
7534
|
function aggregateBySplit(scores2) {
|
|
@@ -7196,7 +7789,7 @@ function createDefaultReviewer(options) {
|
|
|
7196
7789
|
|
|
7197
7790
|
// src/description-length-gate.ts
|
|
7198
7791
|
import { gzipSync } from "zlib";
|
|
7199
|
-
function
|
|
7792
|
+
function runScore3(run) {
|
|
7200
7793
|
const o = run.outcome;
|
|
7201
7794
|
const s = o.holdoutScore ?? o.searchScore ?? o.raw?.score;
|
|
7202
7795
|
return typeof s === "number" && Number.isFinite(s) ? s : void 0;
|
|
@@ -7207,7 +7800,7 @@ function taskKey(run) {
|
|
|
7207
7800
|
function perTaskMeanScore(runs) {
|
|
7208
7801
|
const acc = /* @__PURE__ */ new Map();
|
|
7209
7802
|
for (const run of runs) {
|
|
7210
|
-
const s =
|
|
7803
|
+
const s = runScore3(run);
|
|
7211
7804
|
if (s === void 0) continue;
|
|
7212
7805
|
const key = taskKey(run);
|
|
7213
7806
|
const cur = acc.get(key) ?? { sum: 0, n: 0 };
|
|
@@ -7359,10 +7952,10 @@ async function discoverPersonas(dir, opts = {}) {
|
|
|
7359
7952
|
function matchGoldens(goldens, candidates, options = {}) {
|
|
7360
7953
|
const extract = options.text ?? defaultExtract2;
|
|
7361
7954
|
const haystacks = candidates.map((c) => extract(c).toLowerCase());
|
|
7362
|
-
const
|
|
7955
|
+
const matches2 = goldens.map((golden) => goldenMatched(golden, haystacks));
|
|
7363
7956
|
return {
|
|
7364
|
-
matches,
|
|
7365
|
-
hits:
|
|
7957
|
+
matches: matches2,
|
|
7958
|
+
hits: matches2.filter(Boolean).length,
|
|
7366
7959
|
total: goldens.length
|
|
7367
7960
|
};
|
|
7368
7961
|
}
|
|
@@ -8517,6 +9110,7 @@ export {
|
|
|
8517
9110
|
CaptureIntegrityError,
|
|
8518
9111
|
ConfigError,
|
|
8519
9112
|
ConvergenceTracker,
|
|
9113
|
+
CostLedger,
|
|
8520
9114
|
CostTracker,
|
|
8521
9115
|
CrossFamilyError,
|
|
8522
9116
|
DEFAULT_AGENT_SLOS,
|
|
@@ -8538,6 +9132,8 @@ export {
|
|
|
8538
9132
|
DockerSandboxDriver,
|
|
8539
9133
|
DualAgentBench,
|
|
8540
9134
|
ERROR_COUNT_PATTERNS,
|
|
9135
|
+
EvalTraceStore,
|
|
9136
|
+
ExperimentTracker,
|
|
8541
9137
|
FAILURE_CLASSES,
|
|
8542
9138
|
FAILURE_MODE_KIND_SPEC,
|
|
8543
9139
|
FileSystemFeedbackTrajectoryStore,
|
|
@@ -8630,6 +9226,7 @@ export {
|
|
|
8630
9226
|
assertRunCaptured,
|
|
8631
9227
|
assertSingleBackend,
|
|
8632
9228
|
assignFeedbackSplit,
|
|
9229
|
+
assignHeldOutTag,
|
|
8633
9230
|
attributeCounterfactuals,
|
|
8634
9231
|
backoffMs,
|
|
8635
9232
|
deterministicSplit as benchmarkDeterministicSplit,
|
|
@@ -8677,6 +9274,7 @@ export {
|
|
|
8677
9274
|
compilerJudge,
|
|
8678
9275
|
composeParsers,
|
|
8679
9276
|
composeValidators,
|
|
9277
|
+
computeExperimentStats,
|
|
8680
9278
|
computeFindingId,
|
|
8681
9279
|
computeToolUseMetrics,
|
|
8682
9280
|
computeTraceMetrics,
|
|
@@ -8689,6 +9287,7 @@ export {
|
|
|
8689
9287
|
convertTraceStoresToOtlp,
|
|
8690
9288
|
corpusInterRaterAgreement,
|
|
8691
9289
|
corpusInterRaterAgreementFromJudgeScores,
|
|
9290
|
+
costForUsage,
|
|
8692
9291
|
createAnalystAi,
|
|
8693
9292
|
createAntiSlopJudge,
|
|
8694
9293
|
createChatClient,
|
|
@@ -8744,6 +9343,9 @@ export {
|
|
|
8744
9343
|
extractErrorCount,
|
|
8745
9344
|
extractOtlpAttributes,
|
|
8746
9345
|
extractProducedState,
|
|
9346
|
+
extractUsage,
|
|
9347
|
+
extractUsageFromResponse,
|
|
9348
|
+
extractUsageFromSse,
|
|
8747
9349
|
feedbackTrajectoriesToDatasetScenarios,
|
|
8748
9350
|
feedbackTrajectoriesToOptimizerRows,
|
|
8749
9351
|
feedbackTrajectoryToDatasetScenario,
|
|
@@ -8751,6 +9353,7 @@ export {
|
|
|
8751
9353
|
fieldAgreement,
|
|
8752
9354
|
fileContains,
|
|
8753
9355
|
fileExists,
|
|
9356
|
+
fileExperimentStore,
|
|
8754
9357
|
findAutoMatchNoExpectation,
|
|
8755
9358
|
findConstructorCwdDropped,
|
|
8756
9359
|
findFallbackToPass,
|
|
@@ -8760,12 +9363,14 @@ export {
|
|
|
8760
9363
|
firstStringAttr,
|
|
8761
9364
|
flattenOtlpExportToNdjson,
|
|
8762
9365
|
flowLayer,
|
|
9366
|
+
fnv1a32,
|
|
8763
9367
|
formatBenchmarkReport,
|
|
8764
9368
|
formatDriverReport,
|
|
8765
9369
|
formatFindings,
|
|
8766
9370
|
formatScorecardDiff,
|
|
8767
9371
|
gainHistogram,
|
|
8768
9372
|
ghCliClient,
|
|
9373
|
+
gitProvenanceReader,
|
|
8769
9374
|
precision as goldenPrecision,
|
|
8770
9375
|
gradeSemanticStatus,
|
|
8771
9376
|
groupBy,
|
|
@@ -8773,10 +9378,14 @@ export {
|
|
|
8773
9378
|
hashContent,
|
|
8774
9379
|
hashJson,
|
|
8775
9380
|
hashScenarios,
|
|
9381
|
+
hashToUnit,
|
|
8776
9382
|
htmlContainsElement,
|
|
8777
9383
|
httpGithubClient,
|
|
9384
|
+
improvementVerdict,
|
|
9385
|
+
inMemoryExperimentStore,
|
|
8778
9386
|
inMemoryReferenceReplayStore,
|
|
8779
9387
|
inMemoryReviewStore,
|
|
9388
|
+
inMemoryRunRecordBackend,
|
|
8780
9389
|
inferDomainKeywords,
|
|
8781
9390
|
inferOtlpKind,
|
|
8782
9391
|
interRaterReliability,
|
|
@@ -8797,6 +9406,7 @@ export {
|
|
|
8797
9406
|
jsonShape,
|
|
8798
9407
|
jsonlReferenceReplayStore,
|
|
8799
9408
|
jsonlReviewStore,
|
|
9409
|
+
jsonlRunRecordBackend,
|
|
8800
9410
|
judgeFamily,
|
|
8801
9411
|
judgeReplayGate,
|
|
8802
9412
|
judgeSpans,
|
|
@@ -8816,6 +9426,7 @@ export {
|
|
|
8816
9426
|
mergeLayerResults,
|
|
8817
9427
|
mergeSteeringBundle,
|
|
8818
9428
|
modelDescriptionBits,
|
|
9429
|
+
modelPriceKey,
|
|
8819
9430
|
multiToolchainLayer,
|
|
8820
9431
|
nistAiRmfReport,
|
|
8821
9432
|
normalizeScores,
|
|
@@ -8840,6 +9451,7 @@ export {
|
|
|
8840
9451
|
parseRunRecordSafe,
|
|
8841
9452
|
parseRuntimeTrajectoryHookEvent,
|
|
8842
9453
|
partialCredit,
|
|
9454
|
+
partitionHeldOut,
|
|
8843
9455
|
passOrthogonality,
|
|
8844
9456
|
pixelDeltaRatio,
|
|
8845
9457
|
planTraceInsightQuestions,
|
|
@@ -8905,6 +9517,7 @@ export {
|
|
|
8905
9517
|
runProposeReview,
|
|
8906
9518
|
runProposeReviewAsControlLoop,
|
|
8907
9519
|
runReferenceReplay,
|
|
9520
|
+
runScore,
|
|
8908
9521
|
runSelfPlay,
|
|
8909
9522
|
runSemanticConceptJudge,
|
|
8910
9523
|
runTestGradedScenario,
|