@vzn/vx 0.0.636 → 0.0.638

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 (151) hide show
  1. package/README.md +39 -11
  2. package/docs/caching.md +246 -213
  3. package/docs/cli.md +791 -705
  4. package/docs/execution.md +68 -76
  5. package/docs/features.md +47 -42
  6. package/docs/patterns.md +26 -25
  7. package/docs/schema.md +392 -205
  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 +4 -4
  16. package/schemas/show.json +5 -3
  17. package/schemas/why.json +15 -1
  18. package/skills/vx/SKILL.md +28 -13
  19. package/src/bin.ts +30 -9
  20. package/src/cache/archive.ts +11 -0
  21. package/src/cache/cache.ts +124 -53
  22. package/src/cache/chained-cache.ts +4 -0
  23. package/src/cache/git-inputs.ts +277 -63
  24. package/src/cache/index.ts +15 -1
  25. package/src/cache/inputs.ts +102 -36
  26. package/src/cache/key-fold.ts +8 -7
  27. package/src/cache/layer.ts +15 -18
  28. package/src/cache/layered-cache.ts +20 -102
  29. package/src/cache/plugin-cache.ts +248 -0
  30. package/src/cache/policy.ts +32 -27
  31. package/src/cache/run-history.ts +104 -14
  32. package/src/cache/run-store.ts +77 -0
  33. package/src/cache/schema.ts +58 -31
  34. package/src/cache/scoped-cache.ts +237 -0
  35. package/src/cli/cache.ts +173 -34
  36. package/src/cli/completions.ts +11 -7
  37. package/src/cli/core-alias.ts +1 -1
  38. package/src/cli/foreign-flags.ts +13 -12
  39. package/src/cli/help.ts +110 -70
  40. package/src/cli/index.ts +67 -24
  41. package/src/cli/info.ts +43 -27
  42. package/src/cli/init.ts +115 -407
  43. package/src/cli/last.ts +157 -9
  44. package/src/cli/lock.ts +356 -122
  45. package/src/cli/plan-format.ts +7 -5
  46. package/src/cli/plugin-commands.ts +81 -14
  47. package/src/cli/plugin-templates.ts +11 -11
  48. package/src/cli/run.ts +116 -160
  49. package/src/cli/select.ts +50 -47
  50. package/src/cli/show.ts +83 -59
  51. package/src/cli/task-verb.ts +6 -13
  52. package/src/cli/upgrade.ts +415 -149
  53. package/src/cli/watch-cycle.ts +9 -2
  54. package/src/cli/watch-filter.ts +39 -61
  55. package/src/cli/watch-fs.ts +29 -16
  56. package/src/cli/watch-judge.ts +18 -4
  57. package/src/cli/watch-set.ts +16 -42
  58. package/src/cli/watch.ts +227 -230
  59. package/src/cli/why.ts +38 -18
  60. package/src/cli/workspace-config.ts +33 -70
  61. package/src/config.ts +169 -70
  62. package/src/exec/env.ts +1 -31
  63. package/src/exec/executor.ts +1 -5
  64. package/src/exec/kill-tree.ts +2 -1
  65. package/src/exec/sandbox-runtime.ts +12 -1
  66. package/src/exec/sandbox-violations.ts +9 -1
  67. package/src/graph/dependency-spec.ts +1 -1
  68. package/src/graph/index.ts +1 -0
  69. package/src/graph/priorities.ts +1 -1
  70. package/src/graph/scheduler.ts +27 -7
  71. package/src/graph/task-graph.ts +76 -24
  72. package/src/handoff.ts +169 -0
  73. package/src/index.ts +35 -58
  74. package/src/orchestrator/affected-tasks.ts +15 -8
  75. package/src/orchestrator/command-windows.ts +44 -0
  76. package/src/orchestrator/deferred-outputs.ts +12 -10
  77. package/src/orchestrator/doctor.ts +98 -110
  78. package/src/orchestrator/download-policy.ts +3 -3
  79. package/src/orchestrator/events.ts +19 -3
  80. package/src/orchestrator/excluded-keys.ts +0 -2
  81. package/src/orchestrator/execute-task.ts +147 -69
  82. package/src/orchestrator/failure-mode.ts +19 -10
  83. package/src/orchestrator/fingerprint-watch.ts +18 -15
  84. package/src/orchestrator/forecast.ts +10 -2
  85. package/src/orchestrator/framed-output.ts +9 -5
  86. package/src/orchestrator/graph-stage.ts +5 -5
  87. package/src/orchestrator/history.ts +126 -30
  88. package/src/orchestrator/hit-restore.ts +15 -3
  89. package/src/orchestrator/index.ts +20 -5
  90. package/src/orchestrator/local-shortcircuit.ts +2 -1
  91. package/src/orchestrator/lockfile-claim.ts +79 -20
  92. package/src/orchestrator/logger.ts +80 -41
  93. package/src/orchestrator/metrics.ts +78 -9
  94. package/src/orchestrator/miss-save.ts +3 -0
  95. package/src/orchestrator/options.ts +42 -29
  96. package/src/orchestrator/output-host.ts +72 -0
  97. package/src/orchestrator/plan.ts +6 -8
  98. package/src/orchestrator/plugin-host.ts +100 -66
  99. package/src/orchestrator/plugin.ts +173 -67
  100. package/src/orchestrator/prepare.ts +163 -78
  101. package/src/orchestrator/projects.ts +105 -99
  102. package/src/orchestrator/remote-prefetch.ts +11 -6
  103. package/src/orchestrator/run-context.ts +25 -6
  104. package/src/orchestrator/run-failures.ts +17 -8
  105. package/src/orchestrator/run-lock.ts +1 -1
  106. package/src/orchestrator/run-records.ts +1 -1
  107. package/src/orchestrator/run-report.ts +9 -9
  108. package/src/orchestrator/run-settings.ts +48 -0
  109. package/src/orchestrator/run.ts +244 -119
  110. package/src/orchestrator/sandbox-fix.ts +39 -20
  111. package/src/orchestrator/sandbox-request.ts +84 -13
  112. package/src/orchestrator/signals.ts +1 -1
  113. package/src/orchestrator/stable-keys.ts +13 -11
  114. package/src/orchestrator/status-line.ts +49 -7
  115. package/src/orchestrator/summary.ts +115 -106
  116. package/src/orchestrator/task-hash.ts +12 -83
  117. package/src/orchestrator/telemetry.ts +4 -4
  118. package/src/settings.ts +428 -0
  119. package/src/{orchestrator → util}/colors.ts +5 -0
  120. package/src/util/config-frame.ts +31 -2
  121. package/src/util/errors.ts +33 -1
  122. package/src/util/essential-env.ts +31 -0
  123. package/src/util/hide-env.ts +107 -0
  124. package/src/util/index.ts +16 -3
  125. package/src/util/paths.ts +10 -0
  126. package/src/util/refusal-notice.ts +111 -0
  127. package/src/util/report.ts +31 -0
  128. package/src/util/settle.ts +49 -30
  129. package/src/util/size.ts +23 -4
  130. package/src/util/timing.ts +3 -2
  131. package/src/util/ui.ts +186 -0
  132. package/src/util/ulid.ts +1 -1
  133. package/src/util/verbs.ts +0 -15
  134. package/src/workspace/affected.ts +84 -232
  135. package/src/workspace/config-eval.ts +15 -20
  136. package/src/workspace/config-imports.ts +78 -3
  137. package/src/workspace/config-schema.ts +209 -268
  138. package/src/workspace/dependency-rules.ts +65 -0
  139. package/src/workspace/filter.ts +2 -2
  140. package/src/workspace/fingerprint.ts +11 -121
  141. package/src/workspace/index.ts +20 -27
  142. package/src/workspace/load-reads.ts +4 -4
  143. package/src/workspace/lockfile.ts +96 -140
  144. package/src/workspace/migration.ts +307 -215
  145. package/src/workspace/package-graph.ts +30 -216
  146. package/src/workspace/project-loader.ts +80 -285
  147. package/src/workspace/repo-id.ts +2 -1
  148. package/src/workspace/workspace.ts +363 -831
  149. package/src/cache/config-evals.ts +0 -130
  150. package/src/workspace/config-cache.ts +0 -661
  151. package/src/workspace/migrate-scripts.ts +0 -1121
package/src/cli/last.ts CHANGED
@@ -9,11 +9,14 @@ import { Cache } from '../cache/index.js'
9
9
  import { formatBytes } from './format.js'
10
10
  import { flagHint, formatValue, seeHelp } from './help.js'
11
11
  import {
12
+ currentBranch,
12
13
  exitSignal,
14
+ type FailureMode,
13
15
  getInvocation,
14
16
  getRun,
15
17
  type InvocationDetail,
16
18
  listInvocations,
19
+ LocalHistoryProvider,
17
20
  outcomeWord,
18
21
  type RunSummaryRow,
19
22
  resolveRunId,
@@ -21,7 +24,7 @@ import {
21
24
  shortRunId,
22
25
  taskLog,
23
26
  } from '../orchestrator/index.js'
24
- import { formatElapsed, UserError } from '../util/index.js'
27
+ import { formatElapsed, refusal, splitTaskId, UserError, sayNotice, ui } from '../util/index.js'
25
28
  import { findWorkspaceRoot } from '../workspace/index.js'
26
29
  import { cliCacheDir, parseCacheDirFlag } from './workspace-config.js'
27
30
 
@@ -33,6 +36,12 @@ interface LastArgs {
33
36
  format: 'pretty' | 'json'
34
37
  /** `--log <task>`: that task's output in the run, not the run's summary. */
35
38
  log?: string
39
+ /** `--stats`: per-task numbers over the history window, not a run. */
40
+ stats?: boolean
41
+ /** `--stats`' positional: a task id, or a task name in every project. */
42
+ task?: string
43
+ /** `--branch`: the branch `--stats` forecasts from, as a run's `--branch`. */
44
+ branch?: string
36
45
  /** `--cache-dir`: read the history a run with the same flag wrote. */
37
46
  cacheDir?: string
38
47
  error?: string
@@ -46,6 +55,8 @@ const RUN_ID_SHAPE = /^[0-9a-f]{8}(?:-|$)/i
46
55
 
47
56
  export function parseLastArgs(args: readonly string[]): LastArgs {
48
57
  const out: LastArgs = { format: 'pretty' }
58
+ // `vx last app#build --log`: a bare --log takes the positional as its task.
59
+ let bareLog = false
49
60
  for (let i = 0; i < args.length; i++) {
50
61
  const a = args[i]!
51
62
  if (a === '--list' || a.startsWith('--list=')) {
@@ -76,9 +87,30 @@ export function parseLastArgs(args: readonly string[]): LastArgs {
76
87
  out.failed = true
77
88
  continue
78
89
  }
79
- if (a === '--log' || a.startsWith('--log=')) {
80
- const v = a === '--log' ? args[++i] : a.slice('--log='.length)
81
- if (v === undefined || v === '' || v.startsWith('-')) {
90
+ if (a === '--stats') {
91
+ out.stats = true
92
+ continue
93
+ }
94
+ if (a === '--branch' || a.startsWith('--branch=')) {
95
+ const v = a === '--branch' ? args[++i] : a.slice('--branch='.length)
96
+ if (v === undefined || v === '' || (a === '--branch' && v.startsWith('-'))) {
97
+ return { ...out, error: `--branch requires a branch name${seeHelp('last')}` }
98
+ }
99
+ out.branch = v
100
+ continue
101
+ }
102
+ if (a === '--log') {
103
+ const v = args[i + 1]
104
+ if (v === undefined || v.startsWith('-')) bareLog = true
105
+ else {
106
+ out.log = v
107
+ i++
108
+ }
109
+ continue
110
+ }
111
+ if (a.startsWith('--log=')) {
112
+ const v = a.slice('--log='.length)
113
+ if (v === '') {
82
114
  return { ...out, error: `--log requires a task id, like app#build${seeHelp('last')}` }
83
115
  }
84
116
  out.log = v
@@ -103,6 +135,27 @@ export function parseLastArgs(args: readonly string[]): LastArgs {
103
135
  return { ...out, error: `unexpected argument: ${a}${seeHelp('last')}` }
104
136
  out.runId = a
105
137
  }
138
+ if (bareLog) {
139
+ if (out.runId === undefined) {
140
+ return { ...out, error: `--log requires a task id, like app#build${seeHelp('last')}` }
141
+ }
142
+ out.log = out.runId
143
+ delete out.runId
144
+ }
145
+ if (out.stats === true) {
146
+ if (out.list !== undefined || out.failed === true || out.log !== undefined) {
147
+ return {
148
+ ...out,
149
+ error: `--stats does not combine with --list, --failed or --log${seeHelp('last')}`,
150
+ }
151
+ }
152
+ if (out.runId !== undefined) out.task = out.runId
153
+ delete out.runId
154
+ return out
155
+ }
156
+ if (out.branch !== undefined) {
157
+ return { ...out, error: `--branch combines only with --stats${seeHelp('last')}` }
158
+ }
106
159
  // One run's replay or a list of runs, not both: the id was dropped.
107
160
  // Beside --list, a word no run id could be is just an extra one.
108
161
  if (out.runId !== undefined && out.list !== undefined && !RUN_ID_SHAPE.test(out.runId)) {
@@ -137,6 +190,88 @@ function failedInvocations(db: Database, limit: number): InvocationDetail[] {
137
190
  return ids.map((r) => getInvocation(db, r.run_id)).filter((i) => i !== null)
138
191
  }
139
192
 
193
+ /** One task's numbers over the window, every key present (`null` where unknown). */
194
+ export interface TaskStats {
195
+ taskId: string
196
+ runs: number
197
+ p50DurationMs: number | null
198
+ p99DurationMs: number | null
199
+ successRate: number
200
+ hitRate: number
201
+ failureMode: FailureMode
202
+ maxPeakRssBytes: number | null
203
+ maxCpuParallelism: number | null
204
+ }
205
+
206
+ /**
207
+ * `--stats`: what the scheduler and `--dry` learn from history, per task,
208
+ * over the same window (`LocalHistoryProvider`), with the p50 a run on
209
+ * `branch` forecasts.
210
+ */
211
+ async function printStats(
212
+ db: Database,
213
+ target: string | undefined,
214
+ format: 'pretty' | 'json',
215
+ branch: string | null,
216
+ ): Promise<number> {
217
+ const provider = new LocalHistoryProvider(db, undefined, branch)
218
+ const matches = (id: string): boolean =>
219
+ target === undefined || (target.includes('#') ? id === target : splitTaskId(id)[1] === target)
220
+ const stats: TaskStats[] = [...(await provider.loadAll())]
221
+ .filter(([id]) => matches(id))
222
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
223
+ .map(([taskId, h]) => ({
224
+ taskId,
225
+ runs: h.runs,
226
+ p50DurationMs: h.p50DurationMs ?? null,
227
+ p99DurationMs: h.p99DurationMs ?? null,
228
+ successRate: h.successRate,
229
+ hitRate: h.hitRate,
230
+ failureMode: h.failureMode,
231
+ maxPeakRssBytes: h.maxPeakRssBytes ?? null,
232
+ maxCpuParallelism: h.maxCpuParallelism ?? null,
233
+ }))
234
+ if (stats.length === 0 && target !== undefined) {
235
+ throw new UserError(
236
+ `vx last: no recorded run of ${target} in the last ${provider.recent} runs`,
237
+ 'VX_E_NO_HISTORY',
238
+ )
239
+ }
240
+ if (format === 'json') {
241
+ process.stdout.write(`${JSON.stringify({ window: provider.recent, branch, stats })}\n`)
242
+ return 0
243
+ }
244
+ if (stats.length === 0) {
245
+ ui.print('out', ui.status('info', 'vx last', 'no runs yet'))
246
+ return 0
247
+ }
248
+ const pct = (r: number): string => `${Math.round(r * 100)}%`
249
+ const ms = (v: number | null): string => (v === null ? '-' : fmtMs(v))
250
+ const rows = [
251
+ ['task', 'runs', 'p50', 'p99', 'pass', 'hit', 'failure mode', 'peak rss', 'cpu'],
252
+ ...stats.map((s) => [
253
+ s.taskId,
254
+ String(s.runs),
255
+ ms(s.p50DurationMs),
256
+ ms(s.p99DurationMs),
257
+ pct(s.successRate),
258
+ pct(s.hitRate),
259
+ s.failureMode,
260
+ s.maxPeakRssBytes === null ? '-' : formatBytes(s.maxPeakRssBytes),
261
+ s.maxCpuParallelism === null ? '-' : `${s.maxCpuParallelism.toFixed(1)}×`,
262
+ ]),
263
+ ]
264
+ const widths = rows[0]!.map((_, c) => Math.max(...rows.map((r) => r[c]!.length)))
265
+ const lines = rows.map((r) =>
266
+ r
267
+ .map((cell, c) => cell.padEnd(widths[c]!))
268
+ .join(' ')
269
+ .trimEnd(),
270
+ )
271
+ process.stdout.write(`${lines.join('\n')}\n`)
272
+ return 0
273
+ }
274
+
140
275
  const fmtWhen = (ms: number): string => new Date(ms).toISOString()
141
276
 
142
277
  const fmtMs = (ms: number): string => formatElapsed(ms, true)
@@ -312,6 +447,11 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
312
447
  // nothing, while `--list --failed` past green runs said there were none.
313
448
  const noRuns = (): boolean => listInvocations(db, { limit: 1 }).length === 0
314
449
 
450
+ if (parsed.stats === true) {
451
+ const branch = currentBranch(root, undefined, parsed.branch)
452
+ return await printStats(db, parsed.task, parsed.format, branch)
453
+ }
454
+
315
455
  if (parsed.log !== undefined) {
316
456
  const runId =
317
457
  parsed.runId === undefined
@@ -328,8 +468,10 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
328
468
  return 0
329
469
  }
330
470
  if (log.source === null) {
331
- process.stderr.write(
332
- `vx last: ${log.taskId} ended ${log.status}; vx keeps output only for failed and cached tasks\n`,
471
+ sayNotice(
472
+ 'info',
473
+ 'vx last',
474
+ `${log.taskId} ended ${log.status}. vx keeps output only for failed and cached tasks`,
333
475
  )
334
476
  }
335
477
  process.stdout.write(log.output)
@@ -346,9 +488,8 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
346
488
  return 0
347
489
  }
348
490
  if (invocations.length === 0) {
349
- process.stdout.write(
350
- parsed.failed === true && !noRuns() ? 'no recorded run failed\n' : 'no recorded runs\n',
351
- )
491
+ const none = parsed.failed === true && !noRuns() ? 'no failed runs' : 'no runs yet'
492
+ ui.print('out', ui.status('info', 'vx last', none))
352
493
  return 0
353
494
  }
354
495
  const lines = formatRunList(
@@ -364,6 +505,12 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
364
505
  : parsed.failed === true
365
506
  ? (failedInvocations(db, 1)[0] ?? null)
366
507
  : (listInvocations(db, { limit: 1 })[0] ?? null)
508
+ if ((inv === null || inv === undefined) && noRuns()) {
509
+ throw refusal(
510
+ { tone: 'info', headline: 'no runs yet', fix: 'vx run' },
511
+ { code: 'VX_E_NO_HISTORY', message: 'vx last: no recorded runs yet — run something first' },
512
+ )
513
+ }
367
514
  if (inv === null || inv === undefined) {
368
515
  throw new UserError(
369
516
  noRuns()
@@ -388,6 +535,7 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
388
535
  ? t
389
536
  : {
390
537
  ...t,
538
+ ...(f.inputMoved !== undefined ? { inputMoved: f.inputMoved } : {}),
391
539
  output: f.output,
392
540
  locations: f.locations,
393
541
  ...(f.sandboxFix !== undefined ? { sandboxFix: f.sandboxFix } : {}),