Documentation

Limits

What the graph does not see

greppy builds its symbol graph from source text. Edges a program wires up at runtime are invisible to every static tool, greppy included:

  • reflection;
  • runtime dependency injection;
  • monkeypatching;
  • dynamically dispatched calls;
  • macro expansion;
  • code generated during the build.

greppy is built so these blind spots do not turn into wrong answers:

  • search finds code by meaning, not by graph edges. A reflection target or a generated handler stays findable when you describe what it does.
  • who-calls returns incoming CALLS and USAGE edges. search-pattern gives you certainty at the source level.
  • The grep passthrough gives you certainty at the string level.

An example from serde: every call to missing_field in serde/src/private/de.rs comes from code that serde_derive generates during the build. The call sites are tokens inside quote_spanned!. The graph sees no call there. search-pattern finds the call sites, and search finds the definition by its meaning.

$ greppy who-calls missing_field
no callers
$ greppy search-pattern 'de::missing_field'
serde_derive/src/de.rs:795  serde_derive/src/de.rs::Function::expr_is_missing
serde_derive/src/de/enum_adjacently.rs:80  serde_derive/src/de/enum_adjacently.rs::Function::deserialize
$ greppy search "treat a missing Option field as None"
serde/src/private/de.rs:45  MissingFieldDeserializer::deserialize_option — visits the None variant of an Option
…
serde/src/private/de.rs:24  missing_field — deserializes a value from a named field, returning an error if absent
…

So an empty graph result does not prove that no relation exists. Switch the navigation method before you draw a conclusion. The same applies to rename: it reports the references it could not resolve. References the graph does not see, it does not rename.

Language tiers

More than 60 languages have parser support. The graph is fully tested per language, currently for Rust, Python, Java, JavaScript, TypeScript, Go, C++, C#, Kotlin, Swift and Ruby. The other procedural languages extract the same relations without this formal guarantee. Declarative formats such as JSON, YAML, TOML and Markdown have symbols and text search, but no call graph. See Languages.

Evidence and hints

Source and graph are the authoritative evidence. A local model writes the one-sentence hints: they are navigation aids, not source, and they are missing when inference or validation fails. Read the returned code before you rely on what a function does.

Freshness

greppy never knowingly returns stale source as evidence. When the index no longer matches the working tree, the query refreshes it. While a larger refresh is running, the query returns a temporary error that tells you when to try again.

  • search prints at most eight ranked hits. Neither --all nor --limit extends that.
  • While the embeddings are still building, search returns a progress line and no hits. greppy never returns partial semantic results.
$ greppy search "deserialize a map" --all
serde_core/src/de/value.rs:1617  MapAccessDeserializer::new — constructs a MapAccessDeserializer from the given map
serde/src/private/de.rs:1608  MapDeserializer::new — constructs a MapDeserializer from a vector of Content values
serde/src/private/de.rs:2554  MapRefDeserializer::new — constructs a MapRefDeserializer from a slice of Content values
serde_core/src/de/value.rs:1245  MapDeserializer::new — constructs a MapDeserializer from an iterator
serde_core/src/de/value.rs:1227  MapDeserializer
serde_core/src/de/value.rs:1611  MapAccessDeserializer
serde/src/private/de.rs:1656  MapDeserializer::deserialize_any — deserializes a value by visiting its map contents
serde/src/private/de.rs:1600  MapDeserializer

Scope of one index

The graph namespace is per repository. Several packages of a workspace under one root form one shared relation space. You query several repositories one after another with --root; there is no joint query across several roots. greppy stays local: there is no central team server.

Editing

  • patch changes existing files. It cannot create or delete files. You create a new file with greppy write PATH, as a separate transaction.
  • undo refuses when a later change touched the same span (exit 12).
  • --verify never downloads a checker. It uses what is installed locally and stops after a bounded timeout. It checks syntax and build; tests run as well when you set GREPPY_VERIFY_TEST_COMMAND. A failed test ends with exit 17.
$ greppy replace-text util.py "max(lo, min(x, hi))" "min(max(x, lo), hi)"
applied util.py:2  362417
│
│     1  def clamp(x, lo, hi):
│>    2      return min(max(x, lo), hi)
$ greppy replace-text util.py "min(max(x, lo), hi)" "min(max(x, lo), hi)  # clamp"
applied util.py:2  c49237
│
│     1  def clamp(x, lo, hi):
│>    2      return min(max(x, lo), hi)  # clamp
$ greppy undo 362417
util.py no longer looks the way that edit left it

Built-in agent (beta)

  • It needs a local, Anthropic-compatible gateway reachable over plain HTTP (GREPPY_ENDPOINT, default http://127.0.0.1:8317). greppy ships no chat model.
  • It does not support repositories with submodules yet. A repository in the middle of a merge, rebase or cherry-pick, with Git LFS or with checkout filters, stops before the model starts (exit 2).
  • The sandbox confines writes, not reads or network access.
  • Remote control and agent serve run over Unix sockets. Any process running as your user can drive a running session.

See Built-in agent.

Browser tool (beta)

  • The verbs can still change.
  • It ships for macOS and Linux.
  • A target that matches more than one node fails with exit 34, unless you pass --first, --last or --nth N.
  • SubtleCrypto is temporarily disabled in pages. crypto.getRandomValues and crypto.randomUUID remain available.
  • greppy web endpoint, a native Playwright connect endpoint, runs on its own release track.

See Browser tool.

Platforms

Release targets are macOS on Apple Silicon with Metal and Linux x86_64 with NVIDIA CUDA. Windows is not a release target for 0.4.1. macOS on Intel and Linux on ARM64 are not approved for release; the installer refuses them. Release builds do not compute on the CPU: --device cpu ends with exit 73. See Platforms.