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.
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)
|
Tool name used for MCP registration.
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 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
|
|
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,
}
|