@vzn/vx 0.0.637 → 0.0.638

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 (76) hide show
  1. package/docs/caching.md +41 -14
  2. package/docs/cli.md +115 -73
  3. package/docs/execution.md +1 -1
  4. package/docs/features.md +6 -5
  5. package/docs/schema.md +24 -15
  6. package/package.json +5 -5
  7. package/schemas/plan.json +2 -2
  8. package/skills/vx/SKILL.md +13 -2
  9. package/src/bin.ts +13 -7
  10. package/src/cache/cache.ts +21 -2
  11. package/src/cache/chained-cache.ts +4 -0
  12. package/src/cache/index.ts +9 -1
  13. package/src/cache/inputs.ts +0 -24
  14. package/src/cache/layer.ts +7 -1
  15. package/src/cache/layered-cache.ts +4 -0
  16. package/src/cache/plugin-cache.ts +248 -0
  17. package/src/cache/policy.ts +9 -0
  18. package/src/cache/run-history.ts +68 -0
  19. package/src/cache/run-store.ts +77 -0
  20. package/src/cache/schema.ts +38 -9
  21. package/src/cache/scoped-cache.ts +15 -2
  22. package/src/cli/cache.ts +33 -16
  23. package/src/cli/completions.ts +9 -5
  24. package/src/cli/help.ts +16 -7
  25. package/src/cli/index.ts +25 -11
  26. package/src/cli/info.ts +6 -5
  27. package/src/cli/init.ts +14 -15
  28. package/src/cli/last.ts +14 -7
  29. package/src/cli/lock.ts +39 -27
  30. package/src/cli/plan-format.ts +5 -3
  31. package/src/cli/plugin-commands.ts +77 -6
  32. package/src/cli/plugin-templates.ts +1 -1
  33. package/src/cli/run.ts +14 -12
  34. package/src/cli/select.ts +18 -9
  35. package/src/cli/show.ts +66 -40
  36. package/src/cli/task-verb.ts +4 -4
  37. package/src/cli/upgrade.ts +20 -11
  38. package/src/cli/watch-filter.ts +7 -2
  39. package/src/cli/watch-judge.ts +6 -1
  40. package/src/cli/watch.ts +44 -20
  41. package/src/cli/why.ts +10 -1
  42. package/src/cli/workspace-config.ts +2 -2
  43. package/src/graph/scheduler.ts +8 -0
  44. package/src/graph/task-graph.ts +32 -4
  45. package/src/index.ts +11 -1
  46. package/src/orchestrator/affected-tasks.ts +8 -3
  47. package/src/orchestrator/doctor.ts +1 -1
  48. package/src/orchestrator/execute-task.ts +26 -43
  49. package/src/orchestrator/failure-mode.ts +19 -10
  50. package/src/orchestrator/framed-output.ts +1 -2
  51. package/src/orchestrator/history.ts +60 -4
  52. package/src/orchestrator/index.ts +2 -1
  53. package/src/orchestrator/logger.ts +9 -2
  54. package/src/orchestrator/metrics.ts +7 -3
  55. package/src/orchestrator/plugin-host.ts +14 -4
  56. package/src/orchestrator/plugin.ts +80 -8
  57. package/src/orchestrator/prepare.ts +51 -7
  58. package/src/orchestrator/projects.ts +3 -0
  59. package/src/orchestrator/remote-prefetch.ts +9 -5
  60. package/src/orchestrator/run-records.ts +1 -1
  61. package/src/orchestrator/run.ts +126 -27
  62. package/src/orchestrator/status-line.ts +1 -1
  63. package/src/orchestrator/summary.ts +69 -44
  64. package/src/orchestrator/telemetry.ts +1 -1
  65. package/src/settings.ts +12 -1
  66. package/src/{orchestrator → util}/colors.ts +5 -0
  67. package/src/util/config-frame.ts +31 -2
  68. package/src/util/errors.ts +32 -0
  69. package/src/util/index.ts +8 -1
  70. package/src/util/refusal-notice.ts +111 -0
  71. package/src/util/report.ts +31 -0
  72. package/src/util/ui.ts +186 -0
  73. package/src/workspace/affected.ts +20 -23
  74. package/src/workspace/dependency-rules.ts +12 -4
  75. package/src/workspace/migration.ts +87 -91
  76. package/src/workspace/workspace.ts +5 -4
package/docs/caching.md CHANGED
@@ -839,10 +839,10 @@ Skipped when:
839
839
  on, so it DOES clean (the saved snapshot must be clean).
840
840
  - `cache.outputs.clean` is `false` (on the task, else the workspace's
841
841
  `cache.outputs.clean`): the command sees last run's outputs (tsc's
842
- `.tsbuildinfo`). The first attempt stamps what it finds instead;
843
- if the run leaves any old file unchanged, nothing is cached, so no
844
- entry holds a file this run did not write. A retry cleans as usual; a hit still
845
- cleans before its restore.
842
+ `.tsbuildinfo`). You opt out of the clean-tree guarantee: the save
843
+ stores every file the outputs match after the run, so a leftover from
844
+ an older build is cached and restored everywhere. A retry cleans as
845
+ usual; a hit still cleans before its restore.
846
846
 
847
847
  A declared output the process cannot remove (a `dist/` another user
848
848
  wrote, a read-only checkout) fails the task with `cannot remove declared
@@ -1192,7 +1192,8 @@ in `~/.vx/<id>/cache`, and each workspace keeps only its own index in
1192
1192
  its `.vx/cache`: every checkout of the repository hits what another
1193
1193
  saved (a second clone, a worktree), and none reads another's history.
1194
1194
  Task timings are shared too: `task_durations`, what a duration forecast
1195
- reads, so a new worktree has one on its first run.
1195
+ reads, so a new worktree has one on its first run. So are plugins' own
1196
+ entries (`plugin_blobs`, a plugin's `ctx.cache`).
1196
1197
  The id is Nx 23's: 16 hex of a sha256 of the remote (`origin`, then
1197
1198
  `upstream`, `base`, the first; `host/owner/repo` in lower case, so ssh
1198
1199
  and https agree) and the workspace's path in the repository; with no
@@ -1204,7 +1205,7 @@ owner-only; vx closes one of yours that others can read (chmod 700), and one oth
1204
1205
  ~/.vx/<id>/cache/ the shared store
1205
1206
  ├── store.db entries, entry_stdout, output_files,
1206
1207
  │ entry_inputs, task_durations,
1207
- │ store_meta; artifacts
1208
+ │ plugin_blobs, store_meta; artifacts
1208
1209
  │ of at most 32 KiB compressed (v33)
1209
1210
  └── <hash>.tar.zst the larger artifacts (below)
1210
1211
 
@@ -1469,9 +1470,9 @@ keep). The artifacts stay: each is indexed again from its own bytes
1469
1470
  when its task next asks for its key (below).
1470
1471
 
1471
1472
  ```sql
1472
- -- src/cache/schema.ts (SCHEMA_VERSION = 'v36', in cache.ts)
1473
+ -- src/cache/schema.ts (SCHEMA_VERSION = 'v38', in cache.ts)
1473
1474
  -- With a shared store, entries, entry_stdout, output_files, entry_inputs,
1474
- -- store_meta and task_durations live in its store.db, attached as `store`; the rest is
1475
+ -- store_meta, task_durations, task_outcomes, blob_sizes and plugin_blobs live in its store.db, attached as `store`; the rest is
1475
1476
  -- the workspace's cache.db. A named cache dir holds all of them.
1476
1477
 
1477
1478
  CREATE TABLE schema_meta (
@@ -1537,10 +1538,6 @@ CREATE TABLE runs (
1537
1538
  -- the history reader bounds its scan by rowid instead (see history.md).
1538
1539
  CREATE INDEX runs_started_at ON runs(started_at);
1539
1540
  CREATE INDEX runs_run_id ON runs(run_id);
1540
- -- The one keyed index, PARTIAL over failed rows: a green run's inserts only
1541
- -- evaluate its predicate, and the flakiness probe after a miss
1542
- -- (failure-mode.ts, "did this key ever fail?") reads a few leaves.
1543
- CREATE INDEX runs_failed ON runs(hash) WHERE status = 'failed';
1544
1541
 
1545
1542
  -- Every non-group, non-aborted outcome of a run gets a row, so
1546
1543
  -- `invocations.task_count` always equals `COUNT(*)` here for that run_id
@@ -1575,7 +1572,8 @@ CREATE TABLE file_hashes (
1575
1572
  -- The size of each index blob the enumeration checked against the
1576
1573
  -- worktree size git recorded (§ Cache key derivation, A-60): fixed for
1577
1574
  -- its OID, so a warm run asks git for none. Swept with file_hashes by
1578
- -- seen_at, the time the row was written.
1575
+ -- seen_at, the time the row was written. v38: in the store, so a worktree
1576
+ -- reads what another learned.
1579
1577
  CREATE TABLE blob_sizes (
1580
1578
  oid TEXT PRIMARY KEY,
1581
1579
  size INTEGER NOT NULL,
@@ -1636,7 +1634,7 @@ CREATE TABLE invocations (
1636
1634
  run_id TEXT PRIMARY KEY, -- UUIDv7, == runs.run_id
1637
1635
  command TEXT NOT NULL, -- full argv, e.g. "vx run build --all"
1638
1636
  requested_tasks TEXT NOT NULL, -- JSON string[] of options.tasks
1639
- cache_policy TEXT NOT NULL, -- compact flags, e.g. "lR,lW,rR,rW"
1637
+ cache_policy TEXT NOT NULL, -- the --cache-policy spec, e.g. "local:rw,remote:r"
1640
1638
  concurrency INTEGER NOT NULL,
1641
1639
  flow TEXT, -- 'focused' | 'broad' | NULL (programmatic)
1642
1640
  started_at INTEGER NOT NULL, -- ms-epoch
@@ -1696,6 +1694,8 @@ CREATE TABLE store_meta (
1696
1694
  -- v36: each executed success's duration, appended in the run's history
1697
1695
  -- transaction, in the store so every worktree forecasts from it.
1698
1696
  -- source: 'branch:<name>'; the forecast reads the newest five per task.
1697
+ -- v37: 'remote' rows are a default-branch commit's timings a plugin
1698
+ -- imported, one per (sha, task).
1699
1699
  CREATE TABLE task_durations (
1700
1700
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1701
1701
  project TEXT NOT NULL,
@@ -1706,6 +1706,33 @@ CREATE TABLE task_durations (
1706
1706
  recorded_at INTEGER NOT NULL
1707
1707
  );
1708
1708
  CREATE INDEX task_durations_task ON task_durations(project, task, source, id);
1709
+ CREATE UNIQUE INDEX task_durations_remote
1710
+ ON task_durations(sha, project, task) WHERE source = 'remote';
1711
+
1712
+ -- v38: the flaky record (failure-mode.ts), in the store so a key that
1713
+ -- passed in one worktree and failed in another is flaky in both. Every
1714
+ -- executed success or failure that names its key (keyed, cached, not run
1715
+ -- behind a failure), and a key's first hit: a later hit replays a pass on
1716
+ -- record, so a warm run on passed keys writes no row (one index probe,
1717
+ -- ~1 ms per 1,000 hits). Pruned by age with the run history.
1718
+ CREATE TABLE task_outcomes (
1719
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1720
+ project TEXT NOT NULL,
1721
+ task TEXT NOT NULL,
1722
+ hash TEXT NOT NULL,
1723
+ status TEXT NOT NULL,
1724
+ attempts INTEGER,
1725
+ recorded_at INTEGER NOT NULL
1726
+ );
1727
+ CREATE INDEX task_outcomes_hash ON task_outcomes(hash, status);
1728
+
1729
+ -- v37: a plugin's own entries (`ctx.cache`), by core's key for its
1730
+ -- name; written once, pruned by age with the run history.
1731
+ CREATE TABLE plugin_blobs (
1732
+ key TEXT PRIMARY KEY,
1733
+ bytes BLOB NOT NULL,
1734
+ at INTEGER NOT NULL
1735
+ );
1709
1736
 
1710
1737
  -- v33: an artifact of at most 32 KiB compressed, its exact <hash>.tar.zst
1711
1738
  -- bytes, written in the transaction that writes its entry rows. In the
package/docs/cli.md CHANGED
@@ -69,7 +69,8 @@ declare task(s): <name>.` on stderr, a run and `--dry` / `--graph` alike
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
71
71
  project is named zzz; projects: a, b.`, or `a's tasks: build, test.`
72
- (M-56),
72
+ (M-56), each followed by ``A run reads the lock: after editing a
73
+ vx.config, run `vx lock`.`` (a task just added is not in the lock yet),
73
74
  and with `Only projects outside the selection declare <name> — pass
74
75
  --all, or --filter to pick them.` when the run was scoped (the cwd's
75
76
  project, a `--filter`) and a project outside the scope declares it
@@ -999,7 +1000,9 @@ $ vx run build lint --affected=main --dry
999
1000
  `kind` is `input` (a changed `file` is a declared input), `project` (its
1000
1001
  project changed and the task reads it whole: an uncached task, or a changed
1001
1002
  `file` no cached task of the project declares, often an output git does not
1002
- ignore; no `file` for a lockfile claim or a config import),
1003
+ ignore; no `file` for a config import), `workspace` (a workspace-wide
1004
+ `file` changed: the workspace config, or a root file a plugin claims such
1005
+ as a lockfile, reached every task of the project),
1003
1006
  `package` (a `^` edge passes a changed `project` with no such task),
1004
1007
  `named` (`pkg#task` beside a bare task) or `selected` (another filter
1005
1008
  selected its project). `via` is the `dependsOn` chain, nearest first, to
@@ -1392,7 +1395,10 @@ run...` precedes it.
1392
1395
  heard from its nearest directory that exists and each one above it,
1393
1396
  and a watch whose
1394
1397
  directory was replaced is armed again (an OS watch holds the deleted
1395
- one; `watch-recreated-dirs.test.ts`). Under `--filter` or
1398
+ one; `watch-recreated-dirs.test.ts`). There, a project directory
1399
+ that is still the same one starts no cycle: a task writing at its
1400
+ top level moved its mtime, which macOS reports to the parent
1401
+ (`watch-project-dir-write.test.ts`). Under `--filter` or
1396
1402
  `--affected` the scope is the one resolved at start, and a new
1397
1403
  project joins it only as a dependency of it. A task's own declared outputs (`cache.outputs.files`,
1398
1404
  `outputs.workspaceFiles`; a plugin's `project` stage counts, as in
@@ -1432,7 +1438,10 @@ run...` precedes it.
1432
1438
  starts a cycle, since no cache key can see it (a user's edit to one
1433
1439
  still does in a project with a task that has a command and no cache,
1434
1440
  which reads what it likes, item 947, outside any project nested
1435
- in it) — or declared an
1441
+ in it; a write the last cycle made is not, read off the later of
1442
+ its mtime and ctime, so an output a cache hit restores with its
1443
+ saved mtime is the run's, and so is the restore's own `.vx-tmp-*`
1444
+ file, `watch-restored-output.test.ts`) — or declared an
1436
1445
  output, or the loop re-runs on it; after three such cycles in a row
1437
1446
  watch names the path and the remedy, once, and keeps going. A dev
1438
1447
  server the last cycle left running counts as that cycle for as long
@@ -1467,9 +1476,11 @@ watch events within 2000 ms; polling every 250 ms instead`.
1467
1476
  changed, then its task rows as `vx run` prints them, with no footer.
1468
1477
  The named path is the first changed one that still exists, so an
1469
1478
  editor's temporary file renamed away names nothing; a deletion names
1470
- the cycle when nothing else changed. A cycle that re-read the watched
1471
- set (a member came or went, the lock or a plugin changed) says the
1472
- `watching` line again. Errors and refusals go to stderr. The
1479
+ the cycle when nothing else changed. The `watching` line is said
1480
+ once: a cycle that re-reads the watched set (a member came or went,
1481
+ the lock or a plugin changed) says nothing more; `VX_DEBUG_WATCH=1`
1482
+ says each arm on stderr as `vx watch: armed N projects`. Errors and
1483
+ refusals go to stderr. The
1473
1484
  orchestrator is invoked again with the same options, save one: the
1474
1485
  `--affected` diff is read once, for the initial run. A later cycle
1475
1486
  runs the requested task in every project of the scope fixed at start,
@@ -1490,7 +1501,8 @@ Always ignored (no re-trigger):
1490
1501
  tracked + untracked-not-ignored — so a cycle it started could change
1491
1502
  nothing. A tracked file that matches a pattern is not ignored, by
1492
1503
  git's own rule; outside a repository nothing is.
1493
- - Files ending in `.tsbuildinfo` or `~` (editor swap files).
1504
+ - Files ending in `~` (editor swap files), and a restore's own
1505
+ `.vx-tmp-*` file.
1494
1506
  - The run's **resolved cache directory**, wherever it is. `.vx/cache/` covers the
1495
1507
  default, but `defineWorkspace({ cache: { dir } })` and `--cache-dir` can put it
1496
1508
  anywhere; watching it would let vx's own cache writes trigger the next
@@ -1661,16 +1673,19 @@ bound clears the whole cache when that is really what you want.
1661
1673
 
1662
1674
  ```
1663
1675
  $ vx cache clean --older-than 30d
1664
- Pruned 42 entries (1.3 GB freed)
1676
+ ✓ vx cache clean freed 1.3 GB
1665
1677
 
1666
- $ vx cache clean --older-than 7d --max-size 500M
1667
- Pruned 18 entries (320 MB freed)
1678
+ 42 entries
1668
1679
 
1669
1680
  $ vx cache clean --older-than 30d # after a SCHEMA_VERSION bump
1670
- Pruned 0 entries (0 B freed), reaped 42 orphaned artifacts (1.3 GB)
1681
+ ✓ vx cache clean freed 1.3 GB
1682
+
1683
+ 42 orphaned artifacts
1671
1684
 
1672
1685
  $ vx cache clean --older-than 30d --dry-run
1673
- Would prune 42 entries (1.3 GB), would reap 3 orphaned artifacts (12 MB)
1686
+ › vx cache clean would free 1.3 GB (dry run)
1687
+
1688
+ 42 entries · 3 orphaned artifacts
1674
1689
  ```
1675
1690
 
1676
1691
  `--dry-run` picks the victims under the same policy and counts the
@@ -1694,8 +1709,8 @@ artifact that vanishes before its restore is a miss, and the task runs
1694
1709
  Exit codes:
1695
1710
 
1696
1711
  - `0` — pruning completed (zero or more entries evicted). A workspace
1697
- that never ran has no cache: `Pruned 0 entries (0 B freed)`, and
1698
- nothing is created.
1712
+ that never ran has no cache: `› vx cache clean nothing to free`,
1713
+ and nothing is created.
1699
1714
  - `1` — parse error, missing policy, or workspace-discovery error.
1700
1715
 
1701
1716
  `vx cache clean` resolves the workspace root from cwd and honors a
@@ -1723,17 +1738,19 @@ and the directory goes only if that empties it. A directory with no
1723
1738
  names goes only if it is one (`.vx/<id>/cache` holding a `store.db`): the
1724
1739
  index is a file in the checkout, which a commit or a restored CI cache
1725
1740
  could point at any directory. A workspace that never ran says
1726
- `Nothing to clear: no cache here`. The next run misses every task once
1741
+ `› vx cache clean nothing to free`. The next run misses every task once
1727
1742
  and saves again.
1728
1743
 
1729
1744
  ```
1730
1745
  $ vx cache clean
1731
- Cleared this repository's store, shared by its checkouts, and this workspace's run history (1.4 GB freed):
1746
+ ✓ vx cache clean freed 1.4 GB
1747
+
1732
1748
  .vx/cache
1733
1749
  /home/me/.vx/3f2a9c01b4d5e6f7/cache
1734
1750
 
1735
1751
  $ vx cache clean --global --dry-run
1736
- Would clear every repository's store and this workspace's run history (3.1 GB):
1752
+ › vx cache clean would free 3.1 GB (dry run)
1753
+
1737
1754
  .vx/cache
1738
1755
  /home/me/.vx/3f2a9c01b4d5e6f7/cache
1739
1756
  /home/me/.vx/9b8e7d6c5a4f3e2d/cache
@@ -1775,8 +1792,8 @@ add directories (X-143). Before writing, it
1775
1792
  builds the graph of every task the workspace declares, `graph` stage
1776
1793
  included, and checks the graph rules (`run.rules`), which no run
1777
1794
  checks, `dependencies` (which projects a project may depend on,
1778
- `VX_E_DEPENDENCY_RULE`) among them: a broken one prints `vx lock: .vx/projects.lock.json not written:
1779
- <the rule's refusal>` and writes nothing.
1795
+ `VX_E_DEPENDENCY_RULE`) among them: a broken one prints `✗ vx lock lock not written`
1796
+ with the rule's refusal under it and writes nothing.
1780
1797
 
1781
1798
  ```
1782
1799
  vx lock # Evaluate all vx.config.* now; write .vx/projects.lock.json.
@@ -1830,7 +1847,7 @@ A project with no `vx.config.*` — its tasks from a plugin's `project`
1830
1847
  stage (`turbo()` from `@vzn/vx-migrate`), or none at all — has nothing
1831
1848
  to freeze: the lock records nothing for it, `--check` does not audit
1832
1849
  it, and a run still loads its tasks live. Both verbs name such
1833
- projects that have tasks (`locked 0 project configs → .vx/projects.lock.json (no
1850
+ projects that have tasks (`0 project configs → .vx/projects.lock.json (no
1834
1851
  vx.config, so not locked: app, lib)`, five names then `and N more`),
1835
1852
  so an empty lock on a plugin-only workspace never reads like an audit.
1836
1853
  One with no task (core's default `build` aside) is not named.
@@ -2035,8 +2052,9 @@ binary keeps the old one's mode (and, as root, its owner), and must
2035
2052
  answer `--version` before the upgrade reports it installed: one that
2036
2053
  does not start on this machine, or does not answer within 10 s, is
2037
2054
  swapped back for the previous vx, and the command exits 1 saying so
2038
- (item 1097). On the version it already is it says `already at
2039
- <version>` and downloads nothing.
2055
+ (item 1097): `✓ vx upgrade installed 0.0.2`. On the version it
2056
+ already is it says `› vx upgrade already at <version>` and downloads
2057
+ nothing.
2040
2058
 
2041
2059
  **A vx a package manager installed** (the npm package's launcher runs
2042
2060
  `…/node_modules/@vzn/vx-<os>-<arch>/vx`) is upgraded by that package
@@ -2060,10 +2078,16 @@ root adds `--workspace-root`. The lockfile is updated by that manager.
2060
2078
  Run inside a project that installs `@vzn/vx`, a global vx hands
2061
2079
  `vx upgrade` to the project's own (§ Which vx runs), so the project's
2062
2080
  dependency is what moves.
2063
- vx prints what it runs (`vx upgrade: 0.0.1 → 0.0.2: running pnpm add
2064
- @vzn/vx@0.0.2 --save-dev (in /repo)`), runs it there, and exits with an
2065
- error if it fails. vx never installs Node or a package manager: one not
2066
- on `PATH` is refused with the command to run. A vx run from an npx or
2081
+ vx prints what it runs, runs it there, and exits with an error if it
2082
+ fails:
2083
+
2084
+ ```
2085
+ › vx upgrade 0.0.1 → 0.0.2
2086
+
2087
+ bun add @vzn/vx@0.0.2 in /repo
2088
+ ```
2089
+
2090
+ 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
2067
2091
  bunx cache has nothing installed to upgrade, and says to ask for
2068
2092
  `@vzn/vx@latest` instead.
2069
2093
 
@@ -2321,12 +2345,15 @@ lists core's verbs. A plugin's verb exists once its plugin is in
2321
2345
  No project in scope declares a requested task. The message lists the
2322
2346
  tasks it found; `vx show --format json` lists each project's. Check the
2323
2347
  spelling, and the scope: `--filter`, `--all`, or the project the working
2324
- directory is in.
2348
+ directory is in. A run reads its configs from the lock, so a task
2349
+ added to a `vx.config` since the last `vx lock` is not there yet: run
2350
+ `vx lock`.
2325
2351
 
2326
2352
  #### `VX_E_UNKNOWN_PROJECT`
2327
2353
 
2328
2354
  `vx show` names a project the workspace does not have. A project is named
2329
- by its `package.json` `name`; `vx show --format json` lists them.
2355
+ by its config's `name`, else by a plugin (a lockfile plugin names it from
2356
+ its `package.json`), else by its directory; `vx show --format json` lists them.
2330
2357
 
2331
2358
  #### `VX_E_NO_HISTORY`
2332
2359
 
@@ -2487,23 +2514,25 @@ Nx's spellings name these: `vx show projects` (when no project or task
2487
2514
  has that name) and `vx show project <name>` say `vx show` and
2488
2515
  `vx show <name>`.
2489
2516
 
2490
- No target: one line per project — name, root-relative dir, task count,
2491
- and a `(no vx config)` marker for config-less packages; one whose
2492
- tasks all come from plugins reads `N tasks (no vx config; from
2493
- plugins)`, and one a configured project depends on reads `1 task (no
2494
- vx config; default build)`: a run loads it for that dependant's
2495
- `^build` (X-48). A project's `tags` follow in brackets. With `--format json`
2517
+ No target: a table, one row per project — name, root-relative dir,
2518
+ task count, and `no vx config` for config-less packages; one whose
2519
+ tasks all come from plugins reads `N tasks from plugins (no vx
2520
+ config)`, and one a configured project depends on reads `1 task,
2521
+ default build (no vx config)`: a run loads it for that dependant's
2522
+ `^build` (X-48). A `tags` column appears when any project has tags. With `--format json`
2496
2523
  it's an array of `{ name, dir, tags: string[], tasks: string[] }`.
2497
2524
 
2498
2525
  ```
2499
2526
  $ vx show
2500
- app packages/app 3 tasks [scope:web]
2501
- bare packages/bare (no vx config)
2527
+ project dir tasks tags
2528
+ app packages/app 3 tasks scope:web
2529
+ bare packages/bare no vx config
2502
2530
  ```
2503
2531
 
2504
- Under its header `vx show <project>` prints its `tags`.
2505
- `vx show <project>` prints a block per task with every field the run
2506
- reads: description, command (`(group)` for group tasks), `dependsOn`,
2532
+ A report naming the project opens it: its `dir`, its `tags`, and
2533
+ `config none` for a package with no config file. Then
2534
+ `vx show <project>` prints a block per task, headed by its
2535
+ `project#task`, with every field the run reads: description, command (`(group)` for group tasks), `dependsOn`,
2507
2536
  `timeout`, `retries`, `env.passThrough` / `env.define` / `env.secret`,
2508
2537
  `remote`, `sandbox`, `persistent`, `interactive` (`yes` / `no`), and the
2509
2538
  cache block
@@ -2521,15 +2550,15 @@ list and `vx show <project>`, but `vx show build`, like
2521
2550
 
2522
2551
  ```
2523
2552
  $ vx show app#build
2524
- app — packages/app
2525
2553
 
2526
- build
2527
- description: compile the app
2528
- command: tsc -b
2529
- dependsOn: ^build
2530
- inputs.files: src/**
2531
- inputs.env: NODE_ENV
2532
- outputs.files: dist/**
2554
+ ─ app#build ──────────────────────────────────────────────────
2555
+ dir packages/app
2556
+ description compile the app
2557
+ command tsc -b
2558
+ dependsOn ^build
2559
+ inputs.files src/**
2560
+ inputs.env NODE_ENV
2561
+ outputs.files dist/**
2533
2562
  ```
2534
2563
 
2535
2564
  Unknown project / task names exit `1` with the same near-miss hint
@@ -2572,24 +2601,26 @@ counts are zero and `projects lock` says `no` — a broken config is
2572
2601
 
2573
2602
  ```
2574
2603
  $ vx info
2575
- vx: 0.0.0
2576
- bun: 1.4.2
2577
- git: 2.53.0
2578
- git status cache: core.fsmonitor, core.untrackedCache off
2579
- workspace root: /work/repo
2580
- projects: 12 (34 tasks)
2581
- plugins: 2 — @vzn/vx-reapi (executor, cache); @vzn/vx-otel (telemetry)
2582
- workers: 2 — cgroup CPU quota 2 of 8 cores
2583
- memory: 13 GB usable — cgroup limit; the machine has 16 GB
2584
- cache dir: /work/repo/.vx/cache (8.2 MB, this workspace's run history)
2585
- cache store: /home/me/.vx/3f2a9c1e7b4d5a60/cache (1.3 GB, shared by this repo's checkouts)
2586
- cache versions: keys vx-cache-v43 · index schema v36
2587
- cache entries: 42 (1.3 GB)
2588
- orphans: 3 artifacts (12 MB) the index does not know — `vx cache clean` reaps them
2589
- task runs (24h): 7 (5 cache hits)
2590
- flaky tasks: 1 — web#test (3 of 11 runs failed on unchanged inputs)
2591
- sandbox: available (9 tasks declare exec.sandbox)
2592
- projects lock: yes
2604
+
2605
+ ─ vx info ────────────────────────────────────────────────────
2606
+ vx 0.0.0
2607
+ bun 1.4.2
2608
+ git 2.53.0
2609
+ git status cache core.fsmonitor, core.untrackedCache off
2610
+ workspace root /work/repo
2611
+ projects 12 (34 tasks)
2612
+ plugins 2 — @vzn/vx-reapi (executor, cache); @vzn/vx-otel (telemetry)
2613
+ workers 2 — cgroup CPU quota 2 of 8 cores
2614
+ memory 13 GB usable — cgroup limit; the machine has 16 GB
2615
+ cache dir /work/repo/.vx/cache (8.2 MB, this workspace's run history)
2616
+ 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 v38
2618
+ cache entries 42 (1.3 GB)
2619
+ orphans 3 artifacts (12 MB) the index does not know — `vx cache clean` reaps them
2620
+ task runs (24h) 7 (5 cache hits)
2621
+ flaky tasks 1 — web#test (3 of 11 runs failed on unchanged inputs)
2622
+ sandbox available (9 tasks declare exec.sandbox)
2623
+ projects lock yes
2593
2624
  ```
2594
2625
 
2595
2626
  - `git` shows `(not found)` when the binary is missing.
@@ -2905,9 +2936,9 @@ measured. `--stats` does not combine with `--list`, `--failed` or
2905
2936
  `--log`; `--branch` combines only with `--stats`. `--list --format json`
2906
2937
  an array of the same `invocation` objects, newest first. An unknown run id fails
2907
2938
  loud and points at `--list`. Before any run, every form says so
2908
- (`vx last: no recorded runs yet — run something first`, exit 1; `--list`
2909
- prints `no recorded runs`, exit 0), and past green runs only,
2910
- `--list --failed` prints `no recorded run failed`. A run id may be typed as a unique prefix; a
2939
+ (`› vx last no runs yet` with `fix vx run`, exit 1; `--list` prints
2940
+ `› vx last no runs yet`, exit 0), and past green runs only,
2941
+ `--list --failed` prints `› vx last no failed runs`. A run id may be typed as a unique prefix; a
2911
2942
  prefix several runs share fails and lists them. A replayed run with
2912
2943
  failures ends with the command that re-runs them (`re-run what failed:
2913
2944
  vx run app#test -- …`, with the arguments the run forwarded to the
@@ -2994,7 +3025,7 @@ export function mcp(): VxPlugin {
2994
3025
  mcp: {
2995
3026
  description: 'serve the run history to an AI agent over stdio',
2996
3027
  async run(argv, ctx) {
2997
- // ctx.workspaceRoot, ctx.cacheDir, ctx.concurrency, ctx.warn(...)
3028
+ // ctx.workspaceRoot, ctx.cacheDir, ctx.concurrency, ctx.cache, ctx.warn(...)
2998
3029
  return 0 // the process exit code
2999
3030
  },
3000
3031
  },
@@ -3005,6 +3036,9 @@ export function mcp(): VxPlugin {
3005
3036
 
3006
3037
  The plugin's name is its package name, which `definePlugin` reads from
3007
3038
  `import.meta`; a plain object is refused when the workspace loads.
3039
+ `ctx.cache` reads the plugin's own entries as `setup` writes them (the
3040
+ local store, then each remote the workspace's cache plugins supply,
3041
+ under the run's scope), read-only, opened on first use.
3008
3042
 
3009
3043
  (`@vzn/vx-schedule-history` ships exactly this: `vx history`, what it
3010
3044
  learned per task and the reservation its `admit` hook packs.) The dispatcher tries core's verbs
@@ -3066,11 +3100,11 @@ what a TASK sees is `exec.env` in `docs/schema.md`.
3066
3100
  | `VX_CACHE_DOWNLOAD` | [`cache.download`](schema.md#settings) | Which remote hits land on disk: every one, only the requested tasks, or none. |
3067
3101
  | `VX_CACHE_INPUT_STABILITY_STRATEGY` | [`cache.inputStability.strategy`](schema.md#settings) | What a write to an input during the command means for the save, for tasks without their own. |
3068
3102
  | `VX_CACHE_INPUT_STABILITY_SEVERITY` | [`cache.inputStability.severity`](schema.md#settings) | Whether a skipped save warns or fails the task, for tasks without their own. |
3069
- | `VX_CACHE_OUTPUTS_CLEAN` | [`cache.outputs.clean`](schema.md#settings) | Remove a task's declared outputs before it runs on a miss, for tasks without their own. |
3070
3103
  | `VX_LOCK_REVIEW_FILES` | [`lock.reviewFiles`](schema.md#settings) | `vx lock` writes each project's resolved config, pretty, to `vx.lock.json` beside its `vx.config`, for review; vx never reads it. |
3071
3104
  | `VX_WATCH_POLL` | [`watch.poll`](schema.md#settings) | Poll for changes from the start instead of the OS watcher. |
3072
3105
  | `VX_CONFIG_WORKER_TIMEOUT` | [`config.workerTimeout`](schema.md#settings) | How long one config evaluation may take. |
3073
3106
  | `VX_DEBUG_TIMING` | [`debug.timing`](schema.md#settings) | Print the run's stage timings to stderr. |
3107
+ | `VX_DEBUG_WATCH` | [`debug.watch`](schema.md#settings) | `vx watch`: say on stderr each time the loop arms, with the projects it watches. |
3074
3108
  | `VX_DEBUG_DUMPABLE` | [`debug.dumpable`](schema.md#settings) | Linux: keep vx dumpable, so strace or a debugger can read its memory and `/proc/<pid>/fd`, and an unsandboxed task its env. |
3075
3109
  | `VX_HANDED_OFF` | — | Set by a global vx that hands the call to the project's own (§ Which vx runs): the path of the vx that handed off, so the receiver does not hand off again. |
3076
3110
  | `VX_RUN_WORKSPACE`, `VX_RUN_TASK` | — | Set by vx on every task (below); a `vx run` a task starts reads them back. |
@@ -3165,7 +3199,7 @@ A broad run looks like:
3165
3199
  tasks ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰
3166
3200
  2 success · 2 total
3167
3201
  cache ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰
3168
- 1 miss · 1 local
3202
+ local 1↓ 1↑
3169
3203
 
3170
3204
  info 8 workers · local cache
3171
3205
  time 5.34s · 900ms saved
@@ -3174,7 +3208,15 @@ A broad run looks like:
3174
3208
  The three bars are meters: `projects` is what the run covered against
3175
3209
  the workspace, `tasks` is failed / success / skipped, `cache` is miss /
3176
3210
  no-cache / up-to-date / local / remote (a skipped task rides it too, so
3177
- the two bars fill alike; its count is on the tasks legend only). The
3211
+ the two bars fill alike; its count is on the tasks legend only). Its
3212
+ legend reads per layer: `local 1● 1○ 2↓ 4↑ · remote 3↓ 4↑`.
3213
+ `●` is a fresh hit (outputs already on disk, whichever layer answered,
3214
+ so it counts under local), `○` a task with no cache, `↓` is restored
3215
+ from that layer and `↑` saved to it (local `↑` counts only tasks that ran here; remote `↑` counts
3216
+ uploads that landed). A layer shows only when the run's
3217
+ `--cache-policy` uses it. `N not saved` (ran, succeeded, saved nothing),
3218
+ `N not downloaded` (outputs left in the remote store) and `N ↑ failed`
3219
+ show only when non-zero. The
3178
3220
  `time` row is the wall time, then what the cache saved (the hits' stored
3179
3221
  exec times, never the restore), then the spread over executed tasks
3180
3222
  only — a hit's restore time never enters it. The footer never says a
package/docs/execution.md CHANGED
@@ -548,7 +548,7 @@ Status and Cache columns.
548
548
 
549
549
  The colors / framing modules:
550
550
 
551
- - `orchestrator/colors.ts` — ANSI truecolor (`ansi-16m`), gated by
551
+ - `util/colors.ts` — ANSI truecolor (`ansi-16m`), gated by
552
552
  `NO_COLOR` / `FORCE_COLOR` / `isTTY`. Programmatic-logger callers
553
553
  always see plain text.
554
554
  - `orchestrator/framed-output.ts` — the `┌─` frames, the glyph grid
package/docs/features.md CHANGED
@@ -75,7 +75,7 @@ config key or an environment variable is missing here.
75
75
  - **Docs for agents** (`llms.txt`, `llms-full.txt`) — the whole site as markdown for coding agents. [page](https://vznjs.github.io/vx/features/llms-txt/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
76
76
  - **vx docs** (`vx docs`, `--limit`) — search the CLI, config and cache reference offline; it ships inside vx. [page](https://vznjs.github.io/vx/features/vx-docs/) · post: [The reference, offline](https://vznjs.github.io/vx/blog/vx-docs-offline/)
77
77
  - **Affected reasons** (`--affected --dry`) — each kept task says the changed file or the dependency chain that reached it, text or JSON. [page](https://vznjs.github.io/vx/features/affected-reasons/) · post: [See inside a run](https://vznjs.github.io/vx/blog/see-inside-a-run/)
78
- - **Agent skill** (`skills/vx/SKILL.md`, `npx skills add vznjs/vx/packages/vx/skills/vx`) — an installable skill that teaches a coding agent to run, debug and query vx through the CLI's `--format json`; copy it from `node_modules/@vzn/vx/skills/vx/` to match the installed version. [page](https://vznjs.github.io/vx/features/agent-skill/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
78
+ - **Agent skill** (`skills/vx/SKILL.md`, `npx skills add vznjs/vx`) — an installable skill that teaches a coding agent to run, debug and query vx through the CLI's `--format json`; copy it from `node_modules/@vzn/vx/skills/vx/` to match the installed version. [page](https://vznjs.github.io/vx/features/agent-skill/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
79
79
 
80
80
  ## Cache
81
81
 
@@ -83,7 +83,7 @@ config key or an environment variable is missing here.
83
83
  - **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/)
84
84
  - **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/)
85
85
  - **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
86
- - **Keep outputs on a miss** (`cache.outputs.clean`, workspace `cache.outputs.clean`, `VX_CACHE_OUTPUTS_CLEAN`) — `false` leaves a task's outputs in place for a tool that reads its last output (tsc's `.tsbuildinfo`); if the run leaves any old file unchanged, nothing is cached. no post
86
+ - **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
87
87
  - **Outputs are exactly the snapshot** (`run.rules.exclusiveOutputs`, `VX_RUN_RULES_EXCLUSIVE_OUTPUTS`) — a hit leaves the tree as the run did; two tasks may not own one output without an edge. [page](https://vznjs.github.io/vx/features/strict-outputs/) · post: [The tree is exactly the snapshot](https://vznjs.github.io/vx/blog/strict-output-ownership/)
88
88
  - **Keys from git's index** — tracked clean files hash by their blob id, no read. [page](https://vznjs.github.io/vx/features/keys-from-git/) · post: [Your cache key is already in git's index](https://vznjs.github.io/vx/blog/keys-from-git/)
89
89
  - **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/)
@@ -109,7 +109,7 @@ config key or an environment variable is missing here.
109
109
  - **Sandboxed tasks** (`exec.sandbox`: `allow`, `deny`, `ignore`, `weakerNetworkIsolation`, `weakerWhenNested`) — a task reads and writes only what it declares; `allow` names `read`, `write`, `network`, `unixSockets`, `localBinding`, `pty`, `systemInfo`, `gitConfig`, `machLookup`. [page](https://vznjs.github.io/vx/features/sandbox/) · post: [The sandbox](https://vznjs.github.io/vx/blog/the-sandbox/)
110
110
  - **Env isolation** (`exec.env`: `define`, `passThrough`, `secret`, `VX_DEBUG_DUMPABLE`) — a task sees only the env it names; secrets are masked; vx hides its own env from tasks (Linux and macOS). [page](https://vznjs.github.io/vx/features/env-isolation/) · post: [A task sees only the env it names](https://vznjs.github.io/vx/blog/env-isolation/)
111
111
  - **vx lock** (`vx lock`, `--check`, `--format json`, `lock.reviewFiles`, `VX_LOCK_REVIEW_FILES`) — find the projects (every directory holding a `vx.config`), freeze what their configs evaluate to and check the graph rules once; only `vx lock` evaluates configs: runs, `vx show` and `vx info` read the lock and never write it; a pretty `vx.lock.json` beside each config shows reviewers what it resolves to. [page](https://vznjs.github.io/vx/features/vx-lock/) · post: [vx lock: freezing what the key sees](https://vznjs.github.io/vx/blog/lock-and-frozen/)
112
- - **Flaky detection** — a task that fails then passes is reported flaky. [page](https://vznjs.github.io/vx/features/flaky-detection/) · post: [Flaky is a claim](https://vznjs.github.io/vx/blog/flaky-tasks/)
112
+ - **Flaky detection** — a task whose key fails after it passed is reported flaky, judged on every worktree's record of the repo. [page](https://vznjs.github.io/vx/features/flaky-detection/) · post: [Flaky is a claim](https://vznjs.github.io/vx/blog/flaky-tasks/)
113
113
  - **Project boundaries** — globs never cross into another project. [page](https://vznjs.github.io/vx/features/project-boundaries/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
114
114
  - **Artifact integrity checks** — a CRC-32, a key match and an outputs-only check on every artifact; damage is a miss. [page](https://vznjs.github.io/vx/features/artifact-integrity/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
115
115
  - **Sandbox names what to grant** — a refused write is named beside the failed task with the path to allow. [page](https://vznjs.github.io/vx/features/sandbox-grants/) · post: [Guard rails that tell you the fix](https://vznjs.github.io/vx/blog/guard-rails/)
@@ -118,7 +118,7 @@ config key or an environment variable is missing here.
118
118
 
119
119
  ## Daily work
120
120
 
121
- - **Watch mode** (`vx watch`, `--poll`, `watch.poll`, `VX_WATCH_POLL`) — re-run what a change affects, on content, not events. [page](https://vznjs.github.io/vx/features/watch/) · post: [Watch: a content gate](https://vznjs.github.io/vx/blog/watch-mode/)
121
+ - **Watch mode** (`vx watch`, `--poll`, `watch.poll`, `VX_WATCH_POLL`, `VX_DEBUG_WATCH`) — re-run what a change affects, on content, not events. [page](https://vznjs.github.io/vx/features/watch/) · post: [Watch: a content gate](https://vznjs.github.io/vx/blog/watch-mode/)
122
122
  - **Dev servers in the graph** (`exec.persistent`, `readyWhen`, `--ready-notice`, `run.readyNotice`, `VX_RUN_READY_NOTICE`) — a server is a node; dependents start when it is ready. [page](https://vznjs.github.io/vx/features/dev-servers/) · post: [Dev servers as graph nodes](https://vznjs.github.io/vx/blog/dev-servers-in-the-graph/)
123
123
  - **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/)
124
124
  - **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/)
@@ -169,7 +169,7 @@ config key or an environment variable is missing here.
169
169
  - **Remote cache and execution** (`@vzn/vx-reapi`, `exec.remote`) — Bazel REAPI cache and workers; the scheduler stays here. [page](https://vznjs.github.io/vx/features/remote-execution/) · post: [Remote execution without moving the scheduler](https://vznjs.github.io/vx/blog/remote-execution/)
170
170
  - **OpenTelemetry** (`@vzn/vx-otel`) — each run exported to your OpenTelemetry backend, never breaking it. [page](https://vznjs.github.io/vx/features/opentelemetry/) · post: [Observability that cannot break a run](https://vznjs.github.io/vx/blog/telemetry-never-breaks-a-run/)
171
171
  - **vx history** (`@vzn/vx-schedule-history`, `vx history`) — what the scheduler learned per task. [page](https://vznjs.github.io/vx/features/vx-history/) · post: [A scheduler that learns from your runs](https://vznjs.github.io/vx/blog/a-scheduler-that-learns/)
172
- - **Setup and teardown hooks** (`setup`, `teardown`) — plugin code around the run, bounded by a timeout. [page](https://vznjs.github.io/vx/features/setup-teardown/) · post: [Extend vx in an afternoon](https://vznjs.github.io/vx/blog/extend-vx/)
172
+ - **Setup and teardown hooks** (`setup`, `teardown`, `RunReport`, `ctx.cache.getRun`) — plugin code around the run, bounded by a timeout; on a run that writes a remote, vx keeps failed tasks' logs and outputs and uncached tasks' logs there, and `teardown` gets the run, its CI job and every task with the names to read them by, which a plugin verb reads back through its read-only `ctx.cache`. [page](https://vznjs.github.io/vx/features/setup-teardown/) · post: [Extend vx in an afternoon](https://vznjs.github.io/vx/blog/extend-vx/)
173
173
  - **REAPI TLS, mTLS and headers** — connect to hosted servers such as BuildBuddy the way Bazel does. [page](https://vznjs.github.io/vx/features/reapi-tls/) · post: [A remote server you can trust in production](https://vznjs.github.io/vx/blog/reapi-in-production/)
174
174
  - **REAPI execution records** — a repeat remote execution skips the worker and replays outputs and stdout. [page](https://vznjs.github.io/vx/features/reapi-action-cache/) · post: [A remote server you can trust in production](https://vznjs.github.io/vx/blog/reapi-in-production/)
175
175
  - **REAPI verified downloads and deadlines** — a corrupt blob or a wedged server degrades to a miss, never a hang. [page](https://vznjs.github.io/vx/features/reapi-safety/) · post: [A remote server you can trust in production](https://vznjs.github.io/vx/blog/reapi-in-production/)
@@ -177,3 +177,4 @@ config key or an environment variable is missing here.
177
177
  - **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/)
178
178
  - **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/)
179
179
  - **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/)
180
+ - **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/)