flows: flows check --watch — save-time terminal feedback loop
Context
Today, an author writing a flow discovers authoring errors only by running flows check explicitly after each save. That's a save → alt-tab → flows check <path> → read output → alt-tab back loop, easily 8-10 seconds per iteration. For an author actively iterating on a spec, that friction is exactly what turns v1's "wrote a flow, hit step 26, fixed, tried again, hit another problem, fixed, ..." pain into v2's version of itself — a check pipeline that catches the problem is only useful if the author invokes it.
This slice adds a --watch flag on the existing flows check CLI so the author leaves it running in a split terminal and every save re-checks in <1s.
Non-goals (deliberately deferred to sibling slices):
- LSP / editor extension (slice L4)
- TypeScript language service plugin for cross-file checks (slice L2)
- JSON Schema for YAML editor validation (slice L1)
- TUI or interactive controls — this is plain text output, cleared and re-rendered per change
--watch is the cheapest deliverable in the "author sees errors while writing" bucket. It requires no editor plumbing and no per-IDE extension.
Scope
Add a --watch flag on flows check (packages/sdk/src/cli/check.ts). When set:
- Compile the target flow file once. Print the same report the existing
flows check prints (accepted + all diagnostics), preceded by an ANSI clear-screen so a re-render replaces the prior report.
- Watch a set of files for changes:
- The flow file itself.
- Every relative
use: import reachable from the flow file (walk the loader graph once at startup and after every re-check, since the set can grow).
- The nearest
flows.json found by walking parent directories from the flow file (SURFACE.md §5 project-config discovery).
- The kernel/agent/CLI adapter binaries are NOT watched — a change to those isn't an authoring change, and probing them re-runs preflight anyway.
- Debounce changes at ~150 ms. Multiple saves within the debounce window collapse into one re-check.
- On each re-check, clear the screen and print the fresh report.
--json remains supported alongside --watch. In JSON mode, one report object is printed per re-check on its own line; no screen clear (so flows check --watch --json <path> | jq streams).
- Exit cleanly on
Ctrl-C (SIGINT). Exit code is 0 if the last run was accepted, 1 otherwise, 2 if the initial file was missing / unreadable.
Refusals (unchanged)
Every refusal flows check already produces (unknown headers, invalid verification, unresolved refs, cycles, model_unknown, cli_missing, etc.) is preserved verbatim. --watch is a runner around the same pipeline, not a re-implementation.
Acceptance evidence
Files to touch
packages/sdk/src/cli/check.ts — add --watch flag parsing, wire to watcher.
packages/sdk/src/cli-watch.ts — new. File-set watcher, debounce, re-check loop. Prefer chokidar (already common in the SDK toolchain) or fs.watch if lighter-weight is preferred; the check pipeline dominates the cost so watcher-lib choice is secondary.
packages/sdk/tests/cli-watch.test.ts — new. Fixture-based; a Node test harness spawns flows check --watch, writes to the fixture, and asserts the re-checked output over the debounce window.
docs/SURFACE.md §5 — add one paragraph on --watch under flows check. No spec-shape change; the flag is a runner.
Out of scope
- LSP protocol / editor integration.
- Watching
.ts files that aren't use:-reachable but might be imported by them transitively — those are the tsserver plugin's job (slice L2).
- YAML schema-based validation (slice L1).
- Colored diff of report-vs-prior-report — this is nice-to-have; the existing report format is already terse enough that a full re-render is not visually noisy.
Depends on
Nothing new. flows check already ships.
Follow-ups
- Slice L2 (
@relayflows/ts-plugin) — same check pipeline surfaced in TS editors.
- Slice L1 (YAML JSON Schema) — same check pipeline surfaced in YAML editors.
- Slice L4 (full LSP) — only if L1 + L2 + this prove insufficient.
flows:
flows check --watch— save-time terminal feedback loopContext
Today, an author writing a flow discovers authoring errors only by running
flows checkexplicitly after each save. That's a save → alt-tab →flows check <path>→ read output → alt-tab back loop, easily 8-10 seconds per iteration. For an author actively iterating on a spec, that friction is exactly what turns v1's "wrote a flow, hit step 26, fixed, tried again, hit another problem, fixed, ..." pain into v2's version of itself — a check pipeline that catches the problem is only useful if the author invokes it.This slice adds a
--watchflag on the existingflows checkCLI so the author leaves it running in a split terminal and every save re-checks in <1s.Non-goals (deliberately deferred to sibling slices):
--watchis the cheapest deliverable in the "author sees errors while writing" bucket. It requires no editor plumbing and no per-IDE extension.Scope
Add a
--watchflag onflows check(packages/sdk/src/cli/check.ts). When set:flows checkprints (accepted + all diagnostics), preceded by an ANSI clear-screen so a re-render replaces the prior report.use:import reachable from the flow file (walk the loader graph once at startup and after every re-check, since the set can grow).flows.jsonfound by walking parent directories from the flow file (SURFACE.md §5 project-config discovery).--jsonremains supported alongside--watch. In JSON mode, one report object is printed per re-check on its own line; no screen clear (soflows check --watch --json <path> | jqstreams).Ctrl-C(SIGINT). Exit code is0if the last run was accepted,1otherwise,2if the initial file was missing / unreadable.Refusals (unchanged)
Every refusal
flows checkalready produces (unknown headers, invalid verification, unresolved refs, cycles, model_unknown, cli_missing, etc.) is preserved verbatim.--watchis a runner around the same pipeline, not a re-implementation.Acceptance evidence
flows check --watch testdata/hello-deterministic.flow.yamlprints an accepted report; editing the file adds- run: broken syntax— within ~1 s a fresh report appears with the diagnostic, screen cleared../reviewer.flow.tsviause:— editingreviewer.flow.tsretriggers the watch loop for the parent flow.flows.json(adding an unlisted model to the allowlist, then removing it) retriggers.for i in $(seq 20); do touch f; done) produces at most 2 re-checks (one head, one tail after debounce).flows check --watch --json flow.yamlstreams valid JSON, one object per re-check, no ANSI codes.Files to touch
packages/sdk/src/cli/check.ts— add--watchflag parsing, wire to watcher.packages/sdk/src/cli-watch.ts— new. File-set watcher, debounce, re-check loop. Preferchokidar(already common in the SDK toolchain) orfs.watchif lighter-weight is preferred; the check pipeline dominates the cost so watcher-lib choice is secondary.packages/sdk/tests/cli-watch.test.ts— new. Fixture-based; a Node test harnessspawnsflows check --watch, writes to the fixture, and asserts the re-checked output over the debounce window.docs/SURFACE.md§5 — add one paragraph on--watchunderflows check. No spec-shape change; the flag is a runner.Out of scope
.tsfiles that aren'tuse:-reachable but might be imported by them transitively — those are the tsserver plugin's job (slice L2).Depends on
Nothing new.
flows checkalready ships.Follow-ups
@relayflows/ts-plugin) — same check pipeline surfaced in TS editors.