Features

Your agent sees how the code fits together

What you get

Your agent asks about relationships instead of text. Who calls a function, what does it call, what depends on a change: greppy answers in one call with the file, the line and the name of the function. The agent no longer maps matching lines to their functions by hand.

Before and after

The question for serde: Who calls visit_content_map?

Without greppy

grep returns lines, not callers. The agent searches for the word and then maps every match to the function it sits in.

$ grep -rnw visit_content_map --include='*.rs' .
./serde/src/private/de.rs:1103:    fn visit_content_map<'de, V, E>(
./serde/src/private/de.rs:1152:                Content::Map(v) => visit_content_map(v, visitor),
./serde/src/private/de.rs:1399:                Content::Map(v) => visit_content_map(v, visitor),
./serde/src/private/de.rs:1415:                Content::Map(v) => visit_content_map(v, visitor),
$ grep -n 'fn \|impl' serde/src/private/de.rs | awk -F: '$1>=1103 && $1<=1415'
1103:    fn visit_content_map<'de, V, E>(
1120:    impl<'de, E> Deserializer<'de> for ContentDeserializer<'de, E>
1126:        fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1156:        fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
…
1394:        fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1404:        fn deserialize_struct<V>(

With greppy

$ greppy who-calls visit_content_map
serde/src/private/de.rs:1152  ContentDeserializer::deserialize_any
serde/src/private/de.rs:1399  ContentDeserializer::deserialize_map
serde/src/private/de.rs:1415  ContentDeserializer::deserialize_struct
without greppy with greppy
Calls 2 1
Output characters 2,569 204
Tokens 738 45

How it works

Compiled-in tree-sitter parsers split every file into definitions. From them greppy builds a typed symbol graph: functions, methods and types are nodes; calls, usages, type references and imports are edges. who-calls follows a symbol's incoming edges, callees its outgoing ones. impact keeps going transitively and shows in one call everything a change reaches; path connects two symbols through their call sites. Before every query greppy checks the index against the working tree, so the answer matches the current state of your files. When a name is defined more than once, you qualify it with its file, for example serde_derive/src/de/struct_.rs::deserialize_map.

The commands

Command what it does
greppy who-calls S who calls or uses S, with file and line; several symbols in one call
greppy callees S the indexed definitions that S calls directly, with their location
greppy impact S the transitive reach in one call: --direction incoming (default) shows what breaks when you change S, outgoing what S reaches
greppy path --from A --to B every call chain between two symbols as one tree of their call sites
greppy where-am-i the repository at a glance: layout, languages, entry points, test directories

More in the docs