@tangle-network/agent-bench 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Deterministic self-check for the driver-agnostic browser harness — run with
3
+ * `tsx src/browser/verify.ts` (bench convention: a verify script, not a live run).
4
+ *
5
+ * The regression it defends: the verdict is ATTESTABLE — derived from the run's
6
+ * final observable state, NEVER the driver's self-reported success. A driver can
7
+ * claim success=true; if the deterministic criteria fail, resolved MUST be false.
8
+ */
9
+
10
+ import { judgeBrowserRun, type BrowserTask } from './agent-adapter'
11
+ import { badReportToRun } from './adapters/bad'
12
+
13
+ let failures = 0
14
+ function check(name: string, cond: boolean, detail = '') {
15
+ if (cond) {
16
+ console.log(` ✓ ${name}`)
17
+ } else {
18
+ failures += 1
19
+ console.log(` ✗ ${name} ${detail}`)
20
+ }
21
+ }
22
+
23
+ // A bad report.json shaped exactly like the real one, with the OpenSCAD page reached.
24
+ const successReport = {
25
+ results: [
26
+ {
27
+ testCase: { id: 'wiki-openscad' },
28
+ agentResult: {
29
+ success: true,
30
+ result: "The 'OpenSCAD' article is open on Wikipedia.",
31
+ turns: [
32
+ { turn: 1, state: { url: 'https://en.wikipedia.org/wiki/Main_Page', snapshot: 'search box' }, action: { action: 'type', text: 'OpenSCAD' } },
33
+ { turn: 2, state: { url: 'https://en.wikipedia.org/wiki/Main_Page' }, action: { action: 'click', selector: '@result' } },
34
+ { turn: 3, state: { url: 'https://en.wikipedia.org/wiki/OpenSCAD', snapshot: 'OpenSCAD - Wikipedia. Constructive Solid Geometry modeller.' }, action: { action: 'complete' } },
35
+ ],
36
+ },
37
+ },
38
+ ],
39
+ }
40
+
41
+ // The SAME driver claiming success, but the browser never left Main_Page.
42
+ const liarReport = {
43
+ results: [
44
+ {
45
+ testCase: { id: 'wiki-openscad' },
46
+ agentResult: {
47
+ success: true, // self-reported — must be IGNORED
48
+ result: 'Done!',
49
+ turns: [{ turn: 1, state: { url: 'https://en.wikipedia.org/wiki/Main_Page', snapshot: 'main page' }, action: { action: 'complete' } }],
50
+ },
51
+ },
52
+ ],
53
+ }
54
+
55
+ const task: BrowserTask = {
56
+ id: 'wiki-openscad',
57
+ goal: 'Open the OpenSCAD article',
58
+ startUrl: 'https://en.wikipedia.org',
59
+ success: [{ type: 'url-contains', value: 'OpenSCAD' }],
60
+ }
61
+
62
+ console.log('browser harness verify:')
63
+
64
+ const okRun = badReportToRun(successReport, 'wiki-openscad')
65
+ check('mapper extracts final url from last turn', okRun.finalUrl === 'https://en.wikipedia.org/wiki/OpenSCAD', okRun.finalUrl)
66
+ check('mapper records all steps', okRun.steps.length === 3)
67
+ check('mapper carries selfReportedSuccess', okRun.selfReportedSuccess === true)
68
+ const okV = judgeBrowserRun(task, okRun)
69
+ check('genuine success attests resolved=true score=1', okV.resolved === true && okV.score === 1, okV.detail)
70
+
71
+ const liarRun = badReportToRun(liarReport, 'wiki-openscad')
72
+ const liarV = judgeBrowserRun(task, liarRun)
73
+ check('SELF-REPORTED success is IGNORED — failed criteria → resolved=false', liarV.resolved === false && liarV.score === 0, liarV.detail)
74
+
75
+ // Partial credit + dom check: 2 criteria, only url passes (snapshot lacks the phrase).
76
+ const twoCrit: BrowserTask = {
77
+ ...task,
78
+ success: [{ type: 'url-contains', value: 'OpenSCAD' }, { type: 'dom-contains', value: 'Marching Cubes' }],
79
+ }
80
+ const partialV = judgeBrowserRun(twoCrit, okRun)
81
+ check('partial credit: 1 of 2 criteria → score=0.5 resolved=false', partialV.score === 0.5 && partialV.resolved === false, partialV.detail)
82
+
83
+ // Fail loud: no criteria is a harness error, not a silent pass.
84
+ let threw = false
85
+ try {
86
+ judgeBrowserRun({ ...task, success: [] }, okRun)
87
+ } catch {
88
+ threw = true
89
+ }
90
+ check('empty success spec throws (fail-loud, never silent-attest)', threw)
91
+
92
+ if (failures > 0) {
93
+ console.log(`\n❌ ${failures} check(s) failed`)
94
+ process.exit(1)
95
+ }
96
+ console.log('\n✅ browser harness verified — verdict is attestable, self-report ignored')
@@ -0,0 +1,314 @@
1
+ /**
2
+ * CL-Bench (Continual) Codebase Adaptation — verifier-grounded selector gate.
3
+ *
4
+ * Of CL-Bench's six domains, codebase_adaptation is the ONLY deployable checker:
5
+ * its scorer applies the instance's provided `test_patch` and runs pytest in the
6
+ * instance's Docker image, keying off the exit code (the rest grade against realized
7
+ * outcomes the agent never has = oracles). That makes it the clean analogue of the
8
+ * HumanEval gate — an INDEPENDENT deployable check (tests ≠ the answer), unlike the
9
+ * CL-bench Context gate where the rubric judge IS the metric.
10
+ *
11
+ * Each instance is SWE-bench format (repo @ base_commit + a GitHub issue). The worker
12
+ * is a real sandbox rollout (opencode clones the repo, fixes the source, writes a diff
13
+ * to a file we read back — a large diff truncates in the chat stream). We score each
14
+ * candidate patch with CL-Bench's OWN scorer via `clbench_codebase_judge.py` (run in
15
+ * CL-Bench's venv) — verified to self-check (gold patch passes, empty fails). Two
16
+ * paired arms over the same instances:
17
+ * random@K — K identical-issue rollouts (the compute control)
18
+ * diverse@K — K rollouts, the i-th with a strategy lens prepended (composeStrategies)
19
+ * verifierGroundedSelect picks by pytest-pass; we report blind / random@k / diverse@k /
20
+ * oracle@k with paired-bootstrap CIs, and write a corpus RunRecord/task the existing
21
+ * `corpus-replay --selector=verifier` + `corpus-report` consume unchanged. Fail loud:
22
+ * an infra-errored rollout is excluded (infraError), never scored 0.
23
+ *
24
+ * dotenvx run -f … -- env N=4 K=3 WORKER_MODEL=deepseek-chat CONCURRENCY=2 \
25
+ * CLBENCH_DIR=/tmp/clbench-continual CORPUS=/tmp/clbench-codebase.jsonl \
26
+ * tsx src/clbench-codebase-gate.mts
27
+ */
28
+
29
+ import { execFile } from 'node:child_process'
30
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
31
+ import { tmpdir } from 'node:os'
32
+ import { join } from 'node:path'
33
+ import { promisify } from 'node:util'
34
+ import { acquireSandbox } from '@tangle-network/agent-runtime/loops'
35
+ import { Sandbox } from '@tangle-network/sandbox'
36
+ import { composeStrategies } from './directives'
37
+ import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
38
+ import { verifierGroundedSelect } from './selector'
39
+ import { type PairedLift, pairedLift, pool } from './stats.mts'
40
+
41
+ const execFileAsync = promisify(execFile)
42
+ const PATCH_PATH = '/tmp/solution.patch'
43
+ const randomSuffix = () => Math.random().toString(36).slice(2, 10)
44
+
45
+ function must(name: string): string {
46
+ const v = process.env[name]
47
+ if (!v) throw new Error(`env ${name} is required`)
48
+ return v
49
+ }
50
+
51
+ interface Instance {
52
+ instanceId: string
53
+ repo: string
54
+ baseCommit: string
55
+ problemStatement: string
56
+ }
57
+
58
+ /** Load CL-Bench codebase_adaptation instances (SWE-bench format) from the cloned
59
+ * repo's final-dataset.jsonl. Fail loud on a malformed/short record. */
60
+ function loadInstances(clbenchDir: string, limit: number, offset: number): Instance[] {
61
+ const path = join(clbenchDir, 'data/codebase_adaptation/final-dataset.jsonl')
62
+ const text = readFileSync(path, 'utf8')
63
+ const out: Instance[] = []
64
+ for (const line of text.split('\n')) {
65
+ if (line.trim() === '') continue
66
+ const d = JSON.parse(line) as { instance_id?: string; repo?: string; base_commit?: string; problem_statement?: string }
67
+ if (!d.instance_id || !d.repo || !d.base_commit || !d.problem_statement) {
68
+ throw new Error(`malformed codebase_adaptation record: ${line.slice(0, 120)}`)
69
+ }
70
+ out.push({ instanceId: d.instance_id, repo: d.repo, baseCommit: d.base_commit, problemStatement: d.problem_statement })
71
+ }
72
+ if (out.length === 0) throw new Error('codebase_adaptation parsed to 0 instances')
73
+ if (offset >= out.length) throw new Error(`OFFSET ${offset} >= dataset size ${out.length}`)
74
+ return out.slice(offset, offset + limit)
75
+ }
76
+
77
+ function rolloutPrompt(inst: Instance, lens: string | undefined): string {
78
+ return [
79
+ lens ? `${lens}\n` : '',
80
+ `Clone https://github.com/${inst.repo} into /work, then \`cd /work && git checkout ${inst.baseCommit}\`.`,
81
+ '',
82
+ 'Resolve this GitHub issue by editing the SOURCE only (never the tests — the evaluation re-runs its own hidden tests on a fresh clone):',
83
+ '',
84
+ inst.problemStatement,
85
+ '',
86
+ 'Work iteratively: install the package editable (`pip install -e .`), reproduce the issue, implement the fix, and re-run the existing tests until they pass.',
87
+ `When done, from /work run EXACTLY:`,
88
+ ` git add -A && git diff --cached -- . ':(exclude)*/test*' > ${PATCH_PATH}`,
89
+ `Then stop. The patch file is the only deliverable — do NOT paste the diff in your reply.`,
90
+ ].filter((s) => s !== '').join('\n')
91
+ }
92
+
93
+ interface ShotCfg {
94
+ sandboxBaseUrl: string
95
+ routerBaseUrl: string
96
+ routerKey: string
97
+ model: string
98
+ /** in-box opencode provider. `openai-compat` (default) is the generic passthrough —
99
+ * it does NOT validate the model against opencode's registry, so router-served cheap
100
+ * models (deepseek-chat, moonshotai/kimi-k2.6, glm) work; `openai`/`anthropic` only
101
+ * accept their registered model names (e.g. gpt-4.1). */
102
+ provider: string
103
+ timeoutMs: number
104
+ }
105
+
106
+ interface Shot {
107
+ patch: string
108
+ /** rollout completed and produced a (possibly empty) patch; false ⇒ infra error (excluded). */
109
+ ran: boolean
110
+ detail?: string
111
+ }
112
+
113
+ /** One fault-isolated sandbox rollout → a patch (read from the box FS). ANY rollout
114
+ * error becomes a recorded infra failure (ran=false), never a throw that kills the pool. */
115
+ async function runRollout(inst: Instance, lens: string | undefined, cfg: ShotCfg): Promise<Shot> {
116
+ const client = new Sandbox({ baseUrl: cfg.sandboxBaseUrl, apiKey: cfg.routerKey })
117
+ let box: Awaited<ReturnType<typeof acquireSandbox>> | undefined
118
+ try {
119
+ box = await acquireSandbox(client, {
120
+ name: `clbench-cb-${inst.instanceId}-${randomSuffix()}`.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 60),
121
+ environment: 'universal',
122
+ // backend.model pins provider/model/baseUrl only — in-box model auth is the
123
+ // box-provisioned OPENCODE_MODEL_API_KEY (foreign keys are 403'd at egress).
124
+ backend: { type: 'opencode', model: { provider: cfg.provider, model: cfg.model, baseUrl: cfg.routerBaseUrl } },
125
+ })
126
+ const signal = cfg.timeoutMs > 0 ? AbortSignal.timeout(cfg.timeoutMs) : undefined
127
+ for await (const _ev of box.streamPrompt(rolloutPrompt(inst, lens), signal ? { signal } : {})) {
128
+ // drain; the deliverable is the patch FILE, not the stream
129
+ }
130
+ let patch = ''
131
+ try {
132
+ patch = await box.fs.read(PATCH_PATH)
133
+ } catch {
134
+ patch = '' // missing patch file ⇒ the agent produced nothing (a real empty, ran=true)
135
+ }
136
+ return { patch, ran: true }
137
+ } catch (err) {
138
+ return { patch: '', ran: false, detail: `rollout error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}` }
139
+ } finally {
140
+ try {
141
+ if (box) await box.delete()
142
+ } catch {
143
+ // staging reaps on expiry
144
+ }
145
+ }
146
+ }
147
+
148
+ /** Score one candidate patch with CL-Bench's deployable pytest checker via the bridge
149
+ * (run in CL-Bench's venv, cwd = its repo root so `src.tasks...` resolves). Returns
150
+ * pass (0/1) or null on an infra/judge failure (excluded, never scored 0). */
151
+ async function judgePatch(inst: Instance, patch: string, clbenchDir: string): Promise<number | null> {
152
+ if (patch.trim() === '') return 0 // empty patch is a legitimate fail, not an infra error
153
+ const dir = mkdtempSync(join(tmpdir(), 'clbench-cb-'))
154
+ const patchFile = join(dir, 'candidate.patch')
155
+ writeFileSync(patchFile, patch)
156
+ try {
157
+ const { stdout } = await execFileAsync(
158
+ join(clbenchDir, '.venv/bin/python'),
159
+ [
160
+ join(process.cwd(), 'scripts/clbench_codebase_judge.py'),
161
+ '--dataset',
162
+ join(clbenchDir, 'data/codebase_adaptation/final-dataset.jsonl'),
163
+ '--instance-id',
164
+ inst.instanceId,
165
+ '--patch-file',
166
+ patchFile,
167
+ ],
168
+ { cwd: clbenchDir, maxBuffer: 8 * 1024 * 1024, timeout: 600_000 },
169
+ )
170
+ const last = stdout.trim().split('\n').at(-1) ?? '{}'
171
+ const verdict = JSON.parse(last) as { success?: boolean }
172
+ return verdict.success ? 1 : 0
173
+ } catch (err) {
174
+ console.error(` judge infra error ${inst.instanceId}: ${(err instanceof Error ? err.message : String(err)).slice(0, 160)}`)
175
+ return null
176
+ } finally {
177
+ rmSync(dir, { recursive: true, force: true })
178
+ }
179
+ }
180
+
181
+ const pct = (x: number) => `${(x * 100).toFixed(1)}%`
182
+ const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
183
+
184
+ async function main(): Promise<void> {
185
+ const n = Number(process.env.N ?? 4)
186
+ const k = Number(process.env.K ?? 3)
187
+ const offset = Number(process.env.OFFSET ?? 0)
188
+ const model = process.env.WORKER_MODEL ?? 'deepseek-chat'
189
+ // openai-compat = generic passthrough so cheap router models resolve in-box (see ShotCfg).
190
+ const provider = process.env.WORKER_PROVIDER ?? 'openai-compat'
191
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
192
+ const routerKey = must('TANGLE_API_KEY')
193
+ const sandboxBaseUrl = process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools'
194
+ const clbenchDir = process.env.CLBENCH_DIR ?? '/tmp/clbench-continual'
195
+ const rolloutConc = Number(process.env.CONCURRENCY ?? 3)
196
+ const judgeConc = Number(process.env.JUDGE_CONCURRENCY ?? 2)
197
+ const timeoutMs = process.env.SHOT_TIMEOUT_MS ? Number(process.env.SHOT_TIMEOUT_MS) : 900_000
198
+ const corpusPath = process.env.CORPUS ?? '/tmp/clbench-codebase.jsonl'
199
+ if (!Number.isInteger(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
200
+ if (!Number.isInteger(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
201
+
202
+ const cfg: ShotCfg = { sandboxBaseUrl, routerBaseUrl, routerKey, model, provider, timeoutMs }
203
+ console.log(`=== CL-Bench Codebase Adaptation selector gate · N=${n} K=${k} offset=${offset} model=${provider}/${model} ===`)
204
+ console.log(` sandbox=${sandboxBaseUrl} judge=CL-Bench pytest-in-Docker (deployable) clbench=${clbenchDir}`)
205
+
206
+ const instances = loadInstances(clbenchDir, n, offset)
207
+ console.log(`loaded ${instances.length} instance(s): ${instances.map((i) => i.instanceId).join(', ')}`)
208
+
209
+ type Unit = { instIdx: number; arm: 'random' | 'diverse'; shot: number; lens: string | undefined }
210
+ const units: Unit[] = []
211
+ for (let ii = 0; ii < instances.length; ii += 1) {
212
+ const lenses = composeStrategies('', k) // lens prefixes only ('' base ⇒ "<lens>\n\n")
213
+ for (let s = 0; s < k; s += 1) {
214
+ units.push({ instIdx: ii, arm: 'random', shot: s, lens: undefined })
215
+ units.push({ instIdx: ii, arm: 'diverse', shot: s, lens: (lenses[s] as string).trim() })
216
+ }
217
+ }
218
+ console.log(`\n▶ phase 1: ${units.length} rollouts (${instances.length}×${k}×2 arms) via sandbox, conc=${rolloutConc}`)
219
+ const shots = await pool(units, rolloutConc, async (u) => {
220
+ const inst = instances[u.instIdx] as Instance
221
+ const s = await runRollout(inst, u.lens, cfg)
222
+ console.log(` rollout ${inst.instanceId} ${u.arm}#${u.shot}: ${s.ran ? `patch ${s.patch.length}B` : `INFRA (${s.detail})`}`)
223
+ return s
224
+ })
225
+
226
+ console.log(`\n▶ phase 2: judging ${shots.length} patches with the deployable checker, conc=${judgeConc}`)
227
+ const passes = await pool(units, judgeConc, async (u, i) => {
228
+ const shot = shots[i] as Shot
229
+ if (!shot.ran) return null // infra error ⇒ excluded
230
+ const inst = instances[u.instIdx] as Instance
231
+ const p = await judgePatch(inst, shot.patch, clbenchDir)
232
+ console.log(` judge ${inst.instanceId} ${u.arm}#${u.shot}: ${p === null ? 'INFRA' : p ? 'PASS' : 'fail'}`)
233
+ return p
234
+ })
235
+
236
+ // Regroup; an attempt with a null pass (infra) is dropped from its arm.
237
+ const byInst = instances.map(() => ({ random: [] as (number | null)[], diverse: [] as (number | null)[], rPatch: [] as string[], dPatch: [] as string[] }))
238
+ units.forEach((u, i) => {
239
+ const grp = byInst[u.instIdx] as { random: (number | null)[]; diverse: (number | null)[]; rPatch: string[]; dPatch: string[] }
240
+ const pass = passes[i] as number | null
241
+ const patch = (shots[i] as Shot).patch
242
+ if (u.arm === 'random') { grp.random[u.shot] = pass; grp.rPatch[u.shot] = patch } else { grp.diverse[u.shot] = pass; grp.dPatch[u.shot] = patch }
243
+ })
244
+
245
+ // Per-instance {0,1} outcomes; instances with no valid attempt in BOTH arms are excluded.
246
+ const blind: number[] = []
247
+ const randomAtK: number[] = []
248
+ const diverseAtK: number[] = []
249
+ const oracleAtK: number[] = []
250
+ let excluded = 0
251
+ for (const grp of byInst) {
252
+ const rValid = grp.random.filter((p): p is number => p !== null && p !== undefined)
253
+ const dValid = grp.diverse.filter((p): p is number => p !== null && p !== undefined)
254
+ if (rValid.length === 0 || dValid.length === 0) { excluded += 1; continue }
255
+ blind.push(rValid[0] as number)
256
+ randomAtK.push(rValid[verifierGroundedSelect(rValid)] as number)
257
+ diverseAtK.push(dValid[verifierGroundedSelect(dValid)] as number)
258
+ oracleAtK.push(dValid.some((p) => p > 0) ? 1 : 0)
259
+ }
260
+ const rate = (xs: number[]) => (xs.length === 0 ? 0 : xs.reduce((s, x) => s + x, 0) / xs.length)
261
+
262
+ console.log(`\n${'='.repeat(78)}`)
263
+ console.log(`RESULTS · CL-Bench Codebase Adaptation · n=${blind.length} scored (${excluded} excluded) · k=${k} · model=${model}`)
264
+ console.log('='.repeat(78))
265
+ console.log(` blind pass@1 ${pct(rate(blind))}`)
266
+ console.log(` random@k (verifier-pick) ${pct(rate(randomAtK))}`)
267
+ console.log(` diverse@k (verifier-pick) ${pct(rate(diverseAtK))}`)
268
+ console.log(` oracle@k (diverse, any) ${pct(rate(oracleAtK))}`)
269
+ if (blind.length >= 2) {
270
+ const row = (label: string, l: PairedLift) =>
271
+ console.log(` ${label.padEnd(34)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] (paired ${l.pairs}, discordant ${l.discordant})`)
272
+ console.log(`\n PAIRED LIFTS (95% bootstrap CI):`)
273
+ row('random@k − blind (compute)', pairedLift(blind, randomAtK))
274
+ row('diverse@k − random@k (verifier)', pairedLift(randomAtK, diverseAtK))
275
+ row('diverse@k − blind (total)', pairedLift(blind, diverseAtK))
276
+ } else {
277
+ console.log('\n (n<2 scored — paired CIs need ≥2; this is a plumbing smoke, not a signal)')
278
+ }
279
+
280
+ // Corpus: random@k arm, ranked by the deployable pytest verifier.
281
+ for (let ii = 0; ii < instances.length; ii += 1) {
282
+ const inst = instances[ii] as Instance
283
+ const grp = byInst[ii] as { random: (number | null)[]; rPatch: string[] }
284
+ const attempts: AttemptRecord[] = grp.random.map((p, round) => ({
285
+ round,
286
+ prompt: 'clbench-codebase-rollout',
287
+ output: (grp.rPatch[round] ?? '').slice(0, 4000),
288
+ ...(p === null ? { error: 'infra' } : { valid: p > 0, score: p }),
289
+ eventCount: 1,
290
+ eventTypes: { 'sandbox.stream': 1 },
291
+ traceTail: (grp.rPatch[round] ?? '').slice(-600),
292
+ }))
293
+ const validPasses = grp.random.filter((p): p is number => p !== null && p !== undefined)
294
+ const record = buildRunRecordFromAttempts(attempts, {
295
+ benchmark: 'clbench-codebase',
296
+ instanceId: inst.instanceId,
297
+ condition: `random@${k}`,
298
+ model,
299
+ // blindResolved reads off the FIRST non-null score (== full pass), not
300
+ // attempts[0].valid — a partial-credit first shot is valid but not "blind
301
+ // resolved". Pass it explicitly so the helper preserves the exact value.
302
+ blindResolved: validPasses[0] === 1,
303
+ resolved: validPasses.some((p) => p > 0),
304
+ infraError: validPasses.length === 0,
305
+ })
306
+ await appendRunRecord(corpusPath, record)
307
+ }
308
+ console.log(`\n=== wrote ${instances.length} task(s) → ${corpusPath} · gate: tsx src/corpus-replay.mts ${corpusPath} --selector=verifier ===`)
309
+ }
310
+
311
+ main().catch((err) => {
312
+ console.error(`clbench-codebase-gate: ${err instanceof Error ? err.message : String(err)}`)
313
+ process.exit(1)
314
+ })