reeval 0.0.1
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/LICENSE +202 -0
- package/THIRD_PARTY_NOTICES.md +2160 -0
- package/assets/package.json +9 -0
- package/assets/pnpm-lock.yaml +1336 -0
- package/dist/agent-DVHkx8aT.js +532 -0
- package/dist/agent-DVHkx8aT.js.map +1 -0
- package/dist/analysis-CxSCt1YY.js +16 -0
- package/dist/analysis-CxSCt1YY.js.map +1 -0
- package/dist/assemble-B-nmz8oH.js +695 -0
- package/dist/assemble-B-nmz8oH.js.map +1 -0
- package/dist/assemble-CmRVDZ2Q.js +2 -0
- package/dist/attempt-BdRQ9jKX.js +176 -0
- package/dist/attempt-BdRQ9jKX.js.map +1 -0
- package/dist/bridge/index.mjs +1911 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1041 -0
- package/dist/bridge/pnpm-workspace.yaml +2 -0
- package/dist/budget-DBUrH4jX.js +8 -0
- package/dist/budget-DBUrH4jX.js.map +1 -0
- package/dist/cli--CQ3Iy9q.js +1865 -0
- package/dist/cli--CQ3Iy9q.js.map +1 -0
- package/dist/cli-spec-DKAu09Gi.js +478 -0
- package/dist/cli-spec-DKAu09Gi.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-QV9dFVhN.js +34 -0
- package/dist/colors-QV9dFVhN.js.map +1 -0
- package/dist/command-BSJzHMtP.js +280 -0
- package/dist/command-BSJzHMtP.js.map +1 -0
- package/dist/command-CEm8PZ1N.js +1043 -0
- package/dist/command-CEm8PZ1N.js.map +1 -0
- package/dist/command-D-jnQWVG.js +673 -0
- package/dist/command-D-jnQWVG.js.map +1 -0
- package/dist/command-DQAvbG7d.js +432 -0
- package/dist/command-DQAvbG7d.js.map +1 -0
- package/dist/commands-A4udfyKZ.js +2 -0
- package/dist/commands-CF8mOfiu.js +207 -0
- package/dist/commands-CF8mOfiu.js.map +1 -0
- package/dist/config-Cuj0kVYj.js +415 -0
- package/dist/config-Cuj0kVYj.js.map +1 -0
- package/dist/context-loi9OCZv.js +106 -0
- package/dist/context-loi9OCZv.js.map +1 -0
- package/dist/core-tLN-z1Be.js +96 -0
- package/dist/core-tLN-z1Be.js.map +1 -0
- package/dist/db-Cjj2ZsfQ.js +2 -0
- package/dist/db-DxDzMhtB.js +827 -0
- package/dist/db-DxDzMhtB.js.map +1 -0
- package/dist/debug-D24NOQH1.js +89 -0
- package/dist/debug-D24NOQH1.js.map +1 -0
- package/dist/debug-SEmW4w8f.js +2 -0
- package/dist/dist-BZXYHN2X.js +1673 -0
- package/dist/dist-BZXYHN2X.js.map +1 -0
- package/dist/docker-DQDZWXF9.js +20 -0
- package/dist/docker-DQDZWXF9.js.map +1 -0
- package/dist/docker-DnWAVdEm.js +2 -0
- package/dist/docker-oSbCExHL.js +153 -0
- package/dist/docker-oSbCExHL.js.map +1 -0
- package/dist/doctor-CxlH1pDM.js +525 -0
- package/dist/doctor-CxlH1pDM.js.map +1 -0
- package/dist/egress-73SQfKdx.js +619 -0
- package/dist/egress-73SQfKdx.js.map +1 -0
- package/dist/env-BQqj93gu.js +21 -0
- package/dist/env-BQqj93gu.js.map +1 -0
- package/dist/environment-Mz7wvrtD.js +552 -0
- package/dist/environment-Mz7wvrtD.js.map +1 -0
- package/dist/errors-C9w_ud7g.js +160 -0
- package/dist/errors-C9w_ud7g.js.map +1 -0
- package/dist/errors-CSxfJ4qu.js +11 -0
- package/dist/errors-CSxfJ4qu.js.map +1 -0
- package/dist/files--6CEUXm7.js +2 -0
- package/dist/files-BLlrhU4-.js +62 -0
- package/dist/files-BLlrhU4-.js.map +1 -0
- package/dist/files-Bd25OAQH.js +2 -0
- package/dist/files-BvHCFWgD.js +2 -0
- package/dist/files-D2-qn_Dl.js +275 -0
- package/dist/files-D2-qn_Dl.js.map +1 -0
- package/dist/files-bF2fPuT9.js +99 -0
- package/dist/files-bF2fPuT9.js.map +1 -0
- package/dist/git-D6hMeieS.js +44 -0
- package/dist/git-D6hMeieS.js.map +1 -0
- package/dist/harness-ls5aX1UO.js +131 -0
- package/dist/harness-ls5aX1UO.js.map +1 -0
- package/dist/harness.d.ts +74 -0
- package/dist/harness.js +2 -0
- package/dist/header-DzFDoMCI.js +78 -0
- package/dist/header-DzFDoMCI.js.map +1 -0
- package/dist/ids-Bp6ut7ZD.js +49 -0
- package/dist/ids-Bp6ut7ZD.js.map +1 -0
- package/dist/images-Cc1-coFc.js +286 -0
- package/dist/images-Cc1-coFc.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +1959 -0
- package/dist/index.js.map +1 -0
- package/dist/init-CFljS8BJ.js +344 -0
- package/dist/init-CFljS8BJ.js.map +1 -0
- package/dist/init-z2ZPn9Tj.js +2 -0
- package/dist/invocations-rXktgSAn.js +157 -0
- package/dist/invocations-rXktgSAn.js.map +1 -0
- package/dist/jiti-BOLXpKyG.js +46 -0
- package/dist/jiti-BOLXpKyG.js.map +1 -0
- package/dist/judge-D8Uf4dA9.js +146 -0
- package/dist/judge-D8Uf4dA9.js.map +1 -0
- package/dist/judge.d.ts +21 -0
- package/dist/judge.js +19 -0
- package/dist/judge.js.map +1 -0
- package/dist/live-Bs3LReMV.js +98 -0
- package/dist/live-Bs3LReMV.js.map +1 -0
- package/dist/matchers-DyLvm7EG.js +374 -0
- package/dist/matchers-DyLvm7EG.js.map +1 -0
- package/dist/model-BdO79gWz.js +44 -0
- package/dist/model-BdO79gWz.js.map +1 -0
- package/dist/model-CDL98xue.js +725 -0
- package/dist/model-CDL98xue.js.map +1 -0
- package/dist/model-v-0_Ik7S.js +2 -0
- package/dist/node-kG64XxdE.js +506 -0
- package/dist/node-kG64XxdE.js.map +1 -0
- package/dist/opencode-providers-DZuD_oYt.js +3149 -0
- package/dist/opencode-providers-DZuD_oYt.js.map +1 -0
- package/dist/options-CAQhu--C.js +452 -0
- package/dist/options-CAQhu--C.js.map +1 -0
- package/dist/patch-BkKje4cp.js +69 -0
- package/dist/patch-BkKje4cp.js.map +1 -0
- package/dist/paths-CBvm6pap.js +72 -0
- package/dist/paths-CBvm6pap.js.map +1 -0
- package/dist/plan-gwk6dJ8c.js +237 -0
- package/dist/plan-gwk6dJ8c.js.map +1 -0
- package/dist/preflight-BtubXKQ8.js +2 -0
- package/dist/preflight-BypZMNBG.js +185 -0
- package/dist/preflight-BypZMNBG.js.map +1 -0
- package/dist/prepare-Cwv0bHs_.js +3 -0
- package/dist/prepare-D6Uzcubb.js +213 -0
- package/dist/prepare-D6Uzcubb.js.map +1 -0
- package/dist/provenance-Dfb8YY5Y.js +129 -0
- package/dist/provenance-Dfb8YY5Y.js.map +1 -0
- package/dist/provider-BDbNOQhQ.js +2 -0
- package/dist/provider-CmAYTQQs.js +14 -0
- package/dist/provider-CmAYTQQs.js.map +1 -0
- package/dist/providers-2p3ylMHI.js +107 -0
- package/dist/providers-2p3ylMHI.js.map +1 -0
- package/dist/providers-BUOxitcT.js +2 -0
- package/dist/query-CFATHZmV.js +91 -0
- package/dist/query-CFATHZmV.js.map +1 -0
- package/dist/query-DR4m6CXf.js +2 -0
- package/dist/record-DQ8wzyco.js +99 -0
- package/dist/record-DQ8wzyco.js.map +1 -0
- package/dist/redact-C4fL_bZP.js +167 -0
- package/dist/redact-C4fL_bZP.js.map +1 -0
- package/dist/results-CeXb8Wde.js +37 -0
- package/dist/results-CeXb8Wde.js.map +1 -0
- package/dist/root-CFDjpWuc.js +110 -0
- package/dist/root-CFDjpWuc.js.map +1 -0
- package/dist/sandbox-By2gkdkC.js +133 -0
- package/dist/sandbox-By2gkdkC.js.map +1 -0
- package/dist/sandbox.d.ts +41 -0
- package/dist/sandbox.js +2 -0
- package/dist/server-Bi61uiY0.js +350 -0
- package/dist/server-Bi61uiY0.js.map +1 -0
- package/dist/server-CsSBL8NC.js +2 -0
- package/dist/source-BXzrmof5.js +2 -0
- package/dist/source-Bsfa3LZH.js +40 -0
- package/dist/source-Bsfa3LZH.js.map +1 -0
- package/dist/src-55j1zNHH.js +3 -0
- package/dist/src-AEwxd1UP.js +602 -0
- package/dist/src-AEwxd1UP.js.map +1 -0
- package/dist/src-BDwSuyIo.js +3 -0
- package/dist/src-BQyMiWri.js +490 -0
- package/dist/src-BQyMiWri.js.map +1 -0
- package/dist/src-C-LHyTSV.js +3 -0
- package/dist/src-Cmp-7YrD.js +1136 -0
- package/dist/src-Cmp-7YrD.js.map +1 -0
- package/dist/src-D0RdqpNb.js +252 -0
- package/dist/src-D0RdqpNb.js.map +1 -0
- package/dist/src-D8KYzxBX.js +793 -0
- package/dist/src-D8KYzxBX.js.map +1 -0
- package/dist/src-DEgff9Z7.js +4 -0
- package/dist/src-DXx_29pq.js +567 -0
- package/dist/src-DXx_29pq.js.map +1 -0
- package/dist/src-DwwiVLqv.js +2937 -0
- package/dist/src-DwwiVLqv.js.map +1 -0
- package/dist/src-N7E_mfcZ.js +2 -0
- package/dist/src-W9g-y5X5.js +3 -0
- package/dist/src-daS0wKH2.js +3 -0
- package/dist/state-Cyw3QTsN.js +2 -0
- package/dist/state-DZ7eqb1F.js +167 -0
- package/dist/state-DZ7eqb1F.js.map +1 -0
- package/dist/stats-BNAHlNMD.js +114 -0
- package/dist/stats-BNAHlNMD.js.map +1 -0
- package/dist/studio-app/assets/index-D6XyfFSH.js +58 -0
- package/dist/studio-app/assets/index-xwzWdcE_.css +2 -0
- package/dist/studio-app/favicon.svg +1 -0
- package/dist/studio-app/index.html +15 -0
- package/dist/summary-D7Ttky0z.js +166 -0
- package/dist/summary-D7Ttky0z.js.map +1 -0
- package/dist/text-gEcKjAyp.js +29 -0
- package/dist/text-gEcKjAyp.js.map +1 -0
- package/dist/types-C5vI3g1C.js +9 -0
- package/dist/types-C5vI3g1C.js.map +1 -0
- package/dist/types-DLec-6Cx.d.ts +1535 -0
- package/dist/usage-DsYUqIUM.js +24 -0
- package/dist/usage-DsYUqIUM.js.map +1 -0
- package/dist/view/index.d.ts +645 -0
- package/dist/view/index.js +8 -0
- package/dist/views-BrHOv6wg.js +220 -0
- package/dist/views-BrHOv6wg.js.map +1 -0
- package/dist/views-CwCWLWqo.js +3 -0
- package/dist/worker-entry-DnYkANPG.js +506 -0
- package/dist/worker-entry-DnYkANPG.js.map +1 -0
- package/dist/workspace-C2S0Z6gl.js +327 -0
- package/dist/workspace-C2S0Z6gl.js.map +1 -0
- package/dist/workspace-CyoXG6CY.js +2 -0
- package/docker/Dockerfile +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
import { c as shardPath, i as manifestPath, s as shardOf } from "./paths-CBvm6pap.js";
|
|
2
|
+
import { a as quantile, i as minimumP, n as mannWhitney, r as median } from "./stats-BNAHlNMD.js";
|
|
3
|
+
import { s as readableTranscriptLine } from "./record-DQ8wzyco.js";
|
|
4
|
+
import { n as promptTokens } from "./usage-DsYUqIUM.js";
|
|
5
|
+
//#region src/view/compare.ts
|
|
6
|
+
const STACK_FIELDS = [
|
|
7
|
+
{
|
|
8
|
+
field: "imageIds",
|
|
9
|
+
label: "image ids",
|
|
10
|
+
outside: false
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
field: "agentClis",
|
|
14
|
+
label: "agent CLI",
|
|
15
|
+
outside: false
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
field: "harnesses",
|
|
19
|
+
label: "harnesses",
|
|
20
|
+
outside: false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
field: "environments",
|
|
24
|
+
label: "environments",
|
|
25
|
+
outside: false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
field: "judgeModel",
|
|
29
|
+
label: "judge model",
|
|
30
|
+
outside: false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
field: "threshold",
|
|
34
|
+
label: "judge threshold",
|
|
35
|
+
outside: false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
field: "judgeServed",
|
|
39
|
+
label: "judge model served",
|
|
40
|
+
outside: true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
field: "judgeVersion",
|
|
44
|
+
label: "judge version",
|
|
45
|
+
outside: false
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
field: "fingerprintScope",
|
|
49
|
+
label: "fingerprint scope",
|
|
50
|
+
outside: true
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
const show = (value) => Array.isArray(value) ? value.join(", ") || "none" : value === null ? "none" : String(value).replace(/^sha256:/, "").slice(0, 24);
|
|
54
|
+
/** The stack fields that differ by which cells ran, read off each cell. */
|
|
55
|
+
const PER_CELL = {
|
|
56
|
+
imageIds: (c) => c.fingerprintParts.image,
|
|
57
|
+
agentClis: (c) => c.fingerprintParts.agentCli,
|
|
58
|
+
harnesses: (c) => c.harness,
|
|
59
|
+
environments: (c) => `${c.environment}@${(c.environmentDigest ?? "").replace(/^sha256:/, "").slice(0, 12)}`
|
|
60
|
+
};
|
|
61
|
+
/** The cells both runs have (same eval, same cell key), as `[base, head]` pairs. */
|
|
62
|
+
function sharedCells(base, head) {
|
|
63
|
+
if (![base, head].every((r) => r.evals?.every((e) => Array.isArray(e.cells)))) return null;
|
|
64
|
+
if (!base.evals || !head.evals) return null;
|
|
65
|
+
const before = new Map(base.evals.map((e) => [e.id, new Map(e.cells.map((c) => [c.key, c]))]));
|
|
66
|
+
return head.evals.flatMap((e) => e.cells.flatMap((c) => {
|
|
67
|
+
const b = before.get(e.id)?.get(c.key);
|
|
68
|
+
return b ? [[b, c]] : [];
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What changed in the stack between two runs. With both runs' cells, images, agent CLIs,
|
|
73
|
+
* harnesses and environments compare over the cells both have, so a run over fewer cells isn't
|
|
74
|
+
* drift. Reported served-judge lists only compare when both have one.
|
|
75
|
+
*/
|
|
76
|
+
function stackChanges(base, head) {
|
|
77
|
+
const shared = sharedCells(base, head);
|
|
78
|
+
const changes = [];
|
|
79
|
+
for (const { field, label, outside } of STACK_FIELDS) {
|
|
80
|
+
const pick = PER_CELL[field];
|
|
81
|
+
const differ = pick && shared ? shared.filter(([x, y]) => pick(x) !== pick(y)) : [];
|
|
82
|
+
const over = (side) => [...new Set(differ.flatMap((pair) => pick?.(pair[side]) ?? []))].sort();
|
|
83
|
+
const b = pick && shared ? over(0) : base.stack[field];
|
|
84
|
+
const h = pick && shared ? over(1) : head.stack[field];
|
|
85
|
+
if (field === "judgeServed" && (!b.length || !h.length)) continue;
|
|
86
|
+
if (JSON.stringify(b) === JSON.stringify(h)) continue;
|
|
87
|
+
changes.push({
|
|
88
|
+
field,
|
|
89
|
+
label,
|
|
90
|
+
base: show(b),
|
|
91
|
+
head: show(h),
|
|
92
|
+
outsideFingerprint: outside
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return changes;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether any of these cells changed fingerprint since `seen` last had it, then records them.
|
|
99
|
+
* A cell `seen` doesn't have yet is new coverage, not a change.
|
|
100
|
+
*/
|
|
101
|
+
function cellsChanged(seen, evalId, cells) {
|
|
102
|
+
let changed = false;
|
|
103
|
+
for (const c of cells) {
|
|
104
|
+
const key = `${evalId}\0${c.key}`;
|
|
105
|
+
const was = seen.get(key);
|
|
106
|
+
if (was !== void 0 && was !== c.fingerprint) changed = true;
|
|
107
|
+
seen.set(key, c.fingerprint);
|
|
108
|
+
}
|
|
109
|
+
return changed;
|
|
110
|
+
}
|
|
111
|
+
/** Which source files were added, removed or changed. */
|
|
112
|
+
function sourceChanges(before, after) {
|
|
113
|
+
const a = new Map(before.map((s) => [s.path, s.sha256]));
|
|
114
|
+
const b = new Map(after.map((s) => [s.path, s.sha256]));
|
|
115
|
+
return [.../* @__PURE__ */ new Set([...a.keys(), ...b.keys()])].sort().flatMap((path) => {
|
|
116
|
+
if (!a.has(path)) return [`${path} added`];
|
|
117
|
+
if (!b.has(path)) return [`${path} removed`];
|
|
118
|
+
return a.get(path) === b.get(path) ? [] : [`${path} changed`];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const PART_LABELS = {
|
|
122
|
+
harness: "harness config",
|
|
123
|
+
environment: "environment",
|
|
124
|
+
image: "image",
|
|
125
|
+
agentCli: "agent CLI",
|
|
126
|
+
judge: "judge",
|
|
127
|
+
context: "agent context"
|
|
128
|
+
};
|
|
129
|
+
/** Why two runs of an eval aren't comparable: the source files and fingerprint parts that differ. */
|
|
130
|
+
function whyChanged(base, head) {
|
|
131
|
+
const out = sourceChanges(base.sources, head.sources);
|
|
132
|
+
const keys = [...new Set([...base.cells, ...head.cells].map((c) => c.key))];
|
|
133
|
+
const parts = /* @__PURE__ */ new Set();
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
const b = base.cells.find((c) => c.key === key);
|
|
136
|
+
const h = head.cells.find((c) => c.key === key);
|
|
137
|
+
if (!b || !h) {
|
|
138
|
+
out.push(`cell ${key} ${b ? "removed" : "added"}`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (const part of Object.keys(PART_LABELS)) if (b.fingerprintParts[part] !== h.fingerprintParts[part]) parts.add(PART_LABELS[part]);
|
|
142
|
+
}
|
|
143
|
+
return [...out, ...[...parts].map((p) => `${p} changed`)];
|
|
144
|
+
}
|
|
145
|
+
/** An eval's outcome from some of its cells, as the run rolls it up: ungated failures pass. */
|
|
146
|
+
function outcomeOf(cells) {
|
|
147
|
+
const outcomes = cells.map((c) => c.gate === "none" && c.outcome === "fail" ? "pass" : c.outcome);
|
|
148
|
+
if (outcomes.includes("fail")) return "fail";
|
|
149
|
+
if (outcomes.includes("infra_error")) return "infra_error";
|
|
150
|
+
return outcomes.every((o) => o === "skipped") ? "skipped" : "pass";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* When two runs of an eval differ only in which cells ran (a filtered run, a harness added), its
|
|
154
|
+
* outcome on the cells both have, and the cells only one has. Null when no cell is shared or a
|
|
155
|
+
* shared cell changed.
|
|
156
|
+
*/
|
|
157
|
+
function sharedScope(b, h) {
|
|
158
|
+
const before = new Map(b.cells.map((c) => [c.key, c]));
|
|
159
|
+
const shared = h.cells.filter((c) => before.has(c.key));
|
|
160
|
+
if (!shared.length || shared.some((c) => before.get(c.key)?.fingerprint !== c.fingerprint)) return null;
|
|
161
|
+
const keys = new Set(h.cells.map((c) => c.key));
|
|
162
|
+
return {
|
|
163
|
+
base: outcomeOf(shared.map((c) => before.get(c.key) ?? c)),
|
|
164
|
+
head: outcomeOf(shared),
|
|
165
|
+
cells: {
|
|
166
|
+
added: h.cells.filter((c) => !before.has(c.key)).map((c) => c.key),
|
|
167
|
+
removed: b.cells.filter((c) => !keys.has(c.key)).map((c) => c.key)
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Cells in both runs that have something to compare: a lift or effort on either side. */
|
|
172
|
+
function compareCells(base, head) {
|
|
173
|
+
const pick = (c) => ({
|
|
174
|
+
...c.lift ? { lift: c.lift } : {},
|
|
175
|
+
effort: c.effort ?? null,
|
|
176
|
+
pass: c.pass,
|
|
177
|
+
fail: c.fail
|
|
178
|
+
});
|
|
179
|
+
return (head.cells ?? []).flatMap((h) => {
|
|
180
|
+
const b = base.cells?.find((c) => c.key === h.key);
|
|
181
|
+
if (!b || !(b.lift || h.lift || b.effort && h.effort)) return [];
|
|
182
|
+
return [{
|
|
183
|
+
key: h.key,
|
|
184
|
+
base: pick(b),
|
|
185
|
+
head: pick(h)
|
|
186
|
+
}];
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function compare(base, head) {
|
|
190
|
+
const graderChanged = stackChanges(base, head).some((c) => c.outsideFingerprint);
|
|
191
|
+
return [.../* @__PURE__ */ new Set([...head.evals.map((e) => e.id), ...base.evals.map((e) => e.id)])].map((id) => {
|
|
192
|
+
const b = base.evals.find((e) => e.id === id);
|
|
193
|
+
const h = head.evals.find((e) => e.id === id);
|
|
194
|
+
if (!b) return {
|
|
195
|
+
id,
|
|
196
|
+
base: null,
|
|
197
|
+
head: h?.outcome ?? null,
|
|
198
|
+
status: "added"
|
|
199
|
+
};
|
|
200
|
+
if (!h) return {
|
|
201
|
+
id,
|
|
202
|
+
base: b.outcome,
|
|
203
|
+
head: null,
|
|
204
|
+
status: "removed"
|
|
205
|
+
};
|
|
206
|
+
const scoped = b.fingerprint === h.fingerprint ? null : sharedScope(b, h);
|
|
207
|
+
const row = scoped ? {
|
|
208
|
+
id,
|
|
209
|
+
...scoped
|
|
210
|
+
} : {
|
|
211
|
+
id,
|
|
212
|
+
base: b.outcome,
|
|
213
|
+
head: h.outcome
|
|
214
|
+
};
|
|
215
|
+
const perCell = compareCells(b, h);
|
|
216
|
+
if (perCell.length) row.perCell = perCell;
|
|
217
|
+
if (row.base === "infra_error" || row.head === "infra_error") return {
|
|
218
|
+
...row,
|
|
219
|
+
status: "infra"
|
|
220
|
+
};
|
|
221
|
+
if (b.fingerprint !== h.fingerprint && !scoped) return {
|
|
222
|
+
...row,
|
|
223
|
+
status: "drift",
|
|
224
|
+
changes: whyChanged(b, h)
|
|
225
|
+
};
|
|
226
|
+
if (graderChanged && row.base !== row.head) return {
|
|
227
|
+
...row,
|
|
228
|
+
status: "drift"
|
|
229
|
+
};
|
|
230
|
+
if (row.base === "pass" && row.head === "fail") return {
|
|
231
|
+
...row,
|
|
232
|
+
status: "regressed"
|
|
233
|
+
};
|
|
234
|
+
if (row.base === "fail" && row.head === "pass") return {
|
|
235
|
+
...row,
|
|
236
|
+
status: "fixed"
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
...row,
|
|
240
|
+
status: "same"
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/view/effort.ts
|
|
246
|
+
/** Below this two-sided p, an effort difference is an effect. */
|
|
247
|
+
const EFFORT_ALPHA = .05;
|
|
248
|
+
const effortSample = (a) => ({
|
|
249
|
+
tools: a.activity.tools,
|
|
250
|
+
agentMs: a.timings.agentMs,
|
|
251
|
+
fileEdits: a.activity.fileEdits
|
|
252
|
+
});
|
|
253
|
+
const counted = (a) => a.outcome === "pass" || a.outcome === "fail";
|
|
254
|
+
const spread = (values) => ({
|
|
255
|
+
median: median(values) ?? 0,
|
|
256
|
+
q1: quantile(values, .25) ?? 0,
|
|
257
|
+
q3: quantile(values, .75) ?? 0
|
|
258
|
+
});
|
|
259
|
+
function effortOf(samples) {
|
|
260
|
+
if (samples.length === 0) return null;
|
|
261
|
+
return {
|
|
262
|
+
n: samples.length,
|
|
263
|
+
tools: spread(samples.map((s) => s.tools)),
|
|
264
|
+
agentMs: spread(samples.map((s) => s.agentMs)),
|
|
265
|
+
fileEdits: median(samples.map((s) => s.fileEdits)) ?? 0
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** The verdict of an exact rank test of `xs` (variant) against `ys` (control). */
|
|
269
|
+
function effortVerdict(xs, ys) {
|
|
270
|
+
const p = mannWhitney(xs, ys);
|
|
271
|
+
if (minimumP(xs.length, ys.length) >= .05) return {
|
|
272
|
+
p,
|
|
273
|
+
verdict: "insufficient"
|
|
274
|
+
};
|
|
275
|
+
if (p === null || p >= .05) return {
|
|
276
|
+
p,
|
|
277
|
+
verdict: "noise"
|
|
278
|
+
};
|
|
279
|
+
return {
|
|
280
|
+
p,
|
|
281
|
+
verdict: (median(xs) ?? 0) < (median(ys) ?? 0) ? "fewer" : "more"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const ratio = (a, b) => b === 0 ? null : a / b;
|
|
285
|
+
function contrastEffort(variant, control) {
|
|
286
|
+
const v = effortOf(variant);
|
|
287
|
+
const c = effortOf(control);
|
|
288
|
+
if (!v || !c) return null;
|
|
289
|
+
const tools = effortVerdict(variant.map((s) => s.tools), control.map((s) => s.tools));
|
|
290
|
+
const time = effortVerdict(variant.map((s) => s.agentMs), control.map((s) => s.agentMs));
|
|
291
|
+
const vt = variant.map((s) => s.tools);
|
|
292
|
+
const ct = control.map((s) => s.tools);
|
|
293
|
+
return {
|
|
294
|
+
variant: v,
|
|
295
|
+
control: c,
|
|
296
|
+
toolsRatio: ratio(v.tools.median, c.tools.median),
|
|
297
|
+
agentMsRatio: ratio(v.agentMs.median, c.agentMs.median),
|
|
298
|
+
agentMsDelta: v.agentMs.median - c.agentMs.median,
|
|
299
|
+
toolsP: tools.p,
|
|
300
|
+
agentMsP: time.p,
|
|
301
|
+
separated: Math.max(...vt) < Math.min(...ct) || Math.min(...vt) > Math.max(...ct),
|
|
302
|
+
verdict: tools.verdict,
|
|
303
|
+
timeVerdict: time.verdict
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** The counted attempts of one cell of an eval, as effort samples. */
|
|
307
|
+
const cellSamples = (e, cellKey) => e.attempts.filter((a) => a.cell === cellKey && counted(a)).map(effortSample);
|
|
308
|
+
const cellEffort = (e, cellKey) => effortOf(cellSamples(e, cellKey));
|
|
309
|
+
/** A variant cell's effort against its control's, in the same run; null for controls. */
|
|
310
|
+
function cellContrast(e, cellKey) {
|
|
311
|
+
const cell = e.cells.find((c) => c.key === cellKey);
|
|
312
|
+
if (!cell?.lift) return null;
|
|
313
|
+
return contrastEffort(cellSamples(e, cellKey), cellSamples(e, cell.lift.vs));
|
|
314
|
+
}
|
|
315
|
+
/** `4`, `4.5`: medians of counts are whole or halves. */
|
|
316
|
+
const formatCount = (n) => Number.isInteger(n) ? String(n) : n.toFixed(1);
|
|
317
|
+
const formatRatio = (r) => r === null ? "×–" : `×${r.toFixed(2)}`;
|
|
318
|
+
/** Seconds for agent time: `21s`, `1m 05s`. */
|
|
319
|
+
function formatSeconds(ms) {
|
|
320
|
+
if (ms < 9950) return ms < 50 ? "0s" : `${(ms / 1e3).toFixed(1)}s`;
|
|
321
|
+
const s = Math.round(ms / 1e3);
|
|
322
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s`;
|
|
323
|
+
}
|
|
324
|
+
const formatP = (p) => p === null ? "" : ` (p = ${p < .001 ? "<0.001" : p.toFixed(3)})`;
|
|
325
|
+
const VERDICT_TEXT = {
|
|
326
|
+
fewer: (what) => `fewer ${what}`,
|
|
327
|
+
more: (what) => `more ${what}`,
|
|
328
|
+
noise: () => "within noise",
|
|
329
|
+
insufficient: () => "too few attempts to test"
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* `tools 5 vs 43 (×0.11), agent 21s vs 46s (×0.47): too few attempts to test` for a variant
|
|
333
|
+
* against its control; with enough attempts, `fewer tool calls (p = 0.029)`.
|
|
334
|
+
*/
|
|
335
|
+
function effortLabel(c) {
|
|
336
|
+
return `${`tools ${formatCount(c.variant.tools.median)} vs ${formatCount(c.control.tools.median)} (${formatRatio(c.toolsRatio)})`}, ${`agent ${formatSeconds(c.variant.agentMs.median)} vs ${formatSeconds(c.control.agentMs.median)} (${formatRatio(c.agentMsRatio)})`}: ${c.verdict === "fewer" || c.verdict === "more" ? `${VERDICT_TEXT[c.verdict]("tool calls")}${formatP(c.toolsP)}` : VERDICT_TEXT[c.verdict]("")}`;
|
|
337
|
+
}
|
|
338
|
+
/** One cell's effort on its own: `tools 43 · agent 46s`. */
|
|
339
|
+
const effortSummary = (e) => `tools ${formatCount(e.tools.median)} · agent ${formatSeconds(e.agentMs.median)}`;
|
|
340
|
+
/** The same cell between two runs: `tools 44 → 10 (×0.23) · agent 45s → 21s (×0.47)`. */
|
|
341
|
+
function effortChangeLabel(base, head) {
|
|
342
|
+
const t = ratio(head.tools.median, base.tools.median);
|
|
343
|
+
const a = ratio(head.agentMs.median, base.agentMs.median);
|
|
344
|
+
return `tools ${formatCount(base.tools.median)} → ${formatCount(head.tools.median)} (${formatRatio(t)}) · agent ${formatSeconds(base.agentMs.median)} → ${formatSeconds(head.agentMs.median)} (${formatRatio(a)})`;
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/view/model.ts
|
|
348
|
+
/** Splits `file › group › … › name` back into its parts. */
|
|
349
|
+
function splitEvalId(id) {
|
|
350
|
+
const [file = id, ...path] = id.split(" › ");
|
|
351
|
+
return {
|
|
352
|
+
file,
|
|
353
|
+
path: path.length ? path : [file]
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/** Month shards (newest run first) and the manifest over them, from every run's header. */
|
|
357
|
+
function buildIndex(entries, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
358
|
+
const sorted = [...entries].sort((a, b) => a.runId < b.runId ? 1 : -1);
|
|
359
|
+
const shards = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const entry of sorted) {
|
|
361
|
+
const id = shardOf(entry.startedAt);
|
|
362
|
+
const list = shards.get(id) ?? [];
|
|
363
|
+
list.push(entry);
|
|
364
|
+
shards.set(id, list);
|
|
365
|
+
}
|
|
366
|
+
const evals = /* @__PURE__ */ new Map();
|
|
367
|
+
for (const run of sorted) for (const e of run.evals) {
|
|
368
|
+
const known = evals.get(e.slug);
|
|
369
|
+
if (known) {
|
|
370
|
+
known.runs++;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
evals.set(e.slug, {
|
|
374
|
+
slug: e.slug,
|
|
375
|
+
id: e.id,
|
|
376
|
+
...splitEvalId(e.id),
|
|
377
|
+
runs: 1,
|
|
378
|
+
latest: {
|
|
379
|
+
runId: run.runId,
|
|
380
|
+
outcome: e.outcome
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
manifest: {
|
|
386
|
+
generatedAt,
|
|
387
|
+
shards: [...shards].map(([id, runs]) => ({
|
|
388
|
+
id,
|
|
389
|
+
runs: runs.length
|
|
390
|
+
})),
|
|
391
|
+
latest: sorted[0]?.runId ?? null,
|
|
392
|
+
evals: [...evals.values()].sort((a, b) => a.id < b.id ? -1 : 1)
|
|
393
|
+
},
|
|
394
|
+
shards
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** Every file the index consists of, by relative path. */
|
|
398
|
+
function indexFiles(entries, generatedAt) {
|
|
399
|
+
const { manifest, shards } = buildIndex(entries, generatedAt);
|
|
400
|
+
const files = /* @__PURE__ */ new Map([[manifestPath, manifest]]);
|
|
401
|
+
for (const [id, list] of shards) files.set(shardPath(id), list);
|
|
402
|
+
return files;
|
|
403
|
+
}
|
|
404
|
+
function formatDuration(ms) {
|
|
405
|
+
if (ms === null || ms === void 0) return "–";
|
|
406
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
407
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
|
|
408
|
+
const m = Math.floor(ms / 6e4);
|
|
409
|
+
const s = Math.round(ms % 6e4 / 1e3);
|
|
410
|
+
if (m < 60) return s ? `${m}m ${s}s` : `${m}m`;
|
|
411
|
+
return `${Math.floor(m / 60)}h ${m % 60}m`;
|
|
412
|
+
}
|
|
413
|
+
const formatPercent = (rate) => rate === null ? "–" : `${Math.round(rate * 100)}%`;
|
|
414
|
+
/**
|
|
415
|
+
* A harness of the run's stack as `cursor (model composer-2.5, served composer-2.5[fast=true])`:
|
|
416
|
+
* the configured model, then the variant the harness says it resolved.
|
|
417
|
+
*/
|
|
418
|
+
function harnessLabel(h) {
|
|
419
|
+
const parts = [...h.model ? [`model ${h.model}`] : [], ...h.resolvedModels.length ? [`served ${h.resolvedModels.join(", ")}`] : []];
|
|
420
|
+
return parts.length ? `${h.id} (${parts.join(", ")})` : h.id;
|
|
421
|
+
}
|
|
422
|
+
/** Harness-reported tokens as `127k in (98k cached) · 776 out`; null when none were reported. */
|
|
423
|
+
function formatUsage(usage) {
|
|
424
|
+
if (!usage) return null;
|
|
425
|
+
const k = (n) => n >= 1e4 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
426
|
+
const cached = usage.cacheRead ? ` (${k(usage.cacheRead)} cached)` : "";
|
|
427
|
+
return `${k(promptTokens(usage))} in${cached} · ${k(usage.output)} out`;
|
|
428
|
+
}
|
|
429
|
+
const shortSha = (sha) => sha ? sha.slice(0, 7) : "no git";
|
|
430
|
+
/**
|
|
431
|
+
* An attempt's effort, counted as the CLI's turn lines count it: tool calls, then file edits.
|
|
432
|
+
* Edit calls become one file edit per file they change, so they're counted apart, never summed.
|
|
433
|
+
*/
|
|
434
|
+
function activityLabel(a) {
|
|
435
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
436
|
+
return `${plural(a.tools, "tool call")} · ${plural(a.fileEdits, "file edit")}`;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* A run's outcome, as `reeval run`'s exit code has it: a failed gated cell (or a budget stop) fails
|
|
440
|
+
* the run, while failures in ungated cells (controls, by default) don't.
|
|
441
|
+
*/
|
|
442
|
+
function runOutcome(run) {
|
|
443
|
+
if (run.status === "interrupted") return "skipped";
|
|
444
|
+
const outcomes = run.evals.map((e) => e.outcome);
|
|
445
|
+
if (outcomes.includes("fail") || run.status === "budget") return "fail";
|
|
446
|
+
if (outcomes.includes("infra_error")) return "infra_error";
|
|
447
|
+
return "pass";
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Newest first. Drift is measured per cell, against the last run that had it: a run over fewer
|
|
451
|
+
* cells (a filtered run) is no drift, and neither is the full run after it. The judge's settings
|
|
452
|
+
* compare with the next older complete run.
|
|
453
|
+
*/
|
|
454
|
+
function runRows(entries) {
|
|
455
|
+
const oldest = [...entries].sort((a, b) => a.runId < b.runId ? -1 : 1);
|
|
456
|
+
const seen = /* @__PURE__ */ new Map();
|
|
457
|
+
const lastCells = /* @__PURE__ */ new Map();
|
|
458
|
+
let previous = null;
|
|
459
|
+
const rows = [];
|
|
460
|
+
for (const entry of oldest) {
|
|
461
|
+
const { pass, fail, infraError } = entry.summary;
|
|
462
|
+
const base = previous && {
|
|
463
|
+
stack: previous.stack,
|
|
464
|
+
evals: [...lastCells].map(([id, cells]) => ({
|
|
465
|
+
id,
|
|
466
|
+
cells: [...cells.values()]
|
|
467
|
+
}))
|
|
468
|
+
};
|
|
469
|
+
let changedEvals = 0;
|
|
470
|
+
for (const e of entry.evals) {
|
|
471
|
+
if (cellsChanged(seen, e.id, e.cells)) changedEvals++;
|
|
472
|
+
const cells = lastCells.get(e.id) ?? /* @__PURE__ */ new Map();
|
|
473
|
+
for (const c of e.cells) cells.set(c.key, c);
|
|
474
|
+
lastCells.set(e.id, cells);
|
|
475
|
+
}
|
|
476
|
+
rows.push({
|
|
477
|
+
entry,
|
|
478
|
+
passed: pass,
|
|
479
|
+
total: pass + fail + infraError,
|
|
480
|
+
drift: base ? stackChanges(base, entry) : [],
|
|
481
|
+
changedEvals
|
|
482
|
+
});
|
|
483
|
+
if (entry.status === "complete") previous = entry;
|
|
484
|
+
}
|
|
485
|
+
return rows.reverse();
|
|
486
|
+
}
|
|
487
|
+
const tallyOf = (e) => ({
|
|
488
|
+
pass: e.attempts.filter((a) => a.outcome === "pass").length,
|
|
489
|
+
fail: e.attempts.filter((a) => a.outcome === "fail" && !isUngatedMiss(e, a)).length,
|
|
490
|
+
missed: e.attempts.filter((a) => isUngatedMiss(e, a)).length,
|
|
491
|
+
infra: e.attempts.filter((a) => a.outcome === "infra_error").length,
|
|
492
|
+
skipped: e.attempts.filter((a) => a.outcome === "skipped").length
|
|
493
|
+
});
|
|
494
|
+
const addTally = (a, b) => ({
|
|
495
|
+
pass: a.pass + b.pass,
|
|
496
|
+
fail: a.fail + b.fail,
|
|
497
|
+
missed: a.missed + b.missed,
|
|
498
|
+
infra: a.infra + b.infra,
|
|
499
|
+
skipped: a.skipped + b.skipped
|
|
500
|
+
});
|
|
501
|
+
const ZERO = {
|
|
502
|
+
pass: 0,
|
|
503
|
+
fail: 0,
|
|
504
|
+
missed: 0,
|
|
505
|
+
infra: 0,
|
|
506
|
+
skipped: 0
|
|
507
|
+
};
|
|
508
|
+
/** Files, then groups, then evals, each with attempt counts; failing branches sort first. */
|
|
509
|
+
function evalTree(record) {
|
|
510
|
+
const root = {
|
|
511
|
+
kind: "group",
|
|
512
|
+
key: "",
|
|
513
|
+
name: "",
|
|
514
|
+
tally: ZERO,
|
|
515
|
+
children: []
|
|
516
|
+
};
|
|
517
|
+
for (const e of record.evals) {
|
|
518
|
+
const trail = [e.file, ...e.group];
|
|
519
|
+
let node = root;
|
|
520
|
+
for (const [i, name] of trail.entries()) {
|
|
521
|
+
const key = trail.slice(0, i + 1).join(" › ");
|
|
522
|
+
let child = node.children.find((c) => c.kind === "group" && c.key === key);
|
|
523
|
+
if (!child) {
|
|
524
|
+
child = {
|
|
525
|
+
kind: "group",
|
|
526
|
+
key,
|
|
527
|
+
name,
|
|
528
|
+
tally: ZERO,
|
|
529
|
+
children: []
|
|
530
|
+
};
|
|
531
|
+
node.children.push(child);
|
|
532
|
+
}
|
|
533
|
+
node = child;
|
|
534
|
+
}
|
|
535
|
+
node.children.push({
|
|
536
|
+
kind: "eval",
|
|
537
|
+
key: e.id,
|
|
538
|
+
name: e.name,
|
|
539
|
+
tally: tallyOf(e),
|
|
540
|
+
eval: e
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const finish = (n) => {
|
|
544
|
+
if (n.kind === "eval") return n;
|
|
545
|
+
const children = n.children.map(finish);
|
|
546
|
+
children.sort((a, b) => rank(a.tally) - rank(b.tally));
|
|
547
|
+
return {
|
|
548
|
+
...n,
|
|
549
|
+
children,
|
|
550
|
+
tally: children.reduce((t, c) => addTally(t, c.tally), ZERO)
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
return finish(root).children;
|
|
554
|
+
}
|
|
555
|
+
const rank = (t) => t.fail ? 0 : t.infra ? 1 : t.pass ? 2 : 3;
|
|
556
|
+
/** One row per cell: controls and variants, with each variant's recorded lift. */
|
|
557
|
+
function cellRows(e) {
|
|
558
|
+
return e.cells.map((cell) => ({
|
|
559
|
+
key: cell.key,
|
|
560
|
+
harness: cell.harness,
|
|
561
|
+
environment: cell.environment,
|
|
562
|
+
sandbox: cell.sandbox,
|
|
563
|
+
role: cell.role,
|
|
564
|
+
gated: cell.gate !== "none",
|
|
565
|
+
gate: cell.gate,
|
|
566
|
+
overrides: cell.overrides,
|
|
567
|
+
runs: cell.runs,
|
|
568
|
+
pass: cell.pass,
|
|
569
|
+
fail: cell.fail,
|
|
570
|
+
infraError: cell.infraError,
|
|
571
|
+
skipped: cell.skipped,
|
|
572
|
+
passRate: cell.passRate,
|
|
573
|
+
outcome: cell.outcome,
|
|
574
|
+
lift: cell.lift ? Math.round(cell.lift.points * 100) : null,
|
|
575
|
+
liftInterval: cell.lift ? {
|
|
576
|
+
lo: Math.round(cell.lift.interval.lo * 100),
|
|
577
|
+
hi: Math.round(cell.lift.interval.hi * 100),
|
|
578
|
+
withinNoise: cell.lift.withinNoise
|
|
579
|
+
} : null,
|
|
580
|
+
belowMinLift: cell.lift?.minLift !== void 0 && cell.lift.interval.lo < cell.lift.minLift,
|
|
581
|
+
effort: cellEffort(e, cell.key),
|
|
582
|
+
effortVsControl: cellContrast(e, cell.key),
|
|
583
|
+
attempts: e.attempts.filter((a) => a.cell === cell.key)
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
/** Whether an eval needs the cells table: several cells, or several runs of one. */
|
|
587
|
+
const isMatrix = (e) => e.cells.length > 1 || e.cells.some((c) => c.runs > 1);
|
|
588
|
+
/** One eval across runs, oldest first. */
|
|
589
|
+
function evalHistory(entries, slug) {
|
|
590
|
+
const oldest = [...entries].sort((a, b) => a.runId < b.runId ? -1 : 1);
|
|
591
|
+
const points = [];
|
|
592
|
+
const seen = /* @__PURE__ */ new Map();
|
|
593
|
+
for (const run of oldest) {
|
|
594
|
+
const e = run.evals.find((x) => x.slug === slug);
|
|
595
|
+
if (!e) continue;
|
|
596
|
+
points.push({
|
|
597
|
+
run,
|
|
598
|
+
outcome: e.outcome,
|
|
599
|
+
passRate: e.passRate,
|
|
600
|
+
cells: e.cells,
|
|
601
|
+
changed: cellsChanged(seen, e.id, e.cells)
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return points;
|
|
605
|
+
}
|
|
606
|
+
const meanScore = (e) => {
|
|
607
|
+
const scores = e?.attempts.flatMap((a) => a.judgements.map((j) => j.score)) ?? [];
|
|
608
|
+
return scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : null;
|
|
609
|
+
};
|
|
610
|
+
const COMPARE_ORDER = [
|
|
611
|
+
"regressed",
|
|
612
|
+
"fixed",
|
|
613
|
+
"drift",
|
|
614
|
+
"infra",
|
|
615
|
+
"added",
|
|
616
|
+
"removed",
|
|
617
|
+
"same"
|
|
618
|
+
];
|
|
619
|
+
/**
|
|
620
|
+
* Whether some of an eval's cells aren't gated (`gate: 'none'`, e.g. controls under
|
|
621
|
+
* `compare.gate: 'variants'`). Its pass rate then covers fewer attempts than its pass count, so
|
|
622
|
+
* the eval line labels the rate `gated`: `2/4 · 100% gated`.
|
|
623
|
+
*/
|
|
624
|
+
const hasUngatedCells = (cells) => cells.some((c) => c.gate === "none");
|
|
625
|
+
/**
|
|
626
|
+
* An expected miss: a failed attempt in an ungated cell (a control, by default). It's reported
|
|
627
|
+
* apart from failures, since it fails neither its eval nor the run.
|
|
628
|
+
*/
|
|
629
|
+
const isUngatedMiss = (e, a) => a.outcome === "fail" && a.error?.kind !== "aborted" && e.cells.find((c) => c.key === a.cell)?.gate === "none";
|
|
630
|
+
/** Passes over passes and fails in the gated cells, as an eval's `passRate` counts them. */
|
|
631
|
+
function gatedPassRate(cells) {
|
|
632
|
+
const gated = cells.filter((c) => c.gate !== "none");
|
|
633
|
+
const pass = gated.reduce((n, c) => n + c.pass, 0);
|
|
634
|
+
const counted = pass + gated.reduce((n, c) => n + c.fail, 0);
|
|
635
|
+
return counted ? pass / counted : null;
|
|
636
|
+
}
|
|
637
|
+
/** `reeval compare`'s classification, plus pass rates and judge-score deltas from the records. */
|
|
638
|
+
function compareView(base, head, records = {}) {
|
|
639
|
+
const rows = compare(base, head).map((row) => {
|
|
640
|
+
const b = base.evals.find((e) => e.id === row.id);
|
|
641
|
+
const h = head.evals.find((e) => e.id === row.id);
|
|
642
|
+
const baseKeys = new Set(b?.cells.map((c) => c.key));
|
|
643
|
+
const shared = row.cells && new Set(h?.cells.filter((c) => baseKeys.has(c.key)).map((c) => c.key));
|
|
644
|
+
const passRate = (e) => {
|
|
645
|
+
if (!e) return null;
|
|
646
|
+
return shared ? gatedPassRate(e.cells.filter((c) => shared.has(c.key))) : e.passRate;
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
...row,
|
|
650
|
+
slug: h?.slug ?? b?.slug ?? null,
|
|
651
|
+
basePassRate: passRate(b),
|
|
652
|
+
headPassRate: passRate(h),
|
|
653
|
+
baseScore: meanScore(records.base?.evals.find((e) => e.id === row.id)),
|
|
654
|
+
headScore: meanScore(records.head?.evals.find((e) => e.id === row.id)),
|
|
655
|
+
perCell: row.perCell ?? []
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
rows.sort((a, b) => COMPARE_ORDER.indexOf(a.status) - COMPARE_ORDER.indexOf(b.status));
|
|
659
|
+
return {
|
|
660
|
+
rows,
|
|
661
|
+
counts: Object.fromEntries(COMPARE_ORDER.map((s) => [s, rows.filter((r) => r.status === s).length])),
|
|
662
|
+
changes: stackChanges(base, head)
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
/** Parses `transcript.jsonl`, skipping lines that aren't JSON (a truncated tail, say). */
|
|
666
|
+
function parseTranscript(text) {
|
|
667
|
+
const lines = [];
|
|
668
|
+
for (const line of text.split("\n")) {
|
|
669
|
+
if (!line.trim()) continue;
|
|
670
|
+
try {
|
|
671
|
+
const value = JSON.parse(line);
|
|
672
|
+
if (value && typeof value.type === "string") lines.push(readableTranscriptLine(value));
|
|
673
|
+
} catch {}
|
|
674
|
+
}
|
|
675
|
+
return lines;
|
|
676
|
+
}
|
|
677
|
+
function transcriptItems(lines) {
|
|
678
|
+
return lines.map((line) => {
|
|
679
|
+
if (line.type === "prompt") return {
|
|
680
|
+
kind: "turn",
|
|
681
|
+
turn: line.turn,
|
|
682
|
+
prompt: line.text,
|
|
683
|
+
ts: line.ts
|
|
684
|
+
};
|
|
685
|
+
if (line.type === "turn-end") return {
|
|
686
|
+
kind: "turn-end",
|
|
687
|
+
turn: line.turn,
|
|
688
|
+
status: line.status,
|
|
689
|
+
durationMs: line.durationMs
|
|
690
|
+
};
|
|
691
|
+
return {
|
|
692
|
+
kind: "event",
|
|
693
|
+
turn: line.turn,
|
|
694
|
+
event: line
|
|
695
|
+
};
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
const HEADER = /^diff --git a\/(.+?) b\/(.+)$/m;
|
|
699
|
+
/** Splits a git patch into one entry per file, with line counts. */
|
|
700
|
+
function splitPatch(patch) {
|
|
701
|
+
if (!patch.trim()) return [];
|
|
702
|
+
return patch.split(/(?=^diff --git )/m).filter((part) => part.startsWith("diff --git ")).map((text) => {
|
|
703
|
+
const header = HEADER.exec(text);
|
|
704
|
+
const path = header?.[2] ?? header?.[1] ?? "unknown";
|
|
705
|
+
let added = 0;
|
|
706
|
+
let removed = 0;
|
|
707
|
+
for (const line of text.split("\n")) if (line.startsWith("+") && !line.startsWith("+++ ")) added++;
|
|
708
|
+
else if (line.startsWith("-") && !line.startsWith("--- ")) removed++;
|
|
709
|
+
return {
|
|
710
|
+
path,
|
|
711
|
+
status: /^new file mode/m.test(text) ? "added" : /^deleted file mode/m.test(text) ? "deleted" : /^rename from/m.test(text) ? "renamed" : /^(Binary files|GIT binary patch)/m.test(text) ? "binary" : "modified",
|
|
712
|
+
added,
|
|
713
|
+
removed,
|
|
714
|
+
text
|
|
715
|
+
};
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
const findEval = (record, slug) => record.evals.find((e) => e.slug === slug);
|
|
719
|
+
const findAttempt = (e, attemptId) => e.attempts.find((a) => a.id === attemptId);
|
|
720
|
+
/** The attempt to open by default: the first failure, else the first attempt. */
|
|
721
|
+
const defaultAttempt = (e) => e.attempts.find((a) => a.outcome === "fail" || a.outcome === "infra_error") ?? e.attempts[0];
|
|
722
|
+
//#endregion
|
|
723
|
+
export { cellSamples as A, formatSeconds as B, splitEvalId as C, VERDICT_TEXT as D, EFFORT_ALPHA as E, effortSample as F, whyChanged as G, compare as H, effortSummary as I, effortVerdict as L, effortChangeLabel as M, effortLabel as N, cellContrast as O, effortOf as P, formatCount as R, shortSha as S, transcriptItems as T, sourceChanges as U, cellsChanged as V, stackChanges as W, isMatrix as _, compareView as a, runOutcome as b, evalTree as c, formatDuration as d, formatPercent as f, indexFiles as g, hasUngatedCells as h, cellRows as i, contrastEffort as j, cellEffort as k, findAttempt as l, harnessLabel as m, activityLabel as n, defaultAttempt as o, formatUsage as p, buildIndex as r, evalHistory as s, COMPARE_ORDER as t, findEval as u, isUngatedMiss as v, splitPatch as w, runRows as x, parseTranscript as y, formatRatio as z };
|
|
724
|
+
|
|
725
|
+
//# sourceMappingURL=model-CDL98xue.js.map
|