[tool.pytest.ini_options] testpaths = ["tests"] addopts = "--cov=. --cov-report=term-missing --cov-fail-under=75 --cov-config=.coveragerc -v" asyncio_mode = "auto" # ── Coverage ────────────────────────────────────────────────────────────────── [tool.coverage.run] source = ["."] omit = [ "tests/*", "eval_agent.py", # standalone evaluation harness — not production code "ml_status.py", # Streamlit dashboard "ml_pipeline.py", # long-running training pipeline "settings.py", # root-level alias, covered via config/settings.py "*/__init__.py", "*/migrations/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if __name__ == .__main__.:", "raise NotImplementedError", "\\.\\.\\.", ] # ── Radon — cognitive / cyclomatic complexity ────────────────────────────────── # Run: radon cc . -s -a --min B # Target: average complexity A/B, max cognitive score ≤ 15 per function [tool.radon] min = "B" max = 15 show_complexity = true # ── Bandit — security SAST ──────────────────────────────────────────────────── [tool.bandit] exclude_dirs = ["tests", "eval_agent.py"] skips = [] # fail on everything in OWASP Top-10 scope # ── Vulture — dead code ─────────────────────────────────────────────────────── # Run: vulture . --min-confidence 80 [tool.vulture] min_confidence = 80 ignore_names = ["lifespan", "on_event"] # FastAPI lifecycle names # ── File-size gate (enforced in CI) ────────────────────────────────────────── # See scripts/check_file_sizes.sh — fails if any .py file exceeds 500 lines