From code-quality
Sets up Python code quality toolchain with ruff linting/formatting, mypy type checking, complexity gating, dead code detection, pre-commit hooks, pyproject.toml config, and Makefile targets.
npx claudepluginhub sam-dumont/claude-skills --plugin code-qualityThis skill uses the workspace's default tool permissions.
This skill sets up and enforces comprehensive Python code quality using a battle-tested
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
This skill sets up and enforces comprehensive Python code quality using a battle-tested
toolchain. Based on real production Makefiles using uv for fast dependency management.
uv run and uvx for instant tool execution — no global installs| Tool | Purpose | Config Location |
|---|---|---|
| ruff | Linting + formatting (replaces flake8, isort, black) | pyproject.toml |
| mypy | Static type checking | pyproject.toml |
| xenon | Cyclomatic complexity gating | CLI flags |
| vulture | Dead code detection | CLI flags |
| pre-commit | Git hook automation | .pre-commit-config.yaml |
When setting up code quality for a Python project, add these sections to pyproject.toml:
# =============================================================================
# Ruff — Linting & Formatting
# =============================================================================
[tool.ruff]
target-version = "py312" # Adjust to project's minimum Python version
line-length = 120
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort (import sorting)
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"SIM", # flake8-simplify
"S", # flake8-bandit (security)
"A", # flake8-builtins
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"T20", # flake8-print
"PT", # flake8-pytest-style
"RET", # flake8-return
"PTH", # flake8-use-pathlib
"ERA", # eradicate (commented-out code)
"PL", # pylint subset
"RUF", # ruff-specific rules
]
ignore = [
"S101", # assert usage (fine in tests)
"PLR0913", # too many arguments (relax for data-heavy functions)
]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "PLR2004", "T20"] # Allow asserts, magic values, prints in tests
[tool.ruff.lint.isort]
known-first-party = ["PROJECT_NAME"] # Replace with actual package name
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
# =============================================================================
# Mypy — Type Checking
# =============================================================================
[tool.mypy]
python_version = "3.12" # Adjust to project's minimum Python version
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
strict_equality = true
warn_redundant_casts = true
warn_unused_ignores = true
no_implicit_reexport = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false # Relax for test functions
# =============================================================================
# Pytest
# =============================================================================
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --strict-markers"
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
]
select not extend-select: Explicit about exactly which rules are activeS rules included: Ruff's built-in bandit subset catches common security issues during lintingdisallow_untyped_defs forces type annotations — relax per-module if neededAdd to pyproject.toml under dev dependencies:
[project.optional-dependencies]
dev = [
"ruff>=0.8",
"mypy>=1.13",
"pytest>=8.0",
"pytest-cov>=6.0",
"pre-commit>=4.0",
]
Or with uv groups:
[dependency-groups]
dev = [
"ruff>=0.8",
"mypy>=1.13",
"pytest>=8.0",
"pytest-cov>=6.0",
"pre-commit>=4.0",
]
Tools that run via uvx (no install needed): xenon, vulture.
Add these targets to the project Makefile:
# =============================================================================
# Code Quality
# =============================================================================
lint:
uv run ruff check src tests
lint-fix:
uv run ruff check --fix src tests
format:
uv run ruff format src tests
format-check:
uv run ruff format --check src tests
typecheck:
uv run mypy src/PROJECT_NAME
# File length gate (max 500 lines per .py file)
MAX_LINES := 500
file-length:
@FAILED=0; \
for f in $$(find src/ -name '*.py'); do \
count=$$(wc -l < "$$f"); \
if [ "$$count" -gt $(MAX_LINES) ]; then \
echo "ERROR: $$f has $$count lines (max $(MAX_LINES))"; \
FAILED=1; \
fi; \
done; \
if [ "$$FAILED" -eq 1 ]; then \
echo ""; \
echo "Files exceeding $(MAX_LINES) lines must be split into smaller modules."; \
exit 1; \
fi; \
echo "All files under $(MAX_LINES) lines."
# Cyclomatic complexity gate (Xenon grade C)
complexity:
cd /tmp && uvx xenon --max-absolute C --max-modules D --max-average C $(CURDIR)/src/
# Dead code detection
dead-code:
uvx vulture src/ --min-confidence 90
# Ensure dev dependencies are installed
ensure-dev:
@uv sync --all-extras --quiet
# Run all checks (same as CI)
check: ensure-dev lint format-check typecheck file-length complexity test
@echo "All checks passed!"
# Full CI-equivalent pipeline (locally)
ci: ensure-dev lint format-check typecheck file-length complexity dead-code test
@echo "Full CI pipeline passed!"
Create .pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6 # Pin to a specific version
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [] # Add stubs your project needs
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
args: ['--maxkb=500']
- id: check-merge-conflict
- id: debug-statements
Install hooks:
uv run pre-commit install
make lint format-check typecheck
make check
This runs: lint → format-check → typecheck → file-length → complexity → test
make ci
Adds: dead-code to the check pipeline.
When asked to review or improve code quality in an existing project, follow this order:
# Check if quality tools are configured
cat pyproject.toml | grep -A5 'tool.ruff'
cat pyproject.toml | grep -A5 'tool.mypy'
ls .pre-commit-config.yaml
ls Makefile
make lint 2>/dev/null || uv run ruff check src/
make typecheck 2>/dev/null || uv run mypy src/
Structure findings as:
make lint-fixmake format| Metric | Tool | Threshold | Action |
|---|---|---|---|
| Cyclomatic complexity | xenon | Grade C (max per function) | Refactor functions with complexity > 10 |
| Module complexity | xenon | Grade D (max per module) | Split modules that are too complex |
| Average complexity | xenon | Grade C (project average) | Overall project health indicator |
| File length | wc -l | 500 lines | Split into submodules |
| Dead code confidence | vulture | 90% | Investigate and remove confirmed dead code |
| Code | Name | What It Catches |
|---|---|---|
| E/W | pycodestyle | Basic style violations |
| F | pyflakes | Unused imports, undefined names |
| I | isort | Import ordering |
| N | pep8-naming | Naming convention violations |
| UP | pyupgrade | Python version upgrade opportunities |
| B | flake8-bugbear | Common bugs and design problems |
| SIM | flake8-simplify | Code that can be simplified |
| S | flake8-bandit | Security issues |
| C4 | flake8-comprehensions | Unnecessary list/dict/set calls |
| PTH | flake8-use-pathlib | os.path → pathlib suggestions |
| ERA | eradicate | Commented-out code |
| PL | pylint | Subset of pylint checks |
| RUF | ruff-specific | Ruff's own rules |
--ignore-missing-imports everywhere: Fix the imports, add stubsuv run and uvx — no system pollutionWhen a project already has some quality tooling:
disallow_untyped_defs = false, then tighten# type: ignore[specific-error] for known issues, never blanket ignores--min-confidence 90 for vulture to reduce false positives in new projects