Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью. Конвенции перенесены из jellybit; места, где код им не следует, помечены строкой «Расхождение» как объявленный долг. tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и многопользовательский режим), два направления (все форматы, долгие записи) и пять задач в беклоге. openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет. CLAUDE.md переписан по форме канона: инварианты с severity, семантика гейта, запреты с путями. Taskfile получил task gate.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Конвенции кода
|
||||
|
||||
Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что
|
||||
система делает, и от [../architecture.md](../architecture.md), который описывает,
|
||||
как она сложена.
|
||||
|
||||
**Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее
|
||||
правилом линтера или тестом-сканером, отсюда **удаляется** и переезжает в
|
||||
перечень «Механизировано» ниже. Причина: файл на несколько сотен строк
|
||||
размазывает внимание по тривиальному — и модель, и человек добросовестно
|
||||
проверят именование и не дойдут до формы решения.
|
||||
|
||||
Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты с
|
||||
severity — в [CLAUDE.md](../../CLAUDE.md).
|
||||
|
||||
## Откуда взяты и что с расхождениями
|
||||
|
||||
Все пять записей перенесены из проекта jellybit — тот же Go, тот же автор, те же
|
||||
задачи. Код transcriber написан раньше и **части правил не следует**: ключи —
|
||||
UUID вместо ULID, время берётся `time.Now()` по месту, лог пишется на каждом
|
||||
шаге и дублируется воркером, доменные ошибки проверяются приведением типа.
|
||||
|
||||
Каждое такое место названо в своей записи строкой «*Расхождение:*». Читается оно
|
||||
как **долг, а не как нарушение**: правила действуют на новый код, переписывание
|
||||
существующего — отдельная работа. Проходу ревью строка «Расхождение» говорит, что
|
||||
находка на этом месте уже известна и новой не считается.
|
||||
|
||||
## Записи
|
||||
|
||||
- [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 на htmx: партиал равен странице равен
|
||||
фрагменту, ветвление по `HX-Request`, деградация без JS, ошибка на htmx-пути
|
||||
как 200 плюс фрагмент, самозавершающийся опрос, вендоринг статики. **Записана
|
||||
наперёд: веб-UI ещё нет.**
|
||||
|
||||
## Механизировано
|
||||
|
||||
Проверяется командами из [CLAUDE.md](../../CLAUDE.md); прозой не дублируется и в
|
||||
промптах ревью не пересказывается.
|
||||
|
||||
| Правило | Где механизировано |
|
||||
| --- | --- |
|
||||
| Сравнение ошибок через `errors.Is` и `errors.As`, не `==` и не приведением типа | `.golangci.yml` → `errorlint` |
|
||||
| Непроверенное возвращаемое значение ошибки | `.golangci.yml` → `errcheck` (кроме `defer Close` и `send`) |
|
||||
| Форматирование исходников | `.golangci.yml` → `gofmt` |
|
||||
| Подозрительные конструкции языка | `.golangci.yml` → `govet`, `staticcheck`, `ineffassign`, `unused` |
|
||||
| Секреты в коммите | `lefthook.yml` → `gitleaks git --staged` |
|
||||
| Раскладка документов, битые ссылки, миграция без правки `database.md` | `docs.py check` |
|
||||
|
||||
Не названное здесь место механизации означает, что проход по конвенциям будет
|
||||
добросовестно проверять уже проверенное.
|
||||
|
||||
**Из перечисленного в записях правилом выражено одно** — сравнение ошибок через
|
||||
`errors.Is` и `errors.As` (`errorlint`, строка таблицы выше). Прозой остаётся всё
|
||||
прочее: ни константный `msg` лога (`sloglint`), ни запрет `fmt.Print*` и
|
||||
`os.Getenv` (`forbidigo`), ни запрет сторонних пакетов ошибок (`depguard`), ни
|
||||
архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
|
||||
оставшееся прозой, проверяет человек на каждом ревью заново.
|
||||
Reference in New Issue
Block a user