@vzn/vx 0.0.641 → 0.0.643

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 (77) hide show
  1. package/package.json +5 -12
  2. package/skills/vx/SKILL.md +4 -4
  3. package/src/cache/archive.ts +525 -104
  4. package/src/cache/cache.ts +85 -11
  5. package/src/cache/fold-memo.ts +58 -0
  6. package/src/cache/git-inputs.ts +213 -31
  7. package/src/cache/index.ts +12 -3
  8. package/src/cache/inputs.ts +169 -79
  9. package/src/cache/key-fold.ts +90 -69
  10. package/src/cache/layer.ts +4 -6
  11. package/src/cache/layered-cache.ts +96 -10
  12. package/src/cache/output-index.ts +25 -4
  13. package/src/cache/plugin-cache.ts +23 -16
  14. package/src/cache/remote-lane.ts +25 -8
  15. package/src/cache/run-store.ts +4 -1
  16. package/src/cache/schema.ts +13 -3
  17. package/src/cache/scoped-cache.ts +74 -237
  18. package/src/cache/umask.ts +29 -0
  19. package/src/cli/docs.ts +71 -7
  20. package/src/cli/help.ts +6 -8
  21. package/src/cli/plan-format.ts +4 -1
  22. package/src/cli/plugin-commands.ts +11 -6
  23. package/src/cli/run.ts +14 -15
  24. package/src/cli/select.ts +9 -9
  25. package/src/cli/workspace-config.ts +8 -2
  26. package/src/config.ts +7 -16
  27. package/src/exec/runner.ts +5 -0
  28. package/src/exec/sandbox-runtime.ts +81 -1
  29. package/src/graph/priorities.ts +14 -1
  30. package/src/history/history.ts +43 -33
  31. package/src/index.ts +14 -0
  32. package/src/orchestrator/affected-tasks.ts +23 -2
  33. package/src/orchestrator/deferred-outputs.ts +3 -2
  34. package/src/orchestrator/execute-task.ts +20 -14
  35. package/src/orchestrator/hit-restore.ts +10 -2
  36. package/src/orchestrator/index.ts +5 -0
  37. package/src/orchestrator/lockfile-claim.ts +54 -8
  38. package/src/orchestrator/miss-save.ts +27 -17
  39. package/src/orchestrator/options.ts +9 -1
  40. package/src/orchestrator/output-host.ts +9 -1
  41. package/src/orchestrator/plan.ts +4 -0
  42. package/src/orchestrator/plugin-host.ts +38 -4
  43. package/src/orchestrator/plugin.ts +49 -5
  44. package/src/orchestrator/prepare.ts +141 -22
  45. package/src/orchestrator/projects.ts +25 -6
  46. package/src/orchestrator/run-context.ts +80 -0
  47. package/src/orchestrator/run.ts +38 -15
  48. package/src/orchestrator/sandbox-request.ts +1 -48
  49. package/src/orchestrator/save-lane.ts +17 -10
  50. package/src/orchestrator/signals.ts +7 -10
  51. package/src/orchestrator/stable-keys.ts +83 -25
  52. package/src/orchestrator/task-hash.ts +52 -12
  53. package/src/report/events.ts +12 -0
  54. package/src/report/framed-output.ts +2 -1
  55. package/src/report/logger.ts +53 -13
  56. package/src/report/status-line.ts +45 -4
  57. package/src/report/summary.ts +8 -2
  58. package/src/settings.ts +11 -11
  59. package/src/util/errors.ts +1 -1
  60. package/src/util/hidden-prompt.ts +74 -0
  61. package/src/util/index.ts +12 -2
  62. package/src/util/paths.ts +33 -14
  63. package/src/util/secrets.ts +150 -0
  64. package/src/workspace/affected.ts +182 -75
  65. package/src/workspace/config-schema.ts +81 -19
  66. package/src/workspace/index.ts +3 -0
  67. package/src/workspace/project-loader.ts +1 -0
  68. package/src/workspace/workspace.ts +17 -11
  69. package/docs/caching.md +0 -2376
  70. package/docs/cli.md +0 -3285
  71. package/docs/execution.md +0 -676
  72. package/docs/features.md +0 -178
  73. package/docs/patterns.md +0 -219
  74. package/docs/schema.md +0 -2468
  75. package/docs/security.md +0 -253
  76. package/src/cli/docs-corpus.ts +0 -21
  77. package/src/cli/docs-text.d.ts +0 -5
package/docs/features.md DELETED
@@ -1,178 +0,0 @@
1
- # Every feature
2
-
3
- Every user-facing feature of vx, one line each, grouped by what it is
4
- for. Each line names its surface (verb, flag, config key, plugin), links
5
- its page on the site's [Features](https://vznjs.github.io/vx/features/) hub when it has one, and
6
- its blog post, or says "no post" when the story is still owed.
7
-
8
- **Rule:** a change that adds or changes a user-facing feature updates
9
- this file, and its post, in the same commit (`CLAUDE.md`).
10
- `tests/features-inventory.unsafe.test.ts` fails when a CLI verb, a flag, a
11
- config key or an environment variable is missing here.
12
-
13
- ## Run
14
-
15
- - **Run a task** (`vx run`) — run a task in the cwd's project, `pkg#task` directly, or several at once. [page](https://vznjs.github.io/vx/features/quickstart/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
16
- - **Every project** (`--all`) — run the task in every project that declares it. [page](https://vznjs.github.io/vx/features/quickstart/) · post: [The basics, done carefully](https://vznjs.github.io/vx/blog/the-basics/)
17
- - **pnpm-style filters** (`--filter`) — select projects by name, glob, path, dependencies (`foo...`), dependents (`...foo`), negation or a git range. [page](https://vznjs.github.io/vx/features/affected/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
18
- - **Affected only** (`--affected`, `run.affectedBase`, `VX_RUN_AFFECTED_BASE`) — run what a change reaches, following task edges from a git base. [page](https://vznjs.github.io/vx/features/affected/) · post: [Run only what a change reaches](https://vznjs.github.io/vx/blog/affected/)
19
- - **Concurrency** (`--concurrency`, `run.concurrency`, `VX_RUN_CONCURRENCY`) — a count or a share of the CPUs this process may use (`50%`). [page](https://vznjs.github.io/vx/features/concurrency/) · post: [One failure, and exactly what it takes down](https://vznjs.github.io/vx/blog/when-a-build-fails/)
20
- - **Skip dependencies** (`--exclude-dependencies`) — skip all `dependsOn` edges, or named ones. [page](https://vznjs.github.io/vx/features/skip-dependencies/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
21
- - **Failure policy** (`--continue`, `run.continue`, `VX_RUN_CONTINUE`) — never, deps-ok (default: dependents skip) or always. [page](https://vznjs.github.io/vx/features/skipped-blockers/) · post: [One failure, and exactly what it takes down](https://vznjs.github.io/vx/blog/when-a-build-fails/)
22
- - **Every skip names its blocker** — a skipped task says which failure blocked it. [page](https://vznjs.github.io/vx/features/skipped-blockers/) · post: [One failure, and exactly what it takes down](https://vznjs.github.io/vx/blog/when-a-build-fails/)
23
- - **Retries** (`--retries`, `exec.retries`, `run.retries`, `VX_RUN_RETRIES`) — re-run a failed task; a pass after a failure marks it flaky. [page](https://vznjs.github.io/vx/features/flaky-detection/) · post: [Flaky is a claim only declared inputs can back](https://vznjs.github.io/vx/blog/flaky-tasks/)
24
- - **Timeouts** (`--timeout`, `exec.timeout`, `run.timeout`, `VX_RUN_TIMEOUT`) — kill and fail a runaway task. [page](https://vznjs.github.io/vx/features/timeouts/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
25
- - **Argument forwarding** (`--`) — args after `--` reach the task's command and fold into its key. [page](https://vznjs.github.io/vx/features/forward-args/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
26
- - **Task picker** — `vx run` with no task in a terminal lists tasks to pick. [page](https://vznjs.github.io/vx/features/task-picker/) · post: [The small things](https://vznjs.github.io/vx/blog/the-small-things/)
27
- - **Typo hints** — an unknown task, project or filter suggests the nearest name. [page](https://vznjs.github.io/vx/features/filter-hints/) · post: [The small things](https://vznjs.github.io/vx/blog/the-small-things/)
28
- - **Turbo and Nx spellings** (`-t`, `-p`, `--exclude`, `--parallel`, `--base`, `--dry-run`, `--skip-nx-cache`) — accepted, or refused naming the vx spelling. [page](https://vznjs.github.io/vx/features/turbo-nx-flags/) · post: [Flags you already know](https://vznjs.github.io/vx/blog/flags-you-already-know/)
29
- - **Ctrl-C leaves nothing running** — the whole process tree stops, then teardown runs. [page](https://vznjs.github.io/vx/features/ctrl-c/) · post: [Ctrl-C leaves nothing running](https://vznjs.github.io/vx/blog/ctrl-c/)
30
- - **Longest chain first** — the scheduler starts the critical path first; `@vzn/vx-schedule-history` learns it from past runs. [page](https://vznjs.github.io/vx/features/critical-path/) · post: [Bitsets, popcount, and a scheduler tick](https://vznjs.github.io/vx/blog/bitsets-and-the-scheduler/)
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
- - **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
- - **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** (`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
- - **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
- - **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
- - **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/)
38
- - **Tag filters** (`--filter tag:<pattern>`) — select projects by their config `tags`. [page](https://vznjs.github.io/vx/features/tag-filters/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
39
- - **Directory and root filters** (`./<dir>`, `{<dir>}`, `.`, `//`) — select projects by path, or the root project. [page](https://vznjs.github.io/vx/features/dir-filters/) · post: [Say exactly which tasks to run](https://vznjs.github.io/vx/blog/pick-your-tasks/)
40
- - **Executor pools** (executor `capacity`) — a remote pool is admitted against its own width, not the laptop's cores. [page](https://vznjs.github.io/vx/features/executor-pools/) · post: [Remote execution without moving the scheduler](https://vznjs.github.io/vx/blog/remote-execution/)
41
- - **Runs take turns** — two vx runs on one workspace wait on a lock and name who they wait for. [page](https://vznjs.github.io/vx/features/run-lock/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
42
-
43
- ## Output
44
-
45
- - **Framed output** — each task's log in its own frame, never interleaved. [page](https://vznjs.github.io/vx/features/framed-output/) · post: [A run you can read](https://vznjs.github.io/vx/blog/a-run-you-can-read/)
46
- - **Output modes** (`--output-logs`, `VX_RUN_OUTPUT_LOGS`) — full, errors-only, hash-only or none; the default follows the flow. [page](https://vznjs.github.io/vx/features/output-modes/) · post: [Output that fits the run](https://vznjs.github.io/vx/blog/output-that-fits-the-run/)
47
- - **Run summary** — one block: projects, tasks, cache, time; nothing prints below it. [page](https://vznjs.github.io/vx/features/run-summary/) · post: [A run you can read](https://vznjs.github.io/vx/blog/a-run-you-can-read/)
48
- - **Per-task table** (`--verbosity`) — a per-task summary after the run. [page](https://vznjs.github.io/vx/features/per-task-table/) · post: [One failure, and exactly what it takes down](https://vznjs.github.io/vx/blog/when-a-build-fails/)
49
- - **Failed output kept for agents** — a failure's full log is saved and pointed to. [page](https://vznjs.github.io/vx/features/failed-output-kept/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
50
- - **Trace profile** (`--profile`) — Chrome-trace JSON of the run. [page](https://vznjs.github.io/vx/features/profile/) · post: [Output that fits the run](https://vznjs.github.io/vx/blog/output-that-fits-the-run/)
51
- - **Run branch** (`--branch`, `VX_RUN_BRANCH`) — name the branch a run records in its history and forecasts from; unset, git's branch, then the CI provider's. no post
52
- - **Stage timing** (`VX_DEBUG_TIMING`) — vx's own stage table, for performance work. [page](https://vznjs.github.io/vx/features/stage-timing/) · post: [See inside a run](https://vznjs.github.io/vx/blog/see-inside-a-run/)
53
- - **Output flows** — what is printed follows the run's intent (focused, broad or CI); a truthy `CI` wins. [page](https://vznjs.github.io/vx/features/output-flows/) · post: [Output that fits the run](https://vznjs.github.io/vx/blog/output-that-fits-the-run/)
54
- - **Cache-aware glyphs** — each task line's glyph shows ran, fresh, restored locally or remotely, failed, skipped or persistent. [page](https://vznjs.github.io/vx/features/cache-glyphs/) · post: [Output that fits the run](https://vznjs.github.io/vx/blog/output-that-fits-the-run/)
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
- - **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
- - **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 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
- - **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
-
61
- ## Plan and explain
62
-
63
- - **Dry run** (`--dry`) — the task graph and the predicted hits and misses, text or JSON (with the predicted wall time and its critical path), nothing runs. [page](https://vznjs.github.io/vx/features/dry-run/) · post: [See the plan before you run it](https://vznjs.github.io/vx/blog/dry-run/)
64
- - **vx why** (`vx why`, `--run`) — why a task re-ran, down to the file, env var or config that changed, and the task that wrote a moved input. [page](https://vznjs.github.io/vx/features/vx-why/) · post: [Why did this re-run?](https://vznjs.github.io/vx/blog/why-did-this-rerun/)
65
- - **vx show** (`vx show`) — every project, a project's or a task's resolved config, read from the lock as a run reads it. [page](https://vznjs.github.io/vx/features/vx-show/) · post: [See what a task really is](https://vznjs.github.io/vx/blog/vx-show/)
66
- - **vx last** (`vx last`, `--list`, `--failed`, `--log`, `--stats`, `--branch`) — replay a recorded run's summary, list recent runs, print one task's output, or per-task history stats (p50 as the live estimate forecasts it on a branch, p99, pass and hit rate, failure mode, peak RSS). [page](https://vznjs.github.io/vx/features/vx-last/) · post: [The last run, on request](https://vznjs.github.io/vx/blog/vx-last/)
67
- - **vx info** (`vx info`) — workspace doctor: versions, projects, cache size against `cache.retention.maxSize`, every setting and where it came from. [page](https://vznjs.github.io/vx/features/vx-info/) · post: [One command to know your workspace](https://vznjs.github.io/vx/blog/know-your-workspace/)
68
- - **JSON everywhere** (`--format`) — `show`, `info`, `why`, `last` and `cache` print JSON for agents. [page](https://vznjs.github.io/vx/features/json-everywhere/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
69
- - **Run analytics in SQLite** — every task's time, CPU, peak memory and cache result, queryable with `sqlite3`. [page](https://vznjs.github.io/vx/features/run-analytics/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
70
- - **Stable error codes** (`VX_E_…`) — under `--format json` a refusal is a JSON line on stdout with a code an agent branches on and a `docs` link to its fix (`vx docs <code>` offline). [page](https://vznjs.github.io/vx/features/error-codes/) · post: [A refusal an agent can read](https://vznjs.github.io/vx/blog/error-codes/)
71
- - **Shipped JSON Schemas** (`@vzn/vx/schemas/`) — every `--format json` shape has a schema, and stdout always parses. [page](https://vznjs.github.io/vx/features/json-schemas/) · post: [Built for the agent at the keyboard](https://vznjs.github.io/vx/blog/built-for-agents/)
72
- - **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/)
73
- - **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/)
74
- - **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/)
75
- - **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/)
76
-
77
- ## Cache
78
-
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
- - **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
- - **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
- - **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
84
- - **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/)
85
- - **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/)
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
- - **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
- - **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** (`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
- - **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
- - **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
- - **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
- - **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
- - **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
- - **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
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/)
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/)
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/)
100
- - **Restore lane** — cache restores run on their own lane, up to twice `--concurrency`. [page](https://vznjs.github.io/vx/features/restore-lane/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
101
- - **Line-ending-correct keys** — files git filters (`eol`, `core.autocrlf`) key on the bytes the build sees. [page](https://vznjs.github.io/vx/features/line-endings/) · post: [Your cache key is already in git's index](https://vznjs.github.io/vx/blog/keys-from-git/)
102
- - **Background remote uploads** — remote writes drain at the end of the run and never fail the build; the footer counts them running (`⇡`) and failed (`✗`). [page](https://vznjs.github.io/vx/features/background-uploads/) · post: [What goes into a key, and what comes back](https://vznjs.github.io/vx/blog/inside-a-cache-hit/)
103
- - **Bring your own remote cache** (plugin `cache`) — plug any cache server in through one interface. [page](https://vznjs.github.io/vx/features/custom-remote-cache/) · post: [Extend vx in an afternoon](https://vznjs.github.io/vx/blog/extend-vx/)
104
-
105
- ## Correctness
106
-
107
- - **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/)
108
- - **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/)
109
- - **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/)
110
- - **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/)
111
- - **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/)
112
- - **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/)
113
- - **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/)
114
- - **Strict numeric flags** — `0x10`, `1e3` and `2.7` are refused, never reinterpreted. [page](https://vznjs.github.io/vx/features/strict-numbers/) · post: [An upgrade you can trust](https://vznjs.github.io/vx/blog/upgrade-you-can-trust/)
115
- - **Verified releases** — binaries carry provenance, `vx upgrade` checks npm's registry signature and the SHA-512, npm publishes with provenance. [page](https://vznjs.github.io/vx/features/verified-releases/) · post: [An upgrade you can trust](https://vznjs.github.io/vx/blog/upgrade-you-can-trust/)
116
-
117
- ## Daily work
118
-
119
- - **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/)
120
- - **Dev servers in the graph** (`exec.persistent`, `readyWhen`) — 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/)
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/)
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/)
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)
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/)
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/)
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/)
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/)
128
-
129
- ## Config
130
-
131
- - **Config in TypeScript** (`vx.config.ts`, `defineProject`, `.vx/workspace.ts`, `defineWorkspace`) — typed, composable; no named inputs. `.vx/workspace.ts` is required: the nearest one up from the cwd, never above the git top-level, marks the workspace root. [page](https://vznjs.github.io/vx/features/typescript-config/) · post: [Config in TypeScript](https://vznjs.github.io/vx/blog/config-in-typescript/)
132
- - **Tasks** (`tasks`, `exec.command`, `dependsOn`, `description`, `tags`) — one command per task; the shell is the API. [page](https://vznjs.github.io/vx/features/one-command-tasks/) · post: [One command per task](https://vznjs.github.io/vx/blog/one-command-per-task/)
133
- - **Workspace rules** (`rules`) — speed-only checks, on by default, configurable. [page](https://vznjs.github.io/vx/features/workspace-rules/) · post: [Guard rails that tell you the fix](https://vznjs.github.io/vx/blog/guard-rails/)
134
- - **Config worker timeout** (`VX_CONFIG_WORKER_TIMEOUT`) — bound a config's evaluation. [page](https://vznjs.github.io/vx/features/config-timeout/) · post: [Guard rails that tell you the fix](https://vznjs.github.io/vx/blog/guard-rails/)
135
- - **No nested runs** (`VX_RUN_TASK`, `VX_RUN_WORKSPACE`) — set on every task; a `vx run` inside a task of the same workspace is refused. [page](https://vznjs.github.io/vx/features/no-nested-runs/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
136
- - **Typed config helpers** (`defineProject`) — autocomplete for task names in `dependsOn`, errors while you edit. [page](https://vznjs.github.io/vx/features/typed-helpers/) · post: [Config in TypeScript, and why there are no named inputs](https://vznjs.github.io/vx/blog/config-in-typescript/)
137
- - **Presets** — a TypeScript function returning a task config, shared across projects. [page](https://vznjs.github.io/vx/features/presets/) · post: [Config in TypeScript, and why there are no named inputs](https://vznjs.github.io/vx/blog/config-in-typescript/)
138
- - **Clean config errors** — a bad config fails at load naming the key, with no stack trace. [page](https://vznjs.github.io/vx/features/config-errors/) · post: [When a task misbehaves](https://vznjs.github.io/vx/blog/tasks-that-misbehave/)
139
-
140
- ## CI
141
-
142
- - **Results on GitHub** (`@vzn/vx-ci`: `github()`) — job summary and Checks API annotations. [page](https://vznjs.github.io/vx/features/github-ci/) · post: [Your run, on the pull request](https://vznjs.github.io/vx/blog/results-on-github/)
143
- - **PR check run** (`github({ checks })`) — a check run on the commit with the run summary as its output. [page](https://vznjs.github.io/vx/features/pr-check-run/) · post: [Your run, on the pull request](https://vznjs.github.io/vx/blog/results-on-github/)
144
- - **Cache scope from the ref** (`github({ cacheScope })`) — main writes the default keys; a PR writes only its own scope. [page](https://vznjs.github.io/vx/features/ref-cache-scope/) · post: [The cache on your terms](https://vznjs.github.io/vx/blog/the-cache-on-your-terms/)
145
-
146
- ## Adoption
147
-
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/)
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/)
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/)
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/)
152
- - **The playground** — vx's planner in the browser. [page](https://vznjs.github.io/vx/features/playground/) · post: [Try the planner in your browser](https://vznjs.github.io/vx/blog/the-playground/)
153
- - **Graph explorer** (`--dry=json`) — paste your own plan to see what each task waits on, what waits on it, and why. [page](https://vznjs.github.io/vx/guides/graph-explorer/)
154
- - **Benchmarks you can re-run** (`@vzn/vx-bench`) — vx against Turborepo and Nx. [page](https://vznjs.github.io/vx/features/benchmarks/) · posts: [Benchmarks you can re-run](https://vznjs.github.io/vx/blog/honest-benchmarks/), [Why vx is fast](https://vznjs.github.io/vx/blog/why-vx-is-fast/)
155
- - **npm pre/post scripts** — `pre<x>` and `post<x>` hooks fold into `x`'s command when `vx init` maps scripts. [page](https://vznjs.github.io/vx/features/npm-hooks/) · post: [From npm scripts or Vite Task](https://vznjs.github.io/vx/blog/from-scripts-and-vite-task/)
156
- - **Vite Task adoption** (`bunx @vzn/vx-migrate`) — writes configs from vite-plus `run.tasks` as well as Turbo, Nx and `package.json` scripts (`--from scripts` picks them beside the others). [page](https://vznjs.github.io/vx/features/vite-task-adoption/) · post: [From npm scripts or Vite Task](https://vznjs.github.io/vx/blog/from-scripts-and-vite-task/)
157
- - **Nx executors as one process** (`nx-exec`) — any Nx executor runs as one vx task with its Nx env set. [page](https://vznjs.github.io/vx/features/nx-exec/) · post: [From Nx: keep the graph, drop the platform](https://vznjs.github.io/vx/blog/from-nx/)
158
- - **Programmatic API** (`run`, `planRun`) — run or plan from your own scripts via `@vzn/vx`. [page](https://vznjs.github.io/vx/features/programmatic-api/) · post: [Extend vx in an afternoon](https://vznjs.github.io/vx/blog/extend-vx/)
159
-
160
- ## Plugins
161
-
162
- - **A pipeline with seams** (`plugins`, `definePlugin`; stages `config` `discover` `project` `graph` `key` `fingerprint` `schedule` `admit` `executor` `cache` `telemetry` `output` `commands`) — every stage is a plugin seam; an `undefined`, `null` or `false` entry is skipped. [page](https://vznjs.github.io/vx/features/plugins/) · post: [A pipeline with seams](https://vznjs.github.io/vx/blog/pipeline-with-seams/)
163
- - **A project is a directory with a vx.config** (`name`) — in any language; named by its config's `name`, else a plugin's, else its path (`//` for the root); two of one name are refused. [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/)
164
- - **Plugin projects and edges** (`discover`, `DiscoveredProject`) — a plugin adds and names projects and gives them edges as directories: `deps` orders `^task` and reaches `--filter` and `--affected`; `reach` only reaches. [page](https://vznjs.github.io/vx/features/plugins/)
165
- - **Write a plugin** (`vx init --plugin`) — scaffold a runnable plugin for a seam. [page](https://vznjs.github.io/vx/features/plugins/) · post: [Extend vx in an afternoon](https://vznjs.github.io/vx/blog/extend-vx/)
166
- - **The local floor** — running and caching here are core's, not plugins. [page](https://vznjs.github.io/vx/features/local-floor/) · post: [The local floor](https://vznjs.github.io/vx/blog/the-local-floor/)
167
- - **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/)
168
- - **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/)
169
- - **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/)
170
- - **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/)
171
- - **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/)
172
- - **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/)
173
- - **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/)
174
- - **Install as a remote action** (`exec.remote: 'only'`) — `node_modules` is built by an action, so stateless workers have it. [page](https://vznjs.github.io/vx/features/remote-install/) · post: [Remote execution without moving the scheduler](https://vznjs.github.io/vx/blog/remote-execution/)
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/)
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/)
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/)
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 DELETED
@@ -1,219 +0,0 @@
1
- # Shared patterns with Turborepo and Nx
2
-
3
- The companion to [`comparison.md`](./comparison.md). That doc lists
4
- what Turbo / Nx do that vx doesn't. This one lists what we deliberately
5
- inherited or independently converged on — the parts a Turbo or Nx user
6
- should find familiar.
7
-
8
- Every row cites the vx source file that implements the pattern; where
9
- a comment on the line names the upstream tool, that's the explicit
10
- parity claim.
11
-
12
- ## The 30-second view
13
-
14
- vx's overall shape is **Turborepo with three swaps and a few subtractions**:
15
-
16
- - per-package config (Turbo's `turbo.json` → vx's `vx.config.ts`),
17
- - opt-in cache keyed by inputs + env + upstream + lockfile,
18
- - shell-only task contract,
19
- - tar artifact + a plugin-driven remote-cache layer,
20
- - topological scheduler with bounded parallelism.
21
-
22
- The swaps: TypeScript config instead of JSON, resolved-config hash
23
- instead of file hash, strict output ownership instead of additive
24
- restore. Everything else lines up with Turbo or Nx by design.
25
-
26
- ## Subsystem-by-subsystem
27
-
28
- ### Cache key composition
29
-
30
- The set of bytes fed into the hash mirrors Turbo's key recipe; the
31
- package-manager parts come from a plugin.
32
-
33
- | Component | Turbo | Nx | vx | vx source |
34
- | -------------------------------------- | ------------ | ---------------------- | ------ | --------------------------------------------- |
35
- | Task identity (`project#task`) | yes | yes | yes | `src/cache/layer.ts` (`CacheKeyInput.taskId`) |
36
- | Resolved command + env declarations | yes | yes | yes | `src/cache/layer.ts` (`taskConfigHash`) |
37
- | Declared input file contents | yes | yes | yes | `src/cache/layer.ts` (`inputFiles`) |
38
- | Declared env-var **values** at runtime | yes | yes | yes | `src/cache/layer.ts` (`envValues`) |
39
- | Upstream task hashes (cascade) | yes | yes | yes | `src/cache/layer.ts` (`upstreamHashes`) |
40
- | Workspace lockfile | yes | yes | plugin | `@vzn/vx-lockfile` (`VxPlugin.fingerprint`) |
41
- | Forwarded CLI args (after `--`) | yes | yes | yes | `src/cache/layer.ts` (`forwardArgs`) |
42
- | Project `package.json` bytes | via lockfile | `externalDependencies` | plugin | `@vzn/vx-lockfile` (an input file) |
43
-
44
- Core keys no package file (2026-10-14): the lockfile and each
45
- project's `package.json` key a task through `@vzn/vx-lockfile`, which
46
- claims the lockfile and adds `package.json` to every cached task's
47
- inputs, so narrow `inputs.files` like `['src/**']` doesn't miss a
48
- `package.json` dep bump.
49
-
50
- The two **divergences** from Turbo's recipe — TypeScript-resolved
51
- config hash and per-task forwarded-args fold — are documented in
52
- [`caching.md`](./caching.md) and listed under "Where vx is ahead" in
53
- [`comparison.md`](./comparison.md).
54
-
55
- ### Input enumeration
56
-
57
- | Pattern | Source | vx source |
58
- | -------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------- |
59
- | Defer to git: tracked from `git ls-files -s -v`, untracked-but-not-ignored from `git status --porcelain -uall` | Turbo + Nx | `src/cache/git-inputs.ts`, `src/cache/inputs.ts` ("same as Turbo and Nx") |
60
- | Git blob OIDs as per-file content hashes (index-harvested) | Turbo | `src/cache/git-inputs.ts` + `src/cache/file-hashes.ts` |
61
- | Project-boundary enforcement (no cross-project globs) | Turbo + Nx | `src/workspace/nested-dirs.ts` |
62
-
63
- (vx hard-requires git — there is no fallback walker; a non-repo
64
- workspace gets a clean `UserError` telling the user to `git init`.)
65
-
66
- ### `dependsOn` micro-syntax
67
-
68
- The string DSL is verbatim Turbo (Nx supports the same forms plus a
69
- richer object form).
70
-
71
- | Form | Meaning | Source | vx source |
72
- | ------------ | ----------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
73
- | `'lint'` | Same project, other task | Turbo + Nx | `src/graph/dependency-spec.ts` ("Turbo/Nx-style") |
74
- | `'^lint'` | Same task in workspace dependencies | Turbo + Nx | `src/graph/dependency-spec.ts` |
75
- | `'pkg#lint'` | Arbitrary other package's task | Turbo + Nx | `src/graph/dependency-spec.ts` |
76
- | `'*' / '^*'` | Wildcard upstream (filter-only) | Turbo's `$TURBO_DEFAULT$`-adjacent | `src/graph/dependency-spec.ts` ("filter-only"), `src/orchestrator/upstream.ts` ("like Turbo/Nx") |
77
- | `'!<form>'` | Negation (filter-only) | Turbo `inputs` exclusion | `src/graph/dependency-spec.ts` ("filter-only") |
78
-
79
- Graph construction rejects bare wildcards / negation in `dependsOn`
80
- itself (`src/graph/task-graph.ts`) — they're filter-only, as the
81
- parser's docblock marks them (`src/graph/dependency-spec.ts`).
82
-
83
- ### Filter DSL + selection
84
-
85
- | Capability | Source | vx source |
86
- | ------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------ |
87
- | pnpm-style `--filter` (`pkg`, `pkg...`, `...pkg`, path globs) | Turbo + pnpm | `src/workspace/filter.ts` |
88
- | Transitive-dep expansion (`pkg...`) | Turbo + pnpm | `src/workspace/filter.ts` (the DSL comment) |
89
- | `[<since>]` git-relative selection | Turbo | `src/workspace/filter.ts` ("Turbo-style"), `src/workspace/affected.ts` ("Matches Turbo's") |
90
- | `--affected[=<base>]` flag on `vx run` | Turbo + Nx | `src/workspace/affected.ts` |
91
- | `pkg#task` direct addressing | Turbo + Nx | `src/cli/run.ts` |
92
-
93
- ### Workspace discovery
94
-
95
- | Capability | Source | vx source |
96
- | ----------------------------------------------------- | ------------ | --------------------------------- |
97
- | Read `pnpm-workspace.yaml` (adoption) | pnpm + Turbo | `@vzn/vx-migrate`'s `packages.ts` |
98
- | Read `package.json` `workspaces` (adoption) | Turbo | `@vzn/vx-migrate`'s `packages.ts` |
99
- | Build package-dep graph from workspace `dependencies` | Turbo + Nx | `src/workspace/package-graph.ts` |
100
- | Lockfile fingerprint at workspace level | Turbo + Nx | `src/workspace/fingerprint.ts` |
101
-
102
- ### Cache topology
103
-
104
- | Layer | Turbo | Nx | vx | vx source |
105
- | ---------------------------------------- | ---------------------------------- | ------------------ | -------------------------------------------------------------- | -------------------------------------------------------------------- |
106
- | Local: content-addressed artifacts | tarball-per-hash in `.turbo/cache` | `.nx/cache` SQLite | SQLite index + `<hash>.tar.zst`, shared in `~/.vx/<id>/cache/` | `src/cache/cache.ts` |
107
- | Local: skip-restore when tree is current | yes (fingerprint check) | yes | yes — `isOutputsCurrent` stat check → up-to-date | `src/cache/output-index.ts` |
108
- | Read-through then write-through layering | yes | yes | yes | `src/cache/layered-cache.ts` |
109
- | Run-history table for analytics | (no — `--summarize` JSON) | (Nx Cloud) | `runs` + `invocations` tables in `cache.db` | `src/cache/run-history.ts` (tables created in `src/cache/schema.ts`) |
110
-
111
- ### Remote cache wire
112
-
113
- The remote cache is **plugin-driven** (owner directive 2026-07-10): core keeps the seams
114
- (`LayeredCache` + the `RemoteCacheLayer` interface + the `cache`
115
- plugin capability), and a plugin ships the wire — `@vzn/vx-reapi` speaks
116
- Bazel's ActionCache + CAS, so any REAPI server works as a remote cache.
117
- Turbo `/v8/artifacts` compatibility was
118
- dropped from core — `turboCache()` in `@vzn/vx-migrate` is that wire as a plugin;
119
- the recipe for any other lives in the plugins guide.
120
-
121
- ### Tar artifact format
122
-
123
- Inside the tar we depart from Turbo (we don't mimic Turbo's
124
- `.turbo/turbo-<task>-<project>.log` file naming — our metadata lives in
125
- SQLite). But the **on-the-wire framing** stays POSIX-tar so any
126
- Turbo-aware cache server can transit our blobs unchanged.
127
-
128
- | Pattern | Source | vx source |
129
- | ----------------------------------------- | --------------------------------------------- | ---------------------------------------- |
130
- | POSIX tar framing, in-process pack | Turbo (Rust `tar` crate, `Header::new_gnu()`) | `src/cache/archive.ts` (`tar-stream.ts`) |
131
- | No AppleDouble `._*` companions | Turbo (in-process writer, no recurse) | `src/cache/archive.ts` (no `tar` spawn) |
132
- | Entry-name validation on the extract side | (vx-only defense-in-depth) | `src/cache/archive.ts` |
133
- | zstd compression on the wire | Turbo | `Bun.zstdCompress` (tar.zst artifacts) |
134
-
135
- ### Scheduler + execution
136
-
137
- | Pattern | Source | vx source |
138
- | ------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------- |
139
- | Topological order, bounded parallelism | Turbo + Nx | `src/graph/scheduler.ts` |
140
- | Cascade skip: failed task's transitive dependents are skipped | Turbo (mid-mode) + Nx | `src/graph/scheduler.ts` |
141
- | Independent siblings continue past failure | Turbo `--continue=dependencies-successful` | `src/graph/scheduler.ts` |
142
- | Persistent / long-running tasks (dev servers) | Turbo `persistent`, Nx `continuous` | `src/exec/runner.ts` (`runPersistent`) + `src/orchestrator/execute-task.ts` |
143
- | Project-local `node_modules/.bin` on PATH | Turbo + pnpm | `src/exec/env.ts` (`binPaths`) |
144
- | Project-`package.json` invalidation, by a plugin | Turbo (via lockfile) + Nx (`externalDependencies`) | `@vzn/vx-lockfile` (its `project` stage lists it as an input) |
145
-
146
- ### Output handling
147
-
148
- | Pattern | Source | vx source |
149
- | ------------------------------------------------------------------ | ---------- | ------------------------------------------------ |
150
- | Glob-based `outputs` declaration | Turbo + Nx | `src/config.ts`, `src/cache/inputs.ts` |
151
- | Restore by file (archive extract, not symlink/hardlink) | Turbo + Nx | `src/cache/archive.ts` (`extractArtifactStream`) |
152
- | Log replay on cache hit | Turbo + Nx | `src/orchestrator/hit-restore.ts` |
153
- | **Wipe outputs before exec AND before restore** (strict ownership) | (vx-only) | `src/cache/inputs.ts` (`cleanOutputs`) |
154
-
155
- ### CLI conventions
156
-
157
- | Flag / behavior | Source | vx source |
158
- | ---------------------------------------------------------------------------------- | -------------- | ----------------------------------- |
159
- | `--` separator forwards args to task | Turbo | `src/cli/run.ts` |
160
- | `--filter` DSL (pnpm shape) | Turbo + pnpm | `src/workspace/filter.ts` |
161
- | `--concurrency <n>` | Turbo | `src/cli/run.ts` |
162
- | `--no-cache` (all off) / `--force` (reads off, writes on) — distinct, not synonyms | Turbo-adjacent | `src/cli/run.ts` |
163
- | `--dry` / `--dry=json` (plan output) | Turbo | `src/orchestrator/plan.ts` |
164
- | `--profile[=<path>]` (Chrome-trace) | Turbo | `src/orchestrator/run-artifacts.ts` |
165
- | `--affected[=<base>]` | Turbo + Nx | `src/workspace/affected.ts` |
166
- | `watch <task>` subcommand | Turbo + Nx | `src/cli/watch.ts` |
167
-
168
- ### Output presentation
169
-
170
- | Pattern | Source | vx source |
171
- | ---------------------------------------------------- | ------------------------------------------------ | --------------------------------------------- |
172
- | Framed per-task blocks (`┌─ … └─`) | Turbo | `src/report/framed-output.ts` |
173
- | End-of-run summary footer (task/cache meters + time) | Turbo-adjacent (vx renders meters, not a banner) | `src/report/summary.ts` |
174
- | Per-task output buffered until task finishes | Turbo | `src/report/framed-output.ts` ("Turbo-style") |
175
-
176
- (There is no `>>> FULL TURBO`-style all-cached banner — a fully-green
177
- cache meter carries the message.)
178
-
179
- ## Performance
180
-
181
- Sharing the patterns doesn't mean sharing the overhead. On the
182
- 476-package / 1,428-node synthetic workspace
183
- (`packages/vx-bench/compare.ts 20 25 1`, 2026-09-02):
184
-
185
- | Runner | Warm (no restore) | Warm (restore) |
186
- | ------ | ----------------- | -------------- |
187
- | **vx** | **297 ms** | **416 ms** |
188
- | Turbo | 342 ms | 612 ms |
189
- | Nx | 1.38 s | 1.33 s |
190
-
191
- A cached run is all runner overhead. Cold builds are compared by the
192
- time the runner adds, on [`benchmarks.md`](./benchmarks.md).
193
-
194
- Full breakdown + methodology in [`benchmarks.md`](./benchmarks.md).
195
-
196
- ## Where the lineage diverges
197
-
198
- For features Turbo or Nx have that vx **lacks**, see
199
- [`comparison.md` § Gaps](./comparison.md#gaps-for-vznvx-the-running-list).
200
-
201
- For places vx made a deliberately different call (TypeScript config,
202
- resolved-config hash, strict output ownership, no daemon, no TUI), see
203
- [`comparison.md` § Where vx is ahead](./comparison.md#where-vx-is-ahead)
204
- and [`README.md`](./README.md#the-problems).
205
-
206
- ## Quick citation index
207
-
208
- Every "Turbo" / "Nx parity" claim above is anchored in a comment in
209
- the vx source. To audit:
210
-
211
- ```sh
212
- grep -rn "Turbo\|Nx parity\|turborepo" src/
213
- ```
214
-
215
- Each line is a deliberate decision to mirror an upstream pattern, and
216
- every phrase this page quotes beside a source path is held to that
217
- file by `tests/patterns-doc-drift.test.ts`. Adding to that list is preferable to inventing
218
- new vocabulary — vx's value is in the swaps, not in renaming the
219
- parts we kept.