37 KiB
Беклог
Единый список будущих задач по проекту: то, что уже решили сделать, и
идеи, которые ещё надо обдумать. Это не план реализации (он — в
drafts/roadmap.md) и не источник истины: принятое и
реализованное переезжает в docs/specs/docs/adr.
Приоритет — грубая оценка «ценность / стоимость», не обязательство к порядку. Спекулятивные пункты (ещё без решения «делаем») помечены (идея) — их сперва надо проработать.
Высокий
Проблема второго сезона
Если первый сезон сериала уже разложен, а мы добавляем второй/третий/…,
распознавание должно привязать новый сезон к тому же названию и папке,
а не завести рядом почти одинаковую вторую папку. Ключ — стабильный
provider_id: один и тот же [tvdbid-…] → одна папка сериала, новые
Season NN доливаются внутрь. Нужно: при матче учитывать уже существующие
в библиотеке сериалы (или прошлые распознавания с тем же провайдер-id) и
склонять LLM/выбор кандидата к согласованности с ними.
Связано: recognition.md (модель уверенности, матч в базе), jellyfin-layout.md (папка сериала с провайдер-id).
Удаление средствами jellybit («единое окно», path 2)
Распознавание ручного удаления (источник из qBittorrent / цель из
Jellyfin) и пометка рассинхрона уже сделаны: фоновая сверка по матрице
«источник × цель» → состояния target_missing/orphaned/deleted,
безопасный undo (не снимает последнюю копию, nlink <= 1), синхронный
preflight перед действиями. См. openspec/specs/state-reconciliation/,
workflow.md → «Сверка с реальностью».
Осталось (path 2) — продолжение «единого окна»: удалять просмотренное из самого jellybit, не идя руками в qBittorrent/Jellyfin. Нужно продумать: команду удаления (снять наши хардлинки + опц. удалить раздачу из qBittorrent с файлами), подтверждение осознанности (а не случайный клик) и как это сочетается с инвариантом «источник неприкосновенен», когда пользователь сам просит убрать источник.
Связано: ADR-2026-06-13-hardlinks, architecture.md → «Раскладка файлов», workflow.md.
Ревью: выбор источника совпадения и предпросмотр
Переработать страницу ревью так, чтобы показывать все совпавшие результаты по метабазам списком и дать выбрать из них. Принцип: совпадение есть всегда — мы лишь выбираем источник. Поэтому матч нейронки — это отдельная строка в том же списке (наравне с кандидатами TMDB/TVDB), а не особый режим.
Возможности экрана:
- список кандидатов из баз + строка «распознано нейронкой»;
- выбрать один кандидат, переключиться на другой, отменить матч с базой в пользу нейронки;
- добавить кандидат вручную (по id/url базы), когда автопоиск промахнулся;
- при выборе/переключении — предпросмотр полей (название, режиссёр, год) и предпросмотр раскладки (целевые пути) до применения.
Развивает «показывать матч с записью метабазы» (web-сторона), пересекается с «Расширенной информацией о загрузке» (детальный экран) и быстрым выбором в Telegram. Веб остаётся точкой точных правок.
Связано: review-ux.md (выбор кандидата, «без базы»,
переключатель типа), recognition.md (кандидаты
матча, провайдер-id), «Улучшения UI: показывать матч»,
пакет httpapi.
Наблюдаемость: метрики и учёт стоимости LLM
Сейчас единственное окно в систему — slog. Нет быстрых ответов на
вопросы «сколько задач висит в review», «сколько токенов и денег съело
распознавание», «какова медиана времени ingest → done». Нужны метрики:
эндпоинт /metrics (Prometheus-формат) со счётчиками загрузок по
состояниям, длительностями стадий и расходом LLM (токены/стоимость на
задачу).
Расход LLM уже снимается с провода — llm.openai парсит usage
(prompt/completion/total tokens и cost, который отдаёт шлюз) в
llm.Response.Usage и пишет в лог. Не хватает только персистентности и
отображения: сохранять usage у попытки распознавания (recognition) и
показывать в карточке загрузки + агрегатом в /metrics. Где провайдер не
шлёт cost — считать из токенов по таблице «модель → цена» в конфиге.
Отдельный LLM-прокси (LiteLLM и т.п.) для этого не нужен и противоречит
принципам «один бинарь» / «минимум компонентов»: подсчёт токенов уже в коде,
а роль мульти-модельного шлюза играет используемый OpenAI-совместимый
эндпоинт (он и возвращает cost); разные модели подключаются сменой
[llm].model или новым типом провайдера за интерфейсом llm.Provider.
Связано: architecture.md → «Логирование»,
recognition.md (провайдер LLM, [llm].type),
пакеты worker, llm, httpapi.
Ретеншн и очистка БД
Терминальные задачи (done/cancelled/failed/reverted), их попытки
recognition с сырыми ответами LLM и metadata_candidate копятся вечно —
со временем БД и список загрузок распухают и становятся нечитаемыми. Нужна
авточистка старше N дней (с настройкой в [storage] или [worker]) и/или
ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере
эксплуатации.
Связано: architecture.md → «Хранилище» (таблицы
download/recognition/metadata_candidate/file_link), пакет store.
Eval-харнес распознавания (корпус кейсов + метрика точности)
Распознавание — ядро продукта, но смена модели или правка промпта сейчас
вслепую: регрессий не видно. Нужен корпус размеченных кейсов (русские
релизы, аниме, сезон-паки, репаки, спецвыпуски) и прогон распознавания по
нему с метрикой точности (тип/название/год/нумерация). Тогда можно
сравнивать LLM-провайдеры и версии промпта по числам, а не на ощупь.
Прогон — отдельной командой (jellybit eval или тестом), на фикстурах, без
реального qBittorrent.
Связано: recognition.md (конвейер, модель
уверенности), пакет recognize.
НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)
Сейчас потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в нефункциональные требования целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: она задаёт рамку для решений ниже по списку. Отдельно (уже дороже) — аудит узких мест под эту цифру: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве.
Связано: architecture.md → «Отслеживание
загрузки»/«Хранилище», пакеты worker, store, qbt, llm.
Средний
Словарь единого языка (ubiquitous language)
Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом, и в диалоге с агентом приходится каждый раз сверять понятия. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований (см. «Агенты-ревьюверы качества»).
Связано: docs/conventions (кросс-каттинг), architecture.md (домен), новый файл-глоссарий.
Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль)
Набор узких сабагентов-ревьюверов поверх ревью-процесса из CLAUDE.md,
каждый со своей оптикой: соответствие наименований словарю единого языка,
соблюдение архитектурных границ (единое ядро/тонкие транспорты, инварианты
безопасности данных), конвенций (ошибки, логирование, конфиг, TZ), стиля
кода на высоком уровне и поиск дублирования. Запускаются как чекпоинт перед
archive/коммитом. Развивает ревью-процесс OpenSpec в сторону
воспроизводимых автоматических проверок, не заменяя человеческое ревью.
Связано: CLAUDE.md (ревью-процесс, конвенции),
docs/conventions,
«Словарь единого языка».
Автогенерируемый идентификатор загрузки (ULID/UUID)
Сейчас загрузка фактически идентифицируется хешем торрента (infohash). Это
хрупко: у одной логической загрузки может быть несколько хешей
(перезаливы, докачивание, репаки, v1/v2 infohash), и привязка домена к хешу
мешает слиянию и истории. Ввести собственный стабильный идентификатор
(ULID/UUID), генерируемый при приёме, как первичный ключ домена;
infohash(ы) — отдельный атрибут/таблица «многие к одному», по которому
остаётся поиск и дедуп для обратной совместимости. Enabler для
«докачивания», «второго сезона», «версий/качества» и истории переходов.
Связано: database.md (PK download, infohash),
«Раздачи с докачиванием»,
architecture.md → «Идентификация торрента», пакет
store.
История переходов загрузки
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, а разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки.
Связано: «Расширенная информация о загрузке»
(лог переходов), workflow.md (граф состояний),
«Наблюдаемость: метрики»
(длительности стадий), database.md, пакеты worker,
store.
Главная: список загрузок вместо таблицы
Переделать главную страницу из таблицы в список карточек. Для каждой
загрузки: название (как распознали при добавлении в qBittorrent), infohash
с кнопкой быстрого копирования, статус и кнопки действий. Контекст (исходное
сообщение/magnet) спрятать под спойлер или вынести на отдельный детальный
экран (см. «Расширенная информация о загрузке»). Естественно сочетается с
фильтром/поиском/пагинацией по мере роста БД.
Связано: «Расширенная информация о загрузке в web-UI»,
«Список загрузок: фильтр, поиск, пагинация»,
review-ux.md, пакет httpapi.
Расширенная информация о загрузке в web-UI
Отдельная страница просмотра одной загрузки — целиком отображение того, что лежит в БД по задаче: актуальный статус (текущее состояние + лог переходов), исходный контекст и magnet, распознанные данные и матч в метабазе (см. «показывать матч»), а также точная раскладка, если есть — целевые пути и созданные хардлинки. Помогает разбираться, когда что-то пошло не так, без чтения логов сервера. Сюда же выносится контекст с карточки в списке загрузок.
Связано: «Главная: список загрузок»,
review-ux.md, architecture.md
→ «Хранилище» (download/recognition/file_link), пакет httpapi.
Машина состояний на go-библиотеке
Сейчас FSM реализована вручную в worker. Выбрать подходящую go-библиотеку
для описания воркфлоу/машины состояний и перевести переходы на неё — ради
декларативности, проверяемости переходов и единого места правды. Кандидаты
для оценки: looplab/fsm, qmuntal/stateless (и аналоги). Граф и переходы
уже формализованы — переносим один в один.
Связано: workflow.md (текущий граф состояний).
Привязка уведомлений к источнику в ботах (мульти-бот)
Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему.
Связано: review-ux.md (разделение труда транспортов, веб = точные правки), architecture.md → «Транспорты».
Раздачи с докачиванием (слияние при повторном добавлении)
Свежий сериал часто раздают по мере выхода: торрент содержит 5 эпизодов из 10. Позже его перезаливают целиком (или добавляют недостающие серии), и пользователь повторно добавляет тот же торрент. Нужно распознать, что это та же раздача/сезон, и повторить раскладку с слиянием: доложить недостающие хардлинки, не дублируя уже разложенное и не перезаписывая существующее (инвариант «существующее не трогаем»). Перекликается с «Проблемой второго сезона», но здесь доливаются эпизоды внутри одного сезона, а не новый сезон. Нужно продумать: как опознать повторное добавление (хеш торрента / провайдер-id + сезон), как сверять состав файлов и доливать только новые.
Связано: «Проблема второго сезона», jellyfin-layout.md (раскладка, идемпотентность), workflow.md (повторный прогон загрузки).
Улучшения UI: показывать матч с записью метабазы
Во всех транспортах (веб, Telegram) показывать, с какой именно записью метабазы (TMDB/TVDB) сматчилась загрузка: название, год, провайдер-id, ссылку. Сейчас результат распознавания непрозрачен — пользователь не видит, к чему привязались, и не может быстро поймать ошибочный матч. На web-стороне развивается в полноценный выбор источника — см. «Ревью: выбор источника совпадения».
Связано: review-ux.md, recognition.md (матч в базе), architecture.md → «Транспорты».
Аниме с абсолютной нумерацией
Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а
Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию —
надёжнее всего через TVDB (там есть absolute order). Отдельный крайний
случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
Связано: recognition.md (конвейер, сезон-паки), jellyfin-layout.md (нумерация серий), review-ux.md (крайние сценарии).
Добавление торрентов файлом/ссылкой — «единое окно»
Поддержать источники помимо magnet: .torrent-файл и URL (отдаём их в
qBittorrent, без исходящих запросов на пользовательский URL — SSRF
исключён). Идеал — одно поле «единого окна»: кидаем туда текст или файл, а
сервис сам разбирает, что это (magnet / ссылка / .torrent / сообщение
бота), и заводит загрузку.
Связано: architecture.md → «Транспорты»
(source_type = magnet|torrent|url уже в схеме), пакет ingest (сейчас
поддержан только magnet).
Бэкап SQLite
architecture.md требует «бекапить data-том», но как — не описано. Без
понятной стратегии сбой или редеплой стирают всё in-flight состояние.
Зафиксировать решение и реализовать: периодический VACUUM INTO в
/data/backups по расписанию (с ротацией) либо потоковая репликация
(litestream). Лучше сделать, пока БД маленькая.
Связано: architecture.md → «Деплой» (data-том,
«бекапить-и-не-терять»), пакет store.
Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)
Фильм уже разложен, позже добавили раздачу лучшего качества — сейчас это просто новая задача, упирающаяся в «коллизию цели → review», без понятия «это та же вещь, заменить версию». Нужно осознанно обработать апгрейд качества: распознать тот же тайтл, предложить замену существующей раскладки либо сосуществование версий (Jellyfin поддерживает несколько версий одного фильма). Близко к «докачиванию», но про качество, а не про эпизоды.
Связано: «Раздачи с докачиванием», jellyfin-layout.md (never-overwrite, коллизия), architecture.md → «Идентификация торрента» (репаки = разные infohash → разные задачи).
Глубокий healthcheck и статус зависимостей
/healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза
недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка
ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent
недоступен»), чтобы причина простоя была видна сразу.
Связано: architecture.md → «Деплой» (healthcheck),
пакеты qbt, llm, metadata, httpapi.
Обучение на правках человека (few-shot из прошлых ревью)
Когда человек поправил матч, тип или нумерацию — сохранять это как пример и
подмешивать похожие в будущие промпты. Системно повышает точность на «твоих»
трекерах и форматах имён без смены модели. Развитие идеи многоступенчатой
верификации, но дешевле: учимся на уже собранных hint/override.
Связано: recognition.md (конвейер, промпт),
«Многоступенчатая верификация»,
architecture.md → «Хранилище» (hint, override).
Список загрузок: фильтр, поиск, пагинация
Прямое следствие роста БД (см. «Ретеншн»): плоский список загрузок со временем становится непригоден. Нужны фильтр по состоянию, поиск по названию и пагинация. Естественно ложится на список-карточки главной и на экран расширенной информации.
Частный случай как разумный дефолт: на главной по умолчанию скрывать
загрузки в статусе deleted (терминальные, удалённые из источника и
цели — шум в ленте), с переключателем/фильтром «показать всё». Маленькая
часть, может приехать раньше полноценного фильтра.
Связано: «Главная: список загрузок»,
«Расширенная информация о загрузке в web-UI»,
пакет httpapi.
Низкий
Мгновенные обновления через SSE
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда.
Связано: architecture.md → «Транспорты»,
review-ux.md, пакет httpapi.
Полировка веб-UI: список и карточка загрузки
Мелкие правки вёрстки и согласованности (низкая цена, заметный эффект):
- в списке на главной показывать имя загрузки ровно то, что отдаём в qBittorrent как имя раздачи, — чтобы список и клиент были синхронны (уточняет «Главная: список загрузок»);
- в карточке блок «Распознано как» растянуть на всю ширину блока (сейчас обрывается на середине);
- в блоке «Файлы и раскладка» приклеить стрелку к первой строке (файл-источник), чтобы источник и цель выводились строго один под другим.
Связано: «Главная: список загрузок»,
review-ux.md, пакет httpapi.
Многоступенчатая верификация привязки (идея)
Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Требует проработки: когда включать, как мерджить расхождения, стоимость/латентность.
Связано: recognition.md (конвейер и модель уверенности).
Выбор из нескольких находок метабазы в Telegram
Когда распознавание даёт несколько подходящих кандидатов в метабазе, предлагать их в Telegram списком (кнопки) для ручного выбора, а не молча брать первый/лучший. Веб остаётся точкой точных правок (полный выбор источника — см. «Ревью: выбор источника совпадения»), бот — быстрый выбор из готового короткого списка.
Связано: review-ux.md (боты — быстрые действия, веб — точные правки), recognition.md (кандидаты матча).
Проверка свободного места перед copy-fallback
Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл,
дублируя место на диске. На забитом диске это упрётся в полку посреди
раскладки. Перед копированием проверять доступное место и при нехватке
внятно уходить в failed с понятной причиной, а не падать на полпути.
Связано: architecture.md → «Раскладка файлов»
(фолбэк-копирование), пакет layout.
Кэш метабаз (и опционально LLM)
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками).
Связано: recognition.md (сверка с базой), пакеты
metadata, llm.
guessit как сервис-спутник (идея)
go-ptn слабее питоновского guessit. Если точности пред-парса не
хватит — завернуть guessit в крошечный HTTP-сервис (один файл,
поставляется рядом с бинарём jellybit) и спрашивать его на шаге
пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
Связано: recognition.md → «На будущее» (пред-парс).
Завершение загрузки через webhook (идея)
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent. Решим по опыту эксплуатации.
Связано: architecture.md → «Отслеживание загрузки»,
пакет worker.
Авторизация веб-UI (на будущее)
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей
(http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита:
токен/Basic в самом приложении или вынос за reverse-proxy с
аутентификацией.
Связано: architecture.md → «Транспорты» (доступ к
веб-UI), пакет httpapi.
Современный Web-UI как PWA
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы.
Связано: review-ux.md (веб = точные правки),
пакет httpapi.