--- prefix: META --- # Как мы ведём конвенции Конвенция описывает повторяющийся выбор: как называть директории, как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как принято», а не «что здесь происходит». Как записывается сама конвенция — правила, модальность, обоснования — в [LANGUAGE.md](LANGUAGE.md). Здесь — про то, зачем конвенции заводятся, где живут и как соотносятся с соседними видами документов. ## Область действия Правила ниже адресованы автору конвенции — тому, кто заводит новый файл или правит существующий, в каноне и в копиях репозиториев. Обязательность живёт на отдельном правиле, а не на файле; шкала модальных слов — в [LANGUAGE.md](LANGUAGE.md). ## Отличие от соседей - `docs/adr/` — **решение**, принятое однажды и постфактум («почему выбрали Authelia, а не Keycloak»). Запись неизменяема. - `docs/specs/` и OpenSpec, где они есть, — **что** система делает, наблюдаемое поведение как контракт. Конвенция — **как** написан код; в спеки она не переносится, это не capability. - `docs/drafts/` — оперативная хроника и черновики, «что собираюсь сделать». - `docs/conventions/` — **правило на будущее**, применяемое многократно. Живой документ: правится, когда договорённость меняется. ## Оформление Имя файла — kebab-case по теме: `app-directories.md`. Правилом это не записано: обоснование сводится к «чтобы имя файла в реестре префиксов писалось одним способом», а проверить нарушение всё равно проще глазом, чем сформулировать норму. Номер META-16, под которым это правило существовало, оставлен свободным и не переиспользуется. ## Канон и копии Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона `dev-conventions`, а не собственные документы репозитория. Репозиторное живёт только внутри локальных регионов ``: они исключены из сравнения с каноном, и расхождение по ним — норма, а не дрейф. Правка вне регионов означает одно из двух: улучшение, которое возвращают в канон, или сознательное расхождение, записанное в ключ `local:` шапки. Состояние копий показывает `conv status`, различия — `conv diff`, обновление из канона — `conv pull`; всё через раннер репозитория. Имя региона обязательно и стабильно: перенос содержимого при обновлении идёт по именам, и переименование осиротит содержимое во всех копиях. ## Правила ### META-1. Одна конвенция — один файл **ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор. **Почему.** Подписка — это набор лежащих в репозитории файлов, и берут файл целиком. Файл, собравший две темы, вынуждает репозиторий взять правила, которые ему не нужны, и получать шум в `diff` по чужой половине. Разрезать позже дорого: путь файла — часть адреса правила, и после разреза внешние ссылки указывают не туда. ### META-2. Конвенция заводится, когда решение принимается третий раз **СЛЕДУЕТ.** Поводом служит одно и то же решение, принятое третий раз и каждый раз чуть по-другому. **Почему.** По одному-двум случаям не видно, что в решении повторяется, а что было частностью места: правило, выведенное из первого случая, кодирует частность и дальше мешает больше, чем помогает. Единичный выбор, к тому же спорный, читателю нужен вместе с мотивом — это ADR, где мотив и есть содержание записи. ### META-3. Новая конвенция пишется там, где заболело **СЛЕДУЕТ.** Первые шаги пути «находка → конвенция → правило линтера → удаление прозы» делаются в репозитории, где случилась находка; в канон продвигается общая часть. **Почему.** Правило, написанное сразу в общем виде, не проверено ни одним применением, и условие применимости у него придумано, а не найдено, — платят за это все потребители сразу. Формулировка, обкатанная на одном репозитории, приезжает в канон уже с известной границей. ### META-4. В тексте конвенции нет утверждений о состоянии репозитория **НЕ ДОЛЖЕН.** Норма пишется в настоящем предписывающем времени, без описаний того, как сейчас устроен конкретный репозиторий. **Почему.** Такое утверждение устаревает молча и подменяет норму описанием: читатель перестаёт понимать, что от него требуется, а что просто констатировано. В копиях у остальных потребителей чужой факт вдобавок ложен с первого дня. «Так сделано у нас» — содержимое региона отступлений, где оно и локально, и проверяемо. ### META-5. Расхождение кода с правилом — отступление, а не повод переписать правило **ДОЛЖЕН.** Правило правится, только когда неверно по существу: содержит фактическую ошибку, внутреннее противоречие или условие применимости, которое не даёт ответа. **Почему.** Правило, подогнанное под текущий код, перестаёт что-либо требовать — оно описывает то, что и так происходит, и первое же расхождение переписывает его снова. Направление «конвенция → код» держится ровно тем, что факт не считается аргументом. ### META-6. `ДОЛЖЕН` без механической проверки либо механизируется, либо понижается **ДОЛЖЕН.** Правило, нарушение которого объявлено ошибкой, получает машинную проверку или переводится в СЛЕДУЕТ. **Почему.** Без проверки правило держится на внимании: нарушения копятся молча и всплывают выборочно — на том ревью, куда дошли руки. Для СЛЕДУЕТ это честно, а ДОЛЖЕН при этом обещает то, чего не делает, и через несколько таких случаев обесценивает остальные ДОЛЖЕН в файле. Отсюда следствие: правило, машинная проверка которого невозможна в принципе (вкус формулировки, выбор границы, суждение о ситуации), не может быть ДОЛЖЕН — его модальность СЛЕДУЕТ по построению, а не по слабости. ### META-7. Факт механизации фиксируется в локальном регионе со ссылкой на правило **ДОЛЖЕН.** Регион `механизировано` называет идентификатор правила и конкретную проверку. **Почему.** Механизация — состояние конкретного репозитория, канон о ней не знает, а без записи следующий автор либо заведёт вторую проверку того же, либо будет вычитывать глазами уже проверенное машиной. Без идентификатора читатель догадывается сам, к какому утверждению относится проверка, — и догадывается по-разному. ### META-8. Формулировка не удаляется из канона, пока механизирована не у всех **НЕ ДОЛЖЕН.** Норма остаётся в каноне, пока хотя бы у одного потребителя машинной проверки нет. **Почему.** У кого линтера нет, тот после удаления остаётся без правила вообще: ни проверки, ни текста. Механизация у одного потребителя ничего не говорит о прочих, поэтому удалять по факту «у нас уже проверяется» — значит чинить свой файл за чужой счёт. Списка подписчиков канон по построению не знает, поэтому факт «механизировано у всех» устанавливается обходом репозиториев вручную — это часть работы по удалению нормы, а не то, что можно проверить машиной (см. `LANGUAGE.md`, состояние МЕХАНИЗИРОВАНО). ### META-9. Общая механизация разрешает удалить норму из канона **ДОПУСКАЕТСЯ.** Правило, уехавшее в общий конфиг линтера или в общую роль, удаляется из канона одним `push`. **Почему.** Формулировка, дублирующая работающую у всех проверку, размазывает внимание: файл на несколько сотен строк заставляет человека и агента добросовестно вычитывать тривиальное именование и не доходить до формы решения. Явное разрешение нужно, чтобы META-8 не читался как запрет удалять вообще. ### META-10. Обоснование не удаляется никогда **НЕ ДОЛЖЕН.** Блок «Почему» остаётся и после того, как норма уехала в линтер. **Почему.** Линтер сообщает, что нарушено, но не сообщает, зачем правило существует. Без обоснования не видно, когда причина отпала, — проверка продолжает работать по инерции, и возразить ей нечем, кроме как отключив. ### META-11. У трудноизменяемого слоя область действия пишется явно **ДОЛЖЕН.** Конвенция о схеме БД, формате хранения или раскладке директорий называет, к чему применяется: к новым таблицам и миграциям, а не к состоянию схемы. **Почему.** Здесь не работает привычное «новое пишем правильно, старое переезжает по мере касания»: таблица не переезжает от того, что её потрогали. Без явной рамки правило читается как требование к текущему состоянию, и оба исхода плохи — миграция живых данных ради опрятности либо молчаливый вывод, что конвенция не соблюдается совсем. ### META-12. Механизируется граница изменения, а не состояние **ДОЛЖЕН.** Проверка запрещает нарушение в новых миграциях, а не в уже существующей схеме. **Почему.** Проверка состояния краснеет на легаси с первого дня: её отключают или обвешивают вечным списком исключений — и она перестаёт ловить новое, ради чего заводилась. Проверка границы оставляет старое в покое и делает новую ошибку невозможной. ### META-13. Список отступлений трудноизменяемого слоя — постоянный **ДОЛЖЕН.** Перечисленные старые таблицы и раскладки живут как есть, а не как задачи на дочистку. **Почему.** Список, записанный долгом, требует либо мигрировать живые данные без выгоды, либо год за годом объяснять невыполненный план. Второе кончается тем, что список перестают вести, — и пропадает единственное место, где видно, где именно правило не действует. ### META-14. Отступления перечисляются поимённо, со ссылкой на правила **ДОЛЖЕН.** В локальном регионе перечислены отступления, которые уже есть в коде, с идентификатором правила и причиной. **Почему.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить это можно только чтением всего кода. Со ссылками отступления счётны: видно, сколько правил конвенции репозиторий реально не соблюдает. Пустой регион при этом почти всегда означает не отсутствие отступлений, а то, что их не искали. ### META-15. Запись в регионе отступлений разбирается по масштабу **ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано: | № | Что записано | Куда идёт | |---|---|---| | META-15.1 | правилу не следуем в перечисленных местах, причина названа | остаётся отступлением | | META-15.2 | правило не применяется в репозитории целиком | чинится условие применимости — в каноне | | META-15.3 | конвенция репозиторию не нужна | копия удаляется, подписки нет | **Почему.** Отступление описывает исключение, и по нему видно, какая часть правила нарушена. Запись «мы это правило вообще не применяем» такой информации не несёт и маскирует одну из двух чинимых причин: неверную рамку правила в каноне, которую чинят один раз для всех, или лишнюю подписку, где файл просто не нужен. Оставленная отступлением, она прячет обе. ### META-17. Репо-специфичная часть «Связано» — в локальном регионе **ДОЛЖЕН.** Раздел «Связано» стоит в конце файла; канонические ссылки — в общем тексте, ссылки на ADR, код и файлы конкретного репозитория — в локальном регионе. **Почему.** Общий текст уезжает `push`-ем ко всем потребителям, и ссылка на чужой файл у них битая с первого дня. Регион исключён из сравнения, поэтому та же ссылка внутри него никого не задевает и не даёт вечного шума в `diff`. ### META-18. README директории перечисляет конвенции с однострочным описанием **СЛЕДУЕТ.** Одна плоская таблица: файл и строка о том, про что он. **Почему.** Подписка — это набор лежащих файлов, и без описаний вопрос «какая из них про мой случай» решается открыванием каждой. Ценой в десяток файлов это означает, что не открывают ни одной. ### META-19. Короткие инварианты дублируются в точку входа агента **ДОЛЖЕН.** В `AGENTS.md` / `CLAUDE.md` едет одна строка на правило с его идентификатором; детали остаются в конвенции. **Почему.** Сама по себе конвенция агенту не видна: он дойдёт до неё, только если его туда отправили, — а безусловно он читает точку входа. Строка с идентификатором служит и напоминанием, и адресом, по которому за подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух текстов.