@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.
Files changed (256) hide show
  1. package/lib/answerers/persona/index.d.ts +2 -1
  2. package/lib/answerers/persona/index.d.ts.map +1 -1
  3. package/lib/answerers/persona/index.js +20 -5
  4. package/lib/answerers/persona/index.js.map +1 -1
  5. package/lib/answerers/terminal/index.js +1 -1
  6. package/lib/answerers/terminal/index.js.map +1 -1
  7. package/lib/assertions/authoring.d.ts +14 -0
  8. package/lib/assertions/authoring.d.ts.map +1 -0
  9. package/lib/assertions/authoring.js +3 -0
  10. package/lib/assertions/authoring.js.map +1 -0
  11. package/lib/assertions/core/helpers.d.ts +2 -7
  12. package/lib/assertions/core/helpers.d.ts.map +1 -1
  13. package/lib/assertions/core/helpers.js +2 -6
  14. package/lib/assertions/core/helpers.js.map +1 -1
  15. package/lib/assertions/core/index.d.ts.map +1 -1
  16. package/lib/assertions/core/index.js +2 -1
  17. package/lib/assertions/core/index.js.map +1 -1
  18. package/lib/assertions/core/resource-checks.d.ts +19 -0
  19. package/lib/assertions/core/resource-checks.d.ts.map +1 -1
  20. package/lib/assertions/core/resource-checks.js +32 -0
  21. package/lib/assertions/core/resource-checks.js.map +1 -1
  22. package/lib/authoring.d.ts +1 -0
  23. package/lib/authoring.d.ts.map +1 -1
  24. package/lib/authoring.js +1 -0
  25. package/lib/authoring.js.map +1 -1
  26. package/lib/cli/program.d.ts.map +1 -1
  27. package/lib/cli/program.js +12 -67
  28. package/lib/cli/program.js.map +1 -1
  29. package/lib/cli/render/reporter.d.ts.map +1 -1
  30. package/lib/cli/render/reporter.js +16 -3
  31. package/lib/cli/render/reporter.js.map +1 -1
  32. package/lib/cli/run-options.d.ts +1 -0
  33. package/lib/cli/run-options.d.ts.map +1 -1
  34. package/lib/cli/run-options.js.map +1 -1
  35. package/lib/cli/view.d.ts +13 -0
  36. package/lib/cli/view.d.ts.map +1 -0
  37. package/lib/cli/view.js +126 -0
  38. package/lib/cli/view.js.map +1 -0
  39. package/lib/core/action-events.d.ts +29 -0
  40. package/lib/core/action-events.d.ts.map +1 -0
  41. package/lib/core/action-events.js +51 -0
  42. package/lib/core/action-events.js.map +1 -0
  43. package/lib/core/agent-activity.d.ts +49 -0
  44. package/lib/core/agent-activity.d.ts.map +1 -0
  45. package/lib/core/agent-activity.js +206 -0
  46. package/lib/core/agent-activity.js.map +1 -0
  47. package/lib/core/agent-events.d.ts +26 -0
  48. package/lib/core/agent-events.d.ts.map +1 -0
  49. package/lib/core/agent-events.js +13 -0
  50. package/lib/core/agent-events.js.map +1 -0
  51. package/lib/core/artifact-evidence.d.ts +8 -0
  52. package/lib/core/artifact-evidence.d.ts.map +1 -1
  53. package/lib/core/artifact-evidence.js +36 -4
  54. package/lib/core/artifact-evidence.js.map +1 -1
  55. package/lib/core/assertion.d.ts +19 -9
  56. package/lib/core/assertion.d.ts.map +1 -1
  57. package/lib/core/assertion.js +26 -0
  58. package/lib/core/assertion.js.map +1 -1
  59. package/lib/core/case-authoring-state.d.ts +12 -0
  60. package/lib/core/case-authoring-state.d.ts.map +1 -0
  61. package/lib/core/case-authoring-state.js +43 -0
  62. package/lib/core/case-authoring-state.js.map +1 -0
  63. package/lib/core/case.d.ts +29 -1
  64. package/lib/core/case.d.ts.map +1 -1
  65. package/lib/core/case.js +14 -6
  66. package/lib/core/case.js.map +1 -1
  67. package/lib/core/driver-metrics.d.ts +98 -0
  68. package/lib/core/driver-metrics.d.ts.map +1 -0
  69. package/lib/core/driver-metrics.js +36 -0
  70. package/lib/core/driver-metrics.js.map +1 -0
  71. package/lib/core/driver.d.ts +3 -0
  72. package/lib/core/driver.d.ts.map +1 -1
  73. package/lib/core/driver.js.map +1 -1
  74. package/lib/core/events.d.ts +36 -0
  75. package/lib/core/events.d.ts.map +1 -1
  76. package/lib/core/events.js +12 -2
  77. package/lib/core/events.js.map +1 -1
  78. package/lib/core/experiment.d.ts +19 -0
  79. package/lib/core/experiment.d.ts.map +1 -1
  80. package/lib/core/experiment.js +2 -0
  81. package/lib/core/experiment.js.map +1 -1
  82. package/lib/core/index.d.ts +4 -0
  83. package/lib/core/index.d.ts.map +1 -1
  84. package/lib/core/index.js +4 -0
  85. package/lib/core/index.js.map +1 -1
  86. package/lib/core/lifecycle-fixtures.d.ts +3 -1
  87. package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
  88. package/lib/core/lifecycle-fixtures.js +4 -2
  89. package/lib/core/lifecycle-fixtures.js.map +1 -1
  90. package/lib/core/lifecycle-hooks.d.ts +9 -0
  91. package/lib/core/lifecycle-hooks.d.ts.map +1 -1
  92. package/lib/core/lifecycle-hooks.js +8 -2
  93. package/lib/core/lifecycle-hooks.js.map +1 -1
  94. package/lib/core/run.d.ts +150 -0
  95. package/lib/core/run.d.ts.map +1 -1
  96. package/lib/core/run.js +9 -0
  97. package/lib/core/run.js.map +1 -1
  98. package/lib/core/wire.d.ts +12 -0
  99. package/lib/core/wire.d.ts.map +1 -1
  100. package/lib/core/wire.js +5 -1
  101. package/lib/core/wire.js.map +1 -1
  102. package/lib/drivers/claude-code/parse-events.d.ts.map +1 -1
  103. package/lib/drivers/claude-code/parse-events.js +73 -8
  104. package/lib/drivers/claude-code/parse-events.js.map +1 -1
  105. package/lib/drivers/codex/app-events.d.ts +9 -2
  106. package/lib/drivers/codex/app-events.d.ts.map +1 -1
  107. package/lib/drivers/codex/app-events.js +93 -12
  108. package/lib/drivers/codex/app-events.js.map +1 -1
  109. package/lib/drivers/codex/index.d.ts.map +1 -1
  110. package/lib/drivers/codex/index.js +10 -14
  111. package/lib/drivers/codex/index.js.map +1 -1
  112. package/lib/drivers/subprocess/index.d.ts.map +1 -1
  113. package/lib/drivers/subprocess/index.js +6 -0
  114. package/lib/drivers/subprocess/index.js.map +1 -1
  115. package/lib/index.d.ts +10 -6
  116. package/lib/index.d.ts.map +1 -1
  117. package/lib/index.js +10 -6
  118. package/lib/index.js.map +1 -1
  119. package/lib/orchestrator/annotation-store.d.ts.map +1 -1
  120. package/lib/orchestrator/annotation-store.js +6 -2
  121. package/lib/orchestrator/annotation-store.js.map +1 -1
  122. package/lib/orchestrator/case-extensions.d.ts +6 -0
  123. package/lib/orchestrator/case-extensions.d.ts.map +1 -0
  124. package/lib/orchestrator/case-extensions.js +28 -0
  125. package/lib/orchestrator/case-extensions.js.map +1 -0
  126. package/lib/orchestrator/compare.d.ts +13 -1
  127. package/lib/orchestrator/compare.d.ts.map +1 -1
  128. package/lib/orchestrator/compare.js +12 -0
  129. package/lib/orchestrator/compare.js.map +1 -1
  130. package/lib/orchestrator/config.d.ts.map +1 -1
  131. package/lib/orchestrator/config.js +0 -2
  132. package/lib/orchestrator/config.js.map +1 -1
  133. package/lib/orchestrator/doctor.d.ts.map +1 -1
  134. package/lib/orchestrator/doctor.js +5 -3
  135. package/lib/orchestrator/doctor.js.map +1 -1
  136. package/lib/orchestrator/experiment-contracts.d.ts +19 -0
  137. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
  138. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
  139. package/lib/orchestrator/experiment-evaluation.js +17 -0
  140. package/lib/orchestrator/experiment-evaluation.js.map +1 -1
  141. package/lib/orchestrator/experiment-report.d.ts +19 -0
  142. package/lib/orchestrator/experiment-report.d.ts.map +1 -1
  143. package/lib/orchestrator/experiment-runs.d.ts +38 -0
  144. package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
  145. package/lib/orchestrator/grader-experiment.d.ts +12 -0
  146. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  147. package/lib/orchestrator/grading.d.ts +2 -1
  148. package/lib/orchestrator/grading.d.ts.map +1 -1
  149. package/lib/orchestrator/grading.js +16 -4
  150. package/lib/orchestrator/grading.js.map +1 -1
  151. package/lib/orchestrator/index.d.ts +1 -1
  152. package/lib/orchestrator/index.d.ts.map +1 -1
  153. package/lib/orchestrator/index.js +1 -1
  154. package/lib/orchestrator/index.js.map +1 -1
  155. package/lib/orchestrator/list-runs.d.ts +7 -1
  156. package/lib/orchestrator/list-runs.d.ts.map +1 -1
  157. package/lib/orchestrator/list-runs.js +50 -6
  158. package/lib/orchestrator/list-runs.js.map +1 -1
  159. package/lib/orchestrator/loader.d.ts.map +1 -1
  160. package/lib/orchestrator/loader.js +2 -0
  161. package/lib/orchestrator/loader.js.map +1 -1
  162. package/lib/orchestrator/orchestrator.d.ts +3 -0
  163. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  164. package/lib/orchestrator/orchestrator.js +42 -14
  165. package/lib/orchestrator/orchestrator.js.map +1 -1
  166. package/lib/orchestrator/owned-lock.d.ts.map +1 -1
  167. package/lib/orchestrator/owned-lock.js +52 -38
  168. package/lib/orchestrator/owned-lock.js.map +1 -1
  169. package/lib/orchestrator/regrade.d.ts +6 -0
  170. package/lib/orchestrator/regrade.d.ts.map +1 -1
  171. package/lib/orchestrator/regrade.js +8 -4
  172. package/lib/orchestrator/regrade.js.map +1 -1
  173. package/lib/orchestrator/run-execution.d.ts +2 -1
  174. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  175. package/lib/orchestrator/run-execution.js +20 -5
  176. package/lib/orchestrator/run-execution.js.map +1 -1
  177. package/lib/orchestrator/suite-compare.d.ts +34 -0
  178. package/lib/orchestrator/suite-compare.d.ts.map +1 -1
  179. package/lib/orchestrator/suite-summary.d.ts +68 -0
  180. package/lib/orchestrator/suite-summary.d.ts.map +1 -1
  181. package/lib/orchestrator/suite-summary.js +45 -7
  182. package/lib/orchestrator/suite-summary.js.map +1 -1
  183. package/lib/previewer/artifacts.d.ts +25 -0
  184. package/lib/previewer/artifacts.d.ts.map +1 -0
  185. package/lib/previewer/artifacts.js +180 -0
  186. package/lib/previewer/artifacts.js.map +1 -0
  187. package/lib/previewer/server.d.ts +105 -0
  188. package/lib/previewer/server.d.ts.map +1 -0
  189. package/lib/previewer/server.js +542 -0
  190. package/lib/previewer/server.js.map +1 -0
  191. package/package.json +2 -2
  192. package/skills/lux/SKILL.md +7 -3
  193. package/skills/lux/references/cli.md +25 -2
  194. package/skills/lux/references/eval-authoring.md +158 -4
  195. package/skills/lux-answerer/SKILL.md +1 -1
  196. package/src/answerers/persona/index.ts +21 -4
  197. package/src/answerers/terminal/index.ts +1 -1
  198. package/src/assertions/authoring.ts +81 -0
  199. package/src/assertions/core/helpers.ts +3 -6
  200. package/src/assertions/core/index.ts +2 -0
  201. package/src/assertions/core/resource-checks.ts +35 -0
  202. package/src/authoring.ts +1 -0
  203. package/src/cli/program.ts +14 -93
  204. package/src/cli/render/reporter.ts +18 -3
  205. package/src/cli/run-options.ts +1 -0
  206. package/src/cli/view.ts +168 -0
  207. package/src/core/action-events.ts +59 -0
  208. package/src/core/agent-activity.ts +236 -0
  209. package/src/core/agent-events.ts +17 -0
  210. package/src/core/artifact-evidence.ts +47 -4
  211. package/src/core/assertion.ts +41 -22
  212. package/src/core/case-authoring-state.ts +65 -0
  213. package/src/core/case.ts +57 -6
  214. package/src/core/driver-metrics.ts +51 -0
  215. package/src/core/driver.ts +3 -0
  216. package/src/core/events.ts +14 -2
  217. package/src/core/experiment.ts +2 -0
  218. package/src/core/index.ts +4 -0
  219. package/src/core/lifecycle-fixtures.ts +7 -2
  220. package/src/core/lifecycle-hooks.ts +16 -2
  221. package/src/core/run.ts +9 -0
  222. package/src/core/wire.ts +5 -1
  223. package/src/drivers/claude-code/parse-events.ts +98 -12
  224. package/src/drivers/codex/app-events.ts +116 -16
  225. package/src/drivers/codex/index.ts +16 -14
  226. package/src/drivers/subprocess/index.ts +6 -0
  227. package/src/index.ts +50 -6
  228. package/src/orchestrator/annotation-store.ts +5 -1
  229. package/src/orchestrator/case-extensions.ts +44 -0
  230. package/src/orchestrator/compare.ts +18 -0
  231. package/src/orchestrator/config.ts +0 -2
  232. package/src/orchestrator/doctor.ts +5 -3
  233. package/src/orchestrator/experiment-evaluation.ts +23 -0
  234. package/src/orchestrator/grading.ts +21 -2
  235. package/src/orchestrator/index.ts +1 -0
  236. package/src/orchestrator/list-runs.ts +66 -5
  237. package/src/orchestrator/loader.ts +2 -0
  238. package/src/orchestrator/orchestrator.ts +86 -40
  239. package/src/orchestrator/owned-lock.ts +54 -34
  240. package/src/orchestrator/regrade.ts +8 -7
  241. package/src/orchestrator/run-execution.ts +24 -3
  242. package/src/orchestrator/suite-summary.ts +101 -8
  243. package/src/previewer/artifacts.ts +221 -0
  244. package/src/previewer/server.ts +765 -0
  245. package/viewer/app.css +2075 -0
  246. package/viewer/app.js +606 -0
  247. package/viewer/artifact-view.js +48 -0
  248. package/viewer/charts.js +86 -0
  249. package/viewer/detail.js +1214 -0
  250. package/viewer/format.js +129 -0
  251. package/viewer/index.html +151 -0
  252. package/lib/answerers/scripted/index.d.ts +0 -8
  253. package/lib/answerers/scripted/index.d.ts.map +0 -1
  254. package/lib/answerers/scripted/index.js +0 -163
  255. package/lib/answerers/scripted/index.js.map +0 -1
  256. package/src/answerers/scripted/index.ts +0 -186
@@ -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...]", { isDefault: true })
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
- return view.assertions.length === 0 ? "—" : `${passed}/${view.assertions.length}`;
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
- if (assertions.length > 0) {
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
  }
@@ -8,6 +8,7 @@ export type RunOptions = {
8
8
  listTags?: boolean;
9
9
  interactive?: boolean;
10
10
  claudeAnswerer?: boolean;
11
+ skipJudge?: boolean;
11
12
  runsRoot?: string;
12
13
  profile?: string;
13
14
  subjectRoot?: string;
@@ -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
+ };