@vzn/vx 0.0.0 → 0.0.12

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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +332 -1
  3. package/launcher.mjs +84 -0
  4. package/package.json +44 -3
  5. package/src/bin.ts +25 -0
  6. package/src/cache/cache.ts +1832 -0
  7. package/src/cache/cas-backend.ts +97 -0
  8. package/src/cache/digest.ts +47 -0
  9. package/src/cache/index.ts +29 -0
  10. package/src/cache/inputs.ts +833 -0
  11. package/src/cache/layered-cache.ts +335 -0
  12. package/src/cache/remote-cache.ts +251 -0
  13. package/src/cache/tar.ts +343 -0
  14. package/src/cli/backend.ts +23 -0
  15. package/src/cli/cache.ts +101 -0
  16. package/src/cli/format.ts +15 -0
  17. package/src/cli/help.ts +123 -0
  18. package/src/cli/index.ts +85 -0
  19. package/src/cli/info.ts +83 -0
  20. package/src/cli/lock.ts +135 -0
  21. package/src/cli/mcp-rpc.ts +292 -0
  22. package/src/cli/mcp.ts +92 -0
  23. package/src/cli/migrate-nx.ts +450 -0
  24. package/src/cli/migrate-turbo.ts +364 -0
  25. package/src/cli/migrate.ts +219 -0
  26. package/src/cli/plan-format.ts +148 -0
  27. package/src/cli/run.ts +661 -0
  28. package/src/cli/show.ts +201 -0
  29. package/src/cli/upgrade.ts +103 -0
  30. package/src/cli/watch.ts +287 -0
  31. package/src/config.ts +421 -0
  32. package/src/exec/env.ts +74 -0
  33. package/src/exec/index.ts +13 -0
  34. package/src/exec/runner.ts +470 -0
  35. package/src/exec/sandbox-runtime.ts +578 -0
  36. package/src/graph/dependency-spec.ts +69 -0
  37. package/src/graph/index.ts +18 -0
  38. package/src/graph/scheduler.ts +482 -0
  39. package/src/graph/task-graph.ts +325 -0
  40. package/src/index.ts +197 -0
  41. package/src/orchestrator/colors.ts +63 -0
  42. package/src/orchestrator/devframe-surface.ts +74 -0
  43. package/src/orchestrator/events.ts +277 -0
  44. package/src/orchestrator/execute-task.ts +883 -0
  45. package/src/orchestrator/framed-output.ts +461 -0
  46. package/src/orchestrator/history.ts +148 -0
  47. package/src/orchestrator/index.ts +163 -0
  48. package/src/orchestrator/local-shortcircuit.ts +147 -0
  49. package/src/orchestrator/logger.ts +526 -0
  50. package/src/orchestrator/metrics.ts +1576 -0
  51. package/src/orchestrator/options.ts +176 -0
  52. package/src/orchestrator/plan.ts +129 -0
  53. package/src/orchestrator/plugin-host.ts +190 -0
  54. package/src/orchestrator/plugin.ts +283 -0
  55. package/src/orchestrator/predict.ts +106 -0
  56. package/src/orchestrator/prepare.ts +297 -0
  57. package/src/orchestrator/protocol.ts +142 -0
  58. package/src/orchestrator/remote-cache-setup.ts +60 -0
  59. package/src/orchestrator/remote-prefetch.ts +81 -0
  60. package/src/orchestrator/run-artifacts.ts +111 -0
  61. package/src/orchestrator/run-context.ts +205 -0
  62. package/src/orchestrator/run-report.ts +132 -0
  63. package/src/orchestrator/run-state.ts +145 -0
  64. package/src/orchestrator/run.ts +842 -0
  65. package/src/orchestrator/stable-keys.ts +177 -0
  66. package/src/orchestrator/status-line.ts +287 -0
  67. package/src/orchestrator/summary.ts +291 -0
  68. package/src/orchestrator/tally.ts +69 -0
  69. package/src/orchestrator/task-hash.ts +238 -0
  70. package/src/orchestrator/telemetry-host.ts +80 -0
  71. package/src/orchestrator/telemetry.ts +350 -0
  72. package/src/orchestrator/upstream.ts +93 -0
  73. package/src/orchestrator/verify.ts +141 -0
  74. package/src/orchestrator/wire-render.ts +92 -0
  75. package/src/orchestrator/wire.ts +212 -0
  76. package/src/util/errors.ts +11 -0
  77. package/src/util/hash.ts +25 -0
  78. package/src/util/index.ts +7 -0
  79. package/src/util/paths.ts +16 -0
  80. package/src/util/ulid.ts +15 -0
  81. package/src/version.ts +10 -0
  82. package/src/workspace/affected.ts +118 -0
  83. package/src/workspace/filter.ts +155 -0
  84. package/src/workspace/fingerprint.ts +41 -0
  85. package/src/workspace/index.ts +27 -0
  86. package/src/workspace/lockfile.ts +118 -0
  87. package/src/workspace/nested-dirs.ts +34 -0
  88. package/src/workspace/package-graph.ts +148 -0
  89. package/src/workspace/project-loader.ts +470 -0
  90. package/src/workspace/workspace.ts +176 -0
@@ -0,0 +1,132 @@
1
+ // Markdown run report (`vx run --report=markdown`). A moon-style table —
2
+ // one row per task plus a header line of totals — written verbatim to
3
+ // stdout after a run so CI can append it to a step summary
4
+ // (`vx run ci --report=markdown >> $GITHUB_STEP_SUMMARY`). Pure: the
5
+ // run's finished outcomes in, a markdown string out. No ANSI, no live
6
+ // region — keep it machine-clean and diffable.
7
+
8
+ import type { RunResult } from './protocol.js'
9
+ import type { OutcomeView } from './events.js'
10
+
11
+ /**
12
+ * Outcome buckets for the report header. Mirrors the terminal summary's
13
+ * `tallyOutcomes` partition (success counts hits; `aborted` is no work
14
+ * and excluded), but derives from the serializable `OutcomeView` the CLI
15
+ * holds after a run rather than the in-process `TaskOutcome` — so it
16
+ * needs no graph back-ref and stays self-contained here.
17
+ */
18
+ interface ReportTally {
19
+ total: number
20
+ successful: number
21
+ failed: number
22
+ skipped: number
23
+ cached: number
24
+ /** Wall-clock ms summed over tasks that actually executed (misses). */
25
+ executedMs: number
26
+ /** Wall-clock ms summed over cache hits — the work the cache skipped. */
27
+ savedMs: number
28
+ }
29
+
30
+ function tally(outcomes: readonly OutcomeView[]): ReportTally {
31
+ const t: ReportTally = {
32
+ total: 0,
33
+ successful: 0,
34
+ failed: 0,
35
+ skipped: 0,
36
+ cached: 0,
37
+ executedMs: 0,
38
+ savedMs: 0,
39
+ }
40
+ for (const o of outcomes) {
41
+ // A child killed by a shutdown signal (Ctrl-C teardown) is no work —
42
+ // excluded from totals and every bucket, like the terminal tally.
43
+ if (o.status === 'aborted') continue
44
+ t.total++
45
+ switch (o.status) {
46
+ case 'success':
47
+ t.successful++
48
+ t.executedMs += o.durationMs
49
+ break
50
+ case 'cache-hit':
51
+ case 'cache-hit-remote':
52
+ t.successful++
53
+ t.cached++
54
+ t.savedMs += o.durationMs
55
+ break
56
+ case 'failed':
57
+ t.failed++
58
+ t.executedMs += o.durationMs
59
+ break
60
+ case 'skipped':
61
+ t.skipped++
62
+ break
63
+ }
64
+ }
65
+ return t
66
+ }
67
+
68
+ function statusWord(o: OutcomeView): string {
69
+ switch (o.status) {
70
+ case 'success':
71
+ return 'success'
72
+ case 'cache-hit':
73
+ case 'cache-hit-remote':
74
+ return 'success'
75
+ case 'failed':
76
+ return `failed (exit ${o.exitCode})`
77
+ default:
78
+ return o.status
79
+ }
80
+ }
81
+
82
+ function cacheWord(o: OutcomeView): string {
83
+ switch (o.status) {
84
+ case 'cache-hit':
85
+ return o.restored === false ? 'up-to-date' : 'local'
86
+ case 'cache-hit-remote':
87
+ return o.restored === false ? 'up-to-date' : 'remote'
88
+ case 'success':
89
+ case 'failed':
90
+ return 'miss'
91
+ default:
92
+ return '—'
93
+ }
94
+ }
95
+
96
+ function fmtDuration(ms: number): string {
97
+ if (ms < 1000) return `${Math.round(ms)}ms`
98
+ return `${(ms / 1000).toFixed(2)}s`
99
+ }
100
+
101
+ /**
102
+ * Render the finished run as a markdown report. A header line of totals
103
+ * followed by a per-task table. Self-contained — everything is derived
104
+ * from the result's outcomes.
105
+ */
106
+ export function formatRunReportMarkdown(result: RunResult): string {
107
+ const t = tally(result.outcomes)
108
+ const parts = [
109
+ `**${t.total} task${t.total === 1 ? '' : 's'}**`,
110
+ `${t.successful} success`,
111
+ `${t.failed} failed`,
112
+ `${t.cached} cached`,
113
+ ]
114
+ if (t.skipped > 0) parts.push(`${t.skipped} skipped`)
115
+ parts.push(`${fmtDuration(t.executedMs)} total`)
116
+ if (t.savedMs > 0) parts.push(`${fmtDuration(t.savedMs)} saved`)
117
+
118
+ const lines: string[] = []
119
+ lines.push(`## vx run — ${result.ok ? 'passed' : 'failed'}`)
120
+ lines.push('')
121
+ lines.push(parts.join(' · '))
122
+ lines.push('')
123
+ lines.push('| Task | Status | Cache | Duration |')
124
+ lines.push('| --- | --- | --- | --- |')
125
+ for (const o of result.outcomes) {
126
+ if (o.status === 'aborted') continue
127
+ lines.push(
128
+ `| ${o.taskId} | ${statusWord(o)} | ${cacheWord(o)} | ${fmtDuration(o.durationMs)} |`,
129
+ )
130
+ }
131
+ return lines.join('\n') + '\n'
132
+ }
@@ -0,0 +1,145 @@
1
+ // The derived run aggregate — the single view-model every surface holds.
2
+ // Raw RunEvents are the source of truth; this reduces them into the same
3
+ // counters/per-task status the terminal summary already renders, so a
4
+ // web/TUI/MCP surface can render reactively from one place instead of
5
+ // re-deriving. A devframe shared-state holds a RunState and re-reduces on
6
+ // each event; the terminal keeps its own inline counters (byte-identical,
7
+ // untouched). See docs/design/event-stream-2026-06.md.
8
+
9
+ import { isGroupTask, type TaskStatus } from '../graph/index.js'
10
+ import type { RunEvent } from './events.js'
11
+
12
+ /** Per-task lifecycle as seen by a surface. */
13
+ export type TaskState = 'running' | TaskStatus
14
+
15
+ export interface TaskRecord {
16
+ id: string
17
+ state: TaskState
18
+ /** Final duration once complete; absent while running. */
19
+ durationMs?: number
20
+ /** True for a persistent task left running after its outcome landed. */
21
+ persistent?: boolean
22
+ }
23
+
24
+ /** Cache-miss duration spread — the same numbers the final summary shows. */
25
+ export interface Spread {
26
+ maxMs: number
27
+ minMs: number
28
+ sumMs: number
29
+ count: number
30
+ }
31
+
32
+ export interface RunState {
33
+ total: number
34
+ done: number
35
+ failed: number
36
+ succeeded: number
37
+ upToDate: number
38
+ restoredLocal: number
39
+ restoredRemote: number
40
+ skipped: number
41
+ /** Ids currently executing (a worker holds them). */
42
+ running: string[]
43
+ /** Per-task records, keyed by id. Group tasks are excluded (no work). */
44
+ tasks: Record<string, TaskRecord>
45
+ spread: Spread | null
46
+ }
47
+
48
+ export function initRunState(total = 0): RunState {
49
+ return {
50
+ total,
51
+ done: 0,
52
+ failed: 0,
53
+ succeeded: 0,
54
+ upToDate: 0,
55
+ restoredLocal: 0,
56
+ restoredRemote: 0,
57
+ skipped: 0,
58
+ running: [],
59
+ tasks: {},
60
+ spread: null,
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Fold one event into the aggregate, returning a NEW state (pure — never
66
+ * mutates `prev`, so it's safe to feed a devframe `sharedState.mutate`
67
+ * that diffs old vs new). Mirrors `defaultLogger`'s inline bookkeeping:
68
+ * group tasks (no exec) are ignored, an `aborted` task reverts to pending
69
+ * (freed, never counted), and the spread tracks success+failed only.
70
+ */
71
+ export function reduce(prev: RunState, event: RunEvent): RunState {
72
+ switch (event.kind) {
73
+ case 'run:start':
74
+ return initRunState(event.info.total)
75
+
76
+ case 'task:start': {
77
+ if (isGroupTask(event.node)) return prev
78
+ const id = event.node.id
79
+ return {
80
+ ...prev,
81
+ running: [...prev.running, id],
82
+ tasks: { ...prev.tasks, [id]: { id, state: 'running' } },
83
+ }
84
+ }
85
+
86
+ case 'task:complete': {
87
+ if (isGroupTask(event.node)) return prev
88
+ const id = event.node.id
89
+ const { status, durationMs } = event.outcome
90
+ const running = prev.running.filter((r) => r !== id)
91
+
92
+ // Aborted (killed by a shutdown signal): free the slot, count
93
+ // nothing, drop the record — the run is tearing down.
94
+ if (status === 'aborted') {
95
+ const tasks = { ...prev.tasks }
96
+ delete tasks[id]
97
+ return { ...prev, running, tasks }
98
+ }
99
+
100
+ const persistent = event.node.config.exec?.persistent !== undefined && status === 'success'
101
+ const next: RunState = {
102
+ ...prev,
103
+ done: prev.done + 1,
104
+ running,
105
+ tasks: {
106
+ ...prev.tasks,
107
+ [id]: { id, state: status, durationMs, ...(persistent ? { persistent: true } : {}) },
108
+ },
109
+ }
110
+ switch (status) {
111
+ case 'success':
112
+ next.succeeded = prev.succeeded + 1
113
+ break
114
+ case 'cache-hit':
115
+ if (event.outcome.restored === false) next.upToDate = prev.upToDate + 1
116
+ else next.restoredLocal = prev.restoredLocal + 1
117
+ break
118
+ case 'cache-hit-remote':
119
+ if (event.outcome.restored === false) next.upToDate = prev.upToDate + 1
120
+ else next.restoredRemote = prev.restoredRemote + 1
121
+ break
122
+ case 'failed':
123
+ next.failed = prev.failed + 1
124
+ break
125
+ case 'skipped':
126
+ next.skipped = prev.skipped + 1
127
+ break
128
+ }
129
+ if (status === 'success' || status === 'failed') {
130
+ const s = prev.spread
131
+ next.spread = {
132
+ maxMs: Math.max(s?.maxMs ?? 0, durationMs),
133
+ minMs: Math.min(s?.minMs ?? Infinity, durationMs),
134
+ sumMs: (s?.sumMs ?? 0) + durationMs,
135
+ count: (s?.count ?? 0) + 1,
136
+ }
137
+ }
138
+ return next
139
+ }
140
+
141
+ // stdout/stderr/status/end carry no aggregate signal.
142
+ default:
143
+ return prev
144
+ }
145
+ }