Skip to content

feat(cli): vx watch — initial run then re-run on file changes - #67

Merged
Exelord merged 1 commit into
mainfrom
claude/vx-watch
May 13, 2026
Merged

feat(cli): vx watch — initial run then re-run on file changes#67
Exelord merged 1 commit into
mainfrom
claude/vx-watch

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

New subcommand mirroring turbo watch / nx watch. Closes the last open Turbo-alignment gap.

  • Initial run uses the same orchestrator path as vx run.
  • Per-project recursive fs.watch (Bun supports recursive on every platform).
  • Workspace root non-recursive watch for lockfile / pnpm-workspace.yaml changes — so a bun install bump triggers a cycle even when no project dir saw the change.
  • Path filter ignores node_modules / .git / .vx segments and .tsbuildinfo / ~ (editor swap) suffixes.
  • Debounced ~150 ms after the last event.
  • Reentrancy guard: while a cycle is running, further events set pending = true; the loop drains them after the current cycle so two bursty events collapse into one re-run.
  • Ctrl+C closes watchers, exits 0.
  • Failed cycles don't break the loop — they print the FAILED block and wait for the next change.

Cache is the workhorse — most re-runs are cache hits (~tens of ms). We deliberately don't filter events through cache.inputs.files globs: the cache key is the source of truth, and pre-filtering would redo the glob + boundary work on every event.

Rejected flags

Parser exits 1 before the initial run for flags that don't make sense in a loop:

  • --dry / --graph — skip execution.
  • --summarize / --profile — would overwrite the target file per cycle.

Otherwise the flag surface is identical to vx run. Extracted resolveRunOptions(parsed, cwd, tasks) from cli/run.ts so both subcommands share the scope resolution.

Persistent tasks

Re-spawn each cycle. For dev servers that should stay up, use the tool's own watch (vite, tsc -b -w, bun --watch); vx watch is for vx watch test / vx watch lint / vx watch build.

Files

  • New: src/cli/watch.ts, tests/cli.test.ts (7 new tests), docs/modules/cli-watch.md
  • Modified: src/cli.ts (dispatcher), src/cli/run.ts (extracted resolveRunOptions), src/cli/help.ts, docs/cli.md, docs/comparison.md, docs/modules/README.md, docs/modules/cli-run.md, CLAUDE.md

Test plan

  • 6 parser-side validations (no task, --dry / --graph / --summarize / --profile rejection, parser error prefix).
  • 1 end-to-end re-run-on-change: fixture workspace with a task that cats a file; the test writes a new value mid-watch and asserts the re-run surfaces it; SIGINT exits 0.
  • bun src/bin.ts run lint — clean
  • bun src/bin.ts run format-check — clean (oxfmt ran)
  • bun test — 338 pass, 0 fail (was 331)
  • Smoke test against tmpdir fixture: vx watch with no task / --dry / --help text all behave correctly.
  • CI green.

Generated by Claude Code

New subcommand mirroring `turbo watch` / `nx watch`. Initial run uses
the same orchestrator path as `vx run`. After it completes:

- Per-project recursive `fs.watch` (Bun supports recursive on every
  platform).
- Non-recursive watch of the workspace root for fingerprint files
  (`pnpm-lock.yaml`, `bun.lock`, `pnpm-workspace.yaml`, …) so an
  install bump triggers a cycle even when no project dir saw it.
- Path filter ignores `node_modules` / `.git` / `.vx` segments and
  `.tsbuildinfo` / `~` (editor swap) suffixes.
- Events debounce ~150 ms after the last one before triggering.
- Reentrancy guard: while a cycle is running, further events set a
  `pending` flag; the loop drains it after the current cycle so two
  bursty events collapse into one re-run.
- Ctrl+C closes watchers, exits 0.

Cache is the workhorse — most re-runs are cache hits (~tens of ms).
We deliberately don't filter events through `cache.inputs.files`
globs: the cache key is the source of truth, and pre-filtering would
redo the glob + boundary work on every event.

Rejected at parse time (would be nonsensical in a loop):
  --dry, --graph, --summarize, --profile

Flag surface is otherwise identical to `vx run` — scope resolution
(`--all` / `--filter` / `--affected` / `pkg#task`), `--concurrency`,
`--no-cache`, `--excludeDependencies`, `--verbosity`, forwarded `--`
args. Extracted `resolveRunOptions(parsed, cwd, tasks)` from
`cli/run.ts` so both subcommands share the scope resolution.

Persistent tasks re-spawn each cycle (the orchestrator SIGTERMs them
at end-of-run, then the next cycle launches fresh). For dev-server
workflows where the server should stay up across changes, use the
tool's own watch (vite, tsc -b -w, bun --watch); `vx watch` is for
`vx watch test` / `vx watch lint` / `vx watch build`.

Tests:
- 6 parser-side validations (no task, --dry / --graph / --summarize
  / --profile rejection, parser error prefix).
- 1 end-to-end re-run-on-change: fixture workspace with a task that
  `cat`s a file; the test writes a new value mid-watch and asserts
  the re-run surfaces it; SIGINT exits 0.

Docs:
- docs/cli.md: new `## vx watch` section with lifecycle, path filter,
  constraints, exit codes.
- docs/comparison.md: watch flipped from gap to shipped in both
  matrices; gap-list item rewritten as shipped.
- docs/modules/README.md + new docs/modules/cli-watch.md.
- docs/modules/cli-run.md: documents the new exported
  `resolveRunOptions`.
- src/cli/help.ts: added watch line + section.
- CLAUDE.md: decision-log entry + roadmap updated (watch removed
  from list; "Recently shipped" entry added).
@Exelord
Exelord merged commit 84db8cd into main May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants