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

3.4 KiB
Raw Blame History

paths
backend/**/*.py

Backend conventions — Python 3.12 / FastAPI

psycopg v3 (CRITICAL — recurring bug class)

  • import psycopg2ModuleNotFoundErrorего нет в зависимостях. Только 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 после :namepsycopg.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:

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:

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 py312dict | 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.