@vzn/vx 0.0.638 → 0.0.640
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/README.md +1 -1
- package/docs/caching.md +152 -95
- package/docs/cli.md +225 -372
- package/docs/execution.md +22 -47
- package/docs/features.md +14 -16
- package/docs/patterns.md +6 -8
- package/docs/schema.md +268 -215
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/last.json +7 -4
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +10 -7
- package/src/cache/cache.ts +44 -11
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +8 -2
- package/src/cache/index.ts +5 -2
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +33 -18
- package/src/cache/layered-cache.ts +108 -315
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cache/plugin-cache.ts +2 -10
- package/src/cache/remote-lane.ts +383 -0
- package/src/cache/run-history.ts +24 -8
- package/src/cache/run-store.ts +4 -4
- package/src/cache/schema.ts +6 -4
- package/src/cache/scoped-cache.ts +7 -0
- package/src/cli/completions.ts +2 -2
- package/src/cli/foreign-flags.ts +11 -23
- package/src/cli/help.ts +6 -31
- package/src/cli/info.ts +2 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +30 -4
- package/src/cli/plan-format.ts +5 -58
- package/src/cli/plugin-commands.ts +2 -2
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +23 -185
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +15 -6
- package/src/cli/upgrade.ts +215 -55
- package/src/cli/watch-cycle.ts +2 -1
- package/src/cli/watch-filter.ts +22 -6
- package/src/cli/watch-judge.ts +49 -5
- package/src/cli/watch-set.ts +18 -8
- package/src/cli/watch.ts +103 -16
- package/src/cli/why.ts +9 -7
- package/src/config.ts +19 -21
- package/src/exec/executor.ts +5 -5
- package/src/exec/sandbox-runtime.ts +1 -1
- package/src/exec/sandbox-violations.ts +3 -1
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +17 -2
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +45 -31
- package/src/graph/task-graph.ts +190 -58
- package/src/graph/task-status.ts +95 -0
- package/src/{orchestrator → history}/failure-mode.ts +1 -1
- package/src/{orchestrator → history}/history.ts +1 -1
- package/src/history/index.ts +48 -0
- package/src/{orchestrator → history}/metrics.ts +7 -20
- package/src/{orchestrator → history}/miss-reason.ts +1 -1
- package/src/index.ts +11 -29
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-args.ts +80 -0
- package/src/orchestrator/execute-task.ts +50 -411
- package/src/orchestrator/fingerprint-watch.ts +2 -2
- package/src/orchestrator/index.ts +2 -73
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/miss-save.ts +33 -3
- package/src/orchestrator/options.ts +1 -36
- package/src/orchestrator/output-host.ts +4 -3
- package/src/orchestrator/persistent-task.ts +321 -0
- package/src/orchestrator/persistent.ts +130 -3
- package/src/orchestrator/placement.ts +5 -5
- package/src/orchestrator/plan.ts +19 -6
- package/src/orchestrator/plugin-host.ts +129 -69
- package/src/orchestrator/plugin.ts +18 -8
- package/src/orchestrator/prepare.ts +40 -34
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/remote-prefetch.ts +14 -1
- package/src/orchestrator/run-artifacts.ts +5 -177
- package/src/orchestrator/run-context.ts +55 -0
- package/src/orchestrator/run-failures.ts +15 -3
- package/src/orchestrator/run-records.ts +35 -3
- package/src/orchestrator/run-report.ts +151 -171
- package/src/orchestrator/run-settings.ts +1 -16
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run-stop.ts +153 -0
- package/src/orchestrator/run.ts +293 -604
- package/src/orchestrator/sandbox-fix.ts +1 -17
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/signals.ts +14 -6
- package/src/orchestrator/stable-keys.ts +164 -27
- package/src/orchestrator/steps.ts +70 -0
- package/src/orchestrator/task-hash.ts +34 -25
- package/src/orchestrator/task-log-buffer.ts +1 -1
- package/src/orchestrator/task-process.ts +94 -0
- package/src/orchestrator/telemetry-host.ts +4 -4
- package/src/orchestrator/telemetry.ts +14 -86
- package/src/orchestrator/upstream.ts +4 -181
- package/src/{orchestrator → report}/events.ts +41 -8
- package/src/{orchestrator → report}/forecast.ts +35 -2
- package/src/{orchestrator → report}/framed-output.ts +40 -34
- package/src/report/index.ts +36 -0
- package/src/{orchestrator → report}/logger.ts +158 -20
- package/src/{orchestrator → report}/status-line.ts +88 -7
- package/src/{orchestrator → report}/summary.ts +126 -35
- package/src/report/tally.ts +83 -0
- package/src/settings.ts +12 -39
- package/src/util/colors.ts +44 -4
- package/src/util/git-ignored.ts +143 -0
- package/src/util/index.ts +19 -4
- package/src/util/paths.ts +1 -1
- package/src/util/report.ts +13 -5
- package/src/util/settle.ts +14 -39
- package/src/util/tail.ts +1 -1
- package/src/util/ui.ts +17 -6
- package/src/workspace/affected.ts +81 -2
- package/src/workspace/config-imports.ts +1 -1
- package/src/workspace/config-schema.ts +142 -143
- package/src/workspace/lockfile.ts +1 -1
- package/src/workspace/migration.ts +19 -3
- package/schemas/summary.json +0 -133
- package/src/cache/chained-cache.ts +0 -280
- package/src/orchestrator/tally.ts +0 -112
- /package/src/{orchestrator → report}/path-links.ts +0 -0
- /package/src/{orchestrator → report}/plain-output.ts +0 -0
- /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/docs/cli.md
CHANGED
|
@@ -64,7 +64,7 @@ included.
|
|
|
64
64
|
|
|
65
65
|
**Every requested name must resolve.** If any positional matches no
|
|
66
66
|
project in scope, the run refuses to start — `vx run: no projects
|
|
67
|
-
declare task(s): <name>.` on stderr, a run and `--dry`
|
|
67
|
+
declare task(s): <name>.` on stderr, a run and `--dry` alike
|
|
68
68
|
(a run printed it to stdout until 2026-10-03), exit 1, with `Did you mean <task>?` when a
|
|
69
69
|
declared task (or, for `pkg#task`, a runnable spec) is within two edits,
|
|
70
70
|
and with no name that near, what exists: `Tasks: build, test.`, `No
|
|
@@ -124,20 +124,21 @@ If no task name is given:
|
|
|
124
124
|
`missing task name (stdin is not a TTY, so no picker; tasks here: build, test)`,
|
|
125
125
|
naming the cwd project's tasks, else every project's (twelve, then
|
|
126
126
|
`and N more`); outside a workspace it reads `vx run <task>, e.g. vx run build`.
|
|
127
|
-
- **Under `--
|
|
127
|
+
- **Under `--dry=json`**, on a terminal too — the
|
|
128
128
|
same refusal (`a JSON answer opens no picker; …`), with the
|
|
129
129
|
`VX_E_USAGE` error document on stdout: a program cannot pick.
|
|
130
130
|
|
|
131
131
|
Exit codes:
|
|
132
132
|
|
|
133
|
-
| Code | When
|
|
134
|
-
| --------------------- |
|
|
135
|
-
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task.
|
|
136
|
-
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error.
|
|
137
|
-
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM),
|
|
133
|
+
| Code | When |
|
|
134
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
135
|
+
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task. |
|
|
136
|
+
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error. |
|
|
137
|
+
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM), a 2 s grace to go, then SIGKILL; a second signal skips the grace. vx then dies of the signal, so a shell script running it stops too (a run that handed a task the terminal exits with the code, so the terminal is restored). |
|
|
138
138
|
|
|
139
139
|
A task runs in its own session, so a terminal's Ctrl-C reaches vx alone,
|
|
140
|
-
and each task hears it once: from vx, as SIGINT.
|
|
140
|
+
and each task hears it once: from vx, as SIGINT. A stop starts no more remote
|
|
141
|
+
cache uploads and does not wait on one in flight.
|
|
141
142
|
|
|
142
143
|
Installed from npm, `vx` is a sh launcher that execs the platform
|
|
143
144
|
binary, so every signal reaches vx directly and no Node starts first
|
|
@@ -300,13 +301,13 @@ change seeds tasks in the projects it touches:
|
|
|
300
301
|
repository is one its `workspaceFiles` may reach into (git reports it
|
|
301
302
|
as one path; a glob whose fixed prefix is above or inside it counts);
|
|
302
303
|
- every task of a project whose `vx.config.*`
|
|
303
|
-
changed, or that holds a changed path no
|
|
304
|
+
changed, or that holds a changed path no task of its declares
|
|
304
305
|
(vx cannot prove that path re-keys nothing), or that holds a changed
|
|
305
306
|
submodule or embedded repository (git reports it as one path, and no
|
|
306
307
|
task's globs can say whether they reach inside), or that a lockfile
|
|
307
308
|
claim or a config import names (a dependency an install added or
|
|
308
309
|
removed reaches here as the lockfile's change);
|
|
309
|
-
-
|
|
310
|
+
- a task with no `inputs` when a changed path lies in its project or the
|
|
310
311
|
project is reached whole; a root file another task of its declares
|
|
311
312
|
(`workspaceFiles`) is no change there. A group seeds nothing.
|
|
312
313
|
|
|
@@ -353,6 +354,14 @@ change to it, and every project under it is selected, the project that
|
|
|
353
354
|
holds it included. A repository's own request to hide submodules from a diff
|
|
354
355
|
(`diff.ignoreSubmodules`, `submodule.<name>.ignore`) does not apply:
|
|
355
356
|
the key sees the change whatever git is told to show.
|
|
357
|
+
A symlinked input keys what it points to, so a link whose target
|
|
358
|
+
changed (a file the diff names, or one under a linked directory) is a
|
|
359
|
+
changed path of its project, and a link to a target git cannot diff
|
|
360
|
+
(outside the workspace, or git-ignored) is one on every run.
|
|
361
|
+
A gitignored file a task's inputs name outright (`.env.local`,
|
|
362
|
+
`dist/**`) never selects the task on its own, as in Nx: `--affected`
|
|
363
|
+
reflects only what git sees. The key still hashes it, so a run of the
|
|
364
|
+
task misses when it changed.
|
|
356
365
|
`.vx/projects.lock.json` is filtered out of the changed set — a `vx lock`
|
|
357
366
|
re-write never marks every project affected.
|
|
358
367
|
|
|
@@ -480,34 +489,23 @@ than run with the args unheard.
|
|
|
480
489
|
| `--schedule <rule>` | value | `run.schedule`, else `most-work` | Which ready task starts first when more are ready than workers: `most-work`, `critical-path`, `direct-dependents`, `ready-order` (`docs/schema.md` § `run.schedule`). |
|
|
481
490
|
| `--branch <name>` | value | `VX_RUN_BRANCH`, else git's branch, then the CI provider's | The branch the run records in its history and forecasts from. No config key: a committed branch would mislabel every other branch's runs. |
|
|
482
491
|
| `--no-cache` | boolean | off | Disable caching entirely (no reads, no writes); output globs are NOT cleaned. |
|
|
483
|
-
| `--force` | boolean | off | Re-execute everything (skip cache reads)
|
|
492
|
+
| `--force` | boolean | off | Re-execute everything (skip cache reads); writes stay on. A key already stored is kept, and a remote that holds it is not sent it again. Output globs are cleaned (so the saved snapshot is clean). |
|
|
484
493
|
| `--cache-policy <spec>` (`--cache-policy`) | value | `VX_CACHE_POLICY` / `cache.policy` | Per-layer read/write control. See below. An EMPTY spec (`--cache-policy=`) is a parse error — it applied nothing and left every axis on; pass `--no-cache` to disable them all. |
|
|
485
494
|
| `--cache-scope <name>` | value | `VX_CACHE_SCOPE` / workspace `cache.scope` | This run's `cache.scope` (`pr-123`), above `VX_CACHE_SCOPE`, `.vx/workspace`'s and `github()`'s. A name `cache.scope` would refuse is refused. `--cache-scope=<name>` form too. Never folded into a cache key. |
|
|
486
495
|
| `--cache-dir <path>` | value | `VX_CACHE_DIR` / `cache.dir` / `~/.vx/<id>/cache` | Cache directory override, resolved relative to cwd (absolute paths used as-is). Beats `VX_CACHE_DIR`, the workspace's `cache.dir` and the default; like them, the directory then holds the whole cache, shared through no store, for every cache the run opens, so the workspace's default dir is not created beside it. A per-run knob — never folded into a cache key. `--cache-dir=<path>` form too; the space form rejects a value starting with `-`. A directory this user cannot write into fails the run before any task with `cache directory <path> is not writable (EACCES: …)` — every run records its history there. |
|
|
487
496
|
| `--retries <n>` | value | `run.retries`, else `0` | Re-run a failed task up to `n` more times; never a persistent one. A task's own `exec.retries` wins (even an explicit `0`). Never affects cache keys. `--retries=<n>` form too. |
|
|
488
497
|
| `--continue[=<mode>]` | value | `deps-ok` | What a failed task takes down with it. `never` stops dispatch on the first failure; `deps-ok` (default) skips only its dependents; `always` (bare `--continue`) runs dependents anyway. See § Failure propagation. |
|
|
489
498
|
| `--timeout <duration>` | duration | `run.timeout`, else none | A task's time limit (`30s`, `10m`), for tasks without their own `exec.timeout`, which always wins. A runaway task is killed + `failed`. A bare number is refused naming the forms with units. Never affects cache keys. `--timeout=<duration>` form too. |
|
|
490
|
-
| `--kill-grace <duration>` | duration | `run.killGrace`, else `2s` | How long a task that ignores SIGTERM gets before SIGKILL: on a timeout, on a signal, and at the end-of-run shutdown of persistent tasks. |
|
|
491
|
-
| `--teardown-timeout <duration>` | duration | `run.teardownTimeout`, else `3s` | The bound on each plugin's end-of-run flush or teardown, and on its `telemetry()` setup, so a third party's I/O cannot hold the run. |
|
|
492
|
-
| `--ready-notice <duration>` | duration | `run.readyNotice`, else `10s` | How long a persistent task may take to match `readyWhen` before vx says once what it waits for. |
|
|
493
499
|
| `--output-logs <mode>` | value | `VX_RUN_OUTPUT_LOGS`, else flow-derived | `full` \| `errors-only` \| `hash-only` \| `none` — explicit output override. See § `--output-logs`. `--output-logs=<mode>` form too. |
|
|
494
500
|
| `--cache-download <mode>` | value | `cache.download`, else `all` | `all` \| `toplevel` \| `none` — where a REMOTELY-executed task's outputs land. `none` leaves them in the remote CAS and fetches lazily, only when a locally-placed task needs them. Never affects cache keys. See § `--cache-download`. `--cache-download=<mode>` form too. |
|
|
495
501
|
| `--verbosity <n>` | int (0+) | `0` | `1` or more prints a per-task table (groups left out) after the framed blocks, above the footer. `--verbosity=<n>` form too. |
|
|
496
502
|
| `--dry[=text\|json]` | optional value | off | Print the task graph + predicted cache hit/miss; skip execution. `VX_DEBUG_TIMING=1` prints the stage table here as it does for a run. |
|
|
497
|
-
| `--graph[=<path>]` | optional value | off | Emit Graphviz DOT (stdout if no path, its directory made if missing); skip execution. A path it cannot write is one line and exit 1. |
|
|
498
|
-
| `--format <fmt>` | value | `pretty` | `json` prints the `--summarize` document on stdout when the run ends and moves every other line to stderr ([Machine-readable output](#machine-readable-output)). `--format=<fmt>` form too. |
|
|
499
|
-
| `--summarize[=<path>]` | optional value | off | Write per-run JSON to `<cacheDir>/runs/<run_id>.json` (or the explicit path). |
|
|
500
503
|
| `--profile[=<path>]` | optional value | off (`profile.json` when set) | Write Chrome-trace JSON of the run's wallclock spans. |
|
|
501
|
-
| `--tag <k=v>` | repeatable | (none) | Label this invocation. Recorded on the run's `invocations` row so dashboards can filter runs. `--tag=k=v` form too. |
|
|
502
|
-
| `--report[=markdown]` | optional value | off | At the end of the run, print a markdown run report to stdout, above the footer. Only `markdown` is supported (`json` is reserved). |
|
|
503
|
-
| `--report-file <path>` | value | off | After the run, APPEND the same markdown report to `<path>`, making its directory as the other output paths do. Use this for `$GITHUB_STEP_SUMMARY` — redirecting stdout captures the whole run log too. `--report-file=<path>` form too. |
|
|
504
504
|
|
|
505
505
|
Mutual exclusion:
|
|
506
506
|
|
|
507
|
-
- `--dry`
|
|
508
|
-
|
|
509
|
-
`--report-file` — each needs a real run to write about. `--report` and
|
|
510
|
-
`--report-file` were accepted and silently wrote nothing until item 992.
|
|
507
|
+
- `--dry` with `--profile` — the profile needs a real run to write
|
|
508
|
+
about.
|
|
511
509
|
|
|
512
510
|
Unknown flags are a parse error (`unknown flag: --foo`), naming the
|
|
513
511
|
nearest flag the verb accepts when one is within two edits
|
|
@@ -531,13 +529,10 @@ name is the verb, and would change what `vx build` means the day one
|
|
|
531
529
|
was declared.
|
|
532
530
|
|
|
533
531
|
**Optional-value flags take their value with `=` only.** `--affected`,
|
|
534
|
-
`--exclude-dependencies`, `--dry
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
`build`, affected scope", and there is no way to tell that apart from
|
|
539
|
-
"`build` is the git base". Write `--graph=out.dot` or
|
|
540
|
-
`--affected=origin/main`. Getting it wrong is loud, not silent: the value
|
|
532
|
+
`--exclude-dependencies`, `--dry` and `--profile` are all valid bare,
|
|
533
|
+
so a following word is read as a task name — `vx run --affected build`
|
|
534
|
+
means "run `build`, affected scope", and there is no way to tell that
|
|
535
|
+
apart from "`build` is the git base". Write `--affected=origin/main`. Getting it wrong is loud, not silent: the value
|
|
541
536
|
becomes a positional that matches no project, so the run refuses to
|
|
542
537
|
start (see "Every requested name must resolve" above).
|
|
543
538
|
|
|
@@ -547,9 +542,9 @@ Pass bare `--exclude-dependencies` for the first, omit the flag for the
|
|
|
547
542
|
second.
|
|
548
543
|
|
|
549
544
|
Value flags (`--filter`, `--concurrency`, `--output-logs`,
|
|
550
|
-
`--verbosity`, `--cache-dir`,
|
|
545
|
+
`--verbosity`, `--cache-dir`, …)
|
|
551
546
|
accept both `--flag value` and `--flag=value`. In the space form,
|
|
552
|
-
`--cache-dir`
|
|
547
|
+
`--cache-dir` rejects a value
|
|
553
548
|
starting with `-`: that is always a swallowed flag (an unquoted empty
|
|
554
549
|
shell variable), never a path or task id. Use the `=` form for a literal
|
|
555
550
|
leading dash.
|
|
@@ -559,15 +554,13 @@ leading dash.
|
|
|
559
554
|
fractional (`2.7`), signed (`+4`) and space-padded forms, plus anything
|
|
560
555
|
past `2^53` (it would parse to a number you did not type). These all
|
|
561
556
|
used to be silently reinterpreted — `--concurrency 0x10` ran 16
|
|
562
|
-
workers. A duration flag (`--timeout`,
|
|
563
|
-
`--teardown-timeout`, `--ready-notice`) takes its unit (`500ms`, `30s`,
|
|
557
|
+
workers. A duration flag (`--timeout`) takes its unit (`500ms`, `30s`,
|
|
564
558
|
`10m`): a bare `500` is refused, naming both readings.
|
|
565
559
|
|
|
566
|
-
An empty `=` value means "no value" on
|
|
567
|
-
their bare forms: `--profile=` writes `profile.json
|
|
568
|
-
writes `<cacheDir>/runs/<run_id>.json`, `--graph=` prints to stdout and
|
|
560
|
+
An empty `=` value means "no value" on two flags, which then act like
|
|
561
|
+
their bare forms: `--profile=` writes `profile.json` and
|
|
569
562
|
`--affected=` uses the default base. Every other flag refuses an empty
|
|
570
|
-
value: `--dry=`, `--
|
|
563
|
+
value: `--dry=`, `--continue=` and
|
|
571
564
|
`--exclude-dependencies=` as well as the ones with no bare form
|
|
572
565
|
(`--retries=`, `--timeout=`, `--cache-dir=`, `--filter=`, `--cache-policy=`).
|
|
573
566
|
|
|
@@ -602,10 +595,13 @@ So `--no-cache` always wins over `--force`. The common cases:
|
|
|
602
595
|
- `--no-cache` → nothing reads, nothing writes, and declared output
|
|
603
596
|
globs are left untouched (you're debugging; vx won't manage your
|
|
604
597
|
tree).
|
|
605
|
-
- `--force` → re-execute every task (reads off)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
598
|
+
- `--force` → re-execute every task (reads off) with writes on. An
|
|
599
|
+
entry is immutable: a key the local cache already holds is kept, and
|
|
600
|
+
each remote that can batch (`hasMany`) is asked once, in the
|
|
601
|
+
background, which keys it holds; one that cannot is asked `has` per
|
|
602
|
+
key before its upload. Held keys are not uploaded again (a probe that
|
|
603
|
+
fails uploads, as before). Output globs ARE cleaned before each task,
|
|
604
|
+
so the disk holds this run's outputs.
|
|
609
605
|
|
|
610
606
|
`--cache-policy=<spec>` is a comma-separated list of `<layer>:<flags>`
|
|
611
607
|
segments. `layer` is `local` or `remote`; `flags` is any subset of `r`
|
|
@@ -625,8 +621,9 @@ layer keeps its current value. Both `--cache-policy=<spec>` and the space form
|
|
|
625
621
|
a remote hit is still delivered (the artifact has to land on disk to be
|
|
626
622
|
extracted), it just never short-circuits the remote read.
|
|
627
623
|
|
|
628
|
-
Combine with `--force` for "re-execute and
|
|
629
|
-
`--cache-policy=local: --force` (local off, reads off, remote write-only
|
|
624
|
+
Combine with `--force` for "re-execute and fill only the remote":
|
|
625
|
+
`--cache-policy=local: --force` (local off, reads off, remote write-only;
|
|
626
|
+
keys the remote already holds are not sent).
|
|
630
627
|
|
|
631
628
|
Invalid layers/flags are a parse error
|
|
632
629
|
(`--cache-policy: invalid layer 'disk'`, `--cache-policy: invalid flag 'x'`, …);
|
|
@@ -691,8 +688,7 @@ What the shapes mean in each column:
|
|
|
691
688
|
everything, the blocker included (`⊘ skipped app#deploy • blocked by
|
|
692
689
|
lib#build`; a fail-fast skip, which nothing blocked, carries no
|
|
693
690
|
suffix). Every view that lists tasks prints it, a dependency's
|
|
694
|
-
included: nothing prints below the footer.
|
|
695
|
-
`skipped (blocked by lib#build)`.
|
|
691
|
+
included: nothing prints below the footer.
|
|
696
692
|
- **`frame, or one-liner if quiet`** — a cache hit with stored stdout is
|
|
697
693
|
worth a frame (the output is the point); a hit with nothing to replay
|
|
698
694
|
compresses to one line, which is what keeps a 2000-task warm run
|
|
@@ -746,7 +742,7 @@ tracks the run live. Top to bottom:
|
|
|
746
742
|
persistent tasks it depends on, until it — or, with several, the
|
|
747
743
|
first of them — exits (one kept only as a dependency that exits 0,
|
|
748
744
|
a daemon that forked and returned, does not end it); the rest still up are then torn
|
|
749
|
-
down (SIGTERM,
|
|
745
|
+
down (SIGTERM, a 2 s grace, SIGKILL), one status line names
|
|
750
746
|
the task and its code (`vx: app#dev exited with code 1; stopping 1
|
|
751
747
|
other persistent task`), and a non-zero exit makes the run exit 1
|
|
752
748
|
and is what `vx last` records.
|
|
@@ -762,16 +758,43 @@ other persistent task`), and a non-zero exit makes the run exit 1
|
|
|
762
758
|
3. **Worker rows** — one per worker slot that has been busy (at most
|
|
763
759
|
`min(concurrency, 10)`), no glyph and no spinner: the live ticking
|
|
764
760
|
elapsed time leads (` 568ms running <id>`). A task stays in
|
|
765
|
-
its row for its whole life
|
|
766
|
-
|
|
767
|
-
|
|
761
|
+
its row for its whole life, so nothing jumps; overflow shows as
|
|
762
|
+
`+k more`. A second in, an idle worker row holds a task an
|
|
763
|
+
unfinished dependency blocks, one per row, in the order the
|
|
764
|
+
scheduler starts them (most dependents first):
|
|
765
|
+
`blocked solid-js#build by solid-js#types` (dim `blocked`, the id,
|
|
766
|
+
then the running task that holds it back). The task starts in the
|
|
767
|
+
row that held it. A row with no blocked task left to hold leaves the
|
|
768
|
+
region and the rows below close up; blocked tasks only dwindle, so
|
|
769
|
+
it never comes back. A running row says what waits on it:
|
|
770
|
+
`critical path` (yellow) on the running task
|
|
768
771
|
the longest unfinished chain waits on, by the same p50s as the
|
|
769
772
|
forecast below and only while two or more run, and a dim
|
|
770
773
|
`blocks N`, its unfinished dependents direct and transitive. Where
|
|
771
774
|
local history knows the task, its row also shows its usual duration
|
|
772
775
|
(its executed p50) in the cache column, fixed while the elapsed time
|
|
773
776
|
counts up toward it (`3.33s running ~5.2s solid-js#types`).
|
|
774
|
-
4. **
|
|
777
|
+
4. **vx's own steps** — what vx does around the tasks that the run
|
|
778
|
+
waits on, before its first task starts or before it ends, as rows of
|
|
779
|
+
their own below the worker rows: `vx:load` (configs and graph from
|
|
780
|
+
the lock), `vx:run-lock`, `<plugin>:setup`, `vx:keys` (keys and local
|
|
781
|
+
probes), `<plugin>:schedule`, then after the graph `vx:uploads`,
|
|
782
|
+
`vx:history`, `<plugin>:telemetry`, `<plugin>:teardown`,
|
|
783
|
+
`vx:snapshots`, `vx:retention`, `vx:close` and `vx:sandbox`. A step
|
|
784
|
+
shows once it has run 150 ms, ticking like a task
|
|
785
|
+
(` 1.2s running internal vx:uploads`); no worker slot or
|
|
786
|
+
`--concurrency` holds it back. When it ends it leaves a list row
|
|
787
|
+
like a task's, `internal` in the cache column
|
|
788
|
+
(`⏺︎ 4.60s success internal vx:uploads`); a quicker one shows
|
|
789
|
+
nowhere. A step that runs beside the tasks, `vx:prefetch` (a
|
|
790
|
+
remote's downloads), never shows: `vx last` lists it. `:` is no task
|
|
791
|
+
id's separator, so a step never reads as a task: a project named
|
|
792
|
+
`vx` keeps `vx#build`. No step is a task: no count, bar, history
|
|
793
|
+
row, forecast, flaky verdict or telemetry record holds one;
|
|
794
|
+
`--profile` and `VX_DEBUG_TIMING` time vx's stages their own way.
|
|
795
|
+
Steps show only beside a live region; a log (CI, a pipe) prints
|
|
796
|
+
none.
|
|
797
|
+
5. **The live summary section** — the SAME meters the final footer
|
|
775
798
|
prints (`tasks` + `cache` bars with legends, `time`), filling in as
|
|
776
799
|
the run progresses, under a bare `vx` wordmark rule. A second in,
|
|
777
800
|
the `time` row adds what is left (`time 1.41s · ~3s left`): each
|
|
@@ -814,11 +837,11 @@ redraws within 30 ms of the last draw coalesce into a single
|
|
|
814
837
|
trailing draw when the floor expires (the final state always lands).
|
|
815
838
|
On a 3,270-task warm run this cuts ~6.7 MB of redraw ANSI to ~20 KB.
|
|
816
839
|
|
|
817
|
-
Identity coloring:
|
|
818
|
-
|
|
819
|
-
in every run and every surface
|
|
820
|
-
|
|
821
|
-
read as an outcome.
|
|
840
|
+
Identity coloring: each half of a `project#task` renders in a stable
|
|
841
|
+
hue hashed from its own name (same project = same color, same target
|
|
842
|
+
name = same color, in every run and every surface: every `#build`
|
|
843
|
+
matches), with a dim `#` between, all outside the status palette, so
|
|
844
|
+
an id can never read as an outcome.
|
|
822
845
|
|
|
823
846
|
On GitHub Actions (`GITHUB_ACTIONS` truthy, full output mode), each
|
|
824
847
|
task's block is wrapped in `::group::<id> (<outcome> <duration>)` /
|
|
@@ -890,22 +913,22 @@ Locally-executed tasks always write in place and ignore the flag, and
|
|
|
890
913
|
it in either direction. **It never affects a cache key**: transfer
|
|
891
914
|
tuning cannot change what a command produces.
|
|
892
915
|
|
|
893
|
-
One safety gate: a task whose outputs another task's `
|
|
916
|
+
One safety gate: a task whose outputs another task's `inputs`
|
|
894
917
|
globs could read on disk is silently kept eager, because deferring it
|
|
895
918
|
would make that key depend on whether the bytes arrived. A task with no
|
|
896
919
|
`cache` that a cached task depends on counts as reading its whole
|
|
897
920
|
project (C-16). `--dry` reports
|
|
898
921
|
how many tasks would keep their outputs remote and names each downgrade,
|
|
899
922
|
so a run that defers nothing says why. A run in which any task declares a
|
|
900
|
-
`
|
|
923
|
+
`inputs.runtime` / `workspaceRuntime` command defers **nothing**:
|
|
901
924
|
a shell command's reads cannot be bounded, so vx cannot prove it will
|
|
902
925
|
not read a deferred output (the same reason vx refuses to infer inputs
|
|
903
926
|
by tracing). When bytes are fetched later, vx saves an ordinary
|
|
904
927
|
cache entry for them, so the next run is a plain local hit.
|
|
905
928
|
|
|
906
|
-
## Planning mode (`--dry
|
|
929
|
+
## Planning mode (`--dry`)
|
|
907
930
|
|
|
908
|
-
|
|
931
|
+
`--dry` short-circuits execution. It builds the full task graph,
|
|
909
932
|
compute every task's cache key, and probe the cache to predict the
|
|
910
933
|
hit/miss outcome. Against a remote cache the probe is a lightweight
|
|
911
934
|
existence check — planning never downloads or ingests artifacts.
|
|
@@ -913,8 +936,8 @@ existence check — planning never downloads or ingests artifacts.
|
|
|
913
936
|
```
|
|
914
937
|
$ vx run ci --dry
|
|
915
938
|
would run:
|
|
916
|
-
|
|
917
|
-
|
|
939
|
+
✓ @vzn/vx#format-check cache hit (local) 02bfe8a9
|
|
940
|
+
✓ @vzn/vx#lint cache hit (local) d66cfed2
|
|
918
941
|
▶ @vzn/vx#test cache miss — would exec 68595e49 ~72.64s
|
|
919
942
|
|
|
920
943
|
3 task(s) planned, 2 cache hits (2 local), 1 would run.
|
|
@@ -947,11 +970,11 @@ Status legend:
|
|
|
947
970
|
|
|
948
971
|
| Symbol | Meaning |
|
|
949
972
|
| ------ | ------------------------------------------------------------------------------------------ |
|
|
950
|
-
|
|
|
973
|
+
| `✓` | cache hit (local) — entry already in `<cacheDir>/` |
|
|
951
974
|
| `↓` | cache hit (remote) — entry would be fetched from the layer |
|
|
952
975
|
| `▶` | cache miss — task would execute (under `--force` too: nothing is read, what runs is saved) |
|
|
953
|
-
|
|
|
954
|
-
|
|
|
976
|
+
| `∅` | no-cache — task opts out (no `cache` block, or `--no-cache`) |
|
|
977
|
+
| `·` | `@noop` — task would not run anywhere |
|
|
955
978
|
| `−` | not started — a server you did not request whose every dependant restores a local hit |
|
|
956
979
|
| `○` | group task (suppressed in human view; in DOT + JSON) |
|
|
957
980
|
|
|
@@ -998,8 +1021,8 @@ $ vx run build lint --affected=main --dry
|
|
|
998
1021
|
```
|
|
999
1022
|
|
|
1000
1023
|
`kind` is `input` (a changed `file` is a declared input), `project` (its
|
|
1001
|
-
project changed and the task reads it whole:
|
|
1002
|
-
`file` no
|
|
1024
|
+
project changed and the task reads it whole: a task with no `inputs`, or a
|
|
1025
|
+
changed `file` no task of the project declares, often an output git does not
|
|
1003
1026
|
ignore; no `file` for a config import), `workspace` (a workspace-wide
|
|
1004
1027
|
`file` changed: the workspace config, or a root file a plugin claims such
|
|
1005
1028
|
as a lockfile, reached every task of the project),
|
|
@@ -1013,150 +1036,12 @@ against: a bare `--affected` guesses it (`origin/HEAD`, a trunk branch,
|
|
|
1013
1036
|
else `HEAD~1`), so a reason that names no file may come from a commit
|
|
1014
1037
|
the base reaches, a root `package.json` edit among them.
|
|
1015
1038
|
|
|
1016
|
-
|
|
1039
|
+
## Run artifacts (`--profile`)
|
|
1017
1040
|
|
|
1018
|
-
`--
|
|
1019
|
-
writes a file):
|
|
1020
|
-
|
|
1021
|
-
```
|
|
1022
|
-
vx run ci --graph | dot -Tsvg > graph.svg
|
|
1023
|
-
vx run ci --graph=graph.dot
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
Node `fillcolor` varies by predicted status (green = local hit,
|
|
1027
|
-
sky-blue = remote hit, orange = miss, gray = no-cache, fuchsia =
|
|
1028
|
-
group, white = not started). Edges are unstyled.
|
|
1029
|
-
|
|
1030
|
-
## Run artifacts (`--summarize`, `--profile`)
|
|
1031
|
-
|
|
1032
|
-
Both flags add a side-effect after a real run completes. Errors
|
|
1041
|
+
`--profile` adds a side-effect after a real run completes. Errors
|
|
1033
1042
|
writing the artifact are surfaced via `vx: failed to write …` but
|
|
1034
1043
|
don't change the run's exit code — the run already happened.
|
|
1035
1044
|
|
|
1036
|
-
### `--summarize[=<path>]`
|
|
1037
|
-
|
|
1038
|
-
Writes a per-run JSON file; `schemas/summary.json` is its JSON Schema:
|
|
1039
|
-
|
|
1040
|
-
```json
|
|
1041
|
-
{
|
|
1042
|
-
"runId": "01a0e3ef-206c-708d-95bf-c2b07211adf6",
|
|
1043
|
-
"ok": true,
|
|
1044
|
-
"exitCode": 0,
|
|
1045
|
-
"startedAt": "2026-09-27T17:34:54.572Z",
|
|
1046
|
-
"endedAt": "2026-09-27T17:34:54.586Z",
|
|
1047
|
-
"totalMs": 14.123643,
|
|
1048
|
-
"savedMs": 9,
|
|
1049
|
-
"tasks": [
|
|
1050
|
-
{
|
|
1051
|
-
"id": "a#build",
|
|
1052
|
-
"project": "a",
|
|
1053
|
-
"task": "build",
|
|
1054
|
-
"status": "cache-hit",
|
|
1055
|
-
"exitCode": 0,
|
|
1056
|
-
"durationMs": 3,
|
|
1057
|
-
"hash": "48007ccadd42ed7d",
|
|
1058
|
-
"storedDurationMs": 9,
|
|
1059
|
-
"storedCpuMs": 2.059,
|
|
1060
|
-
"wallclockStartNs": "9456491",
|
|
1061
|
-
"wallclockEndNs": "12902888"
|
|
1062
|
-
}
|
|
1063
|
-
],
|
|
1064
|
-
"aborted": [],
|
|
1065
|
-
"summary": {
|
|
1066
|
-
"successful": 1,
|
|
1067
|
-
"failed": 0,
|
|
1068
|
-
"skipped": 0,
|
|
1069
|
-
"cachedLocal": 1,
|
|
1070
|
-
"restoredLocal": 0,
|
|
1071
|
-
"restoredRemote": 0,
|
|
1072
|
-
"upToDate": 1,
|
|
1073
|
-
"cachedRemote": 0,
|
|
1074
|
-
"aborted": 0,
|
|
1075
|
-
"total": 1
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
`runId` is a UUIDv7. Default path: `<cacheDir>/runs/<run_id>.json`. hrtime fields are
|
|
1081
|
-
strings (bigints serialized as strings) to preserve ns precision
|
|
1082
|
-
through JSON. `cpuMs` / `peakRssBytes` are what the task's own
|
|
1083
|
-
execution used and appear on executed rows only (`peakRssBytes` only
|
|
1084
|
-
when the task's peak rose above vx's own footprint — a lighter task's
|
|
1085
|
-
figure would be vx's, handed back by the kernel; a Linux sandboxed task
|
|
1086
|
-
reports neither, since what bwrap's pid namespace used never reaches vx);
|
|
1087
|
-
`restored` appears on a hit only: `true` when its outputs were restored,
|
|
1088
|
-
`false` when they were already up to date. A hit's `durationMs`
|
|
1089
|
-
is the restore it cost, and what the PRODUCING execution used rides the
|
|
1090
|
-
artifact and appears under its own keys, `storedDurationMs` /
|
|
1091
|
-
`storedCpuMs` / `storedPeakRssBytes` (the work the hit skipped) — a
|
|
1092
|
-
remote worker's peak is never presented as this run's. `savedMs` is
|
|
1093
|
-
their sum over the hits, the footer's `saved` as a number. `admissionHeldMs` appears on a row
|
|
1094
|
-
only when an `admit` policy (a plugin's — `@vzn/vx-schedule-history`
|
|
1095
|
-
packs learned reservations) refused the task while a worker was free:
|
|
1096
|
-
the wait from that first refusal to its dispatch, the plugin's hand on
|
|
1097
|
-
the run. The footer's `info` row sums the waits, task-seconds:
|
|
1098
|
-
`admit held 3 tasks, 4.2s in all` (85 held tasks can read 4783s beside
|
|
1099
|
-
a 160 s run).
|
|
1100
|
-
|
|
1101
|
-
**`ok` / `exitCode`** are the run's verdict — the same value the CLI
|
|
1102
|
-
exits with: a stopping signal's code (130 for Ctrl-C), and with a kept
|
|
1103
|
-
server, its exit (the file is written again when the server ends). Gate on these rather than re-deriving a pass from the
|
|
1104
|
-
buckets: a run can be red without a single failed task (see `aborted`).
|
|
1105
|
-
|
|
1106
|
-
**`tasks[]` and `summary` describe the same population**, so
|
|
1107
|
-
`tasks.length === summary.total` always holds. Group tasks (no `exec`)
|
|
1108
|
-
are in neither — they do no work.
|
|
1109
|
-
|
|
1110
|
-
**`aborted[]`** lists tasks whose attempt ended while the run was
|
|
1111
|
-
stopping (Ctrl-C, a signal to vx), with their exit code, and tasks the stop reached before they started, with
|
|
1112
|
-
exit 1, `durationMs: 0` and no `wallclockStartNs`. Neither joins an
|
|
1113
|
-
outcome bucket or `total`, but they make the run red, so they are
|
|
1114
|
-
listed separately and counted as `summary.aborted`.
|
|
1115
|
-
|
|
1116
|
-
**`noCache: true`** marks a task no cache answered for: it declares no
|
|
1117
|
-
`cache` block (it executes every run by design), or the run's `--cache-policy`
|
|
1118
|
-
read and wrote nothing (`--no-cache`), so a hit rate should leave it out
|
|
1119
|
-
of the denominator. The key is present only when true; every other row is
|
|
1120
|
-
unchanged. Its `hash` is still set: dependents fold it.
|
|
1121
|
-
|
|
1122
|
-
**`notReady`** is present only on a failed persistent task: why it never
|
|
1123
|
-
became ready — `timeout` (the readiness deadline fired; `exitCode` is
|
|
1124
|
-
the kill's, 143 or 137 after the grace, and 143 for a server that
|
|
1125
|
-
traps the TERM and exits 0, as an ordinary timeout's),
|
|
1126
|
-
`exited` (the child exited first; `exitCode` is then its own) or `spawn`
|
|
1127
|
-
(the spawn itself failed). Every label reads it, `failed (never ready:
|
|
1128
|
-
timed out, exit 143)`. A server the run's stop (a Ctrl-C) killed while it started is
|
|
1129
|
-
`aborted`, not failed, as any task the stop kills; so is one that
|
|
1130
|
-
became ready only after the stop.
|
|
1131
|
-
|
|
1132
|
-
**`sandboxViolations`** is present only on a sandboxed task with a
|
|
1133
|
-
SANDBOX VIOLATIONS section — the count of its denials (vx's own notes
|
|
1134
|
-
there count none), and the reason the task failed (its exit is forced to 1
|
|
1135
|
-
when it was 0); every label counts it, `failed (exit 1, 2 sandbox
|
|
1136
|
-
violations)`.
|
|
1137
|
-
|
|
1138
|
-
**`timedOut: true`** is present only on a `failed` row vx's own
|
|
1139
|
-
`timeout` killed: its exit is the shell's 143, and every label reads
|
|
1140
|
-
`failed (timed out, exit 143)` rather than a signal.
|
|
1141
|
-
|
|
1142
|
-
**`blockedBy`** is present only on a `skipped` row: the id of the failed
|
|
1143
|
-
(or aborted) task at the root of what blocked it, through any chain of
|
|
1144
|
-
skips between — what the skipped row prints, for a script.
|
|
1145
|
-
A fail-fast skip has none.
|
|
1146
|
-
|
|
1147
|
-
**`flaky: { passes, failures, attempts }`** is present only on a task
|
|
1148
|
-
this run proved flaky (its row's `flaky` note, typed): `passes` and
|
|
1149
|
-
`failures` count the outcomes on record for this exact `hash`, this run
|
|
1150
|
-
included, and `attempts` is what this run took. A consumer gating on
|
|
1151
|
-
`status: "failed"` can tell a break (no `flaky` key) from a flake
|
|
1152
|
-
without reading the history. Every other row is byte-identical.
|
|
1153
|
-
|
|
1154
|
-
**`durationMs` is always what THIS run spent on the task.** For a cache
|
|
1155
|
-
hit that is the probe + restore, not the exec time the entry was stored
|
|
1156
|
-
with — so it is small even for an expensive task. The work a hit
|
|
1157
|
-
_skipped_ is a different number; `--report`'s "N saved" is the surface
|
|
1158
|
-
that reports it.
|
|
1159
|
-
|
|
1160
1045
|
### `--profile[=<path>]`
|
|
1161
1046
|
|
|
1162
1047
|
Writes a Chrome-trace JSON of every task's wallclock span. Open in
|
|
@@ -1192,91 +1077,6 @@ from the per-task `hrtime.bigint()` spans the runner captures.
|
|
|
1192
1077
|
|
|
1193
1078
|
Default path: `profile.json` (cwd-relative).
|
|
1194
1079
|
|
|
1195
|
-
### `--report[=markdown]`
|
|
1196
|
-
|
|
1197
|
-
At the end of a real run, prints a markdown run report to **stdout**,
|
|
1198
|
-
just above the footer: nothing prints below the footer. The report
|
|
1199
|
-
itself is machine-clean. One header line of
|
|
1200
|
-
totals plus a table, one row per task:
|
|
1201
|
-
|
|
1202
|
-
```markdown
|
|
1203
|
-
## vx run — passed
|
|
1204
|
-
|
|
1205
|
-
**3 tasks** · 3 success · 0 failed · 2 cached (1 up-to-date, 1 restored) · 1.23s total · 2.65s saved
|
|
1206
|
-
|
|
1207
|
-
| Task | Status | Cache | Duration |
|
|
1208
|
-
| --------- | ------- | ---------- | -------- |
|
|
1209
|
-
| web#build | success | miss | 1.23s |
|
|
1210
|
-
| web#test | success | local | 5ms |
|
|
1211
|
-
| api#test | success | up-to-date | 3ms |
|
|
1212
|
-
```
|
|
1213
|
-
|
|
1214
|
-
`Status` is the task outcome (`success` / `failed (exit N)`, with the
|
|
1215
|
-
signal an exit above 128 stands for, `failed (exit 137, 128 + SIGKILL)`,
|
|
1216
|
-
or a timeout's reason, `failed (timed out, exit 143)`, a persistent
|
|
1217
|
-
task's `never ready: …`, or a sandboxed task's violation count, or
|
|
1218
|
-
`skipped`, naming what blocked it: `skipped (blocked by lib#build)`);
|
|
1219
|
-
`Cache` is its provenance (`miss` / `no-cache` for a task no cache
|
|
1220
|
-
answered for — no `cache` block, or a `--no-cache` / `--cache-policy=local:` run —
|
|
1221
|
-
which never consulted one / `local` / `remote` / `up-to-date` /
|
|
1222
|
-
`—`). A stopped run (a Ctrl-C teardown) reads `## vx run — interrupted`,
|
|
1223
|
-
and its tasks keep the terminal's words: one the signal killed is
|
|
1224
|
-
`aborted` with its time so far, one the stop reached before it ran is
|
|
1225
|
-
`not run` with no time. Both count in the header's task total, listed
|
|
1226
|
-
as `1 aborted · 1 not run`, so a red report with no failing row still
|
|
1227
|
-
says why. Group tasks (no `exec`) get neither — they are not
|
|
1228
|
-
work, and the header's counts match the terminal summary and
|
|
1229
|
-
`--summarize` exactly.
|
|
1230
|
-
|
|
1231
|
-
The two durations in the header mean different things, and the
|
|
1232
|
-
distinction is the point:
|
|
1233
|
-
|
|
1234
|
-
- **`N total`** sums `Duration` over the tasks that actually EXECUTED,
|
|
1235
|
-
an aborted task's time so far included — the time this run spent.
|
|
1236
|
-
- **`N saved`** sums the exec times the cache hits SKIPPED, read from
|
|
1237
|
-
each entry as it was stored (above, 2.01s and 640ms). It is
|
|
1238
|
-
deliberately not the hits' `Duration` column, which is the restore
|
|
1239
|
-
they cost this run: summing that reported a task taking 2.01s cold
|
|
1240
|
-
as "6ms saved".
|
|
1241
|
-
|
|
1242
|
-
Only `markdown` is supported today (`json` is reserved; a bad value is a
|
|
1243
|
-
parse error). Built purely from the run's outcomes after it returns — it
|
|
1244
|
-
adds zero cost when both flags are absent.
|
|
1245
|
-
|
|
1246
|
-
### `--report-file <path>`
|
|
1247
|
-
|
|
1248
|
-
Writes the same markdown report to a file instead of (or as well as)
|
|
1249
|
-
stdout, and is the form to use for a CI step summary:
|
|
1250
|
-
|
|
1251
|
-
```sh
|
|
1252
|
-
vx run ci --report-file="$GITHUB_STEP_SUMMARY"
|
|
1253
|
-
```
|
|
1254
|
-
|
|
1255
|
-
**Do not redirect stdout for this.** The report itself is machine-clean,
|
|
1256
|
-
but stdout is not vx's alone — the status logger writes frames, meter
|
|
1257
|
-
bars and `::group::` workflow commands to the same stream, so
|
|
1258
|
-
`--report=markdown >> "$GITHUB_STEP_SUMMARY"` puts the entire run log
|
|
1259
|
-
into the step summary above the table.
|
|
1260
|
-
|
|
1261
|
-
The report is **appended**, never truncated: `$GITHUB_STEP_SUMMARY` is a
|
|
1262
|
-
shared, append-only file that other steps in the same job also write to,
|
|
1263
|
-
so overwriting it would silently discard their content. Passing both
|
|
1264
|
-
flags writes the report to stdout AND appends it to the file. A run
|
|
1265
|
-
stopped by Ctrl-C or SIGTERM still writes both, its stopped tasks
|
|
1266
|
-
`aborted`. A write
|
|
1267
|
-
failure is reported (`vx: failed to write report to …`) but does not
|
|
1268
|
-
change the exit code — the run already happened, the same contract
|
|
1269
|
-
`--summarize` and `--profile` follow.
|
|
1270
|
-
|
|
1271
|
-
### `--tag <k=v>`
|
|
1272
|
-
|
|
1273
|
-
Labels the invocation. Repeatable; `--tag=k=v` form too. The pair is
|
|
1274
|
-
split on the **first** `=`, so values may contain `=` (e.g. a URL). An
|
|
1275
|
-
empty key is a parse error. Tags are recorded on the run's
|
|
1276
|
-
`invocations` row so dashboards can filter runs by label, and reach
|
|
1277
|
-
telemetry as `vx.tag.<key>`; a secret value in one is masked there as
|
|
1278
|
-
in the task's output (see [Masking](./schema.md)).
|
|
1279
|
-
|
|
1280
1080
|
## Sandbox
|
|
1281
1081
|
|
|
1282
1082
|
Sandbox isolation is opt-in **per task** via an `exec.sandbox` block in
|
|
@@ -1298,7 +1098,8 @@ export default {
|
|
|
1298
1098
|
},
|
|
1299
1099
|
},
|
|
1300
1100
|
},
|
|
1301
|
-
|
|
1101
|
+
inputs: { files: ['src/**'] },
|
|
1102
|
+
cache: { outputs: { files: ['dist/**'] } },
|
|
1302
1103
|
},
|
|
1303
1104
|
},
|
|
1304
1105
|
}
|
|
@@ -1322,7 +1123,7 @@ task's `SANDBOX VIOLATIONS` section ends with a dimmed `fix:` line,
|
|
|
1322
1123
|
the grants that would have allowed each denial, project-relative:
|
|
1323
1124
|
`fix: exec.sandbox.allow: { read: ['tsconfig.json'], write: ['dist/'] }`.
|
|
1324
1125
|
A write grants the top directory it landed in, so the next file there
|
|
1325
|
-
passes too, unless `
|
|
1126
|
+
passes too, unless `inputs.files` reads that directory: then it
|
|
1326
1127
|
grants the file alone, so the task cannot write its own inputs; a read the output depends on is a key input as well, and
|
|
1327
1128
|
the line says so, since a grant never adds one. A credential path
|
|
1328
1129
|
(`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube`, `~/.config/gh`,
|
|
@@ -1417,8 +1218,10 @@ run...` precedes it.
|
|
|
1417
1218
|
not change since the loop last saw it is not an edit (a `chmod` is one,
|
|
1418
1219
|
as the key reads the executable bit), nor is a directory
|
|
1419
1220
|
whose entries (names and sizes) did not, nor a path that stayed
|
|
1420
|
-
gone; a symlink is its target string
|
|
1421
|
-
|
|
1221
|
+
gone; a symlink is its target string and what it points to, as the
|
|
1222
|
+
key folds them, so a retarget is an edit, and so is an edit behind
|
|
1223
|
+
it: watch arms each link's target, wherever it lies, and names the
|
|
1224
|
+
link; a path the loop has never judged is an edit only if it was
|
|
1422
1225
|
modified after the watchers went live (macOS delivers the initial
|
|
1423
1226
|
run's own writes after the arm; the later of the path's mtime and
|
|
1424
1227
|
ctime says which side of it a path belongs to, so a file moved in
|
|
@@ -1449,7 +1252,7 @@ run...` precedes it.
|
|
|
1449
1252
|
`.gitignore` as the remedy (a persistent task declares no outputs;
|
|
1450
1253
|
item 948). A file git tracks is never blamed on the server: three
|
|
1451
1254
|
saves of a source file are the user's (WD-7). When any project's config declares
|
|
1452
|
-
`
|
|
1255
|
+
`inputs.workspaceFiles`, the per-project watchers are swapped
|
|
1453
1256
|
for ONE recursive root watcher (boundaries are off for those globs,
|
|
1454
1257
|
so a root-relative glob can name a file anywhere). That watcher
|
|
1455
1258
|
hears every write in the tree and keeps only the ones a key can
|
|
@@ -1497,7 +1300,8 @@ Always ignored (no re-trigger):
|
|
|
1497
1300
|
|
|
1498
1301
|
- `node_modules/`, `.git/`, `.vx/cache/` anywhere in the path.
|
|
1499
1302
|
- A path git ignores (`git check-ignore`, asked once per debounce
|
|
1500
|
-
window, never per event)
|
|
1303
|
+
window, never per event), unless a task's inputs name it outright
|
|
1304
|
+
(`.env.local`, `dist/**`): invisible to every other cache key — inputs are
|
|
1501
1305
|
tracked + untracked-not-ignored — so a cycle it started could change
|
|
1502
1306
|
nothing. A tracked file that matches a pattern is not ignored, by
|
|
1503
1307
|
git's own rule; outside a repository nothing is.
|
|
@@ -1509,7 +1313,7 @@ Always ignored (no re-trigger):
|
|
|
1509
1313
|
cycle, which writes again — a loop that never settles.
|
|
1510
1314
|
|
|
1511
1315
|
Everything else triggers a cycle. We deliberately don't filter events
|
|
1512
|
-
against per-task `
|
|
1316
|
+
against per-task `inputs.files` — the cache hash is the source
|
|
1513
1317
|
of truth. A change to an irrelevant file produces a cache-hit run
|
|
1514
1318
|
(typically tens of ms); the cost is much smaller than the engineering
|
|
1515
1319
|
cost of a per-event glob match.
|
|
@@ -1553,10 +1357,10 @@ re-reads the watched set (item 971).
|
|
|
1553
1357
|
The following flags are rejected (parser exits 1 before the initial
|
|
1554
1358
|
run):
|
|
1555
1359
|
|
|
1556
|
-
- `--dry`
|
|
1557
|
-
- `--
|
|
1558
|
-
- `--
|
|
1559
|
-
|
|
1360
|
+
- `--dry` — it skips execution; nothing to watch.
|
|
1361
|
+
- `--profile` — would overwrite its target per cycle.
|
|
1362
|
+
- `--verbosity <n>` (n > 0) — formats
|
|
1363
|
+
ONE run's result; a watch loop has no single run to report. (`--verbosity 0`
|
|
1560
1364
|
is accepted: it asks for what watch already prints.)
|
|
1561
1365
|
|
|
1562
1366
|
A requested persistent task (`exec.persistent`, a dev server) starts
|
|
@@ -1581,7 +1385,7 @@ re-runs itself and what it depends on.
|
|
|
1581
1385
|
- `0` — clean Ctrl+C / SIGTERM exit. A cycle in flight is torn down
|
|
1582
1386
|
first — its children, and the dev server held between cycles, get the
|
|
1583
1387
|
signal watch received (a Ctrl-C as SIGINT; a SIGTERM or SIGHUP as SIGTERM),
|
|
1584
|
-
|
|
1388
|
+
a 2 s grace, then SIGKILL — and the process leaves only
|
|
1585
1389
|
once it has returned, so a CI cancellation never orphans a task.
|
|
1586
1390
|
Also `0`, with no watch, when `--affected` selects nothing: watch
|
|
1587
1391
|
prints `vx watch: nothing affected since <ref>` and exits as `vx run`
|
|
@@ -2075,11 +1879,16 @@ manager is its lockfile (`pnpm-lock.yaml`, `bun.lock`, `yarn.lock`,
|
|
|
2075
1879
|
`package-lock.json`), else `packageManager`, else npm; the field it is
|
|
2076
1880
|
listed in is kept (`--save-dev`, `--dev`, …), and pnpm at a workspace
|
|
2077
1881
|
root adds `--workspace-root`. The lockfile is updated by that manager.
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
1882
|
+
Every `@vzn/vx-*` plugin the same package.json lists from the registry
|
|
1883
|
+
moves in the same install, to the same release, since every @vzn
|
|
1884
|
+
package releases together: `pnpm add @vzn/vx@0.0.2 @vzn/vx-otel@0.0.2
|
|
1885
|
+
--save-dev`. A plugin left behind is installed even when core is
|
|
1886
|
+
already at the release (`› vx upgrade plugins → 0.0.2`); a plugin in
|
|
1887
|
+
another field than core is refused, so no install moves a dependency's
|
|
1888
|
+
field. The versions are pinned, so pnpm's `minimumReleaseAge` refuses a
|
|
1889
|
+
release too young rather than mixing releases, and the error names
|
|
1890
|
+
`minimumReleaseAgeExclude`. vx prints what it runs, runs it there, and
|
|
1891
|
+
exits with an error if it fails:
|
|
2083
1892
|
|
|
2084
1893
|
```
|
|
2085
1894
|
› vx upgrade 0.0.1 → 0.0.2
|
|
@@ -2087,6 +1896,28 @@ fails:
|
|
|
2087
1896
|
bun add @vzn/vx@0.0.2 in /repo
|
|
2088
1897
|
```
|
|
2089
1898
|
|
|
1899
|
+
**Inside a project with its own vx**, a global vx hands `vx upgrade` to
|
|
1900
|
+
the project's (§ Which vx runs), and one run moves both to the same
|
|
1901
|
+
version (`TAG`, or latest): first the project's dependency, then the
|
|
1902
|
+
global vx that started the run, each the way it was installed. Each
|
|
1903
|
+
line names its install, and a refusal names the one that failed
|
|
1904
|
+
(`vx upgrade: global vx /usr/local/bin/vx: cannot write to …`); a
|
|
1905
|
+
project that fails leaves the global as it was. The project's vx run
|
|
1906
|
+
directly (a package script, `node_modules/.bin/vx`) has no global behind it, so only
|
|
1907
|
+
the project moves.
|
|
1908
|
+
|
|
1909
|
+
```
|
|
1910
|
+
› vx upgrade project 0.0.1 → 0.0.2
|
|
1911
|
+
|
|
1912
|
+
bun add @vzn/vx@0.0.2 in /repo
|
|
1913
|
+
|
|
1914
|
+
› vx upgrade global 0.0.1 → 0.0.2
|
|
1915
|
+
|
|
1916
|
+
/usr/local/bin/vx
|
|
1917
|
+
|
|
1918
|
+
✓ vx upgrade global installed 0.0.2
|
|
1919
|
+
```
|
|
1920
|
+
|
|
2090
1921
|
vx never installs Node or a package manager: one not on `PATH` is refused with the command to run. A vx run from an npx or
|
|
2091
1922
|
bunx cache has nothing installed to upgrade, and says to ask for
|
|
2092
1923
|
`@vzn/vx@latest` instead.
|
|
@@ -2103,9 +1934,11 @@ or is missing.
|
|
|
2103
1934
|
runs in, or one above it up to the git top-level, holds a
|
|
2104
1935
|
`package.json`, `pnpm-workspace.yaml`, `turbo.json`, `turbo.jsonc` or
|
|
2105
1936
|
`nx.json`, it runs `@vzn/vx-migrate` there, so `vx init` is the one
|
|
2106
|
-
command (owner, 2026-10-08): the
|
|
2107
|
-
|
|
2108
|
-
|
|
1937
|
+
command (owner, 2026-10-08): the latest release through `bun x`,
|
|
1938
|
+
never an installed one, so init always installs the latest `@vzn/vx`
|
|
1939
|
+
and moves every `@vzn/vx-*` plugin the root lists to it (M,
|
|
1940
|
+
2026-10-16; a source checkout runs the nearest installed
|
|
1941
|
+
`node_modules/@vzn/vx-migrate` instead) (`BUN_BE_BUN=1` makes the compiled
|
|
2109
1942
|
binary that runtime). `--dry`, `--format`, `--force`, `--mjs`,
|
|
2110
1943
|
`--native` and `--keep` pass through, and the exit is vx-migrate's.
|
|
2111
1944
|
vx-migrate maps a Turbo, Nx or Vite Task repo, else the `package.json`
|
|
@@ -2201,8 +2034,8 @@ none is dropped in silence. An Nx flag's camelCase spelling (`--nxBail`,
|
|
|
2201
2034
|
`--skipNxCache`), which Nx's parser takes and its docs print, is its
|
|
2202
2035
|
kebab-case row. A `[=<bool>]` row takes `=true` (the flag bare) and `=false` (left out), as Turbo's and Nx's parsers do. `vx run-many` and `vx affected` name the
|
|
2203
2036
|
`vx run` that does the same, and the other verbs a hand types from
|
|
2204
|
-
either tool name what does it here: `graph` (`--
|
|
2205
|
-
(`vx show`), `
|
|
2037
|
+
either tool name what does it here: `graph` and `query` (`--dry=json`), `ls`
|
|
2038
|
+
(`vx show`), `reset` (`vx cache clean`), and
|
|
2206
2039
|
`daemon`, `login`, `logout`, `link` and `unlink`, which vx has no use
|
|
2207
2040
|
for. Nx's `project:target` (`vx run web:build`,
|
|
2208
2041
|
when `web` declares `build`) is answered with `vx run web#build`: from
|
|
@@ -2221,11 +2054,10 @@ copy to the source.
|
|
|
2221
2054
|
| turbo | `--continue=dependencies-successful` | alias | `--continue=deps-ok` |
|
|
2222
2055
|
| turbo | `--continue` | same | `--continue[=never\|deps-ok\|always]` |
|
|
2223
2056
|
| turbo | `--dry-run` | alias | `--dry[=text\|json]` |
|
|
2224
|
-
| turbo | `--graph
|
|
2225
|
-
| turbo | `--graph` | same | `--graph[=<file>.dot]` |
|
|
2057
|
+
| turbo | `--graph` | refuse | `--dry=json` prints the planned task graph, each task with its deps |
|
|
2226
2058
|
| turbo | `--force[=<bool>]` | same | `--force`: skip cache reads, keep writes |
|
|
2227
2059
|
| turbo | `--affected` | same | `--affected[=<base>]` |
|
|
2228
|
-
| turbo | `--summarize
|
|
2060
|
+
| turbo | `--summarize` | refuse | use `vx last --format json` for the run's JSON record |
|
|
2229
2061
|
| turbo | `--output-logs=new-only` | refuse | use `--output-logs=full` (a hit replays its log) or `errors-only` |
|
|
2230
2062
|
| turbo | `--output-logs <v>` | same | `--output-logs full\|errors-only\|hash-only\|none` |
|
|
2231
2063
|
| turbo | `--no-cache` | same | `--no-cache` reads nothing either; Turbo's (reads, no writes) is `--cache-policy local:r,remote:r` |
|
|
@@ -2253,9 +2085,9 @@ copy to the source.
|
|
|
2253
2085
|
| turbo | `--dangerously-disable-package-manager-check` | refuse | vx reads no `packageManager` field: drop it |
|
|
2254
2086
|
| turbo | `--env-mode <v>` | refuse | vx passes only the variables a task declares (strict): list the rest in `exec.env.passThrough` |
|
|
2255
2087
|
| turbo | `--framework-inference <v>` | refuse | under `turbo()` inference is Turbo's; take a name back with a `!` entry in the task's `env` |
|
|
2256
|
-
| turbo | `--global-deps <v>` | refuse | declare them in `
|
|
2257
|
-
| turbo | `--json` | refuse | use
|
|
2258
|
-
| turbo | `--log-file` | refuse | use
|
|
2088
|
+
| turbo | `--global-deps <v>` | refuse | declare them in `inputs.workspaceFiles` (under `turbo()`, turbo.json's `globalDependencies`) |
|
|
2089
|
+
| turbo | `--json` | refuse | use `vx last --format json` for the run's result, `--dry=json` for the plan |
|
|
2090
|
+
| turbo | `--log-file` | refuse | use `vx last --format json` for the run's JSON record |
|
|
2259
2091
|
| turbo | `--preflight` | refuse | set `turboCache({ preflight: true })` or `TURBO_PREFLIGHT=1` |
|
|
2260
2092
|
| turbo | `--remote-cache-timeout <v>` | refuse | set `turboCache({ timeoutMs })` or `TURBO_REMOTE_CACHE_TIMEOUT` |
|
|
2261
2093
|
| turbo | `--single-package` | refuse | a repo with no workspaces is one project already: drop it |
|
|
@@ -2297,9 +2129,8 @@ copy to the source.
|
|
|
2297
2129
|
## Machine-readable output
|
|
2298
2130
|
|
|
2299
2131
|
`vx show`, `vx info`, `vx why`, `vx last` and `vx cache clean` take
|
|
2300
|
-
`--format json`. Each prints one JSON document on one line,
|
|
2301
|
-
|
|
2302
|
-
checked-in JSON Schema (draft 2020-12) shipped with the package:
|
|
2132
|
+
`--format json`. Each prints one JSON document on one line, whose shape
|
|
2133
|
+
is a checked-in JSON Schema (draft 2020-12) shipped with the package:
|
|
2303
2134
|
`schemas/show.json`, `schemas/info.json`, `schemas/why.json`,
|
|
2304
2135
|
`schemas/last.json`, `schemas/cache.json`
|
|
2305
2136
|
(`node_modules/@vzn/vx/schemas/` in an install). Every object closes its
|
|
@@ -2311,13 +2142,9 @@ schemas: each `{ … }` a verb's section shows is an object its schema
|
|
|
2311
2142
|
closes with those keys, each object the verb prints whole is shown, and
|
|
2312
2143
|
`vx info`'s field list is its schema's top level.
|
|
2313
2144
|
|
|
2314
|
-
`vx
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
the frame and the tasks' output with it, goes to stderr, so
|
|
2318
|
-
`vx run build --all --format json | jq .ok` reads only the result. It
|
|
2319
|
-
refuses `--dry` (use `--dry=json`), a bare `--graph` and
|
|
2320
|
-
`--report=markdown`, which print on stdout too.
|
|
2145
|
+
A run's result as JSON is `vx last --format json`, read after the run
|
|
2146
|
+
from the run history: one row per task with its status, key and cache
|
|
2147
|
+
result. `vx run --dry=json` prints the plan.
|
|
2321
2148
|
|
|
2322
2149
|
### Error codes
|
|
2323
2150
|
|
|
@@ -2433,8 +2260,9 @@ target, fix its spelling, or drop the entry.
|
|
|
2433
2260
|
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2434
2261
|
|
|
2435
2262
|
A `^task` with no edge source is refused too: ``<id>: ^build needs
|
|
2436
|
-
project edges; add pnpm()/bun()/npm()/yarn()
|
|
2437
|
-
|
|
2263
|
+
project edges; add pnpm()/bun()/npm()/yarn() and install so its
|
|
2264
|
+
lockfile exists, or a plugin that returns `deps` ``. A lockfile plugin
|
|
2265
|
+
whose lockfile is absent gives no edges. Core reads no edge from a manifest, so with no plugin
|
|
2438
2266
|
returning `deps` every `^` would resolve to nothing and fold no
|
|
2439
2267
|
upstream into the key. Declare the lockfile plugin in
|
|
2440
2268
|
`.vx/workspace.ts` (or a plugin of your own that returns `deps`); a run
|
|
@@ -2449,7 +2277,7 @@ its own output path, or make one depend on the other.
|
|
|
2449
2277
|
|
|
2450
2278
|
#### `VX_E_INPUT_IS_OUTPUT`
|
|
2451
2279
|
|
|
2452
|
-
A task's `
|
|
2280
|
+
A task's `inputs` match outputs: its own (vx cleans outputs before
|
|
2453
2281
|
a run, so it would delete its sources) or another task's (its key must
|
|
2454
2282
|
not read them; `dependsOn` already cascades). Exclude the outputs from
|
|
2455
2283
|
the inputs with a `!` glob, as the message shows, or declare no outputs
|
|
@@ -2470,12 +2298,11 @@ https://github.com/vznjs/vx/issues.
|
|
|
2470
2298
|
A plugin can give its refusal a code: `new UserError(message, 'MY_CODE')`.
|
|
2471
2299
|
|
|
2472
2300
|
**Streams.** A verb whose stdout is a product (a `--format json`
|
|
2473
|
-
document, `--dry` / `--dry=json`,
|
|
2301
|
+
document, `--dry` / `--dry=json`, a completion
|
|
2474
2302
|
script) writes that product alone there; a notice or warning it meets
|
|
2475
2303
|
on the way (a cache index from another vx version, a plugin's warning) goes
|
|
2476
2304
|
to stderr, as every refusal does. `vx run`'s stdout is the run's frame,
|
|
2477
|
-
the tasks' output it carries
|
|
2478
|
-
moves to stderr. `tests/cli-streams.test.ts` holds each
|
|
2305
|
+
the tasks' output it carries. `tests/cli-streams.test.ts` holds each
|
|
2479
2306
|
product verb to it under a notice.
|
|
2480
2307
|
|
|
2481
2308
|
## `vx show`
|
|
@@ -2571,6 +2398,9 @@ or `; projects and tasks: …` (M-57).
|
|
|
2571
2398
|
|
|
2572
2399
|
An empty target (`vx show ''`) is refused: omit it to list every project.
|
|
2573
2400
|
|
|
2401
|
+
In a workspace with no project, the list is empty and `vx show` says so
|
|
2402
|
+
on stderr (`no projects`) and names `vx init`.
|
|
2403
|
+
|
|
2574
2404
|
Exit codes: `0` success; `1` parse error or unknown target.
|
|
2575
2405
|
|
|
2576
2406
|
Two runs on one workspace take turns: the second waits for the first's
|
|
@@ -2614,7 +2444,7 @@ $ vx info
|
|
|
2614
2444
|
memory 13 GB usable — cgroup limit; the machine has 16 GB
|
|
2615
2445
|
cache dir /work/repo/.vx/cache (8.2 MB, this workspace's run history)
|
|
2616
2446
|
cache store /home/me/.vx/3f2a9c1e7b4d5a60/cache (1.3 GB, shared by this repo's checkouts)
|
|
2617
|
-
cache versions keys vx-cache-v43 · index schema
|
|
2447
|
+
cache versions keys vx-cache-v43 · index schema v40
|
|
2618
2448
|
cache entries 42 (1.3 GB)
|
|
2619
2449
|
orphans 3 artifacts (12 MB) the index does not know — `vx cache clean` reaps them
|
|
2620
2450
|
task runs (24h) 7 (5 cache hits)
|
|
@@ -2783,7 +2613,7 @@ app#build — run 01a1193a-7b39-75a9-870b-5c03e26d7104
|
|
|
2783
2613
|
changed file packages/app/src/input.txt 78981922613b2afb6025042ff6bd878ac1994e85 → 61780798228d17af2d34fce4cfbdf35556832472
|
|
2784
2614
|
|
|
2785
2615
|
what to do:
|
|
2786
|
-
file an edit re-runs by design; a file the task does not read belongs out of
|
|
2616
|
+
file an edit re-runs by design; a file the task does not read belongs out of inputs.files
|
|
2787
2617
|
```
|
|
2788
2618
|
|
|
2789
2619
|
A changed key's verdict names what moved, kind and name, three at most
|
|
@@ -2824,7 +2654,7 @@ A hit's line is `cache-hit · key …` (or `cache-hit-remote`): the status
|
|
|
2824
2654
|
names the hit and its tier, so only an executed run carries the word.
|
|
2825
2655
|
A skipped task never ran and derived no key: its line is `skipped · no key`.
|
|
2826
2656
|
A row's kind is what the key folded: `file` (an input file, by blob
|
|
2827
|
-
id), `env` (a declared variable, by digest), `runtime` and
|
|
2657
|
+
id), `linked` (the content behind a `file` that is a symlink), `env` (a declared variable, by digest), `runtime` and
|
|
2828
2658
|
`ws-runtime` (a declared command's output, `inputs.runtime` and
|
|
2829
2659
|
`inputs.workspaceRuntime`), `forward` (the argv forwarded after `--`),
|
|
2830
2660
|
`config` (the evaluated task config), `upstream` (a dependency's input
|
|
@@ -2839,9 +2669,9 @@ is gone (pruned, or the run failed and never saved one) the verb still
|
|
|
2839
2669
|
names the hash change and says the component diff is unavailable. When
|
|
2840
2670
|
only the previous run failed, the diff is taken against the last run
|
|
2841
2671
|
that saved an entry instead, and the detail line names that run. A
|
|
2842
|
-
task with no `cache` block derives a key too — it is what
|
|
2843
|
-
fold — but saves no entry, so for it the verb can only report
|
|
2844
|
-
change and says so.
|
|
2672
|
+
task with `inputs` and no `cache` block derives a key too — it is what
|
|
2673
|
+
dependents fold — but saves no entry, so for it the verb can only report
|
|
2674
|
+
the key change and says so.
|
|
2845
2675
|
`--format json` emits one machine-readable object (`{ taskId, runId,
|
|
2846
2676
|
why, diff, roots, writers }`); when the task's recorded runs predate run ids, `{ taskId,
|
|
2847
2677
|
why, diff, explanation }`, `why` and `diff` null and `explanation` the
|
|
@@ -2875,12 +2705,19 @@ command that exits 137 on its own reads the same). Where the run's
|
|
|
2875
2705
|
footer gave a reason, the row ends with it instead: `timed out`,
|
|
2876
2706
|
`never ready: exited`, `2 sandbox violations`, and for a skipped task
|
|
2877
2707
|
`after lib#build failed` (the v27 columns; rows older than them read
|
|
2878
|
-
as before).
|
|
2708
|
+
as before). A task whose key met a remote failure ends with it, read or
|
|
2709
|
+
write alike: `remote ✗ download: connection refused; upload: 503` (the
|
|
2710
|
+
v40 column, `remoteError` in `--format json`). Rows run failures first, then what executed or was
|
|
2879
2711
|
skipped, then hits; past sixteen hits the rest fold into one line with
|
|
2880
2712
|
their count (`… +980 more cache hits`) — a thousand-task warm run is a
|
|
2881
2713
|
thousand rows otherwise, with the one failure a screen above the
|
|
2882
2714
|
prompt — and the sixteen shown are the slowest restores, the one thing
|
|
2883
2715
|
a hit's row tells. `--format json` lists every row.
|
|
2716
|
+
Below the tasks, the steps the run did not wait on (`vx:prefetch`, a
|
|
2717
|
+
remote's downloads beside the tasks), which its own output never shows:
|
|
2718
|
+
`success vx:prefetch 1.20s internal`. They are kept per run, the
|
|
2719
|
+
newest 50, in `<cache dir>/steps/<run id>.json`, written only by a run
|
|
2720
|
+
that had one; `--format json` adds them as `steps`.
|
|
2884
2721
|
`vx last --list` prints the N most recent runs (default 10), one per
|
|
2885
2722
|
line in aligned columns (verdict, start, run id, counts, duration,
|
|
2886
2723
|
command), each run id cut to the shortest prefix no other run shares, 13 characters at least (`--list 5`
|
|
@@ -2902,7 +2739,7 @@ sandboxed task's row adds `sandboxFix`, the `exec.sandbox.allow`
|
|
|
2902
2739
|
entries (`read`, `write`, `network`, `systemInfo`) that would have
|
|
2903
2740
|
granted its denials, the same grants the terminal prints as `fix:`;
|
|
2904
2741
|
`withheld` lists the credential paths it denied and left out (a `.env` file, or a store under home the sandbox denies, `security.md`), and, for
|
|
2905
|
-
a cached task, `inputs` the reads of its project no `
|
|
2742
|
+
a cached task, `inputs` the reads of its project no `inputs.files`
|
|
2906
2743
|
glob matches (the terminal's `and, if the output depends on them, …`
|
|
2907
2744
|
line). A task
|
|
2908
2745
|
failed by `inputStability.severity: 'error'` adds `inputMoved`, the
|
|
@@ -3066,8 +2903,8 @@ names no package: it lists what the workspace itself declares.
|
|
|
3066
2903
|
|
|
3067
2904
|
## Environment variables vx reads
|
|
3068
2905
|
|
|
3069
|
-
Every setting has a variable derived from its path (`run.
|
|
3070
|
-
`
|
|
2906
|
+
Every setting has a variable derived from its path (`run.timeout` →
|
|
2907
|
+
`VX_RUN_TIMEOUT`), beating `.vx/workspace` and beaten by the flag; the
|
|
3071
2908
|
table in `docs/schema.md` § Settings gives each value's form and
|
|
3072
2909
|
default. Each is checked once before any verb runs: an empty one is
|
|
3073
2910
|
unset, a value its kind does not take is refused naming the form, and a
|
|
@@ -3087,11 +2924,7 @@ what a TASK sees is `exec.env` in `docs/schema.md`.
|
|
|
3087
2924
|
| `VX_RUN_RETRIES` | [`run.retries`](schema.md#settings) | Re-runs of a failed task, for tasks without their own `exec.retries`. |
|
|
3088
2925
|
| `VX_RUN_CONTINUE` | [`run.continue`](schema.md#settings) | What a failed task takes down: `never` stops the run, `deps-ok` skips its dependents, `always` runs them too. |
|
|
3089
2926
|
| `VX_RUN_OUTPUT_LOGS` | [`run.outputLogs`](schema.md#settings) | Which task output the run prints. Chosen before the workspace loads, so flag and env only. |
|
|
3090
|
-
| `VX_RUN_KILL_GRACE` | [`run.killGrace`](schema.md#settings) | How long a task that ignores SIGTERM gets before SIGKILL. |
|
|
3091
|
-
| `VX_RUN_TEARDOWN_TIMEOUT` | [`run.teardownTimeout`](schema.md#settings) | The bound on each plugin's end-of-run flush and teardown. |
|
|
3092
|
-
| `VX_RUN_READY_NOTICE` | [`run.readyNotice`](schema.md#settings) | How long a persistent task may take to match `readyWhen` before vx says it waits. |
|
|
3093
2927
|
| `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | [`run.rules.exclusiveOutputs`](schema.md#settings) | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
3094
|
-
| `VX_RUN_RULES_UPFRONT_KEYS` | [`run.rules.upfrontKeys`](schema.md#settings) | Refuse a task whose inputs can match another task's outputs. |
|
|
3095
2928
|
| `VX_CACHE_DIR` | [`cache.dir`](schema.md#settings) | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
|
|
3096
2929
|
| `VX_CACHE_RETENTION_MAX_SIZE` | [`cache.retention.maxSize`](schema.md#settings) | Evict least-recently-used entries at each run's end until the cache is under this (`'10GB'`). |
|
|
3097
2930
|
| `VX_CACHE_RETENTION_OLDER_THAN` | [`cache.retention.olderThan`](schema.md#settings) | Evict entries unused for this long at each run's end (`'30d'`). |
|
|
@@ -3121,9 +2954,8 @@ Colors are the two conventions in § Output format › Colors (`NO_COLOR`,
|
|
|
3121
2954
|
`FORCE_COLOR`). A truthy `CI` picks the CI output flow and
|
|
3122
2955
|
`GITHUB_ACTIONS` its `::group::` framing (§ Output); the run's
|
|
3123
2956
|
`invocations` row records the provider, the first truthy of
|
|
3124
|
-
`GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, then `CI`. Core never reads `GITHUB_STEP_SUMMARY
|
|
3125
|
-
|
|
3126
|
-
Actions pass `--report-file="$GITHUB_STEP_SUMMARY"`. A sandboxed
|
|
2957
|
+
`GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, then `CI`. Core never reads `GITHUB_STEP_SUMMARY`; the job summary is
|
|
2958
|
+
`@vzn/vx-ci`'s. A sandboxed
|
|
3127
2959
|
task's temp dir sits under `CLAUDE_CODE_TMPDIR` (else `CLAUDE_TMPDIR`,
|
|
3128
2960
|
else `/tmp/claude`), the sandbox runtime's own convention
|
|
3129
2961
|
(`docs/modules/sandbox-runtime.md`).
|
|
@@ -3158,8 +2990,8 @@ write ../shared/notes.txt
|
|
|
3158
2990
|
Every section is conditional: the `$ <command>` line only for an
|
|
3159
2991
|
executed task (success or failed — a hit replays its stored output and
|
|
3160
2992
|
shows none), `OUTPUT` only when the task wrote something,
|
|
3161
|
-
`SANDBOX VIOLATIONS (N)` only when the sandbox recorded some
|
|
3162
|
-
has a note on a failure. The
|
|
2993
|
+
`SANDBOX VIOLATIONS (N)` only when the sandbox recorded some (`N`
|
|
2994
|
+
counts them) or `SANDBOX` when vx has only a note on a failure. The
|
|
3163
2995
|
header carries the outcome (`restored-local • abc12345`, `failed (exit
|
|
3164
2996
|
N)`, …) and the footer repeats it after the duration. A test renders
|
|
3165
2997
|
this block and checks it against this page, byte for byte.
|
|
@@ -3206,17 +3038,27 @@ A broad run looks like:
|
|
|
3206
3038
|
```
|
|
3207
3039
|
|
|
3208
3040
|
The three bars are meters: `projects` is what the run covered against
|
|
3209
|
-
the workspace, `tasks` is failed / success / skipped, `cache`
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3041
|
+
the workspace, `tasks` is failed / success / skipped, `cache` has a
|
|
3042
|
+
segment per legend state (a skipped task rides it too, so the two bars
|
|
3043
|
+
fill alike; its count is on the tasks legend only). Its legend reads
|
|
3044
|
+
per layer: `local 1✓ 1∅ 2↓ 1↑ · remote 3↓ 4↑ 2✗ 1⇡`. `✓` is a fresh
|
|
3045
|
+
hit (outputs already on disk, whichever layer answered, so it counts
|
|
3046
|
+
under local), `∅` a task with no cache, `↓` is restored from that
|
|
3047
|
+
layer and `↑` saved to it, `✗` a task a remote read or write failed
|
|
3048
|
+
for, and `⇡` an upload still running (live only, always last). The
|
|
3049
|
+
region stays through vx's closing steps, worker rows gone, and the
|
|
3050
|
+
final footer replaces it in place. Each task counts once, under its strongest state: a
|
|
3051
|
+
failed remote read or write, then an upload running, then saved to a
|
|
3052
|
+
remote, then saved here, then restored, then fresh, then no cache; so
|
|
3053
|
+
a task saved here and uploaded is a remote `↑`. A key the remote already
|
|
3054
|
+
held is not sent, so it is no remote `↑`. Each count is painted
|
|
3055
|
+
in its segment's colour, number and glyph alike: `✓` green, `∅` dim,
|
|
3056
|
+
local blue, remote dark blue, `✗` and a miss red, `⇡` cyan; the
|
|
3057
|
+
`local` and `remote` words are dim. A layer shows only when the run's
|
|
3058
|
+
`--cache-policy` uses it. `N not saved` (ran, succeeded, saved
|
|
3059
|
+
nothing), `N not downloaded` (outputs left in the remote store) and
|
|
3060
|
+
every count show only when non-zero. `vx last` names each remote
|
|
3061
|
+
failure on its task's row. The
|
|
3220
3062
|
`time` row is the wall time, then what the cache saved (the hits' stored
|
|
3221
3063
|
exec times, never the restore), then the spread over executed tasks
|
|
3222
3064
|
only — a hit's restore time never enters it. The footer never says a
|
|
@@ -3234,12 +3076,22 @@ running a group focused surfaces its real member tasks instead.
|
|
|
3234
3076
|
**Nothing below the footer.** The footer is the run's last word.
|
|
3235
3077
|
What a run has to say about one task rides that task's row; what it
|
|
3236
3078
|
says about the run (a server that died, outputs left remote, a file
|
|
3237
|
-
`--
|
|
3079
|
+
`--profile` wrote, a history, upload, plugin or sandbox
|
|
3238
3080
|
warning) prints above the footer: it prints once the run's closing work
|
|
3239
3081
|
is done, the `VX_DEBUG_TIMING=1` table included. Cache housekeeping
|
|
3240
3082
|
(`cache.retention`) prints nothing. Only a kept server's own output
|
|
3241
3083
|
follows it.
|
|
3242
3084
|
|
|
3085
|
+
**Narrow terminals.** The footer is 62 columns: its rule and 50-cell
|
|
3086
|
+
bars. On a terminal narrower than that the bars shrink and the rule
|
|
3087
|
+
with them, as do a frame's section rules, so none wraps onto a second
|
|
3088
|
+
row. Off a terminal they keep their width.
|
|
3089
|
+
|
|
3090
|
+
**Warnings on stderr.** A run's warnings, such as `[vx] app#build:
|
|
3091
|
+
cache.outputs matched no files …` or a plugin's, go to stderr. Stdout
|
|
3092
|
+
carries only the rows, the frames and the footer, so `vx run build >
|
|
3093
|
+
log.txt` keeps them out of the log.
|
|
3094
|
+
|
|
3243
3095
|
**Skipped rows.** A red run lists every task that never started as a
|
|
3244
3096
|
row naming the failure that blocked it, so the footer's `1 skipped`
|
|
3245
3097
|
always has a name above it:
|
|
@@ -3301,6 +3153,7 @@ ANSI truecolor (`ansi-16m`) sequences, gated by env:
|
|
|
3301
3153
|
| `FORCE_COLOR=0` / `=false` | Force off, on a TTY too. |
|
|
3302
3154
|
| `FORCE_COLOR=…` (any other) | Force on — `1`, `true`, `2`, `3`, the empty string, etc. |
|
|
3303
3155
|
| (neither) | On iff `stdout.isTTY`. |
|
|
3156
|
+
| `TERM=dumb` | Off unless `FORCE_COLOR` forces it, and no live region. |
|
|
3304
3157
|
|
|
3305
3158
|
`0` and `false` are `FORCE_COLOR`'s "off" by the convention
|
|
3306
3159
|
supports-color, chalk and Node follow; until 2026-09-24 vx read every
|
|
@@ -3326,7 +3179,7 @@ Only a run's own output (`vx run`, `vx watch`) is painted, and the
|
|
|
3326
3179
|
help on a terminal: headings bold, `vx <verb>` and each flag or example
|
|
3327
3180
|
in cyan; `NO_COLOR` or `FORCE_COLOR=0` turns it off
|
|
3328
3181
|
(`tests/help-colors.test.ts`). Every other verb (`show`, `info`, `why`,
|
|
3329
|
-
`last`, a `--dry` plan,
|
|
3182
|
+
`last`, a `--dry` plan, `cache clean`), and the help off a
|
|
3330
3183
|
terminal, prints plain text whatever `FORCE_COLOR` says, so a script
|
|
3331
3184
|
that forces colour for a run's log still parses them
|
|
3332
3185
|
(`tests/cli-colors-e2e.test.ts`).
|
|
@@ -3343,7 +3196,7 @@ Reads try local first, then remote (hydrating local on remote hit),
|
|
|
3343
3196
|
with a background prefetch pass overlapping remote GETs with
|
|
3344
3197
|
execution. Writes go to local immediately; the remote upload is a
|
|
3345
3198
|
fire-and-forget background task drained at end of run — failures are
|
|
3346
|
-
|
|
3199
|
+
warned but never fail the build.
|
|
3347
3200
|
|
|
3348
3201
|
For any OTHER cache server (a Turbo-wire deployment, S3-direct, …),
|
|
3349
3202
|
implement core's `RemoteCacheLayer` interface in a plugin's `cache`
|
|
@@ -3358,7 +3211,7 @@ consult).
|
|
|
3358
3211
|
size, runs + hits in the last 24 h). For anything deeper, vx records
|
|
3359
3212
|
every task to a `runs` table in `cache.db` (UUIDv7 `run_id`, hrtime
|
|
3360
3213
|
wallclock spans, cpu_ms, peak RSS, status, cache_hit flag) plus one
|
|
3361
|
-
`invocations` header row per run (command, git/CI context,
|
|
3214
|
+
`invocations` header row per run (command, git/CI context,
|
|
3362
3215
|
counts). The SQLite file IS the API:
|
|
3363
3216
|
|
|
3364
3217
|
```sh
|
|
@@ -3407,7 +3260,7 @@ Surface:
|
|
|
3407
3260
|
|
|
3408
3261
|
- `run(options)` — execute. Returns `Promise<RunSummary>`.
|
|
3409
3262
|
- `planRun(options)` — predict, no execute. Returns
|
|
3410
|
-
`Promise<RunPlan>`. Used by `--dry
|
|
3263
|
+
`Promise<RunPlan>`. Used by `--dry`.
|
|
3411
3264
|
- `prepareRun(options, log)` — the shared setup (discovery → configs →
|
|
3412
3265
|
graph → cache). What an embedder builds on.
|
|
3413
3266
|
- `defineProject` / `defineWorkspace` — identity helpers for type
|