gendesign/.claude/rules/backend.md
lekss361 ae0fce3528
refactor(claude): split 700-line CLAUDE.md into path-scoped .claude/rules/ (#188)
Old CLAUDE.md was 700 lines — exceeded Anthropic 200-line recommendation,
adherence degraded under context pressure.

Refactor per research findings:

- CLAUDE.md 700 -> 97 lines (essential only, pointers to rules)
- .claude/rules/{backend,frontend,sql,git-pr,deploy}.md (NEW, path-scoped)
- ~/.claude/CLAUDE.md (NEW, 21 lines) — personal cross-project prefs
- memory/ — deleted 12 feedback files promoted to proper layer (CLAUDE.md
  rules table, .claude/rules/, ~/.claude/CLAUDE.md). 7 remaining = workflow
  details.
- .gitignore: !.claude/agents/, !.claude/rules/ exceptions

Total auto-loaded per session: 97 (CLAUDE.md) + 21 (~/.claude) = 118 lines
vs prior 700. Rules in .claude/rules/ load only when matching paths touched.

Resolves conflict no_self_review vs pre_push_review: now single rule in
git-pr.md Review workflow section (pre-push = local lint; post-push =
external Claude window posting as lekss361).

Code-reviewer feedback applied:
- paths: frontmatter added to git-pr.md
- Auto-merge scope blocked-list now includes .claude/rules/*.md changes
  to prevent self-extending rules without human approval.

Co-authored-by: lekss361 <claudestars@proton.me>
2026-05-15 23:06:33 +03:00

81 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
paths: backend/**/*.py
---
# Backend conventions — Python 3.12 / FastAPI
## psycopg v3 (CRITICAL — recurring bug class)
- `import psycopg2`**ModuleNotFoundError**его нет в зависимостях. Только `psycopg[binary]>=3.2.0`.
- Bulk INSERT: `cur.executemany()` или COPY — НЕ `execute_values` (это psycopg2 API).
- В SQL `text(...)`: всегда `CAST(:name AS type)` — НИКОГДА `:name::type`.
- SQLAlchemy 2.0 + psycopg3 **игнорирует** `::type` после `:name``psycopg.errors.SyntaxError: syntax error at or near ':'`
- Исключение: `ARRAY[:rc]::int[]` РАБОТАЕТ — `::` приклеено к `)`, не к bind-name
- Pre-PR check: `grep -nE ':[a-z_]+::[a-z]' backend/app` → должен быть пуст
- Reference: vault `Pattern_CAST_AS_Type`, `Bug_SQLAlchemy_DoubleColon_Cast`
## SAVEPOINT pattern (loop upserts)
В цикле INSERT/UPSERT — **обязательно** `with db.begin_nested():` per-row:
```python
for row in items:
try:
with db.begin_nested():
db.execute(text("INSERT INTO ..."), {...})
except Exception as e:
logger.warning("row failed: %s", e)
```
❌ Bare `db.rollback()` в loop — откатывает **всю outer tx**, счётчики `inserted`/`updated` продолжают расти → inconsistent state.
Reference: vault `Bug_Pzz_Loader_Missing_Savepoint_May14`, `domrf_kn.py:427/452/472`.
## SQL injection prevention
**Никогда**:
- f-string в SQL: `text(f"INSERT ... VALUES ({values})")`
- Home-rolled quote escape: `c.replace(chr(39), chr(39)*2)`
- String concat: `"... WHERE id = " + str(id)`
**Canonical**: parametrized batch через SQLAlchemy `text` + list of dicts:
```python
db.execute(
text("INSERT INTO targets (job_id, cad_num) VALUES (:job_id, :cad_num) "
"ON CONFLICT (job_id, cad_num) DO NOTHING"),
[{"job_id": j, "cad_num": c} for c in cad_nums],
)
```
Reference: vault `Bug_Nspd_Geo_Sql_Injection_May14`.
## Ruff / code style
- **Line length 100** (`backend/pyproject.toml`)
- Типичные ловушки E501:
- SQL-литералы в `text("""...""")` — переноси после `FROM` / `WHERE` / `AND`
- `logger.info("...", a, b, c, d)` — разбивай на 2 строки
- Длинная сигнатура — аргументы по одному
- Ruff rules: `E F I B UP N RUF ASYNC` (RUF001/002/003 игнор — кириллица OK)
- mypy `strict`: `app.services.generative.*` + `app.services.site_finder.scorer`
- target-version `py312``dict | None`, `list[int]`, walrus OK
## Async / Celery
- FastAPI handlers: `async def`
- Celery tasks: `def` (Celery sync) — НЕ `async def` внутри task
- Sync↔async bridge через `asyncio.run()` внутри Celery task
- Tests: pytest + `asyncio_mode = auto`
## Запреты
-`print()` — только `logger.info/warning/error`
-`import requests` — только `httpx`
-`except Exception: pass` без re-raise или `logger.exception`
- ❌ Hardcode credentials — `os.environ.get("KEY")` или `settings.KEY`
-`Any` без комментария почему
## Worker crash checklist
Worker падает на старте → скорее всего import error: `pyproject.toml` имеет dep но `uv.lock` не обновлён → `cd backend && uv lock` → commit lock → push.