@vzn/vx 0.0.650 → 0.0.651

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +5 -20
  2. package/package.json +5 -5
  3. package/schemas/last.json +15 -0
  4. package/src/bin.ts +9 -31
  5. package/src/cache/cache.ts +202 -25
  6. package/src/cache/file-hashes.ts +3 -0
  7. package/src/cache/git-inputs.ts +188 -39
  8. package/src/cache/index.ts +1 -0
  9. package/src/cache/inputs.ts +3 -0
  10. package/src/cache/layer.ts +11 -0
  11. package/src/cache/layered-cache.ts +4 -0
  12. package/src/cache/linked-inputs.ts +3 -0
  13. package/src/cache/lockfile-memo.ts +3 -2
  14. package/src/cache/output-index.ts +5 -2
  15. package/src/cache/remote-lane.ts +2 -1
  16. package/src/cache/run-history.ts +11 -1
  17. package/src/cache/schema.ts +11 -1
  18. package/src/cli/cache.ts +26 -16
  19. package/src/cli/completions.ts +4 -2
  20. package/src/cli/docs.ts +10 -11
  21. package/src/cli/help.ts +12 -23
  22. package/src/cli/index.ts +4 -9
  23. package/src/cli/info.ts +15 -7
  24. package/src/cli/init.ts +23 -15
  25. package/src/cli/last.ts +213 -203
  26. package/src/cli/lock.ts +55 -5
  27. package/src/cli/plan-format.ts +100 -118
  28. package/src/cli/plugin-commands.ts +8 -2
  29. package/src/cli/plugin-templates.ts +2 -2
  30. package/src/cli/run.ts +46 -27
  31. package/src/cli/select.ts +43 -5
  32. package/src/cli/show.ts +38 -42
  33. package/src/cli/task-verb.ts +22 -0
  34. package/src/cli/upgrade.ts +3 -1
  35. package/src/cli/watch-fs.ts +6 -1
  36. package/src/cli/watch-judge.ts +2 -1
  37. package/src/cli/watch.ts +83 -44
  38. package/src/cli/why.ts +123 -89
  39. package/src/cli/workspace-config.ts +2 -1
  40. package/src/exec/executor.ts +2 -1
  41. package/src/exec/kill-tree.ts +58 -5
  42. package/src/exec/local-executor.ts +1 -1
  43. package/src/exec/pipes.ts +152 -0
  44. package/src/exec/runner.ts +199 -182
  45. package/src/exec/sandbox-binds.ts +1 -1
  46. package/src/exec/sandbox-runtime.ts +11 -2
  47. package/src/exec/spawn-off.ts +154 -0
  48. package/src/exec/warm-shell.ts +146 -0
  49. package/src/graph/critical-path.ts +40 -28
  50. package/src/graph/priorities.ts +98 -44
  51. package/src/graph/scheduler.ts +14 -10
  52. package/src/graph/task-graph.ts +27 -1
  53. package/src/handoff.ts +10 -7
  54. package/src/history/history.ts +5 -0
  55. package/src/history/metrics.ts +9 -1
  56. package/src/index.ts +3 -1
  57. package/src/orchestrator/doctor.ts +1 -1
  58. package/src/orchestrator/execute-task.ts +16 -2
  59. package/src/orchestrator/graph-memo.ts +50 -1
  60. package/src/orchestrator/graph-stage.ts +6 -0
  61. package/src/orchestrator/index.ts +3 -0
  62. package/src/orchestrator/live-usage.ts +16 -11
  63. package/src/orchestrator/locked-digests.ts +63 -0
  64. package/src/orchestrator/lockfile-claim.ts +108 -29
  65. package/src/orchestrator/miss-save.ts +31 -3
  66. package/src/orchestrator/persistent-task.ts +3 -1
  67. package/src/orchestrator/plan.ts +5 -1
  68. package/src/orchestrator/plugin-host.ts +61 -0
  69. package/src/orchestrator/plugin.ts +47 -2
  70. package/src/orchestrator/prepare.ts +16 -1
  71. package/src/orchestrator/projects.ts +4 -1
  72. package/src/orchestrator/run-context.ts +29 -10
  73. package/src/orchestrator/run-failures.ts +1 -1
  74. package/src/orchestrator/run-records.ts +10 -0
  75. package/src/orchestrator/run.ts +53 -22
  76. package/src/orchestrator/sandbox-request.ts +2 -2
  77. package/src/orchestrator/signals.ts +8 -5
  78. package/src/orchestrator/task-hash.ts +17 -3
  79. package/src/report/events.ts +3 -11
  80. package/src/report/forecast.ts +10 -32
  81. package/src/report/framed-output.ts +272 -173
  82. package/src/report/index.ts +8 -3
  83. package/src/report/logger.ts +73 -38
  84. package/src/report/plain-output.ts +1 -1
  85. package/src/report/status-line.ts +49 -357
  86. package/src/report/summary.ts +100 -131
  87. package/src/ui/ansi.ts +48 -0
  88. package/src/ui/components.ts +506 -0
  89. package/src/ui/index.ts +79 -0
  90. package/src/ui/kit.ts +37 -0
  91. package/src/ui/live-region.ts +363 -0
  92. package/src/{util/refusal-notice.ts → ui/refusal.ts} +7 -5
  93. package/src/ui/terminal.ts +51 -0
  94. package/src/ui/theme.ts +224 -0
  95. package/src/ui/version-line.ts +28 -0
  96. package/src/util/config-frame.ts +65 -10
  97. package/src/util/edit-distance.ts +6 -1
  98. package/src/util/index.ts +2 -15
  99. package/src/util/paths.ts +20 -2
  100. package/src/util/secret-mask.ts +39 -3
  101. package/src/util/settle.ts +31 -0
  102. package/src/util/timing.ts +4 -16
  103. package/src/workspace/affected.ts +89 -30
  104. package/src/workspace/config-schema.ts +4 -2
  105. package/src/workspace/dependencies-lock.ts +191 -0
  106. package/src/workspace/filter.ts +28 -0
  107. package/src/workspace/index.ts +9 -0
  108. package/src/workspace/lockfile.ts +159 -6
  109. package/src/workspace/migration.ts +4 -3
  110. package/src/workspace/workspace.ts +3 -0
  111. package/src/util/colors.ts +0 -133
  112. package/src/util/report.ts +0 -44
  113. package/src/util/ui.ts +0 -258
  114. /package/src/{report/path-links.ts → ui/links.ts} +0 -0
  115. /package/src/{report → ui}/terminal-signals.ts +0 -0
@@ -30,9 +30,14 @@ export {
30
30
  resolveOutputView,
31
31
  warnOf,
32
32
  } from './logger.js'
33
- export { formatPersistentList } from './framed-output.js'
34
- export { blocksCounts, createForecast } from './forecast.js'
35
- export { fileLocations, fileMemo, type OutputLocation } from './path-links.js'
33
+ export {
34
+ formatPersistentList,
35
+ formatRecordedRow,
36
+ planList,
37
+ type PlannedRow,
38
+ taskList,
39
+ } from './framed-output.js'
40
+ export { createForecast } from './forecast.js'
36
41
  export { plainOutput } from './plain-output.js'
37
42
  export type { LiveUsage } from './status-line.js'
38
43
  export { formatOutcomeTable, formatRunSummary } from './summary.js'
@@ -6,26 +6,22 @@ import {
6
6
  formatKeptLines,
7
7
  formatPersistentTailBlock,
8
8
  formatListHeader,
9
+ usualCell,
9
10
  formatTaskAbortedLine,
10
11
  formatTaskBlock,
11
12
  executedCells,
12
- waited,
13
13
  formatTaskExecutedLine,
14
14
  flakyNote,
15
15
  pathNote,
16
16
  formatTaskHashLine,
17
17
  formatTaskHitLine,
18
18
  formatTaskSkippedLine,
19
- leavesStyle,
20
- RESET,
21
19
  type OutputChunk,
22
20
  } from './framed-output.js'
23
21
  import {
24
- createOutputWriter,
25
22
  formatFailureLine,
26
23
  formatStatusRegion,
27
24
  formatStepLine,
28
- type StatusStream,
29
25
  type WorkerSlot,
30
26
  } from './status-line.js'
31
27
  import {
@@ -44,21 +40,26 @@ import {
44
40
  resetTail,
45
41
  tailText,
46
42
  type Tail,
47
- detectColors,
48
43
  envFlagOn,
49
- type ColorSupport,
50
- ui,
51
44
  } from '../util/index.js'
52
- import { isCacheHit } from '../graph/index.js'
53
- import { failedLabel, outcomeWord, ranNoCache } from './events.js'
54
- import type { BlockedTask, Forecast } from './forecast.js'
45
+ import * as ui from '../ui/index.js'
55
46
  import {
47
+ createOutputWriter,
48
+ detectColors,
49
+ hasEscape,
50
+ leavesStyle,
56
51
  notifyEscape,
57
52
  PROGRESS_CLEAR,
58
53
  progressEscape,
54
+ STYLE_END,
59
55
  terminalLinks,
60
56
  terminalSignals,
61
- } from './terminal-signals.js'
57
+ type ColorSupport,
58
+ type StatusStream,
59
+ } from '../ui/index.js'
60
+ import { isCacheHit } from '../graph/index.js'
61
+ import { failedLabel, outcomeWord, ranNoCache } from './events.js'
62
+ import type { BlockedTask, Forecast } from './forecast.js'
62
63
 
63
64
  export interface Logger {
64
65
  /** Header / footer / status text. Written verbatim, one trailing \n added. */
@@ -92,8 +93,7 @@ export interface Logger {
92
93
  /** The run's canonical start (epoch ms) — equals the final summary's `startedAt`. */
93
94
  startedAtMs?: number
94
95
  }): void
95
- /** `waitedMs`: how long it waited ready for a slot, when not started at once. */
96
- taskStart?(node: TaskNode, info?: { waitedMs?: number }): void
96
+ taskStart?(node: TaskNode): void
97
97
  /** The task graph finished (any outcome); vx's own closing steps follow. Idempotent. */
98
98
  runEnd?(): void
99
99
  /**
@@ -355,9 +355,7 @@ export function defaultLogger(
355
355
  // Tasks the region showed on the critical path: their finished rows say so too.
356
356
  const onPath = new Set<string>()
357
357
  let blockedCount = 0
358
- let expected: ReadonlyMap<string, number> | undefined
359
358
  // Each task's usual time once history is read: the results' `±usual`.
360
- let usualOf: ReadonlyMap<string, number> | undefined
361
359
  let blocked: readonly BlockedTask[] | undefined
362
360
  let blockedBy: ReadonlyMap<string, number> | undefined
363
361
  let etaAtMs = 0
@@ -522,10 +520,8 @@ export function defaultLogger(
522
520
  : Math.min(deadlineMs ?? now, now)
523
521
  critical = f.critical
524
522
  blockedCount = f.waiting
525
- expected = f.expected
526
523
  blocked = f.blocked
527
524
  blockedBy = f.blockedBy
528
- usualOf = f.usual
529
525
  }
530
526
  const etaMs = deadlineMs === undefined ? undefined : deadlineMs - now
531
527
  if (signals.progress && total > 0) {
@@ -610,7 +606,7 @@ export function defaultLogger(
610
606
  summaryLines,
611
607
  ...(runContext?.whereRunning !== undefined ? { where: runContext.whereRunning } : {}),
612
608
  ...(runContext?.usage !== undefined ? { usage: runContext.usage } : {}),
613
- ...(expected !== undefined ? { expected } : {}),
609
+ ...(runContext?.usual !== undefined ? { usual: runContext.usual } : {}),
614
610
  ...(critical !== undefined ? { critical } : {}),
615
611
  },
616
612
  colors,
@@ -660,7 +656,7 @@ export function defaultLogger(
660
656
  // task`, and took its colour.
661
657
  let streamStyled = false
662
658
  const endStreamLine = (): void => {
663
- const end = (streamStyled ? RESET : '') + (streamMidLine ? '\n' : '')
659
+ const end = (streamStyled ? STYLE_END : '') + (streamMidLine ? '\n' : '')
664
660
  streamStyled = false
665
661
  streamMidLine = false
666
662
  if (end.length > 0) logWrite(end)
@@ -672,10 +668,11 @@ export function defaultLogger(
672
668
  const streamWrite = (chunk: string): void => {
673
669
  streamedSinceBlock = true
674
670
  if (chunk.length > 0) streamMidLine = !chunk.endsWith('\n')
675
- if (chunk.includes('\x1b[')) streamStyled = leavesStyle(chunk)
671
+ if (hasEscape(chunk)) streamStyled = leavesStyle(chunk)
676
672
  logWrite(chunk)
677
673
  }
678
674
  const logWrite = (chunk: string): void => {
675
+ if (panelOpen !== undefined && !inPanel && chunk.length > 0) closePanel()
679
676
  if (chunk.length > 0) endsBlank = chunk.endsWith('\n\n')
680
677
  if (!logOpened && chunk.length > 0) {
681
678
  logOpened = true
@@ -688,15 +685,33 @@ export function defaultLogger(
688
685
  taskWrite(`${line}\n`)
689
686
  lineEmitted = true
690
687
  }
691
- // The list's header, once over its first row, where the live region
692
- // heads its own rows the same way (2026-10-21).
693
- let headed = !writer.enabled
688
+ // The open panel: rows of one kind share one box, the Tasks list on the
689
+ // log's stream and the Warnings on stderr; anything else said closes it
690
+ // first, and the next row opens it again. Painted, a box; plain on a
691
+ // terminal, the Tasks rule and header over the rows, as the live region
692
+ // heads its own (2026-10-21); piped, the bare rows a script reads.
693
+ const panels = colors.enabled || writer.enabled
694
+ let panelOpen: 'Tasks' | 'Warnings' | undefined
695
+ let inPanel = false
696
+ function closePanel(): void {
697
+ const was = panelOpen
698
+ if (was === undefined) return
699
+ panelOpen = undefined
700
+ }
694
701
  const emitRow = (line: string): void => {
695
- if (!headed) {
696
- headed = true
702
+ if (!panels) {
703
+ emitLine(line)
704
+ return
705
+ }
706
+ if (panelOpen === 'Warnings') closePanel()
707
+ inPanel = true
708
+ if (panelOpen !== 'Tasks') {
709
+ emitLine(ui.panelTop('Tasks', colors))
697
710
  emitLine(formatListHeader(colors))
711
+ panelOpen = 'Tasks'
698
712
  }
699
713
  emitLine(line)
714
+ inPanel = false
700
715
  }
701
716
  // formatTaskBlock returns '' for group tasks (no exec) — skip the
702
717
  // write so a stray newline doesn't sneak into the output.
@@ -739,14 +754,14 @@ export function defaultLogger(
739
754
 
740
755
  // How many tasks are downstream of each: every results row's `blocks`.
741
756
  const blocksOf = (id: string): number | undefined => runContext?.blocks?.().get(id)
757
+ // A row's usual time: null for a task history never timed, undefined with no history to ask.
758
+ const usualMsOf = (id: string): number | null | undefined =>
759
+ runContext?.usual === undefined ? undefined : (runContext.usual(id) ?? null)
742
760
  const failureRow = (node: TaskNode, outcome: TaskOutcome): string =>
743
- formatFailureLine(
744
- node.id,
745
- outcome.durationMs,
746
- colors,
747
- cacheWordOf(outcome),
748
- executedCells(outcome, blocksOf(node.id)),
749
- ) +
761
+ formatFailureLine(node.id, outcome.durationMs, colors, cacheWordOf(outcome), {
762
+ ...executedCells(outcome, blocksOf(node.id)),
763
+ usual: usualCell(outcome.durationMs, usualMsOf(node.id), colors),
764
+ }) +
750
765
  (onPath.has(node.id) ? pathNote(colors) : '') +
751
766
  flakyNote(outcome, colors)
752
767
 
@@ -764,6 +779,7 @@ export function defaultLogger(
764
779
  // A live-streamed server keeps writing after its frame closes, so a
765
780
  // summary row or "exited with code" can follow its partial line.
766
781
  endStreamLine()
782
+ closePanel()
767
783
  if (line === '' && endsBlank) return
768
784
  endsBlank = line === ''
769
785
  writer.write(`${line}\n`)
@@ -771,7 +787,20 @@ export function defaultLogger(
771
787
  warn(line) {
772
788
  flushKept()
773
789
  endStreamLine()
774
- writer.writeTo(errOut, `${ui.tagged('warn', line, colors)}\n`)
790
+ if (!colors.enabled) {
791
+ writer.writeTo(errOut, `${ui.tagged('warn', line, colors)}\n`)
792
+ return
793
+ }
794
+ if (panelOpen === 'Tasks') closePanel()
795
+ if (panelOpen !== 'Warnings') {
796
+ writer.writeTo(errOut, `${ui.panelTop('Warnings', colors)}\n`)
797
+ panelOpen = 'Warnings'
798
+ }
799
+ const rows = line
800
+ .replace(/\n+$/, '')
801
+ .split('\n')
802
+ .map((l) => ui.tagged('warn', l, colors))
803
+ writer.writeTo(errOut, `${rows.join('\n')}\n`)
775
804
  },
776
805
  runStart(info) {
777
806
  total = info.total
@@ -779,7 +808,13 @@ export function defaultLogger(
779
808
  runContext = info.context
780
809
  startedAtMs = Date.now()
781
810
  started = true
782
- slots = Array.from({ length: Math.max(1, info.concurrency ?? 10) }, () => null)
811
+ // No more slots than tasks: one per allowed worker, every frame walked
812
+ // them all (`--concurrency=100000000` over six tasks took 80 s, and
813
+ // 2^32 threw here).
814
+ slots = Array.from(
815
+ { length: Math.max(1, Math.min(info.concurrency ?? 10, info.total)) },
816
+ () => null,
817
+ )
783
818
  tick()
784
819
  refresh(true)
785
820
  },
@@ -811,7 +846,7 @@ export function defaultLogger(
811
846
  tail = false
812
847
  killStatus()
813
848
  },
814
- taskStart(node, info) {
849
+ taskStart(node) {
815
850
  if (isGroupTask(node)) return
816
851
  if (holdsTerminal(node)) {
817
852
  killStatus()
@@ -837,7 +872,6 @@ export function defaultLogger(
837
872
  id: node.id,
838
873
  startedMs: Date.now(),
839
874
  cache: ranNoCache({ node }) ? 'no-cache' : 'miss',
840
- ...(waited(info?.waitedMs) ? { waitedMs: info!.waitedMs! } : {}),
841
875
  }
842
876
  const free = slots.indexOf(null)
843
877
  if (free >= 0) slots[free] = slot
@@ -930,6 +964,7 @@ export function defaultLogger(
930
964
  flushedFailures = true
931
965
  for (const block of deferredFailures) emitBlock(fenced(block))
932
966
  }
967
+ closePanel()
933
968
  // The summary and the CLI's own lines follow on the stream directly:
934
969
  // nothing may still be held, and nothing after this is.
935
970
  writer.settle()
@@ -1161,7 +1196,7 @@ export function defaultLogger(
1161
1196
  colors,
1162
1197
  onPath.has(node.id),
1163
1198
  blocksOf(node.id),
1164
- usualOf?.get(node.id),
1199
+ usualMsOf(node.id),
1165
1200
  ),
1166
1201
  )
1167
1202
  } else if (outcome.status === 'skipped') {
@@ -42,7 +42,7 @@ export function plainOutput(sink: Logger): Logger {
42
42
  status: (line) => sink.status(line),
43
43
  ...(sink.warn && { warn: (line) => sink.warn!(line) }),
44
44
  ...(sink.runStart && { runStart: (info) => sink.runStart!(info) }),
45
- ...(sink.taskStart && { taskStart: (node, info) => sink.taskStart!(node, info) }),
45
+ ...(sink.taskStart && { taskStart: (node) => sink.taskStart!(node) }),
46
46
  ...(sink.runEnd && { runEnd: () => sink.runEnd!() }),
47
47
  ...(sink.stepStart && { stepStart: (id) => sink.stepStart!(id) }),
48
48
  ...(sink.stepEnd && { stepEnd: (id, info) => sink.stepEnd!(id, info) }),