Index
axm_ast
axm-ast — AST introspection CLI for AI agents, powered by tree-sitter.
This package provides deterministic, fast parsing of Python libraries to extract structured information (functions, classes, imports, docstrings, call graphs) at multiple granularity levels.
Example
from axm_ast import analyze_package from pathlib import Path
pkg = analyze_package(Path("src/mylib")) [m.path.name for m in pkg.modules]
['__init__.py', 'core.py', 'utils.py']
CallSite
Bases: BaseModel
A single function/method call location.
Example
cs = CallSite( ... module="cli", ... symbol="greet", ... line=42, ... column=8, ... context="main", ... call_expression='greet("world")', ... ) cs.module 'cli'
Source code in packages/axm-ast/src/axm_ast/models/calls.py
ClassInfo
Bases: BaseModel
Metadata for a single class.
Example
cls = ClassInfo(name="Parser", line_start=1, line_end=50) cls.is_public True
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
is_public
property
Whether this class is part of the public API.
DeadSymbol
dataclass
An unreferenced symbol detected by dead code analysis.
Source code in packages/axm-ast/src/axm_ast/core/dead_code.py
FlowStep
Bases: BaseModel
A single step in a traced execution flow.
Source code in packages/axm-ast/src/axm_ast/core/flows.py
FunctionInfo
Bases: BaseModel
Metadata for a single function or method.
Example
fn = FunctionInfo(name="parse", line_start=10, line_end=25) fn.is_public True
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
is_public
property
Whether this function is part of the public API.
model_post_init(__context)
Compute signature if not explicitly provided.
Strips Annotated[T, ...] wrappers from parameter and return-type
annotations so that generated signatures show only the base type.
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
FunctionKind
Bases: StrEnum
Classification of a callable based on its decorators.
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
ImportInfo
Bases: BaseModel
A single import statement.
Example
imp = ImportInfo(module="pathlib", names=["Path"]) imp.is_relative False
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
ModuleInfo
Bases: BaseModel
Full introspection result for a single Python module.
Example
mod = ModuleInfo(path=Path("foo.py")) len(mod.functions) 0
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
public_classes
property
Classes that are part of the public API.
public_functions
property
Functions that are part of the public API.
PackageInfo
Bases: BaseModel
Full introspection result for a Python package.
Example
pkg = PackageInfo(name="mylib", root=Path("src/mylib")) len(pkg.modules) 0
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
module_names
property
List of dotted module names.
public_api
property
All public functions and classes across the package.
ParameterInfo
Bases: BaseModel
A single function/method parameter.
Example
p = ParameterInfo(name="path", annotation="Path", default="None") p.name 'path'
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
StructuralDiffResult
Bases: TypedDict
Output of :func:structural_diff.
total=False so the error variant ({"error": str}) also matches.
Source code in packages/axm-ast/src/axm_ast/core/structural_diff.py
| Python | |
|---|---|
VariableInfo
Bases: BaseModel
A module-level variable or constant.
Example
v = VariableInfo(name="all", line=5) v.name 'all'
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
WorkspaceInfo
Bases: BaseModel
Multi-package workspace introspection result.
Aggregates multiple PackageInfo from a uv workspace.
Example
ws = WorkspaceInfo(name="my-ws", root=Path("/ws")) len(ws.packages) 0
Source code in packages/axm-ast/src/axm_ast/models/nodes.py
analyze_package(path)
Analyze a Python package directory.
Discovers all .py files, parses them with tree-sitter, and
builds a complete PackageInfo with dependency edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
Path to the package root directory. |
required |
Returns:
| Type | Description |
|---|---|
PackageInfo
|
PackageInfo with all modules and dependency edges. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If path is not a directory. |
Example
pkg = analyze_package(Path("src/mylib")) pkg.name 'mylib'
Source code in packages/axm-ast/src/axm_ast/core/analyzer.py
analyze_workspace(path, *, detected=None)
Analyze all packages in a uv workspace.
Discovers workspace members, analyzes each with analyze_package(),
and builds inter-package dependency edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
Path to workspace root. |
required |
detected
|
WorkspaceInfo | None
|
A |
None
|
Returns:
| Type | Description |
|---|---|
WorkspaceInfo
|
WorkspaceInfo with all packages and dependency edges. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If path is not a workspace root. |
Example
ws = analyze_workspace(Path("/path/to/workspace")) len(ws.packages) > 0 True
Source code in packages/axm-ast/src/axm_ast/core/workspace.py
build_workspace_module_graph(ws)
Build a merged module-level import graph across all packages.
Reuses :func:build_import_graph per package and namespaces every
node as {package_name}.{module}. Cross-package import targets are
resolved to their owning package so edges stay namespaced rather than
bare module names (lets anvil tell which package each node belongs to).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ws
|
WorkspaceInfo
|
Analyzed workspace info with |
required |
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
Adjacency-list dict mapping |
dict[str, list[str]]
|
|
Example
graph = build_workspace_module_graph(ws) graph["axm-mcp.cli"]
['axm.tools']
Source code in packages/axm-ast/src/axm_ast/core/workspace.py
find_callers(pkg, symbol)
Find all call-sites of a given symbol across a package.
Searches every module in the package for calls matching the given symbol name. Uses cached call-sites when available to avoid re-parsing files on repeated queries.
.. warning:: Matching is by name only. The receiver is ignored, so
self.foo(), obj.foo() and a bare foo() all collapse to the
name foo. This is an intrinsic tree-sitter limitation — no type
inference is performed — and it may surface false-positive callers
that call a distinct, like-named symbol on a different receiver.
Each returned :class:~axm_ast.models.calls.CallSite carries a
syntactic confidence (1.0 for direct/self calls, lower for an
attribute call on another receiver) to help triage this ambiguity.
The set of callers returned is never affected by confidence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pkg
|
PackageInfo
|
Analyzed package info. |
required |
symbol
|
str
|
Name of the function/method to search for. |
required |
Returns:
| Type | Description |
|---|---|
list[CallSite]
|
List of CallSite objects where the symbol is called. |
Example
results = find_callers(pkg, "greet") results[0].module 'cli'
Source code in packages/axm-ast/src/axm_ast/core/callers.py
find_dead_code(pkg, *, include_tests=False)
Detect unreferenced symbols across a package.
Algorithm
- Enumerate all functions and classes across all modules.
- For each symbol, check if it has any callers or references.
- Apply exemptions (dunders, tests, exports, decorators, entry points, etc.).
- For methods, check override chains.
- Also scan a sibling
tests/directory for callers. - Detect lazy imports inside function bodies.
.. warning:: Reference matching is by name only. Liveness is decided
against a single global set[str] of referenced names, so a dead
symbol that shares its name with a live, distinct symbol elsewhere is
wrongly considered referenced and omitted from the result (a false
negative). This is an intrinsic tree-sitter limitation — no type or
scope inference is performed — and mirrors the homonym ambiguity
documented on :func:~axm_ast.core.callers.find_callers. Symbols
reported as dead are therefore high-confidence; truly-dead symbols that
are homonymous with a live one may be silently missed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pkg
|
PackageInfo
|
Analyzed package from |
required |
include_tests
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
list[DeadSymbol]
|
List of dead symbols, sorted by module path then line number. |
Source code in packages/axm-ast/src/axm_ast/core/dead_code.py
search_symbols(pkg, *, name=None, returns=None, kind=None, inherits=None)
Search for symbols across a package with filters.
All filters are AND-combined. A symbol must match all provided filters to be included in results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pkg
|
PackageInfo
|
Analyzed package info. |
required |
name
|
str | None
|
Filter by symbol name (substring match). |
None
|
returns
|
str | None
|
Filter functions by return type (substring match). |
None
|
kind
|
SymbolKind | None
|
Filter by SymbolKind (function, method, property, classmethod, staticmethod, abstract, class, variable). |
None
|
inherits
|
str | None
|
Filter classes by base class name. |
None
|
Returns:
| Type | Description |
|---|---|
list[tuple[str, FunctionInfo | ClassInfo | VariableInfo]]
|
List of (module_name, symbol) tuples for matching symbols. |
Example
results = search_symbols(pkg, returns="str") [sym.name for _, sym in results]
['greet', 'version']
Source code in packages/axm-ast/src/axm_ast/core/analyzer.py
structural_diff(pkg_path, base, head)
Compare two git refs at symbol level.
Uses git worktrees to checkout the base ref, runs
analyze_package() on both versions, and diffs the
symbol sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pkg_path
|
Path
|
Path to the package directory. |
required |
base
|
str
|
Base git ref (branch, tag, or commit). |
required |
head
|
str
|
Head git ref (branch, tag, or commit). |
required |
Returns:
| Type | Description |
|---|---|
StructuralDiffResult
|
Dict with |
StructuralDiffResult
|
|
StructuralDiffResult
|
|
Example
result = structural_diff(Path("src/mylib"), "main", "feature") len(result["added"]) 3
Source code in packages/axm-ast/src/axm_ast/core/structural_diff.py
trace_flow(pkg, entry, *, max_depth=5, cross_module=False, detail='trace', callee_index=None, exclude_stdlib=True)
Trace execution flow from an entry point via BFS.
Follows the forward call graph from entry up to max_depth levels deep. Uses a visited set to handle circular calls.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pkg
|
PackageInfo
|
Analyzed package info. |
required |
entry
|
str
|
Name of the entry point function to trace from. |
required |
max_depth
|
int
|
Maximum BFS depth (default 5). |
5
|
cross_module
|
bool
|
If True, resolve imports and continue BFS into external modules on-demand. |
False
|
detail
|
str
|
Level of detail — |
'trace'
|
callee_index
|
dict[tuple[str, str], list[CallSite]] | None
|
Optional pre-computed index from
:func: |
None
|
exclude_stdlib
|
bool
|
If True (default), skip callees whose name
matches a stdlib module or Python builtin (e.g. |
True
|
Returns:
| Type | Description |
|---|---|
list[FlowStep]
|
Tuple of (steps, truncated) where steps is a list of FlowStep |
bool
|
objects ordered by depth then discovery, and truncated is True |
tuple[list[FlowStep], bool]
|
when at least one frontier node at max_depth had unexpanded |
tuple[list[FlowStep], bool]
|
children. |
Example
steps, truncated = trace_flow(pkg, "main", max_depth=3) for s in steps: ... print(f"{' ' * s.depth}{s.name} ({s.module}:{s.line})")
Source code in packages/axm-ast/src/axm_ast/core/flows.py
| Python | |
|---|---|
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 | |