- сами конвенции переехали в conventions/, описательное — в корень: LANGUAGE.md (язык записи) и GUIDE.md (как ведут конвенции) - conv синхронизирует только conventions/, пути в origin даются относительно неё — раскладка копий в репозиториях не меняется
19 KiB
Как мы ведём конвенции
Конвенция описывает повторяющийся выбор: как называть директории, как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как принято», а не «что здесь происходит».
Как записывается сама конвенция — правила, модальность, обоснования — в LANGUAGE.md. Здесь — про то, зачем конвенции заводятся, где живут и как соотносятся с соседними видами документов.
Область действия
Правила ниже адресованы автору конвенции — тому, кто заводит новый файл или правит существующий, в каноне и в копиях репозиториев. Обязательность живёт на отдельном правиле, а не на файле; шкала модальных слов — в LANGUAGE.md.
Отличие от соседей
docs/adr/— решение, принятое однажды и постфактум («почему выбрали Authelia, а не Keycloak»). Запись неизменяема.docs/specs/и OpenSpec, где они есть, — что система делает, наблюдаемое поведение как контракт. Конвенция — как написан код; в спеки она не переносится, это не capability.docs/drafts/— оперативная хроника и черновики, «что собираюсь сделать».docs/conventions/— правило на будущее, применяемое многократно. Живой документ: правится, когда договорённость меняется.
Канон и копии
Файлы в docs/conventions/ с шапкой origin: — копии из общего канона
dev-conventions, а не собственные документы репозитория. Репозиторное
живёт только внутри локальных регионов <!-- local:имя --> … <!-- /local -->:
они исключены из сравнения с каноном, и расхождение по ним — норма, а не
дрейф. Правка вне регионов означает одно из двух: улучшение, которое
возвращают в канон, или сознательное расхождение, записанное в ключ local:
шапки. Состояние копий показывает conv status, различия — conv diff,
обновление из канона — conv pull; всё через раннер репозитория.
Имя региона обязательно и стабильно: перенос содержимого при обновлении идёт по именам, и переименование осиротит содержимое во всех копиях.
Правила
R1. Одна конвенция — один файл
ДОЛЖЕН. Файл описывает ровно один повторяющийся выбор.
Почему. Подписка — это набор лежащих в репозитории файлов, и берут файл
целиком. Файл, собравший две темы, вынуждает репозиторий взять правила,
которые ему не нужны, и получать шум в diff по чужой половине. Разрезать
позже дорого: путь файла — часть адреса правила, и после разреза внешние
ссылки указывают не туда.
R2. Конвенция заводится, когда решение принимается третий раз
СЛЕДУЕТ. Поводом служит одно и то же решение, принятое третий раз и каждый раз чуть по-другому.
Почему. По одному-двум случаям не видно, что в решении повторяется, а что было частностью места: правило, выведенное из первого случая, кодирует частность и дальше мешает больше, чем помогает. Единичный выбор, к тому же спорный, читателю нужен вместе с мотивом — это ADR, где мотив и есть содержание записи.
R3. Новая конвенция пишется там, где заболело
СЛЕДУЕТ. Первые шаги пути «находка → конвенция → правило линтера → удаление прозы» делаются в репозитории, где случилась находка; в канон продвигается общая часть.
Почему. Правило, написанное сразу в общем виде, не проверено ни одним применением, и условие применимости у него придумано, а не найдено, — платят за это все потребители сразу. Формулировка, обкатанная на одном репозитории, приезжает в канон уже с известной границей.
R4. В тексте конвенции нет утверждений о состоянии репозитория
НЕ ДОЛЖЕН. Норма пишется в настоящем предписывающем времени, без описаний того, как сейчас устроен конкретный репозиторий.
Почему. Такое утверждение устаревает молча и подменяет норму описанием: читатель перестаёт понимать, что от него требуется, а что просто констатировано. В копиях у остальных потребителей чужой факт вдобавок ложен с первого дня. «Так сделано у нас» — содержимое региона отступлений, где оно и локально, и проверяемо.
R5. Расхождение кода с правилом — отступление, а не повод переписать правило
ДОЛЖЕН. Правило правится, только когда неверно по существу: содержит фактическую ошибку, внутреннее противоречие или условие применимости, которое не даёт ответа.
Почему. Правило, подогнанное под текущий код, перестаёт что-либо требовать — оно описывает то, что и так происходит, и первое же расхождение переписывает его снова. Направление «конвенция → код» держится ровно тем, что факт не считается аргументом.
R6. ДОЛЖЕН без механической проверки либо механизируется, либо понижается
ДОЛЖЕН. Правило, нарушение которого объявлено ошибкой, получает машинную проверку или переводится в СЛЕДУЕТ.
Почему. Без проверки правило держится на внимании: нарушения копятся молча и всплывают выборочно — на том ревью, куда дошли руки. Для СЛЕДУЕТ это честно, а ДОЛЖЕН при этом обещает то, чего не делает, и через несколько таких случаев обесценивает остальные ДОЛЖЕН в файле.
R7. Факт механизации фиксируется в локальном регионе со ссылкой на номер
ДОЛЖЕН. Регион механизировано называет номер правила и конкретную
проверку.
Почему. Механизация — состояние конкретного репозитория, канон о ней не знает, а без записи следующий автор либо заведёт вторую проверку того же, либо будет вычитывать глазами уже проверенное машиной. Без номера правила читатель догадывается сам, к какому утверждению относится проверка, — и догадывается по-разному.
R8. Формулировка не удаляется из канона, пока механизирована не у всех
НЕ ДОЛЖЕН. Норма остаётся в каноне, пока хотя бы у одного потребителя машинной проверки нет.
Почему. У кого линтера нет, тот после удаления остаётся без правила вообще: ни проверки, ни текста. Механизация у одного потребителя ничего не говорит о прочих, поэтому удалять по факту «у нас уже проверяется» — значит чинить свой файл за чужой счёт.
R9. Общая механизация разрешает удалить норму из канона
ДОПУСКАЕТСЯ. Правило, уехавшее в общий конфиг линтера или в общую роль,
удаляется из канона одним push.
Почему. Формулировка, дублирующая работающую у всех проверку, размазывает внимание: файл на несколько сотен строк заставляет человека и агента добросовестно вычитывать тривиальное именование и не доходить до формы решения. Явное разрешение нужно, чтобы R8 не читался как запрет удалять вообще.
R10. Обоснование не удаляется никогда
НЕ ДОЛЖЕН. Блок «Почему» остаётся и после того, как норма уехала в линтер.
Почему. Линтер сообщает, что нарушено, но не сообщает, зачем правило существует. Без обоснования не видно, когда причина отпала, — проверка продолжает работать по инерции, и возразить ей нечем, кроме как отключив.
R11. У трудноизменяемого слоя область действия пишется явно
ДОЛЖЕН. Конвенция о схеме БД, формате хранения или раскладке директорий называет, к чему применяется: к новым таблицам и миграциям, а не к состоянию схемы.
Почему. Здесь не работает привычное «новое пишем правильно, старое переезжает по мере касания»: таблица не переезжает от того, что её потрогали. Без явной рамки правило читается как требование к текущему состоянию, и оба исхода плохи — миграция живых данных ради опрятности либо молчаливый вывод, что конвенция не соблюдается совсем.
R12. Механизируется граница изменения, а не состояние
ДОЛЖЕН. Проверка запрещает нарушение в новых миграциях, а не в уже существующей схеме.
Почему. Проверка состояния краснеет на легаси с первого дня: её отключают или обвешивают вечным списком исключений — и она перестаёт ловить новое, ради чего заводилась. Проверка границы оставляет старое в покое и делает новую ошибку невозможной.
R13. Список отступлений трудноизменяемого слоя — постоянный
ДОЛЖЕН. Перечисленные старые таблицы и раскладки живут как есть, а не как задачи на дочистку.
Почему. Список, записанный долгом, требует либо мигрировать живые данные без выгоды, либо год за годом объяснять невыполненный план. Второе кончается тем, что список перестают вести, — и пропадает единственное место, где видно, где именно правило не действует.
R14. Отступления перечисляются поимённо, со ссылкой на номера правил
ДОЛЖЕН. В локальном регионе перечислены отступления, которые уже есть в коде, с номером правила и причиной.
Почему. Иначе репозиторий выглядит соблюдающим конвенцию, а проверить это можно только чтением всего кода. Со ссылками отступления счётны: видно, сколько правил конвенции репозиторий реально не соблюдает. Пустой регион при этом почти всегда означает не отсутствие отступлений, а то, что их не искали.
R15. Запись в регионе отступлений разбирается по масштабу
ДОЛЖЕН. Судьба записи зависит от того, что в ней сказано:
| № | Что записано | Куда идёт |
|---|---|---|
| R15.1 | правилу не следуем в перечисленных местах, причина названа | остаётся отступлением |
| R15.2 | правило не применяется в репозитории целиком | чинится условие применимости — в каноне |
| R15.3 | конвенция репозиторию не нужна | копия удаляется, подписки нет |
Почему. Отступление описывает исключение, и по нему видно, какая часть правила нарушена. Запись «мы это правило вообще не применяем» такой информации не несёт и маскирует одну из двух чинимых причин: неверную рамку правила в каноне, которую чинят один раз для всех, или лишнюю подписку, где файл просто не нужен. Оставленная отступлением, она прячет обе.
R16. Имя файла — kebab-case по теме
СЛЕДУЕТ. app-directories.md, а не вариации регистра и разделителя.
Почему. Имя файла — часть глобального адреса правила
(stack/ansible/app-directories.md R4) и значение ключа origin в каждой
копии. Один способ записи избавляет от нескольких написаний одного адреса,
а ошибка в адресе обнаруживается только тем, кто по нему пришёл и ничего не
нашёл.
R17. Репо-специфичная часть «Связано» — в локальном регионе
ДОЛЖЕН. Раздел «Связано» стоит в конце файла; канонические ссылки — в общем тексте, ссылки на ADR, код и файлы конкретного репозитория — в локальном регионе.
Почему. Общий текст уезжает push-ем ко всем потребителям, и ссылка на
чужой файл у них битая с первого дня. Регион исключён из сравнения, поэтому
та же ссылка внутри него никого не задевает и не даёт вечного шума в diff.
R18. README директории перечисляет конвенции с однострочным описанием
СЛЕДУЕТ. Одна плоская таблица: файл и строка о том, про что он.
Почему. Подписка — это набор лежащих файлов, и без описаний вопрос «какая из них про мой случай» решается открыванием каждой. Ценой в десяток файлов это означает, что не открывают ни одной.
R19. Короткие инварианты дублируются в точку входа агента
ДОЛЖЕН. В AGENTS.md / CLAUDE.md едет одна строка на правило с его
номером; детали остаются в конвенции.
Почему. Сама по себе конвенция агенту не видна: он дойдёт до неё, только если его туда отправили, — а безусловно он читает точку входа. Строка с номером служит и напоминанием, и адресом, по которому за подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух текстов.