## 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`
