Metadata-Version: 2.4
Name: axm-anvil
Version: 0.1.0
Summary: Deterministic CST-based refactoring toolkit for Python — move, rename, split, merge symbols atomically across files.
Project-URL: Homepage, https://forge.axm-protocols.io
Project-URL: Documentation, https://forge.axm-protocols.io
Project-URL: Repository, https://github.com/axm-protocols/axm-forge
Project-URL: Issues, https://github.com/axm-protocols/axm-forge/issues
Author-email: AXM Protocols <gabriel@axm-protocols.io>
License-Expression: Apache-2.0
Keywords: ast,cst,libcst,mcp,move-symbol,refactoring,rename
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: axm
Requires-Dist: axm-ast
Requires-Dist: axm-edit
Requires-Dist: cyclopts>=4.5
Requires-Dist: libcst>=1.5.0
Requires-Dist: pydantic>=2.0.0
Description-Content-Type: text/markdown

# axm-anvil

**Deterministic CST-based refactoring toolkit for Python.**

<p align="center">
  <a href="https://forge.axm-protocols.io/audit/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-anvil/axm-audit.json" alt="axm-audit"></a>
  <a href="https://forge.axm-protocols.io/init/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-anvil/axm-init.json" alt="axm-init"></a>
  <a href="https://github.com/axm-protocols/axm-forge/actions/workflows/axm-quality.yml"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-anvil/coverage.json" alt="Coverage"></a>
  <img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
</p>

---

## 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](https://github.com/Instagram/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` | `_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

```bash
uv add axm-anvil
```

Or as a workspace dependency in `pyproject.toml`:

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

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

## Quick Start

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

```bash
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:

```bash
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):

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

Python / MCP:

```python
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](docs/reference/cli.md) for every flag and the warnings each one can emit.

## Development

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

```bash
# 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
