Skip to content

Verify

verify

Verify tool — consolidated quality check with AST enrichment.

Orchestrates axm-audit + axm-init check in one shot, then enriches failures with AST context from axm-ast (callers, impact, test files).

This module is decoupled: it receives discovered tools as a dict and calls them via Python. No subprocess nesting.

VerifyTool

AXMTool wrapper around :func:verify_project.

Returns ToolResult(data=..., text=...) so MCP consumers see a compact rendered report while programmatic callers (hooks, gates) can still read the structured data dict.

Source code in packages/axm-mcp/src/axm_mcp/verify.py
Python
class VerifyTool:
    """AXMTool wrapper around :func:`verify_project`.

    Returns ``ToolResult(data=..., text=...)`` so MCP consumers see a
    compact rendered report while programmatic callers (hooks, gates)
    can still read the structured ``data`` dict.
    """

    agent_hint = (
        "One-shot project verification: audit + init check + AST enrichment. "
        "Returns compact text plus structured data."
    )

    def __init__(self, tools: Mapping[str, object] | None = None) -> None:
        # The discovery layer hands us a ``Mapping[str, ToolEntry]`` (structural
        # protocols), but every entry-point-registered tool is in fact an
        # ``AXMTool`` instance. We accept ``object`` at the boundary so
        # ``mcp_app.py`` stays decoupled from ``axm.*`` core, then trust the
        # runtime invariant via a cast.
        self._tools: Mapping[str, AXMTool] = cast(
            "Mapping[str, AXMTool]", tools if tools is not None else {}
        )

    @property
    def name(self) -> str:
        """Tool name used for MCP registration."""
        return "verify"

    def execute(self, *, path: str = ".", **_: object) -> ToolResult:
        """One-shot project verification: audit + init check + AST enrichment.

        Args:
            path: Path to project root to verify.
        """
        data = verify_project(str(path), self._tools)
        text = format_verify_text(data)
        return ToolResult(success=True, data=data, text=text)
name property

Tool name used for MCP registration.

execute(*, path='.', **_)

One-shot project verification: audit + init check + AST enrichment.

Parameters:

Name Type Description Default
path str

Path to project root to verify.

'.'
Source code in packages/axm-mcp/src/axm_mcp/verify.py
Python
def execute(self, *, path: str = ".", **_: object) -> ToolResult:
    """One-shot project verification: audit + init check + AST enrichment.

    Args:
        path: Path to project root to verify.
    """
    data = verify_project(str(path), self._tools)
    text = format_verify_text(data)
    return ToolResult(success=True, data=data, text=text)

enrich_failure(tools, path, failure)

Enrich a failure with aggregated AST context.

Calls _extract_symbols, then ast_impact on each symbol. Impact scores are ordinal strings (LOW < MEDIUM < HIGH); the maximum across all symbols is kept.

Returns aggregated context dict or None if no enrichment possible.

Source code in packages/axm-mcp/src/axm_mcp/verify.py
Python
def enrich_failure(
    tools: Mapping[str, AXMTool],
    path: str,
    failure: dict[str, object],
) -> dict[str, object] | None:
    """Enrich a failure with aggregated AST context.

    Calls _extract_symbols, then ast_impact on each symbol.
    Impact scores are ordinal strings (LOW < MEDIUM < HIGH); the
    maximum across all symbols is kept.

    Returns aggregated context dict or None if no enrichment possible.
    """
    ast_tool = tools.get("ast_impact")
    if ast_tool is None:
        return None

    symbols = _extract_symbols(failure)
    if not symbols:
        return None

    agg = _ImpactAggregate()
    for symbol in symbols:
        _aggregate_symbol_impact(ast_tool, path, symbol, agg)

    if agg.success_count == 0:
        return None

    callers = agg.callers
    total_callers = len(callers)
    if total_callers > _MAX_CALLERS:
        callers = callers[:_MAX_CALLERS]
        callers.append(
            {
                "note": f"... and {total_callers - _MAX_CALLERS} "
                "more callers omitted for brevity"
            }
        )

    return {
        "affected_modules": list(dict.fromkeys(symbols)),
        "callers": callers,
        "test_files": list(dict.fromkeys(agg.test_files)),
        "impact_score": agg.max_score,
        "symbols_analyzed": agg.success_count,
    }

run_tool(tools, tool_name, **kwargs)

Run a discovered tool, returning its data or None if unavailable.

Source code in packages/axm-mcp/src/axm_mcp/verify.py
Python
def run_tool(
    tools: Mapping[str, AXMTool],
    tool_name: str,
    **kwargs: object,
) -> dict[str, object] | None:
    """Run a discovered tool, returning its data or None if unavailable."""
    tool = tools.get(tool_name)
    if tool is None:
        logger.info("Tool '%s' not installed, skipping.", tool_name)
        return None

    try:
        result = tool.execute(**kwargs)
        if result.success:
            data = cast(dict[str, object], result.data)
            return data
        logger.warning("Tool '%s' failed: %s", tool_name, result.error)
        return {"error": result.error}
    except Exception as exc:  # noqa: BLE001
        logger.warning("Tool '%s' raised: %s", tool_name, exc, exc_info=True)
        return {"error": str(exc)}

verify_project(path, tools)

One-shot project verification: audit + init check + AST enrichment.

Parameters:

Name Type Description Default
path str

Path to project root.

required
tools Mapping[str, AXMTool]

Dict of discovered tools (from discover_tools()).

required

Returns:

Type Description
dict[str, object]

Consolidated result with 'audit' and 'governance' sections.

dict[str, object]

Each section is None if the corresponding tool is not installed.

Source code in packages/axm-mcp/src/axm_mcp/verify.py
Python
def verify_project(
    path: str,
    tools: Mapping[str, AXMTool],
) -> dict[str, object]:
    """One-shot project verification: audit + init check + AST enrichment.

    Args:
        path: Path to project root.
        tools: Dict of discovered tools (from ``discover_tools()``).

    Returns:
        Consolidated result with 'audit' and 'governance' sections.
        Each section is None if the corresponding tool is not installed.
    """
    audit_data = run_tool(tools, "audit", path=path)
    governance_data = run_tool(tools, "init_check", path=path)

    # Enrich audit failures with AST context
    if audit_data is not None:
        failed_raw = audit_data.get("failed", [])
        failed = cast(list[dict[str, object]], failed_raw) if failed_raw else []
        if failed and "ast_impact" in tools:
            for failure in failed:
                context = enrich_failure(tools, path, failure)
                if context:
                    failure["context"] = context

    return {
        "audit": audit_data,
        "governance": governance_data,
    }