Skip to content

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']

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
class ClassInfo(BaseModel):
    """Metadata for a single class.

    Example:
        >>> cls = ClassInfo(name="Parser", line_start=1, line_end=50)
        >>> cls.is_public
        True
    """

    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Class name")
    bases: list[str] = Field(default_factory=list, description="Base class names")
    methods: list[FunctionInfo] = Field(default_factory=list, description="Methods")
    docstring: str | None = Field(default=None, description="Docstring content")
    decorators: list[str] = Field(default_factory=list, description="Decorator names")
    line_start: int = Field(description="Start line (1-indexed)")
    line_end: int = Field(description="End line (1-indexed)")

    @property
    def is_public(self) -> bool:
        """Whether this class is part of the public API."""
        return not self.name.startswith("_")
is_public property

Whether this class is part of the public API.

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
class FunctionInfo(BaseModel):
    """Metadata for a single function or method.

    Example:
        >>> fn = FunctionInfo(name="parse", line_start=10, line_end=25)
        >>> fn.is_public
        True
    """

    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Function/method name")
    params: list[ParameterInfo] = Field(default_factory=list, description="Parameters")
    return_type: str | None = Field(default=None, description="Return type annotation")
    docstring: str | None = Field(default=None, description="Docstring content")
    decorators: list[str] = Field(default_factory=list, description="Decorator names")
    kind: FunctionKind = Field(
        default=FunctionKind.FUNCTION, description="Callable classification"
    )
    line_start: int = Field(description="Start line (1-indexed)")
    line_end: int = Field(description="End line (1-indexed)")
    is_async: bool = Field(default=False, description="Whether function is async")

    @property
    def is_public(self) -> bool:
        """Whether this function is part of the public API."""
        return not self.name.startswith("_")

    @property
    def signature(self) -> str:
        """Human-readable signature string."""
        params_str = ", ".join(
            p.name + (f": {p.annotation}" if p.annotation else "") for p in self.params
        )
        ret = f" -> {self.return_type}" if self.return_type else ""
        prefix = "async " if self.is_async else ""
        return f"{prefix}def {self.name}({params_str}){ret}"
is_public property

Whether this function is part of the public API.

signature property

Human-readable signature string.

FunctionKind

Bases: StrEnum

Classification of a callable based on its decorators.

Source code in packages/axm-ast/src/axm_ast/models/nodes.py
class FunctionKind(enum.StrEnum):
    """Classification of a callable based on its decorators."""

    FUNCTION = "function"
    METHOD = "method"
    PROPERTY = "property"
    CLASSMETHOD = "classmethod"
    STATICMETHOD = "staticmethod"
    ABSTRACT = "abstract"

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
class ImportInfo(BaseModel):
    """A single import statement.

    Example:
        >>> imp = ImportInfo(module="pathlib", names=["Path"])
        >>> imp.is_relative
        False
    """

    model_config = ConfigDict(extra="forbid")

    module: str | None = Field(
        default=None, description="Module path (None for 'import x')"
    )
    names: list[str] = Field(default_factory=list, description="Imported names")
    alias: str | None = Field(default=None, description="Alias (as ...)")
    is_relative: bool = Field(default=False, description="Relative import")
    level: int = Field(default=0, description="Number of leading dots")

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
class ModuleInfo(BaseModel):
    """Full introspection result for a single Python module.

    Example:
        >>> mod = ModuleInfo(path=Path("foo.py"))
        >>> len(mod.functions)
        0
    """

    model_config = ConfigDict(extra="forbid")

    path: Path = Field(description="File path")
    docstring: str | None = Field(default=None, description="Module-level docstring")
    functions: list[FunctionInfo] = Field(
        default_factory=list, description="Top-level functions"
    )
    classes: list[ClassInfo] = Field(
        default_factory=list, description="Top-level classes"
    )
    imports: list[ImportInfo] = Field(
        default_factory=list, description="Import statements"
    )
    variables: list[VariableInfo] = Field(
        default_factory=list, description="Module-level variables"
    )
    all_exports: list[str] | None = Field(
        default=None,
        description="Contents of __all__, None if not defined",
    )

    @property
    def public_functions(self) -> list[FunctionInfo]:
        """Functions that are part of the public API."""
        if self.all_exports is not None:
            return [f for f in self.functions if f.name in self.all_exports]
        return [f for f in self.functions if f.is_public]

    @property
    def public_classes(self) -> list[ClassInfo]:
        """Classes that are part of the public API."""
        if self.all_exports is not None:
            return [c for c in self.classes if c.name in self.all_exports]
        return [c for c in self.classes if c.is_public]
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
class PackageInfo(BaseModel):
    """Full introspection result for a Python package.

    Example:
        >>> pkg = PackageInfo(name="mylib", root=Path("src/mylib"))
        >>> len(pkg.modules)
        0
    """

    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Package name")
    root: Path = Field(description="Package root directory")
    modules: list[ModuleInfo] = Field(default_factory=list, description="All modules")
    dependency_edges: list[tuple[str, str]] = Field(
        default_factory=list,
        description="Internal import edges (from_module, to_module)",
    )

    @property
    def public_api(self) -> list[FunctionInfo | ClassInfo]:
        """All public functions and classes across the package."""
        result: list[FunctionInfo | ClassInfo] = []
        for mod in self.modules:
            result.extend(mod.public_functions)
            result.extend(mod.public_classes)
        return result

    @property
    def module_names(self) -> list[str]:
        """List of dotted module names."""
        names: list[str] = []
        for mod in self.modules:
            try:
                rel = mod.path.relative_to(self.root)
            except ValueError:
                names.append(mod.path.stem)
                continue
            parts = list(rel.with_suffix("").parts)
            if parts and parts[-1] == "__init__":
                parts = parts[:-1]
            if parts:
                names.append(".".join(parts))
            else:
                names.append(self.name)
        return names
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
class ParameterInfo(BaseModel):
    """A single function/method parameter.

    Example:
        >>> p = ParameterInfo(name="path", annotation="Path", default="None")
        >>> p.name
        'path'
    """

    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Parameter name")
    annotation: str | None = Field(
        default=None, description="Type annotation as string"
    )
    default: str | None = Field(default=None, description="Default value as string")

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
class VariableInfo(BaseModel):
    """A module-level variable or constant.

    Example:
        >>> v = VariableInfo(name="__all__", line=5)
        >>> v.name
        '__all__'
    """

    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Variable name")
    annotation: str | None = Field(default=None, description="Type annotation")
    value_repr: str | None = Field(
        default=None, description="Short repr of assigned value"
    )
    line: int = Field(description="Line number (1-indexed)")

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
def analyze_package(path: Path) -> PackageInfo:
    """Analyze a Python package directory.

    Discovers all ``.py`` files, parses them with tree-sitter, and
    builds a complete ``PackageInfo`` with dependency edges.

    Args:
        path: Path to the package root directory.

    Returns:
        PackageInfo with all modules and dependency edges.

    Raises:
        ValueError: If path is not a directory.

    Example:
        >>> pkg = analyze_package(Path("src/mylib"))
        >>> pkg.name
        'mylib'
    """
    path = Path(path).resolve()
    if not path.is_dir():
        msg = f"{path} is not a directory"
        raise ValueError(msg)

    t0 = time.perf_counter()

    # Discover all .py files, skipping virtual envs and caches
    py_files = sorted(_discover_py_files(path))
    modules: list[ModuleInfo] = []
    for py_file in py_files:
        modules.append(extract_module_info(py_file))

    # Build dependency edges from internal imports
    dep_edges = _build_edges(modules, path)

    pkg = PackageInfo(
        name=path.name,
        root=path,
        modules=modules,
        dependency_edges=dep_edges,
    )

    elapsed = time.perf_counter() - t0
    logger.debug("Analyzed %s in %.2fs (%d modules)", path.name, elapsed, len(modules))

    return pkg

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[FunctionInfo | ClassInfo | VariableInfo]

List of matching symbols.

Example

results = search_symbols(pkg, returns="str") [r.name for r in results]['greet', 'version']

Source code in packages/axm-ast/src/axm_ast/core/analyzer.py
def search_symbols(
    pkg: PackageInfo,
    *,
    name: str | None = None,
    returns: str | None = None,
    kind: SymbolKind | None = None,
    inherits: str | None = None,
) -> list[FunctionInfo | ClassInfo | VariableInfo]:
    """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.

    Args:
        pkg: Analyzed package info.
        name: Filter by symbol name (substring match).
        returns: Filter functions by return type (substring match).
        kind: Filter by SymbolKind (function, method, property,
            classmethod, staticmethod, abstract, class, variable).
        inherits: Filter classes by base class name.

    Returns:
        List of matching symbols.

    Example:
        >>> results = search_symbols(pkg, returns="str")
        >>> [r.name for r in results]
        ['greet', 'version']
    """
    results: list[FunctionInfo | ClassInfo | VariableInfo] = []

    for mod in pkg.modules:
        results.extend(
            _search_module(
                mod,
                name=name,
                returns=returns,
                kind=kind,
                inherits=inherits,
            )
        )

    return results