@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,796 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { appendFile, link, lstat, mkdir, open, readdir, readFile, realpath, rename, unlink, writeFile, } from "node:fs/promises";
4
+ import { hostname } from "node:os";
5
+ import { basename, join, relative, resolve, sep } from "node:path";
6
+ import { z } from "zod";
7
+ import { CandidateEvaluationSchema, CandidateIdSchema, CandidateSchema, ConfidenceIntervalSchema, ExperimentCampaignSchema, emptyUsage, TokenUsageSchema, totalTokens, } from "../core/index.js";
8
+ import { assertCandidateEnvelopeIntegrity, candidateEnvelopeHash } from "./candidate-integrity.js";
9
+ import { safeReadJson } from "./fs-read.js";
10
+ const EXPERIMENTS_DIR = ".experiments";
11
+ const MANIFEST_FILE = "experiment.json";
12
+ const EXECUTION_LOCK_FILE = ".execution.lock";
13
+ const ACCOUNTING_JOURNAL_FILE = "accounting.jsonl";
14
+ const ExperimentEvaluationSchema = CandidateEvaluationSchema.extend({
15
+ candidateId: CandidateIdSchema,
16
+ repetitions: z.number().int().positive(),
17
+ summaryPath: z.exactOptional(z.string()),
18
+ evidencePath: z.exactOptional(z.string()),
19
+ });
20
+ const ExperimentCandidateRefSchema = z.object({
21
+ id: CandidateIdSchema,
22
+ path: z.string(),
23
+ candidateHash: z.string(),
24
+ sourceBytes: z.number().int().nonnegative(),
25
+ /** Hash of the complete schema-normalized Candidate record, not only its components. */
26
+ candidateEnvelopeHash: z.string().optional(),
27
+ });
28
+ const ExperimentBudgetUsageSchema = z.object({
29
+ metricCalls: z.number().int().nonnegative(),
30
+ judgeCalls: z.number().int().nonnegative(),
31
+ unpricedModelCalls: z.number().int().nonnegative().optional(),
32
+ proposalCalls: z.number().int().nonnegative(),
33
+ usage: TokenUsageSchema,
34
+ costUsd: z.number().nonnegative(),
35
+ });
36
+ const AccountingDeltaSchema = ExperimentBudgetUsageSchema.partial();
37
+ const AccountingJournalEntrySchema = z
38
+ .object({
39
+ id: z.string().min(1),
40
+ delta: AccountingDeltaSchema,
41
+ completedModelAttemptIds: z.array(z.string().min(1)).optional(),
42
+ })
43
+ .strict();
44
+ export const ExperimentSelectionSchema = z.object({
45
+ decidedAt: z.string(),
46
+ /** Candidate population used for the final ranking; excludes discarded search-only candidates. */
47
+ decisionCandidateIds: z.array(CandidateIdSchema).optional(),
48
+ /** Optimize-only checkpoint naming the final promotion population. */
49
+ promotionFinalistCandidateIds: z.array(CandidateIdSchema).optional(),
50
+ rankedCandidateIds: z.array(CandidateIdSchema),
51
+ eligibleCandidateIds: z.array(CandidateIdSchema),
52
+ championCandidateId: z.exactOptional(CandidateIdSchema),
53
+ constraints: z.array(z.object({
54
+ candidateId: CandidateIdSchema,
55
+ metric: z.string(),
56
+ profile: z.exactOptional(z.string()),
57
+ split: z.enum(["train", "validation", "test"]),
58
+ values: z.array(z.number()),
59
+ passed: z.boolean(),
60
+ interval: z.exactOptional(ConfidenceIntervalSchema),
61
+ note: z.exactOptional(z.string()),
62
+ })),
63
+ });
64
+ export const ExperimentRunSchema = z
65
+ .object({
66
+ schemaVersion: z.literal(1),
67
+ experimentId: z.string(),
68
+ campaignId: z.string(),
69
+ campaignPath: z.string(),
70
+ campaignHash: z.string(),
71
+ campaignSnapshot: ExperimentCampaignSchema,
72
+ resolvedProfiles: z.record(z.string(), z.unknown()),
73
+ evaluatorVersion: z.literal(1),
74
+ luxVersion: z.string(),
75
+ evaluationKind: z.enum(["agent", "grader"]).default("agent"),
76
+ evaluationHash: z.exactOptional(z.string()),
77
+ mode: z.enum(["experiment", "optimize"]),
78
+ status: z.enum(["running", "passed", "failed", "aborted"]),
79
+ startedAt: z.string(),
80
+ endedAt: z.string().nullable(),
81
+ /** Accumulated active execution time across completed process segments. */
82
+ activeTimeMs: z.number().nonnegative().default(0),
83
+ /** Start of the process segment that currently owns this running ledger. */
84
+ activeSegmentStartedAt: z.exactOptional(z.string()),
85
+ subjectRoot: z.string(),
86
+ baseSourceHash: z.string(),
87
+ /** Present after legacy candidate refs have been upgraded to full-envelope integrity. */
88
+ candidateIntegrityVersion: z.exactOptional(z.literal(1)),
89
+ candidates: z.array(ExperimentCandidateRefSchema),
90
+ evaluations: z.array(ExperimentEvaluationSchema),
91
+ /** Unit IDs whose observed model spend has been folded into budgetUsage. */
92
+ accountedEvaluationUnits: z.array(z.string()).default([]),
93
+ /**
94
+ * Number of durable accounting journal entries folded into budgetUsage.
95
+ * The append-only journal avoids rewriting an ever-growing manifest after
96
+ * every individual grader assertion.
97
+ */
98
+ accountingJournalEntries: z.number().int().nonnegative().default(0),
99
+ budgetUsage: ExperimentBudgetUsageSchema,
100
+ championCandidateId: z.exactOptional(CandidateIdSchema),
101
+ selection: z.exactOptional(ExperimentSelectionSchema),
102
+ errorMessage: z.exactOptional(z.string()),
103
+ })
104
+ .superRefine((manifest, ctx) => {
105
+ if (manifest.candidateIntegrityVersion !== 1)
106
+ return;
107
+ for (const [index, candidate] of manifest.candidates.entries()) {
108
+ if (candidate.candidateEnvelopeHash !== undefined)
109
+ continue;
110
+ ctx.addIssue({
111
+ code: "custom",
112
+ path: ["candidates", index, "candidateEnvelopeHash"],
113
+ message: "required for candidate integrity version 1",
114
+ });
115
+ }
116
+ });
117
+ const timestampId = (date) => date.toISOString().replace(/[:.]/g, "-");
118
+ const writeJsonAtomic = async (path, value) => {
119
+ const temporary = `${path}.${process.pid}.tmp`;
120
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`);
121
+ await rename(temporary, path);
122
+ };
123
+ const persistJsonImmutable = async (path, value) => {
124
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
125
+ try {
126
+ const handle = await open(temporary, "wx");
127
+ try {
128
+ await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`);
129
+ await handle.sync();
130
+ }
131
+ finally {
132
+ await handle.close();
133
+ }
134
+ try {
135
+ // A hard link publishes the fully written inode without replacing an
136
+ // existing candidate. EEXIST is handled by comparing the persisted
137
+ // envelope in saveCandidate.
138
+ await link(temporary, path);
139
+ }
140
+ catch (error) {
141
+ if (error.code !== "EEXIST")
142
+ throw error;
143
+ }
144
+ }
145
+ finally {
146
+ await unlink(temporary).catch((error) => {
147
+ if (error.code !== "ENOENT")
148
+ throw error;
149
+ });
150
+ }
151
+ };
152
+ const ModelAttemptKindSchema = z.enum(["agent", "answerer", "judge", "proposal"]);
153
+ const MODEL_ATTEMPT_START_PREFIX = "model-attempt-start:";
154
+ const MODEL_ATTEMPT_COMPLETE_PREFIX = "model-attempt-complete:";
155
+ const MODEL_ATTEMPT_ABANDONED_PREFIX = "model-attempt-abandoned:";
156
+ const ExecutionLockSchema = z
157
+ .object({
158
+ pid: z.number().int().positive(),
159
+ hostname: z.string().min(1),
160
+ acquiredAt: z.string().datetime(),
161
+ })
162
+ .strict();
163
+ const modelAttemptParts = (id, prefix) => {
164
+ if (!id.startsWith(prefix))
165
+ return null;
166
+ const [kind, attemptId] = id.slice(prefix.length).split(":", 2);
167
+ const parsedKind = ModelAttemptKindSchema.safeParse(kind);
168
+ if (!parsedKind.success || !attemptId)
169
+ return null;
170
+ return { kind: parsedKind.data, attemptId };
171
+ };
172
+ const modelAttemptId = (prefix, kind, attemptId) => `${prefix}${kind}:${attemptId}`;
173
+ const acquireExecutionLock = async (path) => {
174
+ const lockPath = join(path, EXECUTION_LOCK_FILE);
175
+ const acquire = async (allowStaleRecovery) => {
176
+ try {
177
+ const handle = await open(lockPath, "wx");
178
+ const owner = ExecutionLockSchema.parse({
179
+ pid: process.pid,
180
+ hostname: hostname(),
181
+ acquiredAt: new Date().toISOString(),
182
+ });
183
+ await handle.writeFile(`${JSON.stringify(owner)}\n`);
184
+ return handle;
185
+ }
186
+ catch (error) {
187
+ if (error.code !== "EEXIST")
188
+ throw error;
189
+ if (!allowStaleRecovery) {
190
+ throw new Error(`experiment is already owned by another process: ${path}`);
191
+ }
192
+ let rawOwner;
193
+ try {
194
+ rawOwner = JSON.parse(await readFile(lockPath, "utf8"));
195
+ }
196
+ catch {
197
+ throw new Error(`experiment execution lock is present but unreadable: ${lockPath}`);
198
+ }
199
+ const parsedOwner = ExecutionLockSchema.safeParse(rawOwner);
200
+ if (!parsedOwner.success || parsedOwner.data.hostname !== hostname()) {
201
+ throw new Error(`experiment is already owned by another process: ${path}`);
202
+ }
203
+ const owner = parsedOwner.data;
204
+ try {
205
+ process.kill(owner.pid, 0);
206
+ throw new Error(`experiment is already owned by process ${owner.pid}: ${path}`);
207
+ }
208
+ catch (ownerError) {
209
+ if (ownerError.code !== "ESRCH")
210
+ throw ownerError;
211
+ }
212
+ await unlink(lockPath);
213
+ return acquire(false);
214
+ }
215
+ };
216
+ const handle = await acquire(true);
217
+ let released = false;
218
+ return {
219
+ release: async () => {
220
+ if (released)
221
+ return;
222
+ released = true;
223
+ await handle.close();
224
+ await unlink(lockPath).catch((error) => {
225
+ if (error.code !== "ENOENT")
226
+ throw error;
227
+ });
228
+ },
229
+ };
230
+ };
231
+ const openedRun = (path, manifest, release, accountedIds) => {
232
+ const write = () => {
233
+ if (manifest.status !== "running" && manifest.activeSegmentStartedAt) {
234
+ const segmentStartedAt = Date.parse(manifest.activeSegmentStartedAt);
235
+ const segmentEndedAt = manifest.endedAt ? Date.parse(manifest.endedAt) : Date.now();
236
+ if (Number.isFinite(segmentStartedAt) && Number.isFinite(segmentEndedAt)) {
237
+ manifest.activeTimeMs += Math.max(0, segmentEndedAt - segmentStartedAt);
238
+ }
239
+ delete manifest.activeSegmentStartedAt;
240
+ }
241
+ return writeJsonAtomic(join(path, MANIFEST_FILE), manifest);
242
+ };
243
+ const appendAccounting = async (id, rawDelta, completedModelAttemptIds = []) => {
244
+ if (accountedIds.has(id))
245
+ return false;
246
+ const delta = AccountingDeltaSchema.parse(rawDelta);
247
+ const completed = [...new Set(completedModelAttemptIds)];
248
+ for (const attemptId of completed) {
249
+ if (!modelAttemptParts(attemptId, MODEL_ATTEMPT_START_PREFIX)) {
250
+ throw new Error(`invalid model attempt start id: ${attemptId}`);
251
+ }
252
+ }
253
+ const entry = AccountingJournalEntrySchema.parse({
254
+ id,
255
+ delta,
256
+ ...(completed.length > 0 ? { completedModelAttemptIds: completed } : {}),
257
+ });
258
+ await appendFile(join(path, ACCOUNTING_JOURNAL_FILE), `${JSON.stringify(entry)}\n`, "utf8");
259
+ accountedIds.add(id);
260
+ applyAccountingDelta(manifest.budgetUsage, delta);
261
+ manifest.accountingJournalEntries++;
262
+ return true;
263
+ };
264
+ const recordAccounting = (id, delta) => appendAccounting(id, delta);
265
+ const startModelAttempt = async (kind) => {
266
+ const attemptId = randomUUID();
267
+ await recordAccounting(modelAttemptId(MODEL_ATTEMPT_START_PREFIX, kind, attemptId), kind === "judge" ? { judgeCalls: 1 } : {});
268
+ return modelAttemptId(MODEL_ATTEMPT_START_PREFIX, kind, attemptId);
269
+ };
270
+ const completeModelAttempts = async (attemptIds) => {
271
+ for (const startId of new Set(attemptIds)) {
272
+ const attempt = modelAttemptParts(startId, MODEL_ATTEMPT_START_PREFIX);
273
+ if (!attempt)
274
+ throw new Error(`invalid model attempt start id: ${startId}`);
275
+ await recordAccounting(modelAttemptId(MODEL_ATTEMPT_COMPLETE_PREFIX, attempt.kind, attempt.attemptId), {});
276
+ }
277
+ };
278
+ const settleModelAttempts = (id, delta, attemptIds) => appendAccounting(id, delta, attemptIds);
279
+ const saveCandidate = async (candidate) => {
280
+ if (manifest.candidateIntegrityVersion !== 1) {
281
+ throw new Error("legacy experiment candidate refs must be migrated before saving");
282
+ }
283
+ const verified = assertCandidateEnvelopeIntegrity(candidate);
284
+ const envelopeHash = candidateEnvelopeHash(verified);
285
+ const matches = manifest.candidates.filter((entry) => entry.id === verified.id);
286
+ if (matches.length > 1) {
287
+ throw new Error(`experiment candidate ID '${verified.id}' is duplicated`);
288
+ }
289
+ const existing = matches[0];
290
+ if (existing) {
291
+ const persisted = await verifiedCandidate(path, manifest, existing);
292
+ if (candidateEnvelopeHash(persisted) !== envelopeHash) {
293
+ throw new Error(`experiment candidate '${verified.id}' already exists with a different envelope`);
294
+ }
295
+ // Upgrade a legacy v1 reference after proving its persisted candidate is
296
+ // equivalent across the complete schema-normalized envelope.
297
+ if (existing.candidateEnvelopeHash === undefined) {
298
+ existing.candidateEnvelopeHash = envelopeHash;
299
+ await write();
300
+ }
301
+ return containedPath(path, existing.path);
302
+ }
303
+ const candidatesDir = containedPath(path, "candidates");
304
+ const candidatesDirInfo = await lstat(candidatesDir);
305
+ if (candidatesDirInfo.isSymbolicLink() || !candidatesDirInfo.isDirectory()) {
306
+ throw new Error("experiment candidates directory must be a real directory");
307
+ }
308
+ if ((await realpath(candidatesDir)) !== resolve(await realpath(path), "candidates")) {
309
+ throw new Error("experiment candidates directory escapes its physical ledger");
310
+ }
311
+ const candidatePath = containedPath(path, `candidates/${verified.id}.json`);
312
+ const ref = {
313
+ id: verified.id,
314
+ path: relative(path, candidatePath),
315
+ candidateHash: verified.candidateHash,
316
+ sourceBytes: verified.sourceBytes,
317
+ candidateEnvelopeHash: envelopeHash,
318
+ };
319
+ await persistJsonImmutable(candidatePath, verified);
320
+ const persisted = await verifiedCandidate(path, manifest, ref);
321
+ if (candidateEnvelopeHash(persisted) !== envelopeHash) {
322
+ throw new Error(`experiment candidate '${verified.id}' already exists with a different envelope`);
323
+ }
324
+ manifest.candidates.push(ref);
325
+ await write();
326
+ return candidatePath;
327
+ };
328
+ return {
329
+ path,
330
+ manifest,
331
+ write,
332
+ recordAccounting,
333
+ startModelAttempt,
334
+ completeModelAttempts,
335
+ settleModelAttempts,
336
+ saveCandidate,
337
+ release,
338
+ };
339
+ };
340
+ const applyAccountingDelta = (usage, delta) => {
341
+ usage.metricCalls += delta.metricCalls ?? 0;
342
+ usage.judgeCalls += delta.judgeCalls ?? 0;
343
+ usage.proposalCalls += delta.proposalCalls ?? 0;
344
+ usage.unpricedModelCalls = (usage.unpricedModelCalls ?? 0) + (delta.unpricedModelCalls ?? 0);
345
+ usage.usage = {
346
+ input: usage.usage.input + (delta.usage?.input ?? 0),
347
+ output: usage.usage.output + (delta.usage?.output ?? 0),
348
+ cacheRead: usage.usage.cacheRead + (delta.usage?.cacheRead ?? 0),
349
+ cacheCreation: usage.usage.cacheCreation + (delta.usage?.cacheCreation ?? 0),
350
+ };
351
+ usage.costUsd += delta.costUsd ?? 0;
352
+ };
353
+ const loadAccountingJournal = async (path, checkpointedEntries) => {
354
+ const journalPath = join(path, ACCOUNTING_JOURNAL_FILE);
355
+ if (!existsSync(journalPath))
356
+ return [];
357
+ const text = await readFile(journalPath, "utf8");
358
+ const lines = text.split("\n").filter((line) => line.trim().length > 0);
359
+ const entries = [];
360
+ for (const [index, line] of lines.entries()) {
361
+ try {
362
+ let parsed;
363
+ try {
364
+ parsed = JSON.parse(line);
365
+ }
366
+ catch {
367
+ throw new Error(`invalid accounting journal JSON at line ${index + 1}`);
368
+ }
369
+ const result = AccountingJournalEntrySchema.safeParse(parsed);
370
+ if (!result.success) {
371
+ throw new Error(`invalid accounting journal entry at line ${index + 1}`);
372
+ }
373
+ entries.push(result.data);
374
+ }
375
+ catch (error) {
376
+ const isUncheckpointedTail = index >= checkpointedEntries && index === lines.length - 1;
377
+ if (!isUncheckpointedTail)
378
+ throw error;
379
+ // appendFile can be torn by a hard kill. Only an invalid final entry
380
+ // beyond the manifest checkpoint is safe to discard.
381
+ await writeFile(journalPath, entries.length > 0 ? `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n` : "");
382
+ break;
383
+ }
384
+ }
385
+ return entries;
386
+ };
387
+ const containedPath = (rootPath, relativePath) => {
388
+ const root = resolve(rootPath);
389
+ const path = resolve(root, relativePath);
390
+ if (path === root || !path.startsWith(`${root}${sep}`)) {
391
+ throw new Error(`experiment candidate path escapes its ledger: ${relativePath}`);
392
+ }
393
+ return path;
394
+ };
395
+ const verifiedCandidate = async (experimentDir, manifest, ref) => {
396
+ const path = containedPath(experimentDir, ref.path);
397
+ const root = resolve(experimentDir);
398
+ const pathFromRoot = relative(root, path);
399
+ const components = pathFromRoot.split(sep);
400
+ let parent = root;
401
+ for (const component of components.slice(0, -1)) {
402
+ parent = join(parent, component);
403
+ const parentInfo = await lstat(parent).catch((error) => {
404
+ if (error.code === "ENOENT")
405
+ return null;
406
+ throw error;
407
+ });
408
+ if (!parentInfo || parentInfo.isSymbolicLink() || !parentInfo.isDirectory()) {
409
+ throw new Error(`experiment candidate '${ref.id}' has an invalid path component`);
410
+ }
411
+ }
412
+ const info = await lstat(path).catch((error) => {
413
+ if (error.code === "ENOENT")
414
+ return null;
415
+ throw error;
416
+ });
417
+ if (!info || info.isSymbolicLink() || !info.isFile()) {
418
+ throw new Error(`experiment candidate '${ref.id}' is missing or invalid`);
419
+ }
420
+ const physicalRoot = await realpath(root);
421
+ const physicalPath = await realpath(path);
422
+ if (physicalPath !== resolve(physicalRoot, pathFromRoot)) {
423
+ throw new Error(`experiment candidate '${ref.id}' escapes the physical ledger`);
424
+ }
425
+ const parsedCandidate = await safeReadJson(path, CandidateSchema);
426
+ if (!parsedCandidate)
427
+ throw new Error(`experiment candidate '${ref.id}' is missing or invalid`);
428
+ let candidate;
429
+ try {
430
+ candidate = assertCandidateEnvelopeIntegrity(parsedCandidate);
431
+ }
432
+ catch {
433
+ throw new Error(`experiment candidate '${ref.id}' failed its integrity check`);
434
+ }
435
+ if (candidate.id !== ref.id ||
436
+ candidate.candidateHash !== ref.candidateHash ||
437
+ candidate.sourceBytes !== ref.sourceBytes ||
438
+ candidate.baseSourceHash !== manifest.baseSourceHash ||
439
+ (ref.candidateEnvelopeHash !== undefined &&
440
+ candidateEnvelopeHash(candidate) !== ref.candidateEnvelopeHash)) {
441
+ throw new Error(`experiment candidate '${ref.id}' failed its integrity check`);
442
+ }
443
+ return candidate;
444
+ };
445
+ const migrateLegacyCandidateIntegrity = async (experimentDir, manifest) => {
446
+ if (manifest.candidateIntegrityVersion === 1)
447
+ return;
448
+ for (const ref of manifest.candidates) {
449
+ ref.candidateEnvelopeHash = candidateEnvelopeHash(await verifiedCandidate(experimentDir, manifest, ref));
450
+ }
451
+ manifest.candidateIntegrityVersion = 1;
452
+ };
453
+ export const openExperimentRun = async (options) => {
454
+ const startedAt = options.startedAt ?? new Date();
455
+ const root = join(resolve(options.runsRoot), EXPERIMENTS_DIR);
456
+ await mkdir(root, { recursive: true });
457
+ const prefix = `${timestampId(startedAt)}-${options.campaignId.replace(/[^a-zA-Z0-9_-]+/g, "-")}`;
458
+ let experimentId = prefix;
459
+ let path = join(root, experimentId);
460
+ for (let suffix = 2;; suffix++) {
461
+ try {
462
+ await mkdir(path);
463
+ break;
464
+ }
465
+ catch (error) {
466
+ if (error.code !== "EEXIST")
467
+ throw error;
468
+ experimentId = `${prefix}-${suffix}`;
469
+ path = join(root, experimentId);
470
+ }
471
+ }
472
+ await mkdir(join(path, "candidates"), { recursive: true });
473
+ const manifest = {
474
+ schemaVersion: 1,
475
+ experimentId,
476
+ campaignId: options.campaignId,
477
+ campaignPath: options.campaignPath,
478
+ campaignHash: options.campaignHash,
479
+ campaignSnapshot: options.campaignSnapshot,
480
+ resolvedProfiles: options.resolvedProfiles,
481
+ evaluatorVersion: 1,
482
+ luxVersion: options.luxVersion,
483
+ evaluationKind: options.evaluationKind ?? "agent",
484
+ ...(options.evaluationHash ? { evaluationHash: options.evaluationHash } : {}),
485
+ mode: options.mode,
486
+ status: "running",
487
+ startedAt: startedAt.toISOString(),
488
+ endedAt: null,
489
+ activeTimeMs: 0,
490
+ activeSegmentStartedAt: startedAt.toISOString(),
491
+ subjectRoot: options.subjectRoot,
492
+ baseSourceHash: options.baseSourceHash,
493
+ candidateIntegrityVersion: 1,
494
+ candidates: [],
495
+ evaluations: [],
496
+ accountedEvaluationUnits: [],
497
+ accountingJournalEntries: 0,
498
+ budgetUsage: {
499
+ metricCalls: 0,
500
+ judgeCalls: 0,
501
+ unpricedModelCalls: 0,
502
+ proposalCalls: 0,
503
+ usage: emptyUsage(),
504
+ costUsd: 0,
505
+ },
506
+ };
507
+ const ownership = await acquireExecutionLock(path);
508
+ const opened = openedRun(path, manifest, ownership.release, new Set());
509
+ await opened.write();
510
+ return opened;
511
+ };
512
+ export const loadExperimentRun = async (path) => safeReadJson(join(path, MANIFEST_FILE), ExperimentRunSchema);
513
+ export const migrateExperimentCandidateIntegrity = async (path) => {
514
+ const absolute = resolve(path);
515
+ const ownership = await acquireExecutionLock(absolute);
516
+ try {
517
+ const manifest = await loadExperimentRun(absolute);
518
+ if (!manifest)
519
+ throw new Error(`experiment manifest not found or invalid at ${absolute}`);
520
+ if (manifest.candidateIntegrityVersion !== 1) {
521
+ await migrateLegacyCandidateIntegrity(absolute, manifest);
522
+ await writeJsonAtomic(join(absolute, MANIFEST_FILE), manifest);
523
+ }
524
+ return manifest;
525
+ }
526
+ finally {
527
+ await ownership.release();
528
+ }
529
+ };
530
+ export const experimentActiveElapsedMs = (manifest, nowMs = Date.now()) => {
531
+ const segmentStartedAt = manifest.activeSegmentStartedAt
532
+ ? Date.parse(manifest.activeSegmentStartedAt)
533
+ : Number.NaN;
534
+ return (manifest.activeTimeMs +
535
+ (Number.isFinite(segmentStartedAt) ? Math.max(0, nowMs - segmentStartedAt) : 0));
536
+ };
537
+ export const resumeExperimentRun = async (path) => {
538
+ const absolute = resolve(path);
539
+ const manifest = await loadExperimentRun(absolute);
540
+ if (!manifest)
541
+ throw new Error(`experiment manifest not found or invalid at ${absolute}`);
542
+ if (manifest.status === "passed") {
543
+ throw new Error("passed experiments are immutable and cannot be resumed");
544
+ }
545
+ const ownership = await acquireExecutionLock(absolute);
546
+ try {
547
+ await migrateLegacyCandidateIntegrity(absolute, manifest);
548
+ const resumedAt = new Date();
549
+ if (manifest.activeSegmentStartedAt) {
550
+ const previousSegmentStart = Date.parse(manifest.activeSegmentStartedAt);
551
+ const previousSegmentEnd = manifest.endedAt
552
+ ? Date.parse(manifest.endedAt)
553
+ : resumedAt.getTime();
554
+ if (Number.isFinite(previousSegmentStart) && Number.isFinite(previousSegmentEnd)) {
555
+ // A running segment without a terminal checkpoint represents a hard
556
+ // kill. Charge the unknowable gap conservatively through recovery.
557
+ manifest.activeTimeMs += Math.max(0, previousSegmentEnd - previousSegmentStart);
558
+ }
559
+ }
560
+ else if (manifest.activeTimeMs === 0) {
561
+ // Forward-compatible accounting for v1 ledgers written before active
562
+ // segments existed.
563
+ const legacyStart = Date.parse(manifest.startedAt);
564
+ const legacyEnd = manifest.endedAt ? Date.parse(manifest.endedAt) : resumedAt.getTime();
565
+ if (Number.isFinite(legacyStart) && Number.isFinite(legacyEnd)) {
566
+ manifest.activeTimeMs = Math.max(0, legacyEnd - legacyStart);
567
+ }
568
+ }
569
+ const journal = await loadAccountingJournal(absolute, manifest.accountingJournalEntries);
570
+ if (manifest.accountingJournalEntries > journal.length) {
571
+ throw new Error(`accounting journal is truncated: manifest checkpoints ${manifest.accountingJournalEntries} entries but only ${journal.length} remain`);
572
+ }
573
+ const accountedIds = new Set(manifest.accountedEvaluationUnits);
574
+ for (const [index, entry] of journal.entries()) {
575
+ if (accountedIds.has(entry.id)) {
576
+ if (index >= manifest.accountingJournalEntries) {
577
+ throw new Error(`duplicate accounting journal id: ${entry.id}`);
578
+ }
579
+ continue;
580
+ }
581
+ accountedIds.add(entry.id);
582
+ if (index >= manifest.accountingJournalEntries) {
583
+ applyAccountingDelta(manifest.budgetUsage, entry.delta);
584
+ }
585
+ }
586
+ manifest.accountingJournalEntries = journal.length;
587
+ manifest.status = "running";
588
+ manifest.endedAt = null;
589
+ manifest.activeSegmentStartedAt = resumedAt.toISOString();
590
+ delete manifest.errorMessage;
591
+ const opened = openedRun(absolute, manifest, ownership.release, accountedIds);
592
+ const terminals = new Set(journal.flatMap((entry) => {
593
+ const completed = modelAttemptParts(entry.id, MODEL_ATTEMPT_COMPLETE_PREFIX);
594
+ const abandoned = modelAttemptParts(entry.id, MODEL_ATTEMPT_ABANDONED_PREFIX);
595
+ return [
596
+ ...(completed ? [`${completed.kind}:${completed.attemptId}`] : []),
597
+ ...(abandoned ? [`${abandoned.kind}:${abandoned.attemptId}`] : []),
598
+ ...(entry.completedModelAttemptIds ?? []).flatMap((attemptId) => {
599
+ const settled = modelAttemptParts(attemptId, MODEL_ATTEMPT_START_PREFIX);
600
+ return settled ? [`${settled.kind}:${settled.attemptId}`] : [];
601
+ }),
602
+ ];
603
+ }));
604
+ for (const entry of journal) {
605
+ const started = modelAttemptParts(entry.id, MODEL_ATTEMPT_START_PREFIX);
606
+ if (!started || terminals.has(`${started.kind}:${started.attemptId}`))
607
+ continue;
608
+ // A hard-killed provider cannot report tokens or price. Charge one
609
+ // explicit unpriced attempt exactly once before any unit can rerun.
610
+ await opened.recordAccounting(modelAttemptId(MODEL_ATTEMPT_ABANDONED_PREFIX, started.kind, started.attemptId), { unpricedModelCalls: 1 });
611
+ }
612
+ await opened.write();
613
+ return opened;
614
+ }
615
+ catch (error) {
616
+ await ownership.release();
617
+ throw error;
618
+ }
619
+ };
620
+ export const listExperimentRuns = async (runsRoot) => {
621
+ const root = join(resolve(runsRoot), EXPERIMENTS_DIR);
622
+ if (!existsSync(root))
623
+ return [];
624
+ const directories = (await readdir(root, { withFileTypes: true }))
625
+ .filter((entry) => entry.isDirectory())
626
+ .map((entry) => join(root, entry.name));
627
+ const runs = await Promise.all(directories.map(async (experimentDir) => ({
628
+ experimentDir,
629
+ experiment: await loadExperimentRun(experimentDir),
630
+ })));
631
+ return runs
632
+ .flatMap(({ experimentDir, experiment }) => experiment ? [{ ...experiment, experimentDir }] : [])
633
+ .sort((a, b) => b.experimentId.localeCompare(a.experimentId));
634
+ };
635
+ export const loadExperimentCandidate = async (experimentDir, candidateId) => {
636
+ const experiment = await loadExperimentRun(experimentDir);
637
+ const ref = experiment?.candidates.find((entry) => entry.id === candidateId);
638
+ return experiment && ref ? verifiedCandidate(experimentDir, experiment, ref) : null;
639
+ };
640
+ export const loadExperimentCandidates = async (experimentDir) => {
641
+ const experiment = await loadExperimentRun(experimentDir);
642
+ if (!experiment)
643
+ throw new Error(`experiment manifest not found or invalid at ${experimentDir}`);
644
+ return Promise.all(experiment.candidates.map((candidate) => verifiedCandidate(experimentDir, experiment, candidate)));
645
+ };
646
+ const strictAccountingJournal = async (experimentDir) => {
647
+ const path = join(resolve(experimentDir), ACCOUNTING_JOURNAL_FILE);
648
+ const info = await lstat(path).catch((error) => {
649
+ if (error.code === "ENOENT")
650
+ return null;
651
+ throw error;
652
+ });
653
+ if (!info)
654
+ return [];
655
+ if (info.isSymbolicLink() || !info.isFile()) {
656
+ throw new Error("experiment accounting journal must be a regular file");
657
+ }
658
+ return (await readFile(path, "utf8"))
659
+ .split("\n")
660
+ .filter((line) => line.trim().length > 0)
661
+ .map((line, index) => {
662
+ try {
663
+ return AccountingJournalEntrySchema.parse(JSON.parse(line));
664
+ }
665
+ catch {
666
+ throw new Error(`invalid accounting journal entry at line ${index + 1}`);
667
+ }
668
+ });
669
+ };
670
+ export const verifyExperimentRunIntegrity = async (experimentDir, experiment) => {
671
+ const manifestPath = join(resolve(experimentDir), MANIFEST_FILE);
672
+ const manifestInfo = await lstat(manifestPath);
673
+ if (manifestInfo.isSymbolicLink() || !manifestInfo.isFile()) {
674
+ throw new Error("experiment manifest must be a regular file");
675
+ }
676
+ const candidateIds = experiment.candidates.map((candidate) => candidate.id);
677
+ const candidatePaths = experiment.candidates.map((candidate) => candidate.path);
678
+ if (new Set(candidateIds).size !== candidateIds.length) {
679
+ throw new Error("experiment candidate IDs must be unique");
680
+ }
681
+ if (new Set(candidatePaths).size !== candidatePaths.length) {
682
+ throw new Error("experiment candidate paths must be unique");
683
+ }
684
+ const candidates = await Promise.all(experiment.candidates.map((candidate) => verifiedCandidate(experimentDir, experiment, candidate)));
685
+ const journal = await strictAccountingJournal(experimentDir);
686
+ if (journal.length !== experiment.accountingJournalEntries) {
687
+ throw new Error(`accounting journal checkpoint mismatch: manifest=${experiment.accountingJournalEntries} journal=${journal.length}`);
688
+ }
689
+ const ids = new Set();
690
+ const reconstructed = {
691
+ metricCalls: 0,
692
+ judgeCalls: 0,
693
+ proposalCalls: 0,
694
+ unpricedModelCalls: 0,
695
+ usage: emptyUsage(),
696
+ costUsd: 0,
697
+ };
698
+ for (const entry of journal) {
699
+ if (ids.has(entry.id))
700
+ throw new Error(`duplicate accounting journal id: ${entry.id}`);
701
+ ids.add(entry.id);
702
+ applyAccountingDelta(reconstructed, entry.delta);
703
+ }
704
+ const actual = experiment.budgetUsage;
705
+ if (reconstructed.metricCalls !== actual.metricCalls ||
706
+ reconstructed.judgeCalls !== actual.judgeCalls ||
707
+ reconstructed.proposalCalls !== actual.proposalCalls ||
708
+ (reconstructed.unpricedModelCalls ?? 0) !== (actual.unpricedModelCalls ?? 0) ||
709
+ reconstructed.usage.input !== actual.usage.input ||
710
+ reconstructed.usage.output !== actual.usage.output ||
711
+ reconstructed.usage.cacheRead !== actual.usage.cacheRead ||
712
+ reconstructed.usage.cacheCreation !== actual.usage.cacheCreation ||
713
+ reconstructed.costUsd !== actual.costUsd) {
714
+ throw new Error("experiment budget usage does not match its durable accounting journal");
715
+ }
716
+ return candidates;
717
+ };
718
+ export const isExperimentRun = (path) => existsSync(join(path, MANIFEST_FILE));
719
+ const conciseNumber = (value) => Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
720
+ const conciseText = (value, maximum = 160) => value.length <= maximum ? value : `${value.slice(0, maximum - 1)}…`;
721
+ export const formatExperimentRun = (path, experiment) => {
722
+ const usage = experiment.budgetUsage.usage;
723
+ const tokens = totalTokens(usage);
724
+ const lines = [
725
+ `experimentDir: ${path}`,
726
+ `experiment: ${experiment.experimentId}`,
727
+ `campaign: ${experiment.campaignId}`,
728
+ `evaluation: ${experiment.evaluationKind}`,
729
+ `mode: ${experiment.mode}`,
730
+ `status: ${experiment.status}`,
731
+ `started: ${experiment.startedAt}`,
732
+ `ended: ${experiment.endedAt ?? "—"}`,
733
+ `candidates: ${experiment.candidates.length}`,
734
+ `evaluations: ${experiment.evaluations.length}`,
735
+ `champion: ${experiment.championCandidateId ?? "—"}`,
736
+ `spend: metric=${experiment.budgetUsage.metricCalls} judge=${experiment.budgetUsage.judgeCalls} proposal=${experiment.budgetUsage.proposalCalls} unpriced=${experiment.budgetUsage.unpricedModelCalls ?? 0}`,
737
+ `tokens: ${tokens} (input=${usage.input} output=${usage.output} cache-read=${usage.cacheRead} cache-create=${usage.cacheCreation})`,
738
+ `reported cost: $${experiment.budgetUsage.costUsd.toFixed(4)}`,
739
+ ];
740
+ if (experiment.errorMessage)
741
+ lines.push(`error: ${experiment.errorMessage}`);
742
+ if (experiment.selection) {
743
+ lines.push(`ranking: ${experiment.selection.rankedCandidateIds.join(" > ") || "—"}`, `eligible: ${experiment.selection.eligibleCandidateIds.join(", ") || "none"}`, "gates:");
744
+ for (const constraint of experiment.selection.constraints) {
745
+ const scope = [
746
+ constraint.profile ? `profile=${constraint.profile}` : null,
747
+ `split=${constraint.split}`,
748
+ ]
749
+ .filter(Boolean)
750
+ .join(" ");
751
+ const interval = constraint.interval
752
+ ? ` ${constraint.interval.method}=${conciseNumber(constraint.interval.estimate)} [${conciseNumber(constraint.interval.lower)},${conciseNumber(constraint.interval.upper)}] n=${constraint.interval.n}`
753
+ : "";
754
+ const note = constraint.note ? ` note=${conciseText(constraint.note)}` : "";
755
+ lines.push(` ${constraint.passed ? "✓" : "✗"} ${constraint.candidateId} ${constraint.metric} ${scope} values=${constraint.values.map(conciseNumber).join(",") || "—"}${interval}${note}`);
756
+ }
757
+ }
758
+ if (experiment.evaluations.length > 0)
759
+ lines.push("evidence:");
760
+ for (const evaluation of experiment.evaluations) {
761
+ const evaluationTokens = evaluation.usage ? totalTokens(evaluation.usage) : 0;
762
+ lines.push(` ${evaluation.candidateId} profile=${evaluation.profileId} split=${evaluation.split} repetitions=${evaluation.repetitions} pass=${conciseNumber(evaluation.passRate)} calls=${evaluation.metricCalls ?? "—"}/${evaluation.judgeCalls ?? "—"} tokens=${evaluationTokens} unpriced=${evaluation.unpricedModelCalls ?? 0} cost=$${(evaluation.costUsd ?? 0).toFixed(4)}`, ` cases: ${Object.entries(evaluation.casePassRates)
763
+ .sort(([a], [b]) => a.localeCompare(b))
764
+ .map(([id, passRate]) => `${id}=${conciseNumber(passRate)}`)
765
+ .join(", ") || "—"}`);
766
+ for (const failure of evaluation.failedCases) {
767
+ lines.push(` failure: ${failure.caseId} score=${failure.score === null ? "—" : conciseNumber(failure.score)} ${conciseText(failure.feedback[0] ?? "no feedback")}`);
768
+ }
769
+ }
770
+ if (experiment.candidates.length > 0)
771
+ lines.push("candidates:");
772
+ for (const candidate of experiment.candidates) {
773
+ lines.push(` ${candidate.id === experiment.championCandidateId ? "★" : " "} ${candidate.id} ${candidate.sourceBytes} bytes ${basename(candidate.path)}`);
774
+ }
775
+ return lines.join("\n");
776
+ };
777
+ export const formatExperimentRunsTable = (experiments) => {
778
+ if (experiments.length === 0)
779
+ return "(no experiment runs found)";
780
+ const rows = [
781
+ ["status", "experiment", "campaign", "mode", "candidates", "champion"],
782
+ ...experiments.map((experiment) => [
783
+ experiment.status.toUpperCase(),
784
+ basename(experiment.experimentDir),
785
+ experiment.campaignId,
786
+ experiment.mode,
787
+ String(experiment.candidates.length),
788
+ experiment.championCandidateId ?? "—",
789
+ ]),
790
+ ];
791
+ const widths = (rows[0] ?? []).map((_, column) => Math.max(...rows.map((row) => row[column]?.length ?? 0)));
792
+ return rows
793
+ .map((row) => row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" "))
794
+ .join("\n");
795
+ };
796
+ //# sourceMappingURL=experiment-runs.js.map