Files
dev-conventions/common/conventions-guide.md
T
av 22c6855968 common: добавлен язык записи конвенций
- правило = номер + модальность (ДОЛЖЕН / СЛЕДУЕТ / ДОПУСКАЕТСЯ) плюс
  обязательный блок «Почему»; из OpenSpec взяты идентификаторы правил, но
  не SHALL и не GIVEN/WHEN/THEN
- файловый статус отменён: обязательность живёт на правиле, а в шапке ещё
  не переведённых конвенций ключ остаётся меткой «это проза»
2026-07-25 18:56:29 +03:00

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