- Раскладка docs/ приведена к канону 2: заведены passport/architecture/ database/security/review и research; docs/specs, drafts, backlog, review/ и BRIEF.md разобраны и удалены, беклог переехал в docs/tasks (34 задачи, 6 целей, слаги на английский). - Нарративы specs удалены как дубли openspec-спек после поимённой сверки; остаток заведён задачами (редактор маппинга ревью, крайние случаи именования), отказ от сущности title промоутнут в ADR. - Проектные копии агентов и скиллов ревью/пайплайна удалены в пользу плагинов av-dev-pm и av-dev-pipeline; в task gate добавлен шаг canon вместо er-schema.
15 KiB
CLAUDE.md
Памятка для работы над jellybit. Перед задачей прочитай также docs/passport.md, docs/architecture.md и docs/conventions/. Разработка идёт по Spec Driven Development через OpenSpec — см. раздел ниже.
Что это
Связующий сервис qBittorrent ↔ Jellyfin: принимает торрент с текстовым
контекстом, качает через qBittorrent, распознаёт фильм или сериал (LLM +
контекст + опц. метабазы) и раскладывает файлы для Jellyfin хардлинками, не
трогая исходную раздачу. Деплоится на домашний медиа-сервер umbar
(/home/av/projects/private/umbar).
Чего не делает: не ищет раздачи в трекерах, не ведёт профили качества, не подписывается на выходящие серии, не хранит медиа и не заменяет Jellyfin. Полная граница домена — docs/passport.md.
Стек
Go 1.26, один статический бинарь (CGO_ENABLED=0). Module path —
git.vakhrushev.me/av/jellybit. SQLite через modernc.org/sqlite + sqlx,
миграции goose, HTTP — chi + html/template + htmx, конфиг —
pelletier/go-toml/v2, логи — log/slog (структурированный JSON).
Инварианты
Нарушать нельзя. Severity стоит здесь, а не выводится каждым проходом ревью заново.
- Источник неприкосновенен — под
paths.downloadsдопустимы только чтение иlink(2); никакихunlink,rename, записи. Нарушение уничтожает невосстановимые данные пользователя. Необратимо.critical. - Последняя копия не снимается —
Undoотклоняется целиком, если у цели не осталось других жёстких ссылок (nlink <= 1) или исходного файла уже нет. Частичный откат тоже стёр бы часть данных. Необратимо.critical. - Целевой путь строго под библиотекой — после санитизации и
filepath.Cleanпуть обязан лежать подpaths.movies/paths.series, иначе операция отклоняется. Выход за песочницу означает запись в чужие каталоги. Необратимо.critical. Выход LLM недоверенный: безопасность держится на этой проверке, а не на промпте. - Существующее не перезаписываем — цель занята другим файлом → коллизия →
review. Обратимо (задача уходит в ревью), но потеря чужого файла — нет.
critical. - Секреты не попадают в логи, диагностику и ответы API — пароль
qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin.
Утёкший в лог секрет отзывается вручную.
major. - Авто-раскладка только при подтверждённом матче в метабазе — самооценка
LLM гейтом не является
(ADR). Обратимо
через
Undo.major. - Переходы состояний — только через
workerпод per-download блокировкой, и только легальные по декларативному графу. Обход даёт гонку двух транспортов.major. - Время — только
store.Now()(UTC), идентификаторы — толькоident;ident.Parseна каждой входной границе. Механизировано линтером.minor.
Команды
Запуск через Task (task --list — полный список):
task setup— установка тулинга (golangci-lint + git-хуки lefthook)task run— локальный запуск (go run ./cmd/jellybit --config ./config.toml)task build— статический бинарьlinux/amd64для сервераtask test/task lint— тесты и golangci-linttask gate— детерминированный гейт ревью (см. ниже)task review:context— карта проекта для архитектурного прохода ревьюtask tidy—go mod tidytask image— docker-образ из готового бинаря
Гейт
- Команда целиком:
task gate(BASE=<rev>задаёт базу диффа). БезBASEбаза —git merge-base HEAD master, а на самомmaster—HEAD~1. - Где логи шагов:
tmp/gate/<шаг>.log, по одному файлу на шаг. - Что означает исход: статусы
OK/FAIL(краснит) /WARN(виден, не блокирует) /SKIP(не применим, всегда с причиной). Код возврата 1, если есть хоть одинFAIL. Гейт не останавливается на первом отказе — ревьюверу нужна полная картина. - Что красит безусловно: сборка,
go vet,golangci-lint,gofmt, тесты, флаки-прогон (второй прогон разошёлся с первым),-race, накат миграций с нуля,gitleaks, канон документации (docs.py check— раскладкаdocs/, битые ссылки, «миграция изменена, аdatabase.mdнет»). Причина одна: у каждого из них есть объективный оракул, спорить не о чем. - Чего в гейте намеренно нет и кто обязан это гонять:
govulncheckдаётWARN, а неFAIL: находка тут — состояние зависимостей, а не диффа. Разбирает агент ревью по трассам вызовов.-raceбез gcc уходит вSKIPс явным «гонки НЕ проверены» — тогда их проверяет проходopsрассуждением, и это идёт в границы покрытия.- Ничего не гоняется против живого qBittorrent, LLM и метабаз: интеграционные тесты за env-гейтами, запускает человек вручную.
- Качество распознавания гейтом не проверяется вовсе — нужен корпус кейсов
(задача
recognition-eval-harness).
Запреты
- Не запускать против рабочей БД
/data/jellybit.dbна umbar и против любого файла, на который указывает боевой[storage].db_path. Локально — только./jellybit.db. - Не писать в
/srv/media/downloadsи вообще никуда подpaths.downloads: там живут раздачи, которые qBittorrent продолжает сидировать. - Не ходить в боевой qBittorrent, Jellyfin и Telegram-бота из тестов и
отладочных прогонов. Интеграционные тесты — за env-гейтами
(
*_integration_test.go), включает человек осознанно. - Не расходовать лимиты метабаз и платного LLM прогонами «посмотреть, что
будет»: у
recognize --dry-runесть цена. testdataотдельным каталогом не заводился: фикстуры чужих форматов живут константами в тестах пакета-разборщика (internal/tgbot/parse_test.go,internal/magnet,internal/torrent).- Временное — только в
tmp/(в.gitignore); туда же пишет гейт. Не в/tmp, не рядом с исходниками.
Работа
- Основная ветка:
master. От неё считается база диффа (git merge-base HEAD master), в неё вливает батч, от неё ветвятся задачи. - Необратимое (спрашивается у человека всегда): всё, что пишет в
paths.downloadsили удаляет оттуда; удаление раздачи из qBittorrent вместе с файлами (Delete); снятие последней копии данных; правка уже применённой миграции;git push --force; удаление или перезапись файла в библиотеке Jellyfin, которого мы не создавали. - Общий станок — покрасневший
task gateнаmasterврывается в замороженный спринт: пока он красный, ни одна задача не считается сделанной. - Ориентир по размеру спринта: 5–8 задач. Ориентир, а не закон.
- Что такое «сделана»: пайплайн задачи пройден целиком (спека → код → оба чекпоинта ревью → archive) и критерии приёмки проверены поимённо.
Spec Driven Development (OpenSpec)
Изменения ведём через OpenSpec
(CLI openspec, v1.x). Сначала спецификация — потом код.
openspec/specs/<capability>/spec.md— нормативный дом поведения: что система делает сейчас. Capability — это поведение или домен (ingest,recognition,file-layout,review,notifications), а не пакет кода.openspec/changes/<id>/— предлагаемое изменение:proposal.md,design.md(для нетривиальных), дельта-спеки (ADDED/MODIFIED/REMOVED Requirements),tasks.md. После реализации change архивируется вopenspec/changes/archive/, дельты вливаются вopenspec/specs/.openspec/config.yaml— только нужды генерации артефактов: язык, правила именования capability, придирки валидатора.
Поток работы — через слэш-команды opsx:*: opsx:explore (продумать),
opsx:propose (завести change), opsx:apply (реализовать tasks),
opsx:sync/opsx:archive (влить и архивировать).
Правила спек:
- Каждое
### RequirementОБЯЗАНО содержать литералSHALLилиMUST— иначеopenspec validateпадает. - Структурные заголовки и ключевые слова — английские (
### Requirement:,#### Scenario:,GIVEN/WHEN/THEN, RFC 2119), остальной текст — русский. openspec validate --strictперед коммитом change.
Ревью — два чекпоинта: профиль design на предложении (после design/specs, ДО
кода) и ревью изменения после apply, до archive. Настройка конвейера под проект
и журнал дефектов — docs/review.md.
Документация
Раскладка задана каноном av-dev; проверяет её docs.py check внутри task gate.
- docs/passport.md — зачем и для кого, чем не является.
- docs/architecture.md — обзор, эксплуатация, единые
точки, деплой. Поведения здесь нет — оно в
openspec/specs/. - docs/database.md — схема, представление данных, настройки с числовым значением.
- docs/security.md — периметр, недоверенный вход, что вне модели.
- docs/conventions/ — как пишем код.
- docs/research/ — наблюдения за чужими форматами.
- docs/adr/ — журнал решений, неизменяемый.
- docs/review.md — настройка конвейера ревью и журнал дефектов.
- docs/tasks/ — задачи и цели: одна запись = один файл
в
items/+ строка в индексе. Ведётся скилломav-dev-pm:tasks, ритуал спринта —av-dev-pm:session.
Tududi (проект jellybit, project_id 14) — только инбокс сырых идей. Идея
становится задачей, когда её оформляют файлом в docs/tasks/items/.
Конвенции кода
- Раскладка:
cmd/jellybit(точка входа) +internal/<пакет>по компонентам из docs/architecture.md. - Механизируемое проверяет
task gate(.golangci.yml+internal/archrules): форма ошибок и логов, конфиг мимо env, время мимоstore.Now(),AUTOINCREMENTв миграциях, направление зависимостей ядро↔транспорты. Перечень с местом механизации — docs/conventions/README.md; пересказывать эти правила прозой не нужно. - Прозой остаётся только то, что правилом не выражается, и читается в источнике: ошибки, логи, конфиг, БД, веб-UI.
- Миграции БД (goose,
internal/store/migrations; SQL для DDL, Go — когда нужен код): при изменении структуры в том же change обновляем ER-схему в docs/database.md — иначе краснеет шагcanonгейта.
Язык
- Документация, комментарии, сообщения коммитов — русский.
- Код и идентификаторы — английский.