# К обсуждению Черновик для следующего разговора: вопросы и варианты, а не принятые решения. ## 1. Ссылка на `LANGUAGE.md` не переживает сборку Все двенадцать конвенций во вводной прозе пишут «Форма записи — `LANGUAGE.md`». Обвязка в репозиторий не едет (`conv` синхронизирует только `conventions/`), поэтому в собранной копии эта ссылка указывает в никуда. Это тот же класс дефекта, который вчера закрыло META-21 для ссылок между темами: путь, верный в каноне, молча умирает при сборке. Разница в том, что META-21 предлагает заменить путь на имя темы — а здесь заменять не на что, целевого документа в репозитории просто нет. Варианты, которые видно сейчас: - **Убрать упоминание из тел конвенций.** Язык записи — забота того, кто пишет канон, а не того, кто читает конвенцию в своём репозитории. Читателю достаточно самого текста: модальные слова и «Почему» самоописательны. Дешевле всего, но копия теряет указание, по каким правилам её править. - **Возить обвязку вместе с конвенциями.** Тогда `LANGUAGE.md` и, возможно, `GUIDE.md` появляются в `docs/conventions/` как ещё одни синхронизируемые файлы. Честно, но противоречит нынешнему разделению «канон — конвенции, корень — обвязка» и добавляет в репозиторий текст, который агенту при чтении конвенции не нужен. - **Вкладывать короткую преамбулу в собранный файл.** Сборщик пишет в шапку три-четыре строки: что такое модальное слово, что «Почему» обязательно, где лежит полный документ. Самодостаточно и не тащит весь язык, но преамбула дублируется в каждом файле темы. Появился четвёртый вариант, и он выглядит лучше трёх: **boilerplate-строка как в RFC 8174**. Каждая конвенция несёт одну фразу — «ключевые слова … толкуются как описано в „Языке конвенций“ версии 1 — тогда и только тогда, когда написаны заглавными». Пути нет, документа рядом нет, а норму исполнить можно: строка сама несёт и словарь, и правило капса. Разбирается в заходе про язык записи, вместе с версией — она же цепляет вопрос 9. Сопутствующее: `GUIDE.md` в телах конвенций не упоминается ни разу — проверено, так что вопрос только про `LANGUAGE.md`. ## 2. Тулинг: две разные задачи в одном `conv` Сейчас в `conv` смешаны две категории работы, и они расходятся по всему — по частоте запуска, по тому, кто запускает, и по тому, что считается провалом. **Целостность канона.** Префиксы уникальны и не переиспользованы, шапка совпадает с реестром, номера без дыр вниз, у каждого правила модальность и «Почему», ссылки разрешаются, чужой префикс не лезет в норму (META-20), путей канона в тексте нет (META-21). Запускается в каноне, при каждой правке, провал — это ошибка. Логика уже написана и много раз прогнана руками, но живёт в скретчпаде, а не в репозитории. **Установка в проект.** Манифест `.conventions.toml`, сборка файла темы из секций (arch → языки → стеки → local), сохранение локальной секции при пересборке, отчёт «канон ушёл вперёд», предупреждение о висячих ссылках на неподписанные темы. Запускается в репозитории-потребителе, изредка, провал — это чаще «посмотри глазами», чем «ошибка». Что обсудить: - Разделять ли на два исполняемых файла, или хватит подкоманд с честной границей внутри. - Валидацию канона стоит ли отдать агенту скиллом вместо (или вдобавок к) скрипту: часть проверок формулируется как «прочитай и скажи, самодостаточна ли норма» — механически это не берётся, а агентом берётся. - Куда в этой раскладке ложится запаркованное предупреждение о висячих ссылках: это установка, а не целостность, но список подписок ему нужен из манифеста. ## 3. Модель сборки — записана, закрыто Записана в `README.md` (разделы «Копия в репозитории» и «Манифест») и в `GUIDE.md` (META-22, META-23). Плоская раскладка, файл на тему, один маркер ``, манифест из источника и списка тем, направление строго одностороннее. Против прежних заготовок разошлось в трёх местах: - **лока нет** — «что было в прошлый раз» знает git, копии закоммичены, и автоматического обновления не существует; заодно из шапки копии ушли `origin_hash` и `synced`, осталось одно `origin:`; - **маркеров секций нет** — раз обновление перезаписывает всё выше локального маркера, разметка слоёв тулингу не нужна; слои идут обычными заголовками; - **локальные префиксы не объявляются в манифесте** — за репозиториями зарезервирована буква `X`, столкновение невозможно по построению. Остаётся переписать `conv`: сейчас в нём зеркальное дерево, именованные регионы, `origin_hash` и команды `status`/`diff`/`push`. Это вопрос 2. ## 4. Именованные регионы — удалить из канона Решение принято и записано: регионов нет, есть один маркер ``, который ставит сборщик в копии. Значит регионы не переносятся в единую секцию, а **удаляются**: в каноне их нечем наполнять, локальное принадлежит копии. Остаётся механическая работа — **31 регион в 12 файлах**: ``` 7 связано 7 отступления 7 механизировано 1 эталон / эталоны / словарь / секреты / секретные-поля 1 проверки / поля / модель-владельца / маппинг / границы ``` Вопрос про `связано` снят: META-17 переписан, канонический раздел «Связано» остаётся в тексте и содержит только ссылки, верные у всех, а репо-специфичные уходят под маркер. ## 5. Пары слоёв и темы без базы Отложено сознательно, но список стоит держать перед глазами: - `SLOG` объявляет `extends: arch/time.md` — расширение **чужой** темы. Сборщик темы `logging` на это наткнётся: базового слоя с темой `logging` нет, а `arch/time.md` он тянуть не должен. Чинится переводом в обычную ссылку «связано». - Темы без арх-слоя: `db-schema`, `errors`, `web-ui`. Собираются в файл с одной секцией — само по себе не ломается, но это и есть тот невыделенный арх-слой из известного долга. - Имена тем в паре не совпадают: `arch/db-identifiers.md` против `lang/go/db-schema.md`. При сборке по имени темы это две разные темы — проверить, что так и задумано. - В `lang/go/db-schema.md` сидит целый пласт `stack/sqlite/` (типы колонок), тоже из известного долга README. ## 6. Подключение к репозиториям Ничего ещё не подключено. Кандидаты — jellybit и pet-project-server. Понадобится: заполнить локальные секции тем, что сейчас в этих репозиториях записано по факту; обёртка в раннере (`inv conventions` / `task conventions`, единый интерфейс команд у трёх ansible-репозиториев); строка в `AGENTS.md` каждого потребителя про то, что файлы в `docs/conventions/` — копии. Открытый кусок с прошлого раза: **как копия ссылается на канон, не ломая самодостаточность**. Абсолютный путь `~/projects/private/dev-conventions` в закоммиченном файле не годится — репозиторий перестаёт быть самодостаточным и получает хардкод путей. Пересекается с вопросом 1. ## 7. Не переизобретено ли это — разобрано Проверка перед вложением в тулинг сделана. По слоям: - **Язык записи** — велосипед, но собранный из проверенных деталей: словарь совпадает с RFC 2119/8174 вплоть до правила «нормативен только капс», обязательное «Почему» — дисциплина из requirements engineering (ISO/IEC/IEEE 29148), стабильные идентификаторы — из semgrep/ESLint. Менять архитектуру нечего, остались шесть точечных дельт — отдельным заходом. - **Оси и сборка** — аналога не нашлось. Ближайшие соседи (каскад `extends` в ESLint, слои стилей Vale) дают праву слоя **отменять** базу; наш запрет строже, и это содержательная часть. Вкладываться сюда. - **Транспорт** — переизобретался, но задача у нас другая, чем у copier и cruft. У них трёхсторонний merge потому, что шаблон порождает код, который правят везде: расхождение неограниченно. У нас расхождение ограничено и живёт под маркером, поэтому исключение из перезаписи лучше merge — детерминированно и без конфликтных маркеров. Из vendir/cruft взят разбор «манифест против лока», из ansible `blockinfile` — идея размечать управляемый кусок, а не локальный. Лок при этом не взят: его роль играет git. - **Раздача агентам** — не наша задача, остаёмся на `AGENTS.md`/`CLAUDE.md` плюс скиллы. **Vale отклонён.** Из восьми проверок в `LANGUAGE.md` он честно закрывает две-три (модальные слова вне правил, запрещённые формулировки); остальные пять структурные — «у каждого `### ПРЕФИКС-N` есть модальность и Почему», «номера без дыр вниз», «префикс совпадает с реестром». Это разбор документа, а не проверка токенов в области. Ради двух-трёх проверок тянуть ещё один бинарь и директорию стилей при одном пользователе не стоит. **Что стоит посмотреть перед переписыванием `conv`** (вопрос 2): дистрибуцию пакетов Vale (`.vale.ini` → `vale sync` → `styles/`) как готовый прототип манифеста, и `vendir.yml` — как пример того, где проходит граница между «чего хочу» и «что получил». ## 8. Мультиязычность ключевых слов Модальные слова сейчас русские, и это осознанно: разный словарь держит границу «конвенция — не спецификация» бесплатно. Вопрос — нужен ли английский набор параллельно. За: канон может однажды понадобиться на английском; агенты натренированы на MUST/SHOULD плотнее, чем на ДОЛЖЕН/СЛЕДУЕТ. Против: два набора — это два способа записать одно, и проверка «модальные слова не встречаются вне правил» усложняется вдвое. Если делать, то таблица ключевых слов должна принадлежать **описанию языка**, а не каждой конвенции — то есть вопрос завязан на следующий. ## 9. Описание языка отдельно от набора конвенций `LANGUAGE.md` и `GUIDE.md` описывают, **как** пишутся конвенции; `conventions/` — **один конкретный** набор. Сейчас они склеены в одном репозитории, и из одного описания нельзя собрать второй набор (рабочий, доменный, чужой). Что это даёт, если разнести: - канон объявляет, какой версии языка следует, а тулинг валидирует набор **против объявленного описания**, а не против зашитых в код правил; - таблица модальных слов (вопрос 8) живёт в описании и одна на все наборы; - вопрос 1 (`LANGUAGE.md` не переживает сборку) меняет форму: собранный файл ссылается не на путь, а на язык с версией. Цена — ещё одна сущность и ещё одна синхронизация. Проверить, не выйдет ли лечение хуже болезни при одном пользователе. Оценка выросла: у выноса появилась вторая, независимая причина. Boilerplate- строка из вопроса 1 обязана назвать язык **с версией** — иначе она врёт при первом же изменении словаря. Версия предполагает документ, у которого версия бывает, то есть отдельный от набора. Так IETF и решил ровно эту задачу: RFC 2119 — самостоятельный документ, на который спецификации ссылаются номером, а не путём. ## 10. Тулинг на Go, живущий независимо Сейчас `conv` — питоновский скрипт внутри канона, то есть тулинг и данные в одном репозитории и правятся одним движением. Мысль: вынести в отдельный Go-бинарь со своим релизным циклом, ставить через `eget` (механизм уже есть в pet-project-server). Что за этим стоит помимо вкуса: независимый бинарь физически не даёт править инструмент «заодно» с правкой конвенции, работает против **любого** канона и любого потребителя — что прямо требуется вопросом 9, — и снимает питон из зависимостей репозиториев-потребителей. Связано с вопросом 2: если тулинг всё равно переписывается, разделение «целостность канона / установка в проект» дешевле заложить сразу, чем отпиливать потом. Порядок при этом обратный написанному: пока вопрос 9 не сделан, инструмент всё равно работает против одного конкретного канона, и независимый релизный цикл ему нечего обслуживать. Сначала 9, потом 10. ## 11. Ссылки на родительский слой своей темы Из `lang/` и `stack/` ссылаться на идентификаторы своего же арх-слоя безопасно: при сборке они оказываются секциями одного файла, и ссылка никуда не ведёт — правило рядом. Ограничение META-20 писалось именно про **чужую тему**, так что формально это уже разрешено. Но стоит проговорить явно, потому что сейчас читается уже как запрет: - в `LANGUAGE.md` проверка сформулирована как «**чужой префикс** не встречается в абзаце с модальностью» — по букве это ловит и `GTIM` → `TIME-3`, то есть ложно срабатывает на ровно том случае, который разрешён. Должно быть «префикс **чужой темы**»; - обсудить, не добавить ли в META-20 явную строку **ДОПУСКАЕТСЯ** про родительский слой: правило, которое читают как более строгое, чем оно есть, заставляет авторов дублировать текст без нужды. ## Из вчерашнего, не закрыто - Вынос арх-ядра из `errors` и `logging`: закроет две хрупкие ссылки из `web-ui` (стек) в go-слой — единственные ссылки стек → язык в каноне. - `conv check` должен уметь отличать ссылку на удалённое правило от упоминания дыры: сейчас `META-16` в прозе «Оформления» даёт ложное срабатывание.