Skip to content

Structure

structure

Audit checks for project structure (7 checks, 17 pts).

check_contributing(project)

Check 27: CONTRIBUTING.md exists.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_contributing(project: Path) -> CheckResult:
    """Check 27: CONTRIBUTING.md exists."""
    if not (project / "CONTRIBUTING.md").exists():
        return CheckResult(
            name="structure.contributing",
            category="structure",
            passed=False,
            weight=2,
            message="CONTRIBUTING.md not found",
            details=[],
            fix="Create CONTRIBUTING.md with dev setup and commit conventions.",
        )
    return CheckResult(
        name="structure.contributing",
        category="structure",
        passed=True,
        weight=2,
        message="CONTRIBUTING.md found",
        details=[],
        fix="",
    )

check_license_file(project)

Check 28: LICENSE file exists.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_license_file(project: Path) -> CheckResult:
    """Check 28: LICENSE file exists."""
    if not (project / "LICENSE").exists():
        return CheckResult(
            name="structure.license",
            category="structure",
            passed=False,
            weight=3,
            message="LICENSE file not found",
            details=[],
            fix="Create a LICENSE file (MIT, Apache-2.0, or EUPL-1.2).",
        )
    return CheckResult(
        name="structure.license",
        category="structure",
        passed=True,
        weight=3,
        message="LICENSE file found",
        details=[],
        fix="",
    )

check_py_typed(project)

Check 25: py.typed marker in package.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_py_typed(project: Path) -> CheckResult:
    """Check 25: py.typed marker in package."""
    src = project / "src"
    if not src.is_dir():
        return CheckResult(
            name="structure.py_typed",
            category="structure",
            passed=False,
            weight=2,
            message="src/ directory not found",
            details=[],
            fix="Create src/<package_name>/py.typed marker file.",
        )
    packages = _find_packages(src)
    for pkg in packages:
        if (pkg / "py.typed").exists():
            return CheckResult(
                name="structure.py_typed",
                category="structure",
                passed=True,
                weight=2,
                message="py.typed marker found",
                details=[],
                fix="",
            )
    return CheckResult(
        name="structure.py_typed",
        category="structure",
        passed=False,
        weight=2,
        message="py.typed marker not found",
        details=["PEP 561: py.typed marks package as providing type information"],
        fix="Create an empty src/<package_name>/py.typed file.",
    )

check_python_version(project)

Check 33: .python-version file exists.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_python_version(project: Path) -> CheckResult:
    """Check 33: .python-version file exists."""
    if not (project / ".python-version").exists():
        return CheckResult(
            name="structure.python_version",
            category="structure",
            passed=False,
            weight=1,
            message=".python-version not found",
            details=["Pin Python version for consistent environments"],
            fix="Run `uv python pin 3.12` to create .python-version.",
        )
    return CheckResult(
        name="structure.python_version",
        category="structure",
        passed=True,
        weight=1,
        message=".python-version found",
        details=[],
        fix="",
    )

check_src_layout(project)

Check 24: src// layout with init.py.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_src_layout(project: Path) -> CheckResult:
    """Check 24: src/<pkg>/ layout with __init__.py."""
    src = project / "src"
    if not src.is_dir():
        return CheckResult(
            name="structure.src_layout",
            category="structure",
            passed=False,
            weight=4,
            message="src/ directory not found",
            details=["Expected: src/<package_name>/__init__.py"],
            fix="Migrate to src/ layout: move package into src/<package_name>/.",
        )
    packages = _find_packages(src)
    if not packages:
        return CheckResult(
            name="structure.src_layout",
            category="structure",
            passed=False,
            weight=4,
            message="No Python package found in src/",
            details=["src/ exists but contains no package with __init__.py"],
            fix="Create src/<package_name>/__init__.py.",
        )
    pkg_names = sorted(d.relative_to(src).as_posix() for d in packages)
    return CheckResult(
        name="structure.src_layout",
        category="structure",
        passed=True,
        weight=4,
        message=f"src/ layout with {len(packages)} package(s)",
        details=[f"packages: {', '.join(pkg_names)}"],
        fix="",
    )

check_tests_dir(project)

Check the resolved test suite has a complete three-level pyramid.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_tests_dir(project: Path) -> CheckResult:
    """Check the resolved test suite has a complete three-level pyramid."""
    expected_name = canonical_suite_name(project)
    tests = resolve_suite_dir(project)
    if tests is None:
        return CheckResult(
            name="structure.tests_dir",
            category="structure",
            passed=False,
            weight=3,
            message=f"{expected_name}/ directory not found",
            details=[],
            fix=(
                f"Create {expected_name}/ directory with unit/, integration/, "
                "e2e/ subdirectories."
            ),
        )

    suite_name = tests.name
    required = ["unit", "integration", "e2e"]
    missing = [sub for sub in required if not (tests / sub).is_dir()]
    if missing:
        missing_paths = [f"{suite_name}/{sub}/" for sub in missing]
        return CheckResult(
            name="structure.tests_dir",
            category="structure",
            passed=False,
            weight=3,
            message=f"Missing test pyramid subdirectories: {', '.join(missing_paths)}",
            details=[f"Expected: {path}" for path in missing_paths],
            fix=f"Create missing subdirectories: {', '.join(missing_paths)}",
        )

    test_files = list(tests.rglob("test_*.py"))
    if not test_files:
        pyramid = f"{suite_name}/{{unit,integration,e2e}}/test_*.py"
        return CheckResult(
            name="structure.tests_dir",
            category="structure",
            passed=False,
            weight=3,
            message=f"No test files found in {suite_name}/",
            details=[f"Expected: {pyramid} files"],
            fix=(
                "Add test files matching test_*.py under "
                f"{suite_name}/unit/, {suite_name}/integration/, "
                f"or {suite_name}/e2e/."
            ),
        )

    return CheckResult(
        name="structure.tests_dir",
        category="structure",
        passed=True,
        weight=3,
        message=f"{len(test_files)} test file(s) found across unit/integration/e2e",
        details=[],
        fix="",
    )

check_uv_lock(project)

Check 32: uv.lock committed for reproducible builds.

Source code in packages/axm-init/src/axm_init/checks/structure.py
Python
def check_uv_lock(project: Path) -> CheckResult:
    """Check 32: uv.lock committed for reproducible builds."""
    lock = _find_uv_lock(project)
    if lock is None:
        return CheckResult(
            name="structure.uv_lock",
            category="structure",
            passed=False,
            weight=2,
            message="uv.lock not found",
            details=["Commit uv.lock for reproducible dependency resolution"],
            fix="Run `uv lock` and commit the generated uv.lock file.",
        )
    at_root = lock.parent != project.resolve()
    msg = "uv.lock found (workspace root)" if at_root else "uv.lock found"
    return CheckResult(
        name="structure.uv_lock",
        category="structure",
        passed=True,
        weight=2,
        message=msg,
        details=[],
        fix="",
    )