Everything your agent knows keeps working
What you get
Your agent works with grep and ripgrep. With greppy it keeps both: greppy -rn PATTERN . is grep, greppy rg … is ripgrep, and output and exit code are the same. On top of that it gets greppy's commands for graph, search, reading and editing. One tool for everything, and no familiar call breaks.
Before and after
The question for serde: Where are the traits that start with pub trait Deserialize?
With grep
$ grep -rn "pub trait Deserialize" --include=*.rs .
./serde_core/src/de/mod.rs:554:pub trait Deserialize<'de>: Sized {
./serde_core/src/de/mod.rs:632:pub trait DeserializeOwned: for<'de> Deserialize<'de> {}
./serde_core/src/de/mod.rs:803:pub trait DeserializeSeed<'de>: Sized {
./serde_core/src/de/mod.rs:945:pub trait Deserializer<'de>: Sized {
./test_suite/tests/test_serde_path.rs:38: pub trait Deserialize<'a>: Sized {
With greppy
$ greppy -rn "pub trait Deserialize" --include=*.rs .
./serde_core/src/de/mod.rs:554:pub trait Deserialize<'de>: Sized {
./serde_core/src/de/mod.rs:632:pub trait DeserializeOwned: for<'de> Deserialize<'de> {}
./serde_core/src/de/mod.rs:803:pub trait DeserializeSeed<'de>: Sized {
./serde_core/src/de/mod.rs:945:pub trait Deserializer<'de>: Sized {
./test_suite/tests/test_serde_path.rs:38: pub trait Deserialize<'a>: Sized {
Byte for byte
cmp compares both outputs, for grep and for ripgrep.
$ grep -rn "pub trait Deserialize" --include=*.rs . > grep.out; echo "exit $?"
exit 0
$ greppy -rn "pub trait Deserialize" --include=*.rs . > greppy.out; echo "exit $?"
exit 0
$ cmp grep.out greppy.out && echo identical
identical
$ rg -n -t rust --sort path "pub trait Deserialize" . > rg.out; echo "exit $?"
exit 0
$ greppy rg -n -t rust --sort path "pub trait Deserialize" . > greppy-rg.out; echo "exit $?"
exit 0
$ cmp rg.out greppy-rg.out && echo identical
identical
The same holds for error cases, pipes and ripgrep flags. Every case ran once directly and once through greppy, with stdout and stderr compared separately:
| Case | Call through greppy | Exit direct / through greppy | stdout and stderr |
|---|---|---|---|
| hit, 374 bytes | greppy -rn "pub trait Deserialize" --include=*.rs . |
0 / 0 | identical |
| no hit | greppy -rn "no_such_symbol_xyz" --include=*.rs . |
1 / 1 | identical |
| file missing | greppy -n "Deserialize" does/not/exist.rs |
2 / 2 | identical, including the message grep: does/not/exist.rs: No such file or directory |
| pipe on stdin | cat serde_core/src/de/mod.rs | greppy -c "fn deserialize" |
0 / 0 | identical (38) |
leading grep word |
greppy grep -rlw visit_content_map serde |
0 / 0 | identical |
| ripgrep | greppy rg -n -t rust --sort path "pub trait Deserialize" |
0 / 0 | identical |
ripgrep flags without the rg word |
greppy --smart-case -t rust --sort path "pub trait deserialize" |
0 / 0 | identical |
Where greppy steps in
greppy passes real grep calls through. It recognizes two cases beforehand and says what is meant, instead of letting grep search for the wrong thing.
$ greppy -rn "visit_content_map" --code serde
`--code` is a greppy flag, not a grep flag - it belongs to greppy's navigation commands. Drop it, or name the command it goes with.
$ greppy who-call visit_content_map
status: invalid_invocation
command: `who-call`
message: unknown greppy command; nothing was passed to grep
next: did you mean `greppy who-calls ...`?
Both end with exit 64. A leading -p starts the built-in agent; for the pattern -p you write greppy -e -p FILE, with the same output as grep -e -p FILE.
How it works
greppy tells from the shape of a call whether one of its commands is meant. Everything else starts the system grep with the same arguments, and stdout, stderr and exit code come back unchanged. The passthrough calls the system grep directly and needs no index: in a directory without an index, greppy -rn TODO . creates neither a store nor a file in greppy's data directory.
greppy recognizes ripgrep calls by the word rg or by flags that exist in ripgrep alone, such as --smart-case or -t rust. It hands them to your installed rg. ripgrep searches files in parallel; with --sort path the order is the same on every run.
greppy installs itself under the name greppy and never as a grep alias. Your system grep stays your grep.
The commands
| Command | what it does |
|---|---|
greppy PATTERN [FILE], greppy -rn PATTERN PATH |
real grep, output and exit code byte-identical |
greppy grep … |
the same with a leading grep word |
greppy rg … |
ripgrep through your installed rg, byte-identical |
greppy --smart-case -t rust PATTERN |
ripgrep flags are recognized and routed to rg |
greppy -e -p FILE |
grep for the pattern -p |
greppy search-pattern REGEX |
text search that names the enclosing definition for every hit |
More in the docs
- grep and ripgrep passthrough: detection, order of the ripgrep routes, stdin
- Search:
search,search-symbolandsearch-pattern - Agent setup: what the prompt tells your agent about grep