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

10 KiB
Raw Blame History

Как мы ведём конвенции

Конвенция описывает повторяющийся выбор: как называть директории, как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как принято», а не «что здесь происходит». Одна конвенция — один файл.

Как записывается сама конвенция — правила, модальность, обоснования — в 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.

Правило без механической проверки держится только на внимании. Для СЛЕДУЕТ это нормально, для ДОЛЖЕН — плохо: такое правило либо механизируется, либо честно понижается.

Когда заводить

Когда одно и то же решение принимается третий раз и каждый раз чуть по-другому. Единичный выбор — не конвенция; если он ещё и был спорным, ему место в ADR.

Путь находки: находка → конвенция → правило линтера → удаление прозы. Первые два шага делаются в репозитории, где заболело; общая часть продвигается в канон.

Прозой — только то, что не выражается правилом

Как только свойство удаётся проверить машиной, его формулировка перестаёт работать: файл на несколько сотен строк размазывает внимание по тривиальному, и человек с агентом добросовестно проверят именование, не дойдя до формы решения.

Но удаление прозы в общем каноне устроено иначе, чем в одиночном репозитории. Механизация — состояние конкретного репозитория:

  • из канона формулировка не удаляется, пока правило не механизировано у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
  • факт механизации фиксируется в локальном регионе механизировано — со ссылкой на номер правила и на конкретную проверку;
  • когда механизация стала общей (правило уехало в общий конфиг линтера или в общую роль), формулировка удаляется из канона одним push.

Обоснование правила («Почему») не удаляется никогда, даже когда сама норма уехала в линтер: линтер сообщает, что нарушено, но не сообщает, зачем правило существует, — а именно это нужно, чтобы понять, когда его пора отменить.

Трудноизменяемые слои

У схемы БД, формата хранения и раскладки директорий не работает привычное «новое пишем правильно, старое переезжает по мере касания»: таблица не переезжает от того, что её потрогали. Для таких конвенций:

  • область действия пишется явно — «применяется к новым таблицам и миграциям», а не к состоянию схемы;
  • механизируется граница изменения, а не состояние — линтер запрещает AUTOINCREMENT в новых миграциях, а не в существующей схеме: старое не падает, новая ошибка невозможна;
  • список отступлений постоянный, а не список задач на дочистку.

Честный список отступлений

В локальном регионе перечисляем отступления, которые уже есть в коде, — со ссылкой на номера правил. Иначе репозиторий делает вид, что конвенции следует, а проверить это можно только чтением всего кода.

Пустой список отступлений почти всегда означает, что их не искали.

Отступление — это «правилу не следуем здесь и вот почему». Если регион разросся до «мы это правило вообще не применяем», значит либо у правила неверно сформулировано условие применимости (чинить в каноне), либо репозиторию не нужна эта конвенция (не подписываться).

Оформление

  • Имя файла — kebab-case по теме: app-directories.md.
  • Раздел «Связано» в конце: ADR с обоснованием, спеки, код, который эту конвенцию механизирует. Репо-специфичная часть «Связано» — в локальном регионе, канонические ссылки — в общем тексте.
  • README директории перечисляет конвенции с однострочным описанием, чтобы список читался без открывания файлов.
  • Короткие инварианты дублируются туда, что агент читает безусловно (AGENTS.md / CLAUDE.md): сама по себе конвенция агенту не видна, он дойдёт до неё, только если его туда отправили. Детали остаются здесь, в файл-точку-входа едет одна строка на правило с его номером.