smart code reading · for humans & agents

give grep, tree-sitter, and cat a shared brain.

tilth reads code the way an agent needs it. small files come back whole, large files as a structural outline, and every search leads to where a symbol is defined. it also answers what text search can’t, such as who calls a function. structural awareness in one call.

cargo install tilth  ·  npx tilth  ·  Rust, zero runtime deps  ·  MIT

src/auth.ts tilth 258 lines · 3.4k tok · [outline] [1-12] [24-42] [44-89] [91-258] [99-130] [132-180]
tilth · callers
$ tilth isTrustedProxy --callers --scope . # Callers of "isTrustedProxy" in ~/gin — 5 call sites ## context.go:1011 [caller: ClientIP] -> trusted = c.engine.isTrustedProxy(remoteIP) ## context_test.go:3094 [caller: TestRemoteIPFail] -> trust := c.engine.isTrustedProxy(ip) ... ## gin.go:496 [caller: validateHeader] -> if (i == 0) || (!engine.isTrustedProxy(ip)) {
the round-trip tax

an agent makes six calls to find one function.

glob → read → “too big” → grep → read again → read another file. six round-trips to answer a question the file’s own structure already knew.

without tilth

built-in tools answer in fragments

grep finds where a string appears. it cannot say where a symbol is defined. cat returns a whole file when you needed eight lines, or overflows the context window, so the agent greps and reads again. structure is rediscovered on every call.

with tilth

structure, resolved in one call

the outline says what is in the file. search says where things are defined, with each hit shown in its surrounding structure. --section returns exactly the lines you asked for. one call, already structured.

computed from the syntax tree

questions grep can’t answer.

grep finds where text appears. to learn who calls a function, an agent then has to read files and work it out, and that answer can be wrong without anyone noticing. tilth parses the code and computes the answer. everything below is real output from the FastAPI and Gin repositories, with ... where lines are cut.

01

where is it defined?

Search uses the syntax tree to tell a definition from a mention, and lists definitions first. Each match sits among the symbols around it, so you know where you are without opening the file.

tilth · search
$ tilth get_typed_signature --scope fastapi # Search: "get_typed_signature" in fastapi — 2 matches (1 definitions, 1 usages) ### dependencies/utils.py:218-232 [definition] [203-215] fn _get_signature def _get_signature(call: Callable[..., Any]) -> inspect.Signature -> [218-232] fn get_typed_signature def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature [235-241] fn get_typed_annotation ### dependencies/utils.py:277 [usage in function get_dependant] ...
02

what does it call?

An expanded definition ends with a -- calls -- footer. It lists the functions the definition calls, and the ones those call, each with file, line range and signature. An agent can follow the chain without searching again.

tilth · search --expand
$ tilth ServeHTTP --scope . --glob '!*_test.go' --expand=1 ... -- calls -- updateRouteTrees gin.go:517-521 func (engine *Engine) updateRouteTrees() -> updateRouteTree gin.go:504-514 func updateRouteTree(n *node) handleHTTPRequest gin.go:690-760 func (engine *Engine) handleHTTPRequest(c *Context) ... -> Next context.go:188-196 func (c *Context) Next() -> Set context.go:276-284 func (c *Context) Set(key any, value any) -> Header context.go:1079-1085 func (c *Context) Header(key, value string) ... reset context.go:103-118 func (c *Context) reset() Get context.go:288-293 func (c *Context) Get(key any) (value any, exists bool)
03

what depends on this file?

--deps shows what a file uses and what uses it, down to the function and the symbol. Read it before you rename or remove an export.

tilth · deps
$ tilth recovery.go --deps # Deps: recovery.go — 5 local, 0 external, 3 dependents ## Uses (local) context.go Abort, AbortWithStatus, Error, Next debug.go IsDebugging fs.go Open gin.go New routergroup.go handle ## Used by benchmarks_test.go:22 BenchmarkRecoveryMiddleware → Recovery benchmarks_test.go:36 BenchmarkManyHandlers → Recovery gin.go:239 Default → Recovery ...
04

what changed?

The diff is reported per function. It says what was added, what was removed, and whether a signature or only a body changed, so a review starts from the symbols that moved.

tilth · diff
$ tilth diff HEAD~1 # Diff: HEAD~1 — 10 files, 10 modified, 9 added (~447 tokens) ... ## binding/bson.go (3 symbols) [+] Name L16 (new, 3 lines) [+] Bind L20 (new, 7 lines) [+] BindBody L28 (new, 3 lines) ## context.go (3 symbols) [~] <const> L31 (body, 13→14 lines) [~] Negotiate L1357 (body, 30→34 lines) [+] BSON L1242 ...

tilth parses syntax and does not resolve types. Callers and dependencies are matched by name, so two functions with the same name in different modules look alike, and dynamic dispatch is out of reach.

outline · drill · search · follow

it shows you the shape first.

tree-sitter parses the file into an abstract syntax tree, so tilth knows the difference between a definition and a mention. what it returns is decided by token count. a 1-line minified bundle gets outlined, and a 120-line focused module prints whole.

step 01
outline

Read a file. Under ~6k tokens it comes back whole with line numbers; over, you get a structural outline with line ranges for every symbol.

step 02
drill

--section 44-89 returns exactly those lines. --section "## Install" returns exactly that markdown heading. No re-reading the whole file.

step 03
search

Definitions first, then usages, matched on the syntax tree. Each hit is shown inside its surrounding file structure, so you know what you found.

step 04
follow

An expanded definition carries a -- calls -- footer of resolved callees, with file and line. Follow the call chain without a fresh search for each one.

token-aware, by design

it decides what to show

inputbehaviour
empty / binary[empty] / [skipped] with mime
generated (lockfile, .min.js)[generated]
< ~6k tokensfull content, line-numbered
> ~6k tokensstructural outline with ranges
in MCP mode

it remembers what the agent saw

Definitions already expanded once come back as [shown earlier] on later searches, so the agent does not receive the same body twice. expand defaults to 2, so there is no flag to remember.

Install with --edit and tilth adds hash-anchored writing: each line carries a hash, and an edit is rejected if the file changed since you read it.

one binary, the whole loop

read, search, and navigate by structure.

The same engine behind the CLI is exposed as MCP tools, so an agent reaches for the right one without a wall of upfront instructions.

tilth <symbol> --scope

Definition-first search. Several symbols fit in one call, such as "ServeHTTP, Next", and each gets its own block. --expand inlines source for the top matches.

tilth <symbol> --callers

Every call site of a symbol, matched on the syntax tree, so comments and strings that mention the name are left out.

tilth <path> --deps

What a file uses and what depends on it, with the symbols each dependent uses. Read it before you rename an export.

tilth grok <symbol>

Everything about one symbol in a single call: signature, doc, callers, callees, siblings, tests. The whole neighbourhood at once.

tilth diff HEAD~1

A function-level diff that shows which symbols were added, changed, or had their signature altered. It can stand in for git diff when an agent reviews a change.

tilth "/regex/" --scope

Content and glob search on ripgrep’s own engine. When results are cut, the header says how many matches there are in total.

Rust · ~35,000 lines · no runtime dependencies

fast enough to sit in the loop.

tilth is one binary with nothing to install behind it. Definitions and usages are searched in parallel, reads are memory-mapped, and the codebase walk pre-filters files with SIMD so most are skipped quickly.

17 languages

AST-aware, via tree-sitter

Definitions, callees, callers and outlines, all from the syntax tree, across:

RustTypeScriptTSXJavaScriptPythonGoJavaScalaCC++RubyPHPC#SwiftKotlinElixirBash
measured on real repositories

milliseconds per call

operationginfastapi
file read3ms3ms
callers13ms64ms
symbol search20ms77ms
codebase map52ms184ms

Gin has 130 files and FastAPI has 2,700. Median of 15 runs on an Apple M5 Pro, process startup included. Search walks the whole tree, so time grows with the repository. MCP mode pays startup once.

built on the good parts

ripgrep’s engine, tree-sitter’s parser

Content search runs on ripgrep’s own grep-regex and grep-searcher. The ignore crate walks directories, memmap2 reads files, and DashMap holds the outline cache, which is invalidated by mtime.

one server, every agent host

it installs where your agent already lives.

One command writes tilth into a host’s MCP config. Pick your host below to see it.

tilth · install
$ tilth install claude-code # ~/.claude.json $ tilth install claude-code --edit # + hash-anchored editing

Smaller models sometimes prefer their built-in tools. Run with --disallowedTools "Bash,Grep,Glob" to make tilth the only way through. You can also call the CLI straight from bash, and the agent prompt lives in AGENTS.md.

a plain answer

is tilth for you?

tilth earns its place when something reads a lot of code and needs to get the structure right. That is most agents, and plenty of humans.

reach for tilth when · a fit

  • an AI agent navigates your code, and you want its answers computed from the syntax tree
  • the repo is large, unfamiliar, or spans several languages
  • you need definitions and call chains, and grep only gives you string matches
  • your agent’s built-in search is weak or missing

less to gain when · maybe not

  • it’s one small script you already hold in your head
  • only you read the code, and only occasionally
  • your language isn’t among the 17 tree-sitter grammars
  • you need type-aware answers, since tilth matches callers by name and does not resolve types
start

install it, point an agent at it.

install

cargo install tilth

also: npx tilth  ·  prebuilt binaries on the releases page

read, search, navigate

# outline a file (whole, if it's small) tilth src/auth.ts # exactly the lines you need tilth src/auth.ts --section 44-89 # definitions first, with source inlined tilth handleAuth --scope src/ --expand

where tilth is

version
0.10.1
languages
17 tree-sitter
runtime deps
0 one binary

tilth is the state of soil that’s ready for planting. Your codebase is the soil; tilth gives it structure, so you can find where to dig. Follow along on GitHub →