# Конвенции кода Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что система делает, и от [../architecture.md](../architecture.md), который описывает, как она сложена. **Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее правилом линтера или тестом-сканером, отсюда **удаляется** и переезжает в перечень «Механизировано» записи [go-linters.md](go-linters.md) — дома правил, которыми машина читает код. Причина: файл на несколько сотен строк размазывает внимание по тривиальному — и модель, и человек добросовестно проверят именование и не дойдут до формы решения. Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты с severity — в [CLAUDE.md](../../CLAUDE.md). ## Откуда взяты и что с расхождениями Четыре записи перенесены из проекта jellybit — тот же Go, тот же автор, те же задачи. Код transcriber написан раньше и **части правил не следует**: ключи — UUID вместо ULID, лог пишется на каждом шаге и дублируется воркером, `msg` — предложение с заглавной буквы вместо константной категории. Из этого перечня закрыты два. Доменные ошибки проверялись приведением типа до 2026-08-11, задача `errors-as-instead-of-typecast`. Время брали `time.Now()` по месту до 2026-08-13 — теперь его читает единая точка `internal/clock`, и правило держит линтер. Оба места больше не долг, а регрессия. Пятая, `web-ui.md`, тоже пришла оттуда, но не прижилась: jellybit работает на htmx, а здесь решено делать SPA — и перенесённый текст снят целиком. Каждое такое место названо в своей записи строкой «*Расхождение:*». Читается оно как **долг, а не как нарушение**: правила действуют на новый код, переписывание существующего — отдельная работа. Проходу ревью строка «Расхождение» говорит, что находка на этом месте уже известна и новой не считается. ## Записи - [logging.md](logging.md) — логирование: уровень по адресату, единая логирующая точка на доменной границе, словарь полей, `ext.*`, что не логируем. - [errors.md](errors.md) — ошибки: stdlib, обёртка `%w`, `errors.Is` и `errors.As`, трансляция доменной ошибки на внешней границе, sentinel против типизированной. - [config.md](config.md) — конфигурация: TOML, секреты рендерит выкладка в файл `0600`, самодокументируемый `config.dist.toml`, проверка на старте. - [database.md](database.md) — БД и идентификаторы: время в UTC RFC 3339, TEXT ULID, разбор на входной границе, естественные ключи у деталей. - [web-ui.md](web-ui.md) — веб-UI: Vue 3 с Vite и статикой в бинарнике, однофайловые компоненты, таблица маршрутов, состояние в экране, одна обёртка над `fetch`, показ ошибок и состояний списка. - [go-linters.md](go-linters.md) — линтеры и механизированные проверки: лестница механизации, два круга (pre-commit и гейт), перечень правил и подавлений, порядок заведения нового правила. Про инструменты, а не про то, как писать тесты. ## Что из этого проверяет машина Перечень правил, доведённых до проверки, и место настройки каждого — в записи [go-linters.md](go-linters.md). Там же сказано, что из перечисленного в прочих записях осталось прозой и потому проверяется человеком на каждом ревью заново, и там же названы остатки правил — то, что правило не ловит. Числа механизированного здесь нет намеренно: оно протухает при каждом новом правиле.