Skip to content

Engine

engine

Batch file editing engine.

Implements the validate-then-apply strategy from the axm-edit spec:

  1. Read all affected files once (snapshot)
  2. Validate every operation against the snapshot
  3. Resolve line positions (fuzzy search for old content)
  4. Sort replace edits bottom-to-top to avoid line-shift
  5. Apply all operations atomically (or fail with 0 files touched)

ResolvedEdit dataclass

An edit whose line position has been resolved against the file.

Source code in packages/axm-edit/src/axm_edit/core/engine.py
Python
@dataclass(frozen=True)
class ResolvedEdit:
    """An edit whose line position has been resolved against the file."""

    line: int  # actual 1-indexed start line found
    old: str
    new: str
    indent: str = ""  # indent prefix to apply to new (from dedent match)

batch_apply(root, operations)

Validate and apply a batch of file operations.

Atomicity contract:

  • Validation is the gate. All operations are validated first; if any fails the batch is rejected wholesale (success=False) before a single byte is written — a true all-or-nothing guarantee.
  • Apply is best-effort with automatic rollback. Once validation passes, a targeted checkpoint of every touched path is captured and the operations are applied. If any exception occurs mid-apply (anchor drift, a write_text / unlink / mkdir failure, a permission error, …) the partial work is rolled back to that checkpoint — touched files are restored to their pre-batch bytes and batch-created files (and the empty directories created for them) are removed — and a failing BatchResult is returned. The filesystem is not made transactional at the OS level; the rollback restores only the snapshotted paths.

Parameters:

Name Type Description Default
root Path

Project root directory (all paths are relative to this).

required
operations Sequence[Operation]

List of replace, create, and delete operations.

required

Returns:

Type Description
BatchResult

BatchResult with success status, a targeted-path snapshot

BatchResult

(checkpoint) for rollback, and a summary.

Source code in packages/axm-edit/src/axm_edit/core/engine.py
Python
def batch_apply(root: Path, operations: Sequence[Operation]) -> BatchResult:
    """Validate and apply a batch of file operations.

    Atomicity contract:

    * **Validation is the gate.** All operations are validated first; if any
      fails the batch is rejected wholesale (``success=False``) before a
      single byte is written — a true all-or-nothing guarantee.
    * **Apply is best-effort with automatic rollback.** Once validation
      passes, a targeted checkpoint of every touched path is captured and the
      operations are applied. If *any* exception occurs mid-apply (anchor
      drift, a ``write_text`` / ``unlink`` / ``mkdir`` failure, a permission
      error, …) the partial work is rolled back to that checkpoint — touched
      files are restored to their pre-batch bytes and batch-created files (and
      the empty directories created for them) are removed — and a failing
      ``BatchResult`` is returned. The filesystem is *not* made transactional
      at the OS level; the rollback restores only the snapshotted paths.

    Args:
        root: Project root directory (all paths are relative to this).
        operations: List of replace, create, and delete operations.

    Returns:
        BatchResult with success status, a targeted-path snapshot
        (``checkpoint``) for rollback, and a summary.
    """
    root = root.resolve()
    grouped = _group_operations(root, operations)
    resolved_by_file, errors = _validate_all(root, grouped)

    if errors:
        return BatchResult(
            success=False,
            error="Validation failed",
            details=errors,
        )

    checkpoint = create_checkpoint(root, operations)

    try:
        total_applied = 0
        for file_rel, resolved in resolved_by_file.items():
            total_applied += _apply_replace(root, file_rel, resolved)
        total_applied += _apply_creates_deletes(root, grouped.creates, grouped.deletes)
    except _AnchorDriftError as drift:
        rb = rollback(root, checkpoint)
        return BatchResult(
            success=False,
            checkpoint=checkpoint,
            error="Apply aborted: file drifted between validation and apply",
            details=[drift.detail],
            rollback_failed=not rb.ok,
        )
    except Exception as exc:  # noqa: BLE001 - any apply failure must roll back
        rb = rollback(root, checkpoint)
        return BatchResult(
            success=False,
            checkpoint=checkpoint,
            error=f"Apply aborted and rolled back: {exc}",
            rollback_failed=not rb.ok,
        )

    return BatchResult(
        success=True,
        checkpoint=checkpoint,
        applied=total_applied,
        summary={
            "modified": len(resolved_by_file),
            "created": len(grouped.creates),
            "deleted": len(grouped.deletes),
        },
    )