@vzn/vx 0.0.16 → 0.0.21

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 (138) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +135 -124
  3. package/index.ts +10 -0
  4. package/launcher.mjs +10 -11
  5. package/package.json +11 -7
  6. package/src/bin.ts +14 -4
  7. package/src/cache/archive.ts +659 -0
  8. package/src/cache/cache.ts +769 -1101
  9. package/src/cache/chained-cache.ts +194 -0
  10. package/src/cache/config-evals.ts +89 -0
  11. package/src/cache/file-hashes.ts +262 -0
  12. package/src/cache/git-inputs.ts +637 -0
  13. package/src/cache/index.ts +24 -5
  14. package/src/cache/inputs.ts +373 -456
  15. package/src/cache/layer.ts +604 -0
  16. package/src/cache/layered-cache.ts +96 -43
  17. package/src/cache/output-index.ts +224 -0
  18. package/src/cache/policy.ts +84 -0
  19. package/src/cache/run-history.ts +147 -0
  20. package/src/cache/tar-stream.ts +358 -0
  21. package/src/cache/zstd.ts +160 -0
  22. package/src/cli/cache.ts +75 -21
  23. package/src/cli/completions.ts +104 -0
  24. package/src/cli/core-alias.ts +25 -0
  25. package/src/cli/help.ts +195 -128
  26. package/src/cli/index.ts +116 -46
  27. package/src/cli/info.ts +122 -63
  28. package/src/cli/init.ts +75 -0
  29. package/src/cli/last.ts +172 -0
  30. package/src/cli/lock.ts +2 -1
  31. package/src/cli/plan-format.ts +72 -6
  32. package/src/cli/plugin-commands.ts +103 -0
  33. package/src/cli/run.ts +245 -271
  34. package/src/cli/select.ts +332 -0
  35. package/src/cli/show.ts +144 -69
  36. package/src/cli/upgrade.ts +9 -7
  37. package/src/cli/watch.ts +858 -110
  38. package/src/cli/why.ts +223 -0
  39. package/src/cli/workspace-config.ts +126 -0
  40. package/src/config.ts +187 -150
  41. package/src/exec/env.ts +18 -1
  42. package/src/exec/executor.ts +224 -0
  43. package/src/exec/index.ts +24 -2
  44. package/src/exec/local-executor.ts +41 -0
  45. package/src/exec/runner.ts +166 -40
  46. package/src/exec/sandbox-binds.ts +182 -0
  47. package/src/exec/sandbox-paths.ts +50 -0
  48. package/src/exec/sandbox-runtime.ts +642 -260
  49. package/src/exec/sandbox-violations.ts +280 -0
  50. package/src/graph/index.ts +4 -10
  51. package/src/graph/scheduler.ts +232 -203
  52. package/src/graph/task-graph.ts +222 -8
  53. package/src/index.ts +146 -153
  54. package/src/orchestrator/admission.ts +137 -0
  55. package/src/orchestrator/deferred-outputs.ts +147 -0
  56. package/src/orchestrator/doctor.ts +255 -0
  57. package/src/orchestrator/download-policy.ts +171 -0
  58. package/src/orchestrator/events.ts +72 -8
  59. package/src/orchestrator/execute-task.ts +374 -549
  60. package/src/orchestrator/failure-mode.ts +202 -0
  61. package/src/orchestrator/framed-output.ts +126 -69
  62. package/src/orchestrator/history.ts +126 -59
  63. package/src/orchestrator/hit-restore.ts +221 -0
  64. package/src/orchestrator/index.ts +77 -93
  65. package/src/orchestrator/local-shortcircuit.ts +22 -0
  66. package/src/orchestrator/lockfile-claim.ts +281 -0
  67. package/src/orchestrator/logger.ts +208 -35
  68. package/src/orchestrator/metrics.ts +102 -1388
  69. package/src/orchestrator/miss-save.ts +169 -0
  70. package/src/orchestrator/options.ts +55 -34
  71. package/src/orchestrator/persistent.ts +79 -0
  72. package/src/orchestrator/placement.ts +182 -0
  73. package/src/orchestrator/plan.ts +144 -1
  74. package/src/orchestrator/plugin-host.ts +357 -120
  75. package/src/orchestrator/plugin.ts +298 -58
  76. package/src/orchestrator/prepare.ts +202 -106
  77. package/src/orchestrator/projects.ts +259 -0
  78. package/src/orchestrator/remote-prefetch.ts +12 -8
  79. package/src/orchestrator/run-artifacts.ts +63 -16
  80. package/src/orchestrator/run-context.ts +167 -29
  81. package/src/orchestrator/run-records.ts +149 -0
  82. package/src/orchestrator/run-report.ts +74 -47
  83. package/src/orchestrator/run.ts +534 -417
  84. package/src/orchestrator/sandbox-request.ts +243 -0
  85. package/src/orchestrator/save-lane.ts +47 -0
  86. package/src/orchestrator/signals.ts +111 -0
  87. package/src/orchestrator/stable-keys.ts +129 -31
  88. package/src/orchestrator/status-line.ts +49 -9
  89. package/src/orchestrator/summary.ts +90 -26
  90. package/src/orchestrator/tally.ts +78 -35
  91. package/src/orchestrator/task-hash.ts +111 -15
  92. package/src/orchestrator/task-log-buffer.ts +288 -0
  93. package/src/orchestrator/telemetry-host.ts +34 -6
  94. package/src/orchestrator/telemetry.ts +182 -36
  95. package/src/orchestrator/upstream.ts +72 -31
  96. package/src/util/cgroup.ts +160 -0
  97. package/src/util/edit-distance.ts +70 -0
  98. package/src/util/errors.ts +15 -1
  99. package/src/util/index.ts +15 -2
  100. package/src/util/num.ts +37 -0
  101. package/src/util/paths.ts +64 -0
  102. package/src/util/settle.ts +67 -0
  103. package/src/util/size.ts +5 -1
  104. package/src/util/tail.ts +66 -0
  105. package/src/util/timing.ts +63 -0
  106. package/src/util/verbs.ts +39 -0
  107. package/src/workspace/affected.ts +250 -23
  108. package/src/workspace/config-cache.ts +313 -0
  109. package/src/workspace/config-eval.ts +217 -0
  110. package/src/workspace/config-imports.ts +162 -0
  111. package/src/workspace/config-schema.ts +837 -0
  112. package/src/workspace/filter.ts +65 -10
  113. package/src/workspace/fingerprint.ts +53 -4
  114. package/src/workspace/index.ts +42 -3
  115. package/src/workspace/lockfile.ts +25 -9
  116. package/src/workspace/migrate-scripts.ts +134 -0
  117. package/src/workspace/migration.ts +314 -0
  118. package/src/workspace/nested-dirs.ts +21 -7
  119. package/src/workspace/package-graph.ts +114 -41
  120. package/src/workspace/project-loader.ts +224 -450
  121. package/src/workspace/workspace.ts +298 -72
  122. package/src/cache/cas-backend.ts +0 -97
  123. package/src/cache/digest.ts +0 -47
  124. package/src/cache/tar.ts +0 -343
  125. package/src/cli/backend.ts +0 -23
  126. package/src/cli/mcp-rpc.ts +0 -292
  127. package/src/cli/mcp.ts +0 -92
  128. package/src/cli/migrate-nx.ts +0 -450
  129. package/src/cli/migrate-turbo.ts +0 -364
  130. package/src/cli/migrate.ts +0 -219
  131. package/src/orchestrator/devframe-surface.ts +0 -74
  132. package/src/orchestrator/predict.ts +0 -106
  133. package/src/orchestrator/protocol.ts +0 -156
  134. package/src/orchestrator/resources.ts +0 -59
  135. package/src/orchestrator/run-state.ts +0 -145
  136. package/src/orchestrator/verify.ts +0 -164
  137. package/src/orchestrator/wire-render.ts +0 -92
  138. package/src/orchestrator/wire.ts +0 -212
@@ -14,6 +14,7 @@
14
14
  import type { CacheLayer, CachePolicy, GitFilesCache } from '../cache/index.js'
15
15
  import { FULL_CACHE_POLICY } from '../cache/index.js'
16
16
  import { isGroupTask, runGraph, type TaskNode, type TaskOutcome } from '../graph/index.js'
17
+ import type { HistoryProvider } from './history.js'
17
18
  import { computeGroupHash, computeTaskHash } from './task-hash.js'
18
19
 
19
20
  export type CacheStatus =
@@ -29,14 +30,67 @@ export interface PlannedTask {
29
30
  cacheStatus: CacheStatus
30
31
  /** Sorted task ids this task depends on (same as TaskNode.deps). */
31
32
  deps: readonly string[]
33
+ /**
34
+ * The task's typical executed duration (wall-clock p50 over its recent
35
+ * non-hit runs in the local cache.db), when history exists. Attached to
36
+ * every real task so a would-run task can show what it usually costs.
37
+ */
38
+ p50Ms?: number
39
+ /**
40
+ * Name of the executor this task would be PLACED on. Attached only when
41
+ * the workspace declares more than one executor — with a single one every
42
+ * line would carry the same label and say nothing. Absent for group tasks
43
+ * and persistent tasks, which never reach an executor.
44
+ */
45
+ executor?: string
46
+ /**
47
+ * `'deferred'` when this task's outputs would stay in the remote store
48
+ * (`--download=none` / `toplevel`). Attached only when it is TRUE — an
49
+ * eager task is the default and saying so on every line says nothing.
50
+ */
51
+ download?: 'deferred'
52
+ }
53
+
54
+ /**
55
+ * Time prediction over the plan: what would this run cost? Derived purely
56
+ * from recorded history (each task's p50) — cache hits and groups cost 0
57
+ * (a restore is a tar extract, near-instant next to execution).
58
+ */
59
+ export interface PlanPrediction {
60
+ /** Predicted wall-clock: the longest dependency chain of would-run cost. */
61
+ wallMs: number
62
+ /** Total predicted execution across would-run tasks (the CPU-time story). */
63
+ workMs: number
64
+ /** Would-run tasks with NO history — their cost is unknown and counted as
65
+ * 0, so wallMs/workMs are lower bounds when this is > 0. */
66
+ unknownCount: number
32
67
  }
33
68
 
34
69
  export interface RunPlan {
35
70
  tasks: PlannedTask[]
71
+ /** Present when a history provider was available and usable. */
72
+ predicted?: PlanPrediction
73
+ /**
74
+ * Requested task specs that matched no project. Non-empty means the
75
+ * plan was abandoned: planning what WOULD run is meaningless when the
76
+ * equivalent `vx run` would refuse to start.
77
+ */
78
+ unresolvedTasks?: readonly string[]
79
+ /**
80
+ * Producers `--download` WANTED to defer but could not, mapped to why.
81
+ * Without this the gate is invisible: a user asks for `--download=none`,
82
+ * gets no deferral, and has nothing to read. Empty/absent under
83
+ * `--download=all`, which defers nothing by definition.
84
+ */
85
+ downloadDowngrades?: ReadonlyArray<{ taskId: string; reason: string }>
36
86
  }
37
87
 
38
88
  export interface PlanArgs {
39
89
  nodes: Map<string, TaskNode>
90
+ /** Per-task `--download` decision, when the policy is not `all`. */
91
+ downloadOf?: (id: string) => 'eager' | 'deferred' | 'never' | undefined
92
+ /** See `RunPlan.downloadDowngrades`. */
93
+ downloadDowngrades?: ReadonlyArray<{ taskId: string; reason: string }>
40
94
  workspaceRoot: string
41
95
  workspaceFingerprint: string
42
96
  cache: CacheLayer
@@ -45,6 +99,11 @@ export interface PlanArgs {
45
99
  nestedDirsByProject: Map<string, string[]>
46
100
  gitFilesCache?: GitFilesCache
47
101
  hashCache?: import('./task-hash.js').HashCache
102
+ /** When provided, per-task p50s are attached and `RunPlan.predicted` is
103
+ * computed. Failing open: a history error yields a plan without them. */
104
+ history?: HistoryProvider
105
+ /** Placement lookup (`run.ts`), passed only when >1 executor is declared. */
106
+ executorOf?: (id: string) => string | undefined
48
107
  }
49
108
 
50
109
  /**
@@ -108,14 +167,98 @@ export async function plan(args: PlanArgs): Promise<RunPlan> {
108
167
  const o = outcomes.get(id)
109
168
  const node = args.nodes.get(id)
110
169
  if (!o || !node) continue
170
+ const executor = args.executorOf?.(id)
111
171
  tasks.push({
112
172
  node,
113
173
  hash: o.hash ?? '',
114
174
  cacheStatus: cacheStatusById.get(id) ?? 'no-cache',
115
175
  deps: node.deps,
176
+ ...(executor !== undefined ? { executor } : {}),
177
+ ...(args.downloadOf?.(id) === 'deferred' ? { download: 'deferred' as const } : {}),
116
178
  })
117
179
  }
118
- return { tasks }
180
+
181
+ if (args.history !== undefined) {
182
+ try {
183
+ const realIds = tasks.filter((t) => t.cacheStatus !== 'group').map((t) => t.node.id)
184
+ const table = await args.history.loadFor(realIds)
185
+ for (const t of tasks) {
186
+ const p50 = table.get(t.node.id)?.p50DurationMs
187
+ if (p50 !== undefined) t.p50Ms = p50
188
+ }
189
+ return { tasks, predicted: predictPlan(tasks), ...downgradeField(args) }
190
+ } catch {
191
+ // Failing open: prediction is a nicety — a broken history read must
192
+ // never break `--dry`.
193
+ }
194
+ }
195
+ return { tasks, ...downgradeField(args) }
196
+ }
197
+
198
+ /** Carried onto the plan only when the gate actually refused something. */
199
+ function downgradeField(args: PlanArgs): {
200
+ downloadDowngrades?: ReadonlyArray<{ taskId: string; reason: string }>
201
+ } {
202
+ const d = args.downloadDowngrades
203
+ return d !== undefined && d.length > 0 ? { downloadDowngrades: d } : {}
204
+ }
205
+
206
+ /** A task the plan expects to EXECUTE (a no-cache task executes every run). */
207
+ function wouldRun(t: PlannedTask): boolean {
208
+ return t.cacheStatus === 'miss' || t.cacheStatus === 'no-cache'
209
+ }
210
+
211
+ /**
212
+ * Longest dependency chain of predicted cost, via one pass over a Kahn
213
+ * topological order — no recursion, so a pathologically deep chain can't
214
+ * overflow the stack. Hits + groups cost 0; a would-run task with no
215
+ * history also costs 0 (counted in `unknownCount`, making the totals
216
+ * honest lower bounds).
217
+ */
218
+ function predictPlan(tasks: PlannedTask[]): PlanPrediction {
219
+ const cost = (t: PlannedTask): number => (wouldRun(t) ? (t.p50Ms ?? 0) : 0)
220
+ const byId = new Map(tasks.map((t) => [t.node.id, t]))
221
+ const dependents = new Map<string, string[]>()
222
+ const indeg = new Map<string, number>()
223
+ for (const t of tasks) {
224
+ const deps = t.deps.filter((d) => byId.has(d))
225
+ indeg.set(t.node.id, deps.length)
226
+ for (const d of deps) {
227
+ const list = dependents.get(d)
228
+ if (list) list.push(t.node.id)
229
+ else dependents.set(d, [t.node.id])
230
+ }
231
+ }
232
+ const dist = new Map<string, number>()
233
+ const queue: string[] = []
234
+ for (const [id, n] of indeg) if (n === 0) queue.push(id)
235
+ let head = 0
236
+ let wallMs = 0
237
+ while (head < queue.length) {
238
+ const id = queue[head++]!
239
+ const t = byId.get(id)!
240
+ let best = 0
241
+ for (const d of t.deps) {
242
+ const v = dist.get(d)
243
+ if (v !== undefined && v > best) best = v
244
+ }
245
+ const v = best + cost(t)
246
+ dist.set(id, v)
247
+ if (v > wallMs) wallMs = v
248
+ for (const dep of dependents.get(id) ?? []) {
249
+ const n = indeg.get(dep)! - 1
250
+ indeg.set(dep, n)
251
+ if (n === 0) queue.push(dep)
252
+ }
253
+ }
254
+ let workMs = 0
255
+ let unknownCount = 0
256
+ for (const t of tasks) {
257
+ if (!wouldRun(t)) continue
258
+ if (t.p50Ms === undefined) unknownCount++
259
+ else workMs += t.p50Ms
260
+ }
261
+ return { wallMs, workMs, unknownCount }
119
262
  }
120
263
 
121
264
  function planOutcome(node: TaskNode, hash: string): TaskOutcome {