@vzn/vx 0.0.639 → 0.0.641

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +1 -1
  2. package/docs/caching.md +158 -106
  3. package/docs/cli.md +80 -45
  4. package/docs/execution.md +11 -10
  5. package/docs/features.md +9 -8
  6. package/docs/patterns.md +1 -1
  7. package/docs/schema.md +252 -190
  8. package/docs/security.md +5 -4
  9. package/launcher.sh +9 -2
  10. package/package.json +5 -5
  11. package/schemas/show.json +14 -14
  12. package/skills/vx/SKILL.md +4 -3
  13. package/src/bin.ts +8 -6
  14. package/src/cache/archive.ts +248 -41
  15. package/src/cache/cache.ts +46 -19
  16. package/src/cache/file-hashes.ts +7 -12
  17. package/src/cache/fs-clock.ts +62 -0
  18. package/src/cache/git-inputs.ts +2 -2
  19. package/src/cache/index.ts +6 -0
  20. package/src/cache/inputs.ts +228 -111
  21. package/src/cache/key-fold.ts +23 -3
  22. package/src/cache/layer.ts +15 -8
  23. package/src/cache/layered-cache.ts +8 -7
  24. package/src/cache/linked-inputs.ts +87 -0
  25. package/src/cache/scoped-cache.ts +8 -2
  26. package/src/cli/foreign-flags.ts +1 -1
  27. package/src/cli/help.ts +7 -1
  28. package/src/cli/init.ts +23 -11
  29. package/src/cli/last.ts +24 -1
  30. package/src/cli/plugin-templates.ts +2 -2
  31. package/src/cli/select.ts +16 -6
  32. package/src/cli/show.ts +9 -6
  33. package/src/cli/upgrade.ts +132 -21
  34. package/src/cli/watch-filter.ts +23 -12
  35. package/src/cli/watch-fs.ts +2 -1
  36. package/src/cli/watch-judge.ts +49 -5
  37. package/src/cli/watch-set.ts +18 -8
  38. package/src/cli/watch.ts +74 -2
  39. package/src/cli/why.ts +5 -3
  40. package/src/config.ts +19 -15
  41. package/src/exec/executor.ts +4 -4
  42. package/src/graph/dependency-spec.ts +2 -2
  43. package/src/graph/folds.ts +236 -0
  44. package/src/graph/index.ts +8 -1
  45. package/src/graph/scheduler.ts +41 -15
  46. package/src/graph/task-graph.ts +135 -73
  47. package/src/handoff.ts +5 -0
  48. package/src/index.ts +2 -2
  49. package/src/orchestrator/affected-tasks.ts +20 -19
  50. package/src/orchestrator/download-policy.ts +16 -22
  51. package/src/orchestrator/excluded-keys.ts +4 -5
  52. package/src/orchestrator/execute-task.ts +29 -25
  53. package/src/orchestrator/hit-restore.ts +101 -27
  54. package/src/orchestrator/index.ts +1 -0
  55. package/src/orchestrator/keyed-projects.ts +12 -51
  56. package/src/orchestrator/local-shortcircuit.ts +9 -1
  57. package/src/orchestrator/persistent-task.ts +3 -3
  58. package/src/orchestrator/placement.ts +2 -2
  59. package/src/orchestrator/plan.ts +1 -1
  60. package/src/orchestrator/plugin-host.ts +66 -27
  61. package/src/orchestrator/plugin.ts +12 -2
  62. package/src/orchestrator/prepare.ts +12 -5
  63. package/src/orchestrator/projects.ts +7 -10
  64. package/src/orchestrator/run-report.ts +145 -46
  65. package/src/orchestrator/run-steps.ts +57 -0
  66. package/src/orchestrator/run.ts +76 -44
  67. package/src/orchestrator/sandbox-fix.ts +1 -1
  68. package/src/orchestrator/sandbox-request.ts +2 -2
  69. package/src/orchestrator/stable-keys.ts +59 -26
  70. package/src/orchestrator/steps.ts +26 -6
  71. package/src/orchestrator/task-hash.ts +31 -9
  72. package/src/orchestrator/telemetry-host.ts +3 -3
  73. package/src/orchestrator/telemetry.ts +9 -4
  74. package/src/orchestrator/upstream.ts +4 -181
  75. package/src/report/framed-output.ts +1 -1
  76. package/src/report/logger.ts +5 -2
  77. package/src/report/status-line.ts +7 -3
  78. package/src/report/summary.ts +6 -4
  79. package/src/settings.ts +11 -17
  80. package/src/util/index.ts +3 -0
  81. package/src/util/paths.ts +39 -5
  82. package/src/workspace/affected.ts +81 -2
  83. package/src/workspace/config-eval.ts +2 -2
  84. package/src/workspace/config-imports.ts +1 -1
  85. package/src/workspace/config-schema.ts +142 -143
  86. package/src/workspace/lockfile.ts +1 -1
  87. package/src/workspace/migration.ts +19 -3
  88. package/src/workspace/project-loader.ts +5 -4
package/docs/cli.md CHANGED
@@ -37,7 +37,7 @@ vx show [PROJECT[#TASK] | TASK] [--filter <pattern>] [--affected[=<ref>]] [--for
37
37
  vx info [--format pretty|json] [--cache-dir <path>]
38
38
  vx why (TASK | PKG#TASK) [--run RUNID] [--format pretty|json] [--cache-dir <path>]
39
39
  vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]
40
- vx upgrade [TAG] # upgrade vx the way it was installed
40
+ vx upgrade [TAG] [-g|--global]
41
41
  vx docs <query> [--limit N] [--format pretty|json]
42
42
  vx completions bash|zsh|fish
43
43
 
@@ -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 cached task of its declares
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
- - an uncached task when a changed path lies in its project or the
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, as rows of
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), `vx:prefetch`, `vx:schedule`, then after the graph
773
- `vx:uploads`, `vx:history`, `vx:keep-run`, `vx:telemetry`,
774
- `<plugin>:teardown`, `vx:snapshots`, `vx:retention`, `vx:close` and
775
- `vx:sandbox`. A step shows once it has run 150 ms, ticking like a
776
- task (` 1.2s running vx:uploads`); no worker slot or
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, with no cache column (`⏺︎ 1.2s success vx:uploads`);
779
- a quicker one shows nowhere. `:` is no task id's separator, so a
780
- step never reads as a task: a project named `vx` keeps `vx#build`.
781
- No step is a task: no count, bar, history row, forecast, flaky
782
- verdict, `vx last` row or telemetry record holds one; `--profile`
783
- and `VX_DEBUG_TIMING` time vx's stages their own way. Steps show only
784
- beside a live region; a log (CI, a pipe) prints none.
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 `cache.inputs`
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
- `cache.inputs.runtime` / `workspaceRuntime` command defers **nothing**:
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: an uncached task, or a changed
1013
- `file` no cached task of the project declares, often an output git does not
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
- cache: { inputs: { files: ['src/**'] }, outputs: { files: ['dist/**'] } },
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 `cache.inputs.files` reads that directory: then it
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, as the key folds it, so a
1209
- retarget is an edit; a path the loop has never judged is an edit only if it was
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
@@ -1228,8 +1243,7 @@ run...` precedes it.
1228
1243
  which reads what it likes, item 947, outside any project nested
1229
1244
  in it; a write the last cycle made is not, read off the later of
1230
1245
  its mtime and ctime, so an output a cache hit restores with its
1231
- saved mtime is the run's, and so is the restore's own `.vx-tmp-*`
1232
- file, `watch-restored-output.test.ts`) — or declared an
1246
+ saved mtime is the run's, `watch-restored-output.test.ts`) — or declared an
1233
1247
  output, or the loop re-runs on it; after three such cycles in a row
1234
1248
  watch names the path and the remedy, once, and keeps going. A dev
1235
1249
  server the last cycle left running counts as that cycle for as long
@@ -1237,7 +1251,7 @@ run...` precedes it.
1237
1251
  `.gitignore` as the remedy (a persistent task declares no outputs;
1238
1252
  item 948). A file git tracks is never blamed on the server: three
1239
1253
  saves of a source file are the user's (WD-7). When any project's config declares
1240
- `cache.inputs.workspaceFiles`, the per-project watchers are swapped
1254
+ `inputs.workspaceFiles`, the per-project watchers are swapped
1241
1255
  for ONE recursive root watcher (boundaries are off for those globs,
1242
1256
  so a root-relative glob can name a file anywhere). That watcher
1243
1257
  hears every write in the tree and keeps only the ones a key can
@@ -1285,19 +1299,20 @@ Always ignored (no re-trigger):
1285
1299
 
1286
1300
  - `node_modules/`, `.git/`, `.vx/cache/` anywhere in the path.
1287
1301
  - A path git ignores (`git check-ignore`, asked once per debounce
1288
- window, never per event): invisible to every cache key — inputs are
1302
+ window, never per event), unless a task's inputs name it outright
1303
+ (`.env.local`, `dist/**`): invisible to every other cache key — inputs are
1289
1304
  tracked + untracked-not-ignored — so a cycle it started could change
1290
1305
  nothing. A tracked file that matches a pattern is not ignored, by
1291
1306
  git's own rule; outside a repository nothing is.
1292
- - Files ending in `~` (editor swap files), and a restore's own
1293
- `.vx-tmp-*` file.
1307
+ - Files ending in `~` (editor swap files), and `.vx/tmp/`, where
1308
+ restores stage.
1294
1309
  - The run's **resolved cache directory**, wherever it is. `.vx/cache/` covers the
1295
1310
  default, but `defineWorkspace({ cache: { dir } })` and `--cache-dir` can put it
1296
1311
  anywhere; watching it would let vx's own cache writes trigger the next
1297
1312
  cycle, which writes again — a loop that never settles.
1298
1313
 
1299
1314
  Everything else triggers a cycle. We deliberately don't filter events
1300
- against per-task `cache.inputs.files` — the cache hash is the source
1315
+ against per-task `inputs.files` — the cache hash is the source
1301
1316
  of truth. A change to an irrelevant file produces a cache-hit run
1302
1317
  (typically tens of ms); the cost is much smaller than the engineering
1303
1318
  cost of a per-event glob match.
@@ -1863,8 +1878,16 @@ manager is its lockfile (`pnpm-lock.yaml`, `bun.lock`, `yarn.lock`,
1863
1878
  `package-lock.json`), else `packageManager`, else npm; the field it is
1864
1879
  listed in is kept (`--save-dev`, `--dev`, …), and pnpm at a workspace
1865
1880
  root adds `--workspace-root`. The lockfile is updated by that manager.
1866
- vx prints what it runs, runs it there, and exits with an error if it
1867
- fails:
1881
+ Every `@vzn/vx-*` plugin the same package.json lists from the registry
1882
+ moves in the same install, to the same release, since every @vzn
1883
+ package releases together: `pnpm add @vzn/vx@0.0.2 @vzn/vx-otel@0.0.2
1884
+ --save-dev`. A plugin left behind is installed even when core is
1885
+ already at the release (`› vx upgrade plugins → 0.0.2`); a plugin in
1886
+ another field than core is refused, so no install moves a dependency's
1887
+ field. The versions are pinned, so pnpm's `minimumReleaseAge` refuses a
1888
+ release too young rather than mixing releases, and the error names
1889
+ `minimumReleaseAgeExclude`. vx prints what it runs, runs it there, and
1890
+ exits with an error if it fails:
1868
1891
 
1869
1892
  ```
1870
1893
  › vx upgrade 0.0.1 → 0.0.2
@@ -1882,6 +1905,12 @@ project that fails leaves the global as it was. The project's vx run
1882
1905
  directly (a package script, `node_modules/.bin/vx`) has no global behind it, so only
1883
1906
  the project moves.
1884
1907
 
1908
+ **`vx upgrade -g`** (`--global`) moves only the global vx, wherever it
1909
+ runs: it never hands the call to a project's vx, and it never touches a
1910
+ `package.json` or a plugin. Run as the project's own vx, it refuses:
1911
+ `vx upgrade -g: no global vx ran — <path> is the project's own. Install
1912
+ one (npm install -g @vzn/vx), or run \`vx upgrade\` to move the project's`.
1913
+
1885
1914
  ```
1886
1915
  › vx upgrade project 0.0.1 → 0.0.2
1887
1916
 
@@ -1910,9 +1939,11 @@ or is missing.
1910
1939
  runs in, or one above it up to the git top-level, holds a
1911
1940
  `package.json`, `pnpm-workspace.yaml`, `turbo.json`, `turbo.jsonc` or
1912
1941
  `nx.json`, it runs `@vzn/vx-migrate` there, so `vx init` is the one
1913
- command (owner, 2026-10-08): the installed one (the nearest
1914
- `node_modules/@vzn/vx-migrate` up from the working directory), else
1915
- this vx's version through `bun x` (`BUN_BE_BUN=1` makes the compiled
1942
+ command (owner, 2026-10-08): the latest release through `bun x`,
1943
+ never an installed one, so init always installs the latest `@vzn/vx`
1944
+ and moves every `@vzn/vx-*` plugin the root lists to it (M,
1945
+ 2026-10-16; a source checkout runs the nearest installed
1946
+ `node_modules/@vzn/vx-migrate` instead) (`BUN_BE_BUN=1` makes the compiled
1916
1947
  binary that runtime). `--dry`, `--format`, `--force`, `--mjs`,
1917
1948
  `--native` and `--keep` pass through, and the exit is vx-migrate's.
1918
1949
  vx-migrate maps a Turbo, Nx or Vite Task repo, else the `package.json`
@@ -2059,7 +2090,7 @@ copy to the source.
2059
2090
  | turbo | `--dangerously-disable-package-manager-check` | refuse | vx reads no `packageManager` field: drop it |
2060
2091
  | turbo | `--env-mode <v>` | refuse | vx passes only the variables a task declares (strict): list the rest in `exec.env.passThrough` |
2061
2092
  | 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 `cache.inputs.workspaceFiles` (under `turbo()`, turbo.json's `globalDependencies`) |
2093
+ | turbo | `--global-deps <v>` | refuse | declare them in `inputs.workspaceFiles` (under `turbo()`, turbo.json's `globalDependencies`) |
2063
2094
  | turbo | `--json` | refuse | use `vx last --format json` for the run's result, `--dry=json` for the plan |
2064
2095
  | turbo | `--log-file` | refuse | use `vx last --format json` for the run's JSON record |
2065
2096
  | turbo | `--preflight` | refuse | set `turboCache({ preflight: true })` or `TURBO_PREFLIGHT=1` |
@@ -2251,7 +2282,7 @@ its own output path, or make one depend on the other.
2251
2282
 
2252
2283
  #### `VX_E_INPUT_IS_OUTPUT`
2253
2284
 
2254
- A task's `cache.inputs` match outputs: its own (vx cleans outputs before
2285
+ A task's `inputs` match outputs: its own (vx cleans outputs before
2255
2286
  a run, so it would delete its sources) or another task's (its key must
2256
2287
  not read them; `dependsOn` already cascades). Exclude the outputs from
2257
2288
  the inputs with a `!` glob, as the message shows, or declare no outputs
@@ -2587,7 +2618,7 @@ app#build — run 01a1193a-7b39-75a9-870b-5c03e26d7104
2587
2618
  changed file packages/app/src/input.txt 78981922613b2afb6025042ff6bd878ac1994e85 → 61780798228d17af2d34fce4cfbdf35556832472
2588
2619
 
2589
2620
  what to do:
2590
- file an edit re-runs by design; a file the task does not read belongs out of cache.inputs.files
2621
+ file an edit re-runs by design; a file the task does not read belongs out of inputs.files
2591
2622
  ```
2592
2623
 
2593
2624
  A changed key's verdict names what moved, kind and name, three at most
@@ -2628,7 +2659,7 @@ A hit's line is `cache-hit · key …` (or `cache-hit-remote`): the status
2628
2659
  names the hit and its tier, so only an executed run carries the word.
2629
2660
  A skipped task never ran and derived no key: its line is `skipped · no key`.
2630
2661
  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
2662
+ id), `linked` (the content behind a `file` that is a symlink), `env` (a declared variable, by digest), `runtime` and
2632
2663
  `ws-runtime` (a declared command's output, `inputs.runtime` and
2633
2664
  `inputs.workspaceRuntime`), `forward` (the argv forwarded after `--`),
2634
2665
  `config` (the evaluated task config), `upstream` (a dependency's input
@@ -2643,9 +2674,9 @@ is gone (pruned, or the run failed and never saved one) the verb still
2643
2674
  names the hash change and says the component diff is unavailable. When
2644
2675
  only the previous run failed, the diff is taken against the last run
2645
2676
  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 dependents
2647
- fold — but saves no entry, so for it the verb can only report the key
2648
- change and says so.
2677
+ task with `inputs` and no `cache` block derives a key too — it is what
2678
+ dependents fold — but saves no entry, so for it the verb can only report
2679
+ the key change and says so.
2649
2680
  `--format json` emits one machine-readable object (`{ taskId, runId,
2650
2681
  why, diff, roots, writers }`); when the task's recorded runs predate run ids, `{ taskId,
2651
2682
  why, diff, explanation }`, `why` and `diff` null and `explanation` the
@@ -2687,6 +2718,11 @@ their count (`… +980 more cache hits`) — a thousand-task warm run is a
2687
2718
  thousand rows otherwise, with the one failure a screen above the
2688
2719
  prompt — and the sixteen shown are the slowest restores, the one thing
2689
2720
  a hit's row tells. `--format json` lists every row.
2721
+ Below the tasks, the steps the run did not wait on (`vx:prefetch`, a
2722
+ remote's downloads beside the tasks), which its own output never shows:
2723
+ `success vx:prefetch 1.20s internal`. They are kept per run, the
2724
+ newest 50, in `<cache dir>/steps/<run id>.json`, written only by a run
2725
+ that had one; `--format json` adds them as `steps`.
2690
2726
  `vx last --list` prints the N most recent runs (default 10), one per
2691
2727
  line in aligned columns (verdict, start, run id, counts, duration,
2692
2728
  command), each run id cut to the shortest prefix no other run shares, 13 characters at least (`--list 5`
@@ -2708,7 +2744,7 @@ sandboxed task's row adds `sandboxFix`, the `exec.sandbox.allow`
2708
2744
  entries (`read`, `write`, `network`, `systemInfo`) that would have
2709
2745
  granted its denials, the same grants the terminal prints as `fix:`;
2710
2746
  `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 `cache.inputs.files`
2747
+ a cached task, `inputs` the reads of its project no `inputs.files`
2712
2748
  glob matches (the terminal's `and, if the output depends on them, …`
2713
2749
  line). A task
2714
2750
  failed by `inputStability.severity: 'error'` adds `inputMoved`, the
@@ -2894,7 +2930,6 @@ what a TASK sees is `exec.env` in `docs/schema.md`.
2894
2930
  | `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
2931
  | `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
2932
  | `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
2933
  | `VX_CACHE_DIR` | [`cache.dir`](schema.md#settings) | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
2899
2934
  | `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
2935
  | `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 cache.inputs.runtime probe, C-65), wait
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
@@ -174,12 +174,13 @@ terminal and a task succeeding or failing. Read it alongside
174
174
  │ overlap is the point); drained before cache.close(). At most
175
175
  │ one remote GET per key (shared in-flight map with the lazy
176
176
  │ read-through).
177
- │ • LOCAL SHORT-CIRCUIT (no remote layer, local reads on, ≥1
178
- │ task) — derive the same stable keys and probe them in ONE
179
- │ batched cache.getMany (a layer without getMany: cache.get once
180
- │ per task under a bounded pool) → a `preProbed` map (hits AND
181
- │ stable misses; execute reuses these probes) + a `restoreTier`
182
- │ set (confirmed hits). Awaited before scheduling; never throws
177
+ │ • LOCAL SHORT-CIRCUIT (local reads on, ≥1 task) — derive the
178
+ │ same stable keys and probe the LOCAL store in ONE batched
179
+ │ getMany (a layer without getMany: get once per task under a
180
+ │ bounded pool) → a `preProbed` map (hits AND stable misses;
181
+ │ with a remote declared, hits only: a local miss may be a
182
+ │ remote hit) + a `restoreTier` set (confirmed hits). Awaited
183
+ │ before scheduling; never asks a remote; never throws
183
184
  │ (degrades to the normal schedule).
184
185
  │
185
186
  ├─ Scheduling (src/graph/scheduler.ts:runGraph — two-tier)
@@ -243,7 +244,7 @@ terminal and a task succeeding or failing. Read it alongside
243
244
  │ UserError that fails this task — its dependents skip,
244
245
  │ independent tasks continue)
245
246
  │ 2. hashTaskConfig (memoized per run via HashCache)
246
- │ 3. filterUpstreamHashes (apply cache.inputs.tasks filter)
247
+ │ 3. filterUpstreamHashes (apply inputs.tasks filter)
247
248
  │ 4. cache.key({ taskId, taskConfigHash,
248
249
  │ forwardArgs, envValues, runtimeValues,
249
250
  │ workspaceRuntimeValues, upstreamHashes,
@@ -430,7 +431,7 @@ broader access has cache-stability implications).
430
431
  | Two projects of one name | `checkProjectNames` throws with both paths |
431
432
  | Cycle in task graph | `detectCycle` throws with the cycle path |
432
433
  | Malformed config | `loadProjectConfigs` throws (UserError) with file + field |
433
- | `cache.inputs.runtime` command exits non-zero | UserError naming the command + exit code; that task is `failed`, dependents skip |
434
+ | `inputs.runtime` command exits non-zero | UserError naming the command + exit code; that task is `failed`, dependents skip |
434
435
 
435
436
  By default (`--continue=deps-ok`) failures don't kill the scheduler —
436
437
  independent tasks already in flight finish, and unrelated tasks not yet
@@ -618,7 +619,7 @@ runs the same setup steps — workspace discovery, config load, package
618
619
  graph, task graph — and the same per-task hash derivation, then
619
620
  probes the cache. The probe is read-only; against a remote layer it
620
621
  is an existence check only (no artifact download, no ingest). The one
621
- deliberate side effect: `cache.inputs.runtime` / `workspaceRuntime`
622
+ deliberate side effect: `inputs.runtime` / `workspaceRuntime`
622
623
  probe commands DO run, because predicting a key requires resolving
623
624
  them — keep runtime inputs side-effect-free.
624
625
 
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** (`cache` 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/)
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** (`cache.inputs.files`) — a task caches only when it names its inputs. [page](https://vznjs.github.io/vx/features/explicit-inputs/) · post: [Explicit over magical](https://vznjs.github.io/vx/blog/explicit-over-magical/)
80
- - **Inputs** (`cache.inputs.env`, `cache.inputs.runtime`, `cache.inputs.tasks`, `cache.inputs.workspaceFiles`, `cache.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/)
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** (`run.rules.upfrontKeys`, `VX_RUN_RULES_UPFRONT_KEYS`) — refuses an input glob a same-project task's outputs could match, so every key is known before anything runs. [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/)
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; `vx upgrade -g` moves only the global. [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`; 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
+ - **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 `cache.inputs.files` like `['src/**']` doesn't miss a
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