Skip to content

Architecture

Overview

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

graph TD
    subgraph "User Interface"
        CLI["Generic axm CLI"]
        MCP["MCP"]
        Tools["AXMTools"]
    end

    subgraph "Core Logic"
        CheckEngine["CheckEngine"]
        Templates["Template Resolution"]
        Reserver["PyPI Reserver"]
    end

    subgraph "Checks"
        PyprojectChecks["pyproject checks"]
        CIChecks["CI checks"]
        ToolingChecks["tooling checks"]
        DocsChecks["docs checks"]
        StructureChecks["structure checks"]
        DepsChecks["deps checks"]
        ChangelogChecks["changelog checks"]
        WorkspaceChecks["workspace checks"]
        PaperChecks["paper checks"]
        ProtocolChecks["protocol checks (explicit)"]
    end

    subgraph "Adapters"
        Copier["CopierAdapter"]
        PyPI["PyPIAdapter"]
        Creds["CredentialManager"]
    end

    subgraph "External"
        CopierEngine["Copier Engine"]
        PyPIAPI["PyPI API"]
        Vault["axm-vault catalog"]
    end

    CLI --> Tools
    MCP --> Tools
    Tools --> CheckEngine
    Tools --> Templates
    Tools --> Reserver
    CheckEngine --> PyprojectChecks
    CheckEngine --> CIChecks
    CheckEngine --> ToolingChecks
    CheckEngine --> DocsChecks
    CheckEngine --> StructureChecks
    CheckEngine --> DepsChecks
    CheckEngine --> ChangelogChecks
    CheckEngine --> WorkspaceChecks
    CheckEngine --> PaperChecks
    CheckEngine -. explicit category .-> ProtocolChecks
    Reserver --> PyPI
    Reserver --> Copier
    Templates --> Copier
    Copier --> CopierEngine
    PyPI --> PyPIAPI
    Creds --> Vault

Layers

1. AXMTool interfaces (tools/)

Each request-response operation is declared once as an AXMTool. The shared axm package derives the generic CLI, MCP exposure and DAG node from its typed signature.

Command Tool Description
init_scaffold InitScaffoldTool Scaffold a new project
init_check InitCheckTool Score against AXM standard
init_reserve InitReserveTool Reserve PyPI package name

2. Core Logic (core/)

Application orchestration and domain logic separated from tool presentation:

Module Key Symbols Purpose
checker.py CheckEngine, SKIP_BY_CONTEXT, REDIRECT_BY_CONTEXT, validate_context_tables(), format_report(), format_json(), format_agent() Run checks (dynamic discovery via importlib), format output. Every result is re-stamped with the canonical check name — get_check_name()'s category.function_name_without_check_ form — so context skips (SKIP_BY_CONTEXT), member redirects (REDIRECT_BY_CONTEXT), [tool.axm-init].exclude matching, and the displayed name all key off one string
templates.py TemplateInfo, TemplateType, get_template_path(), template_chain() Exact type/framework template selection and ordered layer resolution; see template catalogue and research contracts
reserver.py reserve_pypi(), create_minimal_package(), build_package(), publish_package() PyPI name reservation workflow (the ReserveResult model lives in models/results.py)
protocol_planner.py plan_protocol_scaffold(), ProtocolScaffoldPlan, PlanOperation Pure, deterministic protocol filesystem planning; owned compatible implementations are preserved as unchanged
protocol_scaffolder.py prepare_protocol_request(), preview_protocol_scaffold() Preview or apply the planner result. Application serializes preflight-through-rollback by canonical target root, while distinct roots remain concurrent; it preflights confinement and symlinks before writing, then provides in-memory rollback for partial application failures. Missing profiles and profile-domain conflicts are rejected at the external InitScaffoldTool boundary before mutation; lower-level preview and registration deliberately retain automatic profile adoption

3. Checks (checks/)

The default Python registry spans 10 categories; each is an independently callable filesystem check (Path) → CheckResult. _discover_checks() walks checks/ with pkgutil. Modules marked explicit-only are resolved lazily when their category is selected and stay out of unfiltered runs, preserving scores and check counts for projects that did not request them:

Module Category Checks Purpose
_utils.py (internal) — Shared utilities: load_toml for TOML parsing, @requires_toml decorator that loads pyproject.toml once and short-circuits with a failure if missing. For workspace members, load_toml_with_workspace_fallback deep-merges the workspace root's tool sections as a base layer — merge_tool_sections uses _deep_merge to recursively merge nested dicts (member wins on conflicts; lists and non-dict values are replaced, not merged)
pyproject.py pyproject 10
ci.py CI 7 declared, 6 active (workflow_exists superseded)
tooling.py tooling 7
docs.py docs 7
structure.py structure 7
deps.py deps 2
changelog.py changelog 2
workspace.py workspace 10
paper.py (in axm-lab) paper 3 Form checks and implementation boundary
protocols.py protocols (explicit-only) 2 Statically validate [tool.axm-init.protocols], distribution/module identity, required package layout, and bidirectional component inventory. A separate resource check verifies both that every declared prompts/*.md file exists and that Hatch force-include ships the protocol package; at a workspace root, both checks aggregate profiled-member findings under each member's identity, without importing inspected code
_workspace.py (internal) — Context detection and uv workspace resolution; see context policy

4. Adapters (adapters/)

Each adapter wraps a single external dependency:

Adapter Wraps Purpose
CopierAdapter / CopierConfig copier.run_copy() Template-based scaffolding, including ordered multi-layer application in one destination (CopierConfig is the Pydantic input model)
PyPIAdapter / AvailabilityStatus PyPI JSON API Package name availability check
CredentialManager axm-vault catalog (PYPI_API_TOKEN or pypi.token); optional interactive adapter method Token retrieval, validation, and persistence (returns False on PermissionError)
patch_all() / PatchReport pyproject.toml, Makefile, CI workflows Workspace root file patching after member scaffold; returns a PatchReport that truthfully partitions files into patched (real writes only), skipped (no-op or absent), and failed (caught PermissionError/UnicodeDecodeError — partial-state signal, never raised)

CopierAdapter.apply_chain() applies TemplateLayer values in declaration order. Caller data is merged with each layer's own data, with layer values taking precedence. Every layer receives a distinct .copier-answers.<layer>.yml file: without that separation, a later template would replace the first template's ownership record. Keeping the records independent lets Copier regenerate files owned by an earlier layer while _skip_if_exists continues to preserve user-owned files declared by a later layer during reapplication.

Standalone learning scaffolds use this composition deliberately: the ordinary Python project owns repository-wide tooling and the learning template owns only the domain overlay. After both layers render, the learning profile is merged back into the composed pyproject.toml; this preserves the base tool tables while adding the requested domain and the training entry point. Workspace members remain single-layer learning scaffolds because their repository tooling is owned by the workspace root.

Credential resolution

CredentialManager.get_pypi_token() resolves the declared pypi/token credential from the axm-vault catalog. The catalog owns its own resolution layers, including PYPI_API_TOKEN; this method returns None if resolution fails. The reservation tool calls this non-interactive method.

CredentialManager.resolve_pypi_token() returns that catalog value when it is available. Otherwise, it exits with code 1 in non-interactive sessions and points the user to PYPI_API_TOKEN or the axm-vault catalog. On a TTY it prompts for a pypi- token, validates it, persists it as pypi/token in the catalog, and returns the typed value.

5. Models (models/)

Pydantic models for structured data exchange between layers:

Model Module Purpose
CheckResult check.py Single check outcome (passed, message, fix)
CategoryScore check.py Aggregated score per category
ProjectResult check.py Full project check result
Grade check.py A–F grade enum
ScaffoldResult results.py Outcome of a scaffolding operation

6. Tools (tools/)

MCP tool wrappers for AI agent integration. All tools satisfy the AXMTool protocol (imported from axm.tools.base).

Tool Class Entry Point
init_check InitCheckTool axm.tools → init_check
init_scaffold InitScaffoldTool axm.tools → init_scaffold
init_reserve InitReserveTool axm.tools → init_reserve

Design Decisions

Decision Rationale
Hexagonal architecture Testable core, swappable adapters
Pydantic models Structured validation and serialization
Copier for project scaffolding Jinja2 templates, supports project updates
Private Python packages by default A private marker is reversible by removing one classifier; an unintended PyPI upload is irreversible, so standalone and member templates choose the safe failure mode
Plans for protocol scaffolding The deterministic plan is authoritative for preview and application; a canonical-root lock covers snapshot, preflight, writes and rollback so compatible concurrent declarations cannot overwrite one another, without serializing distinct roots
src/ layout PEP 621 best practice, no import conflicts
Independent check functions Each check takes a project path and returns a CheckResult; filesystem access remains explicit in tests
Dynamic check registry checker.py discovers default checks via importlib/inspect; explicit-only categories are loaded by name only when selected, avoiding score and count drift in unrelated projects
Parallel check execution ThreadPoolExecutor — checks are I/O-bound and independent

Protocol planning/application is implemented in core/protocol_planner.py and core/protocol_scaffolder.py. Its declaration contract and workflow describe preflight and rollback.