@vzn/vx 0.0.15 → 0.0.18

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 (111) hide show
  1. package/README.md +91 -130
  2. package/index.ts +10 -0
  3. package/launcher.mjs +10 -11
  4. package/package.json +19 -5
  5. package/plugins/local-cache/index.ts +2 -0
  6. package/plugins/local-executor/index.ts +2 -0
  7. package/plugins/schedule-history/index.ts +2 -0
  8. package/src/bin.ts +2 -2
  9. package/src/cache/archive.ts +557 -0
  10. package/src/cache/cache.ts +929 -250
  11. package/src/cache/cas-backend.ts +20 -2
  12. package/src/cache/chained-cache.ts +202 -0
  13. package/src/cache/digest.ts +0 -15
  14. package/src/cache/index.ts +13 -3
  15. package/src/cache/inputs.ts +445 -41
  16. package/src/cache/layered-cache.ts +129 -30
  17. package/src/cache/tar-stream.ts +358 -0
  18. package/src/cli/cache.ts +36 -10
  19. package/src/cli/help.ts +167 -128
  20. package/src/cli/index.ts +74 -43
  21. package/src/cli/info.ts +30 -0
  22. package/src/cli/last.ts +140 -0
  23. package/src/cli/migrate-emit.ts +21 -0
  24. package/src/cli/migrate-nx.ts +23 -0
  25. package/src/cli/migrate-scripts.ts +127 -0
  26. package/src/cli/migrate-turbo.ts +43 -6
  27. package/src/cli/migrate.ts +119 -33
  28. package/src/cli/plan-format.ts +58 -3
  29. package/src/cli/plugin-commands.ts +96 -0
  30. package/src/cli/prune.ts +291 -0
  31. package/src/cli/run.ts +272 -80
  32. package/src/cli/upgrade.ts +7 -6
  33. package/src/cli/watch.ts +259 -61
  34. package/src/cli/why.ts +193 -0
  35. package/src/config.ts +40 -23
  36. package/src/exec/env.ts +7 -1
  37. package/src/exec/executor.ts +239 -0
  38. package/src/exec/index.ts +19 -1
  39. package/src/exec/runner.ts +116 -29
  40. package/src/exec/sandbox-runtime.ts +194 -24
  41. package/src/graph/index.ts +3 -1
  42. package/src/graph/scheduler.ts +92 -18
  43. package/src/graph/task-graph.ts +220 -6
  44. package/src/index.ts +82 -84
  45. package/src/orchestrator/deferred-outputs.ts +144 -0
  46. package/src/orchestrator/download-policy.ts +171 -0
  47. package/src/orchestrator/events.ts +30 -0
  48. package/src/orchestrator/execute-task.ts +328 -94
  49. package/src/orchestrator/failure-mode.ts +61 -0
  50. package/src/orchestrator/framed-output.ts +87 -35
  51. package/src/orchestrator/history.ts +78 -31
  52. package/src/orchestrator/index.ts +43 -90
  53. package/src/orchestrator/local-shortcircuit.ts +22 -0
  54. package/src/orchestrator/logger.ts +196 -14
  55. package/src/orchestrator/metrics.ts +57 -1376
  56. package/src/orchestrator/missing-plugin.ts +9 -0
  57. package/src/orchestrator/options.ts +22 -4
  58. package/src/orchestrator/plan.ts +144 -1
  59. package/src/orchestrator/plugin-host.ts +199 -119
  60. package/src/orchestrator/plugin.ts +144 -57
  61. package/src/orchestrator/prepare.ts +220 -69
  62. package/src/orchestrator/remote-prefetch.ts +27 -8
  63. package/src/orchestrator/resources.ts +20 -34
  64. package/src/orchestrator/run-artifacts.ts +41 -16
  65. package/src/orchestrator/run-context.ts +167 -29
  66. package/src/orchestrator/run-report.ts +73 -46
  67. package/src/orchestrator/run.ts +590 -122
  68. package/src/orchestrator/stable-keys.ts +74 -33
  69. package/src/orchestrator/status-line.ts +47 -8
  70. package/src/orchestrator/summary.ts +22 -1
  71. package/src/orchestrator/tally.ts +78 -35
  72. package/src/orchestrator/task-hash.ts +105 -11
  73. package/src/orchestrator/task-log-buffer.ts +291 -0
  74. package/src/orchestrator/telemetry-host.ts +24 -6
  75. package/src/orchestrator/telemetry.ts +181 -21
  76. package/src/orchestrator/upstream.ts +72 -31
  77. package/src/orchestrator/verify.ts +12 -2
  78. package/src/plugins/local-cache/index.ts +11 -0
  79. package/src/plugins/local-executor/index.ts +45 -0
  80. package/src/plugins/schedule-history/index.ts +109 -0
  81. package/src/util/edit-distance.ts +21 -0
  82. package/src/util/errors.ts +15 -1
  83. package/src/util/index.ts +7 -2
  84. package/src/util/num.ts +37 -0
  85. package/src/util/paths.ts +43 -0
  86. package/src/util/settle.ts +48 -0
  87. package/src/util/size.ts +5 -1
  88. package/src/util/tail.ts +66 -0
  89. package/src/util/timing.ts +63 -0
  90. package/src/workspace/affected.ts +142 -15
  91. package/src/workspace/config-cache.ts +304 -0
  92. package/src/workspace/config-eval.ts +210 -0
  93. package/src/workspace/config-imports.ts +161 -0
  94. package/src/workspace/filter.ts +22 -4
  95. package/src/workspace/fingerprint.ts +16 -1
  96. package/src/workspace/index.ts +15 -3
  97. package/src/workspace/lockfile.ts +13 -2
  98. package/src/workspace/nested-dirs.ts +21 -7
  99. package/src/workspace/package-graph.ts +23 -20
  100. package/src/workspace/project-loader.ts +503 -59
  101. package/src/workspace/workspace.ts +213 -72
  102. package/src/cache/tar.ts +0 -343
  103. package/src/cli/backend.ts +0 -23
  104. package/src/cli/mcp-rpc.ts +0 -292
  105. package/src/cli/mcp.ts +0 -92
  106. package/src/orchestrator/devframe-surface.ts +0 -74
  107. package/src/orchestrator/predict.ts +0 -106
  108. package/src/orchestrator/protocol.ts +0 -156
  109. package/src/orchestrator/run-state.ts +0 -145
  110. package/src/orchestrator/wire-render.ts +0 -92
  111. package/src/orchestrator/wire.ts +0 -212
@@ -1,64 +1,131 @@
1
- // Plugin API — in-process extensions on top of the run event bus.
1
+ // Plugin API — the ONE `VxPlugin` contract (docs/design/pipeline-2026-09.md).
2
2
  //
3
- // Collapses what `architecture-review-2026-06.md §4.1` calls the
4
- // in-process subscriber + the WS subscriber into ONE Plugin contract.
5
- // Users register plugins via defineWorkspace({ plugins }); each plugin
6
- // receives a PluginContext with the bus + workspace metadata and can
7
- // install lifecycle hooks. Plugins observe; they do not redirect.
3
+ // Users register plugins via defineWorkspace({ plugins }). Behaviour
4
+ // capabilities (`cache`, `executor`) return objects core calls INTO;
5
+ // observe capabilities (`telemetry`, `setup` on the bus) can only read.
8
6
  //
9
- // Crash isolation: a plugin that throws on setup() is wrapped in a
10
- // UserError-style failure message naming the plugin (clean abort, no
11
- // stack), matching the existing 'broken vx.config.ts' error style. A
12
- // plugin that throws inside a hook is logged + the plugin is disabled
13
- // for the remainder of the run (mirrors the deleted Observer's
14
- // makeSafeObserver pattern; surfaces are isolated from execution).
15
- //
16
- // Hook return values: today every hook is fire-and-forget (the bus IS
17
- // the message pipe; hooks just give users an ergonomic place to read
18
- // it). One write-capable hook is reserved for a future iteration —
19
- // onCacheLookup returning { skip: true } — but not in v1.
7
+ // Crash isolation: a plugin that throws in a load-bearing hook aborts the
8
+ // run with a clean UserError naming plugin + hook. A plugin that throws
9
+ // inside a bus hook is logged and disabled for the remainder of the run.
20
10
 
21
11
  import type { Cache, CacheLayer, CachePolicy } from '../cache/index.js'
12
+ import type { ProjectConfig, WorkspaceConfig } from '../config.js'
13
+ import type { TaskExecutor } from '../exec/index.js'
22
14
  import type { TaskNode, TaskOutcome } from '../graph/index.js'
23
15
  import { UserError } from '../util/index.js'
24
- import type { EventBus, RunStartInfo, WireEvent } from './events.js'
25
- import type { RunBackend, RunRequest } from './protocol.js'
16
+ import type { EventBus, RunStartInfo } from './events.js'
26
17
  import type { TelemetryContext, TelemetrySink } from './telemetry.js'
27
18
 
28
19
  /**
29
- * A vx plugin. Contributes any subset of three RUN-LEVEL infrastructure
30
- * capabilities — where work routes (backend), which cache is used (cache),
31
- * who observes the run (eventSink). It NEVER changes how a task executes
32
- * (Architecture principle #3: shell is the API). Registered explicitly in
33
- * vx.workspace.ts via defineWorkspace({ plugins: [...] }). No auto-discovery.
20
+ * A vx plugin. Contributes any subset of the run-level capabilities —
21
+ * where work runs (executor), which cache is used (cache), who
22
+ * observes the run (telemetry). It never changes WHAT a task is (the
23
+ * command string — principle #3), only where and how that command is
24
+ * executed. Registered explicitly in vx.workspace.ts via
25
+ * defineWorkspace({ plugins: [...] }). No auto-discovery.
34
26
  *
35
27
  * The old observe-only `Plugin` (`{ name, setup(ctx) }`) is a subset of
36
28
  * this shape: a plugin with only `setup` installs and runs exactly as
37
- * before via `installPlugins`. The new capabilities are consulted by
38
- * `plugin-host.ts`, each falling back to today's default.
29
+ * before via `installPlugins`. The capabilities are consulted by
30
+ * `plugin-host.ts`; core's own executor and cache are plugins too
31
+ * (src/plugins/), declared by the workspace — there is no fallback
32
+ * outside the list.
39
33
  */
40
34
  export interface VxPlugin {
41
35
  /** Stable identifier, convention `'org/name'`. Used in errors + precedence logs. */
42
36
  readonly name: string
43
37
 
44
- // --- BEHAVIOR capabilities (change WHAT/HOW work runs — opt-in) -----------
38
+ // --- PIPELINE stages (shape the run before it executes — opt-in) ----------
39
+ //
40
+ // Each receives the object core is about to use and edits it IN PLACE;
41
+ // core re-validates after the last plugin, so a plugin cannot produce what
42
+ // the loader would refuse from a user. Declaration order is the order.
43
+ // Whatever a stage changes reaches the cache key by construction: the key
44
+ // hashes the task config AFTER `project` ran. See
45
+ // docs/design/pipeline-2026-09.md.
46
+
47
+ /**
48
+ * The workspace config, before anything is derived from it (concurrency,
49
+ * cacheDir, timeout, …). `plugins` is already fixed by the time this runs.
50
+ */
51
+ config?(workspace: WorkspaceConfig, ctx: WorkspaceHookContext): void | Promise<void>
52
+
53
+ /**
54
+ * One project's validated config, right after it loaded and before the
55
+ * graph is built. Add, remove or edit tasks. Runs for every loaded project
56
+ * on every run — a config's cached evaluation is the user's file, and the
57
+ * hook is applied on top of it live.
58
+ */
59
+ project?(config: ProjectConfig, ctx: ProjectHookContext): void | Promise<void>
45
60
 
46
61
  /**
47
- * Contribute a run backend. Returns a RunBackend (run(request) → result),
48
- * or undefined to decline (core then tries the next plugin, else the
49
- * fallback). Consulted ONCE per run, before scheduling. At most one
50
- * plugin's backend is used (first non-undefined, in declaration order).
62
+ * The task graph, after `dependsOn` expansion and before scheduling. Add
63
+ * or drop edges (`node.deps`), mark tasks requested, adjust resources. A
64
+ * dep naming a task that is not in the graph, or a cycle, is refused with
65
+ * the plugin's name.
51
66
  */
52
- backend?(ctx: BackendContext): RunBackend | undefined | Promise<RunBackend | undefined>
67
+ graph?(nodes: Map<string, TaskNode>, ctx: GraphHookContext): void | Promise<void>
68
+
69
+ /**
70
+ * Extra cache-key material for one task: a `{ name: value }` record that
71
+ * is folded into the key (and named in `vx why`) — a tool version, a
72
+ * feature flag, anything the declared inputs cannot see. Runs once per
73
+ * task per run, before any key is derived. Return undefined to add
74
+ * nothing. Values must be deterministic for the same inputs or the key
75
+ * never hits.
76
+ */
77
+ key?(
78
+ task: TaskNode,
79
+ ctx: KeyHookContext,
80
+ ):
81
+ | Readonly<Record<string, string>>
82
+ | undefined
83
+ | Promise<Readonly<Record<string, string>> | undefined>
84
+
85
+ /**
86
+ * Scheduling priorities: task id → weight, higher runs first among READY
87
+ * tasks (merged over the structural baseline, which stays the tie-break).
88
+ * Runs once, after the graph is final. A later plugin's weight for a task
89
+ * overrides an earlier one's. Return undefined to leave the baseline.
90
+ */
91
+ schedule?(
92
+ nodes: ReadonlyMap<string, TaskNode>,
93
+ ctx: ScheduleHookContext,
94
+ ): ReadonlyMap<string, number> | undefined | Promise<ReadonlyMap<string, number> | undefined>
95
+
96
+ // --- CLI verbs (opt-in) ---------------------------------------------------
97
+
98
+ /**
99
+ * Verbs this plugin adds to the `vx` CLI, keyed by name. Consulted only
100
+ * for a verb core does not know — core's own verbs always win — and only
101
+ * when the cwd is inside a workspace that declares the plugin. `vx help`
102
+ * lists them under "Plugin commands". A verb's exit code is the process
103
+ * exit code; a thrown `UserError` prints cleanly, like core's own.
104
+ */
105
+ readonly commands?: Readonly<Record<string, PluginCommand>>
106
+
107
+ // --- BEHAVIOR capabilities (change WHAT/HOW work runs — opt-in) -----------
53
108
 
54
109
  /**
55
110
  * Contribute a cache layer. Returns a CacheLayer wrapping (or replacing)
56
111
  * the local Cache, or undefined to decline. Consulted ONCE per prepareRun.
57
- * Precedence: first non-undefined plugin cache wins; else core's env-var
58
- * Turbo-wire LayeredCache; else the bare local Cache.
112
+ * Precedence: first non-undefined plugin cache wins, in declaration
113
+ * order. `@vzn/vx/plugins/local-cache` hands back the bare local Cache;
114
+ * a list with no provider fails the run before any task starts.
59
115
  */
60
116
  cache?(ctx: CacheContext): CacheLayer | undefined | Promise<CacheLayer | undefined>
61
117
 
118
+ /**
119
+ * Contribute a task executor — WHERE one task's command runs. Consulted
120
+ * ONCE per run; every contributed executor is kept, in declaration order,
121
+ * and per task the first whose `accepts()` passes executes it. Nothing is
122
+ * appended: a workspace declares `localExecutorPlugin()` (from
123
+ * `@vzn/vx/plugins/local-executor`) like any other, and a run with no
124
+ * executor at all fails before any task runs. Persistent tasks never
125
+ * reach an executor (local by construction).
126
+ */
127
+ executor?(ctx: ExecutorContext): TaskExecutor | undefined | Promise<TaskExecutor | undefined>
128
+
62
129
  // --- OBSERVE-ONLY capability (cannot change behavior — by construction) ---
63
130
 
64
131
  /**
@@ -78,15 +145,6 @@ export interface VxPlugin {
78
145
  | undefined
79
146
  | Promise<TelemetrySink | TelemetrySink[] | undefined>
80
147
 
81
- /**
82
- * @deprecated Prefer `telemetry`. Contribute an event sink — a consumer of
83
- * the serializable WireEvent stream, subscribed for the whole run via
84
- * wireForwarder. Fire-and-forget; a throwing sink is isolated and cannot
85
- * break the run. Kept as a back-compat path; `telemetry` is the canonical,
86
- * analytics-shaped export contract.
87
- */
88
- eventSink?(ctx: EventSinkContext): EventSink | undefined | Promise<EventSink | undefined>
89
-
90
148
  /**
91
149
  * Optional one-time setup before any capability is consulted (validate the
92
150
  * workspace, open a connection, read a token). Throwing aborts the run with
@@ -98,12 +156,6 @@ export interface VxPlugin {
98
156
  teardown?(): void | Promise<void>
99
157
  }
100
158
 
101
- /** A WireEvent consumer. onEvent is fire-and-forget; flush is awaited at teardown. */
102
- export interface EventSink {
103
- onEvent(event: WireEvent): void
104
- flush?(): Promise<void>
105
- }
106
-
107
159
  /** Shared, read-only context every capability factory receives. */
108
160
  interface BaseContext {
109
161
  readonly workspaceRoot: string
@@ -113,11 +165,41 @@ interface BaseContext {
113
165
  }
114
166
 
115
167
  export interface PluginSetupContext extends BaseContext {}
116
- export interface EventSinkContext extends BaseContext {}
117
168
 
118
- export interface BackendContext extends BaseContext {
119
- /** The resolved RunRequest about to be executed — cwd, tasks, policy, flow. */
120
- readonly request: RunRequest
169
+ /** One CLI verb contributed by a plugin. */
170
+ export interface PluginCommand {
171
+ /** One line for `vx help`. */
172
+ readonly description: string
173
+ run(argv: readonly string[], ctx: CommandContext): number | Promise<number>
174
+ }
175
+
176
+ export interface CommandContext extends BaseContext {}
177
+
178
+ /** `config` runs before the cache dir is known — it may be what the hook changes. */
179
+ export interface WorkspaceHookContext {
180
+ readonly workspaceRoot: string
181
+ warn(message: string): void
182
+ }
183
+
184
+ export interface ProjectHookContext extends BaseContext {
185
+ /** The package name (`package.json#name`). */
186
+ readonly name: string
187
+ /** The project's directory, absolute. */
188
+ readonly dir: string
189
+ /** The parsed `package.json`, read-only. */
190
+ readonly packageJson: Readonly<Record<string, unknown>>
191
+ }
192
+
193
+ export interface GraphHookContext extends BaseContext {
194
+ /** Task ids the user asked for (the rest were pulled in by `dependsOn`). */
195
+ readonly requested: readonly string[]
196
+ }
197
+
198
+ export interface KeyHookContext extends BaseContext {}
199
+
200
+ export interface ScheduleHookContext extends BaseContext {
201
+ /** The run's local cache handle — its `dbHandle()` holds the run history a policy can learn from. */
202
+ readonly localCache: Cache
121
203
  }
122
204
 
123
205
  export interface CacheContext extends BaseContext {
@@ -127,6 +209,11 @@ export interface CacheContext extends BaseContext {
127
209
  readonly policy: CachePolicy
128
210
  }
129
211
 
212
+ export interface ExecutorContext extends BaseContext {
213
+ /** The run's worker count — an executor that paces itself reads it here. */
214
+ readonly concurrency: number
215
+ }
216
+
130
217
  export interface PluginContext {
131
218
  /** Where the workspace lives on disk. */
132
219
  readonly workspaceRoot: string
@@ -170,7 +257,7 @@ export interface Plugin {
170
257
  * be installed synchronously inside setup() so no events are missed.
171
258
  *
172
259
  * OPTIONAL: a capability-only plugin (one that contributes
173
- * `backend`/`cache`/`eventSink` but no `setup`) is simply skipped by
260
+ * `cache`/`executor`/`telemetry` but no `setup`) is simply skipped by
174
261
  * `installPlugins` — its capabilities are consulted by `plugin-host.ts`.
175
262
  */
176
263
  setup?(ctx: PluginContext): void | Promise<void>
@@ -195,8 +282,8 @@ export interface InstallPluginsArgs {
195
282
  * hooks are subscribed before the next plugin's setup runs. Throws if any
196
283
  * plugin's setup() throws (the run cannot start with a broken plugin).
197
284
  *
198
- * A plugin without a `setup` is a capability-only plugin (backend / cache
199
- * / eventSink) — skipped here; those capabilities are consulted by
285
+ * A plugin without a `setup` is a capability-only plugin (cache
286
+ * / executor / telemetry) — skipped here; those capabilities are consulted by
200
287
  * `plugin-host.ts`.
201
288
  */
202
289
  export async function installPlugins(args: InstallPluginsArgs): Promise<() => void> {
@@ -209,7 +296,7 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
209
296
  if (typeof plugin.name !== 'string' || plugin.name.length === 0) {
210
297
  throw new UserError('plugin missing `name` field')
211
298
  }
212
- // No setup → a capability-only plugin (backend / cache / eventSink),
299
+ // No setup → a capability-only plugin (cache / executor / telemetry),
213
300
  // consulted by plugin-host.ts; skip the hook install. A setup that's
214
301
  // present but not callable is a real authoring error — reject it.
215
302
  if (plugin.setup === undefined) continue