Кейс «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>
3.1 KiB
3.1 KiB
1. Homoglyph-таблица и fold
- 1.1 В
internal/recognizeзавести курируемую таблицу кирилло-латинских двойников (~15–20 пар, строчные и заглавные) как единственный источник правды - 1.2 Реализовать потокенную
foldHomoglyphs(s string) string: одно-скриптовый токен не трогаем, в смешанном — доминирующий скрипт по числу буквенных рун, меньшинство мапим по таблице; при равенстве оставляем как есть - 1.3 Юнит-тесты
foldHomoglyphs: «Hаrold»→«Harold», честная кириллица нетронута, смешанное двуязычное название, равенство скриптов, пустой ввод
2. Санитайзинг полей плана (recognition)
- 2.1 Реализовать
sanitizeTitle: strip control/zero-width (C0/C1, U+200B и родственные, BOM), collapse пробелов + trim, затемfoldHomoglyphs - 2.2 Применить санитайзинг к
title,original_title,provider_hintна границе разбора плана — после unmarshal, ДО структурной валидации и сверки;files[].srcне трогать - 2.3
slog.Debugwhen поле реально переписано (before/after), логгер из ctx - 2.4 Тесты: поля чистятся,
files[].srcнеприкосновенны, лог при изменении
3. Fold в гейте нормализации (metadata-match)
- 3.1 Добавить
foldHomoglyphsвnormalize(internal/recognize/metadata.go) послеё→е, переиспользуя ту же таблицу - 3.2 Тест: план и кандидат, различающиеся лишь скриптом буквы, нормализованно совпадают
4. Безгодовой второй проход сверки (metadata-match)
- 4.1 Обернуть перебор в
matchMetadataв два прохода: pass 1 — как сейчас (Searchс годом), pass 2 — только приplan.Year > 0и отсутствии матча в pass 1, тот же переборsearchKeys × providersсYear = 0 - 4.2 Кандидаты копятся через оба прохода с существующей дедупликацией по
provider:idи потолкомmaxCandidates; гейтstrongMatchesне менять - 4.3 Тесты: кривой год находится без года; при матче в pass 1 второго прохода
нет; при
year = 0второго прохода нет; несколько кандидатов из pass 2 → матч не подтверждён
5. Проверка и вычитка
- 5.1
task testиtask lint— зелёные - 5.2
openspec validate --strict sanitize-llm-titles-yearless-retry