# Yandex Performance Ops

> Глобальный навык для Яндекс.Директ/Wordstat/Roistat/Метрики: сбор новой семантики, аудит и оптимизация действующих кампаний, live-валидация, media-plan, competitor research и client-overlay контракт для любого локального проекта.

- Skill: `concertonotes/yandex-performance-ops` (Agent Skill, multi-file: 136 files)
- Install (CLI): `npx skillmds@latest add concertonotes/yandex-performance-ops`
- Raw SKILL.md: https://api.skillmd.com/api/skills/concertonotes/yandex-performance-ops/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: ConcertoNotes (https://skillmd.com/u/concertonotes)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/concertonotes/yandex-performance-ops

---


# Yandex Performance Ops

Глобальный навык для работы с performance-маркетингом Яндекса из любого локального проекта.

## Path Contract

- `<plugin-root>` = корень этого bundle, где лежат `.codex-plugin/plugin.json`, `skills/`, `mcp/`, `scripts/`.
- Repo-local пример: `./plugins/yandex-direct-for-all`
- Home-compatible install пример: `~/.codex/plugins/yandex-direct-for-all` или `~/.claude/plugins/yandex-direct-for-all`
- `<ops-skill-root>` = `<plugin-root>/skills/yandex-performance-ops`
- `<client-lifecycle-root>` = `<plugin-root>/skills/yandex-direct-client-lifecycle`
- В bundled docs и командах по умолчанию использовать `<plugin-root>/...` или `<ops-skill-root>/...`, а не жёсткий `~/.codex/skills/...`

Цель:
- хранить reusable-методологию, скрипты, промты и уроки в одном месте;
- держать в локальном проекте только контекст клиента и его уникальные правила;
- не терять наработки между `kartinium`, `siz`, `tenevoy` и новыми проектами.

## Что объединяет этот навык

Собрано и нормализовано из:
- `kartinium/.claude/skills/direct-search-semantics`
- `kartinium/.claude/skills/yandex-direct`
- `kartinium/.claude/skills/yandex-wordstat`
- `kartinium/.claude/skills/roistat-direct`
- `kartinium/.claude/skills/media-plan`
- `ads/siz/.claude/skills/direct-optimization`
- `ads/tenevoy/.claude/skills/direct-optimization`
- `ads/siz/.claude/skills/yandex-metrika`
- `ads/siz/.claude/skills/competitive-ads-extractor`
- статистические подходы из `ads/siz/.claude/skills/ppc-data-analysis`

Полная ревизия источников:
- [source_inventory.md](references/source_inventory.md)
- [completeness_audit_2026-03-05.md](references/completeness_audit_2026-03-05.md)

Этот global skill тоже не имеет права “перепридумывать” `Wordstat/Direct` слой мимо исходных source-skills.
Если есть конфликт между краткой интеграцией и source-skill по `Wordstat`, `direct-search-semantics` или `yandex-direct`, использовать source-skill как верхний канон.

Для `Wordstat` канонический reusable reference этого global skill лежит здесь:
- [wordstat_collection_framework.md](references/wordstat_collection_framework.md)
- [future_session_start_checklist.md](references/future_session_start_checklist.md)
Global skill обязан уже сам содержать полный канон Wordstat-сбора.
Локальные project docs могут только добавлять client-specific терминологию, но не заменять global framework.
Для клиентского research-отчета Wordstat теперь считать обязательным три отдельных слоя:
- спрос по базовым маскам;
- сезонность;
- география.

## Когда использовать

Используй этот навык, когда задача относится к одному из блоков:
- сбор новой поисковой семантики через официальный API;
- аудит и оптимизация уже работающих кампаний Яндекс.Директ;
- настройка и pre-moderation валидация поисковых кампаний;
- массовые проверки live-state через Direct API;
- SQR, минус-слова, структура, автотаргетинг, объявления, ExcludedSites;
- Roistat-first анализ лидов/продаж;
- Yandex Metrika отчёты и атрибуция;
- media-plan и plan-vs-fact;
- competitor creative research;
- синхронизация задач в YouGile;
- фиксация reusable-уроков после цикла работ.

## Главный принцип

Global skill = методология + reusable tooling.

Local project = клиентский overlay + client-specific rules.

## Обязательный client-facing report после live-apply

После любого live-изменения в кабинете нельзя ограничиваться коротким "сделано" или ссылкой на сырые JSON.
Нужно сразу собрать и показать пользователю human-readable report с тремя блоками:
- `что было` — исходная проблема, counts, затронутые `campaign_id/adgroup_id/ad_id`;
- `что сделал` — точные live-мутации, какие поля менялись, что сознательно не трогалось;
- `что стало` — read-back и post-check с цифрами `before/after`.

Минимум, который обязан попасть в такой отчёт:
- список затронутых сущностей;
- количество реально изменённых объектов;
- пути к артефактам `apply_results / readback / summary`;
- явное объяснение, почему были выбраны именно эти правки.

Если live-правки уже сделаны, но user-facing report ещё не показан, работа считается незавершённой.

Если user явно просит ускорить большой manual-review через локальные Codex CLI воркеры, это теперь канонический reusable path, а не разовая импровизация:
- launcher: `scripts/codex_cli_swarm_manual_review.py`
- search prompt: `templates/codex_swarm_search_worker_prompt.md`
- rsya prompt: `templates/codex_swarm_rsya_worker_prompt.md`
- search schema: `schemas/codex_swarm_search_chunk_response.schema.json`
- rsya schema: `schemas/codex_swarm_rsya_chunk_response.schema.json`

Этот swarm-path разрешён только как ускорение ручного verdict-слоя:
- launcher обязан собирать единый full `worker knowledge pack` из overlay + product/rules/lessons и отдавать его как canonical context для всего run;
- launcher обязан поверх full pack собирать `chunk focus context` как deterministic extract по текущему chunk;
- каждый Codex worker получает как required reads только `chunk focus context + prior manual context + chunk TSV`;
- full knowledge pack остаётся fallback-слоем и не должен быть обязательным read, если focus-context и prior-context уже достаточны;
- каждый worker обязан вручную покрыть каждый `candidate_id` своего chunk;
- scripts могут только чанковать, запускать, валидировать coverage и merge'ить;

Для 15d creative/growth refresh теперь каноничен отдельный reusable path:
- если задача = `ротация text/image losers` + `новые группы / growth plan`, не надо собирать полный giant-wave заново;
- собрать `Direct account snapshot` на нужное окно через `direct-orchestrator/scripts/collector_direct_account_snapshot.py` c `mode=ads`, чтобы получить:
  - `raw/direct_account_snapshot_v2/raw_bundle/ads/all_ads.tsv`
  - `raw/direct_account_snapshot_v2/raw_bundle/ad_texts/all_source_ads.json`
  - `raw/direct_account_snapshot_v2/raw_bundle/all_campaign_window_totals.tsv`
- собрать search SQR на то же окно через `direct-orchestrator/scripts/collector_search_query_wave.py` или fallback `<ops-skill-root>/scripts/fetch_sqr.sh`;
- `direct-orchestrator/scripts/local_wave_review.py` теперь обязан терпеть волны без `placements` и без `campaigns_meta.json`: в creative-only / growth-only refresh он должен падать обратно на `all_campaign_window_totals.tsv`;
- reusable creative builder: `scripts/build_creative_rotation_from_outliers.py`;
- reusable growth builder: `scripts/build_growth_structure_from_routes.py`;
- creative builder обязан выпускать совместимый комплект:
  - `03_creative_rotation_candidates.tsv`
  - `03_creative_rotation_candidates_v2.tsv`
  - `03_creative_rotation_skipped.tsv`
  - `03_creative_rotation_review.md`
- growth builder обязан выпускать:
  - `09_new_groups_candidates.tsv`
  - `09_missing_phrases_growth_review.md`
  - `12_growth_acceleration_pack.md`
  - а `09_structure_action_plan.tsv` затем собирается штатным `direct-orchestrator/scripts/build_structure_action_plan.py`;
- verdict по новым standalone search-кампаниям нельзя высасывать из воздуха: если 15d growth-layer подтверждает только `new group` / `test layer`, так и писать `новая РК не подтверждена`.
- Если пользователь дал `go` на live apply после такого refresh, канонический reusable apply-path теперь такой:
  1. собрать text-rotation validation/apply pack через `scripts/build_text_rotation_apply_pack_from_tsv.py`;
  2. прогнать `dry-run`, затем live apply через `scripts/apply_ad_replacement_pack.py`;
  3. собрать manifest новых search adgroups и применить его через `scripts/apply_search_adgroup_manifest.py`;
  4. сделать strict readback именно по новым `ad_ids` и `adgroup_ids`, а не полагаться только на giant `campaign_autotest.py` по всей кампании;
  5. отправлять на модерацию только новые объявления через `send_to_moderation.py --ad-ids ...`;
  6. RSYA image replacements не применять автоматически без visual/manual validation даже если refresh-docs их уже рекомендуют.
- `campaign_autotest.py` может быть полезен как общий фон, но для creative/growth live-wave он не должен быть единственным safety-gate: старые `WARN/FAIL` по legacy-сущностям слишком шумные. Канонический gate = strict readback новых сущностей + targeted moderation readback.

Для strict pre-apply перед live-правками теперь каноничен отдельный local-pack path:
- Search strict local pack builder: `scripts/build_local_search_negatives_pack.py`
- Search live drift-check: `scripts/dry_run_search_negatives_pack.py`
- RSYA strict local pack builder: `scripts/build_local_rsya_excluded_sites_pack.py`
- RSYA validator: `scripts/validate_excluded_sites_pack.py`
- RSYA live drift-check/apply helper: `scripts/apply_no_moderation_pack.py`

Правила этого preflight-контура:
- Search server-pack должен собираться только из `high-confidence stop-only` слоя; generic single-word adjectives, numeric junk и low-confidence typo garbage должны отрезаться builder'ом до dry-run.
- RSYA local pack обязан строиться по live `ExcludedSites` baseline, по placements evidence и по client formula (`tail_formula_v3`), а не тупо из manual decisions TSV.
- RSYA builder обязан быть `slot-aware`: если в кампании мало свободных `ExcludedSites`, pack берёт только strongest candidates и паркует overflow в blocked audit.
- RSYA apply/readback обязан канонизировать идентификаторы площадок (`strip/lower`, удаление схемы, `www.` и хвостового `/`) до drift-check и post-apply verify, потому что Direct может сам нормализовать домены/ids на readback.
- Validator и tail-formula не могут жить разными правилами: если client использует `tail_formula_v3`, `validate_excluded_sites_pack.py` обязан валидировать именно по ней, а не по legacy `>5 clicks & CTR>1%`.
- scripts не имеют права придумывать verdict вместо worker-а.
- каждый chunk должен запускаться в изолированном `CODEX_HOME`, чтобы parallel workers не дрались за system skills/install state.
- prompt обязан зажимать worker в короткий command-budget: сначала head knowledge-pack, потом prior-context и chunk, потом только targeted `rg/sed` при реальном противоречии.
- `<cheap-codex-model>` можно использовать как cheap default для bulk swarm only with guardrail: на Search он не должен быть единственным verdict-слоем. Mixed benchmark `2026-03-15` показал сильный bias к `keep`, особенно на brand-stop / route-fix / growth rows. Канонический режим: `mini` для draft/triage, сильная модель или человек для спорного хвоста и финального QA.

Для огромных Search SQR очередей канонический deterministic reduction-layer теперь такой:
- reusable script: `scripts/search_negative_marker_engine.py`;
- project wrapper: `direct-orchestrator/scripts/run_search_negative_marker_cycle.py`;
- это не auto-verdict и не auto-stop, а только shrink-engine перед manual negative review;
- обязательный порядок:
  - bootstrap уже вручную подтверждённых `exclude` / `growth` правил;
  - apply этих правил к новой SQR очереди с audit-файлами `excluded` и `growth_hold`;
  - split остатка на `negative_candidate_rows` и `protected_route_hold`;
  - build compact marker cards только по `negative_candidate_rows`;
- marker cards разрешены только двух типов: `token` и `phrase`;
- `phrase` имеет приоритет над `token`;
- growth/route-like хвост не должен смешиваться с negative-review и обязан уходить в `protected_route_hold` или другой explicit hold-layer;
- user ничего не подтверждает вручную: все manual rules в этот слой приносит агент из уже просмотренных строк;
- карточка marker review обязана быть короткой: `marker`, `scope`, `matched_rows`, `cost/clicks`, `3-5 примеров`.

Минимальный reusable запуск:

```bash
python3 <ops-skill-root>/scripts/codex_cli_swarm_manual_review.py \
  --kind search \
  --queue <review/manual queue.tsv> \
  --project-root <client project root> \
  --merge-into <review/manual_decisions.tsv> \
  --overlay <client overlay json> \
  --local-skill <local skill path> \
  --product-catalog <product catalog path> \
  --search-rules <search rules path> \
  --lessons <lessons path> \
  --manual-decisions <existing manual decisions tsv> \
  --workers 4 \
  --chunk-size 25 \
  --model <cheap-codex-model> \
  --reasoning-effort medium \
  --sandbox danger-full-access \
  --approval-policy never
```

Секреты, board ids и брендовые аксиомы не зашивать в global-skill.

### Правило 0: CLAUDE.md в каждом клиентском проекте (ОБЯЗАТЕЛЬНО!)

При инициализации ЛЮБОГО нового клиентского проекта Директа — ПЕРВЫМ делом создать `.claude/CLAUDE.md` в папке проекта со следующим содержимым:
```markdown
# Проект: [Имя клиента]

## ЖЕЛЕЗНОЕ ПРАВИЛО
ПЕРЕД любым действием с кампаниями — СНАЧАЛА прочитай навык:
- `<ops-skill-root>/SKILL.md`
- optional project-local companion skill, если он реально существует
- `memory/lessons.md` в папке проекта (если есть)

НИКОГДА не импровизируй со ставками, стратегиями, минус-словами.
Все решения — ТОЛЬКО на основе навыка и данных.

## Валюта аккаунта: [KZT/RUB/BYN]
## OAuth токен: [путь к файлу]
## Логин: [client-login]
```

Без этого файла работа с проектом ЗАПРЕЩЕНА. Файл гарантирует что агент не начнёт импровизировать, а сначала прочитает навык и lessons.

Client overlay обязан переопределять build-layer до первого API write, если в нём заданы:
- стратегии `Search` / `РСЯ`;
- `GoalId` / `PriorityGoals`;
- гео (`RegionIds`);
- правило по минус-словам для `РСЯ`;
- формат `group names`;
- формат `DisplayUrlPath`.
- text guardrails и запрещённые термины для Direct copy.

Если overlay противоречит generic defaults скрипта, применять overlay, а не generic defaults.
Если overlay требует `РСЯ без минус-фраз`, clearing pattern в API = `NegativeKeywords: null`; пустой `Items: []` не считать допустимым способом очистки.

Гео-правило на будущее:
- `МО` в речи клиента неоднозначно и нельзя автоматически трактовать как `область без Москвы`;
- если клиент говорит `вся МО`, `полный МО`, `Москва и область`, использовать полный регион `RegionIds=[1]`;
- вариант `область без Москвы` допустим только при явном подтверждении и тогда задаётся как `RegionIds=[1,-213]`.

Client-specific rule sets для discovery и review должны лежать в локальных reference-файлах проекта и подключаться через overlay.

Минимум для reusable full-review:
- `references/product-catalog.md`
- `references/search-stop-word-rules.json`
- `references/rsya-placement-rules.json`

Скрипты не должны знать конкретный `GoalId`, `client_key`, бренд клиента или special-case campaign id из кода. Эти значения надо брать из overlay, raw bundle или local references.

Перед любым новым циклом работ сначала проходить `future_session_start_checklist.md`.
Если в проекте уже есть готовые артефакты и рабочие скрипты, нельзя игнорировать их и идти через импровизацию.

Для `RSYA stop-sites` reusable-правило теперь такое:
- `Clicks >= 5 + (CTR > 1% или app/game/vpn)` = только базовый каркас;
- финальный verdict обязан учитывать ещё `Cost`, `AvgCpc`, `goal conversions`, `campaign benchmark CPA`, тип площадки и protected-platform hints;
- крупные платформы/marketplace нельзя блокировать по одному package-id;
- если есть `clicks > 0` и `cost = 0`, агент не перекладывает это на пользователя, а сам ставит действие `перепроверка raw/source -> потом стоп` или `мониторинг следующей волны`.

Для больших `RSYA placement` очередей теперь каноничен отдельный deterministic `queue prefilter` ДО ручного verdict-слоя:
- reusable script: `scripts/prefilter_rsya_manual_queue.py`;
- это не auto-stop и не auto-verdict, а только фильтр очереди `manual review / monitor / anomaly quarantine`;
- safe-default для всех клиентов:
  - `low_signal_skip`: `conversions = 0`, `clicks < 3`, `CTR < 1%`, `cost < 20`;
  - `zero_click_tail_skip`: `clicks = 0`, `cost = 0`, `impressions < 150`;
  - `protected_low_signal_skip`: `protected/yandex`, `conversions = 0`, `clicks < 5`, `cost < 100`;
  - `app_like_low_signal_skip`: `app-like`, `conversions = 0`, `clicks < 2`, `cost < 35`;
  - `anomaly_quarantine`: `clicks > 0 and cost = 0` или `conversions > 0 and cost = 0`.
- пороги prefilter хранить в client/local rules file (`queue_prefilter`), а не в коде;
- строки из `auto_skipped` нельзя считать готовыми stop-sites: это только `monitor/skip from manual stop review`;
- строки из `anomaly_quarantine` не должны попадать ни в auto-stop, ни в обычный manual-stop shortlist до raw/source recheck.

Если build, структура или тексты начинаются после upstream-исследования через lifecycle-слой, downstream skill обязан сначала проверить наличие трех ручных артефактов:

1. `research/analysis/единая-карта-конкурентов.md`
2. `research/analysis/пакет-структуры-будущего-кабинета.md`
3. `research/analysis/пакет-текстов-и-офферов.md`
4. `research/analysis/готовые-тексты-для-директа.tsv`

Если их нет, не перескакивать сразу к сборке кабинета "из головы", а сначала дособрать этот upstream handoff.

Перед сборкой текстов или отправкой их человеку предпочтителен такой путь:

1. ручная подготовка текстов в `готовые-тексты-для-директа.tsv`;
2. прогон через `scripts/validate_direct_copy_pack.py`;
3. только потом перенос в build-слой.

Если задача дошла до стадии `pre-moderation`, reusable-канон теперь требует отдельного channel split:

1. `Search` и `РСЯ` считаются разными deliverables и не собираются из одного универсального copy-pack.
2. До build/moderation handoff должны существовать отдельные артефакты:
   - `search-group-map`
   - `search-negative-bundles`
   - `search-ad-copy-pack`
   - `rsya-group-map`
   - `rsya-copy-and-image-pack`
3. Для `Search` обязательны:
   - точная группа/интент;
   - landing;
   - negative bundle;
   - text guardrails.
4. Для `РСЯ` обязательны:
   - audience;
   - trigger;
   - message angle;
   - image brief;
   - avoid-list для модерации и mismatch intent.
   - `5` уникальных ad variants на каждую группу;
   - `5` уникальных изображений на каждую группу;
   - file-first карта `group x variant -> template_id/title/image_url`, если изображения берутся из продуктовых шаблонов.
5. Нельзя переносить поисковый copy-pack в `РСЯ` без отдельной переработки под visual/message intent.
6. Нельзя считать `модерация готова`, пока этот channel split не собран и не проверен.
7. Для `Search` production default:
   - на каждую группу должно быть `ровно 5` уникальных объявлений до стадии `moderation-ready`;
   - все `5` должны соответствовать ключам внутри этой группы и заметно отличаться друг от друга по angle / promise / pain / CTA.
   - default bidding strategy = `WB_MAXIMUM_CONVERSION_RATE` с оплатой за клики;
   - default `GoalId` для стратегии = `13`, если overlay явно не задаёт другой search goal;
   - для `Search` в auto strategy нельзя оставлять `BidCeiling=null`; нужен явный `BidCeiling` / max CPC из overlay или approved bid baseline;
   - если в кампании до этого стоял manual bidding и есть `DailyBudget`, при переводе в auto strategy budget нужно переносить в weekly layer, а `DailyBudget` сбрасывать в `null`.
8. Для `РСЯ` production default:
   - на каждую группу должно быть `ровно 5` уникальных объявлений;
   - на каждую такую пятёрку должно быть `5` уникальных изображений;
   - default bidding strategy = `PAY_FOR_CONVERSION_MULTIPLE_GOALS`;
   - default optimisation intent для `РСЯ` = все approved lead goals клиента: звонок / форма / messenger, если пользователь не задал иной shortlist;
   - для unified `РСЯ` с несколькими целями использовать exact enum `PAY_FOR_CONVERSION_MULTIPLE_GOALS` и nested block `PayForConversionMultipleGoals`;
   - `PriorityGoals` должны содержать все approved lead goals клиента; без них multi-goal стратегия невалидна;
   - single-goal `PAY_FOR_CONVERSION` допустим только как явно согласованный fallback; в нём обязателен `GoalId` и `Cpa`;
   - `WB_MAXIMUM_CLICKS` в `РСЯ` нельзя ставить по умолчанию.
- если пользователь просит брать изображения из шаблонов, default source = `последние реальные template covers` из live-каталога/БД, а не выдуманные concept-art placeholders.
- если изображения берутся с сайта клиента, брать их только с соответствующей landing/page этого кластера; переносить фото между чужими посадочными нельзя.
- `DisplayUrlPath` не должен быть техническим id/slugs вида `p1-*`, если у клиента нет такого явного правила. Default = человекочитаемый путь.
- `DisplayUrlPath` должен проходить build-time валидацию: человекочитаемый, без технических префиксов и длиной не более `20` символов.
- имена групп в кабинете не должны оставаться техническими кодами, если не требуется служебная отладка. Default = человекопонятные имена.
- если клиент явно требует `РСЯ` без минус-фраз, build-layer не должен автоматически протаскивать generic negative bundles в `РСЯ`.
- если клиент задаёт разные правила оптимизации для `Search` и `РСЯ`, global skill обязан сохранить channel-specific split и не пытаться выровнять стратегии между каналами.
- в текстах Direct слово `WhatsApp` запрещено: не использовать его в `Title`, `Title2`, `Text`, `callouts`, `sitelink titles/descriptions`.
- если на сайте есть WhatsApp как факт, в Direct copy использовать нейтральные замены типа `быстрая связь`, `связь с менеджером`, `быстрый расчет`.
- статус `live / включено` нельзя объявлять по одному `ResumeResults`. После `campaigns.resume` обязателен свежий `campaigns.get` с проверкой `State`, `Status`, `StatusPayment`, `StatusClarification`.
- если после `resume` кампания остаётся `State=OFF`, skill обязан назвать точный blocker из live API и не писать, что запуск завершён.

Товарный или фидовый слой не считать обязательной частью стандартного пакета.
Добавлять его только по прямому запросу пользователя или после отдельного решения в клиентском документе.

Для competitor-review reusable-слой обязан хранить не только лидирующие домены, но и сами тексты объявлений конкурентов:
- отдельный generated file с колонками `query / region / domain / title / snippet / url`;
- HTML-отчёт обязан показывать этот слой напрямую, а не только счётчики появлений.

Для клиентского веб-отчета канонический путь теперь такой:

1. подготовленные ручные артефакты;
2. машинные рендеры таблиц;
3. HTML-страница без ссылок на внутренние markdown-файлы;
4. `build_secure_client_report.py`;
5. локальная проверка;
6. mobile check `390px`;
7. live check после деплоя.

## Client Overlay Contract

По умолчанию навык ищет локальный файл клиента в таком порядке:
- `./.codex/yandex-performance-client.json`
- `./claude/yandex-performance-client.json`
- `./.claude/yandex-performance-client.json`
- путь из `YANDEX_PERFORMANCE_CLIENT_CONTEXT`

В public bundle client overlays не хранятся. Они должны жить в локальном private project-layer вне git, например:
- `./.codex/yandex-performance-client.json`

Шаблоны:
- [client_context.example.json](templates/client_context.example.json)
- [routing_map.example.tsv](templates/routing_map.example.tsv)
- [campaign_id_map.example.json](templates/campaign_id_map.example.json)
- [copy_map.example.json](templates/copy_map.example.json)

Описание полей:
- [local_overlay_contract.md](references/local_overlay_contract.md)
- [yandex_cloud_search_handoffs.md](references/yandex_cloud_search_handoffs.md)

Быстрый scaffold:
```bash
python3 <ops-skill-root>/scripts/init_client_context.py \
  --output ./.codex/yandex-performance-client.json \
  --client-key acme
```

## Источники данных и иерархия истины

1. Direct API / Reports API / Wordstat API / Roistat API / Metrika API
2. Локальные raw-файлы, собранные скриптами
3. Локальный client overlay
4. Локальные client-specific skills/docs
5. Markdown-документация и агентские выводы

Если live-data конфликтует с локальными доками, верить live-data.
Если пользователь сказал что локальные raw-выгрузки устарели, пункт `2` временно исключается из принятия решений до нового live-сбора.

## Жёсткие правила

1. `ПАРСИНГ != АНАЛИЗ`
- парсинг только официальными API и скриптами;
- анализ делать только вручную мной по raw-файлам;
- не домешивать новые API-вызовы в фазу анализа.
- если в локальном проекте есть `build_decision_report.py`, `build_executive_review.py`, `build_executive_html.py` или похожие рендеры, они не имеют права повышать `review/generated/*safe_ready.tsv` до пользовательского статуса `ready_now`. User-facing verdict слой обязан идти только из `review/manual/*.tsv` и, если проект хранит решения отдельными файлами, `review/manual_decisions/*.tsv`.
- если ручной verdict по строке не внесён, верхний слой обязан прямо показывать `manual gate incomplete` / `manual_verdict_required`, а не маскировать machine shortlist под готовый пакет правок.
- reusable queue-builders обязаны поддерживать и legacy raw paths, и новые `*_v2/raw_bundle/*` пути; path drift не оправдывает переход назад к machine-only verdict.
- scripts имеют право только:
  - собирать;
  - чистить;
  - нормализовать;
  - сортировать;
  - чанковать;
  - рендерить данные.
- scripts не имеют права:
  - ставить вердикты;
  - предлагать стоп-слова, стоп-площадки, рост, ставки, новые группы, мониторинг;
  - решать что target / non-target вместо ручного построчного анализа.
- analysis-скрипты для классификации ключей, фраз, минус-слов и масок запрещены.
- анализ ключевых слов из Wordstat, SQR и Roistat делать без скриптов, только вручную по raw-выгрузкам.
- upstream research по SERP/footprint/competitor pages тоже не собирать вручную по одной фразе.
  Default path = batch job-spec + collector script.
  Для этого использовать:
  - `<client-lifecycle-root>/scripts/yandex_search_batch.py`
  - `<client-lifecycle-root>/scripts/yandex_search_ads_batch.py`
  - `<client-lifecycle-root>/scripts/build_domain_shortlist_from_serp.py`
  - `<client-lifecycle-root>/scripts/firecrawl_scrape.py --jobs-file ...`
  - `<client-lifecycle-root>/scripts/build_followup_jobs_from_serp.py`
  - `<client-lifecycle-root>/scripts/split_tsv_batch.py`
  - `<client-lifecycle-root>/scripts/merge_sitemap_batch_outputs.py`
  - `<client-lifecycle-root>/scripts/render_serp_wave.py`
  - `<client-lifecycle-root>/scripts/render_ad_serp_wave.py`
  - `<client-lifecycle-root>/scripts/render_sitemap_candidates.py`
  - `<client-lifecycle-root>/scripts/render_page_capture_inventory.py`
- полный competitor collection строить не от случайных стартовых запросов, а от вручную валидированного keyword set.
  Допустим ранний scout/reconnaissance для проверки рынка и пайплайна, но exhaustive `organic SERP` / `ad SERP` waves запускаются только после этапа:
  - official `Wordstat` raw;
  - ручная валидация масок, ключей и минус-логики;
  - формирование job-matrix `keyword x geo`.
- для каждого validated keyword нужно сохранять raw-query trail и потом расширять найденные домены через sitemap/page-capture.
- до follow-up сборов сначала строить таблицу повторяемости доменов и вручную утверждать укороченный shortlist.
  Default path на будущее:
  - брать `топ-15` повторяющихся доменов из подтвержденной выдачи Яндекса;
  - до shortlist-builder-а механически исключать очевидные некоммерческие URL-паттерны: статьи, новости, справочники, PDF;
  - не тянуть `sitemap/page-capture` по длинному хвосту слабых доменов.
- после live `organic SERP` wave follow-up jobs должны тоже строиться скриптом, а не вручную:
  - `serp_results.tsv -> page-capture-jobs.tsv`
  - `serp_results.tsv -> sitemap-jobs.tsv`
  - затем только batch collectors по этим job-файлам.
- если batch слишком большой или медленный, разбивать jobs нужно тоже скриптом:
  - `split_tsv_batch.py` для chunk-files;
  - затем несколько collector workers по chunk-TSV;
  - затем merge/normalize step скриптом, без ручной склейки.

2. Wordstat только официальный
- запрещён веб-скрейп Wordstat;
- `numPhrases=2000` обязателен для полного охвата масок.
- канонический порядок всегда такой:
  - `СТРУКТУРА -> МАСКИ -> РЕВЬЮ МАСОК -> ПАРСИНГ СКРИПТОМ -> [полный успех] -> АНАЛИЗ -> ЧИСТКА -> ГРУППИРОВКА`;
  - нельзя перепрыгивать из масок сразу в анализ;
  - нельзя делать one-off `wordstat_*` вызовы вместо wave-collector workflow.
- до любого парсинга обязателен `product map`:
  - официальные названия;
  - разговорные названия;
  - тендерные/закупочные формулировки;
  - жаргон;
  - аббревиатуры;
  - ошибки написания;
  - латиница/кириллица;
  - применения по отраслям.
- `Wave 1` обязан начинаться с `L1` root-масок.
  Где это семантически возможно, root-маски должны быть однословными.
- после `L1` в тот же `Wave 1` добавляются `L2` product masks.
  Для нового круга правил:
  - `Wave 1` в `9/10` случаев строится на однословных масках;
  - `Wave 2` допускает двухсловные маски;
  - трехсловные маски не считать default path без явной причины.
- в клиентском или внутреннем отчете слой спроса из Wordstat нужно показывать отдельно:
  - широкие корневые маски как обзорный ландшафт;
  - точные базовые маски как рабочий слой;
  - использовать только `totalCount` по маске;
  - не суммировать вложенные запросы и не складывать маски между собой как единый объем рынка.
- но для ручного анализа `totalCount` недостаточен:
  - по каждой approved mask надо собирать полный официальный ceiling `2000 строк = 40 страниц`;
  - затем лично просматривать каждую строку `topRequests` и `associations`;
  - только после такого row-by-row review разрешено выделять `target`, `new mask`, `adjacent`, `stop-candidate`, `noise`.
- для SQR manual-review разрешены только два вида deterministic propagation из manual-approved слоя:
  - если стоп-слово уже подтверждено вручную в прошлой или текущей волне, можно автоматически убрать из новой очереди unresolved-строки, где это exact single-word минус встречается в `query`/`criterion`;
  - это не новый verdict, а dedupe/preprocessing;
  - если уже внесён ручной verdict c exact query / exact token / exact phrase внутри конкретного `ad_group_name`, можно строить `manual-approved rulebook` и детерминированно распространять это решение на unresolved-строки только при exact/scope-safe match;
  - такой propagation не придумывает новый action: он берёт только уже утверждённый `assistant_action`/`assistant_reason` из manual decision;
  - конфликтующие matches не auto-apply, а уходят в отдельный conflict-файл;
  - любой skip/propagation обязан идти с audit-файлами `что исключено`, `rulebook`, `auto-decisions`, `conflicts`, `remaining`.
- если full row-by-row manual-review Search-хвоста становится неэкономным, перед любым swarm/manual escalation разрешён только один дополнительный deterministic слой:
  - `search_negative_marker_engine.py`;
  - он не имеет права принимать verdict за строку;
  - он имеет право только:
    - bootstrap already-approved `exclude` и `park_growth` rules;
    - автоматически вычитать строки, уже покрытые этими правилами;
    - автоматически парковать `growth/route/protected` хвост вне negative-review;
    - строить компактные marker cards по оставшемуся `negative_candidate` слою.
- канонические выходы этого слоя:
  - `search_excluded_by_marker_rules.tsv`
  - `search_growth_hold.tsv`
  - `search_protected_route_hold.tsv`
  - `search_negative_candidate_rows.tsv`
  - `search_negative_marker_cards.tsv`
  - `search_negative_marker_examples.tsv`
- good-state для этого слоя:
  - active negative review становится на порядок меньше raw queue;
  - целевые хвосты типа `потолки/LED/скрытый монтаж/парящий профиль/плинтус` не попадают в marker cards только потому, что там встретился случайный модификатор;
  - явный non-target хвост (`другой товар`, `B2B`, `чужой бренд`, `marketplace`, `alien use-case`) остаётся в negative candidates.

Для Search negatives перед live apply теперь обязателен отдельный dry-run слой, а не только validation JSON:
- reusable script: `scripts/dry_run_search_negatives_pack.py`;
- он читает уже подготовленный `search_negatives_pack_apply.json`, снимает live `NegativeKeywords` по adgroup и проверяет:
  - drift между live baseline и `before_keywords` из pack;
  - сколько минусов уже стоят в группе;
  - сколько реально будет добавлено после merge;
- если есть drift, status должен быть `blocked`, а live apply запрещён до пересборки pack;
- canonical outputs: JSON + text report с `drift_count`, `dry_run_add_count`, `skip_existing_count`.

Для RSYA apply теперь канонический hard blocker такой:
- если validation/analysis слой всё ещё содержит `manual_review_count > 0`, `validation_pack_rsya.py` обязан ставить status=`blocked`;
- `prepare_apply_rsya_excluded_sites.py` обязан повторно hard-fail'ить, если в validation summary не ноль manual tail;
- правило простое: `RSYA not touch until manual tail is closed`.
- если пользователь просит `просмотреть поисковые фразы`, `собрать минус-фразы`, `разобрать SQR`, `посмотреть новые поисковые фразы` или явно требует `вручную каждую строку`, default path только такой:
  - свежий live/raw сбор;
  - полный ручной row-by-row review каждой строки;
  - сохранение verdict-слоя в `review/manual/*.tsv` или эквивалентный manual-layer;
  - сбор decision table/report;
  - reduction-layer: phrase-level evidence из manual-layer обязано быть сведено к production-safe stop-words / коротким safe-маскам на нужном scope;
  - отдельный validation-layer: `single-token only` или явно одобренные короткие safe-маски, плюс conflict-check с target words;
  - и только потом pre-apply pack из `approved_negative`.
- если объём manual-review слишком велик и user явно разрешил swarm:
  - резать очередь на bounded chunks;
  - запускать несколько локальных `codex exec` воркеров на `<cheap-codex-model>` с `model_reasoning_effort="medium"` по умолчанию;
  - для escalation / conflict-validation / final QA поднимать более сильную модель (`<strong-codex-model>` или project-approved codex model) только на спорный хвост;
  - для local file-only review по умолчанию НЕ копировать пользовательский `config.toml` в worker `CODEX_HOME`, чтобы воркеры не поднимали лишние MCP-серверы и не тратили токены на startup-шум;
  - промт воркера обязан ссылаться на global skill, local skill, overlay, product catalog / local rules, existing manual decisions и chunk TSV;
  - worker не имеет права редактировать master queue / master decisions напрямую, только вернуть schema-valid JSON;
  - launcher обязан провалить chunk, если `candidate_id` coverage неполный, есть extra ids, есть duplicates или пустые `assistant_action` / `assistant_reason`;
  - merge в `manual_decisions.tsv` разрешён только после такого validation pass.
- запрещено начинать такой workflow с:
  - machine shortlist;
  - `safe_ready`;
  - auto-mined stop words;
  - broad phrase collapse;
  - удаления уже добавленных или новых поисковых фраз до ручного verdict по строке.
- если в кабинете уже есть добавленные фразы, новые поисковые фразы или ранее залитые минуса, это не повод механически их убирать.
  Сначала вручную смотреть сырые строки поисковых фраз, потом принимать решение по exact query/token/phrase.
- live apply/rollback по SQR-минусам заблокирован, пока manual gate не закрыт полностью.
- дубликаты можно схлопывать только после ручного verdict по exact query.
  Нельзя сначала схлопнуть хвост, а потом делать вид, что вся группа строк уже просмотрена вручную.
- phrase-level evidence не равно production-ready минус-фраза.
  Фразы из manual SQR review нельзя лить в кабинет как есть, если из них можно безопасно выделить короткий блокирующий токен или короткую safe-маску.
- канонический production-layer для SQR-negatives:
  - `review/manual/*` = evidence и verdict;
  - `review/manual_reduced/*` или эквивалент = сокращённые stop-words / safe-маски;
  - `live_apply/*negative_tasks*.tsv` разрешён только из reduced-layer.
- reusable apply-path по умолчанию обязан отклонять tasks, где negative params содержат `phrase`, если нет отдельного explicit override от пользователя и письменного объяснения, почему token-reduction невозможен.
- user-facing/client-facing отчет обязан различать:
  - `по каким поисковым фразам нашли проблему`;
  - `какие короткие стоп-слова или safe-маски реально добавили`.
  Нельзя выдавать phrase-level evidence за список реально добавленных production-stop-слов.
- канонический renderer для этого слоя:
  - `scripts/render_wordstat_mask_demand.py`
  - вход = config TSV с approved masks и путями к raw;
  - выход = `wordstat-demand-exact.tsv`, `wordstat-demand-roots.tsv`, `_summary.json`
- обязательные соседние renderer-слои:
  - `scripts/render_wordstat_seasonality.py`
  - `scripts/render_wordstat_geo.py`
  - выход = `wordstat-seasonality-matrix.tsv`, `wordstat-geo-priority.tsv`, `_summary.json`
- канонический collector обязан уметь собирать и эти raw-слои:
  - `--dynamics true`
  - `--regions-report true`
  - `--regions-tree true`
- если для сезонности или географии возникает соблазн сделать разовый `wordstat_*` вызов вручную, это считать нарушением workflow.
  Сначала расширять или переиспользовать `scripts/wordstat_collect_wave.js`.
- после составления `Wave 1` обязателен отдельный `mask review`:
  - web/source synonym review;
  - Wordstat association review на широких масках;
  - только потом запуск collector-а.

3. `Pre-moderation` = отдельный gate, а не хвост build-этапа
- до `ads.moderate` должны быть собраны и проверены:
  - отдельный `Search` pack;
  - отдельный `РСЯ` pack;
  - channel-specific negatives / intent guards;
  - moderation-safe promises;
  - image brief / actual creatives для `РСЯ`;
  - live-readiness checks.
- если чего-то из этого нет, статус должен оставаться `handoff-ready`, но не `moderation-ready`.
- `Wave 1` и `Wave 2` обязательны.
  `Wave 2` строится из gap-analysis по итогам `Wave 1`, а не угадыванием “что еще спросить”.
- парсинг Wordstat допустим только reusable collector-ом из `masks-file -> raw files`.
  Парсинг вручную по одной маске через MCP/tool вызовы запрещён.
- канонический Wordstat entrypoint на этом маке:
  - `bash <ops-skill-root>/scripts/wordstat_tool.sh preflight ...`
  - `bash <ops-skill-root>/scripts/wordstat_tool.sh collect-wave ...`
  - `bash <ops-skill-root>/scripts/wordstat_tool.sh preflight-save ...`
  - `bash <ops-skill-root>/scripts/wordstat_tool.sh collect-wave-save ...`
- discovery order для Wordstat всегда такой:
  - global wrapper `<ops-skill-root>/scripts/wordstat_tool.sh`;
  - global `wordstat_preflight.sh` / `wordstat_collect_wave.js`;
  - только потом project-local fallback из `.claude/skills/direct-search-semantics/scripts/`.
- локальные project scripts нельзя молча считать primary path, если global canonical wrapper доступен.
- режим по умолчанию для агентской работы = `file-first`:
  - raw, summaries, logs и render outputs сначала сохранять в файлы;
  - в контекст не вытаскивать сырые rows/JSON, если это не нужно для точечной проверки;
  - после сбора открывать уже сохранённые `.tsv/.json/.md` частями через `sed/head/rg`.
- до анализа обязателен completeness gate:
  - число raw-файлов должно совпадать с числом масок;
  - пустые/ошибочные raw-файлы должны быть выявлены;
  - новые маски из associations должны быть вынесены в gap/wave2 backlog.
- analysis-скрипты для классификации Wordstat-ключей и минус-слов запрещены.
  После полного raw collection анализ делать только вручную агентами/оператором по raw bundle.
- Не считать Wordstat автоматически только `OAuth`-задачей или только `Cloud`-задачей.
- Сначала нужно live-проверкой определить, какой официальный путь реально доступен клиенту:
  - существующий legacy OAuth-app path;
  - или `Yandex Cloud Search API -> Wordstat`.
- Если legacy path исторически работал у клиента, его нельзя отбрасывать без проверки.
- Если `oauth` токен содержит `wordstat:api`, но live collector получает `403 Forbidden`, это не считать просто "нужно заново авторизоваться".
  Нужно проверить:
  - корректный method/header;
  - не упирается ли проект в `ClientId/app approval`;
  - не нужен ли переход на cloud-path.
- Если preflight написан на `httpx`, не использовать `response.ok`: у `httpx` authoritative-флаг успеха это `response.is_success`.
- Operator-facing Wordstat status должен различать:
  - внутренний баг интеграции;
  - `blocked` по `401/403`;
  - `ready`.
  Нельзя показывать человеку общее `failed`, если live diagnostics уже доказывают конкретный `blocked` verdict по endpoint checks.
- Для cloud-варианта заранее фиксировать:
  - `folder_id`
  - auth mode (`API key` или `IAM token`/service account)
  - роль на сервис-аккаунте `search-api.webSearch.user`
  - какой именно Search API endpoint используется в collect

…(truncated)
