MCPcopy Index your code
hub / github.com/aeroxy/ast-bro

github.com/aeroxy/ast-bro @3.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 3.0.0 ↗ · + Follow
1,961 symbols 5,689 edges 177 files 258 documented · 13%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

ast-bro

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.

crates.io npm PyPI License: MIT Ask DeepWiki

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 any ast-bro command 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 any ast-outline entries in your MCP config → ast-bro. The legacy ast-outline binary is still installed as a thin proxy that execs into ast-bro, and a shorter sb alias ships alongside, so existing scripts keep working.


Purpose

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:

  1. Shape over bytes. 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.
  2. Published API in one call. 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.
  3. Dependency graph for free. 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).
  4. Symbol-level call graph. 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.
  5. Hybrid semantic search. 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.
  6. Blast radius in one shot. 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.
  7. Token-budgeted context. 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.
  8. Squeeze logs, not just code. 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).
  9. Nineteen native MCP tools. Every analysis command is also exposed as an MCP tool — ast-bro install --mcp <agent> wires it into Claude Code, Cursor, Gemini, Codex, or VS Code Copilot in one line.

The workflow

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.


Supported languages

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.


What gets walked

ast-bro skips a lot of files when walking a directory — by design. Filters apply uniformly across every subcommand.

  1. .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).
  2. Hardcoded denylist — directories almost no one wants walked, even if .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.
  3. .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.


Install

Homebrew (macOS)

brew install aeroxy/tap/ast-bro

npm

npm install -g @ast-bro/cli

pip

pip install ast-bro

Cargo

cargo install ast-bro

This installs the ast-bro CLI globally into ~/.cargo/bin — make sure that's on your PATH.

Nix

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 ];
      };
    };
}

Quick start

```bash

Map the structure of one file

ast-bro map path/to/Player.rs ast-bro map path/to/user_service.py

Map a whole directory (recurses supported extensions in parallel)

ast-bro map src/

Print the exact source of one specific method

ast-bro show Player.cs TakeDamage

Compact public-API map of a whole module

ast-bro digest src/Services

True public surface (resolves 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/

Every class that inherits/implements a given type

ast-bro implements IDamageable src/

Dependency graph: forward, reverse, cycles, full

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)

Call graph: who calls X, what does X call (AST-accurate, all 14 langs)

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

Bl

Extension points exported contracts — how you extend this code

LanguageAdapter (Interface)
(no doc) [11 implementers]
src/adapters/base.rs
Installer (Interface)
(no doc) [7 implementers]
src/installers/mod.rs
Greeter (Interface)
---- (1) impl regrouping: `impl Trait for Foo` lifts Trait into Foo.bases. [1 implementers]
tests/fixtures/rust_adapter/sample.rs
BpeStrategy (Interface)
(no doc) [2 implementers]
src/squeeze/mod.rs
Storage (Interface)
---- (5) trait associated types and consts.
tests/fixtures/rust_adapter/sample.rs
Spec (Interface)
(no doc)
tests/fixtures/surface/ts_barrel/src/types.ts
Payable (Interface)
(no doc)
tests/fixtures/php_adapter/sample.php

Core symbols most depended-on inside this repo

is_empty
called by 314
src/search/cache.rs
write
called by 135
tests/calls_e2e.rs
collapse_ws
called by 133
src/adapters/base.rs
as_str
called by 93
src/calls/graph.rs
find
called by 93
tests/fixtures/ruby_adapter/sample.rb
as_str
called by 68
src/core.rs
ok
called by 56
src/mcp/protocol.rs
write
called by 56
src/graph_cache/cache.rs

Shape

Function 1,456
Method 253
Class 211
Enum 34
Interface 7

Languages

Rust96%
TypeScript1%
Ruby1%
Python1%
PHP1%
C++1%

Modules by API surface

src/search/index.rs60 symbols
src/core.rs58 symbols
tests/calls_e2e.rs53 symbols
src/installers/claude_code.rs48 symbols
src/mcp/tools.rs46 symbols
src/adapters/kotlin.rs42 symbols
src/search/ranking.rs41 symbols
src/adapters/scala.rs41 symbols
src/deps/extract.rs36 symbols
src/adapters/typescript.rs35 symbols
src/squeeze/mod.rs34 symbols
src/surface/imports.rs33 symbols

For agents

$ claude mcp add ast-bro \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact