@tangle-network/agent-bench 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/HARNESS.md +6 -2
- package/README.md +1 -4
- package/dist/benchmarks/swe-bench.js +4 -9
- package/dist/benchmarks/swe-bench.js.map +1 -1
- package/package.json +5 -5
- package/scripts/run-package-tests.mjs +2 -2
- package/src/benchmarks/swe-bench.test.mts +49 -0
- package/src/benchmarks/swe-bench.ts +4 -9
- package/src/quant-arena/README.md +0 -144
- package/src/quant-arena/backtest.test.mts +0 -135
- package/src/quant-arena/backtest.ts +0 -218
- package/src/quant-arena/data.test.mts +0 -44
- package/src/quant-arena/data.ts +0 -141
- package/src/quant-arena/driver.test.mts +0 -253
- package/src/quant-arena/driver.ts +0 -219
- package/src/quant-arena/fixtures/data/PROVENANCE.md +0 -26
- package/src/quant-arena/fixtures/data/holdout/IDX.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S01.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S02.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S03.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S04.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S05.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S06.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S07.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S08.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S09.csv +0 -523
- package/src/quant-arena/fixtures/data/holdout/S10.csv +0 -523
- package/src/quant-arena/fixtures/data/insample/IDX.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S01.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S02.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S03.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S04.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S05.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S06.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S07.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S08.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S09.csv +0 -2087
- package/src/quant-arena/fixtures/data/insample/S10.csv +0 -2087
- package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +0 -16
- package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +0 -5
- package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +0 -171
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +0 -119
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +0 -119
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +0 -105
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +0 -102
- package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +0 -4
- package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +0 -2
- package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +0 -84
- package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +0 -117
- package/src/quant-arena/holdout-certify.mts +0 -206
- package/src/quant-arena/holdout-certify.test.mts +0 -82
- package/src/quant-arena/leak-audit.test.mts +0 -79
- package/src/quant-arena/leak-audit.ts +0 -95
- package/src/quant-arena/make-fixtures.mts +0 -161
- package/src/quant-arena/multiplicity.test.mts +0 -68
- package/src/quant-arena/multiplicity.ts +0 -87
- package/src/quant-arena/nautilus-certify.ts +0 -31
- package/src/quant-arena/oms.ts +0 -90
- package/src/quant-arena/profiles/quant-researcher.profile.json +0 -12
- package/src/quant-arena/python/pyproject.toml +0 -8
- package/src/quant-arena/python/uv.lock +0 -1297
- package/src/quant-arena/python/vbt-worker.py +0 -192
- package/src/quant-arena/quant-loop.mts +0 -840
- package/src/quant-arena/quant-loop.test.mts +0 -75
- package/src/quant-arena/strategies/buy-hold-index/strategy.ts +0 -11
- package/src/quant-arena/strategies/equal-weight/strategy.ts +0 -20
- package/src/quant-arena/strategies/sma-crossover/strategy.ts +0 -42
- package/src/quant-arena/types.ts +0 -133
- package/src/quant-arena/vbt-client.ts +0 -321
- package/src/quant-arena/vbt-parity.test.mts +0 -183
- package/src/quant-arena/windows.test.mts +0 -45
- package/src/quant-arena/windows.ts +0 -54
- package/src/rollout-ledger/backfill-swe-arena.mts +0 -610
- package/src/rollout-ledger/backfill-swe-arena.test.mts +0 -347
- package/src/rollout-ledger/settle-capture.mts +0 -448
- package/src/rollout-ledger/settle-capture.test.mts +0 -270
- package/src/swe-arena/activation.mts +0 -225
- package/src/swe-arena/activation.test.mts +0 -300
- package/src/swe-arena/analyze.ts +0 -211
- package/src/swe-arena/arms.ts +0 -862
- package/src/swe-arena/bootstrap-meta.mts +0 -188
- package/src/swe-arena/bootstrap-meta.test.mts +0 -51
- package/src/swe-arena/briefing.mts +0 -217
- package/src/swe-arena/briefing.test.mts +0 -179
- package/src/swe-arena/calibrate.ts +0 -217
- package/src/swe-arena/capabilities.mts +0 -76
- package/src/swe-arena/capabilities.test.mts +0 -57
- package/src/swe-arena/capacity.ts +0 -198
- package/src/swe-arena/cell-evidence.mts +0 -437
- package/src/swe-arena/cell-evidence.test.mts +0 -248
- package/src/swe-arena/diagnosis-ensemble.test.mts +0 -210
- package/src/swe-arena/diagnosis-ensemble.ts +0 -523
- package/src/swe-arena/execution.test.mts +0 -1171
- package/src/swe-arena/factory-command-container.ts +0 -284
- package/src/swe-arena/factory-judge-child.mts +0 -228
- package/src/swe-arena/factory.test.mts +0 -645
- package/src/swe-arena/fixtures/analyze.py +0 -80
- package/src/swe-arena/fixtures/excludes.txt +0 -8
- package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +0 -51
- package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +0 -29
- package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +0 -64
- package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +0 -48
- package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +0 -29
- package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +0 -48
- package/src/swe-arena/fixtures/factory/loops-28/calibration.md +0 -47
- package/src/swe-arena/fixtures/factory/loops-28/manifest.json +0 -30
- package/src/swe-arena/fixtures/factory/loops-28/spec.md +0 -50
- package/src/swe-arena/fixtures/gen1-salvage/README.md +0 -45
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +0 -116
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +0 -293
- package/src/swe-arena/fixtures/holdout-preregister.log +0 -12
- package/src/swe-arena/fixtures/holdout.json +0 -44
- package/src/swe-arena/fixtures/instances.json +0 -146
- package/src/swe-arena/fixtures/ledger.jsonl +0 -12
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +0 -36
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +0 -33
- package/src/swe-arena/fixtures/rejudge.jsonl +0 -15
- package/src/swe-arena/fixtures/rematch.jsonl +0 -3
- package/src/swe-arena/fixtures/rematch2.jsonl +0 -3
- package/src/swe-arena/fixtures/rematch3.jsonl +0 -3
- package/src/swe-arena/fixtures/run-report/README.md +0 -43
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +0 -173
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +0 -100
- package/src/swe-arena/fixtures/run-report/gen3-rollup.json +0 -551
- package/src/swe-arena/fixtures/run-report/gen3-rollup.md +0 -64
- package/src/swe-arena/fixtures/sup-journal-true.json +0 -19
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +0 -48
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +0 -50
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +0 -76
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +0 -44
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +0 -32
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +0 -51
- package/src/swe-arena/fixtures/worker-tokens.json +0 -42
- package/src/swe-arena/fixtures.ts +0 -237
- package/src/swe-arena/gepa-seat.mts +0 -886
- package/src/swe-arena/gepa-seat.test.mts +0 -1136
- package/src/swe-arena/holdout-certify.mts +0 -408
- package/src/swe-arena/holdout-certify.test.mts +0 -160
- package/src/swe-arena/implementation-ref.test.mts +0 -64
- package/src/swe-arena/implementation-ref.ts +0 -62
- package/src/swe-arena/judge-child.mts +0 -37
- package/src/swe-arena/ledger-orphans.mts +0 -77
- package/src/swe-arena/ledger-orphans.test.mts +0 -149
- package/src/swe-arena/manifest.mts +0 -293
- package/src/swe-arena/manifest.test.mts +0 -169
- package/src/swe-arena/materialize.ts +0 -142
- package/src/swe-arena/outer-loop.mts +0 -2854
- package/src/swe-arena/outer-loop.test.mts +0 -714
- package/src/swe-arena/parity.test.mts +0 -87
- package/src/swe-arena/premeasured-from-cells.mts +0 -296
- package/src/swe-arena/premeasured-from-cells.test.mts +0 -201
- package/src/swe-arena/proc.test.mts +0 -172
- package/src/swe-arena/proc.ts +0 -260
- package/src/swe-arena/profiles/deepseek-author.profile.json +0 -12
- package/src/swe-arena/profiles/default-author.profile.json +0 -12
- package/src/swe-arena/proposer-fanout.mts +0 -736
- package/src/swe-arena/proposer-fanout.test.mts +0 -660
- package/src/swe-arena/proposer-provenance.mts +0 -176
- package/src/swe-arena/proposer-provenance.test.mts +0 -106
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +0 -183
- package/src/swe-arena/replay.test.mts +0 -300
- package/src/swe-arena/run-experiment.mts +0 -729
- package/src/swe-arena/run-report.mts +0 -75
- package/src/swe-arena/run-supervisor.mjs +0 -297
- package/src/swe-arena/run-supervisor.test.mts +0 -539
- package/src/swe-arena/score-split.mts +0 -140
- package/src/swe-arena/score-split.test.mts +0 -123
- package/src/swe-arena/scratch-worktree-serialization.test.mts +0 -72
- package/src/swe-arena/scratch-worktree.test.mts +0 -56
- package/src/swe-arena/scratch-worktree.ts +0 -64
- package/src/swe-arena/serialized-judge.ts +0 -414
- package/src/swe-arena/types.ts +0 -218
|
@@ -1,660 +0,0 @@
|
|
|
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 { makeProposalFinding, type ProposalFinding } from '@tangle-network/agent-eval'
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
7
|
-
import {
|
|
8
|
-
defaultGen3Config,
|
|
9
|
-
defaultGen4Config,
|
|
10
|
-
defaultGen5Config,
|
|
11
|
-
defaultRound4Config,
|
|
12
|
-
GEN3_IMPROVEMENT_SET,
|
|
13
|
-
GEN3_PARETO_PARENTS,
|
|
14
|
-
GEN3_SPARE_POOL,
|
|
15
|
-
resolveSmokeInstance,
|
|
16
|
-
type OuterLoopConfig,
|
|
17
|
-
} from './outer-loop.mts'
|
|
18
|
-
import type { EvidenceCell, R4Artifact } from './cell-evidence.mts'
|
|
19
|
-
import {
|
|
20
|
-
defaultProposers,
|
|
21
|
-
fanOutLoopsGenerator,
|
|
22
|
-
loadAuthorProfile,
|
|
23
|
-
materializeParetoParents,
|
|
24
|
-
mergeAuthorPrompt,
|
|
25
|
-
parentsPromptSection,
|
|
26
|
-
PROFILES_DIR,
|
|
27
|
-
proposerBuildPrompt,
|
|
28
|
-
resolveAuthorProfile,
|
|
29
|
-
sliceFindings,
|
|
30
|
-
type ParetoParentContext,
|
|
31
|
-
type ProposerSpec,
|
|
32
|
-
type SmokeRunner,
|
|
33
|
-
} from './proposer-fanout.mts'
|
|
34
|
-
import { runOk } from './proc.ts'
|
|
35
|
-
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Pure pieces.
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
const finding = (
|
|
41
|
-
over: Partial<Pick<ProposalFinding, 'analyst_id' | 'area' | 'claim'>>,
|
|
42
|
-
): ProposalFinding =>
|
|
43
|
-
makeProposalFinding({
|
|
44
|
-
analyst_id: 'analyst',
|
|
45
|
-
severity: 'high',
|
|
46
|
-
area: 'mechanism',
|
|
47
|
-
claim: 'workers drop build artifacts on clone',
|
|
48
|
-
evidence_refs: [],
|
|
49
|
-
confidence: 0.9,
|
|
50
|
-
...over,
|
|
51
|
-
proposal_origin: 'search',
|
|
52
|
-
produced_at: new Date(0).toISOString(),
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe('sliceFindings', () => {
|
|
56
|
-
const mech = finding({ claim: 'sandbox clone drops untracked build artifacts' })
|
|
57
|
-
const prompty = finding({ claim: 'the worker instruction wording invents new user-facing message text' })
|
|
58
|
-
const steering = finding({ analyst_id: 'round4-protocol', area: 'constraint', claim: 'change-space' })
|
|
59
|
-
const rawTrace = finding({ analyst_id: 'raw-trace-distiller', area: 'raw-trace-context', claim: 'traces at /x' })
|
|
60
|
-
|
|
61
|
-
it("'all' and undefined pass everything through", () => {
|
|
62
|
-
const all = [mech, prompty, steering, rawTrace]
|
|
63
|
-
expect(sliceFindings(all, 'all')).toEqual(all)
|
|
64
|
-
expect(sliceFindings(all, undefined)).toEqual(all)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it("'prompts' keeps prompt-flavored findings; 'mechanics' keeps the rest — steering + raw-trace always pass", () => {
|
|
68
|
-
const all = [mech, prompty, steering, rawTrace]
|
|
69
|
-
expect(sliceFindings(all, 'prompts')).toEqual([prompty, steering, rawTrace])
|
|
70
|
-
expect(sliceFindings(all, 'mechanics')).toEqual([mech, steering, rawTrace])
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
describe('proposerBuildPrompt', () => {
|
|
75
|
-
it('appends the lens AFTER the shared protocol prompt, leaving the change-space text intact', () => {
|
|
76
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi', lens: 'Prefer code-path fixes.' }
|
|
77
|
-
const prompt = proposerBuildPrompt({ findings: [] }, spec)
|
|
78
|
-
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
79
|
-
expect(prompt.indexOf('DECLARED CHANGE-SPACE')).toBeLessThan(prompt.indexOf('YOUR AUTHORING LENS (x)'))
|
|
80
|
-
expect(prompt).toContain('Prefer code-path fixes.')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('is the bare round prompt without a lens', () => {
|
|
84
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
85
|
-
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('AUTHORING LENS')
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// GEN-4: pinned models, Pareto-parent seeding, and the merge seat.
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
const PARENTS: ParetoParentContext[] = [
|
|
94
|
-
{
|
|
95
|
-
commit: 'aaaa111122223333aaaa111122223333aaaa1111',
|
|
96
|
-
label: 'parent-a',
|
|
97
|
-
resolvedInstances: ['pydata__xarray-4687', 'sphinx-doc__sphinx-9658'],
|
|
98
|
-
note: 'mechanical risk scan',
|
|
99
|
-
diff: 'diff --git a/src/worker-evidence.ts b/src/worker-evidence.ts\n+scan',
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
commit: 'bbbb111122223333bbbb111122223333bbbb1111',
|
|
103
|
-
label: 'parent-b',
|
|
104
|
-
resolvedInstances: ['django__django-11532'],
|
|
105
|
-
diff: 'diff --git a/extensions/pi/prompts/supervisor-system.md b/...\n+hidden suite',
|
|
106
|
-
},
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
describe('resolveAuthorProfile', () => {
|
|
110
|
-
it('returns the exact committed profile when provenance mirrors agree', () => {
|
|
111
|
-
const spec: ProposerSpec = {
|
|
112
|
-
name: 'x',
|
|
113
|
-
profile: 'default-author.profile.json',
|
|
114
|
-
harness: 'pi',
|
|
115
|
-
model: 'glm-5.2',
|
|
116
|
-
}
|
|
117
|
-
const profile = resolveAuthorProfile(spec)
|
|
118
|
-
expect(profile?.name).toBe('swe-arena-default-author')
|
|
119
|
-
expect(profile?.harness).toBe('pi')
|
|
120
|
-
expect(profile?.model?.default).toBe('glm-5.2')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('rejects duplicated harness or model fields that conflict with the profile', () => {
|
|
124
|
-
expect(() =>
|
|
125
|
-
resolveAuthorProfile({
|
|
126
|
-
name: 'bad-harness',
|
|
127
|
-
profile: 'default-author.profile.json',
|
|
128
|
-
harness: 'codex',
|
|
129
|
-
}),
|
|
130
|
-
).toThrow(/conflicts with profile\.harness/)
|
|
131
|
-
expect(() =>
|
|
132
|
-
resolveAuthorProfile({
|
|
133
|
-
name: 'bad-model',
|
|
134
|
-
profile: 'default-author.profile.json',
|
|
135
|
-
model: 'different-model',
|
|
136
|
-
}),
|
|
137
|
-
).toThrow(/conflicts with profile\.model\.default/)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('does not synthesize an ambient profile when the path is missing', () => {
|
|
141
|
-
const spec: ProposerSpec = { name: 'x', profile: 'default-author.profile.json', harness: 'pi' }
|
|
142
|
-
expect(resolveAuthorProfile(spec)).toEqual(loadAuthorProfile(spec))
|
|
143
|
-
expect(resolveAuthorProfile({ name: 'bare', harness: 'pi' })).toBeUndefined()
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('proposerBuildPrompt with pareto parents', () => {
|
|
148
|
-
it('appends the parents section (evidence + diffs) after the protocol prompt and lens', () => {
|
|
149
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi', lens: 'Prefer code-path fixes.' }
|
|
150
|
-
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
151
|
-
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
152
|
-
expect(prompt).toContain('PARETO PARENTS')
|
|
153
|
-
expect(prompt.indexOf('YOUR AUTHORING LENS')).toBeLessThan(prompt.indexOf('PARETO PARENTS'))
|
|
154
|
-
expect(prompt).toContain('parent-a (aaaa111122) — resolved: pydata__xarray-4687, sphinx-doc__sphinx-9658')
|
|
155
|
-
expect(prompt).toContain('mechanical risk scan')
|
|
156
|
-
expect(prompt).toContain('+scan')
|
|
157
|
-
expect(prompt).toContain('+hidden suite')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('leaves the prompt untouched when no parents are seeded (gen-3 behavior)', () => {
|
|
161
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
162
|
-
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('PARETO PARENTS')
|
|
163
|
-
expect(parentsPromptSection(PARENTS)).toContain('measured evidence')
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('mergeAuthorPrompt', () => {
|
|
168
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: 'pi', merge: true }
|
|
169
|
-
|
|
170
|
-
it('keeps the change-space contract and presents BOTH parent diffs with the coherent-union task', () => {
|
|
171
|
-
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
172
|
-
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
173
|
-
expect(prompt).toContain('MERGE SEAT')
|
|
174
|
-
expect(prompt).toContain('UNION of the')
|
|
175
|
-
expect(prompt).toContain('KEEPING BOTH BEHAVIORS')
|
|
176
|
-
expect(prompt).toContain('=== parent parent-a (aaaa111122) ===')
|
|
177
|
-
expect(prompt).toContain('=== parent parent-b (bbbb111122) ===')
|
|
178
|
-
expect(prompt).toContain('+scan')
|
|
179
|
-
expect(prompt).toContain('+hidden suite')
|
|
180
|
-
// Per-instance evidence rides along for both parents.
|
|
181
|
-
expect(prompt).toContain('resolved: django__django-11532')
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('fails loud with fewer than two parents', () => {
|
|
185
|
-
expect(() => mergeAuthorPrompt({ findings: [] }, spec, [PARENTS[0]!])).toThrow(/>=2/)
|
|
186
|
-
expect(() => proposerBuildPrompt({ findings: [] }, spec, [])).toThrow(/>=2/)
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
describe('defaultGen4Config', () => {
|
|
191
|
-
const config = defaultGen4Config()
|
|
192
|
-
|
|
193
|
-
it('seats Pi authors with exact GLM and DeepSeek profiles', () => {
|
|
194
|
-
expect(config.proposers).toHaveLength(3)
|
|
195
|
-
expect(config.populationSize).toBe(3)
|
|
196
|
-
const byName = Object.fromEntries(config.proposers!.map((p) => [p.name, p]))
|
|
197
|
-
expect(byName['glm-author']).toMatchObject({ harness: 'pi', model: 'glm-5.2' })
|
|
198
|
-
expect(byName['deepseek-author']).toMatchObject({
|
|
199
|
-
harness: 'pi',
|
|
200
|
-
model: 'deepseek-v4-flash',
|
|
201
|
-
profile: 'deepseek-author.profile.json',
|
|
202
|
-
})
|
|
203
|
-
expect(byName['merge-author']).toMatchObject({ harness: 'pi', merge: true })
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('can omit the DeepSeek seat while keeping population aligned', () => {
|
|
207
|
-
const noDeepseek = defaultGen4Config(undefined, { includeDeepseek: false })
|
|
208
|
-
expect(noDeepseek.proposers!.map((p) => p.name)).toEqual(['glm-author', 'merge-author'])
|
|
209
|
-
expect(noDeepseek.populationSize).toBe(2)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('seeds the gen-3 frontier as pareto parents and points at a fresh gen4 outDir + baseline artifact', () => {
|
|
213
|
-
expect(config.paretoParents).toEqual(GEN3_PARETO_PARENTS)
|
|
214
|
-
expect(config.paretoParents!.map((p) => p.commit.slice(0, 10))).toEqual(['cc0d95584c', 'a7a2a982e5'])
|
|
215
|
-
expect(config.outDir).toContain('gen4')
|
|
216
|
-
expect(config.premeasuredBaselinePath).toContain('gen4')
|
|
217
|
-
expect(config.premeasuredBaselinePath).not.toBe(defaultGen3Config().premeasuredBaselinePath)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it('keeps the gen-3 protocol frame: 6-instance set, 2 reps, 1 generation, prefilter, 2-rep holdout', () => {
|
|
221
|
-
expect(config.instances).toEqual([...GEN3_IMPROVEMENT_SET])
|
|
222
|
-
expect(config.repsPerInstance).toBe(2)
|
|
223
|
-
expect(config.generations).toBe(1)
|
|
224
|
-
expect(config.prefilter).toEqual({ enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false })
|
|
225
|
-
expect(config.holdoutRepsPerInstance).toBe(2)
|
|
226
|
-
expect(config.holdoutBaseline).toBe('measure')
|
|
227
|
-
for (const iid of config.instances) expect(config.holdoutInstances).not.toContain(iid)
|
|
228
|
-
})
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
describe('loadAuthorProfile', () => {
|
|
232
|
-
it('loads the complete committed Pi/GLM author profile', () => {
|
|
233
|
-
const profile = loadAuthorProfile({
|
|
234
|
-
name: 'a',
|
|
235
|
-
profile: 'default-author.profile.json',
|
|
236
|
-
harness: 'pi',
|
|
237
|
-
})
|
|
238
|
-
expect(profile?.name).toBe('swe-arena-default-author')
|
|
239
|
-
expect(profile?.harness).toBe('pi')
|
|
240
|
-
expect(profile?.prompt?.systemPrompt).toContain('measured failure evidence')
|
|
241
|
-
expect(profile?.model).toMatchObject({ provider: 'tangle-router', default: 'glm-5.2' })
|
|
242
|
-
expect(existsSync(join(PROFILES_DIR, 'default-author.profile.json'))).toBe(true)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('returns undefined without a profile path', () => {
|
|
246
|
-
expect(loadAuthorProfile({ name: 'a', harness: 'pi' })).toBeUndefined()
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('preserves profile resources for Runtime materialization', async () => {
|
|
250
|
-
const dir = await mkdtemp(join(tmpdir(), 'profile-'))
|
|
251
|
-
try {
|
|
252
|
-
const path = join(dir, 'with-resources.json')
|
|
253
|
-
await writeFile(
|
|
254
|
-
path,
|
|
255
|
-
JSON.stringify({
|
|
256
|
-
name: 'r',
|
|
257
|
-
harness: 'pi',
|
|
258
|
-
model: { provider: 'test', default: 'model' },
|
|
259
|
-
prompt: { systemPrompt: 'Author changes.' },
|
|
260
|
-
resources: { files: [] },
|
|
261
|
-
}),
|
|
262
|
-
)
|
|
263
|
-
expect(loadAuthorProfile({ name: 'a', profile: path, harness: 'pi' })?.resources).toEqual({
|
|
264
|
-
files: [],
|
|
265
|
-
})
|
|
266
|
-
} finally {
|
|
267
|
-
await rm(dir, { recursive: true, force: true })
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
describe('resolveSmokeInstance', () => {
|
|
273
|
-
const cell = (iid: string, wallS: number): EvidenceCell => ({
|
|
274
|
-
scenarioId: iid,
|
|
275
|
-
rep: 0,
|
|
276
|
-
artifact: { kind: 'swe-arm', iid, wallS } as R4Artifact,
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('passes an explicit iid through', () => {
|
|
280
|
-
expect(resolveSmokeInstance('pallets__flask-5014', ['a', 'b'], null)).toBe('pallets__flask-5014')
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it("'cheapest-of-set' picks the instance with the smallest summed baseline wall", () => {
|
|
284
|
-
// Summed across reps: a=1800, b=450, c=500 — b is cheapest.
|
|
285
|
-
const cells = [cell('a', 900), cell('a', 900), cell('b', 200), cell('b', 250), cell('c', 500)]
|
|
286
|
-
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b', 'c'], cells)).toBe('b')
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it("'cheapest-of-set' falls back to the first instance without baseline cells", () => {
|
|
290
|
-
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b'], null)).toBe('a')
|
|
291
|
-
expect(resolveSmokeInstance('cheapest-of-set', ['a', 'b'], [])).toBe('a')
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
describe('defaultGen3Config', () => {
|
|
296
|
-
const config = defaultGen3Config()
|
|
297
|
-
|
|
298
|
-
it('widens the improvement set to 6 with the three both-fail head-to-head instances', () => {
|
|
299
|
-
expect(config.instances).toEqual([...GEN3_IMPROVEMENT_SET])
|
|
300
|
-
expect(config.instances).toHaveLength(6)
|
|
301
|
-
for (const added of ['pydata__xarray-4687', 'pytest-dev__pytest-6197', 'sphinx-doc__sphinx-9658']) {
|
|
302
|
-
expect(config.instances).toContain(added)
|
|
303
|
-
}
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
it('keeps the improvement set disjoint from the pre-registered holdout AND the spare pool', () => {
|
|
307
|
-
for (const iid of config.instances) {
|
|
308
|
-
expect(config.holdoutInstances).not.toContain(iid)
|
|
309
|
-
expect(GEN3_SPARE_POOL).not.toContain(iid)
|
|
310
|
-
}
|
|
311
|
-
expect(config.holdoutInstances).toEqual(defaultRound4Config().holdoutInstances)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
it('assigns one candidate slot per proposer and pins the 2-rep holdout protocol', () => {
|
|
315
|
-
expect(config.proposers).toBeDefined()
|
|
316
|
-
expect(config.populationSize).toBe(config.proposers!.length)
|
|
317
|
-
expect(config.holdoutRepsPerInstance).toBe(2)
|
|
318
|
-
expect(config.holdoutBaseline).toBe('measure')
|
|
319
|
-
expect(config.prefilter).toEqual({ enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false })
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
it('points the premeasured baseline at a NEW gen3 artifact (full-split digest rule)', () => {
|
|
323
|
-
expect(config.premeasuredBaselinePath).toContain('gen3')
|
|
324
|
-
expect(config.premeasuredBaselinePath).not.toBe(defaultRound4Config().premeasuredBaselinePath)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it('has committed verify fixtures for every improvement-set instance', () => {
|
|
328
|
-
for (const iid of config.instances) {
|
|
329
|
-
expect(existsSync(join(config.verifyDir, `${iid}.sh`))).toBe(true)
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('defaultProposers uses one exact Pi/GLM profile', () => {
|
|
334
|
-
expect(defaultProposers()).toEqual([
|
|
335
|
-
{
|
|
336
|
-
name: 'default-author',
|
|
337
|
-
profile: 'default-author.profile.json',
|
|
338
|
-
harness: 'pi',
|
|
339
|
-
model: 'glm-5.2',
|
|
340
|
-
},
|
|
341
|
-
])
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
// ---------------------------------------------------------------------------
|
|
346
|
-
// The fan-out generator against a real (temp) git repo — stub authors.
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
|
|
349
|
-
describe('fanOutLoopsGenerator', () => {
|
|
350
|
-
let loopsRepo: string
|
|
351
|
-
let outDir: string
|
|
352
|
-
let driverWt: string
|
|
353
|
-
|
|
354
|
-
const git = async (args: string[], cwd: string): Promise<string> =>
|
|
355
|
-
(await runOk('git', ['-C', cwd, ...args])).stdout.trim()
|
|
356
|
-
|
|
357
|
-
beforeEach(async () => {
|
|
358
|
-
loopsRepo = await mkdtemp(join(tmpdir(), 'fanout-repo-'))
|
|
359
|
-
outDir = await mkdtemp(join(tmpdir(), 'fanout-out-'))
|
|
360
|
-
await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
|
|
361
|
-
await runOk('git', ['-C', loopsRepo, 'config', 'core.hooksPath', '/dev/null'])
|
|
362
|
-
await git(['config', 'user.email', 't@t.dev'], loopsRepo)
|
|
363
|
-
await git(['config', 'user.name', 'T'], loopsRepo)
|
|
364
|
-
await writeFile(join(loopsRepo, 'src.ts'), 'base\n')
|
|
365
|
-
await git(['add', '-A'], loopsRepo)
|
|
366
|
-
await git(['commit', '-q', '-m', 'init'], loopsRepo)
|
|
367
|
-
// The driver-owned candidate worktree the generator applies patches onto.
|
|
368
|
-
driverWt = join(outDir, 'driver-wt')
|
|
369
|
-
await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
afterEach(async () => {
|
|
373
|
-
await rm(outDir, { recursive: true, force: true })
|
|
374
|
-
await rm(loopsRepo, { recursive: true, force: true })
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
const baseConfig = (proposers: ProposerSpec[]): OuterLoopConfig => ({
|
|
378
|
-
...defaultRound4Config(),
|
|
379
|
-
loopsRepo,
|
|
380
|
-
outDir,
|
|
381
|
-
populationSize: proposers.length,
|
|
382
|
-
proposers,
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
const generatorArgs = (candidateIndex: number) => ({
|
|
386
|
-
worktreePath: driverWt,
|
|
387
|
-
findings: [] as ProposalFinding[],
|
|
388
|
-
maxShots: 1,
|
|
389
|
-
signal: new AbortController().signal,
|
|
390
|
-
generation: 0,
|
|
391
|
-
candidateIndex,
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
it('authors ALL proposers concurrently in separate worktrees and applies each patch to its candidate slot', async () => {
|
|
395
|
-
const proposers: ProposerSpec[] = [
|
|
396
|
-
{ name: 'alpha', harness: 'pi' },
|
|
397
|
-
{ name: 'beta', harness: 'pi' },
|
|
398
|
-
]
|
|
399
|
-
let inFlight = 0
|
|
400
|
-
let maxInFlight = 0
|
|
401
|
-
const seenWorktrees: string[] = []
|
|
402
|
-
let releaseAuthors!: () => void
|
|
403
|
-
const bothAuthorsStarted = new Promise<void>((resolve) => {
|
|
404
|
-
releaseAuthors = resolve
|
|
405
|
-
})
|
|
406
|
-
const gen = fanOutLoopsGenerator(baseConfig(proposers), {
|
|
407
|
-
author: async (proposer, args) => {
|
|
408
|
-
inFlight++
|
|
409
|
-
maxInFlight = Math.max(maxInFlight, inFlight)
|
|
410
|
-
seenWorktrees.push(args.worktreePath)
|
|
411
|
-
if (inFlight === proposers.length) releaseAuthors()
|
|
412
|
-
// Neither author may finish before both have started. A serial implementation
|
|
413
|
-
// therefore fails this test instead of passing or failing with machine speed.
|
|
414
|
-
await bothAuthorsStarted
|
|
415
|
-
// Inside the declared change-space (extensions/pi/**) so the always-on
|
|
416
|
-
// change-space pre-filter does not kill the candidate.
|
|
417
|
-
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
418
|
-
await writeFile(join(args.worktreePath, 'extensions', 'pi', `${proposer.name}.ts`), `${proposer.name} change\n`)
|
|
419
|
-
inFlight--
|
|
420
|
-
return { applied: true, summary: `${proposer.name} edit` }
|
|
421
|
-
},
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
const first = await gen.generate(generatorArgs(0))
|
|
425
|
-
const second = await gen.generate(generatorArgs(1))
|
|
426
|
-
|
|
427
|
-
// Parallel authoring: both stub authors were in flight at once, in two
|
|
428
|
-
// DIFFERENT scratch worktrees (never the driver worktree).
|
|
429
|
-
expect(maxInFlight).toBe(2)
|
|
430
|
-
expect(new Set(seenWorktrees).size).toBe(2)
|
|
431
|
-
for (const wt of seenWorktrees) expect(wt).not.toBe(driverWt)
|
|
432
|
-
|
|
433
|
-
expect(first).toMatchObject({ applied: true, label: 'alpha' })
|
|
434
|
-
expect(second).toMatchObject({ applied: true, label: 'beta' })
|
|
435
|
-
expect(first.rationale).toContain('proposer alpha (pi)')
|
|
436
|
-
// Both patches landed on the SAME driver worktree across the two calls —
|
|
437
|
-
// the driver discards/finalizes between calls in production; here we only
|
|
438
|
-
// assert each call applied its own proposer's file.
|
|
439
|
-
expect(await readFile(join(driverWt, 'extensions', 'pi', 'alpha.ts'), 'utf8')).toBe('alpha change\n')
|
|
440
|
-
expect(await readFile(join(driverWt, 'extensions', 'pi', 'beta.ts'), 'utf8')).toBe('beta change\n')
|
|
441
|
-
expect(gen.drainPrefilterKills()).toEqual([])
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
it('kills a candidate at the smoke pre-filter: applied=false, no patch applied, kill recorded with reason', async () => {
|
|
445
|
-
const proposers: ProposerSpec[] = [
|
|
446
|
-
{ name: 'good', harness: 'pi' },
|
|
447
|
-
{ name: 'bad', harness: 'pi' },
|
|
448
|
-
]
|
|
449
|
-
const config = baseConfig(proposers)
|
|
450
|
-
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
451
|
-
const smokeCalls: string[] = []
|
|
452
|
-
const smokeRunner: SmokeRunner = async ({ proposer, scratchPath }) => {
|
|
453
|
-
smokeCalls.push(proposer.name)
|
|
454
|
-
expect(existsSync(scratchPath)).toBe(true)
|
|
455
|
-
const pass = proposer.name === 'good'
|
|
456
|
-
return {
|
|
457
|
-
iid: 'astropy__astropy-13033',
|
|
458
|
-
pass,
|
|
459
|
-
reason: pass ? 'smoke ok' : 'smoke astropy__astropy-13033: resolved=false — below the mechanism bar',
|
|
460
|
-
resolved: pass,
|
|
461
|
-
patchLines: pass ? 3 : 0,
|
|
462
|
-
wallS: 5,
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
const gen = fanOutLoopsGenerator(config, {
|
|
466
|
-
author: async (proposer, args) => {
|
|
467
|
-
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
468
|
-
await writeFile(join(args.worktreePath, 'extensions', 'pi', `${proposer.name}.ts`), 'x\n')
|
|
469
|
-
return { applied: true, summary: `${proposer.name} edit` }
|
|
470
|
-
},
|
|
471
|
-
smokeRunner,
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
const survivor = await gen.generate(generatorArgs(0))
|
|
475
|
-
const killed = await gen.generate(generatorArgs(1))
|
|
476
|
-
|
|
477
|
-
expect(smokeCalls.sort()).toEqual(['bad', 'good'])
|
|
478
|
-
expect(survivor.applied).toBe(true)
|
|
479
|
-
expect(killed.applied).toBe(false)
|
|
480
|
-
// The killed proposer's patch never reached the driver worktree.
|
|
481
|
-
expect(existsSync(join(driverWt, 'extensions', 'pi', 'bad.ts'))).toBe(false)
|
|
482
|
-
const kills = gen.drainPrefilterKills()
|
|
483
|
-
expect(kills).toHaveLength(1)
|
|
484
|
-
expect(kills[0]).toMatchObject({
|
|
485
|
-
proposer: 'bad',
|
|
486
|
-
stage: 'smoke',
|
|
487
|
-
generation: 0,
|
|
488
|
-
candidateIndex: 1,
|
|
489
|
-
})
|
|
490
|
-
expect(kills[0]!.reason).toContain('below the mechanism bar')
|
|
491
|
-
// Kill forensics: the killed diff is persisted with a digest.
|
|
492
|
-
expect(kills[0]!.patchPath).not.toBeNull()
|
|
493
|
-
expect(kills[0]!.diffSha256).toMatch(/^sha256:/)
|
|
494
|
-
expect(existsSync(kills[0]!.patchPath!)).toBe(true)
|
|
495
|
-
// Draining clears the buffer.
|
|
496
|
-
expect(gen.drainPrefilterKills()).toEqual([])
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
it('kills an out-of-space diff at the change-space pre-filter before any smoke spend', async () => {
|
|
500
|
-
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: 'pi' }]
|
|
501
|
-
const config = baseConfig(proposers)
|
|
502
|
-
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
503
|
-
let smokeRan = false
|
|
504
|
-
const gen = fanOutLoopsGenerator(config, {
|
|
505
|
-
author: async (_proposer, args) => {
|
|
506
|
-
// `judge.py` is outside the declared change-space.
|
|
507
|
-
await writeFile(join(args.worktreePath, 'judge.py'), 'tampered\n')
|
|
508
|
-
return { applied: true, summary: 'rogue edit' }
|
|
509
|
-
},
|
|
510
|
-
smokeRunner: async () => {
|
|
511
|
-
smokeRan = true
|
|
512
|
-
return { iid: 'x', pass: true, reason: 'ok', resolved: true, patchLines: 1, wallS: 1 }
|
|
513
|
-
},
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
const result = await gen.generate(generatorArgs(0))
|
|
517
|
-
|
|
518
|
-
expect(result.applied).toBe(false)
|
|
519
|
-
expect(smokeRan).toBe(false)
|
|
520
|
-
const kills = gen.drainPrefilterKills()
|
|
521
|
-
expect(kills).toHaveLength(1)
|
|
522
|
-
expect(kills[0]).toMatchObject({ proposer: 'rogue', stage: 'change-space' })
|
|
523
|
-
expect(kills[0]!.reason).toContain('judge.py')
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
it('returns applied:false without a kill when a proposer authors nothing', async () => {
|
|
527
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: 'pi' }]), {
|
|
528
|
-
author: async () => ({ applied: false, summary: '' }),
|
|
529
|
-
})
|
|
530
|
-
expect((await gen.generate(generatorArgs(0))).applied).toBe(false)
|
|
531
|
-
expect(gen.drainPrefilterKills()).toEqual([])
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
it('fails loud when candidateIndex exceeds the proposer list (populationSize drift)', async () => {
|
|
535
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: 'pi' }]), {
|
|
536
|
-
author: async () => ({ applied: false, summary: '' }),
|
|
537
|
-
})
|
|
538
|
-
await expect(gen.generate(generatorArgs(1))).rejects.toThrow(/populationSize must equal/)
|
|
539
|
-
})
|
|
540
|
-
|
|
541
|
-
it('materializes pareto parents from real commits (full diff, truncation, missing-commit fail-loud)', async () => {
|
|
542
|
-
await writeFile(join(loopsRepo, 'src.ts'), 'base\nchanged\n')
|
|
543
|
-
await git(['add', '-A'], loopsRepo)
|
|
544
|
-
await git(['commit', '-q', '-m', 'parent change'], loopsRepo)
|
|
545
|
-
const commit = await git(['rev-parse', 'HEAD'], loopsRepo)
|
|
546
|
-
const seed = { commit, label: 'p1', resolvedInstances: ['inst-a'] }
|
|
547
|
-
const [parent] = await materializeParetoParents(loopsRepo, [seed])
|
|
548
|
-
expect(parent).toMatchObject(seed)
|
|
549
|
-
expect(parent!.diff).toContain('parent change')
|
|
550
|
-
expect(parent!.diff).toContain('+changed')
|
|
551
|
-
const [truncated] = await materializeParetoParents(loopsRepo, [seed], 40)
|
|
552
|
-
expect(truncated!.diff).toContain('[... diff truncated at 40 chars ...]')
|
|
553
|
-
await expect(
|
|
554
|
-
materializeParetoParents(loopsRepo, [{ commit: 'deadbeef'.repeat(5), label: 'ghost', resolvedInstances: [] }]),
|
|
555
|
-
).rejects.toThrow(/not found/)
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
it('refuses a merge seat without >=2 materialized parents', () => {
|
|
559
|
-
const config = baseConfig([{ name: 'merge-author', harness: 'pi', merge: true }])
|
|
560
|
-
expect(() => fanOutLoopsGenerator(config, { author: async () => ({ applied: false, summary: '' }) })).toThrow(
|
|
561
|
-
/merge proposer/,
|
|
562
|
-
)
|
|
563
|
-
expect(() =>
|
|
564
|
-
fanOutLoopsGenerator(config, {
|
|
565
|
-
author: async () => ({ applied: false, summary: '' }),
|
|
566
|
-
parents: [PARENTS[0]!],
|
|
567
|
-
}),
|
|
568
|
-
).toThrow(/needs >=2|merge proposer/)
|
|
569
|
-
// With two parents the generator constructs.
|
|
570
|
-
expect(() =>
|
|
571
|
-
fanOutLoopsGenerator(config, { author: async () => ({ applied: false, summary: '' }), parents: PARENTS }),
|
|
572
|
-
).not.toThrow()
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
it('rejects duplicate proposer names and empty proposer lists', () => {
|
|
576
|
-
expect(() =>
|
|
577
|
-
fanOutLoopsGenerator(baseConfig([
|
|
578
|
-
{ name: 'dup', harness: 'pi' },
|
|
579
|
-
{ name: 'dup', harness: 'pi' },
|
|
580
|
-
])),
|
|
581
|
-
).toThrow(/duplicate/)
|
|
582
|
-
expect(() => fanOutLoopsGenerator({ ...defaultRound4Config(), loopsRepo, outDir })).toThrow(/empty/)
|
|
583
|
-
})
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
// ---------------------------------------------------------------------------
|
|
587
|
-
// GEN-5: config bundle + prompt sections (MAP+TOOLBOX briefing, activation
|
|
588
|
-
// contract) on every seat, merge included.
|
|
589
|
-
// ---------------------------------------------------------------------------
|
|
590
|
-
|
|
591
|
-
describe('defaultGen5Config', () => {
|
|
592
|
-
const config = defaultGen5Config()
|
|
593
|
-
|
|
594
|
-
it('keeps the full gen-4 shape (seats, parents, 2 reps, deferred holdout) under a fresh gen5 outDir', () => {
|
|
595
|
-
expect(config.proposers!.map((p) => p.name)).toEqual(
|
|
596
|
-
defaultGen4Config().proposers!.map((p) => p.name),
|
|
597
|
-
)
|
|
598
|
-
expect(config.populationSize).toBe(config.proposers!.length)
|
|
599
|
-
expect(config.paretoParents).toEqual(GEN3_PARETO_PARENTS)
|
|
600
|
-
expect(config.repsPerInstance).toBe(2)
|
|
601
|
-
expect(config.generations).toBe(1)
|
|
602
|
-
expect(config.holdoutRepsPerInstance).toBe(2)
|
|
603
|
-
expect(config.outDir).toContain('gen5')
|
|
604
|
-
expect(config.premeasuredBaselinePath).toContain('gen5')
|
|
605
|
-
})
|
|
606
|
-
|
|
607
|
-
it('turns on the five gen-5 mechanisms as a unit', () => {
|
|
608
|
-
expect(config.scoreSplit).toEqual({ publicCount: 4 })
|
|
609
|
-
expect(config.briefing).toBe('map-toolbox-v1')
|
|
610
|
-
expect(config.activationGate).toBe(true)
|
|
611
|
-
expect(config.rolloutLedger).toEqual({ enabled: true })
|
|
612
|
-
expect(config.priorEvidenceDirs!.some((d) => d.includes('gen4'))).toBe(true)
|
|
613
|
-
})
|
|
614
|
-
|
|
615
|
-
it('can omit the DeepSeek seat while keeping population aligned', () => {
|
|
616
|
-
const noDeepseek = defaultGen5Config(undefined, { includeDeepseek: false })
|
|
617
|
-
expect(noDeepseek.proposers!.map((p) => p.name)).toEqual(['glm-author', 'merge-author'])
|
|
618
|
-
expect(noDeepseek.populationSize).toBe(2)
|
|
619
|
-
})
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
describe('gen-5 prompt sections', () => {
|
|
623
|
-
const briefing = {
|
|
624
|
-
indexPath: '/tmp/out/evidence-index.md',
|
|
625
|
-
briefingText: 'TOOLBOX: traces CLI, analysts, AxLLM. PERMISSION: subagents.',
|
|
626
|
-
briefingSource: 'default' as const,
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
it('appends EVIDENCE MAP + briefing + activation contract after the protocol prompt', () => {
|
|
630
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
631
|
-
const prompt = proposerBuildPrompt({ findings: [] }, spec, [], {
|
|
632
|
-
briefing,
|
|
633
|
-
activationGate: true,
|
|
634
|
-
})
|
|
635
|
-
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
636
|
-
expect(prompt).toContain('EVIDENCE MAP: /tmp/out/evidence-index.md')
|
|
637
|
-
expect(prompt).toContain('TOOLBOX')
|
|
638
|
-
expect(prompt).toContain('ACTIVATION PREDICATE')
|
|
639
|
-
expect(prompt.indexOf('DECLARED CHANGE-SPACE')).toBeLessThan(prompt.indexOf('EVIDENCE MAP'))
|
|
640
|
-
})
|
|
641
|
-
|
|
642
|
-
it('the merge seat gets the gen-5 sections too', () => {
|
|
643
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: 'pi', merge: true }
|
|
644
|
-
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS, {
|
|
645
|
-
briefing,
|
|
646
|
-
activationGate: true,
|
|
647
|
-
})
|
|
648
|
-
expect(prompt).toContain('MERGE SEAT')
|
|
649
|
-
expect(prompt).toContain('EVIDENCE MAP')
|
|
650
|
-
expect(prompt).toContain('ACTIVATION PREDICATE')
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
it('leaves gen-3/gen-4 prompts byte-identical when no extras are passed', () => {
|
|
654
|
-
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
655
|
-
const legacy = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
656
|
-
expect(legacy).not.toContain('EVIDENCE MAP')
|
|
657
|
-
expect(legacy).not.toContain('ACTIVATION PREDICATE')
|
|
658
|
-
expect(proposerBuildPrompt({ findings: [] }, spec, PARENTS, {})).toBe(legacy)
|
|
659
|
-
})
|
|
660
|
-
})
|