Files
dev-conventions/common/conventions-guide.md
T
av 4a59c71737 заведён канон общих конвенций для личных проектов
- 13 конвенций по осям arch / lang / stack / common; репозитории берут
  оттуда копии в свой docs/conventions/ и коммитят их у себя
- conv — синхронизация копий: add / status / diff / pull / push, локальные
  регионы исключены из сравнения, поэтому расхождение не даёт шума
2026-07-25 18:18:18 +03:00

136 lines
9.9 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.
---
status: обязательная
---
# Как мы ведём конвенции
Конвенция описывает повторяющийся выбор: как называть директории, как
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
принято», а не «что здесь происходит». Одна конвенция — один файл.
Механической проверки у самой этой конвенции нет — осознанное исключение:
проверять «правильно ли написана конвенция» нечем, а обязательный статус
нужен, чтобы правила ниже не обсуждались заново в каждом репозитории.
## Канон и копии
Файлы в этой директории с шапкой `origin:`**копии из общего канона**
`dev-conventions`, а не собственные документы репозитория. Отсюда:
- репозиторное пишется **только внутрь локальных регионов**
`<!-- local:имя --> … <!-- /local -->`: они исключены из сравнения с
каноном, и расхождение по ним — норма, а не дрейф;
- правка вне регионов означает одно из двух: улучшение, которое надо
вернуть в канон, или сознательное расхождение, записанное в ключ `local:`
шапки;
- состояние копий показывает `conv status`, различия — `conv diff`,
обновление из канона — `conv pull`; всё через раннер репозитория.
Имя региона обязательно и стабильно: перенос содержимого при обновлении
идёт по именам.
## Отличие от соседей
- `docs/adr/`**решение**, принятое однажды и постфактум («почему выбрали
Authelia, а не Keycloak»). Запись неизменяема.
- `docs/specs/` и OpenSpec, где они есть, — **что** система делает,
наблюдаемое поведение как контракт. Конвенция — **как** написан код;
в спеки она не переносится, это не capability.
- `docs/drafts/` — оперативная хроника и черновики, «что собираюсь сделать».
- `docs/conventions/`**правило на будущее**, применяемое многократно.
Живой документ: правится, когда договорённость меняется.
## Направление: конвенция → код
Конвенция формулируется независимо от того, как устроено конкретное
приложение. Код следует конвенции, а не наоборот.
Если код расходится с правилом — это отступление, и оно записывается в
локальный регион, а не переписывает правило. Правило меняется только тогда,
когда оно **неверно по существу**: содержит фактическую ошибку, внутреннее
противоречие или условие применимости, которое не даёт ответа.
Практическое следствие: в тексте конвенции не должно быть утверждений о
текущем состоянии репозитория. «Так сделано у нас» — это регион
отступлений; норма пишется в настоящем предписывающем времени.
## Статус
Каждая конвенция объявляет статус в шапке:
- **рекомендуемая** — так стоит делать в новом коде; существующий переезжает
по мере касания, отдельной кампанией не переписывается;
- **обязательная** — нарушение считается ошибкой; по возможности проверяется
линтером или хуком, а не вниманием.
Конвенция без механической проверки держится только на внимании — это
нормально для рекомендуемой и плохо для обязательной.
## Когда заводить
Когда одно и то же решение принимается третий раз и каждый раз чуть
по-другому. Единичный выбор — не конвенция; если он ещё и был спорным, ему
место в ADR.
Путь находки: **находка → конвенция → правило линтера → удаление прозы**.
Первые два шага делаются в репозитории, где заболело; общая часть
продвигается в канон.
## Прозой — только то, что не выражается правилом
Как только свойство удаётся проверить машиной, его формулировка перестаёт
работать: файл на несколько сотен строк размазывает внимание по
тривиальному, и человек с агентом добросовестно проверят именование, не
дойдя до формы решения.
Но удаление прозы в общем каноне устроено иначе, чем в одиночном
репозитории. Механизация — состояние **конкретного** репозитория:
- **из канона формулировка не удаляется**, пока правило не механизировано
у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
- **факт механизации** фиксируется в локальном регионе `механизировано`
со ссылкой на конкретное правило;
- когда механизация стала общей (правило уехало в общий конфиг линтера или
в общую роль), формулировка удаляется из канона одним `push`.
## Трудноизменяемые слои
У схемы БД, формата хранения и раскладки директорий шкала
«рекомендуемая → переезжает по мере касания» не работает: таблица не
переезжает от того, что её потрогали. Для таких конвенций:
- **область действия пишется явно** — «применяется к новым таблицам и
миграциям», а не к состоянию схемы;
- **механизируется граница изменения, а не состояние** — линтер запрещает
`AUTOINCREMENT` в новых миграциях, а не в существующей схеме: старое не
падает, новая ошибка невозможна;
- **список отступлений постоянный**, а не список задач на дочистку.
## Честный список отступлений
В локальном регионе перечисляем отступления, которые уже есть в коде, —
иначе репозиторий делает вид, что правилу следует. У рекомендуемой
конвенции пустой список отступлений почти всегда означает, что их просто не
искали.
Отступление — это «правилу не следуем здесь и вот почему». Если регион
разросся до «мы это правило вообще не применяем», значит либо у правила
неверно сформулировано условие применимости (чинить в каноне), либо
репозиторию не нужна эта конвенция (не подписываться).
## Оформление
- Имя файла — kebab-case по теме: `app-directories.md`.
- Раздел «Связано» в конце: ADR с обоснованием, спеки, код, который эту
конвенцию механизирует. Репо-специфичная часть «Связано» — в локальном
регионе, канонические ссылки — в общем тексте.
- README директории перечисляет конвенции с однострочным описанием, чтобы
список читался без открывания файлов.
- **Короткие инварианты дублируются туда, что агент читает безусловно**
(`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он
дойдёт до неё, только если его туда отправили. Детали остаются здесь,
в файл-точку-входа едет одна строка на правило.
<!-- local:точки-входа -->
<!-- /local -->