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.
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
AddImportsVisitorto 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 singlebatch_editcall (all-or-nothing) - 🔗 Attribute-style caller rewrites — Rewrites
old_module.Symbolchains (includingimport ... asaliases) to the new module, preserving method/subscript chains and skipping shadowed names viaScopeProvider - 🪢
--reexportmode — Leaves callers untouched and injects afrom new_module import <Symbol> # re-export for backwards compatshim into the source module for gradual migration - 🎯 Overload-aware — Detects
@overloadcompanions 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— Usesast_callersandast_graphfor blast radius detection - ✏️
--renameon 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), preservestry/exceptconditional 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 |
_foo → foo + 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c4793bfb3740cfb40472392aed93bdd37ba089d90c04bea92a6202748e4bf1b
|
|
| MD5 |
4ab996d8077eb96de54fb03eee1ba280
|
|
| BLAKE2b-256 |
3a701ae4109a1628a224a764e46f522b69a68568da0b185604b582e36cb81e2d
|
Provenance
The following attestation bundles were made for axm_anvil-0.2.0.tar.gz:
Publisher:
publish.yml on axm-protocols/axm-forge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
axm_anvil-0.2.0.tar.gz -
Subject digest:
1c4793bfb3740cfb40472392aed93bdd37ba089d90c04bea92a6202748e4bf1b - Sigstore transparency entry: 1818490749
- Sigstore integration time:
-
Permalink:
axm-protocols/axm-forge@32c1ad9f2b9ba6ae156187b52b55bc058b5b80a9 -
Branch / Tag:
refs/tags/anvil/v0.2.0 - Owner: https://github.com/axm-protocols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32c1ad9f2b9ba6ae156187b52b55bc058b5b80a9 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
268d7570ea3fc84109a1a7b2ed66e88bb76b511a6bfdc4847c1e73656585ab0f
|
|
| MD5 |
592b894e827dadcb5f88010b15a867d9
|
|
| BLAKE2b-256 |
fefa38a12c7f0a76e1ed8215f24138f83dfad72b8b9ce1c648533057e1420795
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
axm_anvil-0.2.0-py3-none-any.whl -
Subject digest:
268d7570ea3fc84109a1a7b2ed66e88bb76b511a6bfdc4847c1e73656585ab0f - Sigstore transparency entry: 1818490780
- Sigstore integration time:
-
Permalink:
axm-protocols/axm-forge@32c1ad9f2b9ba6ae156187b52b55bc058b5b80a9 -
Branch / Tag:
refs/tags/anvil/v0.2.0 - Owner: https://github.com/axm-protocols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32c1ad9f2b9ba6ae156187b52b55bc058b5b80a9 -
Trigger Event:
push
-
Statement type: