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