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,827 @@
|
|
|
1
|
+
import { t as UsageError } from "./errors-CSxfJ4qu.js";
|
|
2
|
+
import { t as BUILD_ID, u as ownRequire } from "./root-CFDjpWuc.js";
|
|
3
|
+
import { t as RUN_ID } from "./paths-CBvm6pap.js";
|
|
4
|
+
import { c as relativeFile, l as sha256 } from "./ids-Bp6ut7ZD.js";
|
|
5
|
+
import { n as isRuntimeState } from "./context-loi9OCZv.js";
|
|
6
|
+
import { P as effortOf, j as contrastEffort } from "./model-CDL98xue.js";
|
|
7
|
+
import { o as round3, s as wilson, t as liftInterval } from "./stats-BNAHlNMD.js";
|
|
8
|
+
import { f as scratchDir, l as recordNames, o as readRecord, p as warnUnreadable } from "./files-D2-qn_Dl.js";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { mkdir, readFile, stat } from "node:fs/promises";
|
|
11
|
+
/** A failing verdict this close to its threshold points at the judge, not the agent. */
|
|
12
|
+
const JUDGE_MARGIN = .1;
|
|
13
|
+
const DERIVED_TABLES = `
|
|
14
|
+
CREATE TABLE effort_m (
|
|
15
|
+
run_id TEXT, eval_key TEXT, cell_key TEXT, role TEXT, variant TEXT, harness TEXT,
|
|
16
|
+
environment TEXT, n INTEGER, tools_median REAL, tools_q1 REAL, tools_q3 REAL,
|
|
17
|
+
agent_s_median REAL, agent_s_q1 REAL, agent_s_q3 REAL, file_edits_median REAL,
|
|
18
|
+
control_key TEXT, control_n INTEGER, control_tools_median REAL, control_agent_s_median REAL,
|
|
19
|
+
tools_ratio REAL, agent_s_ratio REAL, agent_s_delta REAL, tools_p REAL, agent_s_p REAL,
|
|
20
|
+
separated INTEGER, verdict TEXT, time_verdict TEXT
|
|
21
|
+
);
|
|
22
|
+
CREATE TABLE factor_effects_m (
|
|
23
|
+
factor TEXT, level TEXT, baseline TEXT, context TEXT, tier TEXT, run_id TEXT, eval_key TEXT,
|
|
24
|
+
strata INTEGER, runs INTEGER, variant_pass INTEGER, variant_counted INTEGER,
|
|
25
|
+
control_pass INTEGER, control_counted INTEGER, points REAL, lo REAL, hi REAL,
|
|
26
|
+
within_noise INTEGER, pass_verdict TEXT, tools_ratio REAL, tools_ratios TEXT,
|
|
27
|
+
agent_s_ratio REAL, tools_p REAL, consistent INTEGER, separated INTEGER, effort_verdict TEXT,
|
|
28
|
+
note TEXT
|
|
29
|
+
);
|
|
30
|
+
CREATE TABLE flaky_m (
|
|
31
|
+
eval_key TEXT, cell_key TEXT, fingerprint TEXT, runs INTEGER, flaky_runs INTEGER,
|
|
32
|
+
first_seen TEXT, last_seen TEXT, pass INTEGER, counted INTEGER, infra_error INTEGER,
|
|
33
|
+
rate REAL, lo REAL, hi REAL, verdict TEXT, cause TEXT, min_attempts INTEGER
|
|
34
|
+
);
|
|
35
|
+
CREATE TABLE drift_m (
|
|
36
|
+
run_id TEXT, prev_run_id TEXT, eval_key TEXT, cell_key TEXT, changed TEXT,
|
|
37
|
+
changed_paths TEXT, noise INTEGER, real_changed TEXT
|
|
38
|
+
);
|
|
39
|
+
`;
|
|
40
|
+
const DERIVED = [
|
|
41
|
+
"effort_m",
|
|
42
|
+
"factor_effects_m",
|
|
43
|
+
"flaky_m",
|
|
44
|
+
"drift_m"
|
|
45
|
+
];
|
|
46
|
+
const PARTS = [
|
|
47
|
+
["sources", "fp_sources"],
|
|
48
|
+
["harness", "fp_harness"],
|
|
49
|
+
["environment", "fp_environment"],
|
|
50
|
+
["image", "fp_image"],
|
|
51
|
+
["agent_cli", "fp_agent_cli"],
|
|
52
|
+
["judge", "fp_judge"],
|
|
53
|
+
["context", "fp_context"]
|
|
54
|
+
];
|
|
55
|
+
const r4 = (x) => x === null ? null : Math.round(x * 1e4) / 1e4;
|
|
56
|
+
const s1 = (ms) => Math.round(ms / 100) / 10;
|
|
57
|
+
const cellId = (run, evalKey, cell) => `${run}\0${evalKey}\0${cell}`;
|
|
58
|
+
function passCols(v, c) {
|
|
59
|
+
const lift = liftInterval({
|
|
60
|
+
pass: v.pass,
|
|
61
|
+
n: v.counted
|
|
62
|
+
}, {
|
|
63
|
+
pass: c.pass,
|
|
64
|
+
n: c.counted
|
|
65
|
+
});
|
|
66
|
+
const noise = lift.low <= 0 && lift.high >= 0;
|
|
67
|
+
return {
|
|
68
|
+
variant_pass: v.pass,
|
|
69
|
+
variant_counted: v.counted,
|
|
70
|
+
control_pass: c.pass,
|
|
71
|
+
control_counted: c.counted,
|
|
72
|
+
points: round3(lift.points),
|
|
73
|
+
lo: round3(lift.low),
|
|
74
|
+
hi: round3(lift.high),
|
|
75
|
+
within_noise: noise ? 1 : 0,
|
|
76
|
+
pass_verdict: v.counted && c.counted ? noise ? "noise" : "effect" : "insufficient"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function effortCols(e) {
|
|
80
|
+
return {
|
|
81
|
+
tools_ratio: r4(e?.toolsRatio ?? null),
|
|
82
|
+
tools_ratios: e?.toolsRatio === null || !e ? null : `×${e.toolsRatio.toFixed(2)}`,
|
|
83
|
+
agent_s_ratio: r4(e?.agentMsRatio ?? null),
|
|
84
|
+
tools_p: r4(e?.toolsP ?? null),
|
|
85
|
+
consistent: e && (e.verdict === "fewer" || e.verdict === "more") ? 1 : 0,
|
|
86
|
+
separated: e?.separated ? 1 : 0,
|
|
87
|
+
effort_verdict: e?.verdict ?? null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Across strata: an effect when ≥ 2/3 of them show it in the same direction at p < 0.05. */
|
|
91
|
+
function pooledEffort(strata) {
|
|
92
|
+
const known = strata.filter((s) => s !== null);
|
|
93
|
+
const k = strata.length;
|
|
94
|
+
const count = (v) => known.filter((s) => s.verdict === v).length;
|
|
95
|
+
const needed = Math.ceil(2 * k / 3);
|
|
96
|
+
let verdict = "noise";
|
|
97
|
+
if (count("fewer") >= needed && count("more") === 0) verdict = "fewer";
|
|
98
|
+
else if (count("more") >= needed && count("fewer") === 0) verdict = "more";
|
|
99
|
+
else if (known.length && count("insufficient") === known.length) verdict = "insufficient";
|
|
100
|
+
const sorted = [...known.map((s) => s.toolsRatio).filter((r) => r !== null)].sort((a, b) => a - b);
|
|
101
|
+
const mid = sorted.length ? sorted.length % 2 ? sorted[(sorted.length - 1) / 2] : (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : null;
|
|
102
|
+
return {
|
|
103
|
+
tools_ratio: r4(mid),
|
|
104
|
+
tools_ratios: known.map((s) => s.toolsRatio === null ? "×–" : `×${s.toolsRatio.toFixed(2)}`).join(" / "),
|
|
105
|
+
agent_s_ratio: null,
|
|
106
|
+
tools_p: r4(known.length ? Math.max(...known.map((s) => s.toolsP ?? 1)) : null),
|
|
107
|
+
consistent: verdict === "fewer" || verdict === "more" ? count(verdict) : 0,
|
|
108
|
+
separated: known.filter((s) => s.separated).length,
|
|
109
|
+
effort_verdict: verdict
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const merge = (sides) => ({
|
|
113
|
+
pass: sides.reduce((n, s) => n + s.pass, 0),
|
|
114
|
+
counted: sides.reduce((n, s) => n + s.counted, 0),
|
|
115
|
+
samples: sides.flatMap((s) => s.samples)
|
|
116
|
+
});
|
|
117
|
+
function insertAll(db, table, rows) {
|
|
118
|
+
if (rows.length === 0) return;
|
|
119
|
+
const cols = db.prepare(`PRAGMA table_info("${table}")`).all().map((c) => c.name);
|
|
120
|
+
const statement = db.prepare(`INSERT INTO ${table} (${cols.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`);
|
|
121
|
+
for (const row of rows) statement.run(...cols.map((c) => row[c] ?? null));
|
|
122
|
+
}
|
|
123
|
+
/** Recomputes every derived table from the projected rows; run inside the sync's transaction. */
|
|
124
|
+
function deriveFacts(db) {
|
|
125
|
+
for (const table of DERIVED) db.exec(`DELETE FROM ${table}`);
|
|
126
|
+
const cells = db.prepare("SELECT c.*, r.started_at FROM cells c JOIN runs r USING (run_id) ORDER BY c.run_id, c.rowid").all();
|
|
127
|
+
const attempts = db.prepare("SELECT run_id, eval_key, cell_key, key, outcome, error_kind, tools, agent_ms, file_edits FROM attempts ORDER BY run_id, rowid").all();
|
|
128
|
+
const nearThreshold = new Set(db.prepare(`SELECT DISTINCT run_id, attempt_key FROM judgements WHERE verdict = 'fail' AND ABS(score - threshold) <= ${JUDGE_MARGIN}`).all().map((r) => `${r.run_id}\0${r.attempt_key}`));
|
|
129
|
+
const samples = /* @__PURE__ */ new Map();
|
|
130
|
+
const byCell = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const a of attempts) {
|
|
132
|
+
const id = cellId(a.run_id, a.eval_key, a.cell_key);
|
|
133
|
+
byCell.set(id, [...byCell.get(id) ?? [], a]);
|
|
134
|
+
if (a.outcome !== "pass" && a.outcome !== "fail") continue;
|
|
135
|
+
samples.set(id, [...samples.get(id) ?? [], {
|
|
136
|
+
tools: a.tools,
|
|
137
|
+
agentMs: a.agent_ms,
|
|
138
|
+
fileEdits: a.file_edits
|
|
139
|
+
}]);
|
|
140
|
+
}
|
|
141
|
+
const side = (c) => ({
|
|
142
|
+
pass: c.pass,
|
|
143
|
+
counted: c.pass + c.fail,
|
|
144
|
+
samples: samples.get(cellId(c.run_id, c.eval_key, c.key)) ?? []
|
|
145
|
+
});
|
|
146
|
+
const find = (run, evalKey, key) => cells.find((c) => c.run_id === run && c.eval_key === evalKey && c.key === key);
|
|
147
|
+
insertAll(db, "effort_m", cells.map((c) => {
|
|
148
|
+
const own = effortOf(side(c).samples);
|
|
149
|
+
const control = c.lift_vs ? find(c.run_id, c.eval_key, c.lift_vs) : void 0;
|
|
150
|
+
const vs = control ? contrastEffort(side(c).samples, side(control).samples) : null;
|
|
151
|
+
return {
|
|
152
|
+
run_id: c.run_id,
|
|
153
|
+
eval_key: c.eval_key,
|
|
154
|
+
cell_key: c.key,
|
|
155
|
+
role: c.role,
|
|
156
|
+
variant: c.variant,
|
|
157
|
+
harness: c.harness,
|
|
158
|
+
environment: c.environment,
|
|
159
|
+
n: own?.n ?? 0,
|
|
160
|
+
tools_median: own?.tools.median ?? null,
|
|
161
|
+
tools_q1: own?.tools.q1 ?? null,
|
|
162
|
+
tools_q3: own?.tools.q3 ?? null,
|
|
163
|
+
agent_s_median: own ? s1(own.agentMs.median) : null,
|
|
164
|
+
agent_s_q1: own ? s1(own.agentMs.q1) : null,
|
|
165
|
+
agent_s_q3: own ? s1(own.agentMs.q3) : null,
|
|
166
|
+
file_edits_median: own?.fileEdits ?? null,
|
|
167
|
+
control_key: control?.key ?? null,
|
|
168
|
+
control_n: vs?.control.n ?? null,
|
|
169
|
+
control_tools_median: vs?.control.tools.median ?? null,
|
|
170
|
+
control_agent_s_median: vs ? s1(vs.control.agentMs.median) : null,
|
|
171
|
+
tools_ratio: r4(vs?.toolsRatio ?? null),
|
|
172
|
+
agent_s_ratio: r4(vs?.agentMsRatio ?? null),
|
|
173
|
+
agent_s_delta: vs ? s1(vs.agentMsDelta) : null,
|
|
174
|
+
tools_p: r4(vs?.toolsP ?? null),
|
|
175
|
+
agent_s_p: r4(vs?.agentMsP ?? null),
|
|
176
|
+
separated: vs ? vs.separated ? 1 : 0 : null,
|
|
177
|
+
verdict: vs?.verdict ?? null,
|
|
178
|
+
time_verdict: vs?.timeVerdict ?? null
|
|
179
|
+
};
|
|
180
|
+
}));
|
|
181
|
+
const pairs = [];
|
|
182
|
+
const groups = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const c of cells) {
|
|
184
|
+
const id = `${c.run_id}\0${c.eval_key}`;
|
|
185
|
+
groups.set(id, [...groups.get(id) ?? [], c]);
|
|
186
|
+
}
|
|
187
|
+
for (const group of groups.values()) {
|
|
188
|
+
for (const v of group) {
|
|
189
|
+
const control = v.lift_vs ? group.find((c) => c.key === v.lift_vs) : void 0;
|
|
190
|
+
if (!control) continue;
|
|
191
|
+
pairs.push({
|
|
192
|
+
factor: "environment",
|
|
193
|
+
level: v.variant ?? v.environment,
|
|
194
|
+
baseline: "control",
|
|
195
|
+
context: v.harness,
|
|
196
|
+
run: v.run_id,
|
|
197
|
+
evalKey: v.eval_key,
|
|
198
|
+
pool: `${v.comparable}\0${control.comparable}`,
|
|
199
|
+
v: side(v),
|
|
200
|
+
c: side(control)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const bySetup = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const c of group) {
|
|
205
|
+
const id = `${c.variant ?? c.role}\0${c.sandbox}`;
|
|
206
|
+
bySetup.set(id, [...bySetup.get(id) ?? [], c]);
|
|
207
|
+
}
|
|
208
|
+
for (const same of bySetup.values()) {
|
|
209
|
+
const base = same[0];
|
|
210
|
+
for (const other of same.slice(1)) {
|
|
211
|
+
if (other.harness === base.harness) continue;
|
|
212
|
+
pairs.push({
|
|
213
|
+
factor: other.harness_kind === base.harness_kind ? "model" : "harness",
|
|
214
|
+
level: other.harness,
|
|
215
|
+
baseline: base.harness,
|
|
216
|
+
context: other.variant ?? "control",
|
|
217
|
+
run: other.run_id,
|
|
218
|
+
evalKey: other.eval_key,
|
|
219
|
+
pool: `${other.comparable}\0${base.comparable}`,
|
|
220
|
+
v: side(other),
|
|
221
|
+
c: side(base)
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const factorRows = [];
|
|
227
|
+
const head = (p) => ({
|
|
228
|
+
factor: p.factor,
|
|
229
|
+
level: p.level,
|
|
230
|
+
baseline: p.baseline,
|
|
231
|
+
context: p.context
|
|
232
|
+
});
|
|
233
|
+
for (const p of pairs) factorRows.push({
|
|
234
|
+
...head(p),
|
|
235
|
+
tier: "controlled",
|
|
236
|
+
run_id: p.run,
|
|
237
|
+
eval_key: p.evalKey,
|
|
238
|
+
strata: 1,
|
|
239
|
+
runs: 1,
|
|
240
|
+
...passCols(p.v, p.c),
|
|
241
|
+
...effortCols(contrastEffort(p.v.samples, p.c.samples)),
|
|
242
|
+
note: null
|
|
243
|
+
});
|
|
244
|
+
const pooled = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const p of pairs) {
|
|
246
|
+
const id = [
|
|
247
|
+
p.factor,
|
|
248
|
+
p.level,
|
|
249
|
+
p.baseline,
|
|
250
|
+
p.context,
|
|
251
|
+
p.evalKey,
|
|
252
|
+
p.pool
|
|
253
|
+
].join("\0");
|
|
254
|
+
pooled.set(id, [...pooled.get(id) ?? [], p]);
|
|
255
|
+
}
|
|
256
|
+
const strata = /* @__PURE__ */ new Map();
|
|
257
|
+
for (const group of pooled.values()) {
|
|
258
|
+
const first = group[0];
|
|
259
|
+
const v = merge(group.map((p) => p.v));
|
|
260
|
+
const c = merge(group.map((p) => p.c));
|
|
261
|
+
if (group.length > 1) factorRows.push({
|
|
262
|
+
...head(first),
|
|
263
|
+
tier: "pooled",
|
|
264
|
+
run_id: null,
|
|
265
|
+
eval_key: first.evalKey,
|
|
266
|
+
strata: 1,
|
|
267
|
+
runs: group.length,
|
|
268
|
+
...passCols(v, c),
|
|
269
|
+
...effortCols(contrastEffort(v.samples, c.samples)),
|
|
270
|
+
note: null
|
|
271
|
+
});
|
|
272
|
+
const id = [
|
|
273
|
+
first.factor,
|
|
274
|
+
first.level,
|
|
275
|
+
first.baseline,
|
|
276
|
+
first.context
|
|
277
|
+
].join("\0");
|
|
278
|
+
strata.set(id, [...strata.get(id) ?? [], {
|
|
279
|
+
v,
|
|
280
|
+
c
|
|
281
|
+
}]);
|
|
282
|
+
}
|
|
283
|
+
for (const [id, list] of strata) {
|
|
284
|
+
if (list.length < 2) continue;
|
|
285
|
+
const [factor, level, baseline, context] = id.split("\0");
|
|
286
|
+
const v = merge(list.map((s) => s.v));
|
|
287
|
+
const c = merge(list.map((s) => s.c));
|
|
288
|
+
factorRows.push({
|
|
289
|
+
...head({
|
|
290
|
+
factor,
|
|
291
|
+
level,
|
|
292
|
+
baseline,
|
|
293
|
+
context
|
|
294
|
+
}),
|
|
295
|
+
tier: "pooled",
|
|
296
|
+
run_id: null,
|
|
297
|
+
eval_key: null,
|
|
298
|
+
strata: list.length,
|
|
299
|
+
runs: null,
|
|
300
|
+
...passCols(v, c),
|
|
301
|
+
...pooledEffort(list.map((s) => contrastEffort(s.v.samples, s.c.samples))),
|
|
302
|
+
note: "pass rate: summed counts; effort: an effect in at least 2/3 of strata"
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const driftRows = [];
|
|
306
|
+
const series = /* @__PURE__ */ new Map();
|
|
307
|
+
for (const c of cells) {
|
|
308
|
+
const id = `${c.eval_key}\0${c.key}`;
|
|
309
|
+
series.set(id, [...series.get(id) ?? [], c]);
|
|
310
|
+
}
|
|
311
|
+
const contextOf = db.prepare("SELECT path, sha256 FROM attempt_context WHERE run_id = ? AND attempt_key = ?");
|
|
312
|
+
const cellContext = (c) => {
|
|
313
|
+
const files = /* @__PURE__ */ new Map();
|
|
314
|
+
for (const a of byCell.get(cellId(c.run_id, c.eval_key, c.key)) ?? []) for (const f of contextOf.all(c.run_id, a.key)) files.set(f.path, f.sha256);
|
|
315
|
+
return files;
|
|
316
|
+
};
|
|
317
|
+
for (const list of series.values()) {
|
|
318
|
+
list.sort((a, b) => a.run_id < b.run_id ? -1 : 1);
|
|
319
|
+
for (let i = 1; i < list.length; i++) {
|
|
320
|
+
const prev = list[i - 1];
|
|
321
|
+
const cur = list[i];
|
|
322
|
+
if (prev.fingerprint === cur.fingerprint) continue;
|
|
323
|
+
const changed = PARTS.filter(([, col]) => prev[col] !== cur[col]).map(([name]) => name);
|
|
324
|
+
let paths = [];
|
|
325
|
+
if (changed.includes("context")) {
|
|
326
|
+
const a = cellContext(prev);
|
|
327
|
+
const b = cellContext(cur);
|
|
328
|
+
paths = [.../* @__PURE__ */ new Set([...a.keys(), ...b.keys()])].filter((p) => a.get(p) !== b.get(p)).sort();
|
|
329
|
+
}
|
|
330
|
+
const contextNoise = paths.length > 0 && paths.every(isRuntimeState);
|
|
331
|
+
const real = changed.filter((part) => part !== "context" || !contextNoise);
|
|
332
|
+
driftRows.push({
|
|
333
|
+
run_id: cur.run_id,
|
|
334
|
+
prev_run_id: prev.run_id,
|
|
335
|
+
eval_key: cur.eval_key,
|
|
336
|
+
cell_key: cur.key,
|
|
337
|
+
changed: changed.join(" "),
|
|
338
|
+
changed_paths: paths.length ? paths.join(", ") : null,
|
|
339
|
+
noise: real.length === 0 ? 1 : 0,
|
|
340
|
+
real_changed: real.join(" ") || null
|
|
341
|
+
});
|
|
342
|
+
if (real.length === 0) continue;
|
|
343
|
+
const judgeOnly = real.length === 1 && real[0] === "judge";
|
|
344
|
+
factorRows.push({
|
|
345
|
+
factor: real.length === 1 ? real[0] : "confounded",
|
|
346
|
+
level: cur.run_id,
|
|
347
|
+
baseline: prev.run_id,
|
|
348
|
+
context: `${cur.eval_key} # ${cur.key}`,
|
|
349
|
+
tier: "observed",
|
|
350
|
+
run_id: cur.run_id,
|
|
351
|
+
eval_key: cur.eval_key,
|
|
352
|
+
strata: 1,
|
|
353
|
+
runs: 2,
|
|
354
|
+
...passCols(side(cur), side(prev)),
|
|
355
|
+
...effortCols(contrastEffort(side(cur).samples, side(prev).samples)),
|
|
356
|
+
note: judgeOnly ? "the judge changed: a measurement change, not an effect of the thing under test" : real.length > 1 ? `confounded: ${real.join(", ")} changed together` : null
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
insertAll(db, "drift_m", driftRows);
|
|
361
|
+
const levels = (pick) => [...new Set(cells.map(pick))];
|
|
362
|
+
const single = [
|
|
363
|
+
["harness", levels((c) => c.harness_kind)],
|
|
364
|
+
["model", levels((c) => `${c.harness_kind}:${c.model ?? "-"}`)],
|
|
365
|
+
["sandbox", levels((c) => c.sandbox)]
|
|
366
|
+
];
|
|
367
|
+
if (cells.length && !cells.some((c) => c.role === "variant")) single.push(["environment", levels((c) => c.environment)]);
|
|
368
|
+
for (const [factor, values] of single) {
|
|
369
|
+
if (values.length !== 1) continue;
|
|
370
|
+
factorRows.push({
|
|
371
|
+
factor,
|
|
372
|
+
level: values[0],
|
|
373
|
+
baseline: null,
|
|
374
|
+
context: null,
|
|
375
|
+
tier: "not_measured",
|
|
376
|
+
note: "one level in every run, so its effect is not measured"
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
insertAll(db, "factor_effects_m", factorRows);
|
|
380
|
+
const flaky = /* @__PURE__ */ new Map();
|
|
381
|
+
for (const c of cells) {
|
|
382
|
+
const id = `${c.eval_key}\0${c.key}\0${c.comparable}`;
|
|
383
|
+
flaky.set(id, [...flaky.get(id) ?? [], c]);
|
|
384
|
+
}
|
|
385
|
+
const flakyRows = [];
|
|
386
|
+
for (const list of flaky.values()) {
|
|
387
|
+
const pass = list.reduce((n, c) => n + c.pass, 0);
|
|
388
|
+
const fail = list.reduce((n, c) => n + c.fail, 0);
|
|
389
|
+
if (!(pass > 0 && fail > 0)) continue;
|
|
390
|
+
const first = list[0];
|
|
391
|
+
const infra = list.reduce((n, c) => n + c.infra_error, 0);
|
|
392
|
+
const failing = list.flatMap((c) => (byCell.get(cellId(c.run_id, c.eval_key, c.key)) ?? []).filter((a) => a.outcome === "fail"));
|
|
393
|
+
const w = wilson(pass, pass + fail);
|
|
394
|
+
const cause = infra > 0 || failing.some((a) => a.error_kind === "infra") ? "infra" : failing.some((a) => nearThreshold.has(`${a.run_id}\0${a.key}`)) ? "judge" : "agent";
|
|
395
|
+
const starts = list.map((c) => c.started_at).sort();
|
|
396
|
+
flakyRows.push({
|
|
397
|
+
eval_key: first.eval_key,
|
|
398
|
+
cell_key: first.key,
|
|
399
|
+
fingerprint: first.comparable,
|
|
400
|
+
runs: list.length,
|
|
401
|
+
flaky_runs: list.filter((c) => c.pass > 0 && c.fail > 0).length,
|
|
402
|
+
first_seen: starts[0] ?? null,
|
|
403
|
+
last_seen: starts.at(-1) ?? null,
|
|
404
|
+
pass,
|
|
405
|
+
counted: pass + fail,
|
|
406
|
+
infra_error: infra,
|
|
407
|
+
rate: round3(pass / (pass + fail)),
|
|
408
|
+
lo: round3(w.low),
|
|
409
|
+
hi: round3(w.high),
|
|
410
|
+
verdict: pass + fail >= 6 ? "flaky" : "insufficient",
|
|
411
|
+
cause,
|
|
412
|
+
min_attempts: 6
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
flakyRows.sort((a, b) => String(a.first_seen).localeCompare(String(b.first_seen)));
|
|
416
|
+
insertAll(db, "flaky_m", flakyRows);
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/history/db.ts
|
|
420
|
+
/** Bump when the tables or views change: a database from another version is rebuilt. */
|
|
421
|
+
const DB_VERSION = 2;
|
|
422
|
+
/** This build of reeval, like the header cache: another reeval re-derives every row. */
|
|
423
|
+
const BUILD = BUILD_ID;
|
|
424
|
+
const historyDbPath = (root) => join(root, ".reeval", "history.db");
|
|
425
|
+
/** `node:sqlite` is in Node from 22.13 (package `engines`); before 24 it warns on load. */
|
|
426
|
+
const SQLITE_NODE = "22.13.0";
|
|
427
|
+
function nodeAtLeast(version, min = SQLITE_NODE) {
|
|
428
|
+
const [a = 0, b = 0, c = 0] = version.replace(/^v/, "").split(".").map(Number);
|
|
429
|
+
const [x = 0, y = 0, z = 0] = min.split(".").map(Number);
|
|
430
|
+
return a !== x ? a > x : b !== y ? b > y : c >= z;
|
|
431
|
+
}
|
|
432
|
+
function loadSqlite() {
|
|
433
|
+
if (!nodeAtLeast(process.versions.node)) throw new UsageError(`reeval query needs Node ${SQLITE_NODE} or later for node:sqlite (this is Node ${process.versions.node}).`);
|
|
434
|
+
const emit = process.emitWarning;
|
|
435
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
436
|
+
const text = typeof warning === "string" ? warning : warning.message;
|
|
437
|
+
if (/SQLite/.test(text)) return;
|
|
438
|
+
return emit.call(process, warning, ...rest);
|
|
439
|
+
});
|
|
440
|
+
try {
|
|
441
|
+
return ownRequire("node:sqlite");
|
|
442
|
+
} finally {
|
|
443
|
+
process.emitWarning = emit;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const TABLES = `
|
|
447
|
+
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
448
|
+
-- The record file each run was read from, and its size and mtime when it was.
|
|
449
|
+
CREATE TABLE files (run_id TEXT PRIMARY KEY, path TEXT NOT NULL, size INTEGER NOT NULL, mtime_ms REAL NOT NULL);
|
|
450
|
+
CREATE TABLE runs (
|
|
451
|
+
run_id TEXT PRIMARY KEY, recorded INTEGER NOT NULL, status TEXT, started_at TEXT, ended_at TEXT,
|
|
452
|
+
duration_ms INTEGER, author TEXT, git_sha TEXT, branch TEXT, dirty INTEGER, runner TEXT,
|
|
453
|
+
ci_provider TEXT, ci_url TEXT, pr_number INTEGER, pr_base TEXT, es TEXT, judge_model TEXT,
|
|
454
|
+
judge_version TEXT, profile TEXT, evals INTEGER, attempts INTEGER, pass INTEGER, fail INTEGER,
|
|
455
|
+
not_gated INTEGER, infra_error INTEGER, skipped INTEGER
|
|
456
|
+
);
|
|
457
|
+
CREATE TABLE evals (
|
|
458
|
+
run_id TEXT NOT NULL, key TEXT NOT NULL, id TEXT, slug TEXT, file TEXT, groups TEXT, name TEXT,
|
|
459
|
+
tags TEXT, fingerprint TEXT, outcome TEXT, pass_rate REAL, sources TEXT,
|
|
460
|
+
PRIMARY KEY (run_id, key)
|
|
461
|
+
);
|
|
462
|
+
CREATE TABLE cells (
|
|
463
|
+
run_id TEXT NOT NULL, eval_key TEXT NOT NULL, key TEXT NOT NULL, role TEXT, variant TEXT,
|
|
464
|
+
harness TEXT, harness_kind TEXT, model TEXT, environment TEXT, environment_digest TEXT,
|
|
465
|
+
sandbox TEXT, gate TEXT, gate_pass_rate REAL, fingerprint TEXT, fp_sources TEXT,
|
|
466
|
+
fp_harness TEXT, fp_environment TEXT, fp_image TEXT, fp_agent_cli TEXT, fp_judge TEXT,
|
|
467
|
+
fp_context TEXT, runs INTEGER, pass INTEGER, fail INTEGER, infra_error INTEGER,
|
|
468
|
+
skipped INTEGER, pass_rate REAL, outcome TEXT, lift_vs TEXT, lift_points REAL, lift_lo REAL,
|
|
469
|
+
lift_hi REAL, within_noise INTEGER, min_lift REAL, comparable TEXT,
|
|
470
|
+
PRIMARY KEY (run_id, eval_key, key)
|
|
471
|
+
);
|
|
472
|
+
CREATE TABLE attempts (
|
|
473
|
+
run_id TEXT NOT NULL, eval_key TEXT NOT NULL, cell_key TEXT NOT NULL, key TEXT NOT NULL,
|
|
474
|
+
id TEXT, n INTEGER, seq INTEGER, started_at TEXT, outcome TEXT, error_kind TEXT,
|
|
475
|
+
error_message TEXT, duration_ms INTEGER, image_id TEXT, turns INTEGER, tools INTEGER,
|
|
476
|
+
file_edits INTEGER, setup_ms INTEGER, agent_ms INTEGER, checks_ms INTEGER, judge_ms INTEGER,
|
|
477
|
+
last_text TEXT, input_tokens INTEGER, output_tokens INTEGER, cache_read_tokens INTEGER,
|
|
478
|
+
cache_write_tokens INTEGER, reasoning_tokens INTEGER, served_model TEXT,
|
|
479
|
+
PRIMARY KEY (run_id, key)
|
|
480
|
+
);
|
|
481
|
+
CREATE TABLE judgements (
|
|
482
|
+
run_id TEXT NOT NULL, attempt_key TEXT NOT NULL, key TEXT NOT NULL, question TEXT,
|
|
483
|
+
verdict TEXT, score REAL, threshold REAL, model TEXT, resolved_model TEXT, input_path TEXT,
|
|
484
|
+
PRIMARY KEY (run_id, attempt_key, key)
|
|
485
|
+
);
|
|
486
|
+
CREATE TABLE environments (
|
|
487
|
+
run_id TEXT NOT NULL, name TEXT NOT NULL, digest TEXT NOT NULL, parent TEXT, overrides TEXT,
|
|
488
|
+
PRIMARY KEY (run_id, digest)
|
|
489
|
+
);
|
|
490
|
+
CREATE TABLE env_skills (
|
|
491
|
+
run_id TEXT NOT NULL, digest TEXT NOT NULL, name TEXT NOT NULL, source TEXT, commit_sha TEXT,
|
|
492
|
+
sha256 TEXT, placement TEXT
|
|
493
|
+
);
|
|
494
|
+
CREATE TABLE artifacts (
|
|
495
|
+
run_id TEXT NOT NULL, attempt_key TEXT NOT NULL, kind TEXT NOT NULL, path TEXT NOT NULL,
|
|
496
|
+
sha256 TEXT, bytes INTEGER
|
|
497
|
+
);
|
|
498
|
+
-- What the agent could see, by content hash; runtime = 1 for an agent CLI's own session state.
|
|
499
|
+
CREATE TABLE attempt_context (
|
|
500
|
+
run_id TEXT NOT NULL, attempt_key TEXT NOT NULL, path TEXT NOT NULL, sha256 TEXT, runtime INTEGER
|
|
501
|
+
);
|
|
502
|
+
-- Every tool call in each attempt's transcript: its line in transcript.jsonl (from 1) and target.
|
|
503
|
+
CREATE TABLE tool_calls (
|
|
504
|
+
run_id TEXT NOT NULL, attempt_key TEXT NOT NULL, line INTEGER NOT NULL, turn INTEGER,
|
|
505
|
+
tool TEXT, target TEXT, ok INTEGER, duration_ms INTEGER
|
|
506
|
+
);
|
|
507
|
+
${DERIVED_TABLES}
|
|
508
|
+
CREATE INDEX cells_by_eval ON cells (eval_key, key);
|
|
509
|
+
CREATE INDEX tool_calls_by_attempt ON tool_calls (run_id, attempt_key);
|
|
510
|
+
CREATE INDEX context_by_attempt ON attempt_context (run_id, attempt_key);
|
|
511
|
+
CREATE INDEX attempts_by_cell ON attempts (run_id, eval_key, cell_key);
|
|
512
|
+
CREATE INDEX env_skills_by_digest ON env_skills (run_id, digest);
|
|
513
|
+
`;
|
|
514
|
+
/** The canned questions of docs/history-observability-review.md §2, as views to filter. */
|
|
515
|
+
const VIEWS = `
|
|
516
|
+
-- Variant against control, per harness, per run.
|
|
517
|
+
CREATE VIEW lift AS
|
|
518
|
+
SELECT c.run_id, r.started_at, r.branch, c.eval_key, c.harness, c.model, c.variant,
|
|
519
|
+
c.lift_vs AS control, c.pass AS variant_pass, c.pass + c.fail AS variant_counted,
|
|
520
|
+
k.pass AS control_pass, k.pass + k.fail AS control_counted, c.lift_points AS points,
|
|
521
|
+
c.lift_lo AS lo, c.lift_hi AS hi, c.within_noise, c.min_lift, c.outcome, c.fingerprint
|
|
522
|
+
FROM cells c JOIN runs r USING (run_id)
|
|
523
|
+
JOIN cells k ON k.run_id = c.run_id AND k.eval_key = c.eval_key AND k.key = c.lift_vs
|
|
524
|
+
WHERE c.role = 'variant';
|
|
525
|
+
|
|
526
|
+
-- Cells with mixed outcomes, pooled over runs whose fingerprints match once agent runtime state is
|
|
527
|
+
-- left out; 'flaky' needs FLAKY_MIN_ATTEMPTS counted attempts, else 'insufficient'.
|
|
528
|
+
CREATE VIEW flaky_cells AS SELECT * FROM flaky_m;
|
|
529
|
+
|
|
530
|
+
-- Each run where a cell's fingerprint differs from its previous run: which parts, which context
|
|
531
|
+
-- paths, and noise = 1 when only agent runtime state changed (not a real change).
|
|
532
|
+
CREATE VIEW drift AS SELECT * FROM drift_m;
|
|
533
|
+
|
|
534
|
+
-- Tool calls and agent time per cell, and a variant's against its control in the same run.
|
|
535
|
+
CREATE VIEW effort AS SELECT * FROM effort_m;
|
|
536
|
+
|
|
537
|
+
-- What a factor (environment, harness, model) did, by evidence tier: controlled (one run),
|
|
538
|
+
-- pooled (comparable runs, and across evals), observed (over time), not_measured (one level).
|
|
539
|
+
CREATE VIEW factor_effects AS SELECT * FROM factor_effects_m;
|
|
540
|
+
|
|
541
|
+
-- Whether each attempt read each skill its environment placed, and at which transcript line.
|
|
542
|
+
CREATE VIEW skill_reads AS
|
|
543
|
+
SELECT a.run_id, a.eval_key, a.cell_key, a.key AS attempt_key, a.id AS attempt_id, c.variant,
|
|
544
|
+
s.name AS skill, s.placement,
|
|
545
|
+
(SELECT MIN(t.line) FROM tool_calls t WHERE t.run_id = a.run_id AND t.attempt_key = a.key
|
|
546
|
+
AND t.target LIKE '%skills/' || s.name || '/%') AS read_line,
|
|
547
|
+
(SELECT COUNT(*) FROM tool_calls t WHERE t.run_id = a.run_id AND t.attempt_key = a.key
|
|
548
|
+
AND t.target LIKE '%skills/' || s.name || '/%') AS reads
|
|
549
|
+
FROM attempts a
|
|
550
|
+
JOIN cells c ON c.run_id = a.run_id AND c.eval_key = a.eval_key AND c.key = a.cell_key
|
|
551
|
+
JOIN env_skills s ON s.run_id = c.run_id AND s.digest = c.environment_digest;
|
|
552
|
+
|
|
553
|
+
-- Failed attempts with the cell's harness, model and each of its environment's skills.
|
|
554
|
+
CREATE VIEW failing_attempts AS
|
|
555
|
+
SELECT a.run_id, r.started_at, a.eval_key, a.key AS attempt_key, a.cell_key, c.harness,
|
|
556
|
+
c.model, c.environment, s.name AS skill, a.error_kind, a.error_message, a.duration_ms
|
|
557
|
+
FROM attempts a JOIN runs r USING (run_id)
|
|
558
|
+
JOIN cells c ON c.run_id = a.run_id AND c.eval_key = a.eval_key AND c.key = a.cell_key
|
|
559
|
+
LEFT JOIN env_skills s ON s.run_id = c.run_id AND s.digest = c.environment_digest
|
|
560
|
+
WHERE a.outcome = 'fail';
|
|
561
|
+
|
|
562
|
+
-- One eval per run: outcome and pass rate, with the branch and pull request.
|
|
563
|
+
CREATE VIEW eval_trend AS
|
|
564
|
+
SELECT e.eval_key, r.run_id, r.started_at, r.branch, r.pr_number, r.recorded, e.outcome,
|
|
565
|
+
e.pass_rate, e.fingerprint
|
|
566
|
+
FROM (SELECT key AS eval_key, * FROM evals) e JOIN runs r USING (run_id);
|
|
567
|
+
|
|
568
|
+
-- Each judge question's pass rate per cell per run, where it differs from that cell's last run.
|
|
569
|
+
CREATE VIEW verdict_flips AS
|
|
570
|
+
WITH rates AS (
|
|
571
|
+
SELECT j.run_id, a.eval_key, a.cell_key, j.key AS judgement_key, j.question,
|
|
572
|
+
AVG(j.verdict = 'pass') AS pass_rate, COUNT(*) AS n
|
|
573
|
+
FROM judgements j JOIN attempts a ON a.run_id = j.run_id AND a.key = j.attempt_key
|
|
574
|
+
GROUP BY j.run_id, a.eval_key, a.cell_key, j.key
|
|
575
|
+
), ordered AS (
|
|
576
|
+
SELECT *, LAG(pass_rate) OVER w AS prev_pass_rate, LAG(run_id) OVER w AS prev_run_id
|
|
577
|
+
FROM rates WINDOW w AS (PARTITION BY eval_key, cell_key, judgement_key ORDER BY run_id)
|
|
578
|
+
)
|
|
579
|
+
SELECT run_id, prev_run_id, eval_key, cell_key, judgement_key, question, prev_pass_rate,
|
|
580
|
+
pass_rate, n
|
|
581
|
+
FROM ordered WHERE prev_run_id IS NOT NULL AND pass_rate IS NOT prev_pass_rate;
|
|
582
|
+
`;
|
|
583
|
+
const VIEW_NAMES = [
|
|
584
|
+
"lift",
|
|
585
|
+
"effort",
|
|
586
|
+
"factor_effects",
|
|
587
|
+
"skill_reads",
|
|
588
|
+
"flaky_cells",
|
|
589
|
+
"drift",
|
|
590
|
+
"failing_attempts",
|
|
591
|
+
"eval_trend",
|
|
592
|
+
"verdict_flips"
|
|
593
|
+
];
|
|
594
|
+
const json = (value) => JSON.stringify(value);
|
|
595
|
+
/** `attempts.last_text` is the agent's final message, capped: most "why" questions start there. */
|
|
596
|
+
const LAST_TEXT_LIMIT = 2048;
|
|
597
|
+
/** A tool call's target: its path, pattern, command or URL, else its input, capped. */
|
|
598
|
+
function toolTarget(input) {
|
|
599
|
+
if (typeof input !== "object" || input === null) return String(input ?? "").slice(0, 300);
|
|
600
|
+
const i = input;
|
|
601
|
+
for (const key of [
|
|
602
|
+
"path",
|
|
603
|
+
"file_path",
|
|
604
|
+
"filePath",
|
|
605
|
+
"pattern",
|
|
606
|
+
"glob_pattern",
|
|
607
|
+
"command",
|
|
608
|
+
"url",
|
|
609
|
+
"query"
|
|
610
|
+
]) if (typeof i[key] === "string") return i[key].slice(0, 300);
|
|
611
|
+
return JSON.stringify(input).slice(0, 300);
|
|
612
|
+
}
|
|
613
|
+
/** The tool calls in a transcript.jsonl, numbered by line from 1; lines that aren't JSON are skipped. */
|
|
614
|
+
function toolCalls(transcript) {
|
|
615
|
+
const calls = [];
|
|
616
|
+
transcript.split("\n").forEach((line, i) => {
|
|
617
|
+
if (!line.trim()) return;
|
|
618
|
+
let event;
|
|
619
|
+
try {
|
|
620
|
+
event = JSON.parse(line);
|
|
621
|
+
} catch {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (event?.type !== "tool") return;
|
|
625
|
+
calls.push({
|
|
626
|
+
line: i + 1,
|
|
627
|
+
turn: typeof event.turn === "number" ? event.turn : null,
|
|
628
|
+
tool: String(event.tool ?? ""),
|
|
629
|
+
target: toolTarget(event.input),
|
|
630
|
+
ok: event.ok !== false,
|
|
631
|
+
durationMs: typeof event.durationMs === "number" ? event.durationMs : null
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
return calls;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* What a cell's results can be pooled on across runs: its fingerprint parts, with the context
|
|
638
|
+
* part replaced by the context files that aren't an agent CLI's own runtime state.
|
|
639
|
+
*/
|
|
640
|
+
function comparableFingerprint(cell, attempts) {
|
|
641
|
+
const { context: _context, ...parts } = cell.fingerprintParts;
|
|
642
|
+
const files = /* @__PURE__ */ new Map();
|
|
643
|
+
for (const a of attempts) {
|
|
644
|
+
if (a.cell !== cell.key) continue;
|
|
645
|
+
for (const c of a.context) if (!isRuntimeState(c.path)) files.set(c.path, c.sha256);
|
|
646
|
+
}
|
|
647
|
+
return sha256(json({
|
|
648
|
+
parts,
|
|
649
|
+
context: [...files].sort(([a], [b]) => a < b ? -1 : 1)
|
|
650
|
+
}));
|
|
651
|
+
}
|
|
652
|
+
function insertRun(db, record, recorded, transcripts) {
|
|
653
|
+
const run = (sql, ...values) => db.prepare(sql).run(...values);
|
|
654
|
+
const { runner, summary, git } = record;
|
|
655
|
+
run(`INSERT INTO runs VALUES (${Array(26).fill("?").join(", ")})`, record.runId, recorded ? 1 : 0, record.status, record.startedAt, record.endedAt, record.durationMs, record.author, git?.sha ?? null, git?.branch ?? null, git ? git.dirty ? 1 : 0 : null, runner.kind, runner.kind === "ci" ? runner.provider : null, runner.kind === "ci" ? runner.runUrl : null, runner.kind === "ci" ? runner.pr?.number ?? null : null, runner.kind === "ci" ? runner.pr?.baseRef ?? null : null, record.stack.es, record.stack.judge.model, record.stack.judge.version, record.profile?.name ?? null, summary.evals, summary.attempts, summary.pass, summary.fail, summary.notGated, summary.infraError, summary.skipped);
|
|
656
|
+
for (const env of record.environments) {
|
|
657
|
+
run("INSERT OR IGNORE INTO environments VALUES (?, ?, ?, ?, ?)", record.runId, env.name, env.digest, env.parent, json(env.overrides));
|
|
658
|
+
for (const s of env.skills) run("INSERT INTO env_skills VALUES (?, ?, ?, ?, ?, ?, ?)", record.runId, env.digest, s.name, s.source, s.commit, s.sha256, s.placement);
|
|
659
|
+
}
|
|
660
|
+
for (const e of record.evals) {
|
|
661
|
+
run(`INSERT INTO evals VALUES (${Array(12).fill("?").join(", ")})`, record.runId, e.key, e.id, e.slug, e.file, json(e.group), e.name, json(e.tags), e.fingerprint, e.outcome, e.passRate, json(e.sources));
|
|
662
|
+
for (const c of e.cells) {
|
|
663
|
+
const p = c.fingerprintParts;
|
|
664
|
+
run(`INSERT INTO cells VALUES (${Array(35).fill("?").join(", ")})`, record.runId, e.key, c.key, c.role, c.variant ?? null, c.harness, c.harnessKind, c.model, c.environment, c.environmentDigest, c.sandbox, typeof c.gate === "string" ? c.gate : "passRate", typeof c.gate === "string" ? null : c.gate.passRate, c.fingerprint, p.sources, p.harness, p.environment, p.image, p.agentCli, p.judge, p.context, c.runs, c.pass, c.fail, c.infraError, c.skipped, c.passRate, c.outcome, c.lift?.vs ?? null, c.lift?.points ?? null, c.lift?.interval.lo ?? null, c.lift?.interval.hi ?? null, c.lift ? c.lift.withinNoise ? 1 : 0 : null, c.lift?.minLift ?? null, comparableFingerprint(c, e.attempts));
|
|
665
|
+
}
|
|
666
|
+
for (const a of e.attempts) {
|
|
667
|
+
run(`INSERT INTO attempts VALUES (${Array(27).fill("?").join(", ")})`, record.runId, e.key, a.cell, a.key, a.id, a.n, a.seq, a.startedAt, a.outcome, a.error?.kind ?? null, a.error?.message ?? null, a.durationMs, a.imageId, a.turns.length, a.activity.tools, a.activity.fileEdits, a.timings.setupMs, a.timings.agentMs, a.timings.checksMs, a.timings.judgeMs, a.turns.at(-1)?.text.slice(0, 2048) ?? null, a.usage?.input ?? null, a.usage?.output ?? null, a.usage?.cacheRead ?? null, a.usage?.cacheWrite ?? null, a.usage?.reasoning ?? null, a.turns.findLast((t) => t.model)?.model ?? null);
|
|
668
|
+
for (const c of a.context) run("INSERT INTO attempt_context VALUES (?, ?, ?, ?, ?)", record.runId, a.key, c.path, c.sha256, isRuntimeState(c.path) ? 1 : 0);
|
|
669
|
+
const transcript = transcripts.get(a.key);
|
|
670
|
+
for (const t of transcript ? toolCalls(transcript) : []) run("INSERT INTO tool_calls VALUES (?, ?, ?, ?, ?, ?, ?, ?)", record.runId, a.key, t.line, t.turn, t.tool, t.target, t.ok ? 1 : 0, t.durationMs);
|
|
671
|
+
for (const j of a.judgements) run("INSERT OR IGNORE INTO judgements VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", record.runId, a.key, j.key, j.question, j.verdict, j.score, j.threshold, j.model, j.resolvedModel ?? null, a.files.judgeInput?.path ?? null);
|
|
672
|
+
for (const [kind, ref] of Object.entries(a.files)) {
|
|
673
|
+
if (typeof ref?.path !== "string") continue;
|
|
674
|
+
run("INSERT INTO artifacts VALUES (?, ?, ?, ?, ?, ?)", record.runId, a.key, kind, ref.path, ref.sha256, ref.bytes);
|
|
675
|
+
}
|
|
676
|
+
a.checks.forEach((c, i) => {
|
|
677
|
+
if (!c.log) return;
|
|
678
|
+
run("INSERT INTO artifacts VALUES (?, ?, ?, ?, ?, ?)", record.runId, a.key, `check:${i + 1}`, c.log.path, c.log.sha256, c.log.bytes);
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const DATA_TABLES = [
|
|
684
|
+
"runs",
|
|
685
|
+
"evals",
|
|
686
|
+
"cells",
|
|
687
|
+
"attempts",
|
|
688
|
+
"judgements",
|
|
689
|
+
"environments",
|
|
690
|
+
"env_skills",
|
|
691
|
+
"artifacts",
|
|
692
|
+
"attempt_context",
|
|
693
|
+
"tool_calls"
|
|
694
|
+
];
|
|
695
|
+
function removeRun(db, runId) {
|
|
696
|
+
for (const table of DATA_TABLES) db.prepare(`DELETE FROM ${table} WHERE run_id = ?`).run(runId);
|
|
697
|
+
db.prepare("DELETE FROM files WHERE run_id = ?").run(runId);
|
|
698
|
+
}
|
|
699
|
+
function freshSchema(db) {
|
|
700
|
+
const objects = db.prepare("SELECT type, name FROM sqlite_schema WHERE type IN ('table', 'view')").all();
|
|
701
|
+
for (const o of objects) {
|
|
702
|
+
if (o.name.startsWith("sqlite_")) continue;
|
|
703
|
+
db.exec(`DROP ${o.type === "view" ? "VIEW" : "TABLE"} IF EXISTS "${o.name}"`);
|
|
704
|
+
}
|
|
705
|
+
db.exec(TABLES);
|
|
706
|
+
db.exec(VIEWS);
|
|
707
|
+
db.prepare("INSERT INTO meta VALUES (?, ?), (?, ?)").run("version", String(DB_VERSION), "build", String(BUILD));
|
|
708
|
+
}
|
|
709
|
+
function current(db) {
|
|
710
|
+
try {
|
|
711
|
+
const rows = db.prepare("SELECT key, value FROM meta").all();
|
|
712
|
+
const meta = Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
|
713
|
+
return meta.version === String(DB_VERSION) && meta.build === String(BUILD);
|
|
714
|
+
} catch {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/** Each attempt's transcript.jsonl, by attempt key, where the run kept it. */
|
|
719
|
+
async function readTranscripts(dir, record) {
|
|
720
|
+
const out = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const e of record.evals) for (const a of e.attempts) {
|
|
722
|
+
const ref = a.files.transcript;
|
|
723
|
+
if (!ref) continue;
|
|
724
|
+
const text = await readFile(join(dir, record.runId, ...ref.path.split("/")), "utf8").catch(() => null);
|
|
725
|
+
if (text !== null) out.set(a.key, text);
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Brings the projection up to date with the record files: recorded runs in `historyDir` win over
|
|
731
|
+
* the same run's scratch copy in `.reeval/runs`. Only new, changed or removed files are touched.
|
|
732
|
+
*/
|
|
733
|
+
async function sync(db, root, historyDir) {
|
|
734
|
+
const rebuilt = !current(db);
|
|
735
|
+
if (rebuilt) freshSchema(db);
|
|
736
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
737
|
+
for (const [dir, recorded] of [[historyDir, true], [scratchDir(root), false]]) for (const name of await recordNames(dir) ?? []) {
|
|
738
|
+
const runId = name.slice(0, -5);
|
|
739
|
+
if (!RUN_ID.test(runId) || wanted.has(runId)) continue;
|
|
740
|
+
wanted.set(runId, {
|
|
741
|
+
path: join(dir, name),
|
|
742
|
+
recorded
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
const known = new Map(db.prepare("SELECT run_id, path, size, mtime_ms FROM files").all().map((r) => [r.run_id, r]));
|
|
746
|
+
let added = 0;
|
|
747
|
+
let removed = 0;
|
|
748
|
+
db.exec("BEGIN");
|
|
749
|
+
try {
|
|
750
|
+
for (const runId of known.keys()) if (!wanted.has(runId)) {
|
|
751
|
+
removeRun(db, runId);
|
|
752
|
+
removed++;
|
|
753
|
+
}
|
|
754
|
+
for (const [runId, { path, recorded }] of wanted) {
|
|
755
|
+
const info = await stat(path);
|
|
756
|
+
const rel = relativeFile(root, path);
|
|
757
|
+
const had = known.get(runId);
|
|
758
|
+
if (had && had.path === rel && had.size === info.size && had.mtime_ms === info.mtimeMs) continue;
|
|
759
|
+
let record;
|
|
760
|
+
try {
|
|
761
|
+
record = await readRecord(path);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
warnUnreadable(path, error);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (had) removeRun(db, runId);
|
|
767
|
+
insertRun(db, record, recorded, await readTranscripts(dirname(path), record));
|
|
768
|
+
db.prepare("INSERT INTO files VALUES (?, ?, ?, ?)").run(runId, rel, info.size, info.mtimeMs);
|
|
769
|
+
added++;
|
|
770
|
+
}
|
|
771
|
+
if (added || removed || rebuilt) deriveFacts(db);
|
|
772
|
+
db.exec("COMMIT");
|
|
773
|
+
} catch (error) {
|
|
774
|
+
db.exec("ROLLBACK");
|
|
775
|
+
throw error;
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
runs: wanted.size,
|
|
779
|
+
added,
|
|
780
|
+
removed,
|
|
781
|
+
rebuilt
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
/** Opens `.reeval/history.db`, synced with the records; statements after this are read-only. */
|
|
785
|
+
async function openHistoryDb(root, historyDir, options = {}) {
|
|
786
|
+
const { DatabaseSync } = loadSqlite();
|
|
787
|
+
const path = historyDbPath(root);
|
|
788
|
+
await mkdir(dirname(path), { recursive: true });
|
|
789
|
+
const writer = new DatabaseSync(path);
|
|
790
|
+
let synced;
|
|
791
|
+
try {
|
|
792
|
+
writer.exec("PRAGMA busy_timeout = 5000");
|
|
793
|
+
if (options.rebuild) writer.exec("DROP TABLE IF EXISTS meta");
|
|
794
|
+
synced = await sync(writer, root, historyDir);
|
|
795
|
+
} finally {
|
|
796
|
+
writer.close();
|
|
797
|
+
}
|
|
798
|
+
const db = new DatabaseSync(path, { readOnly: true });
|
|
799
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
800
|
+
return {
|
|
801
|
+
sync: synced,
|
|
802
|
+
query(sql, params = []) {
|
|
803
|
+
try {
|
|
804
|
+
const statement = db.prepare(sql);
|
|
805
|
+
const rows = statement.all(...params);
|
|
806
|
+
return {
|
|
807
|
+
columns: typeof statement.columns === "function" ? statement.columns().map((c) => c.name) : Object.keys(rows[0] ?? {}),
|
|
808
|
+
rows
|
|
809
|
+
};
|
|
810
|
+
} catch (error) {
|
|
811
|
+
const message = error.message;
|
|
812
|
+
throw new UsageError(/readonly|read-only/i.test(message) ? `reeval query is read-only: .reeval/history.db is a projection of the run records (${message})` : `reeval query: ${message}`);
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
schema() {
|
|
816
|
+
return db.prepare(`SELECT name, type FROM sqlite_schema WHERE type IN ('table', 'view') AND name NOT IN ('meta', 'files', ${DERIVED.map((d) => `'${d}'`).join(", ")}) AND name NOT LIKE 'sqlite_%' ORDER BY type, name`).all().map((o) => ({
|
|
817
|
+
...o,
|
|
818
|
+
columns: db.prepare(`PRAGMA table_info("${o.name}")`).all().map((c) => c.name)
|
|
819
|
+
}));
|
|
820
|
+
},
|
|
821
|
+
close: () => db.close()
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
//#endregion
|
|
825
|
+
export { historyDbPath as a, toolCalls as c, comparableFingerprint as i, SQLITE_NODE as n, nodeAtLeast as o, VIEW_NAMES as r, openHistoryDb as s, LAST_TEXT_LIMIT as t };
|
|
826
|
+
|
|
827
|
+
//# sourceMappingURL=db-DxDzMhtB.js.map
|