Skip to content

Add --filter and --filter-regex flags to restrict project scan scope#28

Merged
joshLong145 merged 3 commits into
mainfrom
feat/directory-filter
May 15, 2026
Merged

Add --filter and --filter-regex flags to restrict project scan scope#28
joshLong145 merged 3 commits into
mainfrom
feat/directory-filter

Conversation

@joshLong145

Copy link
Copy Markdown
Owner

Summary

  • PathFilter module (src/filter.rs) — two filter variants: Literal (component-prefix path matching, so src/parser matches src/parser/rust.rs but not src/parser_extra.rs) and Regex (compiled, unanchored regex matched against the slash-normalised project-relative path). Construction is infallible for literals; regex compilation errors surface immediately with the offending pattern in the message. Existence validation for literal paths is a separate validate() step so the CLI can emit a clear error before any scan work begins.

  • --filter <path> — restrict the project scan to a subpath. Leading slash is accepted and normalised away. Errors at startup if the path does not exist under --project.

  • --filter-regex <pattern> — restrict to files matching a regex. Mutually exclusive with --filter (enforced by clap conflicts_with).

  • Scanner integration — both ParserRegistry::scan_project and scan_project_serena accept Option<&PathFilter>. In the tree-walker path, non-matching files are skipped before disk I/O. In the Serena path, files are dropped after pickle parse (cache layout is fixed) but before any downstream consumer sees the list.

Test plan

  • cargo test passes — src/filter.rs has 23 unit tests covering literal normalisation, component-prefix semantics, regex matching, validate edge cases, and display formatting
  • ambits --project . --filter src/parser — TUI shows only src/parser/* files
  • ambits --project . --filter-regex '^src/.*\.rs$' — matches expected files
  • ambits --project . --filter nonexistent — exits with a clear path error before scanning
  • ambits --project . --filter src/foo --filter-regex bar — clap rejects with conflict error

🤖 Generated with [Claude Code

joshLongBI and others added 3 commits May 14, 2026 20:12
…ring

Standalone module providing the data type behind the upcoming `--filter`
and `--filter-regex` CLI flags. No CLI wiring yet — that lands in a
follow-up commit so the filter semantics can be reviewed in isolation.

`PathFilter` has two variants:

- `Literal(Vec<String>)` — a project-relative subpath stored as
  normalized path components. `src/parser` and `/src/parser` both
  parse to `["src", "parser"]`. Matching is path-component-aware so
  `src/parser` matches `src/parser/rust.rs` but NOT
  `src/parser_extra.rs` — avoiding the silent prefix-match footgun.
  An empty literal (`""` or `"/"`) degenerates to "no filter".

- `Regex(regex::Regex)` — compiled regex matched (unanchored, caller
  anchors with `^...$`) against the slash-normalized project-relative
  path. Portable across platforms because `\` is converted to `/`
  before matching.

Construction is split from validation:
  - `literal()` is infallible (only normalization).
  - `regex()` errors with a clear "invalid filter regex" message.
  - `validate(&project_root)` exists separately so the CLI can produce
    a precise "filter path … is not accessible from project root …"
    error before any scan work begins. Regex validation is a no-op (a
    regex is "valid" iff it compiled; empty match sets aren't an
    error).

`display()` provides a user-facing form for upcoming title/header
rendering.

Test coverage (19 new tests, all green) covers: leading-slash
normalization, separator collapsing, component-aware vs prefix-only
matching, exact-file match, deeper-vs-shallower path handling, the
designed-against `src/parser` vs `src/parser_extra.rs` distinction,
empty-filter degeneration, validation success/failure with tempfile,
regex compile errors, unanchored substring matching, and display
formatting.

Adds `regex = "1"` as a normal dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend `ParserRegistry::scan_project` and `serena::scan_project_serena`
to accept an optional `&PathFilter`. When provided, files whose
project-relative path does not satisfy the filter are skipped:

- `scan_project` checks the filter inside the `WalkBuilder` loop *before*
  reading the file from disk, so excluded files cost only a directory
  entry traversal.
- `scan_project_serena` retains only matching files after pickle parse;
  the cache layout is opaque so the filter is applied post-load, but
  before the tree is assembled.

All three production call sites (`main.rs` × 2, the TUI cache-rescan in
`tui.rs`) pass `None` for now — behavior is unchanged. CLI flag and
display wiring follow in subsequent commits.

284 lib tests + 37 integration tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add two mutually exclusive clap args on top of the existing scan
plumbing:

- `--filter <PATH>` — project-relative literal subpath. Leading slash
  accepted but optional. Path-component matching (so `src/parser` does
  NOT silently include `src/parser_extra.rs`). The path must exist
  under --project; otherwise the program exits with
  `filter path "<p>" is not accessible from project root <root>`
  before any scan work begins.

- `--filter-regex <PATTERN>` — regex over the slash-normalized
  project-relative path. Unanchored by default; users anchor with
  `^...$` as needed. Invalid regex syntax surfaces as
  `invalid filter regex "<p>": <details>` at CLI parse time.

Mutual exclusion enforced by clap (`conflicts_with`) — passing both
fails with a clear message before main() runs.

The constructed `PathFilter` is passed through to both
`ParserRegistry::scan_project` and `serena::scan_project_serena` so the
tree-sitter and Serena-cache code paths share a single filter
implementation.

Smoke-tested locally:
  ambits --project . --filter src/filter.rs --coverage
  ambits --project . --filter-regex '^src/parser/.*\.rs$' --coverage
  ambits --project . --filter src/does_not_exist          # errors clearly
  ambits --project . --filter src --filter-regex foo      # clap rejects

TUI re-parse and report-header display land in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joshLong145 joshLong145 merged commit 3764dd8 into main May 15, 2026
2 checks passed
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 83.90244% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.14%. Comparing base (e4511a0) to head (ebc4f75).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/parser/mod.rs 0.00% 11 Missing ⚠️
src/main.rs 0.00% 9 Missing ⚠️
src/serena/mod.rs 0.00% 7 Missing ⚠️
src/filter.rs 97.17% 5 Missing ⚠️
src/tui.rs 0.00% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #28      +/-   ##
==========================================
+ Coverage   73.78%   74.14%   +0.36%     
==========================================
  Files          24       25       +1     
  Lines        6766     6965     +199     
  Branches     6766     6965     +199     
==========================================
+ Hits         4992     5164     +172     
- Misses       1666     1693      +27     
  Partials      108      108              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@joshLong145 joshLong145 deleted the feat/directory-filter branch May 15, 2026 02:24
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.

3 participants