@prismatic-io/lux 0.0.2-preview.14 → 0.0.2-preview.16

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 (388) hide show
  1. package/lib/answerers/claude-code/index.d.ts +14 -2
  2. package/lib/answerers/claude-code/index.d.ts.map +1 -1
  3. package/lib/answerers/claude-code/index.js +3 -3
  4. package/lib/answerers/claude-code/index.js.map +1 -1
  5. package/lib/answerers/persona/index.d.ts +28 -2
  6. package/lib/answerers/persona/index.d.ts.map +1 -1
  7. package/lib/answerers/persona/index.js +3 -3
  8. package/lib/answerers/persona/index.js.map +1 -1
  9. package/lib/answerers/terminal/index.d.ts +8 -2
  10. package/lib/answerers/terminal/index.d.ts.map +1 -1
  11. package/lib/answerers/terminal/index.js +3 -2
  12. package/lib/answerers/terminal/index.js.map +1 -1
  13. package/lib/assertions/core/resource-checks.d.ts.map +1 -1
  14. package/lib/assertions/core/resource-checks.js +3 -5
  15. package/lib/assertions/core/resource-checks.js.map +1 -1
  16. package/lib/assertions/rubric/index.d.ts.map +1 -1
  17. package/lib/assertions/rubric/index.js +7 -1
  18. package/lib/assertions/rubric/index.js.map +1 -1
  19. package/lib/assertions/rubric/internal.d.ts +16 -5
  20. package/lib/assertions/rubric/internal.d.ts.map +1 -1
  21. package/lib/assertions/rubric/internal.js +10 -6
  22. package/lib/assertions/rubric/internal.js.map +1 -1
  23. package/lib/authoring.d.ts +32 -2
  24. package/lib/authoring.d.ts.map +1 -1
  25. package/lib/authoring.js +33 -1
  26. package/lib/authoring.js.map +1 -1
  27. package/lib/cli/bin.js +2 -13
  28. package/lib/cli/bin.js.map +1 -1
  29. package/lib/cli/command-runtime.d.ts +3 -3
  30. package/lib/cli/command-runtime.d.ts.map +1 -1
  31. package/lib/cli/command-runtime.js +17 -6
  32. package/lib/cli/command-runtime.js.map +1 -1
  33. package/lib/cli/init-templates.d.ts +11 -0
  34. package/lib/cli/init-templates.d.ts.map +1 -0
  35. package/lib/cli/init-templates.js +207 -0
  36. package/lib/cli/init-templates.js.map +1 -0
  37. package/lib/cli/init.d.ts +7 -0
  38. package/lib/cli/init.d.ts.map +1 -1
  39. package/lib/cli/init.js +23 -173
  40. package/lib/cli/init.js.map +1 -1
  41. package/lib/cli/output-schemas.d.ts +1399 -0
  42. package/lib/cli/output-schemas.d.ts.map +1 -0
  43. package/lib/cli/output-schemas.js +91 -0
  44. package/lib/cli/output-schemas.js.map +1 -0
  45. package/lib/cli/program.d.ts +135 -4
  46. package/lib/cli/program.d.ts.map +1 -1
  47. package/lib/cli/program.js +725 -520
  48. package/lib/cli/program.js.map +1 -1
  49. package/lib/cli/render/reporter.d.ts.map +1 -1
  50. package/lib/cli/render/reporter.js +24 -4
  51. package/lib/cli/render/reporter.js.map +1 -1
  52. package/lib/cli/run-options.d.ts +0 -4
  53. package/lib/cli/run-options.d.ts.map +1 -1
  54. package/lib/cli/run-options.js +8 -19
  55. package/lib/cli/run-options.js.map +1 -1
  56. package/lib/cli/view.d.ts +5 -3
  57. package/lib/cli/view.d.ts.map +1 -1
  58. package/lib/cli/view.js +120 -78
  59. package/lib/cli/view.js.map +1 -1
  60. package/lib/core/annotation.d.ts +6 -2
  61. package/lib/core/annotation.d.ts.map +1 -1
  62. package/lib/core/annotation.js +2 -1
  63. package/lib/core/annotation.js.map +1 -1
  64. package/lib/core/answerer.d.ts +12 -0
  65. package/lib/core/answerer.d.ts.map +1 -1
  66. package/lib/core/answerer.js +2 -0
  67. package/lib/core/answerer.js.map +1 -1
  68. package/lib/core/assertion.d.ts +3 -0
  69. package/lib/core/assertion.d.ts.map +1 -1
  70. package/lib/core/assertion.js +3 -0
  71. package/lib/core/assertion.js.map +1 -1
  72. package/lib/core/case.d.ts +24 -2
  73. package/lib/core/case.d.ts.map +1 -1
  74. package/lib/core/case.js +5 -1
  75. package/lib/core/case.js.map +1 -1
  76. package/lib/core/driver.d.ts +40 -0
  77. package/lib/core/driver.d.ts.map +1 -1
  78. package/lib/core/driver.js +12 -0
  79. package/lib/core/driver.js.map +1 -1
  80. package/lib/core/environment.d.ts +18 -0
  81. package/lib/core/environment.d.ts.map +1 -0
  82. package/lib/core/environment.js +21 -0
  83. package/lib/core/environment.js.map +1 -0
  84. package/lib/core/evaluation-clusters.d.ts +17 -0
  85. package/lib/core/evaluation-clusters.d.ts.map +1 -0
  86. package/lib/core/evaluation-clusters.js +23 -0
  87. package/lib/core/evaluation-clusters.js.map +1 -0
  88. package/lib/core/experiment.d.ts +11 -0
  89. package/lib/core/experiment.d.ts.map +1 -1
  90. package/lib/core/experiment.js +31 -17
  91. package/lib/core/experiment.js.map +1 -1
  92. package/lib/core/file-tree.d.ts +7 -0
  93. package/lib/core/file-tree.d.ts.map +1 -0
  94. package/lib/core/file-tree.js +35 -0
  95. package/lib/core/file-tree.js.map +1 -0
  96. package/lib/core/index.d.ts +1 -0
  97. package/lib/core/index.d.ts.map +1 -1
  98. package/lib/core/index.js +1 -0
  99. package/lib/core/index.js.map +1 -1
  100. package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
  101. package/lib/core/lifecycle-fixtures.js +4 -2
  102. package/lib/core/lifecycle-fixtures.js.map +1 -1
  103. package/lib/core/platform-process.d.ts +5 -1
  104. package/lib/core/platform-process.d.ts.map +1 -1
  105. package/lib/core/platform-process.js +48 -10
  106. package/lib/core/platform-process.js.map +1 -1
  107. package/lib/core/run.d.ts +93 -1
  108. package/lib/core/run.d.ts.map +1 -1
  109. package/lib/core/run.js +16 -0
  110. package/lib/core/run.js.map +1 -1
  111. package/lib/core/usage.d.ts +5 -0
  112. package/lib/core/usage.d.ts.map +1 -1
  113. package/lib/core/usage.js +3 -0
  114. package/lib/core/usage.js.map +1 -1
  115. package/lib/drivers/claude-code/index.d.ts +65 -2
  116. package/lib/drivers/claude-code/index.d.ts.map +1 -1
  117. package/lib/drivers/claude-code/index.js +30 -3
  118. package/lib/drivers/claude-code/index.js.map +1 -1
  119. package/lib/drivers/codex/app-events.d.ts.map +1 -1
  120. package/lib/drivers/codex/app-events.js +41 -30
  121. package/lib/drivers/codex/app-events.js.map +1 -1
  122. package/lib/drivers/codex/exec-events.d.ts +4 -0
  123. package/lib/drivers/codex/exec-events.d.ts.map +1 -0
  124. package/lib/drivers/codex/exec-events.js +230 -0
  125. package/lib/drivers/codex/exec-events.js.map +1 -0
  126. package/lib/drivers/codex/index.d.ts +76 -4
  127. package/lib/drivers/codex/index.d.ts.map +1 -1
  128. package/lib/drivers/codex/index.js +35 -232
  129. package/lib/drivers/codex/index.js.map +1 -1
  130. package/lib/drivers/cursor/acp-transport.d.ts +38 -0
  131. package/lib/drivers/cursor/acp-transport.d.ts.map +1 -0
  132. package/lib/drivers/cursor/acp-transport.js +152 -0
  133. package/lib/drivers/cursor/acp-transport.js.map +1 -0
  134. package/lib/drivers/cursor/config.d.ts +22 -0
  135. package/lib/drivers/cursor/config.d.ts.map +1 -0
  136. package/lib/drivers/cursor/config.js +26 -0
  137. package/lib/drivers/cursor/config.js.map +1 -0
  138. package/lib/drivers/cursor/events.d.ts +13 -0
  139. package/lib/drivers/cursor/events.d.ts.map +1 -0
  140. package/lib/drivers/cursor/events.js +73 -0
  141. package/lib/drivers/cursor/events.js.map +1 -0
  142. package/lib/drivers/cursor/index.d.ts +37 -0
  143. package/lib/drivers/cursor/index.d.ts.map +1 -0
  144. package/lib/drivers/cursor/index.js +276 -0
  145. package/lib/drivers/cursor/index.js.map +1 -0
  146. package/lib/drivers/cursor/interaction.d.ts +7 -0
  147. package/lib/drivers/cursor/interaction.d.ts.map +1 -0
  148. package/lib/drivers/cursor/interaction.js +66 -0
  149. package/lib/drivers/cursor/interaction.js.map +1 -0
  150. package/lib/drivers/mcp/index.d.ts +35 -2
  151. package/lib/drivers/mcp/index.d.ts.map +1 -1
  152. package/lib/drivers/mcp/index.js +16 -3
  153. package/lib/drivers/mcp/index.js.map +1 -1
  154. package/lib/drivers/shared/experiment-behavior.d.ts +13 -0
  155. package/lib/drivers/shared/experiment-behavior.d.ts.map +1 -0
  156. package/lib/drivers/shared/experiment-behavior.js +197 -0
  157. package/lib/drivers/shared/experiment-behavior.js.map +1 -0
  158. package/lib/drivers/shared/experiment-policy.d.ts +4 -0
  159. package/lib/drivers/shared/experiment-policy.d.ts.map +1 -0
  160. package/lib/drivers/shared/experiment-policy.js +143 -0
  161. package/lib/drivers/shared/experiment-policy.js.map +1 -0
  162. package/lib/drivers/subprocess/index.d.ts +37 -2
  163. package/lib/drivers/subprocess/index.d.ts.map +1 -1
  164. package/lib/drivers/subprocess/index.js +46 -6
  165. package/lib/drivers/subprocess/index.js.map +1 -1
  166. package/lib/index.d.ts +6 -5
  167. package/lib/index.d.ts.map +1 -1
  168. package/lib/index.js +4 -3
  169. package/lib/index.js.map +1 -1
  170. package/lib/orchestrator/annotation-loader.d.ts +6 -2
  171. package/lib/orchestrator/annotation-loader.d.ts.map +1 -1
  172. package/lib/orchestrator/annotation-loader.js +8 -0
  173. package/lib/orchestrator/annotation-loader.js.map +1 -1
  174. package/lib/orchestrator/annotation-store.d.ts.map +1 -1
  175. package/lib/orchestrator/annotation-store.js +16 -2
  176. package/lib/orchestrator/annotation-store.js.map +1 -1
  177. package/lib/orchestrator/campaign-lifecycle.d.ts +1 -0
  178. package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -1
  179. package/lib/orchestrator/campaign-lifecycle.js +4 -1
  180. package/lib/orchestrator/campaign-lifecycle.js.map +1 -1
  181. package/lib/orchestrator/compare.d.ts +4 -0
  182. package/lib/orchestrator/compare.d.ts.map +1 -1
  183. package/lib/orchestrator/compare.js +13 -1
  184. package/lib/orchestrator/compare.js.map +1 -1
  185. package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
  186. package/lib/orchestrator/comparison-identity.js +1 -0
  187. package/lib/orchestrator/comparison-identity.js.map +1 -1
  188. package/lib/orchestrator/config.d.ts +42 -0
  189. package/lib/orchestrator/config.d.ts.map +1 -1
  190. package/lib/orchestrator/config.js +6 -2
  191. package/lib/orchestrator/config.js.map +1 -1
  192. package/lib/orchestrator/discover.d.ts +1 -0
  193. package/lib/orchestrator/discover.d.ts.map +1 -1
  194. package/lib/orchestrator/discover.js +7 -1
  195. package/lib/orchestrator/discover.js.map +1 -1
  196. package/lib/orchestrator/doctor.d.ts.map +1 -1
  197. package/lib/orchestrator/doctor.js +34 -26
  198. package/lib/orchestrator/doctor.js.map +1 -1
  199. package/lib/orchestrator/driver-capabilities.d.ts +3 -0
  200. package/lib/orchestrator/driver-capabilities.d.ts.map +1 -0
  201. package/lib/orchestrator/driver-capabilities.js +10 -0
  202. package/lib/orchestrator/driver-capabilities.js.map +1 -0
  203. package/lib/orchestrator/driver-identity.d.ts +5 -0
  204. package/lib/orchestrator/driver-identity.d.ts.map +1 -0
  205. package/lib/orchestrator/driver-identity.js +41 -0
  206. package/lib/orchestrator/driver-identity.js.map +1 -0
  207. package/lib/orchestrator/experiment-context.d.ts +10 -0
  208. package/lib/orchestrator/experiment-context.d.ts.map +1 -1
  209. package/lib/orchestrator/experiment-contracts.d.ts +10 -0
  210. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
  211. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
  212. package/lib/orchestrator/experiment-corpus.js +17 -19
  213. package/lib/orchestrator/experiment-corpus.js.map +1 -1
  214. package/lib/orchestrator/experiment-effects.d.ts +33 -0
  215. package/lib/orchestrator/experiment-effects.d.ts.map +1 -0
  216. package/lib/orchestrator/experiment-effects.js +44 -0
  217. package/lib/orchestrator/experiment-effects.js.map +1 -0
  218. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
  219. package/lib/orchestrator/experiment-evaluation.js +36 -3
  220. package/lib/orchestrator/experiment-evaluation.js.map +1 -1
  221. package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
  222. package/lib/orchestrator/experiment-identity.js +18 -29
  223. package/lib/orchestrator/experiment-identity.js.map +1 -1
  224. package/lib/orchestrator/experiment-loader.d.ts +7 -2
  225. package/lib/orchestrator/experiment-loader.d.ts.map +1 -1
  226. package/lib/orchestrator/experiment-report.d.ts +70 -0
  227. package/lib/orchestrator/experiment-report.d.ts.map +1 -1
  228. package/lib/orchestrator/experiment-report.js +9 -1
  229. package/lib/orchestrator/experiment-report.js.map +1 -1
  230. package/lib/orchestrator/experiment-runs.d.ts +22 -1
  231. package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
  232. package/lib/orchestrator/experiment-runs.js +13 -5
  233. package/lib/orchestrator/experiment-runs.js.map +1 -1
  234. package/lib/orchestrator/experiment-runtime-identity.d.ts +0 -16
  235. package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -1
  236. package/lib/orchestrator/experiment-runtime-identity.js +5 -220
  237. package/lib/orchestrator/experiment-runtime-identity.js.map +1 -1
  238. package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
  239. package/lib/orchestrator/experiment-selection.js +52 -0
  240. package/lib/orchestrator/experiment-selection.js.map +1 -1
  241. package/lib/orchestrator/experiment.d.ts +0 -2
  242. package/lib/orchestrator/experiment.d.ts.map +1 -1
  243. package/lib/orchestrator/experiment.js +23 -15
  244. package/lib/orchestrator/experiment.js.map +1 -1
  245. package/lib/orchestrator/fs-read.d.ts +1 -6
  246. package/lib/orchestrator/fs-read.d.ts.map +1 -1
  247. package/lib/orchestrator/fs-read.js +2 -32
  248. package/lib/orchestrator/fs-read.js.map +1 -1
  249. package/lib/orchestrator/grader-experiment.d.ts +17 -1
  250. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  251. package/lib/orchestrator/grader-experiment.js +52 -7
  252. package/lib/orchestrator/grader-experiment.js.map +1 -1
  253. package/lib/orchestrator/grading.d.ts.map +1 -1
  254. package/lib/orchestrator/grading.js +13 -6
  255. package/lib/orchestrator/grading.js.map +1 -1
  256. package/lib/orchestrator/list-runs.d.ts +1 -0
  257. package/lib/orchestrator/list-runs.d.ts.map +1 -1
  258. package/lib/orchestrator/list-runs.js +27 -6
  259. package/lib/orchestrator/list-runs.js.map +1 -1
  260. package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -1
  261. package/lib/orchestrator/optimization-lifecycle.js +3 -1
  262. package/lib/orchestrator/optimization-lifecycle.js.map +1 -1
  263. package/lib/orchestrator/orchestrator.d.ts +6 -1
  264. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  265. package/lib/orchestrator/orchestrator.js +17 -143
  266. package/lib/orchestrator/orchestrator.js.map +1 -1
  267. package/lib/orchestrator/paired-effects.d.ts +34 -0
  268. package/lib/orchestrator/paired-effects.d.ts.map +1 -0
  269. package/lib/orchestrator/paired-effects.js +158 -0
  270. package/lib/orchestrator/paired-effects.js.map +1 -0
  271. package/lib/orchestrator/provenance.d.ts +5 -1
  272. package/lib/orchestrator/provenance.d.ts.map +1 -1
  273. package/lib/orchestrator/provenance.js +16 -3
  274. package/lib/orchestrator/provenance.js.map +1 -1
  275. package/lib/orchestrator/regrade.d.ts +1 -0
  276. package/lib/orchestrator/regrade.d.ts.map +1 -1
  277. package/lib/orchestrator/run-execution.d.ts +2 -0
  278. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  279. package/lib/orchestrator/run-execution.js +18 -3
  280. package/lib/orchestrator/run-execution.js.map +1 -1
  281. package/lib/orchestrator/run-lifecycle.d.ts +1 -0
  282. package/lib/orchestrator/run-lifecycle.d.ts.map +1 -1
  283. package/lib/orchestrator/run-lifecycle.js +3 -2
  284. package/lib/orchestrator/run-lifecycle.js.map +1 -1
  285. package/lib/orchestrator/suite-runs.d.ts +3 -1
  286. package/lib/orchestrator/suite-runs.d.ts.map +1 -1
  287. package/lib/orchestrator/suite-runs.js +12 -2
  288. package/lib/orchestrator/suite-runs.js.map +1 -1
  289. package/lib/orchestrator/suite-summary.d.ts.map +1 -1
  290. package/lib/orchestrator/suite-summary.js +2 -1
  291. package/lib/orchestrator/suite-summary.js.map +1 -1
  292. package/lib/previewer/server.d.ts +6 -0
  293. package/lib/previewer/server.d.ts.map +1 -1
  294. package/lib/previewer/server.js +21 -5
  295. package/lib/previewer/server.js.map +1 -1
  296. package/package.json +2 -2
  297. package/skills/lux/references/cli.md +5 -2
  298. package/skills/lux/references/eval-authoring.md +0 -7
  299. package/skills/lux-answerer/SKILL.md +1 -1
  300. package/src/answerers/claude-code/index.ts +3 -3
  301. package/src/answerers/persona/index.ts +3 -3
  302. package/src/answerers/terminal/index.ts +4 -9
  303. package/src/assertions/core/resource-checks.ts +3 -5
  304. package/src/assertions/rubric/index.ts +7 -1
  305. package/src/assertions/rubric/internal.ts +13 -8
  306. package/src/authoring.ts +331 -2
  307. package/src/cli/bin.ts +2 -12
  308. package/src/cli/command-runtime.ts +20 -9
  309. package/src/cli/init-templates.ts +223 -0
  310. package/src/cli/init.ts +38 -184
  311. package/src/cli/output-schemas.ts +106 -0
  312. package/src/cli/program.ts +621 -548
  313. package/src/cli/render/reporter.ts +25 -4
  314. package/src/cli/run-options.ts +8 -25
  315. package/src/cli/view.ts +45 -84
  316. package/src/core/annotation.ts +2 -1
  317. package/src/core/answerer.ts +9 -0
  318. package/src/core/assertion.ts +3 -0
  319. package/src/core/case.ts +5 -1
  320. package/src/core/driver.ts +38 -0
  321. package/src/core/environment.ts +22 -0
  322. package/src/core/evaluation-clusters.ts +35 -0
  323. package/src/core/experiment.ts +60 -23
  324. package/src/core/file-tree.ts +35 -0
  325. package/src/core/index.ts +1 -0
  326. package/src/core/lifecycle-fixtures.ts +4 -2
  327. package/src/core/platform-process.ts +54 -6
  328. package/src/core/run.ts +20 -0
  329. package/src/core/usage.ts +8 -0
  330. package/src/drivers/claude-code/index.ts +34 -3
  331. package/src/drivers/codex/app-events.ts +43 -33
  332. package/src/drivers/codex/exec-events.ts +258 -0
  333. package/src/drivers/codex/index.ts +37 -262
  334. package/src/drivers/cursor/README.md +57 -0
  335. package/src/drivers/cursor/acp-transport.ts +182 -0
  336. package/src/drivers/cursor/config.ts +29 -0
  337. package/src/drivers/cursor/events.ts +83 -0
  338. package/src/drivers/cursor/index.ts +316 -0
  339. package/src/drivers/cursor/interaction.ts +90 -0
  340. package/src/drivers/mcp/index.ts +16 -3
  341. package/src/drivers/shared/experiment-behavior.ts +214 -0
  342. package/src/drivers/shared/experiment-policy.ts +195 -0
  343. package/src/drivers/subprocess/index.ts +45 -6
  344. package/src/index.ts +16 -5
  345. package/src/orchestrator/annotation-loader.ts +10 -0
  346. package/src/orchestrator/annotation-store.ts +24 -3
  347. package/src/orchestrator/campaign-lifecycle.ts +5 -1
  348. package/src/orchestrator/compare.ts +16 -1
  349. package/src/orchestrator/comparison-identity.ts +1 -0
  350. package/src/orchestrator/config.ts +6 -2
  351. package/src/orchestrator/discover.ts +8 -1
  352. package/src/orchestrator/doctor.ts +40 -22
  353. package/src/orchestrator/driver-capabilities.ts +16 -0
  354. package/src/orchestrator/driver-identity.ts +59 -0
  355. package/src/orchestrator/experiment-corpus.ts +23 -28
  356. package/src/orchestrator/experiment-effects.ts +61 -0
  357. package/src/orchestrator/experiment-evaluation.ts +45 -5
  358. package/src/orchestrator/experiment-identity.ts +27 -36
  359. package/src/orchestrator/experiment-report.ts +21 -1
  360. package/src/orchestrator/experiment-runs.ts +11 -4
  361. package/src/orchestrator/experiment-runtime-identity.ts +5 -243
  362. package/src/orchestrator/experiment-selection.ts +77 -0
  363. package/src/orchestrator/experiment.ts +26 -24
  364. package/src/orchestrator/fs-read.ts +2 -32
  365. package/src/orchestrator/grader-experiment.ts +72 -8
  366. package/src/orchestrator/grading.ts +15 -6
  367. package/src/orchestrator/list-runs.ts +29 -6
  368. package/src/orchestrator/optimization-lifecycle.ts +4 -1
  369. package/src/orchestrator/orchestrator.ts +26 -183
  370. package/src/orchestrator/paired-effects.ts +209 -0
  371. package/src/orchestrator/provenance.ts +23 -3
  372. package/src/orchestrator/run-execution.ts +18 -3
  373. package/src/orchestrator/run-lifecycle.ts +4 -2
  374. package/src/orchestrator/suite-runs.ts +10 -1
  375. package/src/orchestrator/suite-summary.ts +2 -1
  376. package/src/previewer/server.ts +27 -5
  377. package/viewer/app.js +27 -6
  378. package/viewer/detail.js +22 -5
  379. package/lib/answerers/scripted/index.d.ts +0 -18
  380. package/lib/answerers/scripted/index.d.ts.map +0 -1
  381. package/lib/answerers/scripted/index.js +0 -63
  382. package/lib/answerers/scripted/index.js.map +0 -1
  383. package/lib/cli/skills.d.ts +0 -38
  384. package/lib/cli/skills.d.ts.map +0 -1
  385. package/lib/cli/skills.js +0 -84
  386. package/lib/cli/skills.js.map +0 -1
  387. package/src/answerers/scripted/index.ts +0 -78
  388. package/src/cli/skills.ts +0 -129
@@ -17,6 +17,7 @@ import {
17
17
  stableJson,
18
18
  totalTokens,
19
19
  } from "../core/index.js";
20
+ import { hasAgentUsage } from "../core/usage.js";
20
21
  import { stableDriverIdentity } from "./comparison-identity.js";
21
22
  import { safeReadJson, walkFiles } from "./fs-read.js";
22
23
 
@@ -59,6 +60,7 @@ export type RunComparison = {
59
60
  b: { ask: number; approve: number; custom: number };
60
61
  };
61
62
  usage: { a: RunUsage; b: RunUsage };
63
+ agentUsageAvailable?: { a: boolean; b: boolean };
62
64
  } | null;
63
65
  grading: {
64
66
  casePassedA: boolean | null;
@@ -232,6 +234,11 @@ export const compareRuns = async (input: ComparisonInput): Promise<RunComparison
232
234
  if (stableJson(runtimeA) !== stableJson(runtimeB)) {
233
235
  caseMismatch.push({ field: "runtime", a: runtimeA, b: runtimeB });
234
236
  }
237
+ const environmentA = a.runJson?.provenance?.environment;
238
+ const environmentB = b.runJson?.provenance?.environment;
239
+ if (stableJson(environmentA) !== stableJson(environmentB)) {
240
+ caseMismatch.push({ field: "environment", a: environmentA, b: environmentB });
241
+ }
235
242
  const sourceA = a.runJson?.provenance?.source;
236
243
  const sourceB = b.runJson?.provenance?.source;
237
244
  const sourceHashUnavailable =
@@ -282,6 +289,10 @@ export const compareRuns = async (input: ComparisonInput): Promise<RunComparison
282
289
  a: a.runJson?.interruptCounts ?? { ask: 0, approve: 0, custom: 0 },
283
290
  b: b.runJson?.interruptCounts ?? { ask: 0, approve: 0, custom: 0 },
284
291
  },
292
+ agentUsageAvailable: {
293
+ a: a.runJson !== null && hasAgentUsage(a.runJson),
294
+ b: b.runJson !== null && hasAgentUsage(b.runJson),
295
+ },
285
296
  usage: {
286
297
  a: a.runJson?.usage ?? emptyRunUsage(),
287
298
  b: b.runJson?.usage ?? emptyRunUsage(),
@@ -350,7 +361,11 @@ export const formatComparisonText = (cmp: RunComparison): string => {
350
361
  );
351
362
  const au = cmp.runDelta.usage.a;
352
363
  const bu = cmp.runDelta.usage.b;
353
- lines.push(` agent tokens: ${totalTokens(au.agent)} → ${totalTokens(bu.agent)}`);
364
+ const agentA =
365
+ cmp.runDelta.agentUsageAvailable?.a === false ? "unavailable" : totalTokens(au.agent);
366
+ const agentB =
367
+ cmp.runDelta.agentUsageAvailable?.b === false ? "unavailable" : totalTokens(bu.agent);
368
+ lines.push(` agent tokens: ${agentA} → ${agentB}`);
354
369
  lines.push(
355
370
  ` harness tokens: ${totalTokens(harnessUsage(au))} → ${totalTokens(harnessUsage(bu))}`,
356
371
  );
@@ -31,6 +31,7 @@ export const runComparisonIdentity = (metadata: RunMetadata): string =>
31
31
  valueHash({
32
32
  source: sourceIdentity(metadata),
33
33
  runtime: metadata.provenance?.runtime,
34
+ environment: metadata.provenance?.environment,
34
35
  caseHash: metadata.provenance?.caseHash,
35
36
  assertionsHash: metadata.provenance?.assertionsHash,
36
37
  fixturesHash: metadata.provenance?.fixturesHash,
@@ -4,11 +4,11 @@ import { pathToFileURL } from "node:url";
4
4
  import { z } from "zod";
5
5
  import { claudeCodeAnswerer } from "../answerers/claude-code/index.js";
6
6
  import { personaAnswerer } from "../answerers/persona/index.js";
7
- import { scriptedAnswerer } from "../answerers/scripted/index.js";
8
7
  import { terminalAnswerer } from "../answerers/terminal/index.js";
9
8
  import { allCoreAssertions } from "../assertions/core/index.js";
10
9
  import { allMcpAssertions } from "../assertions/mcp/index.js";
11
10
  import { createRubricAssertion } from "../assertions/rubric/index.js";
11
+ import { EnvironmentDeclarationSchema } from "../core/environment.js";
12
12
  import {
13
13
  type AnswererPlugin,
14
14
  AnswererRefSchema,
@@ -25,6 +25,7 @@ import {
25
25
  } from "../core/index.js";
26
26
  import { claudeCodeDriver } from "../drivers/claude-code/index.js";
27
27
  import { codexDriver } from "../drivers/codex/index.js";
28
+ import { cursorDriver } from "../drivers/cursor/index.js";
28
29
  import { mcpProbeDriver } from "../drivers/mcp/index.js";
29
30
  import { subprocessDriver } from "../drivers/subprocess/index.js";
30
31
 
@@ -35,6 +36,7 @@ const pluginLike = z.looseObject({
35
36
 
36
37
  export const ExperimentProfileSchema = z
37
38
  .object({
39
+ environment: EnvironmentDeclarationSchema.optional(),
38
40
  driver: DriverRefSchema.optional(),
39
41
  answerer: AnswererRefSchema.optional(),
40
42
  harness: HarnessModelConfigSchema.optional(),
@@ -70,6 +72,7 @@ const LifecycleHooksPluginSchema = z.custom<LifecycleHooksPlugin>().superRefine(
70
72
 
71
73
  const LuxConfigSchema = z
72
74
  .object({
75
+ environment: EnvironmentDeclarationSchema.optional(),
73
76
  casesRoot: z.string().optional(),
74
77
  fixturesRoot: z.string().optional(),
75
78
  runsRoot: z.string().default(".lux-runs"),
@@ -165,6 +168,7 @@ export const loadLuxConfig = async (cwd: string = process.cwd()): Promise<Resolv
165
168
  ...(validated.defaultDriver ? { defaultDriver: validated.defaultDriver } : {}),
166
169
  ...(validated.harness ? { harness: validated.harness } : {}),
167
170
  concurrency: validated.concurrency,
171
+ ...(validated.environment ? { environment: validated.environment } : {}),
168
172
  ...(validated.profiles ? { profiles: validated.profiles } : {}),
169
173
  ...(validated.drivers ? { drivers: validated.drivers } : {}),
170
174
  ...(validated.answerers ? { answerers: validated.answerers } : {}),
@@ -176,13 +180,13 @@ export const loadLuxConfig = async (cwd: string = process.cwd()): Promise<Resolv
176
180
  const builtinDrivers: DriverPlugin<never>[] = [
177
181
  claudeCodeDriver,
178
182
  codexDriver,
183
+ cursorDriver,
179
184
  subprocessDriver,
180
185
  mcpProbeDriver,
181
186
  ];
182
187
  const builtinAnswerers: AnswererPlugin<never>[] = [
183
188
  personaAnswerer,
184
189
  claudeCodeAnswerer,
185
- scriptedAnswerer,
186
190
  terminalAnswerer,
187
191
  ];
188
192
  export const buildRegistryFromConfig = (config: ResolvedLuxConfig): Registry => {
@@ -13,6 +13,7 @@ export type DiscoveredCase = {
13
13
  relPath: string;
14
14
  /** `meta.tags` normalized to strings; empty when unset. */
15
15
  tags: string[];
16
+ cluster?: string;
16
17
  };
17
18
 
18
19
  /**
@@ -96,7 +97,13 @@ export const discoverCases = async (root: string): Promise<DiscoveryResult> => {
96
97
  problems.push({ path, reason: formatZodIssues(parsed.error) });
97
98
  continue;
98
99
  }
99
- cases.push({ path, id: parsed.data.id ?? relPath, relPath, tags: tagsOf(parsed.data.meta) });
100
+ cases.push({
101
+ path,
102
+ id: parsed.data.id ?? relPath,
103
+ relPath,
104
+ tags: tagsOf(parsed.data.meta),
105
+ ...(parsed.data.meta?.cluster ? { cluster: parsed.data.meta.cluster } : {}),
106
+ });
100
107
  }
101
108
  return { cases, problems };
102
109
  };
@@ -8,12 +8,14 @@ import type { EvalCase, Registry } from "../core/index.js";
8
8
  import { registryForEvalCase } from "./case-extensions.js";
9
9
  import { buildRegistryFromConfig, loadLuxConfig, type ResolvedLuxConfig } from "./config.js";
10
10
  import { type DiscoveredCase, type DiscoveryResult, discoverCases } from "./discover.js";
11
- import { cliIdentity, ExperimentCampaignPlanSchema, planExperimentCampaign } from "./experiment.js";
11
+ import { validateDriverCapabilities } from "./driver-capabilities.js";
12
+ import { ExperimentCampaignPlanSchema, planExperimentCampaign } from "./experiment.js";
12
13
  import {
13
14
  experimentCampaignMode,
14
15
  loadExperimentCampaign,
15
16
  resolveExperimentCampaignPath,
16
17
  } from "./experiment-loader.js";
18
+ import { cliIdentity } from "./experiment-runtime-identity.js";
17
19
  import { assertionConfigForSchema } from "./grading.js";
18
20
  import { loadEvalCase } from "./loader.js";
19
21
  import { validatePluginConfig, validatePluginConfigAsync } from "./validate-plugin-config.js";
@@ -142,17 +144,20 @@ const caseDriverReferences = (local: unknown, config: ResolvedLuxConfig): unknow
142
144
  ];
143
145
  };
144
146
 
145
- const driverCommand = (reference: unknown): string | undefined => {
147
+ const driverCommands = (reference: unknown, registry: Registry): string[] => {
146
148
  const name = refName(reference);
147
- const config = refConfig(reference);
148
- if (name === "claude-code") {
149
- return typeof config.command === "string" ? config.command : "claude";
150
- }
151
- if (name === "codex") return typeof config.command === "string" ? config.command : "codex";
152
- if (name === "subprocess") {
153
- return typeof config.command === "string" ? config.command : undefined;
149
+ const plugin = name ? registry.getDriver(name) : undefined;
150
+ if (!plugin?.runtime) return [];
151
+ try {
152
+ const config = validatePluginConfig(
153
+ plugin.configSchema,
154
+ rawRefConfig(reference),
155
+ `driver '${name}'`,
156
+ );
157
+ return plugin.runtime(config).commands;
158
+ } catch {
159
+ return [];
154
160
  }
155
- return undefined;
156
161
  };
157
162
 
158
163
  const answererCommand = (reference: unknown): string | undefined => {
@@ -179,12 +184,13 @@ const rubricCommands = (assertion: unknown, config: ResolvedLuxConfig): string[]
179
184
  return [...new Set(commands)];
180
185
  };
181
186
 
182
- const configuredCliTargets = (config: ResolvedLuxConfig): CliTarget[] => {
187
+ const configuredCliTargets = (config: ResolvedLuxConfig, registry: Registry): CliTarget[] => {
183
188
  const targets: CliTarget[] = [];
184
189
  const add = (label: string, command: string | undefined): void => {
185
190
  if (command) targets.push({ label, command });
186
191
  };
187
- add("default driver", driverCommand(config.defaultDriver));
192
+ for (const command of driverCommands(config.defaultDriver, registry))
193
+ add("default driver", command);
188
194
  add("default answerer", answererCommand(config.defaultAnswerer));
189
195
  if (config.harness) {
190
196
  add(
@@ -195,7 +201,9 @@ const configuredCliTargets = (config: ResolvedLuxConfig): CliTarget[] => {
195
201
  for (const [id, profile] of Object.entries(config.profiles ?? {}).sort(([a], [b]) =>
196
202
  a.localeCompare(b),
197
203
  )) {
198
- add(`profile '${id}' driver`, driverCommand(profile.driver ?? config.defaultDriver));
204
+ for (const command of driverCommands(profile.driver ?? config.defaultDriver, registry)) {
205
+ add(`profile '${id}' driver`, command);
206
+ }
199
207
  add(`profile '${id}' answerer`, answererCommand(profile.answerer ?? config.defaultAnswerer));
200
208
  const harness = profile.harness ?? config.harness;
201
209
  if (harness) {
@@ -418,17 +426,19 @@ const caseDriverFailure = (
418
426
  caseId: string,
419
427
  reference: unknown,
420
428
  registry: Registry,
429
+ required: EvalCase["requires"],
421
430
  ): string | null => {
422
431
  const name = refName(reference);
423
432
  if (!name) return "driver reference has no name";
424
433
  const plugin = registry.getDriver(name);
425
434
  if (!plugin) return `unknown driver '${name}'`;
426
435
  try {
427
- validatePluginConfig(
436
+ const config = validatePluginConfig(
428
437
  plugin.configSchema,
429
438
  rawRefConfig(reference),
430
439
  `case '${caseId}' driver '${name}'`,
431
440
  );
441
+ validateDriverCapabilities(plugin, config, required);
432
442
  return null;
433
443
  } catch (error) {
434
444
  return firstLine(error);
@@ -440,6 +450,7 @@ const validateCaseDrivers = (
440
450
  drivers: readonly unknown[],
441
451
  registry: Registry,
442
452
  recorder: DoctorRecorder,
453
+ required: EvalCase["requires"],
443
454
  ): void => {
444
455
  if (drivers.length === 0) {
445
456
  recorder.add(
@@ -450,7 +461,7 @@ const validateCaseDrivers = (
450
461
  return;
451
462
  }
452
463
  const failures = drivers
453
- .map((driver) => caseDriverFailure(caseId, driver, registry))
464
+ .map((driver) => caseDriverFailure(caseId, driver, registry, required))
454
465
  .filter((failure): failure is string => failure !== null);
455
466
  if (failures.length === drivers.length) {
456
467
  recorder.add(
@@ -542,13 +553,16 @@ const executionTargetsForCase = (
542
553
  caseId: string,
543
554
  drivers: readonly unknown[],
544
555
  answerer: unknown,
556
+ registry: Registry,
545
557
  ): CliTarget[] => {
546
558
  const targets = drivers.flatMap((driver): CliTarget[] => {
547
- const command = driverCommand(driver);
548
- return command ? [{ label: `case '${caseId}' driver`, command }] : [];
559
+ return driverCommands(driver, registry).map((command) => ({
560
+ label: `case '${caseId}' driver`,
561
+ command,
562
+ }));
549
563
  });
550
- const command = answererCommand(answerer);
551
- if (command) targets.push({ label: `case '${caseId}' answerer`, command });
564
+ const answererCli = answererCommand(answerer);
565
+ if (answererCli) targets.push({ label: `case '${caseId}' answerer`, command: answererCli });
552
566
  return targets;
553
567
  };
554
568
 
@@ -563,11 +577,11 @@ const inspectCase = async (
563
577
  const caseRegistry = registryForEvalCase(registry, authored);
564
578
  const drivers = caseDriverReferences(authored.driver, config);
565
579
  const answerer = authored.answerer ?? config.defaultAnswerer;
566
- validateCaseDrivers(discovered.id, drivers, caseRegistry, recorder);
580
+ validateCaseDrivers(discovered.id, drivers, caseRegistry, recorder, authored.requires);
567
581
  validateCaseAnswerer(discovered.id, answerer, caseRegistry, recorder);
568
582
  return [
569
583
  ...(await validateCaseAssertions(discovered.id, authored, config, caseRegistry, recorder)),
570
- ...executionTargetsForCase(discovered.id, drivers, answerer),
584
+ ...executionTargetsForCase(discovered.id, drivers, answerer, caseRegistry),
571
585
  ];
572
586
  } catch (error) {
573
587
  recorder.add(
@@ -735,7 +749,11 @@ export const doctorProject = async (options: DoctorOptions): Promise<DoctorRepor
735
749
  validateConfiguredDrivers(config, registry, recorder);
736
750
  validateConfiguredAnswerers(config, registry, recorder);
737
751
  const caseCliTargets = await inspectCases(config, registry, recorder);
738
- await inspectCliTargets([...configuredCliTargets(config), ...caseCliTargets], config, recorder);
752
+ await inspectCliTargets(
753
+ [...configuredCliTargets(config, registry), ...caseCliTargets],
754
+ config,
755
+ recorder,
756
+ );
739
757
  await inspectCampaigns(parsedOptions.campaigns ?? [], config, registry, recorder);
740
758
  return recorder.finish(config.rootDir);
741
759
  };
@@ -0,0 +1,16 @@
1
+ import type { DriverCapability, DriverPlugin } from "../core/driver.js";
2
+
3
+ export const validateDriverCapabilities = (
4
+ plugin: DriverPlugin,
5
+ config: unknown,
6
+ required: readonly DriverCapability[] = [],
7
+ ): void => {
8
+ if (required.length === 0) return;
9
+ const capabilities = plugin.runtime?.(config).capabilities;
10
+ const missing = required.filter((capability) => capabilities?.[capability] !== true);
11
+ if (missing.length > 0) {
12
+ throw new Error(
13
+ `driver '${plugin.name}' does not declare required capabilities: ${missing.join(", ")}`,
14
+ );
15
+ }
16
+ };
@@ -0,0 +1,59 @@
1
+ import type { DriverPlugin, DriverRuntime } from "../core/driver.js";
2
+ import type { Registry } from "../core/registry.js";
3
+ import { cliIdentity } from "./experiment-runtime-identity.js";
4
+ import { validatePluginConfig } from "./validate-plugin-config.js";
5
+
6
+ const resolvedDriver = (
7
+ reference: unknown,
8
+ registry: Registry,
9
+ ): { plugin: DriverPlugin; config: unknown } | undefined => {
10
+ const name =
11
+ typeof reference === "string" ? reference : (reference as { name?: unknown } | null)?.name;
12
+ if (typeof name !== "string") return undefined;
13
+ const plugin = registry.getDriver(name);
14
+ if (!plugin) throw new Error(`unknown driver '${name}'`);
15
+ const raw =
16
+ typeof reference === "object" && reference !== null
17
+ ? (reference as { config?: unknown }).config
18
+ : undefined;
19
+ return { plugin, config: validatePluginConfig(plugin.configSchema, raw, `driver '${name}'`) };
20
+ };
21
+
22
+ export const referencedDriverRuntime = (reference: unknown, registry: Registry): DriverRuntime => {
23
+ const resolved = resolvedDriver(reference, registry);
24
+ return resolved?.plugin.runtime?.(resolved.config) ?? { commands: [], capabilities: {} };
25
+ };
26
+
27
+ export const experimentDriverIdentity = async (
28
+ reference: unknown,
29
+ registry: Registry,
30
+ rootDir: string,
31
+ ): Promise<unknown> => {
32
+ const resolved = resolvedDriver(reference, registry);
33
+ if (!resolved) return null;
34
+ const { plugin, config } = resolved;
35
+ const experiment = plugin.experiment;
36
+ if (!experiment)
37
+ throw new Error(
38
+ `driver '${plugin.name}' supports ordinary eval runs but does not declare controlled experiment support`,
39
+ );
40
+ if (!experiment.version.trim())
41
+ throw new Error(`driver '${plugin.name}' experiment version must not be empty`);
42
+ const prepared = validatePluginConfig(
43
+ plugin.configSchema,
44
+ experiment.prepare(config),
45
+ `driver '${plugin.name}' experiment configuration`,
46
+ );
47
+ const runtime = plugin.runtime?.(prepared) ?? { commands: [], capabilities: {} };
48
+ const behavior = await experiment.identity(config, { rootDir });
49
+ return {
50
+ driver: plugin.name,
51
+ version: experiment.version,
52
+ config: prepared,
53
+ capabilities: runtime.capabilities,
54
+ executables: await Promise.all(
55
+ runtime.commands.map((command) => cliIdentity(command, rootDir)),
56
+ ),
57
+ behavior,
58
+ };
59
+ };
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { RubricConfigSchema } from "../assertions/rubric/index.js";
4
+ import { assertDisjointClusters } from "../core/evaluation-clusters.js";
4
5
  import {
5
6
  bindSubjectRoot,
6
7
  type Candidate,
@@ -11,16 +12,10 @@ import {
11
12
  } from "../core/index.js";
12
13
  import { assertDisjointAnnotationSplits, selectGraderAnnotations } from "./annotation-loader.js";
13
14
  import { discoverCases, selectCases } from "./discover.js";
15
+ import { experimentDriverIdentity } from "./driver-identity.js";
14
16
  import type { CampaignExecutionOptions, EvaluationCorpus } from "./experiment-context.js";
15
17
  import { profileOrThrow } from "./experiment-context.js";
16
- import {
17
- claudeConfigRoot,
18
- claudeUserBehaviorHash,
19
- cliIdentity,
20
- configuredClaudeSourceHashes,
21
- referencedAnswererCli,
22
- referencedCli,
23
- } from "./experiment-runtime-identity.js";
18
+ import { cliIdentity, referencedAnswererCli } from "./experiment-runtime-identity.js";
24
19
  import { fixtureIdentity } from "./fixture-identity.js";
25
20
  import { loadEvalCase } from "./loader.js";
26
21
  import { resolveAnswererReference, resolveDriverReference } from "./orchestrator.js";
@@ -123,8 +118,6 @@ export const agentCorpusHash = async (
123
118
  .map((evalCase) => [evalCase.path, evalCase]),
124
119
  ).values(),
125
120
  ];
126
- const caseBehaviorHashes = new Map<string, Promise<string | null>>();
127
- const configuredSourceHashes = new Map<string, Promise<string>>();
128
121
  const definitions = await Promise.all(
129
122
  selected.map(async (evalCase) => {
130
123
  const authored = await loadEvalCase(evalCase.path);
@@ -142,30 +135,14 @@ export const agentCorpusHash = async (
142
135
  profile.answerer ?? execution.config.defaultAnswerer,
143
136
  harness,
144
137
  );
145
- const driverCli = referencedCli(driver);
146
138
  const answererCli = referencedAnswererCli(answerer);
147
- const configuredRoot =
148
- driverCli?.name === "claude-code" ? claudeConfigRoot(driver) : null;
149
- let driverClaudeBehaviorHash: string | null = null;
150
- if (configuredRoot) {
151
- let hash = caseBehaviorHashes.get(configuredRoot);
152
- if (!hash) {
153
- hash = claudeUserBehaviorHash(configuredRoot);
154
- caseBehaviorHashes.set(configuredRoot, hash);
155
- }
156
- driverClaudeBehaviorHash = await hash;
157
- }
158
139
  return {
159
140
  profileId,
160
141
  driver,
161
- driverCliIdentity: driverCli
162
- ? await cliIdentity(driverCli.command, execution.config.rootDir)
163
- : null,
164
- driverClaudeBehaviorHash,
165
- configuredDriverSources: await configuredClaudeSourceHashes(
142
+ driverIdentity: await experimentDriverIdentity(
166
143
  driver,
144
+ execution.registry,
167
145
  execution.config.rootDir,
168
- configuredSourceHashes,
169
146
  ),
170
147
  answerer,
171
148
  answererCliIdentity: answererCli
@@ -303,6 +280,24 @@ export const prepareCorpus = async (
303
280
  );
304
281
  }
305
282
  const owners = new Map<string, string>();
283
+ assertDisjointClusters(
284
+ splits.map((split) => ({
285
+ name: split.name,
286
+ items: selectCases(discovery.cases, split.selector),
287
+ })),
288
+ );
289
+ for (const constraint of campaign.objectives.constraints) {
290
+ if (constraint.confidence?.method !== "wilson") continue;
291
+ const selector = splits.find((split) => split.name === constraint.split)?.selector;
292
+ if (
293
+ selector &&
294
+ selectCases(discovery.cases, selector).some((evalCase) => evalCase.cluster !== undefined)
295
+ ) {
296
+ throw new Error(
297
+ "authored case clusters produce bounded means; use Hoeffding or percentile-bootstrap confidence instead of Wilson",
298
+ );
299
+ }
300
+ }
306
301
  const caseIds = new Map<string, string>();
307
302
  const evidenceOwners = new Map<
308
303
  string,
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import { ConfidenceConfigSchema } from "../core/index.js";
3
+ import type { ExperimentRun } from "./experiment-runs.js";
4
+ import { candidateEvaluations } from "./experiment-selection.js";
5
+ import { PairedEffectSchema, pairedEffect } from "./paired-effects.js";
6
+
7
+ export const ExperimentEffectsSchema = z.array(
8
+ PairedEffectSchema.extend({
9
+ candidateId: z.string(),
10
+ profileId: z.string(),
11
+ split: z.enum(["train", "validation", "test"]),
12
+ metric: z.string(),
13
+ }),
14
+ );
15
+
16
+ export const experimentEffects = (
17
+ experiment: ExperimentRun,
18
+ ): z.infer<typeof ExperimentEffectsSchema> => {
19
+ const candidateIds =
20
+ experiment.selection?.decisionCandidateIds ??
21
+ experiment.candidates.map((candidate) => candidate.id);
22
+ const baseline = candidateEvaluations(experiment, "baseline");
23
+ const metrics = new Set([
24
+ "quality.passRate",
25
+ experiment.evaluationKind === "grader" ? "harness.total" : "agent.total",
26
+ ...(experiment.evaluationKind === "agent" ? ["interrupts.ask", "toolCalls"] : []),
27
+ ...experiment.campaignSnapshot.objectives.constraints
28
+ .filter((constraint) => constraint.relativeTo)
29
+ .map((constraint) => constraint.metric),
30
+ ]);
31
+ return candidateIds
32
+ .filter((id) => id !== "baseline")
33
+ .flatMap((candidateId) =>
34
+ candidateEvaluations(experiment, candidateId).flatMap((after) =>
35
+ [...metrics].map((metric) => {
36
+ const before = baseline.find(
37
+ (entry) => entry.profileId === after.profileId && entry.split === after.split,
38
+ );
39
+ const constraint = experiment.campaignSnapshot.objectives.constraints.find(
40
+ (entry) =>
41
+ entry.relativeTo &&
42
+ entry.metric === metric &&
43
+ entry.split === after.split &&
44
+ (!entry.profile || entry.profile === after.profileId),
45
+ );
46
+ const confidence =
47
+ constraint?.confidence ??
48
+ ConfidenceConfigSchema.parse({
49
+ method: metric === "quality.passRate" ? "hoeffding" : "percentile-bootstrap",
50
+ });
51
+ return {
52
+ candidateId,
53
+ profileId: after.profileId,
54
+ split: after.split,
55
+ metric,
56
+ ...pairedEffect(before, after, metric, confidence),
57
+ };
58
+ }),
59
+ ),
60
+ );
61
+ };
@@ -17,6 +17,7 @@ import {
17
17
  valueHash,
18
18
  } from "../core/index.js";
19
19
  import { materializeCandidate } from "./candidates.js";
20
+ import { runComparisonIdentity } from "./comparison-identity.js";
20
21
  import { readContentBoundJson, writeContentBoundJson } from "./content-bound-json.js";
21
22
  import { type discoverCases, selectCases } from "./discover.js";
22
23
  import { ensureBudget, throwIfAborted } from "./experiment-budget.js";
@@ -27,6 +28,7 @@ import { average } from "./experiment-selection.js";
27
28
  import { evaluateGraderCandidate } from "./grader-experiment.js";
28
29
  import { loadEvalCase } from "./loader.js";
29
30
  import { Orchestrator, type SuiteCaseResult, type SuiteItem } from "./orchestrator.js";
31
+ import { clusterCaseSamples, groupedCaseMetricSamples } from "./paired-effects.js";
30
32
  import { loadSuiteSummary, type SuiteSummary } from "./suite-summary.js";
31
33
 
32
34
  type ExperimentSplit = "train" | "validation" | "test";
@@ -280,8 +282,36 @@ const agentEvaluationFromSuite = (input: {
280
282
  summary: SuiteSummary;
281
283
  results: readonly SuiteCaseResult[];
282
284
  suiteDir: string;
285
+ evaluationHash: string | undefined;
286
+ clusterId: string | undefined;
283
287
  }): CandidateEvaluation => {
284
288
  const { candidate, profileId, split, summary, results, suiteDir } = input;
289
+ const caseSamples = clusterCaseSamples(
290
+ summary.samples.map((sample) => {
291
+ const metadata = results.find((result) => result.caseId === sample.caseId)?.result?.metadata;
292
+ // Validated assertion configs contain a fresh candidate workspace path.
293
+ // The campaign identity binds the authored evaluator without that incidental path.
294
+ const identityHash =
295
+ metadata?.provenance && input.evaluationHash
296
+ ? runComparisonIdentity({
297
+ ...metadata,
298
+ provenance: { ...metadata.provenance, evaluatorConfigHash: input.evaluationHash },
299
+ })
300
+ : null;
301
+ return {
302
+ [sample.canonicalCaseId]: {
303
+ identityHash,
304
+ ...(input.clusterId ? { clusterId: input.clusterId } : {}),
305
+ metrics: Object.fromEntries(
306
+ Object.entries({
307
+ ...sample.metrics,
308
+ "quality.passRate": sample.passed === true ? 1 : 0,
309
+ }).map(([name, value]) => [name, { mean: value, n: 1 }]),
310
+ ),
311
+ },
312
+ };
313
+ }),
314
+ );
285
315
  const modelAccounting = results.map((result) =>
286
316
  modelAccountingFromMetadata(result.result?.metadata ?? null),
287
317
  );
@@ -296,6 +326,7 @@ const agentEvaluationFromSuite = (input: {
296
326
  metrics: evaluationMetrics(summary, candidate),
297
327
  metricDefinitions: evaluationMetricDefinitions(summary),
298
328
  samples: evaluationSamples(summary),
329
+ ...(caseSamples ? { caseSamples } : {}),
299
330
  metricCalls: summary.samples.length,
300
331
  judgeCalls: results.reduce(
301
332
  (total, result) =>
@@ -362,7 +393,7 @@ const aggregateAgentUnits = (
362
393
  caseValues.set(evaluatedCaseId, values);
363
394
  }
364
395
  }
365
- const samples = Object.fromEntries(
396
+ const ungroupedSamples = Object.fromEntries(
366
397
  [...samplesByCase].map(([name, byCase]) => [
367
398
  name,
368
399
  [...byCase.entries()]
@@ -374,6 +405,9 @@ const aggregateAgentUnits = (
374
405
  {},
375
406
  ...units.map(({ evaluation }) => evaluation.metricDefinitions ?? {}),
376
407
  );
408
+ const caseSamples = clusterCaseSamples(units.map((unit) => unit.evaluation.caseSamples));
409
+ const grouped = groupedCaseMetricSamples(caseSamples, repetitions);
410
+ const samples = grouped ?? ungroupedSamples;
377
411
  return {
378
412
  candidateId: candidate.id,
379
413
  profileId,
@@ -384,13 +418,16 @@ const aggregateAgentUnits = (
384
418
  [...caseValues].map(([caseId, values]) => [caseId, average(values)]),
385
419
  ),
386
420
  metrics: Object.fromEntries(
387
- [...metricValues].map(([name, values]) => [
388
- name,
389
- name === "subject.bytes" ? candidate.sourceBytes : average(values),
390
- ]),
421
+ [...metricValues]
422
+ .filter(([name]) => !grouped || name === "subject.bytes" || Object.hasOwn(grouped, name))
423
+ .map(([name, values]) => [
424
+ name,
425
+ name === "subject.bytes" ? candidate.sourceBytes : average(grouped?.[name] ?? values),
426
+ ]),
391
427
  ),
392
428
  metricDefinitions,
393
429
  samples,
430
+ ...(caseSamples ? { caseSamples } : {}),
394
431
  metricCalls: units.reduce((total, unit) => total + (unit.evaluation.metricCalls ?? 0), 0),
395
432
  judgeCalls: units.reduce((total, unit) => total + (unit.evaluation.judgeCalls ?? 0), 0),
396
433
  unpricedModelCalls: units.reduce(
@@ -461,6 +498,7 @@ export const evaluateAgentCandidate = async (options: {
461
498
  const defaultDriver = profile.driver ?? execution.config.defaultDriver;
462
499
  const registry = execution.registry.withAssertion(createRubricAssertion(harness));
463
500
  const orchestrator = new Orchestrator({
501
+ environment: profile.environment ?? execution.config.environment,
464
502
  registry,
465
503
  defaultAnswerer: profile.answerer ?? execution.config.defaultAnswerer,
466
504
  ...(defaultDriver ? { defaultDriver } : {}),
@@ -629,6 +667,8 @@ export const evaluateAgentCandidate = async (options: {
629
667
  summary,
630
668
  results: suite.results,
631
669
  suiteDir: suite.suiteDir,
670
+ evaluationHash: experiment.manifest.evaluationHash,
671
+ clusterId: evalCase.cluster,
632
672
  });
633
673
  } finally {
634
674
  await rm(unitRoot, { recursive: true, force: true });