src/orchestrator/summary.ts — end-of-run summary lines
Purpose
Section titled “Purpose”Format the closing footer block — this is the run’s only banner.
The top-of-run header was removed; the run context (version, requested
tasks, project/task/worker counts, cache mode, affected-scope bar) now
rides the footer above the result meters, printed once at the end where
the eye lands. Always printed after a vx run invocation completes
(success or failure). Counts only real tasks — group nodes are filtered
upstream by orchestrator.run before this function is called.
Public surface
Section titled “Public surface”export interface SummaryStats { failed: number successful: number skipped: number total: number upToDate: number restoredLocal: number restoredRemote: number miss: number noCache?: number // a task with no `cache` block never consulted the cache left?: number // still to run: the live section's gray remainder; 0 in the final summary spread: { maxMs: number; minMs: number; sumMs: number; count: number } | null // the time row's per-task spread held?: { count: number; sumMs: number } // what an `admit` policy held, summed}
export interface RunContext { version: string packageCount: number // projects covered → the bar's "affected" half concurrency?: number // worker-pool size (info row) remoteCacheEnabled: boolean workspaceProjectCount?: number // total projects → the bar's denominator}
// The meters + info + time rows from counted stats — the live status region renders these as the run proceeds.export function formatSummarySection( stats: SummaryStats, totalMs: number, colors?: ColorSupport, context?: RunContext,): string[]
export function formatRunSummary( outcomes: readonly TaskOutcome[], totalMs: number, colors?: ColorSupport, context?: RunContext,): string[]
export function formatAbortedSection(outcomes: readonly TaskOutcome[]): string[]export function formatSkippedSection(outcomes: readonly TaskOutcome[]): string[]export function formatFlakySection(findings: readonly FlakyFinding[]): string[]
export function formatDuration(ms: number): stringformatSkippedSection names each skipped task under the failure (or
aborted task) at the root of its chain — blockedBy — with fail-fast’s
skips under their own heading; a blocked group is left out, as every
counter leaves it out, and a long list is capped on one line with the
rest counted. formatAbortedSection lists what a shutdown signal took
down (✗ id — exit N, nothing cached), and under Not started: the
tasks the stop reached before they ran — aborted outcomes with no
wallclockStartNs, which only a started task carries — with no exit,
since the scheduler’s exit 1 on them is not one. Both print after the footer, beside the Flaky section.
formatFlakySection is the post-footer section naming the tasks this
run proved flaky (detectFlaky, history.md): ✗ id — failed on inputs that passed N× before, ✓ id — passed on inputs that failed N× before,
with · N attempts this run when the run retried; empty when nothing
was. It prints beside formatAbortedSection, after the footer.
formatRunSummary returns an array of lines (caller writes one per
log.status). Leading blank line is included so the summary stands
apart from the last framed block. When context is omitted (the live
status region, which fills in the meters as the run proceeds) the rule
reads a bare vx and the projects / info rows are skipped — the
meters-only section the region renders.
Format
Section titled “Format”─ vx 0.0.0 ─────────────────────────────────────────────────── projects ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 1 affected · 2 total tasks ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ 4 success · 4 total cache ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ 4 miss
info 10 workers · local cache time 248ms · max 239ms · avg 215ms · min 190msLabels pad to 8, bars start at column 12, the rule + bars span 50
cells. projects (affected vs workspace total) leads the meter stack;
tasks and cache follow, the tasks legend carrying a dim N total.
A blank line separates the meters from the info row (worker pool +
cache mode, and admit held N tasks · Ns when a policy held any — a
sum, said as one) and the time row. projects and info only
render when a RunContext is passed (the final footer); the live
region shows the meters alone. The block above is
formatRunSummary on four successes of 239, 190, 215 and 216 ms with
that context, pinned in tests/summary.test.ts.
Colors:
successfulis green.failedis bold red (only shown when N > 0).skippedis yellow (only shown when N > 0).
Duration:
<1s→Nms- ≥1s →
N.NNs
tests/summary.test.ts:
- Mixed-status row (success + failed + skipped + cache).
- All-success rendering.
- Empty outcomes (zero-task summary).
- Stacked state meters (50 cells, largest-remainder allocation, every non-zero bucket gets >= 1 cell): tasks bar = failed/success/skipped, cache bar = miss/no-cache (dim: a task with no
cacheblock never consulted it)/up-to-date/local/remote; color-coded legends below each bar. - Gradient wordmark rule (violet -> pink across the dashes).
- Failed task ids are never listed — the count lives in the legend (the frames above carry the names; a run can fail hundreds).
- The run context folds into the footer (version on the rule, the
info row, the projects bar); no context keeps a bare
vxrule. - The Skipped section: each skipped task under the failure at the root of its chain, fail-fast and an aborted upstream named as such, a blocked group left out, the names capped on one line.
- Time row: blank line above, total + dim ‘max / avg / min’ per-task spread (skipped excluded).
- Duration formatting (sub-second vs second+).
- The Flaky section: exact lines for a failure that passed before, a pass that failed before with a retry, and a retry with no history; empty for no findings.