How it works
The problem: a filtered search that looks empty
rg pattern applies three filters by default. It skips files ignored by
.gitignore/.ignore, it skips hidden files and dotfiles, and it skips files
it detects as binary. Those defaults are good for interactive use. They are
dangerous for a caller that reads the result as ground truth, because a search
that skipped most of the tree still exits 0 — indistinguishable from a search
that genuinely found everything.
An agent validating in a scratch directory, then running the same query against a real repository, can get two different answers and never know why.
The idea: filters as layers you can peel
rf treats each default filter as a layer. It runs the search with the
layer on and again with it off, and attributes every newly surfaced file to the
exact layer that had hidden it. The result is not just “here are more matches” —
it is “here is the match, and here is the filter that hid it, and here is the
command that proves it.”
Think of it like developing a photograph one chemical bath at a time and noting which bath brought out each detail, instead of dunking the print once and guessing.
One in-process engine, no subprocess
The layers are not reconstructed from shell output. rf links the
ignore walker and the
grep searcher — the same crates ripgrep and
fd are built from — directly into the binary. Each file’s membership in a filter
is read natively as the walk runs.
This matters because a shell pipe destroys provenance. In
fd -e config | xargs rg pattern, the pipe erases fd’s exit code, and ripgrep’s
exit 1 conflates “no files given” with “no match found.” There is no way,
downstream, to say which stage dropped a file. Reading the filters in-process
keeps that information attached to every match.
Two sources have no Rust binding — git history and ast-grep structural
matching — so rf runs those as subprocesses. Each contributes nothing (rather
than failing the whole run) when its tool is absent.
What you get from it
- content — peels ripgrep’s default filters as layers, plus a parallel UTF-16 encoding probe, attributing each recovered file.
- find — a staged
fd | rgpipeline reproduced in-process, widened with git history and ast-grep, each miss attributed to one stage. - doctor — reports the active ignore mode so you know whether a result will change between a scratch dir and a real repo.
Every verb reports through the same JSON contract and the same exit-code dictionary.