Skip to content
GitHubRSS

src/orchestrator/summary.ts — end-of-run summary lines

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.

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): string

formatSkippedSection 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.

─ 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 190ms

Labels 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:

  • successful is green.
  • failed is bold red (only shown when N > 0).
  • skipped is 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 cache block 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 vx rule.
  • 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.