ADR-003: Formatter and Linter

Field

Value

Status

Accepted

Date

2026-05-18

Deciders

AI Team

Supersedes

None

Superseded by


Context

The sprintstart-ai service is built in Python 3.12 and uses uv with pyproject.toml for dependency management.

Before development continues, the team needs to agree on a formatter and linter setup to ensure consistent code quality across local development and CI.

This decision affects:

  1. Code formatting — ensures consistent style and readability across the project

  2. Linting — catches common issues, unused imports, and style violations early

  3. CI quality gates — enforces consistent standards before merging code

  4. Developer onboarding — simplifies local setup and development workflows

This decision blocks the final CI quality pipeline setup.

Decision Drivers

  • Consistent code style across the project

  • Fast feedback in local development and CI

  • Simple configuration in pyproject.toml

  • Low maintenance overhead

  • Compatibility with Python 3.12

  • Good integration with uv and GitHub Actions

Considered Options

Formatter / Linter

  • Option A – Ruff

  • Option B – Black + isort + Flake8

  • Option C – Pylint + Black + isort

Decision

Formatter and linter: Ruff

Ruff will be configured in pyproject.toml and executed in CI using:

uv run ruff check .
uv run ruff format --check .

Selected Ruff configuration:

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I"]

Rationale

Ruff is the best option for our project because it combines linting, formatting, and import sorting into a single fast tool.

It integrates well with our existing uv + pyproject.toml setup and reduces tooling complexity compared to maintaining multiple separate tools.

Its performance also improves developer experience and CI execution speed.

Pros and Cons of the Options

Formatter / Linter

Option A – Ruff

  • ✅ Extremely fast

  • ✅ Combines linting, formatting, and import sorting

  • ✅ Simple configuration in pyproject.toml

  • ✅ Works well with uv and GitHub Actions

  • ✅ Reduces the number of required tools

  • ❌ Relatively new compared to Black or Flake8

  • ❌ Some team members may be unfamiliar

Option B – Black + isort + Flake8

  • ✅ Mature and widely used

  • ✅ Clear separation of responsibilities

  • ✅ Familiar to many Python developers

  • ❌ Requires multiple tools and configurations

  • ❌ More maintenance overhead

  • ❌ Slower CI execution compared to Ruff

Option C – Pylint + Black + isort

  • ✅ Very detailed static analysis

  • ✅ Mature and feature-rich

  • ✅ Highly configurable

  • ❌ More complex setup and configuration

  • ❌ Can be overly strict for early-stage projects

  • ❌ Slower feedback during development and CI

Consequences

Positive:

  • Consistent formatting across the entire codebase

  • Faster CI quality checks

  • Simpler developer setup

  • Import ordering handled automatically

  • Reduced tooling complexity

Negative / Trade-offs:

  • Team members unfamiliar with Ruff may require a short ramp-up

  • Ruff behavior may differ slightly from Black or Flake8 defaults

Follow-up actions:

  • [x] Add Ruff to dev dependencies

  • [x] Configure Ruff in pyproject.toml

  • [x] Add Ruff lint check to GitHub Actions

  • [x] Add Ruff format check to GitHub Actions

  • [ ] Document local Ruff usage in README