@vzn/vx 0.0.636 → 0.0.637

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 (141) hide show
  1. package/README.md +39 -11
  2. package/docs/caching.md +213 -207
  3. package/docs/cli.md +699 -655
  4. package/docs/execution.md +67 -75
  5. package/docs/features.md +44 -40
  6. package/docs/patterns.md +26 -25
  7. package/docs/schema.md +376 -198
  8. package/docs/security.md +76 -39
  9. package/launcher.sh +43 -0
  10. package/package.json +5 -5
  11. package/schemas/cache.json +41 -19
  12. package/schemas/info.json +23 -11
  13. package/schemas/last.json +48 -2
  14. package/schemas/lock.json +1 -1
  15. package/schemas/plan.json +2 -2
  16. package/schemas/show.json +5 -3
  17. package/schemas/why.json +15 -1
  18. package/skills/vx/SKILL.md +16 -12
  19. package/src/bin.ts +17 -2
  20. package/src/cache/archive.ts +11 -0
  21. package/src/cache/cache.ts +105 -53
  22. package/src/cache/git-inputs.ts +277 -63
  23. package/src/cache/index.ts +7 -1
  24. package/src/cache/inputs.ts +125 -35
  25. package/src/cache/key-fold.ts +8 -7
  26. package/src/cache/layer.ts +8 -17
  27. package/src/cache/layered-cache.ts +16 -102
  28. package/src/cache/policy.ts +25 -29
  29. package/src/cache/run-history.ts +36 -14
  30. package/src/cache/schema.ts +21 -23
  31. package/src/cache/scoped-cache.ts +224 -0
  32. package/src/cli/cache.ts +150 -28
  33. package/src/cli/completions.ts +2 -2
  34. package/src/cli/core-alias.ts +1 -1
  35. package/src/cli/foreign-flags.ts +13 -12
  36. package/src/cli/help.ts +94 -63
  37. package/src/cli/index.ts +42 -13
  38. package/src/cli/info.ts +38 -23
  39. package/src/cli/init.ts +115 -406
  40. package/src/cli/last.ts +145 -4
  41. package/src/cli/lock.ts +333 -111
  42. package/src/cli/plan-format.ts +2 -2
  43. package/src/cli/plugin-commands.ts +5 -9
  44. package/src/cli/plugin-templates.ts +10 -10
  45. package/src/cli/run.ts +103 -149
  46. package/src/cli/select.ts +33 -39
  47. package/src/cli/show.ts +17 -19
  48. package/src/cli/task-verb.ts +2 -9
  49. package/src/cli/upgrade.ts +402 -145
  50. package/src/cli/watch-cycle.ts +9 -2
  51. package/src/cli/watch-filter.ts +33 -60
  52. package/src/cli/watch-fs.ts +29 -16
  53. package/src/cli/watch-judge.ts +12 -3
  54. package/src/cli/watch-set.ts +16 -42
  55. package/src/cli/watch.ts +188 -215
  56. package/src/cli/why.ts +28 -17
  57. package/src/cli/workspace-config.ts +31 -68
  58. package/src/config.ts +169 -70
  59. package/src/exec/env.ts +1 -31
  60. package/src/exec/executor.ts +1 -5
  61. package/src/exec/kill-tree.ts +2 -1
  62. package/src/exec/sandbox-runtime.ts +12 -1
  63. package/src/exec/sandbox-violations.ts +9 -1
  64. package/src/graph/dependency-spec.ts +1 -1
  65. package/src/graph/index.ts +1 -0
  66. package/src/graph/priorities.ts +1 -1
  67. package/src/graph/scheduler.ts +19 -7
  68. package/src/graph/task-graph.ts +44 -20
  69. package/src/handoff.ts +169 -0
  70. package/src/index.ts +24 -57
  71. package/src/orchestrator/affected-tasks.ts +7 -5
  72. package/src/orchestrator/command-windows.ts +44 -0
  73. package/src/orchestrator/deferred-outputs.ts +12 -10
  74. package/src/orchestrator/doctor.ts +98 -110
  75. package/src/orchestrator/download-policy.ts +3 -3
  76. package/src/orchestrator/events.ts +19 -3
  77. package/src/orchestrator/excluded-keys.ts +0 -2
  78. package/src/orchestrator/execute-task.ts +158 -63
  79. package/src/orchestrator/fingerprint-watch.ts +18 -15
  80. package/src/orchestrator/forecast.ts +10 -2
  81. package/src/orchestrator/framed-output.ts +8 -3
  82. package/src/orchestrator/graph-stage.ts +5 -5
  83. package/src/orchestrator/history.ts +70 -30
  84. package/src/orchestrator/hit-restore.ts +15 -3
  85. package/src/orchestrator/index.ts +18 -4
  86. package/src/orchestrator/local-shortcircuit.ts +2 -1
  87. package/src/orchestrator/lockfile-claim.ts +79 -20
  88. package/src/orchestrator/logger.ts +71 -39
  89. package/src/orchestrator/metrics.ts +71 -6
  90. package/src/orchestrator/miss-save.ts +3 -0
  91. package/src/orchestrator/options.ts +42 -29
  92. package/src/orchestrator/output-host.ts +72 -0
  93. package/src/orchestrator/plan.ts +6 -8
  94. package/src/orchestrator/plugin-host.ts +87 -63
  95. package/src/orchestrator/plugin.ts +94 -60
  96. package/src/orchestrator/prepare.ts +113 -72
  97. package/src/orchestrator/projects.ts +102 -99
  98. package/src/orchestrator/remote-prefetch.ts +2 -1
  99. package/src/orchestrator/run-context.ts +25 -6
  100. package/src/orchestrator/run-failures.ts +17 -8
  101. package/src/orchestrator/run-lock.ts +1 -1
  102. package/src/orchestrator/run-report.ts +9 -9
  103. package/src/orchestrator/run-settings.ts +48 -0
  104. package/src/orchestrator/run.ts +121 -95
  105. package/src/orchestrator/sandbox-fix.ts +39 -20
  106. package/src/orchestrator/sandbox-request.ts +84 -13
  107. package/src/orchestrator/signals.ts +1 -1
  108. package/src/orchestrator/stable-keys.ts +13 -11
  109. package/src/orchestrator/status-line.ts +48 -6
  110. package/src/orchestrator/summary.ts +46 -62
  111. package/src/orchestrator/task-hash.ts +12 -83
  112. package/src/orchestrator/telemetry.ts +3 -3
  113. package/src/settings.ts +417 -0
  114. package/src/util/errors.ts +1 -1
  115. package/src/util/essential-env.ts +31 -0
  116. package/src/util/hide-env.ts +107 -0
  117. package/src/util/index.ts +8 -2
  118. package/src/util/paths.ts +10 -0
  119. package/src/util/settle.ts +49 -30
  120. package/src/util/size.ts +23 -4
  121. package/src/util/timing.ts +3 -2
  122. package/src/util/ulid.ts +1 -1
  123. package/src/util/verbs.ts +0 -15
  124. package/src/workspace/affected.ts +77 -222
  125. package/src/workspace/config-eval.ts +15 -20
  126. package/src/workspace/config-imports.ts +78 -3
  127. package/src/workspace/config-schema.ts +209 -268
  128. package/src/workspace/dependency-rules.ts +57 -0
  129. package/src/workspace/filter.ts +2 -2
  130. package/src/workspace/fingerprint.ts +11 -121
  131. package/src/workspace/index.ts +20 -27
  132. package/src/workspace/load-reads.ts +4 -4
  133. package/src/workspace/lockfile.ts +96 -140
  134. package/src/workspace/migration.ts +242 -146
  135. package/src/workspace/package-graph.ts +30 -216
  136. package/src/workspace/project-loader.ts +80 -285
  137. package/src/workspace/repo-id.ts +2 -1
  138. package/src/workspace/workspace.ts +362 -831
  139. package/src/cache/config-evals.ts +0 -130
  140. package/src/workspace/config-cache.ts +0 -661
  141. package/src/workspace/migrate-scripts.ts +0 -1121
package/src/index.ts CHANGED
@@ -9,19 +9,11 @@
9
9
  export { VERSION } from './version.js'
10
10
 
11
11
  // Clean error type — user-input failures print a message without a stack.
12
- // `clampInt` rides along for the same reason the status predicates do:
13
- // without it on the façade an integration package writes its own, and that
14
- // had already happened. A bounds helper whose floor is load-bearing (a
15
- // fractional SQL LIMIT is a datatype mismatch, not a smaller page) should
16
- // have one implementation.
17
- export { clampInt, UserError, isUserError } from './util/index.js'
12
+ export { UserError, isUserError } from './util/index.js'
18
13
  // Every hook a plugin may fill, in pipeline order — the one list the
19
14
  // loader, the host and `vx info` read; exported so a doc pin outside core
20
15
  // (the site's guides) holds its tables to the same list.
21
16
  export { PLUGIN_HOOKS, type PluginHook } from './config.js'
22
- // The workspace doctor's facts (`vx info`), for a host that answers the
23
- // same question over another surface — `@vzn/vx-mcp`'s `getWorkspaceInfo`.
24
- export { collectInfo, type CollectInfoOptions, type InfoFacts } from './orchestrator/index.js'
25
17
  // The machine as this process may use it — cores and memory capped by the
26
18
  // cgroup a container runs under. Core's default worker count reads the
27
19
  // first; a policy that budgets memory (`@vzn/vx-schedule-history`) reads
@@ -31,16 +23,15 @@ export { machineMemoryBytes, machineParallelism } from './util/index.js'
31
23
  // wildcards (`[` `]` are not; item 667) — for a plugin that turns declared
32
24
  // outputs into something else (`@vzn/vx-reapi`'s literal `output_paths`).
33
25
  export { isLiteralPattern, normalizeGlob } from './util/index.js'
34
- // A task's command as vx prints it: values of its `exec.env.secret` names
35
- // masked. `vx show` and telemetry show this; a plugin that hands a command
36
- // to someone else (`@vzn/vx-mcp`'s `listTasks`, an AI agent) does too (L-26).
37
- // `maskedLine` is the same for a line no one task owns: the error `vx mcp`
38
- // hands an agent (L-11).
39
- export { maskedCommand, maskedLine } from './util/index.js'
40
26
 
41
27
  // Schema types and helpers (used by user vx.config files and presets).
42
28
  export type {
43
29
  WorkspaceConfig,
30
+ RunSettings,
31
+ CacheSettings,
32
+ LockSettings,
33
+ WatchSettings,
34
+ Duration,
44
35
  ProjectConfig,
45
36
  TaskConfig,
46
37
  ExecConfig,
@@ -65,7 +56,7 @@ export { run, planRun, prepareRun } from './orchestrator/index.js'
65
56
  // The per-task duration history a `schedule` plugin learns from
66
57
  // (`@vzn/vx-schedule-history` does; a plugin package reaches core only
67
58
  // through this façade, which is what put these here).
68
- export { LocalHistoryProvider } from './orchestrator/index.js'
59
+ export { currentBranch, LocalHistoryProvider } from './orchestrator/index.js'
69
60
  export type {
70
61
  HistoryProvider,
71
62
  HistoryTable,
@@ -82,6 +73,7 @@ export type { AffectedChanges } from './workspace/index.js'
82
73
  export type { RunPlan, PlannedTask } from './orchestrator/index.js'
83
74
  export type { CiContext, GitContext, HostContext, WorkspaceIdentity } from './orchestrator/index.js'
84
75
  export type {
76
+ CacheFlags,
85
77
  CachePolicy,
86
78
  Logger,
87
79
  OutputView,
@@ -96,44 +88,40 @@ export { outputsOverlap, splitTaskId } from './graph/index.js'
96
88
  export type { TaskNode, TaskOutcome, TaskStatus } from './graph/index.js'
97
89
 
98
90
  // Cache classes + the layer interface (the `cache` capability's currency).
99
- export { Cache, LayeredCache } from './cache/index.js'
91
+ export { Cache } from './cache/index.js'
100
92
  export type { CacheLayer, RemoteCacheLayer, RunRecord, InvocationRecord } from './cache/index.js'
101
93
 
102
94
  // Workspace discovery + the project/config catalog surface — an
103
95
  // out-of-process service/CLI needs these. `loadProjectConfig` is the RAW
104
96
  // per-file load (`vx lock` freezes exactly that); the resolved view a run
105
97
  // or `vx show` sees — plugin stages applied — is `loadResolvedProjects`
106
- // below. Workspace's `listProjects` re-exports as `listProjectMetas` (the
107
- // bare name once belonged to a metrics query).
108
- export { findWorkspaceRoot } from './workspace/index.js'
109
- export {
110
- loadWorkspace,
111
- loadProjectConfig,
112
- listProjects as listProjectMetas,
113
- } from './workspace/index.js'
98
+ // below. `PROJECT_CONFIG_FILENAMES` are the names a project's config may
99
+ // have, in precedence order, for a tool that finds or writes one.
100
+ export { findWorkspaceRoot, PROJECT_CONFIG_FILENAMES } from './workspace/index.js'
101
+ export { loadProjectConfig } from './workspace/index.js'
114
102
  export type { ProjectMeta } from './workspace/index.js'
115
- // The package graph as a run sees it (workspace deps by manifest), for a tool
116
- // that needs a project's transitive closure the way `vx run` computes it.
117
- export { buildPackageGraph } from './workspace/index.js'
118
103
  // The run path's RESOLVED view — plugin `config` and `project` stages
119
- // applied, cached evaluations served — for a reader outside the CLI (the
120
- // MCP server's `listTasks`, an embedder's task catalog). What `vx show`
104
+ // applied — for a reader outside the CLI
105
+ // (`@vzn/vx-schedule-history`, an embedder's task catalog). What `vx show`
121
106
  // prints.
122
107
  export { loadResolvedProjects } from './orchestrator/index.js'
123
108
  export type { ProjectEntry } from './workspace/index.js'
124
109
  // The migration seam: how a generated config is planned, rendered, guarded
125
- // and written. `vx init` (package.json scripts) uses it in core;
126
- // `@vzn/vx-migrate` (Turbo, Nx) and any other adoption tool use it from here.
110
+ // and written. `@vzn/vx-migrate` (package.json scripts, Turbo, Nx) and any
111
+ // other adoption tool use it from here; `taskNameProblem` is why a name
112
+ // cannot be a task's, so a mapper leaves such a script out.
127
113
  export {
128
114
  applyMigration,
129
115
  foldScriptHooks,
130
116
  PERSISTENT_TODO,
131
117
  pruneOrphanPersistentNotes,
132
118
  quoteTsLiteral,
119
+ taskNameProblem,
133
120
  } from './workspace/index.js'
134
121
  export type {
135
122
  ApplyMigrationArgs,
136
123
  MigrationFormat,
124
+ MigrationMember,
137
125
  GeneratedProject,
138
126
  GeneratedTask,
139
127
  MigrationPlan,
@@ -143,7 +131,7 @@ export type {
143
131
  // Plugin API — the run-level extension points. Behavior capabilities
144
132
  // (executor / cache) change WHAT/HOW work runs; the observe-only `telemetry`
145
133
  // capability is the canonical data-export path and cannot change behavior.
146
- // A plugin is declared in vx.workspace.ts via defineWorkspace({ plugins }).
134
+ // A plugin is declared in .vx/workspace.ts via defineWorkspace({ plugins }).
147
135
  // See docs/design/observability-architecture-2026-06.md.
148
136
  // The claimant's shell for a lockfile plugin (`@vzn/vx-lockfile`):
149
137
  // the claim, the per-project key, the memo and the `--affected` diff around
@@ -153,6 +141,7 @@ export {
153
141
  reachDigests,
154
142
  type LockfileClaimHooks,
155
143
  type LockfileClaimOptions,
144
+ type LockfileImporter,
156
145
  type ReachGraph,
157
146
  } from './orchestrator/index.js'
158
147
  export type {
@@ -162,6 +151,7 @@ export type {
162
151
  PluginOptionKinds,
163
152
  CacheContext,
164
153
  ExecutorContext,
154
+ OutputContext,
165
155
  CommandContext,
166
156
  FingerprintChange,
167
157
  FingerprintClaim,
@@ -174,7 +164,7 @@ export type {
174
164
  AdmitContext,
175
165
  WorkspaceHookContext,
176
166
  DiscoverContext,
177
- NamedProject,
167
+ DiscoveredProject,
178
168
  PluginSetupContext,
179
169
  PluginHookName,
180
170
  PluginHookHandlers,
@@ -244,26 +234,3 @@ export type {
244
234
 
245
235
  // The serializable run result and its per-task views (what `run()` returns).
246
236
  export type { RunResult, TaskView, OutcomeView } from './orchestrator/index.js'
247
-
248
- // The run-history queries a reader outside the CLI takes today: `vx why`'s
249
- // answer, a run's failures and a task's log, which `@vzn/vx-mcp` serves. The event bus /
250
- // wire form and the other history readers (`listRuns`, `getRun`, …) left the
251
- // façade 2026-09-10 with no consumer; the telemetry seam above is the
252
- // canonical export path.
253
- export {
254
- cacheKeyDiff,
255
- latestRunId,
256
- resolveRunId,
257
- rootCauses,
258
- runFailures,
259
- taskLog,
260
- whyDidThisRerunQuery,
261
- } from './orchestrator/index.js'
262
- export type {
263
- CacheKeyDiff,
264
- OutputLocation,
265
- RootCause,
266
- RunFailures,
267
- TaskFailure,
268
- TaskLog,
269
- } from './orchestrator/index.js'
@@ -29,7 +29,7 @@ import type { VxPlugin } from './plugin.js'
29
29
  import { isDefaultBuild } from './projects.js'
30
30
 
31
31
  /** Every file in this set re-keys each of its project's tasks, whatever their inputs. */
32
- const KEYED_BY_EVERY_TASK = new Set(['package.json', ...PROJECT_CONFIG_FILENAMES])
32
+ const KEYED_BY_EVERY_TASK: ReadonlySet<string> = new Set(PROJECT_CONFIG_FILENAMES)
33
33
 
34
34
  /**
35
35
  * The projects every task of which the change reaches: the whole ones the
@@ -41,8 +41,9 @@ const KEYED_BY_EVERY_TASK = new Set(['package.json', ...PROJECT_CONFIG_FILENAMES
41
41
  function wholeProjects(
42
42
  changes: AffectedChanges,
43
43
  projects: ReadonlyMap<string, ProjectEntry>,
44
- ): Set<string> {
45
- const whole = new Set(changes.whole)
44
+ ): Map<string, string | undefined> {
45
+ // Each whole project, with the path no cached task claims when one did it.
46
+ const whole = new Map<string, string | undefined>([...changes.whole].map((n) => [n, undefined]))
46
47
  for (const [name, rels] of changes.paths) {
47
48
  if (whole.has(name)) continue
48
49
  const caches = Object.values(projects.get(name)?.config.tasks ?? {}).flatMap((t) =>
@@ -50,7 +51,8 @@ function wholeProjects(
50
51
  )
51
52
  const unclaimed = (rel: string): boolean =>
52
53
  KEYED_BY_EVERY_TASK.has(rel) || !caches.some((c) => declaresInput(c, rel, null))
53
- if (rels.some(unclaimed)) whole.add(name)
54
+ const rel = rels.find(unclaimed)
55
+ if (rel !== undefined) whole.set(name, rel)
54
56
  }
55
57
  return whole
56
58
  }
@@ -157,7 +159,7 @@ export function affectedRoots(
157
159
  return hit
158
160
  }
159
161
  if (whole.has(n.projectName)) {
160
- seeds?.set(n.id, inProject(n.projectName, undefined))
162
+ seeds?.set(n.id, inProject(n.projectName, whole.get(n.projectName)))
161
163
  return true
162
164
  }
163
165
  const rel = rels.find((r) => declaresInput(cache, r, null))
@@ -0,0 +1,44 @@
1
+ // When each command of the run ran and where it may write, so
2
+ // `inputStability.strategy: 'mutable'` can tell a task's own identical
3
+ // rewrite from another task's write and restore (STATUS item 28): a file
4
+ // whose bytes match the key after a write is the task's own only if no
5
+ // other command that may write it ran between the describe and that write.
6
+
7
+ import path from 'node:path'
8
+ import type { WriteReach } from './sandbox-request.js'
9
+
10
+ interface Window {
11
+ id: string
12
+ projectDir: string
13
+ reach: WriteReach
14
+ from: number
15
+ to: number
16
+ }
17
+
18
+ export interface CommandWindows {
19
+ /** A command starts; the returned function marks its end. A server never calls it. */
20
+ open(id: string, projectDir: string, reach: WriteReach): () => void
21
+ /** Another command that may write `file` and ran at some point in [from, to]. */
22
+ writerDuring(self: string, file: string, from: number, to: number): string | undefined
23
+ }
24
+
25
+ export function commandWindows(): CommandWindows {
26
+ const windows: Window[] = []
27
+ return {
28
+ open(id, projectDir, reach) {
29
+ if (reach === 'none') return () => {}
30
+ const w: Window = { id, projectDir, reach, from: Date.now(), to: Infinity }
31
+ windows.push(w)
32
+ return () => {
33
+ w.to = Date.now()
34
+ }
35
+ },
36
+ writerDuring(self, file, from, to) {
37
+ for (const w of windows) {
38
+ if (w.id === self || w.from > to || w.to < from) continue
39
+ if (w.reach === 'workspace' || file.startsWith(w.projectDir + path.sep)) return w.id
40
+ }
41
+ return undefined
42
+ },
43
+ }
44
+ }
@@ -1,5 +1,5 @@
1
1
  // The run-scoped registry of tasks whose outputs were left in the remote
2
- // store (`--download=none`), and the lazy materialisation that fetches
2
+ // store (`--cache-download=none`), and the lazy materialisation that fetches
3
3
  // them when a locally-placed task turns out to need them.
4
4
  //
5
5
  // See docs/design/download-policy-cas-cache-2026-08.md §5. The two
@@ -9,11 +9,12 @@
9
9
  // remote store (the executor's own record) is the entry.
10
10
  // - Materialisation CONVERGES: after the bytes land, core runs the
11
11
  // ordinary `cache.save`, so the machine is indistinguishable from a
12
- // `--download=all` run and every later reader (get, prune, verify,
12
+ // `--cache-download=all` run and every later reader (get, prune, verify,
13
13
  // metrics) sees one entry shape, not two.
14
14
 
15
15
  import {
16
16
  cleanOutputs,
17
+ committedFiles,
17
18
  resolveOutputs,
18
19
  type CacheLayer,
19
20
  type GitFilesCache,
@@ -73,7 +74,7 @@ export class DeferredOutputs {
73
74
  */
74
75
  async materializeFor(node: TaskNode): Promise<void> {
75
76
  // Nothing deferred, nothing to find: the closure walk is the size of
76
- // the graph, and every task of a run without `--download=none` paid it
77
+ // the graph, and every task of a run without `--cache-download=none` paid it
77
78
  // (1.4 s of a 3,270-task cold run's main thread).
78
79
  if (this.entries.size === 0) return
79
80
  const needed: string[] = []
@@ -116,7 +117,12 @@ export class DeferredOutputs {
116
117
  // workspace-anchored can reach this path.
117
118
  const outputs = producer.config.cache?.outputs.files ?? []
118
119
  const nestedProjectDirs = this.args.nestedDirsByProject.get(producer.projectName) ?? []
119
- const cleanArgs = { projectDir: producer.projectDir, outputs, nestedProjectDirs }
120
+ const cleanArgs = {
121
+ projectDir: producer.projectDir,
122
+ outputs,
123
+ nestedProjectDirs,
124
+ committed: committedFiles(this.args.gitFilesCache, producer.projectDir),
125
+ }
120
126
 
121
127
  // Mirror `restoreHit`'s sequencing so the two paths cannot drift: wipe
122
128
  // the declared outputs, write, then tell the git snapshot what moved.
@@ -135,14 +141,10 @@ export class DeferredOutputs {
135
141
  } catch (err) {
136
142
  const msg = err instanceof Error ? err.message : String(err)
137
143
  throw new Error(
138
- `could not fetch deferred outputs of ${taskId}: ${msg} — re-run it with --force, or use --download=all`,
144
+ `could not fetch deferred outputs of ${taskId}: ${msg} — re-run it with --force, or use --cache-download=all`,
139
145
  )
140
146
  }
141
- const outputFiles = await resolveOutputs({
142
- projectDir: producer.projectDir,
143
- outputs,
144
- nestedProjectDirs,
145
- })
147
+ const outputFiles = await resolveOutputs(cleanArgs)
146
148
  this.args.gitFilesCache?.markOutputsChanged(
147
149
  producer.projectDir,
148
150
  outputFiles.map((f) => f.slice(producer.projectDir.length + 1)),