Files
jellybit/CLAUDE.md
T
av 42d5b73a04 docs: перевод документации на канон av-dev
- Раскладка 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.
2026-08-04 09:27:26 +03:00

15 KiB
Raw Blame History

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-lint
  • task gate — детерминированный гейт ревью (см. ниже)
  • task review:context — карта проекта для архитектурного прохода ревью
  • task tidygo mod tidy
  • task image — docker-образ из готового бинаря

Гейт

  • Команда целиком: task gate (BASE=<rev> задаёт базу диффа). Без BASE база — git merge-base HEAD master, а на самом masterHEAD~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 гейта.

Язык

  • Документация, комментарии, сообщения коммитов — русский.
  • Код и идентификаторы — английский.