- `docs/.pm.json` переименован в `docs/.docs.json`, версия канона — 14 - ADR принимает записку разведки как источник решения наравне с архивным `design.md` - заведён `tasks/.tasks.json`, а в описании гейта — шаги `tasks.py check` и `openspec.py check`
11 KiB
CLAUDE.md
Памятка для работы над transcriber. Перед задачей прочитай также docs/passport.md, docs/architecture.md и docs/conventions/.
Проект ведём по-русски.
Что это
Сервис расшифровки аудио в текст. Принимает запись двумя входами — Telegram-бот и
HTTP API, — конвертирует её ffmpeg в ogg, отдаёт на отложенное распознавание
Yandex SpeechKit и возвращает текст туда, откуда пришла запись. Состояние задач
и метаданные файлов лежат в SQLite, файлы — на диске.
Чего не делает: не редактирует и не пересказывает текст, не хранит записи как архив, не распознаёт речь сам, не заводит учётные записи и не работает с живым потоком. Границу домена целиком держит docs/passport.md.
Стек
Go 1.24 (нужен CGO из-за mattn/go-sqlite3), gin, goqu, goose, SQLite,
go-telegram-bot-api, aws-sdk-go-v2 для Object Storage, gRPC-клиент Yandex
SpeechKit v3, Prometheus, slog. Сборка — Taskfile, образ — Docker,
выкладка — Ansible из pet-project-server.
Инварианты
Что нарушать нельзя.
- Секрет не покидает конфиг. Токен бота, ключ SpeechKit и пара ключей Object
Storage не попадают в git, в лог, в ответ пользователю и в колонку
error_text. Нарушение необратимо: утёкший ключ отзывают и меняют вручную во всех местах выкладки. critical - Содержимое записи остаётся приватным. Текст расшифровки, имя файла пользователя и его сообщение в лог не пишутся — только длина и идентификаторы. Нарушение необратимо: строки уже уехали в журнал контейнера. critical
- Бот отвечает только тем, кто в белом списке. Бот проверяет отправителя до любой работы, включая скачивание файла. Нарушение обратимо правкой конфига, но чужие записи к тому моменту уже обработаны за наши деньги. critical
- Принятая запись не теряется молча. Отказ на любом шаге либо оставляет
задачу пригодной к повтору, либо переводит её в
failedи сообщает пользователю. Молчаливый выход из шага без записи в лог и без смены состояния запрещён. Обратимо повторной отправкой, но пользователь об этом не узнает. major NoopJobError— не ошибка. Значение «задач в этом состоянии нет» не логируется, не считается в метрику и не поднимает уровень. Нарушение даёт запись раз в секунду на каждый воркер. major- Миграция, уехавшая на сервер, не переписывается. Изменение — только новым файлом. Необратимо: goose считает применённую версию по номеру. critical
- Новая колонка правится во всех четырёх местах репозитория SQLite —
Create,Save,GetByID,FindAndAcquire. Компилятор расхождение не поймает, а проявится оно как потерянное при сохранении поле. major
Команды
go build ./... # нужен CGO
go test ./...
go vet ./...
gofmt -l .
golangci-lint run
go run . -c config.toml # флаг -c или --config, по умолчанию config.toml
task image # docker-образ; тег и раскладка — docs/architecture.md
task gate # весь набор проверок разом
Локальный запуск требует ffmpeg и ffprobe в PATH и своего config.toml —
скопируй config.dist.toml и заполни; известные прорехи образца перечислены в
docs/conventions/config.md строками
«Расхождение:».
Гейт
- Команда целиком:
task gate. База диффа — переменнаяBASE, по умолчаниюorigin/master; переопределяетсяtask gate BASE=<rev>. - Где логи шагов: вывод команды, отдельного файла нет.
- Что означает каждый исход: ненулевой код любого шага роняет гейт. У
docs.py check,tasks.py checkиopenspec.py checkсловарь кодов общий: 0 сошлось, 1 дрейф, 2 ошибка употребления, 3 окружение (не корень проекта, каталог не найден), 4 внутренний сбой. - Что красит безусловно и почему: отказ сборки, тестов,
go vet, неотформатированный файл, находкаgolangci-lint, дрейф раскладки документов, дрейф каталога задач, формаopenspec/config.yaml. Машина проверяет всё перечисленное, и это не обсуждается. Шаг, чей скрипт не найден, краснеет с именем недостающего плагина, а не пропускается молча. - Чего в гейте намеренно нет и кто тогда обязан это гонять:
gitleaks— висит на pre-commit вlefthook.ymlи смотрит только индекс коммита. Полную историю никто не проверяет;- согласованность документов между собой и с кодом — её судят агенты, зовёт
их скилл
av-dev-docs:healthcheck, и звать его надо руками; - покрытие изменённых строк не считается ничем.
Гейт на master сегодня красный, и это объявленный долг, а не поломка дня.
Известный отказ один:
golangci-lint runдаёт 4 замечания в существующем коде: два непроверенныхClose(adapter/recognizer/yandex/speechkit.go:55,main.go:124) и два сравнения ошибок приведением типа (controller/worker/worker.go:51,service/transcribe.go:394). Долг записан в docs/conventions/errors.md, заведён задачейerrors-as-instead-of-typecast.
Новые отказы отличай от этого. Пока он жив, «зелёный гейт» в определении сделанного означает «не добавилось ничего сверх перечисленного».
go test ./... больше долгом не считается. Тесты приёма по HTTP чинены
задачей http-handler-tests-never-green; красный go test теперь означает
поломку, и списывать его на наследство нельзя.
Запреты
- Рабочую БД не трогать.
data/transcriber.dbна сервере и его копии. Локальная база в./data/— своя, её ронять и пересоздавать можно свободно. - Боевой каталог записей не трогать.
data/filesна сервере: там лежат голосовые сообщения живых людей. - Боевым токеном бота не запускаться. Второй процесс с тем же токеном перехватывает обновления у работающего, и пользователь теряет ответы.
- Yandex Cloud за деньги. Распознавание и хранение в Object Storage
оплачиваются по факту. Прогон на реальных ключах ради проверки кода запрещён —
подставляй
internal/adapter/recognizer/memory.go. - Выкладку не запускать.
inv pl -- transcriberизpet-project-serverзапускает человек. testdataв проекте нет. Тесты, которым нужен файл, создают его во временном каталоге и убирают за собой.- Временное —
t.TempDir()в тестах,/tmpвне их. Вdata/временное не писать: этот каталог смонтирован на сервере.
Работа
- Основная ветка:
master. Коммиты идут в неё напрямую, веток и PR нет. - Сообщение коммита без трейлера
Co-Authored-By. - Необратимое (спрашивается у человека всегда): применённая миграция, формат
файла на диске и раскладка
data/files, публичный контракт HTTP API, имя ключа конфига, любое действие с боевыми данными и с Yandex Cloud, ротация секрета. - Что считается сломанным — новый красный шаг гейта, которого не было до твоей правки. Такое чинится прежде любой другой работы. Два объявленных долга из раздела «Гейт» сломанным состоянием не считаются, пока их не закрыли задачами.
- Ориентир по размеру порции: не замерялся.
- Что такое «сделана»:
task gateзелёный и критерии приёмки проверены поимённо.
Язык
- Документация, комментарии, сообщения коммитов — русский.
- Код и идентификаторы — английский.
- Текст, который видит пользователь Telegram, — русский.