Беклог теперь ведётся пользовательским скиллом `backlog` (заведение из диалога, разбор находок ревью, груминг, приоритизация, декомпозиция, штурм). CLAUDE.md делегирует ему формат и держит только проектные тонкости; добавлены источники задач (диалог, Tududi, находки ревью) и кладбище `docs/backlog/CLOSED.md` для выкинутого без реализации. - task-pipeline: шаги 1 и 9 больше не описывают формат сами, ссылаются на скилл; шаг 9 гоняет `backlog.py check` после удаления файла задачи. - review-pipeline: отложенная реальная находка (не для текущего мерджа) заводится задачей через скилл с тегом партии review-ГГГГ-ММ-ДД. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 KiB
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 на предложении
(после 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/drafts/— черновики: планы, идеи, ещё не принятые решения. Не источник истины.
Задачи и беклог
- Единственный источник беклога — каталог docs/backlog/:
одна задача = один markdown-файл (
docs/backlog/<slug>.md) + строка в индексе README.md. Приоритеты: высокий/средний/низкий. Работа с беклогом (заведение из диалога, разбор находок ревью/аудита, груминг, приоритизация, декомпозиция, штурм идеи) — через скиллbacklog; формат файла, слага, индекса и кладбища он и держит, здесь не дублируем. - Источники задач: диалог, инбокс 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 (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— детерминированный гейт ревью (build/vet/lint/test/race/покрытие изменённых строк/миграции/секреты); блокирует опиниативные проходы ревьюtask review:context— карта проекта для архитектурного прохода ревьюtask tidy—go mod tidytask 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. - Механизируемое проверяет
task gate(.golangci.yml+internal/archrules): форма ошибок и логов, конфиг мимо env, время мимоstore.Now(), AUTOINCREMENT в миграциях, направление зависимостей ядро↔транспорты. Пересказывать эти правила не нужно — гейт скажет точнее. - Прозой остаётся то, что правилом не выражается, и читается в источнике:
ошибки (трансляция доменной ошибки на внешней
границе, sentinel против типизированной),
логи (уровень по адресату, единственный
логирующий чокпоинт,
ext.*, что не логируем), конфиг (секреты рендерит деплой в файл0600, самодокументируемыйconfig.example.toml, валидация на старте), БД (время в UTC RFC 3339, TEXT ULID черезinternal/ident,ident.Parseна входной границе). - Миграции БД (goose,
internal/store/migrations; SQL для DDL, Go — когда нужен код) — при изменении структуры (таблица/столбец/индекс/связь) в том же change обновляем ER-схему docs/specs/database.md. - Веб-UI на htmx — единый партиал = страница = фрагмент, ветвление по
isHTMX, деградация без JS, ошибка на htmx-пути = 200 + фрагмент, самозавершающийся поллинг: docs/conventions/web-ui.md.
Кросс-каттинг конвенции (как пишем код, а не что система делает) живут в
docs/conventions/ и не переносятся в OpenSpec.
Механизируемое там не держим: правило уезжает в .golangci.yml или в
internal/archrules и вычёркивается из прозы и из промптов ревью — процедура в
references/promote.md.
Прозой остаётся только то, что правилом не выражается.