@vzn/vx 0.0.639 → 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 +111 -83
- package/docs/cli.md +70 -40
- package/docs/execution.md +4 -4
- package/docs/features.md +9 -8
- package/docs/patterns.md +1 -1
- package/docs/schema.md +235 -180
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +4 -3
- package/src/cache/cache.ts +4 -1
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +2 -2
- package/src/cache/index.ts +3 -0
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +8 -7
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cli/foreign-flags.ts +1 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +24 -1
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +9 -6
- package/src/cli/upgrade.ts +95 -19
- 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 +74 -2
- package/src/cli/why.ts +5 -3
- package/src/config.ts +19 -15
- package/src/exec/executor.ts +4 -4
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +8 -1
- package/src/graph/scheduler.ts +41 -15
- package/src/graph/task-graph.ts +135 -73
- package/src/index.ts +1 -1
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-task.ts +29 -25
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/persistent-task.ts +3 -3
- package/src/orchestrator/placement.ts +2 -2
- package/src/orchestrator/plan.ts +1 -1
- package/src/orchestrator/plugin-host.ts +62 -22
- package/src/orchestrator/plugin.ts +12 -2
- package/src/orchestrator/prepare.ts +12 -5
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/run-report.ts +124 -46
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run.ts +51 -21
- package/src/orchestrator/sandbox-fix.ts +1 -1
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/stable-keys.ts +59 -26
- package/src/orchestrator/steps.ts +26 -6
- package/src/orchestrator/task-hash.ts +31 -9
- package/src/orchestrator/telemetry-host.ts +3 -3
- package/src/orchestrator/telemetry.ts +9 -4
- package/src/orchestrator/upstream.ts +4 -181
- package/src/report/framed-output.ts +1 -1
- package/src/report/status-line.ts +7 -3
- package/src/settings.ts +11 -17
- package/src/util/paths.ts +1 -1
- 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/docs/cli.md
CHANGED
|
@@ -301,13 +301,13 @@ change seeds tasks in the projects it touches:
|
|
|
301
301
|
repository is one its `workspaceFiles` may reach into (git reports it
|
|
302
302
|
as one path; a glob whose fixed prefix is above or inside it counts);
|
|
303
303
|
- every task of a project whose `vx.config.*`
|
|
304
|
-
changed, or that holds a changed path no
|
|
304
|
+
changed, or that holds a changed path no task of its declares
|
|
305
305
|
(vx cannot prove that path re-keys nothing), or that holds a changed
|
|
306
306
|
submodule or embedded repository (git reports it as one path, and no
|
|
307
307
|
task's globs can say whether they reach inside), or that a lockfile
|
|
308
308
|
claim or a config import names (a dependency an install added or
|
|
309
309
|
removed reaches here as the lockfile's change);
|
|
310
|
-
-
|
|
310
|
+
- a task with no `inputs` when a changed path lies in its project or the
|
|
311
311
|
project is reached whole; a root file another task of its declares
|
|
312
312
|
(`workspaceFiles`) is no change there. A group seeds nothing.
|
|
313
313
|
|
|
@@ -354,6 +354,14 @@ change to it, and every project under it is selected, the project that
|
|
|
354
354
|
holds it included. A repository's own request to hide submodules from a diff
|
|
355
355
|
(`diff.ignoreSubmodules`, `submodule.<name>.ignore`) does not apply:
|
|
356
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.
|
|
357
365
|
`.vx/projects.lock.json` is filtered out of the changed set — a `vx lock`
|
|
358
366
|
re-write never marks every project affected.
|
|
359
367
|
|
|
@@ -766,22 +774,26 @@ other persistent task`), and a non-zero exit makes the run exit 1
|
|
|
766
774
|
local history knows the task, its row also shows its usual duration
|
|
767
775
|
(its executed p50) in the cache column, fixed while the elapsed time
|
|
768
776
|
counts up toward it (`3.33s running ~5.2s solid-js#types`).
|
|
769
|
-
4. **vx's own steps** — what vx does around the tasks
|
|
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
|
|
770
779
|
their own below the worker rows: `vx:load` (configs and graph from
|
|
771
780
|
the lock), `vx:run-lock`, `<plugin>:setup`, `vx:keys` (keys and local
|
|
772
|
-
probes),
|
|
773
|
-
`vx:
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
|
777
786
|
`--concurrency` holds it back. When it ends it leaves a list row
|
|
778
|
-
like a task's,
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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.
|
|
785
797
|
5. **The live summary section** — the SAME meters the final footer
|
|
786
798
|
prints (`tasks` + `cache` bars with legends, `time`), filling in as
|
|
787
799
|
the run progresses, under a bare `vx` wordmark rule. A second in,
|
|
@@ -901,14 +913,14 @@ Locally-executed tasks always write in place and ignore the flag, and
|
|
|
901
913
|
it in either direction. **It never affects a cache key**: transfer
|
|
902
914
|
tuning cannot change what a command produces.
|
|
903
915
|
|
|
904
|
-
One safety gate: a task whose outputs another task's `
|
|
916
|
+
One safety gate: a task whose outputs another task's `inputs`
|
|
905
917
|
globs could read on disk is silently kept eager, because deferring it
|
|
906
918
|
would make that key depend on whether the bytes arrived. A task with no
|
|
907
919
|
`cache` that a cached task depends on counts as reading its whole
|
|
908
920
|
project (C-16). `--dry` reports
|
|
909
921
|
how many tasks would keep their outputs remote and names each downgrade,
|
|
910
922
|
so a run that defers nothing says why. A run in which any task declares a
|
|
911
|
-
`
|
|
923
|
+
`inputs.runtime` / `workspaceRuntime` command defers **nothing**:
|
|
912
924
|
a shell command's reads cannot be bounded, so vx cannot prove it will
|
|
913
925
|
not read a deferred output (the same reason vx refuses to infer inputs
|
|
914
926
|
by tracing). When bytes are fetched later, vx saves an ordinary
|
|
@@ -1009,8 +1021,8 @@ $ vx run build lint --affected=main --dry
|
|
|
1009
1021
|
```
|
|
1010
1022
|
|
|
1011
1023
|
`kind` is `input` (a changed `file` is a declared input), `project` (its
|
|
1012
|
-
project changed and the task reads it whole:
|
|
1013
|
-
`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
|
|
1014
1026
|
ignore; no `file` for a config import), `workspace` (a workspace-wide
|
|
1015
1027
|
`file` changed: the workspace config, or a root file a plugin claims such
|
|
1016
1028
|
as a lockfile, reached every task of the project),
|
|
@@ -1086,7 +1098,8 @@ export default {
|
|
|
1086
1098
|
},
|
|
1087
1099
|
},
|
|
1088
1100
|
},
|
|
1089
|
-
|
|
1101
|
+
inputs: { files: ['src/**'] },
|
|
1102
|
+
cache: { outputs: { files: ['dist/**'] } },
|
|
1090
1103
|
},
|
|
1091
1104
|
},
|
|
1092
1105
|
}
|
|
@@ -1110,7 +1123,7 @@ task's `SANDBOX VIOLATIONS` section ends with a dimmed `fix:` line,
|
|
|
1110
1123
|
the grants that would have allowed each denial, project-relative:
|
|
1111
1124
|
`fix: exec.sandbox.allow: { read: ['tsconfig.json'], write: ['dist/'] }`.
|
|
1112
1125
|
A write grants the top directory it landed in, so the next file there
|
|
1113
|
-
passes too, unless `
|
|
1126
|
+
passes too, unless `inputs.files` reads that directory: then it
|
|
1114
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
|
|
1115
1128
|
the line says so, since a grant never adds one. A credential path
|
|
1116
1129
|
(`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube`, `~/.config/gh`,
|
|
@@ -1205,8 +1218,10 @@ run...` precedes it.
|
|
|
1205
1218
|
not change since the loop last saw it is not an edit (a `chmod` is one,
|
|
1206
1219
|
as the key reads the executable bit), nor is a directory
|
|
1207
1220
|
whose entries (names and sizes) did not, nor a path that stayed
|
|
1208
|
-
gone; a symlink is its target string
|
|
1209
|
-
|
|
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
|
|
1210
1225
|
modified after the watchers went live (macOS delivers the initial
|
|
1211
1226
|
run's own writes after the arm; the later of the path's mtime and
|
|
1212
1227
|
ctime says which side of it a path belongs to, so a file moved in
|
|
@@ -1237,7 +1252,7 @@ run...` precedes it.
|
|
|
1237
1252
|
`.gitignore` as the remedy (a persistent task declares no outputs;
|
|
1238
1253
|
item 948). A file git tracks is never blamed on the server: three
|
|
1239
1254
|
saves of a source file are the user's (WD-7). When any project's config declares
|
|
1240
|
-
`
|
|
1255
|
+
`inputs.workspaceFiles`, the per-project watchers are swapped
|
|
1241
1256
|
for ONE recursive root watcher (boundaries are off for those globs,
|
|
1242
1257
|
so a root-relative glob can name a file anywhere). That watcher
|
|
1243
1258
|
hears every write in the tree and keeps only the ones a key can
|
|
@@ -1285,7 +1300,8 @@ Always ignored (no re-trigger):
|
|
|
1285
1300
|
|
|
1286
1301
|
- `node_modules/`, `.git/`, `.vx/cache/` anywhere in the path.
|
|
1287
1302
|
- A path git ignores (`git check-ignore`, asked once per debounce
|
|
1288
|
-
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
|
|
1289
1305
|
tracked + untracked-not-ignored — so a cycle it started could change
|
|
1290
1306
|
nothing. A tracked file that matches a pattern is not ignored, by
|
|
1291
1307
|
git's own rule; outside a repository nothing is.
|
|
@@ -1297,7 +1313,7 @@ Always ignored (no re-trigger):
|
|
|
1297
1313
|
cycle, which writes again — a loop that never settles.
|
|
1298
1314
|
|
|
1299
1315
|
Everything else triggers a cycle. We deliberately don't filter events
|
|
1300
|
-
against per-task `
|
|
1316
|
+
against per-task `inputs.files` — the cache hash is the source
|
|
1301
1317
|
of truth. A change to an irrelevant file produces a cache-hit run
|
|
1302
1318
|
(typically tens of ms); the cost is much smaller than the engineering
|
|
1303
1319
|
cost of a per-event glob match.
|
|
@@ -1863,8 +1879,16 @@ manager is its lockfile (`pnpm-lock.yaml`, `bun.lock`, `yarn.lock`,
|
|
|
1863
1879
|
`package-lock.json`), else `packageManager`, else npm; the field it is
|
|
1864
1880
|
listed in is kept (`--save-dev`, `--dev`, …), and pnpm at a workspace
|
|
1865
1881
|
root adds `--workspace-root`. The lockfile is updated by that manager.
|
|
1866
|
-
vx
|
|
1867
|
-
|
|
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:
|
|
1868
1892
|
|
|
1869
1893
|
```
|
|
1870
1894
|
› vx upgrade 0.0.1 → 0.0.2
|
|
@@ -1910,9 +1934,11 @@ or is missing.
|
|
|
1910
1934
|
runs in, or one above it up to the git top-level, holds a
|
|
1911
1935
|
`package.json`, `pnpm-workspace.yaml`, `turbo.json`, `turbo.jsonc` or
|
|
1912
1936
|
`nx.json`, it runs `@vzn/vx-migrate` there, so `vx init` is the one
|
|
1913
|
-
command (owner, 2026-10-08): the
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
|
1916
1942
|
binary that runtime). `--dry`, `--format`, `--force`, `--mjs`,
|
|
1917
1943
|
`--native` and `--keep` pass through, and the exit is vx-migrate's.
|
|
1918
1944
|
vx-migrate maps a Turbo, Nx or Vite Task repo, else the `package.json`
|
|
@@ -2059,7 +2085,7 @@ copy to the source.
|
|
|
2059
2085
|
| turbo | `--dangerously-disable-package-manager-check` | refuse | vx reads no `packageManager` field: drop it |
|
|
2060
2086
|
| turbo | `--env-mode <v>` | refuse | vx passes only the variables a task declares (strict): list the rest in `exec.env.passThrough` |
|
|
2061
2087
|
| turbo | `--framework-inference <v>` | refuse | under `turbo()` inference is Turbo's; take a name back with a `!` entry in the task's `env` |
|
|
2062
|
-
| turbo | `--global-deps <v>` | refuse | declare them in `
|
|
2088
|
+
| turbo | `--global-deps <v>` | refuse | declare them in `inputs.workspaceFiles` (under `turbo()`, turbo.json's `globalDependencies`) |
|
|
2063
2089
|
| turbo | `--json` | refuse | use `vx last --format json` for the run's result, `--dry=json` for the plan |
|
|
2064
2090
|
| turbo | `--log-file` | refuse | use `vx last --format json` for the run's JSON record |
|
|
2065
2091
|
| turbo | `--preflight` | refuse | set `turboCache({ preflight: true })` or `TURBO_PREFLIGHT=1` |
|
|
@@ -2251,7 +2277,7 @@ its own output path, or make one depend on the other.
|
|
|
2251
2277
|
|
|
2252
2278
|
#### `VX_E_INPUT_IS_OUTPUT`
|
|
2253
2279
|
|
|
2254
|
-
A task's `
|
|
2280
|
+
A task's `inputs` match outputs: its own (vx cleans outputs before
|
|
2255
2281
|
a run, so it would delete its sources) or another task's (its key must
|
|
2256
2282
|
not read them; `dependsOn` already cascades). Exclude the outputs from
|
|
2257
2283
|
the inputs with a `!` glob, as the message shows, or declare no outputs
|
|
@@ -2587,7 +2613,7 @@ app#build — run 01a1193a-7b39-75a9-870b-5c03e26d7104
|
|
|
2587
2613
|
changed file packages/app/src/input.txt 78981922613b2afb6025042ff6bd878ac1994e85 → 61780798228d17af2d34fce4cfbdf35556832472
|
|
2588
2614
|
|
|
2589
2615
|
what to do:
|
|
2590
|
-
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
|
|
2591
2617
|
```
|
|
2592
2618
|
|
|
2593
2619
|
A changed key's verdict names what moved, kind and name, three at most
|
|
@@ -2628,7 +2654,7 @@ A hit's line is `cache-hit · key …` (or `cache-hit-remote`): the status
|
|
|
2628
2654
|
names the hit and its tier, so only an executed run carries the word.
|
|
2629
2655
|
A skipped task never ran and derived no key: its line is `skipped · no key`.
|
|
2630
2656
|
A row's kind is what the key folded: `file` (an input file, by blob
|
|
2631
|
-
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
|
|
2632
2658
|
`ws-runtime` (a declared command's output, `inputs.runtime` and
|
|
2633
2659
|
`inputs.workspaceRuntime`), `forward` (the argv forwarded after `--`),
|
|
2634
2660
|
`config` (the evaluated task config), `upstream` (a dependency's input
|
|
@@ -2643,9 +2669,9 @@ is gone (pruned, or the run failed and never saved one) the verb still
|
|
|
2643
2669
|
names the hash change and says the component diff is unavailable. When
|
|
2644
2670
|
only the previous run failed, the diff is taken against the last run
|
|
2645
2671
|
that saved an entry instead, and the detail line names that run. A
|
|
2646
|
-
task with no `cache` block derives a key too — it is what
|
|
2647
|
-
fold — but saves no entry, so for it the verb can only report
|
|
2648
|
-
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.
|
|
2649
2675
|
`--format json` emits one machine-readable object (`{ taskId, runId,
|
|
2650
2676
|
why, diff, roots, writers }`); when the task's recorded runs predate run ids, `{ taskId,
|
|
2651
2677
|
why, diff, explanation }`, `why` and `diff` null and `explanation` the
|
|
@@ -2687,6 +2713,11 @@ their count (`… +980 more cache hits`) — a thousand-task warm run is a
|
|
|
2687
2713
|
thousand rows otherwise, with the one failure a screen above the
|
|
2688
2714
|
prompt — and the sixteen shown are the slowest restores, the one thing
|
|
2689
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`.
|
|
2690
2721
|
`vx last --list` prints the N most recent runs (default 10), one per
|
|
2691
2722
|
line in aligned columns (verdict, start, run id, counts, duration,
|
|
2692
2723
|
command), each run id cut to the shortest prefix no other run shares, 13 characters at least (`--list 5`
|
|
@@ -2708,7 +2739,7 @@ sandboxed task's row adds `sandboxFix`, the `exec.sandbox.allow`
|
|
|
2708
2739
|
entries (`read`, `write`, `network`, `systemInfo`) that would have
|
|
2709
2740
|
granted its denials, the same grants the terminal prints as `fix:`;
|
|
2710
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
|
|
2711
|
-
a cached task, `inputs` the reads of its project no `
|
|
2742
|
+
a cached task, `inputs` the reads of its project no `inputs.files`
|
|
2712
2743
|
glob matches (the terminal's `and, if the output depends on them, …`
|
|
2713
2744
|
line). A task
|
|
2714
2745
|
failed by `inputStability.severity: 'error'` adds `inputMoved`, the
|
|
@@ -2894,7 +2925,6 @@ what a TASK sees is `exec.env` in `docs/schema.md`.
|
|
|
2894
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. |
|
|
2895
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. |
|
|
2896
2927
|
| `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | [`run.rules.exclusiveOutputs`](schema.md#settings) | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
2897
|
-
| `VX_RUN_RULES_UPFRONT_KEYS` | [`run.rules.upfrontKeys`](schema.md#settings) | Refuse a task whose inputs can match another task's outputs. |
|
|
2898
2928
|
| `VX_CACHE_DIR` | [`cache.dir`](schema.md#settings) | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
|
|
2899
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'`). |
|
|
2900
2930
|
| `VX_CACHE_RETENTION_OLDER_THAN` | [`cache.retention.olderThan`](schema.md#settings) | Evict entries unused for this long at each run's end (`'30d'`). |
|
package/docs/execution.md
CHANGED
|
@@ -154,7 +154,7 @@ terminal and a task succeeding or failing. Read it alongside
|
|
|
154
154
|
│ • SIGINT/SIGTERM/SIGHUP handlers (removed in a finally): on
|
|
155
155
|
│ signal, forward it (SIGHUP as SIGTERM) to everything
|
|
156
156
|
│ in liveChildren + persistentRegistry (and kill any
|
|
157
|
-
│ running
|
|
157
|
+
│ running inputs.runtime probe, C-65), wait
|
|
158
158
|
│ 2 s for their groups, SIGKILL
|
|
159
159
|
│ what is still there, let the run finish its own
|
|
160
160
|
│ end (flush, teardown, cache close), then
|
|
@@ -243,7 +243,7 @@ terminal and a task succeeding or failing. Read it alongside
|
|
|
243
243
|
│ UserError that fails this task — its dependents skip,
|
|
244
244
|
│ independent tasks continue)
|
|
245
245
|
│ 2. hashTaskConfig (memoized per run via HashCache)
|
|
246
|
-
│ 3. filterUpstreamHashes (apply
|
|
246
|
+
│ 3. filterUpstreamHashes (apply inputs.tasks filter)
|
|
247
247
|
│ 4. cache.key({ taskId, taskConfigHash,
|
|
248
248
|
│ forwardArgs, envValues, runtimeValues,
|
|
249
249
|
│ workspaceRuntimeValues, upstreamHashes,
|
|
@@ -430,7 +430,7 @@ broader access has cache-stability implications).
|
|
|
430
430
|
| Two projects of one name | `checkProjectNames` throws with both paths |
|
|
431
431
|
| Cycle in task graph | `detectCycle` throws with the cycle path |
|
|
432
432
|
| Malformed config | `loadProjectConfigs` throws (UserError) with file + field |
|
|
433
|
-
| `
|
|
433
|
+
| `inputs.runtime` command exits non-zero | UserError naming the command + exit code; that task is `failed`, dependents skip |
|
|
434
434
|
|
|
435
435
|
By default (`--continue=deps-ok`) failures don't kill the scheduler —
|
|
436
436
|
independent tasks already in flight finish, and unrelated tasks not yet
|
|
@@ -618,7 +618,7 @@ runs the same setup steps — workspace discovery, config load, package
|
|
|
618
618
|
graph, task graph — and the same per-task hash derivation, then
|
|
619
619
|
probes the cache. The probe is read-only; against a remote layer it
|
|
620
620
|
is an existence check only (no artifact download, no ingest). The one
|
|
621
|
-
deliberate side effect: `
|
|
621
|
+
deliberate side effect: `inputs.runtime` / `workspaceRuntime`
|
|
622
622
|
probe commands DO run, because predicting a key requires resolving
|
|
623
623
|
them — keep runtime inputs side-effect-free.
|
|
624
624
|
|
package/docs/features.md
CHANGED
|
@@ -31,7 +31,7 @@ config key or an environment variable is missing here.
|
|
|
31
31
|
- **Schedule strategy** (`--schedule`, `run.schedule`, `VX_RUN_SCHEDULE`) — pick the order with no timings: most work waiting (default), longest chain, most direct dependents, or ready order; plugins still lead. [page](https://vznjs.github.io/vx/features/critical-path/) · post: [Which task runs first?](https://vznjs.github.io/vx/blog/scheduling-strategies/#pick-the-guess)
|
|
32
32
|
- **No daemon** — every run starts cold and still answers in milliseconds. [page](https://vznjs.github.io/vx/features/no-daemon/) · post: [No daemon, on purpose](https://vznjs.github.io/vx/blog/no-daemon/)
|
|
33
33
|
- **Group tasks** (`dependsOn` with no `exec`) — a task that only runs its dependencies; `dependsOn: []` is a named no-op. [page](https://vznjs.github.io/vx/features/group-tasks/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
|
|
34
|
-
- **Keyed groups** (`
|
|
34
|
+
- **Keyed groups** (`inputs` with no `exec`) — a key a dependant folds: it re-runs on those inputs, and nothing spawns or counts for the group. [page](https://vznjs.github.io/vx/features/group-tasks/) · post: [A key with nothing to run](https://vznjs.github.io/vx/blog/keyed-groups/)
|
|
35
35
|
- **Implicit keyed `build`** — a project with no `build` gets a `^build` group keyed on its files, so source-only packages still move their dependants' keys. [page](https://vznjs.github.io/vx/features/implicit-build/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
|
|
36
36
|
- **dependsOn syntax** (`^name`, `pkg#name`, `name.*`, `^name.*`) — upstream, cross-project and pattern edges; `^` follows the project edges a plugin supplies, and with none is refused naming the fix. [page](https://vznjs.github.io/vx/features/depends-on-syntax/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
|
|
37
37
|
- **Every name must resolve** — `vx run lint test typecheck` refuses to start if one name matches no project. [page](https://vznjs.github.io/vx/features/names-must-resolve/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
|
|
@@ -55,7 +55,7 @@ config key or an environment variable is missing here.
|
|
|
55
55
|
- **GitHub Actions log groups** — on Actions, each task's block folds in a `::group::` with its outcome and time. [page](https://vznjs.github.io/vx/features/actions-log-groups/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
|
|
56
56
|
- **Colors** (`NO_COLOR`, `FORCE_COLOR`) — truecolor output, forced on or off by env; `TERM=dumb` prints plain, with no live region. [page](https://vznjs.github.io/vx/features/colors/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
|
|
57
57
|
- **Signal-named exits** — a failure reads `exit 137, 128 + SIGKILL`. [page](https://vznjs.github.io/vx/features/signal-exits/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
|
|
58
|
-
- **vx's own steps as rows** — on a terminal, what vx does around the tasks (`vx:uploads`, `<plugin>:teardown`) shows as a running row once it passes 150 ms, then a list row; never counted as a task. no post
|
|
58
|
+
- **vx's own steps as rows** — on a terminal, what vx does around the tasks (`vx:uploads`, `<plugin>:teardown`) shows as a running row once it passes 150 ms, then a list row marked `internal`, only when the run waits on it (`vx:prefetch` runs beside the tasks and is listed by `vx last` alone); never counted as a task. no post
|
|
59
59
|
- **Plain output off a TTY** — no live region, and a missing task lists tasks instead of opening the picker. [page](https://vznjs.github.io/vx/features/plain-off-tty/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
|
|
60
60
|
|
|
61
61
|
## Plan and explain
|
|
@@ -76,8 +76,8 @@ config key or an environment variable is missing here.
|
|
|
76
76
|
|
|
77
77
|
## Cache
|
|
78
78
|
|
|
79
|
-
- **Opt-in, explicit inputs** (`
|
|
80
|
-
- **Inputs** (`
|
|
79
|
+
- **Opt-in, explicit inputs** (`inputs.files`) — a task is keyed on the inputs it names, cached or not; one with none has no key, so every task behind it misses. A gitignored path named outright (`.env.local`, `dist/**`) is an input, hashed from disk and never stored; broad globs still skip ignored files. [page](https://vznjs.github.io/vx/features/explicit-inputs/) · post: [Explicit over magical](https://vznjs.github.io/vx/blog/explicit-over-magical/)
|
|
80
|
+
- **Inputs** (`inputs.env`, `inputs.runtime`, `inputs.tasks`, `inputs.workspaceFiles`, `inputs.workspaceRuntime`) — env vars, tool versions, upstream tasks and workspace files in the key. [page](https://vznjs.github.io/vx/features/key-inputs/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
|
|
81
81
|
- **Outputs** (`cache.outputs.files`, `cache.outputs.workspaceFiles`) — what a hit restores. [page](https://vznjs.github.io/vx/features/cache-outputs/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
|
|
82
82
|
- **Input stability** (`cache.inputStability`, workspace `cache.inputStability`, `VX_CACHE_INPUT_STABILITY_STRATEGY`, `VX_CACHE_INPUT_STABILITY_SEVERITY`) — an input written while the command runs withholds the save, on CI too, and the saves of every task that ran over its outputs; `strategy: 'mutable'` accepts a task's identical rewrite of its own input, `'unchecked'` skips the check; `severity: 'error'` fails the run instead of warning. no post
|
|
83
83
|
- **Keep outputs on a miss** (`cache.outputs.clean`, workspace `cache.outputs.clean`) — `false` leaves a task's outputs in place for a tool that reads its last output (tsc's `.tsbuildinfo`) and caches everything the outputs then hold, leftovers included (an opt-out of the clean-tree guarantee). no post
|
|
@@ -86,13 +86,14 @@ config key or an environment variable is missing here.
|
|
|
86
86
|
- **Config keyed as evaluated** — the key sees the resolved config object. [page](https://vznjs.github.io/vx/features/typescript-config/) · post: [Configs are programs](https://vznjs.github.io/vx/blog/resolved-config-hashing/)
|
|
87
87
|
- **Cascade through inputs** — a task's key folds its upstream input keys, never outputs. [page](https://vznjs.github.io/vx/features/cascade/) · post: [Cascade through dependencies](https://vznjs.github.io/vx/blog/cascade-through-inputs/)
|
|
88
88
|
- **Lockfile-aware keys** (`@vzn/vx-lockfile`: `pnpm()`, `bun()`, `npm()`, `yarn()`) — a bump re-keys only the projects whose closure changed; each also adds every member with a `package.json` as a project named by its `name`, gives vx the project edges the lockfile records (core reads no `package.json`), keys each package's `package.json` as an input and folds its manager's install settings (`.npmrc`, `bunfig.toml`, `pnpm-workspace.yaml`, `.yarnrc.yml`) into every task. Core keys no package file, so without one an install re-keys nothing; `npm()` reads `npm-shrinkwrap.json` first, and `bun()` refuses a lone `bun.lockb`. [page](https://vznjs.github.io/vx/features/lockfile-keys/) · post: [A lockfile bump should re-key two tasks](https://vznjs.github.io/vx/blog/lockfile-aware-keys/)
|
|
89
|
-
- **Upfront keys** (`
|
|
89
|
+
- **Upfront keys** (`vx lock`, no opt-out) — refuses an input glob another task's outputs could match, so every key is known before anything runs; only a dependency the key does not fold (left out of `inputs.tasks`) may be read, its bytes hashed after it ran. [page](https://vznjs.github.io/vx/features/upfront-keys/) · post: [Cascade through dependencies by folding input keys, never outputs](https://vznjs.github.io/vx/blog/cascade-through-inputs/)
|
|
90
90
|
- **Dependency rules** (`run.rules.dependencies`) — `vx lock` refuses a project depending on one a tag or name rule denies or does not allow, every broken edge named (`VX_E_DEPENDENCY_RULE`). [schema](https://vznjs.github.io/vx/schema/) · post: [Say which projects may depend on which](https://vznjs.github.io/vx/blog/dependency-rules/)
|
|
91
91
|
- **Cache controls** (`--no-cache`, `--force`, `--cache-policy`, `--cache-policy`, `VX_CACHE_POLICY`, `cache.policy`) — off, re-run past it, or per-layer read/write. [page](https://vznjs.github.io/vx/features/cache-controls/) · post: [The cache on your terms](https://vznjs.github.io/vx/blog/the-cache-on-your-terms/)
|
|
92
92
|
- **Cache location** (`--cache-dir`, `cache.dir`, `VX_CACHE_DIR`) — where the cache lives. [page](https://vznjs.github.io/vx/features/cache-location/) · post: [The cache on your terms](https://vznjs.github.io/vx/blog/the-cache-on-your-terms/)
|
|
93
93
|
- **Cache scope** (`cache.scope`, `VX_CACHE_SCOPE`, `--cache-scope`) — a scope reads the default entries, then its own, and writes only its own. [page](https://vznjs.github.io/vx/features/cache-scope/) · post: [The cache on your terms](https://vznjs.github.io/vx/blog/the-cache-on-your-terms/)
|
|
94
94
|
- **Cache pruning** (`vx cache clean`, `--older-than`, `--max-size`, `--global`, `--dry-run`, `cache.retention`, `maxSize`, `olderThan`, `VX_CACHE_RETENTION_MAX_SIZE`, `VX_CACHE_RETENTION_OLDER_THAN`) — evict by age or size, LRU; with no bound, clear this repo's store (shared by its checkouts) and run history; `--global` clears every repo's store. [page](https://vznjs.github.io/vx/features/cache-prune/) · post: [One command to know your workspace](https://vznjs.github.io/vx/blog/know-your-workspace/)
|
|
95
95
|
- **Remote outputs** (`--cache-download`, `cache.download`, `VX_CACHE_DOWNLOAD`) — all, top-level only, or none. [page](https://vznjs.github.io/vx/features/remote-downloads/) · post: [A remote server you can trust in production](https://vznjs.github.io/vx/blog/reapi-in-production/)
|
|
96
|
+
- **Failure uploads** (`cache.upload.failures`) — which runs keep a failed task's log and outputs in the remote: CI (default), local or always. no post
|
|
96
97
|
- **One store for every checkout** (`~/.vx/<id>/cache`) — clones and worktrees of a repo share cache entries. [page](https://vznjs.github.io/vx/features/shared-store/) · post: [The cache on your terms](https://vznjs.github.io/vx/blog/the-cache-on-your-terms/)
|
|
97
98
|
- **Warm hits restore nothing** — when outputs on disk already match, a hit costs a few stats. [page](https://vznjs.github.io/vx/features/warm-hits/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
|
|
98
99
|
- **Hits replay both streams** — stdout and stderr come back in the order the run printed them. [page](https://vznjs.github.io/vx/features/both-streams/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
|
|
@@ -120,7 +121,7 @@ config key or an environment variable is missing here.
|
|
|
120
121
|
- **Interactive tasks** (`exec.interactive`) — a task that owns the terminal. [page](https://vznjs.github.io/vx/features/interactive-tasks/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
|
|
121
122
|
- **Shell completions** (`vx completions`) — bash, zsh, fish. [page](https://vznjs.github.io/vx/features/completions/) · post: [The small things](https://vznjs.github.io/vx/blog/the-small-things/)
|
|
122
123
|
- **Project-local vx** (`VX_HANDED_OFF`) — a global vx hands every call to the project's own `@vzn/vx`. [page](https://vznjs.github.io/vx/cli/#which-vx-runs)
|
|
123
|
-
- **vx upgrade** (`vx upgrade`) — upgrade vx the way it was installed: a verified binary, or its package manager. Inside a project, the project's vx and the global vx that ran it move together. [page](https://vznjs.github.io/vx/features/upgrade/) · post: [An upgrade you can trust](https://vznjs.github.io/vx/blog/upgrade-you-can-trust/)
|
|
124
|
+
- **vx upgrade** (`vx upgrade`) — upgrade vx the way it was installed: a verified binary, or its package manager. Inside a project, the project's vx, its `@vzn/vx-*` plugins (one install, one release) and the global vx that ran it move together. [page](https://vznjs.github.io/vx/features/upgrade/) · post: [An upgrade you can trust](https://vznjs.github.io/vx/blog/upgrade-you-can-trust/)
|
|
124
125
|
- **Help and version** (`vx help`, `vx version`) — every verb's reference. [page](https://vznjs.github.io/vx/features/help-and-version/) · post: [An upgrade you can trust](https://vznjs.github.io/vx/blog/upgrade-you-can-trust/)
|
|
125
126
|
- **Did-you-mean** — a mistyped flag or verb gets the nearest valid spelling. [page](https://vznjs.github.io/vx/features/did-you-mean/) · post: [The small things](https://vznjs.github.io/vx/blog/the-small-things/)
|
|
126
127
|
- **Task typed as a verb** — `vx build app` answers with the exact `vx run` command that does it. [page](https://vznjs.github.io/vx/features/task-as-verb/) · post: [The small things](https://vznjs.github.io/vx/blog/the-small-things/)
|
|
@@ -144,7 +145,7 @@ config key or an environment variable is missing here.
|
|
|
144
145
|
|
|
145
146
|
## Adoption
|
|
146
147
|
|
|
147
|
-
- **Start in a minute** (`vx init`, `--dry`, `--format json`, `--force`, `--mjs`) — write `.vx/workspace.ts`, one `vx.config.ts` per package (through `@vzn/vx-migrate
|
|
148
|
+
- **Start in a minute** (`vx init`, `--dry`, `--format json`, `--force`, `--mjs`) — write `.vx/workspace.ts`, one `vx.config.ts` per package (through the latest `@vzn/vx-migrate`, which installs the latest `@vzn/vx` and moves every listed `@vzn/vx-*` plugin with it; with no `package.json`, one empty root project) and `.vx/projects.lock.json`; `--dry --format json` hands an agent the plan as data. [page](https://vznjs.github.io/vx/features/quickstart/) · post: [Hello, vx](https://vznjs.github.io/vx/blog/hello-vx/)
|
|
148
149
|
- **Migrate from Turborepo or Nx** (`vx init`, `--native`, `--keep`, `@vzn/vx-migrate`) — native config, or keep `turbo()` / `nx()` as a start. [page](https://vznjs.github.io/vx/features/migrate/) · posts: [From Turborepo](https://vznjs.github.io/vx/blog/from-turborepo/), [From Nx](https://vznjs.github.io/vx/blog/from-nx/)
|
|
149
150
|
- **Keep a Turbo or Nx remote cache** (`turboCache()`, `nxCache()`) — reuse the cache server you have. [page](https://vznjs.github.io/vx/features/keep-remote-cache/) · post: [From Nx: keep the graph, drop the platform](https://vznjs.github.io/vx/blog/from-nx/)
|
|
150
151
|
- **One binary** — one file, nothing to install underneath. [page](https://vznjs.github.io/vx/features/one-binary/) · post: [One binary](https://vznjs.github.io/vx/blog/one-binary/)
|
|
@@ -174,4 +175,4 @@ config key or an environment variable is missing here.
|
|
|
174
175
|
- **OTel live export** (`otel({ live })`) — spans and metrics stream as tasks end, so a dashboard follows a CI run live. [page](https://vznjs.github.io/vx/features/otel-live/) · post: [Watch a CI run while it runs](https://vznjs.github.io/vx/blog/otel-live/)
|
|
175
176
|
- **Memory-aware admission** (`@vzn/vx-schedule-history`) — tasks are packed by the peak memory learned from past runs. [page](https://vznjs.github.io/vx/features/memory-admission/) · post: [A scheduler that learns from your runs](https://vznjs.github.io/vx/blog/a-scheduler-that-learns/)
|
|
176
177
|
- **CI timings file** (`@vzn/vx-schedule-history`, `file`) — one JSON file of learned task times a CI cache carries between runners, so a fresh runner orders by real times. [page](https://vznjs.github.io/vx/features/critical-path/) · post: [Carry the scheduler's memory between CI runners](https://vznjs.github.io/vx/blog/ci-timings-file/)
|
|
177
|
-
- **Shared timings** (`@vzn/vx-schedule-history`, `defaultBranch`) — CI on the default branch writes task timings to the remote cache; a branch with none imports its merge-base's. Plugins keep their own entries there through `ctx.cache`. [page](https://vznjs.github.io/vx/features/critical-path/)
|
|
178
|
+
- **Shared timings** (`@vzn/vx-schedule-history`, `defaultBranch`) — CI on the default branch writes task timings to the remote cache; a branch with none imports its merge-base's without waiting for it, re-ranking the tasks not yet started when they land. Plugins keep their own entries there through `ctx.cache`. [page](https://vznjs.github.io/vx/features/critical-path/)
|
package/docs/patterns.md
CHANGED
|
@@ -44,7 +44,7 @@ package-manager parts come from a plugin.
|
|
|
44
44
|
Core keys no package file (2026-10-14): the lockfile and each
|
|
45
45
|
project's `package.json` key a task through `@vzn/vx-lockfile`, which
|
|
46
46
|
claims the lockfile and adds `package.json` to every cached task's
|
|
47
|
-
inputs, so narrow `
|
|
47
|
+
inputs, so narrow `inputs.files` like `['src/**']` doesn't miss a
|
|
48
48
|
`package.json` dep bump.
|
|
49
49
|
|
|
50
50
|
The two **divergences** from Turbo's recipe — TypeScript-resolved
|