AST CLI for AI agents that parses 16 languages with tree-sitter and reports symbols, references, diff impact, API changes, and dead code as JSON or TOON
astro-sight answers the structural questions an AI coding agent has while it edits code: where a symbol is defined and used, what a diff breaks, which public APIs changed, and which exports nothing calls any more.
It matches identifier nodes in the tree-sitter syntax tree, so refs --name new does not pick up comments or strings the way grep new does. Output is compact JSON by default, with TOON or automatic selection when tokens matter, and the same queries run from the CLI, an NDJSON session, or an MCP server.
Bash/zsh parse failures are reported explicitly: recognizable declarations inside error regions use refs.kind: "unknown", and review separates unverified removals from confirmed deletions. See shell parse errors.
- References by identifier:
refslists the definitions and references of a symbol across a directory, andrefs --nameslooks up many names in one pass - Diff impact:
contextmaps a unified diff to changed symbols, signature changes, and callers, andimpactexits 1 while callers outside the diff are left unresolved (made for Stop hooks) - Rust type aliases: a transparent public alias introduced in the same file can turn an equivalent function signature change into an informational result when the old and new syntax trees prove the underlying type is unchanged
- Rust parameter patterns: verified ordinary parameter renames and destructuring changes keep the same call contract; types and receivers remain checked. See proof boundaries and container limitations.
- Structured review:
reviewcombines the impact, files that usually change together according to git blame (cochange), public API changes, and dead symbols in one run - File structure:
symbols,calls,imports, andastreturn a file's outline, call graph, dependencies, and exact syntax nodes, andsequencedraws the call flow as a Mermaid sequence diagram - Dead code and rules:
dead-codereports exported symbols that nothing references, leaving out the ones test runners and frameworks call at run time, andlintchecks AST pattern rules written in YAML - Token-aware output: compact JSON, TOON, or
--format auto; results for frequent names stop at 100 references or about 3,000 tokens by default, with aresult_summaryof what was left out - Agent integration: skills for Claude Code and Codex, an MCP server, and an NDJSON
sessionthat batches queries
Supported languages (extensions and parser versions are in docs/languages.md):
brew install owayo/astro-sight/astro-sightwinget install owayo.astro-sightRequires Rust 1.99 or later.
cargo install --git https://github.com/owayo/astro-sight --lockedDownload the archive for your platform from Releases, extract it, and put astro-sight on your PATH. Each release also includes SHA256SUMS for checking the downloads.
| Platform | Archive |
|---|---|
| Linux (x86_64) | astro-sight-x86_64-unknown-linux-gnu.tar.gz |
| Linux (x86_64, musl) | astro-sight-x86_64-unknown-linux-musl.tar.gz |
| Linux (ARM64) | astro-sight-aarch64-unknown-linux-gnu.tar.gz |
| macOS (Intel) | astro-sight-x86_64-apple-darwin.tar.gz |
| macOS (Apple Silicon) | astro-sight-aarch64-apple-darwin.tar.gz |
| Windows (x86_64) | astro-sight-x86_64-pc-windows-msvc.zip |
On macOS, if you downloaded the archive with a browser, remove the quarantine attribute before running it: xattr -d com.apple.quarantine astro-sight.
Requires mise (the Rust toolchain is pinned in mise.toml).
git clone https://github.com/owayo/astro-sight.git
cd astro-sight
make installmake install installs to /usr/local/bin. Set INSTALL_PATH to change it (for example make install INSTALL_PATH="$HOME/.local/bin").
Check astro-sight --version after installing. If command -v astro-sight shows a mise shim, mise which astro-sight identifies the executable to update; see installation checks.
After winget install, open a new terminal: the portable package reaches PATH by rewriting it, which terminals that were already open do not pick up. The musl archive is a statically linked build for Linux without glibc (Alpine) or with an old glibc (some Docker images).
Building from source also needs a C compiler for the tree-sitter parsers (Xcode Command Line Tools on macOS). After the binary, make install writes the Claude Code and Codex skills described in AI Agent Integration.
Results are compact JSON on one line by default (batch modes print one line per file). --pretty indents the JSON, and --format toon or --format auto switches to TOON or to whichever of the two is estimated to take fewer tokens. TOON output follows the v4.1 specification; an empty array is [].
# 1. Start the review of a whole diff with review
astro-sight review --dir . --git
# 2. Pair context and impact around an edit
astro-sight context --dir . --git
astro-sight impact --dir . --git
# 3. Read structure with symbols, and look up identifiers with refs
astro-sight symbols --path src/main.rs
astro-sight refs --name "AppService" --dir src/
# 4. Use ast only to pin down an exact syntax node
astro-sight ast --path src/main.rs --line 10 --col 0
# 5. Put structural rules you check repeatedly in lint, and use sequence when call order matters or calls go 3 or more levels deep
astro-sight lint --path src/main.rs --rules rules.yaml
astro-sight sequence --path src/main.rs --function main
# 6. Batch two or more different queries into one session
printf '%s\n' \
'{"command":"symbols","path":"src/main.rs"}' \
'{"command":"refs","name":"AppService","dir":"src"}' \
| astro-sight sessionTypeScript/TSX export const annotation-only edits appear in api_changes.type_annotation_changes with reason: type_annotation_only and compatibility: unverified. Type compatibility needs a TypeScript check. review --hook keeps their references informational; --strict-public-type-annotations requests review through policy.type_annotation, independently of --strict-public-const-values. Annotation formatting alone is omitted and does not block either policy.
Python function parameter annotation changes to a local Protocol can appear in api_changes.modified_closed_in_diff (api.mod_closed in hook output) even when callers are unchanged. The proof follows a local variable annotated as C or C | None, initialized with C(...) or None, to a unique class declared locally or imported directly from a repository module. The class must declare every inherited Protocol method with matching parameter names, types, defaults, return types, and async behavior. Existing conforming classes also qualify.
This checks declared contracts using built-in types and containers. Aliased or relative imports, keyword or spread arguments, omitted changed parameters, complex local scopes or rebinding, decorated or inherited implementation classes, data members, unknown types, parse errors, and companion .pyi files keep the conservative blocking classification. Dynamic or transitive name resolution is outside this proof.
Adding a required property to a TypeScript interface or object type alias appears in api.mod_closed when the same diff updates every function that returns the type, every const x: T / satisfies T value, and the callers of every top-level function that takes the type as a parameter. The added property must carry a value whose type fits the new member. Generic or extends declarations, required methods, and references in unsupported positions (such as T[]) stay blocking even when the reference lines changed. See TypeScript required property additions.
astro-sight refs --name extract_symbols --dir .{
"symbol": "extract_symbols",
"refs": [
{ "path": "src/engine/symbols/mod.rs", "ln": 107, "col": 7, "ctx": "pub fn extract_symbols(...)", "kind": "def" },
{ "path": "src/commands/api_changes/exported.rs", "ln": 45, "col": 39, "ctx": "let syms = crate::engine::symbols::extract_symbols(root, source, lang_id).ok()?;", "kind": "ref" }
]
}path is relative to --dir, and ln and col are 0-based. When a name has more than 100 references or the output would exceed about 3,000 tokens, the output stops there and a result_summary describes the omitted references; --max-results and --token-budget change the limits (unlimited removes them).
Source files in a language astro-sight cannot parse (.vue, .applescript, ...) are listed in truncations instead of being skipped silently, and a scope where no file can be parsed returns UNSUPPORTED_LANGUAGE rather than an empty list. See Files astro-sight Cannot Parse.
astro-sight impact --dir . --gitUnresolved impacts found:
src/engine/symbols/mod.rs changed [extract_symbols]:
→ src/service.rs:284 [extract_symbols]
→ src/commands/api_changes/exported.rs:45 [extract_symbols]
When every caller has been updated, impact prints nothing and exits 0. Otherwise it prints the list above to stderr and exits 1. Line numbers in this text are 1-based so that an editor can open them directly.
review --hook reports TS/TSX trailing optional/default parameter removal as mod_compat with reason trailing_optional_params_removed when unchanged remaining parameters, an explicit unchanged return annotation, and a closed inventory of direct internal calls prove the calls still fit. Their references remain in impact_info. Aliases, dynamic or ambiguous dependencies, and incomplete source coverage keep the existing conservative classification. This proof covers calls in the analyzed static repository; it does not guarantee external compatibility or equivalent runtime behavior.
For a TS/TSX default export that directly registers a single object method through a local identity function with the same explicit interface parameter and return type, starting to receive existing contract parameters can be mod_compat with reason fixed_callback_contract. The interface, registration, and all source outside the implementation parameters/body must remain unchanged. Only simple required parameters and a void method contract are proved; a direct call through the target's default import is required, and observed calls must supply the contract's arguments. Object escapes, aliases, re-exports, dynamic access, incomplete parsing, and source inventories above 512 files, 8 MiB, or 100,000 nodes retain the conservative classification. The current proof requires a JS/TS-only source inventory with relative imports and no computed access or escaped string literals anywhere in that inventory. Raw context evidence remains visible; impact and the review hook accept the proven compatible change. This establishes a limited static contract proof, without guaranteeing equivalent behavior or external consumers.
The full reference is split by topic:
- Every command with its options, batch mode,
session, and the MCP server: docs/usage.md context,impact,review,dead-code, andcochangein depth: docs/diff-analysis.md- Compact keys, TOON, and
--format auto: docs/output-format.md
astro-sight init writes a configuration file to ~/.config/astro-sight/config.toml (--path writes it elsewhere; an existing file is overwritten without asking). --config <path> reads another file for a single run.
debug = false # write debug logs to files
format = "json" # default output format: "json" | "toon" | "auto"
skip_generated = true # leave generated files out of directory scans--format on the command line takes precedence over format. The log directory, the cache under ~/.cache/astro-sight/, and --no-cache are described in docs/configuration.md.
Register astro-sight as a skill so that Claude Code or Codex reaches for it on questions such as "who calls this function?" or "what does this diff affect?":
astro-sight skill-install claude # ~/.claude/skills/astro-sight/SKILL.md
astro-sight skill-install codex # ~/.codex/skills/astro-sight/SKILL.mdTo use it as an MCP server over stdio, start astro-sight mcp from the repository you want to analyze. Files outside that directory are rejected.
{
"mcpServers": {
"astro-sight": {
"command": "astro-sight",
"args": ["mcp"]
}
}
}With only the skill, an agent can still fall back to grep. Rules for CLAUDE.md / AGENTS.md that make it prefer astro-sight, and a Stop hook that keeps it working while impact reports unresolved callers, are in docs/integrations.md.
Requires mise. Tool versions are pinned in mise.toml.
make setup # Install the toolchain (mise) and dependencies
make ci # Run the same checks as CI (no changes)| Command | Description |
|---|---|
make setup |
Install the toolchain (mise) and dependencies |
make build |
Build a debug binary |
make release |
Build a release binary |
make run |
Run the debug binary (arguments via ARGS="...") |
make test |
Run the tests |
make lint |
Run clippy with warnings as errors |
make fmt |
Format the code (rewrites files) |
make fmt-check |
Check the formatting (no changes) |
make check |
Run fmt-check and lint (no changes) |
make ci |
Run the same checks as CI (no changes) |
make install |
Install the release binary to INSTALL_PATH (default /usr/local/bin) |
make uninstall |
Remove the binary from INSTALL_PATH |
make clean |
Remove build artifacts |
Run make to list every target. Releases are published from GitHub Actions (Actions → Release → Run workflow).
Besides mise, the build needs a C compiler for the tree-sitter parsers, and the tests need git. Without mise, add SYSTEM_TOOLS=1 to use the tools on PATH. When a [patch] in .cargo/config.toml swaps in local tree-sitter crates, Cargo.lock changes only on your machine and --locked stops the build; run make ci CARGO_FLAGS= in that case.
Choosing the skills that make install writes, heap profiling, the usage statistics tool, and how a release is built are covered in docs/development.md.
