@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.
Files changed (174) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/HARNESS.md +6 -2
  3. package/README.md +1 -4
  4. package/dist/benchmarks/swe-bench.js +4 -9
  5. package/dist/benchmarks/swe-bench.js.map +1 -1
  6. package/package.json +5 -5
  7. package/scripts/run-package-tests.mjs +2 -2
  8. package/src/benchmarks/swe-bench.test.mts +49 -0
  9. package/src/benchmarks/swe-bench.ts +4 -9
  10. package/src/quant-arena/README.md +0 -144
  11. package/src/quant-arena/backtest.test.mts +0 -135
  12. package/src/quant-arena/backtest.ts +0 -218
  13. package/src/quant-arena/data.test.mts +0 -44
  14. package/src/quant-arena/data.ts +0 -141
  15. package/src/quant-arena/driver.test.mts +0 -253
  16. package/src/quant-arena/driver.ts +0 -219
  17. package/src/quant-arena/fixtures/data/PROVENANCE.md +0 -26
  18. package/src/quant-arena/fixtures/data/holdout/IDX.csv +0 -523
  19. package/src/quant-arena/fixtures/data/holdout/S01.csv +0 -523
  20. package/src/quant-arena/fixtures/data/holdout/S02.csv +0 -523
  21. package/src/quant-arena/fixtures/data/holdout/S03.csv +0 -523
  22. package/src/quant-arena/fixtures/data/holdout/S04.csv +0 -523
  23. package/src/quant-arena/fixtures/data/holdout/S05.csv +0 -523
  24. package/src/quant-arena/fixtures/data/holdout/S06.csv +0 -523
  25. package/src/quant-arena/fixtures/data/holdout/S07.csv +0 -523
  26. package/src/quant-arena/fixtures/data/holdout/S08.csv +0 -523
  27. package/src/quant-arena/fixtures/data/holdout/S09.csv +0 -523
  28. package/src/quant-arena/fixtures/data/holdout/S10.csv +0 -523
  29. package/src/quant-arena/fixtures/data/insample/IDX.csv +0 -2087
  30. package/src/quant-arena/fixtures/data/insample/S01.csv +0 -2087
  31. package/src/quant-arena/fixtures/data/insample/S02.csv +0 -2087
  32. package/src/quant-arena/fixtures/data/insample/S03.csv +0 -2087
  33. package/src/quant-arena/fixtures/data/insample/S04.csv +0 -2087
  34. package/src/quant-arena/fixtures/data/insample/S05.csv +0 -2087
  35. package/src/quant-arena/fixtures/data/insample/S06.csv +0 -2087
  36. package/src/quant-arena/fixtures/data/insample/S07.csv +0 -2087
  37. package/src/quant-arena/fixtures/data/insample/S08.csv +0 -2087
  38. package/src/quant-arena/fixtures/data/insample/S09.csv +0 -2087
  39. package/src/quant-arena/fixtures/data/insample/S10.csv +0 -2087
  40. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +0 -16
  41. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +0 -5
  42. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +0 -171
  43. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +0 -119
  44. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +0 -119
  45. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +0 -105
  46. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +0 -102
  47. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +0 -4
  48. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +0 -2
  49. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +0 -84
  50. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +0 -117
  51. package/src/quant-arena/holdout-certify.mts +0 -206
  52. package/src/quant-arena/holdout-certify.test.mts +0 -82
  53. package/src/quant-arena/leak-audit.test.mts +0 -79
  54. package/src/quant-arena/leak-audit.ts +0 -95
  55. package/src/quant-arena/make-fixtures.mts +0 -161
  56. package/src/quant-arena/multiplicity.test.mts +0 -68
  57. package/src/quant-arena/multiplicity.ts +0 -87
  58. package/src/quant-arena/nautilus-certify.ts +0 -31
  59. package/src/quant-arena/oms.ts +0 -90
  60. package/src/quant-arena/profiles/quant-researcher.profile.json +0 -12
  61. package/src/quant-arena/python/pyproject.toml +0 -8
  62. package/src/quant-arena/python/uv.lock +0 -1297
  63. package/src/quant-arena/python/vbt-worker.py +0 -192
  64. package/src/quant-arena/quant-loop.mts +0 -840
  65. package/src/quant-arena/quant-loop.test.mts +0 -75
  66. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +0 -11
  67. package/src/quant-arena/strategies/equal-weight/strategy.ts +0 -20
  68. package/src/quant-arena/strategies/sma-crossover/strategy.ts +0 -42
  69. package/src/quant-arena/types.ts +0 -133
  70. package/src/quant-arena/vbt-client.ts +0 -321
  71. package/src/quant-arena/vbt-parity.test.mts +0 -183
  72. package/src/quant-arena/windows.test.mts +0 -45
  73. package/src/quant-arena/windows.ts +0 -54
  74. package/src/rollout-ledger/backfill-swe-arena.mts +0 -610
  75. package/src/rollout-ledger/backfill-swe-arena.test.mts +0 -347
  76. package/src/rollout-ledger/settle-capture.mts +0 -448
  77. package/src/rollout-ledger/settle-capture.test.mts +0 -270
  78. package/src/swe-arena/activation.mts +0 -225
  79. package/src/swe-arena/activation.test.mts +0 -300
  80. package/src/swe-arena/analyze.ts +0 -211
  81. package/src/swe-arena/arms.ts +0 -862
  82. package/src/swe-arena/bootstrap-meta.mts +0 -188
  83. package/src/swe-arena/bootstrap-meta.test.mts +0 -51
  84. package/src/swe-arena/briefing.mts +0 -217
  85. package/src/swe-arena/briefing.test.mts +0 -179
  86. package/src/swe-arena/calibrate.ts +0 -217
  87. package/src/swe-arena/capabilities.mts +0 -76
  88. package/src/swe-arena/capabilities.test.mts +0 -57
  89. package/src/swe-arena/capacity.ts +0 -198
  90. package/src/swe-arena/cell-evidence.mts +0 -437
  91. package/src/swe-arena/cell-evidence.test.mts +0 -248
  92. package/src/swe-arena/diagnosis-ensemble.test.mts +0 -210
  93. package/src/swe-arena/diagnosis-ensemble.ts +0 -523
  94. package/src/swe-arena/execution.test.mts +0 -1171
  95. package/src/swe-arena/factory-command-container.ts +0 -284
  96. package/src/swe-arena/factory-judge-child.mts +0 -228
  97. package/src/swe-arena/factory.test.mts +0 -645
  98. package/src/swe-arena/fixtures/analyze.py +0 -80
  99. package/src/swe-arena/fixtures/excludes.txt +0 -8
  100. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +0 -51
  101. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +0 -29
  102. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +0 -64
  103. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +0 -48
  104. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +0 -29
  105. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +0 -48
  106. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +0 -47
  107. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +0 -30
  108. package/src/swe-arena/fixtures/factory/loops-28/spec.md +0 -50
  109. package/src/swe-arena/fixtures/gen1-salvage/README.md +0 -45
  110. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +0 -116
  111. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +0 -293
  112. package/src/swe-arena/fixtures/holdout-preregister.log +0 -12
  113. package/src/swe-arena/fixtures/holdout.json +0 -44
  114. package/src/swe-arena/fixtures/instances.json +0 -146
  115. package/src/swe-arena/fixtures/ledger.jsonl +0 -12
  116. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +0 -36
  117. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +0 -33
  118. package/src/swe-arena/fixtures/rejudge.jsonl +0 -15
  119. package/src/swe-arena/fixtures/rematch.jsonl +0 -3
  120. package/src/swe-arena/fixtures/rematch2.jsonl +0 -3
  121. package/src/swe-arena/fixtures/rematch3.jsonl +0 -3
  122. package/src/swe-arena/fixtures/run-report/README.md +0 -43
  123. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +0 -173
  124. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +0 -100
  125. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +0 -551
  126. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +0 -64
  127. package/src/swe-arena/fixtures/sup-journal-true.json +0 -19
  128. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +0 -48
  129. package/src/swe-arena/fixtures/verify/django__django-11532.sh +0 -50
  130. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +0 -76
  131. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +0 -44
  132. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +0 -32
  133. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +0 -51
  134. package/src/swe-arena/fixtures/worker-tokens.json +0 -42
  135. package/src/swe-arena/fixtures.ts +0 -237
  136. package/src/swe-arena/gepa-seat.mts +0 -886
  137. package/src/swe-arena/gepa-seat.test.mts +0 -1136
  138. package/src/swe-arena/holdout-certify.mts +0 -408
  139. package/src/swe-arena/holdout-certify.test.mts +0 -160
  140. package/src/swe-arena/implementation-ref.test.mts +0 -64
  141. package/src/swe-arena/implementation-ref.ts +0 -62
  142. package/src/swe-arena/judge-child.mts +0 -37
  143. package/src/swe-arena/ledger-orphans.mts +0 -77
  144. package/src/swe-arena/ledger-orphans.test.mts +0 -149
  145. package/src/swe-arena/manifest.mts +0 -293
  146. package/src/swe-arena/manifest.test.mts +0 -169
  147. package/src/swe-arena/materialize.ts +0 -142
  148. package/src/swe-arena/outer-loop.mts +0 -2854
  149. package/src/swe-arena/outer-loop.test.mts +0 -714
  150. package/src/swe-arena/parity.test.mts +0 -87
  151. package/src/swe-arena/premeasured-from-cells.mts +0 -296
  152. package/src/swe-arena/premeasured-from-cells.test.mts +0 -201
  153. package/src/swe-arena/proc.test.mts +0 -172
  154. package/src/swe-arena/proc.ts +0 -260
  155. package/src/swe-arena/profiles/deepseek-author.profile.json +0 -12
  156. package/src/swe-arena/profiles/default-author.profile.json +0 -12
  157. package/src/swe-arena/proposer-fanout.mts +0 -736
  158. package/src/swe-arena/proposer-fanout.test.mts +0 -660
  159. package/src/swe-arena/proposer-provenance.mts +0 -176
  160. package/src/swe-arena/proposer-provenance.test.mts +0 -106
  161. package/src/swe-arena/reconcile.ts +0 -0
  162. package/src/swe-arena/replay.mts +0 -183
  163. package/src/swe-arena/replay.test.mts +0 -300
  164. package/src/swe-arena/run-experiment.mts +0 -729
  165. package/src/swe-arena/run-report.mts +0 -75
  166. package/src/swe-arena/run-supervisor.mjs +0 -297
  167. package/src/swe-arena/run-supervisor.test.mts +0 -539
  168. package/src/swe-arena/score-split.mts +0 -140
  169. package/src/swe-arena/score-split.test.mts +0 -123
  170. package/src/swe-arena/scratch-worktree-serialization.test.mts +0 -72
  171. package/src/swe-arena/scratch-worktree.test.mts +0 -56
  172. package/src/swe-arena/scratch-worktree.ts +0 -64
  173. package/src/swe-arena/serialized-judge.ts +0 -414
  174. 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
- })