Fast, language-agnostic code indexer and symbol navigator built on tree-sitter.
cymbal parses your codebase into a local SQLite index, then gives you instant symbol search, cross-references, impact analysis, and scoped diffs — all from the command line. Designed to be called by AI agents, editor plugins, or directly from your terminal.
Homebrew (macOS / Linux):
brew install 1broseidon/tap/cymbalWindows (PowerShell):
irm https://raw.githubusercontent.com/1broseidon/cymbal/main/install.ps1 | iexTo uninstall (keeps index data by default):
# Remove binary and PATH entry, keep SQLite indexes
irm https://raw.githubusercontent.com/1broseidon/cymbal/main/uninstall.ps1 | iex
# Also remove all SQLite indexes
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/1broseidon/cymbal/main/uninstall.ps1))) -PurgeNote:
-Purgeremoves all per-repo SQLite indexes stored under%LOCALAPPDATA%\cymbal\repos\. Omit it to keep your indexes intact in case you reinstall.
Go (requires CGO for tree-sitter + SQLite):
CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" go install github.com/1broseidon/cymbal@latestOr grab a binary from releases.
No local Go toolchain or CGO setup needed — run cymbal from a pre-built container (linux/amd64 and arm64):
docker pull ghcr.io/1broseidon/cymbal:latestMount any repo and the SQLite index lands at /workspace/.cymbal/index.db inside the container by default (via CYMBAL_DB):
# Index a repo
docker run --rm -v /path/to/your/repo:/workspace ghcr.io/1broseidon/cymbal index .
# Query it (index persists at /path/to/your/repo/.cymbal/index.db)
docker run --rm -v /path/to/your/repo:/workspace ghcr.io/1broseidon/cymbal investigate handleAuth
# Override the DB location if needed
docker run --rm -v /path/to/your/repo:/workspace -e CYMBAL_DB=/some/other/path.db ghcr.io/1broseidon/cymbal index .Pin a specific version if needed:
docker pull ghcr.io/1broseidon/cymbal:v0.8.4Or build the image yourself:
docker build -t cymbal .
# or with docker compose (mounts the current directory by default):
docker compose run --rm cymbal index .Add .cymbal/ to your .gitignore to keep the index out of version control.
Define a shell alias once so every command looks like the native binary:
alias cymbal='docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal'Then:
# Index the current project
cymbal index .
# Investigate any symbol — one call, right answer
cymbal investigate handleAuth # function → source + callers + impact
cymbal investigate UserModel # type → definition + members + references
cymbal trace handleAuth # downward call chain — what does it call?
# Or use specific commands when you need control
cymbal search handleAuth # find a symbol
cymbal search "TODO" --text # full-text grep
cymbal show handleAuth # read source
cymbal outline internal/auth/handler.go # file structure
cymbal refs handleAuth # who calls this?
cymbal importers internal/auth # who imports this package?
cymbal impact handleAuth # what breaks if I change this?
cymbal diff handleAuth main # git diff scoped to a function
cymbal context handleAuth # bundled: source + types + callers + imports
cymbal ls # file treeThe index auto-builds on first use — no manual cymbal index . required. Subsequent queries auto-refresh incrementally (~2ms when nothing changed).
| Command | What it does |
|---|---|
investigate |
Start here. Kind-adaptive exploration — one call, right shape |
structure |
Structural overview — entry points, hotspots, central packages |
trace |
Downward call graph — what does this symbol call? |
index |
Parse and index a directory |
ls |
File tree, repo list, or --stats overview |
search |
Symbol search (or --text for grep) |
show |
Display a symbol's source code |
outline |
List all symbols in a file |
refs |
Find references / call sites |
importers |
Reverse import lookup — who imports this? |
impact |
Transitive callers — what's affected by a change? |
diff |
Git diff scoped to a symbol's line range |
context |
Bundled view: source + types + callers + imports |
Commands that accept symbols support batch: cymbal investigate Foo Bar Baz runs all three in one invocation.
All commands support --json for structured output.
cymbal is designed as the code navigation layer for AI agents. One command handles most investigations — specific commands exist as escape hatches when you need more control.
Add this to your agent's system prompt (e.g., CLAUDE.md, agent.md, or MCP tool descriptions).
Native install:
## Code Exploration Policy
Use `cymbal` CLI for code navigation — prefer it over Read, Grep, Glob, or Bash for code exploration.
- **New to a repo?**: `cymbal structure` — entry points, hotspots, central packages. Start here.
- **To understand a symbol**: `cymbal investigate <symbol>` — returns source, callers, impact, or members based on what the symbol is.
- **To understand multiple symbols**: `cymbal investigate Foo Bar Baz` — batch mode, one invocation.
- **To trace an execution path**: `cymbal trace <symbol>` — follows the call graph downward (what does X call, what do those call).
- **To assess change risk**: `cymbal impact <symbol>` — follows the call graph upward (what breaks if X changes).
- Before reading a file: `cymbal outline <file>` or `cymbal show <file:L1-L2>`
- Before searching: `cymbal search <query>` (symbols) or `cymbal search <query> --text` (grep)
- Before exploring structure: `cymbal ls` (tree) or `cymbal ls --stats` (overview)
- To disambiguate: `cymbal show path/to/file.go:SymbolName` or `cymbal investigate file.go:Symbol`
- The index auto-builds on first use — no manual indexing step needed. Queries auto-refresh incrementally.
- All commands support `--json` for structured output.Docker (no local install required):
## Code Exploration Policy
Use `cymbal` via Docker for code navigation — prefer it over Read, Grep, Glob, or Bash for code exploration.
Run all cymbal commands as: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal <command>`
- **New to a repo?**: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal structure` — entry points, hotspots, central packages. Start here.
- **To understand a symbol**: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal investigate <symbol>` — returns source, callers, impact, or members based on what the symbol is.
- **To understand multiple symbols**: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal investigate Foo Bar Baz` — batch mode, one invocation.
- **To trace an execution path**: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal trace <symbol>` — follows the call graph downward (what does X call, what do those call).
- **To assess change risk**: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal impact <symbol>` — follows the call graph upward (what breaks if X changes).
- Before reading a file: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal outline <file>` or `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal show <file:L1-L2>`
- Before searching: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal search <query>` (symbols) or add `--text` for grep
- Before exploring structure: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal ls` or `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal ls --stats`
- To disambiguate: `docker run --rm -v "$(pwd)":/workspace ghcr.io/1broseidon/cymbal investigate path/to/file.go:Symbol`
- The index auto-builds on first use — no manual indexing step needed. Queries auto-refresh incrementally.
- The SQLite index is stored at `.cymbal/index.db` inside the mounted repo (via `CYMBAL_DB`).
- All commands support `--json` for structured output.An agent tracing an auth flow typically makes 15-20 sequential tool calls: show function → read the code → guess the next function → show that → repeat. Each call costs a reasoning step (~500 tokens). Three commands eliminate this:
| Command | Question it answers | Direction |
|---|---|---|
investigate X |
"Tell me about X" | Adaptive (source + callers + impact or members) |
trace X |
"What does X depend on?" | Downward (callees, depth 3) |
impact X |
"What depends on X?" | Upward (callers, depth 2) |
investigate replaces search → show → refs. trace replaces 10+ sequential show calls to follow a call chain. Together they reduce a 22-call investigation to 4 calls.
cymbal uses tree-sitter grammars. Currently supported:
Go, Python, JavaScript, TypeScript, TSX, Rust, C, C++, C#, Java, Ruby, Swift, Kotlin, Scala, PHP, Lua, Bash, YAML, Elixir, HCL/Terraform, Protobuf, Dart
Adding a language requires a tree-sitter grammar and a symbol extraction query — see internal/parser/ for examples.
-
Index — tree-sitter parses each file into an AST. cymbal extracts symbols (functions, types, variables, imports) and references (calls, type usage) and stores them in SQLite with FTS5 full-text search. Each repo gets its own database under the OS cache directory (
~/.cache/cymbal/repos/<hash>/index.dbon Linux,~/Library/Caches/cymbal/repos/on macOS,%LOCALAPPDATA%\cymbal\repos\on Windows). Override with--db <path>or theCYMBAL_DBenvironment variable. The index auto-builds on first query — no manualcymbal index .required. -
Query — all commands read from the current repo's SQLite index. Symbol lookups, cross-references, and import graphs are SQL queries. No re-parsing needed. No cross-repo bleed.
-
Always fresh — every query automatically checks for changed files and reindexes them before returning results. No manual reindexing, no watch daemons, no hooks. Edit a file, run a query, get the right answer. The mtime+size fast path adds ~10-24ms when nothing changed; only dirty files are re-parsed.
Measured against ripgrep on three real-world repos (gin, kubectl, fastapi) across Go and Python. Full harness in bench/.
go run ./bench setup # clone pinned corpus repos
go run ./bench run # run all benchmarks → bench/RESULTS.mdSpeed — cymbal queries complete in 9-27ms. Reindex with nothing changed: 8-20ms.
Accuracy — 100% automated ground-truth verification across 37 checks (search returns correct file+kind, show returns correct source, refs finds known callers, investigate includes expected signature).
Token efficiency — for targeted lookups, cymbal uses 17-100% fewer tokens than ripgrep. Refs queries show the biggest wins (3-100% savings) because cymbal returns semantic call sites, not every line mentioning the string.
JIT freshness — queries auto-detect and reparse changed files. Overhead: ~10-23ms when nothing changed, ~22-27ms after touching 1 file, ~33-43ms after touching 5 files. Deleted files are automatically pruned.
Agent workflow — cymbal investigate replaces 3 separate ripgrep calls (search + show + refs) with 1 call. Typical savings: 41-100% fewer tokens for focused symbols.
CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" go get github.com/1broseidon/cymbal@latestFour packages are exported:
| Package | What it does |
|---|---|
index |
Indexing engine, SQLite store, and all query APIs |
parser |
Tree-sitter parsing for 22 languages |
symbols |
Core data types (Symbol, Import, Ref) |
walker |
Concurrent file discovery with language detection |
import (
"fmt"
"github.com/1broseidon/cymbal/index"
)
// Index a repo
stats, _ := index.Index("/path/to/repo", "", index.Options{})
fmt.Printf("%d files, %d symbols\n", stats.FilesIndexed, stats.SymbolsFound)
// Query — all functions take a dbPath and return typed results
dbPath, _ := index.RepoDBPath("/path/to/repo")
results, _ := index.SearchSymbols(dbPath, index.SearchQuery{Text: "handleAuth"})
inv, _ := index.Investigate(dbPath, "handleAuth")
trace, _ := index.FindTrace(dbPath, "handleAuth", 3, 50)
impact, _ := index.FindImpact(dbPath, "handleAuth", 2, 100)
refs, _ := index.FindReferences(dbPath, "handleAuth", 50)For the full API reference, streaming patterns, and lower-level store access, see the library guide.