diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..531e443 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,141 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with +code in this repository. + +## Язык + +Общение по репозиторию — на русском. Внутри репозитория язык распределён так, и +это распределение проверяется глазами при ревью: + +- **английский** — комментарии в коде, имена, тексты ошибок, вывод CLI, + сообщения и имена тестов; +- **русский** — `README.md`, `CLAUDE.md`, сообщения коммитов; +- **язык проверяемого набора** — содержимое тестовых фикстур и литералы + словарей в `internal/lang`. Это данные под проверкой, а не текст инструмента, + и переводить их нельзя: сломается то, что они проверяют. + +Слова словаря законно попадают внутрь английских сообщений, потому что +подставляются из набора: `rule GTIM-1 has no ПОЧЕМУ block`. Инструмент говорит +по-английски и цитирует то, чем записан проверяемый набор. + +## Что это + +CLI для управления конвенциями. Модель здесь не описывается: она живёт в +репозитории набора `dev-conventions` — `README.md` (набор и копии), +`LANGUAGE.md` (форма правила и список проверок), `GUIDE.md` (ведение набора, +префикс META), `TOOL.md` (решения об инструменте). При расхождении истина там, +а не в коде и не здесь. + +Пользовательская сторона — в `README.md`. Ниже — то, что нужно знать, правя код. + +## Раскладка + +``` +internal/lang словарь: реестр «версия языка × естественный язык» +internal/manifest suite.toml — чтение и текстовая правка +internal/doc разбор документа: шапка, области правил, блоки +internal/suite сборка набора в память, отбор слоёв под компонент +internal/check проверки: manifest, form, spread, links +internal/cli команды, диалог, два режима +``` + +Зависимость одна — `BurntSushi/toml`. Вторую не заводить: так решено в +`TOOL.md`, и `Undecoded()` этого парсера бесплатно ловит опечатки в ключах +манифеста. + +## Инварианты кода + +- **В коде нет ни одной темы, ни одного префикса, ни одного пути набора.** Всё + это приходит из манифеста. Список проверяемых файлов берётся из + `[prefixes.live]`, а не из дерева директорий: это и есть граница «язык + употребляет» против «язык цитирует». +- **Словарь зашит в бинарь** по паре «версия языка × естественный язык», а не + объявляется в манифесте. Когда спецификация языка уедет в отдельный + репозиторий и там появятся спеки словарей, источником станут они — + подменяется `registry`, тип `Vocabulary` и проверки поверх него не трогаются. +- **Что инструмент пишет, инструмент принимает.** Набор, созданный `suite init`, + `add` и `rule`, обязан проходить `suite check` без правок. Это проверяет + `checkClean` в `internal/cli`; ломать инвариант нельзя. +- **Манифест правится текстом, а не энкодером.** В `suite.toml` комментариев + больше, чем данных. `manifest.AddEntry` вставляет запись, подстраиваясь под + порядок таблицы: отсортированную по алфавиту держит отсортированной, + упорядоченную вручную дополняет в конец. Комментарий, отделённый пустой + строкой, принадлежит таблице **ниже** себя. +- **Разбор опирается на разметку, а не на суждение.** Область правила — от + заголовка до следующего заголовка любого уровня. Метка открывает блок только + первой в абзаце и полужирным. Огороженные блоки кода исключаются везде; + инлайн-код вырезается там, где ищутся ссылки, и не вырезается там, где ищутся + пути канона. + +## Решения, которые уже приняты + +Их не пересматривают без явной просьбы — каждое обсуждалось и стоило времени. + +- **Порядок правил в файле — по читаемости, а не по номерам.** Номер стабилен и + не переиспользуется, поэтому «порядок по номерам» означал бы «порядок по + времени написания» навсегда. Проверяется сплошность нумерации, а не + возрастание. +- **Ссылка на чужую тему вне нормы разрешена** (META-20): обоснование, + потерявшее адресата, деградирует честно. Внутри своей темы ссылаться можно + только в базовый слой, и это проверяется во всём документе, а не только в + норме: гарантированно присутствует в копии один базовый слой. +- **Заголовок правила — объявление, а не ссылка.** При разборе ссылок строки + заголовков пропускаются. +- **Лок-файла, `push`, отчёта о расхождении и перенумерации не будет.** Модель + отвергает каждое явно. + +## Проверки + +Семейства повторяют деление из `LANGUAGE.md`, раздел «Что стоит проверять +машиной», и это деление держится в коде: `form` — в любом файле, который язык +употребляет; `spread` — только в конвенциях, потому что эти проверки о том, что +документ уезжает к потребителю. Третья часть списка (взаимоисключительность +строк таблицы, покрытие области действия, отвечает ли обоснование на «что +сломается») разбором текста не даётся и в коде отсутствует намеренно. + +Новая проверка заводится вместе с двумя тестами: что она срабатывает и что она +**молчит** там, где не должна. Второй важнее: проверка, краснеющая на исправном +файле, выключается целиком. Ложные срабатывания собраны в +`TestNoFalsePositives`. + +Перед тем как заводить проверку, стоит прогнать её замысел по живому канону +(`dev-conventions`): если она покраснеет на исправном наборе, замысел неверен. + +## Известные остатки + +- Набор без документа самоуправления, в котором конвенция потеряла `topic`, + проскочит: признаков «этот документ один» и «у него нет ключей слоя» не + хватает. Закрывается маркером в манифесте — правка формата, не сделана. +- Проверка пути канона считает путём любой токен `*.md`, который резолвится в + файл набора; упоминание `README.md` в конвенции она пометит ошибочно. На + текущем каноне не срабатывает. +- Машиночитаемого вывода находок (`--json`) нет. + +## Тесты + +``` +go test ./... всё +go test ./internal/check/ -v проверки, по одному подтесту на случай +gofmt -l . && go vet ./... перед коммитом +``` + +Тесты фикстурные: набор пишется во временную директорию и прогоняется целиком. +`internal/cli` проверяет обе моды, включая диалог — интерактивный режим иначе не +покрыть, из шелла он требует терминала. + +## Коммиты + +Русский, строчная буква, без точки в конце, прошедшее время или страдательный +залог. Изредка область через двоеточие (`suite check:`). Тело — маркированный +список на 2–4 пункта с переносом по ~76 колонок, объясняет почему. Conventional +Commits и `Co-Authored-By` не используются. + +## Состояние + +Наборная сторона закончена: `init`, `add`, `rule`, `retire`, `list`, `check`. +Проектные команды (`add`, `pull`, `list`, `check` без `suite`) не начаты; отбор +слоёв под компонент для них уже написан — `suite.Assemble`, — и переписывать его +в сборщике не нужно. + +Линтеров и CI нет. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c99e9bd --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# convy + +CLI для управления конвенциями разработки: ведёт набор конвенций и собирает +копии в проектах. + +Модель, которую инструмент реализует, описана не здесь, а в репозитории набора +(`dev-conventions`): `README.md` — устройство набора и копий, `LANGUAGE.md` — +форма правила, `GUIDE.md` — правила ведения набора, `TOOL.md` — решения об этом +инструменте. При расхождении истина там. + +## Термины + +| Уровень | Что это | +|---|---| +| набор | репозиторий с конвенциями, манифестом `suite.toml` и обвязкой | +| тема | набор правил об одном фокусе разработки; единица подписки | +| слой | один файл темы: базовый, языковой или стековый | +| компонент | адресат сборки в проекте: один язык, один стек, один вид приложения | +| префикс | четыре заглавные латинские буквы, адрес правила: `GTIM-3` | + +Тему и ось слоя объявляет шапка файла, а не путь: `topic:`, `lang:`, `stack:`. +Слой без ключей оси — базовый, он попадает в копию всегда. + +## Установка + +Внешняя зависимость одна (`BurntSushi/toml`), сборка обычная: + +``` +go install git.vakhrushev.me/av/convy@latest +``` + +или из клона репозитория: + +``` +go build -o convy . +``` + +Способ раздачи готовых бинарей пока не выбран — вопрос открыт в `TOOL.md`. + +## Команды + +``` +В наборе: + convy suite init завести набор: директория и манифест + convy suite add завести конвенцию: файл, тема и префикс + convy suite rule дописать правило: следующий номер, блоки по порядку + convy suite retire снять правило, конвенцию или тему — без переиспользования + convy suite list что в наборе и что возьмёт компонент + convy suite check целостность набора: префиксы, темы, оси, ссылки, форма + +В проекте (пока не реализовано): + convy add <тема> подписаться и собрать + convy pull пересобрать подписанное + convy list что подключено и что доступно + convy check проверить форму того, что здесь +``` + +Контекст определяется по манифесту рядом: `suite.toml` — набор, +`.conventions.toml` — проект. Наугад не делается ничего: команда не того уровня +отказывает и подсказывает нужную. + +## Два режима + +Команды, которые что-то меняют, работают в двух режимах, и режим читается по +командной строке. + +**Без аргументов — диалог.** Каждое поле спрашивается с подсказкой о том, что +туда кладут и почему. Это для человека. + +``` +$ convy suite add + +The focus the rules are about: time, config, db-schema. A topic is the unit of +subscription — a consumer takes it whole. The name never changes and is never reused. +Topic: logging + +It goes into the manifest and builds the table of conventions in a consumer's README. +One line about the topic: логирование: уровни, структура записи +... +``` + +**С флагами — автоматика.** Всё передаётся сразу, вопросов не задаётся, все +недостающие поля называются разом. Это для агентов и скриптов. + +``` +$ convy suite add --topic logging --about "логирование: уровни" \ + --prefix SLOG --title "Логирование" +``` + +Без терминала пустой вызов отказывает, а не виснет в ожидании ответа, которого +некому дать. + +## Проверка набора + +``` +$ convy suite check +conventions/lang/go/logging.md + error:5 extends points at "arch/time.md" with topic "time", while the file + carries topic "logging": the layers of one topic declare one name [spread] + +suite: 13 files, 8 topics, language version 1 (ru) +errors: 1, warnings: 0 +``` + +Строка находки — `уровень:строка что не так [семейство]`. Семейств четыре: + +- **manifest** — префиксы, темы, объявленные файлы, незарегистрированные файлы; +- **form** — форма правила: нумерация, блоки, модальные слова, словарь. + Проверяется в любом файле, который язык употребляет; +- **spread** — то, что относится к отъезду к потребителю: тема в шапке, ось, + `extends`, пути канона, самодостаточность нормы. Только в конвенциях; +- **links** — разрешение ссылок вида `PREFIX-N`. + +Третья часть списка проверок языка — взаимоисключительность строк таблицы, +покрытие области действия, отвечает ли обоснование на «что сломается» — разбором +текста не даётся и остаётся работой читателя. + +Коды возврата: `0` — чисто, `1` — есть ошибки, `2` — команда набрана неверно или +не в том контексте. + +## Ступени и словарь + +Слова, которыми записаны модальность и метки, — свойство версии языка и +естественного языка набора, а не самого набора. Инструмент знает их сам, и +`suite.toml` их не дублирует: достаточно `[language] version` и `lang`. + +Поэтому ступень называется категорией, а не словом: + +``` +--modality requirement | prohibition | recommendation | not-recommended | permission +``` + +`prohibition` в русском наборе превращается в `**НЕ ДОЛЖЕН.**`, в английском — +в `**MUST NOT.**`. Вызывающему не нужно знать, на каком языке записан набор. + +По той же причине `convy` умеет написать строку о версии языка, и созданный им +файл проходит `suite check` без единой правки. + +## Чего инструмент не делает + +- не сливает трёхсторонне и не разрешает конфликты: правка выше маркера + локальной части теряется, и это заявленное поведение; +- не ведёт лок-файл: копии закоммичены, ответ на «что было в прошлый раз» даёт + git; +- не хранит список подписчиков: подписка — свойство проекта; +- не переносит правки из проекта в набор: операция ручная и редкая; +- не перенумеровывает правила: номер — идентификатор, а не позиция.