@tangle-network/agent-bench 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +13 -1
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +43 -0
- package/dist/adapters.js.map +1 -0
- package/dist/benchmarks/_harness.d.ts +125 -0
- package/dist/benchmarks/_harness.js +33 -0
- package/dist/benchmarks/_harness.js.map +1 -0
- package/dist/benchmarks/aec-bench.d.ts +27 -0
- package/dist/benchmarks/aec-bench.js +8 -0
- package/dist/benchmarks/aec-bench.js.map +1 -0
- package/dist/benchmarks/agentbench.d.ts +16 -0
- package/dist/benchmarks/agentbench.js +10 -0
- package/dist/benchmarks/agentbench.js.map +1 -0
- package/dist/benchmarks/appworld.d.ts +37 -0
- package/dist/benchmarks/appworld.js +14 -0
- package/dist/benchmarks/appworld.js.map +1 -0
- package/dist/benchmarks/bfcl.d.ts +18 -0
- package/dist/benchmarks/bfcl.js +10 -0
- package/dist/benchmarks/bfcl.js.map +1 -0
- package/dist/benchmarks/cad-design.d.ts +45 -0
- package/dist/benchmarks/cad-design.js +7 -0
- package/dist/benchmarks/cad-design.js.map +1 -0
- package/dist/benchmarks/cadbench.d.ts +19 -0
- package/dist/benchmarks/cadbench.js +8 -0
- package/dist/benchmarks/cadbench.js.map +1 -0
- package/dist/benchmarks/cadgenbench.d.ts +22 -0
- package/dist/benchmarks/cadgenbench.js +8 -0
- package/dist/benchmarks/cadgenbench.js.map +1 -0
- package/dist/benchmarks/commit0.d.ts +31 -0
- package/dist/benchmarks/commit0.js +10 -0
- package/dist/benchmarks/commit0.js.map +1 -0
- package/dist/benchmarks/crag.d.ts +14 -0
- package/dist/benchmarks/crag.js +9 -0
- package/dist/benchmarks/crag.js.map +1 -0
- package/dist/benchmarks/dabstep.d.ts +18 -0
- package/dist/benchmarks/dabstep.js +10 -0
- package/dist/benchmarks/dabstep.js.map +1 -0
- package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
- package/dist/benchmarks/enterpriseops-gym.js +10 -0
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
- package/dist/benchmarks/finresearchbench.d.ts +15 -0
- package/dist/benchmarks/finresearchbench.js +8 -0
- package/dist/benchmarks/finresearchbench.js.map +1 -0
- package/dist/benchmarks/finsearchcomp.d.ts +49 -0
- package/dist/benchmarks/finsearchcomp.js +7 -0
- package/dist/benchmarks/finsearchcomp.js.map +1 -0
- package/dist/benchmarks/frames.d.ts +59 -0
- package/dist/benchmarks/frames.js +13 -0
- package/dist/benchmarks/frames.js.map +1 -0
- package/dist/benchmarks/hotpotqa.d.ts +48 -0
- package/dist/benchmarks/hotpotqa.js +15 -0
- package/dist/benchmarks/hotpotqa.js.map +1 -0
- package/dist/benchmarks/humaneval.d.ts +62 -0
- package/dist/benchmarks/humaneval.js +17 -0
- package/dist/benchmarks/humaneval.js.map +1 -0
- package/dist/benchmarks/mind2web.d.ts +41 -0
- package/dist/benchmarks/mind2web.js +9 -0
- package/dist/benchmarks/mind2web.js.map +1 -0
- package/dist/benchmarks/nomiracl.d.ts +15 -0
- package/dist/benchmarks/nomiracl.js +9 -0
- package/dist/benchmarks/nomiracl.js.map +1 -0
- package/dist/benchmarks/open-rag-bench.d.ts +14 -0
- package/dist/benchmarks/open-rag-bench.js +9 -0
- package/dist/benchmarks/open-rag-bench.js.map +1 -0
- package/dist/benchmarks/programbench.d.ts +38 -0
- package/dist/benchmarks/programbench.js +10 -0
- package/dist/benchmarks/programbench.js.map +1 -0
- package/dist/benchmarks/rag-shared.d.ts +42 -0
- package/dist/benchmarks/rag-shared.js +39 -0
- package/dist/benchmarks/rag-shared.js.map +1 -0
- package/dist/benchmarks/ragbench.d.ts +16 -0
- package/dist/benchmarks/ragbench.js +9 -0
- package/dist/benchmarks/ragbench.js.map +1 -0
- package/dist/benchmarks/simpleqa.d.ts +64 -0
- package/dist/benchmarks/simpleqa.js +11 -0
- package/dist/benchmarks/simpleqa.js.map +1 -0
- package/dist/benchmarks/swe-bench.d.ts +56 -0
- package/dist/benchmarks/swe-bench.js +14 -0
- package/dist/benchmarks/swe-bench.js.map +1 -0
- package/dist/benchmarks/t2-ragbench.d.ts +14 -0
- package/dist/benchmarks/t2-ragbench.js +9 -0
- package/dist/benchmarks/t2-ragbench.js.map +1 -0
- package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
- package/dist/benchmarks/tau-bench-shared.js +10 -0
- package/dist/benchmarks/tau-bench-shared.js.map +1 -0
- package/dist/benchmarks/tau2-bench.d.ts +7 -0
- package/dist/benchmarks/tau2-bench.js +11 -0
- package/dist/benchmarks/tau2-bench.js.map +1 -0
- package/dist/benchmarks/tau3-banking.d.ts +15 -0
- package/dist/benchmarks/tau3-banking.js +9 -0
- package/dist/benchmarks/tau3-banking.js.map +1 -0
- package/dist/benchmarks/terminal-bench.d.ts +24 -0
- package/dist/benchmarks/terminal-bench.js +8 -0
- package/dist/benchmarks/terminal-bench.js.map +1 -0
- package/dist/benchmarks/toollm.d.ts +16 -0
- package/dist/benchmarks/toollm.js +10 -0
- package/dist/benchmarks/toollm.js.map +1 -0
- package/dist/benchmarks/trata-hedge.d.ts +32 -0
- package/dist/benchmarks/trata-hedge.js +7 -0
- package/dist/benchmarks/trata-hedge.js.map +1 -0
- package/dist/benchmarks/types.d.ts +107 -0
- package/dist/benchmarks/types.js +1 -0
- package/dist/benchmarks/types.js.map +1 -0
- package/dist/benchmarks/webarena-verified.d.ts +16 -0
- package/dist/benchmarks/webarena-verified.js +10 -0
- package/dist/benchmarks/webarena-verified.js.map +1 -0
- package/dist/chunk-2PVVP7GN.js +197 -0
- package/dist/chunk-2PVVP7GN.js.map +1 -0
- package/dist/chunk-2XU6OGEN.js +170 -0
- package/dist/chunk-2XU6OGEN.js.map +1 -0
- package/dist/chunk-53UPUNBZ.js +325 -0
- package/dist/chunk-53UPUNBZ.js.map +1 -0
- package/dist/chunk-5H5XV76F.js +240 -0
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/chunk-7WSD27QQ.js +118 -0
- package/dist/chunk-7WSD27QQ.js.map +1 -0
- package/dist/chunk-C7T7WEK2.js +103 -0
- package/dist/chunk-C7T7WEK2.js.map +1 -0
- package/dist/chunk-CKUVRZ2T.js +251 -0
- package/dist/chunk-CKUVRZ2T.js.map +1 -0
- package/dist/chunk-HBSWHQNJ.js +30 -0
- package/dist/chunk-HBSWHQNJ.js.map +1 -0
- package/dist/chunk-HHXFIHXC.js +116 -0
- package/dist/chunk-HHXFIHXC.js.map +1 -0
- package/dist/chunk-IFAV6KEM.js +276 -0
- package/dist/chunk-IFAV6KEM.js.map +1 -0
- package/dist/chunk-INNOYXCP.js +387 -0
- package/dist/chunk-INNOYXCP.js.map +1 -0
- package/dist/chunk-J3KDJNX2.js +182 -0
- package/dist/chunk-J3KDJNX2.js.map +1 -0
- package/dist/chunk-JRWWGMK7.js +148 -0
- package/dist/chunk-JRWWGMK7.js.map +1 -0
- package/dist/chunk-JTHWEDEW.js +32 -0
- package/dist/chunk-JTHWEDEW.js.map +1 -0
- package/dist/chunk-KDIKRJGB.js +120 -0
- package/dist/chunk-KDIKRJGB.js.map +1 -0
- package/dist/chunk-LRRD7NAG.js +301 -0
- package/dist/chunk-LRRD7NAG.js.map +1 -0
- package/dist/chunk-ODT47UAY.js +221 -0
- package/dist/chunk-ODT47UAY.js.map +1 -0
- package/dist/chunk-PA2ZKHJC.js +230 -0
- package/dist/chunk-PA2ZKHJC.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-PWQVGAJB.js +144 -0
- package/dist/chunk-PWQVGAJB.js.map +1 -0
- package/dist/chunk-R36V2VP7.js +169 -0
- package/dist/chunk-R36V2VP7.js.map +1 -0
- package/dist/chunk-R67DFVLO.js +142 -0
- package/dist/chunk-R67DFVLO.js.map +1 -0
- package/dist/chunk-SEVJPLZC.js +260 -0
- package/dist/chunk-SEVJPLZC.js.map +1 -0
- package/dist/chunk-SYDW647C.js +318 -0
- package/dist/chunk-SYDW647C.js.map +1 -0
- package/dist/chunk-TBKU5XQI.js +228 -0
- package/dist/chunk-TBKU5XQI.js.map +1 -0
- package/dist/chunk-TSWPNOYM.js +147 -0
- package/dist/chunk-TSWPNOYM.js.map +1 -0
- package/dist/chunk-UAIOHCUK.js +27 -0
- package/dist/chunk-UAIOHCUK.js.map +1 -0
- package/dist/chunk-UPAMRDX4.js +233 -0
- package/dist/chunk-UPAMRDX4.js.map +1 -0
- package/dist/chunk-VQRS7VUC.js +342 -0
- package/dist/chunk-VQRS7VUC.js.map +1 -0
- package/dist/chunk-X3BTXCJ4.js +262 -0
- package/dist/chunk-X3BTXCJ4.js.map +1 -0
- package/dist/chunk-X5YKXC6V.js +211 -0
- package/dist/chunk-X5YKXC6V.js.map +1 -0
- package/dist/chunk-Y6O2OCUO.js +130 -0
- package/dist/chunk-Y6O2OCUO.js.map +1 -0
- package/dist/chunk-YCGY7UIZ.js +208 -0
- package/dist/chunk-YCGY7UIZ.js.map +1 -0
- package/dist/chunk-Z7ML6L77.js +162 -0
- package/dist/chunk-Z7ML6L77.js.map +1 -0
- package/dist/chunk-ZEWMTR5M.js +136 -0
- package/dist/chunk-ZEWMTR5M.js.map +1 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +1908 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -9
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +12 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +788 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/calibrate.ts +116 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +405 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +104 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2145 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +489 -0
- package/src/swe-arena/proposer-fanout.test.mts +372 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +361 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +498 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +166 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import type { AnalystFinding } from '@tangle-network/agent-eval'
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
7
|
+
import {
|
|
8
|
+
defaultGen3Config,
|
|
9
|
+
defaultRound4Config,
|
|
10
|
+
GEN3_IMPROVEMENT_SET,
|
|
11
|
+
GEN3_SPARE_POOL,
|
|
12
|
+
resolveSmokeInstance,
|
|
13
|
+
type OuterLoopConfig,
|
|
14
|
+
} from './outer-loop.mts'
|
|
15
|
+
import type { EvidenceCell, R4Artifact } from './cell-evidence.mts'
|
|
16
|
+
import {
|
|
17
|
+
defaultProposers,
|
|
18
|
+
fanOutLoopsGenerator,
|
|
19
|
+
loadAuthorProfile,
|
|
20
|
+
PROFILES_DIR,
|
|
21
|
+
proposerBuildPrompt,
|
|
22
|
+
sliceFindings,
|
|
23
|
+
type ProposerSpec,
|
|
24
|
+
type SmokeRunner,
|
|
25
|
+
} from './proposer-fanout.mts'
|
|
26
|
+
import { runOk } from './proc.ts'
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Pure pieces.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const finding = (over: Record<string, unknown>): AnalystFinding =>
|
|
33
|
+
({
|
|
34
|
+
schema_version: '1.0.0',
|
|
35
|
+
finding_id: `f-${Math.random().toString(36).slice(2)}`,
|
|
36
|
+
analyst_id: 'analyst',
|
|
37
|
+
produced_at: new Date(0).toISOString(),
|
|
38
|
+
severity: 'high',
|
|
39
|
+
area: 'mechanism',
|
|
40
|
+
claim: 'workers drop build artifacts on clone',
|
|
41
|
+
evidence_refs: [],
|
|
42
|
+
confidence: 0.9,
|
|
43
|
+
...over,
|
|
44
|
+
}) as unknown as AnalystFinding
|
|
45
|
+
|
|
46
|
+
describe('sliceFindings', () => {
|
|
47
|
+
const mech = finding({ claim: 'sandbox clone drops untracked build artifacts' })
|
|
48
|
+
const prompty = finding({ claim: 'the worker instruction wording invents new user-facing message text' })
|
|
49
|
+
const steering = finding({ analyst_id: 'round4-protocol', area: 'constraint', claim: 'change-space' })
|
|
50
|
+
const rawTrace = finding({ analyst_id: 'raw-trace-distiller', area: 'raw-trace-context', claim: 'traces at /x' })
|
|
51
|
+
|
|
52
|
+
it("'all' and undefined pass everything through", () => {
|
|
53
|
+
const all = [mech, prompty, steering, rawTrace]
|
|
54
|
+
expect(sliceFindings(all, 'all')).toEqual(all)
|
|
55
|
+
expect(sliceFindings(all, undefined)).toEqual(all)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("'prompts' keeps prompt-flavored findings; 'mechanics' keeps the rest — steering + raw-trace always pass", () => {
|
|
59
|
+
const all = [mech, prompty, steering, rawTrace]
|
|
60
|
+
expect(sliceFindings(all, 'prompts')).toEqual([prompty, steering, rawTrace])
|
|
61
|
+
expect(sliceFindings(all, 'mechanics')).toEqual([mech, steering, rawTrace])
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('proposerBuildPrompt', () => {
|
|
66
|
+
it('appends the lens AFTER the shared protocol prompt, leaving the change-space text intact', () => {
|
|
67
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude', lens: 'Prefer code-path fixes.' }
|
|
68
|
+
const prompt = proposerBuildPrompt({ report: undefined, findings: [] }, spec)
|
|
69
|
+
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
70
|
+
expect(prompt.indexOf('DECLARED CHANGE-SPACE')).toBeLessThan(prompt.indexOf('YOUR AUTHORING LENS (x)'))
|
|
71
|
+
expect(prompt).toContain('Prefer code-path fixes.')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('is the bare round prompt without a lens', () => {
|
|
75
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude' }
|
|
76
|
+
expect(proposerBuildPrompt({ report: undefined, findings: [] }, spec)).not.toContain('AUTHORING LENS')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('loadAuthorProfile', () => {
|
|
81
|
+
it('loads the committed default-author profile (bare: no prompt, no model)', () => {
|
|
82
|
+
const profile = loadAuthorProfile({ name: 'a', profile: 'default-author.profile.json', harness: 'claude' })
|
|
83
|
+
expect(profile?.name).toBe('swe-arena-default-author')
|
|
84
|
+
expect(profile?.prompt).toBeUndefined()
|
|
85
|
+
expect(profile?.model).toBeUndefined()
|
|
86
|
+
expect(existsSync(join(PROFILES_DIR, 'default-author.profile.json'))).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns undefined without a profile path', () => {
|
|
90
|
+
expect(loadAuthorProfile({ name: 'a', harness: 'claude' })).toBeUndefined()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('fails loud when a non-codex proposer declares profile resources (they would be dropped)', async () => {
|
|
94
|
+
const dir = await mkdtemp(join(tmpdir(), 'profile-'))
|
|
95
|
+
try {
|
|
96
|
+
const path = join(dir, 'with-resources.json')
|
|
97
|
+
await writeFile(path, JSON.stringify({ name: 'r', resources: { files: [] } }))
|
|
98
|
+
expect(() => loadAuthorProfile({ name: 'a', profile: path, harness: 'claude' })).toThrow(/silently drop/)
|
|
99
|
+
} finally {
|
|
100
|
+
await rm(dir, { recursive: true, force: true })
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('resolveSmokeInstance', () => {
|
|
106
|
+
const cell = (iid: string, wallS: number): EvidenceCell => ({
|
|
107
|
+
scenarioId: iid,
|
|
108
|
+
rep: 0,
|
|
109
|
+
artifact: { kind: 'swe-arm', iid, wallS } as R4Artifact,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('passes an explicit iid through', () => {
|
|
113
|
+
expect(resolveSmokeInstance('pallets__flask-5014', ['a', 'b'], null)).toBe('pallets__flask-5014')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("'cheapest-of-set' picks the instance with the smallest summed baseline wall", () => {
|
|
117
|
+
// Summed across reps: a=1800, b=450, c=500 — b is cheapest.
|
|
118
|
+
const cells = [cell('a', 900), cell('a', 900), cell('b', 200), cell('b', 250), cell('c', 500)]
|
|
119
|
+
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b', 'c'], cells)).toBe('b')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it("'cheapest-of-set' falls back to the first instance without baseline cells", () => {
|
|
123
|
+
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b'], null)).toBe('a')
|
|
124
|
+
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b'], [])).toBe('a')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('defaultGen3Config', () => {
|
|
129
|
+
const config = defaultGen3Config()
|
|
130
|
+
|
|
131
|
+
it('widens the improvement set to 6 with the three both-fail head-to-head instances', () => {
|
|
132
|
+
expect(config.instances).toEqual([...GEN3_IMPROVEMENT_SET])
|
|
133
|
+
expect(config.instances).toHaveLength(6)
|
|
134
|
+
for (const added of ['pydata__xarray-4687', 'pytest-dev__pytest-6197', 'sphinx-doc__sphinx-9658']) {
|
|
135
|
+
expect(config.instances).toContain(added)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('keeps the improvement set disjoint from the pre-registered holdout AND the spare pool', () => {
|
|
140
|
+
for (const iid of config.instances) {
|
|
141
|
+
expect(config.holdoutInstances).not.toContain(iid)
|
|
142
|
+
expect(GEN3_SPARE_POOL).not.toContain(iid)
|
|
143
|
+
}
|
|
144
|
+
expect(config.holdoutInstances).toEqual(defaultRound4Config().holdoutInstances)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('assigns one candidate slot per proposer and pins the 2-rep holdout protocol', () => {
|
|
148
|
+
expect(config.proposers).toBeDefined()
|
|
149
|
+
expect(config.populationSize).toBe(config.proposers!.length)
|
|
150
|
+
expect(config.holdoutRepsPerInstance).toBe(2)
|
|
151
|
+
expect(config.holdoutBaseline).toBe('measure')
|
|
152
|
+
expect(config.prefilter).toEqual({ enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false })
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('points the premeasured baseline at a NEW gen3 artifact (full-split digest rule)', () => {
|
|
156
|
+
expect(config.premeasuredBaselinePath).toContain('gen3')
|
|
157
|
+
expect(config.premeasuredBaselinePath).not.toBe(defaultRound4Config().premeasuredBaselinePath)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('has committed verify fixtures for every improvement-set instance', () => {
|
|
161
|
+
for (const iid of config.instances) {
|
|
162
|
+
expect(existsSync(join(config.verifyDir, `${iid}.sh`))).toBe(true)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('defaultProposers codifies the gen-2 author: one bare-profile claude entry', () => {
|
|
167
|
+
expect(defaultProposers()).toEqual([
|
|
168
|
+
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
169
|
+
])
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// The fan-out generator against a real (temp) git repo — stub authors.
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
describe('fanOutLoopsGenerator', () => {
|
|
178
|
+
let loopsRepo: string
|
|
179
|
+
let outDir: string
|
|
180
|
+
let driverWt: string
|
|
181
|
+
|
|
182
|
+
const git = async (args: string[], cwd: string): Promise<string> =>
|
|
183
|
+
(await runOk('git', ['-C', cwd, ...args])).stdout.trim()
|
|
184
|
+
|
|
185
|
+
beforeEach(async () => {
|
|
186
|
+
loopsRepo = await mkdtemp(join(tmpdir(), 'fanout-repo-'))
|
|
187
|
+
outDir = await mkdtemp(join(tmpdir(), 'fanout-out-'))
|
|
188
|
+
await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
|
|
189
|
+
await git(['config', 'user.email', 't@t.dev'], loopsRepo)
|
|
190
|
+
await git(['config', 'user.name', 'T'], loopsRepo)
|
|
191
|
+
await writeFile(join(loopsRepo, 'src.ts'), 'base\n')
|
|
192
|
+
await git(['add', '-A'], loopsRepo)
|
|
193
|
+
await git(['commit', '-q', '-m', 'init'], loopsRepo)
|
|
194
|
+
// The driver-owned candidate worktree the generator applies patches onto.
|
|
195
|
+
driverWt = join(outDir, 'driver-wt')
|
|
196
|
+
await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
afterEach(async () => {
|
|
200
|
+
await rm(outDir, { recursive: true, force: true })
|
|
201
|
+
await rm(loopsRepo, { recursive: true, force: true })
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const baseConfig = (proposers: ProposerSpec[]): OuterLoopConfig => ({
|
|
205
|
+
...defaultRound4Config(),
|
|
206
|
+
loopsRepo,
|
|
207
|
+
outDir,
|
|
208
|
+
populationSize: proposers.length,
|
|
209
|
+
proposers,
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const generatorArgs = (candidateIndex: number) => ({
|
|
213
|
+
worktreePath: driverWt,
|
|
214
|
+
report: undefined,
|
|
215
|
+
findings: [] as AnalystFinding[],
|
|
216
|
+
maxShots: 1,
|
|
217
|
+
signal: new AbortController().signal,
|
|
218
|
+
generation: 0,
|
|
219
|
+
candidateIndex,
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('authors ALL proposers concurrently in separate worktrees and applies each patch to its candidate slot', async () => {
|
|
223
|
+
const proposers: ProposerSpec[] = [
|
|
224
|
+
{ name: 'alpha', harness: 'claude' },
|
|
225
|
+
{ name: 'beta', harness: 'claude' },
|
|
226
|
+
]
|
|
227
|
+
let inFlight = 0
|
|
228
|
+
let maxInFlight = 0
|
|
229
|
+
const seenWorktrees: string[] = []
|
|
230
|
+
const gen = fanOutLoopsGenerator(baseConfig(proposers), {
|
|
231
|
+
author: async (proposer, args) => {
|
|
232
|
+
inFlight++
|
|
233
|
+
maxInFlight = Math.max(maxInFlight, inFlight)
|
|
234
|
+
seenWorktrees.push(args.worktreePath)
|
|
235
|
+
// Hold the slot across a tick so real parallelism is observable.
|
|
236
|
+
await new Promise((r) => setTimeout(r, 30))
|
|
237
|
+
// Inside the declared change-space (extensions/pi/**) so the always-on
|
|
238
|
+
// change-space pre-filter does not kill the candidate.
|
|
239
|
+
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
240
|
+
await writeFile(join(args.worktreePath, 'extensions', 'pi', `${proposer.name}.ts`), `${proposer.name} change\n`)
|
|
241
|
+
inFlight--
|
|
242
|
+
return { applied: true, summary: `${proposer.name} edit` }
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const first = await gen.generate(generatorArgs(0))
|
|
247
|
+
const second = await gen.generate(generatorArgs(1))
|
|
248
|
+
|
|
249
|
+
// Parallel authoring: both stub authors were in flight at once, in two
|
|
250
|
+
// DIFFERENT scratch worktrees (never the driver worktree).
|
|
251
|
+
expect(maxInFlight).toBe(2)
|
|
252
|
+
expect(new Set(seenWorktrees).size).toBe(2)
|
|
253
|
+
for (const wt of seenWorktrees) expect(wt).not.toBe(driverWt)
|
|
254
|
+
|
|
255
|
+
expect(first).toMatchObject({ applied: true, label: 'alpha' })
|
|
256
|
+
expect(second).toMatchObject({ applied: true, label: 'beta' })
|
|
257
|
+
expect(first.rationale).toContain('proposer alpha (claude)')
|
|
258
|
+
// Both patches landed on the SAME driver worktree across the two calls —
|
|
259
|
+
// the driver discards/finalizes between calls in production; here we only
|
|
260
|
+
// assert each call applied its own proposer's file.
|
|
261
|
+
expect(await readFile(join(driverWt, 'extensions', 'pi', 'alpha.ts'), 'utf8')).toBe('alpha change\n')
|
|
262
|
+
expect(await readFile(join(driverWt, 'extensions', 'pi', 'beta.ts'), 'utf8')).toBe('beta change\n')
|
|
263
|
+
expect(gen.drainPrefilterKills()).toEqual([])
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('kills a candidate at the smoke pre-filter: applied=false, no patch applied, kill recorded with reason', async () => {
|
|
267
|
+
const proposers: ProposerSpec[] = [
|
|
268
|
+
{ name: 'good', harness: 'claude' },
|
|
269
|
+
{ name: 'bad', harness: 'claude' },
|
|
270
|
+
]
|
|
271
|
+
const config = baseConfig(proposers)
|
|
272
|
+
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
273
|
+
const smokeCalls: string[] = []
|
|
274
|
+
const smokeRunner: SmokeRunner = async ({ proposer, scratchPath }) => {
|
|
275
|
+
smokeCalls.push(proposer.name)
|
|
276
|
+
expect(existsSync(scratchPath)).toBe(true)
|
|
277
|
+
const pass = proposer.name === 'good'
|
|
278
|
+
return {
|
|
279
|
+
iid: 'astropy__astropy-13033',
|
|
280
|
+
pass,
|
|
281
|
+
reason: pass ? 'smoke ok' : 'smoke astropy__astropy-13033: resolved=false — below the mechanism bar',
|
|
282
|
+
resolved: pass,
|
|
283
|
+
patchLines: pass ? 3 : 0,
|
|
284
|
+
wallS: 5,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const gen = fanOutLoopsGenerator(config, {
|
|
288
|
+
author: async (proposer, args) => {
|
|
289
|
+
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
290
|
+
await writeFile(join(args.worktreePath, 'extensions', 'pi', `${proposer.name}.ts`), 'x\n')
|
|
291
|
+
return { applied: true, summary: `${proposer.name} edit` }
|
|
292
|
+
},
|
|
293
|
+
smokeRunner,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const survivor = await gen.generate(generatorArgs(0))
|
|
297
|
+
const killed = await gen.generate(generatorArgs(1))
|
|
298
|
+
|
|
299
|
+
expect(smokeCalls.sort()).toEqual(['bad', 'good'])
|
|
300
|
+
expect(survivor.applied).toBe(true)
|
|
301
|
+
expect(killed.applied).toBe(false)
|
|
302
|
+
// The killed proposer's patch never reached the driver worktree.
|
|
303
|
+
expect(existsSync(join(driverWt, 'extensions', 'pi', 'bad.ts'))).toBe(false)
|
|
304
|
+
const kills = gen.drainPrefilterKills()
|
|
305
|
+
expect(kills).toHaveLength(1)
|
|
306
|
+
expect(kills[0]).toMatchObject({
|
|
307
|
+
proposer: 'bad',
|
|
308
|
+
stage: 'smoke',
|
|
309
|
+
generation: 0,
|
|
310
|
+
candidateIndex: 1,
|
|
311
|
+
})
|
|
312
|
+
expect(kills[0]!.reason).toContain('below the mechanism bar')
|
|
313
|
+
// Kill forensics: the killed diff is persisted with a digest.
|
|
314
|
+
expect(kills[0]!.patchPath).not.toBeNull()
|
|
315
|
+
expect(kills[0]!.diffSha256).toMatch(/^sha256:/)
|
|
316
|
+
expect(existsSync(kills[0]!.patchPath!)).toBe(true)
|
|
317
|
+
// Draining clears the buffer.
|
|
318
|
+
expect(gen.drainPrefilterKills()).toEqual([])
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('kills an out-of-space diff at the change-space pre-filter before any smoke spend', async () => {
|
|
322
|
+
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: 'claude' }]
|
|
323
|
+
const config = baseConfig(proposers)
|
|
324
|
+
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
325
|
+
let smokeRan = false
|
|
326
|
+
const gen = fanOutLoopsGenerator(config, {
|
|
327
|
+
author: async (_proposer, args) => {
|
|
328
|
+
// `judge.py` is outside the declared change-space.
|
|
329
|
+
await writeFile(join(args.worktreePath, 'judge.py'), 'tampered\n')
|
|
330
|
+
return { applied: true, summary: 'rogue edit' }
|
|
331
|
+
},
|
|
332
|
+
smokeRunner: async () => {
|
|
333
|
+
smokeRan = true
|
|
334
|
+
return { iid: 'x', pass: true, reason: 'ok', resolved: true, patchLines: 1, wallS: 1 }
|
|
335
|
+
},
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const result = await gen.generate(generatorArgs(0))
|
|
339
|
+
|
|
340
|
+
expect(result.applied).toBe(false)
|
|
341
|
+
expect(smokeRan).toBe(false)
|
|
342
|
+
const kills = gen.drainPrefilterKills()
|
|
343
|
+
expect(kills).toHaveLength(1)
|
|
344
|
+
expect(kills[0]).toMatchObject({ proposer: 'rogue', stage: 'change-space' })
|
|
345
|
+
expect(kills[0]!.reason).toContain('judge.py')
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('returns applied:false without a kill when a proposer authors nothing', async () => {
|
|
349
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: 'claude' }]), {
|
|
350
|
+
author: async () => ({ applied: false, summary: '' }),
|
|
351
|
+
})
|
|
352
|
+
expect((await gen.generate(generatorArgs(0))).applied).toBe(false)
|
|
353
|
+
expect(gen.drainPrefilterKills()).toEqual([])
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('fails loud when candidateIndex exceeds the proposer list (populationSize drift)', async () => {
|
|
357
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: 'claude' }]), {
|
|
358
|
+
author: async () => ({ applied: false, summary: '' }),
|
|
359
|
+
})
|
|
360
|
+
await expect(gen.generate(generatorArgs(1))).rejects.toThrow(/populationSize must equal/)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it('rejects duplicate proposer names and empty proposer lists', () => {
|
|
364
|
+
expect(() =>
|
|
365
|
+
fanOutLoopsGenerator(baseConfig([
|
|
366
|
+
{ name: 'dup', harness: 'claude' },
|
|
367
|
+
{ name: 'dup', harness: 'claude' },
|
|
368
|
+
])),
|
|
369
|
+
).toThrow(/duplicate/)
|
|
370
|
+
expect(() => fanOutLoopsGenerator({ ...defaultRound4Config(), loopsRepo, outDir })).toThrow(/empty/)
|
|
371
|
+
})
|
|
372
|
+
})
|
|
Binary file
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay CLI: `tsx src/swe-arena/replay.mts`
|
|
3
|
+
*
|
|
4
|
+
* Reproduces the reference `fixtures/analyze.py` output from the committed
|
|
5
|
+
* fixtures (section 1 matches its printed lines exactly — pinned in
|
|
6
|
+
* replay.test.mts), then prints what the reference script never did:
|
|
7
|
+
* the reconciled valid-denominator verdict, the true SUP spend including
|
|
8
|
+
* worker tokens, the supervisor evolution rounds, and the holdout registry.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { pathToFileURL } from 'node:url'
|
|
12
|
+
import {
|
|
13
|
+
loadHoldout,
|
|
14
|
+
loadLedger,
|
|
15
|
+
loadPreregisterLog,
|
|
16
|
+
loadRejudge,
|
|
17
|
+
loadRematchRounds,
|
|
18
|
+
loadSupJournalTrue,
|
|
19
|
+
loadWorkerTokens,
|
|
20
|
+
} from './fixtures.ts'
|
|
21
|
+
import {
|
|
22
|
+
costRollup,
|
|
23
|
+
ledgerOutcomes,
|
|
24
|
+
pairedSignTest,
|
|
25
|
+
reconciledOutcomes,
|
|
26
|
+
roundsProgression,
|
|
27
|
+
splitPairs,
|
|
28
|
+
type CostRollup,
|
|
29
|
+
type DiscordantSplit,
|
|
30
|
+
type RoundState,
|
|
31
|
+
type SignTestResult,
|
|
32
|
+
} from './analyze.ts'
|
|
33
|
+
import { reconcile, type PairedTable } from './reconcile.ts'
|
|
34
|
+
import type { HoldoutRegistry, LedgerRow, RematchRow } from './types.ts'
|
|
35
|
+
|
|
36
|
+
/** Python-style list repr (single quotes) so section 1 matches analyze.py byte-for-byte. */
|
|
37
|
+
const pyList = (xs: string[]): string => `[${xs.map((x) => `'${x}'`).join(', ')}]`
|
|
38
|
+
const signed = (x: number, digits?: number): string =>
|
|
39
|
+
(x >= 0 ? '+' : '') + (digits === undefined ? String(x) : x.toFixed(digits))
|
|
40
|
+
|
|
41
|
+
export interface Replay {
|
|
42
|
+
ledger: LedgerRow[]
|
|
43
|
+
raw: { split: DiscordantSplit; sign: SignTestResult; soloResolved: number; supResolved: number }
|
|
44
|
+
cost: CostRollup
|
|
45
|
+
table: PairedTable
|
|
46
|
+
valid: { split: DiscordantSplit; sign: SignTestResult; soloResolved: number; supResolved: number }
|
|
47
|
+
rounds: Map<string, RoundState[]>
|
|
48
|
+
rematchRounds: RematchRow[][]
|
|
49
|
+
holdout: HoldoutRegistry
|
|
50
|
+
preregisterLog: string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildReplay(): Replay {
|
|
54
|
+
const ledger = loadLedger()
|
|
55
|
+
const rejudge = loadRejudge()
|
|
56
|
+
const rematchRounds = loadRematchRounds()
|
|
57
|
+
|
|
58
|
+
const rawOutcomes = ledgerOutcomes(ledger)
|
|
59
|
+
const table = reconcile(ledger, rejudge)
|
|
60
|
+
const validOutcomes = reconciledOutcomes(table.valid)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
ledger,
|
|
64
|
+
raw: {
|
|
65
|
+
split: splitPairs(rawOutcomes),
|
|
66
|
+
sign: pairedSignTest(rawOutcomes),
|
|
67
|
+
soloResolved: rawOutcomes.filter((o) => o.solo).length,
|
|
68
|
+
supResolved: rawOutcomes.filter((o) => o.sup).length,
|
|
69
|
+
},
|
|
70
|
+
cost: costRollup(ledger, loadSupJournalTrue(), loadWorkerTokens()),
|
|
71
|
+
table,
|
|
72
|
+
valid: {
|
|
73
|
+
split: splitPairs(validOutcomes),
|
|
74
|
+
sign: pairedSignTest(validOutcomes),
|
|
75
|
+
soloResolved: validOutcomes.filter((o) => o.solo).length,
|
|
76
|
+
supResolved: validOutcomes.filter((o) => o.sup).length,
|
|
77
|
+
},
|
|
78
|
+
rounds: roundsProgression(ledger, rematchRounds),
|
|
79
|
+
rematchRounds,
|
|
80
|
+
holdout: loadHoldout(),
|
|
81
|
+
preregisterLog: loadPreregisterLog(),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Section 1 — byte-faithful reproduction of analyze.py's printed analysis. */
|
|
86
|
+
export function renderReference(r: Replay): string[] {
|
|
87
|
+
const rows = [...r.ledger].sort((a, b) => (a.iid < b.iid ? -1 : a.iid > b.iid ? 1 : 0))
|
|
88
|
+
const n = rows.length
|
|
89
|
+
const { raw, cost } = r
|
|
90
|
+
const bar = '='.repeat(80)
|
|
91
|
+
const lines: string[] = []
|
|
92
|
+
lines.push(bar)
|
|
93
|
+
lines.push(`PAIRED HEAD-TO-HEAD: glm-5.2 SOLO vs glm-5.2 SUPERVISOR (N=${n} paired instances)`)
|
|
94
|
+
lines.push(bar)
|
|
95
|
+
lines.push(`SOLO resolved: ${raw.soloResolved}/${n} = ${((100 * raw.soloResolved) / n).toFixed(1)}%`)
|
|
96
|
+
lines.push(`SUP resolved: ${raw.supResolved}/${n} = ${((100 * raw.supResolved) / n).toFixed(1)}%`)
|
|
97
|
+
const delta = raw.supResolved - raw.soloResolved
|
|
98
|
+
lines.push(`delta (SUP-SOLO): ${signed(delta)} instances (${signed((100 * delta) / n, 1)} pts)`)
|
|
99
|
+
lines.push('')
|
|
100
|
+
lines.push('DISCORDANT PAIRS (the signal):')
|
|
101
|
+
lines.push(` SUP-only wins (SUP✓ SOLO✗): ${raw.split.supOnly.length} ${pyList(raw.split.supOnly)}`)
|
|
102
|
+
lines.push(` SOLO-only wins (SOLO✓ SUP✗): ${raw.split.soloOnly.length} ${pyList(raw.split.soloOnly)}`)
|
|
103
|
+
lines.push(` both resolved: ${raw.split.both.length} | neither: ${raw.split.neither.length} ${pyList(raw.split.neither)}`)
|
|
104
|
+
lines.push(` exact two-sided sign test on discordant pairs: p=${raw.sign.pValue.toFixed(4)}`)
|
|
105
|
+
lines.push('')
|
|
106
|
+
lines.push(
|
|
107
|
+
`COST (measured tokens; USD via shared blended rate $${(cost.blendedRatePerTok * 1e6).toFixed(3)}/1M from SUP accounting):`,
|
|
108
|
+
)
|
|
109
|
+
lines.push(` SOLO total tokens: ${cost.soloTokens.toLocaleString('en-US')} -> derived $${cost.soloUsdDerived.toFixed(4)}`)
|
|
110
|
+
lines.push(` SUP total tokens: ${cost.supBrainTokens.toLocaleString('en-US')} -> runtime $${cost.supUsd.toFixed(4)}`)
|
|
111
|
+
lines.push(` SUP/SOLO token ratio: ${cost.brainTokenRatio.toFixed(2)}x`)
|
|
112
|
+
lines.push(` SUP/SOLO cost ratio (token-derived): ${(cost.supUsd / cost.soloUsdDerived).toFixed(2)}x`)
|
|
113
|
+
lines.push(` [telemetry] instances where runtime spentTokens != journal-true (no-winner zeroing): ${pyList(cost.telemetryGaps)}`)
|
|
114
|
+
lines.push(` WALL: SOLO ${cost.soloWallS}s total vs SUP ${cost.supWallS}s total -> SUP ${cost.wallRatio.toFixed(2)}x wall`)
|
|
115
|
+
lines.push('')
|
|
116
|
+
lines.push(bar)
|
|
117
|
+
lines.push('PER-INSTANCE')
|
|
118
|
+
lines.push(bar)
|
|
119
|
+
lines.push(
|
|
120
|
+
`${'instance'.padEnd(32)} ${'SOLO'.padEnd(5)} ${'SUP'.padEnd(5)} ${'v_s'.padEnd(3)} ${'v_p'.padEnd(3)} ${'wrk'.padEnd(3)} ${'soloTok'.padEnd(8)} ${'supTok'.padEnd(8)} ${'supUSD'.padEnd(7)} ${'soloW'.padEnd(5)} ${'supW'.padEnd(5)}`,
|
|
121
|
+
)
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
const pyBool = (v: boolean): string => (v ? 'True' : 'False')
|
|
124
|
+
lines.push(
|
|
125
|
+
`${row.iid.padEnd(32)} ${pyBool(row.solo_resolved).slice(0, 5).padEnd(5)} ${pyBool(row.sup_resolved).slice(0, 5).padEnd(5)} ` +
|
|
126
|
+
`${pyBool(row.solo_verify_pass)[0].padEnd(3)} ${pyBool(row.sup_verify_pass)[0].padEnd(3)} ` +
|
|
127
|
+
`${String(row.sup_workers ?? '?').padEnd(3)} ${String(row.solo_tokens).padEnd(8)} ${String(row.sup_spentTokens ?? 0).padEnd(8)} ` +
|
|
128
|
+
`${(row.sup_spentUsd ?? 0).toFixed(4)} ${String(row.solo_wall_s).padEnd(5)} ${String(row.sup_wall_s).padEnd(5)}`,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
return lines
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Sections 2-5 — the analysis that lived in session lore, now typed. */
|
|
135
|
+
export function renderReconciled(r: Replay): string[] {
|
|
136
|
+
const bar = '='.repeat(80)
|
|
137
|
+
const lines: string[] = []
|
|
138
|
+
const { table, valid, cost } = r
|
|
139
|
+
const n = table.valid.length
|
|
140
|
+
|
|
141
|
+
lines.push(bar)
|
|
142
|
+
lines.push('RECONCILED VERDICT (re-judged, gold-gated denominator)')
|
|
143
|
+
lines.push(bar)
|
|
144
|
+
for (const e of table.excluded) lines.push(` EXCLUDED ${e.iid}: ${e.excludeReason}`)
|
|
145
|
+
for (const v of table.valid) {
|
|
146
|
+
const src = [v.solo.source !== 'ledger' ? `solo:${v.solo.source}` : null, v.sup.source !== 'ledger' ? `sup:${v.sup.source}` : null]
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.join(' ')
|
|
149
|
+
if (src) lines.push(` RE-JUDGED ${v.iid}: ${src}`)
|
|
150
|
+
}
|
|
151
|
+
lines.push(`SOLO resolved: ${valid.soloResolved}/${n}`)
|
|
152
|
+
lines.push(`SUP resolved: ${valid.supResolved}/${n}`)
|
|
153
|
+
lines.push(`discordant: SUP-only ${pyList(valid.split.supOnly)} | SOLO-only ${pyList(valid.split.soloOnly)}`)
|
|
154
|
+
lines.push(`exact two-sided sign test: p=${valid.sign.pValue.toFixed(4)}`)
|
|
155
|
+
lines.push('')
|
|
156
|
+
lines.push('TRUE SUP SPEND (brain + workers; analyze.py printed brain only):')
|
|
157
|
+
lines.push(` brain ${cost.supBrainTokens.toLocaleString('en-US')} + workers ${cost.supWorkerTokens.toLocaleString('en-US')} = ${cost.supTotalTokens.toLocaleString('en-US')} tokens`)
|
|
158
|
+
lines.push(` SUP/SOLO true token ratio: ${cost.totalTokenRatio.toFixed(2)}x (brain-only ratio: ${cost.brainTokenRatio.toFixed(2)}x)`)
|
|
159
|
+
lines.push('')
|
|
160
|
+
lines.push('SUP EVOLUTION ROUNDS (SUP = original head-to-head run):')
|
|
161
|
+
for (const [iid, states] of r.rounds) {
|
|
162
|
+
const cells = states.map(
|
|
163
|
+
(s) => `${s.round}:${s.resolved ? 'RESOLVED' : 'unresolved'}(${s.patchLines}L,${s.verdict ?? 'null'})`,
|
|
164
|
+
)
|
|
165
|
+
lines.push(` ${iid.padEnd(32)} ${cells.join(' -> ')}`)
|
|
166
|
+
}
|
|
167
|
+
lines.push('')
|
|
168
|
+
lines.push(`HOLDOUT REGISTRY (pre-registered at loops@${r.holdout.selectedAtCommit.slice(0, 10)}, untouched):`)
|
|
169
|
+
for (const e of r.holdout.entries) {
|
|
170
|
+
lines.push(` ${e.iid.padEnd(36)} gold_official_resolved=${e.gold_official_resolved} verify_calibrated=${e.verify_calibrated}`)
|
|
171
|
+
}
|
|
172
|
+
return lines
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function renderReplay(r: Replay = buildReplay()): string {
|
|
176
|
+
return [...renderReference(r), '', ...renderReconciled(r)].join('\n')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const isMain = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
180
|
+
|
|
181
|
+
if (isMain) {
|
|
182
|
+
console.log(renderReplay())
|
|
183
|
+
}
|