@vzn/vx 0.0.637 → 0.0.639

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 (126) hide show
  1. package/docs/caching.md +84 -28
  2. package/docs/cli.md +273 -408
  3. package/docs/execution.md +19 -44
  4. package/docs/features.md +13 -15
  5. package/docs/patterns.md +5 -7
  6. package/docs/schema.md +68 -61
  7. package/package.json +5 -5
  8. package/schemas/last.json +7 -4
  9. package/schemas/plan.json +2 -2
  10. package/skills/vx/SKILL.md +19 -6
  11. package/src/bin.ts +13 -7
  12. package/src/cache/cache.ts +60 -11
  13. package/src/cache/git-inputs.ts +6 -0
  14. package/src/cache/index.ts +10 -2
  15. package/src/cache/inputs.ts +0 -24
  16. package/src/cache/layer.ts +32 -12
  17. package/src/cache/layered-cache.ts +112 -315
  18. package/src/cache/plugin-cache.ts +240 -0
  19. package/src/cache/policy.ts +9 -0
  20. package/src/cache/remote-lane.ts +383 -0
  21. package/src/cache/run-history.ts +92 -8
  22. package/src/cache/run-store.ts +77 -0
  23. package/src/cache/schema.ts +44 -13
  24. package/src/cache/scoped-cache.ts +22 -2
  25. package/src/cli/cache.ts +33 -16
  26. package/src/cli/completions.ts +11 -7
  27. package/src/cli/foreign-flags.ts +10 -22
  28. package/src/cli/help.ts +22 -38
  29. package/src/cli/index.ts +25 -11
  30. package/src/cli/info.ts +8 -6
  31. package/src/cli/init.ts +14 -15
  32. package/src/cli/last.ts +20 -10
  33. package/src/cli/lock.ts +39 -27
  34. package/src/cli/plan-format.ts +10 -61
  35. package/src/cli/plugin-commands.ts +77 -6
  36. package/src/cli/plugin-templates.ts +1 -1
  37. package/src/cli/run.ts +31 -191
  38. package/src/cli/select.ts +18 -9
  39. package/src/cli/show.ts +72 -40
  40. package/src/cli/task-verb.ts +4 -4
  41. package/src/cli/upgrade.ts +132 -39
  42. package/src/cli/watch-cycle.ts +2 -1
  43. package/src/cli/watch-filter.ts +7 -2
  44. package/src/cli/watch-judge.ts +6 -1
  45. package/src/cli/watch.ts +73 -34
  46. package/src/cli/why.ts +14 -5
  47. package/src/cli/workspace-config.ts +2 -2
  48. package/src/config.ts +0 -6
  49. package/src/exec/executor.ts +1 -1
  50. package/src/exec/sandbox-runtime.ts +1 -1
  51. package/src/exec/sandbox-violations.ts +3 -1
  52. package/src/graph/index.ts +9 -1
  53. package/src/graph/priorities.ts +1 -1
  54. package/src/graph/scheduler.ts +12 -16
  55. package/src/graph/task-graph.ts +119 -21
  56. package/src/graph/task-status.ts +95 -0
  57. package/src/{orchestrator → history}/failure-mode.ts +20 -11
  58. package/src/{orchestrator → history}/history.ts +61 -5
  59. package/src/history/index.ts +48 -0
  60. package/src/{orchestrator → history}/metrics.ts +14 -23
  61. package/src/{orchestrator → history}/miss-reason.ts +1 -1
  62. package/src/index.ts +21 -29
  63. package/src/orchestrator/affected-tasks.ts +8 -3
  64. package/src/orchestrator/doctor.ts +2 -2
  65. package/src/orchestrator/execute-args.ts +80 -0
  66. package/src/orchestrator/execute-task.ts +47 -429
  67. package/src/orchestrator/fingerprint-watch.ts +2 -2
  68. package/src/orchestrator/index.ts +3 -74
  69. package/src/orchestrator/miss-save.ts +33 -3
  70. package/src/orchestrator/options.ts +1 -36
  71. package/src/orchestrator/output-host.ts +4 -3
  72. package/src/orchestrator/persistent-task.ts +321 -0
  73. package/src/orchestrator/persistent.ts +130 -3
  74. package/src/orchestrator/placement.ts +3 -3
  75. package/src/orchestrator/plan.ts +18 -5
  76. package/src/orchestrator/plugin-host.ts +77 -47
  77. package/src/orchestrator/plugin.ts +86 -14
  78. package/src/orchestrator/prepare.ts +74 -31
  79. package/src/orchestrator/projects.ts +3 -0
  80. package/src/orchestrator/remote-prefetch.ts +23 -6
  81. package/src/orchestrator/run-artifacts.ts +5 -177
  82. package/src/orchestrator/run-context.ts +55 -0
  83. package/src/orchestrator/run-failures.ts +15 -3
  84. package/src/orchestrator/run-records.ts +36 -4
  85. package/src/orchestrator/run-report.ts +81 -179
  86. package/src/orchestrator/run-settings.ts +1 -16
  87. package/src/orchestrator/run-stop.ts +153 -0
  88. package/src/orchestrator/run.ts +313 -555
  89. package/src/orchestrator/sandbox-fix.ts +0 -16
  90. package/src/orchestrator/signals.ts +14 -6
  91. package/src/orchestrator/stable-keys.ts +107 -3
  92. package/src/orchestrator/steps.ts +50 -0
  93. package/src/orchestrator/task-hash.ts +3 -16
  94. package/src/orchestrator/task-log-buffer.ts +1 -1
  95. package/src/orchestrator/task-process.ts +94 -0
  96. package/src/orchestrator/telemetry-host.ts +1 -1
  97. package/src/orchestrator/telemetry.ts +6 -83
  98. package/src/{orchestrator → report}/events.ts +41 -8
  99. package/src/{orchestrator → report}/forecast.ts +35 -2
  100. package/src/{orchestrator → report}/framed-output.ts +39 -34
  101. package/src/report/index.ts +36 -0
  102. package/src/{orchestrator → report}/logger.ts +166 -21
  103. package/src/{orchestrator → report}/status-line.ts +84 -7
  104. package/src/{orchestrator → report}/summary.ts +179 -63
  105. package/src/report/tally.ts +83 -0
  106. package/src/settings.ts +13 -23
  107. package/src/{orchestrator → util}/colors.ts +49 -4
  108. package/src/util/config-frame.ts +31 -2
  109. package/src/util/errors.ts +32 -0
  110. package/src/util/git-ignored.ts +143 -0
  111. package/src/util/index.ts +25 -3
  112. package/src/util/refusal-notice.ts +111 -0
  113. package/src/util/report.ts +39 -0
  114. package/src/util/settle.ts +14 -39
  115. package/src/util/tail.ts +1 -1
  116. package/src/util/ui.ts +197 -0
  117. package/src/workspace/affected.ts +20 -23
  118. package/src/workspace/dependency-rules.ts +12 -4
  119. package/src/workspace/migration.ts +87 -91
  120. package/src/workspace/workspace.ts +5 -4
  121. package/schemas/summary.json +0 -133
  122. package/src/cache/chained-cache.ts +0 -276
  123. package/src/orchestrator/tally.ts +0 -112
  124. /package/src/{orchestrator → report}/path-links.ts +0 -0
  125. /package/src/{orchestrator → report}/plain-output.ts +0 -0
  126. /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/docs/caching.md CHANGED
@@ -453,8 +453,17 @@ transitive dependant of one with it (their up-front keys fold a
453
453
  preliminary key); a glob with no literal prefix reaches every project.
454
454
  Before item 584 one such declaration emptied the tier graph-wide.
455
455
  `rules.upfrontKeys` (on by default, X-54) refuses at load a task whose
456
- input globs can match another task's declared outputs, so the clauses
457
- on declared outputs fire only with the rule off; undeclared writes, an
456
+ input globs can match another task's declared outputs, unless git
457
+ ignores those outputs whole, so the clauses on declared outputs fire
458
+ only with the rule off. An output git ignores whole is no reach either:
459
+ a key reads only what git lists, so a `**/*` reader beside a gitignored
460
+ `**/dist/**` producer keys up front, the same key before and after the
461
+ build. The run asks git once, beside its walk and only when a reader
462
+ reaches a same-project output: one `git check-ignore` over each such
463
+ output's directory (`dist/`, and a fresh `__vx_probe__/dist/` for a
464
+ glob that starts with `**`), and the walk's listing must hold no file
465
+ the glob matches (a tracked output, a directory a `!` rule takes back);
466
+ anything git cannot answer keeps the key late. Undeclared writes, an
458
467
  `outputs.workspaceFiles` producer upstream, a rewriter the key does not
459
468
  fold, and runtime probes after a writer still leave a key preliminary.
460
469
  The
@@ -553,7 +562,24 @@ Hard invariants of the remote prefetch:
553
562
  later `get` finds it as a now-local hit — still reports
554
563
  `source: 'remote'`, so the outcome is `cache-hit-remote`.
555
564
  - **A remote-read-off policy** (`--no-cache`, `--force`, or
556
- `--cache-policy=remote:`) fires no prefetch.
565
+ `--cache-policy=remote:`) fires no prefetch. With remote writes on
566
+ (`--force`), the same up-front keys go to one `hasMany` per remote
567
+ the run writes but does not read and that can batch, in the
568
+ background: no task waits for it, only each upload does. An upload
569
+ of a key that remote holds is skipped (an entry is immutable) and is
570
+ counted nowhere. A key the probe did not cover, or a remote with no
571
+ `hasMany`, asks `has` for that key inside its upload job; a probe
572
+ that fails uploads, as before.
573
+ - **No `has` before a `put`.** An entry is immutable: a remote's `put`
574
+ of a key it holds keeps the stored bytes, and a wire that would
575
+ overwrite checks inside (`turboCache()` asks HEAD unless a 404 or a
576
+ batch answer this run said the key is absent; `reapi()` looks the
577
+ action up beside its upload and keeps a stored result; Nx servers
578
+ answer 409). `ctx.cache.put` and a run's own entries send the `put`
579
+ alone.
580
+ - **`--dry` batches too.** The plan derives every key first, then asks
581
+ each remote with `hasMany` once; a remote without it is asked `has`
582
+ per key.
557
583
  - **Never fail.** Every remote path (get / put / ingest / prefetch)
558
584
  catches all errors and degrades to a cache miss. A remote 500, a
559
585
  network drop, a corrupt artifact, or a failed integrity check can
@@ -573,16 +599,16 @@ fire-and-forget background upload**. The task's outcome (and its
573
599
  dependents) never wait on upload latency — the uploads race alongside
574
600
  the rest of the run and are drained before `cache.close()`, so a
575
601
  short run still ships every artifact before the process exits. Upload
576
- failures log via `onRemoteError` and are otherwise ignored (the task
602
+ failures are warned once per failure class and are otherwise ignored (the task
577
603
  already succeeded; the only loss is the remote entry).
578
604
 
579
605
  An upload is the outputs as the task wrote them; secret masking does
580
606
  not reach file contents, so a task whose outputs embed a secret is one
581
607
  to leave uncached ([security](./security.md)).
582
608
 
583
- ### Planning probes (`--dry` / `--graph`)
609
+ ### Planning probes (`--dry`)
584
610
 
585
- The planning paths (`vx run --dry`, `--graph`) predict hits without
611
+ The planning path (`vx run --dry`) predicts hits without
586
612
  side effects: against a remote cache they use a **lightweight
587
613
  existence probe** — no artifact download, no local ingest. A predicted
588
614
  `hit-remote` means the artifact exists remotely; the bytes move only
@@ -615,8 +641,8 @@ remote executor's own store.
615
641
  The CLI maps three flags to a policy (precedence: start all-on → apply
616
642
  `--cache-policy` → `--no-cache` forces all off → `--force` forces both reads
617
643
  off): `--no-cache` = everything off (no read, no write, no output
618
- clean); `--force` = reads off / writes on (re-execute and refresh the
619
- cache, outputs cleaned); `--cache-policy=<spec>` = explicit per-layer control.
644
+ clean); `--force` = reads off / writes on (re-execute, outputs cleaned;
645
+ a key already stored, locally or in a remote, is kept as it is); `--cache-policy=<spec>` = explicit per-layer control.
620
646
  Off CI, unless a spec names the remote, `remoteWrite` starts off: a key
621
647
  holds only declared inputs, so a laptop's undeclared tool or file could
622
648
  replay on CI. See `docs/cli.md` § Cache control.
@@ -665,7 +691,10 @@ is on):
665
691
  Until 2026-09-27 (A-3) the rename came first, and a commit refused
666
692
  past the busy timeout left the new bytes beside the old rows: every
667
693
  later hit on the key failed the task as a corrupt artifact.
668
- A re-save (`--force`) first moves the previous artifact aside under
694
+ A `--force` save of a key the index already holds is skipped, so
695
+ `--force` rewrites nothing. A re-save (a key dropped as corrupt, or
696
+ two writers that both found it absent) first moves the previous
697
+ artifact aside under
669
698
  a temp name and unlinks it after the commit: ext4 flushes the
670
699
  incoming file when a rename replaces one, 0.55 ms a save on the main
671
700
  thread against 0.04 (a forced 1,000-task run 4.01 s → 3.46 s,
@@ -839,10 +868,10 @@ Skipped when:
839
868
  on, so it DOES clean (the saved snapshot must be clean).
840
869
  - `cache.outputs.clean` is `false` (on the task, else the workspace's
841
870
  `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.
871
+ `.tsbuildinfo`). You opt out of the clean-tree guarantee: the save
872
+ stores every file the outputs match after the run, so a leftover from
873
+ an older build is cached and restored everywhere. A retry cleans as
874
+ usual; a hit still cleans before its restore.
846
875
 
847
876
  A declared output the process cannot remove (a `dist/` another user
848
877
  wrote, a read-only checkout) fails the task with `cannot remove declared
@@ -1192,7 +1221,8 @@ in `~/.vx/<id>/cache`, and each workspace keeps only its own index in
1192
1221
  its `.vx/cache`: every checkout of the repository hits what another
1193
1222
  saved (a second clone, a worktree), and none reads another's history.
1194
1223
  Task timings are shared too: `task_durations`, what a duration forecast
1195
- reads, so a new worktree has one on its first run.
1224
+ reads, so a new worktree has one on its first run. So are plugins' own
1225
+ entries (`plugin_blobs`, a plugin's `ctx.cache`).
1196
1226
  The id is Nx 23's: 16 hex of a sha256 of the remote (`origin`, then
1197
1227
  `upstream`, `base`, the first; `host/owner/repo` in lower case, so ssh
1198
1228
  and https agree) and the workspace's path in the repository; with no
@@ -1204,7 +1234,7 @@ owner-only; vx closes one of yours that others can read (chmod 700), and one oth
1204
1234
  ~/.vx/<id>/cache/ the shared store
1205
1235
  ├── store.db entries, entry_stdout, output_files,
1206
1236
  │ entry_inputs, task_durations,
1207
- │ store_meta; artifacts
1237
+ │ plugin_blobs, store_meta; artifacts
1208
1238
  │ of at most 32 KiB compressed (v33)
1209
1239
  └── <hash>.tar.zst the larger artifacts (below)
1210
1240
 
@@ -1469,9 +1499,9 @@ keep). The artifacts stay: each is indexed again from its own bytes
1469
1499
  when its task next asks for its key (below).
1470
1500
 
1471
1501
  ```sql
1472
- -- src/cache/schema.ts (SCHEMA_VERSION = 'v36', in cache.ts)
1502
+ -- src/cache/schema.ts (SCHEMA_VERSION = 'v40', in cache.ts)
1473
1503
  -- 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
1504
+ -- store_meta, task_durations, task_outcomes, blob_sizes and plugin_blobs live in its store.db, attached as `store`; the rest is
1475
1505
  -- the workspace's cache.db. A named cache dir holds all of them.
1476
1506
 
1477
1507
  CREATE TABLE schema_meta (
@@ -1526,7 +1556,8 @@ CREATE TABLE runs (
1526
1556
  timed_out INTEGER, -- 1 when vx's own timeout killed it
1527
1557
  sandbox_violations INTEGER, -- the sandbox's violation count
1528
1558
  not_ready TEXT, -- 'timeout' | 'exited' | 'spawn' (persistent task)
1529
- restored INTEGER -- v31, on a hit: 1 = outputs restored, 0 = up to date
1559
+ restored INTEGER, -- v31, on a hit: 1 = outputs restored, 0 = up to date
1560
+ remote_error TEXT -- v40: each remote read or write of the key that failed
1530
1561
  );
1531
1562
 
1532
1563
  -- Two indexes, both append-only under a run's inserts: every row of a run
@@ -1537,10 +1568,6 @@ CREATE TABLE runs (
1537
1568
  -- the history reader bounds its scan by rowid instead (see history.md).
1538
1569
  CREATE INDEX runs_started_at ON runs(started_at);
1539
1570
  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
1571
 
1545
1572
  -- Every non-group, non-aborted outcome of a run gets a row, so
1546
1573
  -- `invocations.task_count` always equals `COUNT(*)` here for that run_id
@@ -1575,7 +1602,8 @@ CREATE TABLE file_hashes (
1575
1602
  -- The size of each index blob the enumeration checked against the
1576
1603
  -- worktree size git recorded (§ Cache key derivation, A-60): fixed for
1577
1604
  -- its OID, so a warm run asks git for none. Swept with file_hashes by
1578
- -- seen_at, the time the row was written.
1605
+ -- seen_at, the time the row was written. v38: in the store, so a worktree
1606
+ -- reads what another learned.
1579
1607
  CREATE TABLE blob_sizes (
1580
1608
  oid TEXT PRIMARY KEY,
1581
1609
  size INTEGER NOT NULL,
@@ -1630,13 +1658,13 @@ CREATE TABLE output_dirs (
1630
1658
 
1631
1659
  -- v22 (Tier 3): one header row per `vx run` invocation. The `runs`
1632
1660
  -- table is per-task; this is the per-invocation record carrying the
1633
- -- command, git/CI/host context, tags, and run-level counts. Recorded
1661
+ -- command, git/CI/host context, and run-level counts. Recorded
1634
1662
  -- atomically alongside `runs` via recordRunBundle (one transaction).
1635
1663
  CREATE TABLE invocations (
1636
1664
  run_id TEXT PRIMARY KEY, -- UUIDv7, == runs.run_id
1637
1665
  command TEXT NOT NULL, -- full argv, e.g. "vx run build --all"
1638
1666
  requested_tasks TEXT NOT NULL, -- JSON string[] of options.tasks
1639
- cache_policy TEXT NOT NULL, -- compact flags, e.g. "lR,lW,rR,rW"
1667
+ cache_policy TEXT NOT NULL, -- the --cache-policy spec, e.g. "local:rw,remote:r"
1640
1668
  concurrency INTEGER NOT NULL,
1641
1669
  flow TEXT, -- 'focused' | 'broad' | NULL (programmatic)
1642
1670
  started_at INTEGER NOT NULL, -- ms-epoch
@@ -1659,8 +1687,7 @@ CREATE TABLE invocations (
1659
1687
  host TEXT, -- os.hostname()
1660
1688
  os TEXT, -- process.platform
1661
1689
  arch TEXT, -- process.arch
1662
- vx_version TEXT NOT NULL,
1663
- tags TEXT NOT NULL DEFAULT '{}' -- JSON object {k:v} from --tag
1690
+ vx_version TEXT NOT NULL
1664
1691
  );
1665
1692
  CREATE INDEX invocations_started ON invocations(started_at);
1666
1693
  CREATE INDEX invocations_branch ON invocations(branch);
@@ -1696,6 +1723,8 @@ CREATE TABLE store_meta (
1696
1723
  -- v36: each executed success's duration, appended in the run's history
1697
1724
  -- transaction, in the store so every worktree forecasts from it.
1698
1725
  -- source: 'branch:<name>'; the forecast reads the newest five per task.
1726
+ -- v37: 'remote' rows are a default-branch commit's timings a plugin
1727
+ -- imported, one per (sha, task).
1699
1728
  CREATE TABLE task_durations (
1700
1729
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1701
1730
  project TEXT NOT NULL,
@@ -1706,6 +1735,33 @@ CREATE TABLE task_durations (
1706
1735
  recorded_at INTEGER NOT NULL
1707
1736
  );
1708
1737
  CREATE INDEX task_durations_task ON task_durations(project, task, source, id);
1738
+ CREATE UNIQUE INDEX task_durations_remote
1739
+ ON task_durations(sha, project, task) WHERE source = 'remote';
1740
+
1741
+ -- v38: the flaky record (failure-mode.ts), in the store so a key that
1742
+ -- passed in one worktree and failed in another is flaky in both. Every
1743
+ -- executed success or failure that names its key (keyed, cached, not run
1744
+ -- behind a failure), and a key's first hit: a later hit replays a pass on
1745
+ -- record, so a warm run on passed keys writes no row (one index probe,
1746
+ -- ~1 ms per 1,000 hits). Pruned by age with the run history.
1747
+ CREATE TABLE task_outcomes (
1748
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1749
+ project TEXT NOT NULL,
1750
+ task TEXT NOT NULL,
1751
+ hash TEXT NOT NULL,
1752
+ status TEXT NOT NULL,
1753
+ attempts INTEGER,
1754
+ recorded_at INTEGER NOT NULL
1755
+ );
1756
+ CREATE INDEX task_outcomes_hash ON task_outcomes(hash, status);
1757
+
1758
+ -- v37: a plugin's own entries (`ctx.cache`), by core's key for its
1759
+ -- name; written once, pruned by age with the run history.
1760
+ CREATE TABLE plugin_blobs (
1761
+ key TEXT PRIMARY KEY,
1762
+ bytes BLOB NOT NULL,
1763
+ at INTEGER NOT NULL
1764
+ );
1709
1765
 
1710
1766
  -- v33: an artifact of at most 32 KiB compressed, its exact <hash>.tar.zst
1711
1767
  -- bytes, written in the transaction that writes its entry rows. In the
@@ -2114,7 +2170,7 @@ breaking footer).
2114
2170
  migration), so this costs one cold rebuild.
2115
2171
  - **SCHEMA v21 → v22 (no `CACHE_VERSION` bump)**: Tier-3 dashboard
2116
2172
  tables — `invocations` (one header row per `vx run` with command,
2117
- git/CI/host context, tags, and run-level counts, recorded with `runs`
2173
+ git/CI/host context, and run-level counts, recorded with `runs`
2118
2174
  via `Cache.recordRunBundle`) and `entry_inputs` (one row per cache-key
2119
2175
  component, keyed by the cache-ENTRY hash — the input-fingerprint
2120
2176
  moat). `entry_inputs` is written **inside the entry-save transaction,