Skip to content

Architecture

Overview

axm-audit follows a layered architecture with clear separation of concerns:

graph TB
    API["CLI / audit_project()"] --> Auditor["get_rules_for_category()"]
    Auditor --> Rules["24 Rules · 10 Categories"]
    Rules -->|subprocess| Runner["run_in_project()"]
    Rules -->|direct| AST["ast · radon · tomllib"]
    Runner --> Tools["Ruff · mypy · Bandit\npip-audit · deptry · pytest-cov"]
    Hooks["AutofixHook"] -->|subprocess| Runner
    Rules --> Result["AuditResult"]
    Result --> Fmt["format_report · format_json · format_agent"]

Layers

1. Public API

  • CLIaxm-audit audit . via cyclopts
  • audit_project() — Python entry point
  • get_rules_for_category() — Get rule instances, optionally filtered

Both return typed Pydantic models for safe agent consumption.

2. Rule Engine

get_rules_for_category() returns rule instances from the auto-discovery registry (populated by @register_rule decorators):

Category Rules Count
lint LintingRule, FormattingRule, DiffSizeRule, DeadCodeRule 4
type TypeCheckRule 1
complexity ComplexityRule 1
security SecurityRule, SecurityPatternRule 2
deps DependencyAuditRule, DependencyHygieneRule 2
testing TestCoverageRule 1
architecture CircularImportRule, GodClassRule, CouplingMetricRule, DuplicationRule 4
practices DocstringCoverageRule, BareExceptRule, BlockingIORule, LoggingPresenceRule, TestMirrorRule 5
structure PyprojectCompletenessRule 1
tooling ToolAvailabilityRule 3 instances

Total: 24 rule instances across 10 categories.

3. Tool Integration

All subprocess-based rules use run_in_project() from core/runner.py, which detects the target project's .venv/ and executes tools via uv run --directory to ensure the correct environment is used. Most rules pass with_packages=[...] to inject audit dependencies (ruff, bandit, etc.) at runtime — the target project does not need these tools in its own environment. Exception: TypeCheckRule does not inject mypy — it uses the project's own mypy from the venv, ensuring the same type-stub availability and configuration as the project's pre-commit hooks. All subprocess calls have a 300-second timeout (configurable) — on timeout, a synthetic result with returncode=124 is returned to prevent indefinite hangs.

Rule Tool Integration
LintingRule Ruff run_in_project(["ruff", "check", ...])
FormattingRule Ruff run_in_project(["ruff", "format", "--check", ...])
TypeCheckRule MyPy run_in_project(["mypy", ...])
ComplexityRule Radon radon.complexity.cc_visit(source) (fallback: radon cc --json subprocess)
SecurityRule Bandit run_in_project(["bandit", ...])
DependencyAuditRule pip-audit run_in_project(["pip-audit", ...])
DependencyHygieneRule deptry run_in_project(["deptry", ...])
TestCoverageRule pytest-cov run_tests(mode="compact") via test_runner.py
Architecture rules Python ast Direct AST parsing
Structure rules tomllib TOML parsing
ToolAvailabilityRule shutil.which PATH lookup

4. Hooks

Pre-gate hooks run before quality evaluation to auto-fix common issues:

Hook Commands Behavior
AutofixHook ruff check --fix ., ruff format . Runs via run_in_project(). Returns HookResult.ok(fixed=N) with fix count parsed from ruff stdout. Skips gracefully when ruff is missing (skipped=True). Tolerates config errors (returncode 2) without failing.

5. Scoring

10-category weighted composite (see Scoring & Grades):

Category Weight
Linting 20%
Type Safety 15%
Complexity 15%
Security 10%
Dependencies 10%
Testing 15%
Architecture 10%
Practices 5%

6. Models

AuditResult, CheckResult, Severity — Pydantic models with extra = "forbid" for strict validation.

7. Output

  • Formatters: format_report() (human-readable), format_json() (machine-readable), format_agent() (agent-optimized)

Data Flow

sequenceDiagram
    participant User
    participant CLI as CLI / audit_project()
    participant Auditor
    participant Rules
    participant Tools

    User->>CLI: axm-audit audit . / audit_project(Path("."))
    CLI->>Auditor: get_rules_for_category(category)
    Auditor-->>CLI: list[ProjectRule]
    loop For each rule
        CLI->>Rules: rule.check(project_path)
        Rules->>Tools: Ruff / MyPy / Radon / Bandit / etc.
        Tools-->>Rules: raw output
        Rules-->>CLI: CheckResult
    end
    CLI-->>User: AuditResult (score, grade, checks)