← Open Source
defendend

Claude-ast-index-search

Cli allows AST search, ~40-50% economy tokens. Claude/Codex/Cursor searches (suitable for any AI agent that can use Bash).

AI EngineeringGive agent knowledgeRust
Open on GitHub
Momentum
+0stars in 24 hours0.0%
574
Stars
42
Forks
+2
This week
14
Contributors
Created 2026-01-19 · Updated 2026-10-14 · #16750 today
Top developers
README

ast-index v3.56.0

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.

https://t.me/defendend_ai_dev

What It Gives You

  • Navigate classes, functions, files, imports, usages, callers, implementations, inheritance, modules, and dependency paths.
  • Start broad with explore, then jump to exact definitions with symbol, class, outline, and refs.
  • Keep the index current with ast-index update after the first rebuild.
  • 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.

How To

# 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 app

# Opt-in data: Git history and the symbol graph (neither runs on rebuild/update)
ast-index hotspots --collect                  # rank files by churn, fixes, authors
ast-index graph build                         # symbol-to-symbol dependency graph
ast-index graph dependents ApplicationService # who depends on it, before a change
ast-index search Service --rank proven        # which match is safe to copy

Use 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 500

Index-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.

Performance

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

Installation

Homebrew (macOS/Linux)

brew tap defendend/ast-index
brew install ast-index

Cargo (crates.io)

Requires a Rust toolchain.

cargo install ast-index --locked

To build the unreleased default branch instead:

cargo install --locked --git https://github.com/defendend/Claude-ast-index-search ast-index

Both build from source. For prebuilt release binaries, use Homebrew, npm, or Winget.

Winget (Windows)

winget install --id defendend.ast-index

Migration from kotlin-index

If you have the old kotlin-index installed:

brew uninstall kotlin-index
brew untap defendend/kotlin-index
brew tap defendend/ast-index
brew install ast-index

From source

git clone https://github.com/defendend/Claude-ast-index-search.git
cd Claude-ast-index-search
cargo build --release
# Binary: target/release/ast-index (~50 MB)

Troubleshooting: Syntax errors on install

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-index

Monorepo Workflow

If 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 ViewModel

This 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".

Worktrees And Intentional Cross-Root Workspaces

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 list

Use --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.

AI Agent Integration

Claude Code Plugin

# 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-plugin

Restart 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 Skill / Plugin

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-index

This 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-search

The 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 Skill / Plugin

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-index

This 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-index

Reload 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 CLI

gemini skills install https://github.com/defendend/Claude-ast-index-search.git --path plugin/skills/ast-index

💝 Support Development


Commands (47+)

Run ast-index rebuild once per project, then use ast-index update to keep the index fresh.

ast-index explore        # One-shot context: ranked symbols, outline/source, neighbours, tests (--rwr for graph)
ast-index search            # Universal structural search
ast-index search  --rank   # Re-rank by history + graph: proven, hotspots, risky, central
ast-index file            # Find files
ast-index symbol             # Find symbols
ast-index symbol  --with-content # Include matched symbol bodies
ast-index search  --with-content # Include matched symbol bodies
ast-index class              # Find classes/interfaces
ast-index outline            # Symbols in file
ast-index imports            # Imports in file
ast-index refs             # Definitions + imports + usages
ast-index usages           # Symbol usages
ast-index callers        # Function call sites
ast-index implementations  # Find implementations
ast-index implementations  --with-content # Include implementation bodies
ast-index hierarchy         # Class hierarchy tree
ast-index changed [--base BRANCH]  # Branch-level changed files (A/M/D/R)
ast-index hotspots [--collect]     # Rank files by Git history (churn, fixes, authors)
ast-index graph        # Symbol graph: build, dependents, impact, path, cycles, top, metrics
ast-index todo [PATTERN]           # TODO/FIXME/HACK comments
ast-index deprecated [QUERY]       # Deprecated items

Paginated JSON schema v2

Limited 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
  }
}

For symbol --with-content and implementations --with-content, each item additionally contains content, truncated, and end_line. content is a line-numbered source body read from disk, capped at 60 lines; truncated tells whether the range extends past those lines, and end_line is the indexed end of the symbol. search --with-content adds the same fields to its symbols array, including ranked search. Text output marks truncated bodies. Without the flag, response shapes are unchanged. The index stores parser supplied start and end lines, not body text. Existing indexes gain ranges as files are reindexed; rebuild fills them immediately.

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 files on the current branch

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 --verbose

Text 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.

Git hotspots

Two files can look equally good to copy from and have very different histories: one absorbed 28 commits in six months with 54% of them bugfixes, the other was written once and left alone for 86 days. hotspots puts that history next to the code.

Per file, collected from the Git log: commit count, churn (added + deleted lines, plus churn relative to the file's current size), bugfix share, distinct author count, age of the first commit, and time since the last change.

Thresholds are percentiles within this repository, not constants. 28 commits is a lot for a small library and unremarkable in a monorepo, so churn:high means p90+ here. Raw numbers are always printed next to the label.

# Collect history into the index, then show the top 20
ast-index hotspots --collect

# Report from already-collected data (no Git subprocess at all)
ast-index hotspots --limit 50 --sort fixes

# Narrow to a directory; percentiles still come from the whole repository
ast-index hotspots --path src/parsers --min-commits 5

# Discard the cursor and rescan the full history
ast-index hotspots --collect --full

# Stable paginated schema v2
ast-index --format json hotspots --limit 10

Collection is never implicit: rebuild and update do not run it, so indexing cost is unchanged for everyone who does not ask for this. The first --collect walks the whole history; every later one reads only what changed. History is stored per commit, so a branch switch, rebase, force-push or reset subtracts the commits HEAD no longer reaches and adds the new ones, and the numbers equal a fresh full collection at that HEAD. Line counts come from the working tree: an uncommitted edit counts once a later --collect recomputes that file (a commit or checkout changes it) or on --full.

Signals live in their own tables, keyed by project-relative path, so they also cover files the parsers never look at (fixtures, configs, migrations). They depend on the repository, not on the index, so rebuild carries them into the new index and the next --collect stays incremental. It leaves them behind, says so, and the next --collect reads the history again only when they were collected by an older version or from another working tree or scope, or cannot be read back intact.

Git hotspots — 289 file(s) with history, 370 commit(s) analyzed, HEAD 50db069dbb, sorted by score:
  Labels are percentiles within this repository (high = p90+, elevated = p75+).
  src/indexer.rs
    score 90 · commits 70 (p98) · fixes 17/70 = 24% (p72) · churn +8558/-3259 (p99, 2.2x file)
    authors 6 (p99) · age 244d · last change 0d ago · 5298 lines
    churn:high rewritten-often authors:many veteran

Labels: churn:high / churn:elevated, rewritten-often (high churn per current line, only for files of 10+ lines), fixes:high / fixes:elevated (only for files with at least four commits, so 1-of-1 is never "100% bugs"), authors:many, veteran. score is the mean of the commit, churn and bugfix-ratio percentiles.

Bugfix detection is a heuristic over commit subjects: leading tracker keys ([ABC-123], ABC-123:, #42, [ABC-1][ABC-2]) are stripped first, then English and Russian bugfix vocabulary is matched on word boundaries — so prefix is not a fix, Исправить падение is, and so is [HOTFIX][ABC-123] (only keys are stripped, not tags like [HOTFIX] or [FIX]). Merge commits are excluded. Renames follow the file: history recorded under the old path moves onto the new one.

--sort accepts score (default), commits, churn, relative-churn, fixes, authors, recent. fixes orders by the bugfix share discounted for thin history — the lower bound of its 95% Wilson interval, so 11 fixes in 17 commits rank above 2 in 2 — and lists files with fewer than four commits after the rest. --subtree is rejected: the signals describe the project's own working tree.

Symbol graph and ranked search

graph build turns indexed references into symbol-to-symbol edges, each with the confidence of its resolution (local, scoped, import, unique, ambiguous). Like history collection it is explicit: rebuild and update never run it, and queries flag a graph gone stale (--refresh rebuilds first).

ast-index graph build
ast-index graph dependents ApplicationService      # incoming edges
ast-index graph impact PaymentGateway --depth 3    # transitive dependents per hop
ast-index graph path OrdersController Invoice      # how one reaches the other
ast-index graph top --kind class --exclude-tests   # most central symbols

search --rank re-orders the Files and Symbols of a search by the collected Git history and the graph: proven (safe to copy), hotspots (keeps being fixed), risky (many dependents and unstable history), central (PageRank). Exact name matches stay first; a preset whose data is missing is not applied and the output names the command that collects it. Formulas and their backtest are in the user guide.

Module analysis

ast-index module          # Find modules
ast-index deps             # Module dependencies
ast-index dependents       # Dependent modules
ast-index unused-deps      # Find unused dependencies (v3.2: +transitive, XML, resources)
ast-index api              # Public API of module

module-route — dependency path between two modules

Show 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:utils

Options:

  • --all — return all simple paths instead of the single shortest
  • --via-kind — filter traversal to one edge kind (default: all)
  • --max-paths — cap on returned paths when --all is set (default: 50)
  • --max-depth — cap on path length in hops (default: 20)
  • --timeout-ms — wall-clock guard in milliseconds (default: 5000)

XML & Resource analysis

ast-index xml-usages        # Find class usages in XML layouts
ast-index resource-usages     # Find resource usages (@drawable/ic_name, R.string.x)
ast-index resource-usages --unused --module   # Find unused resources

iOS-specific commands

ast-index storyboard-usages   # Class usages in storyboards/xibs
ast-index asset-usages [ASSET]       # iOS asset usages (xcassets)
ast-index asset-usages --unused --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 usages

Perl-specific commands

ast-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 statements

Index management

ast-index rebuild [--type TYPE]    # Full reindex
ast-index update                   # Incremental update
ast-index stats                    # Index statistics
ast-index version                  # Version info

Language-Specific Features

TypeScript/JavaScript (new in v3.9)

Supported 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 (`` 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 interfaces

Rust (new in v3.9)

Supported 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 macros

Ruby (new in v3.9)

Supported 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 scopes

C# (new in v3.9)

Supported 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 scripts

Dart/Flutter (new in v3.10)

Supported 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 imports

Python

ast-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 imports

Go

ast-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 imports

Configuration File

Create .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"

# Index only these directories (allow-list; everything else is skipped)
include:
  - "app"
  - "packages/shared"

# Include files ignored by .gitignore
no_ignore: false

All fields are optional. CLI flags override config file values.

Examples

Monorepo with shared libraries:

roots:
  - "../core"
  - "../network"

Project with generated code to skip:

exclude:
  - "generated"
  - "proto/gen"

Changelog

3.56.0

This release merges the symbol graph, Git hotspots and ranked search work contributed in #72 (which also carried #28), the --with-content responses from #73, and follow-up fixes.

  • graph — a symbol dependency graph — graph build resolves indexed references into symbol-to-symbol edges and stores them with per-edge resolution confidence (local, scoped, import, unique, ambiguous). Queries on top of it: dependents, dependencies, impact (transitive dependents), path, cycles, top, status and metrics (fan-in, fan-out, PageRank). Ruby references resolve through namespaces, lexical nesting, constant receivers, inheritance and mixins; JavaScript/TypeScript references through the module's own imports. Metrics count resolved edges only and report ambiguous ones separately, so a name defined in hundreds of places does not inflate a symbol's centrality. The graph is opt-in: rebuild and update never build it, and queries flag a stale graph after the index changes and accept --refresh.
  • hotspots — per-file Git history signals — a new command ranks files by what their history says about them: commit count, churn (absolute and relative to the file's current size), bugfix share, distinct authors, age and time since the last change. Thresholds are percentiles within the repository being indexed, not absolute constants, so "high churn" means something in a 300-file library and in a 30 000-file monorepo alike; raw numbers are printed next to every label. Text and --format json output, paginated JSON schema v2.
  • Collection is opt-in — rebuild and update never walk the log; hotspots --collect does.
  • search --rank — re-rank results by history and structure — four presets re-order the Files and Symbols sections of a search: proven (calm, old, idle and actually used — safe to copy), hotspots (the file's hotspot score), risky (transitive dependents × hotspot score) and central (PageRank). Every result carries its dossier: the score and its terms, raw history and graph numbers with repository percentiles and labels, and the relevance position it came from. Relevance stays in charge: exact name matches stay above partial ones, the pool is the head of the plain order, and the preset only weighs in inside a tier. History is labelled as per-file, third-party code is never scored and goes last, and a preset whose data is missing is not applied — the output names hotspots --collect or graph build instead of ranking by zeros. Formulas were chosen by backtesting next-year bugfixes on a 40k-file monorepo.
  • MCP exposes the symbol graph, Git hotspots and ranked search — seven new tools: graph_dependents (direct dependents, or the transitive blast radius with depth ≥ 2), graph_dependencies, graph_path, graph_metrics (the most central symbols, or metrics of given ones), graph_cycles, graph_build and hotspots; search takes rank (proven, hotspots, risky, central). Descriptions tell the agent when to reach for each one — before changing a symbol, when choosing which match to copy — and what has to be collected first; graph queries accept refresh: true to build a missing or stale graph in place. History collection stays a CLI command, since its first run outlasts common MCP client timeouts, and the tools name it when history is missing. Output is compact text: a ranked search prints each file's history and each symbol's graph numbers once, about a quarter of the JSON size.
  • symbol, search and implementations take --with-content — each matched symbol is followed by its line-numbered source body, read from disk at the range the parser recorded (end_line), so an agent gets the definition in one call instead of a follow-up sed/rg. Bodies are capped at 60 lines; a longer one ends with ... truncated at line N; symbol ends at line M, and JSON items carry content, truncated and end_line. Without the flag the output is unchanged. The MCP symbol, search and implementations tools accept with_content: true.
  • Symbol ranges for every tree-sitter language — end_line used to be filled only for Ruby and TypeScript/JavaScript, so call-tree, explore --rwr and graph fell back to "the last definition above the line" everywhere else. Python, Go, Rust, Java, Kotlin, Swift, C#, C/C++, PHP, Scala, Dart, Lua, Elixir, Zig, Objective-C, Groovy, Bash, SQL, R, MATLAB, GDScript, Common Lisp, BSL and Protobuf now store the last line of every definition, with classes, modules and namespaces enclosing their members. The fallback had blamed the wrong definition for 3.6% of references in a Rust codebase, 11% in a Go checkout and 18–42% in C++; module-level code no longer gets an invented caller. Decorators and annotations stay outside the definition they decorate, since they run in the enclosing scope. The graph treats Rust impl blocks and Swift/Objective-C extensions as the namespace of the type they extend, so Type::new(…) and Self::helper(…) resolve. CSS, SCSS, Less and the regex-based parsers still report no ranges. Run ast-index rebuild to fill ranges in an existing index.
  • outline prints line ranges and supports --format json — :12-40 Invoice [class], rows in source order, JSON schema v1 with end_line.
  • Tell code from prose by the syntax tree — names in comments, docstrings and string literals are no longer references in Ruby, Python, JS/TS, C/C++, Objective-C, Go, Rust, Java, C#, PHP, Swift, Scala, Dart, Lua, Groovy, Elixir, Bash, R, Zig and Proto. Interpolated code stays, and so do strings that name code: Ruby constant-path strings and %w[] words (class_name: 'Invoice'), Python dotted class names ("pkg.models.User"), JS import('./Page'). References drop 7–51% per project.
  • rebuild is about 40% faster — signatures looked up their source line by scanning the file from the top for every symbol (quadratic in file length) and copied the whole line before truncating it; Ruby and TypeScript files were parsed twice (symbols, then references); and parse threads sat idle while each chunk was written. Lines are now indexed once per file, signatures capped before copying, each file parsed once, and writing overlaps parsing in the same order, so the index is byte-for-byte the same: 9.3 → 5.5 s on a 40k-file monorepo.
  • update finds changes 3–4× faster — the project root was canonicalized once per file (a thread and a realpath each), and files were stat'ed and node_modules packages walked one by one; now once per walk and in parallel. A no-op update on a 40k-file project: 2.1 → 0.6 s; 20 changed files: 1.8 → 0.4 s.
  • Minified JavaScript and CSS are left out — .js / .mjs / .cjs / .css files named *.min.* or *-min.*, or whose first 64 KiB is minifier output (lines of 1000+ bytes on average, 100+ of them outside string literals), are no longer indexed, read by the grep-based commands (search contents, callers, call-tree, todo, …) or parsed by outline / imports, which now say the file was skipped. A one-line stylesheet gave each of its thousands of selectors the whole file as a signature: on a Rails monorepo with a 589 KB app.min.css, outline of that file took 4.6 GB and rebuild peaked at 4–7 GB; now 11 MB and 0.21 GB. callers / call-tree no longer answer from bundles (__webpack_require__). Source with a few long strings, SVG paths or data URIs is unaffected, and TypeScript, JSX, SCSS and .d.ts are never judged. update drops minified files an older index kept, without a rebuild. AST_INDEX_SKIP_MINIFIED=0 turns the filter off.
  • Rust use declarations count as references again — use crate::models::Widget; is the first use of Widget in a file, not the declaration of its own last segment; usages and refs list import lines for Rust projects as before. Takes effect on the next rebuild.
  • Build markers inside test fixtures no longer label the project — a pom.xml under tests/fixtures/ or a Package.swift under src/test/ belongs to the sample project a test indexes; stats, map and detect-stacks ignore markers under test, tests, __tests__, fixtures, __fixtures__, test-fixtures and testdata directories.
  • implementations leaves out a namesake from another namespace — a parent written Legacy::ApplicationService no longer counts as ApplicationService when the index defines both classes, and < ::ApplicationService is a direct child; on a large Rails app implementations ApplicationService lists 917 classes instead of 972. Package and namespace spellings of the same type (extends com.acme.Base, : public acme::Base, a namespace the index does not define) still match.
  • A qualified declaration is no usage of itself — class Billing::Invoice no longer records a reference to Invoice on its own line (a C# namespace A.B likewise); Billing stays a reference, and so does the parent of class Admin::User < User. Takes effect on the next rebuild.
  • stats and map name the project — rebuild records the stacks it finds (Web (TypeScript/JavaScript) + Ruby), stats prints them as Project: (JSON project, which the MCP stats tool already expected) and map in its header. Every *.gemspec directory is a Ruby module (engines.billing), so Rails engines show up in module, map and --module.
  • conventions matches frameworks on whole name segments — a rule no longer fires on any import that merely contains it: the Ruby gem sequel-combine is not Swift Combine, preact not React, and ../testing/setup not Go's testing, while CombineExt is still Combine. Rails, Django, Flask, FastAPI, Express, Sidekiq, Celery, ActiveRecord and Sequel are recognised.
  • explore picks tests from the mirrored directory — among test files named after the source, one in the source's own directory comes first, then the ones whose directories end like the source's (spec/services/billing/charge_spec.rb for app/services/billing/charge.rb, src/test/java/a/b/XTest.java for src/main/java/a/b/X.java); when none shares a directory (a flat tests/, separate *.Tests projects) all are listed as before. Up to 50 candidates per name are considered instead of 5.
  • explore --rwr takes callers from the symbol graph — when graph build ran and the index has not changed since, neighbours are the definitions with a resolved edge to each seed instead of every reference sharing its name, which for a namespaced class found none. A seed the graph resolves no edge to (a call through a receiver of unknown type in Java, Swift, Go) keeps the name-matched callers.
  • Performance table in the skill shows a large repository — next to a small project; search on a 40k-file monorepo takes 250–550 ms, not 10.
  • explore finds the class a question names — candidates now also come from one bm25 ranking over all query words, from the words run together (pdf to html service is the PdfToHtmlService token the full-text index keeps whole) and from files whose path holds every word; before, each word sampled its first 40 matches in index order, so a common word such as application never reached ApplicationService. The type a file is named after and the symbol the query spells out rank first, a word in a symbol's own name counts above one in its namespace, and statements (has_many :x, scope, include), namespace-only modules and question words (how, does, work) rank low or are ignored. On a large Rails app 6 of 6 queries naming a class put it first instead of 1 of 6, for about 30 ms more per query on a 300k-symbol index.
  • explore outlines types and modules — a class, module or statement is shown as the outline of the definition around it (:34-53 process [function] rows with line ranges from the index, the chosen symbol marked →, at most 40 rows) instead of the first lines of its source; functions keep their source. JSON files[] carry outline and outline_hidden in place of source for those, and the MCP search fallback renders them.
  • MCP explore answers in compact text — the tool returned the raw CLI JSON, with source escaped into strings, although its description promised text by default; it now renders the report the search fallback prints under an explore: line, less than half the size. format: "json" still returns the JSON.
  • Definitions rank above imports — inside every relevance tier of search (plain, --fuzzy, --rank) definitions now come before imports, and imports never enter the last-segment tier: search InstallRequirement in pip lists the class first instead of 12th behind eleven imports, and search Result in a Rust project no longer answers with a wall of use anyhow::Result.
  • Schema columns named after the query rank first — . now separates name segments like ::, so search email -t column lists users.email and every other *.email column before email_communicator_email_id-style columns.
  • Namespaced Ruby classes found by short or full name — symbol, class, refs, hierarchy and implementations find class Billing::Invoice by Invoice or Billing::Invoice (Ruby stores the full name and no qualified name, so hierarchy and class used to answer "not found"); an exact short name still wins. usages Billing::Invoice lists references to Invoice on lines that spell the full name out.
  • proven favours settled, living examples over abandoned ones — the score is now mean(calm, mature, used) × substance × lineage: files under 10 lines and empty class bodies score half, and so does code built on a base class the project stopped extending; time since the last change is no longer scored. On 11 "which one to copy" queries the top five held 40 good examples of 55 instead of 22.
  • Test symbols after production code — in the partial-match tiers of search, symbols in test files or named test_* / TestX follow the rest; exact names keep their place.
  • usages lists production code first — references in test files follow those in production files, each group by path and line (also in refs); JSON marks test references with "test": true.
  • Skip references inside the Rails schema dump — t.string / t.integer in db/schema.rb no longer count as uses of the project's string / integer methods (≈3.7k references and 2.6k ambiguous graph edges fewer on a large Rails app); tables and columns stay indexed.
  • C/C++ prototypes are declarations — a header prototype, static forward declaration, class method declaration or function-pointer field no longer counts as a use of the function.
  • Ruby callbacks link to their methods — before_save :normalize, validate :check, validates :email, if: / unless:, rescue_from ... with:, alias_method, delegate and &:name are references; the graph links a model to its callback methods and validated columns.
  • Compact outline for schema dumps — db/schema.rb columns fold into a count per table (164 KB → 17 KB on a 335-table schema); --full lists every column.
  • call-tree shows same-named callers with their file — two it "works" blocks or export methods of different files used to print as (recursive) without a path and still use up --limit. Every caller now prints with its file, a caller named like one already expanded is marked (expanded above), and (recursive) marks only a definition already on its own path.
  • call-tree attributes calls to real definitions — an import or annotation line (use, import, include Mod, Rails callbacks) no longer owns a call, and a line declaring the function itself (Go func (s *T) Name(, a JavaScript method, attr_reader :name) is no longer its own caller. The same owner rule serves explore --rwr and the symbol graph.
  • callers / call-tree stop reading symbols and paths as calls — authorize(record, :update?) is no call of update, and use super::name; or Billing::Name without a call is no call of name; callbacks, delegate and &:name still count.
  • The symbol graph resolves Rust paths — files are modules, crate:: / super:: / self:: and use declarations (grouped, aliased, globbed, pub use) are followed, and workspace crates are reached by their Cargo.toml name. On this repository calls through a module path went from 0 to 1160 of 1161 resolved; graph dependents open_db lists 38 dependents instead of none.
  • graph dependents and graph impact take --exclude-tests (MCP graph_dependents: exclude_tests) — test dependents are left out of the list and the counts, and impact does not follow them.
  • Graph answers say what they merge and leave out — the definitions a bare name matches are capped by --limit with a hint to narrow the query, and a column query explains that only reads inside the model are edges and points to usages.
  • Faster graph queries — freshness is checked against an index write generation and the highest row ids instead of counting and summing the tables: about 35 ms off every graph query and search --rank on a large monorepo.
  • hotspots --sort fixes discounts thin history — files are ordered by the lower bound of the 95% Wilson interval of their bugfix share, and files with fewer than four commits follow the rest, so 11 fixes in 17 commits rank above 2 in 2.
  • Bracketed fix tags count as bugfixes — only tracker keys ([ABC-123], ABC-123:, #42, several in a row) are stripped before matching, so [HOTFIX][ABC-123] …, [FIX], [BUGFIX] and хотфикс count as fixes; the store layout moved to commits-v2, so the first --collect recollects once.
  • MCP descriptions no longer overpromise — usages claimed it never matches comments or strings and callers that it attributes calls to the calling function; both now say what they do (callers points to call_tree), and the heaviest descriptions were trimmed: tools/list 27.3 → 24.4 KB.
  • search, callers and call-tree skip files that cannot match — the index now keeps each file's distinct words (file_words, about +7–10% index size), and the grep-based commands no longer open a file whose words lack the searched literal while it is unchanged since indexing; edited and new files are still searched. On a 40k-file Rails monorepo search went from 0.67 to 0.33 s, callers merge from 0.60 to 0.26 s, call-tree --depth 3 from 1.13 to 0.66 s, with the same results. An index built by an older version is searched in full until its next rebuild.
  • callers checks for definitions cheaply and in parallel — the "is this line a definition?" test ran a capturing regex on one thread for every call line; a capture-free pre-check now runs on the search threads.
  • graph build 2.5× faster — references are resolved per file in parallel and import matches memoized, with an identical graph: 4.2 → 1.7 s.
  • hotspots --collect reads history 5× faster — git log windows run in parallel (up to 8), and a window of old bulk-import commits no longer overflows the 16 MB output cap and gets re-read at half size again and again: a full collection of a 25k-commit history took 50 s, now 10 s, with the same stored history.
  • The MCP server answers tool calls concurrently — several calls sent at once used to wait for each other; responses may now arrive out of order, matched by id as JSON-RPC allows.
  • rebuild keeps the collected Git history — the per-commit store, the per-file signals and the collection cursor are copied into the new index instead of being dropped, so hotspots reports right after a rebuild and the next --collect stays incremental: on a 25k-commit monorepo 0.2 s instead of a full rescan of a minute or more, for about 0.4 s added to the rebuild. History collected by an older version, from another working tree or scope, or that cannot be read back intact is left behind with a note, and the next --collect reads it again. --sub-projects / --include rebuilds lost it too and now keep it.
  • snake_case calls are references in every language — the generic reference extractor did not allow _ in a called name, so usages, refs and the graph missed update_profile(user) and self._compute() in Python, Rust, Go, C/C++, PHP, Lua, TypeScript and every other language using it (usages open_db on a Rust codebase went from 0 to 50 hits). Reserved words (sizeof (x), #if defined(X), Go func (r *T), Rust pub(crate), Python None) are no longer recorded as references. In Ruby, a lowercase name( inside a comment, string or heredoc is no longer a reference.
  • BSL and stylesheets use their own reference extractors again — the trait default sent every language to the generic ASCII extractor, so BSL (1C:Enterprise / OneScript) files had no Cyrillic references at all and CSS, SCSS and Less recorded kebab-case fragments. BSL now records Cyrillic calls, modules before . and types after Новый, with the source line as context; stylesheets record none.
  • One test-path rule for explore, the symbol graph and --exclude-tests (graph top, hotspots, search --rank): adds test_*.py, conftest.py and FooTest/FooTests/FooSpec files in JVM/Swift/C#/PHP/C++, and stops treating Ruby namespaces under app//lib/ (e.g. app/jobs/tests/) and PascalCase JS component folders (components/Test/) as tests.
  • First open of a project no longer slows down quadratically with the cache — the cache-migration scan now lists the lease directory once instead of once per cached project (a first rebuild beside 5000 cached projects dropped from about 70 s to about 1.4 s).
  • Cache GC removes lease lock files of caches that no longer exist — .leases kept a {key}.lock and {key}.publish.lock for every cache ever opened, so it grew without bound. The cleanup after rebuild / update now deletes them whatever their age, and only under the cache-layout lock after locking both files itself, so a lock another process holds or is about to take is never split across two files. Every command that resolves the index gets faster on a cache that had accumulated them.
  • Rails schema tables and columns — db/schema.rb (indexed even when gitignored) yields table and column symbols (users.email; search email -t column, outline db/schema.rb). graph build matches tables to models by Active Record's rules — self.table_name, single-table inheritance, nested models, table_name_prefix / isolate_namespace, the pluralized class name — reports tables without a model and models without a table, and resolves column readers and attribute methods called inside a model (email, self.email, email?, saved_change_to_email?) as scoped edges to the column (graph dependents users.email).
  • Ruby calls without parentheses are references — usages and the graph now see recv.name, name arg, snake_case name(...) and bare name calls that are not local variables; core collection and string methods called without parentheses are skipped. RSpec let / subject helpers resolve within their spec file. On a Rails monorepo resolved graph edges grew from 66k to 186k; references nearly doubled for Ruby files.
  • Compound Ruby constants are indexed — Billing::Import = Container.injector becomes a constant named with its enclosing scopes like a class, and CamelCase assignments (Types = Dry.Types()) are indexed as constants too. In the graph a constant defined in an inner scope shadows an outer module of the same name, so include Import[...] inside module Billing no longer links every such class to an unrelated top-level module Import.
  • Production code never resolves into test trees — a spec helper that reopens a class to stub a method is no longer the target of production calls.
  • One definition of third-party code — only node_modules is left out of the graph; a project's vendor/ directory and its own .d.ts files are graph nodes, while search ranking still demotes every .d.ts.
  • hotspots --collect survives branch switches, rebases and resets — history is kept per commit, so moving HEAD subtracts the commits it no longer reaches and adds new ones, reusing diffs already read; when a merge joins or leaves, files that differ between the two HEADs are recomputed too, so a file only a merge edited keeps a correct line count. A switch on a 25k-commit monorepo takes under a second instead of a ~1-minute rescan, and the numbers equal a fresh full collection at the same HEAD (line counts come from the working tree, so an uncommitted edit counts once the file is recomputed). The git tables are ~40% smaller. The first --collect after upgrading recollects once.
  • Hotspot scores no longer collapse into ties — hotspots --sort score and search --rank hotspots|risky|proven order by unrounded percentiles (score_exact in JSON).
  • --exclude-tests for hotspots and ranked search — hotspots --exclude-tests and search --rank … --exclude-tests (MCP: exclude_tests) hide spec/test files; percentiles still rank every file.
  • rewritten-often only for files of 10+ lines — relative churn is computed only for such files, so one-line bundles, fixtures and gutted files no longer read as "3000x file".
  • Namespaced definitions lead search — search MergeService had no exact hit for class A::B::MergeService, and bm25 put the shorter describe "A::B::MergeService" spec above the class. Names whose last :: segment equals the query (case-sensitive) now rank right below exact names; statements such as include A::B::MergeService do not count. search --rank uses the same tier (exact_last_segment).
  • Owning-symbol lookups stay within the file's root — with an attached subtree holding a file under the same relative path, call-tree and explore --rwr could attribute a call to a method of the other root.
  • Drop the redundant idx_symbols_file index — it is the leftmost prefix of idx_symbols_file_line_end; new databases no longer create it and existing ones drop it on open. restore accepts backups with and without it; a backup made by this version cannot be restored by older releases.
  • MCP search keeps its explore fallback — a multi-word query without literal matches now renders fallback: explore — followed by source, ranked symbols, graph neighbours and tests instead of an empty Files: heading.
  • callers and call-tree find bare Ruby predicate and bang calls — if next_page? && … and save! without receiver or parentheses were missed; definitions, #name? in documentation and !=/!~ are still skipped.
  • Tests no longer write to the user's index cache — .cargo/config.toml points AST_INDEX_CACHE_DIR at target/ast-index-cache for everything cargo starts; an explicitly set variable still wins.
  • Name a wrapped default export after what it wraps — export default injectIntl(Header), memo(Button) or connect(mapState)(Page) was indexed under the wrapper's name, so every file applying injectIntl claimed a definition of it. Such an export is now indexed as default(Header), like export default Header. A wrapped inline function or class is indexed like an anonymous default export, under the module name. A call that builds a value from its configuration (createRouter({ … })) keeps the callee's name.
  • Name an index default export after its package, not its build directory — node_modules/pkg/dist/index.d.ts was named dist; the name now comes from the nearest directory above build and source directories.
  • imports lists every TypeScript/JavaScript import — files were read with a line pattern meant for Kotlin, so a multi-line import { printed as a bare {, re-exports were not listed and a barrel index.ts reported "No imports found". Imports and re-exports are now read from the syntax tree, one declaration per line. Import symbols in the index stay limited to project-local specifiers.
  • search --type no longer takes a minute on a large index — with a kind filter the bundled SQLite drove the query from the index on kind, scanning every symbol of that kind and re-running the full-text match for each one: search Job --type class took about a minute on a 320k-symbol index. The full-text match now leads and the kind filters its hits, about a second, with the same results.
  • call-tree prints the same tree on every run — which callers made it under --limit depended on the files the parallel scan happened to reach first and on hash-map iteration order, so repeating a query could print a different tree. Call lines are now taken in path order and attributed in that order; files are still searched in parallel. Definition lines and files outside --in-file no longer use up a function's match budget, so every Sidekiq worker defining perform no longer crowds out the calls, and --in-file no longer prints an empty tree while matching calls exist. The repository is walked once per command rather than once per level: deep trees got faster, while a single frequent name at --depth 1 now pays for the full walk up front.
  • call-tree stops expanding callers nothing can call — a call inside an RSpec block or a Rails DSL call is owned by a symbol named after that block (it "does nothing", let(:fields), attributes :id). Such an owner is still printed as a caller, but its name never occurs in code as a call, so looking up its callers cost a scan of the whole repository and never found any. Only callers whose name is an identifier — Foo::Bar, save!, valid? and name= included — are expanded now.
  • call-tree scans once per level — every function in the tree took a scan of the whole repository to find its callers, so a level with nine callers meant nine scans. Since calls resolve to the symbol that really contains them, levels are wider and call-tree had slowed down several times over. The functions a level needs are now looked up together in one scan that still gives each of them its own match budget, so the printed tree is the same and a level costs about one scan.
  • Calls behind a keyword are no longer taken for definitions — callers and call-tree skip definition lines by reading Type name( as a declaration, and a keyword in the type position fooled it: return foo(, await foo(, new Foo(, if foo(, export default connect( and the like were dropped as if they declared foo. Such lines now count as calls; real typed declarations are still skipped.
  • Index anonymous export default functions and classes — export default () => {}, export default function () {} and export default class {} produced no symbol, so the function could not be found and every call inside it had no owner in call-tree and explore --rwr. Such a value is now indexed with its range under the name it is imported by: the file name (hooks/useMap.js → useMap), or the directory for an index file (Button/index.jsx → Button). A function is classified like a declaration of that name, so a PascalCase component indexes as a class; outline shows the same name. Named default exports are unchanged.
  • Rank project code above dependencies in search — within a ranking tier, hits in node_modules or .d.ts files were ordered against project hits by path alone, so node_modules/… came ahead of spec/ or system/. Project hits now lead within each tier. The tier still decides first, so a library's exact useState stays above a project's partial useStateModal. A project-owned vendor/ directory still counts as project code; explore shares the same definition.
  • Rank symbol searches by relevance — FTS symbol queries used to come back in whatever order the engine produced, or sorted by name length, so a search for ApplicationService listed every shorter sibling and never the class itself. Matches are now ordered by exact name first (case-sensitive, then case-insensitive), then by bm25() weighted towards the symbol name over its signature, then by name length and finally by path and line, so repeated runs return the same order. The candidate pool explore builds keeps its previous unranked order — ranking it narrowed the pool to same-named symbols and made its own scoring worse.
  • Attribute a call site to the function that really contains it — call-tree and explore --rwr used to blame the nearest definition line above a reference, so a module-level call landed on the last method of the file and an include or a constant could be reported as the caller. Both now ask the index which symbol's line range encloses the reference and take the innermost one. Languages whose parsers report no range keep the previous behaviour.

3.55.0

  • Tuist module helpers are understood — besides targets: [...], target declarations inside modules: [...] are read, source directories are found among common layouts (Sources/X, X/Sources, X/Api, Tests/X, X/Tests), and nested implementation: / tests: configurations declare XImpl / XTests targets with their own dependencies. Workspaces that moved to per-module folders no longer lose those modules from the graph.
  • update refreshes the module graph when build files change — adding, removing or editing build.gradle(.kts), Package.swift, Project.swift, pom.xml, ya.make and other build files now updates deps, dependents, module-route and unused-deps without a rebuild. Unchanged build files cost nothing extra.
  • rebuild --type modules keeps resources and usages — it used to delete and re-insert every module, which cascaded into Android resources, XML usages, iOS assets and storyboard usages. Modules are now synced in place and keep their ids.
  • rebuild --verbose lists manifests whose targets could not be read, so an incomplete module graph is visible instead of silent.

3.54.0

  • Tuist projects get a module graph — targets declared in Project.swift (.target(...) or any project helper such as .spmSwiftFolderTarget(name: .Foo, ...)) are indexed as modules with their dependencies: (.target, .external, .project, .product), so module, deps, dependents, module-route and unused-deps work on Tuist workspaces. Previously such a workspace had no module dependencies at all.
  • SwiftPM modules point at their sources and have dependencies — targets resolve to Sources/ / Tests/ (or path: / sources:) instead of /, let targets = [...] declarations are read, and target dependencies are indexed. Swift modules are named after the target — the import name — and only fall back to a package-qualified name when two manifests declare the same target. Manifests are parsed with tree-sitter.
  • unused-deps understands Swift imports — an import Dep in any file of the module marks the dependency as used. Swift import declarations are now indexed.
  • api lists Swift public API — declarations marked public / open.
  • Swift local variables are no longer indexed as properties — let / var inside function bodies, closures and accessors are skipped, matching Kotlin.
  • Module-qualified supertypes resolve — class A : ru.pkg.Base(), extension Foo: Module.Proto are recorded with the simple parent name, so hierarchy and implementations find them (previously the parent became ru / Module).
  • inject finds constructor injection — parameters of @Inject constructor(...) and Java @Inject constructors, plus fields whose annotation is on its own line.
  • --limit with a filter no longer under-reports — flows, suspend, deprecated, suppress, deeplinks applied the query after the limit was spent (e.g. flows --limit 5 could return 1). suspend reports the function name instead of an extension receiver.
  • provides is an order of magnitude faster — scans only files that declare @Provides / @Binds.
  • unused-symbols --module accepts a module name — features.surge.impl or :core:utils, not just a path prefix.
  • rebuild --type modules|deps|files keeps the rest of the index — a partial rebuild used to publish an index without files and symbols.

3.53.0

  • search no longer returns nothing for a multi-word query — when a query with two or more terms has no literal match, search now hands it to the explore ranking engine and prints relevance-ranked symbols with their source, honouring --module / --in-file. Text output is labelled No literal matches …; JSON stays a single document and carries "fallback": "explore" plus a reason. Single-term queries keep exact literal semantics.
  • callers walks attached subtrees — call sites inside a subtree added with subtree add are found and shown as [name] /abs/path, matching usages and refs. --subtree NAME restricts to that subtree, --local to the primary root. Previously both flags were accepted and ignored.
  • explore accepts scope — --module / --in-file narrow the candidate set before ranking.
  • Agent guidance points intent queries at explore — the skill and the MCP search description now say to use explore for a question or a description and search for a known identifier.

3.52.0

  • Index C++ functions that return a pointer or a reference — declarations such as Grid* GetGrid(), Player& FindPlayer(), and const Grid* const GetEmptyPhaseShift() are now indexed instead of being skipped.
  • Install from crates.io — the installation guide now documents cargo install ast-index --locked as the released channel, with the Git build kept as the way to install the unreleased default branch.

3.51.0

  • Report truncated search results instead of hiding them — search, symbol, class, implementations, refs, usages, and callers now print showing N of M with a --limit hint, and emit paginated JSON schema v2 with total, returned, truncated, and limit.
  • Keep the index fresh without blocking edits — update --background --debounce-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-status command 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 local val bindings 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, and module-route now link projects.core.designIcon to :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 with failed to acquire index publication lock … (os error 33).
  • Detect nested project stacks — detect-stacks performs a bounded recursive marker scan and recognizes standard nested Kotlin Multiplatform layouts such as composeApp/src/commonMain.
  • Document worktrees and cross-root workspaces — independent git worktrees keep independent indexes; intentional workspaces are attached with rebuild, then subtree add, then update.
  • Compare ast-index and CodeGraph — docs/comparison.md adds a dated, source-backed feature comparison with pinned snapshots of both projects.

3.50.0

  • Review branch changes quickly without building an index — use changed from 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 changed consumers 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 json and read changes[].status, changes[].path, and changes[].old_path for renames. Library callers can use the deprecated Rust compatibility wrappers while migrating to the new API.

See CHANGELOG.md for earlier releases.

Nearby in the ranking
Similar items