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
- 🔨
anvil_move— Move classes, functions, or constants between files with transitive dependency resolution (imports, constants, helpers) - ✏️
anvil_rename— Rename top-level symbols in place and rewrite cross-file callers atomically (definition + internal usages +from mod import Oldimports and usages in every caller) - 🔁 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— Builds the workspace-wide module graph viaanalyze_workspace+build_workspace_module_graphto detect newly-introduced cross-package import cycles - ✏️
--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
Roadmap
Planned tools
anvil_move, anvil_rename, and anvil_extract are the shipped
operations. The tools below are not yet implemented — they are listed
here to convey the intended direction.
| Tool | Description | Status |
|---|---|---|
anvil_move |
Move symbols between files | shipped |
anvil_rename |
Rename a top-level symbol in place; rewrite cross-file callers (imports + usages) | shipped |
anvil_extract |
Extract symbols into a new module (created on disk) with their transitive dependencies | shipped |
anvil_split |
Split a module into N sub-modules | planned |
anvil_merge |
Merge N modules into one | planned |
anvil_promote |
_foo → foo + add __all__ + update imports |
planned |
anvil_seal |
foo → _foo + verify zero external callers |
planned |
All share the same pipeline: identify (libcst block extraction) → blast radius (libcst caller discovery + the workspace module graph from axm-ast) → transform (libcst) → validate (cst.parse_module() + ruff check --fix) → 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 --package axm-anvil --directory packages/axm-anvil pytest
# From workspace root
make test-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.3.0.tar.gz.
File metadata
- Download URL: axm_anvil-0.3.0.tar.gz
- Upload date:
- Size: 103.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89ab27c20428c98f721cdd9d0abca7e96e76b00aba10bced39ec14c00a54466c
|
|
| MD5 |
f706cc6ef6e9d7cf8c266bfd9bb47efa
|
|
| BLAKE2b-256 |
7aa6612644e7e7b3c4f5f5594dea61c5ddc30b18d91a8d3bea59f2449a09602e
|
Provenance
The following attestation bundles were made for axm_anvil-0.3.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.3.0.tar.gz -
Subject digest:
89ab27c20428c98f721cdd9d0abca7e96e76b00aba10bced39ec14c00a54466c - Sigstore transparency entry: 1916314534
- Sigstore integration time:
-
Permalink:
axm-protocols/axm-forge@811cd8e6dce9caca1f7cac101e4719e4c5d3fa07 -
Branch / Tag:
refs/tags/anvil/v0.3.0 - Owner: https://github.com/axm-protocols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@811cd8e6dce9caca1f7cac101e4719e4c5d3fa07 -
Trigger Event:
push
-
Statement type:
File details
Details for the file axm_anvil-0.3.0-py3-none-any.whl.
File metadata
- Download URL: axm_anvil-0.3.0-py3-none-any.whl
- Upload date:
- Size: 65.1 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 |
0307de9fe1b6dc285582d9327fa21391dc9006c5e1384001d80f02ce1ff4d134
|
|
| MD5 |
abd96eacb1531c1ff2dcde75477a40f2
|
|
| BLAKE2b-256 |
2db136f3666701c650956d7b595c4a983aa518544c8b543d2a362597d57dbef5
|
Provenance
The following attestation bundles were made for axm_anvil-0.3.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.3.0-py3-none-any.whl -
Subject digest:
0307de9fe1b6dc285582d9327fa21391dc9006c5e1384001d80f02ce1ff4d134 - Sigstore transparency entry: 1916314642
- Sigstore integration time:
-
Permalink:
axm-protocols/axm-forge@811cd8e6dce9caca1f7cac101e4719e4c5d3fa07 -
Branch / Tag:
refs/tags/anvil/v0.3.0 - Owner: https://github.com/axm-protocols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@811cd8e6dce9caca1f7cac101e4719e4c5d3fa07 -
Trigger Event:
push
-
Statement type: