Audit checks for CI workflows (7 checks, 18 pts).
check_ci_coverage_upload(project)
Check 12: CI uploads coverage.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_ci_coverage_upload(project: Path) -> CheckResult:
"""Check 12: CI uploads coverage."""
content = _read_ci(project)
if content is None or (
"coveralls" not in content.lower() and "codecov" not in content.lower()
):
return CheckResult(
name="ci.coverage_upload",
category="ci",
passed=False,
weight=2,
message="No coverage upload in CI",
details=["CI should upload coverage to Coveralls or Codecov"],
fix="Add coverallsapp/github-action or codecov/codecov-action step.",
)
return CheckResult(
name="ci.coverage_upload",
category="ci",
passed=True,
weight=2,
message="Coverage upload configured",
details=[],
fix="",
)
|
check_ci_lint_job(project)
Check 9: CI has a lint job.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_ci_lint_job(project: Path) -> CheckResult:
"""Check 9: CI has a lint job."""
content = _read_ci(project)
if content is None or "lint" not in content.lower():
return CheckResult(
name="ci.lint_job",
category="ci",
passed=False,
weight=3,
message="No lint job in CI",
details=["CI should have a lint/type-check job"],
fix="Add a lint job to .github/workflows/ci.yml that runs `make lint`.",
)
return CheckResult(
name="ci.lint_job",
category="ci",
passed=True,
weight=3,
message="Lint job present",
details=[],
fix="",
)
|
check_ci_security_job(project)
Check 11: CI has a security/pip-audit job.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_ci_security_job(project: Path) -> CheckResult:
"""Check 11: CI has a security/pip-audit job."""
content = _read_ci(project)
if content is None or "audit" not in content.lower():
return CheckResult(
name="ci.security_job",
category="ci",
passed=False,
weight=2,
message="No security audit job in CI",
details=["CI should run pip-audit for dependency scanning"],
fix="Add a security job that runs `uv run pip-audit`.",
)
return CheckResult(
name="ci.security_job",
category="ci",
passed=True,
weight=2,
message="Security audit job present",
details=[],
fix="",
)
|
check_ci_test_job(project)
Check 10: CI has a test job with Python matrix.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_ci_test_job(project: Path) -> CheckResult:
"""Check 10: CI has a test job with Python matrix."""
content = _read_ci(project)
if content is None or "test" not in content.lower():
return CheckResult(
name="ci.test_job",
category="ci",
passed=False,
weight=3,
message="No test job in CI",
details=["CI should have a test job with python-version matrix"],
fix="Add a test job with strategy.matrix.python-version.",
)
return CheckResult(
name="ci.test_job",
category="ci",
passed=True,
weight=3,
message="Test job present",
details=[],
fix="",
)
|
check_ci_workflow_exists(project)
Check 8: .github/workflows/ci.yml exists.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_ci_workflow_exists(project: Path) -> CheckResult:
"""Check 8: .github/workflows/ci.yml exists."""
content = _read_ci(project)
if content is None:
return CheckResult(
name="ci.workflow_exists",
category="ci",
passed=False,
weight=4,
message="CI workflow not found",
details=["Expected: .github/workflows/ci.yml"],
fix="Create .github/workflows/ci.yml with lint, test, and security jobs.",
)
return CheckResult(
name="ci.workflow_exists",
category="ci",
passed=True,
weight=4,
message="CI workflow found",
details=[],
fix="",
)
|
check_dependabot(project)
Check 35: .github/dependabot.yml exists.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_dependabot(project: Path) -> CheckResult:
"""Check 35: .github/dependabot.yml exists."""
if not (project / ".github" / "dependabot.yml").exists():
return CheckResult(
name="ci.dependabot",
category="ci",
passed=False,
weight=2,
message="Dependabot config not found",
details=["Dependabot automates dependency security updates"],
fix="Create .github/dependabot.yml with pip and github-actions ecosystems.",
)
return CheckResult(
name="ci.dependabot",
category="ci",
passed=True,
weight=2,
message="Dependabot configured",
details=[],
fix="",
)
|
check_trusted_publishing(project)
Check 34: publish.yml uses Trusted Publishing (OIDC) without API token.
Source code in packages/axm-init/src/axm_init/checks/ci.py
| def check_trusted_publishing(project: Path) -> CheckResult:
"""Check 34: publish.yml uses Trusted Publishing (OIDC) without API token."""
content = _read_publish(project)
if content is None or "id-token" not in content:
return CheckResult(
name="ci.trusted_publishing",
category="ci",
passed=False,
weight=2,
message="No Trusted Publishing (OIDC) in publish workflow",
details=["publish.yml should use permissions: id-token: write"],
fix="Add `permissions: id-token: write` to publish.yml for PyPI OIDC.",
)
if "PYPI_API_TOKEN" in content:
return CheckResult(
name="ci.trusted_publishing",
category="ci",
passed=False,
weight=2,
message="publish.yml still uses PYPI_API_TOKEN alongside OIDC",
details=["Remove secrets.PYPI_API_TOKEN to use true Trusted Publishing"],
fix=(
"Remove `password: ${{ secrets.PYPI_API_TOKEN }}`"
" from publish.yml — OIDC handles auth automatically."
),
)
return CheckResult(
name="ci.trusted_publishing",
category="ci",
passed=True,
weight=2,
message="Trusted Publishing (OIDC) configured",
details=[],
fix="",
)
|