Skip to content

Commit 0f630d2

Browse files
chore(backend): add ruff lint ratchet + CI gate; dedupe email-validator (#193, #194)
#193: backend had no linter. Add ruff with the default correctness rule set (E4/E7/E9/F), gated in CI (ruff check .). Mirrors the frontend ESLint ratchet (#212): the 94 pre-existing violations are baselined in ruff.toml per-file-ignores, so the check is green today and any NEW violation — a new file, or a new rule category in an existing file — fails CI. The baseline can only shrink. Formatting (ruff format) is intentionally left un-gated: enabling it would require a one-shot reformat of the whole backend that would collide with every in-flight PR. Line-length / style stays the formatter's job, run manually for now. Updated the CLAUDE.md lint TODO accordingly. #194: email-validator was listed twice (runtime dep + under # Testing). Removed the duplicate; it now appears once.
1 parent a27b688 commit 0f630d2

4 files changed

Lines changed: 80 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ jobs:
3535
run: |
3636
grep -vE '^(torch|docling|transformers)' requirements.txt > /tmp/req-ci.txt
3737
pip install -r /tmp/req-ci.txt
38+
# #193: ruff lint, gated against the baseline in backend/ruff.toml. New
39+
# violations (new files, or new rule categories in existing files) fail.
40+
- name: Lint (ruff — baselined ratchet)
41+
run: ruff check .
3842
- name: Run pytest
3943
env:
4044
# Non-secret dummy values so config validation / DB-URL construction /

CLAUDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ Docker (full stack from repo root):
4444
docker-compose up
4545
```
4646

47-
Lint: # TODO: no lint command defined (no ruff/flake8/black config in repo).
47+
Lint (run from `backend/`):
48+
49+
```
50+
ruff check . # lint, gated in CI against the ruff.toml baseline (#193)
51+
ruff format . # formatter — available, not yet CI-gated (see ruff.toml)
52+
```
4853

4954
## Conventions
5055

backend/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ python-Levenshtein
2828
# Testing
2929
pytest
3030
pytest-anyio
31-
email-validator
31+
32+
# Linting (#193): `ruff check` is gated in CI against the baseline in ruff.toml.
33+
ruff
3234

3335
# Pydantic AI agents (see docs/decisions/0001-adopt-pydantic-ai.md)
3436
pydantic-ai-slim[google]>=0.0.20

backend/ruff.toml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Backend linter (#193) — the analog of the frontend ESLint ratchet (#212).
2+
#
3+
# Every rule in `select` is enforced. The 94 pre-existing violations are
4+
# baselined in [lint.per-file-ignores] so `ruff check .` is green today, and
5+
# any NEW violation — in a new file, or a NEW rule category in an existing
6+
# file — fails CI. This is a true ratchet: the baseline can only shrink.
7+
#
8+
# Scope is ruff's default correctness set: E4 (import placement), E7 (statement
9+
# errors), E9 (syntax), F (pyflakes — unused imports/vars, undefined names).
10+
# Pure style/formatting (E501 line length, whitespace, quotes) is deliberately
11+
# NOT enforced here — that is `ruff format`'s job, and enabling it would require
12+
# a one-shot reformat of the whole backend that would collide with every
13+
# in-flight PR. `ruff format` is available to run manually; CI does not gate on
14+
# it yet. (line-length below only informs the formatter when it's used.)
15+
#
16+
# Burning the baseline down: most entries are F401 (unused import) and F841
17+
# (unused variable) — delete the dead import/var and remove the file's line
18+
# from the baseline. E402 (import-not-at-top) entries are mostly intentional
19+
# (deliberate import ordering after runtime config); leave those.
20+
21+
target-version = "py313"
22+
line-length = 100
23+
24+
[lint]
25+
select = ["E4", "E7", "E9", "F"]
26+
27+
# Baseline of pre-existing violations as of 2026-06-12 (generated from
28+
# `ruff check . --select E4,E7,E9,F`). Do NOT add new entries — fix the
29+
# violation instead.
30+
[lint.per-file-ignores]
31+
"db/dedup_nodes.py" = ["E402"]
32+
"main.py" = ["E402"]
33+
"routes/admin.py" = ["F401"]
34+
"routes/auth.py" = ["F401"]
35+
"routes/calendar.py" = ["F401"]
36+
"routes/documents.py" = ["E402"]
37+
"routes/flashcards.py" = ["F401"]
38+
"routes/notes.py" = ["F401"]
39+
"routes/profile.py" = ["F401"]
40+
"routes/social.py" = ["F841"]
41+
"services/flashcard_import_service.py" = ["F401"]
42+
"services/gemini_service.py" = ["F541"]
43+
"services/notes_service.py" = ["E741"]
44+
"tests/evals/concept_extraction.py" = ["F401"]
45+
"tests/evals/document_classification.py" = ["F401"]
46+
"tests/evals/document_summary.py" = ["F401"]
47+
"tests/evals/quiz_generation.py" = ["F401"]
48+
"tests/evals/syllabus_extraction.py" = ["F401"]
49+
"tests/test_achievement_service.py" = ["F401", "F841"]
50+
"tests/test_admin_routes.py" = ["F401"]
51+
"tests/test_auth_state.py" = ["E402", "F401"]
52+
"tests/test_calendar_routes.py" = ["F401"]
53+
"tests/test_chat_context_tools.py" = ["F401"]
54+
"tests/test_documents_routes.py" = ["F401"]
55+
"tests/test_encryption.py" = ["F401"]
56+
"tests/test_extraction_backends.py" = ["E402", "F401"]
57+
"tests/test_extraction_service.py" = ["E402", "F401"]
58+
"tests/test_flashcard_import_routes.py" = ["F401"]
59+
"tests/test_flashcard_import_service.py" = ["E402", "F401"]
60+
"tests/test_learn_routes.py" = ["F401"]
61+
"tests/test_notes_service.py" = ["E402", "F401"]
62+
"tests/test_ocr_pipeline.py" = ["E402", "E702"]
63+
"tests/test_onboarding_routes.py" = ["F401"]
64+
"tests/test_profile_routes.py" = ["F401", "F841"]
65+
"tests/test_shared_course_context.py" = ["E701", "F401"]
66+
"tests/test_supabase.py" = ["E402", "F541"]
67+
"tests/test_syllabus_adapter.py" = ["F401"]

0 commit comments

Comments
 (0)