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.2.0.tar.gz (89.9 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.2.0-py3-none-any.whl (53.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: axm_anvil-0.2.0.tar.gz
  • Upload date:
  • Size: 89.9 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.2.0.tar.gz
Algorithm Hash digest
SHA256 1c4793bfb3740cfb40472392aed93bdd37ba089d90c04bea92a6202748e4bf1b
MD5 4ab996d8077eb96de54fb03eee1ba280
BLAKE2b-256 3a701ae4109a1628a224a764e46f522b69a68568da0b185604b582e36cb81e2d

See more details on using hashes here.

Provenance

The following attestation bundles were made for axm_anvil-0.2.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: axm_anvil-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 53.8 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 268d7570ea3fc84109a1a7b2ed66e88bb76b511a6bfdc4847c1e73656585ab0f
MD5 592b894e827dadcb5f88010b15a867d9
BLAKE2b-256 fefa38a12c7f0a76e1ed8215f24138f83dfad72b8b9ce1c648533057e1420795

See more details on using hashes here.

Provenance

The following attestation bundles were made for axm_anvil-0.2.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