- .claude/settings.json включает плагин av-dev-git@av-dev-skills; набор плагинов у канона общий с остальными репозиториями, поэтому лежит в git, а не в локальных настройках
Канон конвенций
Общие конвенции для личных проектов. Репозитории берут отсюда копии в свой
docs/conventions/, коммитят их и живут дальше самостоятельно — как с
ansible-roles: канон не источник истины во время работы, а лавка, из
которой берут и в которую возвращают улучшения.
Сами конвенции лежат в conventions/, обвязка — в корне:
| Файл | Что описывает |
|---|---|
README.md |
устройство канона, оси, синхронизация, жизненный цикл |
| LANGUAGE.md | язык записи правил: идентификаторы, модальность, «Почему» |
| GUIDE.md | как ведут конвенции: когда заводить, механизация, отступления |
prefixes.toml |
реестр префиксов правил |
conv |
синхронизация копий |
Обвязка живёт только в каноне и в репозитории не оказывается — conv
синхронизирует лишь содержимое conventions/. Пока это осознанное
ограничение: копия конвенции ссылается на LANGUAGE.md как на внешний
документ.
Правило то же, что у ролей: деплоится и читается только то, что лежит в git репозитория. Канон никем не подключается на лету.
Направление — конвенция → код
Конвенция формулируется независимо от конкретного приложения. Она задаёт правило; код ему следует. Обратное направление запрещено: то, что приложение уже делает иначе, не является аргументом против правила — это отступление, и его место в локальном регионе того репозитория, а не в переформулировке канона.
Отсюда практические следствия:
- в каноне нет утверждений о том, как что-то устроено в конкретном репозитории («у нас так в девяти плейбуках из тридцати трёх») — только нормы и условия их применимости;
- в каноне нет списка, кто на что подписан: подписка — свойство репозитория, а не конвенции;
- расхождение канона с кодом чинится либо кодом, либо честной записью отступления, либо — если правило оказалось неверным — правкой правила по существу, а не подгонкой под факт.
Оси
conventions/
arch/ решения, переживающие смену языка и инструментов
lang/<язык>/ как решение реализуется и механизируется в языке
stack/<стек>/ привязка к инструменту, хранилищу, транспорту
Пути файлов даются относительно conventions/: arch/db-identifiers.md,
а не conventions/arch/… — так же, как они лягут в docs/conventions/
репозитория. На правила ссылаются идентификатором без пути: KEYS-5.
Префикс уникален по всему канону (реестр — prefixes.toml), поэтому
идентификатор не зависит от того, на какой оси файл лежит сегодня.
Тест — по тому, замена чего убивает правило:
Умирает при смене языка →
lang/. Умирает при смене инструмента, хранилища или транспорта →stack/. Не умирает ни от того, ни от другого →arch/.
PK — ULID, генерирует приложение не умирает ни от чего — это лежит в
данных → arch/. internal/ident, ident.Parse на границах умирают со
сменой языка → lang/go/. enum как TEXT без CHECK переживёт Go → Python,
но не переживёт уход от SQLite → это stack/sqlite/.
Ось определяется природой правила, а не тем, сколько сегодня потребителей. Конвенция независима от приложений по построению, поэтому арх-слой выделяется тогда, когда правило действительно не зависит от языка, а не когда появился второй язык.
Известный долг. По этому тесту lang/go/errors.md, lang/go/logging.md
и lang/go/db-schema.md содержат невыделенные слои: у первых двух —
архитектурное ядро (уровень как адресат, что не логируем; трансляция ошибки
на внешней границе, приватный канал против публичного), у третьего — целый
пласт stack/sqlite/ (типы колонок). Это не принцип, а незавершённая
работа.
Префиксы
Каждый файл канона объявляет в шапке свой префикс правил:
prefix: KEYS
Четыре заглавные латинские буквы, уникальные по всему канону; реестр —
prefixes.toml. Префикс выбирается под файл, а не выводится
по формуле, и не переиспользуется никогда. Правила адресуются идентификатором
KEYS-5 — без пути к файлу. Подробности формы — LANGUAGE.md.
Расширение
Файл в lang/ или stack/ может объявить в шапке ещё и базу:
extends: arch/db-identifiers.md
Расширение только реализует и сужает базу, но не отменяет её. Если слою нужно противоречить базе — это сигнал одного из двух: либо у базы неверно сформулировано условие применимости (чинится в каноне), либо репозиторий на базу просто не подписан.
extends — документация связи, а не механизм: conv о ней только
напоминает при add и никак не следит за тем, чтобы база лежала рядом.
Служебная разметка
Шапка копии ставится при conv add и в каноне не хранится:
---
origin: arch/time.md # откуда взято
origin_hash: a1b2c3d4 # отпечаток канона на момент синхронизации
synced: 2026-07-25
local: нет # или: чем и почему разошлись
---
origin_hash — контент-отпечаток, а не git-SHA. Он позволяет отличать
«канон обновился» от «изменено локально»; без него status умеет только
«differs», а такой отчёт быстро перестают читать. Прочие ключи шапки
(prefix, extends) — часть документа: они сравниваются наравне с телом.
Локальные регионы — куски, принадлежащие репозиторию по определению.
Из сравнения исключаются, поэтому вечного шума в diff не дают:
<!-- local:механизировано -->
`AUTOINCREMENT` в новых миграциях — `internal/archrules`.
<!-- /local -->
Имя обязательно — перенос при pull идёт по именам, безымянные регионы
conv отвергает. Что всегда локально:
- механизация — канон не знает, у кого линтер уже настроен;
- отступления — «у нас пока не так», честно и поимённо;
- разрешение условия — «Здесь: INTEGER PK, id наружу не выходят»;
- эталоны и ссылки — имена функций, файлов, ADR конкретного репозитория;
- список конвенций в README репозитория.
Путь файла в каноне и имя региона — это API: переименование осиротит все
копии (origin строковый). Переименовывать — только вместе с обходом
потребителей.
После pull копию нужно перечитать глазами: содержимое региона могло
устареть относительно переписанного вокруг текста, и автоматика этого не
увидит.
Раскладка в репозитории
Копии повторяют структуру канона:
docs/conventions/
README.md собственный, не синхронизируется
arch/db-identifiers.md
lang/go/db-identifiers.md
Подписка не описана отдельным файлом — она и есть набор лежащих файлов,
видимый в git ls-files. README директории перечисляет их одной плоской
таблицей, чтобы вложенность не мешала навигации.
Контракт с агентом
Копии — обычные файлы, и правка их агентом никак не отличима от правки
любого другого документа. Это главный канал тихого дрейфа, поэтому
AGENTS.md каждого потребителя должен явно говорить:
Файлы в
docs/conventions/с шапкойorigin:— копии из канонаdev-conventions. Репозиторное пишется только внутрь<!-- local:… -->. Правка вне регионов — либоconv pushв канон, либо запись причины вlocal:.
Команды
conv list # что есть в каноне
conv add arch/time.md # взять к себе (можно несколько за раз)
conv status # ok / изменено локально / канон обновился / разошлись
conv diff [arch/time.md] # чем копия отличается, без учёта локальных регионов
conv pull arch/time.md # забрать обновление канона (регионы переносятся)
conv push arch/time.md # вернуть локальное улучшение в канон
conv push --new lang/go/x.md # завести в каноне новую конвенцию
status и diff всегда завершаются кодом 0: это отчёт, а не проверка.
Расхождение — нормальное состояние, а постоянный шум в diff означает не
«догони канон», а «пора разрезать файл». Симметрично: разросшийся до спора
с базой локальный регион означает «пора чинить условие применимости в
каноне».
Запускать из корня репозитория:
~/projects/private/dev-conventions/conv status
Обёртка в раннере репозитория (inv conventions -- status для ansible,
task conventions -- status для Go) — тонкий проброс аргументов, чтобы
логика не размножалась по репозиториям в двух диалектах.
Жизненный цикл
- В канон. Новая конвенция пишется в том репозитории, где заболело, и
продвигается
conv push --new. Локальные регионы при этом опустошаются: в канон едет только норма. - Из канона. Устаревшая конвенция удаляется вместе с обходом потребителей — тихо осиротить копии нельзя.
- История. Канон коммитится при каждом
push:origin_hashотвечает на «отличается ли», но только git канона отвечает на «почему база сформулирована так».