Skip to content

Structure

structure

Structure rules — file and directory existence checks.

DirectoryExistsRule dataclass

Bases: ProjectRule

Rule that checks if a required directory exists.

Not decorated with @register_rule — this rule is consumed by axm-init checklist checks, not auto-discovered during audits. The category property is set manually for the same reason.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
@dataclass
class DirectoryExistsRule(ProjectRule):
    """Rule that checks if a required directory exists.

    Not decorated with ``@register_rule`` — this rule is consumed by
    ``axm-init`` checklist checks, not auto-discovered during audits.
    The ``category`` property is set manually for the same reason.
    """

    dir_name: str

    @property
    def rule_id(self) -> str:
        """Unique identifier for this rule."""
        return f"DIR_EXISTS_{self.dir_name}"

    @property
    def category(self) -> str:
        """Scoring category for this rule."""
        return "structure"

    def check(self, project_path: Path) -> CheckResult:
        """Check if the directory exists in the project.

        Returns a passing result when the directory is present, or a failing
        result with ``fix_hint="mkdir {dir_name}"`` when missing.
        """
        target = project_path / self.dir_name
        if target.exists() and target.is_dir():
            return CheckResult(
                rule_id=self.rule_id,
                passed=True,
                message=f"{self.dir_name}/ exists",
            )
        return CheckResult(
            rule_id=self.rule_id,
            passed=False,
            message=f"{self.dir_name}/ not found",
            fix_hint=f"mkdir {self.dir_name}",
        )
category property

Scoring category for this rule.

rule_id property

Unique identifier for this rule.

check(project_path)

Check if the directory exists in the project.

Returns a passing result when the directory is present, or a failing result with fix_hint="mkdir {dir_name}" when missing.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
def check(self, project_path: Path) -> CheckResult:
    """Check if the directory exists in the project.

    Returns a passing result when the directory is present, or a failing
    result with ``fix_hint="mkdir {dir_name}"`` when missing.
    """
    target = project_path / self.dir_name
    if target.exists() and target.is_dir():
        return CheckResult(
            rule_id=self.rule_id,
            passed=True,
            message=f"{self.dir_name}/ exists",
        )
    return CheckResult(
        rule_id=self.rule_id,
        passed=False,
        message=f"{self.dir_name}/ not found",
        fix_hint=f"mkdir {self.dir_name}",
    )

FileExistsRule dataclass

Bases: ProjectRule

Rule that checks if a required file exists.

Not decorated with @register_rule — this rule is consumed by axm-init checklist checks, not auto-discovered during audits. The category property is set manually for the same reason.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
@dataclass
class FileExistsRule(ProjectRule):
    """Rule that checks if a required file exists.

    Not decorated with ``@register_rule`` — this rule is consumed by
    ``axm-init`` checklist checks, not auto-discovered during audits.
    The ``category`` property is set manually for the same reason.
    """

    file_name: str

    @property
    def rule_id(self) -> str:
        """Unique identifier for this rule."""
        return f"FILE_EXISTS_{self.file_name}"

    @property
    def category(self) -> str:
        """Scoring category for this rule."""
        return "structure"

    def check(self, project_path: Path) -> CheckResult:
        """Check if the file exists in the project.

        Returns a passing result when the file is present, or a failing
        result with ``fix_hint="touch {file_name}"`` when missing.
        """
        target = project_path / self.file_name
        if target.exists() and target.is_file():
            return CheckResult(
                rule_id=self.rule_id,
                passed=True,
                message=f"{self.file_name} exists",
            )
        return CheckResult(
            rule_id=self.rule_id,
            passed=False,
            message=f"{self.file_name} not found",
            fix_hint=f"touch {self.file_name}",
        )
category property

Scoring category for this rule.

rule_id property

Unique identifier for this rule.

check(project_path)

Check if the file exists in the project.

Returns a passing result when the file is present, or a failing result with fix_hint="touch {file_name}" when missing.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
def check(self, project_path: Path) -> CheckResult:
    """Check if the file exists in the project.

    Returns a passing result when the file is present, or a failing
    result with ``fix_hint="touch {file_name}"`` when missing.
    """
    target = project_path / self.file_name
    if target.exists() and target.is_file():
        return CheckResult(
            rule_id=self.rule_id,
            passed=True,
            message=f"{self.file_name} exists",
        )
    return CheckResult(
        rule_id=self.rule_id,
        passed=False,
        message=f"{self.file_name} not found",
        fix_hint=f"touch {self.file_name}",
    )

PyprojectCompletenessRule dataclass

Bases: ProjectRule

Validate PEP 621 field completeness in pyproject.toml.

Checks 9 fields: name, version/dynamic, description, requires-python, license, authors, classifiers, urls, readme. Scoring: (fields_present / 9) x 100.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
@dataclass
@register_rule("structure")
class PyprojectCompletenessRule(ProjectRule):
    """Validate PEP 621 field completeness in pyproject.toml.

    Checks 9 fields: name, version/dynamic, description, requires-python,
    license, authors, classifiers, urls, readme.
    Scoring: (fields_present / 9) x 100.
    """

    @property
    def rule_id(self) -> str:
        """Unique identifier for this rule."""
        return "STRUCTURE_PYPROJECT"

    def check(self, project_path: Path) -> CheckResult:
        """Check pyproject.toml completeness."""
        pyproject_path = project_path / "pyproject.toml"
        if not pyproject_path.exists():
            return CheckResult(
                rule_id=self.rule_id,
                passed=False,
                message="pyproject.toml not found",
                severity=Severity.ERROR,
                score=0,
                details={"fields_present": 0, "total_fields": 9},
                fix_hint="Create pyproject.toml with PEP 621 metadata",
            )

        try:
            data = tomllib.loads(pyproject_path.read_text())
        except (tomllib.TOMLDecodeError, OSError):
            return CheckResult(
                rule_id=self.rule_id,
                passed=False,
                message="pyproject.toml parse error",
                severity=Severity.ERROR,
                score=0,
                details={"fields_present": 0, "total_fields": 9},
                fix_hint="Fix pyproject.toml syntax",
            )

        present, missing = check_fields(data.get("project", {}))
        score = int((present / _TOTAL_FIELDS) * 100)

        return CheckResult(
            rule_id=self.rule_id,
            passed=score >= PASS_THRESHOLD,
            message=f"pyproject.toml completeness: {present}/{_TOTAL_FIELDS} fields",
            severity=Severity.WARNING if score < PASS_THRESHOLD else Severity.INFO,
            text=f"\u2022 missing: {', '.join(missing)}" if missing else None,
            score=int(score),
            details={
                "fields_present": present,
                "total_fields": _TOTAL_FIELDS,
                "missing": missing,
            },
            fix_hint=(
                "Add missing PEP 621 fields to [project]"
                if score < PASS_THRESHOLD
                else None
            ),
        )
rule_id property

Unique identifier for this rule.

check(project_path)

Check pyproject.toml completeness.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
def check(self, project_path: Path) -> CheckResult:
    """Check pyproject.toml completeness."""
    pyproject_path = project_path / "pyproject.toml"
    if not pyproject_path.exists():
        return CheckResult(
            rule_id=self.rule_id,
            passed=False,
            message="pyproject.toml not found",
            severity=Severity.ERROR,
            score=0,
            details={"fields_present": 0, "total_fields": 9},
            fix_hint="Create pyproject.toml with PEP 621 metadata",
        )

    try:
        data = tomllib.loads(pyproject_path.read_text())
    except (tomllib.TOMLDecodeError, OSError):
        return CheckResult(
            rule_id=self.rule_id,
            passed=False,
            message="pyproject.toml parse error",
            severity=Severity.ERROR,
            score=0,
            details={"fields_present": 0, "total_fields": 9},
            fix_hint="Fix pyproject.toml syntax",
        )

    present, missing = check_fields(data.get("project", {}))
    score = int((present / _TOTAL_FIELDS) * 100)

    return CheckResult(
        rule_id=self.rule_id,
        passed=score >= PASS_THRESHOLD,
        message=f"pyproject.toml completeness: {present}/{_TOTAL_FIELDS} fields",
        severity=Severity.WARNING if score < PASS_THRESHOLD else Severity.INFO,
        text=f"\u2022 missing: {', '.join(missing)}" if missing else None,
        score=int(score),
        details={
            "fields_present": present,
            "total_fields": _TOTAL_FIELDS,
            "missing": missing,
        },
        fix_hint=(
            "Add missing PEP 621 fields to [project]"
            if score < PASS_THRESHOLD
            else None
        ),
    )

TestsPyramidRule dataclass

Bases: ProjectRule

Verify the 3-level test pyramid layout and pytest markers.

Checks that tests/unit/, tests/integration/ and tests/e2e/ exist (tests/e2e/ is optional for library packages) and that pyproject.toml declares the integration and e2e pytest markers required by the project's strict-markers policy.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
@dataclass
@register_rule("structure")
class TestsPyramidRule(ProjectRule):
    """Verify the 3-level test pyramid layout and pytest markers.

    Checks that ``tests/unit/``, ``tests/integration/`` and ``tests/e2e/``
    exist (``tests/e2e/`` is optional for library packages) and that
    ``pyproject.toml`` declares the ``integration`` and ``e2e`` pytest
    markers required by the project's strict-markers policy.
    """

    @property
    def rule_id(self) -> str:
        """Unique identifier for this rule."""
        return "STRUCTURE_TESTS_PYRAMID"

    def check(self, project_path: Path) -> CheckResult:
        """Check the test pyramid layout and required markers."""
        if not (project_path / "tests").exists():
            return CheckResult(
                rule_id=self.rule_id,
                passed=False,
                message="tests/ not found",
                severity=Severity.ERROR,
                fix_hint="mkdir -p " + " ".join(_PYRAMID_DIRS),
            )

        self_contained = _is_self_contained(project_path)
        required_dirs, required_markers = _pyramid_requirements(self_contained)
        missing_dirs = [d for d in required_dirs if not (project_path / d).is_dir()]
        missing_markers = _missing_markers(project_path, required_markers)

        if not missing_dirs and not missing_markers:
            present = ", ".join(d.split("/")[-1] for d in _PYRAMID_DIRS)
            return CheckResult(
                rule_id=self.rule_id,
                passed=True,
                message=f"pyramid layout ok: {present}",
                score=100,
                details={"self_contained": self_contained},
            )

        return _pyramid_failure(
            self.rule_id, missing_dirs, missing_markers, self_contained
        )
rule_id property

Unique identifier for this rule.

check(project_path)

Check the test pyramid layout and required markers.

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
def check(self, project_path: Path) -> CheckResult:
    """Check the test pyramid layout and required markers."""
    if not (project_path / "tests").exists():
        return CheckResult(
            rule_id=self.rule_id,
            passed=False,
            message="tests/ not found",
            severity=Severity.ERROR,
            fix_hint="mkdir -p " + " ".join(_PYRAMID_DIRS),
        )

    self_contained = _is_self_contained(project_path)
    required_dirs, required_markers = _pyramid_requirements(self_contained)
    missing_dirs = [d for d in required_dirs if not (project_path / d).is_dir()]
    missing_markers = _missing_markers(project_path, required_markers)

    if not missing_dirs and not missing_markers:
        present = ", ".join(d.split("/")[-1] for d in _PYRAMID_DIRS)
        return CheckResult(
            rule_id=self.rule_id,
            passed=True,
            message=f"pyramid layout ok: {present}",
            score=100,
            details={"self_contained": self_contained},
        )

    return _pyramid_failure(
        self.rule_id, missing_dirs, missing_markers, self_contained
    )

check_fields(project)

Check present PEP 621 fields in project table.

Returns (present_count, missing_field_names).

Source code in packages/axm-audit/src/axm_audit/core/rules/structure.py
Python
def check_fields(project: dict[str, object]) -> tuple[int, list[str]]:
    """Check present PEP 621 fields in project table.

    Returns (present_count, missing_field_names).
    """
    missing: list[str] = [f for f in _REQUIRED_FIELDS if f not in project]

    # Version: static or dynamic
    dynamic = project.get("dynamic", [])
    dynamic_list: list[object] = dynamic if isinstance(dynamic, list) else []
    has_version = "version" in project or "version" in dynamic_list
    if not has_version:
        missing.append("version")

    # URLs section
    if not project.get("urls"):
        missing.append("urls")

    # Optional fields
    missing.extend(f for f in _OPTIONAL_FIELDS if f not in project)

    present = _TOTAL_FIELDS - len(missing)
    return present, missing