Skip to main content

Deterministic CST-based refactoring toolkit for Python — move, rename, split, merge symbols atomically across files.

Project description

axm-anvil

Deterministic CST-based refactoring toolkit for Python.

axm-audit axm-init Coverage Python 3.12+


Overview

Moving symbols (classes, functions, constants) between Python files is error-prone when done by hand or via LLM text generation. axm-anvil replaces those rewrites with deterministic, CST-based transformations that preserve formatting, comments, and semantics exactly.

Built on libcst for lossless round-trip, it exposes a set of MCP tools for agent-driven refactoring.

Features

  • 🔨 ast_move — Move classes, functions, or constants between files with transitive dependency resolution (imports, constants, helpers)
  • 🔁 Smart import merging — Uses AddImportsVisitor to combine imports from the same module instead of duplicating
  • 🧹 Scope-aware orphan cleanup — Source file's unused imports are removed via ruff check --select F401 --fix
  • 📐 Topological constant ordering — Dependencies are always inserted before their dependents
  • 🛡️ Atomic writes — All modifications computed in memory, validated via cst.parse_module(), then applied via a single batch_edit call (all-or-nothing)
  • 🔗 Attribute-style caller rewrites — Rewrites old_module.Symbol chains (including import ... as aliases) to the new module, preserving method/subscript chains and skipping shadowed names via ScopeProvider
  • 🪢 --reexport mode — Leaves callers untouched and injects a from new_module import <Symbol> # re-export for backwards compat shim into the source module for gradual migration
  • 🎯 Overload-aware — Detects @overload companions and moves them together as an indivisible group
  • 📦 Lossless formatting — Comments, whitespace, and trailing commas preserved exactly via libcst round-trip
  • 🤝 Complementary to axm-ast — Uses ast_callers and ast_graph for blast radius detection
  • ✏️ --rename on move — Rename moved definitions in flight (JSON {"Old": "New"}); references, __all__ entries, and string forward-references are all rewritten to the new name
  • 📍 --insert-after — Splice moved blocks after a named target symbol instead of appending at end-of-file
  • 🚫 --no-include-helpers — Skip auto-copying local helpers/constants into the target (imports are still copied); emits a warning listing the un-copied names
  • 🧭 Edge-case awareness — Syncs __all__ (never created spontaneously), preserves try/except conditional imports verbatim, converts relative imports to absolute on cross-package moves, and warns on side-effect decorators (@app.route, @pytest.fixture…), string forward-references, and pytest fixture-scope breaks

Planned tools (see spec.md)

Tool Description
ast_move Move symbols between files (Phase 1-3)
ast_rename Rename a symbol everywhere (def + callers + imports + __all__)
ast_split Split a module into N sub-modules
ast_merge Merge N modules into one
ast_promote _foofoo + add __all__ + update imports
ast_seal foo_foo + verify zero external callers

All share the same pipeline: identify (tree-sitter via axm-ast) → blast radius (ast_callers) → transform (libcst) → validate (cst.parse_module() + audit(lint)) → write atomically (batch_edit) → rollback on error.

Installation

uv add axm-anvil

Or as a workspace dependency in pyproject.toml:

[project]
dependencies = ["axm-anvil"]

[tool.uv.sources]
axm-anvil = { workspace = true }

Quick Start

CLI — preview a move (positional or flag form, both accepted):

axm-anvil move src/mylib/core/models.py src/mylib/core/services.py \
    UserService,_validate_input --dry-run

Rename while moving, and place the result after an existing symbol:

axm-anvil move \
    --from-file src/mylib/core/models.py \
    --to-file   src/mylib/core/services.py \
    --symbols   UserService \
    --rename    '{"UserService": "AccountService"}' \
    --insert-after existing_service

Move without dragging local helpers along (imports are still copied):

axm-anvil move src/mylib/a.py src/mylib/b.py Widget --no-include-helpers

Python / MCP:

from axm_anvil import MoveTool

result = MoveTool().execute(
    path=".",
    symbols="UserService,_validate_input",
    from_file="src/mylib/core/models.py",
    to_file="src/mylib/core/services.py",
    dry_run=True,
)
print(result.data["moved"])
print(result.data["warnings"])  # __all__ sync, conditional imports, decorators, …

See the CLI Reference for every flag and the warnings each one can emit.

Development

This package is part of the axm-forge uv workspace.

# Run tests for this package
uv run pytest --package axm-anvil

# From workspace root
make test-axm-anvil

License

Apache-2.0 — © 2026 AXM Protocols

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

axm_anvil-0.1.0.tar.gz (82.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

axm_anvil-0.1.0-py3-none-any.whl (51.6 kB view details)

Uploaded Python 3

File details

Details for the file axm_anvil-0.1.0.tar.gz.

File metadata

  • Download URL: axm_anvil-0.1.0.tar.gz
  • Upload date:
  • Size: 82.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for axm_anvil-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a512f52ff3d835f98cc4d4b76e273a59f156907dc071839b37ec28f989018554
MD5 0aa6be0e4fbb0d0ed4646b0e28fc8079
BLAKE2b-256 b5193fc809f34a01343d32b04f245bee1327251f334655ac838b3237fd3577b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for axm_anvil-0.1.0.tar.gz:

Publisher: publish.yml on axm-protocols/axm-forge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file axm_anvil-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: axm_anvil-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 51.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for axm_anvil-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4cf371537fd355db62344d75c2c88c1d56dd152196634511dc88f5d2786c46ba
MD5 09393ed81eb3b99b00e3459ba55e230c
BLAKE2b-256 d652a1cab79419f596287c543eed5bbd00d2a6ff32cc9ee1b5f1ee65fc0f1910

See more details on using hashes here.

Provenance

The following attestation bundles were made for axm_anvil-0.1.0-py3-none-any.whl:

Publisher: publish.yml on axm-protocols/axm-forge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page