Skip to content

Precheck fs

precheck_fs

Filesystem-resolving prechecks over a batch_edit operation set.

This layer sits on top of the pure in-memory checks of :mod:axm_edit.core.precheck and adds the only diagnostics that require looking at the disk: a create aimed at an existing path, an anchor that is absent from (or duplicated in) the real file, and a line that is wider than the 88-char default batch_edit lints against yet still legal for the project.

Strictly read-only: every path goes through :func:resolve_safe, and files are only probed (is_file) and read (read_text). Nothing here creates, mutates or removes anything on disk.

check_anchors_on_disk(root, operations)

Resolve every replace anchor against the file actually on disk.

Parameters:

Name Type Description Default
root Path

Project root the batch would be applied to.

required
operations Sequence[FsOperation]

Operations in batch order (models or raw payloads).

required

Returns:

Type Description
list[CheckDiagnostic]

ANCHOR_NOT_FOUND errors for missing anchors and

list[CheckDiagnostic]

ANCHOR_AMBIGUOUS warnings for anchors matching more than once;

list[CheckDiagnostic]

[] when every anchor matches exactly once.

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def check_anchors_on_disk(
    root: Path,
    operations: Sequence[FsOperation],
) -> list[CheckDiagnostic]:
    """Resolve every ``replace`` anchor against the file actually on disk.

    Args:
        root: Project root the batch would be applied to.
        operations: Operations in batch order (models or raw payloads).

    Returns:
        ``ANCHOR_NOT_FOUND`` errors for missing anchors and
        ``ANCHOR_AMBIGUOUS`` warnings for anchors matching more than once;
        ``[]`` when every anchor matches exactly once.
    """
    diagnostics: list[CheckDiagnostic] = []
    for index, op in enumerate(_parse(operations)):
        if not isinstance(op, ReplaceOp):
            continue
        text = _read_text(root, op.file)
        if text is None:
            continue
        diagnostics.extend(_check_anchors(index, op, text))
    return diagnostics

check_create_targets(root, operations)

Flag every create whose target already exists under root.

Parameters:

Name Type Description Default
root Path

Project root the batch would be applied to.

required
operations Sequence[FsOperation]

Operations in batch order (models or raw payloads).

required

Returns:

Type Description
list[CheckDiagnostic]

One CREATE_ON_EXISTING error per colliding create, else [].

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def check_create_targets(
    root: Path,
    operations: Sequence[FsOperation],
) -> list[CheckDiagnostic]:
    """Flag every ``create`` whose target already exists under *root*.

    Args:
        root: Project root the batch would be applied to.
        operations: Operations in batch order (models or raw payloads).

    Returns:
        One ``CREATE_ON_EXISTING`` error per colliding create, else ``[]``.
    """
    return [
        CheckDiagnostic(
            op_index=index,
            file=op.file,
            severity="error",
            code="CREATE_ON_EXISTING",
            message=f"`create` targets {op.file!r}, which already exists.",
            hint=_CREATE_ON_EXISTING_HINT,
        )
        for index, op in enumerate(_parse(operations))
        if isinstance(op, CreateOp) and _exists(root, op.file)
    ]

check_line_length(op_index, file, new, limit)

Flag lines of new wider than 88 chars but within limit.

Pure function: no path is resolved and no file is read.

Parameters:

Name Type Description Default
op_index int

0-indexed position of the operation in the batch.

required
file str

Relative path targeted by that operation.

required
new str

Replacement (or created) text to measure, line by line.

required
limit int

The project's configured line-length.

required

Returns:

Type Description
list[CheckDiagnostic]

One LINE_LENGTH_DEFAULT_MISMATCH warning per line in the

list[CheckDiagnostic]

]88, limit] window, else [].

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def check_line_length(
    op_index: int,
    file: str,
    new: str,
    limit: int,
) -> list[CheckDiagnostic]:
    """Flag lines of *new* wider than 88 chars but within *limit*.

    Pure function: no path is resolved and no file is read.

    Args:
        op_index: 0-indexed position of the operation in the batch.
        file: Relative path targeted by that operation.
        new: Replacement (or created) text to measure, line by line.
        limit: The project's configured ``line-length``.

    Returns:
        One ``LINE_LENGTH_DEFAULT_MISMATCH`` warning per line in the
        ``]88, limit]`` window, else ``[]``.
    """
    return [
        CheckDiagnostic(
            op_index=op_index,
            file=file,
            severity="warning",
            code="LINE_LENGTH_DEFAULT_MISMATCH",
            message=(
                f"Line {number} is {len(line)} chars: over the "
                f"{DEFAULT_LINE_LENGTH}-char default but within the "
                f"configured limit of {limit}."
            ),
            hint=_LINE_LENGTH_HINT,
        )
        for number, line in enumerate(new.splitlines(), start=1)
        if DEFAULT_LINE_LENGTH < len(line) <= limit
    ]

check_rewrite_targets(root, operations)

Classify every rewrite target against the file actually on disk.

The verdict is NOT decided here: the observed facts are handed to :func:~axm_edit.core.rewrite.classify_rewrite_target — the single predicate the apply path shares — and its returned code IS the diagnostic code, so the dry run and the apply can never drift apart.

Parameters:

Name Type Description Default
root Path

Project root the batch would be applied to.

required
operations Sequence[FsOperation]

Operations in batch order (models or raw payloads).

required

Returns:

Type Description
list[CheckDiagnostic]

One blocking diagnostic per refused rewrite target

list[CheckDiagnostic]

(rewrite_target_missing, rewrite_target_not_regular or

list[CheckDiagnostic]

rewrite_checksum_stale), else [].

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def check_rewrite_targets(
    root: Path,
    operations: Sequence[FsOperation],
) -> list[CheckDiagnostic]:
    """Classify every ``rewrite`` target against the file actually on disk.

    The verdict is NOT decided here: the observed facts are handed to
    :func:`~axm_edit.core.rewrite.classify_rewrite_target` — the single
    predicate the apply path shares — and its returned code IS the diagnostic
    code, so the dry run and the apply can never drift apart.

    Args:
        root: Project root the batch would be applied to.
        operations: Operations in batch order (models or raw payloads).

    Returns:
        One blocking diagnostic per refused rewrite target
        (``rewrite_target_missing``, ``rewrite_target_not_regular`` or
        ``rewrite_checksum_stale``), else ``[]``.
    """
    diagnostics: list[CheckDiagnostic] = []
    for index, op in enumerate(_parse(operations)):
        if not isinstance(op, RewriteOp) or not op.file:
            continue
        if not op.expected_checksum:
            continue
        exists, is_regular, actual = _observe_target(root, op.file)
        code = classify_rewrite_target(
            exists=exists,
            is_regular=is_regular,
            actual_checksum=actual,
            expected_checksum=op.expected_checksum,
        )
        if code is None:
            continue
        diagnostics.append(
            CheckDiagnostic(
                op_index=index,
                file=op.file,
                severity="error",
                code=code,
                message=_REWRITE_MESSAGES[code].format(file=op.file),
                hint=_REWRITE_HINTS[code],
            )
        )
    return diagnostics

parse_rewrite_op(raw)

Normalise a raw rewrite payload into its canonical model.

Deliberately lenient — the payload-shape verdict belongs to :func:~axm_edit.core.precheck.check_rewrite_keys, so a rewrite that omits its checksum still surfaces that diagnostic instead of aborting the whole read-only pass with a validation error.

Parameters:

Name Type Description Default
raw Mapping[str, object]

Rewrite mapping as authored, before validation.

required

Returns:

Name Type Description
A RewriteOp

class:~axm_edit.models.operations.RewriteOp whose missing or

RewriteOp

ill-typed members are normalised to the empty string.

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def parse_rewrite_op(raw: Mapping[str, object]) -> RewriteOp:
    """Normalise a raw ``rewrite`` payload into its canonical model.

    Deliberately lenient — the payload-shape verdict belongs to
    :func:`~axm_edit.core.precheck.check_rewrite_keys`, so a rewrite that
    omits its ``checksum`` still surfaces that diagnostic instead of aborting
    the whole read-only pass with a validation error.

    Args:
        raw: Rewrite mapping as authored, before validation.

    Returns:
        A :class:`~axm_edit.models.operations.RewriteOp` whose missing or
        ill-typed members are normalised to the empty string.
    """
    return RewriteOp.model_construct(
        op="rewrite",
        file=_as_text(raw.get("file")),
        content=_as_text(raw.get("content")),
        expected_checksum=_as_text(raw.get(REWRITE_CHECKSUM_KEY)),
    )

run_fs_checks(root, operations)

Aggregate every filesystem-resolving check plus the static ones.

The static checks of :func:run_static_checks are delegated with the contents read from disk, so they see the real files instead of an empty mapping. The whole pass is read-only.

Parameters:

Name Type Description Default
root Path

Project root the batch would be applied to.

required
operations Sequence[FsOperation]

Operations in batch order (models or raw payloads).

required

Returns:

Type Description
list[CheckDiagnostic]

Every diagnostic found, sorted by increasing op_index.

Source code in packages/axm-edit/src/axm_edit/core/precheck_fs.py
Python
def run_fs_checks(
    root: Path,
    operations: Sequence[FsOperation],
) -> list[CheckDiagnostic]:
    """Aggregate every filesystem-resolving check plus the static ones.

    The static checks of :func:`run_static_checks` are delegated with the
    contents read from disk, so they see the real files instead of an
    empty mapping. The whole pass is read-only.

    Args:
        root: Project root the batch would be applied to.
        operations: Operations in batch order (models or raw payloads).

    Returns:
        Every diagnostic found, sorted by increasing ``op_index``.
    """
    parsed = _parse(operations)
    limit = resolve_line_length(root)
    contents = _read_contents(root, parsed)
    diagnostics = [
        *check_create_targets(root, parsed),
        *check_anchors_on_disk(root, parsed),
        *check_rewrite_targets(root, parsed),
        *_check_line_lengths(parsed, limit),
        *run_static_checks(parsed, contents),
    ]
    return sorted(diagnostics, key=lambda diagnostic: diagnostic.op_index)