@prismatic-io/lux 0.0.2-preview.17 → 0.0.2-preview.20

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 (273) hide show
  1. package/lib/answerers/persona/index.d.ts +5 -1
  2. package/lib/answerers/persona/index.d.ts.map +1 -1
  3. package/lib/assertions/rubric/index.d.ts +3 -1
  4. package/lib/assertions/rubric/index.d.ts.map +1 -1
  5. package/lib/authoring.d.ts +3 -1
  6. package/lib/authoring.d.ts.map +1 -1
  7. package/lib/authoring.js +2 -1
  8. package/lib/authoring.js.map +1 -1
  9. package/lib/cli/doctor.d.ts +55 -0
  10. package/lib/cli/doctor.d.ts.map +1 -0
  11. package/lib/cli/doctor.js +14 -0
  12. package/lib/cli/doctor.js.map +1 -0
  13. package/lib/cli/harness-plan.d.ts +26 -0
  14. package/lib/cli/harness-plan.d.ts.map +1 -0
  15. package/lib/cli/harness-plan.js +75 -0
  16. package/lib/cli/harness-plan.js.map +1 -0
  17. package/lib/cli/init-templates.d.ts +2 -2
  18. package/lib/cli/init-templates.d.ts.map +1 -1
  19. package/lib/cli/init-templates.js +11 -4
  20. package/lib/cli/init-templates.js.map +1 -1
  21. package/lib/cli/init.d.ts +2 -0
  22. package/lib/cli/init.d.ts.map +1 -1
  23. package/lib/cli/init.js +6 -3
  24. package/lib/cli/init.js.map +1 -1
  25. package/lib/cli/output-schemas.d.ts +7 -0
  26. package/lib/cli/output-schemas.d.ts.map +1 -1
  27. package/lib/cli/program.d.ts +8 -11
  28. package/lib/cli/program.d.ts.map +1 -1
  29. package/lib/cli/program.js +54 -89
  30. package/lib/cli/program.js.map +1 -1
  31. package/lib/cli/run-options.d.ts +19 -0
  32. package/lib/cli/run-options.d.ts.map +1 -1
  33. package/lib/cli/run-options.js +92 -0
  34. package/lib/cli/run-options.js.map +1 -1
  35. package/lib/core/case.d.ts +2 -0
  36. package/lib/core/case.d.ts.map +1 -1
  37. package/lib/core/case.js +3 -0
  38. package/lib/core/case.js.map +1 -1
  39. package/lib/core/driver.d.ts +13 -0
  40. package/lib/core/driver.d.ts.map +1 -1
  41. package/lib/core/driver.js +10 -0
  42. package/lib/core/driver.js.map +1 -1
  43. package/lib/core/harness-catalog.d.ts +35 -0
  44. package/lib/core/harness-catalog.d.ts.map +1 -0
  45. package/lib/core/harness-catalog.js +45 -0
  46. package/lib/core/harness-catalog.js.map +1 -0
  47. package/lib/core/harness-catalogs/antigravity.d.ts +11 -0
  48. package/lib/core/harness-catalogs/antigravity.d.ts.map +1 -0
  49. package/lib/core/harness-catalogs/antigravity.js +23 -0
  50. package/lib/core/harness-catalogs/antigravity.js.map +1 -0
  51. package/lib/core/harness-catalogs/claude-code.d.ts +12 -0
  52. package/lib/core/harness-catalogs/claude-code.d.ts.map +1 -0
  53. package/lib/core/harness-catalogs/claude-code.js +24 -0
  54. package/lib/core/harness-catalogs/claude-code.js.map +1 -0
  55. package/lib/core/harness-catalogs/codex.d.ts +11 -0
  56. package/lib/core/harness-catalogs/codex.d.ts.map +1 -0
  57. package/lib/core/harness-catalogs/codex.js +26 -0
  58. package/lib/core/harness-catalogs/codex.js.map +1 -0
  59. package/lib/core/harness-catalogs/copilot.d.ts +29 -0
  60. package/lib/core/harness-catalogs/copilot.d.ts.map +1 -0
  61. package/lib/core/harness-catalogs/copilot.js +45 -0
  62. package/lib/core/harness-catalogs/copilot.js.map +1 -0
  63. package/lib/core/harness-catalogs/cursor.d.ts +2 -0
  64. package/lib/core/harness-catalogs/cursor.d.ts.map +1 -0
  65. package/lib/core/harness-catalogs/cursor.js +13 -0
  66. package/lib/core/harness-catalogs/cursor.js.map +1 -0
  67. package/lib/core/harness-catalogs/index.d.ts +67 -0
  68. package/lib/core/harness-catalogs/index.d.ts.map +1 -0
  69. package/lib/core/harness-catalogs/index.js +15 -0
  70. package/lib/core/harness-catalogs/index.js.map +1 -0
  71. package/lib/core/harness-matrix.d.ts +70 -0
  72. package/lib/core/harness-matrix.d.ts.map +1 -0
  73. package/lib/core/harness-matrix.js +194 -0
  74. package/lib/core/harness-matrix.js.map +1 -0
  75. package/lib/core/index.d.ts +3 -0
  76. package/lib/core/index.d.ts.map +1 -1
  77. package/lib/core/index.js +3 -0
  78. package/lib/core/index.js.map +1 -1
  79. package/lib/core/model-cli.d.ts +4 -11
  80. package/lib/core/model-cli.d.ts.map +1 -1
  81. package/lib/core/model-cli.js +23 -17
  82. package/lib/core/model-cli.js.map +1 -1
  83. package/lib/core/platform-process.d.ts +1 -0
  84. package/lib/core/platform-process.d.ts.map +1 -1
  85. package/lib/core/platform-process.js +1 -1
  86. package/lib/core/platform-process.js.map +1 -1
  87. package/lib/core/run.d.ts +4 -0
  88. package/lib/core/run.d.ts.map +1 -1
  89. package/lib/core/run.js +2 -0
  90. package/lib/core/run.js.map +1 -1
  91. package/lib/drivers/antigravity/config.d.ts +46 -0
  92. package/lib/drivers/antigravity/config.d.ts.map +1 -0
  93. package/lib/drivers/antigravity/config.js +39 -0
  94. package/lib/drivers/antigravity/config.js.map +1 -0
  95. package/lib/drivers/antigravity/events.d.ts +4 -0
  96. package/lib/drivers/antigravity/events.d.ts.map +1 -0
  97. package/lib/drivers/antigravity/events.js +193 -0
  98. package/lib/drivers/antigravity/events.js.map +1 -0
  99. package/lib/drivers/antigravity/index.d.ts +60 -0
  100. package/lib/drivers/antigravity/index.d.ts.map +1 -0
  101. package/lib/drivers/antigravity/index.js +90 -0
  102. package/lib/drivers/antigravity/index.js.map +1 -0
  103. package/lib/drivers/claude-code/index.d.ts +93 -7
  104. package/lib/drivers/claude-code/index.d.ts.map +1 -1
  105. package/lib/drivers/claude-code/index.js +8 -6
  106. package/lib/drivers/claude-code/index.js.map +1 -1
  107. package/lib/drivers/codex/config.d.ts +55 -4
  108. package/lib/drivers/codex/config.d.ts.map +1 -1
  109. package/lib/drivers/codex/config.js +9 -5
  110. package/lib/drivers/codex/config.js.map +1 -1
  111. package/lib/drivers/codex/index.d.ts +56 -5
  112. package/lib/drivers/codex/index.d.ts.map +1 -1
  113. package/lib/drivers/codex/index.js +5 -2
  114. package/lib/drivers/codex/index.js.map +1 -1
  115. package/lib/drivers/copilot/config.d.ts +81 -0
  116. package/lib/drivers/copilot/config.d.ts.map +1 -0
  117. package/lib/drivers/copilot/config.js +54 -0
  118. package/lib/drivers/copilot/config.js.map +1 -0
  119. package/lib/drivers/copilot/index.d.ts +103 -0
  120. package/lib/drivers/copilot/index.d.ts.map +1 -0
  121. package/lib/drivers/copilot/index.js +256 -0
  122. package/lib/drivers/copilot/index.js.map +1 -0
  123. package/lib/drivers/cursor/config.d.ts +6 -1
  124. package/lib/drivers/cursor/config.d.ts.map +1 -1
  125. package/lib/drivers/cursor/config.js +2 -3
  126. package/lib/drivers/cursor/config.js.map +1 -1
  127. package/lib/drivers/cursor/index.d.ts +7 -2
  128. package/lib/drivers/cursor/index.d.ts.map +1 -1
  129. package/lib/drivers/cursor/index.js +9 -6
  130. package/lib/drivers/cursor/index.js.map +1 -1
  131. package/lib/drivers/cursor/interaction.d.ts +1 -2
  132. package/lib/drivers/cursor/interaction.d.ts.map +1 -1
  133. package/lib/drivers/cursor/interaction.js +3 -14
  134. package/lib/drivers/cursor/interaction.js.map +1 -1
  135. package/lib/drivers/{cursor/events.d.ts → shared/acp-events.d.ts} +5 -3
  136. package/lib/drivers/shared/acp-events.d.ts.map +1 -0
  137. package/lib/drivers/{cursor/events.js → shared/acp-events.js} +20 -10
  138. package/lib/drivers/shared/acp-events.js.map +1 -0
  139. package/lib/drivers/shared/acp-interaction.d.ts +7 -0
  140. package/lib/drivers/shared/acp-interaction.d.ts.map +1 -0
  141. package/lib/drivers/shared/acp-interaction.js +20 -0
  142. package/lib/drivers/shared/acp-interaction.js.map +1 -0
  143. package/lib/drivers/{cursor → shared}/acp-transport.d.ts +2 -0
  144. package/lib/drivers/shared/acp-transport.d.ts.map +1 -0
  145. package/lib/drivers/{cursor → shared}/acp-transport.js +10 -10
  146. package/lib/drivers/shared/acp-transport.js.map +1 -0
  147. package/lib/index.d.ts +7 -2
  148. package/lib/index.d.ts.map +1 -1
  149. package/lib/index.js +7 -2
  150. package/lib/index.js.map +1 -1
  151. package/lib/optimization/reflective.d.ts +2 -0
  152. package/lib/optimization/reflective.d.ts.map +1 -1
  153. package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
  154. package/lib/orchestrator/comparison-identity.js +1 -0
  155. package/lib/orchestrator/comparison-identity.js.map +1 -1
  156. package/lib/orchestrator/config.d.ts +7 -0
  157. package/lib/orchestrator/config.d.ts.map +1 -1
  158. package/lib/orchestrator/config.js +8 -1
  159. package/lib/orchestrator/config.js.map +1 -1
  160. package/lib/orchestrator/doctor.d.ts +1 -0
  161. package/lib/orchestrator/doctor.d.ts.map +1 -1
  162. package/lib/orchestrator/doctor.js +86 -8
  163. package/lib/orchestrator/doctor.js.map +1 -1
  164. package/lib/orchestrator/experiment-context.d.ts +5 -3
  165. package/lib/orchestrator/experiment-context.d.ts.map +1 -1
  166. package/lib/orchestrator/experiment-context.js.map +1 -1
  167. package/lib/orchestrator/experiment-corpus.d.ts +2 -1
  168. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
  169. package/lib/orchestrator/experiment-corpus.js +9 -1
  170. package/lib/orchestrator/experiment-corpus.js.map +1 -1
  171. package/lib/orchestrator/experiment-identity.d.ts +3 -0
  172. package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
  173. package/lib/orchestrator/experiment-identity.js +1 -1
  174. package/lib/orchestrator/experiment-identity.js.map +1 -1
  175. package/lib/orchestrator/experiment-preflight.d.ts +3 -0
  176. package/lib/orchestrator/experiment-preflight.d.ts.map +1 -0
  177. package/lib/orchestrator/experiment-preflight.js +21 -0
  178. package/lib/orchestrator/experiment-preflight.js.map +1 -0
  179. package/lib/orchestrator/experiment.d.ts +3 -1
  180. package/lib/orchestrator/experiment.d.ts.map +1 -1
  181. package/lib/orchestrator/experiment.js +3 -6
  182. package/lib/orchestrator/experiment.js.map +1 -1
  183. package/lib/orchestrator/harness-catalog-drift.d.ts +22 -0
  184. package/lib/orchestrator/harness-catalog-drift.d.ts.map +1 -0
  185. package/lib/orchestrator/harness-catalog-drift.js +90 -0
  186. package/lib/orchestrator/harness-catalog-drift.js.map +1 -0
  187. package/lib/orchestrator/orchestrator.d.ts +9 -1
  188. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  189. package/lib/orchestrator/orchestrator.js +14 -22
  190. package/lib/orchestrator/orchestrator.js.map +1 -1
  191. package/lib/orchestrator/run-dir.d.ts +2 -0
  192. package/lib/orchestrator/run-dir.d.ts.map +1 -1
  193. package/lib/orchestrator/run-dir.js +1 -1
  194. package/lib/orchestrator/run-dir.js.map +1 -1
  195. package/lib/orchestrator/run-execution.d.ts +1 -0
  196. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  197. package/lib/orchestrator/run-execution.js +2 -0
  198. package/lib/orchestrator/run-execution.js.map +1 -1
  199. package/lib/orchestrator/suite-compare.d.ts +3 -0
  200. package/lib/orchestrator/suite-compare.d.ts.map +1 -1
  201. package/lib/orchestrator/suite-compare.js +27 -17
  202. package/lib/orchestrator/suite-compare.js.map +1 -1
  203. package/lib/orchestrator/suite-runs.d.ts +3 -0
  204. package/lib/orchestrator/suite-runs.d.ts.map +1 -1
  205. package/lib/orchestrator/suite-runs.js +2 -0
  206. package/lib/orchestrator/suite-runs.js.map +1 -1
  207. package/lib/orchestrator/suite-summary.d.ts +9 -0
  208. package/lib/orchestrator/suite-summary.d.ts.map +1 -1
  209. package/lib/orchestrator/suite-summary.js +14 -4
  210. package/lib/orchestrator/suite-summary.js.map +1 -1
  211. package/package.json +1 -1
  212. package/skills/lux-answerer/SKILL.md +1 -1
  213. package/src/authoring.ts +10 -3
  214. package/src/cli/doctor.ts +17 -0
  215. package/src/cli/harness-plan.ts +117 -0
  216. package/src/cli/init-templates.ts +12 -5
  217. package/src/cli/init.ts +12 -5
  218. package/src/cli/program.ts +60 -107
  219. package/src/cli/run-options.ts +123 -0
  220. package/src/core/case.ts +3 -0
  221. package/src/core/driver.ts +26 -0
  222. package/src/core/harness-catalog.ts +100 -0
  223. package/src/core/harness-catalogs/antigravity.ts +24 -0
  224. package/src/core/harness-catalogs/claude-code.ts +25 -0
  225. package/src/core/harness-catalogs/codex.ts +27 -0
  226. package/src/core/harness-catalogs/copilot.ts +46 -0
  227. package/src/core/harness-catalogs/cursor.ts +13 -0
  228. package/src/core/harness-catalogs/index.ts +22 -0
  229. package/src/core/harness-matrix.ts +298 -0
  230. package/src/core/index.ts +3 -0
  231. package/src/core/model-cli.ts +38 -21
  232. package/src/core/platform-process.ts +4 -4
  233. package/src/core/run.ts +2 -0
  234. package/src/drivers/antigravity/README.md +46 -0
  235. package/src/drivers/antigravity/config.ts +44 -0
  236. package/src/drivers/antigravity/events.ts +235 -0
  237. package/src/drivers/antigravity/index.ts +109 -0
  238. package/src/drivers/claude-code/index.ts +12 -5
  239. package/src/drivers/codex/config.ts +11 -5
  240. package/src/drivers/codex/index.ts +5 -1
  241. package/src/drivers/copilot/README.md +52 -0
  242. package/src/drivers/copilot/config.ts +58 -0
  243. package/src/drivers/copilot/index.ts +292 -0
  244. package/src/drivers/cursor/README.md +9 -6
  245. package/src/drivers/cursor/config.ts +2 -3
  246. package/src/drivers/cursor/index.ts +10 -6
  247. package/src/drivers/cursor/interaction.ts +3 -19
  248. package/src/drivers/{cursor/events.ts → shared/acp-events.ts} +20 -9
  249. package/src/drivers/shared/acp-interaction.ts +27 -0
  250. package/src/drivers/{cursor → shared}/acp-transport.ts +15 -10
  251. package/src/index.ts +44 -3
  252. package/src/orchestrator/comparison-identity.ts +1 -0
  253. package/src/orchestrator/config.ts +8 -0
  254. package/src/orchestrator/doctor.ts +149 -8
  255. package/src/orchestrator/experiment-context.ts +3 -2
  256. package/src/orchestrator/experiment-corpus.ts +20 -2
  257. package/src/orchestrator/experiment-identity.ts +2 -2
  258. package/src/orchestrator/experiment-preflight.ts +27 -0
  259. package/src/orchestrator/experiment.ts +3 -5
  260. package/src/orchestrator/harness-catalog-drift.ts +132 -0
  261. package/src/orchestrator/orchestrator.ts +31 -26
  262. package/src/orchestrator/run-dir.ts +8 -1
  263. package/src/orchestrator/run-execution.ts +3 -0
  264. package/src/orchestrator/suite-compare.ts +29 -19
  265. package/src/orchestrator/suite-runs.ts +3 -1
  266. package/src/orchestrator/suite-summary.ts +19 -4
  267. package/lib/drivers/cursor/acp-transport.d.ts.map +0 -1
  268. package/lib/drivers/cursor/acp-transport.js.map +0 -1
  269. package/lib/drivers/cursor/events.d.ts.map +0 -1
  270. package/lib/drivers/cursor/events.js.map +0 -1
  271. package/skills/lux/SKILL.md +0 -89
  272. package/skills/lux/references/cli.md +0 -128
  273. package/skills/lux/references/eval-authoring.md +0 -266
package/src/index.ts CHANGED
@@ -258,9 +258,11 @@ export {
258
258
  type DriverRuntime,
259
259
  defineDriver,
260
260
  driverRef,
261
+ type HarnessSelection,
261
262
  type ResolvedFixtures,
262
263
  ResolvedFixturesSchema,
263
264
  type StartContext,
265
+ selectModelAndReasoningEffort,
264
266
  } from "./core/driver.js";
265
267
  export {
266
268
  type DriverMetric,
@@ -344,6 +346,38 @@ export {
344
346
  type OptimizerInput,
345
347
  } from "./core/experiment.js";
346
348
  export { type GlobalIdParts, tryParseGlobalId } from "./core/global-id.js";
349
+ export {
350
+ type CatalogModels,
351
+ catalogEfforts,
352
+ defaultCatalogEffort,
353
+ defineHarnessCatalog,
354
+ HARNESS_EFFORTS,
355
+ type HarnessCatalog,
356
+ type ReasoningEffort,
357
+ ReasoningEffortSchema,
358
+ } from "./core/harness-catalog.js";
359
+ export {
360
+ antigravityCatalog,
361
+ type BuiltInHarnessDriver,
362
+ builtInHarnessCatalogs,
363
+ claudeCodeCatalog,
364
+ codexCatalog,
365
+ copilotCatalog,
366
+ cursorCatalog,
367
+ harnessCatalogFor,
368
+ } from "./core/harness-catalogs/index.js";
369
+ export {
370
+ type BuiltInHarnessMatrixEntry,
371
+ BuiltInHarnessMatrixEntrySchema,
372
+ type CustomHarnessMatrixEntry,
373
+ expandHarnessMatrix,
374
+ type HarnessMatrix,
375
+ type HarnessMatrixEntry,
376
+ HarnessMatrixEntrySchema,
377
+ HarnessMatrixSchema,
378
+ type HarnessPermutation,
379
+ permutationId,
380
+ } from "./core/harness-matrix.js";
347
381
  export {
348
382
  defineFixtures,
349
383
  type LifecycleFixture,
@@ -379,14 +413,11 @@ export {
379
413
  export {
380
414
  DEFAULT_CLAUDE_MODEL,
381
415
  DEFAULT_CODEX_MODEL,
382
- DEFAULT_CODEX_REASONING_EFFORT,
383
416
  defaultHarnessModel,
384
417
  type HarnessModelConfig,
385
418
  HarnessModelConfigSchema,
386
419
  type ModelCliProvider,
387
420
  ModelCliProviderSchema,
388
- type ReasoningEffort,
389
- ReasoningEffortSchema,
390
421
  } from "./core/model-cli.js";
391
422
  export {
392
423
  type AssertionProgress,
@@ -472,6 +503,11 @@ export {
472
503
  type WireEvent,
473
504
  WireEventSchema,
474
505
  } from "./core/wire.js";
506
+ export {
507
+ type AntigravityDriverConfig,
508
+ AntigravityDriverConfigSchema,
509
+ antigravityDriver,
510
+ } from "./drivers/antigravity/index.js";
475
511
  export {
476
512
  type ClaudeCodeDriverConfig,
477
513
  ClaudeCodeDriverConfigSchema,
@@ -482,6 +518,11 @@ export {
482
518
  CodexDriverConfigSchema,
483
519
  codexDriver,
484
520
  } from "./drivers/codex/index.js";
521
+ export {
522
+ type CopilotDriverConfig,
523
+ CopilotDriverConfigSchema,
524
+ copilotDriver,
525
+ } from "./drivers/copilot/index.js";
485
526
  export {
486
527
  type CursorDriverConfig,
487
528
  CursorDriverConfigSchema,
@@ -36,6 +36,7 @@ export const runComparisonIdentity = (metadata: RunMetadata): string =>
36
36
  assertionsHash: metadata.provenance?.assertionsHash,
37
37
  fixturesHash: metadata.provenance?.fixturesHash,
38
38
  profileId: metadata.provenance?.profileId,
39
+ permutationId: metadata.provenance?.permutationId,
39
40
  driverConfigHash: metadata.provenance?.driverConfigHash,
40
41
  answererConfigHash: metadata.provenance?.answererConfigHash,
41
42
  evaluatorConfigHash: metadata.provenance?.evaluatorConfigHash,
@@ -18,13 +18,16 @@ import {
18
18
  type DriverPlugin,
19
19
  DriverRefSchema,
20
20
  formatZodIssues,
21
+ HarnessMatrixSchema,
21
22
  HarnessModelConfigSchema,
22
23
  type LifecycleHooksPlugin,
23
24
  lifecycleHooksPluginIssue,
24
25
  Registry,
25
26
  } from "../core/index.js";
27
+ import { antigravityDriver } from "../drivers/antigravity/index.js";
26
28
  import { claudeCodeDriver } from "../drivers/claude-code/index.js";
27
29
  import { codexDriver } from "../drivers/codex/index.js";
30
+ import { copilotDriver } from "../drivers/copilot/index.js";
28
31
  import { cursorDriver } from "../drivers/cursor/index.js";
29
32
  import { mcpProbeDriver } from "../drivers/mcp/index.js";
30
33
  import { subprocessDriver } from "../drivers/subprocess/index.js";
@@ -80,6 +83,8 @@ const LuxConfigSchema = z
80
83
  subjectRoot: z.string().optional(),
81
84
  defaultAnswerer: AnswererRefSchema.default("persona"),
82
85
  defaultDriver: DriverRefSchema.optional(),
86
+ /** Driver, model, and effort permutations every case runs through by default. */
87
+ matrix: HarnessMatrixSchema.optional(),
83
88
  /** Shared provider/model defaults for persona answering and rubric judging. */
84
89
  harness: HarnessModelConfigSchema.optional(),
85
90
  concurrency: z.number().int().min(1).default(1),
@@ -166,6 +171,7 @@ export const loadLuxConfig = async (cwd: string = process.cwd()): Promise<Resolv
166
171
  ...(validated.fixturesRoot ? { fixturesRoot: resolve(root, validated.fixturesRoot) } : {}),
167
172
  defaultAnswerer: validated.defaultAnswerer,
168
173
  ...(validated.defaultDriver ? { defaultDriver: validated.defaultDriver } : {}),
174
+ ...(validated.matrix ? { matrix: validated.matrix } : {}),
169
175
  ...(validated.harness ? { harness: validated.harness } : {}),
170
176
  concurrency: validated.concurrency,
171
177
  ...(validated.environment ? { environment: validated.environment } : {}),
@@ -181,6 +187,8 @@ const builtinDrivers: DriverPlugin<never>[] = [
181
187
  claudeCodeDriver,
182
188
  codexDriver,
183
189
  cursorDriver,
190
+ copilotDriver,
191
+ antigravityDriver,
184
192
  subprocessDriver,
185
193
  mcpProbeDriver,
186
194
  ];
@@ -4,7 +4,15 @@ import { resolve } from "node:path";
4
4
  import { glob } from "tinyglobby";
5
5
  import { z } from "zod";
6
6
  import { RubricConfigSchema } from "../assertions/rubric/index.js";
7
- import type { EvalCase, Registry } from "../core/index.js";
7
+ import {
8
+ casePermutations,
9
+ type EvalCase,
10
+ expandHarnessMatrix,
11
+ type HarnessPermutation,
12
+ permutationDriverRef,
13
+ permutationId,
14
+ type Registry,
15
+ } from "../core/index.js";
8
16
  import { registryForEvalCase } from "./case-extensions.js";
9
17
  import { buildRegistryFromConfig, loadLuxConfig, type ResolvedLuxConfig } from "./config.js";
10
18
  import { type DiscoveredCase, type DiscoveryResult, discoverCases } from "./discover.js";
@@ -17,6 +25,12 @@ import {
17
25
  } from "./experiment-loader.js";
18
26
  import { cliIdentity } from "./experiment-runtime-identity.js";
19
27
  import { assertionConfigForSchema } from "./grading.js";
28
+ import {
29
+ describeHarnessCatalogDrift,
30
+ harnessCatalogDrift,
31
+ harnessCatalogProbes,
32
+ hasHarnessCatalogDrift,
33
+ } from "./harness-catalog-drift.js";
20
34
  import { loadEvalCase } from "./loader.js";
21
35
  import { validatePluginConfig, validatePluginConfigAsync } from "./validate-plugin-config.js";
22
36
 
@@ -62,6 +76,7 @@ export const DoctorOptionsSchema = z
62
76
  .object({
63
77
  cwd: z.string().min(1),
64
78
  campaigns: z.array(z.string()).optional(),
79
+ static: z.boolean().optional(),
65
80
  })
66
81
  .strict();
67
82
  export type DoctorOptions = z.input<typeof DoctorOptionsSchema>;
@@ -131,7 +146,28 @@ const effectiveDriverReference = (local: unknown, fallback: unknown): unknown =>
131
146
  };
132
147
  };
133
148
 
134
- const caseDriverReferences = (local: unknown, config: ResolvedLuxConfig): unknown[] => {
149
+ const boundPermutation = (
150
+ permutation: HarnessPermutation,
151
+ config: ResolvedLuxConfig,
152
+ registry: Registry,
153
+ ): unknown => {
154
+ const plugin = registry.getDriver(permutation.driver);
155
+ if (!plugin) throw new Error(`unknown driver '${permutation.driver}'`);
156
+ return permutationDriverRef(permutation, config.defaultDriver, plugin);
157
+ };
158
+
159
+ const caseDriverReferences = (
160
+ authored: Pick<EvalCase, "driver" | "matrix">,
161
+ config: ResolvedLuxConfig,
162
+ registry: Registry,
163
+ ): unknown[] => {
164
+ const local = authored.driver;
165
+ const permutations = casePermutations(authored, config.matrix);
166
+ if (permutations.length > 0) {
167
+ return permutations.map((permutation) =>
168
+ effectiveDriverReference(local, boundPermutation(permutation, config, registry)),
169
+ );
170
+ }
135
171
  const fallbacks = [
136
172
  config.defaultDriver,
137
173
  ...Object.values(config.profiles ?? {}).map((profile) => profile.driver),
@@ -191,6 +227,17 @@ const configuredCliTargets = (config: ResolvedLuxConfig, registry: Registry): Cl
191
227
  };
192
228
  for (const command of driverCommands(config.defaultDriver, registry))
193
229
  add("default driver", command);
230
+ for (const permutation of expandHarnessMatrix(config.matrix ?? [])) {
231
+ try {
232
+ for (const command of driverCommands(
233
+ boundPermutation(permutation, config, registry),
234
+ registry,
235
+ ))
236
+ add(`matrix '${permutationId(permutation)}' driver`, command);
237
+ } catch {
238
+ /* Reported by validateConfiguredMatrix. */
239
+ }
240
+ }
194
241
  add("default answerer", answererCommand(config.defaultAnswerer));
195
242
  if (config.harness) {
196
243
  add(
@@ -356,6 +403,32 @@ const validateConfiguredDrivers = (
356
403
  }
357
404
  };
358
405
 
406
+ const validateConfiguredMatrix = (
407
+ config: ResolvedLuxConfig,
408
+ registry: Registry,
409
+ recorder: DoctorRecorder,
410
+ ): void => {
411
+ for (const permutation of expandHarnessMatrix(config.matrix ?? [])) {
412
+ const id = permutationId(permutation);
413
+ try {
414
+ const reference = boundPermutation(permutation, config, registry);
415
+ const plugin = registry.getDriver(permutation.driver);
416
+ validatePluginConfig(
417
+ plugin?.configSchema,
418
+ rawRefConfig(reference),
419
+ `matrix '${id}' driver '${permutation.driver}'`,
420
+ );
421
+ } catch (error) {
422
+ recorder.add(
423
+ `matrix:${id}`,
424
+ "fail",
425
+ `matrix permutation '${id}' is not runnable`,
426
+ firstLine(error),
427
+ );
428
+ }
429
+ }
430
+ };
431
+
359
432
  const validateConfiguredAnswerers = (
360
433
  config: ResolvedLuxConfig,
361
434
  registry: Registry,
@@ -575,7 +648,7 @@ const inspectCase = async (
575
648
  try {
576
649
  const authored = await loadEvalCase(discovered.path);
577
650
  const caseRegistry = registryForEvalCase(registry, authored);
578
- const drivers = caseDriverReferences(authored.driver, config);
651
+ const drivers = caseDriverReferences(authored, config, caseRegistry);
579
652
  const answerer = authored.answerer ?? config.defaultAnswerer;
580
653
  validateCaseDrivers(discovered.id, drivers, caseRegistry, recorder, authored.requires);
581
654
  validateCaseAnswerer(discovered.id, answerer, caseRegistry, recorder);
@@ -658,6 +731,54 @@ const inspectCliTargets = async (
658
731
  }
659
732
  };
660
733
 
734
+ const catalogDriverReferences = (config: ResolvedLuxConfig): unknown[] => [
735
+ ...configuredDriverReferences(config).map(([, reference]) => reference),
736
+ ...(config.matrix ?? []).map((entry) => ({ name: entry.driver, config: entry.config })),
737
+ ];
738
+
739
+ const configuredDriverCommand = (config: ResolvedLuxConfig, driver: string): string | undefined => {
740
+ for (const reference of catalogDriverReferences(config)) {
741
+ if (refName(reference) !== driver) continue;
742
+ const command = (rawRefConfig(reference) as { command?: unknown } | undefined)?.command;
743
+ if (typeof command === "string") return command;
744
+ }
745
+ return undefined;
746
+ };
747
+
748
+ const inspectHarnessCatalogs = async (
749
+ config: ResolvedLuxConfig,
750
+ recorder: DoctorRecorder,
751
+ ): Promise<void> => {
752
+ const configured = new Set(
753
+ catalogDriverReferences(config)
754
+ .map(refName)
755
+ .filter((name): name is string => name !== undefined),
756
+ );
757
+ for (const probe of harnessCatalogProbes) {
758
+ const driver = probe.catalog.driver;
759
+ if (!configured.has(driver)) continue;
760
+ const live = await probe.read(configuredDriverCommand(config, driver));
761
+ if (!live) {
762
+ recorder.add(
763
+ `catalog:${driver}`,
764
+ "warn",
765
+ `${driver} harness catalog (verified ${probe.catalog.verified}) could not be compared with the installed CLI`,
766
+ `${probe.source} was unavailable`,
767
+ );
768
+ continue;
769
+ }
770
+ const drift = harnessCatalogDrift(probe.catalog, live);
771
+ recorder.add(
772
+ `catalog:${driver}`,
773
+ hasHarnessCatalogDrift(drift) ? "warn" : "pass",
774
+ hasHarnessCatalogDrift(drift)
775
+ ? `${driver} harness catalog (verified ${probe.catalog.verified}) differs from the installed CLI`
776
+ : `${driver} harness catalog matches the installed CLI`,
777
+ describeHarnessCatalogDrift(drift) || undefined,
778
+ );
779
+ }
780
+ };
781
+
661
782
  const isBlockingCampaignWarning = (warning: string): boolean =>
662
783
  warning.includes("below confidence minSamples=") || warning.includes("cannot attain confidence");
663
784
 
@@ -666,6 +787,7 @@ const inspectCampaign = async (
666
787
  config: ResolvedLuxConfig,
667
788
  registry: Registry,
668
789
  recorder: DoctorRecorder,
790
+ staticChecks: boolean,
669
791
  ): Promise<void> => {
670
792
  try {
671
793
  const loaded = await loadExperimentCampaign(path);
@@ -679,7 +801,9 @@ const inspectCampaign = async (
679
801
  placeholders.join(", "),
680
802
  );
681
803
  }
682
- const plan = await planExperimentCampaign({ config, registry, loaded }, mode);
804
+ const plan = await planExperimentCampaign({ config, registry, loaded }, mode, {
805
+ static: staticChecks,
806
+ });
683
807
  recorder.campaigns.push({ path, campaignId: loaded.campaign.id, mode, plan });
684
808
  recorder.add(
685
809
  `campaign:${loaded.campaign.id}`,
@@ -711,6 +835,7 @@ const inspectCampaigns = async (
711
835
  config: ResolvedLuxConfig,
712
836
  registry: Registry,
713
837
  recorder: DoctorRecorder,
838
+ staticChecks: boolean,
714
839
  ): Promise<void> => {
715
840
  let paths: string[];
716
841
  try {
@@ -733,7 +858,7 @@ const inspectCampaigns = async (
733
858
  );
734
859
  }
735
860
  for (const path of paths) {
736
- await inspectCampaign(path, config, registry, recorder);
861
+ await inspectCampaign(path, config, registry, recorder, staticChecks);
737
862
  }
738
863
  };
739
864
 
@@ -747,14 +872,30 @@ export const doctorProject = async (options: DoctorOptions): Promise<DoctorRepor
747
872
  if (!registry) return recorder.finish(config.rootDir);
748
873
 
749
874
  validateConfiguredDrivers(config, registry, recorder);
875
+ validateConfiguredMatrix(config, registry, recorder);
750
876
  validateConfiguredAnswerers(config, registry, recorder);
751
877
  const caseCliTargets = await inspectCases(config, registry, recorder);
752
- await inspectCliTargets(
753
- [...configuredCliTargets(config, registry), ...caseCliTargets],
878
+ if (parsedOptions.static) {
879
+ recorder.add(
880
+ "static-checks",
881
+ "warn",
882
+ "static checks only; installed executables and campaign execution identity were not checked",
883
+ );
884
+ } else {
885
+ await inspectCliTargets(
886
+ [...configuredCliTargets(config, registry), ...caseCliTargets],
887
+ config,
888
+ recorder,
889
+ );
890
+ await inspectHarnessCatalogs(config, recorder);
891
+ }
892
+ await inspectCampaigns(
893
+ parsedOptions.campaigns ?? [],
754
894
  config,
895
+ registry,
755
896
  recorder,
897
+ parsedOptions.static ?? false,
756
898
  );
757
- await inspectCampaigns(parsedOptions.campaigns ?? [], config, registry, recorder);
758
899
  return recorder.finish(config.rootDir);
759
900
  };
760
901
 
@@ -3,17 +3,18 @@ import type { ResolvedLuxConfig } from "./config.js";
3
3
  import type { discoverCases } from "./discover.js";
4
4
  import type { LoadedExperimentCampaign } from "./experiment-loader.js";
5
5
 
6
- export type EvaluationCorpus =
6
+ export type CorpusSelection =
7
7
  | {
8
8
  kind: "agent";
9
9
  cases: Awaited<ReturnType<typeof discoverCases>>["cases"];
10
- hash: string;
11
10
  }
12
11
  | {
13
12
  kind: "grader";
14
13
  hash: string;
15
14
  };
16
15
 
16
+ export type EvaluationCorpus = CorpusSelection & { hash: string };
17
+
17
18
  export type CampaignExecutionOptions = {
18
19
  config: ResolvedLuxConfig;
19
20
  registry: Registry;
@@ -13,7 +13,11 @@ import {
13
13
  import { assertDisjointAnnotationSplits, selectGraderAnnotations } from "./annotation-loader.js";
14
14
  import { discoverCases, selectCases } from "./discover.js";
15
15
  import { experimentDriverIdentity } from "./driver-identity.js";
16
- import type { CampaignExecutionOptions, EvaluationCorpus } from "./experiment-context.js";
16
+ import type {
17
+ CampaignExecutionOptions,
18
+ CorpusSelection,
19
+ EvaluationCorpus,
20
+ } from "./experiment-context.js";
17
21
  import { profileOrThrow } from "./experiment-context.js";
18
22
  import { cliIdentity, referencedAnswererCli } from "./experiment-runtime-identity.js";
19
23
  import { fixtureIdentity } from "./fixture-identity.js";
@@ -252,6 +256,21 @@ export const graderCorpusHash = async (
252
256
  export const prepareCorpus = async (
253
257
  execution: CampaignExecutionOptions,
254
258
  ): Promise<EvaluationCorpus> => {
259
+ const corpus = await inspectCorpus(execution);
260
+ if (corpus.kind === "grader") return corpus;
261
+ return {
262
+ ...corpus,
263
+ hash: await agentCorpusHash(
264
+ execution,
265
+ corpus.cases,
266
+ selectorsBySplit(execution.loaded.campaign),
267
+ ),
268
+ };
269
+ };
270
+
271
+ export const inspectCorpus = async (
272
+ execution: CampaignExecutionOptions,
273
+ ): Promise<CorpusSelection> => {
255
274
  const campaign = execution.loaded.campaign;
256
275
  const splits = selectorsBySplit(campaign);
257
276
  if (campaign.evaluation.kind === "grader") {
@@ -344,7 +363,6 @@ export const prepareCorpus = async (
344
363
  return {
345
364
  kind: "agent",
346
365
  cases: discovery.cases,
347
- hash: await agentCorpusHash(execution, discovery.cases, splits),
348
366
  };
349
367
  };
350
368
 
@@ -253,9 +253,9 @@ const authoredConfigDependencyHash = async (
253
253
  }
254
254
  return authoredDependencyGraphHash(configRoot, [...entrypoints]);
255
255
  };
256
- const validateAgentCaseProfileDrivers = async (
256
+ export const validateAgentCaseProfileDrivers = async (
257
257
  execution: CampaignExecutionOptions,
258
- corpus: Extract<EvaluationCorpus, { kind: "agent" }>,
258
+ corpus: Pick<Extract<EvaluationCorpus, { kind: "agent" }>, "cases">,
259
259
  ): Promise<void> => {
260
260
  const selected = [
261
261
  ...new Map(
@@ -0,0 +1,27 @@
1
+ import { assertValidationArgumentsBound } from "./candidates.js";
2
+ import {
3
+ type CampaignExecutionOptions,
4
+ type CorpusSelection,
5
+ profileOrThrow,
6
+ } from "./experiment-context.js";
7
+ import { inspectCorpus, prepareCorpus } from "./experiment-corpus.js";
8
+ import { evaluationIdentity, validateAgentCaseProfileDrivers } from "./experiment-identity.js";
9
+
10
+ export const preparePlanningCorpus = async (
11
+ execution: CampaignExecutionOptions,
12
+ mode: "experiment" | "optimize",
13
+ staticChecks: boolean,
14
+ ): Promise<CorpusSelection> => {
15
+ if (!staticChecks) {
16
+ const corpus = await prepareCorpus(execution);
17
+ await evaluationIdentity(execution, corpus, mode);
18
+ return corpus;
19
+ }
20
+ const corpus = await inspectCorpus(execution);
21
+ const campaign = execution.loaded.campaign;
22
+ for (const profileId of campaign.profiles) profileOrThrow(execution.config, profileId);
23
+ if (corpus.kind === "agent") await validateAgentCaseProfileDrivers(execution, corpus);
24
+ for (const validation of campaign.subject.validate)
25
+ assertValidationArgumentsBound(execution.loaded.subjectRoot, validation.args);
26
+ return corpus;
27
+ };
@@ -48,6 +48,7 @@ import {
48
48
  resolvedProfileSnapshot,
49
49
  runtimeCliReferences,
50
50
  } from "./experiment-identity.js";
51
+ import { preparePlanningCorpus } from "./experiment-preflight.js";
51
52
  import {
52
53
  type ExperimentEvaluation,
53
54
  loadExperimentCandidates,
@@ -182,6 +183,7 @@ const evaluateAcross = async (options: {
182
183
  export const planExperimentCampaign = async (
183
184
  execution: CampaignExecutionOptions,
184
185
  mode: "experiment" | "optimize",
186
+ options: { static?: boolean } = {},
185
187
  ): Promise<ExperimentCampaignPlan> => {
186
188
  const campaign = execution.loaded.campaign;
187
189
  if (mode === "optimize") {
@@ -192,11 +194,7 @@ export const planExperimentCampaign = async (
192
194
  }
193
195
  const baseline = await captureSeedCandidate(execution.loaded.subjectRoot, campaign.subject);
194
196
  await assertGraderSubject(execution, baseline);
195
- const corpus = await prepareCorpus(execution);
196
- // Plan is the deterministic preflight for execution. Build the exact same
197
- // identity now so profile, behavior-tree, and custom CLI failures happen
198
- // before a user approves any model spend.
199
- await evaluationIdentity(execution, corpus, mode);
197
+ const corpus = await preparePlanningCorpus(execution, mode, options.static ?? false);
200
198
  const splitSelectors = selectorsBySplit(campaign);
201
199
  const graderAnnotations = execution.loaded.graderAnnotations?.annotations ?? [];
202
200
  const splits = splitSelectors.map(({ name, selector }) => ({
@@ -0,0 +1,132 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ import {
6
+ type ClosedHarnessCatalog,
7
+ codexCatalog,
8
+ copilotCatalog,
9
+ exec,
10
+ type ReasoningEffort,
11
+ tryParseJson,
12
+ } from "../core/index.js";
13
+
14
+ export type LiveHarnessModel = {
15
+ id: string;
16
+ efforts?: readonly ReasoningEffort[] | undefined;
17
+ };
18
+
19
+ export type HarnessCatalogDrift = {
20
+ unlisted: string[];
21
+ mismatched: string[];
22
+ unavailable: string[];
23
+ };
24
+
25
+ export const harnessCatalogDrift = (
26
+ catalog: ClosedHarnessCatalog,
27
+ live: readonly LiveHarnessModel[],
28
+ ): HarnessCatalogDrift => {
29
+ const liveIds = new Set(live.map((model) => model.id));
30
+ return {
31
+ unlisted: live.filter((model) => !(model.id in catalog.models)).map((model) => model.id),
32
+ mismatched: live
33
+ .filter(
34
+ (model) =>
35
+ model.efforts !== undefined &&
36
+ model.id in catalog.models &&
37
+ model.efforts.join(",") !== catalog.models[model.id]?.join(","),
38
+ )
39
+ .map((model) => `${model.id} (${model.efforts?.join(", ") || "no effort"})`),
40
+ unavailable: Object.keys(catalog.models).filter((id) => !liveIds.has(id)),
41
+ };
42
+ };
43
+
44
+ export const hasHarnessCatalogDrift = (drift: HarnessCatalogDrift): boolean =>
45
+ drift.unlisted.length > 0 || drift.mismatched.length > 0;
46
+
47
+ export const describeHarnessCatalogDrift = (drift: HarnessCatalogDrift): string =>
48
+ [
49
+ drift.unlisted.length > 0 ? `not in catalog: ${drift.unlisted.join(", ")}` : undefined,
50
+ drift.mismatched.length > 0 ? `efforts differ: ${drift.mismatched.join("; ")}` : undefined,
51
+ drift.unavailable.length > 0 ? `not offered here: ${drift.unavailable.join(", ")}` : undefined,
52
+ ]
53
+ .filter((part) => part !== undefined)
54
+ .join(". ");
55
+
56
+ const CodexModelsCacheSchema = z.object({
57
+ models: z.array(
58
+ z.looseObject({
59
+ slug: z.string(),
60
+ visibility: z.string().optional(),
61
+ supported_reasoning_levels: z.array(z.looseObject({ effort: z.string() })).default([]),
62
+ }),
63
+ ),
64
+ });
65
+
66
+ const codexModelsCachePath = (env: NodeJS.ProcessEnv = process.env): string =>
67
+ join(env.CODEX_HOME ?? join(homedir(), ".codex"), "models_cache.json");
68
+
69
+ export const parseCodexModelsCache = (text: string): LiveHarnessModel[] | undefined => {
70
+ const parsed = CodexModelsCacheSchema.safeParse(tryParseJson(text));
71
+ if (!parsed.success) return undefined;
72
+ return parsed.data.models
73
+ .filter((model) => model.visibility !== "hide")
74
+ .map((model) => ({
75
+ id: model.slug,
76
+ efforts: model.supported_reasoning_levels.map((level) => level.effort as ReasoningEffort),
77
+ }));
78
+ };
79
+
80
+ const readCodexLiveModels = async (
81
+ path = codexModelsCachePath(),
82
+ ): Promise<LiveHarnessModel[] | undefined> => {
83
+ try {
84
+ return parseCodexModelsCache(await readFile(path, "utf8"));
85
+ } catch {
86
+ return undefined;
87
+ }
88
+ };
89
+
90
+ export const parseCopilotModelValues = (helpText: string): LiveHarnessModel[] | undefined => {
91
+ const lines = helpText.split("\n");
92
+ const start = lines.findIndex((line) => /^\s*`model`:/.test(line));
93
+ if (start < 0) return undefined;
94
+ const models: LiveHarnessModel[] = [];
95
+ for (const line of lines.slice(start + 1)) {
96
+ const match = /^\s*-\s*"([^"]+)"\s*$/.exec(line);
97
+ if (!match?.[1]) break;
98
+ models.push({ id: match[1] });
99
+ }
100
+ return models.length > 0 ? models : undefined;
101
+ };
102
+
103
+ const readCopilotLiveModels = async (
104
+ command = "copilot",
105
+ ): Promise<LiveHarnessModel[] | undefined> => {
106
+ try {
107
+ const result = await exec(command, ["help", "config"], { timeoutMs: 15_000 });
108
+ if (result.exitCode !== 0 || result.timedOut) return undefined;
109
+ return parseCopilotModelValues(result.stdout);
110
+ } catch {
111
+ return undefined;
112
+ }
113
+ };
114
+
115
+ export type HarnessCatalogProbe = {
116
+ catalog: ClosedHarnessCatalog;
117
+ source: string;
118
+ read: (command: string | undefined) => Promise<LiveHarnessModel[] | undefined>;
119
+ };
120
+
121
+ export const harnessCatalogProbes: readonly HarnessCatalogProbe[] = [
122
+ {
123
+ catalog: codexCatalog,
124
+ source: "models_cache.json",
125
+ read: () => readCodexLiveModels(),
126
+ },
127
+ {
128
+ catalog: copilotCatalog,
129
+ source: "help config",
130
+ read: (command) => readCopilotLiveModels(command),
131
+ },
132
+ ];