@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.
Files changed (265) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/dist/adapters.js +24 -24
  4. package/dist/benchmarks/_harness.d.ts +1 -1
  5. package/dist/benchmarks/_harness.js +1 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/humaneval.d.ts +10 -1
  16. package/dist/benchmarks/humaneval.js +5 -3
  17. package/dist/benchmarks/nomiracl.js +2 -2
  18. package/dist/benchmarks/open-rag-bench.js +2 -2
  19. package/dist/benchmarks/programbench.js +2 -2
  20. package/dist/benchmarks/ragbench.js +2 -2
  21. package/dist/benchmarks/swe-bench.js +2 -2
  22. package/dist/benchmarks/t2-ragbench.js +2 -2
  23. package/dist/benchmarks/tau-bench-shared.js +2 -2
  24. package/dist/benchmarks/tau2-bench.js +3 -3
  25. package/dist/benchmarks/tau3-banking.js +3 -3
  26. package/dist/benchmarks/terminal-bench.js +2 -2
  27. package/dist/benchmarks/toollm.js +2 -2
  28. package/dist/benchmarks/webarena-verified.js +2 -2
  29. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  30. package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
  31. package/dist/chunk-5H5XV76F.js.map +1 -0
  32. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  33. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  34. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  35. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  36. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  37. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  38. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  39. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  40. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  41. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  42. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  43. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  44. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  45. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  46. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  47. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  48. package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
  49. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  50. package/dist/chunk-WSKWVEQB.js.map +1 -0
  51. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  52. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  53. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  54. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  55. package/dist/index.js +24 -24
  56. package/package.json +6 -5
  57. package/scripts/run-package-tests.mjs +30 -8
  58. package/scripts/verify-packed-consumer.mjs +1 -1
  59. package/scripts/verify-pier-agent.mts +1 -0
  60. package/src/benchmarks/_harness.ts +20 -2
  61. package/src/benchmarks/humaneval.test.mts +122 -0
  62. package/src/benchmarks/humaneval.ts +100 -27
  63. package/src/david-attribution.mts +78 -0
  64. package/src/david-goliath.mts +149 -0
  65. package/src/hev-improve.mts +25 -6
  66. package/src/humaneval-object-ablation.mts +201 -0
  67. package/src/live-improve-campaign-mbpp.mts +641 -0
  68. package/src/live-improve-campaign.mts +500 -0
  69. package/src/mbpp-structural.mts +12 -7
  70. package/src/quant-arena/README.md +144 -0
  71. package/src/quant-arena/backtest.test.mts +135 -0
  72. package/src/quant-arena/backtest.ts +218 -0
  73. package/src/quant-arena/data.test.mts +44 -0
  74. package/src/quant-arena/data.ts +141 -0
  75. package/src/quant-arena/driver.test.mts +253 -0
  76. package/src/quant-arena/driver.ts +219 -0
  77. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  78. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  83. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  84. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  85. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  86. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  87. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  88. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  89. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  94. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  95. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  96. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  97. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  98. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  99. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  100. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  101. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  102. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  103. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  104. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  105. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  106. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  107. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  108. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  109. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  110. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  111. package/src/quant-arena/holdout-certify.mts +206 -0
  112. package/src/quant-arena/holdout-certify.test.mts +82 -0
  113. package/src/quant-arena/leak-audit.test.mts +79 -0
  114. package/src/quant-arena/leak-audit.ts +95 -0
  115. package/src/quant-arena/make-fixtures.mts +161 -0
  116. package/src/quant-arena/multiplicity.test.mts +68 -0
  117. package/src/quant-arena/multiplicity.ts +87 -0
  118. package/src/quant-arena/nautilus-certify.ts +31 -0
  119. package/src/quant-arena/oms.ts +90 -0
  120. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  121. package/src/quant-arena/python/pyproject.toml +8 -0
  122. package/src/quant-arena/python/uv.lock +1297 -0
  123. package/src/quant-arena/python/vbt-worker.py +192 -0
  124. package/src/quant-arena/quant-loop.mts +813 -0
  125. package/src/quant-arena/quant-loop.test.mts +75 -0
  126. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  127. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  128. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  129. package/src/quant-arena/types.ts +133 -0
  130. package/src/quant-arena/vbt-client.ts +321 -0
  131. package/src/quant-arena/vbt-parity.test.mts +183 -0
  132. package/src/quant-arena/windows.test.mts +45 -0
  133. package/src/quant-arena/windows.ts +54 -0
  134. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  135. package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
  136. package/src/rollout-ledger/settle-capture.mts +442 -0
  137. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  138. package/src/stream-observe.py +45 -0
  139. package/src/stream-observe.tpl.html +247 -0
  140. package/src/supervisor-arena.mts +816 -0
  141. package/src/swe-arena/activation.mts +228 -0
  142. package/src/swe-arena/activation.test.mts +303 -0
  143. package/src/swe-arena/analyze.ts +211 -0
  144. package/src/swe-arena/arms.ts +804 -0
  145. package/src/swe-arena/bootstrap-meta.mts +188 -0
  146. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  147. package/src/swe-arena/briefing.mts +217 -0
  148. package/src/swe-arena/briefing.test.mts +178 -0
  149. package/src/swe-arena/calibrate.ts +217 -0
  150. package/src/swe-arena/capabilities.mts +76 -0
  151. package/src/swe-arena/capabilities.test.mts +57 -0
  152. package/src/swe-arena/capacity.ts +194 -0
  153. package/src/swe-arena/cell-evidence.mts +437 -0
  154. package/src/swe-arena/cell-evidence.test.mts +248 -0
  155. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  156. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  157. package/src/swe-arena/execution.test.mts +1170 -0
  158. package/src/swe-arena/factory-command-container.ts +284 -0
  159. package/src/swe-arena/factory-judge-child.mts +228 -0
  160. package/src/swe-arena/factory.test.mts +643 -0
  161. package/src/swe-arena/fixtures/analyze.py +80 -0
  162. package/src/swe-arena/fixtures/excludes.txt +8 -0
  163. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  164. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  165. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  166. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  167. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  168. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  169. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  170. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  171. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  172. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  173. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  174. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  175. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  176. package/src/swe-arena/fixtures/holdout.json +44 -0
  177. package/src/swe-arena/fixtures/instances.json +146 -0
  178. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  179. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  180. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  181. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  182. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  183. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  184. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  185. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  186. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  187. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  188. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  189. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  190. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  191. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  192. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  193. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  194. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  195. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  196. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  197. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  198. package/src/swe-arena/fixtures.ts +237 -0
  199. package/src/swe-arena/gepa-seat.mts +583 -0
  200. package/src/swe-arena/gepa-seat.test.mts +635 -0
  201. package/src/swe-arena/holdout-certify.mts +408 -0
  202. package/src/swe-arena/holdout-certify.test.mts +160 -0
  203. package/src/swe-arena/judge-child.mts +37 -0
  204. package/src/swe-arena/ledger-orphans.mts +77 -0
  205. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  206. package/src/swe-arena/lineage-record.mts +164 -0
  207. package/src/swe-arena/lineage-record.test.mts +115 -0
  208. package/src/swe-arena/manifest.mts +293 -0
  209. package/src/swe-arena/manifest.test.mts +169 -0
  210. package/src/swe-arena/materialize.ts +142 -0
  211. package/src/swe-arena/outer-loop.mts +2795 -0
  212. package/src/swe-arena/outer-loop.test.mts +696 -0
  213. package/src/swe-arena/parity.test.mts +87 -0
  214. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  215. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  216. package/src/swe-arena/proc.test.mts +174 -0
  217. package/src/swe-arena/proc.ts +260 -0
  218. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  219. package/src/swe-arena/proposer-fanout.mts +770 -0
  220. package/src/swe-arena/proposer-fanout.test.mts +619 -0
  221. package/src/swe-arena/proposer-provenance.mts +177 -0
  222. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  223. package/src/swe-arena/reconcile.ts +0 -0
  224. package/src/swe-arena/replay.mts +183 -0
  225. package/src/swe-arena/replay.test.mts +300 -0
  226. package/src/swe-arena/run-experiment.mts +727 -0
  227. package/src/swe-arena/run-report.mts +75 -0
  228. package/src/swe-arena/run-supervisor.mjs +297 -0
  229. package/src/swe-arena/run-supervisor.test.mts +500 -0
  230. package/src/swe-arena/score-split.mts +140 -0
  231. package/src/swe-arena/score-split.test.mts +123 -0
  232. package/src/swe-arena/serialized-judge.ts +414 -0
  233. package/src/swe-arena/types.ts +218 -0
  234. package/src/swe-code-improve.mts +328 -0
  235. package/src/swe-emit-patch.mts +104 -0
  236. package/src/swe-improve.mts +232 -0
  237. package/src/swe-jail.ts +2 -2
  238. package/src/swe-local-proof.mts +169 -0
  239. package/src/swe-repro-calibrate.mts +446 -0
  240. package/src/swe-stream.mts +1497 -0
  241. package/src/swe-structural.mts +245 -837
  242. package/dist/chunk-LRRD7NAG.js.map +0 -1
  243. package/dist/chunk-PPYSEKFM.js.map +0 -1
  244. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  245. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  246. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  247. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  248. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  249. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  250. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  251. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  252. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  253. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  254. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  255. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  256. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  257. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  258. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  259. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  260. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  261. /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
  262. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  263. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  264. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  265. /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Metadata bootstrap — regenerates every per-instance input the experiment
3
+ * used to hand-generate into its scratchpad (`task-meta.json` via
4
+ * `load_meta.py`) straight from the SWE-bench Verified dataset through the
5
+ * bench adapter. The HF dataset cache under ~/.cache survives host reboots;
6
+ * the scratchpad does not — this module removes that scratch dependency
7
+ * permanently (proven necessary: a reboot wiped /tmp and took task-meta.json,
8
+ * the verify scripts, and the round configs with it).
9
+ *
10
+ * tsx src/swe-arena/bootstrap-meta.mts --task-meta <out.json> [iid...]
11
+ * tsx src/swe-arena/bootstrap-meta.mts --problems <outDir> [iid...]
12
+ * tsx src/swe-arena/bootstrap-meta.mts --calibrate <verifyDir> <workDir> [iid...]
13
+ *
14
+ * `iid...` defaults to the round-4 improvement set (defaultRound4Config).
15
+ *
16
+ * HONESTY FIREWALL: `--problems` writes ONLY problem statements — the mode a
17
+ * verify-script author is allowed to read. Gold patches surface ONLY on the
18
+ * `--calibrate` path, where calibrate.ts applies them MECHANICALLY (git apply
19
+ * / patch --fuzz=3) and the official judge grades them; no author-facing
20
+ * output ever contains patch/test_patch/FAIL_TO_PASS content.
21
+ */
22
+
23
+ import { mkdir, writeFile } from 'node:fs/promises'
24
+ import { join, resolve } from 'node:path'
25
+ import { pathToFileURL } from 'node:url'
26
+ import { createSweBenchAdapter } from '../benchmarks/swe-bench.ts'
27
+ import { calibrateInstance, type CalibrationResult } from './calibrate.ts'
28
+ import { defaultRound4Config } from './outer-loop.mts'
29
+ import { loadInstanceImages } from './run-experiment.mts'
30
+ import { createSerializedJudge } from './serialized-judge.ts'
31
+ import type { SweInstance } from './types.ts'
32
+
33
+ /** Validate one adapter task-metadata record into the SweInstance shape the
34
+ * harness consumes. Throws on any missing/empty load-bearing field — a blank
35
+ * problem statement or gold patch must never flow silently into a run. */
36
+ export function assertSweInstance(id: string, metadata: Record<string, unknown> | undefined): SweInstance {
37
+ if (!metadata) throw new Error(`bootstrap-meta: ${id} has no metadata on its adapter task`)
38
+ const str = (key: keyof SweInstance): string => {
39
+ const v = metadata[key]
40
+ if (typeof v !== 'string' || v.length === 0) {
41
+ throw new Error(`bootstrap-meta: ${id} has a missing/empty ${String(key)}`)
42
+ }
43
+ return v
44
+ }
45
+ const optStr = (key: keyof SweInstance): string | null => {
46
+ const v = metadata[key]
47
+ return typeof v === 'string' && v.length > 0 ? v : null
48
+ }
49
+ const instance: SweInstance = {
50
+ instance_id: str('instance_id'),
51
+ repo: str('repo'),
52
+ base_commit: str('base_commit'),
53
+ problem_statement: str('problem_statement'),
54
+ patch: str('patch'),
55
+ test_patch: str('test_patch'),
56
+ FAIL_TO_PASS: str('FAIL_TO_PASS'),
57
+ PASS_TO_PASS: str('PASS_TO_PASS'),
58
+ version: optStr('version'),
59
+ environment_setup_commit: optStr('environment_setup_commit'),
60
+ }
61
+ if (instance.instance_id !== id) {
62
+ throw new Error(`bootstrap-meta: metadata instance_id ${instance.instance_id} != requested ${id}`)
63
+ }
64
+ return instance
65
+ }
66
+
67
+ /** One dataset load for all ids, via the adapter (single source of truth). */
68
+ export async function loadSweInstances(ids: string[]): Promise<Map<string, SweInstance>> {
69
+ if (ids.length === 0) throw new Error('bootstrap-meta: empty instance list')
70
+ const adapter = createSweBenchAdapter()
71
+ const tasks = await adapter.loadTasks({ ids, split: 'test' })
72
+ const byId = new Map(tasks.map((t) => [t.id, t]))
73
+ const out = new Map<string, SweInstance>()
74
+ for (const id of ids) {
75
+ const task = byId.get(id)
76
+ if (!task) throw new Error(`bootstrap-meta: ${id} not found in SWE-bench_Verified`)
77
+ out.set(id, assertSweInstance(id, task.metadata as Record<string, unknown> | undefined))
78
+ }
79
+ return out
80
+ }
81
+
82
+ /** task-meta.json — the experiment's exact shape: `{ [iid]: SweInstance }`. */
83
+ export async function writeTaskMeta(ids: string[], outPath: string): Promise<void> {
84
+ const instances = await loadSweInstances(ids)
85
+ await writeFile(outPath, JSON.stringify(Object.fromEntries(instances), null, 1) + '\n')
86
+ }
87
+
88
+ /** <outDir>/<iid>.problem.md — problem statements ONLY (author-safe). */
89
+ export async function writeProblemStatements(ids: string[], outDir: string): Promise<string[]> {
90
+ const instances = await loadSweInstances(ids)
91
+ await mkdir(outDir, { recursive: true })
92
+ const written: string[] = []
93
+ for (const [id, inst] of instances) {
94
+ const path = join(outDir, `${id}.problem.md`)
95
+ await writeFile(path, inst.problem_statement)
96
+ written.push(path)
97
+ }
98
+ return written
99
+ }
100
+
101
+ /** Dual-calibrate each instance through the M2 path: repro base-fail/gold-pass
102
+ * plus the official-judge gold gate. Gold patches come from the adapter and
103
+ * are only ever applied mechanically. */
104
+ export async function calibrateIds(
105
+ ids: string[],
106
+ opts: { verifyDir: string; workDir: string; instanceImagesPath?: string; keepWorkspaces?: boolean },
107
+ ): Promise<CalibrationResult[]> {
108
+ const instances = await loadSweInstances(ids)
109
+ const images = await loadInstanceImages(opts.instanceImagesPath)
110
+ const judge = createSerializedJudge()
111
+ await mkdir(opts.workDir, { recursive: true })
112
+ const results: CalibrationResult[] = []
113
+ for (const id of ids) {
114
+ const inst = instances.get(id)!
115
+ const entry = images[id]
116
+ if (!entry) throw new Error(`bootstrap-meta: ${id} has no image mapping (fixtures/instances.json)`)
117
+ // verifyCmd runs with cwd = the materialized WORKSPACE (and, downstream,
118
+ // worker clones) — the script path must be absolute or bash exits 127.
119
+ const result = await calibrateInstance({
120
+ instanceId: id,
121
+ image: entry.image,
122
+ baseCommit: entry.base_commit,
123
+ goldPatch: inst.patch,
124
+ verifyCmd: `bash ${resolve(opts.verifyDir, `${id}.sh`)}`,
125
+ workDir: opts.workDir,
126
+ judge,
127
+ ...(opts.keepWorkspaces !== undefined ? { keepWorkspaces: opts.keepWorkspaces } : {}),
128
+ })
129
+ results.push(result)
130
+ console.log(
131
+ `CALIBRATION ${id}: baseRc=${result.baseRc} goldApplyRc=${result.goldApplyRc} goldRc=${result.goldRc} ` +
132
+ `verifyCalibrated=${result.verifyCalibrated} goldOfficialResolved=${result.goldOfficialResolved} ` +
133
+ `experimentValid=${result.experimentValid}`,
134
+ )
135
+ }
136
+ return results
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // CLI.
141
+ // ---------------------------------------------------------------------------
142
+
143
+ const isMain = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href
144
+
145
+ if (isMain) {
146
+ const [mode, ...rest] = process.argv.slice(2)
147
+ const defaultIds = defaultRound4Config().instances
148
+ if (mode === '--task-meta') {
149
+ const [outPath, ...ids] = rest
150
+ if (!outPath) {
151
+ console.error('usage: bootstrap-meta.mts --task-meta <out.json> [iid...]')
152
+ process.exit(2)
153
+ }
154
+ await writeTaskMeta(ids.length > 0 ? ids : defaultIds, outPath)
155
+ console.log(`task-meta → ${outPath}`)
156
+ } else if (mode === '--problems') {
157
+ const [outDir, ...ids] = rest
158
+ if (!outDir) {
159
+ console.error('usage: bootstrap-meta.mts --problems <outDir> [iid...]')
160
+ process.exit(2)
161
+ }
162
+ for (const p of await writeProblemStatements(ids.length > 0 ? ids : defaultIds, outDir)) {
163
+ console.log(`problem statement → ${p}`)
164
+ }
165
+ } else if (mode === '--calibrate') {
166
+ const [verifyDir, workDir, ...ids] = rest
167
+ if (!verifyDir || !workDir) {
168
+ console.error('usage: bootstrap-meta.mts --calibrate <verifyDir> <workDir> [iid...]')
169
+ process.exit(2)
170
+ }
171
+ const results = await calibrateIds(ids.length > 0 ? ids : defaultIds, { verifyDir, workDir })
172
+ const summaryPath = join(workDir, 'calibration.json')
173
+ await writeFile(summaryPath, JSON.stringify(results, null, 2) + '\n')
174
+ console.log(`calibration summary → ${summaryPath}`)
175
+ const invalid = results.filter((r) => !r.experimentValid)
176
+ if (invalid.length > 0) {
177
+ console.error(`NOT experiment-valid: ${invalid.map((r) => r.iid).join(', ')}`)
178
+ process.exit(1)
179
+ }
180
+ } else {
181
+ console.error(
182
+ 'usage: tsx src/swe-arena/bootstrap-meta.mts --task-meta <out.json> [iid...]\n' +
183
+ ' tsx src/swe-arena/bootstrap-meta.mts --problems <outDir> [iid...]\n' +
184
+ ' tsx src/swe-arena/bootstrap-meta.mts --calibrate <verifyDir> <workDir> [iid...]',
185
+ )
186
+ process.exit(2)
187
+ }
188
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unit tests for the metadata bootstrap's validation layer — pure, no dataset
3
+ * load. The adapter-backed loaders are exercised by the calibration CLI.
4
+ */
5
+
6
+ import { describe, expect, it } from 'vitest'
7
+ import { assertSweInstance } from './bootstrap-meta.mts'
8
+
9
+ const full = {
10
+ instance_id: 'django__django-11532',
11
+ repo: 'django/django',
12
+ base_commit: 'a5308514fb4bc5086c9a16a8a24a945eeebb073c',
13
+ problem_statement: 'Email messages crash on non-ASCII domain…',
14
+ patch: 'diff --git a/django/core/mail/utils.py …',
15
+ test_patch: 'diff --git a/tests/mail/tests.py …',
16
+ FAIL_TO_PASS: '["test_unicode_dns (mail.tests.MailTests)"]',
17
+ PASS_TO_PASS: '["test_ascii (mail.tests.MailTests)"]',
18
+ version: '3.0',
19
+ environment_setup_commit: '419a78300f7cd27611196e1e464d50fd0385ff27',
20
+ }
21
+
22
+ describe('assertSweInstance', () => {
23
+ it('accepts a complete metadata record and returns the typed instance', () => {
24
+ const inst = assertSweInstance('django__django-11532', full)
25
+ expect(inst.instance_id).toBe('django__django-11532')
26
+ expect(inst.patch).toContain('diff --git')
27
+ expect(inst.version).toBe('3.0')
28
+ })
29
+
30
+ it('normalizes absent optional fields to null', () => {
31
+ const inst = assertSweInstance('django__django-11532', {
32
+ ...full,
33
+ version: undefined,
34
+ environment_setup_commit: '',
35
+ })
36
+ expect(inst.version).toBeNull()
37
+ expect(inst.environment_setup_commit).toBeNull()
38
+ })
39
+
40
+ it('throws on a missing/empty load-bearing field (never a silent blank)', () => {
41
+ expect(() => assertSweInstance('django__django-11532', { ...full, problem_statement: '' })).toThrow(
42
+ /problem_statement/,
43
+ )
44
+ expect(() => assertSweInstance('django__django-11532', { ...full, patch: undefined })).toThrow(/patch/)
45
+ expect(() => assertSweInstance('django__django-11532', undefined)).toThrow(/no metadata/)
46
+ })
47
+
48
+ it('throws on an instance_id mismatch', () => {
49
+ expect(() => assertSweInstance('astropy__astropy-13033', full)).toThrow(/instance_id/)
50
+ })
51
+ })
@@ -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
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Gen-5 MAP+TOOLBOX briefing: evidence-index generation (one line per
3
+ * evidence path, private instances excluded, leak guard fail-loud), the
4
+ * toolbox/permission prompt section, and the change-space briefing override.
5
+ */
6
+
7
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
8
+ import { existsSync } from 'node:fs'
9
+ import { tmpdir } from 'node:os'
10
+ import { join } from 'node:path'
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
12
+ import {
13
+ AUTHOR_BRIEFING_RELPATH,
14
+ AUTHOR_BRIEFING_VERSION,
15
+ briefingPromptSection,
16
+ collectEvidenceIndexRows,
17
+ defaultAuthorBriefing,
18
+ EVIDENCE_INDEX_FILENAME,
19
+ resolveAuthorBriefing,
20
+ writeEvidenceIndex,
21
+ } from './briefing.mts'
22
+ import { splitInstances } from './score-split.mts'
23
+ import { runOk } from './proc.ts'
24
+
25
+ const SIX = [
26
+ 'astropy__astropy-13033',
27
+ 'django__django-11532',
28
+ 'matplotlib__matplotlib-20826',
29
+ 'pydata__xarray-4687',
30
+ 'pytest-dev__pytest-6197',
31
+ 'sphinx-doc__sphinx-9658',
32
+ ]
33
+
34
+ describe('evidence index', () => {
35
+ let root: string
36
+ let outDir: string
37
+ let roundsDir: string
38
+ let priorDir: string
39
+ const split = splitInstances('r4-seed', SIX, 4)
40
+ const publicIid = split.publicInstances[0]!
41
+ const privateIid = split.privateInstances[0]!
42
+
43
+ beforeEach(async () => {
44
+ root = await mkdtemp(join(tmpdir(), 'briefing-'))
45
+ outDir = join(root, 'out')
46
+ roundsDir = join(root, 'rounds')
47
+ priorDir = join(root, 'gen4')
48
+ await mkdir(outDir, { recursive: true })
49
+ await mkdir(roundsDir, { recursive: true })
50
+ await writeFile(join(roundsDir, 'gen-0.jsonl'), '{}\n')
51
+ await writeFile(join(roundsDir, 'round4-summary-r4-x.json'), '{}\n')
52
+ // Seed artifact dirs: one public, one private instance.
53
+ for (const iid of [publicIid, privateIid]) {
54
+ await mkdir(join(root, 'runs', iid, 'SUP4'), { recursive: true })
55
+ await writeFile(join(root, 'patches', `${iid}.sup4.patch`), 'p\n').catch(async () => {
56
+ await mkdir(join(root, 'patches'), { recursive: true })
57
+ await writeFile(join(root, 'patches', `${iid}.sup4.patch`), 'p\n')
58
+ })
59
+ }
60
+ // Prior run outDir with arm-run judge evidence for a public + a private instance.
61
+ for (const iid of [publicIid, privateIid]) {
62
+ const runDir = join(priorDir, 'arm-runs', 'cand111111', 'rep-0', 'runs', iid)
63
+ await mkdir(runDir, { recursive: true })
64
+ await writeFile(join(runDir, 'judge.json'), '{}\n')
65
+ }
66
+ await mkdir(join(priorDir, 'candidates'), { recursive: true })
67
+ await mkdir(join(priorDir, 'proposer-shots'), { recursive: true })
68
+ })
69
+
70
+ afterEach(async () => {
71
+ await rm(root, { recursive: true, force: true })
72
+ })
73
+
74
+ const args = () => ({
75
+ outDir,
76
+ roundsDir,
77
+ seedArtifactRuns: [publicIid, privateIid].map((iid) => ({
78
+ iid,
79
+ arm: 'SUP4',
80
+ dir: join(root, 'runs', iid, 'SUP4'),
81
+ patchPath: join(root, 'patches', `${iid}.sup4.patch`),
82
+ })),
83
+ priorEvidenceDirs: [priorDir],
84
+ paretoParentPatches: [] as Array<{ label: string; path: string }>,
85
+ split,
86
+ })
87
+
88
+ it('lists one line per evidence path: staircase, summaries, seed runs, prior arm runs, shot receipts', async () => {
89
+ const { path, rows } = await writeEvidenceIndex(args())
90
+ expect(path).toBe(join(outDir, EVIDENCE_INDEX_FILENAME))
91
+ expect(existsSync(path)).toBe(true)
92
+ const text = await readFile(path, 'utf8')
93
+ expect(text).toContain(join(roundsDir, 'gen-0.jsonl'))
94
+ expect(text).toContain(join(roundsDir, 'round4-summary-r4-x.json'))
95
+ expect(text).toContain(join(root, 'runs', publicIid, 'SUP4'))
96
+ expect(text).toContain(join(root, 'patches', `${publicIid}.sup4.patch`))
97
+ expect(text).toContain(join(priorDir, 'candidates'))
98
+ expect(text).toContain(join(priorDir, 'proposer-shots'))
99
+ expect(text).toContain(join(priorDir, 'arm-runs', 'cand111111', 'rep-0', 'runs', publicIid))
100
+ // One markdown bullet per row.
101
+ expect(text.split('\n').filter((l) => l.startsWith('- ')).length).toBe(rows.length)
102
+ })
103
+
104
+ it('NEVER surfaces private instances: no private row, no private id anywhere in the rendered text', async () => {
105
+ const { path, rows } = await writeEvidenceIndex(args())
106
+ const text = await readFile(path, 'utf8')
107
+ for (const iid of split.privateInstances) expect(text).not.toContain(iid)
108
+ expect(rows.every((r) => r.iid === null || !split.privateInstances.includes(r.iid))).toBe(true)
109
+ // The withholding itself is disclosed (count only, no identities).
110
+ expect(text).toContain('2 improvement instance(s) are PRIVATE')
111
+ })
112
+
113
+ it('fails loud when a run-level path would leak a private id into the rendered index', async () => {
114
+ const leakPatch = join(outDir, `${privateIid}.parent.patch`)
115
+ await writeFile(leakPatch, 'x\n')
116
+ await expect(
117
+ writeEvidenceIndex({ ...args(), paretoParentPatches: [{ label: 'leaky', path: leakPatch }] }),
118
+ ).rejects.toThrow(/leaked/)
119
+ })
120
+
121
+ it('collectEvidenceIndexRows includes everything when no split is configured', async () => {
122
+ const rows = await collectEvidenceIndexRows({ ...args(), split: null })
123
+ const iids = rows.map((r) => r.iid).filter((i): i is string => i !== null)
124
+ expect(iids).toContain(privateIid)
125
+ })
126
+ })
127
+
128
+ describe('briefing text', () => {
129
+ it('default briefing names the toolbox (traces CLI, trace analysts, AxLLM) and grants subagent permission with a budget', () => {
130
+ const text = defaultAuthorBriefing()
131
+ expect(text).toContain('npx --yes @tangle-network/traces@latest')
132
+ expect(text).toContain('agent-eval trace analysts')
133
+ expect(text).toContain('AxLLM')
134
+ expect(text).toContain('spawn your own subagents')
135
+ expect(text).toContain('Research budget')
136
+ expect(text).toContain('ONE input')
137
+ expect(text).toContain(AUTHOR_BRIEFING_RELPATH)
138
+ expect(text).toContain(AUTHOR_BRIEFING_VERSION)
139
+ })
140
+
141
+ it('briefingPromptSection leads with the evidence map path', () => {
142
+ const section = briefingPromptSection({
143
+ indexPath: '/tmp/x/evidence-index.md',
144
+ briefingText: defaultAuthorBriefing(),
145
+ briefingSource: 'default',
146
+ })
147
+ expect(section).toContain('EVIDENCE MAP: /tmp/x/evidence-index.md')
148
+ expect(section.indexOf('EVIDENCE MAP')).toBeLessThan(section.indexOf('TOOLBOX'))
149
+ })
150
+
151
+ it('resolveAuthorBriefing prefers the change-space file at the ref (authors may rewrite their own briefing)', async () => {
152
+ const repo = await mkdtemp(join(tmpdir(), 'briefing-repo-'))
153
+ try {
154
+ await runOk('git', ['init', '-q', '-b', 'main', repo])
155
+ await runOk('git', ['-C', repo, 'config', 'user.email', 't@t.dev'])
156
+ await runOk('git', ['-C', repo, 'config', 'user.name', 'T'])
157
+ await writeFile(join(repo, 'base.txt'), 'x\n')
158
+ await runOk('git', ['-C', repo, 'add', '-A'])
159
+ await runOk('git', ['-C', repo, 'commit', '-q', '-m', 'init'])
160
+
161
+ // No override committed → the versioned default.
162
+ const fallback = await resolveAuthorBriefing(repo, 'main')
163
+ expect(fallback.source).toBe('default')
164
+ expect(fallback.text).toBe(defaultAuthorBriefing())
165
+
166
+ // Commit the change-space override → it wins.
167
+ await mkdir(join(repo, 'extensions', 'pi'), { recursive: true })
168
+ await writeFile(join(repo, AUTHOR_BRIEFING_RELPATH), 'MY OWN RESEARCH RULES\n')
169
+ await runOk('git', ['-C', repo, 'add', '-A'])
170
+ await runOk('git', ['-C', repo, 'commit', '-q', '-m', 'briefing override'])
171
+ const override = await resolveAuthorBriefing(repo, 'main')
172
+ expect(override.source).toBe('change-space')
173
+ expect(override.text).toBe('MY OWN RESEARCH RULES\n')
174
+ } finally {
175
+ await rm(repo, { recursive: true, force: true })
176
+ }
177
+ })
178
+ })