Skip to content

Coupling gaps

coupling_gaps

CouplingGapsTool — surface lower-bound coupling gaps for a symbol.

Thin AXMTool wrapper over the already-shipping axm_ast.core.coupling_gaps.analyze_coupling_gaps analysis engine. Reads a package, delegates the three coupling passes (reference, structural Protocol/ABC and contract-literal), and renders a dual-format ToolResult — the raw per-symbol collections under data and a compact caveat + counts under text. It never writes to the analysed package.

CouplingGapsTool

Bases: AXMTool

Surface the coupling a reference-only walk misses for a symbol.

Registered as ast_coupling_gaps via the axm.tools entry point. Read-only: it loads the package and delegates to :func:axm_ast.core.coupling_gaps.analyze_coupling_gaps, never writing to the analysed tree.

Source code in packages/axm-ast/src/axm_ast/tools/coupling_gaps.py
Python
class CouplingGapsTool(AXMTool):
    """Surface the coupling a reference-only walk misses for a symbol.

    Registered as ``ast_coupling_gaps`` via the axm.tools entry point.
    Read-only: it loads the package and delegates to
    :func:`axm_ast.core.coupling_gaps.analyze_coupling_gaps`, never writing to
    the analysed tree.
    """

    agent_hint: str = (
        "Surface lower-bound coupling gaps for a symbol — the structural"
        " Protocol/ABC and contract-literal sites ast_impact's reference walk"
        " misses. Read-only; omit the symbol to scan the whole public API."
    )

    @property
    def name(self) -> str:
        """Return tool name for registry lookup."""
        return "ast_coupling_gaps"

    @safe_execute
    def execute(
        self,
        *,
        path: str = ".",
        symbol: str | None = None,
        symbols: list[str] | None = None,
        **kwargs: object,
    ) -> ToolResult:
        """Report the lower-bound coupling gaps for one or more symbols.

        Args:
            path: Path to the package directory to analyse.
            symbol: A single symbol to analyse.
            symbols: A batch of symbols to analyse. When both ``symbol`` and
                ``symbols`` are omitted, the package's public API is scanned.
            **kwargs: Ignored extra options (interface tolerance).

        Returns:
            ToolResult whose ``data`` carries the ``reference_coupled``,
            ``protocol_coupled`` and ``value_coupled`` per-symbol collections,
            and whose ``text`` renders the lower-bound caveat plus site counts.
        """
        project_path = Path(path).resolve()
        if not project_path.is_dir():
            return ToolResult(success=False, error=f"Not a directory: {project_path}")

        from axm_ast.core.cache import get_package
        from axm_ast.core.coupling_gaps import analyze_coupling_gaps

        pkg = get_package(project_path)
        targets = self._resolve_targets(pkg, symbol, symbols)
        if not targets:
            return ToolResult(success=False, error="no symbols available to analyse")

        result = analyze_coupling_gaps(pkg, targets)
        return ToolResult(
            success=True,
            data=cast("dict[str, object]", dict(result)),
            text=_render_text(result),
        )

    @staticmethod
    def _resolve_targets(
        pkg: object,
        symbol: str | None,
        symbols: list[str] | None,
    ) -> list[str]:
        """Resolve the symbols to analyse.

        Explicit ``symbols``/``symbol`` win; otherwise fall back to the
        package's public API so a bare ``ast_coupling_gaps <pkg>`` invocation
        yields a whole-package report.
        """
        if symbols:
            return list(symbols)
        if symbol:
            return [symbol]
        public_api = getattr(pkg, "public_api", [])
        names = [getattr(member, "name", None) for member in public_api]
        return list(dict.fromkeys(n for n in names if isinstance(n, str)))
name property

Return tool name for registry lookup.

execute(*, path='.', symbol=None, symbols=None, **kwargs)

Report the lower-bound coupling gaps for one or more symbols.

Parameters:

Name Type Description Default
path str

Path to the package directory to analyse.

'.'
symbol str | None

A single symbol to analyse.

None
symbols list[str] | None

A batch of symbols to analyse. When both symbol and symbols are omitted, the package's public API is scanned.

None
**kwargs object

Ignored extra options (interface tolerance).

{}

Returns:

Type Description
ToolResult

ToolResult whose data carries the reference_coupled,

ToolResult

protocol_coupled and value_coupled per-symbol collections,

ToolResult

and whose text renders the lower-bound caveat plus site counts.

Source code in packages/axm-ast/src/axm_ast/tools/coupling_gaps.py
Python
@safe_execute
def execute(
    self,
    *,
    path: str = ".",
    symbol: str | None = None,
    symbols: list[str] | None = None,
    **kwargs: object,
) -> ToolResult:
    """Report the lower-bound coupling gaps for one or more symbols.

    Args:
        path: Path to the package directory to analyse.
        symbol: A single symbol to analyse.
        symbols: A batch of symbols to analyse. When both ``symbol`` and
            ``symbols`` are omitted, the package's public API is scanned.
        **kwargs: Ignored extra options (interface tolerance).

    Returns:
        ToolResult whose ``data`` carries the ``reference_coupled``,
        ``protocol_coupled`` and ``value_coupled`` per-symbol collections,
        and whose ``text`` renders the lower-bound caveat plus site counts.
    """
    project_path = Path(path).resolve()
    if not project_path.is_dir():
        return ToolResult(success=False, error=f"Not a directory: {project_path}")

    from axm_ast.core.cache import get_package
    from axm_ast.core.coupling_gaps import analyze_coupling_gaps

    pkg = get_package(project_path)
    targets = self._resolve_targets(pkg, symbol, symbols)
    if not targets:
        return ToolResult(success=False, error="no symbols available to analyse")

    result = analyze_coupling_gaps(pkg, targets)
    return ToolResult(
        success=True,
        data=cast("dict[str, object]", dict(result)),
        text=_render_text(result),
    )