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>
3.4 KiB
3.4 KiB
| 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
- SQLAlchemy 2.0 + psycopg3 игнорирует
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 строки- Длинная сигнатура — аргументы по одному
- SQL-литералы в
- 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.