## 1. Парсинг полей magnet - [x] 1.1 Расширить `magnet.Info` полями `ExactLength int64` (из `xl`), `Sources []string` (из `xs`), `Keywords []string` (из `kt`) - [x] 1.2 В `Parse` заполнить новые поля из `vals`; `xl` парсить строго как десятичное целое (байты), при ошибке — оставлять 0 (best-effort) - [x] 1.3 Тесты парсинга: magnet с `xl`/`xs`/`kt`, гибридный, невалидный `xl`, отсутствие полей - [x] 1.4 Устойчивость `Parse` к ссылке, скопированной целиком в процент-кодировке (`magnet%3A%3F…`): разовый `QueryUnescape` на фолбек-пути + тест; тест на реальной рутрекер-ссылке (dn с кириллицей → Context) ## 2. Синтез контекста из полей - [x] 2.1 Реализовать `func (Info) Context() string` (или `SynthContext`) в пакете `magnet`: строки-факты через `\n` — название (`dn`, если не заглушка), `Размер:` из `xl` (человекочитаемо), `Трекер:`/происхождение из домена `tr`/`xs`, `Ключевые слова:` из `kt` - [x] 2.2 Детектор заглушки `dn` вида `*-topic-` (не включать как название) - [x] 2.3 Helper нормализации домена трекера/источника из URL (`tr`/`xs`) - [x] 2.4 Тесты синтеза: содержательный `dn`; `dn`-заглушка `rutracker-topic-*`; только размер; только домен; все поля вместе; пустой `Info` → пустая строка; проверить отсутствие сетевых вызовов (чистая функция) ## 3. Слияние в ingest - [x] 3.1 В `Ingest` собрать `enrichedContext = join(nonEmpty(req.Context, info.Context()))` — пользовательский текст первым, синтез следом - [x] 3.2 Класть `enrichedContext` **только** в `download.Context` (вместо сырого `req.Context`); вызов `namer.DeriveName(ctx, req.Context, info.DisplayName)` оставить как есть — namer синтез НЕ получает - [x] 3.3 Тесты ingest: magnet-only (пустой `req.Context`) → `download.Context` синтезирован; непустой текст + поля → оба присутствуют, текст первым; пустой текст и бедный magnet → пустой контекст, приём проходит - [x] 3.4 Регресс-тест: голый rutracker-magnet (`dn=rutracker-topic-`, только `tr`, без текста) → `download.display_name` НЕ равен `Трекер: …` (именование не деградирует), а `download.Context` содержит домен трекера ## 4. Транспорты и проверка - [x] 4.1 Убедиться, что приём голого magnet (пустой контекст) штатно проходит в `httpapi` и `tgbot` (при необходимости — тест на пустой `context`) - [x] 4.2 `task test` и `task lint` зелёные - [x] 4.3 `openspec validate magnet-context-extraction --strict` проходит