@vzn/vx 0.0.586 → 0.0.588

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.586",
3
+ "version": "0.0.588",
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.586",
25
- "@vzn/vx-linux-arm64": "0.0.586",
26
- "@vzn/vx-darwin-x64": "0.0.586",
27
- "@vzn/vx-darwin-arm64": "0.0.586"
24
+ "@vzn/vx-linux-x64": "0.0.588",
25
+ "@vzn/vx-linux-arm64": "0.0.588",
26
+ "@vzn/vx-darwin-x64": "0.0.588",
27
+ "@vzn/vx-darwin-arm64": "0.0.588"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/run.ts CHANGED
@@ -7,11 +7,9 @@ import { defaultAffectedBase, findWorkspaceRoot } from '../workspace/index.js'
7
7
  import {
8
8
  planRun,
9
9
  formatRunReportMarkdown,
10
- outcomeLabel,
11
10
  projectOutcome,
12
11
  run as runOrchestrator,
13
12
  shellQuote,
14
- type OutcomeView,
15
13
  type RunOptions,
16
14
  type RunResult,
17
15
  } from '../orchestrator/index.js'
@@ -567,6 +565,7 @@ export async function resolveRunOptions(
567
565
  flow: detectFlow(parsed),
568
566
  ...(parsed.frozen ? { frozen: true } : {}),
569
567
  ...(parsed.outputLogs !== undefined ? { outputLogs: parsed.outputLogs } : {}),
568
+ ...(parsed.verbosity > 0 ? { summaryTable: true } : {}),
570
569
  ...(parsed.download !== undefined ? { download: parsed.download } : {}),
571
570
  ...(parsed.continueMode !== undefined ? { continueMode: parsed.continueMode } : {}),
572
571
  forwardArgs: parsed.forwardArgs,
@@ -707,7 +706,6 @@ export async function runCmd(args: readonly string[]): Promise<number> {
707
706
  return 1
708
707
  }
709
708
  const result: RunResult = { ok: summary.ok, outcomes: summary.outcomes.map(projectOutcome) }
710
- if (parsed.verbosity > 0) printSummary(result)
711
709
  // Report generation is post-run, gated on the flags — zero cost when
712
710
  // both are absent. Rendered once, however many sinks asked for it.
713
711
  if (parsed.report === 'markdown' || parsed.reportFile !== undefined) {
@@ -742,35 +740,3 @@ export async function runCmd(args: readonly string[]): Promise<number> {
742
740
  }
743
741
  return result.ok ? 0 : 1
744
742
  }
745
-
746
- function printSummary(summary: RunResult): void {
747
- const rows = summary.outcomes.map((o) => formatRow(o))
748
- if (rows.length === 0) return
749
- const widths = {
750
- task: Math.max(4, ...rows.map((r) => r.task.length)),
751
- status: Math.max(6, ...rows.map((r) => r.status.length)),
752
- duration: Math.max(8, ...rows.map((r) => r.duration.length)),
753
- }
754
- const header =
755
- 'TASK'.padEnd(widths.task) +
756
- ' ' +
757
- 'STATUS'.padEnd(widths.status) +
758
- ' ' +
759
- 'DURATION'.padStart(widths.duration)
760
- process.stdout.write(`\n${header}\n`)
761
- process.stdout.write('-'.repeat(header.length) + '\n')
762
- for (const r of rows) {
763
- process.stdout.write(
764
- r.task.padEnd(widths.task) +
765
- ' ' +
766
- r.status.padEnd(widths.status) +
767
- ' ' +
768
- r.duration.padStart(widths.duration) +
769
- '\n',
770
- )
771
- }
772
- }
773
-
774
- function formatRow(o: OutcomeView): { task: string; status: string; duration: string } {
775
- return { task: o.taskId, status: outcomeLabel(o), duration: `${o.durationMs}ms` }
776
- }
@@ -7,7 +7,7 @@
7
7
  import { statSync } from 'node:fs'
8
8
  import path from 'node:path'
9
9
  import { absolutize, atOrUnder, isUnderAny, localBindingOn, toRealPath } from './sandbox-paths.js'
10
- import { bindableReads } from './sandbox-binds.js'
10
+ import { bindableReads, bindableWrites } from './sandbox-binds.js'
11
11
  import type {
12
12
  ResolvedSandboxConfig,
13
13
  SandboxedRunArgs,
@@ -321,17 +321,22 @@ export async function parseStraceViolations(
321
321
  const text = await Bun.file(logPath).text()
322
322
  if (text.length === 0) return []
323
323
 
324
- // Treat every baseAllow + sandbox.allowRead path as "this was
325
- // explicitly permitted; any -ENOENT here is the user's own missing
326
- // file, not a sandbox-induced denial". Same for absolute denyRead
327
- // checks below. Canonical on BOTH sides — the policy is expressed in
328
- // real paths (see `canonicalBaselines`), so comparing a link-path here
329
- // would report an explicitly-allowed read as a violation.
324
+ // Treat every baseAllow + sandbox.allowRead + allowWrite path as "this
325
+ // was explicitly permitted; any -ENOENT here is the user's own missing
326
+ // file, not a sandbox-induced denial". A write grant counts: GNU cp
327
+ // opens its destination before creating it, and tsc probes its
328
+ // buildinfo. Same for absolute denyRead checks below. Canonical on BOTH
329
+ // sides — the policy is expressed in real paths (see
330
+ // `canonicalBaselines`), so comparing a link-path here would report an
331
+ // explicitly-allowed read as a violation.
330
332
  const allowAbs = new Set<string>(
331
- // A grant SRT could not mount (`bindableReads`) permits nothing.
332
- [...baselines.allowRead, ...bindableReads(args.config.allowRead)].map((p) =>
333
- toRealPath(absolutize(p)),
334
- ),
333
+ // A grant SRT could not mount (`bindableReads`, `bindableWrites`)
334
+ // permits nothing.
335
+ [
336
+ ...baselines.allowRead,
337
+ ...bindableReads(args.config.allowRead),
338
+ ...args.config.allowWrite.filter((w) => bindableWrites([w]).length > 0),
339
+ ].map((p) => toRealPath(absolutize(p))),
335
340
  )
336
341
  const denyAnchors = baselines.denyRead.map((p) => toRealPath(absolutize(p)))
337
342
  // A read under a widened write grant's directory is never refused, so it
@@ -38,7 +38,6 @@ export {
38
38
  projectNode,
39
39
  projectOutcome,
40
40
  outcomeWord,
41
- outcomeLabel,
42
41
  } from './events.js'
43
42
  export type {
44
43
  EventBus,
@@ -207,6 +207,11 @@ export interface RunOptions {
207
207
  * is replaced only when the next cycle starts.
208
208
  */
209
209
  holdPersistent?: boolean
210
+ /**
211
+ * Print the per-task table (`--verbosity 1`) just above the footer: the
212
+ * footer is the run's last word, so the caller cannot print it after.
213
+ */
214
+ summaryTable?: boolean
210
215
  /**
211
216
  * vx's stdin is a terminal: each `exec.interactive` task is handed it,
212
217
  * alone (placement.ts `terminalHolders`). The CLI sets it from
@@ -92,7 +92,7 @@ import { assembleRunRecords } from './run-records.js'
92
92
  import { hasEnded, selectKeepAlive, shutdownPersistent } from './persistent.js'
93
93
  import { writeRunProfile, writeRunSummary } from './run-artifacts.js'
94
94
  import { createSaveLane } from './save-lane.js'
95
- import { formatRunSummary } from './summary.js'
95
+ import { formatOutcomeTable, formatRunSummary } from './summary.js'
96
96
  import { detectFlaky, type FlakyCandidate, type FlakyFinding } from './failure-mode.js'
97
97
  import { createMissExplainer } from './miss-reason.js'
98
98
  import type { RunOptions, RunSummary } from './options.js'
@@ -1328,6 +1328,7 @@ async function runOnBus(
1328
1328
  // sandbox reset included, so any warning they raise lands above it. A
1329
1329
  // task's own facts (flaky, blocked) ride its row. Only a kept server's
1330
1330
  // own output follows.
1331
+ if (options.summaryTable === true) for (const line of formatOutcomeTable(list)) log.status(line)
1331
1332
  for (const line of formatRunSummary(list, totalMs, colors, runContext)) log.status(line)
1332
1333
 
1333
1334
  // Edge case the summary already reported: the user requested a
@@ -6,6 +6,7 @@
6
6
  import type { TaskOutcome } from '../graph/index.js'
7
7
  import { paint, type ColorSupport } from './colors.js'
8
8
  import { tallyOutcomes } from './tally.js'
9
+ import { outcomeLabel, projectOutcome } from './events.js'
9
10
  import { isGroupTask } from '../graph/index.js'
10
11
  import { formatElapsed } from '../util/index.js'
11
12
 
@@ -314,6 +315,34 @@ function resultLine(
314
315
  return parts.join(` ${paint('', '\u00b7', colors, { dim: true })} `)
315
316
  }
316
317
 
318
+ /**
319
+ * The `--verbosity 1` per-task table (`RunOptions.summaryTable`). Groups are
320
+ * left out as the footer and `--report` leave them: a group ran no command.
321
+ */
322
+ export function formatOutcomeTable(outcomes: readonly TaskOutcome[]): string[] {
323
+ const rows = outcomes
324
+ .filter((o) => !isGroupTask(o.node))
325
+ .map((o) => {
326
+ const v = projectOutcome(o)
327
+ return { task: v.taskId, status: outcomeLabel(v), duration: `${v.durationMs}ms` }
328
+ })
329
+ if (rows.length === 0) return []
330
+ const widths = {
331
+ task: Math.max(4, ...rows.map((r) => r.task.length)),
332
+ status: Math.max(6, ...rows.map((r) => r.status.length)),
333
+ duration: Math.max(8, ...rows.map((r) => r.duration.length)),
334
+ }
335
+ const line = (task: string, status: string, duration: string) =>
336
+ `${task.padEnd(widths.task)} ${status.padEnd(widths.status)} ${duration.padStart(widths.duration)}`
337
+ const header = line('TASK', 'STATUS', 'DURATION')
338
+ return [
339
+ '',
340
+ header,
341
+ '-'.repeat(header.length),
342
+ ...rows.map((r) => line(r.task, r.status, r.duration)),
343
+ ]
344
+ }
345
+
317
346
  // Local byte formatter — the orchestrator can't import cli/format.ts
318
347
  // (module boundary), and the footer only needs one compact form.
319
348
  export function formatRunSummary(