Fast, AST-based code-navigation toolkit for source files — surface the shape of a file (signatures with line numbers, no method bodies), the true public API of a package, the dependency graph between files, the call graph between symbols, search the repo by symbol or behaviour, the blast radius of touching a symbol, a token-budgeted context pack for any symbol, and squeeze repetitive logs into a smaller, reversible form. Nineteen analysis subcommands, one binary, built for LLM coding agents and humans who’d rather not waste tokens reading every file just to understand a codebase.
ast-bro is written in Rust and uses ast-grep’s incredibly fast tree-sitter bindings. Thanks to rayon, it parses your entire workspace concurrently—often in milliseconds. For Google- or ByteDance-scale monorepos, ast-bro benefits from the additional abstraction layer provided by repolayer.
Renamed from
ast-outline(v2.1.x and earlier). The project outgrew "outline" — it now ships dep graphs, call graphs, hybrid semantic search, true public API resolution, and a structural search/rewrite engine, not just structural outlining. The old name also collided with an unrelated VS Code extension and an npm package — too generic to keep.Upgrading from
ast-outline? Run anyast-brocommand once and it will auto-migrate.ast-outline/→.ast-bro/(cache),.ast-outline-ignore→.ast-bro-ignore(per-repo filter),~/.cache/ast-outline/→~/.cache/ast-bro/(model cache), and anyast-outlineentries in your MCP config →ast-bro. The legacyast-outlinebinary is still installed as a thin proxy that execs intoast-bro, and a shortersbalias ships alongside, so existing scripts keep working.
ast-bro exists to make LLM coding agents faster, cheaper, and smarter when navigating unfamiliar code.
Modern agentic coding tools explore codebases by reading files directly. That's reliable but has a massive cost: on a 1000-line file, the agent pays for 1000 lines of tokens just to answer "what methods exist here?" — and reading is only one of several questions an agent has. "Who imports this?" "What's the public API?" "Are there cycles?" "Where in the repo is the login flow?" — each one historically required dozens of file reads or noisy greps.
ast-bro collapses each of those questions into a single command:
map / digest / show give you signatures and line ranges instead of method bodies — typically a 95% token saving vs reading the file. implements finds subclasses with AST accuracy, no grep false positives.surface resolves pub use re-exports (Rust), __all__ (Python), barrel files (TypeScript), export clauses (Scala) so you see the surface a downstream user actually sees — not the union of every public item per file.deps / reverse-deps / cycles / graph build a file-level import graph (Rust, Python, TS/JS, Java, C#, Kotlin, Scala, Go) cached at .ast-bro/graph/. Use reverse-deps before refactoring to know the blast radius. cycles exits non-zero — wire it into a CI gate. graph emits the full dependency graph (text by default, --json for JSON).callers / callees answer "who calls X" and "what does X call" with AST accuracy across all 14 languages — no grep false positives on overloaded names, comments, or string literals. Both are kind-aware: ask for a function and you get call-sites; ask for a type and you get implementors / constructions / ancestors. A three-pass resolver (same-file → global symbol table → dep-graph disambiguation) tags every edge Exact / Inferred / Ambiguous so you can filter by precision. trace <FROM> <TO> walks the shortest static call path between two symbols, inlining each hop's body — "how does X reach Y?" answered in one call instead of chaining callees. Same on-disk cache as the dep graph.search runs BM25 + dense embeddings via potion-code-16M (a static, no-inference model — ~64 MB, runs on CPU in microseconds). find-related returns chunks structurally similar to one you already have, with a dep-graph-aware boost when a graph cache exists.impact <symbol> combines callers, callees, file-level deps, file-level reverse-deps, transitive callers at --depth N, and test-file detection into one "what would break?" report — replaces a chain of four round-trips with a single call. Four --mode variants: all (default), deps, dependents, tests. --tests / --exclude-tests narrow the filter. Works for both callables and types.context <symbol> --budget N packs "everything an LLM needs to understand this symbol" into a caller-supplied token budget: target body first, then direct callees (bodies while budget permits, signatures otherwise), direct callers (signatures), transitive callees/callers at depth 2 (signatures only). For types: type body, implementors, methods, callers-of-methods. Flags truncated when budget ran short and target_omitted when even the target body didn't fit. Same data as four or five show/callers/callees calls, one round-trip, budget-bounded.squeeze compresses a repetitive log/text file into a smaller, reversible form (a legend plus short tags) so a noisy log costs far fewer tokens to hand to an agent — and falls back to the raw text when squeezing wouldn't help. This is for logs/text, not code (for code, map / digest / show are the token win).ast-bro install --mcp <agent> wires it into Claude Code, Cursor, Gemini, Codex, or VS Code Copilot in one line.Before ast-bro:
Agent: Read Player.cs # 1200 lines of tokens
Agent: Read Enemy.cs # 800 lines of tokens
Agent: Read DamageSystem.cs # 400 lines of tokens
Agent: grep "IDamageable" src/ # noisy, lots of false matches
...
With ast-bro:
Agent: ast-bro surface . # one-page true public API of the crate/package
Agent: ast-bro digest src/Combat # ~100 lines, whole module's structure
Agent: ast-bro implements IDamageable # precise list, no grep noise
Agent: ast-bro search "damage handling" # hybrid BM25 + dense semantic, ranked
Agent: ast-bro show Player.cs TakeDamage # just the method body
Agent: ast-bro reverse-deps Player.cs # who imports this — blast radius before refactor
Agent: ast-bro callers Player.TakeDamage # AST-accurate call sites — no grep false positives
Agent: ast-bro callees Player.TakeDamage # what TakeDamage itself calls
Agent: ast-bro impact Player.TakeDamage # callers + callees + file deps + tests, one call
Agent: ast-bro context Player.TakeDamage --budget 2000 # everything an LLM needs, token-bounded
Agent: ast-bro cycles src/ # find import cycles via Tarjan SCC
Result: same understanding, a fraction of the tokens, a fraction of the round-trips.
For "what does this package actually expose?" — historically the most expensive question, since the answer was "read every file" — surface resolves the re-export graph and gives you the answer directly, often replacing dozens of file reads with a single call. For "what would break if I change this method?" — callers gives you the AST-accurate set of call sites in one shot, instead of grep-ing a homonym across the repo.
| Language | Extensions |
|---|---|
| Rust | .rs |
| C# | .cs |
| C++ | .cpp, .cc, .cxx, .hpp, .hh |
| Python | .py, .pyi |
| TypeScript | .ts, .tsx |
| JavaScript | .js, .jsx, .mjs, .cjs |
| Java | .java |
| Kotlin | .kt, .kts |
| Scala | .scala, .sc |
| Go | .go |
| PHP | .php |
| Ruby | .rb |
| SQL | .sql, .ddl, .dml |
| Markdown | .md, .markdown, .mdx, .mdown |
More coming soon! Adding another language is a single new adapter file leveraging the massive ast-grep language ecosystem.
ast-bro skips a lot of files when walking a directory — by design. Filters apply uniformly across every subcommand.
.gitignore and friends — every level's .gitignore, your global gitignore, .git/info/exclude, and .ignore files (the ignore crate's convention used by ripgrep/fd)..gitignore doesn't list them: .git, node_modules, target, dist, build, __pycache__, .venv, venv, .cache, .idea, .vscode, .next, .nuxt, .turbo, .parcel-cache, .gradle, .tox, .mypy_cache, .pytest_cache, .ruff_cache, .eggs, .ast-bro, and a few others..ast-bro-ignore — per-repo escape hatch. Same syntax as .gitignore. Useful for excluding paths from ast-bro that you don't want excluded from git itself, e.g. test fixtures or vendored corpora:gitignore
# .ast-bro-ignore
tests/fixtures/large_corpus/
benches/data/
*.generated.rs
4. Extension allowlist — files are only opened if their extension is one ast-bro knows how to parse (the table above for map/digest/show/implements; a broader set for the search commands). Explicitly-passed extensionless files fall back to shebang detection (#!/usr/bin/env python3 → Python, #!/usr/bin/ruby → Ruby, #!/usr/bin/env node → TypeScript, etc.) — so CLI scripts like ~/.local/bin/my-script or bin/deploy work without an extension. Directory walks do not open extensionless files; the shebang is only consulted for explicit inputs to keep the walk fast.
Want to see exactly what ast-bro walks? Compare ast-bro digest some/dir with rg --files some/dir — anything in rg but not the digest is being filtered by one of the layers above.
brew install aeroxy/tap/ast-bro
npm install -g @ast-bro/cli
pip install ast-bro
cargo install ast-bro
This installs the ast-bro CLI globally into ~/.cargo/bin — make sure that's on your PATH.
You can run ast-bro directly with Nix without installing:
nix run github:aeroxy/ast-bro
Or add it as a dependency in your Nix flake:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
ast-bro.url = "github:aeroxy/ast-bro";
};
outputs = { self, nixpkgs, ast-bro }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
buildInputs = [ ast-bro.packages.${system}.default ];
};
};
}
```bash
ast-bro map path/to/Player.rs ast-bro map path/to/user_service.py
ast-bro map src/
ast-bro show Player.cs TakeDamage
ast-bro digest src/Services
pub use / __all__, not every pub item)ast-bro surface . # auto-detect Cargo.toml / pyproject.toml / init.py ast-bro surface --tree --include-chain mycrate/
ast-bro implements IDamageable src/
ast-bro deps src/auth.rs --depth 2 # what auth.rs imports (transitively) ast-bro reverse-deps src/auth.rs # who imports auth.rs (refactor blast radius) ast-bro cycles # find import cycles via Tarjan SCC ast-bro graph . # full dependency graph (text) ast-bro graph . --json # same, as JSON (ast-bro.graph.v1)
ast-bro callers TakeDamage # function/method: in-edges ast-bro callers --tests TakeDamage # same, only test files ast-bro callers --hide-ambiguous TakeDamage # drop ambiguous call edges ast-bro callers Player # type: implementors + constructions ast-bro callees Player.TakeDamage # function/method: out-edges ast-bro callees --hide-external Player.TakeDamage # drop unresolved + external callees ast-bro callees Player --depth 2 # type: ancestor walk (transitive) ast-bro callers src/Player.cs:TakeDamage ast-bro trace handle_request render # shortest static call path, each hop inlined
$ claude mcp add ast-bro \
-- python -m otcore.mcp_server <graph>