@prismatic-io/lux 0.0.2-preview.18 → 0.0.2-preview.21
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.
- package/lib/answerers/persona/index.d.ts +5 -1
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.d.ts +3 -1
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/authoring.d.ts +3 -1
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +2 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/harness-plan.d.ts +26 -0
- package/lib/cli/harness-plan.d.ts.map +1 -0
- package/lib/cli/harness-plan.js +75 -0
- package/lib/cli/harness-plan.js.map +1 -0
- package/lib/cli/init-templates.d.ts +2 -2
- package/lib/cli/init-templates.d.ts.map +1 -1
- package/lib/cli/init-templates.js +11 -4
- package/lib/cli/init-templates.js.map +1 -1
- package/lib/cli/init.d.ts +2 -0
- package/lib/cli/init.d.ts.map +1 -1
- package/lib/cli/init.js +6 -3
- package/lib/cli/init.js.map +1 -1
- package/lib/cli/output-schemas.d.ts +7 -0
- package/lib/cli/output-schemas.d.ts.map +1 -1
- package/lib/cli/program.d.ts +5 -10
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +44 -77
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/run-options.d.ts +19 -0
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js +92 -0
- package/lib/cli/run-options.js.map +1 -1
- package/lib/core/case.d.ts +2 -0
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +3 -0
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver.d.ts +13 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +10 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/harness-catalog.d.ts +35 -0
- package/lib/core/harness-catalog.d.ts.map +1 -0
- package/lib/core/harness-catalog.js +45 -0
- package/lib/core/harness-catalog.js.map +1 -0
- package/lib/core/harness-catalogs/antigravity.d.ts +11 -0
- package/lib/core/harness-catalogs/antigravity.d.ts.map +1 -0
- package/lib/core/harness-catalogs/antigravity.js +23 -0
- package/lib/core/harness-catalogs/antigravity.js.map +1 -0
- package/lib/core/harness-catalogs/claude-code.d.ts +12 -0
- package/lib/core/harness-catalogs/claude-code.d.ts.map +1 -0
- package/lib/core/harness-catalogs/claude-code.js +24 -0
- package/lib/core/harness-catalogs/claude-code.js.map +1 -0
- package/lib/core/harness-catalogs/codex.d.ts +11 -0
- package/lib/core/harness-catalogs/codex.d.ts.map +1 -0
- package/lib/core/harness-catalogs/codex.js +26 -0
- package/lib/core/harness-catalogs/codex.js.map +1 -0
- package/lib/core/harness-catalogs/copilot.d.ts +29 -0
- package/lib/core/harness-catalogs/copilot.d.ts.map +1 -0
- package/lib/core/harness-catalogs/copilot.js +45 -0
- package/lib/core/harness-catalogs/copilot.js.map +1 -0
- package/lib/core/harness-catalogs/cursor.d.ts +2 -0
- package/lib/core/harness-catalogs/cursor.d.ts.map +1 -0
- package/lib/core/harness-catalogs/cursor.js +13 -0
- package/lib/core/harness-catalogs/cursor.js.map +1 -0
- package/lib/core/harness-catalogs/index.d.ts +67 -0
- package/lib/core/harness-catalogs/index.d.ts.map +1 -0
- package/lib/core/harness-catalogs/index.js +15 -0
- package/lib/core/harness-catalogs/index.js.map +1 -0
- package/lib/core/harness-matrix.d.ts +70 -0
- package/lib/core/harness-matrix.d.ts.map +1 -0
- package/lib/core/harness-matrix.js +194 -0
- package/lib/core/harness-matrix.js.map +1 -0
- package/lib/core/index.d.ts +3 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +3 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/model-cli.d.ts +4 -11
- package/lib/core/model-cli.d.ts.map +1 -1
- package/lib/core/model-cli.js +23 -17
- package/lib/core/model-cli.js.map +1 -1
- package/lib/core/platform-process.d.ts +1 -0
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +1 -1
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/run.d.ts +4 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +2 -0
- package/lib/core/run.js.map +1 -1
- package/lib/drivers/antigravity/config.d.ts +46 -0
- package/lib/drivers/antigravity/config.d.ts.map +1 -0
- package/lib/drivers/antigravity/config.js +39 -0
- package/lib/drivers/antigravity/config.js.map +1 -0
- package/lib/drivers/antigravity/events.d.ts +4 -0
- package/lib/drivers/antigravity/events.d.ts.map +1 -0
- package/lib/drivers/antigravity/events.js +193 -0
- package/lib/drivers/antigravity/events.js.map +1 -0
- package/lib/drivers/antigravity/index.d.ts +60 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -0
- package/lib/drivers/antigravity/index.js +90 -0
- package/lib/drivers/antigravity/index.js.map +1 -0
- package/lib/drivers/claude-code/index.d.ts +93 -7
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +14 -8
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/codex/config.d.ts +61 -4
- package/lib/drivers/codex/config.d.ts.map +1 -1
- package/lib/drivers/codex/config.js +19 -5
- package/lib/drivers/codex/config.js.map +1 -1
- package/lib/drivers/codex/index.d.ts +63 -5
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +11 -3
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/copilot/config.d.ts +81 -0
- package/lib/drivers/copilot/config.d.ts.map +1 -0
- package/lib/drivers/copilot/config.js +54 -0
- package/lib/drivers/copilot/config.js.map +1 -0
- package/lib/drivers/copilot/index.d.ts +103 -0
- package/lib/drivers/copilot/index.d.ts.map +1 -0
- package/lib/drivers/copilot/index.js +256 -0
- package/lib/drivers/copilot/index.js.map +1 -0
- package/lib/drivers/cursor/config.d.ts +6 -1
- package/lib/drivers/cursor/config.d.ts.map +1 -1
- package/lib/drivers/cursor/config.js +2 -3
- package/lib/drivers/cursor/config.js.map +1 -1
- package/lib/drivers/cursor/index.d.ts +7 -2
- package/lib/drivers/cursor/index.d.ts.map +1 -1
- package/lib/drivers/cursor/index.js +9 -6
- package/lib/drivers/cursor/index.js.map +1 -1
- package/lib/drivers/cursor/interaction.d.ts +1 -2
- package/lib/drivers/cursor/interaction.d.ts.map +1 -1
- package/lib/drivers/cursor/interaction.js +3 -14
- package/lib/drivers/cursor/interaction.js.map +1 -1
- package/lib/drivers/{cursor/events.d.ts → shared/acp-events.d.ts} +5 -3
- package/lib/drivers/shared/acp-events.d.ts.map +1 -0
- package/lib/drivers/{cursor/events.js → shared/acp-events.js} +20 -10
- package/lib/drivers/shared/acp-events.js.map +1 -0
- package/lib/drivers/shared/acp-interaction.d.ts +7 -0
- package/lib/drivers/shared/acp-interaction.d.ts.map +1 -0
- package/lib/drivers/shared/acp-interaction.js +20 -0
- package/lib/drivers/shared/acp-interaction.js.map +1 -0
- package/lib/drivers/{cursor → shared}/acp-transport.d.ts +2 -0
- package/lib/drivers/shared/acp-transport.d.ts.map +1 -0
- package/lib/drivers/{cursor → shared}/acp-transport.js +10 -10
- package/lib/drivers/shared/acp-transport.js.map +1 -0
- package/lib/index.d.ts +7 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -2
- package/lib/index.js.map +1 -1
- package/lib/optimization/reflective.d.ts +2 -0
- package/lib/optimization/reflective.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.js +1 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -1
- package/lib/orchestrator/config.d.ts +7 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +8 -1
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +72 -2
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/experiment-context.d.ts +1 -1
- package/lib/orchestrator/harness-catalog-drift.d.ts +22 -0
- package/lib/orchestrator/harness-catalog-drift.d.ts.map +1 -0
- package/lib/orchestrator/harness-catalog-drift.js +90 -0
- package/lib/orchestrator/harness-catalog-drift.js.map +1 -0
- package/lib/orchestrator/orchestrator.d.ts +9 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +14 -22
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/run-dir.d.ts +2 -0
- package/lib/orchestrator/run-dir.d.ts.map +1 -1
- package/lib/orchestrator/run-dir.js +1 -1
- package/lib/orchestrator/run-dir.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +1 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +2 -0
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +3 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.js +27 -17
- package/lib/orchestrator/suite-compare.js.map +1 -1
- package/lib/orchestrator/suite-runs.d.ts +3 -0
- package/lib/orchestrator/suite-runs.d.ts.map +1 -1
- package/lib/orchestrator/suite-runs.js +2 -0
- package/lib/orchestrator/suite-runs.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +9 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +14 -4
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/authoring.ts +10 -3
- package/src/cli/harness-plan.ts +117 -0
- package/src/cli/init-templates.ts +12 -5
- package/src/cli/init.ts +12 -5
- package/src/cli/program.ts +50 -92
- package/src/cli/run-options.ts +123 -0
- package/src/core/case.ts +3 -0
- package/src/core/driver.ts +26 -0
- package/src/core/harness-catalog.ts +100 -0
- package/src/core/harness-catalogs/antigravity.ts +24 -0
- package/src/core/harness-catalogs/claude-code.ts +25 -0
- package/src/core/harness-catalogs/codex.ts +27 -0
- package/src/core/harness-catalogs/copilot.ts +46 -0
- package/src/core/harness-catalogs/cursor.ts +13 -0
- package/src/core/harness-catalogs/index.ts +22 -0
- package/src/core/harness-matrix.ts +298 -0
- package/src/core/index.ts +3 -0
- package/src/core/model-cli.ts +38 -21
- package/src/core/platform-process.ts +4 -4
- package/src/core/run.ts +2 -0
- package/src/drivers/antigravity/README.md +46 -0
- package/src/drivers/antigravity/config.ts +44 -0
- package/src/drivers/antigravity/events.ts +235 -0
- package/src/drivers/antigravity/index.ts +109 -0
- package/src/drivers/claude-code/index.ts +18 -7
- package/src/drivers/codex/config.ts +23 -5
- package/src/drivers/codex/index.ts +15 -2
- package/src/drivers/copilot/README.md +52 -0
- package/src/drivers/copilot/config.ts +58 -0
- package/src/drivers/copilot/index.ts +292 -0
- package/src/drivers/cursor/README.md +9 -6
- package/src/drivers/cursor/config.ts +2 -3
- package/src/drivers/cursor/index.ts +10 -6
- package/src/drivers/cursor/interaction.ts +3 -19
- package/src/drivers/{cursor/events.ts → shared/acp-events.ts} +20 -9
- package/src/drivers/shared/acp-interaction.ts +27 -0
- package/src/drivers/{cursor → shared}/acp-transport.ts +15 -10
- package/src/index.ts +44 -3
- package/src/orchestrator/comparison-identity.ts +1 -0
- package/src/orchestrator/config.ts +8 -0
- package/src/orchestrator/doctor.ts +125 -3
- package/src/orchestrator/harness-catalog-drift.ts +132 -0
- package/src/orchestrator/orchestrator.ts +31 -26
- package/src/orchestrator/run-dir.ts +8 -1
- package/src/orchestrator/run-execution.ts +3 -0
- package/src/orchestrator/suite-compare.ts +29 -19
- package/src/orchestrator/suite-runs.ts +3 -1
- package/src/orchestrator/suite-summary.ts +19 -4
- package/lib/drivers/cursor/acp-transport.d.ts.map +0 -1
- package/lib/drivers/cursor/acp-transport.js.map +0 -1
- package/lib/drivers/cursor/events.d.ts.map +0 -1
- package/lib/drivers/cursor/events.js.map +0 -1
- package/skills/lux/SKILL.md +0 -89
- package/skills/lux/references/cli.md +0 -128
- 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
|
|
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
|
|
|
@@ -132,7 +146,28 @@ const effectiveDriverReference = (local: unknown, fallback: unknown): unknown =>
|
|
|
132
146
|
};
|
|
133
147
|
};
|
|
134
148
|
|
|
135
|
-
const
|
|
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
|
+
}
|
|
136
171
|
const fallbacks = [
|
|
137
172
|
config.defaultDriver,
|
|
138
173
|
...Object.values(config.profiles ?? {}).map((profile) => profile.driver),
|
|
@@ -192,6 +227,17 @@ const configuredCliTargets = (config: ResolvedLuxConfig, registry: Registry): Cl
|
|
|
192
227
|
};
|
|
193
228
|
for (const command of driverCommands(config.defaultDriver, registry))
|
|
194
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
|
+
}
|
|
195
241
|
add("default answerer", answererCommand(config.defaultAnswerer));
|
|
196
242
|
if (config.harness) {
|
|
197
243
|
add(
|
|
@@ -357,6 +403,32 @@ const validateConfiguredDrivers = (
|
|
|
357
403
|
}
|
|
358
404
|
};
|
|
359
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
|
+
|
|
360
432
|
const validateConfiguredAnswerers = (
|
|
361
433
|
config: ResolvedLuxConfig,
|
|
362
434
|
registry: Registry,
|
|
@@ -576,7 +648,7 @@ const inspectCase = async (
|
|
|
576
648
|
try {
|
|
577
649
|
const authored = await loadEvalCase(discovered.path);
|
|
578
650
|
const caseRegistry = registryForEvalCase(registry, authored);
|
|
579
|
-
const drivers = caseDriverReferences(authored
|
|
651
|
+
const drivers = caseDriverReferences(authored, config, caseRegistry);
|
|
580
652
|
const answerer = authored.answerer ?? config.defaultAnswerer;
|
|
581
653
|
validateCaseDrivers(discovered.id, drivers, caseRegistry, recorder, authored.requires);
|
|
582
654
|
validateCaseAnswerer(discovered.id, answerer, caseRegistry, recorder);
|
|
@@ -659,6 +731,54 @@ const inspectCliTargets = async (
|
|
|
659
731
|
}
|
|
660
732
|
};
|
|
661
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
|
+
|
|
662
782
|
const isBlockingCampaignWarning = (warning: string): boolean =>
|
|
663
783
|
warning.includes("below confidence minSamples=") || warning.includes("cannot attain confidence");
|
|
664
784
|
|
|
@@ -752,6 +872,7 @@ export const doctorProject = async (options: DoctorOptions): Promise<DoctorRepor
|
|
|
752
872
|
if (!registry) return recorder.finish(config.rootDir);
|
|
753
873
|
|
|
754
874
|
validateConfiguredDrivers(config, registry, recorder);
|
|
875
|
+
validateConfiguredMatrix(config, registry, recorder);
|
|
755
876
|
validateConfiguredAnswerers(config, registry, recorder);
|
|
756
877
|
const caseCliTargets = await inspectCases(config, registry, recorder);
|
|
757
878
|
if (parsedOptions.static) {
|
|
@@ -766,6 +887,7 @@ export const doctorProject = async (options: DoctorOptions): Promise<DoctorRepor
|
|
|
766
887
|
config,
|
|
767
888
|
recorder,
|
|
768
889
|
);
|
|
890
|
+
await inspectHarnessCatalogs(config, recorder);
|
|
769
891
|
}
|
|
770
892
|
await inspectCampaigns(
|
|
771
893
|
parsedOptions.campaigns ?? [],
|
|
@@ -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
|
+
];
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
type AnswererRef,
|
|
7
7
|
bindSubjectRoot,
|
|
8
8
|
type DriverRef,
|
|
9
|
+
driverConfigOf,
|
|
10
|
+
driverNameOf,
|
|
9
11
|
type EvalCase,
|
|
10
12
|
type GradingAssertionEntry,
|
|
11
13
|
type GradingReport,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
type HarnessModelConfig,
|
|
14
16
|
type LifecycleHooksPlugin,
|
|
15
17
|
mergeHarnessModelConfig,
|
|
18
|
+
mergePluginConfig,
|
|
16
19
|
type Registry,
|
|
17
20
|
type RunMetadata,
|
|
18
21
|
type RunObserver,
|
|
@@ -142,6 +145,12 @@ export type RunRequest = {
|
|
|
142
145
|
subjectRoot?: string | undefined;
|
|
143
146
|
subjectHash?: string | undefined;
|
|
144
147
|
profileId?: string | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* Driver bound by a harness permutation. It replaces the project default;
|
|
150
|
+
* a case pinned to another driver is rejected rather than silently rerouted.
|
|
151
|
+
*/
|
|
152
|
+
driver?: DriverRef | undefined;
|
|
153
|
+
permutationId?: string | undefined;
|
|
145
154
|
experimentId?: string | undefined;
|
|
146
155
|
variantId?: string | undefined;
|
|
147
156
|
candidateId?: string | undefined;
|
|
@@ -200,7 +209,13 @@ export type SuiteObserver = RunObserver & {
|
|
|
200
209
|
* `caseId` is the canonical id runs are filed under when the case declares
|
|
201
210
|
* none — display decorations (a `--loop` suffix) must not fork run history.
|
|
202
211
|
*/
|
|
203
|
-
export type SuiteItem = {
|
|
212
|
+
export type SuiteItem = {
|
|
213
|
+
path: string;
|
|
214
|
+
id: string;
|
|
215
|
+
caseId?: string | undefined;
|
|
216
|
+
driver?: DriverRef | undefined;
|
|
217
|
+
permutationId?: string | undefined;
|
|
218
|
+
};
|
|
204
219
|
|
|
205
220
|
const resolveCaseId = (casePath: string, evalCase: EvalCase, fallback?: string): string => {
|
|
206
221
|
if (evalCase.id) return evalCase.id;
|
|
@@ -211,39 +226,29 @@ const resolveCaseId = (casePath: string, evalCase: EvalCase, fallback?: string):
|
|
|
211
226
|
return ext ? base.slice(0, -ext.length) : base;
|
|
212
227
|
};
|
|
213
228
|
|
|
214
|
-
const driverNameOf = (ref: DriverRef): string => (typeof ref === "string" ? ref : ref.name);
|
|
215
|
-
|
|
216
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
217
|
-
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
218
|
-
|
|
219
|
-
/** Merge plugin config recursively so nested Codex `config`/`env` overrides stay additive. */
|
|
220
|
-
const mergePluginConfig = (base: unknown, local: unknown): unknown => {
|
|
221
|
-
if (!isRecord(base) || !isRecord(local)) return local ?? base;
|
|
222
|
-
const merged: Record<string, unknown> = { ...base };
|
|
223
|
-
for (const [key, value] of Object.entries(local)) {
|
|
224
|
-
merged[key] = key in merged ? mergePluginConfig(merged[key], value) : value;
|
|
225
|
-
}
|
|
226
|
-
return merged;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
229
|
export const resolveDriverReference = (
|
|
230
230
|
evalCase: EvalCase,
|
|
231
231
|
fallback: DriverRef | undefined,
|
|
232
|
+
permutation?: DriverRef | undefined,
|
|
232
233
|
): DriverRef => {
|
|
233
234
|
const local = evalCase.driver;
|
|
235
|
+
if (permutation && local && driverNameOf(local) !== driverNameOf(permutation)) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`case is pinned to driver '${driverNameOf(local)}' but the run selected '${driverNameOf(permutation)}'`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const base = permutation ?? fallback;
|
|
234
241
|
if (!local) {
|
|
235
|
-
if (!
|
|
236
|
-
return
|
|
242
|
+
if (!base) throw new Error("case names no driver and no defaultDriver is configured");
|
|
243
|
+
return base;
|
|
237
244
|
}
|
|
238
|
-
if (!
|
|
245
|
+
if (!base || driverNameOf(local) !== driverNameOf(base)) return local;
|
|
239
246
|
|
|
240
247
|
// Naming the same driver opts into project defaults. A case object only
|
|
241
248
|
// needs to carry the fields it wants to override (for example a timeout).
|
|
242
|
-
const baseConfig = typeof fallback === "object" ? fallback.config : undefined;
|
|
243
|
-
const localConfig = typeof local === "object" ? local.config : undefined;
|
|
244
249
|
return {
|
|
245
250
|
name: driverNameOf(local),
|
|
246
|
-
config: mergePluginConfig(
|
|
251
|
+
config: mergePluginConfig(driverConfigOf(base), driverConfigOf(local)),
|
|
247
252
|
};
|
|
248
253
|
};
|
|
249
254
|
|
|
@@ -264,9 +269,6 @@ export const resolveAnswererReference = (
|
|
|
264
269
|
|
|
265
270
|
const answererNameOf = (ref: AnswererRef): string => (typeof ref === "string" ? ref : ref.name);
|
|
266
271
|
|
|
267
|
-
const driverConfigOf = (ref: DriverRef): unknown =>
|
|
268
|
-
typeof ref === "object" ? ref.config : undefined;
|
|
269
|
-
|
|
270
272
|
const answererConfigOf = (ref: AnswererRef): unknown =>
|
|
271
273
|
typeof ref === "object" ? ref.config : undefined;
|
|
272
274
|
|
|
@@ -352,7 +354,7 @@ export class Orchestrator {
|
|
|
352
354
|
registry,
|
|
353
355
|
);
|
|
354
356
|
|
|
355
|
-
const driverRef = resolveDriverReference(evalCase, this.defaultDriver);
|
|
357
|
+
const driverRef = resolveDriverReference(evalCase, this.defaultDriver, request.driver);
|
|
356
358
|
const driverName = driverNameOf(driverRef);
|
|
357
359
|
const answererRef = resolveAnswererReference(
|
|
358
360
|
evalCase,
|
|
@@ -422,6 +424,7 @@ export class Orchestrator {
|
|
|
422
424
|
validatedAssertionConfigs,
|
|
423
425
|
caseId,
|
|
424
426
|
driverName,
|
|
427
|
+
permutationId: request.permutationId,
|
|
425
428
|
answererName,
|
|
426
429
|
answererConfig: answererCfg,
|
|
427
430
|
driverConfigHash: valueHash(driverCfg),
|
|
@@ -643,6 +646,8 @@ export class Orchestrator {
|
|
|
643
646
|
subjectRoot: opts.subjectRoot,
|
|
644
647
|
subjectHash: opts.subjectHash,
|
|
645
648
|
profileId: opts.profileId,
|
|
649
|
+
driver: item.driver,
|
|
650
|
+
permutationId: item.permutationId,
|
|
646
651
|
experimentId: opts.experimentId,
|
|
647
652
|
variantId: opts.variantId,
|
|
648
653
|
candidateId: opts.candidateId,
|
|
@@ -154,6 +154,8 @@ export type OpenRunDirOptions = {
|
|
|
154
154
|
runsRoot: string;
|
|
155
155
|
caseId: string;
|
|
156
156
|
driverName: string;
|
|
157
|
+
/** Files the run under the permutation instead of the bare driver name. */
|
|
158
|
+
permutationId?: string | undefined;
|
|
157
159
|
startedAt?: Date | undefined;
|
|
158
160
|
/** Tap on the event stream, called for every appended event. Must not throw. */
|
|
159
161
|
onEvent?: ((event: RunEvent) => void) | undefined;
|
|
@@ -164,7 +166,12 @@ export type OpenRunDirOptions = {
|
|
|
164
166
|
export const openRunDir = async (options: OpenRunDirOptions): Promise<OpenedRunDir> => {
|
|
165
167
|
const ts = isoTimestampSafe(options.startedAt ?? new Date());
|
|
166
168
|
const slug = slugifyCaseId(options.caseId);
|
|
167
|
-
const basePath = join(
|
|
169
|
+
const basePath = join(
|
|
170
|
+
options.runsRoot,
|
|
171
|
+
slug,
|
|
172
|
+
slugifyCaseId(options.permutationId ?? options.driverName),
|
|
173
|
+
ts,
|
|
174
|
+
);
|
|
168
175
|
await mkdir(dirname(basePath), { recursive: true });
|
|
169
176
|
// Same case+driver+millisecond must not share a dir — their events.jsonl
|
|
170
177
|
// streams (opened with flags "a") would interleave silently. A bare mkdir
|
|
@@ -69,6 +69,7 @@ export type RunWithPluginsArgs = {
|
|
|
69
69
|
validatedAssertionConfigs: readonly unknown[];
|
|
70
70
|
caseId: string;
|
|
71
71
|
driverName: string;
|
|
72
|
+
permutationId?: string | undefined;
|
|
72
73
|
answererName: string;
|
|
73
74
|
answererConfig: unknown;
|
|
74
75
|
driverConfigHash: string;
|
|
@@ -338,6 +339,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
338
339
|
runsRoot: resolve(args.request.runsRoot),
|
|
339
340
|
caseId: args.caseId,
|
|
340
341
|
driverName: args.driverName,
|
|
342
|
+
permutationId: args.permutationId,
|
|
341
343
|
startedAt,
|
|
342
344
|
onEvent: observer?.event,
|
|
343
345
|
});
|
|
@@ -653,6 +655,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
653
655
|
...(this.fixturesHash ? { fixturesHash: this.fixturesHash } : {}),
|
|
654
656
|
...(request.subjectHash ? { subjectHash: request.subjectHash } : {}),
|
|
655
657
|
...(request.profileId ? { profileId: request.profileId } : {}),
|
|
658
|
+
...(this.args.permutationId ? { permutationId: this.args.permutationId } : {}),
|
|
656
659
|
...(request.experimentId ? { experimentId: request.experimentId } : {}),
|
|
657
660
|
...(request.variantId ? { variantId: request.variantId } : {}),
|
|
658
661
|
...(request.candidateId ? { candidateId: request.candidateId } : {}),
|