Repository navigation
Closed
Description
Slice 1 of 3 under #953.
Ships the complete non-TTY behavior and a working (if plain) interactive loop. No line editing yet — that is #953's slice 2.
Scope
- New
packages/rust/src/cli/repl.rs, roughly:Prompt (interactive only) → read line → skip blanks →async fn run_repl<R: BufRead, W: Write>( state: &AppState, input: R, out: &mut W, interactive: bool ) -> u8
execute_query→ print result, or print the error to stderr and continue. This continue-on-error is the one real behavioral difference fromrun_query, which exits 1. run_default'sNonearm (packages/rust/src/cli/run.rs:296-301) swaps the usageeprintln!forload_state+run_repl.- TTY/pipe split via
std::io::stdin().is_terminal()— std since 1.70, workspace is on 1.85. TTY → prompt + banner; non-TTY → no prompt, same loop. One boolean covers both halves of Epic: baredirsqlopens an interactive REPL #953. - Exit: bare
exit/quit/ Ctrl-D. No dot-commands — see Epic: baredirsqlopens an interactive REPL #953 for the rationale. - Static sample-query banner on TTY start. Static text only — no live data rows, no scan (see Configless startup walks the entire tree for a provably empty result #986).
Reuses, unchanged
load_state (run.rs:434) and execute_query (cli/execute.rs:59) need no changes. Output is the existing JSON array serialization; the TTY table is slice 3.
Decisions already made (see #953)
- One
AppState, built once before the loop — not a loop of one-shot queries. Cheaper (no re-scan per statement), smaller diff, and the live watcher keeps the index fresh across statements. - Exit code 0 on clean EOF, regardless of per-statement failures (matches interactive
sqlite3). Document it. PARTIAL_SCAN_EXIT(23) has no REPL meaning — report skipped files once up front, drop the code.AppState::Unavailable(bad config) fails identically on every statement — check readiness once before the loop and exit 1 there, rather than looping the same error forever.- Read stdin via
tokio::task::spawn_blocking—run_cliwraps everything inruntime.block_on(run.rs:270), so a bare blocking read wedges a runtime worker.
Behavior change
echo "SELECT 1" | dirsql goes from an exit-2 usage error to running the statement. Verified: nothing in the repo asserts the old behavior — only no_subcommand_and_no_sql_selects_neither (run.rs:667) touches it, and it asserts parsing, not the runtime error.
Testing
- Unit: colocated inline
#[cfg(test)]inrepl.rs. TheBufRead/WriteDI signature exists precisely sounit lint's isolation rule is satisfiable without a TTY. Plusunit mutationon changed lines. Bonus:run_default's error branch is untested today, so this is a net coverage gain. - Integration: N/A — integration tests hit the SDK, never the CLI.
- E2E, pipe path: ordinary subprocess with piped stdin (Rust
tests/*_e2e.rs, python/ts e2e). - E2E, TTY path: curtaincall — real PTY + VT100. Banner renders, prompt appears,
exit/quitterminate,term.key_ctrl_d()terminates. See Epic: baredirsqlopens an interactive REPL #953 for the Python-floor caveat.
Checklist
- Red tests pushed as their own commit; CI observed red for the right reason before implementation
- Changelog fragment under
packages/rust/changelog.d/ - Migration fragment under
packages/rust/migrations.d/— required: runtime behavior change with no API change (baredirsql: exit-2 → REPL) - Docs: new section in
docs/reference/cli.md, plus the three places stating "no subcommand and no SQL: a usage error" —run.rsmodule doc (~line 9), clapabout(~line 25), claplong_about(~line 27) -
PARITY.md: CLI is one shared implementation → no drift; state it in the PR rather than skipping silently - Re-attest both bindings (a
packages/rust/srcchange stales python and ts) - Manual run per AGENTS.md — build it and actually type at the REPL
Metadata
Metadata
Assignees
Labels
No labels
Activity
dirsqlopens an interactive REPL #953feat(cli): bare `dirsql` opens a REPL over the current directory
test: pin the piped REPL contract for bare `dirsql` (RED)