@prismatic-io/lux 0.0.1 → 0.0.2-preview.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/answerers/persona/index.d.ts +2 -1
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +20 -5
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/answerers/terminal/index.js +1 -1
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/authoring.d.ts +14 -0
- package/lib/assertions/authoring.d.ts.map +1 -0
- package/lib/assertions/authoring.js +3 -0
- package/lib/assertions/authoring.js.map +1 -0
- package/lib/assertions/core/helpers.d.ts +2 -7
- package/lib/assertions/core/helpers.d.ts.map +1 -1
- package/lib/assertions/core/helpers.js +2 -6
- package/lib/assertions/core/helpers.js.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/resource-checks.d.ts +19 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +32 -0
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/authoring.d.ts +1 -0
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +1 -0
- package/lib/authoring.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +12 -67
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +16 -3
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +1 -0
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js.map +1 -1
- package/lib/cli/view.d.ts +13 -0
- package/lib/cli/view.d.ts.map +1 -0
- package/lib/cli/view.js +126 -0
- package/lib/cli/view.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-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/artifact-evidence.d.ts +8 -0
- package/lib/core/artifact-evidence.d.ts.map +1 -1
- package/lib/core/artifact-evidence.js +36 -4
- package/lib/core/artifact-evidence.js.map +1 -1
- package/lib/core/assertion.d.ts +19 -9
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +26 -0
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case-authoring-state.d.ts +12 -0
- package/lib/core/case-authoring-state.d.ts.map +1 -0
- package/lib/core/case-authoring-state.js +43 -0
- package/lib/core/case-authoring-state.js.map +1 -0
- package/lib/core/case.d.ts +29 -1
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +14 -6
- package/lib/core/case.js.map +1 -1
- 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 +3 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js.map +1 -1
- package/lib/core/events.d.ts +36 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +12 -2
- package/lib/core/events.js.map +1 -1
- package/lib/core/experiment.d.ts +19 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +2 -0
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +4 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/lifecycle-fixtures.d.ts +3 -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/lifecycle-hooks.d.ts +9 -0
- package/lib/core/lifecycle-hooks.d.ts.map +1 -1
- package/lib/core/lifecycle-hooks.js +8 -2
- package/lib/core/lifecycle-hooks.js.map +1 -1
- package/lib/core/run.d.ts +150 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +9 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/wire.d.ts +12 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/core/wire.js +5 -1
- package/lib/core/wire.js.map +1 -1
- 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 +93 -12
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +10 -14
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +6 -0
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/index.d.ts +10 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +10 -6
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/annotation-store.d.ts.map +1 -1
- package/lib/orchestrator/annotation-store.js +6 -2
- package/lib/orchestrator/annotation-store.js.map +1 -1
- package/lib/orchestrator/case-extensions.d.ts +6 -0
- package/lib/orchestrator/case-extensions.d.ts.map +1 -0
- package/lib/orchestrator/case-extensions.js +28 -0
- package/lib/orchestrator/case-extensions.js.map +1 -0
- package/lib/orchestrator/compare.d.ts +13 -1
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +12 -0
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +0 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +5 -3
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +19 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +17 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +19 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +38 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +12 -0
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grading.d.ts +2 -1
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +16 -4
- package/lib/orchestrator/grading.js.map +1 -1
- 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 +7 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +50 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/loader.d.ts.map +1 -1
- package/lib/orchestrator/loader.js +2 -0
- package/lib/orchestrator/loader.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +3 -0
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +42 -14
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/owned-lock.d.ts.map +1 -1
- package/lib/orchestrator/owned-lock.js +52 -38
- package/lib/orchestrator/owned-lock.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +6 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/regrade.js +8 -4
- package/lib/orchestrator/regrade.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +2 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +20 -5
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +34 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +68 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +45 -7
- 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 +105 -0
- package/lib/previewer/server.d.ts.map +1 -0
- package/lib/previewer/server.js +542 -0
- package/lib/previewer/server.js.map +1 -0
- package/package.json +2 -2
- package/skills/lux/SKILL.md +7 -3
- package/skills/lux/references/cli.md +25 -2
- package/skills/lux/references/eval-authoring.md +158 -4
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/persona/index.ts +21 -4
- package/src/answerers/terminal/index.ts +1 -1
- package/src/assertions/authoring.ts +81 -0
- package/src/assertions/core/helpers.ts +3 -6
- package/src/assertions/core/index.ts +2 -0
- package/src/assertions/core/resource-checks.ts +35 -0
- package/src/authoring.ts +1 -0
- package/src/cli/program.ts +14 -93
- package/src/cli/render/reporter.ts +18 -3
- package/src/cli/run-options.ts +1 -0
- package/src/cli/view.ts +168 -0
- package/src/core/action-events.ts +59 -0
- package/src/core/agent-activity.ts +236 -0
- package/src/core/agent-events.ts +17 -0
- package/src/core/artifact-evidence.ts +47 -4
- package/src/core/assertion.ts +41 -22
- package/src/core/case-authoring-state.ts +65 -0
- package/src/core/case.ts +57 -6
- package/src/core/driver-metrics.ts +51 -0
- package/src/core/driver.ts +3 -0
- package/src/core/events.ts +14 -2
- package/src/core/experiment.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/lifecycle-fixtures.ts +7 -2
- package/src/core/lifecycle-hooks.ts +16 -2
- package/src/core/run.ts +9 -0
- package/src/core/wire.ts +5 -1
- package/src/drivers/claude-code/parse-events.ts +98 -12
- package/src/drivers/codex/app-events.ts +116 -16
- package/src/drivers/codex/index.ts +16 -14
- package/src/drivers/subprocess/index.ts +6 -0
- package/src/index.ts +50 -6
- package/src/orchestrator/annotation-store.ts +5 -1
- package/src/orchestrator/case-extensions.ts +44 -0
- package/src/orchestrator/compare.ts +18 -0
- package/src/orchestrator/config.ts +0 -2
- package/src/orchestrator/doctor.ts +5 -3
- package/src/orchestrator/experiment-evaluation.ts +23 -0
- package/src/orchestrator/grading.ts +21 -2
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/list-runs.ts +66 -5
- package/src/orchestrator/loader.ts +2 -0
- package/src/orchestrator/orchestrator.ts +86 -40
- package/src/orchestrator/owned-lock.ts +54 -34
- package/src/orchestrator/regrade.ts +8 -7
- package/src/orchestrator/run-execution.ts +24 -3
- package/src/orchestrator/suite-summary.ts +101 -8
- package/src/previewer/artifacts.ts +221 -0
- package/src/previewer/server.ts +765 -0
- package/viewer/app.css +2075 -0
- package/viewer/app.js +606 -0
- package/viewer/artifact-view.js +48 -0
- package/viewer/charts.js +86 -0
- package/viewer/detail.js +1214 -0
- package/viewer/format.js +129 -0
- package/viewer/index.html +151 -0
- package/lib/answerers/scripted/index.d.ts +0 -8
- package/lib/answerers/scripted/index.d.ts.map +0 -1
- package/lib/answerers/scripted/index.js +0 -163
- package/lib/answerers/scripted/index.js.map +0 -1
- package/src/answerers/scripted/index.ts +0 -186
package/src/cli/program.ts
CHANGED
|
@@ -24,25 +24,15 @@ import {
|
|
|
24
24
|
formatDoctorReport,
|
|
25
25
|
formatExperimentDecisionMarkdown,
|
|
26
26
|
formatExperimentRun,
|
|
27
|
-
formatExperimentRunsTable,
|
|
28
|
-
formatRunDetail,
|
|
29
|
-
formatRunsTable,
|
|
30
27
|
formatSuiteComparisonText,
|
|
31
|
-
formatSuiteRunDetail,
|
|
32
|
-
formatSuiteRunsTable,
|
|
33
28
|
hasRegression,
|
|
34
29
|
hasSuiteRegression,
|
|
35
|
-
isExperimentRun,
|
|
36
|
-
listExperimentRuns,
|
|
37
|
-
listRuns,
|
|
38
|
-
listSuiteRuns,
|
|
39
30
|
loadEvalCase,
|
|
40
31
|
loadExperimentCampaign,
|
|
41
32
|
loadExperimentRun,
|
|
42
33
|
loadFrozenRegradableRun,
|
|
43
34
|
loadLuxConfig,
|
|
44
35
|
loadRubricDefinition,
|
|
45
|
-
loadRunDetail,
|
|
46
36
|
loadSuiteRun,
|
|
47
37
|
loadVerifiedExperimentDecisionSnapshot,
|
|
48
38
|
Orchestrator,
|
|
@@ -84,6 +74,7 @@ import {
|
|
|
84
74
|
installSkills,
|
|
85
75
|
type SkillsPlatform,
|
|
86
76
|
} from "./skills.js";
|
|
77
|
+
import { parsePreviewPort, viewCommand } from "./view.js";
|
|
87
78
|
|
|
88
79
|
const requireFromHere = createRequire(import.meta.url);
|
|
89
80
|
// From lib/cli/program.js (or src/cli/program.ts under test) to the package root.
|
|
@@ -163,7 +154,7 @@ const registerExperimentCommands = (program: Command): void => {
|
|
|
163
154
|
});
|
|
164
155
|
|
|
165
156
|
program
|
|
166
|
-
.command("run [filters...]"
|
|
157
|
+
.command("run [filters...]")
|
|
167
158
|
.description("discover and run cases; filter by name/path substring or --tag")
|
|
168
159
|
.option(
|
|
169
160
|
"-t, --tag <tag>",
|
|
@@ -184,6 +175,7 @@ const registerExperimentCommands = (program: Command): void => {
|
|
|
184
175
|
"--claude-answerer",
|
|
185
176
|
"delegate questions through the Claude plugin's lux-answerer skill",
|
|
186
177
|
)
|
|
178
|
+
.option("--skip-judge", "skip rubric LLM judge assertions")
|
|
187
179
|
.option("--runs-root <path>", "run-dir root (overrides lux.config.ts)")
|
|
188
180
|
.option("--profile <id>", "named driver/answerer profile from lux.config.ts")
|
|
189
181
|
.option("--subject-root <path>", "root bound into subjectPath() values")
|
|
@@ -495,88 +487,11 @@ const registerReportingCommands = (program: Command): void => {
|
|
|
495
487
|
.option("--case <id>", "filter the run list to one case slug (list mode only)")
|
|
496
488
|
.option("--suites", "list logical suite runs instead of individual runs")
|
|
497
489
|
.option("--experiments", "list experiment runs instead of individual runs")
|
|
490
|
+
.option("--web", "open the local Lux Review web app")
|
|
491
|
+
.option("--port <n>", "listen on a specific local port in web mode", parsePreviewPort)
|
|
492
|
+
.option("--no-open", "start web mode without opening a browser")
|
|
498
493
|
.option("--json", "emit machine-readable JSON")
|
|
499
|
-
.action(
|
|
500
|
-
async (
|
|
501
|
-
runDirArg: string | undefined,
|
|
502
|
-
options: {
|
|
503
|
-
runsRoot?: string;
|
|
504
|
-
case?: string;
|
|
505
|
-
suites?: boolean;
|
|
506
|
-
experiments?: boolean;
|
|
507
|
-
json?: boolean;
|
|
508
|
-
},
|
|
509
|
-
) => {
|
|
510
|
-
if (runDirArg) {
|
|
511
|
-
const path = resolve(runDirArg);
|
|
512
|
-
if (!(await ensureDirectory("view", path))) return;
|
|
513
|
-
if (isExperimentRun(path)) {
|
|
514
|
-
const experiment = await loadExperimentRun(path);
|
|
515
|
-
if (!experiment) {
|
|
516
|
-
process.stderr.write(`lux view: invalid experiment at ${path}\n`);
|
|
517
|
-
failWith(1);
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
process.stdout.write(
|
|
521
|
-
options.json
|
|
522
|
-
? `${JSON.stringify({ experimentDir: path, ...experiment }, null, 2)}\n`
|
|
523
|
-
: `${formatExperimentRun(path, experiment)}\n`,
|
|
524
|
-
);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
const suite = await loadSuiteRun(path);
|
|
528
|
-
if (suite) {
|
|
529
|
-
process.stdout.write(
|
|
530
|
-
options.json
|
|
531
|
-
? `${JSON.stringify({ suiteDir: path, ...suite }, null, 2)}\n`
|
|
532
|
-
: `${formatSuiteRunDetail(path, suite)}\n`,
|
|
533
|
-
);
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
const detail = await loadRunDetail(path);
|
|
537
|
-
if (options.json) {
|
|
538
|
-
process.stdout.write(`${JSON.stringify(detail, null, 2)}\n`);
|
|
539
|
-
} else {
|
|
540
|
-
process.stdout.write(`${formatRunDetail(detail)}\n`);
|
|
541
|
-
}
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const cwd = process.cwd();
|
|
546
|
-
const config = await loadLuxConfig(cwd);
|
|
547
|
-
const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
|
|
548
|
-
if (options.experiments) {
|
|
549
|
-
const experiments = await listExperimentRuns(runsRoot);
|
|
550
|
-
process.stdout.write(
|
|
551
|
-
options.json
|
|
552
|
-
? `${JSON.stringify(experiments, null, 2)}\n`
|
|
553
|
-
: `experiments at ${runsRoot}:\n${formatExperimentRunsTable(experiments)}\n`,
|
|
554
|
-
);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (options.suites) {
|
|
558
|
-
const suites = await listSuiteRuns(runsRoot);
|
|
559
|
-
process.stdout.write(
|
|
560
|
-
options.json
|
|
561
|
-
? `${JSON.stringify(suites, null, 2)}\n`
|
|
562
|
-
: `suite runs at ${runsRoot}:\n${formatSuiteRunsTable(suites)}\n`,
|
|
563
|
-
);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
const all = await listRuns(runsRoot);
|
|
567
|
-
const runs = options.case ? all.filter((r) => r.caseSlug === options.case) : all;
|
|
568
|
-
if (options.json) {
|
|
569
|
-
process.stdout.write(`${JSON.stringify(runs, null, 2)}\n`);
|
|
570
|
-
} else {
|
|
571
|
-
if (!options.case) {
|
|
572
|
-
process.stdout.write(`suite runs at ${runsRoot}:\n`);
|
|
573
|
-
process.stdout.write(`${formatSuiteRunsTable(await listSuiteRuns(runsRoot))}\n\n`);
|
|
574
|
-
}
|
|
575
|
-
process.stdout.write(`runs at ${runsRoot}:\n`);
|
|
576
|
-
process.stdout.write(`${formatRunsTable(runs)}\n`);
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
);
|
|
494
|
+
.action(viewCommand);
|
|
580
495
|
|
|
581
496
|
program
|
|
582
497
|
.command("compare <runA> <runB>")
|
|
@@ -623,11 +538,16 @@ export const buildProgram = (): Command => {
|
|
|
623
538
|
.description(
|
|
624
539
|
"Coding-agent evaluation and improvement with deterministic assertions and human-in-the-loop runs",
|
|
625
540
|
)
|
|
626
|
-
.version(PKG_VERSION)
|
|
541
|
+
.version(PKG_VERSION)
|
|
542
|
+
.addHelpText(
|
|
543
|
+
"after",
|
|
544
|
+
"\nAgent skills:\n $ lux skills list\n $ lux skills install codex --project\n $ lux skills install claude --project",
|
|
545
|
+
);
|
|
627
546
|
registerProjectCommands(program);
|
|
628
547
|
registerExperimentCommands(program);
|
|
629
548
|
registerCandidateCommands(program);
|
|
630
549
|
registerReportingCommands(program);
|
|
550
|
+
program.action(() => program.outputHelp());
|
|
631
551
|
return program;
|
|
632
552
|
};
|
|
633
553
|
|
|
@@ -965,6 +885,7 @@ const runCommand = async (filters: string[], options: RunOptions): Promise<void>
|
|
|
965
885
|
runsRoot,
|
|
966
886
|
fixturesRoot: config.fixturesRoot,
|
|
967
887
|
answererOverride: resolveAnswererOverride(options),
|
|
888
|
+
skipJudge: options.skipJudge,
|
|
968
889
|
observer: reporter,
|
|
969
890
|
abortSignal: controller.signal,
|
|
970
891
|
concurrency,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createToolOutcomeAccumulator,
|
|
5
5
|
emptyRunUsage,
|
|
6
6
|
type GradingAssertionEntry,
|
|
7
|
+
type GradingReport,
|
|
7
8
|
harnessUsage,
|
|
8
9
|
isUsageEmpty,
|
|
9
10
|
type LifecycleStage,
|
|
@@ -77,6 +78,7 @@ type CaseView = {
|
|
|
77
78
|
assertionTotal: number;
|
|
78
79
|
currentAssertion: string | null;
|
|
79
80
|
assertions: GradingAssertionEntry[];
|
|
81
|
+
skippedAssertions: NonNullable<GradingReport["skippedAssertions"]>;
|
|
80
82
|
usage: RunUsage;
|
|
81
83
|
/** Per-API-call agent usage, in order — the split behind `usage.agent`. */
|
|
82
84
|
agentTurns: TurnUsage[];
|
|
@@ -117,7 +119,10 @@ const statusMark = (status: CaseStatus, theme: Theme): string => {
|
|
|
117
119
|
|
|
118
120
|
const assertionTally = (view: CaseView): string => {
|
|
119
121
|
const passed = view.assertions.filter((a) => a.passed).length;
|
|
120
|
-
|
|
122
|
+
const evaluated = view.assertions.length === 0 ? "—" : `${passed}/${view.assertions.length}`;
|
|
123
|
+
return view.skippedAssertions.length > 0
|
|
124
|
+
? `${evaluated} · ${view.skippedAssertions.length} skipped`
|
|
125
|
+
: evaluated;
|
|
121
126
|
};
|
|
122
127
|
|
|
123
128
|
/**
|
|
@@ -233,6 +238,7 @@ export class ConsoleReporter implements SuiteObserver {
|
|
|
233
238
|
assertionTotal: 0,
|
|
234
239
|
currentAssertion: null,
|
|
235
240
|
assertions: [],
|
|
241
|
+
skippedAssertions: [],
|
|
236
242
|
usage: emptyRunUsage(),
|
|
237
243
|
agentTurns: [],
|
|
238
244
|
phaseDurationMs: null,
|
|
@@ -299,6 +305,7 @@ export class ConsoleReporter implements SuiteObserver {
|
|
|
299
305
|
if (result.errorMessage) view.errorMessage = result.errorMessage;
|
|
300
306
|
if (result.result) {
|
|
301
307
|
view.assertions = result.result.grading.assertions;
|
|
308
|
+
view.skippedAssertions = result.result.grading.skippedAssertions ?? [];
|
|
302
309
|
view.usage = result.result.metadata.usage;
|
|
303
310
|
view.phaseDurationMs = result.result.metadata.phaseDurationMs ?? null;
|
|
304
311
|
const tools = result.result.metadata.toolOutcomes;
|
|
@@ -315,6 +322,7 @@ export class ConsoleReporter implements SuiteObserver {
|
|
|
315
322
|
// Assertions reported before the run failed to persist are not results
|
|
316
323
|
// and must not inflate the closing tally of a case that errored.
|
|
317
324
|
view.assertions = [];
|
|
325
|
+
view.skippedAssertions = [];
|
|
318
326
|
}
|
|
319
327
|
|
|
320
328
|
this.region.staticLine(this.finishedLine(view));
|
|
@@ -452,6 +460,11 @@ export class ConsoleReporter implements SuiteObserver {
|
|
|
452
460
|
body: !entry.passed && entry.comment ? [this.theme.dim(entry.comment)] : [],
|
|
453
461
|
});
|
|
454
462
|
}
|
|
463
|
+
for (const skipped of view.skippedAssertions) {
|
|
464
|
+
nodes.push({
|
|
465
|
+
head: `${this.theme.dim("⊘")} ${skipped.name} ${this.theme.dim("skipped: judge disabled")}`,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
455
468
|
|
|
456
469
|
if (view.toolCalls > 0 || view.toolUnmatchedResults > 0 || view.toolMalformed > 0) {
|
|
457
470
|
const parts = [
|
|
@@ -550,15 +563,17 @@ export class ConsoleReporter implements SuiteObserver {
|
|
|
550
563
|
}
|
|
551
564
|
|
|
552
565
|
const assertions = this.cases.flatMap((c) => c.assertions);
|
|
553
|
-
|
|
566
|
+
const skippedAssertions = this.cases.flatMap((c) => c.skippedAssertions);
|
|
567
|
+
if (assertions.length > 0 || skippedAssertions.length > 0) {
|
|
554
568
|
const ok = assertions.filter((a) => a.passed).length;
|
|
555
569
|
const bad = assertions.length - ok;
|
|
556
570
|
const tally = formatTally(
|
|
557
571
|
[
|
|
558
572
|
[ok, this.theme.green("passed")],
|
|
559
573
|
[bad, this.theme.red("failed")],
|
|
574
|
+
[skippedAssertions.length, this.theme.dim("skipped")],
|
|
560
575
|
],
|
|
561
|
-
assertions.length,
|
|
576
|
+
assertions.length + skippedAssertions.length,
|
|
562
577
|
);
|
|
563
578
|
lines.push(` ${label("Assertions")}${tally}`);
|
|
564
579
|
}
|
package/src/cli/run-options.ts
CHANGED
package/src/cli/view.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { InvalidArgumentError } from "commander";
|
|
3
|
+
import {
|
|
4
|
+
formatExperimentRun,
|
|
5
|
+
formatExperimentRunsTable,
|
|
6
|
+
formatRunDetail,
|
|
7
|
+
formatRunsTable,
|
|
8
|
+
formatSuiteRunDetail,
|
|
9
|
+
formatSuiteRunsTable,
|
|
10
|
+
isExperimentRun,
|
|
11
|
+
listExperimentRuns,
|
|
12
|
+
listRuns,
|
|
13
|
+
listSuiteRuns,
|
|
14
|
+
loadExperimentRun,
|
|
15
|
+
loadLuxConfig,
|
|
16
|
+
loadRunDetail,
|
|
17
|
+
loadSuiteRun,
|
|
18
|
+
} from "../orchestrator/index.js";
|
|
19
|
+
import { openPreviewUrl, startPreviewServer } from "../previewer/server.js";
|
|
20
|
+
import { abortOnInterrupt, ensureDirectory, failWith } from "./command-runtime.js";
|
|
21
|
+
|
|
22
|
+
const SIGINT_EXIT_CODE = 130;
|
|
23
|
+
|
|
24
|
+
export type ViewOptions = {
|
|
25
|
+
runsRoot?: string;
|
|
26
|
+
case?: string;
|
|
27
|
+
suites?: boolean;
|
|
28
|
+
experiments?: boolean;
|
|
29
|
+
web?: boolean;
|
|
30
|
+
port?: number;
|
|
31
|
+
open?: boolean;
|
|
32
|
+
json?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const previewKind = async (
|
|
36
|
+
initialPath: string | undefined,
|
|
37
|
+
options: ViewOptions,
|
|
38
|
+
): Promise<"run" | "suite" | "experiment" | undefined> => {
|
|
39
|
+
if (initialPath) {
|
|
40
|
+
if (isExperimentRun(initialPath)) return "experiment";
|
|
41
|
+
if (await loadSuiteRun(initialPath)) return "suite";
|
|
42
|
+
return "run";
|
|
43
|
+
}
|
|
44
|
+
if (options.experiments) return "experiment";
|
|
45
|
+
if (options.suites) return "suite";
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const webViewCommand = async (
|
|
50
|
+
runDirArg: string | undefined,
|
|
51
|
+
options: ViewOptions,
|
|
52
|
+
): Promise<void> => {
|
|
53
|
+
if (options.json) {
|
|
54
|
+
throw new InvalidArgumentError("--web and --json are mutually exclusive");
|
|
55
|
+
}
|
|
56
|
+
const config = await loadLuxConfig(process.cwd());
|
|
57
|
+
const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
|
|
58
|
+
const initialPath = runDirArg ? resolve(runDirArg) : undefined;
|
|
59
|
+
if (initialPath && !(await ensureDirectory("view", initialPath))) return;
|
|
60
|
+
const initialKind = await previewKind(initialPath, options);
|
|
61
|
+
const controller = abortOnInterrupt();
|
|
62
|
+
const preview = await startPreviewServer({
|
|
63
|
+
runsRoot,
|
|
64
|
+
...(options.port !== undefined ? { port: options.port } : {}),
|
|
65
|
+
...(initialPath ? { initialPath } : {}),
|
|
66
|
+
...(initialKind ? { initialKind } : {}),
|
|
67
|
+
...(options.case ? { caseFilter: options.case } : {}),
|
|
68
|
+
signal: controller.signal,
|
|
69
|
+
});
|
|
70
|
+
process.stdout.write(`Lux Review: ${preview.url}\n`);
|
|
71
|
+
process.stdout.write(`Reading evidence from ${runsRoot}\n`);
|
|
72
|
+
if (options.open !== false) {
|
|
73
|
+
try {
|
|
74
|
+
await openPreviewUrl(preview.url);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
process.stderr.write(`lux view: could not open a browser (${message})\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await preview.closed;
|
|
81
|
+
if (controller.signal.aborted) failWith(SIGINT_EXIT_CODE);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const viewCommand = async (
|
|
85
|
+
runDirArg: string | undefined,
|
|
86
|
+
options: ViewOptions,
|
|
87
|
+
): Promise<void> => {
|
|
88
|
+
if (options.web) {
|
|
89
|
+
await webViewCommand(runDirArg, options);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (options.port !== undefined || options.open === false) {
|
|
93
|
+
throw new InvalidArgumentError("--port and --no-open require --web");
|
|
94
|
+
}
|
|
95
|
+
if (runDirArg) {
|
|
96
|
+
const path = resolve(runDirArg);
|
|
97
|
+
if (!(await ensureDirectory("view", path))) return;
|
|
98
|
+
if (isExperimentRun(path)) {
|
|
99
|
+
const experiment = await loadExperimentRun(path);
|
|
100
|
+
if (!experiment) {
|
|
101
|
+
process.stderr.write(`lux view: invalid experiment at ${path}\n`);
|
|
102
|
+
failWith(1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
process.stdout.write(
|
|
106
|
+
options.json
|
|
107
|
+
? `${JSON.stringify({ experimentDir: path, ...experiment }, null, 2)}\n`
|
|
108
|
+
: `${formatExperimentRun(path, experiment)}\n`,
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const suite = await loadSuiteRun(path);
|
|
113
|
+
if (suite) {
|
|
114
|
+
process.stdout.write(
|
|
115
|
+
options.json
|
|
116
|
+
? `${JSON.stringify({ suiteDir: path, ...suite }, null, 2)}\n`
|
|
117
|
+
: `${formatSuiteRunDetail(path, suite)}\n`,
|
|
118
|
+
);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const detail = await loadRunDetail(path);
|
|
122
|
+
process.stdout.write(
|
|
123
|
+
options.json ? `${JSON.stringify(detail, null, 2)}\n` : `${formatRunDetail(detail)}\n`,
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const config = await loadLuxConfig(process.cwd());
|
|
129
|
+
const runsRoot = options.runsRoot ? resolve(options.runsRoot) : config.runsRoot;
|
|
130
|
+
if (options.experiments) {
|
|
131
|
+
const experiments = await listExperimentRuns(runsRoot);
|
|
132
|
+
process.stdout.write(
|
|
133
|
+
options.json
|
|
134
|
+
? `${JSON.stringify(experiments, null, 2)}\n`
|
|
135
|
+
: `experiments at ${runsRoot}:\n${formatExperimentRunsTable(experiments)}\n`,
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (options.suites) {
|
|
140
|
+
const suites = await listSuiteRuns(runsRoot);
|
|
141
|
+
process.stdout.write(
|
|
142
|
+
options.json
|
|
143
|
+
? `${JSON.stringify(suites, null, 2)}\n`
|
|
144
|
+
: `suite runs at ${runsRoot}:\n${formatSuiteRunsTable(suites)}\n`,
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const all = await listRuns(runsRoot);
|
|
149
|
+
const runs = options.case ? all.filter((run) => run.caseSlug === options.case) : all;
|
|
150
|
+
if (options.json) {
|
|
151
|
+
process.stdout.write(`${JSON.stringify(runs, null, 2)}\n`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!options.case) {
|
|
155
|
+
process.stdout.write(`suite runs at ${runsRoot}:\n`);
|
|
156
|
+
process.stdout.write(`${formatSuiteRunsTable(await listSuiteRuns(runsRoot))}\n\n`);
|
|
157
|
+
}
|
|
158
|
+
process.stdout.write(`runs at ${runsRoot}:\n`);
|
|
159
|
+
process.stdout.write(`${formatRunsTable(runs)}\n`);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const parsePreviewPort = (value: string): number => {
|
|
163
|
+
const port = Number(value);
|
|
164
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
165
|
+
throw new InvalidArgumentError("port must be an integer from 0 to 65535");
|
|
166
|
+
}
|
|
167
|
+
return port;
|
|
168
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { RunEvent } from "./events.js";
|
|
3
|
+
|
|
4
|
+
export const ActionCategorySchema = z.enum([
|
|
5
|
+
"query",
|
|
6
|
+
"mutation",
|
|
7
|
+
"durable-mutation",
|
|
8
|
+
"execution",
|
|
9
|
+
"interaction",
|
|
10
|
+
]);
|
|
11
|
+
export type ActionCategory = z.infer<typeof ActionCategorySchema>;
|
|
12
|
+
|
|
13
|
+
export const ActionProgressPayloadSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
category: ActionCategorySchema,
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export type ActionProgressPayload = z.infer<typeof ActionProgressPayloadSchema>;
|
|
20
|
+
|
|
21
|
+
export const ActionTimingSchema = z.object({
|
|
22
|
+
timeToFirstActionMs: z.exactOptional(z.number().nonnegative()),
|
|
23
|
+
timeToFirstMutationMs: z.exactOptional(z.number().nonnegative()),
|
|
24
|
+
timeToFirstDurableMutationMs: z.exactOptional(z.number().nonnegative()),
|
|
25
|
+
});
|
|
26
|
+
export type ActionTiming = z.infer<typeof ActionTimingSchema>;
|
|
27
|
+
|
|
28
|
+
export const actionTiming = (events: readonly RunEvent[]): ActionTiming => {
|
|
29
|
+
const startEvent = events.find(
|
|
30
|
+
(event) => event.type === "lifecycle" && event.stage === "start-driver",
|
|
31
|
+
);
|
|
32
|
+
const startedAt = startEvent?.type === "lifecycle" ? startEvent.ts : undefined;
|
|
33
|
+
if (startedAt === undefined) return {};
|
|
34
|
+
|
|
35
|
+
const actions = events.flatMap((event) => {
|
|
36
|
+
if (event.type !== "progress" || event.kind !== "action") return [];
|
|
37
|
+
const payload = ActionProgressPayloadSchema.safeParse(event.payload).data;
|
|
38
|
+
return payload ? [{ ...payload, ts: event.ts }] : [];
|
|
39
|
+
});
|
|
40
|
+
const elapsed = (category: "any" | "mutation" | "durable-mutation") => {
|
|
41
|
+
const timestamps = actions.flatMap(({ category: candidate, ts }) => {
|
|
42
|
+
if (category === "any") return [ts];
|
|
43
|
+
if (category === "mutation") {
|
|
44
|
+
return candidate === "mutation" || candidate === "durable-mutation" ? [ts] : [];
|
|
45
|
+
}
|
|
46
|
+
return candidate === category ? [ts] : [];
|
|
47
|
+
});
|
|
48
|
+
return timestamps.length > 0 ? Math.max(0, Math.min(...timestamps) - startedAt) : undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const timeToFirstActionMs = elapsed("any");
|
|
52
|
+
const timeToFirstMutationMs = elapsed("mutation");
|
|
53
|
+
const timeToFirstDurableMutationMs = elapsed("durable-mutation");
|
|
54
|
+
return {
|
|
55
|
+
...(timeToFirstActionMs === undefined ? {} : { timeToFirstActionMs }),
|
|
56
|
+
...(timeToFirstMutationMs === undefined ? {} : { timeToFirstMutationMs }),
|
|
57
|
+
...(timeToFirstDurableMutationMs === undefined ? {} : { timeToFirstDurableMutationMs }),
|
|
58
|
+
};
|
|
59
|
+
};
|