# Инструмент: convy Стартовая точка для разработки. Здесь — принятые решения об инструменте и открытые вопросы к нему; модель, которую он реализует, описана в [README.md](README.md), а форма правил — в [LANGUAGE.md](LANGUAGE.md). При расхождении истина там, а не здесь. ## Что это `convy` — CLI для управления конвенциями: собирает копии в проектах и проверяет целостность набора. Пишется на Go, живёт в отдельном репозитории, ставится готовым бинарём. Имя выбрано за то, что держит корень предметной области и звучит как маленький помощник, а уменьшительное `-y` обещает малость — что правда: инструмент копирует файлы, собирает тему из слоёв и проверяет форму, не более. Рядом существует английское слово `convey`, отличающееся одной буквой и подходящее по смыслу, — опечатку в текстах стоит проверять отдельно. Отдельный репозиторий нужен по двум причинам. Инструмент и данные в одном репозитории правятся одним движением, и это мешает: лежащий отдельно бинарь физически не даёт починить инструмент «заодно» с правкой конвенции. Вторая — независимый бинарь работает против любого набора и любого проекта, а не против одного конкретного канона. ## Термины | Уровень | Английский | Русский | Что там лежит | |---|---|---|---| | набор | `suite` | набор | `conventions/`, манифест набора, обвязка | | проект | `project` | проект | манифест подключения, компоненты | | компонент | `component` | компонент | директория копий: один язык, один стек, один вид приложения | `package`, `bundle`, `library` не берём: они тащат багаж менеджеров зависимостей — версии, разрешение, лок, — которого в модели нет. `set` не годится в CLI: в позиции подкоманды читается глаголом. Компонент — адресат сборки: подписка принадлежит проекту, а собранный файл читает тот, кто правит конкретный код. Определение — область, где все выбранные слои действуют одновременно (`sqlite` и `postgres` — да, go и javascript — никогда). Уровнем CLI компонент не становится: это аргумент `--for`, а не подкоманда. «Канон» — имя этого конкретного набора, а не термин уровня; в общих формулировках употребляется «набор». «Потребитель» — слово про роль репозитория, а не про уровень. Манифесты названы по тому, что описывают, а не по уровню ради симметрии: `.conventions-suite.toml` в наборе — идентичность набора, `.conventions.toml` в проекте — подключённые конвенции. Оба начинаются с точки, потому что оба — данные инструмента, а не документы репозитория: они читаются и **переписываются целиком** командами, комментариев не держат, и объяснения к ним живут в соседних файлах. Отсюда же бесплатное определение контекста: по имени рядом видно, где ты. Прежде манифест набора назывался `suite.toml` и точки не имел — на том основании, что правится он руками при каждой новой теме. Основание отпало: правит его инструмент. ## Раскладка команд Глубина команды отражает частоту и адресата. Проектные команды выполняются в каждом репозитории и часто; ведение набора — в одном репозитории и редко. ``` convy add <тема> подписаться и собрать convy pull пересобрать подписанное convy list что подключено и что можно взять convy check проверить форму того, что здесь convy suite check целостность набора: префиксы, темы, оси, ссылки, форма convy suite new новая тема: шапка, префикс, запись в манифест ``` Проектные команды принимают `--for <компонент>`. При одном компоненте флаг не нужен; при нескольких команда без него отказывает и перечисляет имена — тот же принцип, что и с контекстом: наугад не делается ничего. `convy list` группирует вывод по компонентам. Граница проходит не по «проектное против наборного», а по «частое и повсеместное» против «только у автора». Поэтому `check` остаётся наверху: форма правила одна и та же, локальные правила проекта на `X`-префиксах написаны по ней же, и проверять их у себя нужно без подкоманды. Под `suite` уходит то, что в проекте не имеет смысла. Три следствия для реализации: - **контекст определяется и отказ говорится явно.** `.conventions.toml` рядом — проект, `.conventions-suite.toml` — набор. Проектная команда, набранная в наборе, не делает ничего наугад: она отказывает и подсказывает наборный аналог; - **помощь группируется заголовками** «В проекте» и «В наборе»: в плоском списке уровни не видны; - **синонимов нет.** `convy project pull` рядом с `convy pull` не заводим: два способа сказать одно — то, от чего модель избавлялась в остальных местах. В проектах вызов идёт через обёртку раннера (`inv conventions -- pull`, `task conventions -- pull`), которая пробрасывает аргументы. Обёртка уже называет предмет, поэтому короткая проектная команда здесь тоже выигрывает. ## Две задачи, которые нельзя смешивать Они расходятся по частоте, по адресату и по тому, что считается провалом. **Целостность набора.** Префиксы уникальны и не переиспользованы, шапка совпадает с манифестом, тема объявлена и зарегистрирована, ось объявлена ключами и у темы не больше одного базового слоя, у каждого правила модальность с нормой и обоснование либо заглушка, нумерация сплошная, ссылки разрешаются, путей набора в тексте конвенции нет, строка о версии языка на месте. Запускается в наборе при каждой правке; провал — ошибка. **Установка в проект.** Манифест подключения, сборка файла темы из слоёв на каждый компонент, сохранение локальной части, `READING.md` рядом с копиями. Запускается в проекте изредка; провал чаще означает «посмотри глазами», чем «ошибка». Отчёта «набор ушёл вперёд» нет: его делает `git diff` после пересборки. ## Что делает установка Подробности — в README, раздел «Копия в репозитории». Коротко, что важно для реализации: - сборка идёт **по разу на компонент**, в директорию `dir` из его секции; директории компонентов обязаны различаться — иначе копии столкнутся именами, и это ошибка манифеста, а не повод переименовывать файлы; - копия плоская, **один файл на тему**; слои идут секциями в порядке база → язык → стек, выбор слоёв — по `lang` и `stack` компонента, сверяемым с ключами оси в шапке слоя (META-38), а не с путём файла в наборе; слой без ключей оси — базовый и попадает в копию всегда; - набор может быть плоским: у темы один слой, ключей оси нет, `lang` и `stack` в компоненте отсутствуют. Отдельной ветки в коде это не требует — сборка из одного слоя есть копирование; - шапка копии — только `origin:` с именем темы; отпечатков и дат нет; - всё ниже маркера `` переживает пересборку, всё выше перезаписывается; маркер ставит сборщик; - `README.md` в директории принадлежит проекту и не трогается; `READING.md` принадлежит набору и перезаписывается целиком; - транспорта обратно нет: ни `push`, ни отчёта о расхождении, ни лок-файла. ## Что делает проверка Список проверок — в `LANGUAGE.md`, раздел «Что стоит проверять машиной». Он уже разделён на три части, и это деление прямо ложится в код: - **форма правила** — разбором текста, в любом файле, который язык употребляет: конвенции и `GUIDE.md`; - **распространение** — разбором текста, только в файлах конвенций: тема в шапке, префикс чужой темы в норме, пути набора, `X` у локальных правил; - **чтением** — то, что машине не даётся: взаимоисключительность строк таблицы, покрытие области действия, самодостаточность нормы, обоснование отвечает на «что сломается», примеры не расширяют норму. Третья часть — не работа `convy`. Её выполняет агент, и по ней стоит завести скилл, а не пытаться выразить регуляркой. ## Независимость от набора Инструмент не зашивает правила языка в код: версия и документы объявлены в манифесте набора, секция `[language]`. Пока версия одна, это выглядит избыточным — но именно здесь разница между «инструмент для этого канона» и «инструмент для любого набора». Отсюда же: никаких упоминаний конкретных тем, префиксов и путей в коде. ## Чего инструмент не делает - не сливает трёхсторонне и не разрешает конфликты: правка выше маркера теряется, и это заявленное поведение; - не ведёт лок-файл: копии закоммичены, автоматического обновления нет, ответ на «что было в прошлый раз» даёт git; - не хранит список подписчиков: подписка — свойство проекта; - не переносит правки из проекта в набор: это ручная и редкая операция. ## Открытые вопросы - **Один бинарь или два.** Пока — один с подкомандой `suite`. Разделение дешевле заложить сразу, чем отпиливать потом: если задачи разъедутся, у них уже будут разные точки входа. - **Установка.** `eget` умеет GitHub-релизы, а git у меня свой (`git.vakhrushev.me`) — проверить, тянет ли `eget` релизы Gitea, иначе остаётся прямой URL или `go install`. - **TOML.** В stdlib парсера нет, значит одна внешняя зависимость (`BurntSushi/toml` или `pelletier/go-toml`). Выбрать и больше зависимостей не заводить. - **Предупреждение о висячих ссылках** на неподписанные темы: это установка, а не целостность, но список подписок ему нужен из манифеста подключения. - **Проверка на `convey`** в текстах — вместе с остальными проверками формы или отдельной мелочью. - **Прототипы, на которые стоит посмотреть** до того, как писать: дистрибуция пакетов Vale (`.vale.ini` → `vale sync` → `styles/`) как образец манифеста и `vendir.yml` — как пример границы между «чего хочу» и «что получил». ## Состояние `convy` не начат. В наборе лежит `conv` — питоновский скрипт под прежнюю модель копий: зеркальное дерево, именованные регионы, `origin_hash`, команды `status`, `diff`, `push`. Он не истина ни в чём: модель описана в README, проверки — в `LANGUAGE.md`. Логика проверок целостности написана и много раз прогнана руками, но живёт в скретчпадах сессий, а не в репозитории. При старте разработки её стоит перенести первой — это готовая спецификация в виде кода. Переименование `conv` → `convy` в обвязке (`README.md`, `CLAUDE.md`) делается одним заходом, когда инструмент будет готов.