docs: перевод документации на канон av-dev
- Раскладка docs/ приведена к канону 2: заведены passport/architecture/ database/security/review и research; docs/specs, drafts, backlog, review/ и BRIEF.md разобраны и удалены, беклог переехал в docs/tasks (34 задачи, 6 целей, слаги на английский). - Нарративы specs удалены как дубли openspec-спек после поимённой сверки; остаток заведён задачами (редактор маппинга ревью, крайние случаи именования), отказ от сущности title промоутнут в ADR. - Проектные копии агентов и скиллов ревью/пайплайна удалены в пользу плагинов av-dev-pm и av-dev-pipeline; в task gate добавлен шаг canon вместо er-schema.
This commit is contained in:
@@ -1,130 +1,60 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Памятка для работы над jellybit. Перед задачей прочитай также
|
||||
[README.md](README.md), [BRIEF.md](BRIEF.md) и
|
||||
[docs/specs/architecture.md](docs/specs/architecture.md). Разработка идёт
|
||||
по **Spec Driven Development** через OpenSpec — см. раздел ниже.
|
||||
[docs/passport.md](docs/passport.md), [docs/architecture.md](docs/architecture.md)
|
||||
и [docs/conventions/](docs/conventions/README.md). Разработка идёт по **Spec
|
||||
Driven Development** через OpenSpec — см. раздел ниже.
|
||||
|
||||
## Что это
|
||||
|
||||
Связующий сервис qBittorrent ↔ Jellyfin: принимает торрент + контекст,
|
||||
качает, распознаёт фильм/сериал (LLM + контекст + опц. метабазы) и
|
||||
раскладывает файлы для Jellyfin хардлинками. Деплоится на домашний
|
||||
медиа-сервер umbar (`/home/av/projects/private/umbar`) — туда копируется
|
||||
готовый бинарь.
|
||||
Связующий сервис qBittorrent ↔ Jellyfin: принимает торрент с текстовым
|
||||
контекстом, качает через qBittorrent, распознаёт фильм или сериал (LLM +
|
||||
контекст + опц. метабазы) и раскладывает файлы для Jellyfin хардлинками, не
|
||||
трогая исходную раздачу. Деплоится на домашний медиа-сервер umbar
|
||||
(`/home/av/projects/private/umbar`).
|
||||
|
||||
## Стек и принципы
|
||||
**Чего не делает:** не ищет раздачи в трекерах, не ведёт профили качества, не
|
||||
подписывается на выходящие серии, не хранит медиа и не заменяет Jellyfin.
|
||||
Полная граница домена — [docs/passport.md](docs/passport.md).
|
||||
|
||||
- **Go**, один статический бинарь (`CGO_ENABLED=0`). Почему — см.
|
||||
[ADR-2026-06-13-go-single-binary](docs/adr/ADR-2026-06-13-go-single-binary.md).
|
||||
- **SQLite** как хранилище (чистый Go-драйвер `modernc.org/sqlite`).
|
||||
- **Конфигурация — TOML**. **Логи — структурированный JSON** (`log/slog`).
|
||||
- **Хардлинки, источник не трогаем** — qBittorrent продолжает раздачу,
|
||||
диск не дублируется.
|
||||
- **Единое ядро, тонкие транспорты** — вся логика приёма в use-case
|
||||
`Ingest`; HTTP API, веб-UI и Telegram — лишь обёртки над ним.
|
||||
- **Минимум компонентов** — в духе umbar, без зоопарка сервисов. Внешние
|
||||
базы метаданных (TMDB/TVDB) опциональны, включаются конфигом.
|
||||
## Стек
|
||||
|
||||
## Инварианты (безопасность данных)
|
||||
Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module path —
|
||||
`git.vakhrushev.me/av/jellybit`. SQLite через `modernc.org/sqlite` + `sqlx`,
|
||||
миграции `goose`, HTTP — `chi` + `html/template` + htmx, конфиг —
|
||||
`pelletier/go-toml/v2`, логи — `log/slog` (структурированный JSON).
|
||||
|
||||
- **Источник неприкосновенен:** только `mkdir` / `link(2)` / `unlink`
|
||||
своих ссылок; никогда не трогаем файлы под `paths.downloads`.
|
||||
- **Целевой путь санитизируется** и проверяется, что он строго под
|
||||
`paths.movies`/`series` (защита от traversal); существующее не
|
||||
перезаписываем.
|
||||
- **Выход LLM недоверенный** — безопасность на валидации пути, не на
|
||||
промпте. Авто-раскладка только при подтверждённом матче в базе.
|
||||
- **Секреты не попадают в логи** — пароли qBittorrent, API-ключи LLM/метабаз,
|
||||
auth-заголовки. Подробнее — [docs/conventions/logging.md](docs/conventions/logging.md).
|
||||
- **Запуск:** контейнер под `1000:1000`, в общей docker-сети (адресация
|
||||
по именам), mount `/srv/media` (единая песочница) + data-том для
|
||||
SQLite/конфига.
|
||||
## Инварианты
|
||||
|
||||
## Spec Driven Development (OpenSpec)
|
||||
Нарушать нельзя. Severity стоит здесь, а не выводится каждым проходом ревью
|
||||
заново.
|
||||
|
||||
Изменения ведём через [OpenSpec](https://github.com/Fission-AI/OpenSpec)
|
||||
(CLI `openspec`, v1.x). Сначала спецификация — потом код.
|
||||
|
||||
- `openspec/specs/<capability>/spec.md` — **актуальные** capability-спеки:
|
||||
что система делает сейчас. Capability — это поведение/домен (`ingest`,
|
||||
`recognition`, `file-layout`, `review`, `notifications`), а не пакет кода.
|
||||
- `openspec/changes/<id>/` — предлагаемое изменение: `proposal.md` (зачем и
|
||||
что), `design.md` (как, для нетривиальных), дельта-спеки (`ADDED`/
|
||||
`MODIFIED`/`REMOVED Requirements`), `tasks.md` (шаги). После реализации
|
||||
change архивируется в `openspec/changes/archive/`, дельты вливаются в
|
||||
`openspec/specs/`.
|
||||
- `openspec/config.yaml` — язык и правила оформления спек (читай перед
|
||||
написанием).
|
||||
|
||||
Поток работы — через слэш-команды `opsx:*` (канонический набор, его
|
||||
поддерживает `openspec update`): `opsx:explore` (продумать), `opsx:propose`
|
||||
(завести change), `opsx:apply` (реализовать tasks), `opsx:sync`/`opsx:archive`
|
||||
(влить и архивировать). Skills `openspec-*` — то же, но предыдущего
|
||||
поколения; для новой работы используем `opsx:*`.
|
||||
|
||||
Правила спек:
|
||||
|
||||
- Каждое `### Requirement` ОБЯЗАНО содержать литерал `SHALL` или `MUST` —
|
||||
иначе `openspec validate` падает.
|
||||
- Структурные заголовки и ключевые слова — английские (`### Requirement:`,
|
||||
`#### Scenario:`, `GIVEN/WHEN/THEN`, RFC 2119), остальной текст — русский.
|
||||
- Сценарии — в формате `GIVEN/WHEN/THEN`.
|
||||
- `openspec validate --strict` перед коммитом change.
|
||||
|
||||
Ревью (процесс, не артефакт): два чекпоинта — профиль `design` на предложении
|
||||
(после design/specs, ДО кода) и ревью изменения после apply, до archive. Оба
|
||||
идут через скилл `.claude/skills/review-pipeline`: детерминированный гейт
|
||||
(`task gate`) → сверка с дельта-спеками в обе стороны → generative-проходы →
|
||||
триаж с потолком 7 находок. Профиль (`quick`/`standard`/`deep`) выбирается по
|
||||
факту изменения, правило — в скилле.
|
||||
|
||||
**Миграция:** capabilities постепенно переносятся из `docs/specs/` в
|
||||
OpenSpec (пилот — `ingest`). До переноса источник истины по теме —
|
||||
соответствующий файл в `docs/specs/`; перенесённое живёт в
|
||||
`openspec/specs/`.
|
||||
|
||||
## Прочая документация
|
||||
|
||||
- `docs/specs/` — **живые** спецификации целевого состояния (архитектурный
|
||||
обзор + ещё не перенесённые в OpenSpec темы). Меняем по мере развития,
|
||||
держим в соответствии с кодом.
|
||||
- `docs/adr/` — **неизменяемый** журнал решений, пишется постфактум,
|
||||
хранит *почему*. Правила — [docs/adr/README.md](docs/adr/README.md).
|
||||
- `docs/drafts/` — черновики: планы, идеи, ещё не принятые решения. Не
|
||||
источник истины.
|
||||
|
||||
## Задачи и беклог
|
||||
|
||||
- **Единственный источник беклога — каталог [docs/backlog/](docs/backlog/README.md)**:
|
||||
одна задача = один markdown-файл (`docs/backlog/<slug>.md`) + строка в индексе
|
||||
[README.md](docs/backlog/README.md). Приоритеты: высокий/средний/низкий.
|
||||
Работа с беклогом (заведение из диалога, разбор находок ревью/аудита, груминг,
|
||||
приоритизация, декомпозиция, штурм идеи) — через скилл **`backlog`**; формат
|
||||
файла, слага, индекса и кладбища он и держит, здесь не дублируем. Скилл
|
||||
поставляется плагином `av-dev-backlog` (маркетплейс `av-dev-skills`, включён в
|
||||
`.claude/settings.json`); вызов — `/av-dev-backlog:backlog`, свой скрипт
|
||||
`backlog.py` он зовёт сам — путь к нему в проекте не зашиваем.
|
||||
- **Источники задач:** диалог, инбокс Tududi и находки ревью. Отложенная находка
|
||||
`review-pipeline` (реальная, но не для текущего мерджа) заводится задачей через
|
||||
скилл `backlog` с тегом партии `review-ГГГГ-ММ-ДД` — так уже сделаны задачи
|
||||
`review-*` в беклоге.
|
||||
- **Проектные тонкости для скилла `backlog`:**
|
||||
- каталог беклога — `docs/backlog/`, язык — русский, слаги — латиница;
|
||||
- реализованная задача удаляется, её суть переезжает в `docs/specs`/`docs/adr`
|
||||
(это делает пайплайн задачи на шаге 9, не скилл беклога);
|
||||
- выкинутая без реализации уезжает строкой в `docs/backlog/CLOSED.md` (кладбище);
|
||||
- спекулятивные задачи помечены `[idea]` в заголовке (тип — английское
|
||||
ключевое слово idea/epic/task) — сперва штурм.
|
||||
- **Tududi — только инбокс сырых идей** (проект `jellybit`, project_id 14).
|
||||
Беклог там больше не ведём; идея из Tududi становится задачей, когда её
|
||||
оформляют файлом в `docs/backlog/` через скилл `backlog`. Прежняя единая
|
||||
`docs/backlog.md` доступна в истории git.
|
||||
|
||||
## Язык
|
||||
|
||||
- Документация, комментарии, сообщения коммитов — **русский**.
|
||||
- Код и идентификаторы — английский.
|
||||
- **Источник неприкосновенен** — под `paths.downloads` допустимы только чтение
|
||||
и `link(2)`; никаких `unlink`, `rename`, записи. Нарушение уничтожает
|
||||
невосстановимые данные пользователя. **Необратимо. `critical`.**
|
||||
- **Последняя копия не снимается** — `Undo` отклоняется целиком, если у цели не
|
||||
осталось других жёстких ссылок (`nlink <= 1`) или исходного файла уже нет.
|
||||
Частичный откат тоже стёр бы часть данных. **Необратимо. `critical`.**
|
||||
- **Целевой путь строго под библиотекой** — после санитизации и
|
||||
`filepath.Clean` путь обязан лежать под `paths.movies`/`paths.series`, иначе
|
||||
операция отклоняется. Выход за песочницу означает запись в чужие каталоги.
|
||||
**Необратимо. `critical`.** Выход LLM недоверенный: безопасность держится на
|
||||
этой проверке, а не на промпте.
|
||||
- **Существующее не перезаписываем** — цель занята другим файлом → коллизия →
|
||||
review. Обратимо (задача уходит в ревью), но потеря чужого файла — нет.
|
||||
**`critical`.**
|
||||
- **Секреты не попадают в логи, диагностику и ответы API** — пароль
|
||||
qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin.
|
||||
Утёкший в лог секрет отзывается вручную. **`major`.**
|
||||
- **Авто-раскладка только при подтверждённом матче в метабазе** — самооценка
|
||||
LLM гейтом не является
|
||||
([ADR](docs/adr/ADR-2026-06-13-auto-link-requires-db-match.md)). Обратимо
|
||||
через `Undo`. **`major`.**
|
||||
- **Переходы состояний — только через `worker` под per-download блокировкой**,
|
||||
и только легальные по декларативному графу. Обход даёт гонку двух
|
||||
транспортов. **`major`.**
|
||||
- **Время — только `store.Now()` (UTC), идентификаторы — только `ident`**;
|
||||
`ident.Parse` на каждой входной границе. Механизировано линтером. **`minor`.**
|
||||
|
||||
## Команды
|
||||
|
||||
@@ -134,43 +64,140 @@ OpenSpec (пилот — `ingest`). До переноса источник ис
|
||||
- `task run` — локальный запуск (`go run ./cmd/jellybit --config ./config.toml`)
|
||||
- `task build` — статический бинарь `linux/amd64` для сервера
|
||||
- `task test` / `task lint` — тесты и golangci-lint
|
||||
- `task gate` — детерминированный гейт ревью (build/vet/lint/test/race/покрытие
|
||||
изменённых строк/миграции/секреты); блокирует опиниативные проходы ревью
|
||||
- `task gate` — детерминированный гейт ревью (см. ниже)
|
||||
- `task review:context` — карта проекта для архитектурного прохода ревью
|
||||
- `task tidy` — `go mod tidy`
|
||||
- `task image` — docker-образ из готового бинаря
|
||||
|
||||
Module path — `git.vakhrushev.me/av/jellybit`. Go 1.26, `CGO_ENABLED=0`.
|
||||
Стек: `chi`, `sqlx` + `modernc.org/sqlite`, `goose` (миграции),
|
||||
`pelletier/go-toml/v2`, `log/slog`.
|
||||
## Гейт
|
||||
|
||||
- **Команда целиком:** `task gate` (`BASE=<rev>` задаёт базу диффа). Без `BASE`
|
||||
база — `git merge-base HEAD master`, а на самом `master` — `HEAD~1`.
|
||||
- **Где логи шагов:** `tmp/gate/<шаг>.log`, по одному файлу на шаг.
|
||||
- **Что означает исход:** статусы `OK` / `FAIL` (краснит) / `WARN` (виден, не
|
||||
блокирует) / `SKIP` (не применим, всегда с причиной). Код возврата 1, если
|
||||
есть хоть один `FAIL`. Гейт **не** останавливается на первом отказе —
|
||||
ревьюверу нужна полная картина.
|
||||
- **Что красит безусловно:** сборка, `go vet`, `golangci-lint`, `gofmt`, тесты,
|
||||
флаки-прогон (второй прогон разошёлся с первым), `-race`, накат миграций с
|
||||
нуля, `gitleaks`, канон документации (`docs.py check` — раскладка `docs/`,
|
||||
битые ссылки, «миграция изменена, а `database.md` нет»). Причина одна: у
|
||||
каждого из них есть объективный оракул, спорить не о чем.
|
||||
- **Чего в гейте намеренно нет и кто обязан это гонять:**
|
||||
- `govulncheck` даёт `WARN`, а не `FAIL`: находка тут — состояние
|
||||
зависимостей, а не диффа. Разбирает агент ревью по трассам вызовов.
|
||||
- `-race` без gcc уходит в `SKIP` с явным «гонки НЕ проверены» — тогда их
|
||||
проверяет проход `ops` рассуждением, и это идёт в границы покрытия.
|
||||
- Ничего не гоняется против **живого** qBittorrent, LLM и метабаз:
|
||||
интеграционные тесты за env-гейтами, запускает человек вручную.
|
||||
- Качество распознавания гейтом не проверяется вовсе — нужен корпус кейсов
|
||||
(задача `recognition-eval-harness`).
|
||||
|
||||
## Запреты
|
||||
|
||||
- **Не запускать против рабочей БД** `/data/jellybit.db` на umbar и против
|
||||
любого файла, на который указывает боевой `[storage].db_path`. Локально —
|
||||
только `./jellybit.db`.
|
||||
- **Не писать в `/srv/media/downloads`** и вообще никуда под `paths.downloads`:
|
||||
там живут раздачи, которые qBittorrent продолжает сидировать.
|
||||
- **Не ходить в боевой qBittorrent, Jellyfin и Telegram-бота** из тестов и
|
||||
отладочных прогонов. Интеграционные тесты — за env-гейтами
|
||||
(`*_integration_test.go`), включает человек осознанно.
|
||||
- **Не расходовать лимиты метабаз и платного LLM** прогонами «посмотреть, что
|
||||
будет»: у `recognize --dry-run` есть цена.
|
||||
- **`testdata`** отдельным каталогом не заводился: фикстуры чужих форматов
|
||||
живут константами в тестах пакета-разборщика (`internal/tgbot/parse_test.go`,
|
||||
`internal/magnet`, `internal/torrent`).
|
||||
- **Временное — только в `tmp/`** (в `.gitignore`); туда же пишет гейт. Не в
|
||||
`/tmp`, не рядом с исходниками.
|
||||
|
||||
## Работа
|
||||
|
||||
- **Основная ветка:** `master`. От неё считается база диффа
|
||||
(`git merge-base HEAD master`), в неё вливает батч, от неё ветвятся задачи.
|
||||
- **Необратимое** (спрашивается у человека всегда): всё, что пишет в
|
||||
`paths.downloads` или удаляет оттуда; удаление раздачи из qBittorrent вместе
|
||||
с файлами (`Delete`); снятие последней копии данных; правка уже применённой
|
||||
миграции; `git push --force`; удаление или перезапись файла в библиотеке
|
||||
Jellyfin, которого мы не создавали.
|
||||
- **Общий станок** — покрасневший `task gate` на `master` врывается в
|
||||
замороженный спринт: пока он красный, ни одна задача не считается сделанной.
|
||||
- **Ориентир по размеру спринта:** 5–8 задач. Ориентир, а не закон.
|
||||
- **Что такое «сделана»:** пайплайн задачи пройден целиком (спека → код → оба
|
||||
чекпоинта ревью → archive) и критерии приёмки проверены поимённо.
|
||||
|
||||
## Spec Driven Development (OpenSpec)
|
||||
|
||||
Изменения ведём через [OpenSpec](https://github.com/Fission-AI/OpenSpec)
|
||||
(CLI `openspec`, v1.x). Сначала спецификация — потом код.
|
||||
|
||||
- `openspec/specs/<capability>/spec.md` — **нормативный дом поведения**: что
|
||||
система делает сейчас. Capability — это поведение или домен (`ingest`,
|
||||
`recognition`, `file-layout`, `review`, `notifications`), а не пакет кода.
|
||||
- `openspec/changes/<id>/` — предлагаемое изменение: `proposal.md`, `design.md`
|
||||
(для нетривиальных), дельта-спеки (`ADDED`/`MODIFIED`/`REMOVED Requirements`),
|
||||
`tasks.md`. После реализации change архивируется в
|
||||
`openspec/changes/archive/`, дельты вливаются в `openspec/specs/`.
|
||||
- `openspec/config.yaml` — только нужды генерации артефактов: язык, правила
|
||||
именования capability, придирки валидатора.
|
||||
|
||||
Поток работы — через слэш-команды `opsx:*`: `opsx:explore` (продумать),
|
||||
`opsx:propose` (завести change), `opsx:apply` (реализовать tasks),
|
||||
`opsx:sync`/`opsx:archive` (влить и архивировать).
|
||||
|
||||
Правила спек:
|
||||
|
||||
- Каждое `### Requirement` ОБЯЗАНО содержать литерал `SHALL` или `MUST` —
|
||||
иначе `openspec validate` падает.
|
||||
- Структурные заголовки и ключевые слова — английские (`### Requirement:`,
|
||||
`#### Scenario:`, `GIVEN/WHEN/THEN`, RFC 2119), остальной текст — русский.
|
||||
- `openspec validate --strict` перед коммитом change.
|
||||
|
||||
Ревью — два чекпоинта: профиль `design` на предложении (после design/specs, ДО
|
||||
кода) и ревью изменения после apply, до archive. Настройка конвейера под проект
|
||||
и журнал дефектов — [docs/review.md](docs/review.md).
|
||||
|
||||
## Документация
|
||||
|
||||
Раскладка задана каноном av-dev; проверяет её `docs.py check` внутри `task gate`.
|
||||
|
||||
- [docs/passport.md](docs/passport.md) — зачем и для кого, чем **не** является.
|
||||
- [docs/architecture.md](docs/architecture.md) — обзор, эксплуатация, единые
|
||||
точки, деплой. **Поведения здесь нет** — оно в `openspec/specs/`.
|
||||
- [docs/database.md](docs/database.md) — схема, представление данных, настройки
|
||||
с числовым значением.
|
||||
- [docs/security.md](docs/security.md) — периметр, недоверенный вход, что вне
|
||||
модели.
|
||||
- [docs/conventions/](docs/conventions/README.md) — как пишем код.
|
||||
- [docs/research/](docs/research/README.md) — наблюдения за чужими форматами.
|
||||
- [docs/adr/](docs/adr/README.md) — журнал решений, неизменяемый.
|
||||
- [docs/review.md](docs/review.md) — настройка конвейера ревью и журнал дефектов.
|
||||
- [docs/tasks/](docs/tasks/BACKLOG.md) — задачи и цели: одна запись = один файл
|
||||
в `items/` + строка в индексе. Ведётся скиллом `av-dev-pm:tasks`, ритуал
|
||||
спринта — `av-dev-pm:session`.
|
||||
|
||||
**Tududi** (проект `jellybit`, project_id 14) — только инбокс сырых идей. Идея
|
||||
становится задачей, когда её оформляют файлом в `docs/tasks/items/`.
|
||||
|
||||
## Конвенции кода
|
||||
|
||||
- Раскладка: `cmd/jellybit` (точка входа) + `internal/<пакет>` по
|
||||
компонентам из [architecture.md](docs/specs/architecture.md).
|
||||
- Механизируемое проверяет `task gate` (`.golangci.yml` + `internal/archrules`):
|
||||
форма ошибок и логов, конфиг мимо env, время мимо `store.Now()`, AUTOINCREMENT
|
||||
в миграциях, направление зависимостей ядро↔транспорты. Пересказывать эти
|
||||
правила не нужно — гейт скажет точнее.
|
||||
- Прозой остаётся то, что правилом не выражается, и читается в источнике:
|
||||
[ошибки](docs/conventions/errors.md) (трансляция доменной ошибки на внешней
|
||||
границе, sentinel против типизированной),
|
||||
[логи](docs/conventions/logging.md) (уровень по адресату, единственный
|
||||
логирующий чокпоинт, `ext.*`, что не логируем),
|
||||
[конфиг](docs/conventions/config.md) (секреты рендерит деплой в файл `0600`,
|
||||
самодокументируемый `config.example.toml`, валидация на старте),
|
||||
[БД](docs/conventions/database.md) (время в UTC RFC 3339, TEXT ULID через
|
||||
`internal/ident`, `ident.Parse` на входной границе).
|
||||
- Раскладка: `cmd/jellybit` (точка входа) + `internal/<пакет>` по компонентам
|
||||
из [docs/architecture.md](docs/architecture.md).
|
||||
- **Механизируемое проверяет `task gate`** (`.golangci.yml` +
|
||||
`internal/archrules`): форма ошибок и логов, конфиг мимо env, время мимо
|
||||
`store.Now()`, `AUTOINCREMENT` в миграциях, направление зависимостей
|
||||
ядро↔транспорты. Перечень с местом механизации —
|
||||
[docs/conventions/README.md](docs/conventions/README.md); пересказывать эти
|
||||
правила прозой не нужно.
|
||||
- Прозой остаётся только то, что правилом не выражается, и читается в
|
||||
источнике: [ошибки](docs/conventions/errors.md),
|
||||
[логи](docs/conventions/logging.md), [конфиг](docs/conventions/config.md),
|
||||
[БД](docs/conventions/database.md), [веб-UI](docs/conventions/web-ui.md).
|
||||
- Миграции БД (goose, `internal/store/migrations`; SQL для DDL, Go — когда
|
||||
нужен код) — при изменении структуры (таблица/столбец/индекс/связь) в том же
|
||||
change обновляем ER-схему [docs/specs/database.md](docs/specs/database.md).
|
||||
- Веб-UI на htmx — единый партиал = страница = фрагмент, ветвление по `isHTMX`,
|
||||
деградация без JS, ошибка на htmx-пути = 200 + фрагмент, самозавершающийся
|
||||
поллинг: [docs/conventions/web-ui.md](docs/conventions/web-ui.md).
|
||||
нужен код): при изменении структуры в том же change обновляем ER-схему в
|
||||
[docs/database.md](docs/database.md) — иначе краснеет шаг `canon` гейта.
|
||||
|
||||
Кросс-каттинг конвенции (как пишем код, а не что система делает) живут в
|
||||
[docs/conventions/](docs/conventions/README.md) и не переносятся в OpenSpec.
|
||||
Механизируемое там **не держим**: правило уезжает в `.golangci.yml` или в
|
||||
`internal/archrules` и вычёркивается из прозы и из промптов ревью — процедура в
|
||||
[references/promote.md](.claude/skills/review-pipeline/references/promote.md).
|
||||
Прозой остаётся только то, что правилом не выражается.
|
||||
## Язык
|
||||
|
||||
- Документация, комментарии, сообщения коммитов — **русский**.
|
||||
- Код и идентификаторы — английский.
|
||||
|
||||
Reference in New Issue
Block a user