@axlsdk/eval 0.22.3 → 0.23.0

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/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  rescore,
7
7
  runEval,
8
8
  scorerCounts
9
- } from "./chunk-C7CICWIA.js";
9
+ } from "./chunk-FQHKBSZD.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import { readdirSync, statSync } from "fs";
@@ -161,6 +161,24 @@ function parseEvalArgs(args) {
161
161
  };
162
162
  }
163
163
 
164
+ // src/cli-format.ts
165
+ function formatModelTimingLines(modelTiming, nameWidth) {
166
+ if (!modelTiming) return [];
167
+ return Object.entries(modelTiming).map(([model, t]) => {
168
+ const parts = [`wire ${formatMs(t.wireMs.mean)}/${formatMs(t.wireMs.p95)}`];
169
+ if (t.firstTokenMs) {
170
+ parts.push(`first token ${formatMs(t.firstTokenMs.mean)}/${formatMs(t.firstTokenMs.p95)}`);
171
+ }
172
+ parts.push(`queued ${formatMs(t.queuedMs.mean)}`);
173
+ parts.push(`retries ${formatMs(t.retryMs.mean)}`);
174
+ const calls = `${t.calls} call${t.calls === 1 ? "" : "s"}`;
175
+ return ` ${model.padEnd(Math.max(nameWidth - 2, 0))} ${parts.join(" \xB7 ")} (${calls}, mean/p95 per call)`;
176
+ });
177
+ }
178
+ function formatMs(ms) {
179
+ return `${Math.round(ms)}ms`;
180
+ }
181
+
164
182
  // src/cli.ts
165
183
  function refuseToGate(reason) {
166
184
  console.error(`[axl-eval] Refusing to gate: ${reason}`);
@@ -521,6 +539,7 @@ function formatTable(result) {
521
539
  ` ${"Timing".padEnd(maxNameLen)} ${(t.mean / 1e3).toFixed(2).padStart(colWidth)}s ${(t.p50 / 1e3).toFixed(2).padStart(colWidth)}s ${(t.p95 / 1e3).toFixed(2).padStart(colWidth)}s`
522
540
  );
523
541
  }
542
+ lines.push(...formatModelTimingLines(result.summary.modelTiming, maxNameLen));
524
543
  const durationSec = (result.duration / 1e3).toFixed(1);
525
544
  const costStr = result.totalCost > 0 ? `$${result.totalCost.toFixed(2)}` : "$0.00";
526
545
  lines.push("");
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/cli-utils.ts","../src/cli-validate.ts","../src/cli-args.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { readFile as readFileAsync, writeFile as writeFileAsync, mkdir } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { AxlRuntime, EvalExecuteWorkflow } from '@axlsdk/axl';\nimport { evalCompare, evaluateScorerErrorRateGate } from './compare.js';\nimport { runEval } from './runner.js';\nimport { rescore } from './rescore.js';\nimport { aggregateRuns } from './multi-run.js';\nimport type { MultiRunSummary } from './multi-run.js';\nimport type { EvalConfig, EvalResult } from './types.js';\nimport {\n findConfig,\n resolveRuntime,\n importModule,\n registerConditions,\n pickDefault,\n pickExport,\n expandGlob,\n CONFIG_CANDIDATES,\n} from './cli-utils.js';\nimport { validateEvalConfig } from './cli-validate.js';\nimport { parseEvalArgs, envInt } from './cli-args.js';\nimport { scorerCounts } from './utils.js';\n\n/**\n * Refuse to certify a comparison and exit non-zero, with one consistent\n * message format. Routes both the scorer-filtered guard and the\n * `--max-scorer-error-rate` failure-rate gate through a single path so the\n * \"why CI failed\" line always reads the same.\n */\nfunction refuseToGate(reason: string): never {\n console.error(`[axl-eval] Refusing to gate: ${reason}`);\n process.exit(1);\n}\n\n/**\n * Read a scorer's `scored`/`failed` counts from a summary entry, falling back\n * to recomputing from `items` when the fields are absent (pre-0.18.0\n * artifacts). `items` is omitted for multi-run summaries that carry no items —\n * there the fields are authoritative (summed by `aggregateRuns`).\n */\nfunction scorerSampleCounts(\n entry: { scored?: number; failed?: number; skipped?: number },\n name: string,\n items?: EvalResult['items'],\n): { scored: number; failed: number; skipped: number } {\n if (entry.scored != null && entry.failed != null) {\n // `scored`/`failed` are authoritative; `skipped` was added later, so an\n // 0.18.0-era artifact may carry the first two without it — recompute that one\n // field from items when we can, else 0.\n const skipped = entry.skipped ?? (items ? scorerCounts(items, name).skipped : 0);\n return { scored: entry.scored, failed: entry.failed, skipped };\n }\n if (items) return scorerCounts(items, name);\n return { scored: entry.scored ?? 0, failed: entry.failed ?? 0, skipped: entry.skipped ?? 0 };\n}\n\n/** Install a SIGINT/SIGTERM handler that aborts an in-flight eval gracefully.\n * First signal: aborts the controller so runEval / rescore propagate cancellation\n * through ScorerContext.signal into in-flight provider.chat calls. The eval\n * unwinds normally and runtime.shutdown() runs in the `finally` block.\n * Second signal: hard exit — the user has signalled twice; cleanup is hung\n * or taking too long, so we abandon gracefulness rather than appear frozen. */\nfunction installAbortHandler(controller: AbortController): void {\n let signalled = false;\n const onSignal = (sig: NodeJS.Signals) => {\n if (signalled) {\n console.error(`\\n[axl-eval] Second ${sig} — forcing exit.`);\n process.exit(130);\n }\n signalled = true;\n console.error(`\\n[axl-eval] ${sig} received — cancelling eval (press again to force exit)...`);\n controller.abort();\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n\n if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {\n console.log(`\nUsage:\n axl-eval <path> Run eval file(s)\n axl-eval <path> --runs <n> Run eval N times (multi-run)\n axl-eval <path> --output <file> Save results to JSON\n axl-eval <path> --config <file> Use config file for runtime\n axl-eval <path> --conditions <list> Node.js import conditions (comma-separated)\n axl-eval <path> --concurrency <n> Override item concurrency (flag > env\n AXL_EVAL_CONCURRENCY > config > 5).\n Max concurrent scorer calls is\n concurrency x scorerConcurrency (the\n per-eval scorerConcurrency defaults to 5).\n axl-eval <path> --scorers <a,b> Run only the named scorers (single eval\n file only). Stamps the result as a\n subset so compare won't treat it as a\n full baseline.\n axl-eval <path> --capture-traces Populate EvalItem.traces on every item\n (success + failure). Adds memory\n overhead proportional to dataset size\n x turns x agents; off by default.\n axl-eval rescore <results> <eval-file> Re-run scorers on saved outputs\n axl-eval compare <a> <b> Compare two eval result files\n axl-eval compare <a> <b> --threshold <v> Set regression threshold (global or per-scorer)\n axl-eval compare <a> <b> --fail-on-regression Exit 1 if regressions\n axl-eval compare <a> <b> --max-scorer-error-rate <0..1> Exit 1 if a scorer's\n failure rate exceeds the limit on\n either side (deterministic scorers\n tolerate zero failures). Always warns\n when either side rests on a thinned\n sample, even without this flag.\n\nConfig auto-detection (when --config is not specified):\n ${CONFIG_CANDIDATES.join(' -> ')}\n\nWhen a config is found, the exported AxlRuntime is passed to executeWorkflow\nand cost is tracked automatically via runtime.trackCost().\nWhen no config is found, a bare AxlRuntime is created (providers from env vars).\n`);\n process.exit(0);\n }\n\n if (args[0] === 'compare') {\n // `compare` is pure computation — no LLM calls, no AbortSignal plumbing needed.\n await runCompare(args.slice(1));\n return;\n }\n\n // SIGINT/SIGTERM aborts in-flight LLM calls (eval + rescore) instead of leaving\n // the process to hard-exit mid-request, which would skip runtime.shutdown() and\n // leak streaming-buffer state.\n const controller = new AbortController();\n installAbortHandler(controller);\n\n if (args[0] === 'rescore') {\n await runRescore(args.slice(1), controller.signal);\n return;\n }\n\n await runEvalCommand(args, controller.signal);\n}\n\nfunction parseThresholdArg(args: string[]): Record<string, number> | number | undefined {\n const idx = args.indexOf('--threshold');\n if (idx === -1 || idx + 1 >= args.length) return undefined;\n const value = args[idx + 1];\n\n // Global numeric threshold: --threshold 0.05\n if (/^[\\d.]+$/.test(value)) return parseFloat(value);\n\n // Per-scorer map: --threshold accuracy=0,tone=0.1\n const map: Record<string, number> = {};\n for (const pair of value.split(',')) {\n const [key, val] = pair.split('=');\n if (key && val != null) map[key.trim()] = parseFloat(val.trim());\n }\n return Object.keys(map).length > 0 ? map : undefined;\n}\n\n/**\n * Parse `--max-scorer-error-rate <0..1>`. Returns the numeric limit, or\n * `undefined` when the flag is absent. Exits non-zero on a malformed value\n * (fail loud — a typo'd gate threshold must not silently disable the gate).\n */\nfunction parseMaxScorerErrorRate(args: string[]): number | undefined {\n const idx = args.indexOf('--max-scorer-error-rate');\n if (idx === -1) return undefined;\n if (idx + 1 >= args.length) {\n console.error('Error: --max-scorer-error-rate requires a value in [0, 1]');\n process.exit(1);\n }\n const raw = args[idx + 1];\n // Strict numeric parse — `parseFloat('0.5abc')` returns 0.5 and `parseFloat('0,5')`\n // returns 0, both of which would silently set an unintended gate threshold. Reject\n // anything that isn't a clean decimal (matches `parseThresholdArg`'s guard).\n const n = /^\\d*\\.?\\d+$/.test(raw.trim()) ? Number(raw) : NaN;\n if (!Number.isFinite(n) || n < 0 || n > 1) {\n console.error(`Error: --max-scorer-error-rate must be a number in [0, 1], got \"${raw}\"`);\n process.exit(1);\n }\n return n;\n}\n\n/**\n * If a raw compare input (single result or multi-run array) was produced by a\n * `--scorers` filtered run, return the scorer names it ran; otherwise undefined.\n * Reads the first run's metadata — a multi-run group shares the same stamp.\n */\nfunction scorerFilteredScorers(r: EvalResult | EvalResult[]): string[] | undefined {\n const first = Array.isArray(r) ? r[0] : r;\n const meta = first?.metadata;\n if (!meta || meta.scorerFiltered !== true) return undefined;\n return Array.isArray(meta.scorersRun) ? (meta.scorersRun as string[]) : [];\n}\n\nasync function runCompare(args: string[]) {\n const failOnRegression = args.includes('--fail-on-regression');\n const thresholds = parseThresholdArg(args);\n const maxScorerErrorRate = parseMaxScorerErrorRate(args);\n // Exclude flags and the values consumed by value-taking flags so neither a\n // threshold nor an error-rate value is mistaken for a result file path.\n const valueFlags = new Set(['--threshold', '--max-scorer-error-rate']);\n const files = args.filter(\n (a, i) => !a.startsWith('--') && !(i > 0 && valueFlags.has(args[i - 1])),\n );\n\n if (files.length !== 2) {\n console.error(\n 'Usage: axl-eval compare <baseline.json> <candidate.json> [--threshold <value>] [--fail-on-regression] [--max-scorer-error-rate <0..1>]',\n );\n process.exit(1);\n }\n\n const baselineRaw = JSON.parse(await readFileAsync(files[0], 'utf-8'));\n const candidateRaw = JSON.parse(await readFileAsync(files[1], 'utf-8'));\n const baseline: EvalResult | EvalResult[] = baselineRaw;\n const candidate: EvalResult | EvalResult[] = candidateRaw;\n\n // A scorer-filtered result (CLI `--scorers`) tested only a subset of scorers,\n // so it isn't a sound baseline/candidate for a full comparison. Check this\n // BEFORE evalCompare(): a subset-vs-full comparison has mismatched scorer sets\n // and evalCompare() would throw a generic \"different scorers\" error that never\n // mentions --scorers. Warn loudly here so the cause is actionable; under\n // `--fail-on-regression`, refuse outright (a swallowed stderr line wouldn't\n // actually prevent the footgun).\n const filteredSides = (\n [\n ['baseline', baseline],\n ['candidate', candidate],\n ] as const\n ).filter(([, r]) => scorerFilteredScorers(r) != null);\n for (const [side, r] of filteredSides) {\n const ran = scorerFilteredScorers(r);\n const ranStr = ran && ran.length ? ` (ran: ${ran.join(', ')})` : '';\n console.error(\n `[axl-eval] WARNING: ${side} was run with --scorers — it tested only a subset${ranStr}, so this comparison is incomplete.`,\n );\n }\n if (failOnRegression && filteredSides.length > 0) {\n refuseToGate(\n `${filteredSides.map(([s]) => s).join(', ')} was run with --scorers (a scorer subset); re-run without --scorers for a complete baseline.`,\n );\n }\n\n const compareOptions = thresholds != null ? { thresholds } : undefined;\n const comparison = evalCompare(baseline, candidate, compareOptions);\n\n console.log(\n `\\nCompare: baseline (${comparison.baseline.id.slice(0, 8)}) -> candidate (${comparison.candidate.id.slice(0, 8)})\\n`,\n );\n\n const scorerNames = Object.keys(comparison.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 6);\n const hasCI = scorerNames.some((n) => comparison.scorers[n].ci != null);\n\n const ciHeader = hasCI ? ' CI 95% Sig' : '';\n const ciRule = hasCI ? ' ---------------- ---' : '';\n console.log(` ${'Scorer'.padEnd(maxNameLen)} Baseline Candidate Delta Change${ciHeader}`);\n console.log(` ${''.padEnd(maxNameLen, '-')} -------- --------- -------- ------${ciRule}`);\n\n for (const name of scorerNames) {\n const s = comparison.scorers[name];\n const sign = s.delta > 0 ? '+' : '';\n let line = ` ${name.padEnd(maxNameLen)} ${s.baselineMean.toFixed(3).padStart(8)} ${s.candidateMean.toFixed(3).padStart(9)} ${(sign + s.delta.toFixed(3)).padStart(8)} ${(sign + s.deltaPercent.toFixed(1) + '%').padStart(6)}`;\n if (hasCI) {\n if (s.ci) {\n const lo = (s.ci.lower >= 0 ? '+' : '') + s.ci.lower.toFixed(4);\n const hi = (s.ci.upper >= 0 ? '+' : '') + s.ci.upper.toFixed(4);\n const sig = s.significant ? ' *' : ' ';\n line += ` [${lo}, ${hi}]${sig}`;\n } else {\n line += ' —'.padEnd(22);\n }\n }\n console.log(line);\n }\n\n if (comparison.timing) {\n const t = comparison.timing;\n const sign = t.delta > 0 ? '+' : '';\n console.log(\n `\\n Timing: baseline ${(t.baselineMean / 1000).toFixed(2)}s -> candidate ${(t.candidateMean / 1000).toFixed(2)}s (${sign}${t.deltaPercent.toFixed(1)}%)`,\n );\n }\n if (comparison.cost) {\n const c = comparison.cost;\n const sign = c.delta > 0 ? '+' : '';\n console.log(\n ` Cost: baseline $${c.baselineTotal.toFixed(2)} -> candidate $${c.candidateTotal.toFixed(2)} (${sign}${c.deltaPercent.toFixed(1)}%)`,\n );\n }\n\n const baselineRef = Array.isArray(baseline) ? baseline[0] : baseline;\n const stable = Math.max(\n 0,\n baselineRef.items.length - comparison.regressions.length - comparison.improvements.length,\n );\n console.log(\n `\\n Regressions: ${comparison.regressions.length} | Improvements: ${comparison.improvements.length} | Stable: ${stable}\\n`,\n );\n\n // Scorer failure-rate signal. Always WARN loudly when either side rests on a\n // thinned sample (a scorer ran and failed on some items) — a regression check\n // computed over survivors can look green while half the judges errored. Only\n // REFUSE to gate when the user opted in with `--max-scorer-error-rate` AND a\n // scorer is over tolerance (type-aware: deterministic scorers tolerate zero\n // failures, LLM judges use the flag value), or a scorer produced no scored\n // items at all on a side (a zero-sample mean can't be certified).\n let sawThinnedSample = false;\n for (const name of scorerNames) {\n const s = comparison.scorers[name];\n const bFailed = s.baselineFailed ?? 0;\n const cFailed = s.candidateFailed ?? 0;\n if (bFailed > 0 || cFailed > 0) {\n sawThinnedSample = true;\n const bAtt = (s.baselineScored ?? 0) + bFailed;\n const cAtt = (s.candidateScored ?? 0) + cFailed;\n console.error(\n `[axl-eval] WARNING: scorer \"${name}\" failed on some items ` +\n `(baseline ${bFailed}/${bAtt}, candidate ${cFailed}/${cAtt}); ` +\n `the means above are computed over the surviving sample.`,\n );\n }\n // Conditional scorers (`applies`) skip N/A items. Skips never move the\n // delta (excluded from both means), but they make the two per-side means\n // cover different applicable subsets when the sides skipped different COUNTS,\n // OR the same count of DIFFERENT items — the latter shows up as a paired\n // shortfall: fewer items scored on BOTH sides (`n`) than either side scored\n // alone. (`bSkipped !== cSkipped` alone would miss the disjoint-but-equal\n // case.) Note it (advisory only; not a gate; failures are covered by the\n // separate WARNING above).\n const bSkipped = s.baselineSkipped ?? 0;\n const cSkipped = s.candidateSkipped ?? 0;\n const pairedN = s.n ?? 0;\n const minScored = Math.min(s.baselineScored ?? 0, s.candidateScored ?? 0);\n const skipsDiverge =\n (bSkipped > 0 || cSkipped > 0) && (bSkipped !== cSkipped || pairedN < minScored);\n if (skipsDiverge) {\n console.error(\n `[axl-eval] NOTE: scorer \"${name}\" applies to different subsets per side ` +\n `(baseline ${bSkipped} N/A, candidate ${cSkipped} N/A; ${pairedN} item(s) scored on both); ` +\n `the two means cover different applicable subsets, so compare the delta with care.`,\n );\n }\n }\n // Tell the user how to make the advisory warning blocking (when they haven't).\n if (sawThinnedSample && maxScorerErrorRate == null) {\n console.error(\n `[axl-eval] (advisory — pass --max-scorer-error-rate <0..1> to fail CI on a thinned sample)`,\n );\n }\n\n // Gate-side refusal (opt-in). The decision lives in evaluateScorerErrorRateGate\n // (pure + unit-tested) so the same type-aware tolerance + zero-sample logic\n // isn't re-implemented here and at the source-side gate in runEval.\n if (maxScorerErrorRate != null) {\n const reason = evaluateScorerErrorRateGate(comparison, maxScorerErrorRate);\n if (reason) refuseToGate(reason);\n }\n\n if (failOnRegression && comparison.regressions.length > 0) {\n // When CI is available, only fail on significant regressions\n const hasSignificance = scorerNames.some((n) => comparison.scorers[n].significant != null);\n if (hasSignificance) {\n const hasSignificantRegression = scorerNames.some(\n (n) => comparison.scorers[n].significant === true && comparison.scorers[n].delta < 0,\n );\n if (hasSignificantRegression) process.exit(1);\n } else {\n process.exit(1);\n }\n }\n}\n\nasync function runRescore(args: string[], signal: AbortSignal) {\n const { outputPath, configArg, conditions, concurrency, scorerNames, paths } =\n parseEvalArgs(args);\n\n if (paths.length < 2) {\n console.error('Usage: axl-eval rescore <results.json> <eval-file> [--output <file>]');\n process.exit(1);\n }\n\n // --scorers is a run-command-only filter — rescore always re-runs the full\n // scorer set from the eval file. Fail loudly rather than silently ignoring it\n // (it parses as a known flag, so it wouldn't trip the unknown-flag guard).\n if (scorerNames?.length) {\n console.error(\n 'Error: --scorers is not supported with rescore; pass a scorer subset in the eval file instead.',\n );\n process.exit(1);\n }\n\n // Honor the same item-concurrency override as the run command (flag > env).\n // scorerConcurrency keeps its rescore default (5).\n const itemConcurrency = concurrency ?? envInt('AXL_EVAL_CONCURRENCY');\n\n const [resultsPath, evalFilePath] = paths;\n const raw = JSON.parse(await readFileAsync(resultsPath, 'utf-8'));\n const results: EvalResult[] = Array.isArray(raw) ? raw : [raw];\n\n const runtime = await getRuntime(configArg, conditions);\n try {\n const mod = await importModule(path.resolve(evalFilePath), import.meta.url);\n const evalConfig = pickDefault<EvalConfig>(mod);\n\n if (!evalConfig || typeof evalConfig !== 'object') {\n console.error(\n `Error: ${evalFilePath} does not export an eval config object — got ${evalConfig === null ? 'null' : typeof evalConfig}.`,\n );\n process.exit(1);\n }\n if (!Array.isArray(evalConfig.scorers) || evalConfig.scorers.length === 0) {\n console.error(`Error: ${evalFilePath} does not export a non-empty scorers array.`);\n process.exit(1);\n }\n\n const rescored: EvalResult[] = [];\n for (const resultData of results) {\n if (signal.aborted) break;\n rescored.push(\n await rescore(resultData, evalConfig.scorers, runtime, {\n signal,\n ...(itemConcurrency != null ? { concurrency: itemConcurrency } : {}),\n }),\n );\n }\n\n for (const r of rescored) {\n console.log('\\n' + formatTable(r) + '\\n');\n }\n\n if (outputPath) {\n const output = Array.isArray(raw) ? rescored : rescored[0];\n const outputDir = path.dirname(path.resolve(outputPath));\n await mkdir(outputDir, { recursive: true });\n await writeFileAsync(outputPath, JSON.stringify(output, null, 2), 'utf-8');\n console.log(`Rescored results saved to ${outputPath}`);\n }\n } finally {\n await runtime.shutdown().catch(() => {});\n }\n}\n\n/**\n * Resolve a CLI path argument into a list of eval files.\n *\n * Accepts:\n * - An explicit file path (`path/to/foo.eval.ts`)\n * - A directory (lists `*.eval.[mc]?[jt]sx?` inside, non-recursive)\n * - A glob pattern: `evals/*.eval.ts`, `evals/**\\/*.eval.ts`, `**\\/*.eval.ts`\n *\n * Glob expansion mirrors the studio eval-loader's so users get the same\n * behavior in both places. Without it, users running on Windows or with\n * quoted patterns hit confusing \"file not found\" errors when the shell\n * couldn't expand the glob.\n */\nfunction collectEvalFiles(p: string): string[] {\n if (p.includes('*')) return expandGlob(p, process.cwd());\n const resolved = path.resolve(p);\n try {\n const stat = statSync(resolved);\n if (stat.isDirectory()) {\n const entries = readdirSync(resolved);\n return entries\n .filter((e) => /\\.eval\\.[mc]?[jt]sx?$/.test(e))\n .map((e) => path.join(resolved, e));\n }\n return [resolved];\n } catch {\n return [resolved];\n }\n}\n\n/** Render observed workflows for a terse header line. Multiple → comma-separated. */\nfunction formatWorkflows(workflows: unknown): string {\n if (!Array.isArray(workflows) || workflows.length === 0) return '(unknown)';\n return (workflows as unknown[]).filter((w): w is string => typeof w === 'string').join(', ');\n}\n\nfunction formatTable(result: EvalResult): string {\n const lines: string[] = [];\n const scorerNames = Object.keys(result.summary.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 'Scorer'.length);\n const colWidth = 8;\n\n lines.push(\n `Eval: ${formatWorkflows(result.metadata.workflows)} x ${result.dataset} (${result.summary.count} items)`,\n );\n lines.push(\n ` ${'Scorer'.padEnd(maxNameLen)} ${'Mean'.padStart(colWidth)} ${'Min'.padStart(colWidth)} ${'Max'.padStart(colWidth)} ${'p50'.padStart(colWidth)} ${'p95'.padStart(colWidth)}`,\n );\n\n const ruleLen = maxNameLen + 2 + (colWidth + 2) * 5;\n lines.push(' ' + '\\u2500'.repeat(ruleLen));\n\n for (const name of scorerNames) {\n const s = result.summary.scorers[name];\n const { scored, failed, skipped } = scorerSampleCounts(s, name, result.items);\n // Loud trailing annotation: `failed > 0` means the mean rests on a thinned\n // sample; `skipped > 0` (an `applies`-conditional scorer) labels items the\n // scorer deliberately didn't run — crucial so a fully-skipped scorer (all\n // `--`) reads as \"N/A\" rather than \"broken\". Both shown only when non-zero so\n // clean, unconditional runs stay uncluttered.\n const annotationParts: string[] = [];\n if (failed > 0) annotationParts.push(`${scored}/${scored + failed} scored, ${failed} failed`);\n if (skipped > 0) annotationParts.push(`${skipped} N/A`);\n const annotation = annotationParts.length > 0 ? ` (${annotationParts.join(', ')})` : '';\n // Detect scorers with no valid scores (all items errored → computeStats([]) → all zeros)\n if (scored === 0) {\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)}${annotation}`,\n );\n } else {\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${s.mean.toFixed(2).padStart(colWidth)} ${s.min.toFixed(2).padStart(colWidth)} ${s.max.toFixed(2).padStart(colWidth)} ${s.p50.toFixed(2).padStart(colWidth)} ${s.p95.toFixed(2).padStart(colWidth)}${annotation}`,\n );\n }\n }\n\n if (result.summary.timing) {\n const t = result.summary.timing;\n lines.push(\n ` ${'Timing'.padEnd(maxNameLen)} ${(t.mean / 1000).toFixed(2).padStart(colWidth)}s ${(t.p50 / 1000).toFixed(2).padStart(colWidth)}s ${(t.p95 / 1000).toFixed(2).padStart(colWidth)}s`,\n );\n }\n\n const durationSec = (result.duration / 1000).toFixed(1);\n const costStr = result.totalCost > 0 ? `$${result.totalCost.toFixed(2)}` : '$0.00';\n lines.push('');\n lines.push(\n ` Failures: ${result.summary.failures}/${result.summary.count} | Cost: ${costStr} | Duration: ${durationSec}s`,\n );\n\n const itemsWithErrors = result.items.filter((i) => i.scorerErrors?.length);\n if (itemsWithErrors.length > 0) {\n const uniqueErrors = [...new Set(itemsWithErrors.flatMap((i) => i.scorerErrors!))];\n lines.push('');\n lines.push(\n ` Scorer errors (${itemsWithErrors.length}/${result.summary.count} items affected):`,\n );\n for (const err of uniqueErrors.slice(0, 5)) {\n lines.push(` - ${err}`);\n }\n if (uniqueErrors.length > 5) {\n lines.push(` ... and ${uniqueErrors.length - 5} more`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction formatMultiRunTable(summary: MultiRunSummary): string {\n const lines: string[] = [];\n const scorerNames = Object.keys(summary.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 'Scorer'.length);\n const colWidth = 16;\n\n lines.push(\n `Eval: ${formatWorkflows(summary.workflows)} x ${summary.dataset} \\u2014 ${summary.runCount} runs`,\n );\n lines.push(\n ` ${'Scorer'.padEnd(maxNameLen)} ${'Mean \\u00b1 Std'.padStart(colWidth)} ${'Min'.padStart(8)} ${'Max'.padStart(8)}`,\n );\n const ruleLen = maxNameLen + 2 + colWidth + 2 + 8 + 2 + 8;\n lines.push(' ' + '\\u2500'.repeat(ruleLen));\n\n for (const name of scorerNames) {\n const s = summary.scorers[name];\n const meanStd = `${s.mean.toFixed(3)} \\u00b1 ${s.std.toFixed(3)}`;\n // Multi-run summaries have no items, so the summed scored/failed fields are\n // authoritative (no fallback recompute possible). Annotate when failed > 0.\n const { scored, failed } = scorerSampleCounts(s, name);\n const annotation =\n failed > 0 ? ` (${scored}/${scored + failed} scored, ${failed} failed)` : '';\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${meanStd.padStart(colWidth)} ${s.min.toFixed(3).padStart(8)} ${s.max.toFixed(3).padStart(8)}${annotation}`,\n );\n }\n\n if (summary.timing) {\n lines.push(\n ` ${'Timing'.padEnd(maxNameLen)} ${((summary.timing.mean / 1000).toFixed(2) + ' \\u00b1 ' + (summary.timing.std / 1000).toFixed(2) + 's').padStart(colWidth)}`,\n );\n }\n\n const costStr = summary.totalCost > 0 ? `$${summary.totalCost.toFixed(2)}` : '$0.00';\n const durationStr = (summary.totalDuration / 1000).toFixed(1);\n lines.push('');\n lines.push(` Total Cost: ${costStr} | Total Duration: ${durationStr}s`);\n\n return lines.join('\\n');\n}\n\n/**\n * Print each degraded scorer (set by `runEval` when `failOnScorerErrorRate`\n * tripped) to stderr and return whether the run was degraded, so the caller can\n * count the file as failed (→ non-zero exit). Quiet when nothing degraded.\n */\nfunction reportDegraded(result: EvalResult, label: string): boolean {\n const d = result.summary.degraded;\n if (!d || d.length === 0) return false;\n for (const x of d) {\n const attempted = x.scored + x.failed;\n const limitStr =\n x.type === 'deterministic'\n ? 'deterministic zero-tolerance'\n : `${(x.limit * 100).toFixed(1)}% limit`;\n console.error(\n `[axl-eval] DEGRADED: ${label} scorer \"${x.scorer}\" (${x.type}) failed ` +\n `${(x.rate * 100).toFixed(1)}% (${x.failed}/${attempted}), over the ${limitStr}.`,\n );\n }\n return true;\n}\n\n/**\n * Advisory (non-fatal): a conditional scorer (`applies`) that ended up applicable\n * to ZERO items — every item was skipped as N/A, nothing scored or failed. Its\n * mean is a meaningless empty-sample 0 and the failure-rate gate can't assess it\n * (a zero-sample scorer is never `degraded`). That's almost always a\n * misconfiguration: an over-strict predicate, a dataset missing the flagged\n * items, or a predicate that forgot to `return`. The `(N N/A)` table annotation\n * hints at it, but a fully-skipped GATE scorer would otherwise exit 0 with no\n * stderr signal — this closes that silent-green hole without changing the exit\n * code (skips are legitimately conditional; we inform, we don't fail).\n */\nfunction reportFullySkippedScorers(result: EvalResult, label: string): void {\n for (const [name, entry] of Object.entries(result.summary.scorers)) {\n const { scored, failed, skipped } = scorerSampleCounts(entry, name, result.items);\n if (skipped > 0 && scored + failed === 0) {\n console.error(\n `[axl-eval] NOTE: ${label} scorer \"${name}\" was not applicable to any item ` +\n `(${skipped} N/A, 0 scored) — its mean is not meaningful. Check the scorer's ` +\n `\\`applies\\` predicate and dataset if you expected it to run.`,\n );\n }\n }\n}\n\n/**\n * A run where EVERY item errored in the workflow (0 succeeded) produced no valid\n * output to score — the eval is meaningless and must never pass CI green. This\n * is distinct from (and complementary to) the scorer failure-rate gate: that one\n * is about a flaky/failing *scorer*, this is about a broken *workflow*. The\n * scorer gate deliberately ignores a zero-sample scorer (nothing ran), so without\n * this guard a 100%-workflow-error run exits 0 — a silent-green trap.\n *\n * Non-configurable on purpose: a 0%-success eval is unambiguously broken, so this\n * always fails. (A configurable `failOnItemErrorRate` for PARTIAL workflow-failure\n * gating is a reasonable future opt-in; the per-item failure count is already shown\n * loudly in the table either way.) Returns whether the run was a total wipeout.\n */\nfunction reportTotalWipeout(result: EvalResult, label: string): boolean {\n const { count, failures } = result.summary;\n if (count === 0 || failures < count) return false;\n console.error(\n `[axl-eval] FAILED: ${label} — all ${count} item(s) errored in the workflow ` +\n `(0 succeeded); the eval produced no scorable output.`,\n );\n return true;\n}\n\n// ── Runtime resolution ─────────────────────────────────────────────\n\nasync function resolveRuntimeFromConfig(configPath: string): Promise<AxlRuntime> {\n try {\n const mod = await importModule(configPath, import.meta.url);\n const runtime = resolveRuntime(mod) as AxlRuntime;\n\n if (!runtime || typeof runtime.execute !== 'function') {\n console.error(`Config must export a default AxlRuntime instance.`);\n if (runtime) {\n const keys = Object.keys(runtime as object)\n .slice(0, 5)\n .join(', ');\n console.error(` Got: ${typeof runtime}${keys ? ` with keys: { ${keys} }` : ''}`);\n }\n console.error(\n `Example:\\n import { AxlRuntime } from '@axlsdk/axl';\\n export default new AxlRuntime({ ... });`,\n );\n process.exit(1);\n }\n\n return runtime;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (\n /Cannot use import statement|Unexpected reserved word|top-level await|exports is not defined/.test(\n msg,\n )\n ) {\n const ext = path.extname(configPath);\n console.error(`[axl-eval] Config failed to load due to a CJS/ESM compatibility issue.`);\n if (ext === '.ts' || ext === '.tsx') {\n console.error(\n ` Tip: try renaming to .mts to force ESM format, or ensure tsx is installed and up to date.`,\n );\n } else {\n console.error(` Tip: add \"type\": \"module\" to your package.json.`);\n }\n console.error();\n }\n console.error(`Failed to load config:`, err);\n process.exit(1);\n }\n}\n\nasync function getRuntime(configArg?: string, conditions?: string[]): Promise<AxlRuntime> {\n // Register import conditions before any config loading\n if (conditions && conditions.length > 0) {\n await registerConditions(conditions);\n }\n\n // 1. Explicit --config\n if (configArg) {\n const configPath = path.resolve(process.cwd(), configArg);\n const stat = statSync(configPath, { throwIfNoEntry: false });\n if (!stat?.isFile()) {\n console.error(`Config file not found: ${configPath}`);\n process.exit(1);\n }\n console.error(`[axl-eval] Loading config from ${configPath}`);\n return resolveRuntimeFromConfig(configPath);\n }\n\n // 2. Auto-detect\n const found = findConfig(process.cwd());\n if (found) {\n console.error(`[axl-eval] Auto-detected config: ${found}`);\n return resolveRuntimeFromConfig(found);\n }\n\n // 3. Bare runtime (providers from env vars)\n const { AxlRuntime } = await import('@axlsdk/axl');\n return new AxlRuntime();\n}\n\n// ── Main eval command ──────────────────────────────────────────────\n\nasync function runEvalCommand(args: string[], signal: AbortSignal) {\n const {\n outputPath,\n configArg,\n conditions,\n runs,\n captureTraces,\n concurrency,\n scorerNames,\n paths,\n } = parseEvalArgs(args);\n\n if (paths.length === 0) {\n console.error('Error: No eval file path provided');\n process.exit(1);\n }\n\n const evalFiles: string[] = [];\n for (const p of paths) {\n evalFiles.push(...collectEvalFiles(p));\n }\n\n if (evalFiles.length === 0) {\n console.error('Error: No eval files found');\n process.exit(1);\n }\n\n // --scorers is single-file only — filtering by name across a glob/dir of evals\n // with different scorer sets is ambiguous. Fail fast, before importing any\n // module, rather than discovering the conflict mid-loop.\n if (scorerNames?.length && evalFiles.length > 1) {\n console.error(`Error: --scorers requires a single eval file (matched ${evalFiles.length}).`);\n process.exit(1);\n }\n\n const runtime = await getRuntime(configArg, conditions);\n const results: EvalResult[] = [];\n let failedFiles = 0;\n\n try {\n for (const filePath of evalFiles) {\n // Short-circuit if the user aborted between eval files. The in-flight eval\n // unwinds on its own via runEval({ signal }); this prevents starting the next one.\n if (signal.aborted) break;\n try {\n const mod = await importModule(path.resolve(filePath), import.meta.url);\n const evalConfig = pickDefault<EvalConfig>(mod);\n\n const validationError = validateEvalConfig(evalConfig);\n if (validationError) {\n console.error(`Error: ${filePath} ${validationError}`);\n failedFiles++;\n continue;\n }\n\n // Concurrency override precedence: flag > env > config > default.\n // Mutating evalConfig in place mirrors how this loop already stamps\n // result.metadata. `concurrency` is pure scheduling — never changes\n // results — so a per-run override is always safe.\n evalConfig.concurrency =\n concurrency ?? envInt('AXL_EVAL_CONCURRENCY') ?? evalConfig.concurrency ?? 5;\n\n // --scorers: run a subset of scorers for a focused iteration loop.\n // (The single-file guard already ran before the loop.) Validate-then-\n // filter so the error lists every available name.\n if (scorerNames?.length) {\n const available = evalConfig.scorers.map((s) => s.name);\n const unknown = scorerNames.filter((n) => !available.includes(n));\n if (unknown.length) {\n console.error(\n `Error: --scorers: unknown scorer(s): ${unknown.join(', ')}. Available: ${available.join(', ')}`,\n );\n process.exit(1);\n }\n evalConfig.scorers = evalConfig.scorers.filter((s) => scorerNames.includes(s.name));\n if (evalConfig.scorers.length === 0) {\n console.error('Error: --scorers matched no scorers.');\n process.exit(1);\n }\n }\n\n // Resolve executeWorkflow: custom export > registered workflow > error\n // Walk the ESM/CJS interop chain symmetrically with the default export\n // so named exports stay visible when tsx loads the eval module as CJS.\n const exported = pickExport<unknown>(mod, 'executeWorkflow');\n\n // Validate that the export, if present, is callable. Catches the\n // `export const executeWorkflow = 'foo'` typo at the boundary instead\n // of crashing deep inside trackExecution with a confusing stack.\n let customExecute: EvalExecuteWorkflow | undefined;\n if (typeof exported === 'function') {\n customExecute = exported as EvalExecuteWorkflow;\n } else if (exported !== undefined) {\n console.error(\n `[axl-eval] Error: ${filePath} exports executeWorkflow but it is ${typeof exported}, not a function.`,\n );\n failedFiles++;\n continue;\n }\n\n let executeWorkflow: EvalExecuteWorkflow;\n\n if (customExecute) {\n // Wrap custom executeWorkflow with trackExecution for cost + metadata attribution\n executeWorkflow = async (input, rt) => {\n const {\n result,\n cost: trackedCost,\n metadata,\n } = await runtime.trackExecution(async () => {\n return customExecute(input, rt);\n });\n return {\n output: result.output,\n cost: result.cost ?? trackedCost,\n metadata: result.metadata ?? metadata,\n };\n };\n } else if (runtime.getWorkflow(evalConfig.workflow)) {\n // No executeWorkflow exported but workflow is registered — use runtime.execute()\n executeWorkflow = async (input) => {\n const { result, cost, metadata } = await runtime.trackExecution(async () => {\n return runtime.execute(evalConfig.workflow, input);\n });\n return { output: result, cost, metadata };\n };\n } else {\n // Fail loudly. The previous identity-passthrough fallback silently\n // produced all-zero scores in CI — exactly the kind of footgun the\n // \"fail loudly, not silently\" rule is meant to prevent. If you\n // genuinely want identity, export it explicitly:\n // export const executeWorkflow = async (input) => ({ output: input });\n //\n // Surface what WAS exported (top-level + .default) so users\n // recognize a typo like `execute_workflow` and can spot the\n // CJS-interop case that motivates this whole code path: when tsx\n // loads a `.ts` file as CJS, all named exports land on\n // `mod.default.<name>`. The chain walk above already handled this,\n // but a user who removed `executeWorkflow` will see exactly what\n // export names ARE present.\n const exportShape = (() => {\n const top = Object.keys(mod).filter((k) => k !== 'default');\n const fromDefault =\n mod.default && typeof mod.default === 'object'\n ? Object.keys(mod.default as Record<string, unknown>)\n : [];\n const all = [...new Set([...top, ...fromDefault])];\n return all.length > 0 ? `Found exports: [${all.join(', ')}].` : '';\n })();\n console.error(\n `[axl-eval] Error: ${filePath} does not export an executeWorkflow function ` +\n `and no workflow named \"${evalConfig.workflow}\" is registered on the runtime.\\n` +\n (exportShape ? ` ${exportShape}\\n` : '') +\n ` Add either:\\n` +\n ` export async function executeWorkflow(input, runtime) { ... }\\n` +\n ` or register the workflow on your runtime via runtime.registerWorkflow(workflow).\\n` +\n ` Tip: if your eval previously worked, your package.json may be missing\\n` +\n ` \"type\": \"module\" — tsx loads .ts as CJS, putting named exports on\\n` +\n ` mod.default.<name>. The CLI handles that interop, but if you renamed\\n` +\n ` or removed the export the file path is your fix.`,\n );\n failedFiles++;\n continue;\n }\n\n // When --capture-traces is set, forward it to runEval so the runner\n // wraps each item's execution in a nested trackExecution({ captureTraces: true }).\n // This populates EvalItem.traces on both success (via tracked.traces)\n // and failure (via the axlCapturedTraces side-channel on the thrown\n // error). Nested trackExecution scopes walk the AsyncLocalStorage\n // parent chain, so the outer trackExecution above still observes\n // cost/metadata correctly.\n // Always pass the SIGINT-driven signal so Ctrl+C aborts in-flight\n // workflows + scorer LLM calls instead of dropping the connection mid-request.\n const runOptions = { signal, ...(captureTraces ? { captureTraces: true } : {}) };\n\n // Stamp filtered runs so a scorer-subset result can't be silently used\n // as a full baseline. `compare` warns (and refuses to gate) on it. Must\n // be applied to EVERY run in a multi-run batch — Studio's multi-run\n // builder spreads only run[0]'s metadata.\n const scorersRun = scorerNames?.length ? evalConfig.scorers.map((s) => s.name) : undefined;\n const stampFiltered = (result: EvalResult) => {\n if (scorersRun) {\n result.metadata.scorerFiltered = true;\n result.metadata.scorersRun = scorersRun;\n }\n };\n\n if (runs > 1) {\n // Multi-run mode. Buffer per-run results locally so we can mark the\n // batch correctly before committing to outer `results[]`. The\n // original bug was the opposite: results pushed eagerly per-run, a\n // throw mid-batch left partial results posing as a complete run\n // (no `fromPartialBatch` marker, aggregate never ran). The fix ISN'T\n // to throw the partials away — those runs cost money and have\n // statistical signal. Instead we preserve them with explicit\n // partial-batch metadata, aggregate over what completed, and exit\n // non-zero so CI knows the batch wasn't clean.\n const { randomUUID } = await import('node:crypto');\n const runGroupId = randomUUID();\n const runResults: EvalResult[] = [];\n let runFailure: Error | undefined;\n\n for (let r = 0; r < runs; r++) {\n if (signal.aborted) break;\n console.error(`[axl-eval] Run ${r + 1}/${runs}...`);\n try {\n const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);\n result.metadata.runGroupId = runGroupId;\n result.metadata.runIndex = r;\n stampFiltered(result);\n runResults.push(result);\n } catch (err) {\n runFailure = err instanceof Error ? err : new Error(String(err));\n console.error(`[axl-eval] Run ${r + 1}/${runs} failed: ${runFailure.message}`);\n // Stop attempting further runs — we don't know whether this is\n // transient (rate limit) or permanent (auth failure), and\n // burning more API calls speculatively is hostile. Preserve\n // what completed.\n break;\n }\n }\n\n if (runResults.length === 0) {\n // Nothing completed — surface as a per-file failure. No partial\n // artifact to write; the outer try/catch error message would\n // duplicate the \"Run 1/N failed\" line we already printed, so\n // increment and continue without re-throwing.\n console.error(\n `[axl-eval] Aborting ${filePath}: no runs completed (${runFailure?.message ?? 'unknown failure'}).`,\n );\n failedFiles++;\n continue;\n }\n\n const partial = runResults.length < runs;\n if (partial) {\n // Mark every completed run so any consumer reading\n // `result.metadata` sees the partial-batch context. `aggregateRuns`\n // computes statistics over `runResults.length`, which is the\n // honest sample size to report.\n //\n // The flag is `fromPartialBatch` — a per-run marker meaning\n // \"this run came from a batch that did not complete.\" It is\n // NOT named `partialBatch` because that read as \"this run is\n // partial,\" and a defensive consumer wrote\n // `if (run.metadata.partialBatch) skipThisRun()`, throwing\n // away successful work that cost real money. The flag describes\n // the BATCH the run belongs to, not the run itself.\n //\n // `runFailure?.message` can be empty (e.g. `new Error('')` from a\n // misbehaving provider); fall back to `String(runFailure)` so the\n // banner never renders a blank \"Stopped after:\" line. Skip the\n // field entirely if both are empty so `buildMultiRunResult`'s\n // empty-string filter doesn't have to defensively guess.\n const failureMsg = runFailure\n ? runFailure.message || String(runFailure) || undefined\n : undefined;\n for (const r of runResults) {\n r.metadata.fromPartialBatch = true;\n r.metadata.batchCompleted = runResults.length;\n r.metadata.batchAttempted = runs;\n if (failureMsg) r.metadata.batchFailure = failureMsg;\n }\n console.error(\n `[axl-eval] PARTIAL: ${runResults.length} of ${runs} runs completed for ${filePath}.`,\n );\n failedFiles++;\n }\n\n const summary = aggregateRuns(runResults);\n console.log('\\n' + formatMultiRunTable(summary) + '\\n');\n for (const r of runResults) results.push(r);\n\n // Failure-rate gate (opt-in via failOnScorerErrorRate) + total-wipeout\n // guard (always). Report every run; count the file as failed unless it\n // was already counted as a partial batch above (avoids double-count).\n let anyFailing = false;\n for (const r of runResults) {\n // Call both (no short-circuit) so each prints its own diagnostic.\n const wipeout = reportTotalWipeout(r, filePath);\n const degraded = reportDegraded(r, filePath);\n reportFullySkippedScorers(r, filePath); // advisory only\n if (wipeout || degraded) anyFailing = true;\n }\n if (anyFailing && !partial) failedFiles++;\n } else {\n const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);\n stampFiltered(result);\n results.push(result);\n\n console.log('\\n' + formatTable(result) + '\\n');\n // Total-wipeout guard (always) + scorer failure-rate gate (opt-in). They\n // are mutually exclusive (a wipeout has no scored items, so no scorer can\n // be degraded), but report both for clarity; either fails the file.\n const wipeout = reportTotalWipeout(result, filePath);\n const degraded = reportDegraded(result, filePath);\n reportFullySkippedScorers(result, filePath); // advisory only\n if (wipeout || degraded) failedFiles++;\n }\n } catch (err) {\n console.error(\n `Error running eval ${filePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n failedFiles++;\n }\n }\n\n if (outputPath && results.length > 0) {\n const output = results.length === 1 ? results[0] : results;\n const outputDir = path.dirname(path.resolve(outputPath));\n await mkdir(outputDir, { recursive: true });\n await writeFileAsync(outputPath, JSON.stringify(output, null, 2), 'utf-8');\n console.log(`Results saved to ${outputPath}`);\n }\n } finally {\n await runtime.shutdown().catch(() => {});\n }\n\n // Exit non-zero if any eval file failed to load/run. Previously we only\n // exited non-zero when zero evals succeeded, which silently masked\n // misconfigured files in mixed-success runs (a CI footgun).\n if (failedFiles > 0 || results.length === 0) {\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","/**\n * CLI utilities for `axl-eval`.\n *\n * The actual implementations of the loader, glob, and config-detection\n * helpers live in `@axlsdk/axl`'s `cli-internals` so a fix to (e.g.) the\n * tsx loader registration applies to both axl-eval and axl-studio\n * atomically. This file is now a thin re-export plus the symmetric\n * module-resolution helpers from `@axlsdk/axl`.\n */\n\nexport {\n resolveRuntime,\n pickDefault,\n pickExport,\n CONFIG_CANDIDATES,\n findConfig,\n needsTsxLoader,\n importModule,\n expandGlob,\n registerConditions,\n} from '@axlsdk/axl';\n","import type { EvalConfig } from './types.js';\n\n/**\n * Validate an eval config's required fields and produce a diagnostic string\n * if invalid. Returns `undefined` when the config passes.\n *\n * Returns a single-line message tail (caller prepends the file path) so the\n * full error reads like: `Error: <file> <message>`.\n *\n * Goes beyond a falsy check to:\n * - catch `scorers: []` which previously silently ran with no scorers\n * - reject scorers arrays that contain non-callable entries (e.g. typo\n * `scorers: [null, undefined]` would crash deep inside `runEval`)\n * - reject non-string `workflow` (downstream `runtime.getWorkflow(...)`\n * would silently miss it)\n * - reject non-object `dataset` (must be a `dataset()` factory result)\n * - provide a `Got: { keys: [...] }` hint so users can spot typos like\n * `scorerS` vs `scorers` immediately\n *\n * Lives in its own module so unit tests can import it without triggering\n * `cli.ts`'s `main()` at module load time.\n */\nexport function validateEvalConfig(cfg: unknown): string | undefined {\n if (!cfg || typeof cfg !== 'object') {\n return `does not export a valid eval config — got ${cfg === null ? 'null' : typeof cfg}.`;\n }\n const c = cfg as Partial<EvalConfig>;\n const missing: string[] = [];\n if (!c.workflow) missing.push('workflow');\n if (!c.dataset) missing.push('dataset');\n if (!c.scorers) missing.push('scorers');\n\n if (missing.length > 0) {\n const keys = Object.keys(c as object).slice(0, 10);\n const got = keys.length > 0 ? ` Got: { ${keys.join(', ')} }.` : '';\n return `does not export a valid eval config (missing ${missing.join(', ')}).${got}`;\n }\n\n if (typeof c.workflow !== 'string') {\n return `exports a non-string workflow (got ${typeof c.workflow}) — workflow must be the registered workflow name.`;\n }\n if (typeof c.dataset !== 'object' || Array.isArray(c.dataset)) {\n return `exports a non-object dataset (got ${Array.isArray(c.dataset) ? 'array' : typeof c.dataset}) — use the dataset() factory.`;\n }\n if (typeof (c.dataset as { getItems?: unknown }).getItems !== 'function') {\n return `exports a dataset without a getItems() method — use the dataset() factory.`;\n }\n if (!Array.isArray(c.scorers) || c.scorers.length === 0) {\n return `exports an empty scorers array — at least one scorer is required.`;\n }\n const badScorerIdx = c.scorers.findIndex(\n (s) => !s || typeof (s as { score?: unknown }).score !== 'function',\n );\n if (badScorerIdx !== -1) {\n return `exports a scorers array with a non-scorer entry at index ${badScorerIdx} — each scorer must have a score() method (use the scorer() factory).`;\n }\n // Concurrency knobs must be positive integers when present. The worker pool\n // defensively clamps non-positive/non-finite values to 1 at runtime, but a\n // config that wrote `concurrency: 0` (or NaN via `Number(env)`) almost\n // certainly has a bug — surface it loudly, matching the `--concurrency` flag.\n for (const key of ['concurrency', 'scorerConcurrency'] as const) {\n const v = (c as Record<string, unknown>)[key];\n if (v !== undefined && (typeof v !== 'number' || !Number.isInteger(v) || v < 1)) {\n return `exports an invalid ${key} (${typeof v === 'number' ? v : typeof v}) — must be a positive integer.`;\n }\n }\n // The failure-rate gate must fail loud on a bad value, symmetric with the\n // `--max-scorer-error-rate` flag. Without this, a typo (e.g. `5` meaning \"5%\")\n // only `console.warn`s at runtime and silently disables the gate the user\n // explicitly opted into — the exact silent-no-op the feature exists to catch.\n {\n const v = (c as Record<string, unknown>).failOnScorerErrorRate;\n if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1)) {\n return `exports an invalid failOnScorerErrorRate (${typeof v === 'number' ? v : typeof v}) — must be a number between 0 and 1.`;\n }\n }\n return undefined;\n}\n","/**\n * Argument parsing for the `axl-eval` CLI, factored out of `cli.ts` so it can be\n * unit-tested without importing `cli.ts` (whose module body runs `main()` on\n * import).\n */\n\nexport const KNOWN_FLAGS = new Set([\n '--output',\n '--config',\n '--conditions',\n '--fail-on-regression',\n '--threshold',\n '--runs',\n '--capture-traces',\n '--concurrency',\n '--scorers',\n]);\n\n/**\n * The subset of flags `parseEvalArgs` consumes a following value for. NOT every\n * value-taking flag in the CLI: `--threshold` takes a value too but is parsed\n * by `runCompare`'s own parser, not here. Every entry MUST also be in\n * {@link KNOWN_FLAGS} — a value-flag missing from KNOWN_FLAGS would be rejected\n * as \"unknown\", and one missing from VALUE_FLAGS would silently swallow its\n * value into `paths`. A test asserts `VALUE_FLAGS ⊆ KNOWN_FLAGS`.\n */\nexport const VALUE_FLAGS = new Set([\n '--output',\n '--config',\n '--conditions',\n '--runs',\n '--concurrency',\n '--scorers',\n]);\n\nexport type ParsedEvalArgs = {\n outputPath?: string;\n configArg?: string;\n conditions: string[];\n runs: number;\n captureTraces: boolean;\n /** Item-level concurrency override (flag value). Clamped to >= 1. */\n concurrency?: number;\n /** Scorer names from `--scorers` (deduped, in first-seen order). */\n scorerNames?: string[];\n paths: string[];\n};\n\n/**\n * Read a positive integer from an environment variable. Returns `undefined`\n * when the var is absent, non-numeric, or `<= 0`, so a malformed value falls\n * through to the next precedence tier rather than poisoning the run.\n */\nexport function envInt(name: string): number | undefined {\n const raw = process.env[name];\n if (raw == null || raw === '') return undefined;\n const n = parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0) return undefined;\n return n;\n}\n\nexport function parseEvalArgs(args: string[]): ParsedEvalArgs {\n let outputPath: string | undefined;\n let configArg: string | undefined;\n let conditions: string[] = [];\n let runs = 1;\n let captureTraces = false;\n let concurrency: number | undefined;\n let scorerNames: string[] | undefined;\n const paths: string[] = [];\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (VALUE_FLAGS.has(arg)) {\n if (i + 1 >= args.length) {\n console.error(`Error: ${arg} requires a value`);\n process.exit(1);\n }\n const value = args[++i];\n if (arg === '--output') outputPath = value;\n else if (arg === '--config') configArg = value;\n else if (arg === '--runs') runs = Math.max(1, parseInt(value, 10) || 1);\n else if (arg === '--concurrency') {\n // Clamp to a floor of 1 with a warning (matches `--runs`' clamp ethos\n // but surfaces the typo). A `0` here would otherwise spawn zero workers.\n // Note the intentional asymmetry with `envInt` above: an explicit flag\n // typo is worth a warning, whereas a malformed ambient env var falls\n // through silently to the next precedence tier.\n const n = parseInt(value, 10);\n if (!Number.isFinite(n) || n <= 0) {\n console.error(`[axl-eval] Ignoring invalid --concurrency \"${value}\"; using 1.`);\n concurrency = 1;\n } else {\n concurrency = n;\n }\n } else if (arg === '--scorers') {\n scorerNames = [\n ...new Set(\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n ),\n ];\n } else {\n conditions = value\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean);\n }\n } else if (arg === '--capture-traces') {\n // Boolean flag — no value consumed.\n captureTraces = true;\n } else if (arg.startsWith('--')) {\n if (!KNOWN_FLAGS.has(arg)) {\n console.error(`Unknown flag: ${arg}`);\n process.exit(1);\n }\n } else {\n paths.push(arg);\n }\n }\n\n return {\n outputPath,\n configArg,\n conditions,\n runs,\n captureTraces,\n concurrency,\n scorerNames,\n paths,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/cli-utils.ts","../src/cli-validate.ts","../src/cli-args.ts","../src/cli-format.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { readFile as readFileAsync, writeFile as writeFileAsync, mkdir } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { AxlRuntime, EvalExecuteWorkflow } from '@axlsdk/axl';\nimport { evalCompare, evaluateScorerErrorRateGate } from './compare.js';\nimport { runEval } from './runner.js';\nimport { rescore } from './rescore.js';\nimport { aggregateRuns } from './multi-run.js';\nimport type { MultiRunSummary } from './multi-run.js';\nimport type { EvalConfig, EvalResult } from './types.js';\nimport {\n findConfig,\n resolveRuntime,\n importModule,\n registerConditions,\n pickDefault,\n pickExport,\n expandGlob,\n CONFIG_CANDIDATES,\n} from './cli-utils.js';\nimport { validateEvalConfig } from './cli-validate.js';\nimport { parseEvalArgs, envInt } from './cli-args.js';\nimport { formatModelTimingLines } from './cli-format.js';\nimport { scorerCounts } from './utils.js';\n\n/**\n * Refuse to certify a comparison and exit non-zero, with one consistent\n * message format. Routes both the scorer-filtered guard and the\n * `--max-scorer-error-rate` failure-rate gate through a single path so the\n * \"why CI failed\" line always reads the same.\n */\nfunction refuseToGate(reason: string): never {\n console.error(`[axl-eval] Refusing to gate: ${reason}`);\n process.exit(1);\n}\n\n/**\n * Read a scorer's `scored`/`failed` counts from a summary entry, falling back\n * to recomputing from `items` when the fields are absent (pre-0.18.0\n * artifacts). `items` is omitted for multi-run summaries that carry no items —\n * there the fields are authoritative (summed by `aggregateRuns`).\n */\nfunction scorerSampleCounts(\n entry: { scored?: number; failed?: number; skipped?: number },\n name: string,\n items?: EvalResult['items'],\n): { scored: number; failed: number; skipped: number } {\n if (entry.scored != null && entry.failed != null) {\n // `scored`/`failed` are authoritative; `skipped` was added later, so an\n // 0.18.0-era artifact may carry the first two without it — recompute that one\n // field from items when we can, else 0.\n const skipped = entry.skipped ?? (items ? scorerCounts(items, name).skipped : 0);\n return { scored: entry.scored, failed: entry.failed, skipped };\n }\n if (items) return scorerCounts(items, name);\n return { scored: entry.scored ?? 0, failed: entry.failed ?? 0, skipped: entry.skipped ?? 0 };\n}\n\n/** Install a SIGINT/SIGTERM handler that aborts an in-flight eval gracefully.\n * First signal: aborts the controller so runEval / rescore propagate cancellation\n * through ScorerContext.signal into in-flight provider.chat calls. The eval\n * unwinds normally and runtime.shutdown() runs in the `finally` block.\n * Second signal: hard exit — the user has signalled twice; cleanup is hung\n * or taking too long, so we abandon gracefulness rather than appear frozen. */\nfunction installAbortHandler(controller: AbortController): void {\n let signalled = false;\n const onSignal = (sig: NodeJS.Signals) => {\n if (signalled) {\n console.error(`\\n[axl-eval] Second ${sig} — forcing exit.`);\n process.exit(130);\n }\n signalled = true;\n console.error(`\\n[axl-eval] ${sig} received — cancelling eval (press again to force exit)...`);\n controller.abort();\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n\n if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {\n console.log(`\nUsage:\n axl-eval <path> Run eval file(s)\n axl-eval <path> --runs <n> Run eval N times (multi-run)\n axl-eval <path> --output <file> Save results to JSON\n axl-eval <path> --config <file> Use config file for runtime\n axl-eval <path> --conditions <list> Node.js import conditions (comma-separated)\n axl-eval <path> --concurrency <n> Override item concurrency (flag > env\n AXL_EVAL_CONCURRENCY > config > 5).\n Max concurrent scorer calls is\n concurrency x scorerConcurrency (the\n per-eval scorerConcurrency defaults to 5).\n axl-eval <path> --scorers <a,b> Run only the named scorers (single eval\n file only). Stamps the result as a\n subset so compare won't treat it as a\n full baseline.\n axl-eval <path> --capture-traces Populate EvalItem.traces on every item\n (success + failure). Adds memory\n overhead proportional to dataset size\n x turns x agents; off by default.\n axl-eval rescore <results> <eval-file> Re-run scorers on saved outputs\n axl-eval compare <a> <b> Compare two eval result files\n axl-eval compare <a> <b> --threshold <v> Set regression threshold (global or per-scorer)\n axl-eval compare <a> <b> --fail-on-regression Exit 1 if regressions\n axl-eval compare <a> <b> --max-scorer-error-rate <0..1> Exit 1 if a scorer's\n failure rate exceeds the limit on\n either side (deterministic scorers\n tolerate zero failures). Always warns\n when either side rests on a thinned\n sample, even without this flag.\n\nConfig auto-detection (when --config is not specified):\n ${CONFIG_CANDIDATES.join(' -> ')}\n\nWhen a config is found, the exported AxlRuntime is passed to executeWorkflow\nand cost is tracked automatically via runtime.trackCost().\nWhen no config is found, a bare AxlRuntime is created (providers from env vars).\n`);\n process.exit(0);\n }\n\n if (args[0] === 'compare') {\n // `compare` is pure computation — no LLM calls, no AbortSignal plumbing needed.\n await runCompare(args.slice(1));\n return;\n }\n\n // SIGINT/SIGTERM aborts in-flight LLM calls (eval + rescore) instead of leaving\n // the process to hard-exit mid-request, which would skip runtime.shutdown() and\n // leak streaming-buffer state.\n const controller = new AbortController();\n installAbortHandler(controller);\n\n if (args[0] === 'rescore') {\n await runRescore(args.slice(1), controller.signal);\n return;\n }\n\n await runEvalCommand(args, controller.signal);\n}\n\nfunction parseThresholdArg(args: string[]): Record<string, number> | number | undefined {\n const idx = args.indexOf('--threshold');\n if (idx === -1 || idx + 1 >= args.length) return undefined;\n const value = args[idx + 1];\n\n // Global numeric threshold: --threshold 0.05\n if (/^[\\d.]+$/.test(value)) return parseFloat(value);\n\n // Per-scorer map: --threshold accuracy=0,tone=0.1\n const map: Record<string, number> = {};\n for (const pair of value.split(',')) {\n const [key, val] = pair.split('=');\n if (key && val != null) map[key.trim()] = parseFloat(val.trim());\n }\n return Object.keys(map).length > 0 ? map : undefined;\n}\n\n/**\n * Parse `--max-scorer-error-rate <0..1>`. Returns the numeric limit, or\n * `undefined` when the flag is absent. Exits non-zero on a malformed value\n * (fail loud — a typo'd gate threshold must not silently disable the gate).\n */\nfunction parseMaxScorerErrorRate(args: string[]): number | undefined {\n const idx = args.indexOf('--max-scorer-error-rate');\n if (idx === -1) return undefined;\n if (idx + 1 >= args.length) {\n console.error('Error: --max-scorer-error-rate requires a value in [0, 1]');\n process.exit(1);\n }\n const raw = args[idx + 1];\n // Strict numeric parse — `parseFloat('0.5abc')` returns 0.5 and `parseFloat('0,5')`\n // returns 0, both of which would silently set an unintended gate threshold. Reject\n // anything that isn't a clean decimal (matches `parseThresholdArg`'s guard).\n const n = /^\\d*\\.?\\d+$/.test(raw.trim()) ? Number(raw) : NaN;\n if (!Number.isFinite(n) || n < 0 || n > 1) {\n console.error(`Error: --max-scorer-error-rate must be a number in [0, 1], got \"${raw}\"`);\n process.exit(1);\n }\n return n;\n}\n\n/**\n * If a raw compare input (single result or multi-run array) was produced by a\n * `--scorers` filtered run, return the scorer names it ran; otherwise undefined.\n * Reads the first run's metadata — a multi-run group shares the same stamp.\n */\nfunction scorerFilteredScorers(r: EvalResult | EvalResult[]): string[] | undefined {\n const first = Array.isArray(r) ? r[0] : r;\n const meta = first?.metadata;\n if (!meta || meta.scorerFiltered !== true) return undefined;\n return Array.isArray(meta.scorersRun) ? (meta.scorersRun as string[]) : [];\n}\n\nasync function runCompare(args: string[]) {\n const failOnRegression = args.includes('--fail-on-regression');\n const thresholds = parseThresholdArg(args);\n const maxScorerErrorRate = parseMaxScorerErrorRate(args);\n // Exclude flags and the values consumed by value-taking flags so neither a\n // threshold nor an error-rate value is mistaken for a result file path.\n const valueFlags = new Set(['--threshold', '--max-scorer-error-rate']);\n const files = args.filter(\n (a, i) => !a.startsWith('--') && !(i > 0 && valueFlags.has(args[i - 1])),\n );\n\n if (files.length !== 2) {\n console.error(\n 'Usage: axl-eval compare <baseline.json> <candidate.json> [--threshold <value>] [--fail-on-regression] [--max-scorer-error-rate <0..1>]',\n );\n process.exit(1);\n }\n\n const baselineRaw = JSON.parse(await readFileAsync(files[0], 'utf-8'));\n const candidateRaw = JSON.parse(await readFileAsync(files[1], 'utf-8'));\n const baseline: EvalResult | EvalResult[] = baselineRaw;\n const candidate: EvalResult | EvalResult[] = candidateRaw;\n\n // A scorer-filtered result (CLI `--scorers`) tested only a subset of scorers,\n // so it isn't a sound baseline/candidate for a full comparison. Check this\n // BEFORE evalCompare(): a subset-vs-full comparison has mismatched scorer sets\n // and evalCompare() would throw a generic \"different scorers\" error that never\n // mentions --scorers. Warn loudly here so the cause is actionable; under\n // `--fail-on-regression`, refuse outright (a swallowed stderr line wouldn't\n // actually prevent the footgun).\n const filteredSides = (\n [\n ['baseline', baseline],\n ['candidate', candidate],\n ] as const\n ).filter(([, r]) => scorerFilteredScorers(r) != null);\n for (const [side, r] of filteredSides) {\n const ran = scorerFilteredScorers(r);\n const ranStr = ran && ran.length ? ` (ran: ${ran.join(', ')})` : '';\n console.error(\n `[axl-eval] WARNING: ${side} was run with --scorers — it tested only a subset${ranStr}, so this comparison is incomplete.`,\n );\n }\n if (failOnRegression && filteredSides.length > 0) {\n refuseToGate(\n `${filteredSides.map(([s]) => s).join(', ')} was run with --scorers (a scorer subset); re-run without --scorers for a complete baseline.`,\n );\n }\n\n const compareOptions = thresholds != null ? { thresholds } : undefined;\n const comparison = evalCompare(baseline, candidate, compareOptions);\n\n console.log(\n `\\nCompare: baseline (${comparison.baseline.id.slice(0, 8)}) -> candidate (${comparison.candidate.id.slice(0, 8)})\\n`,\n );\n\n const scorerNames = Object.keys(comparison.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 6);\n const hasCI = scorerNames.some((n) => comparison.scorers[n].ci != null);\n\n const ciHeader = hasCI ? ' CI 95% Sig' : '';\n const ciRule = hasCI ? ' ---------------- ---' : '';\n console.log(` ${'Scorer'.padEnd(maxNameLen)} Baseline Candidate Delta Change${ciHeader}`);\n console.log(` ${''.padEnd(maxNameLen, '-')} -------- --------- -------- ------${ciRule}`);\n\n for (const name of scorerNames) {\n const s = comparison.scorers[name];\n const sign = s.delta > 0 ? '+' : '';\n let line = ` ${name.padEnd(maxNameLen)} ${s.baselineMean.toFixed(3).padStart(8)} ${s.candidateMean.toFixed(3).padStart(9)} ${(sign + s.delta.toFixed(3)).padStart(8)} ${(sign + s.deltaPercent.toFixed(1) + '%').padStart(6)}`;\n if (hasCI) {\n if (s.ci) {\n const lo = (s.ci.lower >= 0 ? '+' : '') + s.ci.lower.toFixed(4);\n const hi = (s.ci.upper >= 0 ? '+' : '') + s.ci.upper.toFixed(4);\n const sig = s.significant ? ' *' : ' ';\n line += ` [${lo}, ${hi}]${sig}`;\n } else {\n line += ' —'.padEnd(22);\n }\n }\n console.log(line);\n }\n\n if (comparison.timing) {\n const t = comparison.timing;\n const sign = t.delta > 0 ? '+' : '';\n console.log(\n `\\n Timing: baseline ${(t.baselineMean / 1000).toFixed(2)}s -> candidate ${(t.candidateMean / 1000).toFixed(2)}s (${sign}${t.deltaPercent.toFixed(1)}%)`,\n );\n }\n if (comparison.cost) {\n const c = comparison.cost;\n const sign = c.delta > 0 ? '+' : '';\n console.log(\n ` Cost: baseline $${c.baselineTotal.toFixed(2)} -> candidate $${c.candidateTotal.toFixed(2)} (${sign}${c.deltaPercent.toFixed(1)}%)`,\n );\n }\n\n const baselineRef = Array.isArray(baseline) ? baseline[0] : baseline;\n const stable = Math.max(\n 0,\n baselineRef.items.length - comparison.regressions.length - comparison.improvements.length,\n );\n console.log(\n `\\n Regressions: ${comparison.regressions.length} | Improvements: ${comparison.improvements.length} | Stable: ${stable}\\n`,\n );\n\n // Scorer failure-rate signal. Always WARN loudly when either side rests on a\n // thinned sample (a scorer ran and failed on some items) — a regression check\n // computed over survivors can look green while half the judges errored. Only\n // REFUSE to gate when the user opted in with `--max-scorer-error-rate` AND a\n // scorer is over tolerance (type-aware: deterministic scorers tolerate zero\n // failures, LLM judges use the flag value), or a scorer produced no scored\n // items at all on a side (a zero-sample mean can't be certified).\n let sawThinnedSample = false;\n for (const name of scorerNames) {\n const s = comparison.scorers[name];\n const bFailed = s.baselineFailed ?? 0;\n const cFailed = s.candidateFailed ?? 0;\n if (bFailed > 0 || cFailed > 0) {\n sawThinnedSample = true;\n const bAtt = (s.baselineScored ?? 0) + bFailed;\n const cAtt = (s.candidateScored ?? 0) + cFailed;\n console.error(\n `[axl-eval] WARNING: scorer \"${name}\" failed on some items ` +\n `(baseline ${bFailed}/${bAtt}, candidate ${cFailed}/${cAtt}); ` +\n `the means above are computed over the surviving sample.`,\n );\n }\n // Conditional scorers (`applies`) skip N/A items. Skips never move the\n // delta (excluded from both means), but they make the two per-side means\n // cover different applicable subsets when the sides skipped different COUNTS,\n // OR the same count of DIFFERENT items — the latter shows up as a paired\n // shortfall: fewer items scored on BOTH sides (`n`) than either side scored\n // alone. (`bSkipped !== cSkipped` alone would miss the disjoint-but-equal\n // case.) Note it (advisory only; not a gate; failures are covered by the\n // separate WARNING above).\n const bSkipped = s.baselineSkipped ?? 0;\n const cSkipped = s.candidateSkipped ?? 0;\n const pairedN = s.n ?? 0;\n const minScored = Math.min(s.baselineScored ?? 0, s.candidateScored ?? 0);\n const skipsDiverge =\n (bSkipped > 0 || cSkipped > 0) && (bSkipped !== cSkipped || pairedN < minScored);\n if (skipsDiverge) {\n console.error(\n `[axl-eval] NOTE: scorer \"${name}\" applies to different subsets per side ` +\n `(baseline ${bSkipped} N/A, candidate ${cSkipped} N/A; ${pairedN} item(s) scored on both); ` +\n `the two means cover different applicable subsets, so compare the delta with care.`,\n );\n }\n }\n // Tell the user how to make the advisory warning blocking (when they haven't).\n if (sawThinnedSample && maxScorerErrorRate == null) {\n console.error(\n `[axl-eval] (advisory — pass --max-scorer-error-rate <0..1> to fail CI on a thinned sample)`,\n );\n }\n\n // Gate-side refusal (opt-in). The decision lives in evaluateScorerErrorRateGate\n // (pure + unit-tested) so the same type-aware tolerance + zero-sample logic\n // isn't re-implemented here and at the source-side gate in runEval.\n if (maxScorerErrorRate != null) {\n const reason = evaluateScorerErrorRateGate(comparison, maxScorerErrorRate);\n if (reason) refuseToGate(reason);\n }\n\n if (failOnRegression && comparison.regressions.length > 0) {\n // When CI is available, only fail on significant regressions\n const hasSignificance = scorerNames.some((n) => comparison.scorers[n].significant != null);\n if (hasSignificance) {\n const hasSignificantRegression = scorerNames.some(\n (n) => comparison.scorers[n].significant === true && comparison.scorers[n].delta < 0,\n );\n if (hasSignificantRegression) process.exit(1);\n } else {\n process.exit(1);\n }\n }\n}\n\nasync function runRescore(args: string[], signal: AbortSignal) {\n const { outputPath, configArg, conditions, concurrency, scorerNames, paths } =\n parseEvalArgs(args);\n\n if (paths.length < 2) {\n console.error('Usage: axl-eval rescore <results.json> <eval-file> [--output <file>]');\n process.exit(1);\n }\n\n // --scorers is a run-command-only filter — rescore always re-runs the full\n // scorer set from the eval file. Fail loudly rather than silently ignoring it\n // (it parses as a known flag, so it wouldn't trip the unknown-flag guard).\n if (scorerNames?.length) {\n console.error(\n 'Error: --scorers is not supported with rescore; pass a scorer subset in the eval file instead.',\n );\n process.exit(1);\n }\n\n // Honor the same item-concurrency override as the run command (flag > env).\n // scorerConcurrency keeps its rescore default (5).\n const itemConcurrency = concurrency ?? envInt('AXL_EVAL_CONCURRENCY');\n\n const [resultsPath, evalFilePath] = paths;\n const raw = JSON.parse(await readFileAsync(resultsPath, 'utf-8'));\n const results: EvalResult[] = Array.isArray(raw) ? raw : [raw];\n\n const runtime = await getRuntime(configArg, conditions);\n try {\n const mod = await importModule(path.resolve(evalFilePath), import.meta.url);\n const evalConfig = pickDefault<EvalConfig>(mod);\n\n if (!evalConfig || typeof evalConfig !== 'object') {\n console.error(\n `Error: ${evalFilePath} does not export an eval config object — got ${evalConfig === null ? 'null' : typeof evalConfig}.`,\n );\n process.exit(1);\n }\n if (!Array.isArray(evalConfig.scorers) || evalConfig.scorers.length === 0) {\n console.error(`Error: ${evalFilePath} does not export a non-empty scorers array.`);\n process.exit(1);\n }\n\n const rescored: EvalResult[] = [];\n for (const resultData of results) {\n if (signal.aborted) break;\n rescored.push(\n await rescore(resultData, evalConfig.scorers, runtime, {\n signal,\n ...(itemConcurrency != null ? { concurrency: itemConcurrency } : {}),\n }),\n );\n }\n\n for (const r of rescored) {\n console.log('\\n' + formatTable(r) + '\\n');\n }\n\n if (outputPath) {\n const output = Array.isArray(raw) ? rescored : rescored[0];\n const outputDir = path.dirname(path.resolve(outputPath));\n await mkdir(outputDir, { recursive: true });\n await writeFileAsync(outputPath, JSON.stringify(output, null, 2), 'utf-8');\n console.log(`Rescored results saved to ${outputPath}`);\n }\n } finally {\n await runtime.shutdown().catch(() => {});\n }\n}\n\n/**\n * Resolve a CLI path argument into a list of eval files.\n *\n * Accepts:\n * - An explicit file path (`path/to/foo.eval.ts`)\n * - A directory (lists `*.eval.[mc]?[jt]sx?` inside, non-recursive)\n * - A glob pattern: `evals/*.eval.ts`, `evals/**\\/*.eval.ts`, `**\\/*.eval.ts`\n *\n * Glob expansion mirrors the studio eval-loader's so users get the same\n * behavior in both places. Without it, users running on Windows or with\n * quoted patterns hit confusing \"file not found\" errors when the shell\n * couldn't expand the glob.\n */\nfunction collectEvalFiles(p: string): string[] {\n if (p.includes('*')) return expandGlob(p, process.cwd());\n const resolved = path.resolve(p);\n try {\n const stat = statSync(resolved);\n if (stat.isDirectory()) {\n const entries = readdirSync(resolved);\n return entries\n .filter((e) => /\\.eval\\.[mc]?[jt]sx?$/.test(e))\n .map((e) => path.join(resolved, e));\n }\n return [resolved];\n } catch {\n return [resolved];\n }\n}\n\n/** Render observed workflows for a terse header line. Multiple → comma-separated. */\nfunction formatWorkflows(workflows: unknown): string {\n if (!Array.isArray(workflows) || workflows.length === 0) return '(unknown)';\n return (workflows as unknown[]).filter((w): w is string => typeof w === 'string').join(', ');\n}\n\nfunction formatTable(result: EvalResult): string {\n const lines: string[] = [];\n const scorerNames = Object.keys(result.summary.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 'Scorer'.length);\n const colWidth = 8;\n\n lines.push(\n `Eval: ${formatWorkflows(result.metadata.workflows)} x ${result.dataset} (${result.summary.count} items)`,\n );\n lines.push(\n ` ${'Scorer'.padEnd(maxNameLen)} ${'Mean'.padStart(colWidth)} ${'Min'.padStart(colWidth)} ${'Max'.padStart(colWidth)} ${'p50'.padStart(colWidth)} ${'p95'.padStart(colWidth)}`,\n );\n\n const ruleLen = maxNameLen + 2 + (colWidth + 2) * 5;\n lines.push(' ' + '\\u2500'.repeat(ruleLen));\n\n for (const name of scorerNames) {\n const s = result.summary.scorers[name];\n const { scored, failed, skipped } = scorerSampleCounts(s, name, result.items);\n // Loud trailing annotation: `failed > 0` means the mean rests on a thinned\n // sample; `skipped > 0` (an `applies`-conditional scorer) labels items the\n // scorer deliberately didn't run — crucial so a fully-skipped scorer (all\n // `--`) reads as \"N/A\" rather than \"broken\". Both shown only when non-zero so\n // clean, unconditional runs stay uncluttered.\n const annotationParts: string[] = [];\n if (failed > 0) annotationParts.push(`${scored}/${scored + failed} scored, ${failed} failed`);\n if (skipped > 0) annotationParts.push(`${skipped} N/A`);\n const annotation = annotationParts.length > 0 ? ` (${annotationParts.join(', ')})` : '';\n // Detect scorers with no valid scores (all items errored → computeStats([]) → all zeros)\n if (scored === 0) {\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)} ${'--'.padStart(colWidth)}${annotation}`,\n );\n } else {\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${s.mean.toFixed(2).padStart(colWidth)} ${s.min.toFixed(2).padStart(colWidth)} ${s.max.toFixed(2).padStart(colWidth)} ${s.p50.toFixed(2).padStart(colWidth)} ${s.p95.toFixed(2).padStart(colWidth)}${annotation}`,\n );\n }\n }\n\n if (result.summary.timing) {\n const t = result.summary.timing;\n lines.push(\n ` ${'Timing'.padEnd(maxNameLen)} ${(t.mean / 1000).toFixed(2).padStart(colWidth)}s ${(t.p50 / 1000).toFixed(2).padStart(colWidth)}s ${(t.p95 / 1000).toFixed(2).padStart(colWidth)}s`,\n );\n }\n\n // Per-model provider latency, indented under the wall-clock Timing row. All\n // four buckets are shown because their separation is the point: `wire` is the\n // provider, `queued` is the SDK's own rate limiter, `retries` is the\n // provider's throttling that day, and `first token` is the figure that\n // actually discriminates between models. See `formatModelTimingLines` for why\n // these are the call-weighted means and not the per-item distributions.\n lines.push(...formatModelTimingLines(result.summary.modelTiming, maxNameLen));\n\n const durationSec = (result.duration / 1000).toFixed(1);\n const costStr = result.totalCost > 0 ? `$${result.totalCost.toFixed(2)}` : '$0.00';\n lines.push('');\n lines.push(\n ` Failures: ${result.summary.failures}/${result.summary.count} | Cost: ${costStr} | Duration: ${durationSec}s`,\n );\n\n const itemsWithErrors = result.items.filter((i) => i.scorerErrors?.length);\n if (itemsWithErrors.length > 0) {\n const uniqueErrors = [...new Set(itemsWithErrors.flatMap((i) => i.scorerErrors!))];\n lines.push('');\n lines.push(\n ` Scorer errors (${itemsWithErrors.length}/${result.summary.count} items affected):`,\n );\n for (const err of uniqueErrors.slice(0, 5)) {\n lines.push(` - ${err}`);\n }\n if (uniqueErrors.length > 5) {\n lines.push(` ... and ${uniqueErrors.length - 5} more`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction formatMultiRunTable(summary: MultiRunSummary): string {\n const lines: string[] = [];\n const scorerNames = Object.keys(summary.scorers);\n const maxNameLen = Math.max(...scorerNames.map((n) => n.length), 'Scorer'.length);\n const colWidth = 16;\n\n lines.push(\n `Eval: ${formatWorkflows(summary.workflows)} x ${summary.dataset} \\u2014 ${summary.runCount} runs`,\n );\n lines.push(\n ` ${'Scorer'.padEnd(maxNameLen)} ${'Mean \\u00b1 Std'.padStart(colWidth)} ${'Min'.padStart(8)} ${'Max'.padStart(8)}`,\n );\n const ruleLen = maxNameLen + 2 + colWidth + 2 + 8 + 2 + 8;\n lines.push(' ' + '\\u2500'.repeat(ruleLen));\n\n for (const name of scorerNames) {\n const s = summary.scorers[name];\n const meanStd = `${s.mean.toFixed(3)} \\u00b1 ${s.std.toFixed(3)}`;\n // Multi-run summaries have no items, so the summed scored/failed fields are\n // authoritative (no fallback recompute possible). Annotate when failed > 0.\n const { scored, failed } = scorerSampleCounts(s, name);\n const annotation =\n failed > 0 ? ` (${scored}/${scored + failed} scored, ${failed} failed)` : '';\n lines.push(\n ` ${name.padEnd(maxNameLen)} ${meanStd.padStart(colWidth)} ${s.min.toFixed(3).padStart(8)} ${s.max.toFixed(3).padStart(8)}${annotation}`,\n );\n }\n\n if (summary.timing) {\n lines.push(\n ` ${'Timing'.padEnd(maxNameLen)} ${((summary.timing.mean / 1000).toFixed(2) + ' \\u00b1 ' + (summary.timing.std / 1000).toFixed(2) + 's').padStart(colWidth)}`,\n );\n }\n\n const costStr = summary.totalCost > 0 ? `$${summary.totalCost.toFixed(2)}` : '$0.00';\n const durationStr = (summary.totalDuration / 1000).toFixed(1);\n lines.push('');\n lines.push(` Total Cost: ${costStr} | Total Duration: ${durationStr}s`);\n\n return lines.join('\\n');\n}\n\n/**\n * Print each degraded scorer (set by `runEval` when `failOnScorerErrorRate`\n * tripped) to stderr and return whether the run was degraded, so the caller can\n * count the file as failed (→ non-zero exit). Quiet when nothing degraded.\n */\nfunction reportDegraded(result: EvalResult, label: string): boolean {\n const d = result.summary.degraded;\n if (!d || d.length === 0) return false;\n for (const x of d) {\n const attempted = x.scored + x.failed;\n const limitStr =\n x.type === 'deterministic'\n ? 'deterministic zero-tolerance'\n : `${(x.limit * 100).toFixed(1)}% limit`;\n console.error(\n `[axl-eval] DEGRADED: ${label} scorer \"${x.scorer}\" (${x.type}) failed ` +\n `${(x.rate * 100).toFixed(1)}% (${x.failed}/${attempted}), over the ${limitStr}.`,\n );\n }\n return true;\n}\n\n/**\n * Advisory (non-fatal): a conditional scorer (`applies`) that ended up applicable\n * to ZERO items — every item was skipped as N/A, nothing scored or failed. Its\n * mean is a meaningless empty-sample 0 and the failure-rate gate can't assess it\n * (a zero-sample scorer is never `degraded`). That's almost always a\n * misconfiguration: an over-strict predicate, a dataset missing the flagged\n * items, or a predicate that forgot to `return`. The `(N N/A)` table annotation\n * hints at it, but a fully-skipped GATE scorer would otherwise exit 0 with no\n * stderr signal — this closes that silent-green hole without changing the exit\n * code (skips are legitimately conditional; we inform, we don't fail).\n */\nfunction reportFullySkippedScorers(result: EvalResult, label: string): void {\n for (const [name, entry] of Object.entries(result.summary.scorers)) {\n const { scored, failed, skipped } = scorerSampleCounts(entry, name, result.items);\n if (skipped > 0 && scored + failed === 0) {\n console.error(\n `[axl-eval] NOTE: ${label} scorer \"${name}\" was not applicable to any item ` +\n `(${skipped} N/A, 0 scored) — its mean is not meaningful. Check the scorer's ` +\n `\\`applies\\` predicate and dataset if you expected it to run.`,\n );\n }\n }\n}\n\n/**\n * A run where EVERY item errored in the workflow (0 succeeded) produced no valid\n * output to score — the eval is meaningless and must never pass CI green. This\n * is distinct from (and complementary to) the scorer failure-rate gate: that one\n * is about a flaky/failing *scorer*, this is about a broken *workflow*. The\n * scorer gate deliberately ignores a zero-sample scorer (nothing ran), so without\n * this guard a 100%-workflow-error run exits 0 — a silent-green trap.\n *\n * Non-configurable on purpose: a 0%-success eval is unambiguously broken, so this\n * always fails. (A configurable `failOnItemErrorRate` for PARTIAL workflow-failure\n * gating is a reasonable future opt-in; the per-item failure count is already shown\n * loudly in the table either way.) Returns whether the run was a total wipeout.\n */\nfunction reportTotalWipeout(result: EvalResult, label: string): boolean {\n const { count, failures } = result.summary;\n if (count === 0 || failures < count) return false;\n console.error(\n `[axl-eval] FAILED: ${label} — all ${count} item(s) errored in the workflow ` +\n `(0 succeeded); the eval produced no scorable output.`,\n );\n return true;\n}\n\n// ── Runtime resolution ─────────────────────────────────────────────\n\nasync function resolveRuntimeFromConfig(configPath: string): Promise<AxlRuntime> {\n try {\n const mod = await importModule(configPath, import.meta.url);\n const runtime = resolveRuntime(mod) as AxlRuntime;\n\n if (!runtime || typeof runtime.execute !== 'function') {\n console.error(`Config must export a default AxlRuntime instance.`);\n if (runtime) {\n const keys = Object.keys(runtime as object)\n .slice(0, 5)\n .join(', ');\n console.error(` Got: ${typeof runtime}${keys ? ` with keys: { ${keys} }` : ''}`);\n }\n console.error(\n `Example:\\n import { AxlRuntime } from '@axlsdk/axl';\\n export default new AxlRuntime({ ... });`,\n );\n process.exit(1);\n }\n\n return runtime;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (\n /Cannot use import statement|Unexpected reserved word|top-level await|exports is not defined/.test(\n msg,\n )\n ) {\n const ext = path.extname(configPath);\n console.error(`[axl-eval] Config failed to load due to a CJS/ESM compatibility issue.`);\n if (ext === '.ts' || ext === '.tsx') {\n console.error(\n ` Tip: try renaming to .mts to force ESM format, or ensure tsx is installed and up to date.`,\n );\n } else {\n console.error(` Tip: add \"type\": \"module\" to your package.json.`);\n }\n console.error();\n }\n console.error(`Failed to load config:`, err);\n process.exit(1);\n }\n}\n\nasync function getRuntime(configArg?: string, conditions?: string[]): Promise<AxlRuntime> {\n // Register import conditions before any config loading\n if (conditions && conditions.length > 0) {\n await registerConditions(conditions);\n }\n\n // 1. Explicit --config\n if (configArg) {\n const configPath = path.resolve(process.cwd(), configArg);\n const stat = statSync(configPath, { throwIfNoEntry: false });\n if (!stat?.isFile()) {\n console.error(`Config file not found: ${configPath}`);\n process.exit(1);\n }\n console.error(`[axl-eval] Loading config from ${configPath}`);\n return resolveRuntimeFromConfig(configPath);\n }\n\n // 2. Auto-detect\n const found = findConfig(process.cwd());\n if (found) {\n console.error(`[axl-eval] Auto-detected config: ${found}`);\n return resolveRuntimeFromConfig(found);\n }\n\n // 3. Bare runtime (providers from env vars)\n const { AxlRuntime } = await import('@axlsdk/axl');\n return new AxlRuntime();\n}\n\n// ── Main eval command ──────────────────────────────────────────────\n\nasync function runEvalCommand(args: string[], signal: AbortSignal) {\n const {\n outputPath,\n configArg,\n conditions,\n runs,\n captureTraces,\n concurrency,\n scorerNames,\n paths,\n } = parseEvalArgs(args);\n\n if (paths.length === 0) {\n console.error('Error: No eval file path provided');\n process.exit(1);\n }\n\n const evalFiles: string[] = [];\n for (const p of paths) {\n evalFiles.push(...collectEvalFiles(p));\n }\n\n if (evalFiles.length === 0) {\n console.error('Error: No eval files found');\n process.exit(1);\n }\n\n // --scorers is single-file only — filtering by name across a glob/dir of evals\n // with different scorer sets is ambiguous. Fail fast, before importing any\n // module, rather than discovering the conflict mid-loop.\n if (scorerNames?.length && evalFiles.length > 1) {\n console.error(`Error: --scorers requires a single eval file (matched ${evalFiles.length}).`);\n process.exit(1);\n }\n\n const runtime = await getRuntime(configArg, conditions);\n const results: EvalResult[] = [];\n let failedFiles = 0;\n\n try {\n for (const filePath of evalFiles) {\n // Short-circuit if the user aborted between eval files. The in-flight eval\n // unwinds on its own via runEval({ signal }); this prevents starting the next one.\n if (signal.aborted) break;\n try {\n const mod = await importModule(path.resolve(filePath), import.meta.url);\n const evalConfig = pickDefault<EvalConfig>(mod);\n\n const validationError = validateEvalConfig(evalConfig);\n if (validationError) {\n console.error(`Error: ${filePath} ${validationError}`);\n failedFiles++;\n continue;\n }\n\n // Concurrency override precedence: flag > env > config > default.\n // Mutating evalConfig in place mirrors how this loop already stamps\n // result.metadata. `concurrency` is pure scheduling — never changes\n // results — so a per-run override is always safe.\n evalConfig.concurrency =\n concurrency ?? envInt('AXL_EVAL_CONCURRENCY') ?? evalConfig.concurrency ?? 5;\n\n // --scorers: run a subset of scorers for a focused iteration loop.\n // (The single-file guard already ran before the loop.) Validate-then-\n // filter so the error lists every available name.\n if (scorerNames?.length) {\n const available = evalConfig.scorers.map((s) => s.name);\n const unknown = scorerNames.filter((n) => !available.includes(n));\n if (unknown.length) {\n console.error(\n `Error: --scorers: unknown scorer(s): ${unknown.join(', ')}. Available: ${available.join(', ')}`,\n );\n process.exit(1);\n }\n evalConfig.scorers = evalConfig.scorers.filter((s) => scorerNames.includes(s.name));\n if (evalConfig.scorers.length === 0) {\n console.error('Error: --scorers matched no scorers.');\n process.exit(1);\n }\n }\n\n // Resolve executeWorkflow: custom export > registered workflow > error\n // Walk the ESM/CJS interop chain symmetrically with the default export\n // so named exports stay visible when tsx loads the eval module as CJS.\n const exported = pickExport<unknown>(mod, 'executeWorkflow');\n\n // Validate that the export, if present, is callable. Catches the\n // `export const executeWorkflow = 'foo'` typo at the boundary instead\n // of crashing deep inside trackExecution with a confusing stack.\n let customExecute: EvalExecuteWorkflow | undefined;\n if (typeof exported === 'function') {\n customExecute = exported as EvalExecuteWorkflow;\n } else if (exported !== undefined) {\n console.error(\n `[axl-eval] Error: ${filePath} exports executeWorkflow but it is ${typeof exported}, not a function.`,\n );\n failedFiles++;\n continue;\n }\n\n let executeWorkflow: EvalExecuteWorkflow;\n\n if (customExecute) {\n // Wrap custom executeWorkflow with trackExecution for cost + metadata attribution\n executeWorkflow = async (input, rt) => {\n const {\n result,\n cost: trackedCost,\n metadata,\n } = await runtime.trackExecution(async () => {\n return customExecute(input, rt);\n });\n return {\n output: result.output,\n cost: result.cost ?? trackedCost,\n metadata: result.metadata ?? metadata,\n };\n };\n } else if (runtime.getWorkflow(evalConfig.workflow)) {\n // No executeWorkflow exported but workflow is registered — use runtime.execute()\n executeWorkflow = async (input) => {\n const { result, cost, metadata } = await runtime.trackExecution(async () => {\n return runtime.execute(evalConfig.workflow, input);\n });\n return { output: result, cost, metadata };\n };\n } else {\n // Fail loudly. The previous identity-passthrough fallback silently\n // produced all-zero scores in CI — exactly the kind of footgun the\n // \"fail loudly, not silently\" rule is meant to prevent. If you\n // genuinely want identity, export it explicitly:\n // export const executeWorkflow = async (input) => ({ output: input });\n //\n // Surface what WAS exported (top-level + .default) so users\n // recognize a typo like `execute_workflow` and can spot the\n // CJS-interop case that motivates this whole code path: when tsx\n // loads a `.ts` file as CJS, all named exports land on\n // `mod.default.<name>`. The chain walk above already handled this,\n // but a user who removed `executeWorkflow` will see exactly what\n // export names ARE present.\n const exportShape = (() => {\n const top = Object.keys(mod).filter((k) => k !== 'default');\n const fromDefault =\n mod.default && typeof mod.default === 'object'\n ? Object.keys(mod.default as Record<string, unknown>)\n : [];\n const all = [...new Set([...top, ...fromDefault])];\n return all.length > 0 ? `Found exports: [${all.join(', ')}].` : '';\n })();\n console.error(\n `[axl-eval] Error: ${filePath} does not export an executeWorkflow function ` +\n `and no workflow named \"${evalConfig.workflow}\" is registered on the runtime.\\n` +\n (exportShape ? ` ${exportShape}\\n` : '') +\n ` Add either:\\n` +\n ` export async function executeWorkflow(input, runtime) { ... }\\n` +\n ` or register the workflow on your runtime via runtime.registerWorkflow(workflow).\\n` +\n ` Tip: if your eval previously worked, your package.json may be missing\\n` +\n ` \"type\": \"module\" — tsx loads .ts as CJS, putting named exports on\\n` +\n ` mod.default.<name>. The CLI handles that interop, but if you renamed\\n` +\n ` or removed the export the file path is your fix.`,\n );\n failedFiles++;\n continue;\n }\n\n // When --capture-traces is set, forward it to runEval so the runner\n // wraps each item's execution in a nested trackExecution({ captureTraces: true }).\n // This populates EvalItem.traces on both success (via tracked.traces)\n // and failure (via the axlCapturedTraces side-channel on the thrown\n // error). Nested trackExecution scopes walk the AsyncLocalStorage\n // parent chain, so the outer trackExecution above still observes\n // cost/metadata correctly.\n // Always pass the SIGINT-driven signal so Ctrl+C aborts in-flight\n // workflows + scorer LLM calls instead of dropping the connection mid-request.\n const runOptions = { signal, ...(captureTraces ? { captureTraces: true } : {}) };\n\n // Stamp filtered runs so a scorer-subset result can't be silently used\n // as a full baseline. `compare` warns (and refuses to gate) on it. Must\n // be applied to EVERY run in a multi-run batch — Studio's multi-run\n // builder spreads only run[0]'s metadata.\n const scorersRun = scorerNames?.length ? evalConfig.scorers.map((s) => s.name) : undefined;\n const stampFiltered = (result: EvalResult) => {\n if (scorersRun) {\n result.metadata.scorerFiltered = true;\n result.metadata.scorersRun = scorersRun;\n }\n };\n\n if (runs > 1) {\n // Multi-run mode. Buffer per-run results locally so we can mark the\n // batch correctly before committing to outer `results[]`. The\n // original bug was the opposite: results pushed eagerly per-run, a\n // throw mid-batch left partial results posing as a complete run\n // (no `fromPartialBatch` marker, aggregate never ran). The fix ISN'T\n // to throw the partials away — those runs cost money and have\n // statistical signal. Instead we preserve them with explicit\n // partial-batch metadata, aggregate over what completed, and exit\n // non-zero so CI knows the batch wasn't clean.\n const { randomUUID } = await import('node:crypto');\n const runGroupId = randomUUID();\n const runResults: EvalResult[] = [];\n let runFailure: Error | undefined;\n\n for (let r = 0; r < runs; r++) {\n if (signal.aborted) break;\n console.error(`[axl-eval] Run ${r + 1}/${runs}...`);\n try {\n const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);\n result.metadata.runGroupId = runGroupId;\n result.metadata.runIndex = r;\n stampFiltered(result);\n runResults.push(result);\n } catch (err) {\n runFailure = err instanceof Error ? err : new Error(String(err));\n console.error(`[axl-eval] Run ${r + 1}/${runs} failed: ${runFailure.message}`);\n // Stop attempting further runs — we don't know whether this is\n // transient (rate limit) or permanent (auth failure), and\n // burning more API calls speculatively is hostile. Preserve\n // what completed.\n break;\n }\n }\n\n if (runResults.length === 0) {\n // Nothing completed — surface as a per-file failure. No partial\n // artifact to write; the outer try/catch error message would\n // duplicate the \"Run 1/N failed\" line we already printed, so\n // increment and continue without re-throwing.\n console.error(\n `[axl-eval] Aborting ${filePath}: no runs completed (${runFailure?.message ?? 'unknown failure'}).`,\n );\n failedFiles++;\n continue;\n }\n\n const partial = runResults.length < runs;\n if (partial) {\n // Mark every completed run so any consumer reading\n // `result.metadata` sees the partial-batch context. `aggregateRuns`\n // computes statistics over `runResults.length`, which is the\n // honest sample size to report.\n //\n // The flag is `fromPartialBatch` — a per-run marker meaning\n // \"this run came from a batch that did not complete.\" It is\n // NOT named `partialBatch` because that read as \"this run is\n // partial,\" and a defensive consumer wrote\n // `if (run.metadata.partialBatch) skipThisRun()`, throwing\n // away successful work that cost real money. The flag describes\n // the BATCH the run belongs to, not the run itself.\n //\n // `runFailure?.message` can be empty (e.g. `new Error('')` from a\n // misbehaving provider); fall back to `String(runFailure)` so the\n // banner never renders a blank \"Stopped after:\" line. Skip the\n // field entirely if both are empty so `buildMultiRunResult`'s\n // empty-string filter doesn't have to defensively guess.\n const failureMsg = runFailure\n ? runFailure.message || String(runFailure) || undefined\n : undefined;\n for (const r of runResults) {\n r.metadata.fromPartialBatch = true;\n r.metadata.batchCompleted = runResults.length;\n r.metadata.batchAttempted = runs;\n if (failureMsg) r.metadata.batchFailure = failureMsg;\n }\n console.error(\n `[axl-eval] PARTIAL: ${runResults.length} of ${runs} runs completed for ${filePath}.`,\n );\n failedFiles++;\n }\n\n const summary = aggregateRuns(runResults);\n console.log('\\n' + formatMultiRunTable(summary) + '\\n');\n for (const r of runResults) results.push(r);\n\n // Failure-rate gate (opt-in via failOnScorerErrorRate) + total-wipeout\n // guard (always). Report every run; count the file as failed unless it\n // was already counted as a partial batch above (avoids double-count).\n let anyFailing = false;\n for (const r of runResults) {\n // Call both (no short-circuit) so each prints its own diagnostic.\n const wipeout = reportTotalWipeout(r, filePath);\n const degraded = reportDegraded(r, filePath);\n reportFullySkippedScorers(r, filePath); // advisory only\n if (wipeout || degraded) anyFailing = true;\n }\n if (anyFailing && !partial) failedFiles++;\n } else {\n const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);\n stampFiltered(result);\n results.push(result);\n\n console.log('\\n' + formatTable(result) + '\\n');\n // Total-wipeout guard (always) + scorer failure-rate gate (opt-in). They\n // are mutually exclusive (a wipeout has no scored items, so no scorer can\n // be degraded), but report both for clarity; either fails the file.\n const wipeout = reportTotalWipeout(result, filePath);\n const degraded = reportDegraded(result, filePath);\n reportFullySkippedScorers(result, filePath); // advisory only\n if (wipeout || degraded) failedFiles++;\n }\n } catch (err) {\n console.error(\n `Error running eval ${filePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n failedFiles++;\n }\n }\n\n if (outputPath && results.length > 0) {\n const output = results.length === 1 ? results[0] : results;\n const outputDir = path.dirname(path.resolve(outputPath));\n await mkdir(outputDir, { recursive: true });\n await writeFileAsync(outputPath, JSON.stringify(output, null, 2), 'utf-8');\n console.log(`Results saved to ${outputPath}`);\n }\n } finally {\n await runtime.shutdown().catch(() => {});\n }\n\n // Exit non-zero if any eval file failed to load/run. Previously we only\n // exited non-zero when zero evals succeeded, which silently masked\n // misconfigured files in mixed-success runs (a CI footgun).\n if (failedFiles > 0 || results.length === 0) {\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","/**\n * CLI utilities for `axl-eval`.\n *\n * The actual implementations of the loader, glob, and config-detection\n * helpers live in `@axlsdk/axl`'s `cli-internals` so a fix to (e.g.) the\n * tsx loader registration applies to both axl-eval and axl-studio\n * atomically. This file is now a thin re-export plus the symmetric\n * module-resolution helpers from `@axlsdk/axl`.\n */\n\nexport {\n resolveRuntime,\n pickDefault,\n pickExport,\n CONFIG_CANDIDATES,\n findConfig,\n needsTsxLoader,\n importModule,\n expandGlob,\n registerConditions,\n} from '@axlsdk/axl';\n","import type { EvalConfig } from './types.js';\n\n/**\n * Validate an eval config's required fields and produce a diagnostic string\n * if invalid. Returns `undefined` when the config passes.\n *\n * Returns a single-line message tail (caller prepends the file path) so the\n * full error reads like: `Error: <file> <message>`.\n *\n * Goes beyond a falsy check to:\n * - catch `scorers: []` which previously silently ran with no scorers\n * - reject scorers arrays that contain non-callable entries (e.g. typo\n * `scorers: [null, undefined]` would crash deep inside `runEval`)\n * - reject non-string `workflow` (downstream `runtime.getWorkflow(...)`\n * would silently miss it)\n * - reject non-object `dataset` (must be a `dataset()` factory result)\n * - provide a `Got: { keys: [...] }` hint so users can spot typos like\n * `scorerS` vs `scorers` immediately\n *\n * Lives in its own module so unit tests can import it without triggering\n * `cli.ts`'s `main()` at module load time.\n */\nexport function validateEvalConfig(cfg: unknown): string | undefined {\n if (!cfg || typeof cfg !== 'object') {\n return `does not export a valid eval config — got ${cfg === null ? 'null' : typeof cfg}.`;\n }\n const c = cfg as Partial<EvalConfig>;\n const missing: string[] = [];\n if (!c.workflow) missing.push('workflow');\n if (!c.dataset) missing.push('dataset');\n if (!c.scorers) missing.push('scorers');\n\n if (missing.length > 0) {\n const keys = Object.keys(c as object).slice(0, 10);\n const got = keys.length > 0 ? ` Got: { ${keys.join(', ')} }.` : '';\n return `does not export a valid eval config (missing ${missing.join(', ')}).${got}`;\n }\n\n if (typeof c.workflow !== 'string') {\n return `exports a non-string workflow (got ${typeof c.workflow}) — workflow must be the registered workflow name.`;\n }\n if (typeof c.dataset !== 'object' || Array.isArray(c.dataset)) {\n return `exports a non-object dataset (got ${Array.isArray(c.dataset) ? 'array' : typeof c.dataset}) — use the dataset() factory.`;\n }\n if (typeof (c.dataset as { getItems?: unknown }).getItems !== 'function') {\n return `exports a dataset without a getItems() method — use the dataset() factory.`;\n }\n if (!Array.isArray(c.scorers) || c.scorers.length === 0) {\n return `exports an empty scorers array — at least one scorer is required.`;\n }\n const badScorerIdx = c.scorers.findIndex(\n (s) => !s || typeof (s as { score?: unknown }).score !== 'function',\n );\n if (badScorerIdx !== -1) {\n return `exports a scorers array with a non-scorer entry at index ${badScorerIdx} — each scorer must have a score() method (use the scorer() factory).`;\n }\n // Concurrency knobs must be positive integers when present. The worker pool\n // defensively clamps non-positive/non-finite values to 1 at runtime, but a\n // config that wrote `concurrency: 0` (or NaN via `Number(env)`) almost\n // certainly has a bug — surface it loudly, matching the `--concurrency` flag.\n for (const key of ['concurrency', 'scorerConcurrency'] as const) {\n const v = (c as Record<string, unknown>)[key];\n if (v !== undefined && (typeof v !== 'number' || !Number.isInteger(v) || v < 1)) {\n return `exports an invalid ${key} (${typeof v === 'number' ? v : typeof v}) — must be a positive integer.`;\n }\n }\n // The failure-rate gate must fail loud on a bad value, symmetric with the\n // `--max-scorer-error-rate` flag. Without this, a typo (e.g. `5` meaning \"5%\")\n // only `console.warn`s at runtime and silently disables the gate the user\n // explicitly opted into — the exact silent-no-op the feature exists to catch.\n {\n const v = (c as Record<string, unknown>).failOnScorerErrorRate;\n if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1)) {\n return `exports an invalid failOnScorerErrorRate (${typeof v === 'number' ? v : typeof v}) — must be a number between 0 and 1.`;\n }\n }\n return undefined;\n}\n","/**\n * Argument parsing for the `axl-eval` CLI, factored out of `cli.ts` so it can be\n * unit-tested without importing `cli.ts` (whose module body runs `main()` on\n * import).\n */\n\nexport const KNOWN_FLAGS = new Set([\n '--output',\n '--config',\n '--conditions',\n '--fail-on-regression',\n '--threshold',\n '--runs',\n '--capture-traces',\n '--concurrency',\n '--scorers',\n]);\n\n/**\n * The subset of flags `parseEvalArgs` consumes a following value for. NOT every\n * value-taking flag in the CLI: `--threshold` takes a value too but is parsed\n * by `runCompare`'s own parser, not here. Every entry MUST also be in\n * {@link KNOWN_FLAGS} — a value-flag missing from KNOWN_FLAGS would be rejected\n * as \"unknown\", and one missing from VALUE_FLAGS would silently swallow its\n * value into `paths`. A test asserts `VALUE_FLAGS ⊆ KNOWN_FLAGS`.\n */\nexport const VALUE_FLAGS = new Set([\n '--output',\n '--config',\n '--conditions',\n '--runs',\n '--concurrency',\n '--scorers',\n]);\n\nexport type ParsedEvalArgs = {\n outputPath?: string;\n configArg?: string;\n conditions: string[];\n runs: number;\n captureTraces: boolean;\n /** Item-level concurrency override (flag value). Clamped to >= 1. */\n concurrency?: number;\n /** Scorer names from `--scorers` (deduped, in first-seen order). */\n scorerNames?: string[];\n paths: string[];\n};\n\n/**\n * Read a positive integer from an environment variable. Returns `undefined`\n * when the var is absent, non-numeric, or `<= 0`, so a malformed value falls\n * through to the next precedence tier rather than poisoning the run.\n */\nexport function envInt(name: string): number | undefined {\n const raw = process.env[name];\n if (raw == null || raw === '') return undefined;\n const n = parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0) return undefined;\n return n;\n}\n\nexport function parseEvalArgs(args: string[]): ParsedEvalArgs {\n let outputPath: string | undefined;\n let configArg: string | undefined;\n let conditions: string[] = [];\n let runs = 1;\n let captureTraces = false;\n let concurrency: number | undefined;\n let scorerNames: string[] | undefined;\n const paths: string[] = [];\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (VALUE_FLAGS.has(arg)) {\n if (i + 1 >= args.length) {\n console.error(`Error: ${arg} requires a value`);\n process.exit(1);\n }\n const value = args[++i];\n if (arg === '--output') outputPath = value;\n else if (arg === '--config') configArg = value;\n else if (arg === '--runs') runs = Math.max(1, parseInt(value, 10) || 1);\n else if (arg === '--concurrency') {\n // Clamp to a floor of 1 with a warning (matches `--runs`' clamp ethos\n // but surfaces the typo). A `0` here would otherwise spawn zero workers.\n // Note the intentional asymmetry with `envInt` above: an explicit flag\n // typo is worth a warning, whereas a malformed ambient env var falls\n // through silently to the next precedence tier.\n const n = parseInt(value, 10);\n if (!Number.isFinite(n) || n <= 0) {\n console.error(`[axl-eval] Ignoring invalid --concurrency \"${value}\"; using 1.`);\n concurrency = 1;\n } else {\n concurrency = n;\n }\n } else if (arg === '--scorers') {\n scorerNames = [\n ...new Set(\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n ),\n ];\n } else {\n conditions = value\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean);\n }\n } else if (arg === '--capture-traces') {\n // Boolean flag — no value consumed.\n captureTraces = true;\n } else if (arg.startsWith('--')) {\n if (!KNOWN_FLAGS.has(arg)) {\n console.error(`Unknown flag: ${arg}`);\n process.exit(1);\n }\n } else {\n paths.push(arg);\n }\n }\n\n return {\n outputPath,\n configArg,\n conditions,\n runs,\n captureTraces,\n concurrency,\n scorerNames,\n paths,\n };\n}\n","/**\n * Terminal rendering helpers for the `axl-eval` CLI, factored out of `cli.ts` so\n * they can be unit-tested without importing `cli.ts` (whose module body runs\n * `main()` on import) — same reason as `cli-args.ts`.\n */\n\nimport type { ModelTimingStats } from './types.js';\n\n/**\n * Render the per-model provider-latency rows that sit under the wall-clock\n * `Timing` row.\n *\n * Every figure is per PROVIDER CALL — the whole `modelTiming` surface is now\n * one kind of average, so there is nothing here a reader can accidentally\n * divide by the call count and get wrong.\n *\n * `wire` and `first token` carry a `mean/p95` pair because those are the two\n * model-comparison figures and a tail matters for both. `queued` and `retries`\n * print a mean only: they describe Axl's own limiter and the provider's\n * throttling on the day of the run, so a p95 would lengthen every row without\n * changing a model choice. The full distributions stay on the JSON artifact.\n *\n * Units are milliseconds and every number is suffixed, because these are\n * routinely sub-second and the `Timing` row above renders seconds — an\n * unlabelled column would silently mix the two.\n *\n * `first token` appears only when at least one call actually streamed one, so a\n * non-streaming run shows no misleading `0ms`.\n *\n * @param modelTiming `EvalSummary.modelTiming`, or undefined for no rows.\n * @param nameWidth The scorer-name column width, so model names line up under it.\n */\nexport function formatModelTimingLines(\n modelTiming: Record<string, ModelTimingStats> | undefined,\n nameWidth: number,\n): string[] {\n if (!modelTiming) return [];\n return Object.entries(modelTiming).map(([model, t]) => {\n const parts = [`wire ${formatMs(t.wireMs.mean)}/${formatMs(t.wireMs.p95)}`];\n if (t.firstTokenMs) {\n parts.push(`first token ${formatMs(t.firstTokenMs.mean)}/${formatMs(t.firstTokenMs.p95)}`);\n }\n parts.push(`queued ${formatMs(t.queuedMs.mean)}`);\n parts.push(`retries ${formatMs(t.retryMs.mean)}`);\n const calls = `${t.calls} call${t.calls === 1 ? '' : 's'}`;\n return ` ${model.padEnd(Math.max(nameWidth - 2, 0))} ${parts.join(' · ')} (${calls}, mean/p95 per call)`;\n });\n}\n\n/** Whole milliseconds with an explicit unit. Latencies here are per call, so a\n * seconds-with-one-decimal rendering would collapse most of them to `0.0s`. */\nfunction formatMs(ms: number): string {\n return `${Math.round(ms)}ms`;\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -415,6 +415,27 @@ async function runEval(config, executeWorkflow, runtime, options) {
415
415
  const evalItems = new Array(items.length);
416
416
  let totalCost = 0;
417
417
  let budgetExceeded = false;
418
+ const runCallSamples = /* @__PURE__ */ new Map();
419
+ function absorbTiming(rollup, evalItem) {
420
+ if (!rollup) return;
421
+ const perItem = {};
422
+ for (const [model, bucket] of Object.entries(rollup)) {
423
+ const sums = {
424
+ calls: bucket.calls,
425
+ queuedMs: bucket.queuedMs,
426
+ retryMs: bucket.retryMs,
427
+ wireMs: bucket.wireMs,
428
+ ...bucket.firstTokenMs !== void 0 && bucket.firstTokenCalls !== void 0 ? { firstTokenMs: bucket.firstTokenMs, firstTokenCalls: bucket.firstTokenCalls } : {}
429
+ };
430
+ perItem[model] = sums;
431
+ const samples = bucket.samples;
432
+ if (!samples) continue;
433
+ const pooled = runCallSamples.get(model) ?? [];
434
+ for (const sample of samples) pooled.push(sample);
435
+ runCallSamples.set(model, pooled);
436
+ }
437
+ evalItem.timing = perItem;
438
+ }
418
439
  async function processItem(item, itemIndex) {
419
440
  if (options?.signal?.aborted) {
420
441
  evalItems[itemIndex] = {
@@ -449,13 +470,14 @@ async function runEval(config, executeWorkflow, runtime, options) {
449
470
  if (options?.captureTraces) {
450
471
  const tracked = await runtime.trackExecution(
451
472
  async () => executeWorkflow(item.input, runtime),
452
- { captureTraces: true }
473
+ { captureTraces: true, captureTimingSamples: true }
453
474
  );
454
475
  evalItem.duration = Date.now() - itemStart;
455
476
  evalItem.output = tracked.result.output;
456
477
  evalItem.cost = extractUserCost(tracked.result) ?? tracked.cost;
457
478
  if (tracked.unpriced) evalItem.unpriced = true;
458
479
  evalItem.metadata = extractUserMetadata(tracked.result) ?? tracked.metadata;
480
+ absorbTiming(tracked.modelTiming, evalItem);
459
481
  if (tracked.traces && tracked.traces.length > 0) {
460
482
  evalItem.traces = tracked.traces;
461
483
  }
@@ -463,12 +485,24 @@ async function runEval(config, executeWorkflow, runtime, options) {
463
485
  totalCost += evalItem.cost;
464
486
  }
465
487
  } else {
466
- const result = await executeWorkflow(item.input, runtime);
488
+ let trackedTiming;
489
+ let result;
490
+ if (typeof runtime.trackExecution === "function") {
491
+ const tracked = await runtime.trackExecution(
492
+ async () => executeWorkflow(item.input, runtime),
493
+ { captureTraces: false, captureTimingSamples: true }
494
+ );
495
+ result = tracked.result;
496
+ trackedTiming = tracked.modelTiming;
497
+ } else {
498
+ result = await executeWorkflow(item.input, runtime);
499
+ }
467
500
  evalItem.duration = Date.now() - itemStart;
468
501
  evalItem.output = result.output;
469
502
  evalItem.cost = extractUserCost(result);
470
503
  const meta = extractUserMetadata(result);
471
504
  if (meta) evalItem.metadata = meta;
505
+ absorbTiming(trackedTiming, evalItem);
472
506
  if (evalItem.cost != null) {
473
507
  totalCost += evalItem.cost;
474
508
  }
@@ -536,6 +570,19 @@ async function runEval(config, executeWorkflow, runtime, options) {
536
570
  }
537
571
  const durations = evalItems.filter((i) => !i.error && i.duration != null).map((i) => i.duration);
538
572
  const timing = durations.length > 0 ? computeStats(durations) : void 0;
573
+ const modelTiming = runCallSamples.size > 0 ? Object.fromEntries(
574
+ [...runCallSamples.entries()].map(([model, samples]) => {
575
+ const firstToken = samples.map((s) => s.firstTokenMs).filter((v) => v != null);
576
+ const stats = {
577
+ calls: samples.length,
578
+ wireMs: computeStats(samples.map((s) => s.wireMs)),
579
+ queuedMs: computeStats(samples.map((s) => s.queuedMs)),
580
+ retryMs: computeStats(samples.map((s) => s.retryMs)),
581
+ ...firstToken.length > 0 ? { firstTokenMs: computeStats(firstToken), firstTokenCalls: firstToken.length } : {}
582
+ };
583
+ return [model, stats];
584
+ })
585
+ ) : void 0;
539
586
  const totalModelCalls = /* @__PURE__ */ new Map();
540
587
  for (const item of evalItems) {
541
588
  const itemCounts = item.metadata?.modelCallCounts;
@@ -607,6 +654,7 @@ async function runEval(config, executeWorkflow, runtime, options) {
607
654
  failures,
608
655
  scorers: scorerStats,
609
656
  timing,
657
+ ...modelTiming ? { modelTiming } : {},
610
658
  ...degraded ? { degraded } : {}
611
659
  }
612
660
  };