@prismatic-io/lux 0.0.2-preview.4 → 0.0.2-preview.40
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/claude-code/index.d.ts +15 -3
- package/lib/answerers/claude-code/index.d.ts.map +1 -1
- package/lib/answerers/claude-code/index.js +6 -5
- package/lib/answerers/claude-code/index.js.map +1 -1
- package/lib/answerers/persona/index.d.ts +44 -5
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +60 -34
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/answerers/terminal/index.d.ts +9 -3
- package/lib/answerers/terminal/index.d.ts.map +1 -1
- package/lib/answerers/terminal/index.js +6 -4
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/authoring.d.ts +2 -2
- package/lib/assertions/authoring.d.ts.map +1 -1
- package/lib/assertions/authoring.js.map +1 -1
- package/lib/assertions/core/command-exits-zero.d.ts +5 -2
- package/lib/assertions/core/command-exits-zero.d.ts.map +1 -1
- package/lib/assertions/core/command-exits-zero.js +59 -14
- package/lib/assertions/core/command-exits-zero.js.map +1 -1
- package/lib/assertions/core/event-checks.d.ts +3 -0
- package/lib/assertions/core/event-checks.d.ts.map +1 -1
- package/lib/assertions/core/event-checks.js +49 -3
- package/lib/assertions/core/event-checks.js.map +1 -1
- package/lib/assertions/core/file-checks.d.ts +6 -0
- package/lib/assertions/core/file-checks.d.ts.map +1 -1
- package/lib/assertions/core/index.d.ts.map +1 -1
- package/lib/assertions/core/index.js +2 -1
- package/lib/assertions/core/index.js.map +1 -1
- package/lib/assertions/core/json-pointer-equals.d.ts +1 -0
- package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -1
- package/lib/assertions/core/output-checks.d.ts +3 -0
- package/lib/assertions/core/output-checks.d.ts.map +1 -1
- package/lib/assertions/core/predicate.d.ts +1 -0
- package/lib/assertions/core/predicate.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts +25 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +35 -5
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/assertions/core/safe-regex.d.ts.map +1 -1
- package/lib/assertions/core/safe-regex.js +7 -1
- package/lib/assertions/core/safe-regex.js.map +1 -1
- package/lib/assertions/rubric/evidence.d.ts +88 -0
- package/lib/assertions/rubric/evidence.d.ts.map +1 -0
- package/lib/assertions/rubric/evidence.js +75 -0
- package/lib/assertions/rubric/evidence.js.map +1 -0
- package/lib/assertions/rubric/index.d.ts +88 -34
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +59 -43
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts +30 -5
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +203 -41
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/authoring.d.ts +37 -2
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +36 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/bin.js +2 -13
- package/lib/cli/bin.js.map +1 -1
- package/lib/cli/command-runtime.d.ts +3 -3
- package/lib/cli/command-runtime.d.ts.map +1 -1
- package/lib/cli/command-runtime.js +17 -6
- package/lib/cli/command-runtime.js.map +1 -1
- package/lib/cli/doctor.d.ts +55 -0
- package/lib/cli/doctor.d.ts.map +1 -0
- package/lib/cli/doctor.js +14 -0
- package/lib/cli/doctor.js.map +1 -0
- package/lib/cli/harness-plan.d.ts +27 -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 +11 -0
- package/lib/cli/init-templates.d.ts.map +1 -0
- package/lib/cli/init-templates.js +215 -0
- package/lib/cli/init-templates.js.map +1 -0
- package/lib/cli/init.d.ts +10 -0
- package/lib/cli/init.d.ts.map +1 -1
- package/lib/cli/init.js +34 -173
- package/lib/cli/init.js.map +1 -1
- package/lib/cli/output-schemas.d.ts +1412 -0
- package/lib/cli/output-schemas.d.ts.map +1 -0
- package/lib/cli/output-schemas.js +94 -0
- package/lib/cli/output-schemas.js.map +1 -0
- package/lib/cli/program.d.ts +140 -12
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +783 -650
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/format.d.ts.map +1 -1
- package/lib/cli/render/format.js +1 -1
- package/lib/cli/render/format.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +35 -8
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +19 -4
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js +100 -19
- package/lib/cli/run-options.js.map +1 -1
- package/lib/cli/view.d.ts +15 -0
- package/lib/cli/view.d.ts.map +1 -0
- package/lib/cli/view.js +168 -0
- package/lib/cli/view.js.map +1 -0
- package/lib/container/driver.d.ts +27 -0
- package/lib/container/driver.d.ts.map +1 -0
- package/lib/container/driver.js +199 -0
- package/lib/container/driver.js.map +1 -0
- package/lib/container/paths.d.ts +3 -0
- package/lib/container/paths.d.ts.map +1 -0
- package/lib/container/paths.js +27 -0
- package/lib/container/paths.js.map +1 -0
- package/lib/container/protocol.d.ts +12 -0
- package/lib/container/protocol.d.ts.map +1 -0
- package/lib/container/protocol.js +38 -0
- package/lib/container/protocol.js.map +1 -0
- package/lib/container/resources.d.ts +14 -0
- package/lib/container/resources.d.ts.map +1 -0
- package/lib/container/resources.js +141 -0
- package/lib/container/resources.js.map +1 -0
- package/lib/container/worker.d.ts +2 -0
- package/lib/container/worker.d.ts.map +1 -0
- package/lib/container/worker.js +85 -0
- package/lib/container/worker.js.map +1 -0
- package/lib/core/action-events.d.ts +29 -0
- package/lib/core/action-events.d.ts.map +1 -0
- package/lib/core/action-events.js +51 -0
- package/lib/core/action-events.js.map +1 -0
- package/lib/core/agent-activity.d.ts +49 -0
- package/lib/core/agent-activity.d.ts.map +1 -0
- package/lib/core/agent-activity.js +206 -0
- package/lib/core/agent-activity.js.map +1 -0
- package/lib/core/agent-environment.d.ts +5 -0
- package/lib/core/agent-environment.d.ts.map +1 -0
- package/lib/core/agent-environment.js +47 -0
- package/lib/core/agent-environment.js.map +1 -0
- package/lib/core/agent-events.d.ts +26 -0
- package/lib/core/agent-events.d.ts.map +1 -0
- package/lib/core/agent-events.js +13 -0
- package/lib/core/agent-events.js.map +1 -0
- package/lib/core/annotation.d.ts +6 -2
- package/lib/core/annotation.d.ts.map +1 -1
- package/lib/core/annotation.js +2 -1
- package/lib/core/annotation.js.map +1 -1
- package/lib/core/answerer.d.ts +12 -0
- package/lib/core/answerer.d.ts.map +1 -1
- package/lib/core/answerer.js +2 -0
- package/lib/core/answerer.js.map +1 -1
- package/lib/core/artifact-evidence.d.ts +6 -1
- package/lib/core/artifact-evidence.d.ts.map +1 -1
- package/lib/core/artifact-evidence.js +27 -6
- package/lib/core/artifact-evidence.js.map +1 -1
- package/lib/core/assertion.d.ts +10 -4
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +8 -2
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case-defaults.d.ts +16 -0
- package/lib/core/case-defaults.d.ts.map +1 -0
- package/lib/core/case-defaults.js +42 -0
- package/lib/core/case-defaults.js.map +1 -0
- package/lib/core/case.d.ts +120 -6
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +51 -5
- package/lib/core/case.js.map +1 -1
- package/lib/core/command-verification.d.ts +10 -0
- package/lib/core/command-verification.d.ts.map +1 -0
- package/lib/core/command-verification.js +66 -0
- package/lib/core/command-verification.js.map +1 -0
- package/lib/core/driver-execution.d.ts +83 -0
- package/lib/core/driver-execution.d.ts.map +1 -0
- package/lib/core/driver-execution.js +72 -0
- package/lib/core/driver-execution.js.map +1 -0
- package/lib/core/driver-metrics.d.ts +98 -0
- package/lib/core/driver-metrics.d.ts.map +1 -0
- package/lib/core/driver-metrics.js +36 -0
- package/lib/core/driver-metrics.js.map +1 -0
- package/lib/core/driver.d.ts +64 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +28 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/duration.d.ts +5 -0
- package/lib/core/duration.d.ts.map +1 -0
- package/lib/core/duration.js +49 -0
- package/lib/core/duration.js.map +1 -0
- package/lib/core/environment.d.ts +18 -0
- package/lib/core/environment.d.ts.map +1 -0
- package/lib/core/environment.js +21 -0
- package/lib/core/environment.js.map +1 -0
- package/lib/core/evaluation-clusters.d.ts +17 -0
- package/lib/core/evaluation-clusters.d.ts.map +1 -0
- package/lib/core/evaluation-clusters.js +23 -0
- package/lib/core/evaluation-clusters.js.map +1 -0
- package/lib/core/events.d.ts +34 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +11 -2
- package/lib/core/events.js.map +1 -1
- package/lib/core/experiment.d.ts +33 -2
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +40 -19
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/file-tree.d.ts +7 -0
- package/lib/core/file-tree.d.ts.map +1 -0
- package/lib/core/file-tree.js +35 -0
- package/lib/core/file-tree.js.map +1 -0
- package/lib/core/global-id.d.ts +6 -0
- package/lib/core/global-id.d.ts.map +1 -0
- package/lib/core/global-id.js +17 -0
- package/lib/core/global-id.js.map +1 -0
- 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/grok-build.d.ts +7 -0
- package/lib/core/harness-catalogs/grok-build.d.ts.map +1 -0
- package/lib/core/harness-catalogs/grok-build.js +18 -0
- package/lib/core/harness-catalogs/grok-build.js.map +1 -0
- package/lib/core/harness-catalogs/index.d.ts +73 -0
- package/lib/core/harness-catalogs/index.d.ts.map +1 -0
- package/lib/core/harness-catalogs/index.js +17 -0
- package/lib/core/harness-catalogs/index.js.map +1 -0
- package/lib/core/harness-matrix.d.ts +154 -0
- package/lib/core/harness-matrix.d.ts.map +1 -0
- package/lib/core/harness-matrix.js +204 -0
- package/lib/core/harness-matrix.js.map +1 -0
- package/lib/core/index.d.ts +12 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +12 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
- package/lib/core/lifecycle-fixtures.js +4 -2
- package/lib/core/lifecycle-fixtures.js.map +1 -1
- package/lib/core/model-cli.d.ts +9 -11
- package/lib/core/model-cli.d.ts.map +1 -1
- package/lib/core/model-cli.js +39 -22
- package/lib/core/model-cli.js.map +1 -1
- package/lib/core/platform-process.d.ts +7 -2
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +54 -16
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/reference-defaults.d.ts +5 -0
- package/lib/core/reference-defaults.d.ts.map +1 -0
- package/lib/core/reference-defaults.js +42 -0
- package/lib/core/reference-defaults.js.map +1 -0
- package/lib/core/rubric.d.ts +1 -0
- package/lib/core/rubric.d.ts.map +1 -1
- package/lib/core/run.d.ts +292 -1
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +27 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/secure-copy.d.ts.map +1 -0
- package/lib/core/secure-copy.js.map +1 -0
- package/lib/core/usage.d.ts +5 -0
- package/lib/core/usage.d.ts.map +1 -1
- package/lib/core/usage.js +3 -0
- package/lib/core/usage.js.map +1 -1
- package/lib/core/wire.d.ts +11 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/drivers/antigravity/artifacts.d.ts +2 -0
- package/lib/drivers/antigravity/artifacts.d.ts.map +1 -0
- package/lib/drivers/antigravity/artifacts.js +33 -0
- package/lib/drivers/antigravity/artifacts.js.map +1 -0
- package/lib/drivers/antigravity/config.d.ts +88 -0
- package/lib/drivers/antigravity/config.d.ts.map +1 -0
- package/lib/drivers/antigravity/config.js +87 -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 +241 -0
- package/lib/drivers/antigravity/events.js.map +1 -0
- package/lib/drivers/antigravity/index.d.ts +114 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -0
- package/lib/drivers/antigravity/index.js +113 -0
- package/lib/drivers/antigravity/index.js.map +1 -0
- package/lib/drivers/antigravity/runtime.d.ts +7 -0
- package/lib/drivers/antigravity/runtime.d.ts.map +1 -0
- package/lib/drivers/antigravity/runtime.js +83 -0
- package/lib/drivers/antigravity/runtime.js.map +1 -0
- package/lib/drivers/claude-code/deferred-process.d.ts +17 -0
- package/lib/drivers/claude-code/deferred-process.d.ts.map +1 -0
- package/lib/drivers/claude-code/deferred-process.js +108 -0
- package/lib/drivers/claude-code/deferred-process.js.map +1 -0
- package/lib/drivers/claude-code/index.d.ts +187 -10
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +197 -252
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/claude-code/isolation.d.ts +8 -0
- package/lib/drivers/claude-code/isolation.d.ts.map +1 -0
- package/lib/drivers/claude-code/isolation.js +199 -0
- package/lib/drivers/claude-code/isolation.js.map +1 -0
- package/lib/drivers/claude-code/parse-events.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.js +73 -8
- package/lib/drivers/claude-code/parse-events.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts +9 -2
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +104 -12
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/config.d.ts +69 -5
- package/lib/drivers/codex/config.d.ts.map +1 -1
- package/lib/drivers/codex/config.js +48 -8
- package/lib/drivers/codex/config.js.map +1 -1
- package/lib/drivers/codex/exec-events.d.ts +4 -0
- package/lib/drivers/codex/exec-events.d.ts.map +1 -0
- package/lib/drivers/codex/exec-events.js +230 -0
- package/lib/drivers/codex/exec-events.js.map +1 -0
- package/lib/drivers/codex/index.d.ts +142 -4
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +114 -276
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/copilot/config.d.ts +84 -0
- package/lib/drivers/copilot/config.d.ts.map +1 -0
- package/lib/drivers/copilot/config.js +57 -0
- package/lib/drivers/copilot/config.js.map +1 -0
- package/lib/drivers/copilot/index.d.ts +107 -0
- package/lib/drivers/copilot/index.d.ts.map +1 -0
- package/lib/drivers/copilot/index.js +268 -0
- package/lib/drivers/copilot/index.js.map +1 -0
- package/lib/drivers/cursor/config.d.ts +28 -0
- package/lib/drivers/cursor/config.d.ts.map +1 -0
- package/lib/drivers/cursor/config.js +28 -0
- package/lib/drivers/cursor/config.js.map +1 -0
- package/lib/drivers/cursor/index.d.ts +44 -0
- package/lib/drivers/cursor/index.d.ts.map +1 -0
- package/lib/drivers/cursor/index.js +286 -0
- package/lib/drivers/cursor/index.js.map +1 -0
- package/lib/drivers/cursor/interaction.d.ts +6 -0
- package/lib/drivers/cursor/interaction.d.ts.map +1 -0
- package/lib/drivers/cursor/interaction.js +55 -0
- package/lib/drivers/cursor/interaction.js.map +1 -0
- package/lib/drivers/grok-build/config.d.ts +68 -0
- package/lib/drivers/grok-build/config.d.ts.map +1 -0
- package/lib/drivers/grok-build/config.js +40 -0
- package/lib/drivers/grok-build/config.js.map +1 -0
- package/lib/drivers/grok-build/index.d.ts +87 -0
- package/lib/drivers/grok-build/index.d.ts.map +1 -0
- package/lib/drivers/grok-build/index.js +293 -0
- package/lib/drivers/grok-build/index.js.map +1 -0
- package/lib/drivers/mcp/index.d.ts +37 -4
- package/lib/drivers/mcp/index.d.ts.map +1 -1
- package/lib/drivers/mcp/index.js +25 -11
- package/lib/drivers/mcp/index.js.map +1 -1
- package/lib/drivers/shared/acp-events.d.ts +15 -0
- package/lib/drivers/shared/acp-events.d.ts.map +1 -0
- package/lib/drivers/shared/acp-events.js +83 -0
- 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/shared/acp-session.d.ts +4 -0
- package/lib/drivers/shared/acp-session.d.ts.map +1 -0
- package/lib/drivers/shared/acp-session.js +23 -0
- package/lib/drivers/shared/acp-session.js.map +1 -0
- package/lib/drivers/shared/acp-transport.d.ts +40 -0
- package/lib/drivers/shared/acp-transport.d.ts.map +1 -0
- package/lib/drivers/shared/acp-transport.js +152 -0
- package/lib/drivers/shared/acp-transport.js.map +1 -0
- package/lib/drivers/shared/artifacts.d.ts.map +1 -1
- package/lib/drivers/shared/artifacts.js +3 -2
- package/lib/drivers/shared/artifacts.js.map +1 -1
- package/lib/drivers/shared/experiment-behavior.d.ts +13 -0
- package/lib/drivers/shared/experiment-behavior.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-behavior.js +200 -0
- package/lib/drivers/shared/experiment-behavior.js.map +1 -0
- package/lib/drivers/shared/experiment-policy.d.ts +4 -0
- package/lib/drivers/shared/experiment-policy.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-policy.js +144 -0
- package/lib/drivers/shared/experiment-policy.js.map +1 -0
- package/lib/drivers/shared/skills.d.ts +3 -0
- package/lib/drivers/shared/skills.d.ts.map +1 -0
- package/lib/drivers/shared/skills.js +73 -0
- package/lib/drivers/shared/skills.js.map +1 -0
- package/lib/drivers/subprocess/index.d.ts +39 -4
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +56 -12
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/index.d.ts +24 -8
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +20 -5
- package/lib/index.js.map +1 -1
- package/lib/optimization/reflective.d.ts +3 -1
- package/lib/optimization/reflective.d.ts.map +1 -1
- package/lib/optimization/reflective.js +3 -2
- package/lib/optimization/reflective.js.map +1 -1
- package/lib/orchestrator/annotation-loader.d.ts +6 -2
- package/lib/orchestrator/annotation-loader.d.ts.map +1 -1
- package/lib/orchestrator/annotation-loader.js +8 -0
- package/lib/orchestrator/annotation-loader.js.map +1 -1
- package/lib/orchestrator/annotation-store.d.ts.map +1 -1
- package/lib/orchestrator/annotation-store.js +16 -2
- package/lib/orchestrator/annotation-store.js.map +1 -1
- package/lib/orchestrator/artifact-origin.d.ts +3 -0
- package/lib/orchestrator/artifact-origin.d.ts.map +1 -0
- package/lib/orchestrator/artifact-origin.js +40 -0
- package/lib/orchestrator/artifact-origin.js.map +1 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts +1 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/campaign-lifecycle.js +4 -1
- package/lib/orchestrator/campaign-lifecycle.js.map +1 -1
- package/lib/orchestrator/candidates.d.ts.map +1 -1
- package/lib/orchestrator/candidates.js +3 -2
- package/lib/orchestrator/candidates.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +23 -6
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +45 -12
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.js +2 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -1
- package/lib/orchestrator/config.d.ts +180 -1
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +21 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/discover.d.ts +1 -0
- package/lib/orchestrator/discover.d.ts.map +1 -1
- package/lib/orchestrator/discover.js +7 -1
- package/lib/orchestrator/discover.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts +1 -0
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +134 -69
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/driver-capabilities.d.ts +4 -0
- package/lib/orchestrator/driver-capabilities.d.ts.map +1 -0
- package/lib/orchestrator/driver-capabilities.js +13 -0
- package/lib/orchestrator/driver-capabilities.js.map +1 -0
- package/lib/orchestrator/driver-identity.d.ts +5 -0
- package/lib/orchestrator/driver-identity.d.ts.map +1 -0
- package/lib/orchestrator/driver-identity.js +51 -0
- package/lib/orchestrator/driver-identity.js.map +1 -0
- package/lib/orchestrator/experiment-context.d.ts +51 -3
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/experiment-context.js +2 -1
- package/lib/orchestrator/experiment-context.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +31 -1
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.d.ts +2 -1
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.js +28 -22
- package/lib/orchestrator/experiment-corpus.js.map +1 -1
- package/lib/orchestrator/experiment-effects.d.ts +33 -0
- package/lib/orchestrator/experiment-effects.d.ts.map +1 -0
- package/lib/orchestrator/experiment-effects.js +46 -0
- package/lib/orchestrator/experiment-effects.js.map +1 -0
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +70 -5
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-identity.d.ts +3 -0
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-identity.js +22 -33
- package/lib/orchestrator/experiment-identity.js.map +1 -1
- package/lib/orchestrator/experiment-loader.d.ts +8 -3
- package/lib/orchestrator/experiment-loader.d.ts.map +1 -1
- package/lib/orchestrator/experiment-preflight.d.ts +3 -0
- package/lib/orchestrator/experiment-preflight.d.ts.map +1 -0
- package/lib/orchestrator/experiment-preflight.js +21 -0
- package/lib/orchestrator/experiment-preflight.js.map +1 -0
- package/lib/orchestrator/experiment-report.d.ts +91 -1
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-report.js +9 -1
- package/lib/orchestrator/experiment-report.js.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +63 -2
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.js +13 -5
- package/lib/orchestrator/experiment-runs.js.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.d.ts +0 -16
- package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.js +5 -220
- package/lib/orchestrator/experiment-runtime-identity.js.map +1 -1
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.js +151 -43
- package/lib/orchestrator/experiment-selection.js.map +1 -1
- package/lib/orchestrator/experiment.d.ts +3 -3
- package/lib/orchestrator/experiment.d.ts.map +1 -1
- package/lib/orchestrator/experiment.js +26 -21
- package/lib/orchestrator/experiment.js.map +1 -1
- package/lib/orchestrator/fixture-identity.d.ts +1 -0
- package/lib/orchestrator/fixture-identity.d.ts.map +1 -1
- package/lib/orchestrator/fixture-identity.js +6 -1
- package/lib/orchestrator/fixture-identity.js.map +1 -1
- package/lib/orchestrator/fs-read.d.ts +1 -6
- package/lib/orchestrator/fs-read.d.ts.map +1 -1
- package/lib/orchestrator/fs-read.js +2 -32
- package/lib/orchestrator/fs-read.js.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +23 -5
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grader-experiment.js +54 -9
- package/lib/orchestrator/grader-experiment.js.map +1 -1
- package/lib/orchestrator/grading.d.ts +1 -0
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +41 -33
- package/lib/orchestrator/grading.js.map +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/harness-registry.d.ts +4 -0
- package/lib/orchestrator/harness-registry.d.ts.map +1 -0
- package/lib/orchestrator/harness-registry.js +5 -0
- package/lib/orchestrator/harness-registry.js.map +1 -0
- package/lib/orchestrator/index.d.ts +1 -1
- package/lib/orchestrator/index.d.ts.map +1 -1
- package/lib/orchestrator/index.js +1 -1
- package/lib/orchestrator/index.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +8 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +83 -18
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.js +3 -1
- package/lib/orchestrator/optimization-lifecycle.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +17 -2
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +55 -177
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/paired-effects.d.ts +34 -0
- package/lib/orchestrator/paired-effects.d.ts.map +1 -0
- package/lib/orchestrator/paired-effects.js +164 -0
- package/lib/orchestrator/paired-effects.js.map +1 -0
- package/lib/orchestrator/profile.d.ts +4 -0
- package/lib/orchestrator/profile.d.ts.map +1 -0
- package/lib/orchestrator/profile.js +10 -0
- package/lib/orchestrator/profile.js.map +1 -0
- package/lib/orchestrator/provenance.d.ts +5 -1
- package/lib/orchestrator/provenance.d.ts.map +1 -1
- package/lib/orchestrator/provenance.js +16 -3
- package/lib/orchestrator/provenance.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +4 -2
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/rubric-loader.js +1 -1
- package/lib/orchestrator/rubric-loader.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 +3 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +64 -10
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/run-lifecycle.d.ts +1 -0
- package/lib/orchestrator/run-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/run-lifecycle.js +3 -2
- package/lib/orchestrator/run-lifecycle.js.map +1 -1
- package/lib/orchestrator/run-workspace.d.ts +10 -0
- package/lib/orchestrator/run-workspace.d.ts.map +1 -0
- package/lib/orchestrator/run-workspace.js +58 -0
- package/lib/orchestrator/run-workspace.js.map +1 -0
- package/lib/orchestrator/suite-compare.d.ts +37 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.js +44 -22
- package/lib/orchestrator/suite-compare.js.map +1 -1
- package/lib/orchestrator/suite-runs.d.ts +6 -1
- package/lib/orchestrator/suite-runs.d.ts.map +1 -1
- package/lib/orchestrator/suite-runs.js +14 -2
- package/lib/orchestrator/suite-runs.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +79 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +67 -14
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/artifacts.d.ts +25 -0
- package/lib/previewer/artifacts.d.ts.map +1 -0
- package/lib/previewer/artifacts.js +180 -0
- package/lib/previewer/artifacts.js.map +1 -0
- package/lib/previewer/server.d.ts +111 -0
- package/lib/previewer/server.d.ts.map +1 -0
- package/lib/previewer/server.js +559 -0
- package/lib/previewer/server.js.map +1 -0
- package/package.json +4 -2
- package/skills/lux-answerer/SKILL.md +2 -2
- package/src/answerers/claude-code/index.ts +6 -5
- package/src/answerers/persona/index.ts +75 -34
- package/src/answerers/terminal/index.ts +7 -11
- package/src/assertions/authoring.ts +2 -0
- package/src/assertions/core/command-exits-zero.ts +82 -17
- package/src/assertions/core/event-checks.ts +64 -3
- package/src/assertions/core/index.ts +2 -0
- package/src/assertions/core/resource-checks.ts +38 -5
- package/src/assertions/core/safe-regex.ts +16 -6
- package/src/assertions/rubric/evidence.ts +85 -0
- package/src/assertions/rubric/index.ts +63 -46
- package/src/assertions/rubric/internal.ts +256 -41
- package/src/authoring.ts +346 -2
- package/src/cli/bin.ts +2 -12
- package/src/cli/command-runtime.ts +20 -9
- package/src/cli/doctor.ts +17 -0
- package/src/cli/harness-plan.ts +123 -0
- package/src/cli/init-templates.ts +238 -0
- package/src/cli/init.ts +53 -184
- package/src/cli/output-schemas.ts +109 -0
- package/src/cli/program.ts +697 -730
- package/src/cli/render/format.ts +1 -1
- package/src/cli/render/reporter.ts +36 -8
- package/src/cli/run-options.ts +131 -25
- package/src/cli/view.ts +129 -0
- package/src/container/driver.ts +213 -0
- package/src/container/paths.ts +29 -0
- package/src/container/protocol.ts +48 -0
- package/src/container/resources.ts +165 -0
- package/src/container/worker.ts +107 -0
- package/src/core/action-events.ts +59 -0
- package/src/core/agent-activity.ts +236 -0
- package/src/core/agent-environment.ts +64 -0
- package/src/core/agent-events.ts +17 -0
- package/src/core/annotation.ts +2 -1
- package/src/core/answerer.ts +9 -0
- package/src/core/artifact-evidence.ts +40 -5
- package/src/core/assertion.ts +8 -2
- package/src/core/case-defaults.ts +65 -0
- package/src/core/case.ts +79 -8
- package/src/core/command-verification.ts +74 -0
- package/src/core/driver-execution.ts +84 -0
- package/src/core/driver-metrics.ts +51 -0
- package/src/core/driver.ts +76 -0
- package/src/core/duration.ts +52 -0
- package/src/core/environment.ts +22 -0
- package/src/core/evaluation-clusters.ts +35 -0
- package/src/core/events.ts +13 -2
- package/src/core/experiment.ts +71 -25
- package/src/core/file-tree.ts +35 -0
- package/src/core/global-id.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/grok-build.ts +19 -0
- package/src/core/harness-catalogs/index.ts +31 -0
- package/src/core/harness-matrix.ts +316 -0
- package/src/core/index.ts +12 -0
- package/src/core/lifecycle-fixtures.ts +4 -2
- package/src/core/model-cli.ts +60 -26
- package/src/core/platform-process.ts +65 -16
- package/src/core/reference-defaults.ts +47 -0
- package/src/core/run.ts +31 -0
- package/src/core/usage.ts +8 -0
- package/src/drivers/antigravity/README.md +171 -0
- package/src/drivers/antigravity/artifacts.ts +37 -0
- package/src/drivers/antigravity/config.ts +92 -0
- package/src/drivers/antigravity/events.ts +290 -0
- package/src/drivers/antigravity/index.ts +132 -0
- package/src/drivers/antigravity/runtime.ts +94 -0
- package/src/drivers/claude-code/deferred-process.ts +118 -0
- package/src/drivers/claude-code/index.ts +227 -281
- package/src/drivers/claude-code/isolation.ts +228 -0
- package/src/drivers/claude-code/parse-events.ts +98 -12
- package/src/drivers/codex/app-events.ts +126 -16
- package/src/drivers/codex/config.ts +59 -8
- package/src/drivers/codex/exec-events.ts +258 -0
- package/src/drivers/codex/index.ts +146 -312
- package/src/drivers/copilot/README.md +60 -0
- package/src/drivers/copilot/config.ts +61 -0
- package/src/drivers/copilot/index.ts +310 -0
- package/src/drivers/cursor/README.md +66 -0
- package/src/drivers/cursor/config.ts +31 -0
- package/src/drivers/cursor/index.ts +326 -0
- package/src/drivers/cursor/interaction.ts +74 -0
- package/src/drivers/grok-build/README.md +112 -0
- package/src/drivers/grok-build/config.ts +43 -0
- package/src/drivers/grok-build/index.ts +331 -0
- package/src/drivers/mcp/index.ts +25 -11
- package/src/drivers/shared/acp-events.ts +94 -0
- package/src/drivers/shared/acp-interaction.ts +27 -0
- package/src/drivers/shared/acp-session.ts +40 -0
- package/src/drivers/shared/acp-transport.ts +187 -0
- package/src/drivers/shared/artifacts.ts +6 -2
- package/src/drivers/shared/experiment-behavior.ts +217 -0
- package/src/drivers/shared/experiment-policy.ts +196 -0
- package/src/drivers/shared/skills.ts +74 -0
- package/src/drivers/subprocess/index.ts +55 -12
- package/src/index.ts +117 -6
- package/src/optimization/reflective.ts +3 -2
- package/src/orchestrator/annotation-loader.ts +10 -0
- package/src/orchestrator/annotation-store.ts +24 -3
- package/src/orchestrator/artifact-origin.ts +50 -0
- package/src/orchestrator/campaign-lifecycle.ts +5 -1
- package/src/orchestrator/candidates.ts +3 -2
- package/src/orchestrator/compare.ts +62 -13
- package/src/orchestrator/comparison-identity.ts +2 -0
- package/src/orchestrator/config.ts +23 -1
- package/src/orchestrator/discover.ts +8 -1
- package/src/orchestrator/doctor.ts +211 -65
- package/src/orchestrator/driver-capabilities.ts +21 -0
- package/src/orchestrator/driver-identity.ts +69 -0
- package/src/orchestrator/experiment-context.ts +5 -3
- package/src/orchestrator/experiment-corpus.ts +58 -37
- package/src/orchestrator/experiment-effects.ts +63 -0
- package/src/orchestrator/experiment-evaluation.ts +88 -10
- package/src/orchestrator/experiment-identity.ts +57 -48
- package/src/orchestrator/experiment-preflight.ts +27 -0
- package/src/orchestrator/experiment-report.ts +21 -1
- package/src/orchestrator/experiment-runs.ts +11 -4
- package/src/orchestrator/experiment-runtime-identity.ts +5 -243
- package/src/orchestrator/experiment-selection.ts +178 -18
- package/src/orchestrator/experiment.ts +29 -29
- package/src/orchestrator/fixture-identity.ts +9 -1
- package/src/orchestrator/fs-read.ts +2 -32
- package/src/orchestrator/grader-experiment.ts +74 -13
- package/src/orchestrator/grading.ts +60 -31
- package/src/orchestrator/harness-catalog-drift.ts +132 -0
- package/src/orchestrator/harness-registry.ts +12 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/list-runs.ts +102 -18
- package/src/orchestrator/optimization-lifecycle.ts +4 -1
- package/src/orchestrator/orchestrator.ts +105 -224
- package/src/orchestrator/paired-effects.ts +214 -0
- package/src/orchestrator/profile.ts +24 -0
- package/src/orchestrator/provenance.ts +23 -3
- package/src/orchestrator/rubric-loader.ts +1 -1
- package/src/orchestrator/run-dir.ts +8 -1
- package/src/orchestrator/run-execution.ts +75 -10
- package/src/orchestrator/run-lifecycle.ts +4 -2
- package/src/orchestrator/run-workspace.ts +62 -0
- package/src/orchestrator/suite-compare.ts +47 -23
- package/src/orchestrator/suite-runs.ts +13 -2
- package/src/orchestrator/suite-summary.ts +129 -15
- package/src/previewer/artifacts.ts +221 -0
- package/src/previewer/server.ts +789 -0
- package/viewer/app.css +2111 -0
- package/viewer/app.js +627 -0
- package/viewer/artifact-view.js +48 -0
- package/viewer/charts.js +98 -0
- package/viewer/detail.js +1266 -0
- package/viewer/format.js +129 -0
- package/viewer/index.html +151 -0
- package/lib/cli/skills.d.ts +0 -38
- package/lib/cli/skills.d.ts.map +0 -1
- package/lib/cli/skills.js +0 -84
- package/lib/cli/skills.js.map +0 -1
- package/lib/drivers/shared/secure-copy.d.ts.map +0 -1
- package/lib/drivers/shared/secure-copy.js.map +0 -1
- package/skills/lux/SKILL.md +0 -89
- package/skills/lux/references/cli.md +0 -125
- package/skills/lux/references/eval-authoring.md +0 -257
- package/src/cli/skills.ts +0 -129
- /package/lib/{drivers/shared → core}/secure-copy.d.ts +0 -0
- /package/lib/{drivers/shared → core}/secure-copy.js +0 -0
- /package/src/{drivers/shared → core}/secure-copy.ts +0 -0
package/src/cli/program.ts
CHANGED
|
@@ -2,47 +2,41 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { lstat, mkdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
-
import { basename, dirname,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { Cli, Errors, z } from "incur";
|
|
8
8
|
import {
|
|
9
9
|
type Candidate,
|
|
10
10
|
GraderAnnotationSchema,
|
|
11
|
+
type HarnessFocus,
|
|
11
12
|
matchesAnyGlob,
|
|
13
|
+
ReasoningEffortSchema,
|
|
12
14
|
valueHash,
|
|
13
15
|
} from "../core/index.js";
|
|
16
|
+
import { registryWithHarness } from "../orchestrator/harness-registry.js";
|
|
14
17
|
import {
|
|
15
18
|
applyCandidate,
|
|
16
19
|
buildRegistryFromConfig,
|
|
17
20
|
compareRuns,
|
|
18
21
|
compareSuiteRuns,
|
|
19
|
-
|
|
22
|
+
DoctorReportSchema,
|
|
20
23
|
discoverCases,
|
|
21
|
-
|
|
24
|
+
ExperimentCampaignPlanSchema,
|
|
25
|
+
ExperimentDecisionReportSchema,
|
|
26
|
+
ExperimentRunSchema,
|
|
22
27
|
experimentCampaignMode,
|
|
23
28
|
formatComparisonText,
|
|
24
|
-
formatDoctorReport,
|
|
25
29
|
formatExperimentDecisionMarkdown,
|
|
26
|
-
formatExperimentRun,
|
|
27
|
-
formatExperimentRunsTable,
|
|
28
|
-
formatRunDetail,
|
|
29
|
-
formatRunsTable,
|
|
30
30
|
formatSuiteComparisonText,
|
|
31
|
-
|
|
32
|
-
formatSuiteRunsTable,
|
|
31
|
+
GradingSnapshotSchema,
|
|
33
32
|
hasRegression,
|
|
34
33
|
hasSuiteRegression,
|
|
35
|
-
isExperimentRun,
|
|
36
|
-
listExperimentRuns,
|
|
37
|
-
listRuns,
|
|
38
|
-
listSuiteRuns,
|
|
39
34
|
loadEvalCase,
|
|
40
35
|
loadExperimentCampaign,
|
|
41
36
|
loadExperimentRun,
|
|
42
37
|
loadFrozenRegradableRun,
|
|
43
38
|
loadLuxConfig,
|
|
44
39
|
loadRubricDefinition,
|
|
45
|
-
loadRunDetail,
|
|
46
40
|
loadSuiteRun,
|
|
47
41
|
loadVerifiedExperimentDecisionSnapshot,
|
|
48
42
|
Orchestrator,
|
|
@@ -53,591 +47,608 @@ import {
|
|
|
53
47
|
resolveExperimentCampaignPath,
|
|
54
48
|
runExperimentCampaign,
|
|
55
49
|
type SuiteCaseResult,
|
|
56
|
-
type SuiteItem,
|
|
57
|
-
selectCases,
|
|
58
50
|
upsertGraderAnnotation,
|
|
59
51
|
verifyExperimentDecisionIntegrity,
|
|
60
52
|
verifyExperimentRunIntegrity,
|
|
61
53
|
} from "../orchestrator/index.js";
|
|
54
|
+
import { resolveProfile } from "../orchestrator/profile.js";
|
|
62
55
|
import {
|
|
63
56
|
abortOnInterrupt,
|
|
64
57
|
ensureDirectory,
|
|
65
58
|
failWith,
|
|
66
59
|
relativePathWithin,
|
|
67
60
|
} from "./command-runtime.js";
|
|
68
|
-
import {
|
|
61
|
+
import { doctorCommand } from "./doctor.js";
|
|
62
|
+
import { initProject } from "./init.js";
|
|
63
|
+
import { CompareOutputSchema, ViewOutputSchema } from "./output-schemas.js";
|
|
69
64
|
import { ConsoleReporter } from "./render/reporter.js";
|
|
70
65
|
import {
|
|
71
|
-
|
|
72
|
-
collectValues,
|
|
66
|
+
expandLoop,
|
|
73
67
|
parseAnnotationAssertion,
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
partitionFilters,
|
|
69
|
+
planSuiteItems,
|
|
76
70
|
type RunOptions,
|
|
77
71
|
resolveAnswererOverride,
|
|
78
72
|
resolveConcurrency,
|
|
73
|
+
selectSuite,
|
|
74
|
+
suiteTitle,
|
|
79
75
|
} from "./run-options.js";
|
|
80
|
-
import {
|
|
81
|
-
type BundledSkillName,
|
|
82
|
-
formatSkillsInstall,
|
|
83
|
-
formatSkillsList,
|
|
84
|
-
installSkills,
|
|
85
|
-
type SkillsPlatform,
|
|
86
|
-
} from "./skills.js";
|
|
76
|
+
import { type ViewOptions, viewCommand, webViewCommand } from "./view.js";
|
|
87
77
|
|
|
88
78
|
const requireFromHere = createRequire(import.meta.url);
|
|
89
79
|
// From lib/cli/program.js (or src/cli/program.ts under test) to the package root.
|
|
90
80
|
const PKG_VERSION = (requireFromHere("../../package.json") as { version: string }).version;
|
|
91
81
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
82
|
+
const nonEmptyString = z.string().min(1);
|
|
83
|
+
const positiveInteger = z.coerce.number().int().positive();
|
|
84
|
+
const unitInterval = z.coerce.number().min(0).max(1);
|
|
85
|
+
const tagList = z
|
|
86
|
+
.array(nonEmptyString)
|
|
87
|
+
.default([])
|
|
88
|
+
.describe("Repeatable list; each value may be comma-separated");
|
|
89
|
+
const normalizeTags = (values: string[]): string[] =>
|
|
90
|
+
values.flatMap((value) => value.split(",").map((tag) => tag.trim())).filter(Boolean);
|
|
91
|
+
const initOutput = z.object({
|
|
92
|
+
created: z.array(z.string()),
|
|
93
|
+
updated: z.array(z.string()),
|
|
94
|
+
skipped: z.array(z.object({ path: z.string(), reason: z.string() })),
|
|
95
|
+
experiment: z.boolean(),
|
|
96
|
+
});
|
|
97
|
+
const runOutput = z.union([
|
|
98
|
+
z.object({
|
|
99
|
+
caseCount: z.number().int(),
|
|
100
|
+
cases: z.array(
|
|
101
|
+
z.object({
|
|
102
|
+
path: z.string(),
|
|
103
|
+
id: z.string(),
|
|
104
|
+
caseId: z.string().optional(),
|
|
105
|
+
permutationId: z.string().optional(),
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
}),
|
|
109
|
+
z.object({
|
|
110
|
+
caseCount: z.number().int(),
|
|
111
|
+
tags: z.array(z.object({ tag: z.string(), count: z.number().int() })),
|
|
112
|
+
}),
|
|
113
|
+
z.object({
|
|
114
|
+
suiteDir: z.string(),
|
|
115
|
+
results: z.array(
|
|
116
|
+
z.object({
|
|
117
|
+
caseId: z.string(),
|
|
118
|
+
runDir: z.string().nullable(),
|
|
119
|
+
exitReason: z.string(),
|
|
120
|
+
casePassed: z.boolean().nullable(),
|
|
121
|
+
caseScore: z.number().nullable(),
|
|
122
|
+
assertionsPassed: z.number().int(),
|
|
123
|
+
assertionsTotal: z.number().int(),
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
}),
|
|
127
|
+
]);
|
|
128
|
+
const experimentOutput = z.union([
|
|
129
|
+
ExperimentCampaignPlanSchema,
|
|
130
|
+
ExperimentRunSchema.extend({ experimentDir: z.string() }),
|
|
131
|
+
]);
|
|
132
|
+
const gradingOutput = GradingSnapshotSchema.extend({ gradingPath: z.string() });
|
|
133
|
+
const reportOutput = z.union([
|
|
134
|
+
ExperimentDecisionReportSchema,
|
|
135
|
+
z.object({ path: z.string(), report: ExperimentDecisionReportSchema }),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const campaignArgs = z.object({ campaign: nonEmptyString.describe("Experiment campaign path") });
|
|
139
|
+
const campaignOptions = z.object({
|
|
140
|
+
runsRoot: nonEmptyString.optional().describe("Run directory root overriding lux.config.ts"),
|
|
141
|
+
resume: nonEmptyString.optional().describe("Interrupted experiment directory to resume"),
|
|
142
|
+
plan: z.boolean().optional().describe("Validate and estimate without calling models"),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const buildCli = () =>
|
|
146
|
+
Cli.create("lux", {
|
|
147
|
+
description:
|
|
148
|
+
"Coding-agent evaluation and improvement with deterministic assertions and human-in-the-loop runs",
|
|
149
|
+
version: PKG_VERSION,
|
|
150
|
+
sync: {
|
|
151
|
+
include: [fileURLToPath(new URL("../../skills/*", import.meta.url))],
|
|
152
|
+
depth: 2,
|
|
153
|
+
suggestions: [
|
|
154
|
+
"Use Lux to inspect the eval cases in this project",
|
|
155
|
+
"Use Lux to diagnose the latest failed eval run",
|
|
156
|
+
"Use Lux to plan an evidence-based improvement experiment",
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
mcp: {
|
|
160
|
+
title: "Lux coding-agent evaluation",
|
|
161
|
+
instructions:
|
|
162
|
+
"Inspect and plan before running model-spending commands. Apply only a verified promoted experiment champion.",
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
.command("init", {
|
|
166
|
+
description: "Scaffold a new eval project in the current directory",
|
|
167
|
+
options: z.object({
|
|
168
|
+
force: z.boolean().optional().describe("Overwrite existing files"),
|
|
169
|
+
experiment: z.boolean().optional().describe("Also scaffold an optimization campaign"),
|
|
170
|
+
driver: z
|
|
171
|
+
.enum([
|
|
172
|
+
"subprocess",
|
|
173
|
+
"claude-code",
|
|
174
|
+
"codex",
|
|
175
|
+
"cursor",
|
|
176
|
+
"copilot",
|
|
177
|
+
"antigravity",
|
|
178
|
+
"grok-build",
|
|
179
|
+
])
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Starter driver (default: deterministic subprocess)"),
|
|
182
|
+
model: nonEmptyString.optional().describe("Explicit subject model; required for Cursor"),
|
|
183
|
+
}),
|
|
184
|
+
output: initOutput,
|
|
185
|
+
destructive: true,
|
|
186
|
+
mcp: { annotations: { destructiveHint: true, openWorldHint: false } },
|
|
187
|
+
examples: [
|
|
188
|
+
{ description: "Scaffold a basic eval project" },
|
|
189
|
+
{ options: { experiment: true }, description: "Scaffold an optimization project" },
|
|
190
|
+
],
|
|
191
|
+
run: async (c) =>
|
|
192
|
+
c.ok(await initCommand(c.options), { cta: { commands: ["doctor", "run --list"] } }),
|
|
193
|
+
})
|
|
194
|
+
.command("doctor", {
|
|
195
|
+
description: "Preflight project configuration, CLIs, cases, campaigns, and budgets",
|
|
196
|
+
args: z.object({ campaigns: z.array(z.string()).default([]).describe("Campaign paths") }),
|
|
197
|
+
options: z.object({
|
|
198
|
+
static: z
|
|
199
|
+
.boolean()
|
|
200
|
+
.optional()
|
|
201
|
+
.describe("Validate project files without checking installed executables (for CI)"),
|
|
202
|
+
}),
|
|
203
|
+
output: DoctorReportSchema,
|
|
204
|
+
mcp: {
|
|
205
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
206
|
+
},
|
|
207
|
+
run: async (c) =>
|
|
208
|
+
c.ok(await doctorCommand(c.args.campaigns, c.options), {
|
|
209
|
+
cta: { commands: ["run --list"] },
|
|
210
|
+
}),
|
|
211
|
+
})
|
|
212
|
+
.command("run", {
|
|
213
|
+
description: "Discover and run cases, filtered by name, path, or tag",
|
|
214
|
+
args: z.object({ filters: z.array(z.string()).default([]).describe("Case names or paths") }),
|
|
215
|
+
options: z.object({
|
|
216
|
+
tag: tagList.describe("Required case tag; repeatable and comma-separated"),
|
|
217
|
+
loop: positiveInteger.optional().describe("Run each matched eval this many times"),
|
|
218
|
+
concurrency: positiveInteger.optional().describe("Maximum cases running concurrently"),
|
|
219
|
+
list: z.boolean().optional().describe("List matching cases without running"),
|
|
220
|
+
listTags: z.boolean().optional().describe("List tags across matching cases"),
|
|
221
|
+
interactive: z.boolean().optional().describe("Answer agent questions in this terminal"),
|
|
222
|
+
claudeAnswerer: z.boolean().optional().describe("Use Claude Code as the answerer"),
|
|
223
|
+
skipJudge: z.boolean().optional().describe("Skip rubric LLM judge assertions"),
|
|
224
|
+
runsRoot: nonEmptyString.optional().describe("Run directory root"),
|
|
225
|
+
profile: nonEmptyString.optional().describe("Named driver and answerer profile"),
|
|
226
|
+
driver: nonEmptyString
|
|
227
|
+
.optional()
|
|
228
|
+
.describe("Focus on one harness driver, replacing the matrix"),
|
|
229
|
+
model: nonEmptyString.optional().describe("Focus on one subject model"),
|
|
230
|
+
effort: ReasoningEffortSchema.optional().describe("Focus on one reasoning effort"),
|
|
231
|
+
subjectRoot: nonEmptyString.optional().describe("Root bound into subjectPath values"),
|
|
232
|
+
verbose: z.boolean().optional().describe("Include phase timings and run directories"),
|
|
233
|
+
}),
|
|
234
|
+
alias: { tag: "t", concurrency: "j", interactive: "i", verbose: "v" },
|
|
235
|
+
output: runOutput,
|
|
236
|
+
outputPolicy: "agent-only",
|
|
237
|
+
mcp: { annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } },
|
|
238
|
+
examples: [
|
|
239
|
+
{ options: { list: true, tag: [] }, description: "List all discovered cases" },
|
|
240
|
+
{
|
|
241
|
+
args: { filters: ["smoke"] },
|
|
242
|
+
options: { tag: [] },
|
|
243
|
+
description: "Run matching cases",
|
|
131
244
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
245
|
+
{
|
|
246
|
+
args: { filters: ["smoke"] },
|
|
247
|
+
options: { tag: [], driver: "codex", model: "gpt-5.6-luna", effort: "low" },
|
|
248
|
+
description: "Focus on one harness permutation",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
run: async (c) => {
|
|
252
|
+
const result = await runCommand(c.args.filters, c.options as RunOptions, !c.agent);
|
|
253
|
+
const suiteDir = "suiteDir" in result ? result.suiteDir : undefined;
|
|
254
|
+
return c.ok(result, {
|
|
255
|
+
cta: {
|
|
256
|
+
description: "Inspect saved results:",
|
|
257
|
+
commands: suiteDir ? [{ command: "view", args: { runDir: suiteDir } }] : [],
|
|
258
|
+
},
|
|
146
259
|
});
|
|
147
|
-
process.stdout.write(`${formatSkillsInstall(installed, cwd)}\n`);
|
|
148
260
|
},
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
261
|
+
})
|
|
262
|
+
.command("experiment", {
|
|
263
|
+
description: "Evaluate controlled source variants across cases and model profiles",
|
|
264
|
+
args: campaignArgs,
|
|
265
|
+
options: campaignOptions,
|
|
266
|
+
output: experimentOutput,
|
|
267
|
+
mcp: { annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } },
|
|
268
|
+
run: async (c) => experimentCommand("experiment", c.args.campaign, c.options),
|
|
269
|
+
})
|
|
270
|
+
.command("optimize", {
|
|
271
|
+
description: "Evolve candidates from grading feedback and promote on held-out cases",
|
|
272
|
+
args: campaignArgs,
|
|
273
|
+
options: campaignOptions,
|
|
274
|
+
output: experimentOutput,
|
|
275
|
+
destructive: true,
|
|
276
|
+
mcp: { annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true } },
|
|
277
|
+
run: async (c) => experimentCommand("optimize", c.args.campaign, c.options),
|
|
278
|
+
})
|
|
279
|
+
.command("grade", {
|
|
280
|
+
description: "Re-run current graders against a persisted run",
|
|
281
|
+
args: z.object({ runDir: nonEmptyString.describe("Persisted run directory") }),
|
|
282
|
+
options: z.object({ case: nonEmptyString.optional().describe("Current case definition") }),
|
|
283
|
+
output: gradingOutput,
|
|
284
|
+
mcp: { annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } },
|
|
285
|
+
run: async (c) => gradeCommand(c.args.runDir, c.options),
|
|
286
|
+
})
|
|
287
|
+
.command("annotate", {
|
|
288
|
+
description: "Add or update a human grader-alignment label",
|
|
289
|
+
args: z.object({ runDir: nonEmptyString.describe("Persisted run directory") }),
|
|
290
|
+
options: z.object({
|
|
291
|
+
out: nonEmptyString.describe("JSON annotation dataset to create or update"),
|
|
292
|
+
rubric: nonEmptyString.describe("Candidate-relative declarative rubric path"),
|
|
293
|
+
id: nonEmptyString.optional().describe("Stable annotation ID"),
|
|
294
|
+
label: z.enum(["pass", "fail"]).optional().describe("Expected overall verdict"),
|
|
295
|
+
score: unitInterval.optional().describe("Expected score from zero to one"),
|
|
296
|
+
assertion: z
|
|
297
|
+
.array(nonEmptyString)
|
|
298
|
+
.default([])
|
|
299
|
+
.describe("Assertion label id=pass|fail[:score]"),
|
|
300
|
+
split: z.enum(["train", "validation", "test"]).optional().describe("Dataset split"),
|
|
301
|
+
tag: tagList.describe("Selection tag; repeatable and comma-separated"),
|
|
302
|
+
feedback: nonEmptyString.optional().describe("Human rationale for mismatches"),
|
|
303
|
+
annotator: nonEmptyString.optional().describe("Label author"),
|
|
304
|
+
reviewer: nonEmptyString.optional().describe("Independent reviewer"),
|
|
305
|
+
source: z.enum(["human", "synthetic"]).default("human").describe("Label provenance"),
|
|
306
|
+
}),
|
|
307
|
+
output: z.object({ id: z.string(), path: z.string(), labels: z.number().int() }),
|
|
308
|
+
destructive: true,
|
|
309
|
+
mcp: {
|
|
310
|
+
annotations: {
|
|
311
|
+
readOnlyHint: false,
|
|
312
|
+
destructiveHint: false,
|
|
313
|
+
idempotentHint: true,
|
|
314
|
+
openWorldHint: false,
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
run: async (c) => annotateCommand(c.args.runDir, c.options),
|
|
318
|
+
})
|
|
319
|
+
.command("apply", {
|
|
320
|
+
description: "Apply the verified promoted experiment champion",
|
|
321
|
+
args: z.object({
|
|
322
|
+
experimentDir: nonEmptyString.describe("Experiment directory"),
|
|
323
|
+
candidateId: nonEmptyString.optional().describe("Expected promoted candidate ID"),
|
|
324
|
+
}),
|
|
325
|
+
output: z.object({
|
|
326
|
+
candidateId: z.string(),
|
|
327
|
+
subjectRoot: z.string(),
|
|
328
|
+
sourceBytes: z.number(),
|
|
329
|
+
}),
|
|
330
|
+
destructive: true,
|
|
331
|
+
mcp: {
|
|
332
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
333
|
+
},
|
|
334
|
+
run: async (c) => applyCommand(c.args.experimentDir, c.args.candidateId),
|
|
335
|
+
})
|
|
336
|
+
.command("report", {
|
|
337
|
+
description: "Render a PR-ready report from a verified experiment record",
|
|
338
|
+
args: z.object({ experimentDir: nonEmptyString.describe("Experiment directory") }),
|
|
339
|
+
options: z.object({
|
|
340
|
+
out: nonEmptyString.optional().describe("Write Markdown to this path"),
|
|
341
|
+
force: z.boolean().optional().describe("Overwrite an existing output file"),
|
|
342
|
+
}),
|
|
343
|
+
output: reportOutput,
|
|
344
|
+
mcp: { annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false } },
|
|
345
|
+
run: async (c) => reportCommand(c.args.experimentDir, c.options),
|
|
346
|
+
})
|
|
347
|
+
.command("view", {
|
|
348
|
+
description: "List past runs or show one run in detail",
|
|
349
|
+
args: z.object({ runDir: nonEmptyString.optional().describe("Run or suite directory") }),
|
|
350
|
+
options: z.object({
|
|
351
|
+
runsRoot: nonEmptyString.optional().describe("Run directory root"),
|
|
352
|
+
case: nonEmptyString.optional().describe("Filter lists to one case slug"),
|
|
353
|
+
suites: z.boolean().optional().describe("List logical suite runs"),
|
|
354
|
+
experiments: z.boolean().optional().describe("List experiment runs"),
|
|
355
|
+
web: z.boolean().optional().describe("Open the Lux Review web app"),
|
|
356
|
+
port: z.coerce.number().int().min(0).max(65535).optional().describe("Review server port"),
|
|
357
|
+
open: z.boolean().default(true).describe("Open a browser in web mode"),
|
|
358
|
+
}),
|
|
359
|
+
output: ViewOutputSchema,
|
|
360
|
+
mcp: { annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false } },
|
|
361
|
+
run: (c) => {
|
|
362
|
+
if (c.options.web) {
|
|
363
|
+
if (c.formatExplicit && c.format !== "jsonl") {
|
|
364
|
+
throw new Errors.ParseError({
|
|
365
|
+
message: "--web requires streaming output; omit --format or use --format jsonl",
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return webViewCommand(c.args.runDir, c.options as ViewOptions);
|
|
369
|
+
}
|
|
370
|
+
return viewCommand(c.args.runDir, c.options as ViewOptions).then((result) =>
|
|
371
|
+
ViewOutputSchema.parse(result),
|
|
372
|
+
);
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
.command("compare", {
|
|
376
|
+
description: "Diff two run or suite directories and fail on regression",
|
|
377
|
+
args: z.object({
|
|
378
|
+
runA: nonEmptyString.describe("Baseline run"),
|
|
379
|
+
runB: nonEmptyString.describe("Current run"),
|
|
380
|
+
}),
|
|
381
|
+
options: z.object({
|
|
382
|
+
passRateTolerance: unitInterval.optional().describe("Allowed per-case pass-rate decrease"),
|
|
383
|
+
}),
|
|
384
|
+
output: CompareOutputSchema,
|
|
385
|
+
mcp: { annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false } },
|
|
386
|
+
run: async (c) =>
|
|
387
|
+
CompareOutputSchema.parse(await compareCommand(c.args.runA, c.args.runB, c.options)),
|
|
163
388
|
});
|
|
164
389
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
.option("--skip-judge", "skip rubric LLM judge assertions")
|
|
188
|
-
.option("--runs-root <path>", "run-dir root (overrides lux.config.ts)")
|
|
189
|
-
.option("--profile <id>", "named driver/answerer profile from lux.config.ts")
|
|
190
|
-
.option("--subject-root <path>", "root bound into subjectPath() values")
|
|
191
|
-
.option("-v, --verbose", "add phase timings and the run dir to every case")
|
|
192
|
-
.action(async (filters: string[], options: RunOptions) => {
|
|
193
|
-
await runCommand(filters, options);
|
|
390
|
+
const initCommand = async (options: Omit<Parameters<typeof initProject>[0], "cwd">) =>
|
|
391
|
+
initProject({ cwd: process.cwd(), ...options });
|
|
392
|
+
|
|
393
|
+
const gradeCommand = async (runDir: string, options: { case?: string | undefined }) => {
|
|
394
|
+
const directory = resolve(runDir);
|
|
395
|
+
if (!(await ensureDirectory("grade", directory))) failWith(1);
|
|
396
|
+
const config = await loadLuxConfig(process.cwd());
|
|
397
|
+
using controller = abortOnInterrupt();
|
|
398
|
+
const result = await regradeRun({
|
|
399
|
+
runDir: directory,
|
|
400
|
+
registry: buildRegistryFromConfig(config),
|
|
401
|
+
graderContext: config.harness ?? null,
|
|
402
|
+
...(options.case ? { case: await loadEvalCase(resolve(options.case)) } : {}),
|
|
403
|
+
abortSignal: controller.signal,
|
|
404
|
+
});
|
|
405
|
+
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
406
|
+
if (result.snapshot.report.casePassed !== true) {
|
|
407
|
+
throw new Errors.IncurError({
|
|
408
|
+
code: "GRADING_FAILED",
|
|
409
|
+
message: `The persisted run did not pass regrading. Grading saved to ${result.path}.`,
|
|
410
|
+
exitCode: 1,
|
|
194
411
|
});
|
|
412
|
+
}
|
|
413
|
+
return { gradingPath: result.path, ...result.snapshot };
|
|
414
|
+
};
|
|
195
415
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
);
|
|
416
|
+
type AnnotationOptions = {
|
|
417
|
+
out: string;
|
|
418
|
+
rubric: string;
|
|
419
|
+
id?: string | undefined;
|
|
420
|
+
label?: "pass" | "fail" | undefined;
|
|
421
|
+
score?: number | undefined;
|
|
422
|
+
assertion: string[];
|
|
423
|
+
split?: "train" | "validation" | "test" | undefined;
|
|
424
|
+
tag: string[];
|
|
425
|
+
feedback?: string | undefined;
|
|
426
|
+
annotator?: string | undefined;
|
|
427
|
+
reviewer?: string | undefined;
|
|
428
|
+
source: "human" | "synthetic";
|
|
211
429
|
};
|
|
212
430
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
abortSignal: controller.signal,
|
|
230
|
-
});
|
|
231
|
-
const report = result.snapshot.report;
|
|
232
|
-
let gradingStatus = "ungraded";
|
|
233
|
-
if (report.casePassed === true) gradingStatus = "PASS";
|
|
234
|
-
if (report.casePassed === false) gradingStatus = "FAIL";
|
|
235
|
-
let output: string;
|
|
236
|
-
if (options.json) {
|
|
237
|
-
output = `${JSON.stringify({ gradingPath: result.path, ...result.snapshot }, null, 2)}\n`;
|
|
238
|
-
} else {
|
|
239
|
-
const score = report.caseScore === null ? "—" : `${(report.caseScore * 100).toFixed(0)}%`;
|
|
240
|
-
output = [
|
|
241
|
-
`grading: ${gradingStatus}`,
|
|
242
|
-
`score: ${score}`,
|
|
243
|
-
`checks: ${report.assertions.filter((assertion) => assertion.passed).length}/${report.assertions.length}`,
|
|
244
|
-
`saved: ${result.path}`,
|
|
245
|
-
"",
|
|
246
|
-
].join("\n");
|
|
247
|
-
}
|
|
248
|
-
process.stdout.write(output);
|
|
249
|
-
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
250
|
-
else if (report.casePassed !== true) failWith(1);
|
|
431
|
+
const annotateCommand = async (runDir: string, options: AnnotationOptions) => {
|
|
432
|
+
const directory = resolve(runDir);
|
|
433
|
+
if (!(await ensureDirectory("annotate", directory))) failWith(1);
|
|
434
|
+
const parsedAssertions = options.assertion.map(parseAnnotationAssertion);
|
|
435
|
+
const assertionIds = parsedAssertions.map(([id]) => id);
|
|
436
|
+
if (new Set(assertionIds).size !== assertionIds.length) {
|
|
437
|
+
throw new Errors.ParseError({ message: "--assertion IDs must not be repeated" });
|
|
438
|
+
}
|
|
439
|
+
const assertions = Object.fromEntries(parsedAssertions);
|
|
440
|
+
if (
|
|
441
|
+
options.label === undefined &&
|
|
442
|
+
options.score === undefined &&
|
|
443
|
+
Object.keys(assertions).length === 0
|
|
444
|
+
) {
|
|
445
|
+
throw new Errors.ParseError({
|
|
446
|
+
message: "Provide --label, --score, or at least one --assertion",
|
|
251
447
|
});
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
const directory = resolve(runDir);
|
|
292
|
-
if (!(await ensureDirectory("annotate", directory))) return;
|
|
293
|
-
if (options.label && options.label !== "pass" && options.label !== "fail") {
|
|
294
|
-
throw new InvalidArgumentError("--label must be pass or fail");
|
|
295
|
-
}
|
|
296
|
-
if (options.split && !["train", "validation", "test"].includes(options.split)) {
|
|
297
|
-
throw new InvalidArgumentError("--split must be train, validation, or test");
|
|
298
|
-
}
|
|
299
|
-
if (options.source !== "human" && options.source !== "synthetic") {
|
|
300
|
-
throw new InvalidArgumentError("--source must be human or synthetic");
|
|
301
|
-
}
|
|
302
|
-
const parsedAssertions = options.assertion.map(parseAnnotationAssertion);
|
|
303
|
-
const assertionIds = parsedAssertions.map(([id]) => id);
|
|
304
|
-
if (new Set(assertionIds).size !== assertionIds.length) {
|
|
305
|
-
throw new InvalidArgumentError("--assertion ids must not be repeated");
|
|
306
|
-
}
|
|
307
|
-
const assertions = Object.fromEntries(parsedAssertions);
|
|
308
|
-
if (
|
|
309
|
-
options.label === undefined &&
|
|
310
|
-
options.score === undefined &&
|
|
311
|
-
Object.keys(assertions).length === 0
|
|
312
|
-
) {
|
|
313
|
-
throw new Error("lux annotate: provide --label, --score, or at least one --assertion");
|
|
314
|
-
}
|
|
315
|
-
const out = resolve(options.out);
|
|
316
|
-
const run = await loadFrozenRegradableRun(directory);
|
|
317
|
-
try {
|
|
318
|
-
const rubric = await loadRubricDefinition(
|
|
319
|
-
await resolveAuthoredFile(options.rubric, "rubric"),
|
|
320
|
-
);
|
|
321
|
-
if (rubric.id !== run.case.id) {
|
|
322
|
-
throw new Error(
|
|
323
|
-
`lux annotate: rubric id '${rubric.id}' does not match persisted case '${run.case.id}'`,
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
const rubricIds = new Set(rubric.assertions.map((assertion) => assertion.id));
|
|
327
|
-
const unknownIds = assertionIds.filter((assertionId) => !rubricIds.has(assertionId));
|
|
328
|
-
if (unknownIds.length > 0) {
|
|
329
|
-
throw new Error(`lux annotate: unknown rubric assertion ids: ${unknownIds.join(", ")}`);
|
|
330
|
-
}
|
|
331
|
-
const tags = [...(options.split ? [options.split] : []), ...options.tag].filter(
|
|
332
|
-
(tag, index, all) => all.indexOf(tag) === index,
|
|
333
|
-
);
|
|
334
|
-
const caseId = run.case.id;
|
|
335
|
-
const portableRunDir = (relative(dirname(out), directory) || ".").split(sep).join("/");
|
|
336
|
-
const id =
|
|
337
|
-
options.id ??
|
|
338
|
-
`${caseId}-${basename(directory)}-${valueHash(portableRunDir).slice(0, 10)}`.replace(
|
|
339
|
-
/[^a-zA-Z0-9_-]+/g,
|
|
340
|
-
"-",
|
|
341
|
-
);
|
|
342
|
-
const annotation = GraderAnnotationSchema.parse({
|
|
343
|
-
id,
|
|
344
|
-
runDir: portableRunDir,
|
|
345
|
-
rubricPath: options.rubric,
|
|
346
|
-
tags,
|
|
347
|
-
source: options.source,
|
|
348
|
-
...(options.annotator ? { annotator: options.annotator } : {}),
|
|
349
|
-
labeledAt: new Date().toISOString(),
|
|
350
|
-
...(options.reviewer ? { reviewer: options.reviewer } : {}),
|
|
351
|
-
expected: {
|
|
352
|
-
...(options.label ? { casePassed: options.label === "pass" } : {}),
|
|
353
|
-
...(options.score !== undefined ? { caseScore: options.score } : {}),
|
|
354
|
-
assertions,
|
|
355
|
-
},
|
|
356
|
-
...(options.feedback ? { feedback: options.feedback } : {}),
|
|
357
|
-
});
|
|
358
|
-
const dataset = await upsertGraderAnnotation(out, annotation);
|
|
359
|
-
process.stdout.write(
|
|
360
|
-
`annotated ${annotation.id} in ${out} (${dataset.annotations.length} labels)\n`,
|
|
361
|
-
);
|
|
362
|
-
} finally {
|
|
363
|
-
await releaseFrozenRegradableRun(run);
|
|
364
|
-
}
|
|
448
|
+
}
|
|
449
|
+
const out = resolve(options.out);
|
|
450
|
+
const run = await loadFrozenRegradableRun(directory);
|
|
451
|
+
try {
|
|
452
|
+
const rubric = await loadRubricDefinition(await resolveAuthoredFile(options.rubric, "rubric"));
|
|
453
|
+
if (rubric.id !== run.case.id) {
|
|
454
|
+
throw new Error(`Rubric ID '${rubric.id}' does not match persisted case '${run.case.id}'.`);
|
|
455
|
+
}
|
|
456
|
+
const rubricIds = new Set(rubric.assertions.map((assertion) => assertion.id));
|
|
457
|
+
const unknownIds = assertionIds.filter((assertionId) => !rubricIds.has(assertionId));
|
|
458
|
+
if (unknownIds.length > 0) {
|
|
459
|
+
throw new Errors.ParseError({
|
|
460
|
+
message: `Unknown rubric assertion IDs: ${unknownIds.join(", ")}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
const annotationTags = [
|
|
464
|
+
...(options.split ? [options.split] : []),
|
|
465
|
+
...normalizeTags(options.tag),
|
|
466
|
+
].filter((tag, index, all) => all.indexOf(tag) === index);
|
|
467
|
+
const portableRunDir = (relative(dirname(out), directory) || ".").split(sep).join("/");
|
|
468
|
+
const id =
|
|
469
|
+
options.id ??
|
|
470
|
+
`${run.case.id}-${basename(directory)}-${valueHash(portableRunDir).slice(0, 10)}`.replace(
|
|
471
|
+
/[^a-zA-Z0-9_-]+/g,
|
|
472
|
+
"-",
|
|
473
|
+
);
|
|
474
|
+
const annotation = GraderAnnotationSchema.parse({
|
|
475
|
+
id,
|
|
476
|
+
runDir: portableRunDir,
|
|
477
|
+
rubricPath: options.rubric,
|
|
478
|
+
tags: annotationTags,
|
|
479
|
+
source: options.source,
|
|
480
|
+
...(options.annotator ? { annotator: options.annotator } : {}),
|
|
481
|
+
labeledAt: new Date().toISOString(),
|
|
482
|
+
...(options.reviewer ? { reviewer: options.reviewer } : {}),
|
|
483
|
+
expected: {
|
|
484
|
+
...(options.label ? { casePassed: options.label === "pass" } : {}),
|
|
485
|
+
...(options.score !== undefined ? { caseScore: options.score } : {}),
|
|
486
|
+
assertions,
|
|
365
487
|
},
|
|
366
|
-
|
|
488
|
+
...(options.feedback ? { feedback: options.feedback } : {}),
|
|
489
|
+
});
|
|
490
|
+
const dataset = await upsertGraderAnnotation(out, annotation);
|
|
491
|
+
return { id: annotation.id, path: out, labels: dataset.annotations.length };
|
|
492
|
+
} finally {
|
|
493
|
+
await releaseFrozenRegradableRun(run);
|
|
494
|
+
}
|
|
367
495
|
};
|
|
368
496
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
.action(
|
|
378
|
-
async (
|
|
379
|
-
campaign: string,
|
|
380
|
-
options: { runsRoot?: string; resume?: string; json?: boolean; plan?: boolean },
|
|
381
|
-
) => {
|
|
382
|
-
await experimentCommand("optimize", campaign, options);
|
|
383
|
-
},
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
program
|
|
387
|
-
.command("apply <experimentDir> [candidateId]")
|
|
388
|
-
.description("apply the promoted experiment champion after verifying the source base hash")
|
|
389
|
-
.action(async (experimentDir: string, candidateId?: string) => {
|
|
390
|
-
const directory = resolve(experimentDir);
|
|
391
|
-
const experiment = await loadExperimentRun(directory);
|
|
392
|
-
if (!experiment) {
|
|
393
|
-
process.stderr.write(`lux apply: experiment manifest not found at ${directory}\n`);
|
|
394
|
-
failWith(1);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (experiment.status !== "passed") {
|
|
398
|
-
process.stderr.write(
|
|
399
|
-
`lux apply: experiment status is '${experiment.status}', not 'passed'\n`,
|
|
400
|
-
);
|
|
401
|
-
failWith(1);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const championId = experiment.championCandidateId;
|
|
405
|
-
if (!championId) {
|
|
406
|
-
process.stderr.write("lux apply: passed experiment has no promoted champion\n");
|
|
407
|
-
failWith(1);
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
if (candidateId && candidateId !== championId) {
|
|
411
|
-
process.stderr.write(
|
|
412
|
-
`lux apply: candidate '${candidateId}' is not the promoted champion '${championId}'\n`,
|
|
413
|
-
);
|
|
414
|
-
failWith(1);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
let candidate: Candidate | undefined;
|
|
418
|
-
try {
|
|
419
|
-
const candidates = await verifyExperimentRunIntegrity(directory, experiment);
|
|
420
|
-
verifyExperimentDecisionIntegrity(experiment);
|
|
421
|
-
candidate = candidates.find((entry) => entry.id === championId);
|
|
422
|
-
if (!candidate) {
|
|
423
|
-
throw new Error(`champion candidate '${championId}' not found`);
|
|
424
|
-
}
|
|
425
|
-
} catch (error) {
|
|
426
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
427
|
-
process.stderr.write(`lux apply: experiment integrity check failed: ${message}\n`);
|
|
428
|
-
failWith(1);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
await applyCandidate(experiment.subjectRoot, experiment.campaignSnapshot.subject, candidate);
|
|
432
|
-
process.stdout.write(
|
|
433
|
-
`applied candidate ${candidate.id} to ${experiment.subjectRoot} (${candidate.sourceBytes} bytes)\n`,
|
|
434
|
-
);
|
|
497
|
+
const applyCommand = async (experimentDir: string, candidateId?: string) => {
|
|
498
|
+
const directory = resolve(experimentDir);
|
|
499
|
+
const experiment = await loadExperimentRun(directory);
|
|
500
|
+
if (!experiment) {
|
|
501
|
+
throw new Errors.IncurError({
|
|
502
|
+
code: "EXPERIMENT_NOT_FOUND",
|
|
503
|
+
message: `Experiment manifest not found at ${directory}.`,
|
|
504
|
+
exitCode: 1,
|
|
435
505
|
});
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
physicalOut,
|
|
471
|
-
physicalDirectory,
|
|
472
|
-
physicalSubjectRoot,
|
|
473
|
-
experiment.campaignSnapshot.subject.exclude,
|
|
474
|
-
);
|
|
475
|
-
if (outputStats && !options.force) {
|
|
476
|
-
throw new Error(`lux report: output already exists (use --force): ${out}`);
|
|
477
|
-
}
|
|
478
|
-
await mkdir(dirname(out), { recursive: true });
|
|
479
|
-
const physicalParent = await realpath(dirname(out));
|
|
480
|
-
const finalPhysicalOut = resolve(physicalParent, basename(out));
|
|
481
|
-
assertReportDestination(
|
|
482
|
-
finalPhysicalOut,
|
|
483
|
-
physicalDirectory,
|
|
484
|
-
physicalSubjectRoot,
|
|
485
|
-
experiment.campaignSnapshot.subject.exclude,
|
|
486
|
-
);
|
|
487
|
-
await writeReportSafely(finalPhysicalOut, rendered, options.force ?? false);
|
|
488
|
-
process.stdout.write(`wrote experiment report to ${out}\n`);
|
|
489
|
-
},
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
program
|
|
493
|
-
.command("view [runDir]")
|
|
494
|
-
.description("list past runs, or show one run's detail when given a runDir")
|
|
495
|
-
.option("--runs-root <path>", "run-dir root (defaults to lux.config.ts)")
|
|
496
|
-
.option("--case <id>", "filter the run list to one case slug (list mode only)")
|
|
497
|
-
.option("--suites", "list logical suite runs instead of individual runs")
|
|
498
|
-
.option("--experiments", "list experiment runs instead of individual runs")
|
|
499
|
-
.option("--json", "emit machine-readable JSON")
|
|
500
|
-
.action(
|
|
501
|
-
async (
|
|
502
|
-
runDirArg: string | undefined,
|
|
503
|
-
options: {
|
|
504
|
-
runsRoot?: string;
|
|
505
|
-
case?: string;
|
|
506
|
-
suites?: boolean;
|
|
507
|
-
experiments?: boolean;
|
|
508
|
-
json?: boolean;
|
|
509
|
-
},
|
|
510
|
-
) => {
|
|
511
|
-
if (runDirArg) {
|
|
512
|
-
const path = resolve(runDirArg);
|
|
513
|
-
if (!(await ensureDirectory("view", path))) return;
|
|
514
|
-
if (isExperimentRun(path)) {
|
|
515
|
-
const experiment = await loadExperimentRun(path);
|
|
516
|
-
if (!experiment) {
|
|
517
|
-
process.stderr.write(`lux view: invalid experiment at ${path}\n`);
|
|
518
|
-
failWith(1);
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
process.stdout.write(
|
|
522
|
-
options.json
|
|
523
|
-
? `${JSON.stringify({ experimentDir: path, ...experiment }, null, 2)}\n`
|
|
524
|
-
: `${formatExperimentRun(path, experiment)}\n`,
|
|
525
|
-
);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const suite = await loadSuiteRun(path);
|
|
529
|
-
if (suite) {
|
|
530
|
-
process.stdout.write(
|
|
531
|
-
options.json
|
|
532
|
-
? `${JSON.stringify({ suiteDir: path, ...suite }, null, 2)}\n`
|
|
533
|
-
: `${formatSuiteRunDetail(path, suite)}\n`,
|
|
534
|
-
);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
const detail = await loadRunDetail(path);
|
|
538
|
-
if (options.json) {
|
|
539
|
-
process.stdout.write(`${JSON.stringify(detail, null, 2)}\n`);
|
|
540
|
-
} else {
|
|
541
|
-
process.stdout.write(`${formatRunDetail(detail)}\n`);
|
|
542
|
-
}
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const cwd = process.cwd();
|
|
547
|
-
const config = await loadLuxConfig(cwd);
|
|
548
|
-
const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
|
|
549
|
-
if (options.experiments) {
|
|
550
|
-
const experiments = await listExperimentRuns(runsRoot);
|
|
551
|
-
process.stdout.write(
|
|
552
|
-
options.json
|
|
553
|
-
? `${JSON.stringify(experiments, null, 2)}\n`
|
|
554
|
-
: `experiments at ${runsRoot}:\n${formatExperimentRunsTable(experiments)}\n`,
|
|
555
|
-
);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
if (options.suites) {
|
|
559
|
-
const suites = await listSuiteRuns(runsRoot);
|
|
560
|
-
process.stdout.write(
|
|
561
|
-
options.json
|
|
562
|
-
? `${JSON.stringify(suites, null, 2)}\n`
|
|
563
|
-
: `suite runs at ${runsRoot}:\n${formatSuiteRunsTable(suites)}\n`,
|
|
564
|
-
);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const all = await listRuns(runsRoot);
|
|
568
|
-
const runs = options.case ? all.filter((r) => r.caseSlug === options.case) : all;
|
|
569
|
-
if (options.json) {
|
|
570
|
-
process.stdout.write(`${JSON.stringify(runs, null, 2)}\n`);
|
|
571
|
-
} else {
|
|
572
|
-
if (!options.case) {
|
|
573
|
-
process.stdout.write(`suite runs at ${runsRoot}:\n`);
|
|
574
|
-
process.stdout.write(`${formatSuiteRunsTable(await listSuiteRuns(runsRoot))}\n\n`);
|
|
575
|
-
}
|
|
576
|
-
process.stdout.write(`runs at ${runsRoot}:\n`);
|
|
577
|
-
process.stdout.write(`${formatRunsTable(runs)}\n`);
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
program
|
|
583
|
-
.command("compare <runA> <runB>")
|
|
584
|
-
.description("diff two run or suite directories")
|
|
585
|
-
.option(
|
|
586
|
-
"--pass-rate-tolerance <fraction>",
|
|
587
|
-
"allowed per-case pass-rate decrease for suite comparison",
|
|
588
|
-
parseUnitInterval,
|
|
589
|
-
)
|
|
590
|
-
.action(async (runA: string, runB: string, options: { passRateTolerance?: number }) => {
|
|
591
|
-
const pathA = resolve(runA);
|
|
592
|
-
const pathB = resolve(runB);
|
|
593
|
-
// Validate both up front so one typo'd path in CI cannot read as
|
|
594
|
-
// "0 regressed"; report every bad path, not just the first.
|
|
595
|
-
const okA = await ensureDirectory("compare", pathA);
|
|
596
|
-
const okB = await ensureDirectory("compare", pathB);
|
|
597
|
-
if (!okA || !okB) return;
|
|
598
|
-
const [suiteA, suiteB] = await Promise.all([loadSuiteRun(pathA), loadSuiteRun(pathB)]);
|
|
599
|
-
if (Boolean(suiteA) !== Boolean(suiteB)) {
|
|
600
|
-
process.stderr.write("lux compare: both paths must be runs or both must be suites\n");
|
|
601
|
-
failWith(1);
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (suiteA && suiteB) {
|
|
605
|
-
const comparison = await compareSuiteRuns(pathA, pathB, {
|
|
606
|
-
...(options.passRateTolerance !== undefined
|
|
607
|
-
? { passRateTolerance: options.passRateTolerance }
|
|
608
|
-
: {}),
|
|
609
|
-
});
|
|
610
|
-
process.stdout.write(`${formatSuiteComparisonText(comparison)}\n`);
|
|
611
|
-
if (hasSuiteRegression(comparison)) failWith(1);
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
const cmp = await compareRuns({ pathA, pathB });
|
|
615
|
-
process.stdout.write(`${formatComparisonText(cmp)}\n`);
|
|
616
|
-
if (hasRegression(cmp)) failWith(1);
|
|
506
|
+
}
|
|
507
|
+
if (experiment.status !== "passed") {
|
|
508
|
+
throw new Errors.IncurError({
|
|
509
|
+
code: "EXPERIMENT_NOT_PROMOTED",
|
|
510
|
+
message: `Experiment status is '${experiment.status}', not 'passed'.`,
|
|
511
|
+
exitCode: 1,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const championId = experiment.championCandidateId;
|
|
515
|
+
if (!championId) {
|
|
516
|
+
throw new Errors.IncurError({
|
|
517
|
+
code: "CHAMPION_NOT_FOUND",
|
|
518
|
+
message: "The passed experiment has no promoted champion.",
|
|
519
|
+
exitCode: 1,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (candidateId && candidateId !== championId) {
|
|
523
|
+
throw new Errors.IncurError({
|
|
524
|
+
code: "CANDIDATE_NOT_PROMOTED",
|
|
525
|
+
message: `Candidate '${candidateId}' is not the promoted champion '${championId}'.`,
|
|
526
|
+
exitCode: 1,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
let candidate: Candidate | undefined;
|
|
530
|
+
try {
|
|
531
|
+
const candidates = await verifyExperimentRunIntegrity(directory, experiment);
|
|
532
|
+
verifyExperimentDecisionIntegrity(experiment);
|
|
533
|
+
candidate = candidates.find((entry) => entry.id === championId);
|
|
534
|
+
if (!candidate) throw new Error(`Champion candidate '${championId}' not found.`);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
throw new Errors.IncurError({
|
|
537
|
+
code: "EXPERIMENT_INTEGRITY_FAILED",
|
|
538
|
+
message: error instanceof Error ? error.message : String(error),
|
|
539
|
+
exitCode: 1,
|
|
617
540
|
});
|
|
541
|
+
}
|
|
542
|
+
await applyCandidate(experiment.subjectRoot, experiment.campaignSnapshot.subject, candidate);
|
|
543
|
+
return {
|
|
544
|
+
candidateId: candidate.id,
|
|
545
|
+
subjectRoot: experiment.subjectRoot,
|
|
546
|
+
sourceBytes: candidate.sourceBytes,
|
|
547
|
+
};
|
|
618
548
|
};
|
|
619
549
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
550
|
+
const reportCommand = async (
|
|
551
|
+
experimentDir: string,
|
|
552
|
+
options: { out?: string | undefined; force?: boolean | undefined },
|
|
553
|
+
) => {
|
|
554
|
+
const directory = resolve(experimentDir);
|
|
555
|
+
if (!(await ensureDirectory("report", directory))) failWith(1);
|
|
556
|
+
const snapshot = await loadVerifiedExperimentDecisionSnapshot(directory);
|
|
557
|
+
const { experiment, report } = snapshot;
|
|
558
|
+
if (!options.out) return report;
|
|
559
|
+
const out = resolve(options.out);
|
|
560
|
+
const outputStats = await lstat(out).catch((error: NodeJS.ErrnoException) => {
|
|
561
|
+
if (error.code === "ENOENT") return null;
|
|
562
|
+
throw error;
|
|
563
|
+
});
|
|
564
|
+
if (outputStats && (outputStats.isSymbolicLink() || !outputStats.isFile())) {
|
|
565
|
+
throw new Error("An existing --out must be a regular, non-symlink file.");
|
|
566
|
+
}
|
|
567
|
+
const [physicalDirectory, physicalSubjectRoot, physicalOut] = await Promise.all([
|
|
568
|
+
realpath(directory),
|
|
569
|
+
realpath(experiment.subjectRoot),
|
|
570
|
+
physicalDestinationPath(out),
|
|
571
|
+
]);
|
|
572
|
+
assertReportDestination(
|
|
573
|
+
physicalOut,
|
|
574
|
+
physicalDirectory,
|
|
575
|
+
physicalSubjectRoot,
|
|
576
|
+
experiment.campaignSnapshot.subject.exclude,
|
|
577
|
+
);
|
|
578
|
+
if (outputStats && !options.force) {
|
|
579
|
+
throw new Error(`Output already exists (use --force): ${out}`);
|
|
580
|
+
}
|
|
581
|
+
await mkdir(dirname(out), { recursive: true });
|
|
582
|
+
const physicalParent = await realpath(dirname(out));
|
|
583
|
+
const finalPhysicalOut = resolve(physicalParent, basename(out));
|
|
584
|
+
assertReportDestination(
|
|
585
|
+
finalPhysicalOut,
|
|
586
|
+
physicalDirectory,
|
|
587
|
+
physicalSubjectRoot,
|
|
588
|
+
experiment.campaignSnapshot.subject.exclude,
|
|
589
|
+
);
|
|
590
|
+
await writeReportSafely(
|
|
591
|
+
finalPhysicalOut,
|
|
592
|
+
`${formatExperimentDecisionMarkdown(report)}\n`,
|
|
593
|
+
options.force ?? false,
|
|
594
|
+
);
|
|
595
|
+
return { path: out, report };
|
|
638
596
|
};
|
|
639
597
|
|
|
640
|
-
const
|
|
598
|
+
const compareCommand = async (
|
|
599
|
+
runA: string,
|
|
600
|
+
runB: string,
|
|
601
|
+
options: { passRateTolerance?: number | undefined },
|
|
602
|
+
) => {
|
|
603
|
+
const pathA = resolve(runA);
|
|
604
|
+
const pathB = resolve(runB);
|
|
605
|
+
const invalid = (
|
|
606
|
+
await Promise.all(
|
|
607
|
+
[pathA, pathB].map(async (path) =>
|
|
608
|
+
existsSync(path) && (await stat(path)).isDirectory() ? undefined : path,
|
|
609
|
+
),
|
|
610
|
+
)
|
|
611
|
+
).filter((path): path is string => path !== undefined);
|
|
612
|
+
if (invalid.length > 0) {
|
|
613
|
+
throw new Errors.IncurError({
|
|
614
|
+
code: "RUN_DIRECTORY_NOT_FOUND",
|
|
615
|
+
message: invalid.map((path) => `${path} is not a directory`).join("; "),
|
|
616
|
+
exitCode: 1,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
const [suiteA, suiteB] = await Promise.all([loadSuiteRun(pathA), loadSuiteRun(pathB)]);
|
|
620
|
+
if (Boolean(suiteA) !== Boolean(suiteB)) {
|
|
621
|
+
throw new Errors.IncurError({
|
|
622
|
+
code: "INCOMPATIBLE_RUN_TYPES",
|
|
623
|
+
message: "Both paths must be runs or both must be suites.",
|
|
624
|
+
exitCode: 1,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
if (suiteA && suiteB) {
|
|
628
|
+
const comparison = await compareSuiteRuns(pathA, pathB, {
|
|
629
|
+
...(options.passRateTolerance !== undefined
|
|
630
|
+
? { passRateTolerance: options.passRateTolerance }
|
|
631
|
+
: {}),
|
|
632
|
+
});
|
|
633
|
+
if (hasSuiteRegression(comparison)) {
|
|
634
|
+
throw new Errors.IncurError({
|
|
635
|
+
code: "REGRESSION_DETECTED",
|
|
636
|
+
message: formatSuiteComparisonText(comparison),
|
|
637
|
+
exitCode: 1,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return comparison;
|
|
641
|
+
}
|
|
642
|
+
const comparison = await compareRuns({ pathA, pathB });
|
|
643
|
+
if (hasRegression(comparison)) {
|
|
644
|
+
throw new Errors.IncurError({
|
|
645
|
+
code: "REGRESSION_DETECTED",
|
|
646
|
+
message: formatComparisonText(comparison),
|
|
647
|
+
exitCode: 1,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return comparison;
|
|
651
|
+
};
|
|
641
652
|
|
|
642
653
|
const SIGINT_EXIT_CODE = 130;
|
|
643
654
|
|
|
@@ -711,98 +722,22 @@ const isFailure = (outcome: { exitReason: string; casePassed: boolean | null }):
|
|
|
711
722
|
|
|
712
723
|
const firstLine = (message: string): string => (message.split("\n")[0] ?? message).trim();
|
|
713
724
|
|
|
714
|
-
const suiteTitle = (
|
|
715
|
-
patterns: string[],
|
|
716
|
-
tags: string[],
|
|
717
|
-
explicit: SuiteItem[],
|
|
718
|
-
loop: number,
|
|
719
|
-
): string => {
|
|
720
|
-
const parts = [...patterns, ...explicit.map((e) => e.id), ...tags.map((t) => `#${t}`)];
|
|
721
|
-
const filter = parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
722
|
-
const repeat = loop > 1 ? ` ×${loop}` : "";
|
|
723
|
-
return `run${filter}${repeat}`;
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
type FilterPartition = { explicit: SuiteItem[]; patterns: string[] };
|
|
727
|
-
|
|
728
|
-
// A filter that names an existing `.ts` file runs that file directly — it may
|
|
729
|
-
// sit outside the discovery root. Everything else is a substring pattern — but
|
|
730
|
-
// a `.ts` filter that resolves to nothing is almost always a typo'd path, so
|
|
731
|
-
// the silent downgrade to a pattern gets called out.
|
|
732
|
-
const partitionFilters = (filters: string[], cwd: string): FilterPartition => {
|
|
733
|
-
const explicit: SuiteItem[] = [];
|
|
734
|
-
const patterns: string[] = [];
|
|
735
|
-
for (const f of filters) {
|
|
736
|
-
const abs = resolve(cwd, f);
|
|
737
|
-
if (f.endsWith(".ts") && existsSync(abs)) {
|
|
738
|
-
explicit.push({ path: abs, id: caseIdOf(f) });
|
|
739
|
-
} else {
|
|
740
|
-
if (f.endsWith(".ts")) {
|
|
741
|
-
process.stderr.write(`lux: ${abs} does not exist; treating "${f}" as a pattern\n`);
|
|
742
|
-
}
|
|
743
|
-
patterns.push(f);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
return { explicit, patterns };
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
// The selected set — substring/tag matches plus explicitly named files — is
|
|
750
|
-
// the same whether it is run, `--list`ed, or `--list-tags`ed. No filters at
|
|
751
|
-
// all means "everything discovered"; any filter narrows.
|
|
752
|
-
const selectSuite = (
|
|
753
|
-
cases: DiscoveredCase[],
|
|
754
|
-
{ explicit, patterns }: FilterPartition,
|
|
755
|
-
tags: string[],
|
|
756
|
-
): DiscoveredCase[] => {
|
|
757
|
-
const hasFilter = explicit.length > 0 || patterns.length > 0 || tags.length > 0;
|
|
758
|
-
const byPath = new Map<string, DiscoveredCase>();
|
|
759
|
-
if (!hasFilter) {
|
|
760
|
-
for (const c of cases) byPath.set(c.path, c);
|
|
761
|
-
} else {
|
|
762
|
-
if (patterns.length > 0 || tags.length > 0) {
|
|
763
|
-
for (const c of selectCases(cases, { patterns, tags })) byPath.set(c.path, c);
|
|
764
|
-
}
|
|
765
|
-
for (const e of explicit) {
|
|
766
|
-
const discovered = cases.find((c) => c.path === e.path);
|
|
767
|
-
// Discovery knows the case's declared id; the filename-derived one is
|
|
768
|
-
// only a fallback for files discovery never saw.
|
|
769
|
-
byPath.set(e.path, {
|
|
770
|
-
path: e.path,
|
|
771
|
-
id: discovered?.id ?? e.id,
|
|
772
|
-
relPath: discovered?.relPath ?? e.id,
|
|
773
|
-
tags: discovered?.tags ?? [],
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return [...byPath.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
// `--loop` repeats each eval before moving to the next; each repeat gets a
|
|
781
|
-
// `#n` display suffix so the reporter keeps them apart, while `caseId` keeps
|
|
782
|
-
// every repeat filed under one run history.
|
|
783
|
-
const expandLoop = (base: SuiteItem[], loop: number): SuiteItem[] =>
|
|
784
|
-
loop > 1
|
|
785
|
-
? base.flatMap((b) =>
|
|
786
|
-
Array.from({ length: loop }, (_, i) => ({
|
|
787
|
-
path: b.path,
|
|
788
|
-
id: `${b.id} #${i + 1}`,
|
|
789
|
-
caseId: b.caseId ?? b.id,
|
|
790
|
-
})),
|
|
791
|
-
)
|
|
792
|
-
: base;
|
|
793
|
-
|
|
794
725
|
const experimentCommand = async (
|
|
795
726
|
mode: "experiment" | "optimize",
|
|
796
727
|
campaignPath: string,
|
|
797
|
-
options: {
|
|
798
|
-
|
|
728
|
+
options: {
|
|
729
|
+
runsRoot?: string | undefined;
|
|
730
|
+
resume?: string | undefined;
|
|
731
|
+
plan?: boolean | undefined;
|
|
732
|
+
},
|
|
733
|
+
) => {
|
|
799
734
|
const cwd = process.cwd();
|
|
800
735
|
const config = await loadLuxConfig(cwd);
|
|
801
736
|
const campaign = await resolveExperimentCampaignPath(config.rootDir, campaignPath);
|
|
802
737
|
const loaded = await loadExperimentCampaign(campaign);
|
|
803
738
|
experimentCampaignMode(campaign, loaded.campaign, mode);
|
|
804
739
|
const registry = buildRegistryFromConfig(config);
|
|
805
|
-
|
|
740
|
+
using controller = abortOnInterrupt();
|
|
806
741
|
const execution = {
|
|
807
742
|
config,
|
|
808
743
|
registry,
|
|
@@ -813,64 +748,55 @@ const experimentCommand = async (
|
|
|
813
748
|
};
|
|
814
749
|
if (options.plan) {
|
|
815
750
|
const plan = await planExperimentCampaign(execution, mode);
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
`campaign: ${plan.campaignId}`,
|
|
823
|
-
`mode: ${plan.mode}`,
|
|
824
|
-
`evaluation: ${plan.evaluationKind}`,
|
|
825
|
-
`profiles: ${plan.profiles.join(", ")}`,
|
|
826
|
-
`splits: ${plan.splits.map((split) => `${split.split}=${split.examples}`).join(", ")}`,
|
|
827
|
-
`components: ${plan.mutableComponents.length}`,
|
|
828
|
-
`candidates: ${plan.projected.candidates}`,
|
|
829
|
-
`metric calls: ${plan.projected.metricCalls}/${plan.budget.maxMetricCalls}`,
|
|
830
|
-
`judge calls: ${plan.projected.judgeCalls}/${plan.budget.maxJudgeCalls}`,
|
|
831
|
-
`proposal calls:${plan.projected.proposalCalls}/${plan.budget.maxProposalCalls}`,
|
|
832
|
-
`unpriced calls: observed at runtime (cap ${plan.budget.maxUnpricedModelCalls})`,
|
|
833
|
-
`projected work within budget: ${projectedStatus}`,
|
|
834
|
-
...plan.warnings.map((warning) => `warning: ${warning}`),
|
|
835
|
-
"",
|
|
836
|
-
].join("\n");
|
|
751
|
+
if (!plan.fitsProjectedCallBudgets) {
|
|
752
|
+
throw new Errors.IncurError({
|
|
753
|
+
code: "EXPERIMENT_BUDGET_EXCEEDED",
|
|
754
|
+
message: "The projected experiment work exceeds its configured call budgets.",
|
|
755
|
+
exitCode: 1,
|
|
756
|
+
});
|
|
837
757
|
}
|
|
838
|
-
|
|
839
|
-
if (!plan.fitsProjectedCallBudgets) failWith(1);
|
|
840
|
-
return;
|
|
758
|
+
return plan;
|
|
841
759
|
}
|
|
842
760
|
const result =
|
|
843
761
|
mode === "optimize"
|
|
844
762
|
? await optimizeExperimentCampaign(execution)
|
|
845
763
|
: await runExperimentCampaign(execution);
|
|
846
|
-
process.stdout.write(
|
|
847
|
-
options.json
|
|
848
|
-
? `${JSON.stringify({ experimentDir: result.experimentDir, ...result.manifest }, null, 2)}\n`
|
|
849
|
-
: `${formatExperimentRun(result.experimentDir, result.manifest)}\n`,
|
|
850
|
-
);
|
|
851
764
|
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
852
|
-
|
|
765
|
+
if (result.manifest.status !== "passed") {
|
|
766
|
+
throw new Errors.IncurError({
|
|
767
|
+
code: "EXPERIMENT_FAILED",
|
|
768
|
+
message: `Experiment ${result.manifest.experimentId} finished with status '${result.manifest.status}'.`,
|
|
769
|
+
exitCode: 1,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
return { experimentDir: result.experimentDir, ...result.manifest };
|
|
853
773
|
};
|
|
854
774
|
|
|
855
|
-
|
|
775
|
+
/** A profile pins one driver, so callers pass the project matrix only without one. */
|
|
776
|
+
const runCommand = async (filters: string[], options: RunOptions, human = false) => {
|
|
856
777
|
if (options.interactive && options.claudeAnswerer) {
|
|
857
|
-
throw new
|
|
778
|
+
throw new Errors.ParseError({
|
|
779
|
+
message: "--interactive and --claude-answerer are mutually exclusive",
|
|
780
|
+
});
|
|
858
781
|
}
|
|
859
782
|
const cwd = process.cwd();
|
|
860
783
|
const config = await loadLuxConfig(cwd);
|
|
861
|
-
const
|
|
862
|
-
if (options.profile && !
|
|
784
|
+
const authoredProfile = options.profile ? config.profiles?.[options.profile] : undefined;
|
|
785
|
+
if (options.profile && !authoredProfile) {
|
|
863
786
|
const available = Object.keys(config.profiles ?? {}).sort();
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
787
|
+
throw new Errors.IncurError({
|
|
788
|
+
code: "PROFILE_NOT_FOUND",
|
|
789
|
+
message: `Unknown profile '${options.profile}'${available.length > 0 ? `; available: ${available.join(", ")}` : ""}.`,
|
|
790
|
+
exitCode: 1,
|
|
791
|
+
});
|
|
869
792
|
}
|
|
870
|
-
const
|
|
871
|
-
const
|
|
793
|
+
const profile = resolveProfile(config, authoredProfile);
|
|
794
|
+
const harness = profile.harness;
|
|
795
|
+
const registry = registryWithHarness(buildRegistryFromConfig(config), config, harness);
|
|
872
796
|
const defaultDriver = profile?.driver ?? config.defaultDriver;
|
|
873
797
|
const orchestrator = new Orchestrator({
|
|
798
|
+
driverDefaults: config.driverDefaults,
|
|
799
|
+
environment: profile?.environment ?? config.environment,
|
|
874
800
|
registry,
|
|
875
801
|
defaultAnswerer: profile?.answerer ?? config.defaultAnswerer,
|
|
876
802
|
...(defaultDriver ? { defaultDriver } : {}),
|
|
@@ -888,23 +814,26 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
|
|
|
888
814
|
// broken config, not an empty suite — never mask it behind "no cases
|
|
889
815
|
// matched" or a lucky explicit-file run.
|
|
890
816
|
if (config.casesRoot && !existsSync(config.casesRoot)) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
817
|
+
throw new Errors.IncurError({
|
|
818
|
+
code: "CASES_ROOT_NOT_FOUND",
|
|
819
|
+
message: `casesRoot does not exist: ${config.casesRoot}`,
|
|
820
|
+
exitCode: 1,
|
|
821
|
+
});
|
|
894
822
|
}
|
|
895
823
|
|
|
896
824
|
const { explicit, patterns } = partitionFilters(filters, cwd);
|
|
897
|
-
const tags = options.tag ?? [];
|
|
825
|
+
const tags = normalizeTags(options.tag ?? []);
|
|
898
826
|
const hasFilter = explicit.length > 0 || patterns.length > 0 || tags.length > 0;
|
|
899
827
|
|
|
900
828
|
// Explicitly named files need no discovery, so they run even without a
|
|
901
829
|
// project; anything else would need a tree walk with nowhere safe to walk.
|
|
902
830
|
if (config.configPath === null && !existsSync(root) && explicit.length === 0) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
831
|
+
throw new Errors.IncurError({
|
|
832
|
+
code: "PROJECT_NOT_INITIALIZED",
|
|
833
|
+
message: `No lux.config.ts or cases/ directory in ${cwd}.`,
|
|
834
|
+
hint: "Run `lux init` or name a case file directly.",
|
|
835
|
+
exitCode: 1,
|
|
836
|
+
});
|
|
908
837
|
}
|
|
909
838
|
|
|
910
839
|
const { cases, problems } = existsSync(root)
|
|
@@ -919,35 +848,60 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
|
|
|
919
848
|
// An empty selection fails every mode the same way — running, `--list`, and
|
|
920
849
|
// `--list-tags` all exit 1, so a typo'd filter can never read as a clean pass.
|
|
921
850
|
if (selected.length === 0) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
851
|
+
throw new Errors.IncurError({
|
|
852
|
+
code: "NO_CASES_MATCHED",
|
|
853
|
+
message: hasFilter
|
|
854
|
+
? "No cases matched the given filters."
|
|
855
|
+
: `No cases were discovered under ${root}.`,
|
|
856
|
+
exitCode: 1,
|
|
857
|
+
});
|
|
929
858
|
}
|
|
930
859
|
|
|
931
860
|
// List the tag vocabulary of the selected cases — "which --tag values exist?".
|
|
932
861
|
if (options.listTags) {
|
|
933
862
|
const counts = new Map<string, number>();
|
|
934
863
|
for (const c of selected) for (const t of c.tags) counts.set(t, (counts.get(t) ?? 0) + 1);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
864
|
+
const result = {
|
|
865
|
+
caseCount: selected.length,
|
|
866
|
+
tags: [...counts]
|
|
867
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
868
|
+
.map(([tag, count]) => ({ tag, count })),
|
|
869
|
+
};
|
|
870
|
+
if (human)
|
|
871
|
+
process.stdout.write(
|
|
872
|
+
`${result.caseCount} cases\n${result.tags.map(({ tag, count }) => ` ${tag}: ${count}`).join("\n")}\n`,
|
|
873
|
+
);
|
|
874
|
+
return result;
|
|
942
875
|
}
|
|
943
876
|
|
|
944
|
-
const
|
|
877
|
+
const focus: HarnessFocus = {
|
|
878
|
+
driver: options.driver,
|
|
879
|
+
model: options.model,
|
|
880
|
+
effort: options.effort,
|
|
881
|
+
};
|
|
882
|
+
const base = await planSuiteItems(selected, {
|
|
883
|
+
driverDefaults: config.driverDefaults,
|
|
884
|
+
matrix: authoredProfile ? undefined : config.matrix,
|
|
885
|
+
defaultDriver,
|
|
886
|
+
registry,
|
|
887
|
+
focus,
|
|
888
|
+
});
|
|
945
889
|
|
|
946
890
|
// Collect-only: answer "what would run?" without spawning a single agent.
|
|
947
891
|
if (options.list) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
892
|
+
if (human)
|
|
893
|
+
process.stdout.write(
|
|
894
|
+
`${base.length} cases\n${base.map(({ id, path }) => ` ${id} ${path}`).join("\n")}\n`,
|
|
895
|
+
);
|
|
896
|
+
return {
|
|
897
|
+
caseCount: base.length,
|
|
898
|
+
cases: base.map(({ path, id, caseId, permutationId }) => ({
|
|
899
|
+
path,
|
|
900
|
+
id,
|
|
901
|
+
...(caseId !== undefined ? { caseId } : {}),
|
|
902
|
+
...(permutationId !== undefined ? { permutationId } : {}),
|
|
903
|
+
})),
|
|
904
|
+
};
|
|
951
905
|
}
|
|
952
906
|
|
|
953
907
|
const loop = options.loop ?? 1;
|
|
@@ -956,10 +910,10 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
|
|
|
956
910
|
const { concurrency, note } = resolveConcurrency(options, config.concurrency);
|
|
957
911
|
if (note) process.stderr.write(note);
|
|
958
912
|
|
|
959
|
-
const reporter = new ConsoleReporter({ verbose: options.verbose ?? false });
|
|
960
|
-
|
|
961
|
-
reporter
|
|
962
|
-
title: suiteTitle(patterns, tags, explicit, loop),
|
|
913
|
+
const reporter = human ? new ConsoleReporter({ verbose: options.verbose ?? false }) : undefined;
|
|
914
|
+
using controller = abortOnInterrupt();
|
|
915
|
+
reporter?.begin({
|
|
916
|
+
title: suiteTitle(patterns, tags, explicit, loop, focus),
|
|
963
917
|
caseIds: items.map((i) => i.id),
|
|
964
918
|
runsRoot,
|
|
965
919
|
loop,
|
|
@@ -972,28 +926,41 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
|
|
|
972
926
|
fixturesRoot: config.fixturesRoot,
|
|
973
927
|
answererOverride: resolveAnswererOverride(options),
|
|
974
928
|
skipJudge: options.skipJudge,
|
|
975
|
-
observer: reporter,
|
|
929
|
+
...(reporter ? { observer: reporter } : {}),
|
|
976
930
|
abortSignal: controller.signal,
|
|
977
931
|
concurrency,
|
|
978
932
|
subjectRoot,
|
|
979
933
|
profileId: options.profile,
|
|
980
934
|
});
|
|
981
935
|
results = suite.results;
|
|
982
|
-
reporter
|
|
936
|
+
reporter?.setSuiteDir(suite.suiteDir);
|
|
937
|
+
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
938
|
+
if (results.some(isFailure)) {
|
|
939
|
+
throw new Errors.IncurError({
|
|
940
|
+
code: "SUITE_FAILED",
|
|
941
|
+
message: `${results.filter(isFailure).length} of ${results.length} case attempts failed. Suite: ${suite.suiteDir}`,
|
|
942
|
+
exitCode: 1,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
suiteDir: suite.suiteDir,
|
|
947
|
+
results: results.map((result) => ({
|
|
948
|
+
caseId: result.caseId,
|
|
949
|
+
runDir: result.runDir ?? null,
|
|
950
|
+
exitReason: result.exitReason,
|
|
951
|
+
casePassed: result.casePassed,
|
|
952
|
+
caseScore: result.result?.grading?.caseScore ?? null,
|
|
953
|
+
assertionsPassed:
|
|
954
|
+
result.result?.grading?.assertions.filter((assertion) => assertion.passed).length ?? 0,
|
|
955
|
+
assertionsTotal:
|
|
956
|
+
result.result?.grading?.assertions.filter((assertion) => assertion.passed !== null)
|
|
957
|
+
.length ?? 0,
|
|
958
|
+
})),
|
|
959
|
+
};
|
|
983
960
|
} finally {
|
|
984
|
-
reporter
|
|
961
|
+
reporter?.finish();
|
|
985
962
|
}
|
|
986
|
-
|
|
987
|
-
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
988
|
-
else if (results.some(isFailure)) failWith(1);
|
|
989
963
|
};
|
|
990
964
|
|
|
991
965
|
// Internal exports for testing
|
|
992
|
-
export {
|
|
993
|
-
expandLoop,
|
|
994
|
-
isFailure,
|
|
995
|
-
parsePositiveInt,
|
|
996
|
-
partitionFilters,
|
|
997
|
-
resolveConcurrency,
|
|
998
|
-
selectSuite,
|
|
999
|
-
};
|
|
966
|
+
export { isFailure, resolveConcurrency };
|