@vzn/vx 0.0.638 → 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.
- package/docs/caching.md +44 -15
- package/docs/cli.md +168 -345
- package/docs/execution.md +18 -43
- package/docs/features.md +7 -10
- package/docs/patterns.md +5 -7
- package/docs/schema.md +47 -49
- package/package.json +5 -5
- package/schemas/last.json +7 -4
- package/skills/vx/SKILL.md +6 -4
- package/src/cache/cache.ts +40 -10
- package/src/cache/git-inputs.ts +6 -0
- package/src/cache/index.ts +2 -2
- package/src/cache/layer.ts +25 -11
- package/src/cache/layered-cache.ts +108 -315
- package/src/cache/plugin-cache.ts +2 -10
- package/src/cache/remote-lane.ts +383 -0
- package/src/cache/run-history.ts +24 -8
- package/src/cache/run-store.ts +4 -4
- package/src/cache/schema.ts +6 -4
- package/src/cache/scoped-cache.ts +7 -0
- package/src/cli/completions.ts +2 -2
- package/src/cli/foreign-flags.ts +10 -22
- package/src/cli/help.ts +6 -31
- package/src/cli/info.ts +2 -1
- package/src/cli/last.ts +6 -3
- package/src/cli/plan-format.ts +5 -58
- package/src/cli/plugin-commands.ts +2 -2
- package/src/cli/run.ts +23 -185
- package/src/cli/show.ts +6 -0
- package/src/cli/upgrade.ts +123 -39
- package/src/cli/watch-cycle.ts +2 -1
- package/src/cli/watch.ts +29 -14
- package/src/cli/why.ts +4 -4
- package/src/config.ts +0 -6
- package/src/exec/executor.ts +1 -1
- package/src/exec/sandbox-runtime.ts +1 -1
- package/src/exec/sandbox-violations.ts +3 -1
- package/src/graph/index.ts +9 -1
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +4 -16
- package/src/graph/task-graph.ts +87 -17
- package/src/graph/task-status.ts +95 -0
- package/src/{orchestrator → history}/failure-mode.ts +1 -1
- package/src/{orchestrator → history}/history.ts +1 -1
- package/src/history/index.ts +48 -0
- package/src/{orchestrator → history}/metrics.ts +7 -20
- package/src/{orchestrator → history}/miss-reason.ts +1 -1
- package/src/index.ts +10 -28
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/execute-args.ts +80 -0
- package/src/orchestrator/execute-task.ts +21 -386
- package/src/orchestrator/fingerprint-watch.ts +2 -2
- package/src/orchestrator/index.ts +1 -73
- package/src/orchestrator/miss-save.ts +33 -3
- package/src/orchestrator/options.ts +1 -36
- package/src/orchestrator/output-host.ts +4 -3
- package/src/orchestrator/persistent-task.ts +321 -0
- package/src/orchestrator/persistent.ts +130 -3
- package/src/orchestrator/placement.ts +3 -3
- package/src/orchestrator/plan.ts +18 -5
- package/src/orchestrator/plugin-host.ts +67 -47
- package/src/orchestrator/plugin.ts +6 -6
- package/src/orchestrator/prepare.ts +28 -29
- package/src/orchestrator/remote-prefetch.ts +14 -1
- package/src/orchestrator/run-artifacts.ts +5 -177
- package/src/orchestrator/run-context.ts +55 -0
- package/src/orchestrator/run-failures.ts +15 -3
- package/src/orchestrator/run-records.ts +35 -3
- package/src/orchestrator/run-report.ts +81 -179
- package/src/orchestrator/run-settings.ts +1 -16
- package/src/orchestrator/run-stop.ts +153 -0
- package/src/orchestrator/run.ts +260 -601
- package/src/orchestrator/sandbox-fix.ts +0 -16
- package/src/orchestrator/signals.ts +14 -6
- package/src/orchestrator/stable-keys.ts +107 -3
- package/src/orchestrator/steps.ts +50 -0
- package/src/orchestrator/task-hash.ts +3 -16
- package/src/orchestrator/task-log-buffer.ts +1 -1
- package/src/orchestrator/task-process.ts +94 -0
- package/src/orchestrator/telemetry-host.ts +1 -1
- package/src/orchestrator/telemetry.ts +5 -82
- package/src/{orchestrator → report}/events.ts +41 -8
- package/src/{orchestrator → report}/forecast.ts +35 -2
- package/src/{orchestrator → report}/framed-output.ts +39 -33
- package/src/report/index.ts +36 -0
- package/src/{orchestrator → report}/logger.ts +158 -20
- package/src/{orchestrator → report}/status-line.ts +84 -7
- package/src/{orchestrator → report}/summary.ts +126 -35
- package/src/report/tally.ts +83 -0
- package/src/settings.ts +1 -22
- package/src/util/colors.ts +44 -4
- package/src/util/git-ignored.ts +143 -0
- package/src/util/index.ts +19 -4
- package/src/util/report.ts +13 -5
- package/src/util/settle.ts +14 -39
- package/src/util/tail.ts +1 -1
- package/src/util/ui.ts +17 -6
- package/schemas/summary.json +0 -133
- package/src/cache/chained-cache.ts +0 -280
- package/src/orchestrator/tally.ts +0 -112
- /package/src/{orchestrator → report}/path-links.ts +0 -0
- /package/src/{orchestrator → report}/plain-output.ts +0 -0
- /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/docs/cli.md
CHANGED
|
@@ -64,7 +64,7 @@ included.
|
|
|
64
64
|
|
|
65
65
|
**Every requested name must resolve.** If any positional matches no
|
|
66
66
|
project in scope, the run refuses to start — `vx run: no projects
|
|
67
|
-
declare task(s): <name>.` on stderr, a run and `--dry`
|
|
67
|
+
declare task(s): <name>.` on stderr, a run and `--dry` alike
|
|
68
68
|
(a run printed it to stdout until 2026-10-03), exit 1, with `Did you mean <task>?` when a
|
|
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
|
|
@@ -124,20 +124,21 @@ If no task name is given:
|
|
|
124
124
|
`missing task name (stdin is not a TTY, so no picker; tasks here: build, test)`,
|
|
125
125
|
naming the cwd project's tasks, else every project's (twelve, then
|
|
126
126
|
`and N more`); outside a workspace it reads `vx run <task>, e.g. vx run build`.
|
|
127
|
-
- **Under `--
|
|
127
|
+
- **Under `--dry=json`**, on a terminal too — the
|
|
128
128
|
same refusal (`a JSON answer opens no picker; …`), with the
|
|
129
129
|
`VX_E_USAGE` error document on stdout: a program cannot pick.
|
|
130
130
|
|
|
131
131
|
Exit codes:
|
|
132
132
|
|
|
133
|
-
| Code | When
|
|
134
|
-
| --------------------- |
|
|
135
|
-
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task.
|
|
136
|
-
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error.
|
|
137
|
-
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM),
|
|
133
|
+
| Code | When |
|
|
134
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
135
|
+
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task. |
|
|
136
|
+
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error. |
|
|
137
|
+
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM), a 2 s grace to go, then SIGKILL; a second signal skips the grace. vx then dies of the signal, so a shell script running it stops too (a run that handed a task the terminal exits with the code, so the terminal is restored). |
|
|
138
138
|
|
|
139
139
|
A task runs in its own session, so a terminal's Ctrl-C reaches vx alone,
|
|
140
|
-
and each task hears it once: from vx, as SIGINT.
|
|
140
|
+
and each task hears it once: from vx, as SIGINT. A stop starts no more remote
|
|
141
|
+
cache uploads and does not wait on one in flight.
|
|
141
142
|
|
|
142
143
|
Installed from npm, `vx` is a sh launcher that execs the platform
|
|
143
144
|
binary, so every signal reaches vx directly and no Node starts first
|
|
@@ -480,34 +481,23 @@ than run with the args unheard.
|
|
|
480
481
|
| `--schedule <rule>` | value | `run.schedule`, else `most-work` | Which ready task starts first when more are ready than workers: `most-work`, `critical-path`, `direct-dependents`, `ready-order` (`docs/schema.md` § `run.schedule`). |
|
|
481
482
|
| `--branch <name>` | value | `VX_RUN_BRANCH`, else git's branch, then the CI provider's | The branch the run records in its history and forecasts from. No config key: a committed branch would mislabel every other branch's runs. |
|
|
482
483
|
| `--no-cache` | boolean | off | Disable caching entirely (no reads, no writes); output globs are NOT cleaned. |
|
|
483
|
-
| `--force` | boolean | off | Re-execute everything (skip cache reads)
|
|
484
|
+
| `--force` | boolean | off | Re-execute everything (skip cache reads); writes stay on. A key already stored is kept, and a remote that holds it is not sent it again. Output globs are cleaned (so the saved snapshot is clean). |
|
|
484
485
|
| `--cache-policy <spec>` (`--cache-policy`) | value | `VX_CACHE_POLICY` / `cache.policy` | Per-layer read/write control. See below. An EMPTY spec (`--cache-policy=`) is a parse error — it applied nothing and left every axis on; pass `--no-cache` to disable them all. |
|
|
485
486
|
| `--cache-scope <name>` | value | `VX_CACHE_SCOPE` / workspace `cache.scope` | This run's `cache.scope` (`pr-123`), above `VX_CACHE_SCOPE`, `.vx/workspace`'s and `github()`'s. A name `cache.scope` would refuse is refused. `--cache-scope=<name>` form too. Never folded into a cache key. |
|
|
486
487
|
| `--cache-dir <path>` | value | `VX_CACHE_DIR` / `cache.dir` / `~/.vx/<id>/cache` | Cache directory override, resolved relative to cwd (absolute paths used as-is). Beats `VX_CACHE_DIR`, the workspace's `cache.dir` and the default; like them, the directory then holds the whole cache, shared through no store, for every cache the run opens, so the workspace's default dir is not created beside it. A per-run knob — never folded into a cache key. `--cache-dir=<path>` form too; the space form rejects a value starting with `-`. A directory this user cannot write into fails the run before any task with `cache directory <path> is not writable (EACCES: …)` — every run records its history there. |
|
|
487
488
|
| `--retries <n>` | value | `run.retries`, else `0` | Re-run a failed task up to `n` more times; never a persistent one. A task's own `exec.retries` wins (even an explicit `0`). Never affects cache keys. `--retries=<n>` form too. |
|
|
488
489
|
| `--continue[=<mode>]` | value | `deps-ok` | What a failed task takes down with it. `never` stops dispatch on the first failure; `deps-ok` (default) skips only its dependents; `always` (bare `--continue`) runs dependents anyway. See § Failure propagation. |
|
|
489
490
|
| `--timeout <duration>` | duration | `run.timeout`, else none | A task's time limit (`30s`, `10m`), for tasks without their own `exec.timeout`, which always wins. A runaway task is killed + `failed`. A bare number is refused naming the forms with units. Never affects cache keys. `--timeout=<duration>` form too. |
|
|
490
|
-
| `--kill-grace <duration>` | duration | `run.killGrace`, else `2s` | How long a task that ignores SIGTERM gets before SIGKILL: on a timeout, on a signal, and at the end-of-run shutdown of persistent tasks. |
|
|
491
|
-
| `--teardown-timeout <duration>` | duration | `run.teardownTimeout`, else `3s` | The bound on each plugin's end-of-run flush or teardown, and on its `telemetry()` setup, so a third party's I/O cannot hold the run. |
|
|
492
|
-
| `--ready-notice <duration>` | duration | `run.readyNotice`, else `10s` | How long a persistent task may take to match `readyWhen` before vx says once what it waits for. |
|
|
493
491
|
| `--output-logs <mode>` | value | `VX_RUN_OUTPUT_LOGS`, else flow-derived | `full` \| `errors-only` \| `hash-only` \| `none` — explicit output override. See § `--output-logs`. `--output-logs=<mode>` form too. |
|
|
494
492
|
| `--cache-download <mode>` | value | `cache.download`, else `all` | `all` \| `toplevel` \| `none` — where a REMOTELY-executed task's outputs land. `none` leaves them in the remote CAS and fetches lazily, only when a locally-placed task needs them. Never affects cache keys. See § `--cache-download`. `--cache-download=<mode>` form too. |
|
|
495
493
|
| `--verbosity <n>` | int (0+) | `0` | `1` or more prints a per-task table (groups left out) after the framed blocks, above the footer. `--verbosity=<n>` form too. |
|
|
496
494
|
| `--dry[=text\|json]` | optional value | off | Print the task graph + predicted cache hit/miss; skip execution. `VX_DEBUG_TIMING=1` prints the stage table here as it does for a run. |
|
|
497
|
-
| `--graph[=<path>]` | optional value | off | Emit Graphviz DOT (stdout if no path, its directory made if missing); skip execution. A path it cannot write is one line and exit 1. |
|
|
498
|
-
| `--format <fmt>` | value | `pretty` | `json` prints the `--summarize` document on stdout when the run ends and moves every other line to stderr ([Machine-readable output](#machine-readable-output)). `--format=<fmt>` form too. |
|
|
499
|
-
| `--summarize[=<path>]` | optional value | off | Write per-run JSON to `<cacheDir>/runs/<run_id>.json` (or the explicit path). |
|
|
500
495
|
| `--profile[=<path>]` | optional value | off (`profile.json` when set) | Write Chrome-trace JSON of the run's wallclock spans. |
|
|
501
|
-
| `--tag <k=v>` | repeatable | (none) | Label this invocation. Recorded on the run's `invocations` row so dashboards can filter runs. `--tag=k=v` form too. |
|
|
502
|
-
| `--report[=markdown]` | optional value | off | At the end of the run, print a markdown run report to stdout, above the footer. Only `markdown` is supported (`json` is reserved). |
|
|
503
|
-
| `--report-file <path>` | value | off | After the run, APPEND the same markdown report to `<path>`, making its directory as the other output paths do. Use this for `$GITHUB_STEP_SUMMARY` — redirecting stdout captures the whole run log too. `--report-file=<path>` form too. |
|
|
504
496
|
|
|
505
497
|
Mutual exclusion:
|
|
506
498
|
|
|
507
|
-
- `--dry`
|
|
508
|
-
|
|
509
|
-
`--report-file` — each needs a real run to write about. `--report` and
|
|
510
|
-
`--report-file` were accepted and silently wrote nothing until item 992.
|
|
499
|
+
- `--dry` with `--profile` — the profile needs a real run to write
|
|
500
|
+
about.
|
|
511
501
|
|
|
512
502
|
Unknown flags are a parse error (`unknown flag: --foo`), naming the
|
|
513
503
|
nearest flag the verb accepts when one is within two edits
|
|
@@ -531,13 +521,10 @@ name is the verb, and would change what `vx build` means the day one
|
|
|
531
521
|
was declared.
|
|
532
522
|
|
|
533
523
|
**Optional-value flags take their value with `=` only.** `--affected`,
|
|
534
|
-
`--exclude-dependencies`, `--dry
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
`build`, affected scope", and there is no way to tell that apart from
|
|
539
|
-
"`build` is the git base". Write `--graph=out.dot` or
|
|
540
|
-
`--affected=origin/main`. Getting it wrong is loud, not silent: the value
|
|
524
|
+
`--exclude-dependencies`, `--dry` and `--profile` are all valid bare,
|
|
525
|
+
so a following word is read as a task name — `vx run --affected build`
|
|
526
|
+
means "run `build`, affected scope", and there is no way to tell that
|
|
527
|
+
apart from "`build` is the git base". Write `--affected=origin/main`. Getting it wrong is loud, not silent: the value
|
|
541
528
|
becomes a positional that matches no project, so the run refuses to
|
|
542
529
|
start (see "Every requested name must resolve" above).
|
|
543
530
|
|
|
@@ -547,9 +534,9 @@ Pass bare `--exclude-dependencies` for the first, omit the flag for the
|
|
|
547
534
|
second.
|
|
548
535
|
|
|
549
536
|
Value flags (`--filter`, `--concurrency`, `--output-logs`,
|
|
550
|
-
`--verbosity`, `--cache-dir`,
|
|
537
|
+
`--verbosity`, `--cache-dir`, …)
|
|
551
538
|
accept both `--flag value` and `--flag=value`. In the space form,
|
|
552
|
-
`--cache-dir`
|
|
539
|
+
`--cache-dir` rejects a value
|
|
553
540
|
starting with `-`: that is always a swallowed flag (an unquoted empty
|
|
554
541
|
shell variable), never a path or task id. Use the `=` form for a literal
|
|
555
542
|
leading dash.
|
|
@@ -559,15 +546,13 @@ leading dash.
|
|
|
559
546
|
fractional (`2.7`), signed (`+4`) and space-padded forms, plus anything
|
|
560
547
|
past `2^53` (it would parse to a number you did not type). These all
|
|
561
548
|
used to be silently reinterpreted — `--concurrency 0x10` ran 16
|
|
562
|
-
workers. A duration flag (`--timeout`,
|
|
563
|
-
`--teardown-timeout`, `--ready-notice`) takes its unit (`500ms`, `30s`,
|
|
549
|
+
workers. A duration flag (`--timeout`) takes its unit (`500ms`, `30s`,
|
|
564
550
|
`10m`): a bare `500` is refused, naming both readings.
|
|
565
551
|
|
|
566
|
-
An empty `=` value means "no value" on
|
|
567
|
-
their bare forms: `--profile=` writes `profile.json
|
|
568
|
-
writes `<cacheDir>/runs/<run_id>.json`, `--graph=` prints to stdout and
|
|
552
|
+
An empty `=` value means "no value" on two flags, which then act like
|
|
553
|
+
their bare forms: `--profile=` writes `profile.json` and
|
|
569
554
|
`--affected=` uses the default base. Every other flag refuses an empty
|
|
570
|
-
value: `--dry=`, `--
|
|
555
|
+
value: `--dry=`, `--continue=` and
|
|
571
556
|
`--exclude-dependencies=` as well as the ones with no bare form
|
|
572
557
|
(`--retries=`, `--timeout=`, `--cache-dir=`, `--filter=`, `--cache-policy=`).
|
|
573
558
|
|
|
@@ -602,10 +587,13 @@ So `--no-cache` always wins over `--force`. The common cases:
|
|
|
602
587
|
- `--no-cache` → nothing reads, nothing writes, and declared output
|
|
603
588
|
globs are left untouched (you're debugging; vx won't manage your
|
|
604
589
|
tree).
|
|
605
|
-
- `--force` → re-execute every task (reads off)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
590
|
+
- `--force` → re-execute every task (reads off) with writes on. An
|
|
591
|
+
entry is immutable: a key the local cache already holds is kept, and
|
|
592
|
+
each remote that can batch (`hasMany`) is asked once, in the
|
|
593
|
+
background, which keys it holds; one that cannot is asked `has` per
|
|
594
|
+
key before its upload. Held keys are not uploaded again (a probe that
|
|
595
|
+
fails uploads, as before). Output globs ARE cleaned before each task,
|
|
596
|
+
so the disk holds this run's outputs.
|
|
609
597
|
|
|
610
598
|
`--cache-policy=<spec>` is a comma-separated list of `<layer>:<flags>`
|
|
611
599
|
segments. `layer` is `local` or `remote`; `flags` is any subset of `r`
|
|
@@ -625,8 +613,9 @@ layer keeps its current value. Both `--cache-policy=<spec>` and the space form
|
|
|
625
613
|
a remote hit is still delivered (the artifact has to land on disk to be
|
|
626
614
|
extracted), it just never short-circuits the remote read.
|
|
627
615
|
|
|
628
|
-
Combine with `--force` for "re-execute and
|
|
629
|
-
`--cache-policy=local: --force` (local off, reads off, remote write-only
|
|
616
|
+
Combine with `--force` for "re-execute and fill only the remote":
|
|
617
|
+
`--cache-policy=local: --force` (local off, reads off, remote write-only;
|
|
618
|
+
keys the remote already holds are not sent).
|
|
630
619
|
|
|
631
620
|
Invalid layers/flags are a parse error
|
|
632
621
|
(`--cache-policy: invalid layer 'disk'`, `--cache-policy: invalid flag 'x'`, …);
|
|
@@ -691,8 +680,7 @@ What the shapes mean in each column:
|
|
|
691
680
|
everything, the blocker included (`⊘ skipped app#deploy • blocked by
|
|
692
681
|
lib#build`; a fail-fast skip, which nothing blocked, carries no
|
|
693
682
|
suffix). Every view that lists tasks prints it, a dependency's
|
|
694
|
-
included: nothing prints below the footer.
|
|
695
|
-
`skipped (blocked by lib#build)`.
|
|
683
|
+
included: nothing prints below the footer.
|
|
696
684
|
- **`frame, or one-liner if quiet`** — a cache hit with stored stdout is
|
|
697
685
|
worth a frame (the output is the point); a hit with nothing to replay
|
|
698
686
|
compresses to one line, which is what keeps a 2000-task warm run
|
|
@@ -746,7 +734,7 @@ tracks the run live. Top to bottom:
|
|
|
746
734
|
persistent tasks it depends on, until it — or, with several, the
|
|
747
735
|
first of them — exits (one kept only as a dependency that exits 0,
|
|
748
736
|
a daemon that forked and returned, does not end it); the rest still up are then torn
|
|
749
|
-
down (SIGTERM,
|
|
737
|
+
down (SIGTERM, a 2 s grace, SIGKILL), one status line names
|
|
750
738
|
the task and its code (`vx: app#dev exited with code 1; stopping 1
|
|
751
739
|
other persistent task`), and a non-zero exit makes the run exit 1
|
|
752
740
|
and is what `vx last` records.
|
|
@@ -762,16 +750,39 @@ other persistent task`), and a non-zero exit makes the run exit 1
|
|
|
762
750
|
3. **Worker rows** — one per worker slot that has been busy (at most
|
|
763
751
|
`min(concurrency, 10)`), no glyph and no spinner: the live ticking
|
|
764
752
|
elapsed time leads (` 568ms running <id>`). A task stays in
|
|
765
|
-
its row for its whole life
|
|
766
|
-
|
|
767
|
-
|
|
753
|
+
its row for its whole life, so nothing jumps; overflow shows as
|
|
754
|
+
`+k more`. A second in, an idle worker row holds a task an
|
|
755
|
+
unfinished dependency blocks, one per row, in the order the
|
|
756
|
+
scheduler starts them (most dependents first):
|
|
757
|
+
`blocked solid-js#build by solid-js#types` (dim `blocked`, the id,
|
|
758
|
+
then the running task that holds it back). The task starts in the
|
|
759
|
+
row that held it. A row with no blocked task left to hold leaves the
|
|
760
|
+
region and the rows below close up; blocked tasks only dwindle, so
|
|
761
|
+
it never comes back. A running row says what waits on it:
|
|
762
|
+
`critical path` (yellow) on the running task
|
|
768
763
|
the longest unfinished chain waits on, by the same p50s as the
|
|
769
764
|
forecast below and only while two or more run, and a dim
|
|
770
765
|
`blocks N`, its unfinished dependents direct and transitive. Where
|
|
771
766
|
local history knows the task, its row also shows its usual duration
|
|
772
767
|
(its executed p50) in the cache column, fixed while the elapsed time
|
|
773
768
|
counts up toward it (`3.33s running ~5.2s solid-js#types`).
|
|
774
|
-
4. **
|
|
769
|
+
4. **vx's own steps** — what vx does around the tasks, as rows of
|
|
770
|
+
their own below the worker rows: `vx:load` (configs and graph from
|
|
771
|
+
the lock), `vx:run-lock`, `<plugin>:setup`, `vx:keys` (keys and local
|
|
772
|
+
probes), `vx:prefetch`, `vx:schedule`, then after the graph
|
|
773
|
+
`vx:uploads`, `vx:history`, `vx:keep-run`, `vx:telemetry`,
|
|
774
|
+
`<plugin>:teardown`, `vx:snapshots`, `vx:retention`, `vx:close` and
|
|
775
|
+
`vx:sandbox`. A step shows once it has run 150 ms, ticking like a
|
|
776
|
+
task (` 1.2s running vx:uploads`); no worker slot or
|
|
777
|
+
`--concurrency` holds it back. When it ends it leaves a list row
|
|
778
|
+
like a task's, with no cache column (`⏺︎ 1.2s success vx:uploads`);
|
|
779
|
+
a quicker one shows nowhere. `:` is no task id's separator, so a
|
|
780
|
+
step never reads as a task: a project named `vx` keeps `vx#build`.
|
|
781
|
+
No step is a task: no count, bar, history row, forecast, flaky
|
|
782
|
+
verdict, `vx last` row or telemetry record holds one; `--profile`
|
|
783
|
+
and `VX_DEBUG_TIMING` time vx's stages their own way. Steps show only
|
|
784
|
+
beside a live region; a log (CI, a pipe) prints none.
|
|
785
|
+
5. **The live summary section** — the SAME meters the final footer
|
|
775
786
|
prints (`tasks` + `cache` bars with legends, `time`), filling in as
|
|
776
787
|
the run progresses, under a bare `vx` wordmark rule. A second in,
|
|
777
788
|
the `time` row adds what is left (`time 1.41s · ~3s left`): each
|
|
@@ -814,11 +825,11 @@ redraws within 30 ms of the last draw coalesce into a single
|
|
|
814
825
|
trailing draw when the floor expires (the final state always lands).
|
|
815
826
|
On a 3,270-task warm run this cuts ~6.7 MB of redraw ANSI to ~20 KB.
|
|
816
827
|
|
|
817
|
-
Identity coloring:
|
|
818
|
-
|
|
819
|
-
in every run and every surface
|
|
820
|
-
|
|
821
|
-
read as an outcome.
|
|
828
|
+
Identity coloring: each half of a `project#task` renders in a stable
|
|
829
|
+
hue hashed from its own name (same project = same color, same target
|
|
830
|
+
name = same color, in every run and every surface: every `#build`
|
|
831
|
+
matches), with a dim `#` between, all outside the status palette, so
|
|
832
|
+
an id can never read as an outcome.
|
|
822
833
|
|
|
823
834
|
On GitHub Actions (`GITHUB_ACTIONS` truthy, full output mode), each
|
|
824
835
|
task's block is wrapped in `::group::<id> (<outcome> <duration>)` /
|
|
@@ -903,9 +914,9 @@ not read a deferred output (the same reason vx refuses to infer inputs
|
|
|
903
914
|
by tracing). When bytes are fetched later, vx saves an ordinary
|
|
904
915
|
cache entry for them, so the next run is a plain local hit.
|
|
905
916
|
|
|
906
|
-
## Planning mode (`--dry
|
|
917
|
+
## Planning mode (`--dry`)
|
|
907
918
|
|
|
908
|
-
|
|
919
|
+
`--dry` short-circuits execution. It builds the full task graph,
|
|
909
920
|
compute every task's cache key, and probe the cache to predict the
|
|
910
921
|
hit/miss outcome. Against a remote cache the probe is a lightweight
|
|
911
922
|
existence check — planning never downloads or ingests artifacts.
|
|
@@ -913,8 +924,8 @@ existence check — planning never downloads or ingests artifacts.
|
|
|
913
924
|
```
|
|
914
925
|
$ vx run ci --dry
|
|
915
926
|
would run:
|
|
916
|
-
|
|
917
|
-
|
|
927
|
+
✓ @vzn/vx#format-check cache hit (local) 02bfe8a9
|
|
928
|
+
✓ @vzn/vx#lint cache hit (local) d66cfed2
|
|
918
929
|
▶ @vzn/vx#test cache miss — would exec 68595e49 ~72.64s
|
|
919
930
|
|
|
920
931
|
3 task(s) planned, 2 cache hits (2 local), 1 would run.
|
|
@@ -947,11 +958,11 @@ Status legend:
|
|
|
947
958
|
|
|
948
959
|
| Symbol | Meaning |
|
|
949
960
|
| ------ | ------------------------------------------------------------------------------------------ |
|
|
950
|
-
|
|
|
961
|
+
| `✓` | cache hit (local) — entry already in `<cacheDir>/` |
|
|
951
962
|
| `↓` | cache hit (remote) — entry would be fetched from the layer |
|
|
952
963
|
| `▶` | cache miss — task would execute (under `--force` too: nothing is read, what runs is saved) |
|
|
953
|
-
|
|
|
954
|
-
|
|
|
964
|
+
| `∅` | no-cache — task opts out (no `cache` block, or `--no-cache`) |
|
|
965
|
+
| `·` | `@noop` — task would not run anywhere |
|
|
955
966
|
| `−` | not started — a server you did not request whose every dependant restores a local hit |
|
|
956
967
|
| `○` | group task (suppressed in human view; in DOT + JSON) |
|
|
957
968
|
|
|
@@ -1013,150 +1024,12 @@ against: a bare `--affected` guesses it (`origin/HEAD`, a trunk branch,
|
|
|
1013
1024
|
else `HEAD~1`), so a reason that names no file may come from a commit
|
|
1014
1025
|
the base reaches, a root `package.json` edit among them.
|
|
1015
1026
|
|
|
1016
|
-
|
|
1027
|
+
## Run artifacts (`--profile`)
|
|
1017
1028
|
|
|
1018
|
-
`--
|
|
1019
|
-
writes a file):
|
|
1020
|
-
|
|
1021
|
-
```
|
|
1022
|
-
vx run ci --graph | dot -Tsvg > graph.svg
|
|
1023
|
-
vx run ci --graph=graph.dot
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
Node `fillcolor` varies by predicted status (green = local hit,
|
|
1027
|
-
sky-blue = remote hit, orange = miss, gray = no-cache, fuchsia =
|
|
1028
|
-
group, white = not started). Edges are unstyled.
|
|
1029
|
-
|
|
1030
|
-
## Run artifacts (`--summarize`, `--profile`)
|
|
1031
|
-
|
|
1032
|
-
Both flags add a side-effect after a real run completes. Errors
|
|
1029
|
+
`--profile` adds a side-effect after a real run completes. Errors
|
|
1033
1030
|
writing the artifact are surfaced via `vx: failed to write …` but
|
|
1034
1031
|
don't change the run's exit code — the run already happened.
|
|
1035
1032
|
|
|
1036
|
-
### `--summarize[=<path>]`
|
|
1037
|
-
|
|
1038
|
-
Writes a per-run JSON file; `schemas/summary.json` is its JSON Schema:
|
|
1039
|
-
|
|
1040
|
-
```json
|
|
1041
|
-
{
|
|
1042
|
-
"runId": "01a0e3ef-206c-708d-95bf-c2b07211adf6",
|
|
1043
|
-
"ok": true,
|
|
1044
|
-
"exitCode": 0,
|
|
1045
|
-
"startedAt": "2026-09-27T17:34:54.572Z",
|
|
1046
|
-
"endedAt": "2026-09-27T17:34:54.586Z",
|
|
1047
|
-
"totalMs": 14.123643,
|
|
1048
|
-
"savedMs": 9,
|
|
1049
|
-
"tasks": [
|
|
1050
|
-
{
|
|
1051
|
-
"id": "a#build",
|
|
1052
|
-
"project": "a",
|
|
1053
|
-
"task": "build",
|
|
1054
|
-
"status": "cache-hit",
|
|
1055
|
-
"exitCode": 0,
|
|
1056
|
-
"durationMs": 3,
|
|
1057
|
-
"hash": "48007ccadd42ed7d",
|
|
1058
|
-
"storedDurationMs": 9,
|
|
1059
|
-
"storedCpuMs": 2.059,
|
|
1060
|
-
"wallclockStartNs": "9456491",
|
|
1061
|
-
"wallclockEndNs": "12902888"
|
|
1062
|
-
}
|
|
1063
|
-
],
|
|
1064
|
-
"aborted": [],
|
|
1065
|
-
"summary": {
|
|
1066
|
-
"successful": 1,
|
|
1067
|
-
"failed": 0,
|
|
1068
|
-
"skipped": 0,
|
|
1069
|
-
"cachedLocal": 1,
|
|
1070
|
-
"restoredLocal": 0,
|
|
1071
|
-
"restoredRemote": 0,
|
|
1072
|
-
"upToDate": 1,
|
|
1073
|
-
"cachedRemote": 0,
|
|
1074
|
-
"aborted": 0,
|
|
1075
|
-
"total": 1
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
`runId` is a UUIDv7. Default path: `<cacheDir>/runs/<run_id>.json`. hrtime fields are
|
|
1081
|
-
strings (bigints serialized as strings) to preserve ns precision
|
|
1082
|
-
through JSON. `cpuMs` / `peakRssBytes` are what the task's own
|
|
1083
|
-
execution used and appear on executed rows only (`peakRssBytes` only
|
|
1084
|
-
when the task's peak rose above vx's own footprint — a lighter task's
|
|
1085
|
-
figure would be vx's, handed back by the kernel; a Linux sandboxed task
|
|
1086
|
-
reports neither, since what bwrap's pid namespace used never reaches vx);
|
|
1087
|
-
`restored` appears on a hit only: `true` when its outputs were restored,
|
|
1088
|
-
`false` when they were already up to date. A hit's `durationMs`
|
|
1089
|
-
is the restore it cost, and what the PRODUCING execution used rides the
|
|
1090
|
-
artifact and appears under its own keys, `storedDurationMs` /
|
|
1091
|
-
`storedCpuMs` / `storedPeakRssBytes` (the work the hit skipped) — a
|
|
1092
|
-
remote worker's peak is never presented as this run's. `savedMs` is
|
|
1093
|
-
their sum over the hits, the footer's `saved` as a number. `admissionHeldMs` appears on a row
|
|
1094
|
-
only when an `admit` policy (a plugin's — `@vzn/vx-schedule-history`
|
|
1095
|
-
packs learned reservations) refused the task while a worker was free:
|
|
1096
|
-
the wait from that first refusal to its dispatch, the plugin's hand on
|
|
1097
|
-
the run. The footer's `info` row sums the waits, task-seconds:
|
|
1098
|
-
`admit held 3 tasks, 4.2s in all` (85 held tasks can read 4783s beside
|
|
1099
|
-
a 160 s run).
|
|
1100
|
-
|
|
1101
|
-
**`ok` / `exitCode`** are the run's verdict — the same value the CLI
|
|
1102
|
-
exits with: a stopping signal's code (130 for Ctrl-C), and with a kept
|
|
1103
|
-
server, its exit (the file is written again when the server ends). Gate on these rather than re-deriving a pass from the
|
|
1104
|
-
buckets: a run can be red without a single failed task (see `aborted`).
|
|
1105
|
-
|
|
1106
|
-
**`tasks[]` and `summary` describe the same population**, so
|
|
1107
|
-
`tasks.length === summary.total` always holds. Group tasks (no `exec`)
|
|
1108
|
-
are in neither — they do no work.
|
|
1109
|
-
|
|
1110
|
-
**`aborted[]`** lists tasks whose attempt ended while the run was
|
|
1111
|
-
stopping (Ctrl-C, a signal to vx), with their exit code, and tasks the stop reached before they started, with
|
|
1112
|
-
exit 1, `durationMs: 0` and no `wallclockStartNs`. Neither joins an
|
|
1113
|
-
outcome bucket or `total`, but they make the run red, so they are
|
|
1114
|
-
listed separately and counted as `summary.aborted`.
|
|
1115
|
-
|
|
1116
|
-
**`noCache: true`** marks a task no cache answered for: it declares no
|
|
1117
|
-
`cache` block (it executes every run by design), or the run's `--cache-policy`
|
|
1118
|
-
read and wrote nothing (`--no-cache`), so a hit rate should leave it out
|
|
1119
|
-
of the denominator. The key is present only when true; every other row is
|
|
1120
|
-
unchanged. Its `hash` is still set: dependents fold it.
|
|
1121
|
-
|
|
1122
|
-
**`notReady`** is present only on a failed persistent task: why it never
|
|
1123
|
-
became ready — `timeout` (the readiness deadline fired; `exitCode` is
|
|
1124
|
-
the kill's, 143 or 137 after the grace, and 143 for a server that
|
|
1125
|
-
traps the TERM and exits 0, as an ordinary timeout's),
|
|
1126
|
-
`exited` (the child exited first; `exitCode` is then its own) or `spawn`
|
|
1127
|
-
(the spawn itself failed). Every label reads it, `failed (never ready:
|
|
1128
|
-
timed out, exit 143)`. A server the run's stop (a Ctrl-C) killed while it started is
|
|
1129
|
-
`aborted`, not failed, as any task the stop kills; so is one that
|
|
1130
|
-
became ready only after the stop.
|
|
1131
|
-
|
|
1132
|
-
**`sandboxViolations`** is present only on a sandboxed task with a
|
|
1133
|
-
SANDBOX VIOLATIONS section — the count of its denials (vx's own notes
|
|
1134
|
-
there count none), and the reason the task failed (its exit is forced to 1
|
|
1135
|
-
when it was 0); every label counts it, `failed (exit 1, 2 sandbox
|
|
1136
|
-
violations)`.
|
|
1137
|
-
|
|
1138
|
-
**`timedOut: true`** is present only on a `failed` row vx's own
|
|
1139
|
-
`timeout` killed: its exit is the shell's 143, and every label reads
|
|
1140
|
-
`failed (timed out, exit 143)` rather than a signal.
|
|
1141
|
-
|
|
1142
|
-
**`blockedBy`** is present only on a `skipped` row: the id of the failed
|
|
1143
|
-
(or aborted) task at the root of what blocked it, through any chain of
|
|
1144
|
-
skips between — what the skipped row prints, for a script.
|
|
1145
|
-
A fail-fast skip has none.
|
|
1146
|
-
|
|
1147
|
-
**`flaky: { passes, failures, attempts }`** is present only on a task
|
|
1148
|
-
this run proved flaky (its row's `flaky` note, typed): `passes` and
|
|
1149
|
-
`failures` count the outcomes on record for this exact `hash`, this run
|
|
1150
|
-
included, and `attempts` is what this run took. A consumer gating on
|
|
1151
|
-
`status: "failed"` can tell a break (no `flaky` key) from a flake
|
|
1152
|
-
without reading the history. Every other row is byte-identical.
|
|
1153
|
-
|
|
1154
|
-
**`durationMs` is always what THIS run spent on the task.** For a cache
|
|
1155
|
-
hit that is the probe + restore, not the exec time the entry was stored
|
|
1156
|
-
with — so it is small even for an expensive task. The work a hit
|
|
1157
|
-
_skipped_ is a different number; `--report`'s "N saved" is the surface
|
|
1158
|
-
that reports it.
|
|
1159
|
-
|
|
1160
1033
|
### `--profile[=<path>]`
|
|
1161
1034
|
|
|
1162
1035
|
Writes a Chrome-trace JSON of every task's wallclock span. Open in
|
|
@@ -1192,91 +1065,6 @@ from the per-task `hrtime.bigint()` spans the runner captures.
|
|
|
1192
1065
|
|
|
1193
1066
|
Default path: `profile.json` (cwd-relative).
|
|
1194
1067
|
|
|
1195
|
-
### `--report[=markdown]`
|
|
1196
|
-
|
|
1197
|
-
At the end of a real run, prints a markdown run report to **stdout**,
|
|
1198
|
-
just above the footer: nothing prints below the footer. The report
|
|
1199
|
-
itself is machine-clean. One header line of
|
|
1200
|
-
totals plus a table, one row per task:
|
|
1201
|
-
|
|
1202
|
-
```markdown
|
|
1203
|
-
## vx run — passed
|
|
1204
|
-
|
|
1205
|
-
**3 tasks** · 3 success · 0 failed · 2 cached (1 up-to-date, 1 restored) · 1.23s total · 2.65s saved
|
|
1206
|
-
|
|
1207
|
-
| Task | Status | Cache | Duration |
|
|
1208
|
-
| --------- | ------- | ---------- | -------- |
|
|
1209
|
-
| web#build | success | miss | 1.23s |
|
|
1210
|
-
| web#test | success | local | 5ms |
|
|
1211
|
-
| api#test | success | up-to-date | 3ms |
|
|
1212
|
-
```
|
|
1213
|
-
|
|
1214
|
-
`Status` is the task outcome (`success` / `failed (exit N)`, with the
|
|
1215
|
-
signal an exit above 128 stands for, `failed (exit 137, 128 + SIGKILL)`,
|
|
1216
|
-
or a timeout's reason, `failed (timed out, exit 143)`, a persistent
|
|
1217
|
-
task's `never ready: …`, or a sandboxed task's violation count, or
|
|
1218
|
-
`skipped`, naming what blocked it: `skipped (blocked by lib#build)`);
|
|
1219
|
-
`Cache` is its provenance (`miss` / `no-cache` for a task no cache
|
|
1220
|
-
answered for — no `cache` block, or a `--no-cache` / `--cache-policy=local:` run —
|
|
1221
|
-
which never consulted one / `local` / `remote` / `up-to-date` /
|
|
1222
|
-
`—`). A stopped run (a Ctrl-C teardown) reads `## vx run — interrupted`,
|
|
1223
|
-
and its tasks keep the terminal's words: one the signal killed is
|
|
1224
|
-
`aborted` with its time so far, one the stop reached before it ran is
|
|
1225
|
-
`not run` with no time. Both count in the header's task total, listed
|
|
1226
|
-
as `1 aborted · 1 not run`, so a red report with no failing row still
|
|
1227
|
-
says why. Group tasks (no `exec`) get neither — they are not
|
|
1228
|
-
work, and the header's counts match the terminal summary and
|
|
1229
|
-
`--summarize` exactly.
|
|
1230
|
-
|
|
1231
|
-
The two durations in the header mean different things, and the
|
|
1232
|
-
distinction is the point:
|
|
1233
|
-
|
|
1234
|
-
- **`N total`** sums `Duration` over the tasks that actually EXECUTED,
|
|
1235
|
-
an aborted task's time so far included — the time this run spent.
|
|
1236
|
-
- **`N saved`** sums the exec times the cache hits SKIPPED, read from
|
|
1237
|
-
each entry as it was stored (above, 2.01s and 640ms). It is
|
|
1238
|
-
deliberately not the hits' `Duration` column, which is the restore
|
|
1239
|
-
they cost this run: summing that reported a task taking 2.01s cold
|
|
1240
|
-
as "6ms saved".
|
|
1241
|
-
|
|
1242
|
-
Only `markdown` is supported today (`json` is reserved; a bad value is a
|
|
1243
|
-
parse error). Built purely from the run's outcomes after it returns — it
|
|
1244
|
-
adds zero cost when both flags are absent.
|
|
1245
|
-
|
|
1246
|
-
### `--report-file <path>`
|
|
1247
|
-
|
|
1248
|
-
Writes the same markdown report to a file instead of (or as well as)
|
|
1249
|
-
stdout, and is the form to use for a CI step summary:
|
|
1250
|
-
|
|
1251
|
-
```sh
|
|
1252
|
-
vx run ci --report-file="$GITHUB_STEP_SUMMARY"
|
|
1253
|
-
```
|
|
1254
|
-
|
|
1255
|
-
**Do not redirect stdout for this.** The report itself is machine-clean,
|
|
1256
|
-
but stdout is not vx's alone — the status logger writes frames, meter
|
|
1257
|
-
bars and `::group::` workflow commands to the same stream, so
|
|
1258
|
-
`--report=markdown >> "$GITHUB_STEP_SUMMARY"` puts the entire run log
|
|
1259
|
-
into the step summary above the table.
|
|
1260
|
-
|
|
1261
|
-
The report is **appended**, never truncated: `$GITHUB_STEP_SUMMARY` is a
|
|
1262
|
-
shared, append-only file that other steps in the same job also write to,
|
|
1263
|
-
so overwriting it would silently discard their content. Passing both
|
|
1264
|
-
flags writes the report to stdout AND appends it to the file. A run
|
|
1265
|
-
stopped by Ctrl-C or SIGTERM still writes both, its stopped tasks
|
|
1266
|
-
`aborted`. A write
|
|
1267
|
-
failure is reported (`vx: failed to write report to …`) but does not
|
|
1268
|
-
change the exit code — the run already happened, the same contract
|
|
1269
|
-
`--summarize` and `--profile` follow.
|
|
1270
|
-
|
|
1271
|
-
### `--tag <k=v>`
|
|
1272
|
-
|
|
1273
|
-
Labels the invocation. Repeatable; `--tag=k=v` form too. The pair is
|
|
1274
|
-
split on the **first** `=`, so values may contain `=` (e.g. a URL). An
|
|
1275
|
-
empty key is a parse error. Tags are recorded on the run's
|
|
1276
|
-
`invocations` row so dashboards can filter runs by label, and reach
|
|
1277
|
-
telemetry as `vx.tag.<key>`; a secret value in one is masked there as
|
|
1278
|
-
in the task's output (see [Masking](./schema.md)).
|
|
1279
|
-
|
|
1280
1068
|
## Sandbox
|
|
1281
1069
|
|
|
1282
1070
|
Sandbox isolation is opt-in **per task** via an `exec.sandbox` block in
|
|
@@ -1553,10 +1341,10 @@ re-reads the watched set (item 971).
|
|
|
1553
1341
|
The following flags are rejected (parser exits 1 before the initial
|
|
1554
1342
|
run):
|
|
1555
1343
|
|
|
1556
|
-
- `--dry`
|
|
1557
|
-
- `--
|
|
1558
|
-
- `--
|
|
1559
|
-
|
|
1344
|
+
- `--dry` — it skips execution; nothing to watch.
|
|
1345
|
+
- `--profile` — would overwrite its target per cycle.
|
|
1346
|
+
- `--verbosity <n>` (n > 0) — formats
|
|
1347
|
+
ONE run's result; a watch loop has no single run to report. (`--verbosity 0`
|
|
1560
1348
|
is accepted: it asks for what watch already prints.)
|
|
1561
1349
|
|
|
1562
1350
|
A requested persistent task (`exec.persistent`, a dev server) starts
|
|
@@ -1581,7 +1369,7 @@ re-runs itself and what it depends on.
|
|
|
1581
1369
|
- `0` — clean Ctrl+C / SIGTERM exit. A cycle in flight is torn down
|
|
1582
1370
|
first — its children, and the dev server held between cycles, get the
|
|
1583
1371
|
signal watch received (a Ctrl-C as SIGINT; a SIGTERM or SIGHUP as SIGTERM),
|
|
1584
|
-
|
|
1372
|
+
a 2 s grace, then SIGKILL — and the process leaves only
|
|
1585
1373
|
once it has returned, so a CI cancellation never orphans a task.
|
|
1586
1374
|
Also `0`, with no watch, when `--affected` selects nothing: watch
|
|
1587
1375
|
prints `vx watch: nothing affected since <ref>` and exits as `vx run`
|
|
@@ -2075,9 +1863,6 @@ manager is its lockfile (`pnpm-lock.yaml`, `bun.lock`, `yarn.lock`,
|
|
|
2075
1863
|
`package-lock.json`), else `packageManager`, else npm; the field it is
|
|
2076
1864
|
listed in is kept (`--save-dev`, `--dev`, …), and pnpm at a workspace
|
|
2077
1865
|
root adds `--workspace-root`. The lockfile is updated by that manager.
|
|
2078
|
-
Run inside a project that installs `@vzn/vx`, a global vx hands
|
|
2079
|
-
`vx upgrade` to the project's own (§ Which vx runs), so the project's
|
|
2080
|
-
dependency is what moves.
|
|
2081
1866
|
vx prints what it runs, runs it there, and exits with an error if it
|
|
2082
1867
|
fails:
|
|
2083
1868
|
|
|
@@ -2087,6 +1872,28 @@ fails:
|
|
|
2087
1872
|
bun add @vzn/vx@0.0.2 in /repo
|
|
2088
1873
|
```
|
|
2089
1874
|
|
|
1875
|
+
**Inside a project with its own vx**, a global vx hands `vx upgrade` to
|
|
1876
|
+
the project's (§ Which vx runs), and one run moves both to the same
|
|
1877
|
+
version (`TAG`, or latest): first the project's dependency, then the
|
|
1878
|
+
global vx that started the run, each the way it was installed. Each
|
|
1879
|
+
line names its install, and a refusal names the one that failed
|
|
1880
|
+
(`vx upgrade: global vx /usr/local/bin/vx: cannot write to …`); a
|
|
1881
|
+
project that fails leaves the global as it was. The project's vx run
|
|
1882
|
+
directly (a package script, `node_modules/.bin/vx`) has no global behind it, so only
|
|
1883
|
+
the project moves.
|
|
1884
|
+
|
|
1885
|
+
```
|
|
1886
|
+
› vx upgrade project 0.0.1 → 0.0.2
|
|
1887
|
+
|
|
1888
|
+
bun add @vzn/vx@0.0.2 in /repo
|
|
1889
|
+
|
|
1890
|
+
› vx upgrade global 0.0.1 → 0.0.2
|
|
1891
|
+
|
|
1892
|
+
/usr/local/bin/vx
|
|
1893
|
+
|
|
1894
|
+
✓ vx upgrade global installed 0.0.2
|
|
1895
|
+
```
|
|
1896
|
+
|
|
2090
1897
|
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
|
|
2091
1898
|
bunx cache has nothing installed to upgrade, and says to ask for
|
|
2092
1899
|
`@vzn/vx@latest` instead.
|
|
@@ -2201,8 +2008,8 @@ none is dropped in silence. An Nx flag's camelCase spelling (`--nxBail`,
|
|
|
2201
2008
|
`--skipNxCache`), which Nx's parser takes and its docs print, is its
|
|
2202
2009
|
kebab-case row. A `[=<bool>]` row takes `=true` (the flag bare) and `=false` (left out), as Turbo's and Nx's parsers do. `vx run-many` and `vx affected` name the
|
|
2203
2010
|
`vx run` that does the same, and the other verbs a hand types from
|
|
2204
|
-
either tool name what does it here: `graph` (`--
|
|
2205
|
-
(`vx show`), `
|
|
2011
|
+
either tool name what does it here: `graph` and `query` (`--dry=json`), `ls`
|
|
2012
|
+
(`vx show`), `reset` (`vx cache clean`), and
|
|
2206
2013
|
`daemon`, `login`, `logout`, `link` and `unlink`, which vx has no use
|
|
2207
2014
|
for. Nx's `project:target` (`vx run web:build`,
|
|
2208
2015
|
when `web` declares `build`) is answered with `vx run web#build`: from
|
|
@@ -2221,11 +2028,10 @@ copy to the source.
|
|
|
2221
2028
|
| turbo | `--continue=dependencies-successful` | alias | `--continue=deps-ok` |
|
|
2222
2029
|
| turbo | `--continue` | same | `--continue[=never\|deps-ok\|always]` |
|
|
2223
2030
|
| turbo | `--dry-run` | alias | `--dry[=text\|json]` |
|
|
2224
|
-
| turbo | `--graph
|
|
2225
|
-
| turbo | `--graph` | same | `--graph[=<file>.dot]` |
|
|
2031
|
+
| turbo | `--graph` | refuse | `--dry=json` prints the planned task graph, each task with its deps |
|
|
2226
2032
|
| turbo | `--force[=<bool>]` | same | `--force`: skip cache reads, keep writes |
|
|
2227
2033
|
| turbo | `--affected` | same | `--affected[=<base>]` |
|
|
2228
|
-
| turbo | `--summarize
|
|
2034
|
+
| turbo | `--summarize` | refuse | use `vx last --format json` for the run's JSON record |
|
|
2229
2035
|
| turbo | `--output-logs=new-only` | refuse | use `--output-logs=full` (a hit replays its log) or `errors-only` |
|
|
2230
2036
|
| turbo | `--output-logs <v>` | same | `--output-logs full\|errors-only\|hash-only\|none` |
|
|
2231
2037
|
| turbo | `--no-cache` | same | `--no-cache` reads nothing either; Turbo's (reads, no writes) is `--cache-policy local:r,remote:r` |
|
|
@@ -2254,8 +2060,8 @@ copy to the source.
|
|
|
2254
2060
|
| turbo | `--env-mode <v>` | refuse | vx passes only the variables a task declares (strict): list the rest in `exec.env.passThrough` |
|
|
2255
2061
|
| turbo | `--framework-inference <v>` | refuse | under `turbo()` inference is Turbo's; take a name back with a `!` entry in the task's `env` |
|
|
2256
2062
|
| turbo | `--global-deps <v>` | refuse | declare them in `cache.inputs.workspaceFiles` (under `turbo()`, turbo.json's `globalDependencies`) |
|
|
2257
|
-
| turbo | `--json` | refuse | use
|
|
2258
|
-
| turbo | `--log-file` | refuse | use
|
|
2063
|
+
| turbo | `--json` | refuse | use `vx last --format json` for the run's result, `--dry=json` for the plan |
|
|
2064
|
+
| turbo | `--log-file` | refuse | use `vx last --format json` for the run's JSON record |
|
|
2259
2065
|
| turbo | `--preflight` | refuse | set `turboCache({ preflight: true })` or `TURBO_PREFLIGHT=1` |
|
|
2260
2066
|
| turbo | `--remote-cache-timeout <v>` | refuse | set `turboCache({ timeoutMs })` or `TURBO_REMOTE_CACHE_TIMEOUT` |
|
|
2261
2067
|
| turbo | `--single-package` | refuse | a repo with no workspaces is one project already: drop it |
|
|
@@ -2297,9 +2103,8 @@ copy to the source.
|
|
|
2297
2103
|
## Machine-readable output
|
|
2298
2104
|
|
|
2299
2105
|
`vx show`, `vx info`, `vx why`, `vx last` and `vx cache clean` take
|
|
2300
|
-
`--format json`. Each prints one JSON document on one line,
|
|
2301
|
-
|
|
2302
|
-
checked-in JSON Schema (draft 2020-12) shipped with the package:
|
|
2106
|
+
`--format json`. Each prints one JSON document on one line, whose shape
|
|
2107
|
+
is a checked-in JSON Schema (draft 2020-12) shipped with the package:
|
|
2303
2108
|
`schemas/show.json`, `schemas/info.json`, `schemas/why.json`,
|
|
2304
2109
|
`schemas/last.json`, `schemas/cache.json`
|
|
2305
2110
|
(`node_modules/@vzn/vx/schemas/` in an install). Every object closes its
|
|
@@ -2311,13 +2116,9 @@ schemas: each `{ … }` a verb's section shows is an object its schema
|
|
|
2311
2116
|
closes with those keys, each object the verb prints whole is shown, and
|
|
2312
2117
|
`vx info`'s field list is its schema's top level.
|
|
2313
2118
|
|
|
2314
|
-
`vx
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
the frame and the tasks' output with it, goes to stderr, so
|
|
2318
|
-
`vx run build --all --format json | jq .ok` reads only the result. It
|
|
2319
|
-
refuses `--dry` (use `--dry=json`), a bare `--graph` and
|
|
2320
|
-
`--report=markdown`, which print on stdout too.
|
|
2119
|
+
A run's result as JSON is `vx last --format json`, read after the run
|
|
2120
|
+
from the run history: one row per task with its status, key and cache
|
|
2121
|
+
result. `vx run --dry=json` prints the plan.
|
|
2321
2122
|
|
|
2322
2123
|
### Error codes
|
|
2323
2124
|
|
|
@@ -2433,8 +2234,9 @@ target, fix its spelling, or drop the entry.
|
|
|
2433
2234
|
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2434
2235
|
|
|
2435
2236
|
A `^task` with no edge source is refused too: ``<id>: ^build needs
|
|
2436
|
-
project edges; add pnpm()/bun()/npm()/yarn()
|
|
2437
|
-
|
|
2237
|
+
project edges; add pnpm()/bun()/npm()/yarn() and install so its
|
|
2238
|
+
lockfile exists, or a plugin that returns `deps` ``. A lockfile plugin
|
|
2239
|
+
whose lockfile is absent gives no edges. Core reads no edge from a manifest, so with no plugin
|
|
2438
2240
|
returning `deps` every `^` would resolve to nothing and fold no
|
|
2439
2241
|
upstream into the key. Declare the lockfile plugin in
|
|
2440
2242
|
`.vx/workspace.ts` (or a plugin of your own that returns `deps`); a run
|
|
@@ -2470,12 +2272,11 @@ https://github.com/vznjs/vx/issues.
|
|
|
2470
2272
|
A plugin can give its refusal a code: `new UserError(message, 'MY_CODE')`.
|
|
2471
2273
|
|
|
2472
2274
|
**Streams.** A verb whose stdout is a product (a `--format json`
|
|
2473
|
-
document, `--dry` / `--dry=json`,
|
|
2275
|
+
document, `--dry` / `--dry=json`, a completion
|
|
2474
2276
|
script) writes that product alone there; a notice or warning it meets
|
|
2475
2277
|
on the way (a cache index from another vx version, a plugin's warning) goes
|
|
2476
2278
|
to stderr, as every refusal does. `vx run`'s stdout is the run's frame,
|
|
2477
|
-
the tasks' output it carries
|
|
2478
|
-
moves to stderr. `tests/cli-streams.test.ts` holds each
|
|
2279
|
+
the tasks' output it carries. `tests/cli-streams.test.ts` holds each
|
|
2479
2280
|
product verb to it under a notice.
|
|
2480
2281
|
|
|
2481
2282
|
## `vx show`
|
|
@@ -2571,6 +2372,9 @@ or `; projects and tasks: …` (M-57).
|
|
|
2571
2372
|
|
|
2572
2373
|
An empty target (`vx show ''`) is refused: omit it to list every project.
|
|
2573
2374
|
|
|
2375
|
+
In a workspace with no project, the list is empty and `vx show` says so
|
|
2376
|
+
on stderr (`no projects`) and names `vx init`.
|
|
2377
|
+
|
|
2574
2378
|
Exit codes: `0` success; `1` parse error or unknown target.
|
|
2575
2379
|
|
|
2576
2380
|
Two runs on one workspace take turns: the second waits for the first's
|
|
@@ -2614,7 +2418,7 @@ $ vx info
|
|
|
2614
2418
|
memory 13 GB usable — cgroup limit; the machine has 16 GB
|
|
2615
2419
|
cache dir /work/repo/.vx/cache (8.2 MB, this workspace's run history)
|
|
2616
2420
|
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
|
|
2421
|
+
cache versions keys vx-cache-v43 · index schema v40
|
|
2618
2422
|
cache entries 42 (1.3 GB)
|
|
2619
2423
|
orphans 3 artifacts (12 MB) the index does not know — `vx cache clean` reaps them
|
|
2620
2424
|
task runs (24h) 7 (5 cache hits)
|
|
@@ -2875,7 +2679,9 @@ command that exits 137 on its own reads the same). Where the run's
|
|
|
2875
2679
|
footer gave a reason, the row ends with it instead: `timed out`,
|
|
2876
2680
|
`never ready: exited`, `2 sandbox violations`, and for a skipped task
|
|
2877
2681
|
`after lib#build failed` (the v27 columns; rows older than them read
|
|
2878
|
-
as before).
|
|
2682
|
+
as before). A task whose key met a remote failure ends with it, read or
|
|
2683
|
+
write alike: `remote ✗ download: connection refused; upload: 503` (the
|
|
2684
|
+
v40 column, `remoteError` in `--format json`). Rows run failures first, then what executed or was
|
|
2879
2685
|
skipped, then hits; past sixteen hits the rest fold into one line with
|
|
2880
2686
|
their count (`… +980 more cache hits`) — a thousand-task warm run is a
|
|
2881
2687
|
thousand rows otherwise, with the one failure a screen above the
|
|
@@ -3066,8 +2872,8 @@ names no package: it lists what the workspace itself declares.
|
|
|
3066
2872
|
|
|
3067
2873
|
## Environment variables vx reads
|
|
3068
2874
|
|
|
3069
|
-
Every setting has a variable derived from its path (`run.
|
|
3070
|
-
`
|
|
2875
|
+
Every setting has a variable derived from its path (`run.timeout` →
|
|
2876
|
+
`VX_RUN_TIMEOUT`), beating `.vx/workspace` and beaten by the flag; the
|
|
3071
2877
|
table in `docs/schema.md` § Settings gives each value's form and
|
|
3072
2878
|
default. Each is checked once before any verb runs: an empty one is
|
|
3073
2879
|
unset, a value its kind does not take is refused naming the form, and a
|
|
@@ -3087,9 +2893,6 @@ what a TASK sees is `exec.env` in `docs/schema.md`.
|
|
|
3087
2893
|
| `VX_RUN_RETRIES` | [`run.retries`](schema.md#settings) | Re-runs of a failed task, for tasks without their own `exec.retries`. |
|
|
3088
2894
|
| `VX_RUN_CONTINUE` | [`run.continue`](schema.md#settings) | What a failed task takes down: `never` stops the run, `deps-ok` skips its dependents, `always` runs them too. |
|
|
3089
2895
|
| `VX_RUN_OUTPUT_LOGS` | [`run.outputLogs`](schema.md#settings) | Which task output the run prints. Chosen before the workspace loads, so flag and env only. |
|
|
3090
|
-
| `VX_RUN_KILL_GRACE` | [`run.killGrace`](schema.md#settings) | How long a task that ignores SIGTERM gets before SIGKILL. |
|
|
3091
|
-
| `VX_RUN_TEARDOWN_TIMEOUT` | [`run.teardownTimeout`](schema.md#settings) | The bound on each plugin's end-of-run flush and teardown. |
|
|
3092
|
-
| `VX_RUN_READY_NOTICE` | [`run.readyNotice`](schema.md#settings) | How long a persistent task may take to match `readyWhen` before vx says it waits. |
|
|
3093
2896
|
| `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | [`run.rules.exclusiveOutputs`](schema.md#settings) | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
3094
2897
|
| `VX_RUN_RULES_UPFRONT_KEYS` | [`run.rules.upfrontKeys`](schema.md#settings) | Refuse a task whose inputs can match another task's outputs. |
|
|
3095
2898
|
| `VX_CACHE_DIR` | [`cache.dir`](schema.md#settings) | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
|
|
@@ -3121,9 +2924,8 @@ Colors are the two conventions in § Output format › Colors (`NO_COLOR`,
|
|
|
3121
2924
|
`FORCE_COLOR`). A truthy `CI` picks the CI output flow and
|
|
3122
2925
|
`GITHUB_ACTIONS` its `::group::` framing (§ Output); the run's
|
|
3123
2926
|
`invocations` row records the provider, the first truthy of
|
|
3124
|
-
`GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, then `CI`. Core never reads `GITHUB_STEP_SUMMARY
|
|
3125
|
-
|
|
3126
|
-
Actions pass `--report-file="$GITHUB_STEP_SUMMARY"`. A sandboxed
|
|
2927
|
+
`GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, then `CI`. Core never reads `GITHUB_STEP_SUMMARY`; the job summary is
|
|
2928
|
+
`@vzn/vx-ci`'s. A sandboxed
|
|
3127
2929
|
task's temp dir sits under `CLAUDE_CODE_TMPDIR` (else `CLAUDE_TMPDIR`,
|
|
3128
2930
|
else `/tmp/claude`), the sandbox runtime's own convention
|
|
3129
2931
|
(`docs/modules/sandbox-runtime.md`).
|
|
@@ -3158,8 +2960,8 @@ write ../shared/notes.txt
|
|
|
3158
2960
|
Every section is conditional: the `$ <command>` line only for an
|
|
3159
2961
|
executed task (success or failed — a hit replays its stored output and
|
|
3160
2962
|
shows none), `OUTPUT` only when the task wrote something,
|
|
3161
|
-
`SANDBOX VIOLATIONS (N)` only when the sandbox recorded some
|
|
3162
|
-
has a note on a failure. The
|
|
2963
|
+
`SANDBOX VIOLATIONS (N)` only when the sandbox recorded some (`N`
|
|
2964
|
+
counts them) or `SANDBOX` when vx has only a note on a failure. The
|
|
3163
2965
|
header carries the outcome (`restored-local • abc12345`, `failed (exit
|
|
3164
2966
|
N)`, …) and the footer repeats it after the duration. A test renders
|
|
3165
2967
|
this block and checks it against this page, byte for byte.
|
|
@@ -3206,17 +3008,27 @@ A broad run looks like:
|
|
|
3206
3008
|
```
|
|
3207
3009
|
|
|
3208
3010
|
The three bars are meters: `projects` is what the run covered against
|
|
3209
|
-
the workspace, `tasks` is failed / success / skipped, `cache`
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3011
|
+
the workspace, `tasks` is failed / success / skipped, `cache` has a
|
|
3012
|
+
segment per legend state (a skipped task rides it too, so the two bars
|
|
3013
|
+
fill alike; its count is on the tasks legend only). Its legend reads
|
|
3014
|
+
per layer: `local 1✓ 1∅ 2↓ 1↑ · remote 3↓ 4↑ 2✗ 1⇡`. `✓` is a fresh
|
|
3015
|
+
hit (outputs already on disk, whichever layer answered, so it counts
|
|
3016
|
+
under local), `∅` a task with no cache, `↓` is restored from that
|
|
3017
|
+
layer and `↑` saved to it, `✗` a task a remote read or write failed
|
|
3018
|
+
for, and `⇡` an upload still running (live only, always last). The
|
|
3019
|
+
region stays through vx's closing steps, worker rows gone, and the
|
|
3020
|
+
final footer replaces it in place. Each task counts once, under its strongest state: a
|
|
3021
|
+
failed remote read or write, then an upload running, then saved to a
|
|
3022
|
+
remote, then saved here, then restored, then fresh, then no cache; so
|
|
3023
|
+
a task saved here and uploaded is a remote `↑`. A key the remote already
|
|
3024
|
+
held is not sent, so it is no remote `↑`. Each count is painted
|
|
3025
|
+
in its segment's colour, number and glyph alike: `✓` green, `∅` dim,
|
|
3026
|
+
local blue, remote dark blue, `✗` and a miss red, `⇡` cyan; the
|
|
3027
|
+
`local` and `remote` words are dim. A layer shows only when the run's
|
|
3028
|
+
`--cache-policy` uses it. `N not saved` (ran, succeeded, saved
|
|
3029
|
+
nothing), `N not downloaded` (outputs left in the remote store) and
|
|
3030
|
+
every count show only when non-zero. `vx last` names each remote
|
|
3031
|
+
failure on its task's row. The
|
|
3220
3032
|
`time` row is the wall time, then what the cache saved (the hits' stored
|
|
3221
3033
|
exec times, never the restore), then the spread over executed tasks
|
|
3222
3034
|
only — a hit's restore time never enters it. The footer never says a
|
|
@@ -3234,12 +3046,22 @@ running a group focused surfaces its real member tasks instead.
|
|
|
3234
3046
|
**Nothing below the footer.** The footer is the run's last word.
|
|
3235
3047
|
What a run has to say about one task rides that task's row; what it
|
|
3236
3048
|
says about the run (a server that died, outputs left remote, a file
|
|
3237
|
-
`--
|
|
3049
|
+
`--profile` wrote, a history, upload, plugin or sandbox
|
|
3238
3050
|
warning) prints above the footer: it prints once the run's closing work
|
|
3239
3051
|
is done, the `VX_DEBUG_TIMING=1` table included. Cache housekeeping
|
|
3240
3052
|
(`cache.retention`) prints nothing. Only a kept server's own output
|
|
3241
3053
|
follows it.
|
|
3242
3054
|
|
|
3055
|
+
**Narrow terminals.** The footer is 62 columns: its rule and 50-cell
|
|
3056
|
+
bars. On a terminal narrower than that the bars shrink and the rule
|
|
3057
|
+
with them, as do a frame's section rules, so none wraps onto a second
|
|
3058
|
+
row. Off a terminal they keep their width.
|
|
3059
|
+
|
|
3060
|
+
**Warnings on stderr.** A run's warnings, such as `[vx] app#build:
|
|
3061
|
+
cache.outputs matched no files …` or a plugin's, go to stderr. Stdout
|
|
3062
|
+
carries only the rows, the frames and the footer, so `vx run build >
|
|
3063
|
+
log.txt` keeps them out of the log.
|
|
3064
|
+
|
|
3243
3065
|
**Skipped rows.** A red run lists every task that never started as a
|
|
3244
3066
|
row naming the failure that blocked it, so the footer's `1 skipped`
|
|
3245
3067
|
always has a name above it:
|
|
@@ -3301,6 +3123,7 @@ ANSI truecolor (`ansi-16m`) sequences, gated by env:
|
|
|
3301
3123
|
| `FORCE_COLOR=0` / `=false` | Force off, on a TTY too. |
|
|
3302
3124
|
| `FORCE_COLOR=…` (any other) | Force on — `1`, `true`, `2`, `3`, the empty string, etc. |
|
|
3303
3125
|
| (neither) | On iff `stdout.isTTY`. |
|
|
3126
|
+
| `TERM=dumb` | Off unless `FORCE_COLOR` forces it, and no live region. |
|
|
3304
3127
|
|
|
3305
3128
|
`0` and `false` are `FORCE_COLOR`'s "off" by the convention
|
|
3306
3129
|
supports-color, chalk and Node follow; until 2026-09-24 vx read every
|
|
@@ -3326,7 +3149,7 @@ Only a run's own output (`vx run`, `vx watch`) is painted, and the
|
|
|
3326
3149
|
help on a terminal: headings bold, `vx <verb>` and each flag or example
|
|
3327
3150
|
in cyan; `NO_COLOR` or `FORCE_COLOR=0` turns it off
|
|
3328
3151
|
(`tests/help-colors.test.ts`). Every other verb (`show`, `info`, `why`,
|
|
3329
|
-
`last`, a `--dry` plan,
|
|
3152
|
+
`last`, a `--dry` plan, `cache clean`), and the help off a
|
|
3330
3153
|
terminal, prints plain text whatever `FORCE_COLOR` says, so a script
|
|
3331
3154
|
that forces colour for a run's log still parses them
|
|
3332
3155
|
(`tests/cli-colors-e2e.test.ts`).
|
|
@@ -3343,7 +3166,7 @@ Reads try local first, then remote (hydrating local on remote hit),
|
|
|
3343
3166
|
with a background prefetch pass overlapping remote GETs with
|
|
3344
3167
|
execution. Writes go to local immediately; the remote upload is a
|
|
3345
3168
|
fire-and-forget background task drained at end of run — failures are
|
|
3346
|
-
|
|
3169
|
+
warned but never fail the build.
|
|
3347
3170
|
|
|
3348
3171
|
For any OTHER cache server (a Turbo-wire deployment, S3-direct, …),
|
|
3349
3172
|
implement core's `RemoteCacheLayer` interface in a plugin's `cache`
|
|
@@ -3358,7 +3181,7 @@ consult).
|
|
|
3358
3181
|
size, runs + hits in the last 24 h). For anything deeper, vx records
|
|
3359
3182
|
every task to a `runs` table in `cache.db` (UUIDv7 `run_id`, hrtime
|
|
3360
3183
|
wallclock spans, cpu_ms, peak RSS, status, cache_hit flag) plus one
|
|
3361
|
-
`invocations` header row per run (command, git/CI context,
|
|
3184
|
+
`invocations` header row per run (command, git/CI context,
|
|
3362
3185
|
counts). The SQLite file IS the API:
|
|
3363
3186
|
|
|
3364
3187
|
```sh
|
|
@@ -3407,7 +3230,7 @@ Surface:
|
|
|
3407
3230
|
|
|
3408
3231
|
- `run(options)` — execute. Returns `Promise<RunSummary>`.
|
|
3409
3232
|
- `planRun(options)` — predict, no execute. Returns
|
|
3410
|
-
`Promise<RunPlan>`. Used by `--dry
|
|
3233
|
+
`Promise<RunPlan>`. Used by `--dry`.
|
|
3411
3234
|
- `prepareRun(options, log)` — the shared setup (discovery → configs →
|
|
3412
3235
|
graph → cache). What an embedder builds on.
|
|
3413
3236
|
- `defineProject` / `defineWorkspace` — identity helpers for type
|