# Конвенции кода

Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что
система делает, и [architecture.md](../architecture.md), который описывает, как
она сложена.

**Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее
правилом линтера, отсюда удаляется и переезжает в перечень «Механизировано».

Язык документации и кода — в [CLAUDE.md](../../CLAUDE.md): это правило шире
кода, оно касается и коммитов, и документов.

## Записи

- [errors.md](errors.md) — ошибки: обёртка, sentinel против типа, трансляция на
  границе, что глушим.
- [logging.md](logging.md) — логи: уровень по адресату, единственный логирующий
  чекпоинт, что не попадает в лог никогда.
- [config.md](config.md) — конфигурация: TOML, валидация на старте,
  самодокументируемый образец.
- [storage.md](storage.md) — база и идентификаторы: ULID и естественные ключи,
  время в UTC, правило выбора между версиями, отпечаток витрины, миграции.
- [testing.md](testing.md) — тесты: реальные пакеты в `testdata`,
  идемпотентность, перебор версий, замер на живом архиве.

## Механизировано

Проверяет `task lint` по [.golangci.yml](../../.golangci.yml). Пересказывать эти
правила прозой не нужно — линтер скажет точнее и всегда актуальнее.

| Правило | Где механизировано |
| --- | --- |
| `msg` лога — константная категория, данные атрибутами | `sloglint` |
| Без `fmt.Print*` — логируем через `slog` | `forbidigo` |
| Конфигурация только из TOML, без `os.Getenv` | `forbidigo` |
| Время генерирует `store.Now()`, не `time.Now()` | `forbidigo` |
| Сравнение ошибок через `errors.Is`/`As`, не `==` | `errorlint` |
| Ошибки только stdlib `errors` + `fmt.Errorf` | `depguard` |
| Стек-трейсы избыточны — контекст несёт `slog` | `depguard` |

Плюс шаги [`task gate`](../../Taskfile.yml): сборка, `go vet`, `gofmt`, тесты,
гонки, покрытие изменённых строк, миграции, образцы конфига, секреты в индексе,
данные о здоровье в индексе.

Непойманное место механизации означает, что проход по конвенциям будет
добросовестно проверять уже проверенное.
