@vzn/vx 0.0.630 → 0.0.632

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 (62) hide show
  1. package/README.md +7 -8
  2. package/package.json +7 -5
  3. package/schemas/cache.json +24 -0
  4. package/schemas/error.json +23 -0
  5. package/schemas/info.json +147 -0
  6. package/schemas/last.json +188 -0
  7. package/schemas/plan.json +88 -0
  8. package/schemas/show.json +124 -0
  9. package/schemas/summary.json +133 -0
  10. package/schemas/why.json +168 -0
  11. package/skills/vx/SKILL.md +85 -0
  12. package/src/bin.ts +9 -0
  13. package/src/cache/archive.ts +153 -51
  14. package/src/cache/cache.ts +45 -19
  15. package/src/cache/layer.ts +2 -1
  16. package/src/cache/schema.ts +2 -1
  17. package/src/cache/tar-stream.ts +204 -66
  18. package/src/cache/zstd.ts +1 -1
  19. package/src/cli/cache.ts +14 -14
  20. package/src/cli/foreign-flags.ts +1 -1
  21. package/src/cli/help.ts +30 -5
  22. package/src/cli/index.ts +23 -11
  23. package/src/cli/info.ts +38 -9
  24. package/src/cli/init.ts +97 -19
  25. package/src/cli/last.ts +54 -3
  26. package/src/cli/plan-format.ts +23 -1
  27. package/src/cli/plugin-commands.ts +3 -0
  28. package/src/cli/plugin-templates.ts +1 -1
  29. package/src/cli/run.ts +40 -21
  30. package/src/cli/show.ts +10 -7
  31. package/src/cli/upgrade.ts +1 -1
  32. package/src/cli/watch.ts +7 -2
  33. package/src/cli/why.ts +14 -40
  34. package/src/exec/runner.ts +27 -10
  35. package/src/graph/scheduler.ts +36 -0
  36. package/src/graph/task-graph.ts +1 -1
  37. package/src/index.ts +22 -5
  38. package/src/orchestrator/admission.ts +29 -1
  39. package/src/orchestrator/affected-tasks.ts +87 -18
  40. package/src/orchestrator/doctor.ts +14 -1
  41. package/src/orchestrator/execute-task.ts +149 -64
  42. package/src/orchestrator/failure-mode.ts +5 -3
  43. package/src/orchestrator/framed-output.ts +17 -3
  44. package/src/orchestrator/index.ts +11 -0
  45. package/src/orchestrator/logger.ts +57 -6
  46. package/src/orchestrator/metrics.ts +65 -0
  47. package/src/orchestrator/options.ts +12 -0
  48. package/src/orchestrator/output-log.ts +14 -1
  49. package/src/orchestrator/path-links.ts +79 -0
  50. package/src/orchestrator/plan.ts +20 -3
  51. package/src/orchestrator/plugin.ts +7 -0
  52. package/src/orchestrator/prepare.ts +14 -4
  53. package/src/orchestrator/run-artifacts.ts +18 -2
  54. package/src/orchestrator/run-failures.ts +192 -0
  55. package/src/orchestrator/run-records.ts +10 -8
  56. package/src/orchestrator/run.ts +87 -22
  57. package/src/orchestrator/terminal-signals.ts +16 -0
  58. package/src/orchestrator/upstream.ts +65 -10
  59. package/src/util/errors.ts +55 -1
  60. package/src/util/index.ts +3 -0
  61. package/src/util/secret-mask.ts +13 -1
  62. package/src/workspace/workspace.ts +1 -0
@@ -0,0 +1,133 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "vx run --summarize",
4
+ "description": "Every task's outcome in one run: <cacheDir>/runs/<runId>.json, or the --summarize=<path> file.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "runId",
9
+ "ok",
10
+ "exitCode",
11
+ "startedAt",
12
+ "endedAt",
13
+ "totalMs",
14
+ "savedMs",
15
+ "tasks",
16
+ "aborted",
17
+ "summary"
18
+ ],
19
+ "properties": {
20
+ "runId": { "type": "string" },
21
+ "ok": { "type": "boolean", "description": "the run's verdict: exitCode is 0" },
22
+ "exitCode": { "type": "integer" },
23
+ "startedAt": { "type": "string", "description": "ISO 8601" },
24
+ "endedAt": { "type": "string", "description": "ISO 8601" },
25
+ "totalMs": { "type": "number" },
26
+ "savedMs": {
27
+ "type": "number",
28
+ "description": "what the cache hits skipped: the sum of their storedDurationMs"
29
+ },
30
+ "tasks": {
31
+ "type": "array",
32
+ "description": "every counted task: not a group, not aborted",
33
+ "items": { "$ref": "#/$defs/task" }
34
+ },
35
+ "aborted": {
36
+ "type": "array",
37
+ "description": "tasks a shutdown signal killed; in no bucket and no total",
38
+ "items": { "$ref": "#/$defs/task" }
39
+ },
40
+ "summary": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": [
44
+ "successful",
45
+ "failed",
46
+ "skipped",
47
+ "cachedLocal",
48
+ "restoredLocal",
49
+ "restoredRemote",
50
+ "upToDate",
51
+ "cachedRemote",
52
+ "aborted",
53
+ "total"
54
+ ],
55
+ "properties": {
56
+ "successful": { "type": "integer" },
57
+ "failed": { "type": "integer" },
58
+ "skipped": { "type": "integer" },
59
+ "cachedLocal": { "type": "integer" },
60
+ "restoredLocal": { "type": "integer" },
61
+ "restoredRemote": { "type": "integer" },
62
+ "upToDate": { "type": "integer" },
63
+ "cachedRemote": { "type": "integer" },
64
+ "aborted": { "type": "integer" },
65
+ "total": { "type": "integer" }
66
+ }
67
+ }
68
+ },
69
+ "$defs": {
70
+ "task": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "required": ["id", "project", "task", "status", "exitCode", "durationMs", "hash"],
74
+ "properties": {
75
+ "id": { "type": "string" },
76
+ "project": { "type": "string" },
77
+ "task": { "type": "string" },
78
+ "status": {
79
+ "enum": ["success", "cache-hit", "cache-hit-remote", "failed", "skipped", "aborted"]
80
+ },
81
+ "exitCode": { "type": "integer" },
82
+ "durationMs": { "type": "number" },
83
+ "hash": { "type": ["string", "null"] },
84
+ "restored": {
85
+ "type": "boolean",
86
+ "description": "on a hit only: true when outputs were restored, false when they were already up to date"
87
+ },
88
+ "noCache": {
89
+ "const": true,
90
+ "description": "the task declares no cache block: it could never hit"
91
+ },
92
+ "flaky": {
93
+ "type": "object",
94
+ "description": "this key both passed and failed on record, or the task needed a retry",
95
+ "additionalProperties": false,
96
+ "required": ["passes", "failures", "attempts"],
97
+ "properties": {
98
+ "passes": { "type": "integer" },
99
+ "failures": { "type": "integer" },
100
+ "attempts": { "type": "integer" }
101
+ }
102
+ },
103
+ "cpuMs": { "type": "number" },
104
+ "peakRssBytes": { "type": "integer" },
105
+ "storedDurationMs": {
106
+ "type": "number",
107
+ "description": "a hit: how long the producing execution ran, what the hit skipped"
108
+ },
109
+ "storedCpuMs": {
110
+ "type": "number",
111
+ "description": "a hit: what the producing execution used"
112
+ },
113
+ "storedPeakRssBytes": { "type": "integer" },
114
+ "admissionHeldMs": {
115
+ "type": "number",
116
+ "description": "an admit policy held it with a worker free"
117
+ },
118
+ "blockedBy": {
119
+ "type": "string",
120
+ "description": "a skipped task: the failed task at the root of what blocked it"
121
+ },
122
+ "timedOut": { "const": true },
123
+ "sandboxViolations": { "type": "integer" },
124
+ "notReady": { "enum": ["timeout", "exited", "spawn"] },
125
+ "wallclockStartNs": {
126
+ "type": "string",
127
+ "description": "decimal nanoseconds from the run's start"
128
+ },
129
+ "wallclockEndNs": { "type": "string" }
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,168 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "vx why --format json",
4
+ "description": "A task's run against its previous run. On history older than run ids, `why` and `diff` are null and `explanation` carries the latest cache entry.",
5
+ "oneOf": [
6
+ {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": ["taskId", "runId", "why", "diff", "roots"],
10
+ "properties": {
11
+ "taskId": { "type": "string" },
12
+ "runId": { "type": "string" },
13
+ "why": { "$ref": "#/$defs/why" },
14
+ "diff": { "$ref": "#/$defs/diff" },
15
+ "roots": {
16
+ "type": "array",
17
+ "description": "The tasks under moved upstreams whose own key components moved in this run, each with the chain from the asked task down to it.",
18
+ "items": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": ["chain", "entries"],
22
+ "properties": {
23
+ "chain": { "type": "array", "items": { "type": "string" } },
24
+ "entries": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "required": ["kind", "name", "change", "before", "after"],
30
+ "properties": {
31
+ "kind": { "type": "string" },
32
+ "name": { "type": "string" },
33
+ "change": { "enum": ["added", "removed", "changed"] },
34
+ "before": { "type": ["string", "null"] },
35
+ "after": { "type": ["string", "null"] }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ },
44
+ {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["taskId", "why", "diff", "explanation"],
48
+ "properties": {
49
+ "taskId": { "type": "string" },
50
+ "why": { "type": "null" },
51
+ "diff": { "type": "null" },
52
+ "explanation": { "$ref": "#/$defs/explanation" }
53
+ }
54
+ }
55
+ ],
56
+ "$defs": {
57
+ "why": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "required": ["runId", "taskId", "found", "thisRun", "previousRun", "hashChanged", "note"],
61
+ "properties": {
62
+ "runId": { "type": "string" },
63
+ "taskId": { "type": "string" },
64
+ "found": { "const": true },
65
+ "thisRun": {
66
+ "type": "object",
67
+ "additionalProperties": false,
68
+ "required": ["hash", "status", "cacheHit", "restored", "cached", "startedAt"],
69
+ "properties": {
70
+ "hash": {
71
+ "type": "string",
72
+ "description": "empty for a task that never ran (skipped)"
73
+ },
74
+ "status": { "type": "string" },
75
+ "cacheHit": { "type": ["boolean", "null"] },
76
+ "restored": {
77
+ "type": ["boolean", "null"],
78
+ "description": "on a hit: true when outputs were restored, false when up-to-date"
79
+ },
80
+ "cached": { "type": ["boolean", "null"] },
81
+ "startedAt": { "type": "integer", "description": "epoch ms" }
82
+ }
83
+ },
84
+ "previousRun": {
85
+ "type": ["object", "null"],
86
+ "description": "null on the task's first recorded run",
87
+ "additionalProperties": false,
88
+ "required": ["hash", "status", "cacheHit", "restored", "startedAt"],
89
+ "properties": {
90
+ "hash": { "type": "string" },
91
+ "status": { "type": "string" },
92
+ "cacheHit": { "type": ["boolean", "null"] },
93
+ "restored": {
94
+ "type": ["boolean", "null"],
95
+ "description": "on a hit: true when outputs were restored, false when up-to-date"
96
+ },
97
+ "startedAt": { "type": "integer" }
98
+ }
99
+ },
100
+ "hashChanged": {
101
+ "type": ["boolean", "null"],
102
+ "description": "null when either run has no key"
103
+ },
104
+ "note": { "type": "string" }
105
+ }
106
+ },
107
+ "diff": {
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+ "required": [
111
+ "runId",
112
+ "taskId",
113
+ "found",
114
+ "previousRunId",
115
+ "entries",
116
+ "unchangedCount",
117
+ "note"
118
+ ],
119
+ "properties": {
120
+ "runId": { "type": "string" },
121
+ "taskId": { "type": "string" },
122
+ "found": { "type": "boolean" },
123
+ "previousRunId": { "type": ["string", "null"] },
124
+ "entries": {
125
+ "type": "array",
126
+ "items": {
127
+ "type": "object",
128
+ "additionalProperties": false,
129
+ "required": ["kind", "name", "change", "before", "after"],
130
+ "properties": {
131
+ "kind": { "type": "string", "description": "files, env, runtime, upstream, …" },
132
+ "name": { "type": "string" },
133
+ "change": { "enum": ["added", "removed", "changed"] },
134
+ "before": { "type": ["string", "null"] },
135
+ "after": { "type": ["string", "null"] }
136
+ }
137
+ }
138
+ },
139
+ "unchangedCount": { "type": "integer" },
140
+ "note": { "type": "string" }
141
+ }
142
+ },
143
+ "explanation": {
144
+ "type": "object",
145
+ "additionalProperties": false,
146
+ "required": ["taskId", "project", "task", "latestEntry", "note"],
147
+ "properties": {
148
+ "taskId": { "type": "string" },
149
+ "project": { "type": "string" },
150
+ "task": { "type": "string" },
151
+ "latestEntry": {
152
+ "type": ["object", "null"],
153
+ "additionalProperties": false,
154
+ "required": ["hash", "command", "exitCode", "durationMs", "sizeBytes", "createdAt"],
155
+ "properties": {
156
+ "hash": { "type": "string" },
157
+ "command": { "type": "string" },
158
+ "exitCode": { "type": "integer" },
159
+ "durationMs": { "type": "number" },
160
+ "sizeBytes": { "type": "integer" },
161
+ "createdAt": { "type": "integer" }
162
+ }
163
+ },
164
+ "note": { "type": "string" }
165
+ }
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: vx
3
+ description: Run, inspect and debug tasks in a vx monorepo (vx.config.ts, vx.workspace.ts). Use to build or test projects, find what a change affects, read why a task failed or re-ran, and query the cache as JSON.
4
+ ---
5
+
6
+ # vx
7
+
8
+ vx is the workspace's task runner and build cache. Run tasks through it, not
9
+ through package scripts, so results are cached and ordered by their
10
+ dependencies.
11
+
12
+ ## Run
13
+
14
+ ```sh
15
+ vx run build --all # a task in every project that declares it
16
+ vx run test --affected=origin/main # only what the diff since origin/main reaches
17
+ vx run app#build # one project's task (and what it depends on)
18
+ vx run build --all --format json # the result as JSON on stdout; logs go to stderr
19
+ ```
20
+
21
+ The exit code is the verdict: `0` every task passed or hit the cache, `1` a
22
+ task failed or was skipped, or the command was refused. `--format json`
23
+ prints `schemas/summary.json` (`runId`, `ok`, `exitCode`, a row per task).
24
+ Without a TTY a missing task name never prompts: vx exits 1 and lists the
25
+ tasks it found.
26
+
27
+ ## Look before running
28
+
29
+ ```sh
30
+ vx show --format json # projects, their tasks and dependencies
31
+ vx run test --affected=origin/main --dry=json # what would run, cache hit or miss, and why
32
+ ```
33
+
34
+ Under `--affected`, each kept task in `--dry=json` carries `affected`:
35
+ `{ kind, file?, project?, via? }`, where `kind` is `input` (a changed file
36
+ is one of its inputs), `project`, `package`, `named` or `selected`, and
37
+ `via` is the `dependsOn` chain that carried the change.
38
+
39
+ ## When a task fails
40
+
41
+ ```sh
42
+ vx last --failed --format json # the last failed run; a failed row has output and locations
43
+ vx last --log app#build # one task's output: a failure's, or its cached log
44
+ ```
45
+
46
+ A failed row's `output` is the task's output as plain text (secrets masked)
47
+ and `locations` the files it names as `{ file, line?, col? }`. Read those
48
+ files, fix, and re-run the same command: cached tasks that passed replay from the cache.
49
+
50
+ ## When a task re-ran unexpectedly
51
+
52
+ ```sh
53
+ vx why app#build --format json # which input changed, down to the root cause
54
+ ```
55
+
56
+ ## Workspace health
57
+
58
+ ```sh
59
+ vx info --format json # vx and Bun versions, projects, config errors, plugins, cache
60
+ ```
61
+
62
+ ## MCP
63
+
64
+ When `@vzn/vx-mcp` is a plugin in `vx.workspace.ts`, `vx mcp` serves the
65
+ same facts and runs tasks. Adoption does not add it: install
66
+ `@vzn/vx-mcp` and put `mcp()` (from `'@vzn/vx-mcp'`) in `plugins`. Its tools: `listTasks`, `getCacheStats`,
67
+ `getRunHistory`, `explainCacheKey`, `whyDidThisRerun`, `getFailures`, `getTaskLog`, `getConfig`,
68
+ `getWorkspaceInfo`, `runTasks`, `planTasks`. `runTasks` answers what
69
+ `vx run <tasks> --format json` prints, `planTasks` what
70
+ `vx run <tasks> --dry=json` prints; the others only read.
71
+
72
+ ## Rules
73
+
74
+ - Never delete or edit the cache by hand; vx repairs it itself.
75
+ - A sandboxed task that fails on a denied path: grant that one path in
76
+ `exec.sandbox.allow`. Never remove `sandbox` or widen it further.
77
+ - A cached task declares its inputs (`cache.inputs.files`); if a change
78
+ does not re-run a task, the file is not one of its inputs. Fix the
79
+ config, do not force.
80
+ - Every JSON shape is a JSON Schema in `node_modules/@vzn/vx/schemas/`.
81
+ - Docs as markdown: https://vznjs.github.io/vx/llms.txt (its index).
82
+ When that host is unreachable, the same pages are
83
+ `https://raw.githubusercontent.com/vznjs/vx/main/packages/vx-docs/src/content/docs/<page>.md`:
84
+ `quickstart`, `guides/migrate`, `guides/configure`, `guides/agents`,
85
+ `guides/troubleshooting`, `guides/ci`, `guides/plugins`.
package/src/bin.ts CHANGED
@@ -32,12 +32,15 @@ async function main(): Promise<void> {
32
32
  } catch (err) {
33
33
  const {
34
34
  configErrorFrame,
35
+ errorCode,
36
+ errorDocument,
35
37
  fsRefusalHint,
36
38
  isFsRefusal,
37
39
  isOutOfFds,
38
40
  isUserError,
39
41
  maskedLine,
40
42
  OUT_OF_FDS_HINT,
43
+ wantsJson,
41
44
  } = await import('./util/index.js')
42
45
  // A plugin's failure quotes what it was told (a remote's reply, a
43
46
  // header), and a config's own throw is its author's text: a secret in
@@ -64,6 +67,12 @@ async function main(): Promise<void> {
64
67
  const message = err instanceof Error ? (err.stack ?? err.message) : String(err)
65
68
  say(`vx: ${message}\n`)
66
69
  }
70
+ // A verb asked for JSON answers JSON on a refusal too: an agent reading
71
+ // stdout got nothing to parse, and only prose on stderr to branch on.
72
+ if (wantsJson(argv)) {
73
+ const message = err instanceof Error ? err.message : String(err)
74
+ process.stdout.write(errorDocument(errorCode(err), maskedLine(message)))
75
+ }
67
76
  // Same reason as the success path above: a large stderr is truncated by
68
77
  // `process.exit` too, and an error message cut in half is the one a
69
78
  // reader most needs whole.