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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/lib/answerers/claude-code/index.d.ts +14 -2
  2. package/lib/answerers/claude-code/index.d.ts.map +1 -1
  3. package/lib/answerers/claude-code/index.js +3 -3
  4. package/lib/answerers/claude-code/index.js.map +1 -1
  5. package/lib/answerers/persona/index.d.ts +28 -2
  6. package/lib/answerers/persona/index.d.ts.map +1 -1
  7. package/lib/answerers/persona/index.js +3 -3
  8. package/lib/answerers/persona/index.js.map +1 -1
  9. package/lib/answerers/terminal/index.d.ts +8 -2
  10. package/lib/answerers/terminal/index.d.ts.map +1 -1
  11. package/lib/answerers/terminal/index.js +3 -2
  12. package/lib/answerers/terminal/index.js.map +1 -1
  13. package/lib/assertions/core/resource-checks.d.ts.map +1 -1
  14. package/lib/assertions/core/resource-checks.js +3 -5
  15. package/lib/assertions/core/resource-checks.js.map +1 -1
  16. package/lib/assertions/rubric/index.d.ts.map +1 -1
  17. package/lib/assertions/rubric/index.js +7 -1
  18. package/lib/assertions/rubric/index.js.map +1 -1
  19. package/lib/assertions/rubric/internal.d.ts +16 -5
  20. package/lib/assertions/rubric/internal.d.ts.map +1 -1
  21. package/lib/assertions/rubric/internal.js +10 -6
  22. package/lib/assertions/rubric/internal.js.map +1 -1
  23. package/lib/authoring.d.ts +32 -2
  24. package/lib/authoring.d.ts.map +1 -1
  25. package/lib/authoring.js +33 -1
  26. package/lib/authoring.js.map +1 -1
  27. package/lib/cli/bin.js +2 -13
  28. package/lib/cli/bin.js.map +1 -1
  29. package/lib/cli/command-runtime.d.ts +3 -3
  30. package/lib/cli/command-runtime.d.ts.map +1 -1
  31. package/lib/cli/command-runtime.js +17 -6
  32. package/lib/cli/command-runtime.js.map +1 -1
  33. package/lib/cli/init-templates.d.ts +11 -0
  34. package/lib/cli/init-templates.d.ts.map +1 -0
  35. package/lib/cli/init-templates.js +207 -0
  36. package/lib/cli/init-templates.js.map +1 -0
  37. package/lib/cli/init.d.ts +7 -0
  38. package/lib/cli/init.d.ts.map +1 -1
  39. package/lib/cli/init.js +23 -173
  40. package/lib/cli/init.js.map +1 -1
  41. package/lib/cli/output-schemas.d.ts +1399 -0
  42. package/lib/cli/output-schemas.d.ts.map +1 -0
  43. package/lib/cli/output-schemas.js +91 -0
  44. package/lib/cli/output-schemas.js.map +1 -0
  45. package/lib/cli/program.d.ts +135 -4
  46. package/lib/cli/program.d.ts.map +1 -1
  47. package/lib/cli/program.js +725 -520
  48. package/lib/cli/program.js.map +1 -1
  49. package/lib/cli/render/reporter.d.ts.map +1 -1
  50. package/lib/cli/render/reporter.js +24 -4
  51. package/lib/cli/render/reporter.js.map +1 -1
  52. package/lib/cli/run-options.d.ts +0 -4
  53. package/lib/cli/run-options.d.ts.map +1 -1
  54. package/lib/cli/run-options.js +8 -19
  55. package/lib/cli/run-options.js.map +1 -1
  56. package/lib/cli/view.d.ts +5 -3
  57. package/lib/cli/view.d.ts.map +1 -1
  58. package/lib/cli/view.js +120 -78
  59. package/lib/cli/view.js.map +1 -1
  60. package/lib/core/annotation.d.ts +6 -2
  61. package/lib/core/annotation.d.ts.map +1 -1
  62. package/lib/core/annotation.js +2 -1
  63. package/lib/core/annotation.js.map +1 -1
  64. package/lib/core/answerer.d.ts +12 -0
  65. package/lib/core/answerer.d.ts.map +1 -1
  66. package/lib/core/answerer.js +2 -0
  67. package/lib/core/answerer.js.map +1 -1
  68. package/lib/core/assertion.d.ts +3 -0
  69. package/lib/core/assertion.d.ts.map +1 -1
  70. package/lib/core/assertion.js +3 -0
  71. package/lib/core/assertion.js.map +1 -1
  72. package/lib/core/case.d.ts +24 -2
  73. package/lib/core/case.d.ts.map +1 -1
  74. package/lib/core/case.js +5 -1
  75. package/lib/core/case.js.map +1 -1
  76. package/lib/core/driver.d.ts +40 -0
  77. package/lib/core/driver.d.ts.map +1 -1
  78. package/lib/core/driver.js +12 -0
  79. package/lib/core/driver.js.map +1 -1
  80. package/lib/core/environment.d.ts +18 -0
  81. package/lib/core/environment.d.ts.map +1 -0
  82. package/lib/core/environment.js +21 -0
  83. package/lib/core/environment.js.map +1 -0
  84. package/lib/core/evaluation-clusters.d.ts +17 -0
  85. package/lib/core/evaluation-clusters.d.ts.map +1 -0
  86. package/lib/core/evaluation-clusters.js +23 -0
  87. package/lib/core/evaluation-clusters.js.map +1 -0
  88. package/lib/core/experiment.d.ts +11 -0
  89. package/lib/core/experiment.d.ts.map +1 -1
  90. package/lib/core/experiment.js +31 -17
  91. package/lib/core/experiment.js.map +1 -1
  92. package/lib/core/file-tree.d.ts +7 -0
  93. package/lib/core/file-tree.d.ts.map +1 -0
  94. package/lib/core/file-tree.js +35 -0
  95. package/lib/core/file-tree.js.map +1 -0
  96. package/lib/core/index.d.ts +1 -0
  97. package/lib/core/index.d.ts.map +1 -1
  98. package/lib/core/index.js +1 -0
  99. package/lib/core/index.js.map +1 -1
  100. package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
  101. package/lib/core/lifecycle-fixtures.js +4 -2
  102. package/lib/core/lifecycle-fixtures.js.map +1 -1
  103. package/lib/core/platform-process.d.ts +5 -1
  104. package/lib/core/platform-process.d.ts.map +1 -1
  105. package/lib/core/platform-process.js +48 -10
  106. package/lib/core/platform-process.js.map +1 -1
  107. package/lib/core/run.d.ts +93 -1
  108. package/lib/core/run.d.ts.map +1 -1
  109. package/lib/core/run.js +16 -0
  110. package/lib/core/run.js.map +1 -1
  111. package/lib/core/usage.d.ts +5 -0
  112. package/lib/core/usage.d.ts.map +1 -1
  113. package/lib/core/usage.js +3 -0
  114. package/lib/core/usage.js.map +1 -1
  115. package/lib/drivers/claude-code/index.d.ts +65 -2
  116. package/lib/drivers/claude-code/index.d.ts.map +1 -1
  117. package/lib/drivers/claude-code/index.js +30 -3
  118. package/lib/drivers/claude-code/index.js.map +1 -1
  119. package/lib/drivers/codex/app-events.d.ts.map +1 -1
  120. package/lib/drivers/codex/app-events.js +41 -30
  121. package/lib/drivers/codex/app-events.js.map +1 -1
  122. package/lib/drivers/codex/exec-events.d.ts +4 -0
  123. package/lib/drivers/codex/exec-events.d.ts.map +1 -0
  124. package/lib/drivers/codex/exec-events.js +230 -0
  125. package/lib/drivers/codex/exec-events.js.map +1 -0
  126. package/lib/drivers/codex/index.d.ts +76 -4
  127. package/lib/drivers/codex/index.d.ts.map +1 -1
  128. package/lib/drivers/codex/index.js +35 -232
  129. package/lib/drivers/codex/index.js.map +1 -1
  130. package/lib/drivers/cursor/acp-transport.d.ts +38 -0
  131. package/lib/drivers/cursor/acp-transport.d.ts.map +1 -0
  132. package/lib/drivers/cursor/acp-transport.js +152 -0
  133. package/lib/drivers/cursor/acp-transport.js.map +1 -0
  134. package/lib/drivers/cursor/config.d.ts +22 -0
  135. package/lib/drivers/cursor/config.d.ts.map +1 -0
  136. package/lib/drivers/cursor/config.js +26 -0
  137. package/lib/drivers/cursor/config.js.map +1 -0
  138. package/lib/drivers/cursor/events.d.ts +13 -0
  139. package/lib/drivers/cursor/events.d.ts.map +1 -0
  140. package/lib/drivers/cursor/events.js +73 -0
  141. package/lib/drivers/cursor/events.js.map +1 -0
  142. package/lib/drivers/cursor/index.d.ts +37 -0
  143. package/lib/drivers/cursor/index.d.ts.map +1 -0
  144. package/lib/drivers/cursor/index.js +276 -0
  145. package/lib/drivers/cursor/index.js.map +1 -0
  146. package/lib/drivers/cursor/interaction.d.ts +7 -0
  147. package/lib/drivers/cursor/interaction.d.ts.map +1 -0
  148. package/lib/drivers/cursor/interaction.js +66 -0
  149. package/lib/drivers/cursor/interaction.js.map +1 -0
  150. package/lib/drivers/mcp/index.d.ts +35 -2
  151. package/lib/drivers/mcp/index.d.ts.map +1 -1
  152. package/lib/drivers/mcp/index.js +16 -3
  153. package/lib/drivers/mcp/index.js.map +1 -1
  154. package/lib/drivers/shared/experiment-behavior.d.ts +13 -0
  155. package/lib/drivers/shared/experiment-behavior.d.ts.map +1 -0
  156. package/lib/drivers/shared/experiment-behavior.js +197 -0
  157. package/lib/drivers/shared/experiment-behavior.js.map +1 -0
  158. package/lib/drivers/shared/experiment-policy.d.ts +4 -0
  159. package/lib/drivers/shared/experiment-policy.d.ts.map +1 -0
  160. package/lib/drivers/shared/experiment-policy.js +143 -0
  161. package/lib/drivers/shared/experiment-policy.js.map +1 -0
  162. package/lib/drivers/subprocess/index.d.ts +37 -2
  163. package/lib/drivers/subprocess/index.d.ts.map +1 -1
  164. package/lib/drivers/subprocess/index.js +46 -6
  165. package/lib/drivers/subprocess/index.js.map +1 -1
  166. package/lib/index.d.ts +6 -5
  167. package/lib/index.d.ts.map +1 -1
  168. package/lib/index.js +4 -3
  169. package/lib/index.js.map +1 -1
  170. package/lib/orchestrator/annotation-loader.d.ts +6 -2
  171. package/lib/orchestrator/annotation-loader.d.ts.map +1 -1
  172. package/lib/orchestrator/annotation-loader.js +8 -0
  173. package/lib/orchestrator/annotation-loader.js.map +1 -1
  174. package/lib/orchestrator/annotation-store.d.ts.map +1 -1
  175. package/lib/orchestrator/annotation-store.js +16 -2
  176. package/lib/orchestrator/annotation-store.js.map +1 -1
  177. package/lib/orchestrator/campaign-lifecycle.d.ts +1 -0
  178. package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -1
  179. package/lib/orchestrator/campaign-lifecycle.js +4 -1
  180. package/lib/orchestrator/campaign-lifecycle.js.map +1 -1
  181. package/lib/orchestrator/compare.d.ts +4 -0
  182. package/lib/orchestrator/compare.d.ts.map +1 -1
  183. package/lib/orchestrator/compare.js +13 -1
  184. package/lib/orchestrator/compare.js.map +1 -1
  185. package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
  186. package/lib/orchestrator/comparison-identity.js +1 -0
  187. package/lib/orchestrator/comparison-identity.js.map +1 -1
  188. package/lib/orchestrator/config.d.ts +42 -0
  189. package/lib/orchestrator/config.d.ts.map +1 -1
  190. package/lib/orchestrator/config.js +6 -2
  191. package/lib/orchestrator/config.js.map +1 -1
  192. package/lib/orchestrator/discover.d.ts +1 -0
  193. package/lib/orchestrator/discover.d.ts.map +1 -1
  194. package/lib/orchestrator/discover.js +7 -1
  195. package/lib/orchestrator/discover.js.map +1 -1
  196. package/lib/orchestrator/doctor.d.ts.map +1 -1
  197. package/lib/orchestrator/doctor.js +34 -26
  198. package/lib/orchestrator/doctor.js.map +1 -1
  199. package/lib/orchestrator/driver-capabilities.d.ts +3 -0
  200. package/lib/orchestrator/driver-capabilities.d.ts.map +1 -0
  201. package/lib/orchestrator/driver-capabilities.js +10 -0
  202. package/lib/orchestrator/driver-capabilities.js.map +1 -0
  203. package/lib/orchestrator/driver-identity.d.ts +5 -0
  204. package/lib/orchestrator/driver-identity.d.ts.map +1 -0
  205. package/lib/orchestrator/driver-identity.js +41 -0
  206. package/lib/orchestrator/driver-identity.js.map +1 -0
  207. package/lib/orchestrator/experiment-context.d.ts +10 -0
  208. package/lib/orchestrator/experiment-context.d.ts.map +1 -1
  209. package/lib/orchestrator/experiment-contracts.d.ts +10 -0
  210. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
  211. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
  212. package/lib/orchestrator/experiment-corpus.js +17 -19
  213. package/lib/orchestrator/experiment-corpus.js.map +1 -1
  214. package/lib/orchestrator/experiment-effects.d.ts +33 -0
  215. package/lib/orchestrator/experiment-effects.d.ts.map +1 -0
  216. package/lib/orchestrator/experiment-effects.js +44 -0
  217. package/lib/orchestrator/experiment-effects.js.map +1 -0
  218. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
  219. package/lib/orchestrator/experiment-evaluation.js +36 -3
  220. package/lib/orchestrator/experiment-evaluation.js.map +1 -1
  221. package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
  222. package/lib/orchestrator/experiment-identity.js +18 -29
  223. package/lib/orchestrator/experiment-identity.js.map +1 -1
  224. package/lib/orchestrator/experiment-loader.d.ts +7 -2
  225. package/lib/orchestrator/experiment-loader.d.ts.map +1 -1
  226. package/lib/orchestrator/experiment-report.d.ts +70 -0
  227. package/lib/orchestrator/experiment-report.d.ts.map +1 -1
  228. package/lib/orchestrator/experiment-report.js +9 -1
  229. package/lib/orchestrator/experiment-report.js.map +1 -1
  230. package/lib/orchestrator/experiment-runs.d.ts +22 -1
  231. package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
  232. package/lib/orchestrator/experiment-runs.js +13 -5
  233. package/lib/orchestrator/experiment-runs.js.map +1 -1
  234. package/lib/orchestrator/experiment-runtime-identity.d.ts +0 -16
  235. package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -1
  236. package/lib/orchestrator/experiment-runtime-identity.js +5 -220
  237. package/lib/orchestrator/experiment-runtime-identity.js.map +1 -1
  238. package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
  239. package/lib/orchestrator/experiment-selection.js +52 -0
  240. package/lib/orchestrator/experiment-selection.js.map +1 -1
  241. package/lib/orchestrator/experiment.d.ts +0 -2
  242. package/lib/orchestrator/experiment.d.ts.map +1 -1
  243. package/lib/orchestrator/experiment.js +23 -15
  244. package/lib/orchestrator/experiment.js.map +1 -1
  245. package/lib/orchestrator/fs-read.d.ts +1 -6
  246. package/lib/orchestrator/fs-read.d.ts.map +1 -1
  247. package/lib/orchestrator/fs-read.js +2 -32
  248. package/lib/orchestrator/fs-read.js.map +1 -1
  249. package/lib/orchestrator/grader-experiment.d.ts +17 -1
  250. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  251. package/lib/orchestrator/grader-experiment.js +52 -7
  252. package/lib/orchestrator/grader-experiment.js.map +1 -1
  253. package/lib/orchestrator/grading.d.ts.map +1 -1
  254. package/lib/orchestrator/grading.js +13 -6
  255. package/lib/orchestrator/grading.js.map +1 -1
  256. package/lib/orchestrator/list-runs.d.ts +1 -0
  257. package/lib/orchestrator/list-runs.d.ts.map +1 -1
  258. package/lib/orchestrator/list-runs.js +27 -6
  259. package/lib/orchestrator/list-runs.js.map +1 -1
  260. package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -1
  261. package/lib/orchestrator/optimization-lifecycle.js +3 -1
  262. package/lib/orchestrator/optimization-lifecycle.js.map +1 -1
  263. package/lib/orchestrator/orchestrator.d.ts +6 -1
  264. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  265. package/lib/orchestrator/orchestrator.js +17 -143
  266. package/lib/orchestrator/orchestrator.js.map +1 -1
  267. package/lib/orchestrator/paired-effects.d.ts +34 -0
  268. package/lib/orchestrator/paired-effects.d.ts.map +1 -0
  269. package/lib/orchestrator/paired-effects.js +158 -0
  270. package/lib/orchestrator/paired-effects.js.map +1 -0
  271. package/lib/orchestrator/provenance.d.ts +5 -1
  272. package/lib/orchestrator/provenance.d.ts.map +1 -1
  273. package/lib/orchestrator/provenance.js +16 -3
  274. package/lib/orchestrator/provenance.js.map +1 -1
  275. package/lib/orchestrator/regrade.d.ts +1 -0
  276. package/lib/orchestrator/regrade.d.ts.map +1 -1
  277. package/lib/orchestrator/run-execution.d.ts +2 -0
  278. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  279. package/lib/orchestrator/run-execution.js +18 -3
  280. package/lib/orchestrator/run-execution.js.map +1 -1
  281. package/lib/orchestrator/run-lifecycle.d.ts +1 -0
  282. package/lib/orchestrator/run-lifecycle.d.ts.map +1 -1
  283. package/lib/orchestrator/run-lifecycle.js +3 -2
  284. package/lib/orchestrator/run-lifecycle.js.map +1 -1
  285. package/lib/orchestrator/suite-runs.d.ts +3 -1
  286. package/lib/orchestrator/suite-runs.d.ts.map +1 -1
  287. package/lib/orchestrator/suite-runs.js +12 -2
  288. package/lib/orchestrator/suite-runs.js.map +1 -1
  289. package/lib/orchestrator/suite-summary.d.ts.map +1 -1
  290. package/lib/orchestrator/suite-summary.js +2 -1
  291. package/lib/orchestrator/suite-summary.js.map +1 -1
  292. package/lib/previewer/server.d.ts +6 -0
  293. package/lib/previewer/server.d.ts.map +1 -1
  294. package/lib/previewer/server.js +21 -5
  295. package/lib/previewer/server.js.map +1 -1
  296. package/package.json +2 -2
  297. package/skills/lux/references/cli.md +5 -2
  298. package/skills/lux/references/eval-authoring.md +0 -7
  299. package/skills/lux-answerer/SKILL.md +1 -1
  300. package/src/answerers/claude-code/index.ts +3 -3
  301. package/src/answerers/persona/index.ts +3 -3
  302. package/src/answerers/terminal/index.ts +4 -9
  303. package/src/assertions/core/resource-checks.ts +3 -5
  304. package/src/assertions/rubric/index.ts +7 -1
  305. package/src/assertions/rubric/internal.ts +13 -8
  306. package/src/authoring.ts +331 -2
  307. package/src/cli/bin.ts +2 -12
  308. package/src/cli/command-runtime.ts +20 -9
  309. package/src/cli/init-templates.ts +223 -0
  310. package/src/cli/init.ts +38 -184
  311. package/src/cli/output-schemas.ts +106 -0
  312. package/src/cli/program.ts +621 -548
  313. package/src/cli/render/reporter.ts +25 -4
  314. package/src/cli/run-options.ts +8 -25
  315. package/src/cli/view.ts +45 -84
  316. package/src/core/annotation.ts +2 -1
  317. package/src/core/answerer.ts +9 -0
  318. package/src/core/assertion.ts +3 -0
  319. package/src/core/case.ts +5 -1
  320. package/src/core/driver.ts +38 -0
  321. package/src/core/environment.ts +22 -0
  322. package/src/core/evaluation-clusters.ts +35 -0
  323. package/src/core/experiment.ts +60 -23
  324. package/src/core/file-tree.ts +35 -0
  325. package/src/core/index.ts +1 -0
  326. package/src/core/lifecycle-fixtures.ts +4 -2
  327. package/src/core/platform-process.ts +54 -6
  328. package/src/core/run.ts +20 -0
  329. package/src/core/usage.ts +8 -0
  330. package/src/drivers/claude-code/index.ts +34 -3
  331. package/src/drivers/codex/app-events.ts +43 -33
  332. package/src/drivers/codex/exec-events.ts +258 -0
  333. package/src/drivers/codex/index.ts +37 -262
  334. package/src/drivers/cursor/README.md +57 -0
  335. package/src/drivers/cursor/acp-transport.ts +182 -0
  336. package/src/drivers/cursor/config.ts +29 -0
  337. package/src/drivers/cursor/events.ts +83 -0
  338. package/src/drivers/cursor/index.ts +316 -0
  339. package/src/drivers/cursor/interaction.ts +90 -0
  340. package/src/drivers/mcp/index.ts +16 -3
  341. package/src/drivers/shared/experiment-behavior.ts +214 -0
  342. package/src/drivers/shared/experiment-policy.ts +195 -0
  343. package/src/drivers/subprocess/index.ts +45 -6
  344. package/src/index.ts +16 -5
  345. package/src/orchestrator/annotation-loader.ts +10 -0
  346. package/src/orchestrator/annotation-store.ts +24 -3
  347. package/src/orchestrator/campaign-lifecycle.ts +5 -1
  348. package/src/orchestrator/compare.ts +16 -1
  349. package/src/orchestrator/comparison-identity.ts +1 -0
  350. package/src/orchestrator/config.ts +6 -2
  351. package/src/orchestrator/discover.ts +8 -1
  352. package/src/orchestrator/doctor.ts +40 -22
  353. package/src/orchestrator/driver-capabilities.ts +16 -0
  354. package/src/orchestrator/driver-identity.ts +59 -0
  355. package/src/orchestrator/experiment-corpus.ts +23 -28
  356. package/src/orchestrator/experiment-effects.ts +61 -0
  357. package/src/orchestrator/experiment-evaluation.ts +45 -5
  358. package/src/orchestrator/experiment-identity.ts +27 -36
  359. package/src/orchestrator/experiment-report.ts +21 -1
  360. package/src/orchestrator/experiment-runs.ts +11 -4
  361. package/src/orchestrator/experiment-runtime-identity.ts +5 -243
  362. package/src/orchestrator/experiment-selection.ts +77 -0
  363. package/src/orchestrator/experiment.ts +26 -24
  364. package/src/orchestrator/fs-read.ts +2 -32
  365. package/src/orchestrator/grader-experiment.ts +72 -8
  366. package/src/orchestrator/grading.ts +15 -6
  367. package/src/orchestrator/list-runs.ts +29 -6
  368. package/src/orchestrator/optimization-lifecycle.ts +4 -1
  369. package/src/orchestrator/orchestrator.ts +26 -183
  370. package/src/orchestrator/paired-effects.ts +209 -0
  371. package/src/orchestrator/provenance.ts +23 -3
  372. package/src/orchestrator/run-execution.ts +18 -3
  373. package/src/orchestrator/run-lifecycle.ts +4 -2
  374. package/src/orchestrator/suite-runs.ts +10 -1
  375. package/src/orchestrator/suite-summary.ts +2 -1
  376. package/src/previewer/server.ts +27 -5
  377. package/viewer/app.js +27 -6
  378. package/viewer/detail.js +22 -5
  379. package/lib/answerers/scripted/index.d.ts +0 -18
  380. package/lib/answerers/scripted/index.d.ts.map +0 -1
  381. package/lib/answerers/scripted/index.js +0 -63
  382. package/lib/answerers/scripted/index.js.map +0 -1
  383. package/lib/cli/skills.d.ts +0 -38
  384. package/lib/cli/skills.d.ts.map +0 -1
  385. package/lib/cli/skills.js +0 -84
  386. package/lib/cli/skills.js.map +0 -1
  387. package/src/answerers/scripted/index.ts +0 -78
  388. package/src/cli/skills.ts +0 -129
@@ -19,6 +19,7 @@ import {
19
19
  TurnUsageSchema,
20
20
  totalTokens,
21
21
  } from "../../core/index.js";
22
+ import { hasAgentUsage } from "../../core/usage.js";
22
23
  import type { CaseObserver, SuiteCaseResult, SuiteObserver } from "../../orchestrator/index.js";
23
24
  import {
24
25
  formatAlignedUsage,
@@ -80,6 +81,7 @@ type CaseView = {
80
81
  assertions: GradingAssertionEntry[];
81
82
  skippedAssertions: NonNullable<GradingReport["skippedAssertions"]>;
82
83
  usage: RunUsage;
84
+ agentUsageReported: boolean;
83
85
  /** Per-API-call agent usage, in order — the split behind `usage.agent`. */
84
86
  agentTurns: TurnUsage[];
85
87
  phaseDurationMs: PhaseDurations | null;
@@ -159,6 +161,7 @@ const cell = (text: string, width: number): string => {
159
161
  */
160
162
  const scoreNote = (entry: GradingAssertionEntry): string => {
161
163
  if (entry.errorMessage) return entry.errorMessage;
164
+ if (entry.abstention) return "insufficient evidence";
162
165
  if (entry.passed) return "";
163
166
  const score = formatScore(entry.score);
164
167
  if (entry.threshold > 0) return `score ${score} < ${formatScore(entry.threshold)}`;
@@ -240,6 +243,7 @@ export class ConsoleReporter implements SuiteObserver {
240
243
  assertions: [],
241
244
  skippedAssertions: [],
242
245
  usage: emptyRunUsage(),
246
+ agentUsageReported: false,
243
247
  agentTurns: [],
244
248
  phaseDurationMs: null,
245
249
  runDir: null,
@@ -273,6 +277,13 @@ export class ConsoleReporter implements SuiteObserver {
273
277
  view.toolUnresolved = tools.unresolved;
274
278
  view.toolUnmatchedResults = tools.unmatchedResults;
275
279
  view.toolMalformed = tools.malformedEvents;
280
+ } else if (event.type === "progress" && event.kind === "driver-stderr" && this.verbose) {
281
+ const payload = event.payload as { text?: unknown; truncated?: unknown } | null;
282
+ if (payload && typeof payload.text === "string") {
283
+ this.region.staticLine(this.theme.dim(` [${view.caseId}] ${payload.text}`));
284
+ if (payload.truncated === true)
285
+ this.region.staticLine(this.theme.dim(" [driver stderr truncated]"));
286
+ }
276
287
  } else if (event.type === "progress" && event.kind === "usage") {
277
288
  const turn = TurnUsageSchema.safeParse(event.payload).data;
278
289
  if (turn) view.agentTurns.push(turn);
@@ -307,6 +318,7 @@ export class ConsoleReporter implements SuiteObserver {
307
318
  view.assertions = result.result.grading.assertions;
308
319
  view.skippedAssertions = result.result.grading.skippedAssertions ?? [];
309
320
  view.usage = result.result.metadata.usage;
321
+ view.agentUsageReported = hasAgentUsage(result.result.metadata);
310
322
  view.phaseDurationMs = result.result.metadata.phaseDurationMs ?? null;
311
323
  const tools = result.result.metadata.toolOutcomes;
312
324
  if (tools) {
@@ -451,10 +463,13 @@ export class ConsoleReporter implements SuiteObserver {
451
463
  Math.max(NAME_MIN, ...view.assertions.map((a) => a.name.length)),
452
464
  );
453
465
  for (const entry of view.assertions) {
454
- const mark = entry.passed ? this.theme.green("✓") : this.theme.red("✗");
466
+ let mark = entry.passed ? this.theme.green("✓") : this.theme.red("✗");
467
+ if (entry.abstention) mark = this.theme.dim("?");
455
468
  const duration = formatDuration(entry.durationMs).padStart(DURATION_COL);
456
469
  const note = scoreNote(entry);
457
- const suffix = note ? ` ${this.theme.red(note)}` : "";
470
+ const suffix = note
471
+ ? ` ${entry.abstention ? this.theme.dim(note) : this.theme.red(note)}`
472
+ : "";
458
473
  nodes.push({
459
474
  head: `${mark} ${cell(entry.name, width)} ${this.theme.dim(duration)}${suffix}`,
460
475
  body: !entry.passed && entry.comment ? [this.theme.dim(entry.comment)] : [],
@@ -630,11 +645,17 @@ export class ConsoleReporter implements SuiteObserver {
630
645
  emptyRunUsage(),
631
646
  );
632
647
  const harness = harnessUsage(total);
633
- if (isUsageEmpty(total.agent) && isUsageEmpty(harness)) {
648
+ const reported = this.cases.filter(
649
+ (view) => view.agentUsageReported || !isUsageEmpty(this.agentUsageOf(view)),
650
+ ).length;
651
+ if (reported === 0 && isUsageEmpty(total.agent) && isUsageEmpty(harness)) {
634
652
  return live ? [] : [` ${label("Tokens")}${this.theme.dim("none reported")}`];
635
653
  }
636
654
 
637
- const agentText = isUsageEmpty(total.agent) ? this.theme.dim("none") : formatUsage(total.agent);
655
+ const missing = this.cases.length - reported;
656
+ const partial = missing > 0 ? ` (${missing} run${missing === 1 ? "" : "s"} unavailable)` : "";
657
+ const agentText =
658
+ reported === 0 ? this.theme.dim("unavailable") : `${formatUsage(total.agent)}${partial}`;
638
659
  if (live) {
639
660
  const harnessText = isUsageEmpty(harness)
640
661
  ? ""
@@ -1,4 +1,4 @@
1
- import { InvalidArgumentError } from "commander";
1
+ import { Errors } from "incur";
2
2
 
3
3
  export type RunOptions = {
4
4
  tag?: string[];
@@ -15,22 +15,14 @@ export type RunOptions = {
15
15
  verbose?: boolean;
16
16
  };
17
17
 
18
- export const collectTags = (value: string, previous: string[]): string[] =>
19
- previous.concat(
20
- value
21
- .split(",")
22
- .map((tag) => tag.trim())
23
- .filter(Boolean),
24
- );
25
-
26
- export const collectValues = (value: string, previous: string[]): string[] => [...previous, value];
27
-
28
18
  export const parseAnnotationAssertion = (
29
19
  value: string,
30
20
  ): [string, { passed: boolean; score?: number }] => {
31
21
  const match = /^(.+)=(pass|fail)(?::(.+))?$/.exec(value);
32
- if (!match?.[1] || !match[2]) {
33
- throw new InvalidArgumentError("--assertion must use id=pass, id=fail, or id=pass:0.8 syntax");
22
+ if (!match?.[1]?.trim() || !match[2]) {
23
+ throw new Errors.ParseError({
24
+ message: "--assertion must use id=pass, id=fail, or id=pass:0.8 syntax",
25
+ });
34
26
  }
35
27
  const score = match[3] === undefined ? undefined : parseUnitInterval(match[3]);
36
28
  return [
@@ -42,19 +34,10 @@ export const parseAnnotationAssertion = (
42
34
  ];
43
35
  };
44
36
 
45
- export const parsePositiveInt = (value: string): number => {
46
- // Number() rejects partial numbers such as "3abc" and "2.9".
47
- const parsed = Number(value);
48
- if (!Number.isInteger(parsed) || parsed < 1) {
49
- throw new InvalidArgumentError("must be a positive integer");
50
- }
51
- return parsed;
52
- };
53
-
54
- export const parseUnitInterval = (value: string): number => {
37
+ const parseUnitInterval = (value: string): number => {
55
38
  const parsed = Number(value);
56
- if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
57
- throw new InvalidArgumentError("must be a number between 0 and 1");
39
+ if (!value.trim() || !Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
40
+ throw new Errors.ParseError({ message: "must be a number between 0 and 1" });
58
41
  }
59
42
  return parsed;
60
43
  };
package/src/cli/view.ts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { resolve } from "node:path";
2
- import { InvalidArgumentError } from "commander";
2
+ import { Errors } from "incur";
3
3
  import {
4
- formatExperimentRun,
5
- formatExperimentRunsTable,
6
- formatRunDetail,
7
- formatRunsTable,
8
- formatSuiteRunDetail,
9
- formatSuiteRunsTable,
10
4
  isExperimentRun,
11
5
  listExperimentRuns,
12
6
  listRuns,
@@ -29,7 +23,6 @@ export type ViewOptions = {
29
23
  web?: boolean;
30
24
  port?: number;
31
25
  open?: boolean;
32
- json?: boolean;
33
26
  };
34
27
 
35
28
  const previewKind = async (
@@ -46,19 +39,16 @@ const previewKind = async (
46
39
  return undefined;
47
40
  };
48
41
 
49
- const webViewCommand = async (
42
+ export async function* webViewCommand(
50
43
  runDirArg: string | undefined,
51
44
  options: ViewOptions,
52
- ): Promise<void> => {
53
- if (options.json) {
54
- throw new InvalidArgumentError("--web and --json are mutually exclusive");
55
- }
45
+ ): AsyncGenerator<{ url: string; runsRoot: string }, void> {
56
46
  const config = await loadLuxConfig(process.cwd());
57
47
  const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
58
48
  const initialPath = runDirArg ? resolve(runDirArg) : undefined;
59
- if (initialPath && !(await ensureDirectory("view", initialPath))) return;
49
+ if (initialPath && !(await ensureDirectory("view", initialPath))) failWith(1);
60
50
  const initialKind = await previewKind(initialPath, options);
61
- const controller = abortOnInterrupt();
51
+ using controller = abortOnInterrupt();
62
52
  const preview = await startPreviewServer({
63
53
  runsRoot,
64
54
  ...(options.port !== undefined ? { port: options.port } : {}),
@@ -67,30 +57,29 @@ const webViewCommand = async (
67
57
  ...(options.case ? { caseFilter: options.case } : {}),
68
58
  signal: controller.signal,
69
59
  });
70
- process.stdout.write(`Lux Review: ${preview.url}\n`);
71
- process.stdout.write(`Reading evidence from ${runsRoot}\n`);
72
- if (options.open !== false) {
73
- try {
74
- await openPreviewUrl(preview.url);
75
- } catch (error) {
76
- const message = error instanceof Error ? error.message : String(error);
77
- process.stderr.write(`lux view: could not open a browser (${message})\n`);
60
+ try {
61
+ yield { url: preview.url, runsRoot };
62
+ if (options.open !== false) {
63
+ try {
64
+ await openPreviewUrl(preview.url);
65
+ } catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ process.stderr.write(`lux view: could not open a browser (${message})\n`);
68
+ }
78
69
  }
70
+ await preview.closed;
71
+ if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
72
+ } finally {
73
+ await preview.close();
79
74
  }
80
- await preview.closed;
81
- if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
82
- };
75
+ }
83
76
 
84
77
  export const viewCommand = async (
85
78
  runDirArg: string | undefined,
86
79
  options: ViewOptions,
87
- ): Promise<void> => {
88
- if (options.web) {
89
- await webViewCommand(runDirArg, options);
90
- return;
91
- }
80
+ ): Promise<unknown> => {
92
81
  if (options.port !== undefined || options.open === false) {
93
- throw new InvalidArgumentError("--port and --no-open require --web");
82
+ throw new Errors.ParseError({ message: "--port and --no-open require --web" });
94
83
  }
95
84
  if (runDirArg) {
96
85
  const path = resolve(runDirArg);
@@ -98,71 +87,43 @@ export const viewCommand = async (
98
87
  if (isExperimentRun(path)) {
99
88
  const experiment = await loadExperimentRun(path);
100
89
  if (!experiment) {
101
- process.stderr.write(`lux view: invalid experiment at ${path}\n`);
102
- failWith(1);
103
- return;
90
+ throw new Errors.IncurError({
91
+ code: "INVALID_EXPERIMENT",
92
+ message: `Invalid experiment at ${path}.`,
93
+ exitCode: 1,
94
+ });
104
95
  }
105
- process.stdout.write(
106
- options.json
107
- ? `${JSON.stringify({ experimentDir: path, ...experiment }, null, 2)}\n`
108
- : `${formatExperimentRun(path, experiment)}\n`,
109
- );
110
- return;
96
+ return { experimentDir: path, ...experiment };
111
97
  }
112
98
  const suite = await loadSuiteRun(path);
113
99
  if (suite) {
114
- process.stdout.write(
115
- options.json
116
- ? `${JSON.stringify({ suiteDir: path, ...suite }, null, 2)}\n`
117
- : `${formatSuiteRunDetail(path, suite)}\n`,
118
- );
119
- return;
100
+ return { suiteDir: path, ...suite };
120
101
  }
121
- const detail = await loadRunDetail(path);
122
- process.stdout.write(
123
- options.json ? `${JSON.stringify(detail, null, 2)}\n` : `${formatRunDetail(detail)}\n`,
124
- );
125
- return;
102
+ return loadRunDetail(path);
126
103
  }
127
104
 
128
105
  const config = await loadLuxConfig(process.cwd());
129
106
  const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
107
+ const readErrors: Array<{ path: string; message: string }> = [];
108
+ const listingOptions = {
109
+ onReadError: (path: string, error: unknown) => {
110
+ readErrors.push({ path, message: error instanceof Error ? error.message : String(error) });
111
+ },
112
+ };
130
113
  if (options.experiments) {
131
- const experiments = await listExperimentRuns(runsRoot);
132
- process.stdout.write(
133
- options.json
134
- ? `${JSON.stringify(experiments, null, 2)}\n`
135
- : `experiments at ${runsRoot}:\n${formatExperimentRunsTable(experiments)}\n`,
136
- );
137
- return;
114
+ const experiments = await listExperimentRuns(runsRoot, listingOptions);
115
+ return { runsRoot, experiments, ...(readErrors.length ? { readErrors } : {}) };
138
116
  }
139
117
  if (options.suites) {
140
- const suites = await listSuiteRuns(runsRoot);
141
- process.stdout.write(
142
- options.json
143
- ? `${JSON.stringify(suites, null, 2)}\n`
144
- : `suite runs at ${runsRoot}:\n${formatSuiteRunsTable(suites)}\n`,
145
- );
146
- return;
118
+ const suites = await listSuiteRuns(runsRoot, listingOptions);
119
+ return { runsRoot, suites, ...(readErrors.length ? { readErrors } : {}) };
147
120
  }
148
121
  const all = await listRuns(runsRoot);
149
122
  const runs = options.case ? all.filter((run) => run.caseSlug === options.case) : all;
150
- if (options.json) {
151
- process.stdout.write(`${JSON.stringify(runs, null, 2)}\n`);
152
- return;
153
- }
154
- if (!options.case) {
155
- process.stdout.write(`suite runs at ${runsRoot}:\n`);
156
- process.stdout.write(`${formatSuiteRunsTable(await listSuiteRuns(runsRoot))}\n\n`);
157
- }
158
- process.stdout.write(`runs at ${runsRoot}:\n`);
159
- process.stdout.write(`${formatRunsTable(runs)}\n`);
160
- };
161
-
162
- export const parsePreviewPort = (value: string): number => {
163
- const port = Number(value);
164
- if (!Number.isInteger(port) || port < 0 || port > 65_535) {
165
- throw new InvalidArgumentError("port must be an integer from 0 to 65535");
166
- }
167
- return port;
123
+ return {
124
+ runsRoot,
125
+ ...(!options.case ? { suites: await listSuiteRuns(runsRoot, listingOptions) } : {}),
126
+ runs,
127
+ ...(readErrors.length ? { readErrors } : {}),
128
+ };
168
129
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { EvaluationMetadataSchema } from "./evaluation-clusters.js";
2
3
  import { formatZodIssues } from "./zod.js";
3
4
 
4
5
  const ExpectedBooleanLabelSchema = z
@@ -35,7 +36,7 @@ export const GraderAnnotationSchema = z
35
36
  reviewer: z.string().min(1).optional(),
36
37
  /** Human rationale supplied to the rubric optimizer on mismatches. */
37
38
  feedback: z.string().optional(),
38
- meta: z.record(z.string(), z.unknown()).optional(),
39
+ meta: EvaluationMetadataSchema.optional(),
39
40
  })
40
41
  .strict()
41
42
  .superRefine((annotation, ctx) => {
@@ -73,3 +73,12 @@ export type AnswererPlugin<Cfg = unknown> = {
73
73
  configSchema?: z.ZodTypeAny;
74
74
  create: (config: Cfg) => Answerer | Promise<Answerer>;
75
75
  };
76
+
77
+ export const defineAnswerer = <S extends z.ZodType>(
78
+ definition: Omit<AnswererPlugin<z.output<S>>, "configSchema"> & { configSchema: S },
79
+ ): Omit<AnswererPlugin<z.output<S>>, "configSchema"> & { configSchema: S } => definition;
80
+
81
+ export const answererRef = <S extends z.ZodType>(
82
+ plugin: { name: string; configSchema: S },
83
+ config: z.input<S>,
84
+ ): { name: string; config: z.input<S> } => ({ name: plugin.name, config });
@@ -9,6 +9,8 @@ export const AssertionResultSchema = z.object({
9
9
  score: z.number().min(0).max(1),
10
10
  comment: z.exactOptional(z.string()),
11
11
  detail: z.unknown().optional(),
12
+ /** The evaluator ran successfully but lacked evidence for a verdict. */
13
+ abstention: z.exactOptional(z.literal("insufficient-evidence")),
12
14
  /** Present when the assertion could not produce a judgment. */
13
15
  errorMessage: z.exactOptional(z.string()),
14
16
  /** Tokens the assertion itself spent. Only LLM-judge assertions report this. */
@@ -143,6 +145,7 @@ export const GradingAssertionEntrySchema = z.object({
143
145
  durationMs: z.number(),
144
146
  comment: z.exactOptional(z.string()),
145
147
  detail: z.unknown().optional(),
148
+ abstention: z.exactOptional(z.literal("insufficient-evidence")),
146
149
  errorMessage: z.exactOptional(z.string()),
147
150
  usage: z.exactOptional(TokenUsageSchema),
148
151
  });
package/src/core/case.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import type { AssertionPlugin } from "./assertion.js";
3
3
  import { collectEvalCaseExtensions, setEvalCaseExtensions } from "./case-authoring-state.js";
4
+ import { DriverCapabilitySchema } from "./driver.js";
5
+ import { EvaluationMetadataSchema } from "./evaluation-clusters.js";
4
6
  import type { LifecycleHooksPlugin } from "./lifecycle-hooks.js";
5
7
  import { formatZodIssues } from "./zod.js";
6
8
 
@@ -46,8 +48,9 @@ const EvalCaseObjectSchema = z.object({
46
48
  driver: DriverRefSchema.optional(),
47
49
  answerer: AnswererRefSchema.optional(),
48
50
  fixtures: FixturesRefSchema.optional(),
51
+ requires: z.array(DriverCapabilitySchema).optional(),
49
52
  assertions: z.array(AssertionSchema).optional(),
50
- meta: z.record(z.string(), z.unknown()).optional(),
53
+ meta: EvaluationMetadataSchema.optional(),
51
54
  });
52
55
 
53
56
  export const EvalCaseSchema = EvalCaseObjectSchema
@@ -149,6 +152,7 @@ export const PersistedCaseSchema = EvalCaseObjectSchema.partial()
149
152
  driver: PersistedDriverRefSchema.optional(),
150
153
  answerer: PersistedAnswererRefSchema.optional(),
151
154
  fixtures: PersistedFixturesRefSchema.optional(),
155
+ meta: z.record(z.string(), z.unknown()).optional(),
152
156
  })
153
157
  .loose();
154
158
  export type PersistedCase = z.infer<typeof PersistedCaseSchema>;
@@ -71,8 +71,46 @@ export interface AgentDriver {
71
71
  close(): Promise<void>;
72
72
  }
73
73
 
74
+ export const DriverCapabilitySchema = z.enum([
75
+ "interactive",
76
+ "tool-calls",
77
+ "tool-results",
78
+ "artifacts",
79
+ "subagents",
80
+ "usage",
81
+ "cost",
82
+ "isolation",
83
+ ]);
84
+ export type DriverCapability = z.infer<typeof DriverCapabilitySchema>;
85
+
86
+ export type DriverRuntime = {
87
+ commands: string[];
88
+ /** Missing keys mean unknown; false means the adapter cannot provide the capability. */
89
+ capabilities: Partial<Record<DriverCapability, boolean>>;
90
+ };
91
+
92
+ export type DriverExperiment<Cfg> = {
93
+ version: string;
94
+ /** Bind a configuration to the experiment environment; Lux validates the returned config. */
95
+ prepare(config: Cfg): unknown;
96
+ /** Fingerprint behavior outside authored cases/configuration and the reported executables. */
97
+ identity(config: Cfg, context: { rootDir: string }): unknown | Promise<unknown>;
98
+ };
99
+
74
100
  export type DriverPlugin<Cfg = unknown> = {
75
101
  name: string;
76
102
  configSchema?: z.ZodTypeAny;
77
103
  create: (config: Cfg) => AgentDriver | Promise<AgentDriver>;
104
+ runtime?: (config: Cfg) => DriverRuntime;
105
+ /** Ordinary runs remain supported when controlled experiments are not declared. */
106
+ experiment?: DriverExperiment<Cfg>;
78
107
  };
108
+
109
+ export const defineDriver = <S extends z.ZodType>(
110
+ definition: Omit<DriverPlugin<z.output<S>>, "configSchema"> & { configSchema: S },
111
+ ): Omit<DriverPlugin<z.output<S>>, "configSchema"> & { configSchema: S } => definition;
112
+
113
+ export const driverRef = <S extends z.ZodType>(
114
+ plugin: { name: string; configSchema: S },
115
+ config: z.input<S>,
116
+ ): { name: string; config: z.input<S> } => ({ name: plugin.name, config });
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ /** Author-declared execution conditions. Lux records these without enforcing or verifying them. */
4
+ export const EnvironmentDeclarationSchema = z
5
+ .object({
6
+ imageDigest: z
7
+ .string()
8
+ .regex(/^(?:[^@\s]+@)?sha256:[a-f0-9]{64}$/)
9
+ .optional(),
10
+ toolchains: z.record(z.string().min(1), z.string().min(1)).optional(),
11
+ network: z.enum(["disabled", "restricted", "unrestricted"]).optional(),
12
+ resources: z
13
+ .object({
14
+ cpuCount: z.number().positive().optional(),
15
+ memoryBytes: z.number().int().positive().optional(),
16
+ diskBytes: z.number().int().positive().optional(),
17
+ })
18
+ .strict()
19
+ .optional(),
20
+ })
21
+ .strict();
22
+ export type EnvironmentDeclaration = z.infer<typeof EnvironmentDeclarationSchema>;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+
3
+ export const EvaluationMetadataSchema = z
4
+ .object({
5
+ /** Related perturbations share one independent statistical unit. */
6
+ cluster: z.string().trim().min(1).optional(),
7
+ })
8
+ .catchall(z.unknown());
9
+
10
+ export const clusterKey = (id: string, cluster: string | undefined): string =>
11
+ JSON.stringify(cluster === undefined ? ["case", id] : ["cluster", cluster]);
12
+
13
+ export const independentClusterCount = (
14
+ items: readonly { id: string; cluster?: string | undefined }[],
15
+ ): number => new Set(items.map((item) => clusterKey(item.id, item.cluster))).size;
16
+
17
+ export const assertDisjointClusters = (
18
+ splits: readonly {
19
+ name: string;
20
+ items: readonly { id: string; cluster?: string | undefined }[];
21
+ }[],
22
+ ): void => {
23
+ const owners = new Map<string, string>();
24
+ for (const split of splits) {
25
+ for (const item of split.items) {
26
+ if (item.cluster === undefined) continue;
27
+ const owner = owners.get(item.cluster);
28
+ if (owner !== undefined && owner !== split.name)
29
+ throw new Error(
30
+ `evaluation cluster '${item.cluster}' appears in both ${owner} and ${split.name} splits`,
31
+ );
32
+ owners.set(item.cluster, split.name);
33
+ }
34
+ }
35
+ };
@@ -84,6 +84,8 @@ export type ExperimentVariant = z.output<typeof ExperimentVariantSchema>;
84
84
  export const ExperimentConstraintSchema = z
85
85
  .object({
86
86
  metric: z.string().min(1),
87
+ /** Bounds apply to paired candidate-minus-baseline case means. */
88
+ relativeTo: z.exactOptional(z.literal("baseline")),
87
89
  min: z.number().optional(),
88
90
  max: z.number().optional(),
89
91
  profile: z.string().optional(),
@@ -151,6 +153,47 @@ const DEFAULT_EXPERIMENT_BUDGET: ExperimentBudget = {
151
153
  maxWallTimeMs: 3_600_000,
152
154
  };
153
155
 
156
+ const validateConstraintConfidence = (
157
+ constraint: ExperimentConstraint,
158
+ index: number,
159
+ boundedMetrics: Set<string>,
160
+ ctx: z.RefinementCtx,
161
+ ): void => {
162
+ const issue = (message: string): void =>
163
+ ctx.addIssue({ code: "custom", path: ["objectives", "constraints", index], message });
164
+ if (constraint.relativeTo && constraint.confidence?.method === "wilson") {
165
+ issue(
166
+ "paired differences are not Bernoulli observations; use Hoeffding or percentile-bootstrap confidence",
167
+ );
168
+ }
169
+ if (
170
+ constraint.relativeTo &&
171
+ ["subject.bytes", "quality.caseRegressions"].includes(constraint.metric)
172
+ ) {
173
+ issue("relative constraints require a metric with per-case observations");
174
+ }
175
+ const bounded = boundedMetrics.has(constraint.metric);
176
+ if (
177
+ (constraint.confidence?.method === "hoeffding" || constraint.confidence?.method === "wilson") &&
178
+ !bounded
179
+ ) {
180
+ issue(
181
+ `metric '${constraint.metric}' has no declared [0,1] support; use percentile-bootstrap confidence`,
182
+ );
183
+ }
184
+ const minimum = constraint.relativeTo ? -1 : 0;
185
+ if (
186
+ bounded &&
187
+ [constraint.min, constraint.max].some(
188
+ (bound) => bound !== undefined && (bound < minimum || bound > 1),
189
+ )
190
+ ) {
191
+ issue(
192
+ `metric '${constraint.metric}' has support [${minimum},1]; constraint bounds must stay in that interval`,
193
+ );
194
+ }
195
+ };
196
+
154
197
  export const ExperimentCampaignSchema = z
155
198
  .object({
156
199
  id: z.string().min(1),
@@ -215,6 +258,8 @@ export const ExperimentCampaignSchema = z
215
258
  "grader.macroFalseNegativeRate",
216
259
  "grader.coverage",
217
260
  "grader.abstentionRate",
261
+ "grader.insufficientEvidenceRate",
262
+ "grader.operationalErrorRate",
218
263
  "grader.scoreMae",
219
264
  ]);
220
265
  const unitIntervalMetrics = graderConfidenceMetrics;
@@ -289,29 +334,7 @@ export const ExperimentCampaignSchema = z
289
334
  profiles.add(profile);
290
335
  }
291
336
  for (const [index, constraint] of campaign.objectives.constraints.entries()) {
292
- if (
293
- (constraint.confidence?.method === "hoeffding" ||
294
- constraint.confidence?.method === "wilson") &&
295
- !unitIntervalMetrics.has(constraint.metric)
296
- ) {
297
- ctx.addIssue({
298
- code: "custom",
299
- path: ["objectives", "constraints", index, "confidence", "method"],
300
- message: `metric '${constraint.metric}' has no declared [0,1] support; use percentile-bootstrap confidence`,
301
- });
302
- }
303
- if (
304
- unitIntervalMetrics.has(constraint.metric) &&
305
- [constraint.min, constraint.max].some(
306
- (bound) => bound !== undefined && (bound < 0 || bound > 1),
307
- )
308
- ) {
309
- ctx.addIssue({
310
- code: "custom",
311
- path: ["objectives", "constraints", index],
312
- message: `metric '${constraint.metric}' has support [0,1]; constraint bounds must stay in that interval`,
313
- });
314
- }
337
+ validateConstraintConfidence(constraint, index, unitIntervalMetrics, ctx);
315
338
  if (constraint.profile && !profiles.has(constraint.profile)) {
316
339
  ctx.addIssue({
317
340
  code: "custom",
@@ -424,6 +447,20 @@ export const CandidateEvaluationSchema = z.object({
424
447
  costUsd: z.exactOptional(z.number().nonnegative()),
425
448
  /** Raw observations used by confidence-aware constraints. */
426
449
  samples: z.exactOptional(z.record(z.string(), z.array(z.number()))),
450
+ /** Independent cases with repetition coverage retained for paired comparisons. */
451
+ caseSamples: z.exactOptional(
452
+ z.record(
453
+ z.string(),
454
+ z.object({
455
+ identityHash: z.string().nullable(),
456
+ clusterId: z.exactOptional(z.string().min(1)),
457
+ metrics: z.record(
458
+ z.string(),
459
+ z.object({ mean: z.number().finite(), n: z.number().int().positive() }),
460
+ ),
461
+ }),
462
+ ),
463
+ ),
427
464
  failedCases: z.array(
428
465
  z.object({
429
466
  caseId: z.string(),
@@ -0,0 +1,35 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir } from "node:fs/promises";
3
+ import { join, relative, resolve } from "node:path";
4
+
5
+ /**
6
+ * Walk authored trees without silently following or omitting a symlink that
7
+ * can change outside the experiment identity. Exact ignored paths support
8
+ * generated state directories whose names are project-configurable.
9
+ */
10
+ export const walkAuthoredFiles = async (
11
+ root: string,
12
+ ignoredDirectoryNames: ReadonlySet<string> = new Set(),
13
+ ignoredPaths: ReadonlySet<string> = new Set(),
14
+ ): Promise<string[]> => {
15
+ if (!existsSync(root)) return [];
16
+ const out: string[] = [];
17
+ const stack = [root];
18
+ while (stack.length > 0) {
19
+ const dir = stack.pop();
20
+ if (!dir) continue;
21
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
22
+ const full = join(dir, entry.name);
23
+ if (ignoredPaths.has(resolve(full))) continue;
24
+ if (entry.isDirectory() && ignoredDirectoryNames.has(entry.name)) continue;
25
+ if (entry.isSymbolicLink()) {
26
+ throw new Error(
27
+ `authored evaluation trees cannot contain symlinks: ${relative(root, full)}`,
28
+ );
29
+ }
30
+ if (entry.isDirectory()) stack.push(full);
31
+ else if (entry.isFile()) out.push(relative(root, full));
32
+ }
33
+ }
34
+ return out.sort();
35
+ };
package/src/core/index.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./conversation-turn.js";
11
11
  export * from "./driver.js";
12
12
  export * from "./driver-metrics.js";
13
13
  export * from "./emitter.js";
14
+ export * from "./environment.js";
14
15
  export * from "./events.js";
15
16
  export * from "./exec.js";
16
17
  export * from "./experiment.js";