@vzn/vx 0.0.622 → 0.0.623

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 (2) hide show
  1. package/package.json +5 -5
  2. package/src/cli/why.ts +60 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.622",
3
+ "version": "0.0.623",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.622",
25
- "@vzn/vx-linux-arm64": "0.0.622",
26
- "@vzn/vx-darwin-x64": "0.0.622",
27
- "@vzn/vx-darwin-arm64": "0.0.622"
24
+ "@vzn/vx-linux-x64": "0.0.623",
25
+ "@vzn/vx-linux-arm64": "0.0.623",
26
+ "@vzn/vx-darwin-x64": "0.0.623",
27
+ "@vzn/vx-darwin-arm64": "0.0.623"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/why.ts CHANGED
@@ -9,7 +9,9 @@ import path from 'node:path'
9
9
  import { Cache } from '../cache/index.js'
10
10
  import { flagHint, formatValue, seeHelp } from './help.js'
11
11
  import { splitTaskId } from '../graph/index.js'
12
+ import type { Database } from 'bun:sqlite'
12
13
  import {
14
+ type CacheKeyDiff,
13
15
  cacheKeyDiff,
14
16
  explainCacheKeyQuery as explainCacheKey,
15
17
  latestRunId,
@@ -232,9 +234,13 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
232
234
  )
233
235
  }
234
236
  const diff = cacheKeyDiff(db, runId, taskId)
237
+ // An upstream change is never the cause, only its carrier: follow
238
+ // each moved dependency in this run down to the tasks whose own
239
+ // inputs moved, so one `vx why` names the file, not the next task.
240
+ const roots = rootCauses(db, runId, taskId, diff.entries)
235
241
 
236
242
  if (parsed.format === 'json') {
237
- process.stdout.write(`${JSON.stringify({ taskId, runId, why, diff })}\n`)
243
+ process.stdout.write(`${JSON.stringify({ taskId, runId, why, diff, roots })}\n`)
238
244
  return 0
239
245
  }
240
246
 
@@ -293,7 +299,22 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
293
299
  ` ${e.change.padEnd(7)} ${e.kind.padEnd(kindW)} ${printable(e.name)} ${beforeAfter}`.trimEnd(),
294
300
  )
295
301
  }
296
- const kinds = [...new Set(diff.entries.map((e) => e.kind))].filter((k) => k in WHAT_TO_DO)
302
+ if (roots.length > 0) {
303
+ lines.push('', ' root cause:')
304
+ for (const r of roots.slice(0, ROOTS_SHOWN)) {
305
+ const what = r.entries.map((e) =>
306
+ e.name === e.kind ? e.kind : `${e.kind} ${printable(e.name)}`,
307
+ )
308
+ lines.push(
309
+ ` ${r.chain.join(' \u2190 ')} \u2190 ${what.slice(0, 3).join(', ')}${what.length > 3 ? ` and ${what.length - 3} more` : ''}`,
310
+ )
311
+ }
312
+ if (roots.length > ROOTS_SHOWN) lines.push(` and ${roots.length - ROOTS_SHOWN} more`)
313
+ }
314
+ const rootKinds = roots.flatMap((r) => r.entries.map((e) => e.kind))
315
+ const kinds = [...new Set([...diff.entries.map((e) => e.kind), ...rootKinds])].filter(
316
+ (k) => k in WHAT_TO_DO && !(k === 'upstream' && roots.length > 0),
317
+ )
297
318
  if (kinds.length > 0) {
298
319
  lines.push('', ' what to do:')
299
320
  // An upstream row names the task to ask next: the command, not a
@@ -321,3 +342,40 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
321
342
  cache.close()
322
343
  }
323
344
  }
345
+
346
+ const ROOTS_SHOWN = 5
347
+
348
+ interface RootCause {
349
+ /** From the asked task down to the one whose own inputs moved. */
350
+ chain: string[]
351
+ entries: CacheKeyDiff['entries']
352
+ }
353
+
354
+ /**
355
+ * The tasks under `taskId`'s moved upstreams whose OWN key components
356
+ * moved in the same run, each with the chain that carried it up. A task
357
+ * visited once is not walked again (a diamond names its root once).
358
+ */
359
+ function rootCauses(
360
+ db: Database,
361
+ runId: string,
362
+ taskId: string,
363
+ entries: CacheKeyDiff['entries'],
364
+ ): RootCause[] {
365
+ const roots: RootCause[] = []
366
+ const seen = new Set([taskId])
367
+ const walk = (chain: string[], moved: CacheKeyDiff['entries']): void => {
368
+ for (const e of moved) {
369
+ if (e.kind !== 'upstream' || seen.has(e.name)) continue
370
+ seen.add(e.name)
371
+ const next = [...chain, e.name]
372
+ const d = cacheKeyDiff(db, runId, e.name)
373
+ if (!d.found) continue
374
+ const own = d.entries.filter((x) => x.kind !== 'upstream')
375
+ if (own.length > 0) roots.push({ chain: next, entries: own })
376
+ walk(next, d.entries)
377
+ }
378
+ }
379
+ walk([taskId], entries)
380
+ return roots
381
+ }