# CLAUDE.md Памятка для работы над jellybit. Перед задачей прочитай также [README.md](README.md), [BRIEF.md](BRIEF.md) и [docs/specs/architecture.md](docs/specs/architecture.md). Разработка идёт по **Spec Driven Development** через OpenSpec — см. раздел ниже. ## Что это Связующий сервис qBittorrent ↔ Jellyfin: принимает торрент + контекст, качает, распознаёт фильм/сериал (LLM + контекст + опц. метабазы) и раскладывает файлы для Jellyfin хардлинками. Деплоится на домашний медиа-сервер umbar (`/home/av/projects/private/umbar`) — туда копируется готовый бинарь. ## Стек и принципы - **Go**, один статический бинарь (`CGO_ENABLED=0`). Почему — см. [ADR-2026-06-13-go-single-binary](docs/adr/ADR-2026-06-13-go-single-binary.md). - **SQLite** как хранилище (чистый Go-драйвер `modernc.org/sqlite`). - **Конфигурация — TOML**. **Логи — структурированный JSON** (`log/slog`). - **Хардлинки, источник не трогаем** — qBittorrent продолжает раздачу, диск не дублируется. - **Единое ядро, тонкие транспорты** — вся логика приёма в use-case `Ingest`; HTTP API, веб-UI и Telegram — лишь обёртки над ним. - **Минимум компонентов** — в духе umbar, без зоопарка сервисов. Внешние базы метаданных (TMDB/TVDB) опциональны, включаются конфигом. ## Инварианты (безопасность данных) - **Источник неприкосновенен:** только `mkdir` / `link(2)` / `unlink` своих ссылок; никогда не трогаем файлы под `paths.downloads`. - **Целевой путь санитизируется** и проверяется, что он строго под `paths.movies`/`series` (защита от traversal); существующее не перезаписываем. - **Выход LLM недоверенный** — безопасность на валидации пути, не на промпте. Авто-раскладка только при подтверждённом матче в базе. - **Секреты не попадают в логи** — пароли qBittorrent, API-ключи LLM/метабаз, auth-заголовки. Подробнее — [docs/conventions/logging.md](docs/conventions/logging.md). - **Запуск:** контейнер под `1000:1000`, в общей docker-сети (адресация по именам), mount `/srv/media` (единая песочница) + data-том для SQLite/конфига. ## Spec Driven Development (OpenSpec) Изменения ведём через [OpenSpec](https://github.com/Fission-AI/OpenSpec) (CLI `openspec`, v1.x). Сначала спецификация — потом код. - `openspec/specs//spec.md` — **актуальные** capability-спеки: что система делает сейчас. Capability — это поведение/домен (`ingest`, `recognition`, `file-layout`, `review`, `notifications`), а не пакет кода. - `openspec/changes//` — предлагаемое изменение: `proposal.md` (зачем и что), `design.md` (как, для нетривиальных), дельта-спеки (`ADDED`/ `MODIFIED`/`REMOVED Requirements`), `tasks.md` (шаги). После реализации change архивируется в `openspec/changes/archive/`, дельты вливаются в `openspec/specs/`. - `openspec/config.yaml` — язык и правила оформления спек (читай перед написанием). Поток работы — через слэш-команды `opsx:*` (канонический набор, его поддерживает `openspec update`): `opsx:explore` (продумать), `opsx:propose` (завести change), `opsx:apply` (реализовать tasks), `opsx:sync`/`opsx:archive` (влить и архивировать). Skills `openspec-*` — то же, но предыдущего поколения; для новой работы используем `opsx:*`. Правила спек: - Каждое `### Requirement` ОБЯЗАНО содержать литерал `SHALL` или `MUST` — иначе `openspec validate` падает. - Структурные заголовки и ключевые слова — английские (`### Requirement:`, `#### Scenario:`, `GIVEN/WHEN/THEN`, RFC 2119), остальной текст — русский. - Сценарии — в формате `GIVEN/WHEN/THEN`. - `openspec validate --strict` перед коммитом change. Ревью (процесс, не артефакт): два чекпоинта — профиль `design` на предложении (после design/specs, ДО кода) и ревью изменения после apply, до archive. Оба идут через скилл `.claude/skills/review-pipeline`: детерминированный гейт (`task gate`) → сверка с дельта-спеками в обе стороны → generative-проходы → триаж с потолком 7 находок. Профиль (`quick`/`standard`/`deep`) выбирается по факту изменения, правило — в скилле. **Миграция:** capabilities постепенно переносятся из `docs/specs/` в OpenSpec (пилот — `ingest`). До переноса источник истины по теме — соответствующий файл в `docs/specs/`; перенесённое живёт в `openspec/specs/`. ## Прочая документация - `docs/specs/` — **живые** спецификации целевого состояния (архитектурный обзор + ещё не перенесённые в OpenSpec темы). Меняем по мере развития, держим в соответствии с кодом. - `docs/adr/` — **неизменяемый** журнал решений, пишется постфактум, хранит *почему*. Правила — [docs/adr/README.md](docs/adr/README.md). - `docs/drafts/` — черновики: планы, идеи, ещё не принятые решения. Не источник истины. ## Задачи и беклог - **Единственный источник беклога — каталог [docs/backlog/](docs/backlog/README.md)**: одна задача = один markdown-файл (`docs/backlog/.md`) + строка в индексе [README.md](docs/backlog/README.md). Приоритеты: высокий/средний/низкий. Работа с беклогом (заведение из диалога, разбор находок ревью/аудита, груминг, приоритизация, декомпозиция, штурм идеи) — через скилл **`backlog`**; формат файла, слага, индекса и кладбища он и держит, здесь не дублируем. Скилл поставляется плагином `av-dev-backlog` (маркетплейс `av-dev-skills`, включён в `.claude/settings.json`); вызов — `/av-dev-backlog:backlog`, свой скрипт `backlog.py` он зовёт сам — путь к нему в проекте не зашиваем. - **Источники задач:** диалог, инбокс Tududi и находки ревью. Отложенная находка `review-pipeline` (реальная, но не для текущего мерджа) заводится задачей через скилл `backlog` с тегом партии `review-ГГГГ-ММ-ДД` — так уже сделаны задачи `review-*` в беклоге. - **Проектные тонкости для скилла `backlog`:** - каталог беклога — `docs/backlog/`, язык — русский, слаги — латиница; - реализованная задача удаляется, её суть переезжает в `docs/specs`/`docs/adr` (это делает пайплайн задачи на шаге 9, не скилл беклога); - выкинутая без реализации уезжает строкой в `docs/backlog/CLOSED.md` (кладбище); - спекулятивные задачи помечены `[idea]` в заголовке (тип — английское ключевое слово idea/epic/task) — сперва штурм. - **Tududi — только инбокс сырых идей** (проект `jellybit`, project_id 14). Беклог там больше не ведём; идея из Tududi становится задачей, когда её оформляют файлом в `docs/backlog/` через скилл `backlog`. Прежняя единая `docs/backlog.md` доступна в истории git. ## Язык - Документация, комментарии, сообщения коммитов — **русский**. - Код и идентификаторы — английский. ## Команды Запуск через [Task](https://taskfile.dev) (`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-lint - `task gate` — детерминированный гейт ревью (build/vet/lint/test/race/покрытие изменённых строк/миграции/секреты); блокирует опиниативные проходы ревью - `task review:context` — карта проекта для архитектурного прохода ревью - `task tidy` — `go mod tidy` - `task image` — docker-образ из готового бинаря Module path — `git.vakhrushev.me/av/jellybit`. Go 1.26, `CGO_ENABLED=0`. Стек: `chi`, `sqlx` + `modernc.org/sqlite`, `goose` (миграции), `pelletier/go-toml/v2`, `log/slog`. ## Конвенции кода - Раскладка: `cmd/jellybit` (точка входа) + `internal/<пакет>` по компонентам из [architecture.md](docs/specs/architecture.md). - Механизируемое проверяет `task gate` (`.golangci.yml` + `internal/archrules`): форма ошибок и логов, конфиг мимо env, время мимо `store.Now()`, AUTOINCREMENT в миграциях, направление зависимостей ядро↔транспорты. Пересказывать эти правила не нужно — гейт скажет точнее. - Прозой остаётся то, что правилом не выражается, и читается в источнике: [ошибки](docs/conventions/errors.md) (трансляция доменной ошибки на внешней границе, sentinel против типизированной), [логи](docs/conventions/logging.md) (уровень по адресату, единственный логирующий чокпоинт, `ext.*`, что не логируем), [конфиг](docs/conventions/config.md) (секреты рендерит деплой в файл `0600`, самодокументируемый `config.example.toml`, валидация на старте), [БД](docs/conventions/database.md) (время в UTC RFC 3339, TEXT ULID через `internal/ident`, `ident.Parse` на входной границе). - Миграции БД (goose, `internal/store/migrations`; SQL для DDL, Go — когда нужен код) — при изменении структуры (таблица/столбец/индекс/связь) в том же change обновляем ER-схему [docs/specs/database.md](docs/specs/database.md). - Веб-UI на htmx — единый партиал = страница = фрагмент, ветвление по `isHTMX`, деградация без JS, ошибка на htmx-пути = 200 + фрагмент, самозавершающийся поллинг: [docs/conventions/web-ui.md](docs/conventions/web-ui.md). Кросс-каттинг конвенции (как пишем код, а не что система делает) живут в [docs/conventions/](docs/conventions/README.md) и не переносятся в OpenSpec. Механизируемое там **не держим**: правило уезжает в `.golangci.yml` или в `internal/archrules` и вычёркивается из прозы и из промптов ревью — процедура в [references/promote.md](.claude/skills/review-pipeline/references/promote.md). Прозой остаётся только то, что правилом не выражается.