При подтверждённом матче база папки (имя+год) наследуется от живой папки-якоря того же (provider, provider_id) вместо печати заново из выхода LLM — так второй/последующий сезон ложится в ТУ ЖЕ папку, а не заводит рядом почти одинаковую. Отдельная сущность «тайтл» не вводится. - layout: Plan.FolderBase перекрывает базу в папке и именах файлов; TitleFolder разбирает dst_path в папку тайтла и базу (снятие тега). - store: LiveTitleFolders — dst_path живых ссылок того же матча. - worker: resolveFolderBase (живость якоря — по наличию папки на диске, os.Lstat, а не по статусу ссылки в БД) в linkPlan и в превью ревью (превью=применение); рассинхрон нескольких живых папок → review из linking (deferred→review в графе нет). Схема БД не менялась. Change series-folder-convergence заархивирован, требования влиты в openspec/specs/file-layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.6 KiB
3.6 KiB
1. Layout: приём унаследованной базы и разбор пути
- 1.1 Добавить в
layout.Planопциональное полеFolderBase string(пусто → как прежде) - 1.2 В
BuildLinks: еслиFolderBase != ""— использовать её какbase(черезsanitizeComponent) вместоtitleYear(Title, Year), применяя и к папке, и к именам файлов; тег складывать как прежде - 1.3 Экспортировать хелпер(ы): извлечь папку тайтла из
dst_path(первый сегмент под корнемmovies/series; не под корнем → пусто) и снять базу из папки (убрать хвостовой[...]; пустой результат → пусто) - 1.4 Юнит-тесты layout:
FolderBaseперекрывает базу в папке и в именах файлов (movie и series); извлечение папки/базы из пути — с тегом, без тега, с чужим/старым тегом, не под корнем
2. Store: lookup живых ссылок тайтла
- 2.1 Добавить
LiveTitleFolders(ctx, provider, providerID, excludeDownloadID) ([]string, error):dst_pathссылок соstatus IN linked/copied/existsзагрузок с тем же(provider, provider_id)current recognition, кромеexcludeDownloadID; пустой provider/id → пусто - 2.2 Юнит-тест store: возврат путей по матчу; исключение текущей загрузки; superseded/collision/удалённые не попадают; пустой provider/id → пусто
3. Worker: правило сходимости, рассинхрон, превью
- 3.1 Чистый хелпер
resolveFolderBase(ctx, dl, provider, providerID, mediaType) (base string, desync bool, err error): нет матча → пусто; иначеLiveTitleFolders→ свести к различным папкам тайтла (хелпер 1.3), отбросить несуществующие на диске (os.Lstat) и неразбираемые; 0 → пусто, 1 → база, ≥2 → desync. Без побочных эффектов. - 3.2 В
linkPlan(из состоянияlinking, рядом с обработкой коллизии —deferred→reviewв графе нет): вызватьresolveFolderBase;desync→transition(review, "title_folder_desync"), раскладку не выполнять; иначе прокинутьbaseвtoLayoutPlan/layout.Plan - 3.3 Превью раскладки на ревью (review.go ~921/981) зовёт тот же
resolveFolderBaseи наследует базу (инвариант «превью=применение»);desyncв превью → база из распознавания, без перевода в review - 3.4 Тесты worker/integration: сходимость второго сезона в ту же папку; отсутствие якоря → свежая папка; переименование папки на диске у кандидата → не якорь (печать заново); смена провайдера → не склеивается; рассинхрон (2 живые папки) → review с причиной; превью совпадает с применением
4. Спеки и проверка
- 4.1
openspec validate --strict series-folder-convergence - 4.2
task testиtask lint— зелёные