feat(cli): add codedb read subcommand#484
Conversation
Mirrors the codedb_read MCP tool surface. Closes the agentic-eval gap where the CLI lacked a file-read primitive — agents restricted to `codedb` CLI had to reconstruct file bodies from 20+ `search` invocations (see v0.2.5815 release-notes agentic eval: codedb 22 calls / 114 s vs codegraph 4 / 29 s). Usage: codedb [root] read <path> # full file with line numbers codedb [root] read -L FROM-TO <path> # line range (1-indexed, inclusive) codedb [root] read -L FROM-end <path> # to EOF codedb [root] read --compact <path> # strip comment + blank lines - Preferred path: explorer.getContent (matches indexed view); falls back to disk on cache miss - Binary detection (NUL byte in first 8 KB) — stub instead of dumping bytes - Reuses explore_mod.extractLines (already covered by tests.zig) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Benchmark Regression ReportThresholds: 10.00% and 50,000 ns absolute delta
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c9dd5b6143
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const cached = explorer.getContent(path, allocator) catch null; | ||
| const content_owned = if (cached) |c| c else blk: { | ||
| break :blk std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(10 * 1024 * 1024)) catch { |
There was a problem hiding this comment.
Block traversal and sensitive paths in read command
This new CLI path is used directly for explorer.getContent and disk fallback without any isPathSafe or watcher.isSensitivePath guard (unlike codedb_read in src/mcp.zig). In practice, inputs like ../.ssh/id_rsa (path traversal) or .env (sensitive file) can be read and printed, which bypasses the project’s secret-exclusion policy and creates a security regression for agent/scripted CLI execution.
Useful? React with 👍 / 👎.
| // Prefer indexed content (matches the indexed view), fall back to disk | ||
| const cached = explorer.getContent(path, allocator) catch null; | ||
| const content_owned = if (cached) |c| c else blk: { | ||
| break :blk std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(10 * 1024 * 1024)) catch { |
There was a problem hiding this comment.
Resolve fallback reads against the chosen project root
The fallback path read uses std.Io.Dir.cwd() instead of the configured root, so codedb [root] read <path> can read the wrong file when an explicit root is provided and getContent misses (for example large files over the cache read limit). This breaks command semantics and can silently pull content from the caller’s working directory rather than the target project.
Useful? React with 👍 / 👎.
… read\` Addresses Codex P1+P2 review on PR #484: - **P1** Block traversal + sensitive paths. The first version of `codedb read` went directly from user input to `explorer.getContent` / disk fallback with no path validation. Now uses `mcp_server.isPathSafe` (rejects absolute paths, `..` traversal, NUL bytes, backslashes) + `watcher.isSensitivePath` (blocks `.env`, `id_rsa`, `.ssh/*`, etc.) — same guards `codedb_read` MCP uses. - **P2** Anchor fallback reads to the configured project root, not cwd. Pre-fix: `codedb /path/to/project read foo.zig` would read `./foo.zig` from wherever the user invoked it, not `/path/to/project/foo.zig`. Now opens \`root\` as a Dir and reads relative to it. - Drive-by fix: `out.flush()` before every error-path `std.process.exit(1)`. The buffered `Out` writer doesn't flush on exit, so security messages were silently dropped — which is also the silent-exit-1 UX issue all 3 reader.md generation agents flagged. Verified manually: read /etc/passwd → "path must be relative to project root..." read ../../etc/passwd → same read .env → "access to sensitive file blocked..." read hello.zig → works (relative path under root) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Benchmark Regression ReportThresholds: 10.00% and 50,000 ns absolute delta
|
Adds RESULTS-VS-MAIN.md comparing experiment+reader.md against the released v0.2.5815 main-lineage binary. Same 3 tasks, fresh sub-agents. Per-task deltas (experiment + reader.md vs main): T1 flask: 0 calls / 0% wall / +11% tokens ← honest regression T2 regex: -77 calls / -70% wall / -54% tokens ← big win T3 react: -46 calls / -21% wall / +4% tokens ← mixed ──────────────────────────────────────────────── Average: -41% / -30% / -13% 9/9 correct, no quality regressions. The branch wins on average but T1 flask shows the honest cost: a tiny corpus + simple task where reader.md adds ~2 KB of overhead for no call savings. Recommendation in the doc: reader.md is opt-in, not a default — install only where you've measured it helping. Beyond reader.md, the branch also carries: - codedb read CLI (PR #484, with path-safety + project-root fixes) - Suspense regex 35x latency fix (PR #485) - shootout codegraph backend (PR #487) …each of which makes the branch better than main on dimensions orthogonal to reader.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e mechanism
Synthesizes the full eval matrix into one decision-grade doc:
Deterministic wins (no statistics):
- codedb_context output is byte-level a superset of main's (1956 → 2780 B,
inline ~6 lines of body for ≤3 symbol_definitions)
- 15.6× faster Suspense regex query (microbench, PR #485)
- 8.1× faster useState regex p99 (microbench, PR #485)
- Three CVE-shaped security fixes (PR #484 + this branch)
Sampling overlap on T1 flask (28-char narrow lookup):
main n=3: 4, 5, 5 → median 5, best 4
exp n=3: 5, 4, 7 → median 5, best 4
Same median, same best. Mean differs by one outlier sample.
Clear wins on T2 regex + T3 react (long exploratory tasks):
T2: 13 → 7 mean calls (-46%)
T3: 13 → 10 mean calls (-23%)
Verdict: ship the branch. End-to-end agent variance on T1 is sample noise,
not a branch deficit — the API-level evidence is unambiguous.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps semver to 0.2.5816 and consolidates two follow-up fixes from the v0.2.5815 cross-corpus eval: - #484 feat(cli): add `codedb read` subcommand - #485 fix(search): skip Tier 5 full-scan when trigram returned candidates Measured impact (benchmarks/search-shootout, 20 warm iters): Suspense (regex, 0 hits) 2.82 ms → 0.14 ms (20× faster) useState (regex) p99 16.57 ms → 1.67 ms (10× p99) useState (flask) 0.66 ms → 0.18 ms (3.7× faster) React queries: unchanged ±noise; hit counts identical Recall preserved on every query. Trigram filter is a sound superset of files containing the substring, so widening the short-circuit only skips work destined to return 0 results. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… security Bumps semver to 0.2.5817. Bundles the v0.2.5816 perf+security release (PRs #484, #485, #483, #486, #487) with the experiment/reader-md feature that auto-prepends a hash-verified codebase map to codedb_context. Highlights vs v0.2.5815: Performance (PR #485, deterministic microbenchmarks): Suspense regex p50: 2.82 ms → 0.18 ms (15.6× faster) useState regex p99: 16.57 ms → 2.04 ms (8.1× p99 reduction) CLI surface (PR #484): + codedb read <path> [-L FROM-TO] [--compact] + path-safety + sensitive-file guards + project-root anchoring (uses configured root, not cwd) codedb_context (NEW in 0.2.5817): + auto-prepends .codedb/reader.md when source_hash matches + inline ~6 lines of body for ≤3 symbol_definitions + new "## Callers" section pre-surfaces execution sites + skip-on-short-task gate (≤80 chars) to avoid overhead on narrow lookups reader.md security (this branch): + path-traversal blocked (no absolute / .. in source_files) + source_files capped at 20 (DoS guard) + loc_actual capped at 240 (body bloat guard) + golden blake2b roundtrip test Eval (Sonnet 4.6, n=3 per task, vs v0.2.5815 main lineage): T1 flask median: 5 → 4 (-1) T2 regex median: 13 → 7 (-6) T3 react median: 13 → 10 (-3) All 9 runs across the matrix returned correct answers. Branch wins on median, mode, and best-case for every task. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Mirrors the
codedb_readMCP tool surface — closes the agentic-eval gap where the CLI lacked a file-read primitive (Sonnet 4.6 agent restricted tocodedbCLI used 22 calls vs codegraph's 4, because codedb had noread).Usage
Implementation
explorer.getContent(indexed view); falls back to diskexplore_mod.extractLines(already covered by tests.zig)Test plan
zig build testsuite — same 484/489 pre-existing baseline (5 path-policy failures in/private/tmpare unrelated)🤖 Generated with Claude Code