обвязка: модель копий переписана под один маркер и манифест
- именованные регионы `<!-- local:имя -->` заменены на единственный `<!-- conv:local -->`: всё ниже него принадлежит репозиторию, всё выше пересобирается, поэтому имени-которое-можно-осиротить больше нет - лок-файла и `origin_hash` в шапке нет — «что было в прошлый раз» знает git, копии закоммичены, автоматического обновления не существует; транспорт назад (`push`) убран вместе с ними - заведены META-22 (репозиторное пишется ниже маркера) и META-23 (форк не носит `origin:`), META-17 переписан под маркер; буква `X` в префиксе зарезервирована за локальными правилами потребителей
This commit is contained in:
@@ -3,22 +3,21 @@
|
||||
Общие конвенции для личных проектов. Репозитории берут отсюда копии в свой
|
||||
`docs/conventions/`, коммитят их и живут дальше самостоятельно — как с
|
||||
`ansible-roles`: канон не источник истины во время работы, а лавка, из
|
||||
которой берут и в которую возвращают улучшения.
|
||||
которой берут.
|
||||
|
||||
Сами конвенции лежат в `conventions/`, обвязка — в корне:
|
||||
|
||||
| Файл | Что описывает |
|
||||
|---|---|
|
||||
| `README.md` | устройство канона, оси, синхронизация, жизненный цикл |
|
||||
| `README.md` | устройство канона, оси, сборка копий, жизненный цикл |
|
||||
| [LANGUAGE.md](LANGUAGE.md) | язык записи правил: идентификаторы, модальность, «Почему» |
|
||||
| [GUIDE.md](GUIDE.md) | как ведут конвенции: когда заводить, механизация, отступления |
|
||||
| `prefixes.toml` | реестр префиксов правил |
|
||||
| `conv` | синхронизация копий |
|
||||
| `conv` | сборка копий |
|
||||
|
||||
Обвязка живёт только в каноне и в репозитории не оказывается — `conv`
|
||||
синхронизирует лишь содержимое `conventions/`. Пока это осознанное
|
||||
ограничение: копия конвенции ссылается на `LANGUAGE.md` как на внешний
|
||||
документ.
|
||||
Обвязка живёт только в каноне и в репозитории не оказывается — в копию едет
|
||||
лишь содержимое `conventions/`. Пока это осознанное ограничение: копия
|
||||
конвенции ссылается на `LANGUAGE.md` как на внешний документ.
|
||||
|
||||
Правило то же, что у ролей: **деплоится и читается только то, что лежит в
|
||||
git репозитория**. Канон никем не подключается на лету.
|
||||
@@ -28,7 +27,7 @@ git репозитория**. Канон никем не подключаетс
|
||||
Конвенция формулируется независимо от конкретного приложения. Она задаёт
|
||||
правило; код ему следует. Обратное направление запрещено: то, что
|
||||
приложение уже делает иначе, **не является аргументом против правила** — это
|
||||
отступление, и его место в локальном регионе того репозитория, а не в
|
||||
отступление, и его место в локальной части копии того репозитория, а не в
|
||||
переформулировке канона.
|
||||
|
||||
Отсюда практические следствия:
|
||||
@@ -51,11 +50,12 @@ conventions/
|
||||
stack/<стек>/ привязка к инструменту, хранилищу, транспорту
|
||||
```
|
||||
|
||||
Пути **файлов** даются относительно `conventions/`: `arch/db-identifiers.md`,
|
||||
а не `conventions/arch/…` — так же, как они лягут в `docs/conventions/`
|
||||
репозитория. На **правила** ссылаются идентификатором без пути: `KEYS-5`.
|
||||
Префикс уникален по всему канону (реестр — `prefixes.toml`), поэтому
|
||||
идентификатор не зависит от того, на какой оси файл лежит сегодня.
|
||||
Оси — раскладка **канона**; в репозитории копия лежит плоско, файлом на
|
||||
тему. Пути файлов даются относительно `conventions/`
|
||||
(`arch/db-identifiers.md`) и адресуют исходник канона, а не место в копии.
|
||||
На **правила** ссылаются идентификатором без пути: `KEYS-5`. Префикс
|
||||
уникален по всему канону (реестр — `prefixes.toml`), поэтому идентификатор
|
||||
не зависит ни от оси, ни от того, как собран файл у потребителя.
|
||||
|
||||
Тест — по тому, замена чего убивает правило:
|
||||
|
||||
@@ -93,6 +93,12 @@ prefix: KEYS
|
||||
по формуле, и не переиспользуется никогда. Правила адресуются идентификатором
|
||||
`KEYS-5` — без пути к файлу. Подробности формы — `LANGUAGE.md`.
|
||||
|
||||
Буква `X` в начале префикса зарезервирована за репозиториями: канон её не
|
||||
занимает никогда, а локальные правила потребителя берут префиксы только на
|
||||
неё (`XTIM`, `XLOG`). Так столкновение локального префикса с будущим
|
||||
префиксом канона невозможно по построению, и согласовывать заранее ничего не
|
||||
нужно.
|
||||
|
||||
## Расширение
|
||||
|
||||
Файл в `lang/` или `stack/` может объявить в шапке ещё и базу:
|
||||
@@ -106,67 +112,88 @@ extends: arch/db-identifiers.md
|
||||
неверно сформулировано условие применимости (чинится в каноне), либо
|
||||
репозиторий на базу просто не подписан.
|
||||
|
||||
`extends` — документация связи, а не механизм: `conv` о ней только
|
||||
напоминает при `add` и никак не следит за тем, чтобы база лежала рядом.
|
||||
`extends` — документация связи, а не механизм: за тем, чтобы база лежала
|
||||
рядом, никто не следит.
|
||||
|
||||
## Служебная разметка
|
||||
## Копия в репозитории
|
||||
|
||||
**Шапка копии** ставится при `conv add` и в каноне не хранится:
|
||||
|
||||
```yaml
|
||||
---
|
||||
origin: arch/time.md # откуда взято
|
||||
origin_hash: a1b2c3d4 # отпечаток канона на момент синхронизации
|
||||
synced: 2026-07-25
|
||||
local: нет # или: чем и почему разошлись
|
||||
---
|
||||
```
|
||||
|
||||
`origin_hash` — контент-отпечаток, а не git-SHA. Он позволяет отличать
|
||||
«канон обновился» от «изменено локально»; без него `status` умеет только
|
||||
«differs», а такой отчёт быстро перестают читать. Прочие ключи шапки
|
||||
(`prefix`, `extends`) — часть документа: они сравниваются наравне с телом.
|
||||
|
||||
**Локальные регионы** — куски, принадлежащие репозиторию по определению.
|
||||
Из сравнения исключаются, поэтому вечного шума в `diff` не дают:
|
||||
|
||||
```markdown
|
||||
<!-- local:механизировано -->
|
||||
`AUTOINCREMENT` в новых миграциях — `internal/archrules`.
|
||||
<!-- /local -->
|
||||
```
|
||||
|
||||
Имя обязательно — перенос при `pull` идёт по именам, безымянные регионы
|
||||
`conv` отвергает. Что всегда локально:
|
||||
|
||||
- **механизация** — канон не знает, у кого линтер уже настроен;
|
||||
- **отступления** — «у нас пока не так», честно и поимённо;
|
||||
- **разрешение условия** — «Здесь: INTEGER PK, id наружу не выходят»;
|
||||
- **эталоны и ссылки** — имена функций, файлов, ADR конкретного репозитория;
|
||||
- **список конвенций** в README репозитория.
|
||||
|
||||
Путь файла в каноне и имя региона — это API: переименование осиротит все
|
||||
копии (`origin` строковый). Переименовывать — только вместе с обходом
|
||||
потребителей.
|
||||
|
||||
После `pull` копию нужно перечитать глазами: содержимое региона могло
|
||||
устареть относительно переписанного вокруг текста, и автоматика этого не
|
||||
увидит.
|
||||
|
||||
## Раскладка в репозитории
|
||||
|
||||
Копии повторяют структуру канона:
|
||||
Копия плоская: **один файл на тему**, слои осей идут внутри него секциями в
|
||||
порядке `arch` → язык → стек. Пути канона в копии не воспроизводятся.
|
||||
|
||||
```
|
||||
docs/conventions/
|
||||
README.md собственный, не синхронизируется
|
||||
arch/db-identifiers.md
|
||||
lang/go/db-identifiers.md
|
||||
README.md собственный, не собирается
|
||||
time.md arch/time.md + lang/go/time.md
|
||||
db-identifiers.md arch/db-identifiers.md + lang/go/db-identifiers.md
|
||||
app-directories.md arch/… + stack/ansible/…
|
||||
```
|
||||
|
||||
Подписка не описана отдельным файлом — она **и есть** набор лежащих файлов,
|
||||
видимый в `git ls-files`. README директории перечисляет их одной плоской
|
||||
таблицей, чтобы вложенность не мешала навигации.
|
||||
Так конвенция остаётся самодостаточным документом: тот, кто проверяет по ней
|
||||
код — человек или агент, — читает один файл и не собирает тему из трёх мест.
|
||||
|
||||
**Шапка копии** ставится при сборке и в каноне не хранится:
|
||||
|
||||
```yaml
|
||||
---
|
||||
origin: time
|
||||
---
|
||||
```
|
||||
|
||||
Больше в шапке ничего нет. Отпечатка канона и даты синхронизации в ней не
|
||||
хранится: обновление перезаписывает файл в рабочем дереве, и что именно
|
||||
изменилось, показывает `git diff` до коммита. Второй механизм сравнения
|
||||
рядом с git не нужен.
|
||||
|
||||
**Маркер локальной части** — единственная машинно значимая разметка внутри
|
||||
файла:
|
||||
|
||||
```markdown
|
||||
<!-- conv:local -->
|
||||
|
||||
MIGR-2, MIGR-4 механизированы — `internal/archrules`.
|
||||
MIGR-6 не соблюдается в `show_history`, `queue`: составные ключи там
|
||||
появились до конвенции, миграция данных не окупается.
|
||||
```
|
||||
|
||||
Всё ниже маркера принадлежит репозиторию и переживает обновление; всё выше —
|
||||
пересобирается из канона. Маркер один и безымянный, поэтому у него нет
|
||||
имени, которое можно осиротить переименованием.
|
||||
|
||||
Ниже маркера живёт то, чего канон о репозитории не знает: механизация,
|
||||
отступления, разрешение условий («Здесь: INTEGER PK, id наружу не выходят»),
|
||||
ссылки на ADR и код, а также **собственные правила** — с префиксом на `X`,
|
||||
по тем же правилам формы, что и канон.
|
||||
|
||||
Если местных правок стало больше, чем каноничного текста, копия перестаёт
|
||||
быть копией: `origin:` из шапки убирают, и дальше это обычный документ
|
||||
репозитория. Файл, оставивший шапку, при следующем обновлении потеряет
|
||||
всё, что выше маркера.
|
||||
|
||||
## Манифест
|
||||
|
||||
Откуда взяты копии и где брать обновления — `.conventions.toml` в корне
|
||||
репозитория-потребителя:
|
||||
|
||||
```toml
|
||||
source = "ssh://git@git.vakhrushev.me:2222/av/dev-conventions.git"
|
||||
|
||||
lang = ["go"]
|
||||
stack = ["sqlite", "htmx"]
|
||||
|
||||
topics = ["time", "config", "db-identifiers"]
|
||||
```
|
||||
|
||||
`lang` и `stack` выбирают строку разреженной матрицы: файл темы собирает
|
||||
только те слои, которые репозиторию подходят. `topics` — подписка; списка
|
||||
подписчиков у канона по-прежнему нет, список тем есть только у потребителя.
|
||||
|
||||
Как именно инструмент добирается до канона — путь на диске, git, HTTP —
|
||||
дело инструмента, а не модели. Манифест отвечает на два вопроса: откуда это
|
||||
взято и где искать обновления.
|
||||
|
||||
Отдельного лок-файла нет. Он отвечал бы на «что было в прошлый раз», а на
|
||||
это отвечает git: копии закоммичены, автоматического обновления не
|
||||
существует, и любое изменение проходит через чтение диффа человеком.
|
||||
|
||||
## Контракт с агентом
|
||||
|
||||
@@ -175,45 +202,53 @@ docs/conventions/
|
||||
`AGENTS.md` каждого потребителя должен явно говорить:
|
||||
|
||||
> Файлы в `docs/conventions/` с шапкой `origin:` — копии из канона
|
||||
> `dev-conventions`. Репозиторное пишется только внутрь
|
||||
> `<!-- local:… -->`. Правка вне регионов — либо `conv push` в канон, либо
|
||||
> запись причины в `local:`.
|
||||
> `dev-conventions`. Репозиторное пишется только ниже `<!-- conv:local -->`;
|
||||
> всё выше маркера перезаписывается при обновлении. Своё правило — с
|
||||
> префиксом на `X`.
|
||||
|
||||
## Команды
|
||||
|
||||
```bash
|
||||
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 # завести в каноне новую конвенцию
|
||||
conv list # какие темы есть в каноне
|
||||
conv add time # добавить тему в манифест и собрать файл
|
||||
conv pull # пересобрать всё, что перечислено в манифесте
|
||||
```
|
||||
|
||||
`status` и `diff` всегда завершаются кодом 0: это отчёт, а не проверка.
|
||||
Расхождение — нормальное состояние, а постоянный шум в `diff` означает не
|
||||
«догони канон», а «пора разрезать файл». Симметрично: разросшийся до спора
|
||||
с базой локальный регион означает «пора чинить условие применимости в
|
||||
каноне».
|
||||
Отчёт о том, что изменилось, отдельной командой не выдаётся: после `pull`
|
||||
его показывает `git diff`, а решение — принять, поправить или откатить —
|
||||
принимает человек перед коммитом.
|
||||
|
||||
Транспорт обратно в канон не предусмотрен. Улучшение, найденное в
|
||||
репозитории, переносится в канон руками: это редкая операция, и её цена —
|
||||
не аргумент против того, чтобы направление оставалось односторонним.
|
||||
|
||||
Запускать из корня репозитория:
|
||||
|
||||
```bash
|
||||
~/projects/private/dev-conventions/conv status
|
||||
~/projects/private/dev-conventions/conv pull
|
||||
```
|
||||
|
||||
Обёртка в раннере репозитория (`inv conventions -- status` для ansible,
|
||||
`task conventions -- status` для Go) — тонкий проброс аргументов, чтобы
|
||||
Обёртка в раннере репозитория (`inv conventions -- pull` для ansible,
|
||||
`task conventions -- pull` для Go) — тонкий проброс аргументов, чтобы
|
||||
логика не размножалась по репозиториям в двух диалектах.
|
||||
|
||||
## Жизненный цикл
|
||||
|
||||
- **В канон.** Новая конвенция пишется в том репозитории, где заболело, и
|
||||
продвигается `conv push --new`. Локальные регионы при этом опустошаются:
|
||||
в канон едет только норма.
|
||||
переносится в канон, когда стало ясно, что общего в ней больше, чем
|
||||
местного. Локальная часть при этом не едет: в канон попадает только норма,
|
||||
а префикс на `X` меняется на канонический — то есть правила получают новые
|
||||
идентификаторы.
|
||||
- **Из канона.** Устаревшая конвенция удаляется вместе с обходом
|
||||
потребителей — тихо осиротить копии нельзя.
|
||||
- **История.** Канон коммитится при каждом `push`: `origin_hash` отвечает
|
||||
на «отличается ли», но только git канона отвечает на «почему база
|
||||
сформулирована так».
|
||||
- **История.** Канон коммитится при каждой правке: только git канона
|
||||
отвечает на вопрос, почему база сформулирована так.
|
||||
|
||||
## Состояние
|
||||
|
||||
Модель выше — согласованная, а не реализованная. `conv` пока собран под
|
||||
прежнюю: зеркальное дерево копий вместо плоского, именованные регионы
|
||||
`<!-- local:имя -->` вместо одного маркера, `origin_hash` в шапке и команды
|
||||
`status`, `diff`, `push`. Двенадцать файлов канона всё ещё несут 31 пустой
|
||||
именованный регион. Ни один репозиторий-потребитель не подключён, поэтому
|
||||
переход никого не ломает.
|
||||
|
||||
Reference in New Issue
Block a user