@prismatic-io/lux 0.0.1

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 (636) hide show
  1. package/LICENSE +21 -0
  2. package/lib/answerers/claude-code/index.d.ts +17 -0
  3. package/lib/answerers/claude-code/index.d.ts.map +1 -0
  4. package/lib/answerers/claude-code/index.js +280 -0
  5. package/lib/answerers/claude-code/index.js.map +1 -0
  6. package/lib/answerers/persona/index.d.ts +40 -0
  7. package/lib/answerers/persona/index.d.ts.map +1 -0
  8. package/lib/answerers/persona/index.js +265 -0
  9. package/lib/answerers/persona/index.js.map +1 -0
  10. package/lib/answerers/scripted/index.d.ts +8 -0
  11. package/lib/answerers/scripted/index.d.ts.map +1 -0
  12. package/lib/answerers/scripted/index.js +163 -0
  13. package/lib/answerers/scripted/index.js.map +1 -0
  14. package/lib/answerers/shared.d.ts +13 -0
  15. package/lib/answerers/shared.d.ts.map +1 -0
  16. package/lib/answerers/shared.js +17 -0
  17. package/lib/answerers/shared.js.map +1 -0
  18. package/lib/answerers/terminal/index.d.ts +9 -0
  19. package/lib/answerers/terminal/index.d.ts.map +1 -0
  20. package/lib/answerers/terminal/index.js +97 -0
  21. package/lib/answerers/terminal/index.js.map +1 -0
  22. package/lib/assertions/core/command-exits-zero.d.ts +21 -0
  23. package/lib/assertions/core/command-exits-zero.d.ts.map +1 -0
  24. package/lib/assertions/core/command-exits-zero.js +87 -0
  25. package/lib/assertions/core/command-exits-zero.js.map +1 -0
  26. package/lib/assertions/core/event-checks.d.ts +57 -0
  27. package/lib/assertions/core/event-checks.d.ts.map +1 -0
  28. package/lib/assertions/core/event-checks.js +123 -0
  29. package/lib/assertions/core/event-checks.js.map +1 -0
  30. package/lib/assertions/core/file-checks.d.ts +103 -0
  31. package/lib/assertions/core/file-checks.d.ts.map +1 -0
  32. package/lib/assertions/core/file-checks.js +288 -0
  33. package/lib/assertions/core/file-checks.js.map +1 -0
  34. package/lib/assertions/core/helpers.d.ts +66 -0
  35. package/lib/assertions/core/helpers.d.ts.map +1 -0
  36. package/lib/assertions/core/helpers.js +170 -0
  37. package/lib/assertions/core/helpers.js.map +1 -0
  38. package/lib/assertions/core/index.d.ts +15 -0
  39. package/lib/assertions/core/index.d.ts.map +1 -0
  40. package/lib/assertions/core/index.js +56 -0
  41. package/lib/assertions/core/index.js.map +1 -0
  42. package/lib/assertions/core/json-pointer-equals.d.ts +19 -0
  43. package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -0
  44. package/lib/assertions/core/json-pointer-equals.js +71 -0
  45. package/lib/assertions/core/json-pointer-equals.js.map +1 -0
  46. package/lib/assertions/core/output-checks.d.ts +46 -0
  47. package/lib/assertions/core/output-checks.d.ts.map +1 -0
  48. package/lib/assertions/core/output-checks.js +121 -0
  49. package/lib/assertions/core/output-checks.js.map +1 -0
  50. package/lib/assertions/core/predicate.d.ts +14 -0
  51. package/lib/assertions/core/predicate.d.ts.map +1 -0
  52. package/lib/assertions/core/predicate.js +55 -0
  53. package/lib/assertions/core/predicate.js.map +1 -0
  54. package/lib/assertions/core/resource-checks.d.ts +71 -0
  55. package/lib/assertions/core/resource-checks.d.ts.map +1 -0
  56. package/lib/assertions/core/resource-checks.js +159 -0
  57. package/lib/assertions/core/resource-checks.js.map +1 -0
  58. package/lib/assertions/core/safe-regex.d.ts +23 -0
  59. package/lib/assertions/core/safe-regex.d.ts.map +1 -0
  60. package/lib/assertions/core/safe-regex.js +102 -0
  61. package/lib/assertions/core/safe-regex.js.map +1 -0
  62. package/lib/assertions/core/tool-called-with.d.ts +21 -0
  63. package/lib/assertions/core/tool-called-with.d.ts.map +1 -0
  64. package/lib/assertions/core/tool-called-with.js +69 -0
  65. package/lib/assertions/core/tool-called-with.js.map +1 -0
  66. package/lib/assertions/core/tool-calls.d.ts +22 -0
  67. package/lib/assertions/core/tool-calls.d.ts.map +1 -0
  68. package/lib/assertions/core/tool-calls.js +17 -0
  69. package/lib/assertions/core/tool-calls.js.map +1 -0
  70. package/lib/assertions/core/tool-invocation.d.ts +36 -0
  71. package/lib/assertions/core/tool-invocation.d.ts.map +1 -0
  72. package/lib/assertions/core/tool-invocation.js +170 -0
  73. package/lib/assertions/core/tool-invocation.js.map +1 -0
  74. package/lib/assertions/core/tool-result-checks.d.ts +59 -0
  75. package/lib/assertions/core/tool-result-checks.d.ts.map +1 -0
  76. package/lib/assertions/core/tool-result-checks.js +193 -0
  77. package/lib/assertions/core/tool-result-checks.js.map +1 -0
  78. package/lib/assertions/mcp/discovery-checks.d.ts +36 -0
  79. package/lib/assertions/mcp/discovery-checks.d.ts.map +1 -0
  80. package/lib/assertions/mcp/discovery-checks.js +122 -0
  81. package/lib/assertions/mcp/discovery-checks.js.map +1 -0
  82. package/lib/assertions/mcp/index.d.ts +10 -0
  83. package/lib/assertions/mcp/index.d.ts.map +1 -0
  84. package/lib/assertions/mcp/index.js +25 -0
  85. package/lib/assertions/mcp/index.js.map +1 -0
  86. package/lib/assertions/rubric/index.d.ts +104 -0
  87. package/lib/assertions/rubric/index.d.ts.map +1 -0
  88. package/lib/assertions/rubric/index.js +191 -0
  89. package/lib/assertions/rubric/index.js.map +1 -0
  90. package/lib/assertions/rubric/internal.d.ts +14 -0
  91. package/lib/assertions/rubric/internal.d.ts.map +1 -0
  92. package/lib/assertions/rubric/internal.js +335 -0
  93. package/lib/assertions/rubric/internal.js.map +1 -0
  94. package/lib/authoring.d.ts +3 -0
  95. package/lib/authoring.d.ts.map +1 -0
  96. package/lib/authoring.js +3 -0
  97. package/lib/authoring.js.map +1 -0
  98. package/lib/cli/bin.d.ts +3 -0
  99. package/lib/cli/bin.d.ts.map +1 -0
  100. package/lib/cli/bin.js +15 -0
  101. package/lib/cli/bin.js.map +1 -0
  102. package/lib/cli/command-runtime.d.ts +5 -0
  103. package/lib/cli/command-runtime.d.ts.map +1 -0
  104. package/lib/cli/command-runtime.js +30 -0
  105. package/lib/cli/command-runtime.js.map +1 -0
  106. package/lib/cli/init.d.ts +21 -0
  107. package/lib/cli/init.d.ts.map +1 -0
  108. package/lib/cli/init.js +340 -0
  109. package/lib/cli/init.js.map +1 -0
  110. package/lib/cli/program.d.ts +22 -0
  111. package/lib/cli/program.d.ts.map +1 -0
  112. package/lib/cli/program.js +757 -0
  113. package/lib/cli/program.js.map +1 -0
  114. package/lib/cli/render/format.d.ts +14 -0
  115. package/lib/cli/render/format.d.ts.map +1 -0
  116. package/lib/cli/render/format.js +71 -0
  117. package/lib/cli/render/format.js.map +1 -0
  118. package/lib/cli/render/reporter.d.ts +73 -0
  119. package/lib/cli/render/reporter.d.ts.map +1 -0
  120. package/lib/cli/render/reporter.js +519 -0
  121. package/lib/cli/render/reporter.js.map +1 -0
  122. package/lib/cli/render/tty.d.ts +77 -0
  123. package/lib/cli/render/tty.d.ts.map +1 -0
  124. package/lib/cli/render/tty.js +283 -0
  125. package/lib/cli/render/tty.js.map +1 -0
  126. package/lib/cli/run-options.d.ts +27 -0
  127. package/lib/cli/run-options.d.ts.map +1 -0
  128. package/lib/cli/run-options.js +58 -0
  129. package/lib/cli/run-options.js.map +1 -0
  130. package/lib/cli/skills.d.ts +38 -0
  131. package/lib/cli/skills.d.ts.map +1 -0
  132. package/lib/cli/skills.js +84 -0
  133. package/lib/cli/skills.js.map +1 -0
  134. package/lib/core/annotation.d.ts +56 -0
  135. package/lib/core/annotation.d.ts.map +1 -0
  136. package/lib/core/annotation.js +92 -0
  137. package/lib/core/annotation.js.map +1 -0
  138. package/lib/core/answerer.d.ts +68 -0
  139. package/lib/core/answerer.d.ts.map +1 -0
  140. package/lib/core/answerer.js +17 -0
  141. package/lib/core/answerer.js.map +1 -0
  142. package/lib/core/artifact-evidence.d.ts +50 -0
  143. package/lib/core/artifact-evidence.d.ts.map +1 -0
  144. package/lib/core/artifact-evidence.js +185 -0
  145. package/lib/core/artifact-evidence.js.map +1 -0
  146. package/lib/core/artifact-snapshot.d.ts +37 -0
  147. package/lib/core/artifact-snapshot.d.ts.map +1 -0
  148. package/lib/core/artifact-snapshot.js +339 -0
  149. package/lib/core/artifact-snapshot.js.map +1 -0
  150. package/lib/core/assertion.d.ts +129 -0
  151. package/lib/core/assertion.d.ts.map +1 -0
  152. package/lib/core/assertion.js +75 -0
  153. package/lib/core/assertion.js.map +1 -0
  154. package/lib/core/case.d.ts +79 -0
  155. package/lib/core/case.d.ts.map +1 -0
  156. package/lib/core/case.js +90 -0
  157. package/lib/core/case.js.map +1 -0
  158. package/lib/core/conversation-turn.d.ts +23 -0
  159. package/lib/core/conversation-turn.d.ts.map +1 -0
  160. package/lib/core/conversation-turn.js +31 -0
  161. package/lib/core/conversation-turn.js.map +1 -0
  162. package/lib/core/driver.d.ts +64 -0
  163. package/lib/core/driver.d.ts.map +1 -0
  164. package/lib/core/driver.js +18 -0
  165. package/lib/core/driver.js.map +1 -0
  166. package/lib/core/emitter.d.ts +20 -0
  167. package/lib/core/emitter.d.ts.map +1 -0
  168. package/lib/core/emitter.js +34 -0
  169. package/lib/core/emitter.js.map +1 -0
  170. package/lib/core/events.d.ts +202 -0
  171. package/lib/core/events.d.ts.map +1 -0
  172. package/lib/core/events.js +107 -0
  173. package/lib/core/events.js.map +1 -0
  174. package/lib/core/exec.d.ts +82 -0
  175. package/lib/core/exec.d.ts.map +1 -0
  176. package/lib/core/exec.js +168 -0
  177. package/lib/core/exec.js.map +1 -0
  178. package/lib/core/experiment.d.ts +285 -0
  179. package/lib/core/experiment.d.ts.map +1 -0
  180. package/lib/core/experiment.js +389 -0
  181. package/lib/core/experiment.js.map +1 -0
  182. package/lib/core/glob.d.ts +3 -0
  183. package/lib/core/glob.d.ts.map +1 -0
  184. package/lib/core/glob.js +32 -0
  185. package/lib/core/glob.js.map +1 -0
  186. package/lib/core/hash.d.ts +8 -0
  187. package/lib/core/hash.d.ts.map +1 -0
  188. package/lib/core/hash.js +214 -0
  189. package/lib/core/hash.js.map +1 -0
  190. package/lib/core/index.d.ts +29 -0
  191. package/lib/core/index.d.ts.map +1 -0
  192. package/lib/core/index.js +29 -0
  193. package/lib/core/index.js.map +1 -0
  194. package/lib/core/lifecycle-fixtures.d.ts +20 -0
  195. package/lib/core/lifecycle-fixtures.d.ts.map +1 -0
  196. package/lib/core/lifecycle-fixtures.js +109 -0
  197. package/lib/core/lifecycle-fixtures.js.map +1 -0
  198. package/lib/core/lifecycle-hooks.d.ts +52 -0
  199. package/lib/core/lifecycle-hooks.d.ts.map +1 -0
  200. package/lib/core/lifecycle-hooks.js +42 -0
  201. package/lib/core/lifecycle-hooks.js.map +1 -0
  202. package/lib/core/mcp-events.d.ts +44 -0
  203. package/lib/core/mcp-events.d.ts.map +1 -0
  204. package/lib/core/mcp-events.js +23 -0
  205. package/lib/core/mcp-events.js.map +1 -0
  206. package/lib/core/model-cli.d.ts +57 -0
  207. package/lib/core/model-cli.d.ts.map +1 -0
  208. package/lib/core/model-cli.js +237 -0
  209. package/lib/core/model-cli.js.map +1 -0
  210. package/lib/core/observer.d.ts +31 -0
  211. package/lib/core/observer.d.ts.map +1 -0
  212. package/lib/core/observer.js +25 -0
  213. package/lib/core/observer.js.map +1 -0
  214. package/lib/core/platform-process.d.ts +33 -0
  215. package/lib/core/platform-process.d.ts.map +1 -0
  216. package/lib/core/platform-process.js +214 -0
  217. package/lib/core/platform-process.js.map +1 -0
  218. package/lib/core/registry.d.ts +22 -0
  219. package/lib/core/registry.d.ts.map +1 -0
  220. package/lib/core/registry.js +45 -0
  221. package/lib/core/registry.js.map +1 -0
  222. package/lib/core/rubric.d.ts +18 -0
  223. package/lib/core/rubric.d.ts.map +1 -0
  224. package/lib/core/rubric.js +49 -0
  225. package/lib/core/rubric.js.map +1 -0
  226. package/lib/core/run.d.ts +419 -0
  227. package/lib/core/run.d.ts.map +1 -0
  228. package/lib/core/run.js +126 -0
  229. package/lib/core/run.js.map +1 -0
  230. package/lib/core/statistics.d.ts +39 -0
  231. package/lib/core/statistics.d.ts.map +1 -0
  232. package/lib/core/statistics.js +163 -0
  233. package/lib/core/statistics.js.map +1 -0
  234. package/lib/core/subject.d.ts +4 -0
  235. package/lib/core/subject.d.ts.map +1 -0
  236. package/lib/core/subject.js +46 -0
  237. package/lib/core/subject.js.map +1 -0
  238. package/lib/core/tool-events.d.ts +69 -0
  239. package/lib/core/tool-events.d.ts.map +1 -0
  240. package/lib/core/tool-events.js +242 -0
  241. package/lib/core/tool-events.js.map +1 -0
  242. package/lib/core/usage.d.ts +78 -0
  243. package/lib/core/usage.d.ts.map +1 -0
  244. package/lib/core/usage.js +104 -0
  245. package/lib/core/usage.js.map +1 -0
  246. package/lib/core/wire.d.ts +83 -0
  247. package/lib/core/wire.d.ts.map +1 -0
  248. package/lib/core/wire.js +101 -0
  249. package/lib/core/wire.js.map +1 -0
  250. package/lib/core/zod.d.ts +10 -0
  251. package/lib/core/zod.d.ts.map +1 -0
  252. package/lib/core/zod.js +18 -0
  253. package/lib/core/zod.js.map +1 -0
  254. package/lib/drivers/claude-code/index.d.ts +55 -0
  255. package/lib/drivers/claude-code/index.d.ts.map +1 -0
  256. package/lib/drivers/claude-code/index.js +779 -0
  257. package/lib/drivers/claude-code/index.js.map +1 -0
  258. package/lib/drivers/claude-code/parse-events.d.ts +46 -0
  259. package/lib/drivers/claude-code/parse-events.d.ts.map +1 -0
  260. package/lib/drivers/claude-code/parse-events.js +387 -0
  261. package/lib/drivers/claude-code/parse-events.js.map +1 -0
  262. package/lib/drivers/codex/app-events.d.ts +16 -0
  263. package/lib/drivers/codex/app-events.d.ts.map +1 -0
  264. package/lib/drivers/codex/app-events.js +184 -0
  265. package/lib/drivers/codex/app-events.js.map +1 -0
  266. package/lib/drivers/codex/config.d.ts +55 -0
  267. package/lib/drivers/codex/config.d.ts.map +1 -0
  268. package/lib/drivers/codex/config.js +125 -0
  269. package/lib/drivers/codex/config.js.map +1 -0
  270. package/lib/drivers/codex/index.d.ts +8 -0
  271. package/lib/drivers/codex/index.d.ts.map +1 -0
  272. package/lib/drivers/codex/index.js +904 -0
  273. package/lib/drivers/codex/index.js.map +1 -0
  274. package/lib/drivers/mcp/index.d.ts +52 -0
  275. package/lib/drivers/mcp/index.d.ts.map +1 -0
  276. package/lib/drivers/mcp/index.js +282 -0
  277. package/lib/drivers/mcp/index.js.map +1 -0
  278. package/lib/drivers/mcp/transport.d.ts +50 -0
  279. package/lib/drivers/mcp/transport.d.ts.map +1 -0
  280. package/lib/drivers/mcp/transport.js +190 -0
  281. package/lib/drivers/mcp/transport.js.map +1 -0
  282. package/lib/drivers/shared/artifacts.d.ts +4 -0
  283. package/lib/drivers/shared/artifacts.d.ts.map +1 -0
  284. package/lib/drivers/shared/artifacts.js +81 -0
  285. package/lib/drivers/shared/artifacts.js.map +1 -0
  286. package/lib/drivers/shared/secure-copy.d.ts +2 -0
  287. package/lib/drivers/shared/secure-copy.d.ts.map +1 -0
  288. package/lib/drivers/shared/secure-copy.js +35 -0
  289. package/lib/drivers/shared/secure-copy.js.map +1 -0
  290. package/lib/drivers/subprocess/index.d.ts +24 -0
  291. package/lib/drivers/subprocess/index.d.ts.map +1 -0
  292. package/lib/drivers/subprocess/index.js +451 -0
  293. package/lib/drivers/subprocess/index.js.map +1 -0
  294. package/lib/index.d.ts +40 -0
  295. package/lib/index.d.ts.map +1 -0
  296. package/lib/index.js +48 -0
  297. package/lib/index.js.map +1 -0
  298. package/lib/optimization/reflective.d.ts +27 -0
  299. package/lib/optimization/reflective.d.ts.map +1 -0
  300. package/lib/optimization/reflective.js +257 -0
  301. package/lib/optimization/reflective.js.map +1 -0
  302. package/lib/orchestrator/annotation-loader.d.ts +65 -0
  303. package/lib/orchestrator/annotation-loader.d.ts.map +1 -0
  304. package/lib/orchestrator/annotation-loader.js +73 -0
  305. package/lib/orchestrator/annotation-loader.js.map +1 -0
  306. package/lib/orchestrator/annotation-store.d.ts +3 -0
  307. package/lib/orchestrator/annotation-store.d.ts.map +1 -0
  308. package/lib/orchestrator/annotation-store.js +100 -0
  309. package/lib/orchestrator/annotation-store.js.map +1 -0
  310. package/lib/orchestrator/authored-dependencies.d.ts +3 -0
  311. package/lib/orchestrator/authored-dependencies.d.ts.map +1 -0
  312. package/lib/orchestrator/authored-dependencies.js +146 -0
  313. package/lib/orchestrator/authored-dependencies.js.map +1 -0
  314. package/lib/orchestrator/campaign-lifecycle.d.ts +105 -0
  315. package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -0
  316. package/lib/orchestrator/campaign-lifecycle.js +141 -0
  317. package/lib/orchestrator/campaign-lifecycle.js.map +1 -0
  318. package/lib/orchestrator/candidate-integrity.d.ts +4 -0
  319. package/lib/orchestrator/candidate-integrity.d.ts.map +1 -0
  320. package/lib/orchestrator/candidate-integrity.js +14 -0
  321. package/lib/orchestrator/candidate-integrity.js.map +1 -0
  322. package/lib/orchestrator/candidates.d.ts +13 -0
  323. package/lib/orchestrator/candidates.d.ts.map +1 -0
  324. package/lib/orchestrator/candidates.js +577 -0
  325. package/lib/orchestrator/candidates.js.map +1 -0
  326. package/lib/orchestrator/compare.d.ts +80 -0
  327. package/lib/orchestrator/compare.d.ts.map +1 -0
  328. package/lib/orchestrator/compare.js +270 -0
  329. package/lib/orchestrator/compare.js.map +1 -0
  330. package/lib/orchestrator/comparison-identity.d.ts +10 -0
  331. package/lib/orchestrator/comparison-identity.d.ts.map +1 -0
  332. package/lib/orchestrator/comparison-identity.js +39 -0
  333. package/lib/orchestrator/comparison-identity.js.map +1 -0
  334. package/lib/orchestrator/config.d.ts +121 -0
  335. package/lib/orchestrator/config.d.ts.map +1 -0
  336. package/lib/orchestrator/config.js +176 -0
  337. package/lib/orchestrator/config.js.map +1 -0
  338. package/lib/orchestrator/content-bound-json.d.ts +5 -0
  339. package/lib/orchestrator/content-bound-json.d.ts.map +1 -0
  340. package/lib/orchestrator/content-bound-json.js +59 -0
  341. package/lib/orchestrator/content-bound-json.js.map +1 -0
  342. package/lib/orchestrator/discover.d.ts +45 -0
  343. package/lib/orchestrator/discover.d.ts.map +1 -0
  344. package/lib/orchestrator/discover.js +98 -0
  345. package/lib/orchestrator/discover.js.map +1 -0
  346. package/lib/orchestrator/doctor.d.ts +141 -0
  347. package/lib/orchestrator/doctor.d.ts.map +1 -0
  348. package/lib/orchestrator/doctor.js +525 -0
  349. package/lib/orchestrator/doctor.js.map +1 -0
  350. package/lib/orchestrator/driver-session.d.ts +177 -0
  351. package/lib/orchestrator/driver-session.d.ts.map +1 -0
  352. package/lib/orchestrator/driver-session.js +396 -0
  353. package/lib/orchestrator/driver-session.js.map +1 -0
  354. package/lib/orchestrator/evaluator-identity.d.ts +7 -0
  355. package/lib/orchestrator/evaluator-identity.d.ts.map +1 -0
  356. package/lib/orchestrator/evaluator-identity.js +42 -0
  357. package/lib/orchestrator/evaluator-identity.js.map +1 -0
  358. package/lib/orchestrator/experiment-budget.d.ts +11 -0
  359. package/lib/orchestrator/experiment-budget.d.ts.map +1 -0
  360. package/lib/orchestrator/experiment-budget.js +51 -0
  361. package/lib/orchestrator/experiment-budget.js.map +1 -0
  362. package/lib/orchestrator/experiment-context.d.ts +43 -0
  363. package/lib/orchestrator/experiment-context.d.ts.map +1 -0
  364. package/lib/orchestrator/experiment-context.js +9 -0
  365. package/lib/orchestrator/experiment-context.js.map +1 -0
  366. package/lib/orchestrator/experiment-contracts.d.ts +290 -0
  367. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -0
  368. package/lib/orchestrator/experiment-contracts.js +38 -0
  369. package/lib/orchestrator/experiment-contracts.js.map +1 -0
  370. package/lib/orchestrator/experiment-corpus.d.ts +21 -0
  371. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -0
  372. package/lib/orchestrator/experiment-corpus.js +276 -0
  373. package/lib/orchestrator/experiment-corpus.js.map +1 -0
  374. package/lib/orchestrator/experiment-evaluation.d.ts +44 -0
  375. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -0
  376. package/lib/orchestrator/experiment-evaluation.js +542 -0
  377. package/lib/orchestrator/experiment-evaluation.js.map +1 -0
  378. package/lib/orchestrator/experiment-finish.d.ts +6 -0
  379. package/lib/orchestrator/experiment-finish.d.ts.map +1 -0
  380. package/lib/orchestrator/experiment-finish.js +19 -0
  381. package/lib/orchestrator/experiment-finish.js.map +1 -0
  382. package/lib/orchestrator/experiment-identity.d.ts +6 -0
  383. package/lib/orchestrator/experiment-identity.d.ts.map +1 -0
  384. package/lib/orchestrator/experiment-identity.js +257 -0
  385. package/lib/orchestrator/experiment-identity.js.map +1 -0
  386. package/lib/orchestrator/experiment-loader.d.ts +175 -0
  387. package/lib/orchestrator/experiment-loader.d.ts.map +1 -0
  388. package/lib/orchestrator/experiment-loader.js +77 -0
  389. package/lib/orchestrator/experiment-loader.js.map +1 -0
  390. package/lib/orchestrator/experiment-report.d.ts +450 -0
  391. package/lib/orchestrator/experiment-report.d.ts.map +1 -0
  392. package/lib/orchestrator/experiment-report.js +570 -0
  393. package/lib/orchestrator/experiment-report.js.map +1 -0
  394. package/lib/orchestrator/experiment-runs.d.ts +379 -0
  395. package/lib/orchestrator/experiment-runs.d.ts.map +1 -0
  396. package/lib/orchestrator/experiment-runs.js +796 -0
  397. package/lib/orchestrator/experiment-runs.js.map +1 -0
  398. package/lib/orchestrator/experiment-runtime-identity.d.ts +41 -0
  399. package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -0
  400. package/lib/orchestrator/experiment-runtime-identity.js +411 -0
  401. package/lib/orchestrator/experiment-runtime-identity.js.map +1 -0
  402. package/lib/orchestrator/experiment-selection.d.ts +25 -0
  403. package/lib/orchestrator/experiment-selection.d.ts.map +1 -0
  404. package/lib/orchestrator/experiment-selection.js +286 -0
  405. package/lib/orchestrator/experiment-selection.js.map +1 -0
  406. package/lib/orchestrator/experiment.d.ts +11 -0
  407. package/lib/orchestrator/experiment.d.ts.map +1 -0
  408. package/lib/orchestrator/experiment.js +639 -0
  409. package/lib/orchestrator/experiment.js.map +1 -0
  410. package/lib/orchestrator/fixture-identity.d.ts +7 -0
  411. package/lib/orchestrator/fixture-identity.d.ts.map +1 -0
  412. package/lib/orchestrator/fixture-identity.js +55 -0
  413. package/lib/orchestrator/fixture-identity.js.map +1 -0
  414. package/lib/orchestrator/fs-read.d.ts +19 -0
  415. package/lib/orchestrator/fs-read.d.ts.map +1 -0
  416. package/lib/orchestrator/fs-read.js +113 -0
  417. package/lib/orchestrator/fs-read.js.map +1 -0
  418. package/lib/orchestrator/grader-experiment.d.ts +190 -0
  419. package/lib/orchestrator/grader-experiment.d.ts.map +1 -0
  420. package/lib/orchestrator/grader-experiment.js +579 -0
  421. package/lib/orchestrator/grader-experiment.js.map +1 -0
  422. package/lib/orchestrator/grading.d.ts +31 -0
  423. package/lib/orchestrator/grading.d.ts.map +1 -0
  424. package/lib/orchestrator/grading.js +186 -0
  425. package/lib/orchestrator/grading.js.map +1 -0
  426. package/lib/orchestrator/index.d.ts +22 -0
  427. package/lib/orchestrator/index.d.ts.map +1 -0
  428. package/lib/orchestrator/index.js +30 -0
  429. package/lib/orchestrator/index.js.map +1 -0
  430. package/lib/orchestrator/lifecycle-hook-execution.d.ts +4 -0
  431. package/lib/orchestrator/lifecycle-hook-execution.d.ts.map +1 -0
  432. package/lib/orchestrator/lifecycle-hook-execution.js +67 -0
  433. package/lib/orchestrator/lifecycle-hook-execution.js.map +1 -0
  434. package/lib/orchestrator/list-runs.d.ts +33 -0
  435. package/lib/orchestrator/list-runs.d.ts.map +1 -0
  436. package/lib/orchestrator/list-runs.js +308 -0
  437. package/lib/orchestrator/list-runs.js.map +1 -0
  438. package/lib/orchestrator/loader.d.ts +3 -0
  439. package/lib/orchestrator/loader.d.ts.map +1 -0
  440. package/lib/orchestrator/loader.js +17 -0
  441. package/lib/orchestrator/loader.js.map +1 -0
  442. package/lib/orchestrator/optimization-lifecycle.d.ts +12 -0
  443. package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -0
  444. package/lib/orchestrator/optimization-lifecycle.js +92 -0
  445. package/lib/orchestrator/optimization-lifecycle.js.map +1 -0
  446. package/lib/orchestrator/orchestrator.d.ts +144 -0
  447. package/lib/orchestrator/orchestrator.d.ts.map +1 -0
  448. package/lib/orchestrator/orchestrator.js +632 -0
  449. package/lib/orchestrator/orchestrator.js.map +1 -0
  450. package/lib/orchestrator/owned-lock.d.ts +17 -0
  451. package/lib/orchestrator/owned-lock.d.ts.map +1 -0
  452. package/lib/orchestrator/owned-lock.js +149 -0
  453. package/lib/orchestrator/owned-lock.js.map +1 -0
  454. package/lib/orchestrator/package-version.d.ts +2 -0
  455. package/lib/orchestrator/package-version.d.ts.map +1 -0
  456. package/lib/orchestrator/package-version.js +4 -0
  457. package/lib/orchestrator/package-version.js.map +1 -0
  458. package/lib/orchestrator/provenance.d.ts +5 -0
  459. package/lib/orchestrator/provenance.d.ts.map +1 -0
  460. package/lib/orchestrator/provenance.js +137 -0
  461. package/lib/orchestrator/provenance.js.map +1 -0
  462. package/lib/orchestrator/regrade.d.ts +80 -0
  463. package/lib/orchestrator/regrade.d.ts.map +1 -0
  464. package/lib/orchestrator/regrade.js +433 -0
  465. package/lib/orchestrator/regrade.js.map +1 -0
  466. package/lib/orchestrator/rubric-loader.d.ts +7 -0
  467. package/lib/orchestrator/rubric-loader.d.ts.map +1 -0
  468. package/lib/orchestrator/rubric-loader.js +65 -0
  469. package/lib/orchestrator/rubric-loader.js.map +1 -0
  470. package/lib/orchestrator/run-dir.d.ts +40 -0
  471. package/lib/orchestrator/run-dir.d.ts.map +1 -0
  472. package/lib/orchestrator/run-dir.js +161 -0
  473. package/lib/orchestrator/run-dir.js.map +1 -0
  474. package/lib/orchestrator/run-execution.d.ts +30 -0
  475. package/lib/orchestrator/run-execution.d.ts.map +1 -0
  476. package/lib/orchestrator/run-execution.js +531 -0
  477. package/lib/orchestrator/run-execution.js.map +1 -0
  478. package/lib/orchestrator/run-lifecycle.d.ts +115 -0
  479. package/lib/orchestrator/run-lifecycle.d.ts.map +1 -0
  480. package/lib/orchestrator/run-lifecycle.js +136 -0
  481. package/lib/orchestrator/run-lifecycle.js.map +1 -0
  482. package/lib/orchestrator/suite-compare.d.ts +76 -0
  483. package/lib/orchestrator/suite-compare.d.ts.map +1 -0
  484. package/lib/orchestrator/suite-compare.js +159 -0
  485. package/lib/orchestrator/suite-compare.js.map +1 -0
  486. package/lib/orchestrator/suite-lifecycle.d.ts +157 -0
  487. package/lib/orchestrator/suite-lifecycle.d.ts.map +1 -0
  488. package/lib/orchestrator/suite-lifecycle.js +253 -0
  489. package/lib/orchestrator/suite-lifecycle.js.map +1 -0
  490. package/lib/orchestrator/suite-runs.d.ts +91 -0
  491. package/lib/orchestrator/suite-runs.d.ts.map +1 -0
  492. package/lib/orchestrator/suite-runs.js +157 -0
  493. package/lib/orchestrator/suite-runs.js.map +1 -0
  494. package/lib/orchestrator/suite-summary.d.ts +109 -0
  495. package/lib/orchestrator/suite-summary.d.ts.map +1 -0
  496. package/lib/orchestrator/suite-summary.js +213 -0
  497. package/lib/orchestrator/suite-summary.js.map +1 -0
  498. package/lib/orchestrator/trace-limits.d.ts +20 -0
  499. package/lib/orchestrator/trace-limits.d.ts.map +1 -0
  500. package/lib/orchestrator/trace-limits.js +28 -0
  501. package/lib/orchestrator/trace-limits.js.map +1 -0
  502. package/lib/orchestrator/validate-plugin-config.d.ts +10 -0
  503. package/lib/orchestrator/validate-plugin-config.d.ts.map +1 -0
  504. package/lib/orchestrator/validate-plugin-config.js +26 -0
  505. package/lib/orchestrator/validate-plugin-config.js.map +1 -0
  506. package/package.json +84 -0
  507. package/skills/lux/SKILL.md +85 -0
  508. package/skills/lux/references/cli.md +102 -0
  509. package/skills/lux/references/eval-authoring.md +105 -0
  510. package/skills/lux-answerer/SKILL.md +169 -0
  511. package/src/answerers/claude-code/index.ts +342 -0
  512. package/src/answerers/persona/index.ts +327 -0
  513. package/src/answerers/scripted/index.ts +186 -0
  514. package/src/answerers/shared.ts +23 -0
  515. package/src/answerers/terminal/index.ts +117 -0
  516. package/src/assertions/core/command-exits-zero.ts +94 -0
  517. package/src/assertions/core/event-checks.ts +138 -0
  518. package/src/assertions/core/file-checks.ts +336 -0
  519. package/src/assertions/core/helpers.ts +203 -0
  520. package/src/assertions/core/index.ts +80 -0
  521. package/src/assertions/core/json-pointer-equals.ts +84 -0
  522. package/src/assertions/core/output-checks.ts +134 -0
  523. package/src/assertions/core/predicate.ts +67 -0
  524. package/src/assertions/core/resource-checks.ts +180 -0
  525. package/src/assertions/core/safe-regex.ts +196 -0
  526. package/src/assertions/core/tool-called-with.ts +68 -0
  527. package/src/assertions/core/tool-calls.ts +28 -0
  528. package/src/assertions/core/tool-invocation.ts +198 -0
  529. package/src/assertions/core/tool-result-checks.ts +211 -0
  530. package/src/assertions/mcp/discovery-checks.ts +139 -0
  531. package/src/assertions/mcp/index.ts +37 -0
  532. package/src/assertions/rubric/index.ts +258 -0
  533. package/src/assertions/rubric/internal.ts +367 -0
  534. package/src/authoring.ts +2 -0
  535. package/src/cli/bin.ts +14 -0
  536. package/src/cli/command-runtime.ts +34 -0
  537. package/src/cli/init.ts +410 -0
  538. package/src/cli/program.ts +992 -0
  539. package/src/cli/render/format.ts +69 -0
  540. package/src/cli/render/reporter.ts +643 -0
  541. package/src/cli/render/tty.ts +314 -0
  542. package/src/cli/run-options.ts +82 -0
  543. package/src/cli/skills.ts +129 -0
  544. package/src/core/annotation.ts +104 -0
  545. package/src/core/answerer.ts +75 -0
  546. package/src/core/artifact-evidence.ts +249 -0
  547. package/src/core/artifact-snapshot.ts +429 -0
  548. package/src/core/assertion.ts +156 -0
  549. package/src/core/case.ts +103 -0
  550. package/src/core/conversation-turn.ts +47 -0
  551. package/src/core/driver.ts +75 -0
  552. package/src/core/emitter.ts +38 -0
  553. package/src/core/events.ts +135 -0
  554. package/src/core/exec.ts +263 -0
  555. package/src/core/experiment.ts +467 -0
  556. package/src/core/glob.ts +31 -0
  557. package/src/core/hash.ts +237 -0
  558. package/src/core/index.ts +28 -0
  559. package/src/core/lifecycle-fixtures.ts +145 -0
  560. package/src/core/lifecycle-hooks.ts +101 -0
  561. package/src/core/mcp-events.ts +57 -0
  562. package/src/core/model-cli.ts +271 -0
  563. package/src/core/observer.ts +50 -0
  564. package/src/core/platform-process.ts +284 -0
  565. package/src/core/registry.ts +57 -0
  566. package/src/core/rubric.ts +52 -0
  567. package/src/core/run.ts +144 -0
  568. package/src/core/statistics.ts +194 -0
  569. package/src/core/subject.ts +50 -0
  570. package/src/core/tool-events.ts +292 -0
  571. package/src/core/usage.ts +123 -0
  572. package/src/core/wire.ts +135 -0
  573. package/src/core/zod.ts +20 -0
  574. package/src/drivers/claude-code/index.ts +866 -0
  575. package/src/drivers/claude-code/parse-events.ts +464 -0
  576. package/src/drivers/codex/app-events.ts +218 -0
  577. package/src/drivers/codex/config.ts +128 -0
  578. package/src/drivers/codex/index.ts +995 -0
  579. package/src/drivers/mcp/index.ts +351 -0
  580. package/src/drivers/mcp/transport.ts +222 -0
  581. package/src/drivers/shared/artifacts.ts +86 -0
  582. package/src/drivers/shared/secure-copy.ts +40 -0
  583. package/src/drivers/subprocess/index.ts +490 -0
  584. package/src/index.ts +594 -0
  585. package/src/optimization/reflective.ts +319 -0
  586. package/src/orchestrator/annotation-loader.ts +97 -0
  587. package/src/orchestrator/annotation-store.ts +112 -0
  588. package/src/orchestrator/authored-dependencies.ts +193 -0
  589. package/src/orchestrator/campaign-lifecycle.ts +181 -0
  590. package/src/orchestrator/candidate-integrity.ts +18 -0
  591. package/src/orchestrator/candidates.ts +683 -0
  592. package/src/orchestrator/compare.ts +358 -0
  593. package/src/orchestrator/comparison-identity.ts +45 -0
  594. package/src/orchestrator/config.ts +205 -0
  595. package/src/orchestrator/content-bound-json.ts +71 -0
  596. package/src/orchestrator/discover.ts +132 -0
  597. package/src/orchestrator/doctor.ts +781 -0
  598. package/src/orchestrator/driver-session.ts +507 -0
  599. package/src/orchestrator/evaluator-identity.ts +45 -0
  600. package/src/orchestrator/experiment-budget.ts +61 -0
  601. package/src/orchestrator/experiment-context.ts +41 -0
  602. package/src/orchestrator/experiment-contracts.ts +43 -0
  603. package/src/orchestrator/experiment-corpus.ts +416 -0
  604. package/src/orchestrator/experiment-evaluation.ts +749 -0
  605. package/src/orchestrator/experiment-finish.ts +33 -0
  606. package/src/orchestrator/experiment-identity.ts +356 -0
  607. package/src/orchestrator/experiment-loader.ts +105 -0
  608. package/src/orchestrator/experiment-report.ts +788 -0
  609. package/src/orchestrator/experiment-runs.ts +960 -0
  610. package/src/orchestrator/experiment-runtime-identity.ts +465 -0
  611. package/src/orchestrator/experiment-selection.ts +467 -0
  612. package/src/orchestrator/experiment.ts +991 -0
  613. package/src/orchestrator/fixture-identity.ts +67 -0
  614. package/src/orchestrator/fs-read.ts +126 -0
  615. package/src/orchestrator/grader-experiment.ts +856 -0
  616. package/src/orchestrator/grading.ts +260 -0
  617. package/src/orchestrator/index.ts +186 -0
  618. package/src/orchestrator/lifecycle-hook-execution.ts +90 -0
  619. package/src/orchestrator/list-runs.ts +384 -0
  620. package/src/orchestrator/loader.ts +19 -0
  621. package/src/orchestrator/optimization-lifecycle.ts +126 -0
  622. package/src/orchestrator/orchestrator.ts +919 -0
  623. package/src/orchestrator/owned-lock.ts +147 -0
  624. package/src/orchestrator/package-version.ts +5 -0
  625. package/src/orchestrator/provenance.ts +140 -0
  626. package/src/orchestrator/regrade.ts +580 -0
  627. package/src/orchestrator/rubric-loader.ts +74 -0
  628. package/src/orchestrator/run-dir.ts +209 -0
  629. package/src/orchestrator/run-execution.ts +694 -0
  630. package/src/orchestrator/run-lifecycle.ts +174 -0
  631. package/src/orchestrator/suite-compare.ts +195 -0
  632. package/src/orchestrator/suite-lifecycle.ts +335 -0
  633. package/src/orchestrator/suite-runs.ts +202 -0
  634. package/src/orchestrator/suite-summary.ts +276 -0
  635. package/src/orchestrator/trace-limits.ts +65 -0
  636. package/src/orchestrator/validate-plugin-config.ts +34 -0
@@ -0,0 +1,919 @@
1
+ import { basename, extname, isAbsolute, join, resolve } from "node:path";
2
+ import {
3
+ type Answerer,
4
+ type AnswererModelCallAccounting,
5
+ type AnswererRef,
6
+ bindSubjectRoot,
7
+ type DriverRef,
8
+ type EvalCase,
9
+ type GradingAssertionEntry,
10
+ type GradingReport,
11
+ guardObserverCallback,
12
+ type HarnessModelConfig,
13
+ type LifecycleHooksPlugin,
14
+ mergeHarnessModelConfig,
15
+ type Registry,
16
+ type RunMetadata,
17
+ type RunObserver,
18
+ type SuiteLifecycleHookContext,
19
+ valueHash,
20
+ } from "../core/index.js";
21
+ import { evaluatorImplementationHash } from "./evaluator-identity.js";
22
+ import {
23
+ assertionEvaluatorIdentities,
24
+ graderConfigHash,
25
+ validateAssertionPlan,
26
+ } from "./grading.js";
27
+ import { combineLifecycleFailures, setupLifecycleHooks } from "./lifecycle-hook-execution.js";
28
+ import { loadEvalCase } from "./loader.js";
29
+ import { LUX_VERSION } from "./package-version.js";
30
+ import { writeRunBundleJson } from "./run-dir.js";
31
+ import {
32
+ closeRunDirectory,
33
+ executeRunWithPlugins,
34
+ INLINE_CASE_PATH,
35
+ phaseDurations,
36
+ type RunWithPluginsArgs,
37
+ runDirOfError,
38
+ } from "./run-execution.js";
39
+ import {
40
+ executeSuiteLifecycle,
41
+ type SuiteLifecycleEffects,
42
+ type SuiteLifecycleFinalization,
43
+ validateSuiteConcurrency,
44
+ } from "./suite-lifecycle.js";
45
+ import { openSuiteRun } from "./suite-runs.js";
46
+ import { buildSuiteSummary, writeSuiteSummary } from "./suite-summary.js";
47
+ import { validatePluginConfig } from "./validate-plugin-config.js";
48
+
49
+ const serializeEffect = (effect: () => Promise<void>): (() => Promise<void>) => {
50
+ let pending: Promise<void> = Promise.resolve();
51
+ return () => (pending = pending.catch(() => undefined).then(effect));
52
+ };
53
+
54
+ const errorMessage = (error: unknown, depth = 0): string => {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ if (!(error instanceof AggregateError) || depth >= 3) return message;
57
+ const details = [...error.errors].slice(0, 8).map((failure) => errorMessage(failure, depth + 1));
58
+ return details.length > 0 ? `${message}: ${details.join("; ")}` : message;
59
+ };
60
+
61
+ const causedByAbort = (error: unknown, signal: AbortSignal | undefined): boolean => {
62
+ if (!signal?.aborted || error instanceof AggregateError) return false;
63
+ const reason = signal.reason;
64
+ let current: unknown = error;
65
+ for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
66
+ if (current === reason) return true;
67
+ if (
68
+ current instanceof DOMException &&
69
+ current.name === "AbortError" &&
70
+ (reason === undefined || (reason instanceof DOMException && reason.name === "AbortError"))
71
+ ) {
72
+ return true;
73
+ }
74
+ current = current instanceof Error && "cause" in current ? current.cause : undefined;
75
+ }
76
+ return false;
77
+ };
78
+
79
+ const recordLifecycleCleanupFailure = async (result: RunResult, error: unknown): Promise<void> => {
80
+ const reason = errorMessage(error);
81
+ result.metadata.endedAt = new Date().toISOString();
82
+ result.metadata.exitReason = "error";
83
+ const primary = result.metadata.operationalFailure;
84
+ result.metadata.operationalFailure = primary
85
+ ? {
86
+ phase: primary.phase,
87
+ reason: `${primary.reason}; lifecycle cleanup also failed during close: ${reason}`,
88
+ }
89
+ : { phase: "close", reason };
90
+ result.exitReason = "error";
91
+ result.grading.casePassed = null;
92
+ await Promise.all([
93
+ writeRunBundleJson(join(result.runDir, "run.json"), result.metadata),
94
+ writeRunBundleJson(join(result.runDir, "grading.json"), result.grading),
95
+ ]);
96
+ };
97
+
98
+ export type OrchestratorOptions = {
99
+ registry: Registry;
100
+ /** Answerer used when a case names none. Defaults to "persona". */
101
+ defaultAnswerer?: AnswererRef;
102
+ /** Driver used when a case names none. */
103
+ defaultDriver?: DriverRef;
104
+ /** Shared provider/model defaults for built-in harness components. */
105
+ harness?: HarnessModelConfig;
106
+ /** Versioned project resource fixtures. */
107
+ lifecycleFixtures?: LifecycleHooksPlugin[] | undefined;
108
+ };
109
+
110
+ export type RunRequest = {
111
+ /** Path to a TS case file. Mutually exclusive with `case`. */
112
+ casePath?: string;
113
+ /** Pre-loaded EvalCase. Mutually exclusive with `casePath`. */
114
+ case?: EvalCase;
115
+ runsRoot: string;
116
+ /**
117
+ * Id to file the run under when the case declares none. Discovery derives
118
+ * it from the file's path relative to the cases root, so two id-less cases
119
+ * that share a basename (a/check.ts, b/check.ts) keep separate histories.
120
+ */
121
+ caseId?: string | undefined;
122
+ /** Root that relative `fixtures` dir paths resolve against. */
123
+ fixturesRoot?: string | undefined;
124
+ answererOverride?: string | undefined;
125
+ startedAt?: Date | undefined;
126
+ /** The `runSuite` invocation this case attempt belongs to. */
127
+ suiteId?: string | undefined;
128
+ /** Root substituted into values created with `subjectPath()`. */
129
+ subjectRoot?: string | undefined;
130
+ subjectHash?: string | undefined;
131
+ profileId?: string | undefined;
132
+ experimentId?: string | undefined;
133
+ variantId?: string | undefined;
134
+ candidateId?: string | undefined;
135
+ recordGradingAssertion?: (entry: GradingAssertionEntry, index: number) => void | Promise<void>;
136
+ /** Persists subject/answerer spend before any potentially long grading call. */
137
+ recordPreGradingAccounting?: (metadata: RunMetadata) => void | Promise<void>;
138
+ /** Durable model-attempt markers used by experiment ledgers. */
139
+ recordModelAttemptStart?: (kind: "agent" | "answerer" | "judge") => string | Promise<string>;
140
+ recordModelAttemptComplete?: (
141
+ attemptId: string,
142
+ accounting: AnswererModelCallAccounting,
143
+ ) => void | Promise<void>;
144
+ recordModelAttemptsComplete?: (attemptIds: readonly string[]) => void | Promise<void>;
145
+ abortSignal?: AbortSignal | undefined;
146
+ /** Watches the run as it happens. See {@link RunObserver}. */
147
+ observer?: RunObserver | undefined;
148
+ };
149
+
150
+ export type RunResult = {
151
+ runDir: string;
152
+ metadata: RunMetadata;
153
+ grading: GradingReport;
154
+ exitReason: RunMetadata["exitReason"];
155
+ };
156
+
157
+ export type SuiteCaseResult = {
158
+ caseId: string;
159
+ runDir: string | null;
160
+ exitReason: RunMetadata["exitReason"];
161
+ casePassed: boolean | null;
162
+ errorMessage?: string;
163
+ /** The full result, when the case got far enough to produce one. */
164
+ result?: RunResult;
165
+ };
166
+
167
+ /** Watches one case within a suite; returned by {@link SuiteObserver.caseStart}. */
168
+ export type CaseObserver = RunObserver & {
169
+ /** The case settled. Receives the same entry that lands in `results`. */
170
+ end?(result: SuiteCaseResult): void;
171
+ };
172
+
173
+ /** A {@link RunObserver} that also sees case boundaries within a suite run. */
174
+ export type SuiteObserver = RunObserver & {
175
+ /**
176
+ * Called when a worker picks the case up. Return a {@link CaseObserver} to
177
+ * receive that case's events under their own identity — the only way to
178
+ * tell cases apart when `concurrency > 1`. Returning nothing routes the
179
+ * case's events to this observer's own {@link RunObserver} methods.
180
+ */
181
+ // biome-ignore lint/suspicious/noConfusingVoidType: `| void` (not `| undefined`) lets a log-only caseStart implementation return nothing.
182
+ caseStart?(info: { caseId: string; index: number; total: number }): CaseObserver | void;
183
+ };
184
+
185
+ /**
186
+ * One case to run in a suite: its file and the id to display it under.
187
+ * `caseId` is the canonical id runs are filed under when the case declares
188
+ * none — display decorations (a `--loop` suffix) must not fork run history.
189
+ */
190
+ export type SuiteItem = { path: string; id: string; caseId?: string | undefined };
191
+
192
+ const resolveCaseId = (casePath: string, evalCase: EvalCase, fallback?: string): string => {
193
+ if (evalCase.id) return evalCase.id;
194
+ if (fallback) return fallback;
195
+ if (casePath === INLINE_CASE_PATH) return "inline";
196
+ const base = basename(casePath);
197
+ const ext = extname(base);
198
+ return ext ? base.slice(0, -ext.length) : base;
199
+ };
200
+
201
+ const driverNameOf = (ref: DriverRef): string => (typeof ref === "string" ? ref : ref.name);
202
+
203
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
204
+ Boolean(value) && typeof value === "object" && !Array.isArray(value);
205
+
206
+ /** Merge plugin config recursively so nested Codex `config`/`env` overrides stay additive. */
207
+ const mergePluginConfig = (base: unknown, local: unknown): unknown => {
208
+ if (!isRecord(base) || !isRecord(local)) return local ?? base;
209
+ const merged: Record<string, unknown> = { ...base };
210
+ for (const [key, value] of Object.entries(local)) {
211
+ merged[key] = key in merged ? mergePluginConfig(merged[key], value) : value;
212
+ }
213
+ return merged;
214
+ };
215
+
216
+ export const resolveDriverReference = (
217
+ evalCase: EvalCase,
218
+ fallback: DriverRef | undefined,
219
+ ): DriverRef => {
220
+ const local = evalCase.driver;
221
+ if (!local) {
222
+ if (!fallback) throw new Error("case names no driver and no defaultDriver is configured");
223
+ return fallback;
224
+ }
225
+ if (!fallback || driverNameOf(local) !== driverNameOf(fallback)) return local;
226
+
227
+ // Naming the same driver opts into project defaults. A case object only
228
+ // needs to carry the fields it wants to override (for example a timeout).
229
+ const baseConfig = typeof fallback === "object" ? fallback.config : undefined;
230
+ const localConfig = typeof local === "object" ? local.config : undefined;
231
+ return {
232
+ name: driverNameOf(local),
233
+ config: mergePluginConfig(baseConfig, localConfig),
234
+ };
235
+ };
236
+
237
+ export const resolveAnswererReference = (
238
+ evalCase: EvalCase,
239
+ fallback: AnswererRef,
240
+ harness: HarnessModelConfig | undefined,
241
+ override?: string,
242
+ ): AnswererRef => {
243
+ const ref = override ?? evalCase.answerer ?? fallback;
244
+ const name = typeof ref === "string" ? ref : ref.name;
245
+ if (!harness || name !== "persona") return ref;
246
+ return {
247
+ name: "persona",
248
+ config: mergeHarnessModelConfig(harness, typeof ref === "object" ? ref.config : undefined),
249
+ };
250
+ };
251
+
252
+ const answererNameOf = (ref: AnswererRef): string => (typeof ref === "string" ? ref : ref.name);
253
+
254
+ const driverConfigOf = (ref: DriverRef): unknown =>
255
+ typeof ref === "object" ? ref.config : undefined;
256
+
257
+ const answererConfigOf = (ref: AnswererRef): unknown =>
258
+ typeof ref === "object" ? ref.config : undefined;
259
+
260
+ const EXPERIMENT_BEHAVIOR_SOURCE_ARGS = new Set([
261
+ "--continue",
262
+ "-c",
263
+ "--fork-session",
264
+ "--resume",
265
+ "-r",
266
+ "--add-dir",
267
+ "--append-system-prompt-file",
268
+ "--mcp-config",
269
+ "--plugin-dir",
270
+ "--settings",
271
+ "--setting-sources",
272
+ "--system-prompt-file",
273
+ ]);
274
+
275
+ const CODEX_EXPERIMENT_SOURCE_ARGS = new Set([
276
+ "--add-dir",
277
+ "--cd",
278
+ "-C",
279
+ "--config",
280
+ "-c",
281
+ "--profile",
282
+ "-p",
283
+ ]);
284
+
285
+ const bindSubprocessExperimentConfig = (config: Record<string, unknown>): unknown => {
286
+ if (typeof config.command !== "string" || !isAbsolute(config.command)) {
287
+ throw new Error(
288
+ "experiment subprocess commands must be absolute direct wrappers so Lux can fingerprint the invoked behavior",
289
+ );
290
+ }
291
+ if (typeof config.cwd === "string") {
292
+ throw new Error(
293
+ "experiment runs do not allow an explicit subprocess cwd; use the candidate subject and staged fixtures",
294
+ );
295
+ }
296
+ if (Array.isArray(config.args) && config.args.length > 0) {
297
+ throw new Error(
298
+ "experiment subprocesses do not allow args; put stable behavior in the fingerprinted direct wrapper",
299
+ );
300
+ }
301
+ if (isRecord(config.env) && Object.keys(config.env).length > 0) {
302
+ throw new Error(
303
+ "experiment subprocesses do not allow custom env; put stable behavior in the fingerprinted direct wrapper",
304
+ );
305
+ }
306
+ return { ...config, inheritEnv: false };
307
+ };
308
+
309
+ type AgentDriverName = "claude-code" | "codex";
310
+
311
+ const validateAgentExperimentConfig = (
312
+ driverName: AgentDriverName,
313
+ config: Record<string, unknown>,
314
+ ): { driverEnv: Record<string, unknown>; extraArgs: string[] } => {
315
+ const driverEnv = isRecord(config.env) ? config.env : {};
316
+ const commandResolutionOverride = Object.keys(driverEnv).find((name) =>
317
+ ["PATH", "PATHEXT"].includes(name.toUpperCase()),
318
+ );
319
+ if (commandResolutionOverride) {
320
+ throw new Error(
321
+ `experiment runs do not allow driver env.${commandResolutionOverride}; command resolution must match the fingerprinted host executable`,
322
+ );
323
+ }
324
+ if (typeof config.cwd === "string") {
325
+ throw new Error(
326
+ `experiment runs do not allow an explicit ${driverName} cwd; use the candidate subject and staged fixtures`,
327
+ );
328
+ }
329
+
330
+ const extraArgs = Array.isArray(config.extraArgs)
331
+ ? config.extraArgs.filter((value): value is string => typeof value === "string")
332
+ : [];
333
+ const disallowedArgs =
334
+ driverName === "claude-code" ? EXPERIMENT_BEHAVIOR_SOURCE_ARGS : CODEX_EXPERIMENT_SOURCE_ARGS;
335
+ const behaviorSourceArg = extraArgs.find((arg) =>
336
+ [...disallowedArgs].some((name) => arg === name || arg.startsWith(`${name}=`)),
337
+ );
338
+ if (behaviorSourceArg) {
339
+ throw new Error(
340
+ `experiment runs do not allow behavior-bearing extraArgs '${behaviorSourceArg}'; use structured driver fields so Lux can fingerprint the source`,
341
+ );
342
+ }
343
+
344
+ const isolation = isRecord(config.isolation) ? config.isolation : {};
345
+ const structuredPaths = [
346
+ ...(driverName === "claude-code" && Array.isArray(config.pluginDirs) ? config.pluginDirs : []),
347
+ ...(Array.isArray(config.addDirs) ? config.addDirs : []),
348
+ ...(driverName === "claude-code" && Array.isArray(isolation.allowRead)
349
+ ? isolation.allowRead
350
+ : []),
351
+ ].filter((value): value is string => typeof value === "string");
352
+ const relativeSource = structuredPaths.find((path) => !isAbsolute(path));
353
+ if (relativeSource) {
354
+ throw new Error(
355
+ `experiment driver source paths must be absolute after subjectPath binding: ${relativeSource}`,
356
+ );
357
+ }
358
+ return { driverEnv, extraArgs };
359
+ };
360
+
361
+ const bindCodexExperimentConfig = (
362
+ config: Record<string, unknown>,
363
+ extraArgs: readonly string[],
364
+ ): unknown => {
365
+ if (typeof config.profile === "string") {
366
+ throw new Error(
367
+ "experiment runs do not allow a Codex profile; use inline driver config so Lux can fingerprint it",
368
+ );
369
+ }
370
+ if (config.ephemeral === false) {
371
+ throw new Error("experiment runs require ephemeral Codex sessions");
372
+ }
373
+ const inlineConfig = isRecord(config.config) ? config.config : {};
374
+ const externalConfigKey = ((): string | undefined => {
375
+ const visit = (value: Record<string, unknown>, parent = ""): string | undefined => {
376
+ for (const [key, nested] of Object.entries(value)) {
377
+ const path = parent ? `${parent}.${key}` : key;
378
+ if (/(?:mcp_servers|instructions_file|notify|project_doc)/i.test(key)) return path;
379
+ if (isRecord(nested)) {
380
+ const found = visit(nested, path);
381
+ if (found) return found;
382
+ }
383
+ }
384
+ return undefined;
385
+ };
386
+ return visit(inlineConfig);
387
+ })();
388
+ if (externalConfigKey) {
389
+ throw new Error(
390
+ `experiment runs do not allow path-bearing Codex config '${externalConfigKey}'`,
391
+ );
392
+ }
393
+ const appServer = config.interactionMode !== "exec";
394
+ return {
395
+ ...config,
396
+ ephemeral: true,
397
+ ignoreUserConfig: true,
398
+ ignoreRules: true,
399
+ ...(appServer && !config.isolation ? { isolation: { allowRead: [] } } : {}),
400
+ extraArgs,
401
+ };
402
+ };
403
+
404
+ const bindClaudeExperimentConfig = (
405
+ config: Record<string, unknown>,
406
+ extraArgs: readonly string[],
407
+ driverEnv: Record<string, unknown>,
408
+ ): unknown => {
409
+ if (config.isolation) return config;
410
+ return {
411
+ ...config,
412
+ extraArgs: [
413
+ ...extraArgs,
414
+ "--setting-sources",
415
+ "user",
416
+ "--strict-mcp-config",
417
+ "--settings",
418
+ JSON.stringify({ disableAllHooks: true }),
419
+ ],
420
+ env: {
421
+ ...driverEnv,
422
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
423
+ CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
424
+ CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
425
+ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
426
+ CLAUDE_CODE_AUTO_CONNECT_IDE: "false",
427
+ CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: "1",
428
+ DISABLE_AUTOUPDATER: "1",
429
+ },
430
+ };
431
+ };
432
+
433
+ export const boundExperimentDriverConfig = (
434
+ driverName: string,
435
+ config: unknown,
436
+ experimentId: string | undefined,
437
+ ): unknown => {
438
+ if (!experimentId || !isRecord(config)) return config;
439
+ if (driverName === "subprocess") return bindSubprocessExperimentConfig(config);
440
+ if (driverName !== "claude-code" && driverName !== "codex") return config;
441
+ const { driverEnv, extraArgs } = validateAgentExperimentConfig(driverName, config);
442
+ return driverName === "codex"
443
+ ? bindCodexExperimentConfig(config, extraArgs)
444
+ : bindClaudeExperimentConfig(config, extraArgs, driverEnv);
445
+ };
446
+
447
+ const closeQuietly = async (close: () => Promise<void>, label: string): Promise<void> => {
448
+ try {
449
+ await close();
450
+ } catch (err) {
451
+ // Cleanup failures must not shadow the original error or stop other
452
+ // cleanup; surface them on stderr.
453
+ process.stderr.write(`[lux] warning: ${label} close failed: ${errorMessage(err)}\n`);
454
+ }
455
+ };
456
+
457
+ export class Orchestrator {
458
+ private evaluatorHash: Promise<string> | undefined;
459
+ readonly registry: Registry;
460
+ private readonly defaultAnswerer: AnswererRef;
461
+ private readonly defaultDriver: DriverRef | undefined;
462
+ private readonly harness: HarnessModelConfig | undefined;
463
+ private readonly lifecycleFixtures: readonly LifecycleHooksPlugin[];
464
+
465
+ constructor(options: OrchestratorOptions) {
466
+ this.registry = options.registry;
467
+ this.defaultAnswerer = options.defaultAnswerer ?? "persona";
468
+ this.defaultDriver = options.defaultDriver;
469
+ this.harness = options.harness;
470
+ this.lifecycleFixtures = [...(options.lifecycleFixtures ?? [])];
471
+ }
472
+
473
+ private evaluatorImplementationIdentity(): Promise<string> {
474
+ this.evaluatorHash ??= evaluatorImplementationHash();
475
+ return this.evaluatorHash;
476
+ }
477
+
478
+ async run(request: RunRequest): Promise<RunResult> {
479
+ if (this.lifecycleFixtures.length > 0 && !request.suiteId) {
480
+ throw new Error(
481
+ "Orchestrator.run() cannot execute configured lifecycle fixtures; use runSuite() for a single case",
482
+ );
483
+ }
484
+ if (!request.case && !request.casePath) {
485
+ throw new Error("RunRequest requires either 'case' or 'casePath'");
486
+ }
487
+ if (request.case && request.casePath) {
488
+ throw new Error("RunRequest allows only one of 'case' or 'casePath', not both");
489
+ }
490
+ const casePath = request.casePath ? resolve(request.casePath) : INLINE_CASE_PATH;
491
+ const authoredCase = request.case ?? (await loadEvalCase(casePath));
492
+ const evalCase = bindSubjectRoot(authoredCase, request.subjectRoot);
493
+ const caseId = resolveCaseId(casePath, evalCase, request.caseId);
494
+ const validatedAssertionConfigs = await validateAssertionPlan(
495
+ evalCase.assertions ?? [],
496
+ this.registry,
497
+ );
498
+
499
+ const driverRef = resolveDriverReference(evalCase, this.defaultDriver);
500
+ const driverName = driverNameOf(driverRef);
501
+ const answererRef = resolveAnswererReference(
502
+ evalCase,
503
+ this.defaultAnswerer,
504
+ this.harness,
505
+ request.answererOverride,
506
+ );
507
+ const answererName = answererNameOf(answererRef);
508
+
509
+ const driverPlugin = this.registry.getDriver(driverName);
510
+ if (!driverPlugin) {
511
+ throw new Error(`no driver registered for '${driverName}'`);
512
+ }
513
+ const answererPlugin = this.registry.getAnswerer(answererName);
514
+ if (!answererPlugin) {
515
+ throw new Error(`no answerer registered for '${answererName}'`);
516
+ }
517
+
518
+ // Validate both configs before instantiating either plugin so a
519
+ // schema failure can't leak a half-initialized driver/answerer.
520
+ const driverCfg = boundExperimentDriverConfig(
521
+ driverName,
522
+ validatePluginConfig(
523
+ driverPlugin.configSchema,
524
+ driverConfigOf(driverRef),
525
+ `driver '${driverName}'`,
526
+ ),
527
+ request.experimentId,
528
+ );
529
+ const answererCfg = validatePluginConfig(
530
+ answererPlugin.configSchema,
531
+ answererConfigOf(answererRef),
532
+ `answerer '${answererName}'`,
533
+ );
534
+
535
+ const driver = await driverPlugin.create(driverCfg);
536
+ let answerer: Answerer;
537
+ try {
538
+ answerer = await answererPlugin.create(answererCfg);
539
+ } catch (err) {
540
+ await closeQuietly(() => driver.close(), `driver '${driverName}'`);
541
+ throw err;
542
+ }
543
+
544
+ try {
545
+ const assertionImplementations = assertionEvaluatorIdentities(
546
+ authoredCase.assertions ?? [],
547
+ this.registry,
548
+ );
549
+ const validatedAssertionsHash = valueHash(validatedAssertionConfigs);
550
+ const implementationHash = await this.evaluatorImplementationIdentity();
551
+ const graderHash = graderConfigHash({
552
+ luxVersion: LUX_VERSION,
553
+ implementationHash,
554
+ assertions: assertionImplementations,
555
+ validatedAssertionsHash,
556
+ graderContext: this.harness,
557
+ });
558
+ return await this.runWithPlugins({
559
+ request,
560
+ casePath,
561
+ authoredCase,
562
+ evalCase,
563
+ validatedAssertionConfigs,
564
+ caseId,
565
+ driverName,
566
+ answererName,
567
+ answererConfig: answererCfg,
568
+ driverConfigHash: valueHash(driverCfg),
569
+ answererConfigHash: valueHash(answererCfg),
570
+ validatedAssertionsHash,
571
+ graderConfigHash: graderHash,
572
+ evaluatorConfigHash: valueHash({
573
+ luxVersion: LUX_VERSION,
574
+ implementationHash,
575
+ harness: this.harness,
576
+ driver: { name: driverPlugin.name, create: driverPlugin.create },
577
+ answerer: { name: answererPlugin.name, create: answererPlugin.create },
578
+ assertions: assertionImplementations,
579
+ lifecycleFixtures: this.lifecycleFixtures,
580
+ validatedAssertionsHash,
581
+ }),
582
+ driver,
583
+ answerer,
584
+ });
585
+ } finally {
586
+ await closeQuietly(() => driver.close(), `driver '${driverName}'`);
587
+ await closeQuietly(() => answerer.close(), `answerer '${answererName}'`);
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Run cases through a bounded pool of `concurrency` workers, continuing on
593
+ * per-case failures and aggregating results in input order. `items` is the
594
+ * exact run list — repetition (`--loop`) is the caller repeating entries.
595
+ */
596
+ async runSuite(
597
+ items: SuiteItem[],
598
+ opts: {
599
+ runsRoot: string;
600
+ fixturesRoot?: string | undefined;
601
+ answererOverride?: string | undefined;
602
+ observer?: SuiteObserver | undefined;
603
+ /** Aborts the cases in flight and stops the pool pulling new ones. */
604
+ abortSignal?: AbortSignal | undefined;
605
+ /** How many cases may run at once. Defaults to 1 (sequential). */
606
+ concurrency?: number | undefined;
607
+ subjectRoot?: string | undefined;
608
+ subjectHash?: string | undefined;
609
+ profileId?: string | undefined;
610
+ experimentId?: string | undefined;
611
+ experimentUnitId?: string | undefined;
612
+ experimentAttemptId?: string | undefined;
613
+ variantId?: string | undefined;
614
+ candidateId?: string | undefined;
615
+ recordGradingAssertion?: (
616
+ entry: GradingAssertionEntry,
617
+ index: number,
618
+ ) => void | Promise<void>;
619
+ recordPreGradingAccounting?: (metadata: RunMetadata) => void | Promise<void>;
620
+ recordModelAttemptStart?: (kind: "agent" | "answerer" | "judge") => string | Promise<string>;
621
+ recordModelAttemptComplete?: (
622
+ attemptId: string,
623
+ accounting: AnswererModelCallAccounting,
624
+ ) => void | Promise<void>;
625
+ recordModelAttemptsComplete?: (attemptIds: readonly string[]) => void | Promise<void>;
626
+ },
627
+ ): Promise<{ results: SuiteCaseResult[]; aborted: boolean; suiteDir: string }> {
628
+ const observer = opts.observer;
629
+ const requestedConcurrency = validateSuiteConcurrency(opts.concurrency);
630
+ const concurrency = Math.min(requestedConcurrency, Math.max(items.length, 1));
631
+ const suite = await openSuiteRun({
632
+ runsRoot: opts.runsRoot,
633
+ concurrency,
634
+ items,
635
+ ...(opts.profileId ? { profileId: opts.profileId } : {}),
636
+ ...(opts.experimentId ? { experimentId: opts.experimentId } : {}),
637
+ ...(opts.experimentUnitId ? { experimentUnitId: opts.experimentUnitId } : {}),
638
+ ...(opts.experimentAttemptId ? { experimentAttemptId: opts.experimentAttemptId } : {}),
639
+ ...(opts.variantId ? { variantId: opts.variantId } : {}),
640
+ ...(opts.candidateId ? { candidateId: opts.candidateId } : {}),
641
+ ...(opts.subjectHash ? { subjectHash: opts.subjectHash } : {}),
642
+ fixtures: this.lifecycleFixtures.map(({ name, pluginVersion }) => ({
643
+ name,
644
+ pluginVersion,
645
+ })),
646
+ });
647
+ const persistSuite = serializeEffect(suite.write);
648
+ const persistTerminalSuite = async (
649
+ lifecycleFinalization: SuiteLifecycleFinalization<SuiteCaseResult>,
650
+ ): Promise<void> => {
651
+ suite.manifest.aborted = lifecycleFinalization.status === "aborted";
652
+ suite.manifest.endedAt ??= new Date().toISOString();
653
+ suite.manifest.status =
654
+ lifecycleFinalization.status === "succeeded" ? "passed" : lifecycleFinalization.status;
655
+ if (lifecycleFinalization.error === null) {
656
+ delete suite.manifest.errorMessage;
657
+ } else {
658
+ suite.manifest.errorMessage = errorMessage(lifecycleFinalization.error);
659
+ }
660
+ await persistSuite();
661
+ };
662
+ const startCase = guardObserverCallback(observer?.caseStart?.bind(observer));
663
+ const caseHandles: Array<CaseObserver | undefined> = new Array(items.length);
664
+ const hookContext: SuiteLifecycleHookContext = {
665
+ suiteId: suite.manifest.suiteId,
666
+ suiteDir: suite.path,
667
+ runsRoot: resolve(opts.runsRoot),
668
+ concurrency,
669
+ items: items.map((item) => ({
670
+ id: item.id,
671
+ caseId: item.caseId ?? item.id,
672
+ path: resolve(item.path),
673
+ })),
674
+ ...(opts.abortSignal ? { abortSignal: opts.abortSignal } : {}),
675
+ };
676
+ let cleanupSuiteHooks: () => void | Promise<void> = async () => undefined;
677
+ if (!opts.abortSignal?.aborted) {
678
+ try {
679
+ cleanupSuiteHooks = await setupLifecycleHooks(
680
+ this.lifecycleFixtures,
681
+ "suite",
682
+ (hook) => hook.setupSuite?.(hookContext),
683
+ opts.abortSignal,
684
+ );
685
+ } catch (error) {
686
+ const aborted = causedByAbort(error, opts.abortSignal);
687
+ const failed: SuiteLifecycleFinalization<SuiteCaseResult> = {
688
+ status: aborted ? "aborted" : "failed",
689
+ items: items.map(() => ({ status: "pending", result: null })),
690
+ results: [],
691
+ error: aborted ? null : error,
692
+ };
693
+ await persistTerminalSuite(failed);
694
+ await writeSuiteSummary(
695
+ suite.path,
696
+ buildSuiteSummary(
697
+ suite.manifest,
698
+ failed.items.map(() => undefined),
699
+ ),
700
+ );
701
+ if (aborted) {
702
+ return { results: [], aborted: true, suiteDir: suite.path };
703
+ }
704
+ throw error;
705
+ }
706
+ }
707
+ const effects: SuiteLifecycleEffects<SuiteCaseResult> = {
708
+ itemStarted: async (index) => {
709
+ const suiteItem = suite.manifest.items[index];
710
+ if (suiteItem) suiteItem.status = "running";
711
+ await persistSuite();
712
+ },
713
+ runItem: async (index) => {
714
+ const item = items[index];
715
+ if (!item) throw new Error(`suite item ${index} is missing`);
716
+ const handle = startCase({ caseId: item.id, index, total: items.length }) ?? undefined;
717
+ caseHandles[index] = handle;
718
+ let cleanupCaseHooks: () => void | Promise<void>;
719
+ try {
720
+ cleanupCaseHooks = await setupLifecycleHooks(
721
+ this.lifecycleFixtures,
722
+ "case",
723
+ (hook) =>
724
+ hook.setupCase?.({
725
+ ...hookContext,
726
+ index,
727
+ item: hookContext.items[index] ?? {
728
+ id: item.id,
729
+ caseId: item.caseId ?? item.id,
730
+ path: resolve(item.path),
731
+ },
732
+ }),
733
+ opts.abortSignal,
734
+ );
735
+ } catch (err) {
736
+ return {
737
+ caseId: item.id,
738
+ runDir: null,
739
+ exitReason: "error",
740
+ casePassed: null,
741
+ errorMessage: errorMessage(err),
742
+ };
743
+ }
744
+ let outcome: SuiteCaseResult;
745
+ try {
746
+ const result = await this.run({
747
+ casePath: item.path,
748
+ caseId: item.caseId ?? item.id,
749
+ runsRoot: opts.runsRoot,
750
+ fixturesRoot: opts.fixturesRoot,
751
+ answererOverride: opts.answererOverride,
752
+ observer: handle ?? observer,
753
+ abortSignal: opts.abortSignal,
754
+ suiteId: suite.manifest.suiteId,
755
+ subjectRoot: opts.subjectRoot,
756
+ subjectHash: opts.subjectHash,
757
+ profileId: opts.profileId,
758
+ experimentId: opts.experimentId,
759
+ variantId: opts.variantId,
760
+ candidateId: opts.candidateId,
761
+ ...(opts.recordGradingAssertion
762
+ ? { recordGradingAssertion: opts.recordGradingAssertion }
763
+ : {}),
764
+ ...(opts.recordPreGradingAccounting
765
+ ? { recordPreGradingAccounting: opts.recordPreGradingAccounting }
766
+ : {}),
767
+ ...(opts.recordModelAttemptStart
768
+ ? { recordModelAttemptStart: opts.recordModelAttemptStart }
769
+ : {}),
770
+ ...(opts.recordModelAttemptComplete
771
+ ? { recordModelAttemptComplete: opts.recordModelAttemptComplete }
772
+ : {}),
773
+ ...(opts.recordModelAttemptsComplete
774
+ ? { recordModelAttemptsComplete: opts.recordModelAttemptsComplete }
775
+ : {}),
776
+ });
777
+ outcome = {
778
+ caseId: item.id,
779
+ runDir: result.runDir,
780
+ exitReason: result.exitReason,
781
+ casePassed: result.grading.casePassed,
782
+ result,
783
+ };
784
+ } catch (err) {
785
+ outcome = {
786
+ caseId: item.id,
787
+ runDir: runDirOfError(err),
788
+ exitReason: "error",
789
+ casePassed: false,
790
+ errorMessage: errorMessage(err),
791
+ };
792
+ }
793
+ try {
794
+ await cleanupCaseHooks();
795
+ } catch (caughtCleanupError) {
796
+ let cleanupError = caughtCleanupError;
797
+ if (outcome.result) {
798
+ try {
799
+ await recordLifecycleCleanupFailure(outcome.result, caughtCleanupError);
800
+ } catch (persistenceError) {
801
+ cleanupError = combineLifecycleFailures(
802
+ caughtCleanupError,
803
+ persistenceError,
804
+ `case '${item.id}' lifecycle cleanup and run persistence failed`,
805
+ );
806
+ }
807
+ }
808
+ const failure = outcome.errorMessage
809
+ ? combineLifecycleFailures(
810
+ new Error(outcome.errorMessage),
811
+ cleanupError,
812
+ `case '${item.id}' and its lifecycle cleanup failed`,
813
+ )
814
+ : cleanupError;
815
+ outcome = {
816
+ ...outcome,
817
+ exitReason: "error",
818
+ casePassed: null,
819
+ errorMessage: errorMessage(failure),
820
+ };
821
+ }
822
+ return outcome;
823
+ },
824
+ itemCompleted: async (index, outcome) => {
825
+ const suiteItem = suite.manifest.items[index];
826
+ if (suiteItem) {
827
+ suiteItem.status = "completed";
828
+ suiteItem.runDir = outcome.runDir ? suite.relativeRunDir(outcome.runDir) : null;
829
+ suiteItem.exitReason = outcome.exitReason;
830
+ suiteItem.casePassed = outcome.casePassed;
831
+ if (outcome.errorMessage) suiteItem.errorMessage = outcome.errorMessage;
832
+ }
833
+ await persistSuite();
834
+ const handle = caseHandles[index];
835
+ guardObserverCallback(handle?.end?.bind(handle))(outcome);
836
+ },
837
+ resultFailed: (result) => result.exitReason !== "done" || result.casePassed === false,
838
+ finalize: async (lifecycleFinalization) => {
839
+ let terminalFinalization = lifecycleFinalization;
840
+ try {
841
+ await cleanupSuiteHooks();
842
+ } catch (cleanupError) {
843
+ terminalFinalization = {
844
+ ...lifecycleFinalization,
845
+ status: "failed",
846
+ error:
847
+ lifecycleFinalization.error === null
848
+ ? cleanupError
849
+ : combineLifecycleFailures(
850
+ lifecycleFinalization.error,
851
+ cleanupError,
852
+ "suite execution and lifecycle cleanup failed",
853
+ ),
854
+ };
855
+ }
856
+ let finalizationError = terminalFinalization.error;
857
+ try {
858
+ await persistTerminalSuite(terminalFinalization);
859
+ } catch (persistenceError) {
860
+ finalizationError =
861
+ finalizationError === null
862
+ ? persistenceError
863
+ : combineLifecycleFailures(
864
+ finalizationError,
865
+ persistenceError,
866
+ "suite finalization and manifest persistence failed",
867
+ );
868
+ }
869
+ const slots = terminalFinalization.items.map((item) => item.result ?? undefined);
870
+ try {
871
+ await writeSuiteSummary(suite.path, buildSuiteSummary(suite.manifest, slots));
872
+ } catch (error) {
873
+ const summaryError = new Error(
874
+ `suite summary persistence failed: ${errorMessage(error)}`,
875
+ {
876
+ cause: error,
877
+ },
878
+ );
879
+ finalizationError =
880
+ finalizationError === null
881
+ ? summaryError
882
+ : combineLifecycleFailures(
883
+ finalizationError,
884
+ summaryError,
885
+ "suite finalization and summary persistence failed",
886
+ );
887
+ }
888
+ if (finalizationError !== lifecycleFinalization.error) {
889
+ throw finalizationError;
890
+ }
891
+ },
892
+ recordFinalizationFailure: persistTerminalSuite,
893
+ };
894
+ const finalization = await executeSuiteLifecycle({
895
+ itemCount: items.length,
896
+ concurrency,
897
+ ...(opts.abortSignal ? { abortSignal: opts.abortSignal } : {}),
898
+ effects,
899
+ });
900
+ return {
901
+ results: [...finalization.results],
902
+ aborted: finalization.status === "aborted",
903
+ suiteDir: suite.path,
904
+ };
905
+ }
906
+
907
+ private runWithPlugins(
908
+ args: Omit<RunWithPluginsArgs, "luxVersion" | "registry">,
909
+ ): Promise<RunResult> {
910
+ return executeRunWithPlugins({
911
+ ...args,
912
+ luxVersion: LUX_VERSION,
913
+ registry: this.registry,
914
+ });
915
+ }
916
+ }
917
+
918
+ // Internal exports for testing
919
+ export { closeQuietly, closeRunDirectory, phaseDurations, serializeEffect };