diff --git a/CLAUDE.md b/CLAUDE.md index c401a11..80cf6d0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,8 +106,12 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module - **Что красит безусловно:** сборка, `go vet`, `golangci-lint`, `gofmt`, тесты, флаки-прогон (второй прогон разошёлся с первым), `-race`, накат миграций с нуля, `gitleaks`, канон документации (`docs.py check` — раскладка `docs/`, - битые ссылки, «миграция изменена, а `database.md` нет»). Причина одна: у - каждого из них есть объективный оракул, спорить не о чем. + битые ссылки, «миграция изменена, а `database.md` нет»), каталог задач + (`tasks.py check --dir tasks` — согласованность `tasks/BACKLOG.md` и + `tasks/items/`), форма конфига OpenSpec (`openspec.py check` — незаменённый + пример в `openspec/config.yaml`). Причина одна: у каждого из них есть + объективный оракул, спорить не о чем. Каждый из трёх последних краснеет и + когда своего скрипта нет: молча пропущенная проверка неотличима от пройденной. - **Чего в гейте намеренно нет и кто обязан это гонять:** - `govulncheck` даёт `WARN`, а не `FAIL`: находка тут — состояние зависимостей, а не диффа. Разбирает агент ревью по трассам вызовов. diff --git a/README.md b/README.md index 73333d8..2284817 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,10 @@ Arr-стек (prowlarr/radarr/sonarr) плохо ложится на русск ## Статус -Рабочий прототип с полным сквозным путём: приём magnet → загрузка в -qBittorrent → распознавание (LLM + опционально базы метаданных -TMDB/TVDB/TVMaze) → раскладка в библиотеку хардлинками, автоматически при -уверенном результате либо через подтверждение человеком. Транспорты приёма: -REST API, веб-UI, Telegram-бот и CLI (`jellybit add`). - -Из источников поддержаны magnet и `.torrent`-файл; фетч `.torrent` по обычной -ссылке — в планах. Что дальше — [tasks/ROADMAP.md](tasks/ROADMAP.md). +Рабочий прототип: сквозной путь приём → загрузка → распознавание → раскладка +работает целиком, автоматически при уверенном результате либо через +подтверждение человеком. Что уже умеет и что дальше — +[tasks/ROADMAP.md](tasks/ROADMAP.md). ## Документация @@ -73,7 +69,8 @@ REST API, веб-UI, Telegram-бот и CLI (`jellybit add`). Go (один статический бинарь), SQLite (`modernc.org/sqlite` + `sqlx`, миграции `goose`), HTTP — `chi` + `html/template` + htmx, конфигурация — -TOML, логи — структурированный JSON (`slog`). Подробнее — в +TOML, логи — структурированный JSON (`slog`). Полный перечень с версиями — +[CLAUDE.md](CLAUDE.md) → «Стек»; как эти компоненты сложены — [docs/architecture.md](docs/architecture.md). ## Конфигурация @@ -118,16 +115,12 @@ jellybit recognize --dry-run [--context "..."] --config ./config.toml ## Доставка -Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический -бинарь (`task build`) и `Dockerfile` (упаковка в `distroless/static`). Образ -собирается целиком **локально** на control-хосте (`task image`) и едет на -сервер через `docker save`/`load` (роль `app_image` в umbar), поэтому -Go-тулчейн и `docker build` на сервере не нужны. В distroless нет shell/curl, -поэтому HEALTHCHECK зовёт сам бинарь: `jellybit healthcheck` (GET `/healthz` -по порту из конфига, exit 0/1). - -Контейнер: `user 1000:1000`, порт `8080` на хост, mount `/srv/media` (единая -песочница для хардлинков) + том `/config` (ro, `config.toml`, восстановим при -деплое) + data-том `/data` (SQLite, бекапить); к qBittorrent — по сети Docker. +Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический бинарь +(`task build`) и `Dockerfile`; образ собирается целиком локально на +control-хосте (`task image`) и едет на сервер через `docker save`/`load`. Конкретная деплой-обвязка (плейбук, секреты) держится в отдельном приватном репозитории и в комплект не входит. + +Параметры запуска — сеть, пользователь, монтирования, healthcheck, — разделение +ответственности с umbar и единая песочница `/srv/media`: +[docs/architecture.md](docs/architecture.md) → «Деплой». diff --git a/docs/adr/README.md b/docs/adr/README.md index 4928734..efb233d 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -11,7 +11,7 @@ Верно одно из трёх: - + - **дорогой откат** — переделка стоит дороже переписывания одного файла; - **намеренный отказ** от очевидного подхода; - **пересмотр прежнего решения** — тогда у старой записи обязателен статус diff --git a/docs/architecture.md b/docs/architecture.md index e46a8cc..9638b8f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -48,8 +48,10 @@ | `archrules` | собственный анализатор архитектурных правил (часть гейта) | — | Транспорты приёма — HTTP API, веб-UI, Telegram и CLI (`jellybit add`) — ведут в -один `ingest`; действия пользователя (apply / refine / reject / defer / undo / -retry / delete / dismiss) идут командами к `worker`. +один `ingest`; действия пользователя идут командами к `worker`. Перечень команд +и их эффекты — нормативно в [review](../openspec/specs/review/spec.md), пути +закрытия и удаления — в +[state-reconciliation](../openspec/specs/state-reconciliation/spec.md). ## Внешние границы и форматы diff --git a/docs/database.md b/docs/database.md index f9e2043..d2898b4 100644 --- a/docs/database.md +++ b/docs/database.md @@ -214,4 +214,5 @@ erDiagram | `ingest.MaxTorrentSize` | `8 MiB` | предел размера принимаемого `.torrent`; проверяется **до** разбора, поэтому bencode-аллокации на эту величину не масштабируются (см. [research/torrent-bencode-limits.md](research/torrent-bencode-limits.md)) | **Ретеншена нет ни у одной таблицы**, лимита на размер тела ответа LLM нет, -кэша метабаз нет — всё три пункта в беклоге. +кэша метабаз нет; состояние по каждому пробелу и заведённые под них задачи — +[architecture.md](architecture.md) → «Открытые вопросы». diff --git a/docs/security.md b/docs/security.md index 18046bf..3127e21 100644 --- a/docs/security.md +++ b/docs/security.md @@ -101,8 +101,7 @@ REST API работают **без авторизации** осознанно; бесконечный ответ LLM — это вопросы устойчивости и ресурсов ([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности. Отсутствие лимита на размер ответа LLM — известный пробел - ([architecture.md](architecture.md) → «Открытые вопросы»); задачей он пока не - заведён. + ([architecture.md](architecture.md) → «Открытые вопросы»). - **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота. - **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга. - **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и diff --git a/openspec/specs/download-tracking/spec.md b/openspec/specs/download-tracking/spec.md index 57f5326..586a3df 100644 --- a/openspec/specs/download-tracking/spec.md +++ b/openspec/specs/download-tracking/spec.md @@ -10,7 +10,7 @@ qBittorrent и сопоставление его состояний (downloading ## Requirements ### Requirement: Поллинг qBittorrent и сопоставление состояний -Worker SHALL периодически (`worker.poll_interval`, дефолт 5 с) опрашивать +Worker SHALL периодически (`worker.poll_interval`) опрашивать qBittorrent и сопоставлять его состояния раздачи с состоянием загрузки в БД. Готовые к раскладке состояния (`uploading`/`stalledUP`/`pausedUP`/`stoppedUP`/ `queuedUP`/`forcedUP`, с учётом различий имён между qBit v4 и v5) SHALL переводить @@ -39,8 +39,8 @@ API после завершения переноса. ### Requirement: Таймауты-предохранители downloading -Система SHALL переводить `metaDL`/`forcedMetaDL` дольше `magnet_timeout` (дефолт -`24h`, редкий предохранитель) в `failed` (`error_code` `magnet_timeout`), а +Система SHALL переводить `metaDL`/`forcedMetaDL` дольше `magnet_timeout` +(редкий предохранитель) в `failed` (`error_code` `magnet_timeout`), а `stalledDL` дольше `stuck_after` — в `stuck` (`error_code` `stalled`). Возраст система SHALL считать от времени добавления в qBittorrent (`added_on`), а не от создания задачи, чтобы базис переживал retry и усыновление. diff --git a/tasks/BACKLOG.md b/tasks/BACKLOG.md index 12da4fb..1aba471 100644 --- a/tasks/BACKLOG.md +++ b/tasks/BACKLOG.md @@ -2,12 +2,14 @@ Что **можно взять**. Одна задача = один файл `items/.md` + строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что -берут, роадмап — то, подо что берут. Порядка внутри секции нет: «что делать -дальше» отвечает набор спринта. Ведётся скиллом `tasks`. +берут, роадмап — то, подо что берут. Ведётся скиллом `av-dev-tasks:tasks`. -Секции «блокеры» здесь нет и не заводится: блокер — это состояние -(спринт не может продолжаться ни одной задачей), оно живёт до ответа -человека, а его следы — вопросами в файлах задач. +**Порядок строк внутри секции — это приоритет.** Первая строка секции — то, что +делают следующим. Порядок назначает человек на груминге +(`av-dev-tasks:groom`), машина его не выводит. + +Секции «блокеры» здесь нет и не заводится: блокер — это состояние, оно живёт до +ответа человека, а его следы — вопросами в файлах задач. ## Ядро продукта