раскладка av-dev повышена с канона 12 до версии 5

- три плагина слились в один `av-dev`: служебные `docs/.docs.json` и
  `tasks/.tasks.json` заменены на `.av-dev.toml` в корне, в гейте переехали пути
  трёх скриптов, вызовы скиллов переименованы по всему репозиторию
- тип задачи `goal` и `ROADMAP.md` упразднены: семь целей закрыты с причинами,
  теги сняты, объявлена стадия `support`
- метка `small`/`medium`/`large` снята из процесса — вместо «Триггеров метки» в
  review.md подраздел «Когда звать глубокое ревью»; следом разобран урожай
  doc-consistency: девять фактов сведены к одному дому
This commit is contained in:
av
2026-09-02 09:55:28 +03:00
parent fc9a3b4066
commit 3bce73fc34
68 changed files with 178 additions and 327 deletions
+14
View File
@@ -0,0 +1,14 @@
# Раскладка av-dev в этом проекте: версия и настройки проверок.
# Версия одна на всю раскладку — и на документы канона, и на каталог задач;
# по ней скилл av-dev:canon решает, какие записи журнала применять.
version = 5
[docs]
migrations = "internal/store/migrations"
# Коммит, на котором в последний раз гоняли av-dev:doc-healthcheck (2026-09-02).
healthcheck_last = "fc9a3b406692620ba1503065c05166355b6387ab"
[tasks]
dir = "tasks"
stage = "support"
+1 -3
View File
@@ -2,9 +2,7 @@
"enabledPlugins": { "enabledPlugins": {
"frontend-design@claude-plugins-official": true, "frontend-design@claude-plugins-official": true,
"av-dev-git@av-dev-skills": true, "av-dev-git@av-dev-skills": true,
"av-dev-docs@av-dev-skills": true, "av-dev@av-dev-skills": true
"av-dev-tasks@av-dev-skills": true,
"av-dev-code@av-dev-skills": true
}, },
"extraKnownMarketplaces": { "extraKnownMarketplaces": {
"av-dev-skills": { "av-dev-skills": {
+1 -1
View File
@@ -4,7 +4,7 @@
# staticcheck, unused. Сверх него включены линтеры, которые механизируют # staticcheck, unused. Сверх него включены линтеры, которые механизируют
# конвенции из docs/conventions/*: то, что проверяет правило, не должно # конвенции из docs/conventions/*: то, что проверяет правило, не должно
# оставаться прозой в конвенциях и в промптах ревью (процедура промоута — # оставаться прозой в конвенциях и в промптах ревью (процедура промоута —
# references/promote.md скилла av-dev-pipeline:review-pipeline; перечень уже # references/promote.md скилла av-dev:code-review; перечень уже
# механизированного — docs/conventions/README.md). # механизированного — docs/conventions/README.md).
version: "2" version: "2"
+6 -5
View File
@@ -161,7 +161,7 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
другой работы: гейт один на все задачи. другой работы: гейт один на все задачи.
- **Приоритет — это порядок строк в [tasks/BACKLOG.md](tasks/BACKLOG.md).** - **Приоритет — это порядок строк в [tasks/BACKLOG.md](tasks/BACKLOG.md).**
Первая строка секции — то, что делают следующим. Порядок назначает человек на Первая строка секции — то, что делают следующим. Порядок назначает человек на
груминге (`av-dev-tasks:groom`), машина его не выводит. груминге (`av-dev:task-groom`), машина его не выводит.
- **Ориентир по размеру порции разбора на груминге:** 5–8 задач. Ориентир, а не - **Ориентир по размеру порции разбора на груминге:** 5–8 задач. Ориентир, а не
закон. закон.
- **Что такое «сделана»:** пайплайн задачи пройден целиком (спека → код → оба - **Что такое «сделана»:** пайплайн задачи пройден целиком (спека → код → оба
@@ -193,9 +193,10 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
`openspec validate --strict`. `openspec validate --strict`.
Ревью — два чекпоинта: ревью дизайна на предложении (после design/specs, ДО Ревью — два чекпоинта: ревью дизайна на предложении (после design/specs, ДО
кода) и ревью изменения после apply, до archive. Состав обоих выбирается по кода) и ревью изменения после apply, до archive. Состав обоих постоянен и на
метке задачи (`small` / `medium` / `large`), которую разметка ставит один раз всякой задаче один и тот же; глубину по области даёт отдельный прогон
после propose. Настройка конвейера под проект и журнал дефектов — `av-dev:code-deep-review`, и зовёт его человек. Настройка конвейера под проект,
признаки для глубокого прогона и журнал дефектов —
[docs/review.md](docs/review.md). [docs/review.md](docs/review.md).
## Документация ## Документация
@@ -215,7 +216,7 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
- [docs/review.md](docs/review.md) — настройка конвейера ревью и журнал дефектов. - [docs/review.md](docs/review.md) — настройка конвейера ревью и журнал дефектов.
- [tasks/](tasks/BACKLOG.md) — задачи и цели: одна запись = один файл - [tasks/](tasks/BACKLOG.md) — задачи и цели: одна запись = один файл
в `items/` + строка в индексе, порядок строк = приоритет. Ведётся скиллом в `items/` + строка в индексе, порядок строк = приоритет. Ведётся скиллом
`av-dev-tasks:tasks`, разбор беклога — `av-dev-tasks:groom`. `av-dev:task-track`, разбор беклога — `av-dev:task-groom`.
**Tududi** (проект `jellybit`, project_id 14) — только инбокс сырых идей. Идея **Tududi** (проект `jellybit`, project_id 14) — только инбокс сырых идей. Идея
становится задачей, когда её оформляют файлом в `tasks/items/`. становится задачей, когда её оформляют файлом в `tasks/items/`.
+2 -2
View File
@@ -37,8 +37,8 @@ Arr-стек (prowlarr/radarr/sonarr) плохо ложится на русск
Рабочий прототип: сквозной путь приём → загрузка → распознавание → раскладка Рабочий прототип: сквозной путь приём → загрузка → распознавание → раскладка
работает целиком, автоматически при уверенном результате либо через работает целиком, автоматически при уверенном результате либо через
подтверждение человеком. Что уже умеет и что дальше подтверждение человеком. Что уже умеет `openspec/specs/`, что взято в очередь
[tasks/ROADMAP.md](tasks/ROADMAP.md). [tasks/BACKLOG.md](tasks/BACKLOG.md).
## Документация ## Документация
-4
View File
@@ -1,4 +0,0 @@
{
"canon": 12,
"migrations": "internal/store/migrations"
}
+11 -4
View File
@@ -1,17 +1,24 @@
# Журнал решений # Журнал решений
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**, Одна запись — одно решение. **ADR продвигает уже написанное решение, а не
а не второе сочинение: запись цитирует решение и ссылается на сочиняет его заново**: запись цитирует решение и ссылается на источник —
`openspec/changes/archive/<id>/design.md`. `openspec/changes/archive/<id>/design.md`, а у решения, принятого разведкой без
изменения, на её записку.
Главная ценность записи — сохранить **почему**: намерение и причинность. Это Главная ценность записи — сохранить **почему**: намерение и причинность. Это
важнее аккуратности оформления и полноты остальных секций. важнее аккуратности оформления и полноты остальных секций.
Решение старше OpenSpec либо принятое вне этого репозитория архивного
`design.md` не имеет по построению. Тогда поле **Источник** называет то, что
есть, — историю git, черновик, записку соседнего репозитория — и прямо говорит,
что архивного `design.md` нет. Запись без поля «Источник» вовсе — дефект; запись,
честно назвавшая отсутствие источника, — нет.
## Когда заводить ## Когда заводить
Верно одно из трёх: Верно одно из трёх:
<!-- копия: adr-когда-заводить из av-dev-docs/skills/canon/references/canon.md --> <!-- копия: adr-когда-заводить из av-dev/skills/canon/references/canon.md -->
- **дорогой откат** — переделка стоит дороже переписывания одного файла; - **дорогой откат** — переделка стоит дороже переписывания одного файла;
- **намеренный отказ** от очевидного подхода; - **намеренный отказ** от очевидного подхода;
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус - **пересмотр прежнего решения** — тогда у старой записи обязателен статус
+2 -1
View File
@@ -1,7 +1,8 @@
# Краткий заголовок решения # Краткий заголовок решения
- **Дата:** ГГГГ-ММ-ДД - **Дата:** ГГГГ-ММ-ДД
- **Источник:** openspec/changes/archive/<id>/design.md - **Источник:** openspec/changes/archive/<id>/design.md — либо записка разведки,
если решение принято без изменения
<!-- Статус ставится тем же полем и только при пересмотре: <!-- Статус ставится тем же полем и только при пересмотре:
- **Статус:** заменено на ADR-ГГГГ-ММ-ДД-slug - **Статус:** заменено на ADR-ГГГГ-ММ-ДД-slug
+3 -3
View File
@@ -108,7 +108,7 @@
| Что | Где | | Что | Где |
| --- | --- | | --- | --- |
| Время | `store.Now()` — единственный источник меток времени в данных, всегда UTC; формат хранения — RFC 3339. Вторая санкционированная точка wall-clock — timestamp-часть ULID в `ident.NewID` (исключение `^internal/(ident\|store)/` в `.golangci.yml`). Отдельно от меток в данных стоят замеры длительности: `cmd/jellybit` исключён из `forbidigo` целиком (правило `^cmd/`), плюс точечные `//nolint:forbidigo` в `internal/logging/ext.go` и `internal/httpapi/httpapi.go` | | Время | `store.Now()` — единственный источник меток времени в данных, всегда UTC; формат хранения — [database.md](database.md). Вторая санкционированная точка wall-clock — timestamp-часть ULID в `ident.NewID` (исключение `^internal/(ident\|store)/` в `.golangci.yml`). Отдельно от меток в данных стоят замеры длительности: `cmd/jellybit` исключён из `forbidigo` целиком (правило `^cmd/`), плюс точечные `//nolint:forbidigo` в `internal/logging/ext.go` и `internal/httpapi/httpapi.go` |
| Идентификаторы | `internal/ident` — генерация и нормализация ULID; `ident.Parse` на каждой входной границе | | Идентификаторы | `internal/ident` — генерация и нормализация ULID; `ident.Parse` на каждой входной границе |
| Целевые имена и превью раскладки | `internal/naming` — одна логика для превью в UI и для реального применения | | Целевые имена и превью раскладки | `internal/naming` — одна логика для превью в UI и для реального применения |
| Чистка человекочитаемых значений | три санитайзера с разным предметом, сводить их в один нельзя: `recognize.SanitizeTitle` — значение (недоверенный вход: LLM и метабазы), `layout.sanitizeComponent` — компонент пути под требования ФС, `naming.sanitize` — отображаемый ярлык. Значение метабазы чистится **на каждой** точке входа в план: сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение — [ADR-2026-08-10-sanitize-at-every-entry](adr/ADR-2026-08-10-sanitize-at-every-entry.md) | | Чистка человекочитаемых значений | три санитайзера с разным предметом, сводить их в один нельзя: `recognize.SanitizeTitle` — значение (недоверенный вход: LLM и метабазы), `layout.sanitizeComponent` — компонент пути под требования ФС, `naming.sanitize` — отображаемый ярлык. Значение метабазы чистится **на каждой** точке входа в план: сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение — [ADR-2026-08-10-sanitize-at-every-entry](adr/ADR-2026-08-10-sanitize-at-every-entry.md) |
@@ -116,7 +116,7 @@
| Приём | use-case `ingest` — общий путь для HTTP, веб-UI, Telegram и CLI | | Приём | use-case `ingest` — общий путь для HTTP, веб-UI, Telegram и CLI |
| Переходы состояний | `worker` под per-download блокировкой; легальность перехода задаётся декларативным графом | | Переходы состояний | `worker` под per-download блокировкой; легальность перехода задаётся декларативным графом |
| Хардлинки и удаление своих ссылок | `internal/layout` — единственное место, которое пишет в файловую систему библиотеки | | Хардлинки и удаление своих ссылок | `internal/layout` — единственное место, которое пишет в файловую систему библиотеки |
| Построение и проверка целевого пути | `layout.BuildLinks` — единственная сборка пути; там же обе проверки, и порядок значим: нахождение под корнем библиотеки, затем длина компонента. Отсюда же строятся оба предпросмотра ревью, поэтому показанное и применённое совпадают устройством, а не договорённостью | | Построение и проверка целевого пути | `layout.BuildLinks` — единственная сборка пути; там же обе проверки, порядок нормирован [file-layout](../openspec/specs/file-layout/spec.md). Отсюда же строятся оба предпросмотра ревью, поэтому показанное и применённое совпадают устройством, а не договорённостью |
| Причина, по которой человек не видит плана | считается **на показе** (`worker.ReviewData.PreviewError`) и предпочитается записанной в состоянии: записанной может не быть вовсе, а после смены источника она уже про другой план — [ADR-2026-08-10-reason-computed-on-read](adr/ADR-2026-08-10-reason-computed-on-read.md) | | Причина, по которой человек не видит плана | считается **на показе** (`worker.ReviewData.PreviewError`) и предпочитается записанной в состоянии: записанной может не быть вовсе, а после смены источника она уже про другой план — [ADR-2026-08-10-reason-computed-on-read](adr/ADR-2026-08-10-reason-computed-on-read.md) |
| Условие допуска полного удаления | `store.State.CanDelete()` — «из этого состояния удаление с файлами разрешено»; своего перечня состояний не заводит ни один транспорт (веб-UI, Telegram, страница группового удаления), а проверку в ядре предикат не заменяет: допуск держится без транспорта | | Условие допуска полного удаления | `store.State.CanDelete()` — «из этого состояния удаление с файлами разрешено»; своего перечня состояний не заводит ни один транспорт (веб-UI, Telegram, страница группового удаления), а проверку в ядре предикат не заменяет: допуск держится без транспорта |
| Условие самообновления веб-UI | `store.State.IsObservable()` — «состояние ещё может измениться без человека»; транспорт своего перечня состояний не заводит, а поверхность (карточка списка, страница загрузки) держит **ровно один** поллер на обновляемый корень — [ADR-2026-08-10-observability-is-not-terminality](adr/ADR-2026-08-10-observability-is-not-terminality.md), правило разметки — [conventions/web-ui.md](conventions/web-ui.md) | | Условие самообновления веб-UI | `store.State.IsObservable()` — «состояние ещё может измениться без человека»; транспорт своего перечня состояний не заводит, а поверхность (карточка списка, страница загрузки) держит **ровно один** поллер на обновляемый корень — [ADR-2026-08-10-observability-is-not-terminality](adr/ADR-2026-08-10-observability-is-not-terminality.md), правило разметки — [conventions/web-ui.md](conventions/web-ui.md) |
@@ -185,7 +185,7 @@ Jellyfin указывают на `movies`/`series`, а не на корень
| Область | Чего нет сегодня | Задача | | Область | Чего нет сегодня | Задача |
| --- | --- | --- | | --- | --- | --- |
| Масштаб | ориентир 100/1000 загрузок не зафиксирован, узкие места SQLite, воркера и поллинга не измерены | `scale-100-downloads` | | Масштаб | ориентир 100/1000 загрузок не зафиксирован, узкие места SQLite, воркера и поллинга не измерены | `scale-100-downloads` |
| Ретеншен | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` | | Срок хранения | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` |
| Бекап | бекапить `/data` требуется, а стратегия и ротация не описаны | `sqlite-backup` | | Бекап | бекапить `/data` требуется, а стратегия и ротация не описаны | `sqlite-backup` |
| Метрики и алертинг | healthcheck проверяет только сам сервис; метрик и алертинга нет, отказ виден по застрявшей задаче | `deep-healthcheck-dependencies` | | Метрики и алертинг | healthcheck проверяет только сам сервис; метрик и алертинга нет, отказ виден по застрявшей задаче | `deep-healthcheck-dependencies` |
| Идентичность раздачи | split v1/v2-хеши не связаны, паре `xt` из магнета доверяем | `infohash-identity-integrity` | | Идентичность раздачи | split v1/v2-хеши не связаны, паре `xt` из магнета доверяем | `infohash-identity-integrity` |
+1 -1
View File
@@ -9,7 +9,7 @@
перечень «Механизировано» ниже. Причина: файл на несколько сотен строк перечень «Механизировано» ниже. Причина: файл на несколько сотен строк
размазывает внимание по тривиальному — и модель, и человек добросовестно размазывает внимание по тривиальному — и модель, и человек добросовестно
проверят именование и не дойдут до формы решения. Процедура промоута — проверят именование и не дойдут до формы решения. Процедура промоута —
`references/promote.md` скилла `av-dev-pipeline:review-pipeline`. `references/promote.md` скилла `av-dev:code-review`.
Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты
с severity — в [CLAUDE.md](../../CLAUDE.md). с severity — в [CLAUDE.md](../../CLAUDE.md).
+9 -9
View File
@@ -39,16 +39,16 @@
## Прочее ## Прочее
- Enum-поля (`state`, `kind`, …) — обычный `TEXT` без `CHECK`; допустимые - Enum-поля — обычный `TEXT`/`INTEGER` без `CHECK`; допустимые значения держит
значения держит код (`internal/store`). код (`internal/store`). Какие именно поля — [../database.md](../database.md) →
- Временные метки — `TEXT` в **RFC 3339, UTC (суффикс `Z`)**, напр. «Индексы и ограничения».
`2006-01-02T15:04:05Z` (секундная точность). Фиксированная ширина сохраняет - Временные метки пишет приложение: `store.Now()` + `store.FormatTime`/
лексикографическую сортировку TEXT = хронологию (`ORDER BY created_at`).
Единая точка генерации — приложение: `store.Now()` + `store.FormatTime`/
`ParseTime` (аналогично `ident.NewID` для id), а не дефолт в схеме — так `ParseTime` (аналогично `ident.NewID` для id), а не дефолт в схеме — так
забытая вставка падает громко. Измерение длительности — не метка времени: у забытая вставка падает громко. Формат и точность хранения —
внешних вызовов его засекает `logging.StartCall`. Таймзона отображения в [../database.md](../database.md); фиксированная ширина там выбрана затем, чтобы
UI — конфиг `[general].timezone`. лексикографическая сортировка TEXT совпадала с хронологией. Измерение
длительности — не метка времени: у внешних вызовов его засекает
`logging.StartCall`. Таймзона отображения в UI — конфиг `[general].timezone`.
- Миграции — goose (`internal/store/migrations`): SQL-файлы для DDL; - Миграции — goose (`internal/store/migrations`): SQL-файлы для DDL;
Go-миграции (`goose.AddMigrationContext`) — когда нужен код (генерация Go-миграции (`goose.AddMigrationContext`) — когда нужен код (генерация
id, backfill). При изменении структуры обновляем ER-схему id, backfill). При изменении структуры обновляем ER-схему
+12 -7
View File
@@ -219,12 +219,16 @@ Go-ошибки логируем как атрибут, не как текст
## Безопасность: что не логируем ## Безопасность: что не логируем
Никаких секретов в полях и сообщениях. Под запретом: Никаких секретов в полях и сообщениях. Состав секретов —
[CLAUDE.md](../../CLAUDE.md) → «Инварианты», строка «Секреты не попадают в логи,
диагностику и ответы API»; здесь он не дублируется.
- учётные данные qBittorrent (логин/пароль, cookie сессии); Специфичные для логов **носители** секрета, которых в инварианте нет:
- API-ключ и токен LLM-провайдера, `Authorization`-заголовки;
- ключи TMDB/TVDB и прочих метабаз; - cookie сессии qBittorrent;
- содержимое аутентификационных параметров magnet/трекеров. - заголовки `Authorization`;
- аутентификационные параметры magnet и трекеров;
- URL внутри `*url.Error` (см. ниже).
Дополнительно: Дополнительно:
@@ -246,8 +250,9 @@ Go-ошибки логируем как атрибут, не как текст
- Пишем JSON в `stdout` одним потоком; сбор и ротацию делает окружение - Пишем JSON в `stdout` одним потоком; сбор и ротацию делает окружение
(docker/journald). Не маршрутизируем по файлам. (docker/journald). Не маршрутизируем по файлам.
- Базовый уровень в проде — `INFO`; `DEBUG` включается через конфиг/env при - Базовый уровень в проде — `INFO`; `DEBUG` включается полем `level` секции
необходимости. dev — `DEBUG`. `[log]` в `config.toml` — env для конфига не используется
([config.md](config.md)). dev — `DEBUG`.
## Анализ ## Анализ
+7 -5
View File
@@ -176,7 +176,7 @@ erDiagram
внутри JSON нет, SQL по содержимому этих полей не делается. внутри JSON нет, SQL по содержимому этих полей не делается.
- **`recognition.raw_llm`** — сырой ответ модели как есть, **несжатый**. Это - **`recognition.raw_llm`** — сырой ответ модели как есть, **несжатый**. Это
самое крупное поле в базе и главный кандидат на рост: у каждой попытки самое крупное поле в базе и главный кандидат на рост: у каждой попытки
распознавания свой ответ, попытки не вытесняются, ретеншена нет распознавания свой ответ, попытки не вытесняются, срока хранения нет
(задача в беклоге). (задача в беклоге).
- **`download_torrent.data`** — единственный BLOB: исходные байты `.torrent` - **`download_torrent.data`** — единственный BLOB: исходные байты `.torrent`
(обычно десятки КБ, у больших раздач — сотни). Читается целиком при (обычно десятки КБ, у больших раздач — сотни). Читается целиком при
@@ -224,8 +224,10 @@ erDiagram
| `httpapi.maxBulkDelete` | `20` загрузок | предел размера одной пачки группового удаления. Подтверждение, перечисляющее больше, человек не читает — то есть перестаёт быть подтверждением; плюс один синхронный запрос упирается в столько же последовательных вызовов qBittorrent. Предел называет сама страница выбора; отказ по пределу возвращает выбор с сохранёнными отметками | | `httpapi.maxBulkDelete` | `20` загрузок | предел размера одной пачки группового удаления. Подтверждение, перечисляющее больше, человек не читает — то есть перестаёт быть подтверждением; плюс один синхронный запрос упирается в столько же последовательных вызовов qBittorrent. Предел называет сама страница выбора; отказ по пределу возвращает выбор с сохранёнными отметками |
| `httpapi.bulkFailThreshold` | `3` отказа подряд | сколько подряд идущих отказов внешнего сервиса прекращают проход группового удаления. Удаление снимает библиотечные ссылки раньше, чем сносит раздачу: при недоступном qBittorrent каждая единица успевает выполнить необратимый локальный шаг и упасть на внешнем. Счётчик сбрасывается на успехе; конфликт состояния системным отказом не считается | | `httpapi.bulkFailThreshold` | `3` отказа подряд | сколько подряд идущих отказов внешнего сервиса прекращают проход группового удаления. Удаление снимает библиотечные ссылки раньше, чем сносит раздачу: при недоступном qBittorrent каждая единица успевает выполнить необратимый локальный шаг и упасть на внешнем. Счётчик сбрасывается на успехе; конфликт состояния системным отказом не считается |
| `httpapi.bulkBudget` | `2` минуты | потолок времени на один проход группового удаления. Удаление держит общий замок воркера на всё время обращения к qBittorrent, поэтому медленно, но успешно отвечающий сосед остановил бы фоновую работу целиком, а порог отказов такого не ловит. Проверяется между единицами: начатое удаление не обрывается, иначе оно встанет между снятием ссылок и сносом раздачи | | `httpapi.bulkBudget` | `2` минуты | потолок времени на один проход группового удаления. Удаление держит общий замок воркера на всё время обращения к qBittorrent, поэтому медленно, но успешно отвечающий сосед остановил бы фоновую работу целиком, а порог отказов такого не ловит. Проверяется между единицами: начатое удаление не обрывается, иначе оно встанет между снятием ссылок и сносом раздачи |
| `layout.maxComponentBytes` | `255` байт | предел длины компонента целевого пути (`NAME_MAX` у ext4/xfs/btrfs); меряется в байтах UTF-8, проверяется **до** первой операции с ФС, отказ уводит задачу в `review` с кодом `name_too_long`. У ядра не выясняется; на ФС с меньшим пределом остаётся отказ ядра — лечение правкой константы, а не настройкой | | `layout.maxComponentBytes` | `255` байт | предел длины компонента целевого пути (`NAME_MAX` у ext4/xfs/btrfs); меряется в байтах UTF-8. Что происходит при превышении — [file-layout](../openspec/specs/file-layout/spec.md); величину у ядра не выясняем, и на ФС с меньшим пределом остаётся отказ ядра — чинится правкой константы, а не настройкой |
| `llm.maxResponseBody` | `8 MiB` | предел размера тела ответа LLM-эндпоинта: ответ читается через `io.LimitReader`, а не целиком |
| `metadata.maxBody` | `4 MiB` | то же для ответов TMDB, TVDB и TVMaze |
**Ретеншена нет ни у одной таблицы**, лимита на размер тела ответа LLM нет, **Срока хранения нет ни у одной таблицы**, кэша метабаз нет; состояние по каждому
кэша метабаз нет; состояние по каждому пробелу и заведённые под них задачи — пробелу и заведённые под них задачи — [architecture.md](architecture.md) →
[architecture.md](architecture.md) → «Открытые вопросы». «Открытые вопросы».
+1 -1
View File
@@ -1,7 +1,7 @@
# Паспорт проекта # Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «что уже умеет», паспорт — устроено», [openspec/specs/](../openspec/specs/) — «что уже умеет», паспорт —
«зачем и для кого». «зачем и для кого».
## Цель ## Цель
+1 -1
View File
@@ -10,7 +10,7 @@
не от смены формата. не от смены формата.
**Каждый вывод — с числами и командой или условиями, которыми получен**, чтобы **Каждый вывод — с числами и командой или условиями, которыми получен**, чтобы
его можно было перепроверить. Число без провенанса проход обязан читать как его можно было перепроверить. Число без происхождения проход обязан читать как
условие, а не как замер. Число, чей источник по ссылке не подтвердился, не условие, а не как замер. Число, чей источник по ссылке не подтвердился, не
выбрасывается и не переписывается по догадке — остаётся с пометкой «расходится выбрасывается и не переписывается по догадке — остаётся с пометкой «расходится
с источником: там <что нашли>». с источником: там <что нашли>».
+1 -1
View File
@@ -5,7 +5,7 @@
сообщения берутся **источник** (magnet) и **контекст** для распознавания. Формат сообщения берутся **источник** (magnet) и **контекст** для распознавания. Формат
чужой, ничем не документирован и может измениться без предупреждения. чужой, ничем не документирован и может измениться без предупреждения.
**Провенанс.** Образец снят вручную из личного чата Telegram (сообщение **Происхождение.** Образец снят вручную из личного чата Telegram (сообщение
датировано 2026-03-21) и зафиксирован в [BRIEF.md] проекта; второй образец, датировано 2026-03-21) и зафиксирован в [BRIEF.md] проекта; второй образец,
меньшего размера, живёт константой `botMessage` в меньшего размера, живёт константой `botMessage` в
`internal/tgbot/parse_test.go` и проверяется тестами разборщика. Статистики по `internal/tgbot/parse_test.go` и проверяется тестами разборщика. Статистики по
+1 -1
View File
@@ -4,7 +4,7 @@
из ответа `/search`. Здесь записано, откуда взята форма этого ответа и чего в ней из ответа `/search`. Здесь записано, откуда взята форма этого ответа и чего в ней
не подтверждено. не подтверждено.
**Провенанс — и он слабый.** Всё ниже сверено **по публичной документации** **Происхождение — и оно слабое.** Всё ниже сверено **по публичной документации**
TheTVDB API v4, файл `docs/swagger.yml` репозитория `thetvdb/v4-api`, поле TheTVDB API v4, файл `docs/swagger.yml` репозитория `thetvdb/v4-api`, поле
`info.version` = `4.7.10` (прочитано 2026-08-07). **Живым прогоном не `info.version` = `4.7.10` (прочитано 2026-08-07). **Живым прогоном не
подтверждено ни одно наблюдение**: `CLAUDE.md` → «Запреты» запрещает ходить в подтверждено ни одно наблюдение**: `CLAUDE.md` → «Запреты» запрещает ходить в
+56 -75
View File
@@ -1,8 +1,8 @@
# Ревью: настройка и журнал # Ревью: настройка и журнал
Проектная часть конвейера ревью: чем jellybit отличается от абстрактного Проектная часть конвейера ревью: чем jellybit отличается от абстрактного
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (метки, Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (состав
стадии, контракт находок) живёт в скилле, а не здесь. проходов, стадии, контракт находок) живёт в скилле, а не здесь.
## Как настроен конвейер ## Как настроен конвейер
@@ -135,8 +135,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
### Вопросы по темам ### Вопросы по темам
Форма: `<тема>: <вопрос> (<провенанс>)`. Адресуется теме, а не имени прохода: Форма: `<тема>: <вопрос> (<откуда>)`. Адресуется теме, а не имени прохода:
проход переезжает между метками и упраздняется, тема переезд переживает. Задаёт проход переезжает между скиллами и упраздняется, тема переезд переживает. Задаёт
вопрос тот, кто закрывает тему на текущем прогоне. вопрос тот, кто закрывает тему на текущем прогоне.
- `security`: можно ли, управляя только именами файлов в раздаче и текстом - `security`: можно ли, управляя только именами файлов в раздаче и текстом
@@ -179,8 +179,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
- `requirements`: не завелось ли поведение, которого спека не заказывала — тихий - `requirements`: не завелось ли поведение, которого спека не заказывала — тихий
дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле? дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле?
- `security`: читается ли тело ответа внешнего сервиса целиком без предела — - `security`: читается ли тело ответа внешнего сервиса целиком без предела —
у LLM (8 MiB) и метабаз (4 MiB) предел стоит, и новый исходящий вызов обязан у LLM и метабаз предел стоит ([database.md](database.md) → «Пределы, зашитые
заводить свой ([security.md](security.md) → «Что вне модели») константой кода»), и новый исходящий вызов обязан заводить свой?
- `operations`: гарантия, которую вводит изменение, поставлена на запись или на - `operations`: гарантия, которую вводит изменение, поставлена на запись или на
чтение — и что будет с данными, записанными до деплоя, которые обычный путь чтение — и что будет с данными, записанными до деплоя, которые обычный путь
не перезаписывает? (журнал, 2026-08-10: чистка названия стояла на записи, и не перезаписывает? (журнал, 2026-08-10: чистка названия стояла на записи, и
@@ -201,75 +201,50 @@ Go-сервиса и что здесь уже проскакивало. Устр
логика целевых имён мимо `naming`? логика целевых имён мимо `naming`?
([architecture.md](architecture.md) → «Единые точки проекта») ([architecture.md](architecture.md) → «Единые точки проекта»)
### Триггеры метки ### Когда звать глубокое ревью
Уточняет умолчания конвейера, не отменяет их. Рабочее умолчание — `medium`; Цикл задачи проверяет корректность и механику одним и тем же составом на всякой
миграция схемы и публичный контракт метку **не** поднимают: их проверяют задаче. Глубину даёт только отдельный прогон по области —
проходы, которые в `medium` и так есть. Списка три: два поднимают до `large`, `av-dev:code-deep-review`, — и **зовёт его человек**. Списки ниже уточняют
по одному на ось, третий опускает до `small`. признаки, по которым его здесь зовут, а не заводят расписание.
**Крупное здесь** — про объём, сколько узлов и слоёв трогает изменение: **Области, которые смотрят целиком:**
- перенос ответственности между `worker`, `recognition`, `layout` и `store`; - **правила идентичности, слияния и разбора** — ключ владения раздачей и сверка
- новое состояние в графе переходов загрузки: оно тянет за собой воркер, спеку, с qBittorrent (`ident`, `internal/store`, `state-reconciliation`); построение
отображение в веб-UI и боте и восстановление после рестарта; целевых путей и санитизация имён (`internal/layout`, `naming`); разбор
- правка, идущая насквозь по цепочке приём → распознавание → раскладка; недоверенного входа — bencode, magnet, текст контекста, ответ LLM
- новый провайдер метабазы за существующим интерфейсом: клиент, поле конфига с (`internal/torrent`, `internal/magnet`, `internal/tgbot/parse.go`); слияние
образцом, слияние полей кандидата, ветка «провайдера нет». кандидата метабазы и победитель при конфликте (`internal/metadata`,
`metadata-match`). Сюда задачи возвращаются чаще всего: у одного правила здесь
несколько домов, и правка одного из них молча расходится с остальными.
- **удаление раздачи с файлами** — история инцидентов самая длинная: уборка
своего торрента после отмены сносила чужие файлы (журнал, 2026-08-06), три
дефекта поштучного удаления жили незамеченными до появления пачки и ещё
четыре нашлись на групповой поверхности (журнал, 2026-08-10).
- **граф состояний загрузки и восстановление после рестарта** — новое состояние
тянет за собой воркер, спеку, отображение в веб-UI и боте; расхождение видно
не на диффе, а на живом сервере.
- **код под дорогое решение** — то, на что обопрётся ADR или новая capability:
форму здесь нащупывают по ходу, и цена ошибки не в правке, а в переписывании
решения.
**Незнакомое здесь**про форму решения, которую предстоит нащупать по ходу: **Необратимое здесь****места кода**, которые после мерджа не откатываются
обратной правкой. Находка в таком месте уходит человеку развилкой, а не чинится
молча. Перечень **операций**, которые спрашиваются у человека всегда, — другой
вопрос и другой дом: [CLAUDE.md](../CLAUDE.md) → «Работа» → «Необратимое».
- новый пакет `internal/*` или новая capability в `openspec/specs/`; - **миграция схемы**, уже накатанная на боевую БД umbar;
- новый транспорт приёма или уведомлений рядом с REST, веб-UI, ботом и CLI; - **операции, снимающие данные** — `torrents/delete` с `deleteFiles=true` и
- заводится или меняется **правило идентичности, слияния или разбора**: ключ снятие последней жёсткой ссылки: обе исключены из гарда сознательно, и второй
владения раздачей и сверка с qBittorrent (`ident`, `internal/store`, точки входа в них не бывает дешёвой (журнал, 2026-08-10);
`state-reconciliation`); построение целевых путей и санитизация имён - **раскладка в библиотеке Jellyfin** — переименование или перезапись файла,
(`internal/layout`, `naming`); новый вид входа или новая ветка неоднозначности на который уже смотрят клиенты и закладки;
у разбора недоверенного — bencode, magnet, текст контекста, ответ LLM - **публичный контракт** — форма ответа REST, имена полей конфига,
(`internal/torrent`, `internal/magnet`, `internal/tgbot/parse.go`); новый идентификаторы в ссылках: их держат чужие потребители, и переименование
источник или новый победитель при конфликте в слиянии кандидата метабазы расходится по базе;
(`internal/metadata`, `metadata-match`); merge-раскладка при повторном - **формат на диске** — раскладка `paths.movies`/`paths.series` и имена, по
добавлении раздачи. которым Jellyfin узнаёт тайтл.
- **новая поверхность поверх необратимой операции** — вторая точка входа в
команду, которая удаляет файлы или снимает последнюю копию. Форма решения
здесь нащупывается по ходу: подтверждение, порядок отказов, остаток,
наблюдаемость. Выведено по факту на `bulk-delete-page` (журнал, 2026-08-10):
метка `medium` не дала ни враждебного прохода, ни замера, а именно они нашли
четыре дефекта класса «необратимо».
- «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram**
для единственного пользователя это и есть интерфейс.
**Место из перечня метку не поднимает — поднимает правило.** Перечни выше
отвечают «здесь такие правила водятся», а не «любая правка здесь идёт в
`large`». Метку поднимает то, что даёт работу новому проходу: заводится ключ
сравнения или меняется его состав; у разбора появляется новый вид входа или
новая ветка неоднозначности; в слияние добавляется источник или меняется
победитель при конфликте.
**Мелкое здесь** — опускает до `small`. Перечень закрытый, каждый пункт
проверяется взглядом на дифф и дельта-спеку, любое сработавшее держит метку
внизу:
- **дельта-спека называет исход поимённо** — сценарий уже говорит, что даёт
вырожденный вход, и решать в коде нечего;
- **новых сценариев в дельта-спеке нет** — изменение уточняет уже описанное
поведение, а не заказывает новое;
- **правка сообщения, комментария, записи журнала, имени или теста** в узле из
перечней выше;
- **сужение уже существующей нормализации** без нового вида входа: вход остался
тот же, изменился исход на одном его значении.
Отрицательный тест поверх перечня: что после мерджа не откатывается обратной
правкой — миграция, формат на диске, публичный контракт, имя, — **не** `small`,
каким бы маленьким ни был дифф.
**Ориентир частоты.** `medium` закрывает большинство задач, `large` рассчитана
на 5–10% и приходится на крупную функциональность, а не на уборку: задача типа
`chore` или `fix`, собранная из нитей прошлого ревью, идёт в `small` или
`medium`, даже когда трогает файл из перечней выше. `large` чаще одной задачи из
десяти означает ошибку в критерии, а не полосу сложных задач подряд.
### Недоступно проверке ### Недоступно проверке
@@ -283,7 +258,7 @@ Go-сервиса и что здесь уже проскакивало. Устр
ссылки) на текущее поведение. ссылки) на текущее поведение.
- `architecture`: суждение «этой функциональности не должно существовать». - `architecture`: суждение «этой функциональности не должно существовать».
- `requirements`: качество распознавания как таковое — правильно ли LLM - `requirements`: качество распознавания как таковое — правильно ли LLM
определил фильм. Это вопрос тюнинга модели и промпта, а не ревью кода; определил фильм. Это вопрос настройки модели и промпта, а не ревью кода;
размеченный корпус, по которому это можно было бы судить числом, решено не размеченный корпус, по которому это можно было бы судить числом, решено не
собирать (`tasks/REJECTED.md`, 2026-08-06). собирать (`tasks/REJECTED.md`, 2026-08-06).
@@ -299,10 +274,10 @@ Go-сервиса и что здесь уже проскакивало. Устр
что уже есть в библиотеке»). **Различение «идиоматично против что уже есть в библиотеке»). **Различение «идиоматично против
распространено» теперь не спрашивает никто.** Класс обратимый: портит форму распространено» теперь не спрашивает никто.** Класс обратимый: портит форму
кода, не данные. Пересмотр — задача `quality-review-agents`. кода, не данные. Пересмотр — задача `quality-review-agents`.
- `security`, `operations`, `architecture`: на метках `small` и `medium` не - `security`, `operations`, `architecture`: в цикле задачи не проверяется
проверяется ничто, требующее запуска, — построенных путей атаки, замеров и ничто, требующее запуска, — построенных путей атаки, замеров и
эксплуатационного постмортема там нет по устройству конвейера. Их даёт только эксплуатационного постмортема там нет по устройству конвейера. Их даёт только
`large`, а она приходится на 5–10% задач. отдельный прогон `av-dev:code-deep-review`, который зовёт человек.
## Журнал дефектов ## Журнал дефектов
@@ -312,7 +287,7 @@ Go-сервиса и что здесь уже проскакивало. Устр
Форма: Форма:
<!-- копия: журнал-дефектов-форма из av-dev-pipeline/skills/review-pipeline/references/review-journal.md --> <!-- копия: журнал-дефектов-форма из av-dev/skills/code-review/references/review-journal.md -->
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман] ## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
- **Где:** путь:строка либо «конвейер, а не код» - **Где:** путь:строка либо «конвейер, а не код»
@@ -351,6 +326,9 @@ Go-сервиса и что здесь уже проскакивало. Устр
удержания общего замка воркера (`280.450631ms` при задержке соседа `300ms`) удержания общего замка воркера (`280.450631ms` при задержке соседа `300ms`)
- **Что меняем:** в «Триггеры метки», ось «незнакомое», добавлен пункт про новую - **Что меняем:** в «Триггеры метки», ось «незнакомое», добавлен пункт про новую
поверхность поверх необратимой операции поверхность поверх необратимой операции
- **Отменено 2026-09-02:** метка упразднена вместе с переходом на постоянный
состав прогона. Признак живёт в «Когда звать глубокое ревью» → «Области,
которые смотрят целиком»
## 2026-08-10 — три дефекта поштучного удаления жили незамеченными, пока рядом не появилась пачка [проскочил] ## 2026-08-10 — три дефекта поштучного удаления жили незамеченными, пока рядом не появилась пачка [проскочил]
@@ -374,6 +352,9 @@ Go-сервиса и что здесь уже проскакивало. Устр
- **Что меняем:** три записи в беклоге со ссылкой на оракулы; триггер метки - **Что меняем:** три записи в беклоге со ссылкой на оракулы; триггер метки
дополнен (см. запись выше), чтобы следующая поверхность над необратимой дополнен (см. запись выше), чтобы следующая поверхность над необратимой
операцией шла сразу с доказательными проходами операцией шла сразу с доказательными проходами
- **Отменено 2026-09-02:** метки нет; вывод перенесён в «Когда звать глубокое
ревью» — удаление раздачи с файлами названо там областью, которую смотрят
целиком
## 2026-08-10 — тест остался зелёным навсегда, потому что проверял снятый атрибут [пойман] ## 2026-08-10 — тест остался зелёным навсегда, потому что проверял снятый атрибут [пойман]
+6 -6
View File
@@ -50,10 +50,10 @@ REST API работают **без авторизации** осознанно;
- **Правило:** компоненты санитизируются (убираются разделители пути, `..`, - **Правило:** компоненты санитизируются (убираются разделители пути, `..`,
управляющие символы), финальный `filepath.Clean`-путь обязан быть **строго управляющие символы), финальный `filepath.Clean`-путь обязан быть **строго
под** соответствующей библиотекой, иначе операция отклоняется. Проверка на под** соответствующей библиотекой, иначе операция отклоняется. Проверка на
результате, а не на входе. Следом — длина: каждый компонент обязан помещаться в результате, а не на входе. Длина компонента проверяется следом и только после
255 байт UTF-8, иначе задача уходит в `review`. Порядок значим: путь, вышедший проверки принадлежности — порядок и исход нормированы
за песочницу, отклоняется как выход за библиотеку, а не как длинное имя, иначе [file-layout](../openspec/specs/file-layout/spec.md), величина предела —
находка безопасности спряталась бы за косметической причиной. [database.md](database.md).
- **Исходный путь** = `save_path` из qBittorrent + относительное имя файла из - **Исходный путь** = `save_path` из qBittorrent + относительное имя файла из
`/torrents/files`. Тоже недоверенный, но по нему мы только **читаем и `/torrents/files`. Тоже недоверенный, но по нему мы только **читаем и
линкуем**; писать в `paths.downloads` нельзя вообще. линкуем**; писать в `paths.downloads` нельзя вообще.
@@ -104,8 +104,8 @@ REST API работают **без авторизации** осознанно;
- **Отказ в обслуживании изнутри контура.** Огромная раздача, тысяча файлов, - **Отказ в обслуживании изнутри контура.** Огромная раздача, тысяча файлов,
бесконечный ответ LLM — это вопросы устойчивости и ресурсов бесконечный ответ LLM — это вопросы устойчивости и ресурсов
([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности. ([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности.
Тело ответа внешнего сервиса при этом читается с пределом: LLM — 8 MiB Тело ответа внешнего сервиса при этом читается с пределом — величины в
(`internal/llm`), метабазы — 4 MiB (`internal/metadata`). [database.md](database.md) → «Пределы, зашитые константой кода».
- **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота. - **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота.
- **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга. - **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга.
- **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и - **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и
+1 -1
View File
@@ -4,7 +4,7 @@
// Каждое правило здесь — бывшая строка прозаической конвенции: у него есть // Каждое правило здесь — бывшая строка прозаической конвенции: у него есть
// детерминированный оракул, поэтому ему место в конвейере сборки, а не в // детерминированный оракул, поэтому ему место в конвейере сборки, а не в
// промпте ревью (процедура промоута — references/promote.md скилла // промпте ревью (процедура промоута — references/promote.md скилла
// av-dev-pipeline:review-pipeline). // av-dev:code-review).
package archrules package archrules
import ( import (
+2 -2
View File
@@ -32,8 +32,8 @@ context: |
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
первым молча, и заметно это становится в предложении, которое уже написано. первым молча, и заметно это становится в предложении, которое уже написано.
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом Ревью: состав проходов здесь не пересказываем — их дом скилл
скилл av-dev-pipeline:review-pipeline, проектная настройка — docs/review.md. av-dev:code-review, проектная настройка — docs/review.md.
Конвенции кода: механизированное проверяет `task gate`, прозой остаётся Конвенции кода: механизированное проверяет `task gate`, прозой остаётся
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
+14 -15
View File
@@ -26,20 +26,19 @@ from pathlib import Path
OUT_DIR = Path("tmp/gate") OUT_DIR = Path("tmp/gate")
# Скрипты трёх плагинов av-dev. Каждый проверяет своё и ни один не тянет # Скрипты плагина av-dev. Каждый проверяет своё и ни один не тянет остальных:
# остальных: без своего шага дрейф соответствующего каталога перестаёт ловиться # без своего шага дрейф соответствующего каталога перестаёт ловиться молча.
# молча. # docs.py — раскладка docs/ против канона (av-dev:canon)
# docs.py раскладка docs/ против канона (av-dev-docs:canon) # tasks.py — согласованность индексов каталога задач (av-dev:task-track)
# tasks.py — согласованность индексов каталога задач (av-dev-tasks:tasks) # openspec.py — форма openspec/config.yaml (av-dev:code-openspec)
# openspec.py — форма openspec/config.yaml (av-dev-code:openspec)
# Путь берётся из каталога маркетплейса (он без хеша версии, в отличие от # Путь берётся из каталога маркетплейса (он без хеша версии, в отличие от
# cache/), переопределяется переменной: переустановка плагина не должна # cache/), переопределяется переменной: переустановка плагина не должна
# требовать правки гейта. Если скрипта нет — шаг краснеет, а не пропускается: # требовать правки гейта. Если скрипта нет — шаг краснеет, а не пропускается:
# молча пропущенная проверка даёт ложное ощущение проверенности. # молча пропущенная проверка даёт ложное ощущение проверенности.
MARKETPLACE = Path.home() / ".claude/plugins/marketplaces/av-dev-skills" MARKETPLACE = Path.home() / ".claude/plugins/marketplaces/av-dev-skills"
DOCS_PY_DEFAULT = MARKETPLACE / "av-dev-docs/skills/canon/scripts/docs.py" DOCS_PY_DEFAULT = MARKETPLACE / "av-dev/skills/canon/scripts/docs.py"
TASKS_PY_DEFAULT = MARKETPLACE / "av-dev-tasks/skills/tasks/scripts/tasks.py" TASKS_PY_DEFAULT = MARKETPLACE / "av-dev/skills/task-track/scripts/tasks.py"
OPENSPEC_PY_DEFAULT = MARKETPLACE / "av-dev-code/skills/openspec/scripts/openspec.py" OPENSPEC_PY_DEFAULT = MARKETPLACE / "av-dev/skills/code-openspec/scripts/openspec.py"
OK, FAIL, WARN, SKIP = "OK", "FAIL", "WARN", "SKIP" OK, FAIL, WARN, SKIP = "OK", "FAIL", "WARN", "SKIP"
@@ -219,17 +218,17 @@ def main() -> int:
# а database.md нет» (отдельного шага er-schema поэтому больше нет). # а database.md нет» (отдельного шага er-schema поэтому больше нет).
# База диффа обязательна: без неё сверка миграций со схемой не гоняется. # База диффа обязательна: без неё сверка миграций со схемой не гоняется.
plugin_step( plugin_step(
"canon", "DOCS_PY", DOCS_PY_DEFAULT, "av-dev-docs", "canon", "DOCS_PY", DOCS_PY_DEFAULT, "av-dev",
["check", "--dir", ".", "--base", base], ["check", "--dir", ".", "--base", base],
"раскладка docs/ разошлась с каноном", "раскладка docs/ разошлась с каноном",
) )
# --- Каталог задач --- # --- Каталог задач ---
# Согласованность индексов с файлами items/: бездомная запись, строка без # Согласованность беклога с файлами items/: бездомная запись, строка без
# файла, порядок секций роадмапа, сырьё не в конце секции. docs.py этого не # файла, стадия проекта, сырьё не в конце секции. docs.py этого не смотрит —
# смотрит — каталог принадлежит другому плагину. # каталог задач он не ведёт.
plugin_step( plugin_step(
"tasks", "TASKS_PY", TASKS_PY_DEFAULT, "av-dev-tasks", "tasks", "TASKS_PY", TASKS_PY_DEFAULT, "av-dev",
["check", "--dir", "tasks"], ["check", "--dir", "tasks"],
"индексы задач разошлись с items/", "индексы задач разошлись с items/",
) )
@@ -239,7 +238,7 @@ def main() -> int:
# а работает как пустой. Ловит это только свой шаг: docs.py про config.yaml # а работает как пустой. Ловит это только свой шаг: docs.py про config.yaml
# молчит с канона 10. # молчит с канона 10.
plugin_step( plugin_step(
"openspec", "OPENSPEC_PY", OPENSPEC_PY_DEFAULT, "av-dev-code", "openspec", "OPENSPEC_PY", OPENSPEC_PY_DEFAULT, "av-dev",
["check", "--dir", "."], ["check", "--dir", "."],
"openspec/config.yaml разошёлся с канонической формой", "openspec/config.yaml разошёлся с канонической формой",
) )
-3
View File
@@ -1,3 +0,0 @@
{
"tasks": 1
}
+15 -6
View File
@@ -1,12 +1,21 @@
# Беклог # Беклог
Что **можно взять**. Одна задача = один файл `items/<slug>.md` Что **можно взять**. Одна задача = один файл `items/<slug>.md`
+ строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что + строка здесь. Ведётся скиллом `av-dev:task-track`.
берут, роадмап — то, подо что берут. Ведётся скиллом `av-dev-tasks:tasks`.
**Порядок строк внутри секции — это приоритет.** Первая строка секции — то, что <!-- стадия -->
делают следующим. Порядок назначает человек на груминге Стадия проекта — **доработка** (`[tasks] stage = "support"`).
(`av-dev-tasks:groom`), машина его не выводит. **Порядок строк внутри секции — важность:** первая строка это то,
что делают следующим. Назначает его человек на груминге, машина не
выводит. Секции — полки домена, смысла они не несут. Заводится по
одной, по мере появления; пустой беклог — нормальное состояние.
<!-- /стадия -->
Одно место в очереди назначено не человеком, а типом: сырьё (`research` без
раздела «Вопрос») стоит в конце секции — его не берут.
Тип записи стоит первым полем меты и решает, что у неё может быть:
`feature` 🐞 `fix` 🧹 `chore` 🔬 `research`
Секции «блокеры» здесь нет и не заводится: блокер — это состояние, оно живёт до Секции «блокеры» здесь нет и не заводится: блокер — это состояние, оно живёт до
ответа человека, а его следы — вопросами в файлах задач. ответа человека, а его следы — вопросами в файлах задач.
@@ -41,7 +50,7 @@
## Инфраструктура ## Инфраструктура
- [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации - [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
- [🧹 Откалибровать проходы ревью и завести ревьювер наименований](items/quality-review-agents.md) — конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка) - [🧹 Откалибровать проходы ревью и завести ревьювер наименований](items/quality-review-agents.md) — конвейер ревью переехал в плагин `av-dev`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
- [✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало](items/web-ui-auth.md) — для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита - [✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало](items/web-ui-auth.md) — для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
- [🧹 Бэкапить SQLite по расписанию с ротацией](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая) - [🧹 Бэкапить SQLite по расписанию с ротацией](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
- [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI - [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
+7
View File
@@ -7,3 +7,10 @@
<!-- - ГГГГ-ММ-ДД `slug` — Заголовок. Причина: … Была секция: … --> <!-- - ГГГГ-ММ-ДД `slug` — Заголовок. Причина: … Была секция: … -->
- 2026-08-06 `learn-from-user-corrections` — ✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot). Причина: решено не собирать базу правок человека: направление — тюнинг автоматического распознавания без участия человека. Была секция: ядро продукта. - 2026-08-06 `learn-from-user-corrections` — ✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot). Причина: решено не собирать базу правок человека: направление — тюнинг автоматического распознавания без участия человека. Была секция: ядро продукта.
- 2026-08-06 `recognition-eval-harness` — 🧹 Завести eval-харнес распознавания: корпус кейсов и метрику точности. Причина: решено не собирать размеченный корпус руками: направление — тюнинг автоматического распознавания без участия человека. Была секция: инфраструктура. - 2026-08-06 `recognition-eval-harness` — 🧹 Завести eval-харнес распознавания: корпус кейсов и метрику точности. Причина: решено не собирать размеченный корпус руками: направление — тюнинг автоматического распознавания без участия человека. Была секция: инфраструктура.
- 2026-09-02 `recognition-accuracy` — 🎯 Раздача узнаётся верно без подсказок человека. Причина: тип goal упразднён каноном раскладки 3; зонтик над точностью распознавания снят, работы живут строками беклога сами (tvdb-search-response-live-check, auto-link-confidence-gate, multi-pass-verification, candidate-match-strength, guessit-sidecar). Была секция: Направления.
- 2026-09-02 `complex-releases` — 🎯 Раскладывается не только типовая раздача. Причина: тип goal упразднён каноном раскладки 3; зонтик над нетиповыми раздачами снят, работы живут строками беклога сами (anime-absolute-numbering, disc-image-releases, merge-incremental-redownload, external-subtitles, naming-edge-cases, complex-series-releases, episode-numbering-canon, title-versions-repacks). Была секция: Направления.
- 2026-09-02 `ingest-and-review-interfaces` — 🎯 Раздача приносится и подтверждается из любого транспорта. Причина: тип goal упразднён каноном раскладки 3; зонтик над приёмом и ревью снят, работы живут строками беклога сами (torrent-url-fetch, review-mapping-editor, notification-source-binding, sse-live-updates, web-ui-pwa, web-ui-auth, telegram-messages-audit, completion-webhook). Была секция: Направления.
- 2026-09-02 `state-integrity` — 🎯 По записи загрузки видно, как она сюда попала. Причина: тип goal упразднён каноном раскладки 3; зонтик над целостностью состояния снят, работы живут строками беклога сами (delete-checks-active-infohash-owner, delete-leaves-stale-done, download-transition-history, infohash-identity-integrity). Была секция: Направления.
- 2026-09-02 `operational-resilience` — 🎯 Сервис переживает рост и потерю тома. Причина: тип goal упразднён каноном раскладки 3; зонтик над эксплуатационной устойчивостью снят, работы живут строками беклога сами (sqlite-backup, db-retention-cleanup, deep-healthcheck-dependencies, metadata-cache, free-space-check-copy-fallback, scale-100-downloads). Была секция: Сопровождение.
- 2026-09-02 `dev-process-quality` — 🎯 Домен называется одинаково везде, ревью откалибровано. Причина: тип goal упразднён каноном раскладки 3; зонтик над качеством процесса снят, работы живут строками беклога сами (ubiquitous-language-glossary, quality-review-agents, tests-convention, convention-candidates). Была секция: Сопровождение.
- 2026-09-02 `bulk-download-management` — 🎯 Загрузки удаляются пачкой, а не по одной. Причина: тип goal упразднён каноном раскладки 3; групповое удаление раздач в беклог не берётся — по решению человека при повышении раскладки заводится заново, когда уборка десятка раздач станет болью. Была секция: Направления.
-35
View File
@@ -1,35 +0,0 @@
# Роадмап
Что приложение уже умеет и чего ещё не умеет. Цель — файл типа `goal` в
`items/`; её задачи здесь **не перечисляются** — перечень даёт
`tasks.py list --goal <слаг>`. В `Запланировано` очередь значима и
обосновывается прозой; в `Направлениях` порядка нет; `Сопровождение` — то, чем
держат проект, а не возможности приложения; в `Готово` строку с датой пишет
`close --implemented`.
## Запланировано
Пусто. Фазы Ф0–Ф6 прежней дорожной карты (каркас, приём и трекинг,
распознавание, раскладка и ревью, метаданные, Telegram и UX, деплой) закрыты —
сквозной путь работает и развёрнут; закрытый шаг планом больше не является.
Следующая упорядоченная очередь появится, когда она понадобится.
## Направления
- [🎯 Раздача узнаётся верно без подсказок человека](items/recognition-accuracy.md) — распознавание ошибается молча и правдоподобно, а смена модели или правка промпта идёт вслепую — сдвига точности не видно ни до, ни после
- [🎯 Раскладывается не только типовая раздача](items/complex-releases.md) — типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
- [🎯 Раздача приносится и подтверждается из любого транспорта](items/ingest-and-review-interfaces.md) — путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
- [🎯 По записи загрузки видно, как она сюда попала](items/state-integrity.md) — известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
- [🎯 Загрузки удаляются пачкой, а не по одной](items/bulk-download-management.md) — удаление раздачи с файлами доступно только по одной кнопке на странице одной загрузки — уборка десятка раздач превращается в десяток заходов
## Сопровождение
- [🎯 Сервис переживает рост и потерю тома](items/operational-resilience.md) — сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
- [🎯 Домен называется одинаково везде, ревью откалибровано](items/dev-process-quality.md) — наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
## Готово
Пусто. Фазы Ф0–Ф6 велись прозой и целями в роадмапе не числились, поэтому
строк с датами за ними нет; что было сделано и когда — по архиву
`openspec/changes/archive/`. Первую строку сюда впишет `close --implemented`
на первой достигнутой цели.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта — уступила верх четырём мелочам с готовыми оракулами: сама задача жива и остаётся первой фичей ядра, но стоит дороже и в ближайший заход не влезает - **Категория:** Ядро продукта — уступила верх четырём мелочам с готовыми оракулами: сама задача жива и остаётся первой фичей ядра, но стоит дороже и в ближайший заход не влезает
- **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации - **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
- **Теги:** goal:complex-releases
Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию — надёжнее всего через TVDB (там есть absolute order). Отдельный крайний случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E». Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию — надёжнее всего через TVDB (там есть absolute order). Отдельный крайний случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта — тот же internal/recognize, что и первая задача — окно открыто; решение принято 2026-07-08, осталось снять пере-применение дефолта, понизить 0.85→0.7 и записать в спеку - **Категория:** Ядро продукта — тот же internal/recognize, что и первая задача — окно открыто; решение принято 2026-07-08, осталось снять пере-применение дефолта, понизить 0.85→0.7 и записать в спеку
- **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку - **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
- **Теги:** goal:recognition-accuracy
Аудит спек↔код (2026-07-03) нашёл расхождение: спека recognition считает Аудит спек↔код (2026-07-03) нашёл расхождение: спека recognition считает
`confidence` вспомогательным сигналом (условия авто — только матч в базе + `confidence` вспомогательным сигналом (условия авто — только матч в базе +
-23
View File
@@ -1,23 +0,0 @@
# 🎯 Загрузки удаляются пачкой, а не по одной
- **Тип:** goal
- **Секция:** Направления
- **Зачем:** удаление раздачи с файлами доступно только по одной кнопке на странице одной загрузки — уборка десятка раздач превращается в десяток заходов
Ради чего: разложенные раздачи копятся, и убирают их обычно скопом — после
просмотра сезона, при чистке диска, после серии неудачных заливок. Сегодня
удаление живёт только в danger-секции страницы одной загрузки, поэтому уборка
десяти раздач стоит десяти проходов «список → карточка → подтверждение».
При этом удаление с файлами необратимо: оно зовёт `torrents/delete` с
`deleteFiles=true`, а гард последней копии там выключен сознательно
(инвариант «Источник неприкосновенен», исключение 1). Групповой режим обязан
сделать уборку дешевле, не сделав ошибку дешевле.
## Завершение
Достигнута, когда человек убирает любое число раздач одним проходом: выбирает
их в списке, один раз подтверждает удаление по перечню, где каждая раздача
названа поимённо, и видит поимённый результат — что снесено, что отказало и
почему. Ни одна раздача не сносится без того, чтобы человек увидел её в
подтверждении.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга) - **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
- **Теги:** goal:recognition-accuracy
У кандидата метабазы нет метрики силы совпадения (metadata_candidate хранит provider/id/title/year/url), решение «авто vs review» — по правилу «единственный сильный матч + валидация», не по числу. Для ревью: список кандидатов нечем отсортировать/подсветить по уверенности. Идея — ввести на этапе матча силу совпадения кандидата (точное совпадение названия+года vs частичное) для сортировки и подсказки в UI. Шире — продумать сам процесс распознавания и матчинга: границы «разбор LLM / поиск в базе / сверка», что храним у кандидата, как считаем и показываем уверенность. У кандидата метабазы нет метрики силы совпадения (metadata_candidate хранит provider/id/title/year/url), решение «авто vs review» — по правилу «единственный сильный матч + валидация», не по числу. Для ревью: список кандидатов нечем отсортировать/подсветить по уверенности. Идея — ввести на этапе матча силу совпадения кандидата (точное совпадение названия+года vs частичное) для сортировки и подсказки в UI. Шире — продумать сам процесс распознавания и матчинга: границы «разбор LLM / поиск в базе / сверка», что храним у кандидата, как считаем и показываем уверенность.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации) - **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
- **Теги:** goal:ingest-and-review-interfaces
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent. Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent.
-12
View File
@@ -1,12 +0,0 @@
# 🎯 Раскладывается не только типовая раздача
- **Тип:** goal
- **Секция:** Направления
- **Зачем:** типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
- **Теги:** decomposed
Ради чего: сериальные паки, докачивание, аниме со сквозной нумерацией, образы дисков и внешние субтитры — это ровно тот контент, ради которого проект и заводился вместо arr-стека.
## Завершение
Достигнута, когда сериальный пак, докачивание недостающих серий, аниме со сквозной нумерацией, образ диска и внешние субтитры раскладываются без ручного вмешательства в файлы на диске — либо честно уходят в ревью с названной причиной, а не молча кладутся неверно.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон - **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
- **Теги:** goal:complex-releases
Обычный случай — один сезон (его номер видно глазами и сверяем на ревью — под это сделана сводка сезонов). Но в редких заказах раздача сложнее: все сезоны сериала разом, пак нескольких сезонов, смешанная нумерация, вложенные папки сезонов, разнобойные имена файлов. Сейчас PlanFile.Season задаётся на каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не проработаны: как надёжно распознать, как показать на ревью, как разложить и как стыкуется со сходимостью папки и merge-докачиванием. Решить, что поддерживаем явно, а что уводим в ревью как «сложную раскладку». Обычный случай — один сезон (его номер видно глазами и сверяем на ревью — под это сделана сводка сезонов). Но в редких заказах раздача сложнее: все сезоны сериала разом, пак нескольких сезонов, смешанная нумерация, вложенные папки сезонов, разнобойные имена файлов. Сейчас PlanFile.Season задаётся на каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не проработаны: как надёжно распознать, как показать на ревью, как разложить и как стыкуется со сходимостью папки и merge-докачиванием. Решить, что поддерживаем явно, а что уводим в ревью как «сложную раскладку».
+1 -2
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд - **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
- **Теги:** goal:dev-process-quality
Список копился в черновике `docs/drafts/conventions-backlog.md` (удалён при Список копился в черновике `docs/drafts/conventions-backlog.md` (удалён при
переводе на канон, текст в истории git) под правилом «пишем по мере реального переводе на канон, текст в истории git) под правилом «пишем по мере реального
@@ -46,7 +45,7 @@
- **Время.** Явный TZ всегда, хранение и логи в UTC. Уже частично в `CLAUDE.md` - **Время.** Явный TZ всегда, хранение и логи в UTC. Уже частично в `CLAUDE.md`
и `conventions/logging.md`, а `time.Now` вне `store` запрещён линтером — этот и `conventions/logging.md`, а `time.Now` вне `store` запрещён линтером — этот
пункт, вероятно, закрыт и подлежит вычёркиванию. пункт, вероятно, закрыт и подлежит вычёркиванию.
- **Язык вывода связан с мапперами.** Провенанс — ревью `tvdb-title-locale` - **Язык вывода связан с мапперами.** Откуда — ревью `tvdb-title-locale`
(2026-08-07, (2026-08-07,
[отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md), [отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md),
находка R2 прохода `architecture`). Язык вывода живёт в пяти местах четырёх находка R2 прохода `architecture`). Язык вывода живёт в пяти местах четырёх
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации - **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
- **Теги:** goal:operational-resilience
Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми ответами LLM и metadata_candidate копятся вечно — БД и список загрузок распухают и становятся нечитаемыми. Нужна авточистка старше N дней (настройка в [storage] или [worker]) и/или ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере эксплуатации. Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми ответами LLM и metadata_candidate копятся вечно — БД и список загрузок распухают и становятся нечитаемыми. Нужна авточистка старше N дней (настройка в [storage] или [worker]) и/или ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере эксплуатации.
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI - **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
- **Теги:** goal:operational-resilience
/healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent недоступен»), чтобы причина простоя была видна сразу. /healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent недоступен»), чтобы причина простоя была видна сразу.
@@ -3,7 +3,6 @@
- **Тип:** fix - **Тип:** fix
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** удаление старой закрытой задачи уничтожает файлы живой загрузки с тем же инфохэшем; воспроизведено падающим тестом на ревью bulk-delete-page - **Зачем:** удаление старой закрытой задачи уничтожает файлы живой загрузки с тем же инфохэшем; воспроизведено падающим тестом на ревью bulk-delete-page
- **Теги:** goal:state-integrity
`Delete` зовёт `torrents/delete` с `deleteFiles=true` по хешам своей записи и не `Delete` зовёт `torrents/delete` с `deleteFiles=true` по хешам своей записи и не
спрашивает, не владеет ли этим инфохэшем другая **активная** загрузка. Человек спрашивает, не владеет ли этим инфохэшем другая **активная** загрузка. Человек
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** fix - **Тип:** fix
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** при недоступном qBittorrent задача весь простой соседа показывает done, хотя тайтла в Jellyfin уже нет: сверка падает на первом шаге и до коррекции не доходит - **Зачем:** при недоступном qBittorrent задача весь простой соседа показывает done, хотя тайтла в Jellyfin уже нет: сверка падает на первом шаге и до коррекции не доходит
- **Теги:** goal:state-integrity
`Delete` снимает библиотечные ссылки раньше, чем зовёт `qbt.Delete`. Если сосед `Delete` снимает библиотечные ссылки раньше, чем зовёт `qbt.Delete`. Если сосед
недоступен, локальный шаг проходит, внешний падает, и задача остаётся в `done`. недоступен, локальный шаг проходит, внешний падает, и задача остаётся в `done`.
-12
View File
@@ -1,12 +0,0 @@
# 🎯 Домен называется одинаково везде, ревью откалибровано
- **Тип:** goal
- **Секция:** Сопровождение
- **Зачем:** наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
- **Теги:** decomposed
Ради чего: это не поведение продукта, а то, чем он делается. Единый словарь, калибровка проходов ревью и разбор накопленных кандидатов в конвенции — вложение в скорость всех остальных целей.
## Завершение
Достигнута, когда домен называется одинаково в спеках, коде и интерфейсе, а конвейер ревью откалиброван на журнале реальных дефектов, а не на догадках о том, что он ловит.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** раздача-образ Blu-ray разбирается пофайлово и едет в библиотеку россыпью .m2ts, а Jellyfin умеет такой каталог целиком - **Зачем:** раздача-образ Blu-ray разбирается пофайлово и едет в библиотеку россыпью .m2ts, а Jellyfin умеет такой каталог целиком
- **Теги:** goal:complex-releases
Иногда для очень редкого фильма качается не один видеофайл, а полная копия Иногда для очень редкого фильма качается не один видеофайл, а полная копия
диска — каталог `BDMV/` (Blu-ray) или `VIDEO_TS/` (DVD). Раскладка сегодня диска — каталог `BDMV/` (Blu-ray) или `VIDEO_TS/` (DVD). Раскладка сегодня
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов - **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
- **Теги:** goal:state-integrity
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки и уже реализованный экран /download/{id}. Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки и уже реализованный экран /download/{id}.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега - **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
- **Теги:** goal:complex-releases
Косметика и редкий случай: порядок просмотра не страдает (файлы уже Косметика и редкий случай: порядок просмотра не страдает (файлы уже
пронумерованы канонически и лежат по порядку), разъезжаются только подписи серий пронумерованы канонически и лежат по порядку), разъезжаются только подписи серий
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags - **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
- **Теги:** goal:complex-releases
Базовая привязка субтитр→серия для сериала уже работает: `layout.PlanFile` несёт Базовая привязка субтитр→серия для сериала уже работает: `layout.PlanFile` несёт
`Season/Episode`, а `seriesDst` именует субтитр по стему эпизода `Season/Episode`, а `seriesDst` именует субтитр по стему эпизода
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования - **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
- **Теги:** goal:operational-resilience
Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске. На забитом диске это упрётся в полку посреди раскладки. Перед копированием проверять доступное место и при нехватке внятно уходить в failed с понятной причиной, а не падать на полпути. Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске. На забитом диске это упрётся в полку посреди раскладки. Перед копированием проверять доступное место и при нехватке внятно уходить в failed с понятной причиной, а не падать на полпути.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём - **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
- **Теги:** goal:recognition-accuracy
go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit в крошечный HTTP-сервис (один файл, поставляется рядом с бинарём jellybit) и спрашивать его на шаге пред-парса. Сохраняет «доставку копированием»: два файла вместо одного. go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit в крошечный HTTP-сервис (один файл, поставляется рядом с бинарём jellybit) и спрашивать его на шаге пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта — сырьё, а не дефект: тело само не решает — «change по модели доверия/идентичности либо задокументировать как ограничение», и критерии приёмки писать не из чего; идёт на штурм, взять нельзя - **Категория:** Ядро продукта — сырьё, а не дефект: тело само не решает — «change по модели доверия/идентичности либо задокументировать как ограничение», и критерии приёмки писать не из чего; идёт на штурм, взять нельзя
- **Зачем:** две находки ревью 2026-07-08 упираются в нерешённую модель идентичности: связывать ли v1- и v2-хеши одного торрента и что делать с парой xt, которую qBittorrent не подтверждал — merge, supersede или ограничение в документе - **Зачем:** две находки ревью 2026-07-08 упираются в нерешённую модель идентичности: связывать ли v1- и v2-хеши одного торрента и что делать с парой xt, которую qBittorrent не подтверждал — merge, supersede или ограничение в документе
- **Теги:** goal:state-integrity
Ревью Fable 2026-07-08 (приём). Две связанные находки о доверии к парам xt в magnet (предпосылки к F1). Ревью Fable 2026-07-08 (приём). Две связанные находки о доверии к парам xt в magnet (предпосылки к F1).
@@ -1,12 +0,0 @@
# 🎯 Раздача приносится и подтверждается из любого транспорта
- **Тип:** goal
- **Секция:** Направления
- **Зачем:** путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
- **Теги:** decomposed
Ради чего: приём и ревью — единственные места, где система встречается с человеком. Здесь копятся незакрытые куски: фетч по URL, редактор маппинга, привязка уведомлений к автору, латентность обновлений.
## Завершение
Достигнута, когда любой из поддержанных источников принимается одним действием из любого транспорта, а ревью позволяет довести план до применимого состояния без ухода в другой инструмент.
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов - **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
- **Теги:** goal:complex-releases
Свежий сериал раздают по мере выхода: торрент с 5 из 10 эпизодов позже перезаливают целиком, пользователь добавляет раздачу повторно. Новая загрузка приходит в ту же папку за счёт правила сходимости, а раскладка становится merge — доложить только недостающее. Существующие пути не трогаем (never-overwrite, владение у старой загрузки), новые кладём (владеет новая). Split-ownership сезона принят как норма per-path модели; обе раздачи сидируют независимо. Свежий сериал раздают по мере выхода: торрент с 5 из 10 эпизодов позже перезаливают целиком, пользователь добавляет раздачу повторно. Новая загрузка приходит в ту же папку за счёт правила сходимости, а раскладка становится merge — доложить только недостающее. Существующие пути не трогаем (never-overwrite, владение у старой загрузки), новые кладём (владеет новая). Split-ownership сезона принят как норма per-path модели; обе раздачи сидируют независимо.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** chore - **Тип:** chore
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново» - **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
- **Теги:** goal:operational-resilience
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками). Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками).
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения - **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
- **Теги:** goal:recognition-accuracy
Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Проработать: когда включать, как мерджить расхождения, стоимость/латентность. Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Проработать: когда включать, как мерджить расхождения, стоимость/латентность.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена - **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
- **Теги:** goal:complex-releases
Целевые имена для типового фильма и типового сезона заказаны Целевые имена для типового фильма и типового сезона заказаны
[file-layout](../../openspec/specs/file-layout/spec.md). Крайние случаи там [file-layout](../../openspec/specs/file-layout/spec.md). Крайние случаи там
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота) - **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
- **Теги:** goal:ingest-and-review-interfaces
Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему. Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему.
-12
View File
@@ -1,12 +0,0 @@
# 🎯 Сервис переживает рост и потерю тома
- **Тип:** goal
- **Секция:** Сопровождение
- **Зачем:** сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
- **Теги:** decomposed
Ради чего: сегодня всё держится на том, что загрузок мало и всё рядом работает. Ретеншена нет, бэкапа нет, глубокого healthcheck нет, поведение под сотней загрузок не мерялось.
## Завершение
Достигнута, когда база не растёт бесконечно, состояние переживает потерю тома, отказ любой зависимости виден владельцу раньше, чем по застрявшим задачам, и поведение под сотней одновременных загрузок измерено, а не предположено.
+2 -3
View File
@@ -2,8 +2,7 @@
- **Тип:** chore - **Тип:** chore
- **Категория:** Инфраструктура — уступила первую строку: ready не проходит, а часть про ревьювер наименований ждёт словарь единого языка - **Категория:** Инфраструктура — уступила первую строку: ready не проходит, а часть про ревьювер наименований ждёт словарь единого языка
- **Зачем:** конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка) - **Зачем:** конвейер ревью переехал в плагин `av-dev`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
- **Теги:** goal:dev-process-quality
Набор проходов ревью поверх ревью-процесса из CLAUDE.md. Развивает ревью-процесс Набор проходов ревью поверх ревью-процесса из CLAUDE.md. Развивает ревью-процесс
OpenSpec в сторону воспроизводимых автопроверок, не заменяя человеческое ревью. OpenSpec в сторону воспроизводимых автопроверок, не заменяя человеческое ревью.
@@ -49,7 +48,7 @@ OpenSpec в сторону воспроизводимых автопроверо
оптикой не выделен: зависит от задачи «Словарь единого языка», без глоссария оптикой не выделен: зависит от задачи «Словарь единого языка», без глоссария
проверять не по чему. Завести после неё. проверять не по чему. Завести после неё.
- **Калибровка проходов** по процедуре `references/calibration.md` скилла - **Калибровка проходов** по процедуре `references/calibration.md` скилла
`av-dev-pipeline:review-pipeline` — ни один проход ещё не замерен инъекцией. `av-dev:code-review` — ни один проход ещё не замерен инъекцией.
До замера ничего не удаляем и промпты не правим. До замера ничего не удаляем и промпты не правим.
- **Проходы не сообщают свой потолок находок.** На прогоне `tvdb-title-locale` - **Проходы не сообщают свой потолок находок.** На прогоне `tvdb-title-locale`
(2026-08-07) о потолке промолчали четверо из шести — `autotests`, `specs`, (2026-08-07) о потолке промолчали четверо из шести — `autotests`, `specs`,
-14
View File
@@ -1,14 +0,0 @@
# 🎯 Раздача узнаётся верно без подсказок человека
- **Тип:** goal
- **Секция:** Направления
- **Зачем:** распознавание ошибается молча и правдоподобно, а смена модели или правка промпта идёт вслепую — сдвига точности не видно ни до, ни после
- **Теги:** decomposed
Ради чего: распознавание — единственное место, где система может ошибиться молча и правдоподобно. Сегодня о его точности судят по впечатлению, и сдвиг от смены модели или правки промпта заметен только задним числом.
Размеченный корпус и обучение на правках человека из этой цели исключены сознательно (`REJECTED.md`, 2026-08-06): базу руками не собираем, точность поднимаем тюнингом автоматического распознавания.
## Завершение
Достигнута, когда решение auto/review опирается на измеримую силу совпадения с записью метабазы, а не на самооценку модели, и доля раздач, ушедших в ревью или поправленных после авто-раскладки, видна по рабочему потоку и не растёт от версии к версии.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом - **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
- **Теги:** goal:ingest-and-review-interfaces
Когда распознавание разложило файлы по сериям неверно, единственный путь — Когда распознавание разложило файлы по сериям неверно, единственный путь —
подсказать текстом и перераспознать. Точечно поправить номер серии у одного подсказать текстом и перераспознать. Точечно поправить номер серии у одного
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг) - **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
- **Теги:** goal:operational-resilience
Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в НФТ целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: задаёт рамку для решений ниже. Отдельно (дороже) — аудит узких мест: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве. Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в НФТ целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: задаёт рамку для решений ниже. Отдельно (дороже) — аудит узких мест: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** chore - **Тип:** chore
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая) - **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
- **Теги:** goal:operational-resilience
architecture.md требует «бекапить data-том», но как — не описано. Без понятной стратегии сбой или редеплой стирают всё in-flight состояние. Зафиксировать решение и реализовать: периодический VACUUM INTO в /data/backups по расписанию (с ротацией) либо потоковая репликация (litestream). Лучше сделать, пока БД маленькая. architecture.md требует «бекапить data-том», но как — не описано. Без понятной стратегии сбой или редеплой стирают всё in-flight состояние. Зафиксировать решение и реализовать: периодический VACUUM INTO в /data/backups по расписанию (с ротацией) либо потоковая репликация (litestream). Лучше сделать, пока БД маленькая.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер) - **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
- **Теги:** goal:ingest-and-review-interfaces
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда. Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда.
-12
View File
@@ -1,12 +0,0 @@
# 🎯 По записи загрузки видно, как она сюда попала
- **Тип:** goal
- **Секция:** Направления
- **Зачем:** известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
- **Теги:** decomposed
Ради чего: состояние загрузки — то, по чему судят обо всём остальном. Накопились известные щели: окно namer'а, идентичность split v1/v2, потеря маркера dismiss, отсутствие истории переходов.
## Завершение
Достигнута, когда по записи загрузки можно ответить «как она сюда попала», ни один известный сегодня путь не оставляет состояние, которое не объясняется историей переходов, и идентичность раздачи не подделывается входом.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи - **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
- **Теги:** goal:ingest-and-review-interfaces
Зонтичная задача: пройтись по всем исходящим уведомлениям и запросам подтверждения Зонтичная задача: пройтись по всем исходящим уведомлениям и запросам подтверждения
бота, выправить формулировки, состав данных и оформление. Тексты формируются в бота, выправить формулировки, состав данных и оформление. Тексты формируются в
+1 -1
View File
@@ -24,7 +24,7 @@
**Два кандидата пришли из ревью `tvdb-title-locale`** (2026-08-07, **Два кандидата пришли из ревью `tvdb-title-locale`** (2026-08-07,
[отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md) [отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md)
→ «Promote candidates»), оба с провенансом прохода, а не из головы: → «Promote candidates»), оба с названным проходом, а не из головы:
- **один стенд чужого API на пакет.** В `tvdb_test.go` завелись два фейка одного - **один стенд чужого API на пакет.** В `tvdb_test.go` завелись два фейка одного
и того же API. Когда форма ответа поменяется по факту разведки, забытый и того же API. Когда форма ответа поменяется по факту разведки, забытый
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало - **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало
- **Теги:** goal:complex-releases
По калибровке болей (2026-07-02) — не боль, из приоритета выпало. Сосуществование версий доступно уже сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же путь штатно уходит в review. Явный replace (undo старого хардлинка → lay нового → супересид владения путём) — отдельный change, если/когда станет болью. По калибровке болей (2026-07-02) — не боль, из приоритета выпало. Сосуществование версий доступно уже сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же путь штатно уходит в review. Явный replace (undo старого хардлинка → lay нового → супересид владения путём) — отдельный change, если/когда станет болью.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард) - **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
- **Теги:** goal:ingest-and-review-interfaces
Приём magnet и `.torrent`-файла уже реализован: ветка `TorrentData → torrent.Parse` Приём magnet и `.torrent`-файла уже реализован: ветка `TorrentData → torrent.Parse`
(`internal/ingest/ingest.go`), файл-пикер в веб-форме (`web/templates/index.html`), (`internal/ingest/ingest.go`), файл-пикер в веб-форме (`web/templates/index.html`),
@@ -3,7 +3,6 @@
- **Тип:** research - **Тип:** research
- **Категория:** Ядро продукта — дешевле всех и проверяет только что сделанное: при иной форме ответа TheTVDB локализация молча уходит в фолбэк, а гейт зелёный - **Категория:** Ядро продукта — дешевле всех и проверяет только что сделанное: при иной форме ответа TheTVDB локализация молча уходит в фолбэк, а гейт зелёный
- **Зачем:** форма ответа поиска TheTVDB принята по swagger 4.7.10 и живым прогоном не подтверждена — при иной форме разбор молча уходит в фолбэк, гейт зелёный, локализованное название не работает - **Зачем:** форма ответа поиска TheTVDB принята по swagger 4.7.10 и живым прогоном не подтверждена — при иной форме разбор молча уходит в фолбэк, гейт зелёный, локализованное название не работает
- **Теги:** goal:recognition-accuracy
Задача `tvdb-title-locale` научила клиент TVDB брать локализованное название из Задача `tvdb-title-locale` научила клиент TVDB брать локализованное название из
блока переводов ответа `/search` и заполнять `OriginalTitle` primary name'ом. Но блока переводов ответа `/search` и заполнять `OriginalTitle` primary name'ом. Но
@@ -3,7 +3,6 @@
- **Тип:** chore - **Тип:** chore
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований) - **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
- **Теги:** goal:dev-process-quality
Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований. Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Инфраструктура - **Категория:** Инфраструктура
- **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита - **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
- **Теги:** goal:ingest-and-review-interfaces
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей (http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита: токен/Basic в самом приложении или вынос за reverse-proxy с аутентификацией. Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей (http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита: токен/Basic в самом приложении или вынос за reverse-proxy с аутентификацией.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** feature - **Тип:** feature
- **Категория:** Ядро продукта - **Категория:** Ядро продукта
- **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер - **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
- **Теги:** goal:ingest-and-review-interfaces
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы. Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы.