Files
jellybit/CLAUDE.md
T
avandClaude Fable 5 37f2f6481a Идентичность на ULID: download_infohash, guarded-дедуп, миграция (ulid-identity)
Все сущности переехали с INTEGER AUTOINCREMENT на TEXT ULID (lowercase,
internal/ident — единая точка генерации и разбора; oklog/ulid). Инфохэши
загрузки — множество (download_infohash, v1/v2 гибридных торрентов): дедуп
и сопоставление в поллинге по любому из хешей, magnet-парсер отдаёт оба
хеша гибридной ссылки, усечённый v2-хеш v2-only раздач не хранится.

Инвариант «не более одной активной загрузки на infohash» вместо снятого
unique-индекса держат guarded-методы store в одной write-транзакции
(_txlock=immediate): CreateDownloadIfNoActive (приём/adopt, с доносом
недостающих хешей), ActivateIfNoOtherActive (retry/recovery/relink, отказ
до побочных эффектов), guarded AddInfohashes; SetDownloadState отклоняет
терминал→активное как механический бэкстоп.

Миграция 0006 — первая Go-миграция goose: пересоздание таблиц при
включённых FK, backfill ULID с timestamp из created_at (хронология id
сохранена), разнос infohash, удаление idempotency_key. BREAKING: формат id
в URL/логах/Telegram, REST-поля id (string) и infohashes (список).

Новая конвенция docs/conventions/database.md (без числовых PK), корреляция
в логах grep'ом по голому ULID, ER-схема обновлена. Спеки: новая capability
identity, MODIFIED в state-reconciliation; change заархивирован. Пройдены
ревью дизайна и кода (по 8 углов), все находки исправлены с
регрессионными тестами.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:25:00 +03:00

9.1 KiB
Raw Blame History

CLAUDE.md

Памятка для работы над jellybit. Перед задачей прочитай также README.md, BRIEF.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.
  • 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.
  • Запуск: контейнер под 1000:1000, в общей docker-сети (адресация по именам), mount /srv/media (единая песочница) + data-том для SQLite/конфига.

Spec Driven Development (OpenSpec)

Изменения ведём через OpenSpec (CLI openspec, v1.x). Сначала спецификация — потом код.

  • openspec/specs/<capability>/spec.mdактуальные capability-спеки: что система делает сейчас. 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 — язык и правила оформления спек (читай перед написанием).

Поток работы — через слэш-команды 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/specs, ДО кода; ревью кода после apply, до archive); тривиальная — одного прохода по коду достаточно.

Миграция: capabilities постепенно переносятся из docs/specs/ в OpenSpec (пилот — ingest). До переноса источник истины по теме — соответствующий файл в docs/specs/; перенесённое живёт в openspec/specs/.

Прочая документация

  • docs/specs/живые спецификации целевого состояния (архитектурный обзор + ещё не перенесённые в OpenSpec темы). Меняем по мере развития, держим в соответствии с кодом.
  • docs/adr/неизменяемый журнал решений, пишется постфактум, хранит почему. Правила — docs/adr/README.md.
  • docs/drafts/ — черновики: планы, идеи, ещё не принятые решения. Не источник истины.

Язык

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

Команды

Запуск через 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 tidygo 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.
  • Ошибки — stdlib, обёртка с контекстом (fmt.Errorf("...: %w", err)), проверка через errors.Is/errors.As, трансляция на внешней границе: docs/conventions/errors.md.
  • Логирование только через slog, без fmt.Println — уровни, обязательные поля и что не логировать см. docs/conventions/logging.md.
  • Конфигурация — только TOML; секреты рендерит деплой (Ansible+Vault) в файл (config.toml не коммитится, 0600), не в env; валидация на старте: docs/conventions/config.md.
  • Время — всегда с явным TZ (сервер в Europe/Moscow).
  • Идентификаторы — TEXT ULID (lowercase) через internal/ident, без числовых AUTOINCREMENT; внешние id валидируются ident.Parse на границе: docs/conventions/database.md.
  • Миграции БД (goose, internal/store/migrations; SQL для DDL, Go — когда нужен код) — при изменении структуры (таблица/столбец/индекс/связь) в том же change обновляем ER-схему docs/specs/database.md.

Кросс-каттинг конвенции (как пишем код, а не что система делает) живут в docs/conventions/ и не переносятся в OpenSpec.