Structural, AST-aware code navigation CLI for large, multi-language repositories. It builds a local SQLite index of symbols, references, imports, modules, dependencies, and inheritance so humans and agents can move through code by exact structure instead of grep-style text matches.
- Navigate classes, functions, files, imports, usages, callers, implementations, inheritance, modules, and dependency paths.
- Start broad with
explore, then jump to exact definitions withsymbol,class,outline, andrefs. - Keep the index current with
ast-index updateafter the firstrebuild. - Give coding agents compact, parseable context instead of raw file dumps.
- Save ~40-50% of agent tokens on large repositories by returning structural slices of code instead of whole files.
Languages: Kotlin, Java, Swift, Objective-C, TypeScript, JavaScript, Vue, Svelte, CSS, SCSS, Less, Rust, Zig, C#, Python, Go, C, C++, Scala, PHP, Ruby, Perl, Dart, Protocol Buffers, WSDL, XSD, BSL (1C:Enterprise), Lua, Bash, Elixir, SQL, R, Matlab, Groovy, Common Lisp, GDScript. Project type is auto-detected.
# Install
brew tap defendend/ast-index
brew install ast-index
# Build an index once per project
cd /path/to/project
ast-index rebuild
# Ask code questions
ast-index explore "payment flow"
ast-index search ViewModel
ast-index class BaseFragment
ast-index usages Repository
ast-index implementations Presenter
ast-index deps appUse ast-index update after edits or branch switches. Hooks can queue a
trailing-debounced refresh without losing edits that arrive during an update:
ast-index update --background --debounce-ms 500Index-reading commands wait (with a bounded timeout) for an already queued
generation, so they do not observe stale results. In monorepos with nested
project markers, add --walk-up or AST_INDEX_WALK_UP=1 to reuse the root
index.
Guides: User guide for everyday workflow; command setup guide for install/options/examples; CodeGraph comparison for a dated, source-backed feature comparison.
Benchmarks on large Android project (~29k files, ~300k symbols):
| Command | ast-index | grep | Speedup |
|---|---|---|---|
| imports | 0.3ms | 90ms | 260x |
| dependents | 2ms | 100ms | 100x |
| deps | 3ms | 90ms | 90x |
| class | 1ms | 90ms | 90x |
| search | 11ms | 280ms | 14x |
| usages | 8ms | 90ms | 12x |
brew tap defendend/ast-index
brew install ast-indexRequires a Rust toolchain.
cargo install ast-index --lockedTo build the unreleased default branch instead:
cargo install --locked --git https://github.com/defendend/Claude-ast-index-search ast-indexBoth build from source. For prebuilt release binaries, use Homebrew, npm, or Winget.
winget install --id defendend.ast-indexIf you have the old kotlin-index installed:
brew uninstall kotlin-index
brew untap defendend/kotlin-index
brew tap defendend/ast-index
brew install ast-indexgit clone https://github.com/defendend/Claude-ast-index-search.git
cd Claude-ast-index-search
cargo build --release
# Binary: target/release/ast-index (~44 MB)If brew install ast-index fails with merge conflict errors (<<<<<<< HEAD), reset your local tap:
cd /opt/homebrew/Library/Taps/defendend/homebrew-ast-index
git fetch origin
git reset --hard origin/main
brew install ast-indexIf your repo has subdirectories with their own VCS markers (git submodules,
subtrees, nested Cargo.toml / settings.gradle), read-commands normally
stop at the nearest marker — they won't reuse a parent-level index even
if one exists. Pass --walk-up, or set AST_INDEX_WALK_UP=1, to tell
the lookup to prefer any existing parent DB over nested markers:
# once, in the root
cd /monorepo && ast-index rebuild
# later, from any subproject — reuse the root index
AST_INDEX_WALK_UP=1 ast-index search ViewModel
# or per-call:
ast-index --walk-up search ViewModelThis is opt-in by design: silently preferring a far-away parent DB could
surface a stale or misconfigured index from an earlier accidental
rebuild higher up. With the flag you explicitly say "trust the parent".
Independent git worktrees get independent indexes because their canonical root paths differ. Rebuild once inside each worktree; do not attach worktrees to one another.
For source trees that intentionally form one workspace, create the primary index first, attach a named subtree, then index the attached files:
cd /path/to/application
ast-index rebuild
ast-index subtree add shared ../shared-library
ast-index update # or: ast-index rebuild
ast-index subtree listUse --subtree shared to query only that attachment and --local to query
only the primary project. The legacy root commands remain compatibility
aliases; new automation should use subtree add/remove/list.
# Option 1: via marketplace
claude plugin marketplace add defendend/Claude-ast-index-search
claude plugin install ast-index
# Option 2: if ast-index is already installed
ast-index install-claude-pluginRestart Claude Code to activate.
Update: brew upgrade ast-index && claude plugin update ast-index.
Uninstall: claude plugin uninstall ast-index.
The Claude plugin ships /initialize as the default setup command. It
auto-detects project stack(s), including KMP and polyglot repos, then writes
.claude/settings.json and .claude/rules/ast-index.md. Use
/initialize-android, /initialize-ios, /initialize-web, /initialize-rust,
/initialize-csharp, or /initialize-ruby only as manual overrides.
See examples/.claude/rules/ast-index.md
for a template rules file that teaches the agent to use ast-index for
structural navigation, outline before reading large files, and pass the same
instructions to subagents. Adapt before dropping into your project's
.claude/rules/.
Codex can use the shared ast-index skill directly. For local development,
symlink or copy the skill directory into Codex's global skills directory:
mkdir -p ~/.codex/skills
ln -s /absolute/path/to/Claude-ast-index-search/plugin/skills/ast-index ~/.codex/skills/ast-indexThis repository also includes a Codex plugin manifest at
plugin/.codex-plugin/plugin.json and a
repo marketplace at .agents/plugins/marketplace.json
for Codex builds that support plugin marketplaces.
If your Codex build supports plugin marketplaces, restart Codex in this repo
and install ast-index from the repo marketplace. For a remote marketplace,
add the repository:
codex plugin marketplace add defendend/Claude-ast-index-searchThe Codex package exposes the same ast-index skill. Command-style project
setup is kept out of the Codex manifest because Codex uses skills and local
project configuration as first-class components.
Cursor can use the shared skill directly:
mkdir -p ~/.cursor/skills
ln -s /absolute/path/to/Claude-ast-index-search/plugin/skills/ast-index ~/.cursor/skills/ast-indexThis repository also includes a Cursor plugin manifest at
plugin/.cursor-plugin/plugin.json and a
multi-plugin marketplace at .cursor-plugin/marketplace.json.
For local Cursor testing:
mkdir -p ~/.cursor/plugins/local
ln -s /absolute/path/to/Claude-ast-index-search/plugin ~/.cursor/plugins/local/ast-indexReload Cursor after creating the symlink. The Cursor plugin package exposes the
shared ast-index skill, a project rule in plugin/rules/, and a Cursor-specific
initialize-ast-index command that writes .cursor/rules/ast-index.mdc.
gemini skills install https://github.com/defendend/Claude-ast-index-search.git --path plugin/skills/ast-indexRun ast-index rebuild once per project, then use ast-index update to keep
the index fresh.
ast-index explore <QUERY...> # One-shot context: ranked source + neighbours + tests (--rwr for graph)
ast-index search <QUERY> # Universal structural search
ast-index file <PATTERN> # Find files
ast-index symbol <NAME> # Find symbols
ast-index class <NAME> # Find classes/interfaces
ast-index outline <FILE> # Symbols in file
ast-index imports <FILE> # Imports in file
ast-index refs <SYMBOL> # Definitions + imports + usages
ast-index usages <SYMBOL> # Symbol usages
ast-index callers <FUNCTION> # Function call sites
ast-index implementations <PARENT> # Find implementations
ast-index hierarchy <CLASS> # Class hierarchy tree
ast-index changed [--base BRANCH] # Branch-level changed files (A/M/D/R)
ast-index todo [PATTERN] # TODO/FIXME/HACK comments
ast-index deprecated [QUERY] # Deprecated itemsLimited search commands now report completeness explicitly. symbol, class,
implementations, usages, and callers use this shape:
{
"schema_version": 2,
"items": [],
"pagination": {
"total": 0,
"returned": 0,
"truncated": false,
"limit": 50
}
}search and refs keep their named result arrays and provide one pagination
object per array under pagination. Consumers migrating from bare arrays must
read items for single-result-set commands and must check truncated before
treating a response as complete. This is limit-based pagination, not a cursor:
rerun with a larger --limit when more results are required.
The cache-independent changed command retains its separate JSON schema v1;
its schema version did not change with this pagination migration.
changed asks the detected version-control repository for the files changed from
merge-base(base, HEAD) to HEAD. Without --base, Git resolves
origin/HEAD, then tries origin/main, origin/master, main, master, and
trunk; other supported backends select their conventional mainline. It reads
version-control state directly, so it works without an
ast-index database and does not require rebuild or update. Results are
scoped to the current working directory, while paths remain
repository-relative. Staged and unstaged working-tree edits are not included.
# Compact text summary
ast-index changed
# Stable schema v1; the VCS timeout defaults to 30000 ms
ast-index --format json changed --base origin/main --timeout-ms 30000
# Print the detected root, scope, exact VCS argv, and timing to stderr
ast-index changed --verboseText output uses A (added), M (modified), D (deleted), and R (renamed):
Changed files against origin/main (3):
M README.md
R docs/old-guide.md -> docs/setup-guide.md
M docs/generated\nname.md
Control characters and backslashes in text paths are escaped, so every change stays on one output line. Use JSON instead of parsing this human-readable summary in scripts.
JSON output preserves rename metadata:
{
"schema_version": 1,
"vcs": "git",
"base": "origin/main",
"head": "HEAD",
"scope": null,
"changes": [
{ "status": "M", "path": "README.md" },
{ "status": "R", "path": "docs/setup-guide.md", "old_path": "docs/old-guide.md" }
]
}At the repository root, scope is null; from a nested working directory it
is that repository-relative directory path.
This is a fast file summary for branch review, not a changed-symbol report and not a replacement for your version-control system's diff command when patch hunks are needed.
ast-index module <PATTERN> # Find modules
ast-index deps <MODULE> # Module dependencies
ast-index dependents <MODULE> # Dependent modules
ast-index unused-deps <MODULE> # Find unused dependencies (v3.2: +transitive, XML, resources)
ast-index api <MODULE> # Public API of moduleShow how module A reaches module B through the dependency graph:
# Shortest path (default)
ast-index module-route --from core.utils --to features.payments.api
# All simple paths, filtered to api edges only
ast-index module-route --from app --to core.db --all --via-kind api
# JSON output — machine-readable, no ANSI
ast-index module-route --from app --to core.db --format json
# Mermaid diagram (paste into any markdown renderer)
ast-index module-route --from app --to core.db --format mermaid
# Graphviz DOT
ast-index module-route --from app --to core.db --format dot
# Gradle-style module names work too
ast-index module-route --from :app --to :core:utilsOptions:
--all— return all simple paths instead of the single shortest--via-kind <api|implementation|all>— filter traversal to one edge kind (default:all)--max-paths <N>— cap on returned paths when--allis set (default: 50)--max-depth <N>— cap on path length in hops (default: 20)--timeout-ms <N>— wall-clock guard in milliseconds (default: 5000)
ast-index xml-usages <CLASS> # Find class usages in XML layouts
ast-index resource-usages <RES> # Find resource usages (@drawable/ic_name, R.string.x)
ast-index resource-usages --unused --module <MODULE> # Find unused resourcesast-index storyboard-usages <CLASS> # Class usages in storyboards/xibs
ast-index asset-usages [ASSET] # iOS asset usages (xcassets)
ast-index asset-usages --unused --module <MODULE> # Find unused assets
ast-index swiftui [QUERY] # @State/@Binding/@Published props
ast-index async-funcs [QUERY] # Swift async functions
ast-index publishers [QUERY] # Combine publishers
ast-index main-actor [QUERY] # @MainActor usagesast-index perl-exports [QUERY] # Find @EXPORT/@EXPORT_OK
ast-index perl-subs [QUERY] # Find subroutines
ast-index perl-pod [QUERY] # Find POD documentation (=head1, =item, etc.)
ast-index perl-tests [QUERY] # Find Test::More assertions (ok, is, like, etc.)
ast-index perl-imports [QUERY] # Find use/require statementsast-index init # Initialize DB
ast-index rebuild [--type TYPE] # Full reindex
ast-index update # Incremental update
ast-index stats # Index statistics
ast-index version # Version infoSupported elements:
- Classes, interfaces, type aliases, enums
- Class methods (constructor, getters/setters, static, async)
- Class fields/properties, private
#members, abstract methods - Functions (regular, arrow, async)
- React components and hooks (
useXxx) - Vue SFC (
<script>extraction) - Svelte components
- Decorators (@Controller, @Injectable, etc.)
- Namespaces, constants, imports/exports
ast-index class "Component" # Find React/Vue components
ast-index search "use" # Find React hooks
ast-index search "@Controller" # Find NestJS controllers
ast-index class "Props" # Find prop interfacesSupported elements:
- Structs, enums, traits
- Impl blocks (
impl Trait for Type) - Functions, macros (
macro_rules!) - Type aliases, constants, statics
- Modules, use statements
- Derive attributes
ast-index class "Service" # Find structs
ast-index class "Repository" # Find traits
ast-index search "impl" # Find impl blocks
ast-index search "macro_rules" # Find macrosSupported elements:
- Classes, modules
- Methods (def, def self.)
- RSpec DSL (describe, it, let)
- Rails patterns (has_many, validates, scope, callbacks)
- Require statements, include/extend
ast-index class "Controller" # Find controllers
ast-index search "has_many" # Find associations
ast-index search "describe" # Find RSpec tests
ast-index search "scope" # Find scopesSupported elements:
- Classes, interfaces, structs, records
- Enums, delegates, events
- Methods, properties, fields
- ASP.NET attributes (@ApiController, @HttpGet, etc.)
- Unity attributes (@SerializeField)
- Namespaces, using statements
ast-index class "Controller" # Find ASP.NET controllers
ast-index class "IRepository" # Find interfaces
ast-index search "[HttpGet]" # Find API endpoints
ast-index search "MonoBehaviour" # Find Unity scriptsSupported elements:
- Classes with Dart 3 modifiers (abstract, sealed, final, base, interface, mixin class)
- Mixins, extensions, extension types
- Enhanced enums with implements/with
- Functions, constructors, factory constructors
- Getters/setters, typedefs, properties
- Imports/exports
ast-index class "Widget" # Find widget classes
ast-index class "Provider" # Find providers
ast-index search "mixin" # Find mixins
ast-index implementations "State" # Find State implementations
ast-index outline "main.dart" # Show file structure
ast-index imports "app.dart" # Show importsast-index class "ClassName" # Find Python classes
ast-index symbol "function" # Find functions
ast-index outline "file.py" # Show file structure
ast-index imports "file.py" # Show importsast-index class "StructName" # Find structs/interfaces
ast-index symbol "FuncName" # Find functions
ast-index outline "file.go" # Show file structure
ast-index imports "file.go" # Show importsCreate .ast-index.yaml in your project root to configure ast-index:
# Additional directories to index
roots:
- "../shared-lib"
- "../common-modules"
# Directories to exclude from indexing
exclude:
- "vendor"
- "build"
- "node_modules"
# Include files ignored by .gitignore
no_ignore: falseAll fields are optional. CLI flags override config file values.
Monorepo with shared libraries:
roots:
- "../core"
- "../network"Project with generated code to skip:
exclude:
- "generated"
- "proto/gen"searchno longer returns nothing for a multi-word query — when a query with two or more terms has no literal match,searchnow hands it to theexploreranking engine and prints relevance-ranked symbols with their source, honouring--module/--in-file. Text output is labelledNo literal matches …; JSON stays a single document and carries"fallback": "explore"plus areason. Single-term queries keep exact literal semantics.callerswalks attached subtrees — call sites inside a subtree added withsubtree addare found and shown as[name] /abs/path, matchingusagesandrefs.--subtree NAMErestricts to that subtree,--localto the primary root. Previously both flags were accepted and ignored.exploreaccepts scope —--module/--in-filenarrow the candidate set before ranking.- Agent guidance points intent queries at
explore— the skill and the MCPsearchdescription now say to useexplorefor a question or a description andsearchfor a known identifier.
- Index C++ functions that return a pointer or a reference — declarations
such as
Grid* GetGrid(),Player& FindPlayer(), andconst Grid* const GetEmptyPhaseShift()are now indexed instead of being skipped. - Install from crates.io — the installation guide now documents
cargo install ast-index --lockedas the released channel, with the Git build kept as the way to install the unreleased default branch.
- Report truncated search results instead of hiding them —
search,symbol,class,implementations,refs,usages, andcallersnow printshowing N of Mwith a--limithint, and emit paginated JSON schema v2 withtotal,returned,truncated, andlimit. - Keep the index fresh without blocking edits —
update --background --debounce-ms <ms>queues a coordinated, trailing-debounced generation and returns immediately; index-reading commands wait for a queued generation instead of answering from a stale index. - Scope watcher detection to the project — the new
watch-statuscommand reports whether this project has an active watcher, so a watcher in one repository no longer suppresses updates in another. Session-start and post-edit hooks and the generated Git hooks use it, and the session-start hook now runs asynchronously with a visible status message. - Index Kotlin files containing
suspend { }lambdas — a suspend-lambda syntax error no longer drops the enclosing interface, nested classes, and later declarations from the index, and localvalbindings are no longer published as properties. - Count Kotlin references accurately — references in a symbol's own declaring file are kept when external references exist, and matches inside string literals, comments, and KDoc are excluded while executable string interpolation is still indexed.
- Resolve kebab-case Gradle type-safe project accessors —
deps,dependents,unused-deps, andmodule-routenow linkprojects.core.designIconto:core:design-icon, and report ambiguity instead of picking an arbitrary module. - Rebuild on Windows — the staged index is synced through a
write-capable handle, fixing
failed to sync index file … (os error 5). Concurrent runs are also recognized as ordinary lock contention instead of failing withfailed to acquire index publication lock … (os error 33). - Detect nested project stacks —
detect-stacksperforms a bounded recursive marker scan and recognizes standard nested Kotlin Multiplatform layouts such ascomposeApp/src/commonMain. - Document worktrees and cross-root workspaces — independent git
worktrees keep independent indexes; intentional workspaces are attached
with
rebuild, thensubtree add, thenupdate. - Compare ast-index and CodeGraph —
docs/comparison.mdadds a dated, source-backed feature comparison with pinned snapshots of both projects.
- Review branch changes quickly without building an index — use
changedfrom the CLI or MCP to read cache-independent branch changes with added, modified, deleted, and renamed files, rename metadata, working-directory scope, a bounded VCS timeout, Git base auto-detection, and stable JSON schema v1. - Migrate
changedconsumers to the file-level contract — text output now prints an A/M/D/R file summary instead of regex-derived declaration pseudo-symbols. Scripts should request--format jsonand readchanges[].status,changes[].path, andchanges[].old_pathfor renames. Library callers can use the deprecated Rust compatibility wrappers while migrating to the new API.
See CHANGELOG.md for earlier releases.