раскладка 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:
@@ -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"
|
||||
@@ -2,9 +2,7 @@
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"av-dev-git@av-dev-skills": true,
|
||||
"av-dev-docs@av-dev-skills": true,
|
||||
"av-dev-tasks@av-dev-skills": true,
|
||||
"av-dev-code@av-dev-skills": true
|
||||
"av-dev@av-dev-skills": true
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"av-dev-skills": {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# staticcheck, unused. Сверх него включены линтеры, которые механизируют
|
||||
# конвенции из docs/conventions/*: то, что проверяет правило, не должно
|
||||
# оставаться прозой в конвенциях и в промптах ревью (процедура промоута —
|
||||
# references/promote.md скилла av-dev-pipeline:review-pipeline; перечень уже
|
||||
# references/promote.md скилла av-dev:code-review; перечень уже
|
||||
# механизированного — docs/conventions/README.md).
|
||||
version: "2"
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
|
||||
другой работы: гейт один на все задачи.
|
||||
- **Приоритет — это порядок строк в [tasks/BACKLOG.md](tasks/BACKLOG.md).**
|
||||
Первая строка секции — то, что делают следующим. Порядок назначает человек на
|
||||
груминге (`av-dev-tasks:groom`), машина его не выводит.
|
||||
груминге (`av-dev:task-groom`), машина его не выводит.
|
||||
- **Ориентир по размеру порции разбора на груминге:** 5–8 задач. Ориентир, а не
|
||||
закон.
|
||||
- **Что такое «сделана»:** пайплайн задачи пройден целиком (спека → код → оба
|
||||
@@ -193,9 +193,10 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
|
||||
`openspec validate --strict`.
|
||||
|
||||
Ревью — два чекпоинта: ревью дизайна на предложении (после design/specs, ДО
|
||||
кода) и ревью изменения после apply, до archive. Состав обоих выбирается по
|
||||
метке задачи (`small` / `medium` / `large`), которую разметка ставит один раз
|
||||
после propose. Настройка конвейера под проект и журнал дефектов —
|
||||
кода) и ревью изменения после apply, до archive. Состав обоих постоянен и на
|
||||
всякой задаче один и тот же; глубину по области даёт отдельный прогон
|
||||
`av-dev:code-deep-review`, и зовёт его человек. Настройка конвейера под проект,
|
||||
признаки для глубокого прогона и журнал дефектов —
|
||||
[docs/review.md](docs/review.md).
|
||||
|
||||
## Документация
|
||||
@@ -215,7 +216,7 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
|
||||
- [docs/review.md](docs/review.md) — настройка конвейера ревью и журнал дефектов.
|
||||
- [tasks/](tasks/BACKLOG.md) — задачи и цели: одна запись = один файл
|
||||
в `items/` + строка в индексе, порядок строк = приоритет. Ведётся скиллом
|
||||
`av-dev-tasks:tasks`, разбор беклога — `av-dev-tasks:groom`.
|
||||
`av-dev:task-track`, разбор беклога — `av-dev:task-groom`.
|
||||
|
||||
**Tududi** (проект `jellybit`, project_id 14) — только инбокс сырых идей. Идея
|
||||
становится задачей, когда её оформляют файлом в `tasks/items/`.
|
||||
|
||||
@@ -37,8 +37,8 @@ Arr-стек (prowlarr/radarr/sonarr) плохо ложится на русск
|
||||
|
||||
Рабочий прототип: сквозной путь приём → загрузка → распознавание → раскладка
|
||||
работает целиком, автоматически при уверенном результате либо через
|
||||
подтверждение человеком. Что уже умеет и что дальше —
|
||||
[tasks/ROADMAP.md](tasks/ROADMAP.md).
|
||||
подтверждение человеком. Что уже умеет — `openspec/specs/`, что взято в очередь —
|
||||
[tasks/BACKLOG.md](tasks/BACKLOG.md).
|
||||
|
||||
## Документация
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"canon": 12,
|
||||
"migrations": "internal/store/migrations"
|
||||
}
|
||||
+11
-4
@@ -1,17 +1,24 @@
|
||||
# Журнал решений
|
||||
|
||||
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**,
|
||||
а не второе сочинение: запись цитирует решение и ссылается на
|
||||
`openspec/changes/archive/<id>/design.md`.
|
||||
Одна запись — одно решение. **ADR продвигает уже написанное решение, а не
|
||||
сочиняет его заново**: запись цитирует решение и ссылается на источник —
|
||||
`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 -->
|
||||
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
||||
- **намеренный отказ** от очевидного подхода;
|
||||
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Краткий заголовок решения
|
||||
|
||||
- **Дата:** ГГГГ-ММ-ДД
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md — либо записка разведки,
|
||||
если решение принято без изменения
|
||||
|
||||
<!-- Статус ставится тем же полем и только при пересмотре:
|
||||
- **Статус:** заменено на ADR-ГГГГ-ММ-ДД-slug
|
||||
|
||||
@@ -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/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) |
|
||||
@@ -116,7 +116,7 @@
|
||||
| Приём | use-case `ingest` — общий путь для HTTP, веб-UI, Telegram и CLI |
|
||||
| Переходы состояний | `worker` под per-download блокировкой; легальность перехода задаётся декларативным графом |
|
||||
| Хардлинки и удаление своих ссылок | `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) |
|
||||
| Условие допуска полного удаления | `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) |
|
||||
@@ -185,7 +185,7 @@ Jellyfin указывают на `movies`/`series`, а не на корень
|
||||
| Область | Чего нет сегодня | Задача |
|
||||
| --- | --- | --- |
|
||||
| Масштаб | ориентир 100/1000 загрузок не зафиксирован, узкие места SQLite, воркера и поллинга не измерены | `scale-100-downloads` |
|
||||
| Ретеншен | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` |
|
||||
| Срок хранения | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` |
|
||||
| Бекап | бекапить `/data` требуется, а стратегия и ротация не описаны | `sqlite-backup` |
|
||||
| Метрики и алертинг | healthcheck проверяет только сам сервис; метрик и алертинга нет, отказ виден по застрявшей задаче | `deep-healthcheck-dependencies` |
|
||||
| Идентичность раздачи | split v1/v2-хеши не связаны, паре `xt` из магнета доверяем | `infohash-identity-integrity` |
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
перечень «Механизировано» ниже. Причина: файл на несколько сотен строк
|
||||
размазывает внимание по тривиальному — и модель, и человек добросовестно
|
||||
проверят именование и не дойдут до формы решения. Процедура промоута —
|
||||
`references/promote.md` скилла `av-dev-pipeline:review-pipeline`.
|
||||
`references/promote.md` скилла `av-dev:code-review`.
|
||||
|
||||
Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты
|
||||
с severity — в [CLAUDE.md](../../CLAUDE.md).
|
||||
|
||||
@@ -39,16 +39,16 @@
|
||||
|
||||
## Прочее
|
||||
|
||||
- Enum-поля (`state`, `kind`, …) — обычный `TEXT` без `CHECK`; допустимые
|
||||
значения держит код (`internal/store`).
|
||||
- Временные метки — `TEXT` в **RFC 3339, UTC (суффикс `Z`)**, напр.
|
||||
`2006-01-02T15:04:05Z` (секундная точность). Фиксированная ширина сохраняет
|
||||
лексикографическую сортировку TEXT = хронологию (`ORDER BY created_at`).
|
||||
Единая точка генерации — приложение: `store.Now()` + `store.FormatTime`/
|
||||
- Enum-поля — обычный `TEXT`/`INTEGER` без `CHECK`; допустимые значения держит
|
||||
код (`internal/store`). Какие именно поля — [../database.md](../database.md) →
|
||||
«Индексы и ограничения».
|
||||
- Временные метки пишет приложение: `store.Now()` + `store.FormatTime`/
|
||||
`ParseTime` (аналогично `ident.NewID` для id), а не дефолт в схеме — так
|
||||
забытая вставка падает громко. Измерение длительности — не метка времени: у
|
||||
внешних вызовов его засекает `logging.StartCall`. Таймзона отображения в
|
||||
UI — конфиг `[general].timezone`.
|
||||
забытая вставка падает громко. Формат и точность хранения —
|
||||
[../database.md](../database.md); фиксированная ширина там выбрана затем, чтобы
|
||||
лексикографическая сортировка TEXT совпадала с хронологией. Измерение
|
||||
длительности — не метка времени: у внешних вызовов его засекает
|
||||
`logging.StartCall`. Таймзона отображения в UI — конфиг `[general].timezone`.
|
||||
- Миграции — goose (`internal/store/migrations`): SQL-файлы для DDL;
|
||||
Go-миграции (`goose.AddMigrationContext`) — когда нужен код (генерация
|
||||
id, backfill). При изменении структуры обновляем ER-схему
|
||||
|
||||
@@ -219,12 +219,16 @@ Go-ошибки логируем как атрибут, не как текст
|
||||
|
||||
## Безопасность: что не логируем
|
||||
|
||||
Никаких секретов в полях и сообщениях. Под запретом:
|
||||
Никаких секретов в полях и сообщениях. Состав секретов —
|
||||
[CLAUDE.md](../../CLAUDE.md) → «Инварианты», строка «Секреты не попадают в логи,
|
||||
диагностику и ответы API»; здесь он не дублируется.
|
||||
|
||||
- учётные данные qBittorrent (логин/пароль, cookie сессии);
|
||||
- API-ключ и токен LLM-провайдера, `Authorization`-заголовки;
|
||||
- ключи TMDB/TVDB и прочих метабаз;
|
||||
- содержимое аутентификационных параметров magnet/трекеров.
|
||||
Специфичные для логов **носители** секрета, которых в инварианте нет:
|
||||
|
||||
- cookie сессии qBittorrent;
|
||||
- заголовки `Authorization`;
|
||||
- аутентификационные параметры magnet и трекеров;
|
||||
- URL внутри `*url.Error` (см. ниже).
|
||||
|
||||
Дополнительно:
|
||||
|
||||
@@ -246,8 +250,9 @@ Go-ошибки логируем как атрибут, не как текст
|
||||
|
||||
- Пишем JSON в `stdout` одним потоком; сбор и ротацию делает окружение
|
||||
(docker/journald). Не маршрутизируем по файлам.
|
||||
- Базовый уровень в проде — `INFO`; `DEBUG` включается через конфиг/env при
|
||||
необходимости. dev — `DEBUG`.
|
||||
- Базовый уровень в проде — `INFO`; `DEBUG` включается полем `level` секции
|
||||
`[log]` в `config.toml` — env для конфига не используется
|
||||
([config.md](config.md)). dev — `DEBUG`.
|
||||
|
||||
## Анализ
|
||||
|
||||
|
||||
+7
-5
@@ -176,7 +176,7 @@ erDiagram
|
||||
внутри JSON нет, SQL по содержимому этих полей не делается.
|
||||
- **`recognition.raw_llm`** — сырой ответ модели как есть, **несжатый**. Это
|
||||
самое крупное поле в базе и главный кандидат на рост: у каждой попытки
|
||||
распознавания свой ответ, попытки не вытесняются, ретеншена нет
|
||||
распознавания свой ответ, попытки не вытесняются, срока хранения нет
|
||||
(задача в беклоге).
|
||||
- **`download_torrent.data`** — единственный BLOB: исходные байты `.torrent`
|
||||
(обычно десятки КБ, у больших раздач — сотни). Читается целиком при
|
||||
@@ -224,8 +224,10 @@ erDiagram
|
||||
| `httpapi.maxBulkDelete` | `20` загрузок | предел размера одной пачки группового удаления. Подтверждение, перечисляющее больше, человек не читает — то есть перестаёт быть подтверждением; плюс один синхронный запрос упирается в столько же последовательных вызовов qBittorrent. Предел называет сама страница выбора; отказ по пределу возвращает выбор с сохранёнными отметками |
|
||||
| `httpapi.bulkFailThreshold` | `3` отказа подряд | сколько подряд идущих отказов внешнего сервиса прекращают проход группового удаления. Удаление снимает библиотечные ссылки раньше, чем сносит раздачу: при недоступном 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
@@ -1,7 +1,7 @@
|
||||
# Паспорт проекта
|
||||
|
||||
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
||||
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «что уже умеет», паспорт —
|
||||
устроено», [openspec/specs/](../openspec/specs/) — «что уже умеет», паспорт —
|
||||
«зачем и для кого».
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
не от смены формата.
|
||||
|
||||
**Каждый вывод — с числами и командой или условиями, которыми получен**, чтобы
|
||||
его можно было перепроверить. Число без провенанса проход обязан читать как
|
||||
его можно было перепроверить. Число без происхождения проход обязан читать как
|
||||
условие, а не как замер. Число, чей источник по ссылке не подтвердился, не
|
||||
выбрасывается и не переписывается по догадке — остаётся с пометкой «расходится
|
||||
с источником: там <что нашли>».
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
сообщения берутся **источник** (magnet) и **контекст** для распознавания. Формат
|
||||
чужой, ничем не документирован и может измениться без предупреждения.
|
||||
|
||||
**Провенанс.** Образец снят вручную из личного чата Telegram (сообщение
|
||||
**Происхождение.** Образец снят вручную из личного чата Telegram (сообщение
|
||||
датировано 2026-03-21) и зафиксирован в [BRIEF.md] проекта; второй образец,
|
||||
меньшего размера, живёт константой `botMessage` в
|
||||
`internal/tgbot/parse_test.go` и проверяется тестами разборщика. Статистики по
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
из ответа `/search`. Здесь записано, откуда взята форма этого ответа и чего в ней
|
||||
не подтверждено.
|
||||
|
||||
**Провенанс — и он слабый.** Всё ниже сверено **по публичной документации**
|
||||
**Происхождение — и оно слабое.** Всё ниже сверено **по публичной документации**
|
||||
TheTVDB API v4, файл `docs/swagger.yml` репозитория `thetvdb/v4-api`, поле
|
||||
`info.version` = `4.7.10` (прочитано 2026-08-07). **Живым прогоном не
|
||||
подтверждено ни одно наблюдение**: `CLAUDE.md` → «Запреты» запрещает ходить в
|
||||
|
||||
+56
-75
@@ -1,8 +1,8 @@
|
||||
# Ревью: настройка и журнал
|
||||
|
||||
Проектная часть конвейера ревью: чем jellybit отличается от абстрактного
|
||||
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (метки,
|
||||
стадии, контракт находок) живёт в скилле, а не здесь.
|
||||
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (состав
|
||||
проходов, стадии, контракт находок) живёт в скилле, а не здесь.
|
||||
|
||||
## Как настроен конвейер
|
||||
|
||||
@@ -135,8 +135,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
|
||||
### Вопросы по темам
|
||||
|
||||
Форма: `<тема>: <вопрос> (<провенанс>)`. Адресуется теме, а не имени прохода:
|
||||
проход переезжает между метками и упраздняется, тема переезд переживает. Задаёт
|
||||
Форма: `<тема>: <вопрос> (<откуда>)`. Адресуется теме, а не имени прохода:
|
||||
проход переезжает между скиллами и упраздняется, тема переезд переживает. Задаёт
|
||||
вопрос тот, кто закрывает тему на текущем прогоне.
|
||||
|
||||
- `security`: можно ли, управляя только именами файлов в раздаче и текстом
|
||||
@@ -179,8 +179,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
- `requirements`: не завелось ли поведение, которого спека не заказывала — тихий
|
||||
дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле?
|
||||
- `security`: читается ли тело ответа внешнего сервиса целиком без предела —
|
||||
у LLM (8 MiB) и метабаз (4 MiB) предел стоит, и новый исходящий вызов обязан
|
||||
заводить свой ([security.md](security.md) → «Что вне модели»)
|
||||
у LLM и метабаз предел стоит ([database.md](database.md) → «Пределы, зашитые
|
||||
константой кода»), и новый исходящий вызов обязан заводить свой?
|
||||
- `operations`: гарантия, которую вводит изменение, поставлена на запись или на
|
||||
чтение — и что будет с данными, записанными до деплоя, которые обычный путь
|
||||
не перезаписывает? (журнал, 2026-08-10: чистка названия стояла на записи, и
|
||||
@@ -201,75 +201,50 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
логика целевых имён мимо `naming`?
|
||||
([architecture.md](architecture.md) → «Единые точки проекта»)
|
||||
|
||||
### Триггеры метки
|
||||
### Когда звать глубокое ревью
|
||||
|
||||
Уточняет умолчания конвейера, не отменяет их. Рабочее умолчание — `medium`;
|
||||
миграция схемы и публичный контракт метку **не** поднимают: их проверяют
|
||||
проходы, которые в `medium` и так есть. Списка три: два поднимают до `large`,
|
||||
по одному на ось, третий опускает до `small`.
|
||||
Цикл задачи проверяет корректность и механику одним и тем же составом на всякой
|
||||
задаче. Глубину даёт только отдельный прогон по области —
|
||||
`av-dev:code-deep-review`, — и **зовёт его человек**. Списки ниже уточняют
|
||||
признаки, по которым его здесь зовут, а не заводят расписание.
|
||||
|
||||
**Крупное здесь** — про объём, сколько узлов и слоёв трогает изменение:
|
||||
**Области, которые смотрят целиком:**
|
||||
|
||||
- перенос ответственности между `worker`, `recognition`, `layout` и `store`;
|
||||
- новое состояние в графе переходов загрузки: оно тянет за собой воркер, спеку,
|
||||
отображение в веб-UI и боте и восстановление после рестарта;
|
||||
- правка, идущая насквозь по цепочке приём → распознавание → раскладка;
|
||||
- новый провайдер метабазы за существующим интерфейсом: клиент, поле конфига с
|
||||
образцом, слияние полей кандидата, ветка «провайдера нет».
|
||||
- **правила идентичности, слияния и разбора** — ключ владения раздачей и сверка
|
||||
с qBittorrent (`ident`, `internal/store`, `state-reconciliation`); построение
|
||||
целевых путей и санитизация имён (`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/`;
|
||||
- новый транспорт приёма или уведомлений рядом с REST, веб-UI, ботом и CLI;
|
||||
- заводится или меняется **правило идентичности, слияния или разбора**: ключ
|
||||
владения раздачей и сверка с qBittorrent (`ident`, `internal/store`,
|
||||
`state-reconciliation`); построение целевых путей и санитизация имён
|
||||
(`internal/layout`, `naming`); новый вид входа или новая ветка неоднозначности
|
||||
у разбора недоверенного — bencode, magnet, текст контекста, ответ LLM
|
||||
(`internal/torrent`, `internal/magnet`, `internal/tgbot/parse.go`); новый
|
||||
источник или новый победитель при конфликте в слиянии кандидата метабазы
|
||||
(`internal/metadata`, `metadata-match`); merge-раскладка при повторном
|
||||
добавлении раздачи.
|
||||
|
||||
- **новая поверхность поверх необратимой операции** — вторая точка входа в
|
||||
команду, которая удаляет файлы или снимает последнюю копию. Форма решения
|
||||
здесь нащупывается по ходу: подтверждение, порядок отказов, остаток,
|
||||
наблюдаемость. Выведено по факту на `bulk-delete-page` (журнал, 2026-08-10):
|
||||
метка `medium` не дала ни враждебного прохода, ни замера, а именно они нашли
|
||||
четыре дефекта класса «необратимо».
|
||||
|
||||
- «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram** —
|
||||
для единственного пользователя это и есть интерфейс.
|
||||
|
||||
**Место из перечня метку не поднимает — поднимает правило.** Перечни выше
|
||||
отвечают «здесь такие правила водятся», а не «любая правка здесь идёт в
|
||||
`large`». Метку поднимает то, что даёт работу новому проходу: заводится ключ
|
||||
сравнения или меняется его состав; у разбора появляется новый вид входа или
|
||||
новая ветка неоднозначности; в слияние добавляется источник или меняется
|
||||
победитель при конфликте.
|
||||
|
||||
**Мелкое здесь** — опускает до `small`. Перечень закрытый, каждый пункт
|
||||
проверяется взглядом на дифф и дельта-спеку, любое сработавшее держит метку
|
||||
внизу:
|
||||
|
||||
- **дельта-спека называет исход поимённо** — сценарий уже говорит, что даёт
|
||||
вырожденный вход, и решать в коде нечего;
|
||||
- **новых сценариев в дельта-спеке нет** — изменение уточняет уже описанное
|
||||
поведение, а не заказывает новое;
|
||||
- **правка сообщения, комментария, записи журнала, имени или теста** в узле из
|
||||
перечней выше;
|
||||
- **сужение уже существующей нормализации** без нового вида входа: вход остался
|
||||
тот же, изменился исход на одном его значении.
|
||||
|
||||
Отрицательный тест поверх перечня: что после мерджа не откатывается обратной
|
||||
правкой — миграция, формат на диске, публичный контракт, имя, — **не** `small`,
|
||||
каким бы маленьким ни был дифф.
|
||||
|
||||
**Ориентир частоты.** `medium` закрывает большинство задач, `large` рассчитана
|
||||
на 5–10% и приходится на крупную функциональность, а не на уборку: задача типа
|
||||
`chore` или `fix`, собранная из нитей прошлого ревью, идёт в `small` или
|
||||
`medium`, даже когда трогает файл из перечней выше. `large` чаще одной задачи из
|
||||
десяти означает ошибку в критерии, а не полосу сложных задач подряд.
|
||||
- **миграция схемы**, уже накатанная на боевую БД umbar;
|
||||
- **операции, снимающие данные** — `torrents/delete` с `deleteFiles=true` и
|
||||
снятие последней жёсткой ссылки: обе исключены из гарда сознательно, и второй
|
||||
точки входа в них не бывает дешёвой (журнал, 2026-08-10);
|
||||
- **раскладка в библиотеке Jellyfin** — переименование или перезапись файла,
|
||||
на который уже смотрят клиенты и закладки;
|
||||
- **публичный контракт** — форма ответа REST, имена полей конфига,
|
||||
идентификаторы в ссылках: их держат чужие потребители, и переименование
|
||||
расходится по базе;
|
||||
- **формат на диске** — раскладка `paths.movies`/`paths.series` и имена, по
|
||||
которым Jellyfin узнаёт тайтл.
|
||||
|
||||
### Недоступно проверке
|
||||
|
||||
@@ -283,7 +258,7 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
ссылки) на текущее поведение.
|
||||
- `architecture`: суждение «этой функциональности не должно существовать».
|
||||
- `requirements`: качество распознавания как таковое — правильно ли LLM
|
||||
определил фильм. Это вопрос тюнинга модели и промпта, а не ревью кода;
|
||||
определил фильм. Это вопрос настройки модели и промпта, а не ревью кода;
|
||||
размеченный корпус, по которому это можно было бы судить числом, решено не
|
||||
собирать (`tasks/REJECTED.md`, 2026-08-06).
|
||||
|
||||
@@ -299,10 +274,10 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
что уже есть в библиотеке»). **Различение «идиоматично против
|
||||
распространено» теперь не спрашивает никто.** Класс обратимый: портит форму
|
||||
кода, не данные. Пересмотр — задача `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`)
|
||||
- **Что меняем:** в «Триггеры метки», ось «незнакомое», добавлен пункт про новую
|
||||
поверхность поверх необратимой операции
|
||||
- **Отменено 2026-09-02:** метка упразднена вместе с переходом на постоянный
|
||||
состав прогона. Признак живёт в «Когда звать глубокое ревью» → «Области,
|
||||
которые смотрят целиком»
|
||||
|
||||
## 2026-08-10 — три дефекта поштучного удаления жили незамеченными, пока рядом не появилась пачка [проскочил]
|
||||
|
||||
@@ -374,6 +352,9 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
- **Что меняем:** три записи в беклоге со ссылкой на оракулы; триггер метки
|
||||
дополнен (см. запись выше), чтобы следующая поверхность над необратимой
|
||||
операцией шла сразу с доказательными проходами
|
||||
- **Отменено 2026-09-02:** метки нет; вывод перенесён в «Когда звать глубокое
|
||||
ревью» — удаление раздачи с файлами названо там областью, которую смотрят
|
||||
целиком
|
||||
|
||||
## 2026-08-10 — тест остался зелёным навсегда, потому что проверял снятый атрибут [пойман]
|
||||
|
||||
|
||||
+6
-6
@@ -50,10 +50,10 @@ REST API работают **без авторизации** осознанно;
|
||||
- **Правило:** компоненты санитизируются (убираются разделители пути, `..`,
|
||||
управляющие символы), финальный `filepath.Clean`-путь обязан быть **строго
|
||||
под** соответствующей библиотекой, иначе операция отклоняется. Проверка на
|
||||
результате, а не на входе. Следом — длина: каждый компонент обязан помещаться в
|
||||
255 байт UTF-8, иначе задача уходит в `review`. Порядок значим: путь, вышедший
|
||||
за песочницу, отклоняется как выход за библиотеку, а не как длинное имя, иначе
|
||||
находка безопасности спряталась бы за косметической причиной.
|
||||
результате, а не на входе. Длина компонента проверяется следом и только после
|
||||
проверки принадлежности — порядок и исход нормированы
|
||||
[file-layout](../openspec/specs/file-layout/spec.md), величина предела —
|
||||
[database.md](database.md).
|
||||
- **Исходный путь** = `save_path` из qBittorrent + относительное имя файла из
|
||||
`/torrents/files`. Тоже недоверенный, но по нему мы только **читаем и
|
||||
линкуем**; писать в `paths.downloads` нельзя вообще.
|
||||
@@ -104,8 +104,8 @@ REST API работают **без авторизации** осознанно;
|
||||
- **Отказ в обслуживании изнутри контура.** Огромная раздача, тысяча файлов,
|
||||
бесконечный ответ LLM — это вопросы устойчивости и ресурсов
|
||||
([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности.
|
||||
Тело ответа внешнего сервиса при этом читается с пределом: LLM — 8 MiB
|
||||
(`internal/llm`), метабазы — 4 MiB (`internal/metadata`).
|
||||
Тело ответа внешнего сервиса при этом читается с пределом — величины в
|
||||
[database.md](database.md) → «Пределы, зашитые константой кода».
|
||||
- **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота.
|
||||
- **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга.
|
||||
- **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Каждое правило здесь — бывшая строка прозаической конвенции: у него есть
|
||||
// детерминированный оракул, поэтому ему место в конвейере сборки, а не в
|
||||
// промпте ревью (процедура промоута — references/promote.md скилла
|
||||
// av-dev-pipeline:review-pipeline).
|
||||
// av-dev:code-review).
|
||||
package archrules
|
||||
|
||||
import (
|
||||
|
||||
@@ -32,8 +32,8 @@ context: |
|
||||
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
|
||||
первым молча, и заметно это становится в предложении, которое уже написано.
|
||||
|
||||
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом
|
||||
скилл av-dev-pipeline:review-pipeline, проектная настройка — docs/review.md.
|
||||
Ревью: состав проходов здесь не пересказываем — их дом скилл
|
||||
av-dev:code-review, проектная настройка — docs/review.md.
|
||||
|
||||
Конвенции кода: механизированное проверяет `task gate`, прозой остаётся
|
||||
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
|
||||
|
||||
+14
-15
@@ -26,20 +26,19 @@ from pathlib import Path
|
||||
|
||||
OUT_DIR = Path("tmp/gate")
|
||||
|
||||
# Скрипты трёх плагинов av-dev. Каждый проверяет своё и ни один не тянет
|
||||
# остальных: без своего шага дрейф соответствующего каталога перестаёт ловиться
|
||||
# молча.
|
||||
# docs.py — раскладка docs/ против канона (av-dev-docs:canon)
|
||||
# tasks.py — согласованность индексов каталога задач (av-dev-tasks:tasks)
|
||||
# openspec.py — форма openspec/config.yaml (av-dev-code:openspec)
|
||||
# Скрипты плагина av-dev. Каждый проверяет своё и ни один не тянет остальных:
|
||||
# без своего шага дрейф соответствующего каталога перестаёт ловиться молча.
|
||||
# docs.py — раскладка docs/ против канона (av-dev:canon)
|
||||
# tasks.py — согласованность индексов каталога задач (av-dev:task-track)
|
||||
# openspec.py — форма openspec/config.yaml (av-dev:code-openspec)
|
||||
# Путь берётся из каталога маркетплейса (он без хеша версии, в отличие от
|
||||
# cache/), переопределяется переменной: переустановка плагина не должна
|
||||
# требовать правки гейта. Если скрипта нет — шаг краснеет, а не пропускается:
|
||||
# молча пропущенная проверка даёт ложное ощущение проверенности.
|
||||
MARKETPLACE = Path.home() / ".claude/plugins/marketplaces/av-dev-skills"
|
||||
DOCS_PY_DEFAULT = MARKETPLACE / "av-dev-docs/skills/canon/scripts/docs.py"
|
||||
TASKS_PY_DEFAULT = MARKETPLACE / "av-dev-tasks/skills/tasks/scripts/tasks.py"
|
||||
OPENSPEC_PY_DEFAULT = MARKETPLACE / "av-dev-code/skills/openspec/scripts/openspec.py"
|
||||
DOCS_PY_DEFAULT = MARKETPLACE / "av-dev/skills/canon/scripts/docs.py"
|
||||
TASKS_PY_DEFAULT = MARKETPLACE / "av-dev/skills/task-track/scripts/tasks.py"
|
||||
OPENSPEC_PY_DEFAULT = MARKETPLACE / "av-dev/skills/code-openspec/scripts/openspec.py"
|
||||
|
||||
OK, FAIL, WARN, SKIP = "OK", "FAIL", "WARN", "SKIP"
|
||||
|
||||
@@ -219,17 +218,17 @@ def main() -> int:
|
||||
# а database.md нет» (отдельного шага er-schema поэтому больше нет).
|
||||
# База диффа обязательна: без неё сверка миграций со схемой не гоняется.
|
||||
plugin_step(
|
||||
"canon", "DOCS_PY", DOCS_PY_DEFAULT, "av-dev-docs",
|
||||
"canon", "DOCS_PY", DOCS_PY_DEFAULT, "av-dev",
|
||||
["check", "--dir", ".", "--base", base],
|
||||
"раскладка docs/ разошлась с каноном",
|
||||
)
|
||||
|
||||
# --- Каталог задач ---
|
||||
# Согласованность индексов с файлами items/: бездомная запись, строка без
|
||||
# файла, порядок секций роадмапа, сырьё не в конце секции. docs.py этого не
|
||||
# смотрит — каталог принадлежит другому плагину.
|
||||
# Согласованность беклога с файлами items/: бездомная запись, строка без
|
||||
# файла, стадия проекта, сырьё не в конце секции. docs.py этого не смотрит —
|
||||
# каталог задач он не ведёт.
|
||||
plugin_step(
|
||||
"tasks", "TASKS_PY", TASKS_PY_DEFAULT, "av-dev-tasks",
|
||||
"tasks", "TASKS_PY", TASKS_PY_DEFAULT, "av-dev",
|
||||
["check", "--dir", "tasks"],
|
||||
"индексы задач разошлись с items/",
|
||||
)
|
||||
@@ -239,7 +238,7 @@ def main() -> int:
|
||||
# а работает как пустой. Ловит это только свой шаг: docs.py про config.yaml
|
||||
# молчит с канона 10.
|
||||
plugin_step(
|
||||
"openspec", "OPENSPEC_PY", OPENSPEC_PY_DEFAULT, "av-dev-code",
|
||||
"openspec", "OPENSPEC_PY", OPENSPEC_PY_DEFAULT, "av-dev",
|
||||
["check", "--dir", "."],
|
||||
"openspec/config.yaml разошёлся с канонической формой",
|
||||
)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"tasks": 1
|
||||
}
|
||||
+15
-6
@@ -1,12 +1,21 @@
|
||||
# Беклог
|
||||
|
||||
Что **можно взять**. Одна задача = один файл `items/<slug>.md`
|
||||
+ строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что
|
||||
берут, роадмап — то, подо что берут. Ведётся скиллом `av-dev-tasks:tasks`.
|
||||
+ строка здесь. Ведётся скиллом `av-dev:task-track`.
|
||||
|
||||
**Порядок строк внутри секции — это приоритет.** Первая строка секции — то, что
|
||||
делают следующим. Порядок назначает человек на груминге
|
||||
(`av-dev-tasks:groom`), машина его не выводит.
|
||||
<!-- стадия -->
|
||||
Стадия проекта — **доработка** (`[tasks] stage = "support"`).
|
||||
**Порядок строк внутри секции — важность:** первая строка это то,
|
||||
что делают следующим. Назначает его человек на груминге, машина не
|
||||
выводит. Секции — полки домена, смысла они не несут. Заводится по
|
||||
одной, по мере появления; пустой беклог — нормальное состояние.
|
||||
<!-- /стадия -->
|
||||
|
||||
Одно место в очереди назначено не человеком, а типом: сырьё (`research` без
|
||||
раздела «Вопрос») стоит в конце секции — его не берут.
|
||||
|
||||
Тип записи стоит первым полем меты и решает, что у неё может быть:
|
||||
✨ `feature` 🐞 `fix` 🧹 `chore` 🔬 `research`
|
||||
|
||||
Секции «блокеры» здесь нет и не заводится: блокер — это состояние, оно живёт до
|
||||
ответа человека, а его следы — вопросами в файлах задач.
|
||||
@@ -41,7 +50,7 @@
|
||||
## Инфраструктура
|
||||
|
||||
- [🐞 Тормозить опрос 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 подсетей) — задел на случай, если понадобится защита
|
||||
- [🧹 Бэкапить SQLite по расписанию с ротацией](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
|
||||
- [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
||||
|
||||
@@ -7,3 +7,10 @@
|
||||
<!-- - ГГГГ-ММ-ДД `slug` — Заголовок. Причина: … Была секция: … -->
|
||||
- 2026-08-06 `learn-from-user-corrections` — ✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot). Причина: решено не собирать базу правок человека: направление — тюнинг автоматического распознавания без участия человека. Была секция: ядро продукта.
|
||||
- 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; групповое удаление раздач в беклог не берётся — по решению человека при повышении раскладки заводится заново, когда уборка десятка раздач станет болью. Была секция: Направления.
|
||||
|
||||
@@ -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`
|
||||
на первой достигнутой цели.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта — уступила верх четырём мелочам с готовыми оракулами: сама задача жива и остаётся первой фичей ядра, но стоит дороже и в ближайший заход не влезает
|
||||
- **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию — надёжнее всего через TVDB (там есть absolute order). Отдельный крайний случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта — тот же internal/recognize, что и первая задача — окно открыто; решение принято 2026-07-08, осталось снять пере-применение дефолта, понизить 0.85→0.7 и записать в спеку
|
||||
- **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
Аудит спек↔код (2026-07-03) нашёл расхождение: спека recognition считает
|
||||
`confidence` вспомогательным сигналом (условия авто — только матч в базе +
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# 🎯 Загрузки удаляются пачкой, а не по одной
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** удаление раздачи с файлами доступно только по одной кнопке на странице одной загрузки — уборка десятка раздач превращается в десяток заходов
|
||||
|
||||
Ради чего: разложенные раздачи копятся, и убирают их обычно скопом — после
|
||||
просмотра сезона, при чистке диска, после серии неудачных заливок. Сегодня
|
||||
удаление живёт только в danger-секции страницы одной загрузки, поэтому уборка
|
||||
десяти раздач стоит десяти проходов «список → карточка → подтверждение».
|
||||
|
||||
При этом удаление с файлами необратимо: оно зовёт `torrents/delete` с
|
||||
`deleteFiles=true`, а гард последней копии там выключен сознательно
|
||||
(инвариант «Источник неприкосновенен», исключение 1). Групповой режим обязан
|
||||
сделать уборку дешевле, не сделав ошибку дешевле.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда человек убирает любое число раздач одним проходом: выбирает
|
||||
их в списке, один раз подтверждает удаление по перечню, где каждая раздача
|
||||
названа поимённо, и видит поимённый результат — что снесено, что отказало и
|
||||
почему. Ни одна раздача не сносится без того, чтобы человек увидел её в
|
||||
подтверждении.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
У кандидата метабазы нет метрики силы совпадения (metadata_candidate хранит provider/id/title/year/url), решение «авто vs review» — по правилу «единственный сильный матч + валидация», не по числу. Для ревью: список кандидатов нечем отсортировать/подсветить по уверенности. Идея — ввести на этапе матча силу совпадения кандидата (точное совпадение названия+года vs частичное) для сортировки и подсказки в UI. Шире — продумать сам процесс распознавания и матчинга: границы «разбор LLM / поиск в базе / сверка», что храним у кандидата, как считаем и показываем уверенность.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent.
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Раскладывается не только типовая раздача
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: сериальные паки, докачивание, аниме со сквозной нумерацией, образы дисков и внешние субтитры — это ровно тот контент, ради которого проект и заводился вместо arr-стека.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда сериальный пак, докачивание недостающих серий, аниме со сквозной нумерацией, образ диска и внешние субтитры раскладываются без ручного вмешательства в файлы на диске — либо честно уходят в ревью с названной причиной, а не молча кладутся неверно.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Обычный случай — один сезон (его номер видно глазами и сверяем на ревью — под это сделана сводка сезонов). Но в редких заказах раздача сложнее: все сезоны сериала разом, пак нескольких сезонов, смешанная нумерация, вложенные папки сезонов, разнобойные имена файлов. Сейчас PlanFile.Season задаётся на каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не проработаны: как надёжно распознать, как показать на ревью, как разложить и как стыкуется со сходимостью папки и merge-докачиванием. Решить, что поддерживаем явно, а что уводим в ревью как «сложную раскладку».
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
|
||||
- **Теги:** goal:dev-process-quality
|
||||
|
||||
Список копился в черновике `docs/drafts/conventions-backlog.md` (удалён при
|
||||
переводе на канон, текст в истории git) под правилом «пишем по мере реального
|
||||
@@ -46,7 +45,7 @@
|
||||
- **Время.** Явный TZ всегда, хранение и логи в UTC. Уже частично в `CLAUDE.md`
|
||||
и `conventions/logging.md`, а `time.Now` вне `store` запрещён линтером — этот
|
||||
пункт, вероятно, закрыт и подлежит вычёркиванию.
|
||||
- **Язык вывода связан с мапперами.** Провенанс — ревью `tvdb-title-locale`
|
||||
- **Язык вывода связан с мапперами.** Откуда — ревью `tvdb-title-locale`
|
||||
(2026-08-07,
|
||||
[отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md),
|
||||
находка R2 прохода `architecture`). Язык вывода живёт в пяти местах четырёх
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми ответами LLM и metadata_candidate копятся вечно — БД и список загрузок распухают и становятся нечитаемыми. Нужна авточистка старше N дней (настройка в [storage] или [worker]) и/или ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере эксплуатации.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
/healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent недоступен»), чтобы причина простоя была видна сразу.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** fix
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** удаление старой закрытой задачи уничтожает файлы живой загрузки с тем же инфохэшем; воспроизведено падающим тестом на ревью bulk-delete-page
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
`Delete` зовёт `torrents/delete` с `deleteFiles=true` по хешам своей записи и не
|
||||
спрашивает, не владеет ли этим инфохэшем другая **активная** загрузка. Человек
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** fix
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** при недоступном qBittorrent задача весь простой соседа показывает done, хотя тайтла в Jellyfin уже нет: сверка падает на первом шаге и до коррекции не доходит
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
`Delete` снимает библиотечные ссылки раньше, чем зовёт `qbt.Delete`. Если сосед
|
||||
недоступен, локальный шаг проходит, внешний падает, и задача остаётся в `done`.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Домен называется одинаково везде, ревью откалибровано
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Сопровождение
|
||||
- **Зачем:** наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: это не поведение продукта, а то, чем он делается. Единый словарь, калибровка проходов ревью и разбор накопленных кандидатов в конвенции — вложение в скорость всех остальных целей.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда домен называется одинаково в спеках, коде и интерфейсе, а конвейер ревью откалиброван на журнале реальных дефектов, а не на догадках о том, что он ловит.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** раздача-образ Blu-ray разбирается пофайлово и едет в библиотеку россыпью .m2ts, а Jellyfin умеет такой каталог целиком
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Иногда для очень редкого фильма качается не один видеофайл, а полная копия
|
||||
диска — каталог `BDMV/` (Blu-ray) или `VIDEO_TS/` (DVD). Раскладка сегодня
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки и уже реализованный экран /download/{id}.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Косметика и редкий случай: порядок просмотра не страдает (файлы уже
|
||||
пронумерованы канонически и лежат по порядку), разъезжаются только подписи серий
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Базовая привязка субтитр→серия для сериала уже работает: `layout.PlanFile` несёт
|
||||
`Season/Episode`, а `seriesDst` именует субтитр по стему эпизода
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске. На забитом диске это упрётся в полку посреди раскладки. Перед копированием проверять доступное место и при нехватке внятно уходить в failed с понятной причиной, а не падать на полпути.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit в крошечный HTTP-сервис (один файл, поставляется рядом с бинарём jellybit) и спрашивать его на шаге пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта — сырьё, а не дефект: тело само не решает — «change по модели доверия/идентичности либо задокументировать как ограничение», и критерии приёмки писать не из чего; идёт на штурм, взять нельзя
|
||||
- **Зачем:** две находки ревью 2026-07-08 упираются в нерешённую модель идентичности: связывать ли v1- и v2-хеши одного торрента и что делать с парой xt, которую qBittorrent не подтверждал — merge, supersede или ограничение в документе
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). Две связанные находки о доверии к парам xt в magnet (предпосылки к F1).
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Раздача приносится и подтверждается из любого транспорта
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: приём и ревью — единственные места, где система встречается с человеком. Здесь копятся незакрытые куски: фетч по URL, редактор маппинга, привязка уведомлений к автору, латентность обновлений.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда любой из поддержанных источников принимается одним действием из любого транспорта, а ревью позволяет довести план до применимого состояния без ухода в другой инструмент.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Свежий сериал раздают по мере выхода: торрент с 5 из 10 эпизодов позже перезаливают целиком, пользователь добавляет раздачу повторно. Новая загрузка приходит в ту же папку за счёт правила сходимости, а раскладка становится merge — доложить только недостающее. Существующие пути не трогаем (never-overwrite, владение у старой загрузки), новые кладём (владеет новая). Split-ownership сезона принят как норма per-path модели; обе раздачи сидируют независимо.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками).
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Проработать: когда включать, как мерджить расхождения, стоимость/латентность.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Целевые имена для типового фильма и типового сезона заказаны
|
||||
[file-layout](../../openspec/specs/file-layout/spec.md). Крайние случаи там
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему.
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Сервис переживает рост и потерю тома
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Сопровождение
|
||||
- **Зачем:** сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: сегодня всё держится на том, что загрузок мало и всё рядом работает. Ретеншена нет, бэкапа нет, глубокого healthcheck нет, поведение под сотней загрузок не мерялось.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда база не растёт бесконечно, состояние переживает потерю тома, отказ любой зависимости виден владельцу раньше, чем по застрявшим задачам, и поведение под сотней одновременных загрузок измерено, а не предположено.
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура — уступила первую строку: ready не проходит, а часть про ревьювер наименований ждёт словарь единого языка
|
||||
- **Зачем:** конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
|
||||
- **Теги:** goal:dev-process-quality
|
||||
- **Зачем:** конвейер ревью переехал в плагин `av-dev`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
|
||||
|
||||
Набор проходов ревью поверх ревью-процесса из CLAUDE.md. Развивает ревью-процесс
|
||||
OpenSpec в сторону воспроизводимых автопроверок, не заменяя человеческое ревью.
|
||||
@@ -49,7 +48,7 @@ OpenSpec в сторону воспроизводимых автопроверо
|
||||
оптикой не выделен: зависит от задачи «Словарь единого языка», без глоссария
|
||||
проверять не по чему. Завести после неё.
|
||||
- **Калибровка проходов** по процедуре `references/calibration.md` скилла
|
||||
`av-dev-pipeline:review-pipeline` — ни один проход ещё не замерен инъекцией.
|
||||
`av-dev:code-review` — ни один проход ещё не замерен инъекцией.
|
||||
До замера ничего не удаляем и промпты не правим.
|
||||
- **Проходы не сообщают свой потолок находок.** На прогоне `tvdb-title-locale`
|
||||
(2026-08-07) о потолке промолчали четверо из шести — `autotests`, `specs`,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# 🎯 Раздача узнаётся верно без подсказок человека
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** распознавание ошибается молча и правдоподобно, а смена модели или правка промпта идёт вслепую — сдвига точности не видно ни до, ни после
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: распознавание — единственное место, где система может ошибиться молча и правдоподобно. Сегодня о его точности судят по впечатлению, и сдвиг от смены модели или правки промпта заметен только задним числом.
|
||||
|
||||
Размеченный корпус и обучение на правках человека из этой цели исключены сознательно (`REJECTED.md`, 2026-08-06): базу руками не собираем, точность поднимаем тюнингом автоматического распознавания.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда решение auto/review опирается на измеримую силу совпадения с записью метабазы, а не на самооценку модели, и доля раздач, ушедших в ревью или поправленных после авто-раскладки, видна по рабочему потоку и не растёт от версии к версии.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Когда распознавание разложило файлы по сериям неверно, единственный путь —
|
||||
подсказать текстом и перераспознать. Точечно поправить номер серии у одного
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в НФТ целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: задаёт рамку для решений ниже. Отдельно (дороже) — аудит узких мест: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
architecture.md требует «бекапить data-том», но как — не описано. Без понятной стратегии сбой или редеплой стирают всё in-flight состояние. Зафиксировать решение и реализовать: периодический VACUUM INTO в /data/backups по расписанию (с ротацией) либо потоковая репликация (litestream). Лучше сделать, пока БД маленькая.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда.
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 По записи загрузки видно, как она сюда попала
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: состояние загрузки — то, по чему судят обо всём остальном. Накопились известные щели: окно namer'а, идентичность split v1/v2, потеря маркера dismiss, отсутствие истории переходов.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда по записи загрузки можно ответить «как она сюда попала», ни один известный сегодня путь не оставляет состояние, которое не объясняется историей переходов, и идентичность раздачи не подделывается входом.
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Зонтичная задача: пройтись по всем исходящим уведомлениям и запросам подтверждения
|
||||
бота, выправить формулировки, состав данных и оформление. Тексты формируются в
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
**Два кандидата пришли из ревью `tvdb-title-locale`** (2026-08-07,
|
||||
[отчёт триажа](../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md)
|
||||
→ «Promote candidates»), оба с провенансом прохода, а не из головы:
|
||||
→ «Promote candidates»), оба с названным проходом, а не из головы:
|
||||
|
||||
- **один стенд чужого API на пакет.** В `tvdb_test.go` завелись два фейка одного
|
||||
и того же API. Когда форма ответа поменяется по факту разведки, забытый
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
По калибровке болей (2026-07-02) — не боль, из приоритета выпало. Сосуществование версий доступно уже сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же путь штатно уходит в review. Явный replace (undo старого хардлинка → lay нового → супересид владения путём) — отдельный change, если/когда станет болью.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Приём magnet и `.torrent`-файла уже реализован: ветка `TorrentData → torrent.Parse`
|
||||
(`internal/ingest/ingest.go`), файл-пикер в веб-форме (`web/templates/index.html`),
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта — дешевле всех и проверяет только что сделанное: при иной форме ответа TheTVDB локализация молча уходит в фолбэк, а гейт зелёный
|
||||
- **Зачем:** форма ответа поиска TheTVDB принята по swagger 4.7.10 и живым прогоном не подтверждена — при иной форме разбор молча уходит в фолбэк, гейт зелёный, локализованное название не работает
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
Задача `tvdb-title-locale` научила клиент TVDB брать локализованное название из
|
||||
блока переводов ответа `/search` и заполнять `OriginalTitle` primary name'ом. Но
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
|
||||
- **Теги:** goal:dev-process-quality
|
||||
|
||||
Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей (http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита: токен/Basic в самом приложении или вынос за reverse-proxy с аутентификацией.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user