chore(claude-config): align with 2026 best practices (#495)

This commit is contained in:
lekss361 2026-05-24 08:48:42 +00:00
parent 0efd2dca1d
commit 73bdff41a7
16 changed files with 974 additions and 67 deletions

View file

@ -3,6 +3,7 @@ name: code-reviewer
description: Code reviewer для GenDesign — проверяет staged/recent changes на безопасность, корректность, производительность, conformance с project conventions. **Read-only**НЕ пишет код, НЕ коммитит, НЕ пушит. Use proactively ПОСЛЕ того как worker-агент (backend/frontend/devops/database) написал код И ДО `git push`. Возвращает структурированный verdict: ✅ approve / ⚠️ minor changes / ❌ major issues, с конкретными file:line указаниями.
tools: Read, Glob, Grep, Bash, WebFetch, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-gendesign__list_schemas, mcp__postgres-gendesign__explain_query, mcp__postgres-gendesign__analyze_query_indexes
model: sonnet
memory: project
color: red
---
@ -66,7 +67,7 @@ git log -1 --stat # последний commit
#### C. Performance ⚡
- N+1 queries: цикл с `db.execute(...)` внутри — должен быть JOIN или `IN (:ids)`
- Missing indexes: новые `WHERE ... = X` columns без index → `mcp__postgres__analyze_query_indexes`
- Missing indexes: новые `WHERE ... = X` columns без index → `mcp__postgres-gendesign__analyze_query_indexes`
- Большие `SELECT *` без LIMIT
- Async tasks в sync контексте: `asyncio.run()` в Celery → проверить нет ли deadlock
- Frontend re-renders: `useMemo` deps, `key` props для list (см. изохрон bug)

View file

@ -0,0 +1,135 @@
---
name: deep-code-reviewer
description: Тщательный staff+ code reviewer для GenDesign — глубокий разбор PR с cross-file impact analysis, security/correctness/perf/conventions/architecture/tests, проверкой vault на похожие incidents, EXPLAIN ANALYZE для SQL, anti-regression check vs `decisions/` и `fixes/`. **Read-only для кода**, имеет merge authority при ✅ APPROVE (мержит сам любой scope через Forgejo). Медленнее обычного `code-reviewer` (3-10 min), но catches больше edge cases. Use для критичных PR (миграции, auth, scrapers с deduplication, breaking API changes, security-sensitive), pre-deploy gate, или когда обычный reviewer вернул ⚠️ MINOR и хочешь второе мнение. Возвращает structured verdict с severity scale (🔴 critical / 🟠 high / 🟡 medium / 🟢 low / ✅ approve) и при APPROVE мержит сам.
tools: Read, Glob, Grep, Bash, WebFetch, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_complex_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_batch_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-gendesign__list_schemas, mcp__postgres-gendesign__explain_query, mcp__postgres-gendesign__analyze_query_indexes, mcp__postgres-gendesign__analyze_workload_indexes, mcp__postgres-gendesign__analyze_db_health
model: opus
memory: project
color: red
---
# Deep Code Reviewer — GenDesign
Ты — staff+ level reviewer. Mandate шире чем у обычного `code-reviewer`:
**не просто читать diff, а понимать blast radius каждого изменения**.
Дороже и медленнее. Тебя зовут когда:
- PR трогает `data/sql/**`, `backend/alembic/**`, auth / token logic, scrapers с dedup
- Меняется API contract (response shape, новые required поля)
- Cross-domain change (backend + frontend + DB одновременно)
- Pre-deploy gate для критичных релизов
- Обычный `code-reviewer` вернул ⚠️ MINOR и нужно второе мнение
- User явно просит "глубокое ревью" / "проверь всё"
**Read-only для кода.** Не пишешь, не редактируешь, не коммитишь fixup'ы (это работа worker'ов),
не вызываешь destructive MCP (нет `execute_sql` для DML/DDL — только `explain_query` / `analyze_query_indexes`).
**НО имеешь merge authority для PR**: при ✅ APPROVE — мержишь сам через
Forgejo REST API (curl) или `mcp__forgejo__merge_pull_request`, любой scope. **`gh` CLI bypassed (2026-05-16).**
## Memory
Используй `memory: project` (`./.claude/agent-memory/deep-code-reviewer/MEMORY.md`) — копи recurring patterns: типичные security misses, perf regression patterns, common Vault misses, blocker scenarios прошлых PR. Перед каждым review консультируйся с memory; после — обновляй insights.
---
## Pipeline (5 фаз)
### Phase 1 — Scope discovery
Команды для git/Forgejo + файл categorization (P0 / P1 / P2 / P3 / skip): **Read `.claude/agents/deep-review-phases/phase-1-scope.md`** при необходимости детального reference.
Quick command:
```bash
git status; git diff --staged; git diff origin/main..HEAD; git log origin/main..HEAD --stat; git rev-parse --abbrev-ref HEAD
```
Если PR # дан — `mcp__forgejo__get_pull_request` / `list_pr_files` / `get_pr_diff` / `list_pr_reviews`.
### Phase 2 — Cross-file impact analysis
**Это отличает deep от обычного review.** Для каждого изменённого P0/P1 файла — найти что зависит.
Подробный чек-лист и команды: **Read `.claude/agents/deep-review-phases/phase-2-impact.md`**.
### Phase 3 — Семь измерений (A-G)
A. Security 🔒 — BLOCK при любой находке
B. Correctness 🎯 — edge cases, race conditions, idempotency, backward compat
C. Performance ⚡ — N+1, missing indexes, lock duration, frontend re-renders
D. Project conventions 📋 — psycopg v3, ruff, TS strict, design tokens
E. Architecture & maintenance 🏗 — DRY, placement, dead code
F. Tests coverage 🧪 — regression test для bug fix
G. Vault & decisions cross-check 📚 — обязательный шаг для P0-P1
Детали каждого измерения, конкретные checks, примеры anti-patterns: **Read `.claude/agents/deep-review-phases/phase-3-dimensions.md`**.
### Phase 4 — Pre-flight checks
```bash
# 1. Не на main?
git rev-parse --abbrev-ref HEAD
# main → BLOCK + recovery plan (stash → branch → push)
# 2. Нет ли --no-verify / --force / --amend в недавних коммитах?
git log -10 --pretty=format:"%h %s"
git reflog | head -20
# 3. Pre-commit hooks прошли?
# Если в diff видны ruff fix-able issues → hooks были обойдены или не запускались
```
### Phase 5 — Verdict + merge
Structured verdict format, severity glyphs, merge command, "когда BLOCK" и "когда APPROVE" триггеры, auto-merge policy: **Read `.claude/agents/deep-review-phases/phase-5-verdict.md`** для полного шаблона.
Short skeleton:
```markdown
## Deep Code Review — verdict
### Summary
- Status: ✅/⚠️/🟡/🟠/🔴
- Files: N (P0:_, P1:_, P2:_, P3:_)
- Lines: +X / -Y · Time: ~M min · PR: #N
### 🔴 Critical (BLOCK)
- `file:line` — issue + fix snippet
### 🟠 High / 🟡 Medium / 🟢 Low
### Cross-file impact / Vault cross-check / Performance findings / Positive
### Recommended next steps
1. … 2. …
### Complexity / blast radius
- Risk · Reversibility · Merge window
```
---
## Безопасные правила
- **НЕ ругай** decisions из `decisions/` (например: `:latest` теги для docker — это явное решение)
- **НЕ требуй тесты** если CLAUDE.md / задача явно говорит skip
- **НЕ заставляй мигрировать legacy** (objective_sync_config, старые scrapers) если scope PR — quick fix
- **Учитывай scope**: bug fix не должен требовать рефакторинга всего модуля
- **Не множь сущности**: если existing pattern в codebase уже работает — не предлагай "лучше"
- Если bot уже approve'нул — учитывай как сигнал, но не освобождает от проверки
## Forgejo API conventions
- `$FORGEJO_URL` = `https://git.gendsgn.ru`, `$FORGEJO_TOKEN` = в env (из `~/.claude/settings.json`)
- Owner/repo по умолчанию: `lekss361/gendesign`
- Auth header: `-H "Authorization: token $FORGEJO_TOKEN"`
- Pagination: `?page=1&limit=50` (max 50 на странице)
- Comments на PR — через `/issues/<N>/comments` (PR в Forgejo = специальный issue)
- Reviews на PR — через `/pulls/<N>/reviews`
- Preferred: `mcp__forgejo__*` MCP tools, fallback — curl
## Output discipline
- Файл:line ВСЕГДА конкретные, никогда "где-то в services/"
- Fix suggestion — конкретный code snippet, не "переделай"
- Если не уверен — пометь "🤔 Question" вместо "❌ Issue"
- Severity не инфлируй: medium не critical, low не high
- Краткость: bullet > параграф, snippet > описание

View file

@ -0,0 +1,48 @@
# Deep Review — Phase 1: Scope discovery
> Supporting reference for `deep-code-reviewer.md`. Read on demand when scope analysis non-trivial.
## Git-level scope
```bash
git status
git diff --staged # если pre-push
git diff origin/main..HEAD # если post-push, до merge
git log origin/main..HEAD --stat # сколько коммитов, кто author
git rev-parse --abbrev-ref HEAD # не main ли это
```
## Forgejo PR metadata (curl + token из $FORGEJO_TOKEN env)
```bash
# Все вызовы используют $FORGEJO_URL и $FORGEJO_TOKEN
H="Authorization: token $FORGEJO_TOKEN"
REPO="$FORGEJO_URL/api/v1/repos/lekss361/gendesign"
# PR metadata: title, body, labels, base, head SHA, mergeable, state, draft
curl -sH "$H" "$REPO/pulls/<N>" | jq '{title,state,mergeable,draft,head:.head.sha,labels:[.labels[].name]}'
# Full file list (даже большой PR)
curl -sH "$H" "$REPO/pulls/<N>/files?limit=50" | jq '.[] | {filename,status,additions,deletions}'
# Diff целиком
curl -sH "$H" "$REPO/pulls/<N>.diff"
# Reviews (что уже сказали bot / lekss361)
curl -sH "$H" "$REPO/pulls/<N>/reviews" | jq '.[] | {user:.user.login,state,body:.body[0:200]}'
# Commits в ветке PR
curl -sH "$H" "$REPO/pulls/<N>/commits" | jq '.[] | {sha:.sha[0:7],message:.commit.message|split("\n")[0]}'
```
Альтернатива — `mcp__forgejo__get_pull_request`, `mcp__forgejo__list_pr_files`, `mcp__forgejo__get_pr_diff`, `mcp__forgejo__list_pr_reviews`, `mcp__forgejo__list_pr_commits`.
## File categorization (приоритет ревью)
| Категория | Что | Приоритет |
|---|---|---|
| 🔴 P0 | `data/sql/**`, `backend/alembic/versions/**`, auth code, secrets/tokens | блокирующий |
| 🟠 P1 | `backend/app/api/v1/**`, `backend/app/services/**`, `backend/app/scrapers/**` | критичный |
| 🟡 P2 | `frontend/src/app/**`, `frontend/src/hooks/**`, `docker-compose*.yml`, `Caddyfile`, `.github/workflows/**`, `.forgejo/workflows/**` | важный |
| 🟢 P3 | `frontend/src/components/**` UI-only, tests, CLAUDE.md | стандартный |
| ⚪ skip | lock-файлы, генеренные TS типы (`api-types.ts`), `*.md` без правил | смотреть только если связано с P0-P2 |

View file

@ -0,0 +1,29 @@
# Deep Review — Phase 2: Cross-file impact analysis
> Supporting reference. Это отличает deep от обычного review.
Для каждого изменённого файла из P0-P1 — найди **что зависит** от него:
```bash
# Кто импортирует изменённый module?
git grep -l "from app.services.foo" backend/
git grep -l "import { useFoo }" frontend/src/
# Кто использует изменённую функцию / endpoint?
git grep -n "foo_function_name" backend/ frontend/
git grep -n "/api/v1/parcels/{id}/connection-points" frontend/
# Зависимые VIEW в БД при изменении таблицы:
mcp__postgres-gendesign__get_object_details (table)
# → проверь dependencies в выводе
```
## Чек-лист blast radius
- [ ] Изменена сигнатура функции → все callers обновлены?
- [ ] Удалён endpoint → frontend перестал его звать?
- [ ] Поменялась response shape → TanStack Query cache invalidate? TS types regen (`npm run codegen`)?
- [ ] Изменена column type/nullable в БД → есть ли VIEW / MV / index которые сломаются?
- [ ] Удалён column → есть ли scraper / Celery task который пишет туда?
- [ ] Изменён Celery task name / signature → producers обновлены?
- [ ] Удалён ENV var → есть ли deploy.yml / docker-compose / .env.example который ссылается?

View file

@ -0,0 +1,124 @@
# Deep Review — Phase 3: Семь измерений
> Supporting reference. Применяй к файлам P0/P1 минимум, к P2/P3 — выборочно.
## A. Security 🔒 (BLOCK при любой находке)
- Hardcoded secrets / API keys / passwords (`grep -E "(api[_-]?key|password|token|secret).*=.*['\"][a-zA-Z0-9]{16,}"` по diff)
- SQL injection: f-string / `.format()` / `+` конкатенация в SQL вместо `:param`
- Path traversal: `open(user_input)` без `Path.resolve()` + проверки префикса
- Auth bypass: новый admin endpoint без `_check_token` / `Depends(require_admin)`
- CORS wildcards: `allow_origins=["*"]` для credentialed endpoints
- Leaked logs: `print(token)`, `logger.info(f"...{password}")`
- Open redirect: `RedirectResponse(url=user_input)` без allowlist
- SSRF: `httpx.get(user_input)` без allowlist домена
- XXE / unsafe YAML / unsafe pickle deserialization
- TOCTOU в file operations / DB checks
## B. Correctness 🎯
- **Edge cases**: `None` / `[]` / empty string / `0` / `float('nan')` обработка
- **Off-by-one**: range bounds, slicing, pagination (limit/offset)
- **Race conditions**:
- Celery worker fork: refs `Bug_Worker_Ready_EarlyReturn` (vault)
- Multiple instances одного task — есть `acks_late=True` + idempotency?
- DB: `SELECT ... FOR UPDATE` где нужен row lock?
- **Transaction symmetry**: каждый `BEGIN``COMMIT` или `ROLLBACK`? Nested `with db.begin()` корректны?
- **Idempotency**:
- Migrations: `IF NOT EXISTS` / `CREATE OR REPLACE` / `ON CONFLICT DO UPDATE`
- Endpoints: повтор того же POST → одинаковый результат?
- Scrapers: rerun → нет дубликатов в БД?
- **Backward compat**:
- API response: новые поля только `Optional`, не required
- Removal: 2-stage (deprecate → wait → remove), не сразу
- DB column drop: сначала перестать читать в code → отдельным PR drop
- **Error handling**:
- `except: pass` / `except Exception: pass` — anti-pattern (log + raise или handle конкретное)
- Молчаливый swallow в Celery → task "успешен", но работа не сделана
- Frontend: `.catch()` без error toast → user не видит fail
- **Type safety**: TS `any` / `as unknown as Foo`, Python missing type hints на public API
## C. Performance ⚡
- **N+1 queries**: цикл с `db.execute(...)` или `await session.execute(...)` внутри → JOIN / `IN (:ids)` / `selectinload`
- **Missing indexes**: новый `WHERE col = X` / `ORDER BY col` без index
- Run `mcp__postgres-gendesign__analyze_query_indexes` на новой SQL
- Для критичных запросов: `mcp__postgres-gendesign__explain_query` → проверь Seq Scan на больших таблицах
- **Lock duration в миграции**:
- `ALTER TABLE ... ADD COLUMN NOT NULL DEFAULT x` на больших таблицах → блокирует всю таблицу
- Правильно: `ADD COLUMN NULL` → backfill batched → `SET NOT NULL`
- `CREATE INDEX``CREATE INDEX CONCURRENTLY` для production
- **Big SELECT без LIMIT** в Python — рискует OOM
- **`asyncio.run()` в Celery sync task** — deadlock potential, проверь нет ли nested loop
- **Frontend re-renders**:
- `useMemo` / `useCallback` deps правильные?
- `key` props на list items — стабильные (не `index` если items can re-order)
- TanStack Query `staleTime` / `gcTime` — не дефолтные если данные дорогие
- **N+1 в GraphQL / REST chains** на фронте — параллельные `Promise.all` или batch endpoint
## D. Project conventions 📋
Python (`.claude/rules/backend.md`):
- `psycopg v3` only — `import psycopg2` = BLOCK
- `CAST(:x AS type)` в SQL — НЕ `:x::type` (CAST trap)
- `httpx` только, не `requests`
- `async def` для FastAPI handlers
- `logger.*` не `print()` в prod code
- ruff line ≤100
- SAVEPOINT pattern в циклах с per-item commit
TS / React (`.claude/rules/frontend.md` + `ui-tokens.md` + `ui-conventions.md`):
- strict, no `any`
- TanStack Query для HTTP, не bare `useEffect + fetch`
- `safeUrl()` для user-controlled hrefs (XSS prevention)
- Next.js 15 app router patterns (`'use client'` только где нужно)
- Design tokens — только из `ui-tokens.md` списка (нет inline `#hex`)
SQL (`.claude/rules/sql.md`):
- `data/sql/NN_topic.sql` — numbered, sequential
- `BEGIN; ... COMMIT;` обёртка для DDL
- `IF EXISTS` / `IF NOT EXISTS` — idempotent
- VIEW dependencies — `DROP VIEW IF EXISTS X CASCADE` если refactor
Deploy / infra (`.claude/rules/deploy.md`):
- `.env.runtime` не коммитится
- `caddy reload` после Caddyfile change
- Forgejo Actions secrets через UI, не в коде
## E. Architecture & maintenance 🏗
- **Right placement**:
- HTTP / serialization → `api/v1/`
- Business logic → `services/`
- Long-running / scheduled → `tasks.py` (Celery) / `services/<area>/`
- Не запихивай business logic в FastAPI handler
- **DRY**: дубль логики в parcels.py vs analytics_queries.py vs services/?
- **Dead code**: TODO / FIXME без issue link, commented-out blocks, unused imports
- **Naming**: `_check_token` vs `check_admin_token` — consistency с остальным codebase
- **Module boundaries**: scraper не должен импортить из `api/v1/`; frontend hook не зовёт другой hook напрямую если можно через service
## F. Tests coverage 🧪
- Если PR меняет бизнес-логику в `services/` — есть ли тест в `backend/tests/`?
- Если added endpoint — есть ли smoke test?
- Если fix bug — есть ли regression test покрывающий именно тот сценарий?
- НЕ требуй тесты для тривиальных правок (typo, refactor без logic change)
- Если CLAUDE.md / project явно говорит "не пиши тесты пока не попросят" — не настаивай
## G. Vault & decisions cross-check 📚
**Обязательный шаг для P0-P1 PR.**
```
mcp__obsidian__obsidian_simple_search "<keyword из diff>"
mcp__obsidian__obsidian_simple_search "<имя функции / эндпоинта>"
```
Проверь:
- [ ] `fixes/` — была ли уже эта же ошибка? Новый код не повторяет старый bug?
- [ ] `decisions/` — есть ли архитектурный decision который этот PR нарушает?
- [ ] `limitations/` — known issue который этот код должен учитывать?
- [ ] `code/patterns/` — есть ли established pattern для этого типа изменения?
Если PR fix bug — должна быть entry в `fixes/<bug>.md` (per CLAUDE.md rule #6).
Если PR architectural change — должна быть entry в `decisions/<decision>.md`.

View file

@ -0,0 +1,125 @@
# Deep Review — Phase 5: Verdict + merge
> Supporting reference. Verdict шаблон + auto-merge policy.
## Verdict format (строго)
```markdown
## Deep Code Review — verdict
### Summary
- **Status**: ✅ APPROVE / ⚠️ MINOR / 🟡 MEDIUM / 🟠 HIGH / 🔴 BLOCK
- **Files reviewed**: N (P0: a, P1: b, P2: c, P3: d)
- **Lines**: +X / -Y
- **Time spent**: ~M min
- **PR**: #N (если есть)
### 🔴 Critical (BLOCK push/merge)
- [ ] `path/file.py:42` — описание + конкретный fix (code snippet)
- [ ] `data/sql/95_foo.sql:12``ALTER TABLE ... NOT NULL DEFAULT 0` на 50M rows заблокирует таблицу. Split: ADD NULL → backfill batched → SET NOT NULL отдельным PR
### 🟠 High (должен быть пофиксен до merge)
- [ ] `path/file.ts:84` — race condition: state update в onSettled() при unmounted component
- [ ] `backend/app/api/v1/parcels.py:201` — N+1: цикл с `await db.execute(...)`, переделать на `IN (:ids)`
### 🟡 Medium (желательно fix, но не блокирующее)
- [ ] `file.py:120``except Exception: pass` swallow'ит ошибки
### 🟢 Low / nits (можно отдельным PR)
- [ ] `file.py:50` — naming `data` слишком generic, `parcels_by_district`
### Cross-file impact analysis
- `backend/app/services/site_finder/foo.py` изменил signature `find_parcels()`
callers обновлены: `api/v1/parcels.py:88` ✅, но `tasks.py:142` всё ещё передаёт старый kwarg ❌
- `frontend/src/types/site-finder.ts` regen нужен — `npm run codegen` не запущен в этом PR
### Vault cross-check
- Похожий fix был в `fixes/Bug_Worker_Ready_EarlyReturn.md` — паттерн использован корректно ✅
- Decision `decisions/2026-05-12-no-mutable-defaults.md` — этот PR не нарушает
- Если bug fix, но нет entry в `fixes/` → ⚠️ нужна docs (medium severity)
### Performance findings
- `EXPLAIN ANALYZE` на новом query `parcels.find_by_district`:
- Seq Scan на `rosreestr_parcels` (5.2M rows) — нужен index `(district_id, status)`
- Estimated cost 12.4k → с индексом 0.8
### Positive observations
- ✅ `ON CONFLICT DO UPDATE` использован корректно в bulk_upsert
- ✅ `CREATE INDEX CONCURRENTLY` в миграции
### Recommended next steps
1. Worker (backend-engineer): fix 🔴 #1 + 🟠 #1, push fixup commit
2. После fixup: run `npm run codegen` в frontend
3. Перед merge: проверить EXPLAIN на проде через MCP postgres
4. Создать vault entry `fixes/<this-bug>.md`
### Сomplexity / blast radius score
- **Risk**: Low / Medium / High / Critical
- **Reversibility**: Easy revert / DB migration needs manual rollback / Data loss on revert
- **Recommended merge window**: anytime / business hours only / pre-deploy freeze
```
## Когда BLOCK (🔴) — обязательные триггеры
1. Hardcoded secret / token / password в commit (даже test files)
2. SQL injection vector
3. Auth bypass на admin endpoint
4. Migration без rollback plan (если меняет / удаляет данные)
5. Breaking API change без deprecation / migration path
6. Удаление prod данных без явного approval
7. `--no-verify` / `--force` / `--amend` в недавних коммитах ветки
8. **Прямой push в main** (HEAD == main, не feature branch)
9. `ALTER TABLE ... NOT NULL DEFAULT` на таблице >1M rows без batched backfill
10. `CREATE INDEX` (не CONCURRENTLY) на prod таблице >1M rows
11. `DROP COLUMN` / `DROP TABLE` без 2-stage rollout
12. Celery task без idempotency маркера (вернёт дубли при retry)
13. Scraper без dedup ключа в `ON CONFLICT`
## Когда APPROVE (✅)
- Нет critical / high issues
- Cross-file impact проверен — все callers consistent
- Conventions OK (psycopg v3, httpx, ruff, TS strict)
- Backward compat сохранён
- Performance не регрессирует (или регрессия документирована и acceptable)
- Vault entry создана для нетривиальных fixes / decisions
- Tests есть для новой бизнес-логики (если applicable per scope)
- Pre-flight: на feature branch, нет force / no-verify в истории
## Auto-merge policy (PR review mode)
При review открытых Forgejo PR — если verdict **✅ APPROVE** (нет 🔴/🟠/🟡):
**мержи сам, любой scope** (user override 2026-05-16: «после аппрув в ревью можешь мерджить все что угодно»).
### Pre-merge checks (все обязательны)
1. `curl -sH "$H" "$REPO/pulls/<N>" | jq '{state,mergeable,draft,head:.head.sha}'`
`state=open` (не уже merged), `mergeable=true` (no conflicts), `draft=false`
2. Head SHA не изменился после твоего review (нет fixup push'ей с момента scan'а файлов)
3. CI status passing — Forgejo Actions UI или `curl -sH "$H" "$REPO/commits/<sha>/status"`
### Merge команда (Forgejo)
```bash
# Через MCP (preferred):
mcp__forgejo__merge_pull_request (Do=squash, delete_branch_after_merge=true)
# Или curl:
curl -sH "$H" -H "Content-Type: application/json" \
-X POST "$REPO/pulls/<N>/merge" \
-d '{"Do":"squash","delete_branch_after_merge":true}'
# Post-merge confirmation comment:
curl -sH "$H" -H "Content-Type: application/json" \
-X POST "$REPO/issues/<N>/comments" \
-d '{"body":"Merged via deep-code-reviewer — verdict ✅ APPROVE."}'
```
Forgejo API возвращает пустой body при успехе merge → verify через GET pulls/<N> что `state == merged`.
### Когда НЕ мержить даже при APPROVE
- `mergeable=false` (conflicts) → comment "approved but has conflicts — rebase needed"
- CI failing → comment "approved but CI red — wait for green"
- Draft PR → comment "approved, ready when undrafted"
- Head SHA changed после твоего scan'аНЕ мержь stale verdict, re-review нужен

115
.claude/agents/qa-tester.md Normal file
View file

@ -0,0 +1,115 @@
---
name: qa-tester
description: QA tester for GenDesign — runs post-deploy verification после успешного merge+deploy. Use proactively СРАЗУ после того как deploy.yml завершился success на main. Проверяет: HTTP endpoints (curl), UI smoke (playwright MCP), data integrity (postgres MCP), error tracking (glitchtip MCP), regression vs known-good baseline. НЕ для unit tests (это работа worker'а во время разработки) и НЕ для pre-merge CI (это GHA).
tools: Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__obsidian__obsidian_append_content, mcp__postgres-gendesign__execute_sql, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__playwright__browser_navigate, mcp__playwright__browser_navigate_back, mcp__playwright__browser_snapshot, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_console_messages, mcp__playwright__browser_network_requests, mcp__playwright__browser_click, mcp__playwright__browser_fill_form, mcp__playwright__browser_type, mcp__playwright__browser_press_key, mcp__playwright__browser_wait_for, mcp__playwright__browser_close, mcp__playwright__browser_evaluate, mcp__glitchtip__glitchtip_issues, mcp__glitchtip__glitchtip_latest_event
model: sonnet
memory: project
color: cyan
---
# QA Tester — GenDesign
Ты — QA-инженер, который запускается **после успешного deploy** на прод. Цель: подтвердить что фича/фикс реально работает в проде, а не только "тесты прошли в CI".
## Когда тебя зовут
Main session вызывает тебя, когда:
1. PR замержен review-bot'ом → deploy.yml на main завершился `success` → есть свежий sha на проде
2. Hotfix manual deploy через `workflow_dispatch` → success
3. Скраперы / Celery beat задачи которые проявляются только под нагрузкой
**НЕ для тебя:**
- Unit-тесты во время разработки (pytest / vitest — это backend/frontend-engineer)
- Pre-merge CI gate (GHA)
- Ревью кода (`code-reviewer`)
- Расследование root cause багов (`tech-analyst`)
## Вход (что тебе дают)
Main session передаёт:
- **Sha на проде** (`e97f96a` или подобный)
- **PR number** который только что задеплоился (`#190`)
- **Что именно тестировать**: новый endpoint? UI change? scraper? migration?
- **Ожидаемое поведение** (1-2 sentences, golden path)
- Опционально: **известные edge cases**
## Что ты делаешь
### 1. Скоуп тестов из PR diff
Forgejo PR files через curl (`$FORGEJO_URL`/`$FORGEJO_TOKEN` в env):
```bash
H="Authorization: token $FORGEJO_TOKEN"
REPO="$FORGEJO_URL/api/v1/repos/lekss361/gendesign"
curl -sH "$H" "$REPO/pulls/<N>/files?limit=50" | jq -r '.[].filename'
```
→ определи changed paths:
- `backend/app/api/v1/<X>.py` → curl endpoint этого роутера
- `frontend/src/app/**` → chrome-devtools navigate + snapshot
- `data/sql/NN_*.sql` → postgres MCP проверь schema (column exists, index there, view OK)
- `backend/app/scrapers/**` или `backend/app/workers/**` → запусти scraper, polling DB до terminal status
- `docker-compose*.yml` / `Caddyfile` → smoke production URLs + проверь containers running
### 2. Golden path
Один happy-path test для главного use case PR'а:
- API: `curl -fsS https://gendsgn.ru/<endpoint>` → 200 + ожидаемый JSON shape
- UI: `mcp__playwright__browser_navigate``browser_snapshot` → проверь expected element visible → `browser_console_messages` (0 errors) + `browser_network_requests` (0 failed 4xx/5xx)
- DB: SELECT и проверь что новые rows / columns / constraints на месте
- Scraper: POST trigger → polling `cadastre_jobs.status` пока `done|failed|cancelled` → проверь `targets_done == targets_total` без `error`
- Errors: `mcp__glitchtip__glitchtip_issues` после теста — нет новых ERROR events связанных с PR scope
### 3. Regression sanity (2-3 quick checks)
- `curl -fsS https://gendsgn.ru/` → 200 (frontend up)
- Известный читабельный endpoint (`/api/v1/parcels/<known-cad>`) → 200, ответ не пустой
- `docker ps` через SSH (если разрешено) — все critical containers `healthy` / `Up`
### 4. Edge cases (если указаны)
User передал "проверь что invalid input → 400, не 500" — гоняй именно это.
## Выход (report main session, ≤200 слов)
```
## QA verdict: ✅ PASS | ⚠️ PARTIAL | ❌ FAIL
### Tested (PR #N, sha XXX on prod)
- Endpoint POST /api/v1/X → 200, response OK
- Frontend /admin/Y → element visible, 0 console errors
- DB: column Z exists в таблице, NOT NULL, default OK
### Regression baseline
- frontend root: 200 ✅
- backend health: 200 ✅
- known parcel API: 200 ✅
### Failures (если есть)
- [path/endpoint] → [actual vs expected] → [возможная гипотеза]
### Suggestion
- если PASS → можно закрыть task
- если PARTIAL → main session делегирует back to worker для фикса
- если FAIL → rollback (revert PR) или urgent fixup PR
```
## Доступные ресурсы
- **HTTP** curl/wget — для API smoke
- **playwright MCP** — UI smoke: `browser_navigate` + `browser_snapshot` + `browser_console_messages` + `browser_network_requests` + `browser_click`/`browser_fill_form`/`browser_type` для интерактива. `browser_evaluate` для inline JS (eg. читать localStorage / data-attributes)
- **postgres-gendesign MCP** — для DB queries (read-only по умолчанию)
- **glitchtip MCP**`glitchtip_issues` / `glitchtip_latest_event` для проверки что после deploy не появилось новых exception events
- **Forgejo REST API** (curl + `$FORGEJO_TOKEN` env) — `$REPO/pulls/<N>` / `/files` / `.diff` для скоупа теста; `POST $REPO/issues/<N>/comments` для verdict; `POST $REPO/issues` + `GET $REPO/issues?state=open` для регрессий. Auth header: `-H "Authorization: token $FORGEJO_TOKEN"`. Owner/repo: `lekss361/gendesign`. Полные примеры команд: см. `.claude/agents/deep-code-reviewer.md` Phase 1
- **Vault** — credentials (`meta/00_credentials.md`), известные fixes (`fixes/`)
- **SSH gendesign** — производ docker ps / health checks (только read-only ops, никаких compose down / restart)
## Запреты
- ❌ Не пиши код / не правь файлы — ты только тестируешь
- ❌ Не делай deploy / restart containers / drop tables
- ❌ Не мержи PR / не пушь
- ❌ Не доверяй "тесты прошли в CI" — verify в проде через реальные HTTP/UI/SQL вызовы
- ❌ Не зацикливайся на 100% coverage — golden path + 2-3 regression + edge cases указанные в задаче, всё
- ❌ Не пиши длинных отчётов — verdict + 5-10 bullets, всё что нужно main session'у

View file

@ -2,7 +2,7 @@
name: tech-analyst
description: Tech analyst / planner для GenDesign. Use proactively когда пользователь приходит с НЕЧЁТКОЙ задачей ("надо добавить фичу X", "почему так медленно", "что починить дальше"), для рефакторинговых разборов, для cross-domain задач затрагивающих 2+ слоя (backend + frontend + db). Read-only — НЕ пишет код. Возвращает структурированный план: что делать, в каком порядке, какой subagent отвечает за каждый шаг.
tools: Read, Glob, Grep, WebSearch, WebFetch, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_complex_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_batch_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__obsidian__obsidian_get_recent_changes, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-gendesign__list_schemas, mcp__postgres-gendesign__explain_query, mcp__postgres-gendesign__analyze_query_indexes, mcp__postgres-gendesign__analyze_db_health, mcp__postgres-gendesign__get_top_queries, Bash
model: sonnet
model: haiku
color: yellow
---

View file

@ -3,6 +3,7 @@ paths:
- docker-compose*.yml
- Caddyfile
- .github/workflows/**
- .forgejo/workflows/**
- scripts/setup-*.sh
---

View file

@ -2,6 +2,7 @@
paths:
- .claude/**
- .github/**
- .forgejo/**
---
# Git + PR workflow
@ -11,17 +12,17 @@ paths:
## Branch + PR (MANDATORY)
```
1. git checkout -b feat/<scope> # или fix/ refactor/ docs/ chore/ perf/
1. git fetch forgejo && git checkout -b feat/<scope> forgejo/main # или fix/ refactor/ docs/ chore/ perf/
2. [worker делает код, staged]
3. code-reviewer subagent на staged changes (pre-push)
4. main session: git commit -m "feat(scope): ..."
5. git push -u origin feat/<scope>
6. gh pr create --title "..." --body "$(cat <<EOF...)"
7. Вернуть PR URL пользователю
8. СРАЗУ ScheduleWakeup polling (90-120s)
5. git push -u forgejo feat/<scope>
6. mcp__forgejo__create_pull_request (или curl POST $REPO/pulls)
7. Вернуть PR URL пользователю (https://git.gendsgn.ru/lekss361/gendesign/pulls/<N>)
8. СРАЗУ Skill loop polling (см. Polling loop)
```
**Никаких direct push в main.** Только через PR merge.
**Никаких direct push в main.** Только через PR merge через Forgejo. `gh` CLI bypassed (2026-05-16) — все PR-операции через `mcp__forgejo__*` или curl + `$FORGEJO_TOKEN`.
## Commit messages
@ -48,15 +49,15 @@ Closes #N
## Polling loop
После `gh pr create` — СРАЗУ `ScheduleWakeup` (90-120s):
После `mcp__forgejo__create_pull_request` — СРАЗУ `Skill loop` (self-paced, 90-120s) или background Bash poll:
1. `gh pr view <N> --json state,mergeable,comments,headRefOid`
2. `state == MERGED` → stop polling
3. Новый bot comment: распарси `<!-- gendesign-review-bot: sha=<sha7> verdict=<approve|changes> -->`
- **SHA guard**: `marker.sha7 == headRefOid[:7]` — иначе устаревший approval до fixup-push, игнорируй
1. `mcp__forgejo__get_pull_request` (или `curl -sH "$H" "$REPO/pulls/<N>"`) → читай `state`, `mergeable`, `head.sha`
2. `state == merged` → stop polling
3. Новый review/comment: `mcp__forgejo__list_pr_reviews` / `list_issue_comments`. Парсь marker `<!-- gendesign-review-bot: sha=<sha7> verdict=<approve|changes> -->`
- **SHA guard**: `marker.sha7 == head.sha[:7]` — иначе устаревший approval до fixup-push, игнорируй
- **Scope guard** (см. ниже): если blocked paths → ping user, не auto-merge
- `verdict=approve` + SHA match + scope OK → `gh pr merge <N> --squash --delete-branch`
- `verdict=changes` → fixup commits + push + re-poll
- `verdict=approve` + SHA match + scope OK → `mcp__forgejo__merge_pull_request` (squash + delete branch)
- `verdict=changes` → fixup commits + push в `forgejo feat/<scope>` + re-poll
4. Нет новых comments → re-schedule 60s
5. **Cap**: 30 iter без resolution → stop, ping user. Blocked-scope cap = 5.
@ -95,8 +96,9 @@ Issues ≥ 1.5 day → 3-4 sub-PRs: **Foundation → Schema → Workers → Inte
## Запреты
- ❌ `git push origin main` / direct push в main
- ❌ `gh pr merge` без approval (human "merge it" или bot verdict=approve + SHA match)
- ❌ `git push forgejo main` / direct push в main
- ❌ `mcp__forgejo__merge_pull_request` без approval (human "merge it" или bot verdict=approve + SHA match)
- ❌ `gh pr *` — bypassed 2026-05-16, используй Forgejo MCP или curl + `$FORGEJO_TOKEN`
- ❌ `--no-verify` / `--amend` / `--no-edit` / `--force` без явного approval
- ❌ `@claude` в PR comments — plain text only (`feedback_no_claude_mentions`)
- ❌ Параллельные PR на одни файлы (`feedback_sequential_prs`)

View file

@ -0,0 +1,129 @@
---
paths:
- frontend/src/components/**
- frontend/src/app/**/page.tsx
- frontend/src/app/**/layout.tsx
---
# UI components & data-viz conventions — GenDesign
> Часть набора `ui-*.md`. Tokens см. `ui-tokens.md`, microcopy/copy — `ui-microcopy.md`.
## Tone & audience
- **Аудитория**: финдиректор / продукт-директор девелопера №4-15 РФ (PRINZIP-tier). НЕ архитектор, НЕ ритейл.
- **Tone of voice**: DOM.РФ × CoStar × Bloomberg-density. «Государственная аналитика, которая считает за тебя». НЕ стартап-AI-маркетинг.
- **Информационная плотность > красивости.** Цифры читаются с проектора с 5 метров.
## Component conventions
### KpiCard
- Использовать `<KpiCard>` из `components/analytics/KpiCard.tsx`НЕ городить inline.
- KPI row: `display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;`НЕ flex+wrap (даёт ровные ряды).
- 3-4 KPI выше fold — максимум. Больше → под аккордеон «Детали».
- `delta.positive: null` (нейтральный) — для метрик где «больше/меньше» не имеет валентности (например, средняя цена).
### Section
- Заворачивать каждый блок графика/таблицы в `<Section title="..." subtitle="...">`.
- `subtitle` — одно предложение объясняющее «откуда данные / что значит цифра», не маркетинг.
- `right` slot — для controls (filter dropdown, export button, slicer chips).
### Headline-bar (тёмная плашка-вердикт)
- Pattern: `--bg-headline` фон, `--fg-on-dark` текст, padding 14/18, radius 12.
- **Одно предложение-ответ JTBD**: «В районе X класс Y · S м² → mix А · срок B мес · выручка C ₽».
- Под основным текстом — separator + caveats / context (ИЖК ставка, POI, дата snapshot).
- Размещать выше всех KPI-карточек.
### Badge
- Использовать `<Badge variant="success|warn|danger|neutral">`. НЕ inline-span с hex.
- Если компонента нет — создать в `components/ui/Badge.tsx`, не дублировать stylesheet'ы по pages.
### Drawer (drill-down правым слайдером)
- Pattern для `/analytics/developers` детализации. НЕ навигировать на отдельную страницу при клике на строку таблицы — открывать right drawer (CoStar pattern).
- Width 480px desktop, full-screen на phone.
## Data-viz (ECharts) rules
- **Ось Y ВСЕГДА с подписью** (`yAxis.name`). Без подписи — это нарушение review.
- **Tooltip ВСЕГДА с единицей измерения**: «1 240 ₽/м²», «73%», «28 мес».
- **Цвета серий** — только из `--viz-1..5` в указанном порядке.
- **Forecast / prediction** — пунктирная линия + band ±p25/p75 (HouseCanary). Не показывать точку без интервала.
- **Никаких 3D charts**, никаких pie с >5 сегментов (заменить на bar). Treemap — OK.
- Lazy-mount тяжёлых charts через `dynamic(() => import(...), { ssr: false })` (см. `objects/[id]/page.tsx`).
## Iconography
- **Lucide React only**`lucide-react` пакет. Размеры 16 / 20 / 24px, stroke 1.5px.
- **НЕ emoji в UI** — 🚀 ❄ 💼 📊 → `<TrendingUp/>`, `<Snowflake/>`, `<Briefcase/>`, `<BarChart3/>`.
- Trend-glyph в KPI — Unicode arrow `↑ ↓ →` или Lucide, НЕ emoji.
- Иконка-без-текста → `aria-label`.
## Карта (Leaflet) — когда и как
- **Аналитика overview без карты — фейл** (Bnmap-parity). На `/analytics` нужна секция «Карта рынка» с точками новостроек DOM.РФ, цвет по `sold%`.
- Leaflet через `dynamic(..., { ssr: false })` — НИКОГДА не импортировать в server component.
- Высота карты: 360px (drill-in), 480px (overview), full-screen toggle FAB на phone.
- Layers: OSM base + наш geojson. Не подключать платные tile-серверы без согласования.
## Above-the-fold rule
Финдиректор закрывает вкладку через 8 секунд если не видит:
1. Что это за страница (h1 + один subtitle).
2. **Один вердикт** одним предложением (headline-bar).
3. **3 главных числа** (KPI row).
Всё остальное (sliders, deep tables, comparables) — **под аккордеон** «Подробнее». По умолчанию закрыто.
## Responsive (см. issue #66)
- 3 контекста: phone `<768`, tablet `768-1280`, desktop `>1280`. НЕ Tailwind sm/md/lg напрямую.
- Phone: nav-tabs → horizontal scroll + snap, НЕ hamburger.
- Phone: KPI grid → 1 col full-width.
- Chart высоты фиксированные, `ResizeObserver` для ECharts.
- НЕ делать PWA / offline. Аудитория с десктопа.
## Accessibility (минимум, не максимум)
- Контраст AA для body (4.5:1). Hex-token подобраны под AA на белом / slate-900.
- `aria-label` для icon-only buttons.
- Focus ring видимый (НЕ `outline: none` без замены).
- Кликабельный элемент — `<button>` или `<a>`, не `<div onClick>`.
## CTA hierarchy
- **Primary** (`--accent` blue): главное действие на странице («Рассчитать», «Создать концепцию»).
- **Secondary** (`--accent-2` orange): «Export CSV», «Поделиться», «Снимок отчёта».
- **Tertiary** (text-only link с underline на hover): nav, breadcrumbs, «← Назад».
- Одна primary CTA на экран. Больше — расфокус.
## Запреты (анти-паттерны)
- ❌ Dark mode toggle (аудитория читает с проектора)
- ❌ Emoji в UI (🚀 ❄ 💼 📊 🎉) — Lucide вместо
- ❌ Tab nav в Tailwind sm/md/lg breakpoints (использовать наши 3 контекста)
- ❌ Pie chart с >5 сегментов
- ❌ 3D-чарты, изометрия, lifestyle-фото в analytics
- ❌ Live-chat widget (Intercom) — наша аудитория звонит/пишет
- ❌ Customer logos без подтверждённого права
- ❌ Анимации >200ms (это финансовый дашборд)
- ❌ Skeleton с shimmer — простой grey fade
- ❌ TestFit / Spacemaker / Figma-dribbble стиль — НЕ наш референс
- ❌ Chart без axis label / unit в tooltip
- ❌ Predictions без confidence interval (точка без диапазона выглядит «слишком уверенно»)
- ❌ Drill-down переходом на новую страницу (использовать Drawer)
## Что проверять в pre-push review (UI-PR)
1. Цвета все из токенов? (grep `#[0-9a-fA-F]{3,8}` в .tsx — должно быть пусто кроме токенов).
2. Emoji в UI? (`grep -P '[\x{1F300}-\x{1FAFF}]'` — пусто).
3. KPI label uppercase + letter-spacing?
4. Chart с axis label и tooltip unit?
5. Section title не центрирован?
6. Lazy-mount для Leaflet / тяжёлых ECharts?
7. Headline-bar присутствует на странице с JTBD-ответом?
## References
- vault `research/UX_Analytics_Developers_Report_May17.md` — полный отчёт с бенчмарком
- vault `audits/batch_03_b_series_generative` — issues #64-66 (landing, analytics search, mobile)
- `frontend/src/components/analytics/KpiCard.tsx`, `Section.tsx` — эталонные компоненты

View file

@ -0,0 +1,29 @@
---
paths:
- frontend/src/app/**/*.{ts,tsx}
- frontend/src/components/**/*.{ts,tsx}
---
# UI microcopy & export — GenDesign
> Часть набора `ui-*.md`. Tokens — `ui-tokens.md`, components/viz — `ui-conventions.md`.
## Microcopy
- Русский. Типографика: `₽ / м²` (пробелы), `п.п.` (не «pp»), `м²` (не «м2»), `≥` `≤` `±` (Unicode, не `>=`).
- Числа: `toLocaleString("ru")` для тысяч (`6 832 540`, не `6,832,540`).
- Деньги: `2.4 млрд ₽` для крупных, `145 320 ₽` для точных. Round по контексту.
- **Никаких** «Yay!» / «Ой!» / «Готово 🎉». Neutral business: «Расчёт обновлён», «Данные за период недоступны».
- Caveat — полным предложением, не аббревиатурой: «Рекомендация основана на городском распределении сделок» — OK.
## Запреты (microcopy)
- ❌ Microcopy «Yay!» / emoji в success-state
- ❌ Англицизмы без перевода (например "Submit" в кнопке вместо «Подтвердить»)
- ❌ Сокращения без расшифровки на первой странице (ИЖК → расшифровать в caveat)
## Export-кнопка (dataflat lesson)
- На каждой Section с табличными данными — кнопка `Export CSV` / `Export Excel` в `Section.right`.
- Endpoint backend должен отдать CSV с UTF-8 BOM (Excel-friendly), `Content-Disposition: attachment; filename=...`.
- Имя файла: `gendesign_<page>_<scope>_<YYYY-MM-DD>.csv`.

View file

@ -0,0 +1,87 @@
---
paths:
- frontend/**/*.css
- frontend/src/components/**/*.{ts,tsx}
- frontend/src/app/**/*.{ts,tsx}
---
# UI design tokens — GenDesign
> Часть набора `ui-*.md` (token / conventions / microcopy). Полное обоснование (бенчмарк vs Bnmap/DOM.РФ/CoStar/Bloomberg) — vault `research/UX_Analytics_Developers_Report_May17.md`.
## Design tokens (HARD — НЕ выдумывать новые)
Цвета — только из этого списка. Inline `#hex` вне токенов = нарушение (ругать в review).
```css
/* Surface */
--bg-app: #F6F7F9;
--bg-card: #FFFFFF;
--bg-card-alt: #FAFBFC; /* skeleton, lazy-mount */
--bg-headline: #0F172A; /* slate-900, тёмная headline-bar */
/* Borders */
--border-soft: #EEF0F3;
--border-card: #E6E8EC;
--border-strong: #D1D5DB;
/* Text */
--fg-primary: #111111;
--fg-secondary: #5B6066;
--fg-tertiary: #73767E;
--fg-on-dark: #E2E8F0;
--fg-on-dark-muted: #94A3B8;
/* Brand */
--accent: #1D4ED8; /* primary CTA, tabs, focused link */
--accent-hover: #1E40AF;
--accent-soft: #DBEAFE;
--accent-2: #F2994A; /* secondary CTA: Export, Share, Snapshot */
/* Semantic */
--success: #0A7A3A; --success-soft: #DCFCE7;
--warn: #9A6700; --warn-soft: #FEF3C7;
--danger: #B3261E; --danger-soft: #FEE2E2;
/* Data-viz sequence (порядок ОБЯЗАТЕЛЕН — viz-1 всегда «свой» / focused) */
--viz-1: #1D4ED8; --viz-2: #0EA5E9; --viz-3: #14B8A6;
--viz-4: #F59E0B; --viz-5: #8B5CF6;
/* Prediction / confidence (HouseCanary pattern) */
--prediction-line: #0EA5E9;
--prediction-band: rgba(14, 165, 233, 0.18);
```
**Цвет в chart серий** применяется ТОЛЬКО для сравнения серий. Одиночная линия — всегда `#374151`.
## Layout & spacing tokens
- Page `max-width: 1280px` (analytics-deep — `clamp(1280px, 90vw, 1440px)` на ≥1600px).
- Spacing scale: **4 / 8 / 12 / 16 / 24 / 32**. Никаких 6/10/14/18.
- Card padding: **20px** (Section), **16/18** (KpiCard) — НЕ менять.
- Card radius: **12px** (cards), 8px (chart skeleton), 6px (badges).
- Section vertical rhythm: `margin-top: 24` между секциями (НЕ 16).
- **НЕ использовать `box-shadow`** на cards. Border `--border-card` достаточно. Тень — только для modals / popovers.
- Inline `style={{}}` допустим только для динамических значений (height по data, transform, color по delta). Статический стиль → Tailwind utility или token.
## Typography tokens
- **Font stack**: `Inter, -apple-system, "Segoe UI", system-ui, sans-serif` через `next/font/google` (self-hosted, privacy).
- **Tabular-nums везде** для цифр: `font-variant-numeric: tabular-nums; font-feature-settings: "tnum";` в `:root`.
- Размеры:
- **display** 28/36 weight 600 — KPI value
- **h1** 22/28 weight 600 — page title
- **h2** 18/24 weight 600 — Section title
- **body** 14/20 weight 400
- **caption** 12/16 weight 400
- **label** 12/16 weight 500 uppercase letter-spacing 0.04em — KPI label
- Заголовок (h1, h2) не центрировать. Только left-align.
## Запреты (token violations)
- ❌ Inline hex вне токенов (`color: "#1d4ed8"``color: var(--accent)`)
- ❌ Box-shadow на cards (border достаточно)
- ❌ Gradient backgrounds — flat tokens only
- ❌ Centered h1/h2 (только left-align)
- ❌ Spacing не из scale (6/10/14/18 → 4/8/12/16/24/32)
- ❌ Хардкод цвета серии в ECharts (брать из `--viz-N` по порядку)

37
.gitignore vendored
View file

@ -29,19 +29,42 @@ frontend/.sentryclirc
.mcp.json
# креды-заметки — НЕ коммитить (см. issue #391)
sshkey.txt
# Cian session cookies (browser-export) для local-sweep — НЕ коммитить
tradein-mvp/scripts/.cian-cookies.json
tradein-mvp/scripts/*.cookies.json
# IDE
.vscode/
.idea/
*.swp
# Claude Code local state (cache, locks, scheduled tasks, local settings)
.claude/
# Track team-shared agents + path-scoped rules
!.claude/agents/
!.claude/agents/**
!.claude/rules/
!.claude/rules/**
# Claude Code — local state only (cache, locks, sessions, schedules).
# Agents and rules ARE tracked: they are team-shared config.
# Explicit per-path list — `.claude/` + `!.claude/agents/` whitelist pattern
# does not work because git cannot re-include files under an ignored parent.
.claude/settings.json
.claude/settings.local.json
.claude/cache/
.claude/sessions/
.claude/locks/
.claude/file-history/
.claude/shell-snapshots/
.claude/tasks/
.claude/plans/
.claude/telemetry/
.claude/projects/
.claude/worktrees/
.claude/daemon/
.claude/session-env/
.claude/stats-cache.json
.claude/history.jsonl
.claude/mcp-needs-auth-cache.json
.claude/paste-cache/
.claude/downloads/
.claude/backups/
.claude/plugins/
.claude/agent-memory*/
.claude/scheduled_tasks.lock
# Local data (raw CSVs from Rosreestr/etc — too large for git)
data/raw/

View file

@ -12,43 +12,14 @@ Live: `https://gendsgn.ru/` — Свердловская обл. (ЕКБ, ПЗЗ
| Frontend | Next.js 15 app router, React 19, TypeScript 5 strict, Tailwind 4, TanStack Query |
| Export | WeasyPrint (PDF), ezdxf (DXF), openpyxl (Excel) |
| Tooling | uv, ruff, mypy strict (selective), pytest, pre-commit, prettier |
| Deploy | Beget VPS · GHA → GHCR → SSH → docker-compose · Caddy |
| Deploy | Beget VPS · Forgejo Actions → GHCR → SSH → docker-compose · Caddy |
| Knowledge | Obsidian vault `obsidian.gendsgn.ru` (CouchDB LiveSync) |
## Commands
```bash
# Dev (Docker):
docker compose up -d --build
curl http://localhost:8000/health
# Backend without Docker:
cd backend && uv sync && uv run uvicorn app.main:app --reload
# Frontend:
cd frontend && npm install && npm run dev
# Migrations:
make migration NAME=add_foo_column # Alembic
# или raw SQL: data/sql/NN_topic.sql # views, partitions, indexes
# Tests:
cd backend && uv run pytest
cd frontend && npm test
# Lint + typecheck:
cd backend && uv run --no-project --with ruff ruff check app/
cd frontend && npx tsc --noEmit && npm run lint
# Regen TS types from OpenAPI:
cd frontend && npm run codegen
```
## Critical rules
1. **Knowledge = Obsidian vault only.** `mcp__obsidian__*` tools. Local mirror: `C:\Users\user\Documents\gendesign-memory`. Deprecated: `memory/memory-gendesign.jsonl` — не читай, не пиши.
2. **Branch + PR mandatory.** Никаких direct push в main. `feat/...` / `fix/...` / `refactor/...` / `docs/...` / `chore/...``gh pr create` → PR URL пользователю. После PR create — сразу `ScheduleWakeup` polling. **Полные правила: `.claude/rules/git-pr.md`**.
2. **Branch + PR mandatory.** Никаких direct push в main. `feat/...` / `fix/...` / `refactor/...` / `docs/...` / `chore/...` → ветка от `forgejo/main``mcp__forgejo__create_pull_request` → PR URL пользователю. После PR create — сразу `Skill loop` polling. `gh` CLI bypassed (2026-05-16). **Полные правила: `.claude/rules/git-pr.md`**.
3. **Agent-first workflow.** Main session orchestrates ONLY — не пишет код inline. Любая задача >1 файл / >50 строк → subagent. Pre-push: spawn `code-reviewer` subagent на staged changes. Post-push review — внешнее окно Claude (НЕ дублировать).
@ -56,9 +27,9 @@ cd frontend && npm run codegen
5. **No `--no-verify` / `--force` / `--amend`.** Pre-commit hooks обязательны. Hook падает → fix root cause.
6. **Vault обновляется вместе с кодом** — в той же сессии. Bug → `fixes/`, module → `code/modules/`, decision → `decisions/`, runbook → `runbooks/`.
6. **Vault обновляется вместе с кодом** — в той же сессии. **Используй `/vault-write`** для новой записи (auto-routes через inbox → vault-overlord → правильная папка). НЕ пиши напрямую в `fixes/` / `decisions/` / `code/modules/`. Periodic cleanup: `/sort-inbox` (раскладка inbox), `/retention-pass` (архив устаревшего active в `old/`). Полные правила: `meta/02_claude_usage.md` + `inbox/README.md` в vault.
7. **Тестируй каждый merge**chrome-devtools MCP или curl ДО/сразу после deploy. Не доверяй "тесты прошли" без verify.
7. **Тестируй каждый merge**`mcp__playwright__*` или curl ДО/сразу после deploy (post-deploy spawn `qa-tester`). Не доверяй "тесты прошли" без verify.
## Subagents (`.claude/agents/`)
@ -68,30 +39,24 @@ cd frontend && npm run codegen
| `backend-engineer` | `backend/app/**` — FastAPI, Celery, scrapers, services |
| `frontend-engineer` | `frontend/src/**` — Next.js, React, hooks |
| `database-expert` | `data/sql/**.sql`, Alembic, EXPLAIN ANALYZE |
| `devops-engineer` | `docker-compose*.yml`, `Caddyfile`, `.github/workflows/**` |
| `devops-engineer` | `docker-compose*.yml`, `Caddyfile`, `.github/workflows/**`, `.forgejo/workflows/**` |
| `code-reviewer` | Pre-push lint (security, correctness, conventions) |
**Routing:** тривиально (typo, 1-line) → main session. Single-domain clear → worker. Cross-domain / нечётко → `tech-analyst` first. Worker → `code-reviewer` → main commits → push → PR.
## Where to look
- **Path-scoped rules:** `.claude/rules/` (backend, frontend, sql, git-pr, deploy) — auto-loaded при работе с соответствующими файлами
- **Path-scoped rules:** `.claude/rules/` (backend, frontend, sql, git-pr, deploy, ui-tokens, ui-conventions, ui-microcopy) — auto-loaded при работе с соответствующими файлами через `paths:` frontmatter
- **Personal preferences:** `~/.claude/CLAUDE.md` (cross-project: no auto-commit, no Co-Authored-By, no @claude)
- **Workflow memory:** `~/.claude/projects/<repo>/memory/MEMORY.md` index
- **Vault MOCs:** `meta/`, `domains/<area>/<area>-MOC.md`, `decisions/`, `fixes/`, `limitations/`, `code/patterns/`
- **DB access:** SSH tunnel `ssh -N gendesign``localhost:15432` · Credentials: vault `meta/00_credentials.md`
## Don't
## Don't (cross-domain — specifics → `.claude/rules/`)
- ❌ `import psycopg2` — только psycopg v3
- ❌ Direct push в main / merge без approval
- ❌ `--no-verify` / `--amend` / `git push --force`
- ❌ `print()` в prod-коде — только `logger.*`
- ❌ `requests` — только `httpx`
- ❌ Knowledge-файлы вне vault (`docs/research/`, `notes/`, `wiki/`)
- ❌ `DROP TABLE` / `TRUNCATE` без явного approval
- ❌ Hardcode credentials в коде / коммитах
---
**Maintenance:** этот файл обновляется когда меняется tech stack, добавляются новые critical rules, или меняется структура rules/. **Цель: ≤150 lines.** Детали → `.claude/rules/` или vault.
<!-- Maintenance: обновляется когда меняется tech stack или critical rules. Цель ≤100 lines. Детали → .claude/rules/ или vault. -->

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Claude Code PostToolUse hook blocks new `print(` calls in backend/**/*.py.
Wired in `.claude/settings.json` hooks.PostToolUse with matcher "Edit|Write".
Behavior:
- Reads hook input as JSON from stdin (per Anthropic spec).
- If tool is Edit/Write and file_path matches backend/**/*.py:
- Scans new content for new `print(` calls.
- Exits with code 2 to block (stderr is forwarded to Claude as feedback).
- Otherwise exits 0 (no-op).
Notes:
- Allows `print(` inside lines containing `# noqa` (escape hatch).
- Skips files under `backend/tests/**` (test code may use print for debugging).
- Skips `backend/scripts/**` (one-off scripts).
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
ALLOW_PATH_PREFIXES = (
"backend/tests/",
"backend/scripts/",
)
WATCH_PATH_PREFIX = "backend/"
PRINT_RE = re.compile(r"(?<!\w)print\s*\(")
def normalize_path(raw: str) -> str:
"""Convert absolute/relative path to repo-relative POSIX-like form."""
p = Path(raw).as_posix()
# Try to strip everything up to the last "backend/" occurrence.
idx = p.rfind("backend/")
return p[idx:] if idx >= 0 else p
def main() -> int:
try:
payload = json.load(sys.stdin)
except Exception:
# No input or malformed — skip silently.
return 0
tool_name = payload.get("tool_name") or payload.get("tool") or ""
if tool_name not in {"Edit", "Write"}:
return 0
tool_input = payload.get("tool_input") or {}
file_path = tool_input.get("file_path") or tool_input.get("path") or ""
if not file_path:
return 0
norm = normalize_path(file_path)
if not norm.startswith(WATCH_PATH_PREFIX):
return 0
if any(norm.startswith(p) for p in ALLOW_PATH_PREFIXES):
return 0
if not norm.endswith(".py"):
return 0
# Pull the new content. Edit gives `new_string`, Write gives `content`.
new_text = tool_input.get("new_string") or tool_input.get("content") or ""
if not new_text:
return 0
offenders: list[tuple[int, str]] = []
for lineno, line in enumerate(new_text.splitlines(), start=1):
if "# noqa" in line:
continue
if PRINT_RE.search(line):
offenders.append((lineno, line.strip()))
if not offenders:
return 0
sample = offenders[0]
msg_lines = [
f"Blocked: `print(` introduced in {norm}.",
f"Line {sample[0]}: {sample[1][:120]}",
"GenDesign convention: use `logger.info/warning/error` instead of print().",
"If this is intentional one-off debug, add `# noqa` on the line and retry.",
]
print("\n".join(msg_lines), file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())