# 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 нет.