@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.
- package/LICENSE +1 -1
- package/README.md +135 -124
- package/index.ts +10 -0
- package/launcher.mjs +10 -11
- package/package.json +11 -7
- package/src/bin.ts +14 -4
- package/src/cache/archive.ts +659 -0
- package/src/cache/cache.ts +769 -1101
- package/src/cache/chained-cache.ts +194 -0
- package/src/cache/config-evals.ts +89 -0
- package/src/cache/file-hashes.ts +262 -0
- package/src/cache/git-inputs.ts +637 -0
- package/src/cache/index.ts +24 -5
- package/src/cache/inputs.ts +373 -456
- package/src/cache/layer.ts +604 -0
- package/src/cache/layered-cache.ts +96 -43
- package/src/cache/output-index.ts +224 -0
- package/src/cache/policy.ts +84 -0
- package/src/cache/run-history.ts +147 -0
- package/src/cache/tar-stream.ts +358 -0
- package/src/cache/zstd.ts +160 -0
- package/src/cli/cache.ts +75 -21
- package/src/cli/completions.ts +104 -0
- package/src/cli/core-alias.ts +25 -0
- package/src/cli/help.ts +195 -128
- package/src/cli/index.ts +116 -46
- package/src/cli/info.ts +122 -63
- package/src/cli/init.ts +75 -0
- package/src/cli/last.ts +172 -0
- package/src/cli/lock.ts +2 -1
- package/src/cli/plan-format.ts +72 -6
- package/src/cli/plugin-commands.ts +103 -0
- package/src/cli/run.ts +245 -271
- package/src/cli/select.ts +332 -0
- package/src/cli/show.ts +144 -69
- package/src/cli/upgrade.ts +9 -7
- package/src/cli/watch.ts +858 -110
- package/src/cli/why.ts +223 -0
- package/src/cli/workspace-config.ts +126 -0
- package/src/config.ts +187 -150
- package/src/exec/env.ts +18 -1
- package/src/exec/executor.ts +224 -0
- package/src/exec/index.ts +24 -2
- package/src/exec/local-executor.ts +41 -0
- package/src/exec/runner.ts +166 -40
- package/src/exec/sandbox-binds.ts +182 -0
- package/src/exec/sandbox-paths.ts +50 -0
- package/src/exec/sandbox-runtime.ts +642 -260
- package/src/exec/sandbox-violations.ts +280 -0
- package/src/graph/index.ts +4 -10
- package/src/graph/scheduler.ts +232 -203
- package/src/graph/task-graph.ts +222 -8
- package/src/index.ts +146 -153
- package/src/orchestrator/admission.ts +137 -0
- package/src/orchestrator/deferred-outputs.ts +147 -0
- package/src/orchestrator/doctor.ts +255 -0
- package/src/orchestrator/download-policy.ts +171 -0
- package/src/orchestrator/events.ts +72 -8
- package/src/orchestrator/execute-task.ts +374 -549
- package/src/orchestrator/failure-mode.ts +202 -0
- package/src/orchestrator/framed-output.ts +126 -69
- package/src/orchestrator/history.ts +126 -59
- package/src/orchestrator/hit-restore.ts +221 -0
- package/src/orchestrator/index.ts +77 -93
- package/src/orchestrator/local-shortcircuit.ts +22 -0
- package/src/orchestrator/lockfile-claim.ts +281 -0
- package/src/orchestrator/logger.ts +208 -35
- package/src/orchestrator/metrics.ts +102 -1388
- package/src/orchestrator/miss-save.ts +169 -0
- package/src/orchestrator/options.ts +55 -34
- package/src/orchestrator/persistent.ts +79 -0
- package/src/orchestrator/placement.ts +182 -0
- package/src/orchestrator/plan.ts +144 -1
- package/src/orchestrator/plugin-host.ts +357 -120
- package/src/orchestrator/plugin.ts +298 -58
- package/src/orchestrator/prepare.ts +202 -106
- package/src/orchestrator/projects.ts +259 -0
- package/src/orchestrator/remote-prefetch.ts +12 -8
- package/src/orchestrator/run-artifacts.ts +63 -16
- package/src/orchestrator/run-context.ts +167 -29
- package/src/orchestrator/run-records.ts +149 -0
- package/src/orchestrator/run-report.ts +74 -47
- package/src/orchestrator/run.ts +534 -417
- package/src/orchestrator/sandbox-request.ts +243 -0
- package/src/orchestrator/save-lane.ts +47 -0
- package/src/orchestrator/signals.ts +111 -0
- package/src/orchestrator/stable-keys.ts +129 -31
- package/src/orchestrator/status-line.ts +49 -9
- package/src/orchestrator/summary.ts +90 -26
- package/src/orchestrator/tally.ts +78 -35
- package/src/orchestrator/task-hash.ts +111 -15
- package/src/orchestrator/task-log-buffer.ts +288 -0
- package/src/orchestrator/telemetry-host.ts +34 -6
- package/src/orchestrator/telemetry.ts +182 -36
- package/src/orchestrator/upstream.ts +72 -31
- package/src/util/cgroup.ts +160 -0
- package/src/util/edit-distance.ts +70 -0
- package/src/util/errors.ts +15 -1
- package/src/util/index.ts +15 -2
- package/src/util/num.ts +37 -0
- package/src/util/paths.ts +64 -0
- package/src/util/settle.ts +67 -0
- package/src/util/size.ts +5 -1
- package/src/util/tail.ts +66 -0
- package/src/util/timing.ts +63 -0
- package/src/util/verbs.ts +39 -0
- package/src/workspace/affected.ts +250 -23
- package/src/workspace/config-cache.ts +313 -0
- package/src/workspace/config-eval.ts +217 -0
- package/src/workspace/config-imports.ts +162 -0
- package/src/workspace/config-schema.ts +837 -0
- package/src/workspace/filter.ts +65 -10
- package/src/workspace/fingerprint.ts +53 -4
- package/src/workspace/index.ts +42 -3
- package/src/workspace/lockfile.ts +25 -9
- package/src/workspace/migrate-scripts.ts +134 -0
- package/src/workspace/migration.ts +314 -0
- package/src/workspace/nested-dirs.ts +21 -7
- package/src/workspace/package-graph.ts +114 -41
- package/src/workspace/project-loader.ts +224 -450
- package/src/workspace/workspace.ts +298 -72
- package/src/cache/cas-backend.ts +0 -97
- package/src/cache/digest.ts +0 -47
- package/src/cache/tar.ts +0 -343
- package/src/cli/backend.ts +0 -23
- package/src/cli/mcp-rpc.ts +0 -292
- package/src/cli/mcp.ts +0 -92
- package/src/cli/migrate-nx.ts +0 -450
- package/src/cli/migrate-turbo.ts +0 -364
- package/src/cli/migrate.ts +0 -219
- package/src/orchestrator/devframe-surface.ts +0 -74
- package/src/orchestrator/predict.ts +0 -106
- package/src/orchestrator/protocol.ts +0 -156
- package/src/orchestrator/resources.ts +0 -59
- package/src/orchestrator/run-state.ts +0 -145
- package/src/orchestrator/verify.ts +0 -164
- package/src/orchestrator/wire-render.ts +0 -92
- package/src/orchestrator/wire.ts +0 -212
package/src/orchestrator/plan.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|