The find verb
rf find <pattern> [path] --name <ext> [--structural <pat> --lang <id>] [--json]
find answers “where does pattern appear across files named *.<ext>?” —
the job a fd -e <ext> | xargs rg <pattern> pipe does, but recording which
stage found each match — which a pipe throws away. --name is required and takes an extension with
no leading dot.
Four independent sources
find combines four discovery sources. Every match is attributed to exactly one
stage:
1. fd name filters (in-process)
The walker reproduces fd’s default / -H / -I
behavior and attributes a content match the default walk dropped to the fd stage
that hid it: fd_hidden (a hidden dotfile that matches the name filter, recover
with fd -H), fd_ignore (a gitignored file that matches the name filter, fd -I), fd_filter (a file some other default fd filter dropped, fd -u), or
fd_name (the content match is in a file outside the *.<ext> name filter —
widen the filter). A file the default walk surfaced normally is found.
2. ripgrep binary skip (in-process)
rg_binary marks a file that ripgrep’s default binary detection would skip but
that contains the term when binary-as-text is enabled — diffed against the
default read.
3. git history (subprocess)
git_deleted recovers a match that is gone from the working tree but present
in history — the classic “a secret was committed, then scrubbed.” No
tree-only search, however aggressive, can find it. Powered by git; when git
is absent the stage contributes nothing.
4. ast-grep structural (subprocess)
With --structural PAT --lang LANG, ast_structural finds matches that have
no fixed literal form — a call like db.connect(dsn, ...) that a literal
string search cannot express. A file is ast_structural when ast-grep matches
it and a literal search for the pattern text does not. Powered by ast-grep;
absent it, a STRUCTURAL_UNAVAILABLE warning is emitted and the other stages
still run. --structural without --lang is a user-input error (exit 1).
Stage reference
Every find row carries a stage. The complete enum, in the order the binary
attributes them:
stage |
Source | Meaning | Recovery |
|---|---|---|---|
found |
fd + rg | Surfaced by the default walk and matched by ripgrep. | — |
rg_binary |
rg binary skip | fd passed the file, but ripgrep suppressed it as binary. | rg -a |
fd_hidden |
fd name filters | A hidden dotfile that matches the name filter. | fd -H |
fd_ignore |
fd name filters | A gitignored file that matches the name filter. | fd -I |
fd_filter |
fd name filters | A file some other default fd filter dropped. | fd -u |
fd_name |
fd name filters | The content match is in a file outside the *.<ext> name filter. |
widen --name |
git_deleted |
git history | Gone from the working tree but present in history. | recover from Git |
ast_structural |
ast-grep | Matched only by structural search, with no fixed literal form. | — |
The set is authoritative in rf capabilities --json under the find verb’s
output_schema.data[].stage.
Why a pipe cannot do this
A shell pipe erases fd’s exit code and conflates ripgrep’s “no files” with “no match,” so downstream there is no way to say which stage dropped a file. Reading every stage in one process keeps each match tied to its origin.
Example
rf find DB_DSN . --name config --json
{
"ok": true,
"data": [
{ "file": "app.config", "stage": "found" },
{ "file": ".secret.config", "stage": "fd_hidden" },
{ "file": "build/gen.config", "stage": "fd_ignore" },
{ "file": "blob.config", "stage": "rg_binary" },
{ "file": "history.config", "stage": "git_deleted" }
]
}
A true negative — a *.config with no term in the tree or in history — is
correctly not flagged. The stage reports no false positive.