@tangle-network/agent-bench 0.3.7 → 0.4.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 +11 -0
- package/HARNESS.md +43 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- 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/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-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-PWQVGAJB.js → chunk-V7AEBY6U.js} +21 -21
- 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 +23 -23
- package/package.json +5 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +2 -2
- package/src/gate.ts +1 -1
- package/src/hev-eval.mts +5 -2
- package/src/hev-improve.mts +118 -73
- package/src/official-optimizer-config.mts +89 -0
- package/src/official-optimizer-config.test.mts +88 -0
- package/src/profiles.ts +2 -2
- 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 +342 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +225 -0
- package/src/swe-arena/activation.test.mts +300 -0
- package/src/swe-arena/arms.ts +30 -14
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +102 -1
- package/src/swe-arena/cell-evidence.mts +41 -9
- 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/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/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.ts +135 -2
- package/src/swe-arena/gepa-seat.mts +877 -0
- package/src/swe-arena/gepa-seat.test.mts +1059 -0
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/outer-loop.mts +710 -33
- 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/proposer-fanout.mts +327 -31
- package/src/swe-arena/proposer-fanout.test.mts +246 -0
- package/src/swe-arena/proposer-provenance.mts +172 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/run-experiment.mts +367 -1
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.test.mts +5 -3
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-arena/types.ts +52 -0
- package/src/swe-code-improve.mts +24 -25
- package/src/swe-improve.mts +129 -96
- package/src/swe-local-proof.mts +6 -1
- package/src/swe-stream.mts +4 -2
- package/src/swe-structural.mts +245 -837
- package/src/tb-container-executor.test.mts +30 -6
- package/src/tb-supervisor-sidecar.mts +2 -1
- package/src/trata-gepa.mts +182 -245
- package/dist/chunk-LRRD7NAG.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- /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-PWQVGAJB.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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen-5 activation gate: predicate validation, execution over run artifacts
|
|
3
|
+
* (grep + script, ws/ skip with ws/.loops searched), the prefilter kill for a
|
|
4
|
+
* missing/invalid predicate, and the quarantine verdict path (an improved
|
|
5
|
+
* score with a never-fired mechanism is quarantined, not promoted).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'node:fs'
|
|
9
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { tmpdir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
import type { AnalystFinding } from '@tangle-network/agent-eval'
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
14
|
+
import {
|
|
15
|
+
ACTIVATION_PREDICATE_RELPATH,
|
|
16
|
+
activationPredicateInstruction,
|
|
17
|
+
parseActivationPredicate,
|
|
18
|
+
runActivationPredicate,
|
|
19
|
+
} from './activation.mts'
|
|
20
|
+
import { decideVerdict } from './cell-evidence.mts'
|
|
21
|
+
import { defaultRound4Config, parseStaircaseRow, STAIRCASE_SCHEMA, type OuterLoopConfig, type StaircaseRow } from './outer-loop.mts'
|
|
22
|
+
import { fanOutLoopsGenerator, type ProposerSpec } from './proposer-fanout.mts'
|
|
23
|
+
import { runOk } from './proc.ts'
|
|
24
|
+
|
|
25
|
+
describe('parseActivationPredicate', () => {
|
|
26
|
+
it('accepts a valid grep predicate (with optional files filters)', () => {
|
|
27
|
+
const parsed = parseActivationPredicate(
|
|
28
|
+
JSON.stringify({
|
|
29
|
+
description: 'patchRiskWarnings emitted in at least one settle',
|
|
30
|
+
kind: 'grep',
|
|
31
|
+
pattern: 'patchRiskWarnings|patch-risk',
|
|
32
|
+
files: ['journal.jsonl', 'workers/'],
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
expect(parsed.ok).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('accepts a valid script predicate', () => {
|
|
39
|
+
const parsed = parseActivationPredicate(
|
|
40
|
+
JSON.stringify({ description: 'x', kind: 'script', script: 'grep -rq X .' }),
|
|
41
|
+
)
|
|
42
|
+
expect(parsed.ok).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it.each([
|
|
46
|
+
['not json', 'not valid JSON'],
|
|
47
|
+
['[]', 'JSON object'],
|
|
48
|
+
[JSON.stringify({ description: '', kind: 'grep', pattern: 'a' }), 'description'],
|
|
49
|
+
[JSON.stringify({ description: 'x', kind: 'grep' }), 'pattern'],
|
|
50
|
+
[JSON.stringify({ description: 'x', kind: 'grep', pattern: '(' }), 'RegExp'],
|
|
51
|
+
[JSON.stringify({ description: 'x', kind: 'grep', pattern: 'a', files: [1] }), 'files'],
|
|
52
|
+
[JSON.stringify({ description: 'x', kind: 'script' }), 'script'],
|
|
53
|
+
[JSON.stringify({ description: 'x', kind: 'sql' }), 'kind'],
|
|
54
|
+
])('rejects %s', (raw, want) => {
|
|
55
|
+
const parsed = parseActivationPredicate(raw)
|
|
56
|
+
expect(parsed.ok).toBe(false)
|
|
57
|
+
if (!parsed.ok) expect(parsed.error).toContain(want)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('the prompt instruction carries the template and the quarantine warning', () => {
|
|
61
|
+
const text = activationPredicateInstruction()
|
|
62
|
+
expect(text).toContain(ACTIVATION_PREDICATE_RELPATH)
|
|
63
|
+
expect(text).toContain('QUARANTINED')
|
|
64
|
+
expect(text).toContain('"kind": "grep"')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('runActivationPredicate', () => {
|
|
69
|
+
let runA: string
|
|
70
|
+
let runB: string
|
|
71
|
+
|
|
72
|
+
beforeEach(async () => {
|
|
73
|
+
runA = await mkdtemp(join(tmpdir(), 'act-a-'))
|
|
74
|
+
runB = await mkdtemp(join(tmpdir(), 'act-b-'))
|
|
75
|
+
await writeFile(join(runA, 'driver.log'), 'boot\nno mechanism here\n')
|
|
76
|
+
await writeFile(join(runB, 'driver.log'), 'boot\npatchRiskWarnings: 2 warnings emitted\n')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
afterEach(async () => {
|
|
80
|
+
await rm(runA, { recursive: true, force: true })
|
|
81
|
+
await rm(runB, { recursive: true, force: true })
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const grep = (pattern: string, files?: string[]) => ({
|
|
85
|
+
description: 'd',
|
|
86
|
+
kind: 'grep' as const,
|
|
87
|
+
pattern,
|
|
88
|
+
...(files ? { files } : {}),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('fires with path:line evidence when the pattern appears in ANY run dir', async () => {
|
|
92
|
+
const res = await runActivationPredicate(grep('patchRiskWarnings'), [runA, runB])
|
|
93
|
+
expect(res.fired).toBe(true)
|
|
94
|
+
expect(res.checkedRunDirs).toBe(2)
|
|
95
|
+
expect(res.evidence[0]).toContain(join(runB, 'driver.log'))
|
|
96
|
+
expect(res.evidence[0]).toContain('patchRiskWarnings')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('does not fire when the pattern never appears (fail-closed)', async () => {
|
|
100
|
+
const res = await runActivationPredicate(grep('neverEverPresent'), [runA, runB])
|
|
101
|
+
expect(res.fired).toBe(false)
|
|
102
|
+
expect(res.evidence).toEqual([])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('honors files filters (relative-path substring)', async () => {
|
|
106
|
+
await writeFile(join(runB, 'other.txt'), 'patchRiskWarnings\n')
|
|
107
|
+
const onlyOther = await runActivationPredicate(grep('patchRiskWarnings', ['other.txt']), [runB])
|
|
108
|
+
expect(onlyOther.fired).toBe(true)
|
|
109
|
+
const onlyMissing = await runActivationPredicate(grep('patchRiskWarnings', ['nope.bin']), [runB])
|
|
110
|
+
expect(onlyMissing.fired).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('skips the ws/ workspace subtree EXCEPT ws/.loops (supervisor artifacts)', async () => {
|
|
114
|
+
// Marker only inside ws/ (a checked-out repo) → must NOT count as fired.
|
|
115
|
+
await mkdir(join(runA, 'ws', 'src'), { recursive: true })
|
|
116
|
+
await writeFile(join(runA, 'ws', 'src', 'code.py'), 'patchRiskWarnings in the repo source\n')
|
|
117
|
+
const inWs = await runActivationPredicate(grep('patchRiskWarnings'), [runA])
|
|
118
|
+
expect(inWs.fired).toBe(false)
|
|
119
|
+
// Marker in ws/.loops → the supervisor's own artifacts, searched.
|
|
120
|
+
await mkdir(join(runA, 'ws', '.loops', 'supervisor', 's1'), { recursive: true })
|
|
121
|
+
await writeFile(join(runA, 'ws', '.loops', 'supervisor', 's1', 'journal.jsonl'), '{"k":"patchRiskWarnings"}\n')
|
|
122
|
+
const inLoops = await runActivationPredicate(grep('patchRiskWarnings'), [runA])
|
|
123
|
+
expect(inLoops.fired).toBe(true)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('script kind fires on rc=0 in any run dir, not-fired otherwise', async () => {
|
|
127
|
+
const script = (s: string) => ({ description: 'd', kind: 'script' as const, script: s })
|
|
128
|
+
const hit = await runActivationPredicate(script('grep -q patchRiskWarnings driver.log'), [runA, runB])
|
|
129
|
+
expect(hit.fired).toBe(true)
|
|
130
|
+
expect(hit.evidence[0]).toContain(runB)
|
|
131
|
+
const miss = await runActivationPredicate(script('grep -q neverEverPresent driver.log'), [runA, runB])
|
|
132
|
+
expect(miss.fired).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('quarantine verdict path', () => {
|
|
137
|
+
const base = {
|
|
138
|
+
violations: [],
|
|
139
|
+
coverageComplete: true,
|
|
140
|
+
resolvedCount: 3,
|
|
141
|
+
parentResolvedCount: 1,
|
|
142
|
+
costRatio: 1.0,
|
|
143
|
+
costGuardRatio: 1.2,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
it('quarantines a candidate whose mechanism never fired EVEN when its score improved', () => {
|
|
147
|
+
expect(decideVerdict({ ...base, activationFired: false })).toBe('quarantined-inactive')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('accepts an improved candidate whose mechanism fired; gate-not-applicable behaves as before', () => {
|
|
151
|
+
expect(decideVerdict({ ...base, activationFired: true })).toBe('accepted')
|
|
152
|
+
expect(decideVerdict({ ...base, activationFired: null })).toBe('accepted')
|
|
153
|
+
expect(decideVerdict(base)).toBe('accepted')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('out-of-space still wins over quarantine; quarantine wins over no-gain/cost/coverage', () => {
|
|
157
|
+
expect(decideVerdict({ ...base, violations: ['judge.py'], activationFired: false })).toBe('rejected-out-of-space')
|
|
158
|
+
expect(decideVerdict({ ...base, coverageComplete: false, activationFired: false })).toBe('quarantined-inactive')
|
|
159
|
+
expect(decideVerdict({ ...base, resolvedCount: 1, activationFired: false })).toBe('quarantined-inactive')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('parseStaircaseRow accepts a quarantined row with gen-5 split + activation fields', () => {
|
|
163
|
+
const row: StaircaseRow = {
|
|
164
|
+
schema: STAIRCASE_SCHEMA,
|
|
165
|
+
round: 4,
|
|
166
|
+
generation: 0,
|
|
167
|
+
runId: 'r4-x',
|
|
168
|
+
at: new Date(0).toISOString(),
|
|
169
|
+
candidate: 'hash',
|
|
170
|
+
candidateCommit: 'c'.repeat(40),
|
|
171
|
+
parent: 'baseline',
|
|
172
|
+
parentResolvedCount: 1,
|
|
173
|
+
changedFiles: [],
|
|
174
|
+
changeSpaceViolations: [],
|
|
175
|
+
perInstance: [],
|
|
176
|
+
resolvedCount: 3,
|
|
177
|
+
coverageComplete: true,
|
|
178
|
+
wallS: 10,
|
|
179
|
+
baselineWallS: 10,
|
|
180
|
+
costRatio: 1,
|
|
181
|
+
costGuardRatio: 1.2,
|
|
182
|
+
internallyPromoted: true,
|
|
183
|
+
verdict: 'quarantined-inactive',
|
|
184
|
+
holdout: 'operator-approval-required',
|
|
185
|
+
armProvenance: null,
|
|
186
|
+
diffPath: null,
|
|
187
|
+
diffSha256: null,
|
|
188
|
+
split: {
|
|
189
|
+
publicInstances: ['a', 'b'],
|
|
190
|
+
privateInstances: ['c'],
|
|
191
|
+
publicResolvedCount: 2,
|
|
192
|
+
privateResolvedCount: 1,
|
|
193
|
+
},
|
|
194
|
+
activation: { present: true, description: 'd', fired: false, evidence: [], warnings: [] },
|
|
195
|
+
}
|
|
196
|
+
expect(parseStaircaseRow(JSON.stringify(row))).toEqual(row)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Prefilter enforcement — a candidate without a parseable predicate is killed
|
|
202
|
+
// before any evaluation spend.
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
describe('activation-predicate prefilter', () => {
|
|
206
|
+
let loopsRepo: string
|
|
207
|
+
let outDir: string
|
|
208
|
+
let driverWt: string
|
|
209
|
+
|
|
210
|
+
beforeEach(async () => {
|
|
211
|
+
loopsRepo = await mkdtemp(join(tmpdir(), 'act-repo-'))
|
|
212
|
+
outDir = await mkdtemp(join(tmpdir(), 'act-out-'))
|
|
213
|
+
await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
|
|
214
|
+
await runOk('git', ['-C', loopsRepo, 'config', 'user.email', 't@t.dev'])
|
|
215
|
+
await runOk('git', ['-C', loopsRepo, 'config', 'user.name', 'T'])
|
|
216
|
+
await writeFile(join(loopsRepo, 'src.ts'), 'base\n')
|
|
217
|
+
await runOk('git', ['-C', loopsRepo, 'add', '-A'])
|
|
218
|
+
await runOk('git', ['-C', loopsRepo, 'commit', '-q', '-m', 'init'])
|
|
219
|
+
driverWt = join(outDir, 'driver-wt')
|
|
220
|
+
await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
afterEach(async () => {
|
|
224
|
+
await rm(outDir, { recursive: true, force: true })
|
|
225
|
+
await rm(loopsRepo, { recursive: true, force: true })
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const config = (proposers: ProposerSpec[]): OuterLoopConfig => ({
|
|
229
|
+
...defaultRound4Config(),
|
|
230
|
+
loopsRepo,
|
|
231
|
+
outDir,
|
|
232
|
+
populationSize: proposers.length,
|
|
233
|
+
proposers,
|
|
234
|
+
activationGate: true,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const generatorArgs = (candidateIndex: number) => ({
|
|
238
|
+
worktreePath: driverWt,
|
|
239
|
+
report: undefined,
|
|
240
|
+
findings: [] as AnalystFinding[],
|
|
241
|
+
maxShots: 1,
|
|
242
|
+
signal: new AbortController().signal,
|
|
243
|
+
generation: 0,
|
|
244
|
+
candidateIndex,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('kills a candidate without .improve/activation.json (stage activation-predicate) and passes one WITH it', async () => {
|
|
248
|
+
const proposers: ProposerSpec[] = [
|
|
249
|
+
{ name: 'with-predicate', harness: 'claude' },
|
|
250
|
+
{ name: 'without-predicate', harness: 'claude' },
|
|
251
|
+
{ name: 'invalid-predicate', harness: 'claude' },
|
|
252
|
+
]
|
|
253
|
+
const gen = fanOutLoopsGenerator(config(proposers), {
|
|
254
|
+
author: async (proposer, args) => {
|
|
255
|
+
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
256
|
+
await writeFile(join(args.worktreePath, 'extensions', 'pi', `${proposer.name}.ts`), 'x\n')
|
|
257
|
+
if (proposer.name === 'with-predicate') {
|
|
258
|
+
await mkdir(join(args.worktreePath, '.improve'), { recursive: true })
|
|
259
|
+
await writeFile(
|
|
260
|
+
join(args.worktreePath, ACTIVATION_PREDICATE_RELPATH),
|
|
261
|
+
JSON.stringify({ description: 'd', kind: 'grep', pattern: 'x' }),
|
|
262
|
+
)
|
|
263
|
+
} else if (proposer.name === 'invalid-predicate') {
|
|
264
|
+
await mkdir(join(args.worktreePath, '.improve'), { recursive: true })
|
|
265
|
+
await writeFile(join(args.worktreePath, ACTIVATION_PREDICATE_RELPATH), '{}')
|
|
266
|
+
}
|
|
267
|
+
return { applied: true, summary: `${proposer.name} edit` }
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const survivor = await gen.generate(generatorArgs(0))
|
|
272
|
+
const missing = await gen.generate(generatorArgs(1))
|
|
273
|
+
const invalid = await gen.generate(generatorArgs(2))
|
|
274
|
+
|
|
275
|
+
expect(survivor.applied).toBe(true)
|
|
276
|
+
expect(missing.applied).toBe(false)
|
|
277
|
+
expect(invalid.applied).toBe(false)
|
|
278
|
+
const kills = gen.drainPrefilterKills()
|
|
279
|
+
expect(kills).toHaveLength(2)
|
|
280
|
+
expect(kills.find((k) => k.proposer === 'without-predicate')).toMatchObject({ stage: 'activation-predicate' })
|
|
281
|
+
expect(kills.find((k) => k.proposer === 'without-predicate')!.reason).toContain('missing')
|
|
282
|
+
expect(kills.find((k) => k.proposer === 'invalid-predicate')!.reason).toContain('invalid')
|
|
283
|
+
// The survivor's predicate landed on the driver worktree with the patch.
|
|
284
|
+
expect(existsSync(join(driverWt, ACTIVATION_PREDICATE_RELPATH))).toBe(true)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('does not require a predicate when the gate is off (gen-4 behavior unchanged)', async () => {
|
|
288
|
+
const cfg = config([{ name: 'legacy', harness: 'claude' }])
|
|
289
|
+
cfg.activationGate = false
|
|
290
|
+
const gen = fanOutLoopsGenerator(cfg, {
|
|
291
|
+
author: async (_p, args) => {
|
|
292
|
+
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
293
|
+
await writeFile(join(args.worktreePath, 'extensions', 'pi', 'l.ts'), 'x\n')
|
|
294
|
+
return { applied: true, summary: 'edit' }
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
expect((await gen.generate(generatorArgs(0))).applied).toBe(true)
|
|
298
|
+
expect(gen.drainPrefilterKills()).toEqual([])
|
|
299
|
+
})
|
|
300
|
+
})
|
package/src/swe-arena/arms.ts
CHANGED
|
@@ -346,6 +346,14 @@ export interface ArmRunContext {
|
|
|
346
346
|
instanceId: string
|
|
347
347
|
image: string
|
|
348
348
|
baseCommit: string
|
|
349
|
+
/**
|
|
350
|
+
* Workspace materialization override. Default (undefined) = SWE instance
|
|
351
|
+
* image materialization (docker cp of /testbed). Factory instances inject
|
|
352
|
+
* their archive-export + synthetic-history materialization here so the arm
|
|
353
|
+
* runners themselves stay instance-kind-agnostic. Must leave `dest` a git
|
|
354
|
+
* repo whose HEAD is the diff base for patch extraction (`baseCommit`).
|
|
355
|
+
*/
|
|
356
|
+
materialize?: (dest: string) => Promise<void>
|
|
349
357
|
problemStatement: string
|
|
350
358
|
/** Self-repro verify command (bash -c, cwd = ws) — the MEASUREMENT gate. */
|
|
351
359
|
verifyCmd: string
|
|
@@ -448,13 +456,17 @@ export async function runSoloArm(spec: SoloArmSpec, ctx: ArmRunContext): Promise
|
|
|
448
456
|
const ws = join(runDir, 'ws')
|
|
449
457
|
await mkdir(runDir, { recursive: true })
|
|
450
458
|
const cell = await prepareIsolatedCellEnvironment(runDir)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
459
|
+
// A caller-supplied materializer (synthetic-history factory cells) replaces the
|
|
460
|
+
// SWE-bench image checkout, but still runs inside the isolated cell above.
|
|
461
|
+
if (ctx.materialize) await ctx.materialize(ws)
|
|
462
|
+
else
|
|
463
|
+
await materializeWorkspace({
|
|
464
|
+
instanceId: ctx.instanceId,
|
|
465
|
+
image: ctx.image,
|
|
466
|
+
baseCommit: ctx.baseCommit,
|
|
467
|
+
dest: ws,
|
|
468
|
+
signal: ctx.signal,
|
|
469
|
+
})
|
|
458
470
|
|
|
459
471
|
const promptFile = join(runDir, 'prompt.txt')
|
|
460
472
|
await writeFile(promptFile, ctx.problemStatement + (spec.promptSuffix ?? WORKER_PROMPT_SUFFIX))
|
|
@@ -578,13 +590,17 @@ export async function runSupervisorArm(spec: SupervisorArmSpec, ctx: ArmRunConte
|
|
|
578
590
|
const ws = join(runDir, 'ws')
|
|
579
591
|
await mkdir(runDir, { recursive: true })
|
|
580
592
|
const cell = await prepareIsolatedCellEnvironment(runDir)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
593
|
+
// A caller-supplied materializer (synthetic-history factory cells) replaces the
|
|
594
|
+
// SWE-bench image checkout, but still runs inside the isolated cell above.
|
|
595
|
+
if (ctx.materialize) await ctx.materialize(ws)
|
|
596
|
+
else
|
|
597
|
+
await materializeWorkspace({
|
|
598
|
+
instanceId: ctx.instanceId,
|
|
599
|
+
image: ctx.image,
|
|
600
|
+
baseCommit: ctx.baseCommit,
|
|
601
|
+
dest: ws,
|
|
602
|
+
signal: ctx.signal,
|
|
603
|
+
})
|
|
588
604
|
|
|
589
605
|
const paramsFile = join(runDir, 'params.json')
|
|
590
606
|
await writeFile(
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen-5 author briefing — MAP + TOOLBOX + PERMISSION (gen5_design).
|
|
3
|
+
*
|
|
4
|
+
* Principle: fully agentic evidence access, NO pre-digestion machinery. The
|
|
5
|
+
* dossier-compiler / ranked-briefing pipeline is dead; instead each run writes
|
|
6
|
+
* an INDEX FILE (a map: one line per evidence path) and the author prompt
|
|
7
|
+
* names the pre-existing TOOLS the author may drive itself, plus explicit
|
|
8
|
+
* permission to spawn its own subagents under a research budget. The
|
|
9
|
+
* 3-analyst diagnosis ensemble stays as ONE input among these — no longer the
|
|
10
|
+
* sole channel.
|
|
11
|
+
*
|
|
12
|
+
* SELF-IMPROVING BRIEFING: the briefing text itself lives in the declared
|
|
13
|
+
* change-space — `extensions/pi/author-briefing.md` in the loops repo. When
|
|
14
|
+
* that file exists at the incumbent ref it REPLACES the default text below,
|
|
15
|
+
* so a future generation's author can rewrite its own research instructions
|
|
16
|
+
* and the gate decides whether that rewrite earns its keep. The default here
|
|
17
|
+
* is the versioned fallback (`AUTHOR_BRIEFING_VERSION`).
|
|
18
|
+
*
|
|
19
|
+
* PUBLIC/PRIVATE INTERACTION: the index is proposer-visible text, so
|
|
20
|
+
* per-instance evidence rows for PRIVATE instances (score-split.mts) are
|
|
21
|
+
* excluded and the writer fails loud if a private id leaks into the rendered
|
|
22
|
+
* index.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { existsSync } from 'node:fs'
|
|
26
|
+
import { readdir, writeFile } from 'node:fs/promises'
|
|
27
|
+
import { join } from 'node:path'
|
|
28
|
+
import { leaksPrivateInstance, type ScoreSplit } from './score-split.mts'
|
|
29
|
+
import { run } from './proc.ts'
|
|
30
|
+
|
|
31
|
+
export const AUTHOR_BRIEFING_VERSION = 'map-toolbox-v1'
|
|
32
|
+
|
|
33
|
+
/** Loops-repo path (inside the declared change-space) whose content, when
|
|
34
|
+
* present at the incumbent ref, replaces the default briefing text. */
|
|
35
|
+
export const AUTHOR_BRIEFING_RELPATH = 'extensions/pi/author-briefing.md'
|
|
36
|
+
|
|
37
|
+
export const EVIDENCE_INDEX_FILENAME = 'evidence-index.md'
|
|
38
|
+
|
|
39
|
+
/** The default TOOLBOX + PERMISSION briefing. Every tool named here already
|
|
40
|
+
* exists — nothing bespoke is built for the author. */
|
|
41
|
+
export function defaultAuthorBriefing(): string {
|
|
42
|
+
return [
|
|
43
|
+
`<!-- author-briefing ${AUTHOR_BRIEFING_VERSION} (default; override by committing ${AUTHOR_BRIEFING_RELPATH}) -->`,
|
|
44
|
+
'RESEARCH BRIEFING — you have hands. Read the evidence yourself before editing.',
|
|
45
|
+
'',
|
|
46
|
+
'TOOLBOX (all pre-existing — drive them yourself):',
|
|
47
|
+
'- traces CLI (published): `npx --yes @tangle-network/traces@latest analyze --help` — analyze any',
|
|
48
|
+
' harness run trace (worker/supervisor sessions) for failure patterns, tool-call churn, dead ends.',
|
|
49
|
+
'- agent-eval trace analysts (`@tangle-network/agent-eval/analyst`): structured extraction over run',
|
|
50
|
+
' artifacts when you want findings objects rather than prose.',
|
|
51
|
+
'- AxLLM (`@ax-llm/ax`): question-answering over a corpus too large to read — point it at a run dir',
|
|
52
|
+
' and ask targeted questions instead of paging through megabytes.',
|
|
53
|
+
'- Plain grep/jq over the evidence map below — often the fastest tool.',
|
|
54
|
+
'',
|
|
55
|
+
'PERMISSION: you may spawn your own subagents (research fan-out) before writing any code.',
|
|
56
|
+
'Research budget: up to ~15 minutes / ~200k tokens of reading+subagents before your first edit;',
|
|
57
|
+
'spend it on the evidence paths in the map, not on re-deriving what they already record.',
|
|
58
|
+
'',
|
|
59
|
+
'The diagnosis findings in this prompt come from a 3-analyst ensemble. Treat them as ONE input',
|
|
60
|
+
'among the evidence sources above — verify any finding you build on against the raw artifacts.',
|
|
61
|
+
'',
|
|
62
|
+
`SELF-IMPROVEMENT: this briefing text is part of the change-space (${AUTHOR_BRIEFING_RELPATH}).`,
|
|
63
|
+
'If your research process was hampered by these instructions, you may edit that file in your',
|
|
64
|
+
'candidate alongside your main change; future generations will read your version.',
|
|
65
|
+
].join('\n')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve the briefing text: the change-space override at `ref` when present,
|
|
69
|
+
* else the versioned default. */
|
|
70
|
+
export async function resolveAuthorBriefing(
|
|
71
|
+
loopsRepo: string,
|
|
72
|
+
ref: string,
|
|
73
|
+
): Promise<{ text: string; source: 'change-space' | 'default' }> {
|
|
74
|
+
const show = await run('git', ['-C', loopsRepo, 'show', `${ref}:${AUTHOR_BRIEFING_RELPATH}`])
|
|
75
|
+
if (show.code === 0 && show.stdout.trim().length > 0) {
|
|
76
|
+
return { text: show.stdout, source: 'change-space' }
|
|
77
|
+
}
|
|
78
|
+
return { text: defaultAuthorBriefing(), source: 'default' }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// The evidence index — a MAP, not a briefing: one line per evidence path.
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
export interface EvidenceIndexRow {
|
|
86
|
+
path: string
|
|
87
|
+
note: string
|
|
88
|
+
/** Instance the row is specific to (private rows are dropped); null = run-level. */
|
|
89
|
+
iid: string | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface EvidenceIndexArgs {
|
|
93
|
+
outDir: string
|
|
94
|
+
/** Staircase + round-summary home. */
|
|
95
|
+
roundsDir: string
|
|
96
|
+
/** Prior-round failure artifacts (dir + optional patch), with instance ids. */
|
|
97
|
+
seedArtifactRuns: Array<{ iid: string; arm: string; dir: string; patchPath?: string }>
|
|
98
|
+
/** Prior run outDirs (e.g. the gen-4 outDir) whose arm-runs/judge evidence
|
|
99
|
+
* the author may mine. */
|
|
100
|
+
priorEvidenceDirs?: string[]
|
|
101
|
+
/** Pareto parent diffs materialized to disk (written by the outer loop). */
|
|
102
|
+
paretoParentPatches?: Array<{ label: string; path: string }>
|
|
103
|
+
/** Public/private split; null = everything is public. */
|
|
104
|
+
split: ScoreSplit | null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Collect the index rows. Pure over the filesystem — reads directory names
|
|
108
|
+
* only, never file contents. Private-instance rows are excluded. */
|
|
109
|
+
export async function collectEvidenceIndexRows(args: EvidenceIndexArgs): Promise<EvidenceIndexRow[]> {
|
|
110
|
+
const priv = new Set(args.split?.privateInstances ?? [])
|
|
111
|
+
const rows: EvidenceIndexRow[] = []
|
|
112
|
+
const push = (path: string, note: string, iid: string | null = null): void => {
|
|
113
|
+
if (iid !== null && priv.has(iid)) return
|
|
114
|
+
rows.push({ path, note, iid })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Staircase generations + round summaries (the improvement run's history).
|
|
118
|
+
for (const name of (await readdir(args.roundsDir).catch(() => [])).sort()) {
|
|
119
|
+
if (/^gen-\d+\.jsonl$/.test(name)) {
|
|
120
|
+
push(join(args.roundsDir, name), 'staircase rows — every prior candidate: diff, per-instance verdicts, kill reasons')
|
|
121
|
+
} else if (/^round\d+-summary-.*\.json$/.test(name)) {
|
|
122
|
+
push(join(args.roundsDir, name), 'round summary — baseline, winner, gate reasons, cost rollup')
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Prior-round seed artifacts (worker evidence, judge output, patches).
|
|
127
|
+
for (const seed of args.seedArtifactRuns) {
|
|
128
|
+
if (existsSync(seed.dir)) {
|
|
129
|
+
push(seed.dir, `prior ${seed.arm} run for ${seed.iid} — worker evidence, journal, driver log`, seed.iid)
|
|
130
|
+
}
|
|
131
|
+
if (seed.patchPath && existsSync(seed.patchPath)) {
|
|
132
|
+
push(seed.patchPath, `prior ${seed.arm} delivered patch for ${seed.iid}`, seed.iid)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Prior run outDirs: candidate diffs, arm runs (incl. judge.json near-miss
|
|
137
|
+
// details per instance), proposer shot receipts.
|
|
138
|
+
for (const dir of args.priorEvidenceDirs ?? []) {
|
|
139
|
+
if (!existsSync(dir)) continue
|
|
140
|
+
const candidates = join(dir, 'candidates')
|
|
141
|
+
if (existsSync(candidates)) push(candidates, 'prior-run candidate diffs (one .patch per surface)')
|
|
142
|
+
const armRuns = join(dir, 'arm-runs')
|
|
143
|
+
for (const tag of (await readdir(armRuns).catch(() => [])).sort()) {
|
|
144
|
+
for (const rep of (await readdir(join(armRuns, tag)).catch(() => [])).sort()) {
|
|
145
|
+
const runsRoot = join(armRuns, tag, rep, 'runs')
|
|
146
|
+
for (const iid of (await readdir(runsRoot).catch(() => [])).sort()) {
|
|
147
|
+
push(
|
|
148
|
+
join(runsRoot, iid),
|
|
149
|
+
`prior arm run ${tag} ${rep} on ${iid} — result.json, judge.json (near-miss detail), brain.jsonl, ws/.loops worker evidence`,
|
|
150
|
+
iid,
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const shots = join(dir, 'proposer-shots')
|
|
156
|
+
if (existsSync(shots)) push(shots, 'prior-run proposer shot receipts (what earlier authors tried)')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Pareto parent diffs.
|
|
160
|
+
for (const parent of args.paretoParentPatches ?? []) {
|
|
161
|
+
if (existsSync(parent.path)) push(parent.path, `pareto parent diff — ${parent.label}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return rows
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Render + write `<outDir>/evidence-index.md`. Fails loud if a private
|
|
168
|
+
* instance id leaks into the rendered text (never-surfaced invariant). */
|
|
169
|
+
export async function writeEvidenceIndex(args: EvidenceIndexArgs): Promise<{ path: string; rows: EvidenceIndexRow[] }> {
|
|
170
|
+
const rows = await collectEvidenceIndexRows(args)
|
|
171
|
+
const lines: string[] = [
|
|
172
|
+
`# Evidence map (${AUTHOR_BRIEFING_VERSION})`,
|
|
173
|
+
'',
|
|
174
|
+
'One line per evidence path. This is a MAP, not a digest — open what you need.',
|
|
175
|
+
...(args.split !== null
|
|
176
|
+
? [
|
|
177
|
+
'',
|
|
178
|
+
`NOTE: ${args.split.privateInstances.length} improvement instance(s) are PRIVATE this run: their`,
|
|
179
|
+
'identities and per-instance evidence are withheld from authors, but your candidate is still',
|
|
180
|
+
'selected on the full public+private set. Do not overfit the visible instances.',
|
|
181
|
+
]
|
|
182
|
+
: []),
|
|
183
|
+
'',
|
|
184
|
+
...rows.map((r) => `- ${r.path} — ${r.note}`),
|
|
185
|
+
'',
|
|
186
|
+
]
|
|
187
|
+
const text = lines.join('\n')
|
|
188
|
+
if (args.split !== null) {
|
|
189
|
+
const leaks = leaksPrivateInstance(text, args.split)
|
|
190
|
+
if (leaks.length > 0) {
|
|
191
|
+
throw new Error(`evidence-index: private instance id(s) leaked into the index: ${leaks.join(', ')}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const path = join(args.outDir, EVIDENCE_INDEX_FILENAME)
|
|
195
|
+
await writeFile(path, text)
|
|
196
|
+
return { path, rows }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// The prompt section (MAP + TOOLBOX + PERMISSION), appended per author.
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
export interface BriefingContext {
|
|
204
|
+
indexPath: string
|
|
205
|
+
briefingText: string
|
|
206
|
+
briefingSource: 'change-space' | 'default'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function briefingPromptSection(ctx: BriefingContext): string {
|
|
210
|
+
return [
|
|
211
|
+
`EVIDENCE MAP: ${ctx.indexPath}`,
|
|
212
|
+
'Read it first — one line per evidence path (staircase history, prior arm runs + judge near-miss',
|
|
213
|
+
'details, worker evidence, parent diffs). Open the paths you need; nothing is pre-digested for you.',
|
|
214
|
+
'',
|
|
215
|
+
ctx.briefingText.trimEnd(),
|
|
216
|
+
].join('\n')
|
|
217
|
+
}
|