@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
@@ -5,20 +5,17 @@ import { ReflectiveOptimizerConfigSchema } from "../optimization/reflective.js";
5
5
  import { authoredDependencyGraphHash } from "./authored-dependencies.js";
6
6
  import { assertValidationArgumentsBound } from "./candidates.js";
7
7
  import { selectCases } from "./discover.js";
8
+ import { experimentDriverIdentity, referencedDriverRuntime } from "./driver-identity.js";
8
9
  import { evaluatorImplementationHash } from "./evaluator-identity.js";
9
10
  import type { CampaignExecutionOptions, EvaluationCorpus } from "./experiment-context.js";
10
11
  import { profileOrThrow } from "./experiment-context.js";
11
12
  import { caseRubricCliReferences, selectorsBySplit } from "./experiment-corpus.js";
12
13
  import {
13
- claudeConfigRoot,
14
- claudeUserBehaviorHash,
15
14
  cliIdentity,
16
- configuredClaudeSourceHashes,
17
15
  LUX_VERSION,
18
16
  type RuntimeCliReference,
19
17
  RuntimeCliReferenceSchema,
20
18
  referencedAnswererCli,
21
- referencedCli,
22
19
  runtimeCliIdentityHashForReferences,
23
20
  } from "./experiment-runtime-identity.js";
24
21
  import { loadEvalCase } from "./loader.js";
@@ -33,15 +30,6 @@ import { validatePluginConfig } from "./validate-plugin-config.js";
33
30
  export const resolvedProfileSnapshot = async (
34
31
  execution: CampaignExecutionOptions,
35
32
  ): Promise<Record<string, unknown>> => {
36
- const behaviorHashes = new Map<string, Promise<string | null>>();
37
- const configuredSourceHashes = new Map<string, Promise<string>>();
38
- const behaviorHash = (root: string): Promise<string | null> => {
39
- const existing = behaviorHashes.get(root);
40
- if (existing) return existing;
41
- const created = claudeUserBehaviorHash(root);
42
- behaviorHashes.set(root, created);
43
- return created;
44
- };
45
33
  return Object.fromEntries(
46
34
  await Promise.all(
47
35
  execution.loaded.campaign.profiles.map(async (profileId) => {
@@ -53,20 +41,12 @@ export const resolvedProfileSnapshot = async (
53
41
  execution.loaded.campaign.evaluation.kind === "agent"
54
42
  ? (profile.driver ?? execution.config.defaultDriver)
55
43
  : undefined;
56
- const driverCli = referencedCli(driver);
57
44
  const harness = profile.harness ?? execution.config.harness;
58
45
  const answerer =
59
46
  execution.loaded.campaign.evaluation.kind === "agent"
60
47
  ? (profile.answerer ?? execution.config.defaultAnswerer)
61
48
  : undefined;
62
49
  const answererCli = referencedAnswererCli(answerer);
63
- const driverUserBehaviorHash =
64
- driverCli?.name === "claude-code" ? await behaviorHash(claudeConfigRoot(driver)) : null;
65
- const driverConfiguredSources = await configuredClaudeSourceHashes(
66
- driver,
67
- execution.config.rootDir,
68
- configuredSourceHashes,
69
- );
70
50
  let harnessCommand = harness?.command;
71
51
  if (!harnessCommand) {
72
52
  harnessCommand = harness?.provider === "codex" ? "codex" : "claude";
@@ -80,17 +60,15 @@ export const resolvedProfileSnapshot = async (
80
60
  profileId,
81
61
  {
82
62
  id: profileId,
63
+ environment: profile.environment ?? execution.config.environment ?? null,
64
+ configuredConcurrency: execution.config.concurrency,
65
+ evaluationConcurrency: 1,
83
66
  driver: driver ?? null,
84
- driverCliIdentity: driverCli
85
- ? await cliIdentity(driverCli.command, execution.config.rootDir)
86
- : null,
87
- ...(driverCli?.name === "claude-code"
88
- ? {
89
- driverClaudeConfigRoot: claudeConfigRoot(driver),
90
- driverUserBehaviorHash,
91
- driverConfiguredSources,
92
- }
93
- : {}),
67
+ driverIdentity: await experimentDriverIdentity(
68
+ driver,
69
+ execution.registry,
70
+ execution.config.rootDir,
71
+ ),
94
72
  answerer,
95
73
  answererCliIdentity: answererCli
96
74
  ? await cliIdentity(answererCli.command, execution.config.rootDir)
@@ -130,7 +108,11 @@ const trustedExecutableEntrypoints = async (
130
108
  }
131
109
  for (const profileId of execution.loaded.campaign.profiles) {
132
110
  const profile = profileOrThrow(execution.config, profileId);
133
- add(referencedCli(profile.driver ?? execution.config.defaultDriver)?.command);
111
+ for (const command of referencedDriverRuntime(
112
+ profile.driver ?? execution.config.defaultDriver,
113
+ execution.registry,
114
+ ).commands)
115
+ add(command);
134
116
  add(referencedAnswererCli(profile.answerer ?? execution.config.defaultAnswerer)?.command);
135
117
  add((profile.harness ?? execution.config.harness)?.command);
136
118
  }
@@ -147,7 +129,15 @@ const trustedExecutableEntrypoints = async (
147
129
  await loadEvalCase(evalCase.path),
148
130
  execution.loaded.subjectRoot,
149
131
  );
150
- add(referencedCli(authored.driver)?.command);
132
+ for (const profileId of execution.loaded.campaign.profiles) {
133
+ const profile = profileOrThrow(execution.config, profileId);
134
+ const driver = resolveDriverReference(
135
+ authored,
136
+ profile.driver ?? execution.config.defaultDriver,
137
+ );
138
+ for (const command of referencedDriverRuntime(driver, execution.registry).commands)
139
+ add(command);
140
+ }
151
141
  add(referencedAnswererCli(authored.answerer)?.command);
152
142
  for (const reference of caseRubricCliReferences(execution, authored)) {
153
143
  add(reference.command);
@@ -216,7 +206,8 @@ export const runtimeCliReferences = async (
216
206
  profile.answerer ?? execution.config.defaultAnswerer,
217
207
  harness,
218
208
  );
219
- add(referencedCli(driver)?.command, execution.config.rootDir);
209
+ for (const command of referencedDriverRuntime(driver, execution.registry).commands)
210
+ add(command, execution.config.rootDir);
220
211
  add(referencedAnswererCli(answerer)?.command, execution.config.rootDir);
221
212
  }
222
213
  for (const reference of caseRubricCliReferences(execution, authored)) {
@@ -286,7 +277,6 @@ const validateAgentCaseProfileDrivers = async (
286
277
  );
287
278
  const name = typeof reference === "string" ? reference : reference.name;
288
279
  const rawConfig = typeof reference === "object" ? reference.config : undefined;
289
- boundExperimentDriverConfig(name, rawConfig, "identity-check");
290
280
  const plugin = execution.registry.getDriver(name);
291
281
  if (!plugin) {
292
282
  throw new Error(
@@ -298,7 +288,7 @@ const validateAgentCaseProfileDrivers = async (
298
288
  rawConfig,
299
289
  `case '${evalCase.id}' profile '${profileId}' driver '${name}'`,
300
290
  );
301
- boundExperimentDriverConfig(name, validated, "identity-check");
291
+ boundExperimentDriverConfig(plugin, validated, "identity-check");
302
292
  }
303
293
  }
304
294
  };
@@ -334,6 +324,7 @@ export const evaluationIdentity = async (
334
324
  luxVersion: LUX_VERSION,
335
325
  evaluatorImplementationHash: await evaluatorImplementationHash(),
336
326
  runtime: runtimeIdentity(),
327
+ concurrency: execution.config.concurrency,
337
328
  corpusHash: corpus.hash,
338
329
  profiles,
339
330
  configSourceHash,
@@ -2,6 +2,7 @@ import { basename } from "node:path";
2
2
  import { z } from "zod";
3
3
  import { valueHash } from "../core/index.js";
4
4
  import { deriveExperimentSelection, rankCandidates } from "./experiment.js";
5
+ import { ExperimentEffectsSchema, experimentEffects } from "./experiment-effects.js";
5
6
  import {
6
7
  type ExperimentRun,
7
8
  ExperimentRunSchema,
@@ -24,6 +25,7 @@ export const ExperimentDecisionReportSchema = z
24
25
  status: z.enum(["running", "passed", "failed", "aborted"]),
25
26
  outcome: z.enum(["promote", "retain", "reject", "error", "incomplete"]),
26
27
  error: z.string().nullable(),
28
+ effects: z.exactOptional(ExperimentEffectsSchema),
27
29
  candidateAndAccountingVerified: z.boolean(),
28
30
  championCandidateId: z.string().nullable(),
29
31
  ranking: z.array(z.string()),
@@ -39,6 +41,7 @@ export const ExperimentDecisionReportSchema = z
39
41
  z
40
42
  .object({
41
43
  metric: z.string(),
44
+ relativeTo: z.exactOptional(z.literal("baseline")),
42
45
  profile: z.string().nullable(),
43
46
  split: ExperimentReportSplitSchema,
44
47
  passed: z.boolean(),
@@ -596,6 +599,7 @@ const projectExperimentDecisionReport = (
596
599
  evaluationKind: experiment.evaluationKind,
597
600
  status: experiment.status,
598
601
  error: experiment.errorMessage ?? null,
602
+ effects: experimentEffects(experiment),
599
603
  candidateAndAccountingVerified,
600
604
  outcome: reportOutcome(experiment, championId),
601
605
  championCandidateId: championId,
@@ -610,6 +614,7 @@ const projectExperimentDecisionReport = (
610
614
  .filter((constraint) => constraint.candidateId === targetId)
611
615
  .map((constraint) => ({
612
616
  metric: constraint.metric,
617
+ ...(constraint.relativeTo ? { relativeTo: constraint.relativeTo } : {}),
613
618
  profile: constraint.profile ?? null,
614
619
  split: constraint.split,
615
620
  passed: constraint.passed,
@@ -733,7 +738,7 @@ export const formatExperimentDecisionMarkdown = (report: ExperimentDecisionRepor
733
738
  "| --- | --- | --- | --- | ---: | --- | --- |",
734
739
  ...report.gates.map(
735
740
  (gate) =>
736
- `| ${gate.passed ? "PASS" : "FAIL"} | ${markdown(gate.metric)} | ${markdown(gate.profile ?? "all")} | ${gate.split} | ${gate.values.map(concise).join(", ") || "—"} | ${markdown(gate.interval ?? "—")} | ${markdown(gate.note ?? "—")} |`,
741
+ `| ${gate.passed ? "PASS" : "FAIL"} | ${markdown(gate.metric)}${gate.relativeTo ? " Δ baseline" : ""} | ${markdown(gate.profile ?? "all")} | ${gate.split} | ${gate.values.map(concise).join(", ") || "—"} | ${markdown(gate.interval ?? "—")} | ${markdown(gate.note ?? "—")} |`,
737
742
  ),
738
743
  );
739
744
  }
@@ -763,6 +768,21 @@ export const formatExperimentDecisionMarkdown = (report: ExperimentDecisionRepor
763
768
  ),
764
769
  );
765
770
  }
771
+ if (report.effects?.length) {
772
+ lines.push(
773
+ "",
774
+ "## Paired case effects",
775
+ "",
776
+ "Differences are candidate minus baseline; repetitions are averaged within cases and authored clusters receive equal weight.",
777
+ "",
778
+ "| Candidate | Profile | Split | Metric | Mean difference | Cases | Clusters | Confidence | Note |",
779
+ "| --- | --- | --- | --- | ---: | ---: | ---: | --- | --- |",
780
+ ...report.effects.map(
781
+ (effect) =>
782
+ `| ${markdown(effect.candidateId)} | ${markdown(effect.profileId)} | ${effect.split} | ${markdown(effect.metric)} | ${effect.meanDelta === null ? "—" : signed(effect.meanDelta)} | ${effect.caseCount ?? effect.caseIds.length} | ${effect.n} | ${markdown(intervalText(effect.interval ?? undefined) ?? "—")} | ${markdown(effect.note ?? "—")} |`,
783
+ ),
784
+ );
785
+ }
766
786
  lines.push(
767
787
  "",
768
788
  "## Source and spend",
@@ -86,6 +86,7 @@ export const ExperimentSelectionSchema = z.object({
86
86
  z.object({
87
87
  candidateId: CandidateIdSchema,
88
88
  metric: z.string(),
89
+ relativeTo: z.exactOptional(z.literal("baseline")),
89
90
  profile: z.exactOptional(z.string()),
90
91
  split: z.enum(["train", "validation", "test"]),
91
92
  values: z.array(z.number()),
@@ -738,6 +739,7 @@ export const resumeExperimentRun = async (path: string): Promise<OpenedExperimen
738
739
 
739
740
  export const listExperimentRuns = async (
740
741
  runsRoot: string,
742
+ options: { onReadError?: (path: string, error: unknown) => void } = {},
741
743
  ): Promise<Array<ExperimentRun & { experimentDir: string }>> => {
742
744
  const root = join(resolve(runsRoot), EXPERIMENTS_DIR);
743
745
  if (!existsSync(root)) return [];
@@ -745,10 +747,15 @@ export const listExperimentRuns = async (
745
747
  .filter((entry) => entry.isDirectory())
746
748
  .map((entry) => join(root, entry.name));
747
749
  const runs = await Promise.all(
748
- directories.map(async (experimentDir) => ({
749
- experimentDir,
750
- experiment: await loadExperimentRun(experimentDir),
751
- })),
750
+ directories.map(async (experimentDir) => {
751
+ try {
752
+ return { experimentDir, experiment: await loadExperimentRun(experimentDir) };
753
+ } catch (error) {
754
+ if (!options.onReadError) throw error;
755
+ options.onReadError(experimentDir, error);
756
+ return { experimentDir, experiment: null };
757
+ }
758
+ }),
752
759
  );
753
760
  return runs
754
761
  .flatMap(({ experimentDir, experiment }) =>
@@ -1,61 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
- import { access, constants, lstat, open, readFile, realpath } from "node:fs/promises";
3
- import { homedir } from "node:os";
2
+ import { access, constants, lstat, open, realpath } from "node:fs/promises";
4
3
  import { delimiter, isAbsolute, join, resolve } from "node:path";
5
4
  import { z } from "zod";
6
- import { contentHash, discoverCliVersion, valueHash } from "../core/index.js";
7
- import { walkAuthoredFiles } from "./fs-read.js";
8
- import { boundExperimentDriverConfig } from "./orchestrator.js";
5
+ import { discoverCliVersion, valueHash } from "../core/index.js";
9
6
 
10
7
  export { LUX_VERSION } from "./package-version.js";
11
8
 
12
- const ClaudeSettingsFileSchema = z.looseObject({
13
- enabledPlugins: z.record(z.string(), z.boolean()).optional(),
14
- });
15
- const ClaudeInstalledPluginRecordSchema = z.looseObject({
16
- installPath: z.string(),
17
- });
18
- const ClaudeInstalledPluginsFileSchema = z.looseObject({
19
- plugins: z.record(z.string(), z.array(ClaudeInstalledPluginRecordSchema)),
20
- });
21
-
22
- export const referencedCli = (
23
- reference: unknown,
24
- ): { name: string; command: string } | undefined => {
25
- let name: string | undefined;
26
- if (typeof reference === "string") {
27
- name = reference;
28
- } else if (
29
- typeof reference === "object" &&
30
- reference !== null &&
31
- typeof (reference as { name?: unknown }).name === "string"
32
- ) {
33
- name = (reference as { name: string }).name;
34
- }
35
- const config =
36
- typeof reference === "object" && reference !== null
37
- ? (reference as { config?: unknown }).config
38
- : undefined;
39
- if (name === "subprocess") {
40
- if (
41
- typeof config !== "object" ||
42
- config === null ||
43
- typeof (config as { command?: unknown }).command !== "string"
44
- ) {
45
- return undefined;
46
- }
47
- return { name, command: (config as { command: string }).command };
48
- }
49
- if (name !== "claude-code" && name !== "codex") return undefined;
50
- let command = name === "codex" ? "codex" : "claude";
51
- if (
52
- typeof config === "object" &&
53
- config !== null &&
54
- typeof (config as { command?: unknown }).command === "string"
55
- ) {
56
- command = (config as { command: string }).command;
57
- }
58
- return { name, command };
9
+ const driverConfigRecord = (reference: unknown): Record<string, unknown> => {
10
+ const config = (reference as { config?: unknown } | null)?.config;
11
+ return config && typeof config === "object" ? (config as Record<string, unknown>) : {};
59
12
  };
60
13
 
61
14
  export const referencedAnswererCli = (
@@ -261,197 +214,6 @@ export const cliIdentity = async (command: string, baseRoot: string): Promise<Cl
261
214
  });
262
215
  };
263
216
 
264
- const optionalRegularFileHash = async (path: string): Promise<string | null> => {
265
- try {
266
- const info = await lstat(path);
267
- if (info.isSymbolicLink() || !info.isFile()) {
268
- throw new Error(`behavior-affecting settings must be a regular file: ${path}`);
269
- }
270
- return contentHash(await readFile(path));
271
- } catch (error) {
272
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
273
- throw error;
274
- }
275
- };
276
-
277
- const driverConfigRecord = (driver: unknown): Record<string, unknown> => {
278
- if (!driver || typeof driver !== "object") return {};
279
- const config = (driver as { config?: unknown }).config;
280
- return config && typeof config === "object" ? (config as Record<string, unknown>) : {};
281
- };
282
-
283
- export const claudeConfigRoot = (driver: unknown): string => {
284
- const config = driverConfigRecord(driver);
285
- const env =
286
- config.env && typeof config.env === "object"
287
- ? (config.env as Record<string, unknown>)
288
- : undefined;
289
- const configured = env?.CLAUDE_CONFIG_DIR;
290
- const root =
291
- typeof configured === "string"
292
- ? configured
293
- : (process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
294
- if (!isAbsolute(root)) {
295
- throw new Error("CLAUDE_CONFIG_DIR must be absolute for reproducible experiment identity");
296
- }
297
- return resolve(root);
298
- };
299
-
300
- const hashBehaviorDirectory = async (
301
- root: string,
302
- label: string,
303
- ): Promise<Array<{ path: string; hash: string }>> => {
304
- try {
305
- const info = await lstat(root);
306
- if (info.isSymbolicLink() || !info.isDirectory()) {
307
- throw new Error(`behavior-affecting Claude source must be a real directory: ${root}`);
308
- }
309
- } catch (error) {
310
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
311
- throw error;
312
- }
313
- const paths = await walkAuthoredFiles(root);
314
- if (paths.length > 5_000) {
315
- throw new Error(`behavior-affecting Claude source '${label}' exceeds 5000 files`);
316
- }
317
- let bytes = 0;
318
- const hashes: Array<{ path: string; hash: string }> = [];
319
- for (const path of paths) {
320
- const content = await readFile(join(root, path));
321
- bytes += content.length;
322
- if (bytes > 256 * 1024 * 1024) {
323
- throw new Error(`behavior-affecting Claude source '${label}' exceeds 256 MiB`);
324
- }
325
- hashes.push({ path: `${label}/${path}`, hash: contentHash(content) });
326
- }
327
- return hashes;
328
- };
329
-
330
- /**
331
- * Hash every intentionally loaded user behavior source without persisting its
332
- * contents. This covers user instructions/rules/output styles and the exact
333
- * installed contents of plugins enabled by settings.json.
334
- */
335
- export const claudeUserBehaviorHash = async (configRoot: string): Promise<string | null> => {
336
- const settingsPath = join(configRoot, "settings.json");
337
- const settingsHash = await optionalRegularFileHash(settingsPath);
338
- const sources: Array<{ path: string; hash: string }> = [];
339
- if (settingsHash) sources.push({ path: "settings.json", hash: settingsHash });
340
- const claudeMdHash = await optionalRegularFileHash(join(configRoot, "CLAUDE.md"));
341
- if (claudeMdHash) sources.push({ path: "CLAUDE.md", hash: claudeMdHash });
342
- for (const directory of ["rules", "output-styles", "agents", "commands", "skills"]) {
343
- sources.push(...(await hashBehaviorDirectory(join(configRoot, directory), directory)));
344
- }
345
-
346
- let settings: unknown;
347
- try {
348
- const contents = await readFile(settingsPath, "utf8");
349
- try {
350
- settings = JSON.parse(contents);
351
- } catch {
352
- throw new Error("Claude settings.json contains invalid JSON");
353
- }
354
- } catch (error) {
355
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
356
- settings = undefined;
357
- } else {
358
- throw error;
359
- }
360
- }
361
- const parsedSettings = ClaudeSettingsFileSchema.safeParse(settings);
362
- if (settings !== undefined && !parsedSettings.success) {
363
- throw new Error("Claude settings.json has an invalid enabledPlugins map");
364
- }
365
- const enabledPlugins = parsedSettings.success ? parsedSettings.data.enabledPlugins : undefined;
366
- const enabledIds = Object.entries(enabledPlugins ?? {})
367
- .filter(([, value]) => value === true)
368
- .map(([id]) => id)
369
- .sort();
370
- if (enabledIds.length > 0) {
371
- const installedPath = join(configRoot, "plugins", "installed_plugins.json");
372
- const installedHash = await optionalRegularFileHash(installedPath);
373
- if (installedHash) {
374
- sources.push({ path: "plugins/installed_plugins.json", hash: installedHash });
375
- }
376
- let installed: unknown;
377
- try {
378
- installed = JSON.parse(await readFile(installedPath, "utf8"));
379
- } catch {
380
- installed = undefined;
381
- }
382
- const parsedInstalled = ClaudeInstalledPluginsFileSchema.safeParse(installed);
383
- if (!parsedInstalled.success) {
384
- throw new Error("enabled Claude plugins have invalid installed metadata");
385
- }
386
- const plugins = parsedInstalled.data.plugins;
387
- for (const id of enabledIds) {
388
- const records = plugins[id];
389
- if (!records || records.length === 0) {
390
- throw new Error(`enabled Claude plugin '${id}' has no installed content to hash`);
391
- }
392
- for (const [index, record] of records.entries()) {
393
- const installPath = record.installPath;
394
- sources.push(
395
- ...(await hashBehaviorDirectory(resolve(installPath), `plugins/enabled/${id}/${index}`)),
396
- );
397
- }
398
- }
399
- }
400
- return sources.length === 0 ? null : valueHash(sources);
401
- };
402
-
403
- export const configuredClaudeSourceHashes = async (
404
- driver: unknown,
405
- baseRoot: string,
406
- cache: Map<string, Promise<string>> = new Map(),
407
- ): Promise<Array<{ kind: "plugin" | "read"; path: string; hash: string }>> => {
408
- const driverCli = referencedCli(driver);
409
- if (!driverCli) return [];
410
- const config = driverConfigRecord(driver);
411
- // This also rejects path-bearing extraArgs that would evade the source
412
- // hashes below. Runtime applies the same guard before driver creation.
413
- boundExperimentDriverConfig(driverCli.name, config, "identity-check");
414
- const isolation =
415
- config.isolation && typeof config.isolation === "object"
416
- ? (config.isolation as Record<string, unknown>)
417
- : undefined;
418
- const configured = [
419
- ...(driverCli.name === "claude-code" && Array.isArray(config.pluginDirs)
420
- ? config.pluginDirs.map((path) => ({ kind: "plugin" as const, path }))
421
- : []),
422
- ...(Array.isArray(config.addDirs)
423
- ? config.addDirs.map((path) => ({ kind: "read" as const, path }))
424
- : []),
425
- ...(driverCli.name === "claude-code" && isolation && Array.isArray(isolation.allowRead)
426
- ? isolation.allowRead.map((path) => ({ kind: "read" as const, path }))
427
- : []),
428
- ].filter(
429
- (entry): entry is { kind: "plugin" | "read"; path: string } =>
430
- typeof entry.path === "string" && entry.path.length > 0,
431
- );
432
- return Promise.all(
433
- configured
434
- .sort((a, b) => `${a.kind}:${a.path}`.localeCompare(`${b.kind}:${b.path}`))
435
- .map(async ({ kind, path }) => {
436
- const absolute = resolve(baseRoot, path);
437
- const key = `${kind}:${absolute}`;
438
- let hash = cache.get(key);
439
- if (!hash) {
440
- hash = hashBehaviorDirectory(
441
- absolute,
442
- `driver/${kind}/${contentHash(absolute).slice(0, 12)}`,
443
- ).then(valueHash);
444
- cache.set(key, hash);
445
- }
446
- return {
447
- kind,
448
- path: absolute,
449
- hash: await hash,
450
- };
451
- }),
452
- );
453
- };
454
-
455
217
  export const runtimeCliIdentityHashForReferences = async (
456
218
  references: readonly RuntimeCliReference[],
457
219
  ): Promise<string> =>
@@ -13,6 +13,7 @@ import {
13
13
  ExperimentSelectionSchema,
14
14
  type OpenedExperimentRun,
15
15
  } from "./experiment-runs.js";
16
+ import { confidenceGateIssue, type PairedEffect, pairedEffect } from "./paired-effects.js";
16
17
 
17
18
  type ExperimentSplit = "train" | "validation" | "test";
18
19
 
@@ -103,6 +104,7 @@ const satisfiesConstraint = (
103
104
  constraint: ExperimentConstraint,
104
105
  experiment: OpenedExperimentRun["manifest"],
105
106
  ): boolean => {
107
+ if (constraint.relativeTo && candidate.id === "baseline") return true;
106
108
  let profiles: (string | undefined)[];
107
109
  if (constraint.metric === "subject.bytes" || constraint.profile) {
108
110
  profiles = [constraint.profile];
@@ -110,6 +112,16 @@ const satisfiesConstraint = (
110
112
  profiles = campaign.profiles;
111
113
  }
112
114
  return profiles.every((profile) => {
115
+ if (constraint.relativeTo) {
116
+ const effect = relativeConstraintEffect(candidate, constraint, profile, experiment);
117
+ if (effect.note || effect.meanDelta === null) return false;
118
+ const lower = effect.interval?.lower ?? effect.meanDelta;
119
+ const upper = effect.interval?.upper ?? effect.meanDelta;
120
+ return (
121
+ (constraint.min === undefined || lower >= constraint.min) &&
122
+ (constraint.max === undefined || upper <= constraint.max)
123
+ );
124
+ }
113
125
  const values = metricValues(
114
126
  candidate,
115
127
  constraint.metric,
@@ -135,6 +147,7 @@ const satisfiesConstraint = (
135
147
  return sampleGroups.every((samples) => {
136
148
  try {
137
149
  const interval = bootstrapMeanConfidenceInterval(samples, constraint.confidence);
150
+ if (confidenceGateIssue(interval)) return false;
138
151
  return (
139
152
  (constraint.min === undefined || interval.lower >= constraint.min) &&
140
153
  (constraint.max === undefined || interval.upper <= constraint.max)
@@ -156,12 +169,29 @@ const satisfiesConstraint = (
156
169
  export const average = (values: readonly number[]): number =>
157
170
  values.reduce((sum, value) => sum + value, 0) / Math.max(values.length, 1);
158
171
 
172
+ const relativeConstraintEffect = (
173
+ candidate: ExperimentDecisionCandidate,
174
+ constraint: ExperimentConstraint,
175
+ profile: string | undefined,
176
+ experiment: OpenedExperimentRun["manifest"],
177
+ ): PairedEffect => {
178
+ const scope = (entry: ExperimentEvaluation): boolean =>
179
+ entry.profileId === profile && entry.split === constraint.split;
180
+ return pairedEffect(
181
+ candidateEvaluations(experiment, "baseline").find(scope),
182
+ candidateEvaluations(experiment, candidate.id).find(scope),
183
+ constraint.metric,
184
+ constraint.confidence,
185
+ );
186
+ };
187
+
159
188
  const normalizedConstraintViolation = (
160
189
  campaign: ExperimentCampaign,
161
190
  candidate: ExperimentDecisionCandidate,
162
191
  constraint: ExperimentConstraint,
163
192
  experiment: OpenedExperimentRun["manifest"],
164
193
  ): number => {
194
+ if (constraint.relativeTo && candidate.id === "baseline") return 0;
165
195
  let profiles: Array<string | undefined> = campaign.profiles;
166
196
  if (constraint.metric === "subject.bytes" || constraint.profile) {
167
197
  profiles = [constraint.profile];
@@ -177,6 +207,18 @@ const normalizedConstraintViolation = (
177
207
  );
178
208
  let worst = 0;
179
209
  for (const profile of profiles) {
210
+ if (constraint.relativeTo) {
211
+ const effect = relativeConstraintEffect(candidate, constraint, profile, experiment);
212
+ if (effect.note || effect.meanDelta === null) return Number.POSITIVE_INFINITY;
213
+ worst = Math.max(
214
+ worst,
215
+ violation(
216
+ effect.interval?.lower ?? effect.meanDelta,
217
+ effect.interval?.upper ?? effect.meanDelta,
218
+ ),
219
+ );
220
+ continue;
221
+ }
180
222
  if (constraint.confidence) {
181
223
  const groups = metricSampleGroups(
182
224
  candidate,
@@ -189,6 +231,7 @@ const normalizedConstraintViolation = (
189
231
  for (const samples of groups) {
190
232
  try {
191
233
  const interval = bootstrapMeanConfidenceInterval(samples, constraint.confidence);
234
+ if (confidenceGateIssue(interval)) return Number.POSITIVE_INFINITY;
192
235
  worst = Math.max(worst, violation(interval.lower, interval.upper));
193
236
  } catch {
194
237
  return Number.POSITIVE_INFINITY;
@@ -379,6 +422,36 @@ export const searchFrontier = (
379
422
  });
380
423
  };
381
424
 
425
+ const relativeConstraintReport = (
426
+ campaign: ExperimentCampaign,
427
+ candidate: ExperimentDecisionCandidate,
428
+ constraint: ExperimentConstraint,
429
+ profile: string | undefined,
430
+ experiment: OpenedExperimentRun["manifest"],
431
+ ): ExperimentSelection["constraints"][number] => {
432
+ const effect = relativeConstraintEffect(candidate, constraint, profile, experiment);
433
+ const reference = candidate.id === "baseline";
434
+ let values = effect.meanDelta === null ? [] : [effect.meanDelta];
435
+ if (reference) values = [0];
436
+ const note = reference ? "baseline reference; relative gate does not apply" : effect.note;
437
+ return {
438
+ candidateId: candidate.id,
439
+ metric: constraint.metric,
440
+ relativeTo: "baseline",
441
+ ...(profile ? { profile } : {}),
442
+ split: constraint.split,
443
+ values,
444
+ passed: satisfiesConstraint(
445
+ campaign,
446
+ candidate,
447
+ { ...constraint, ...(profile ? { profile } : {}) },
448
+ experiment,
449
+ ),
450
+ ...(!reference && effect.interval ? { interval: effect.interval } : {}),
451
+ ...(note ? { note } : {}),
452
+ };
453
+ };
454
+
382
455
  export const deriveExperimentSelection = (
383
456
  campaign: ExperimentCampaign,
384
457
  candidates: readonly ExperimentDecisionCandidate[],
@@ -405,6 +478,9 @@ export const deriveExperimentSelection = (
405
478
  ? [constraint.profile]
406
479
  : campaign.profiles;
407
480
  return profiles.map((profile) => {
481
+ if (constraint.relativeTo) {
482
+ return relativeConstraintReport(campaign, candidate, constraint, profile, experiment);
483
+ }
408
484
  const values = metricValues(
409
485
  candidate,
410
486
  constraint.metric,
@@ -426,6 +502,7 @@ export const deriveExperimentSelection = (
426
502
  if (samples) {
427
503
  try {
428
504
  interval = bootstrapMeanConfidenceInterval(samples, constraint.confidence);
505
+ note = confidenceGateIssue(interval);
429
506
  } catch (error) {
430
507
  note = error instanceof Error ? error.message : String(error);
431
508
  }