@vzn/vx 0.0.96 → 0.0.98

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/README.md CHANGED
@@ -93,7 +93,8 @@ npx vx run build --all # restored from the cache, not rebuilt
93
93
  Or copy [`examples/basic`](examples/basic), a two-package starter CI
94
94
  runs on every commit. More: [Quickstart](https://vznjs.github.io/vx/quickstart/) ·
95
95
  [Configure](https://vznjs.github.io/vx/guides/configure/) ·
96
- [CLI](packages/vx/docs/cli.md) · [every config field](packages/vx/docs/schema.md).
96
+ [CLI](packages/vx/docs/cli.md) · [every config field](packages/vx/docs/schema.md) ·
97
+ [playground](https://vznjs.github.io/vx/playground/) (the planner, in your browser).
97
98
 
98
99
  ## Already on Turborepo or Nx?
99
100
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "description": "A fast, correct task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "optionalDependencies": {
20
- "@vzn/vx-linux-x64": "0.0.96",
21
- "@vzn/vx-linux-arm64": "0.0.96",
22
- "@vzn/vx-darwin-x64": "0.0.96",
23
- "@vzn/vx-darwin-arm64": "0.0.96"
20
+ "@vzn/vx-linux-x64": "0.0.98",
21
+ "@vzn/vx-linux-arm64": "0.0.98",
22
+ "@vzn/vx-darwin-x64": "0.0.98",
23
+ "@vzn/vx-darwin-arm64": "0.0.98"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/help.ts CHANGED
@@ -141,7 +141,6 @@ export function helpText(pluginCommands: readonly string[] = []): string {
141
141
  ' task counts, cache dir/entries/size, recent runs,',
142
142
  ' lock status.',
143
143
  ' --format <fmt> pretty (default) | json.',
144
- ' vx stats Deprecated: alias of vx info.',
145
144
  " vx why <task> Why did this task re-run? Compares the task's latest",
146
145
  ' run (or --run <id>) against its previous run: names the',
147
146
  ' exact changed cache-key components (files / env / runtime',
package/src/cli/index.ts CHANGED
@@ -61,9 +61,6 @@ export async function run(argv: readonly string[]): Promise<number> {
61
61
  return await (await import('./upgrade.js')).upgradeCmd(rest)
62
62
  case 'show':
63
63
  return await (await import('./show.js')).showCmd(rest)
64
- case 'stats': // deprecated alias — `vx info` absorbed `vx stats`
65
- process.stderr.write('vx stats is deprecated; use vx info\n')
66
- return await (await import('./info.js')).infoCmd(rest)
67
64
  case 'info':
68
65
  return await (await import('./info.js')).infoCmd(rest)
69
66
  case 'why':
package/src/cli/info.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // `vx info` — workspace doctor printout. One screen of environment +
2
2
  // workspace + cache facts for bug reports and quick sanity checks.
3
- // `vx stats` is a deprecated alias (info absorbed it). The facts come
3
+ // The facts come
4
4
  // from the orchestrator's `collectInfo` (doctor.ts), which `vx mcp` reads
5
5
  // too; this file parses the flags and renders the rows.
6
6
 
package/src/cli/last.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import { formatElapsed, UserError } from '../util/index.js'
18
18
  import { findWorkspaceRoot } from '../workspace/index.js'
19
19
  import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
20
+ import { resolveRunId } from './run-id.js'
20
21
 
21
22
  interface LastArgs {
22
23
  runId?: string
@@ -204,7 +205,7 @@ export async function lastCmd(args: readonly string[]): Promise<number> {
204
205
 
205
206
  const inv =
206
207
  parsed.runId !== undefined
207
- ? getInvocation(db, parsed.runId)
208
+ ? getInvocation(db, resolveRunId(db, parsed.runId, 'vx last') ?? parsed.runId)
208
209
  : (listInvocations(db, { limit: 1 })[0] ?? null)
209
210
  if (inv === null || inv === undefined) {
210
211
  throw new UserError(
@@ -0,0 +1,27 @@
1
+ // A run id as a user types it: the whole id, or a prefix of one, as git
2
+ // takes a short sha. `vx last --list` prints 36-character ids; nobody
3
+ // retypes one whole.
4
+
5
+ import type { Database } from 'bun:sqlite'
6
+ import { UserError } from '../util/index.js'
7
+
8
+ /**
9
+ * The recorded run `raw` names: itself when recorded, else the one run
10
+ * whose id starts with it. Null when none does; a prefix several runs share
11
+ * is refused with those runs, since picking one would replay the wrong run.
12
+ */
13
+ export function resolveRunId(db: Database, raw: string, verb: string): string | null {
14
+ const exact = db.query('SELECT 1 FROM invocations WHERE run_id = ?').get(raw)
15
+ if (exact !== null) return raw
16
+ const rows = db
17
+ .query(
18
+ 'SELECT run_id FROM invocations WHERE substr(run_id, 1, ?) = ? ORDER BY run_id DESC LIMIT 6',
19
+ )
20
+ .all(raw.length, raw) as Array<{ run_id: string }>
21
+ if (rows.length === 0) return null
22
+ if (rows.length === 1) return rows[0]!.run_id
23
+ const shown = rows.slice(0, 5).map((r) => ` ${r.run_id}`)
24
+ throw new UserError(
25
+ `${verb}: run id ${raw} is the start of ${rows.length > 5 ? 'more than 5' : rows.length} runs — type more of it:\n${shown.join('\n')}`,
26
+ )
27
+ }
package/src/cli/run.ts CHANGED
@@ -547,7 +547,9 @@ export async function runCmd(args: readonly string[]): Promise<number> {
547
547
  // explicit anchored positional.
548
548
  if (tasks.length === 0) {
549
549
  if (!process.stdin.isTTY) {
550
- process.stderr.write(`vx run: missing task name (stdin is not a TTY)\n`)
550
+ process.stderr.write(
551
+ `vx run: missing task name (stdin is not a TTY, so no picker; vx run <task>, e.g. vx run build)${seeHelp('run')}\n`,
552
+ )
551
553
  return 1
552
554
  }
553
555
  const picked = await pickTask(
package/src/cli/select.ts CHANGED
@@ -305,7 +305,10 @@ export async function resolveFilters(
305
305
  }
306
306
  // Something matched, so the run proceeds; a pattern that matched nothing
307
307
  // alongside it is still worth a line — it is probably a typo.
308
- for (const f of unmatched) process.stderr.write(`vx: filter "${f}" matched no projects\n`)
308
+ for (const f of unmatched)
309
+ process.stderr.write(
310
+ `vx: filter "${f}" matched no projects${didYouMeanProject([f], projects)}\n`,
311
+ )
309
312
  let staged: Map<string, ProjectEntry> | undefined
310
313
  if (stagedPromise !== undefined) {
311
314
  try {
@@ -398,7 +401,9 @@ export async function pickTask(
398
401
  }
399
402
  const n = Number(answer)
400
403
  if (!Number.isInteger(n) || n < 1 || n > entries.length) {
401
- process.stderr.write(`vx run: invalid selection: ${answer}\n`)
404
+ process.stderr.write(
405
+ `vx run: invalid selection: ${answer} (type a number from 1 to ${entries.length})\n`,
406
+ )
402
407
  return null
403
408
  }
404
409
  return entries[n - 1] ?? null
package/src/cli/why.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import { nearMatches, UserError } from '../util/index.js'
18
18
  import { findWorkspaceRoot } from '../workspace/index.js'
19
19
  import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
20
+ import { resolveRunId } from './run-id.js'
20
21
 
21
22
  interface WhyArgs {
22
23
  target?: string
@@ -166,7 +167,10 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
166
167
  try {
167
168
  const db = cache.dbHandle()
168
169
  const taskId = resolveTarget(cache, parsed.target)
169
- const runId = parsed.runId ?? latestRunId(cache.dbHandle(), taskId)
170
+ const runId =
171
+ parsed.runId !== undefined
172
+ ? (resolveRunId(db, parsed.runId, 'vx why') ?? parsed.runId)
173
+ : latestRunId(db, taskId)
170
174
 
171
175
  if (runId === null) {
172
176
  // Runs exist (resolveTarget passed) but predate run ids — fall back to
@@ -243,7 +243,7 @@ async function sandboxFact(declared: number): Promise<InfoFacts['sandbox']> {
243
243
 
244
244
  /**
245
245
  * The doctor's output is pasted into bug reports and compared between
246
- * invocations (`vx stats` is pinned byte-identical to `vx info`), so a
246
+ * invocations, so a
247
247
  * reason must not carry this process's id. The Linux runtime names its
248
248
  * mux socket after the pid (`srt-mux-<pid>-<n>.sock`), and a listen that
249
249
  * fails — a nested sandbox, a read-only tmpdir — quotes that path.
@@ -154,7 +154,6 @@ export type {
154
154
  CacheKeyExplanation,
155
155
  InputDiffEntry,
156
156
  InvocationDetail,
157
- InvocationRow,
158
157
  ListInvocationsArgs,
159
158
  ListRunsArgs,
160
159
  RunDetail,
@@ -114,7 +114,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
114
114
  /**
115
115
  * One `vx run` invocation header — every `invocations` column, camelCased,
116
116
  * `tags` parsed to a `Record<string,string>` and `dirty`/`ci`/`exitOk`
117
- * surfaced as booleans. Superset of the old `InvocationRow`: the SPA's
117
+ * surfaced as booleans. The SPA's
118
118
  * existing fields (runId / startedAt / endedAt / taskCount / failedCount /
119
119
  * hitCount / totalDurationMs) are all present, so existing views keep working.
120
120
  */
@@ -147,9 +147,6 @@ export interface InvocationDetail {
147
147
  tags: Record<string, string>
148
148
  }
149
149
 
150
- /** @deprecated kept as an alias of `InvocationDetail` for older callers. */
151
- export type InvocationRow = InvocationDetail
152
-
153
150
  interface InvocationRawRow {
154
151
  runId: string
155
152
  command: string
package/src/util/index.ts CHANGED
@@ -45,7 +45,7 @@ export { appendTail, createTail, resetTail, tailText, type Tail } from './tail.j
45
45
  export { ulid } from './ulid.js'
46
46
  export { splitTaskId } from './task-id.js'
47
47
  export { editDistance, nearMatches, nearest } from './edit-distance.js'
48
- export { CORE_VERBS, DISPATCHED_VERBS, MOVED_VERBS } from './verbs.js'
48
+ export { CORE_VERBS, MOVED_VERBS } from './verbs.js'
49
49
  export { isUnsupportedBun, MIN_BUN, unsupportedBunMessage } from './bun-version.js'
50
50
  export { executablePath } from './which.js'
51
51
  export { procfsIsOwn } from './procfs.js'
package/src/util/verbs.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  /**
2
- * The verbs the CLI dispatcher (`cli/index.ts`) owns; `stats` is a
3
- * deprecated alias of `info` and stays out of hints but is still matched
4
- * first. Lives in util rather than cli because the workspace VALIDATOR
2
+ * The verbs the CLI dispatcher (`cli/index.ts`) owns. Lives in util rather than cli because the workspace VALIDATOR
5
3
  * refuses a plugin verb that names one of these — a verb core matches
6
4
  * first could never run — and the workspace module cannot import cli.
7
5
  */
@@ -21,9 +19,6 @@ export const CORE_VERBS = [
21
19
  'version',
22
20
  ] as const
23
21
 
24
- /** Every word the dispatcher matches before consulting plugins. */
25
- export const DISPATCHED_VERBS: readonly string[] = [...CORE_VERBS, 'stats']
26
-
27
22
  /**
28
23
  * Verbs core owned once and a package owns now. Not core verbs: a plugin
29
24
  * may declare them, and the dispatcher prints the
@@ -36,4 +31,5 @@ export const MOVED_VERBS: Readonly<Record<string, string>> = {
36
31
  prune:
37
32
  'vx prune was removed (2026-09-11): the Docker-subset verb is not shipped; copy the ' +
38
33
  'workspace and `--filter` the build instead',
34
+ stats: 'vx stats was removed (H-19): run `vx info`, which prints the same report',
39
35
  }
@@ -15,7 +15,7 @@ import {
15
15
  type WorkspaceConfig,
16
16
  } from '../config.js'
17
17
  import {
18
- DISPATCHED_VERBS,
18
+ CORE_VERBS,
19
19
  MAX_TIMEOUT_MS,
20
20
  nearest,
21
21
  normalizeGlob,
@@ -174,7 +174,7 @@ export function validateWorkspace(config: WorkspaceConfig, configPath: string):
174
174
  // would run the first declared and hide the second. Here, in the
175
175
  // schema, so every loader of the file — a run, a reading verb, the
176
176
  // plugin-verb lookup — refuses it the same way.
177
- if (DISPATCHED_VERBS.includes(verb)) {
177
+ if ((CORE_VERBS as readonly string[]).includes(verb)) {
178
178
  throw new UserError(
179
179
  `${configPath}: plugin '${plug.name}' declares command '${verb}', a core verb — core verbs cannot be shadowed`,
180
180
  )