@vzn/vx 0.0.635 → 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 +47 -19
  2. package/docs/caching.md +222 -210
  3. package/docs/cli.md +699 -664
  4. package/docs/execution.md +67 -75
  5. package/docs/features.md +44 -40
  6. package/docs/patterns.md +31 -32
  7. package/docs/schema.md +418 -202
  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 +13 -4
  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 -61
  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 +123 -144
  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 +191 -71
  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 +25 -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 +192 -70
  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 +43 -28
  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 +114 -73
  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 +127 -88
  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 +251 -241
  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 +22 -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/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, splitTaskId, UserError } 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
+ process.stdout.write('no recorded runs\n')
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
@@ -388,6 +528,7 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
388
528
  ? t
389
529
  : {
390
530
  ...t,
531
+ ...(f.inputMoved !== undefined ? { inputMoved: f.inputMoved } : {}),
391
532
  output: f.output,
392
533
  locations: f.locations,
393
534
  ...(f.sandboxFix !== undefined ? { sandboxFix: f.sandboxFix } : {}),