- шаг vulns гоняет govulncheck последним: ему одному нужна сеть, и он самый долгий; отсутствие инструмента даёт код окружения, а не пропуск - закрыты обе достижимые находки — grpc до 1.82.1, aws-sdk-go-v2/service/s3 до 1.97.3 с eventstream 1.7.8; прогон на реальных ключах Yandex не делался - место шага названо в семантике гейта: он судит достижимость из кода, и недостижимая GO-2026-5932 в golang.org/x/crypto/openpgp его не роняет
Конвенции кода
Как мы пишем код — в отличие от openspec/specs/, который описывает, что
система делает, и от ../architecture.md, который описывает,
как она сложена.
Прозой остаётся только то, что не выражается правилом. Свойство, ставшее правилом линтера или тестом-сканером, отсюда удаляется и переезжает в перечень «Механизировано» ниже. Причина: файл на несколько сотен строк размазывает внимание по тривиальному — и модель, и человек добросовестно проверят именование и не дойдут до формы решения.
Обоснование «почему именно так» живёт в ../adr/; инварианты с severity — в CLAUDE.md.
Откуда взяты и что с расхождениями
Четыре записи перенесены из проекта jellybit — тот же Go, тот же автор, те же
задачи. Код transcriber написан раньше и части правил не следует: ключи —
UUID вместо ULID, время берётся time.Now() по месту, лог пишется на каждом
шаге и дублируется воркером.
Из этого перечня одно уже закрыто: доменные ошибки проверялись приведением типа
до 2026-08-11, задача errors-as-instead-of-typecast. Приведение типа на этом
месте больше не долг, а регрессия.
Пятая, web-ui.md, тоже пришла оттуда, но не прижилась: jellybit работает на
htmx, а здесь решено делать SPA — и перенесённый текст снят целиком.
Каждое такое место названо в своей записи строкой «Расхождение:». Читается оно как долг, а не как нарушение: правила действуют на новый код, переписывание существующего — отдельная работа. Проходу ревью строка «Расхождение» говорит, что находка на этом месте уже известна и новой не считается.
Записи
- logging.md — логирование: уровень по адресату, единая логирующая
точка на доменной границе, словарь полей,
ext.*, что не логируем. - errors.md — ошибки: stdlib, обёртка
%w,errors.Isиerrors.As, трансляция доменной ошибки на внешней границе, sentinel против типизированной. - config.md — конфигурация: TOML, секреты рендерит выкладка в файл
0600, самодокументируемыйconfig.dist.toml, проверка на старте. - database.md — БД и идентификаторы: время в UTC RFC 3339, TEXT ULID, разбор на входной границе, естественные ключи у деталей.
- web-ui.md — веб-UI: Vue 3 с Vite и статикой в бинарнике,
однофайловые компоненты, таблица маршрутов, состояние в экране, одна обёртка
над
fetch, показ ошибок и состояний списка.
Механизировано
Проверяется командами из CLAUDE.md; прозой не дублируется и в промптах ревью не пересказывается.
| Правило | Где механизировано |
|---|---|
Сравнение ошибок через errors.Is и errors.As, не == и не приведением типа |
.golangci.yml → errorlint |
| Непроверенное возвращаемое значение ошибки | .golangci.yml → errcheck, включая присваивание в _ (check-blank). Отказ, который решено не проверять, объявляют в exclude-functions поимённо — там сегодня defer Close и send |
Проверка судит ответ по готовому ответу (Result()), а не по живой карте заголовков обработчика |
.golangci.yml → forbidigo с analyze-types, находки только в *_test.go. Судит по типу приёмника (httptest.ResponseRecorder), поэтому ловит любую форму: цепочкой, через переменную, по индексу карты, обходом, полем HeaderMap. Остаётся ревью проверка, идущая мимо recorder — через свой http.ResponseWriter |
| Форматирование исходников | .golangci.yml → gofmt |
| Подозрительные конструкции языка | .golangci.yml → govet, staticcheck, ineffassign, unused |
| Секреты в коммите | lefthook.yml → gitleaks git --staged |
| Достижимая из кода уязвимость в зависимостях | Taskfile.yml → шаг vulns (govulncheck ./...) |
Раскладка документов, битые ссылки, изменённый шаг схемы без правки database.md |
docs.py check; каталог шагов задаёт ключ migrations в docs/.docs.json |
Одно число версии Go в go.mod, Dockerfile, CLAUDE.md и README.md |
Taskfile.yml → шаг go-version (scripts/check-go-version.sh) |
Не названное здесь место механизации означает, что проход по конвенциям будет добросовестно проверять уже проверенное.
Из перечисленного в записях правилом выражено одно — сравнение ошибок через
errors.Is и errors.As (errorlint, строка таблицы выше). Прозой остаётся всё
прочее: ни константный msg лога (sloglint), ни запрет fmt.Print* и
os.Getenv (forbidigo заведён, но одним правилом — о том, чем судят ответ в
проверках; этих двух запретов в нём нет), ни запрет сторонних пакетов ошибок
(depguard), ни архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
оставшееся прозой, проверяет человек на каждом ревью заново.