Tududi оказался неудобен для ведения беклога проекта — переходим на файлы в репозитории. Каждая задача — отдельный markdown в docs/backlog/ (48 файлов), плюс индекс README.md со списком по приоритетам и хуками. Тело файла хранит исходное описание (контекст, решения, ссылки на спеки/ADR/черновики). CLAUDE.md: источник истины по беклогу теперь docs/backlog/; Tududi понижен до инбокса сырых идей. Живые ссылки в спеках (recognition, architecture, review-ux, jellyfin-layout) «в беклоге (Tududi)» переписаны на прямые ссылки на файлы беклога. Перенесённые задачи удалены из Tududi; завершённые оставлены как история. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 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/specs, ДО кода; ревью кода после apply, до archive); тривиальная — одного прохода по коду достаточно.
Миграция: 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 со списком по приоритетам (высокий/средний/низкий) и хуками. В теле файла — контекст, принятые решения, шаги и ссылки на спеки/ADR/черновики. Заводи задачу новым файлом и строкой в индексе; закрытую (реализованную) — удаляй, суть переезжает вdocs/specs/docs/adr. - Спекулятивные задачи (ещё без решения «делаем») помечены префиксом
[идея]в названии — их сперва прорабатываем. - Tududi — только инбокс сырых идей (проект
jellybit, project_id 14). Беклог там больше не ведём; идея из Tududi становится задачей, когда её оформляют файлом вdocs/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 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. - Ошибки — 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. - Время — храним в UTC, RFC 3339 с суффиксом
Z; генерирует только приложение (store.Now()), таймзона отображения — конфиг[general].timezone: docs/conventions/database.md. - Идентификаторы — 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. - Веб-UI на htmx — единый партиал = страница = фрагмент, ветвление по
isHTMX, деградация без JS, ошибка на htmx-пути = 200 + фрагмент, самозавершающийся поллинг: docs/conventions/web-ui.md.
Кросс-каттинг конвенции (как пишем код, а не что система делает) живут в docs/conventions/ и не переносятся в OpenSpec.