Files
jellybit/openspec/changes/archive/2026-07-10-sanitize-llm-titles-yearless-retry/tasks.md
T
avandClaude Opus 4.8 e2ea1840c9 Распознавание: санитайзинг названий от LLM + безгодовой фолбэк сверки
Кейс «Harold and the Purple Crayon»: LLM отдал title с кириллической
буквой-двойником, сырое название ушло в запрос TVDB дословно (не нашлось),
а гейт нормализации кир/лат двойники не сворачивал — двойной промах, пустой
список кандидатов, ручной ввод id.

- recognition: санитайзинг человекочитаемых полей плана (title/original_title/
  provider_hint) на границе разбора, до валидации: strip control/zero-width,
  collapse пробелов, потокенная свёртка homoglyph-двойников по курируемой
  кир↔лат таблице. files[].src не трогаем (обязаны биться с торрентом).
- metadata-match: тот же fold в normalize (гейт) как defense-in-depth;
  безгодовой второй проход сверки как fallback при известном годе и промахе
  первого — восстанавливает off-by-one авто-матчи и пополняет кандидатов
  review. В fallback требуем известный год кандидата (год-unknown → review,
  не авто); гейт год ±1 и инвариант авто-матча не двигаются.

Спеки recognition/metadata-match обновлены, change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 15:45:19 +03:00

43 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Homoglyph-таблица и fold
- [x] 1.1 В `internal/recognize` завести курируемую таблицу кирилло-латинских
двойников (~15–20 пар, строчные и заглавные) как единственный источник правды
- [x] 1.2 Реализовать потокенную `foldHomoglyphs(s string) string`: одно-скриптовый
токен не трогаем, в смешанном — доминирующий скрипт по числу буквенных рун,
меньшинство мапим по таблице; при равенстве оставляем как есть
- [x] 1.3 Юнит-тесты `foldHomoglyphs`: «Hаrold»→«Harold», честная кириллица
нетронута, смешанное двуязычное название, равенство скриптов, пустой ввод
## 2. Санитайзинг полей плана (recognition)
- [x] 2.1 Реализовать `sanitizeTitle`: strip control/zero-width (C0/C1, U+200B и
родственные, BOM), collapse пробелов + trim, затем `foldHomoglyphs`
- [x] 2.2 Применить санитайзинг к `title`, `original_title`, `provider_hint` на
границе разбора плана — после unmarshal, ДО структурной валидации и сверки;
`files[].src` не трогать
- [x] 2.3 `slog.Debug` when поле реально переписано (before/after), логгер из ctx
- [x] 2.4 Тесты: поля чистятся, `files[].src` неприкосновенны, лог при изменении
## 3. Fold в гейте нормализации (metadata-match)
- [x] 3.1 Добавить `foldHomoglyphs` в `normalize` (`internal/recognize/metadata.go`)
после `ё→е`, переиспользуя ту же таблицу
- [x] 3.2 Тест: план и кандидат, различающиеся лишь скриптом буквы, нормализованно
совпадают
## 4. Безгодовой второй проход сверки (metadata-match)
- [x] 4.1 Обернуть перебор в `matchMetadata` в два прохода: pass 1 — как сейчас
(`Search` с годом), pass 2 — только при `plan.Year > 0` и отсутствии матча в
pass 1, тот же перебор `searchKeys × providers` с `Year = 0`
- [x] 4.2 Кандидаты копятся через оба прохода с существующей дедупликацией по
`provider:id` и потолком `maxCandidates`; гейт `strongMatches` не менять
- [x] 4.3 Тесты: кривой год находится без года; при матче в pass 1 второго прохода
нет; при `year = 0` второго прохода нет; несколько кандидатов из pass 2 → матч
не подтверждён
## 5. Проверка и вычитка
- [x] 5.1 `task test` и `task lint` — зелёные
- [x] 5.2 `openspec validate --strict sanitize-llm-titles-yearless-retry`