---
name: fb-reply
description: "Read who commented on Anton's recent Facebook posts and post PERSONALIZED replies through his real logged-in Chrome (Claude-in-Chrome MCP = low-ban-risk live-tab path), draft-first and rate-limit-guarded. Trigger on “/fb-reply“, “ответь на комменты“, “ответь комментаторам“, “разбери комментарии под постами“, “reply to my facebook comments“, “ответь в фейсбуке на комменты“"
version: 1.0.0
---

# /fb-reply — ответить на комментарии под постами Антона (безопасно)

> ⛔ **ЛИЧНОСТЬ БРАУЗЕРА (канон 30.08.2026).** Chrome профиля Default на хабе = личность Антона (claude.ai там под **[рабочий аккаунт]**, всегда).
> Робот здесь **ЧИТАЕТ и действует, но НЕ ТРОГАЕТ ЛОГИНЫ**: ни logout, ни смены аккаунта, ни «самолечения входа», ни инжекта кук.
> Увидел «не тот аккаунт» → доложи и работай из робо-профиля (`_chrome_profile_a2`, Firefox-профили). Канон: `reglament-hab-brauzernye-lichnosti-bb-i-roboty`.

**Зачем.** Под постами копятся комменты (часто ценные — как контр-тезис Алаева про tailscale). Отвечать на СВОИ посты — низко-рисковая задача, но Facebook всё равно банит за ТЕМП. Поэтому: читаем безопасно, пишем персонально голосом Антона, постим по одному с паузами под счётчиком.

**Главные правила (из Deep-Research #32):**
- **Темп решает, не «бот/человек».** Каждый ответ проходит `fb_guard check reply`: ≤40/день, ≥5 мин между ответами, без серий подряд. Guard физически не даст перебрать.
- **Каждый ответ персональный и разный** (Opus, голос Антона). Одинаковый текст многим = спам-флаг → бан.
- **Draft-first:** показываю Антону пачку черновиков, постю только после `+`.
- **Account safety:** НЕ кликать «View more comments» на репостах — уводит со страницы. Читаем то, что прогрузилось под оригиналом.

---

## 0. Предохранитель — статус на старте
```bash
python "$USERPROFILE/.claude/scripts/fb_guard.py" status
```
Покажет, сколько ответов уже сегодня и не на паузе ли. Если `reply` уже на дневном лимите — скажи Антону, отложи.

⭐ **ВТОРОЙ ПРЕДОХРАНИТЕЛЬ (09.09.2026): жива ли САМА FB-сессия.** До захода в браузер:
```bash
python "$USERPROFILE/.claude/scripts/_shared/fb_session_check.py" --live
```
Флаг `--live` обязателен. Без него прибор читает только НАЛИЧИЕ и СРОК куки с диска и серверную инвалидацию не видит: 07, 08 и 09.09 куки лежали целыми, FB их отвергал, прибор трижды печатал «✅ ЖИВА», сбор приезжал пустым и это читалось как «комментов нет». С `--live` он делает один GET на `/notifications` и различает: `200` = сессия принята · `302` на `index.php` = куки увидели и ОТВЕРГЛИ (экран «Continue as Anton») · `302` на `login.php` = кук нет вовсе. `exit 1` = мертва (нужны руки Антона: Firefox → facebook.com → войти) · `exit 2` = не определил · `exit 3` = **CHECKPOINT**, стоп-кран account-safety: роботом не лечим, не ретраим, сразу к Антону.

⭐ **Обход перед «нужны руки» пройден до конца 14.09 — НЕ переоткрывай его каждой сменой (результат воспроизведён, записан):** (1) перебор ВСЕХ поверхностей узла даёт ровно два FF-профиля с `c_user/xs`, живая проба обоим = 302 Continue-as; Chrome/Edge cookie-store залочены процессом и не рельса (приказ 01.09). (2) Экран `deoia=1` несёт карточку «Anton Dziatkovskii / **Continue**» — она НЕ восстанавливает сессию одним кликом: FB отдаёт форму **пароля** (`input[type=password]`), а пароль набирает только Антон. ⛔ Опровергнутая удобная гипотеза: клон профиля сессию НЕ теряет — `_PROFILE_KEEP` тащит `cookies.sqlite` + `-wal` + `-shm`, `c_user/xs` в свежем клоне ЕСТЬ (sqlite-запрос ДО запуска браузера); в браузере они исчезают потому, что их стирает САМ FB своим 302 → **после входа Антона лейн заводится без единой правки кода**. Два мелких капкана того же захода: `fs.driver_for_profile` = контекст-менеджер (только `with ... as d`), а поиск кнопки веди по точному тексту (`normalize-space(text())='Continue'`) — матчер по подстроке хватает «Log In», имя и кнопка это РАЗНЫЕ элементы.

## 1. Браузер + чтение комментаторов (без рискованных кликов)
> Строго ЛОКАЛЬНО, живая вкладка, логин не трогаем. Chrome-MCP подключён? (`list_connected_browsers`).
> ⛔ IP-гейт (anton 16.07): комментинг FB — ТОЛЬКО с хаба `[машина флота]` (постоянный IP). На другой машине НЕ отвечать — задачу текстом на хаб. Канон: `reglament-ip-sensitive-deystviya-tolko-s-haba`.

1. Открой нужный пост Антона (он даёт ссылку, или идём по `facebook.com/<профиль>` → его последние посты).
   ⛔ **ГРАБЛЯ 2026-07-28: стена профиля НЕ отдаёт пермалинки.** Скролл `facebook.com/AntonyDzi` даёт ноль `pfbid`-ссылок (FB populates href только по hover), а лента виртуализована — JS-скролл обгоняет ленивую загрузку и посты остаются скелетонами. **Рабочий вход = лента уведомлений** `facebook.com/notifications`: там каждая строка «X commented on your post» уже несёт готовый `/posts/pfbid…` и говорит, КТО и КОГДА написал. Один проход по ней заменяет весь скролл стены. Graph-API путь (`fb_posts_poll.py`) пока мёртв — нет `FB_USER_TOKEN`.
   ⚠️ Открытое окно Messenger засоряет выдачу: его `div[role="article"]` = сообщения личек, не комменты. Закрой окно чата перед сбором.
2. **Извлекай комментаторов В САМОЙ СТРАНИЦЕ** (имена/ссылки FB скрывает через MCP-границу → матчим и фильтруем внутри страницы, наружу отдаём только безопасный текст). Через `mcp__Claude_in_Chrome__javascript_tool`:
   ```js
   const out = [];
   const seen = new Set();
   document.querySelectorAll('div[role="article"][aria-label]').forEach(a => {
     const al = a.getAttribute('aria-label') || '';
     if (!/^Comment by|^Ответ от|^Комментар/i.test(al)) return;          // только комменты
     const blocks = [...a.querySelectorAll('div[dir="auto"]')]
       .map(d => (d.innerText || '').trim()).filter(Boolean);
     const text = (blocks.sort((x, y) => y.length - x.length)[0] || '').slice(0, 500);
     if (!text || seen.has(text)) return;
     seen.add(text);
     const link = a.querySelector('a[role="link"][href]');               // профиль автора
     const handle = link ? (new URL(link.href).pathname.replace(/\//g,'')) : '';
     const hasImg = !!a.querySelector('img[src*="scontent"],img[src*="fbcdn"]');
     out.push({ handle, text, hasImg });                                  // handle = username, не имя
   });
   out;
   ```
   Вернётся список `{handle, text, hasImg}` — без скрытых строк, можно показывать.
3. **Виртуализация:** если комментов мало, мягко проскролль область комментов (`computer` scroll вниз по странице, НЕ кликая кнопки-экспандеры) и повтори §1.2. На посте-РЕПОСТЕ кнопку «View more» НЕ жми.
4. **⚠️ ОБЯЗАТЕЛЬНО разверни свёрнутые ветки ПЕРЕД выбором «кому отвечать» (урок 2026-07-05):** FB прячет существующие ответы под «View N replies» — Антон часто УЖЕ ответил сам, снаружи этого не видно (дубль = позор + спам-сигнал). На СВОЁМ посте инлайн-экспандеры веток безопасны (не путать с «View more comments» на репостах). Через JS: кликнуть все тогглы `/View (\d+ )?repl/i`, подождать ~3с, затем собрать `aria-label^="Reply by Anton Dziatkovskii to <Имя>"` → список УЖЕ отвеченных; отвечать только тем, кого в списке нет. В прогоне 2026-07-05 это отсеяло 7 из 8 «кандидатов».

## 2. Черновики ответов (Opus, персонально)

⭐⭐ **РЕЖИМ = ПРОВОКАТОР (приказ anton 05.08.2026, голосом: «скилл боится задираться, а это плохо; фейсбук любит срач; задирайся»).** Апгрейд «дерзко-краткого» режима 31.07: тот дал короткие ответы, но ПОДДАКИВАЮЩИЕ. Замер прогона 05.08: из 10 ответов 8 были согласием («так и сделали», «давай сверим», «уже почувствовали кошельком»), тред не разогнался ни разу. Дерзость это не тон вежливой фразы, а **вектор**: спорить, а не соглашаться.

### 2.1 Покрытие: отвечаем на ВСЁ
Каждый коммент под постом Антона получает ответ. Односложные («Велосипед», «Советую обеими руками», «говорят хорошая») тоже: это не мусор, это открытая дверь для подкола. ⛔ Не отвечаем ТОЛЬКО на: чистый спам/боты · уже отвеченное (дедуп §1.4) · хард-стопы §2.5. «Малоценный коммент» больше НЕ причина молчать: решение «этот не стоит ответа» = та самая трусость, которую чинит этот приказ. Упёрлись в кап `fb_guard` (40/день) → приоритет: спорные > вопросы к нам > односложные; недобранное идёт первым в следующий заход, а не выбрасывается.

### 2.2 Вектор: возражай, не поддакивай
- **Правило 7 из 10**: минимум семь ответов из десяти несут возражение, встречный вызов или подкол. Согласие разрешено максимум трём, и то с ребром: условие, цена, контрпример. Согласен по сути → всё равно добавь то, с чем собеседник поспорит («да, но у тебя это развалится на N»).
- **Каждый второй ответ кончается вопросом или вызовом.** Вопрос это топливо треда; утверждение тред закрывает.
- ⭐ **Сталкивай комментаторов между собой.** Под постом почти всегда есть двое с противоположными тезисами: упомяни второго в ответе первому. Это разгоняет обсуждение сильнее любого нашего остроумия и стоит нам одного ответа вместо двух.
- ⭐ **Держи ветку 2-3 хода, не сливайся.** Ответили на наш подкол → отвечаем снова. Замер 05.08: Долгов ответил на наш ответ через 50 минут, никто не продолжил, тред умер на втором ходу. Проверять свои вчерашние ветки на новые реплики это ШТАТНАЯ часть прогона, а не «если останется время».
- ⛔ **Ban-лист мягких формул** (встретил у себя в черновике = переписать): «спасибо», «согласен», «в точку», «хорошая мысль», «интересно», «попробуем», «гляну», «уже сделали» без цифры, любой смайл. Ответ, который собеседнику не хочется оспорить, провален.

### 2.3 Длина
Потолок прежний: **цель ≤5 слов, максимум 7** (CLAUDE.md §3.3, текст без вычитки Антоном; вопрос входит в эти же 7). Короткая шпилька провоцирует лучше абзаца. Исключение одно: контр-тезис по НАШЕЙ архитектуре, требующий разбора, тогда пишем развёрнуто и по-прежнему спорно, а не вежливо.

### 2.4 Граница: дерзко ≠ хамство
Бьём по ТЕЗИСУ, по цифре, по позиции. Никогда по человеку: ни внешность, ни возраст, ни нация, ни доход, ни ум («ты дурак» в любой обёртке), ни его семья. Тест перед отправкой: **если убрать его тезис, остаётся ли в моём ответе что-то обидное лично про него? Осталось = переписать.** Второй тест: прочитал бы я это ему в лицо за кофе и остались бы мы в нормальных отношениях?
⭐ **Калибровка по адресату** (не отменяет дерзость, меняет её остроту): лид из CRM, потенциальный наниматель, инженер под Mission-2 (Симаков, Русавский и подобные) получают дерзость строго по тезису и ноль сарказма про самого себя, мы их зовём работать. Незнакомый задира получает полную мощность.
⭐ **Личный выпад в наш адрес больше НЕ хард-стоп, а мишень номер один** (отмена практики 04-05.08, где такие комменты молча пропускались). Он даёт треду максимум энергии. Отвечаем коротко, уверенно, без обиды и без зеркального оскорбления. Пример 05.08, пропущенный зря: «Вам нравится использовать людей, не так ли?» → «людей нет, только агенты. завидно?».

### 2.5 Хард-стопы (не тронуты приказом)
Деньги, сделки, обязательства · юридическое (патенты, претензии, суммы) · медицина, особенно чужие дети · секреты · политика и нация · чужой пост, а не пост Антона. Здесь молчим целиком: подкол в этих зонах стоит дороже любого охвата.

### 2.6 До → после на реальных ответах 05.08
Те же комменты, что вчера получили поддакивание, в новом режиме:

| коммент | было (поддакнул) | стало (задирается) |
|---|---|---|
| «прикрути mem0, будет полный фарш» | фарш свой, но mem0 гляну | mem0 умрёт на пятой тысяче |
| «нужна автоматизация из 20 шагов» | двадцать шагов уже крутятся | двадцать шагов? покажи свои три |
| «когда вы уже что-то накодите?» | пока ты писал, накодили | а ты сегодня что зашипил? |
| «отменили выгодные кодинг-планы» | мы уже почувствовали кошельком | плати или ной, выбирай |
| «пилим похожее, вектора и граф» | давай сверим графы | граф без цифр это картинка |
| «БД 194Tb после эмбеддинга» | 194 терабайта, уважаю | 194Tb и какой recall? |

Видно одно: старый вариант закрывает разговор, новый требует ответа.

- Каждый текст уникален (спам-фильтр FB), прогон через ban-лист /ai-slop обязателен (эмодзи, тире, ё в коротком ответе особенно палятся).
- Плашка Майкрофта на 5-словный коммент НЕ вешается отдельной строкой: это тред под постом Антона, раскрытие живёт на уровне поста и группы (§3.3, тон решает канал).

### 2.7 ⛔ ГЕЙТ ПАЧКИ: прогони черновики через дверь, не через ощущение
Собрал пачку → до постинга прогони её машинной проверкой (иначе правило §2 остаётся прозой, а проза размякает: замер 05.08 показал 8 согласий из 10 при формально включённом «дерзком» режиме):
```bash
python ~/.claude/scripts/fb_reply_gate.py check <batch.json> --coverage-answered <N> --coverage-total <M>
```
`batch.json` = `[{"who":"Имя Фамилия","text":"текст ответа"}, ...]`. Коды: 0 PASS · 1 FAIL · 3 кривой ввод.
Гейт считает: длину ≤7 слов, ban-лист мягких формул, долю поддакиваний ≤30%, долю вопросов ≥50%, переход на личность, дубли, эмодзи и тире, покрытие, пустую пачку. **FAIL = переписать пачку, а не постить.** Зелёный гейт значит «это не поддакивание», НЕ «это остроумно»: вкус по-прежнему на нас, гейт ловит только беззубость. Паспорт: `~/.claude/scripts/docs/fb_reply_gate.md`.

Для каждого коммента напиши **отдельный** ответ по правилам выше: без шаблона, тексты разные. Собери пачку и покажи Антону:
```
1. [Денис, deios] коммент: «…tailscale…»   →  черновик: «Денис, в точку. Покажешь, как у тебя транспорт устроен?»
2. ...
```
Жди `+` (или правки). Это draft-first gate.

## 3. Постинг по одному, под счётчиком — ПРОВЕРЕННАЯ МЕХАНИКА (2026-07-04)
> Отлажено вживую: ответ Антона на коммент Alex Kaplunovich лёг как threaded-reply. Ключевые грабли ниже — НЕ обходи их заново.

Для КАЖДОГО одобренного ответа:
1. **Guard-gate:** `python "$USERPROFILE/.claude/scripts/fb_guard.py" check reply`
   - `BLOCKED ...` (exit 3) → СТОП постить. Скажи Антону, сколько ждать / что в очереди. Остаток — позже/следующим заходом.
   - `OK reply` → продолжай.
2. **Открой пост** на `www.facebook.com/<profile>/posts/<pfbid>` (рендерится как модалка `role="dialog"` — это норма, работаем в ней).
   ⚠️ **Проверь, что реально попал на нужный пост** (грабля 2026-07-27): FB умеет увести вкладку на СОСЕДНИЙ пост, а комменты прошлой страницы остаются висеть в DOM скрытыми → цель находится, а ответ уходит не туда. Лечение: после навигации сверить `location.href`/`document.title`, а цель искать ТОЛЬКО среди видимых (`a.offsetParent!==null`).
3. **Скриншот** (`computer` screenshot) чтобы увидеть раскладку и НАЙТИ ГЛАЗАМИ ссылку «Reply». ⚠️ Скрин на тяжёлой модалке иногда виснет («captureScreenshot timed out / renderer frozen») — просто ПОВТОРИ скрин, он отвечает через раз, это не фатально.
4. **Кликни по РЕАЛЬНОЙ ТЕКСТОВОЙ ссылке «Reply»** в строке «Like · Reply · Hide» под нужным комментом — ПО КООРДИНАТЕ из скриншота. ⛔ ГРАБЛЯ: `find` возвращает «Reply»-ref, который при клике уводит фокус на КНОПКУ CLOSE модалки (инлайн-поле НЕ открывается). Кликай визуально по координате текста «Reply», НЕ по find-ref.
5. Откроется инлайн-композер ответа с @упоминанием автора. **Ставь фокус через JS, не кликом по координате** (урок 2026-07-20): модалка реflow-ит между скриншотом и кликом → клик попадает в тело коммента, `type` уходит в никуда (дважды подряд). Рабочий рецепт:

> ⛔ **ГРАБЛИ ВВОДА И ОТПРАВКИ (2026-07-27, доказано вживую — ЭТО ТЕПЕРЬ ОСНОВНОЙ ПУТЬ):**
> **(1) `computer type` СЪЕДАЕТ ПРОБЕЛЫ И ТОЧКИ.** «жду. звёздочку поставлю первым» легло в поле как `ждузвёздочкупоставлюпервым`. Опубликуй не проверив = позор под своим постом. **Вместо `type` вставляй текст через JS** (пробелы сохраняются, @упоминание цело):
> ```js
> f.focus();
> const r=document.createRange(); r.selectNodeContents(f); r.collapse(false);
> const s=getSelection(); s.removeAllRanges(); s.addRange(r);
> document.execCommand('insertText', false, '<текст ответа>');
> ```
> **(2) Дискретные нажатия клавиш (`key Return`, `key BackSpace`) НЕ ДОЛЕТАЮТ до композера** — между MCP-вызовами фокус не держится, поле остаётся с текстом, публикации нет (и Backspace ничего не чистит). **Отправка зависит от ТИПА композера, их ДВА:**
> - **Композер верхнего уровня** (ответ на top-level коммент): есть кнопка — жми её из JS:
> ```js
> let box=f, up=0; while(box && up<8){box=box.parentElement; up++; if(box.querySelector('[aria-label="Post comment"]')) break;}
> box.querySelector('[aria-label="Post comment"]').click();
> ```
> - **Композер вложенной ветки** (ответ на чей-то reply): кнопки `Post comment` НЕТ вообще (проверено подъёмом на 12 уровней — только эмодзи/GIF/стикеры). Отправка = синтетический Enter, **обязательно в ОДНОМ вызове с `focus()`**, иначе фокус теряется и ничего не улетит:
> ```js
> f.focus();
> const r=document.createRange(); r.selectNodeContents(f); r.collapse(false);
> const s=getSelection(); s.removeAllRanges(); s.addRange(r);
> const o={key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true,cancelable:true,composed:true};
> f.dispatchEvent(new KeyboardEvent('keydown',o));
> f.dispatchEvent(new KeyboardEvent('keypress',o));
> f.dispatchEvent(new KeyboardEvent('keyup',o));
> ```
> Универсальный порядок: сначала ищем кнопку, нет кнопки → синтетический Enter.
> **(3) Надо стереть уже набранное** — не Backspace'ом, а выделением в ОДНОМ JS-вызове: каретка в конец → `s.modify('extend','backward','character')` × N → проверить `s.toString().length===N` и что в выделение НЕ попало имя из @упоминания (иначе abort) → `execCommand('insertText', ...)` затрёт выделенное.
> **Вывод одной строкой:** весь цикл (открыть композер → вставить → отправить) делается через JS; `computer` в этом скилле нужен только для скриншотов.

   ```js
   const f=[...document.querySelectorAll('[contenteditable="true"]')]
     .find(e=>/Reply to <Имя>/.test(e.getAttribute('aria-label')||''));
   f.scrollIntoView({block:'center'}); f.focus();
   const r=document.createRange(); r.selectNodeContents(f); r.collapse(false);   // каретка в конец, после @упоминания
   const s=getSelection(); s.removeAllRanges(); s.addRange(r);
   ```
   затем вставить шутку (≤5–7 слов, голос Антона/Opus) через `execCommand('insertText')` — НЕ `computer type`, см. грабли выше. Композер открывать тоже через JS: внутри нужного `article` кликнуть элемент с `innerText==='Reply'`.
6. **Проверь через JS** (скрин может висеть — DOM надёжнее): среди `[contenteditable="true"]` есть поле с `aria-label="Reply to <Имя>"`, и в нём твой текст (упоминание + шутка). Только тогда отправляй.
7. **Отправь:** JS-клик по `[aria-label="Post comment"]` (см. грабли выше). ⛔ `computer key Return` НЕ работает — не долетает до поля.
8. **Подтверди публикацию через JS:** появился `div[role="article"][aria-label^="Reply by <моё-имя> to <Имя>'s comment"]` с твоим текстом И поле ответа очистилось (`innerText===''`). Только это = «опубликовано».
8'. ⚠️ **Проверка ПОСЛЕ релоада требует 2-3 раундов разворачивания веток** (замер 02.08): на свежей загрузке поста вложенный ответ (наш reply на чужой reply) в DOM ОТСУТСТВУЕТ, пока ветка свёрнута — один проход по экспандерам его не достаёт. Прогон 02.08: 4 из 5 нашлись сразу, пятый («дешево, пока не продакшн») проявился только на 2-м раунде кликов. Вывод: «не нашёл после релоада» ≠ «не опубликовалось» — сперва разверни всё, потом делай вывод. Доказательство публикации = наш текст + серверная метка возраста («7m», «29m») в `Reply by Anton Dziatkovskii`.
8''. ⛔ **ЗЕРКАЛЬНАЯ ГРАБЛЯ (2026-08-03, замер): «текст появился в DOM сразу после Enter» — НЕ доказательство.** Композер очищается и FB рисует оптимистичный ответ ещё до подтверждения сервером; если публикация не прошла, этот узел тихо исчезает при следующей загрузке. Прогон 03.08: ответ Николаю [человек] дважды показывал `inDom:true` через 4с после Enter и дважды отсутствовал после релоада (его коммент на месте, свёрнутых веток под ним нет) — то есть не опубликовался ни разу. **Единственное доказательство = после релоада + разворачивания веток найден `Reply by Anton Dziatkovskii to <Имя>` с нашим текстом И серверной меткой возраста («1m», «6m»).** Вместе с 8' это даёт симметричное правило: до релоада НЕ судим вообще — ни «опубликовалось», ни «не опубликовалось».
8'''. ⚠️ **Вкладку уводит на чужой пост прямо во время релоада** (повтор граблей 27.07, замер 03.08: `location.reload()` на посте Антона вернул страницу `facebook.com/<чужой-профиль>/posts/...`, а в DOM остались висеть наши комменты с прошлой страницы → проверка «нашёл текст» врала). После КАЖДОГО релоада сверяй `document.title` / `location.pathname`, и если увело — вернись по прямому URL поста и проверяй заново.
9. `python "$USERPROFILE/.claude/scripts/fb_guard.py" record reply` (⚠️ неудачную попытку тоже записывай: FB её видел, темп она съела).
10. Guard держит паузу ≥5 мин до следующего — НЕ обходи ускорением (темп = главная причина бана).
    Ждать паузу так (foreground `sleep` заблокирован харнесом, `Start-Sleep`+команда тоже):
    ```bash
    until python "$USERPROFILE/.claude/scripts/fb_guard.py" check reply >/dev/null 2>&1; do sleep 15; done; echo "GUARD OPEN"
    ```
    запускать через Bash с `run_in_background: true` — придёт уведомление ровно когда окно открылось.
11. ⛔ **Проверь текст на длинное тире ПЕРЕД отправкой** (`/[—–]/`) — правило [[no-long-dashes]] действует и на комменты. Поймал в поле → не Backspace, а выделение назад на N символов + `insertText` (§3 грабля 3); проверь, что в выделение не попало @упоминание.
12. Видимость элементов: `offsetParent` внутри модалки FB **всегда null** (position:fixed) → фильтр «видимый» строй на `getBoundingClientRect()` + `checkVisibility()`, иначе отсеешь ВСЁ и решишь, что комментов нет.

8''''. ⛔ **Уведомления оставляют ФАНТОМНЫЕ комменты в DOM** (замер 05.08). После перехода с `facebook.com/notifications` на пост первые ~4 секунды в дереве висят `role="article"` с ПРОШЛОЙ страницы: прогон 05.08 «нашёл» коммент Alexander [человек] под постом, где на самом деле `No comments yet`. Лечение: ждать ≥6 с после навигации И проверять, что коммент реально виден на странице (`RPL` ищет article заново перед кликом — это и есть встроенный фильтр); «комменты есть» на 4-й секунде = не улика. Второй симптом того же корня: `localStorage` FB подчищает свои ключи, список целей между переходами держать в `window.name` (переживает навигацию в той же вкладке).
8'''''. ⚠️ **Композер выбирается НЕ по первому `contenteditable`** (замер 05.08, промах вживую): открытые ранее композеры остаются в DOM, и `find(/Reply to/)` отдаёт ЧУЖОЙ — текст для Языкова лёг в поле Дидошика. Правильный отбор: снять список `Reply to`-полей ДО клика, после клика взять РАЗНИЦУ (`fresh`), и только если полей ровно одно — взять его. Проверять надо не `aria-label` поля (у вложенной ветки он = `Reply to Anton Dziatkovskii`, имя адресата там не появится), а @упоминание ВНУТРИ текста: `/Фамилия/.test(f.innerText)`.
8''''''. ⛔ **Зависший черновик JS не стирается.** `execCommand('insertText','')`, `execCommand('delete')` и `s.modify(...)×N` на композере FB не дают ничего (замер 05.08, три попытки). Работает `computer` triple_click по координате поля + `computer key Delete`. Чистить обязательно: черновик переживает релоад, и завтрашний прогон допишет свой текст к вчерашнему.

## 3-бис. Лайк-хвост (штатно, не спрашивать)
Голосовая Антона 04.08: «все наши посты и комментарии должны получать лайки — это ничего не стоит, а людям приятно». После того как ответ ПОДТВЕРЖДЁН опубликованным (§3.8, релоад + развёрнутые ветки):

⭐ **На рельсе Лисы это ДВИЖОК, а не проза** (02.09.2026): `python "$USERPROFILE/.claude/scripts/fb_like_tail.py" --urls <пермалинки> --max 5`. Он сам держит guard (check перед каждым лайком, record после), сам сверяет pfbid, сам не трогает `Remove Like` и сам пишет строку в `fb_like_roster.py`. Ручной порядок ниже оставлен как описание механики и запасной путь под Chrome-MCP.
1. `python "$USERPROFILE/.claude/scripts/fb_guard.py" check like_own` → `BLOCKED` = пропускаем хвост, не обходим.
2. Like на **комментарий, на который отвечали** (человеку приятно, что его реплику заметили).
3. Like на **наш собственный ответ**.
4. Заодно, раз мы уже в этом посте: Like на все остальные комментарии под ним, которые ещё без нашего лайка.
5. После каждого лайка: `python "$USERPROFILE/.claude/scripts/fb_guard.py" record like_own`; один раз на пост — `python "$USERPROFILE/.claude/scripts/fb_like_roster.py" record --person "<permalink>" --kind own --outcome ok --note "хвост после ответов"`.

⭐ **ЛАЙК СТАВИТСЯ ИЗ JS, координаты и скриншоты не нужны** (замер 05.08, 43 лайка без промаха). Кнопка не имеет текста «Like» — она живёт в атрибуте: `[role="button"][aria-label="Like"]` = НЕ лайкнуто, `aria-label="Remove Like"` = УЖЕ лайкнуто. Отсюда состояние читается детерминированно, и случайное снятие лайка исключено by design:
```js
const b=[...art.querySelectorAll('[aria-label]')].find(e=>e.getAttribute('role')==='button'&&e.getAttribute('aria-label')==='Like');
if(b) b.click();   // "Remove Like" не трогаем никогда
```
⚠️ Пауза 4 с между кликами И **не больше 5 лайков за один вызов**: 8 кликов не влезли в лимит CDP 45 с, вызов упал таймаутом уже ПОСЛЕ части кликов (счёт лайков потерян, в guard пришлось писать оценку сверху).
⛔ **Перед КАЖДОЙ пачкой лайков сверять, что мы на нужном посте** (замер 05.08: переход на пост Антона привёл вкладку на чужой `facebook.com/reel/...`, 4 лайка ушли под чужой ролик). Хэш `location.pathname` сверять и до цикла, и после разворачивания веток; не совпал → abort, а не «ну лайкну что вижу».
⛔ Ручной путь (если JS-путь когда-то умрёт): кликать `Like` в строке `Like · Reply · Hide` по координате из свежего скриншота, не по `find`-ref (та же грабля, что в §3.4). Полный порядок: `/fb-likes` ЛЕЙН 1.

## 3-трис. Комментарий содержал СОВЕТ → строка в реестр кредита
Провокатор спорит, но совет по делу — это подарок, и автор обязан дожить до имени в CREDITS.
По каждому комменту, где человек предложил репо/тул/приём/дельную критику:
```
python ~/.claude/scripts/alpha_credit.py add --source fb \
  --author "<Имя>" --url <permalink коммента> \
  --advice "<что посоветовал одной фразой>" --kind repo|tool|method|critique \
  --ref "fb:<comment_id>"
```
`--ref` обязателен (этот скилл перечитывает те же посты — без ключа пойдут дубли).
Дальше — `/alpha-credit`: судить → испытать → публично поблагодарить → вписать имя.

## 4. Доложи
Что ответили, что в очереди (ждёт паузы/лимита), сколько сегодня `reply N/40` и `like_own N/60`.
Плюс: сколько советов ушло в реестр кредита (`alpha_credit.py pending`).

---

## Стоп-краны (account safety)
- Любое предупреждение FB / checkpoint / «слишком часто» → **немедленно СТОП**, доложи Антону, без ретрай-спама.
- НЕ кликать «View more comments» / навигационные кнопки на репостах (уводят со страницы).
- НЕ постить одинаковый текст разным людям.
- Логин/2FA не трогаем.

## ⭐⭐ 3-кватро. ПОСТИНГ ЧЕРЕЗ CHROME-MCP: ПОЛНАЯ МЕХАНИКА (доказана публикацией 20.09.2026)

Движок `fb_reply_post.py` ездит на Selenium-Firefox; когда Firefox-рельса мертва (шаг 0), постим
руками через `claude-in-chrome`. Метод найден 20.09 и доказан опубликованным ответом
(«Reply by Anton Dziatkovskii to יאנינה וורונצוב's comment a few seconds ago»). Порядок ровно такой:

1. **Открыть композер** JS-кликом: у нужного `div[role="article"]` с `aria-label` = `Comment by <Имя> …`
   найти внутри `div[role="button"]`/`span` с текстом ровно `Reply` и кликнуть. Проверить, что
   появился `div[role="textbox"][contenteditable="true"]` с `aria-label` = `Reply to <Имя>`.
2. ⭐ **ТЕКСТ ЗАНОСИТСЯ ТОЛЬКО РЕАЛЬНОЙ ВСТАВКОЙ** — `Set-Clipboard` в PowerShell, затем реальный
   `left_click` в поле, затем реальный `ctrl+v`. ⛔ Три НЕ работающих пути (все проверены 20.09):
   · `document.execCommand('insertText')` — текст ложится символ-в-символ, но ТОЛЬКО в DOM;
   · синтетический `ClipboardEvent('paste')` с DataTransfer — то же самое, DOM-призрак;
   · `computer action=type` — режет пробелы и пунктуацию на кириллице (грабля 15.09).
   Признак, что FB текст ВИДИТ (а не только вы): в форме композера появляется кнопка
   `aria-label="Post comment"`. Нет кнопки = lexical-модель пуста, отправлять нечего, Enter молчит.
   Бонус: реальная вставка заставляет lexical перерисоваться и сама стирает прежние DOM-призраки.
   ⭐⭐ ГРАБЛЯ 21.09 (стоила двух промахов подряд): ОДНОГО КЛИКА В ПОЛЕ НЕ ХВАТАЕТ. Клик ставит
   каретку НА чип-меншен («Vadim Vilkov» подсвечивается синим), и `ctrl+v` в этом состоянии
   уходит В НИКУДА: поле остаётся с одним меншеном, ошибки нет, признака нет. Рабочая
   последовательность из трёх реальных клавиш: `left_click` внутри поля → **`End`** → `ctrl+v`.
   `End` уводит каретку за чип, и текст ложится символ-в-символ (проверено 3/3 ответа 21.09).
   Пропуск `End` = тихий промах, который виден только перечитыванием `innerText` (§3.6).
   ⭐ Масштаб скриншота НЕ константа: 20.09 было 1393×868 при вьюпорте 1040×648 (≈1.34),
   21.09 — 1568×670 при 1576×673 (≈1.0). Не заучивай коэффициент, считай его на месте
   (`screenshot_w / window.innerWidth`) или целься по свежему скриншоту.
3. ⭐ **ОТПРАВКА = КЛИК ПО СИНЕЙ СТРЕЛКЕ, И КООРДИНАТЫ БЕРУТСЯ СО СКРИНШОТА.** `Enter`/`Return`
   не отправляют (проверено дважды). У `computer` система координат — КАДР ПОСЛЕДНЕГО СКРИНШОТА
   (замер 20.09: 1393×868), а `getBoundingClientRect()` отдаёт CSS-пиксели вьюпорта (1040×648),
   масштаб ≈1.34: три клика по кнопке ушли в пустоту, пока не был снят скриншот. Снимай скриншот
   и целься по нему, либо умножай CSS-координаты на `screenshot_w / window.innerWidth`.
4. **Доказательство** (§3.8): перечитать `div[role="article"]` и найти `aria-label`,
   начинающийся с `Reply by Anton Dziatkovskii to …`, плюс серверную метку возраста
   («a few seconds ago»); композер при успехе пуст. Затем `fb_guard.py record reply`.
5. Композер RTL, если имя адресата на иврите: текст в поле рисуется справа налево и `?` уезжает
   влево — это ВЁРСТКА, а не испорченный текст, `innerText` показывает правду.

## Связанное
- 🗺 **Общая карта FB-рельсы (читать первой при любой FB-задаче):** `~/.claude/scripts/docs/fb-rails.md` — где лежат данные, все капы `fb_guard`, IP-гейт, общие грабли браузера, кто из `fb-*` за что отвечает.
- `/fb-likes` — лайк-хвост после ответов (§3-бис) и ежедневный обход лидов.
- `/fb-post` — публикация постов (Фаза 1, тот же guard).
- `fb-dm` (скилла ещё НЕТ) — личка комментаторам (Фаза 2, строгий draft-first; пока не построен).
- Счётчик: `~/.claude/scripts/fb_guard.py`. Голос: `fb-diary-voice`.
- Канон: Decision Memo 2026-06-28, `chrome-autonomy-self-drive`, `browser-work-on-peers-not-hub`.

## ⭐ Чёрный юмор обязателен (anton 10.09.2026, голосом)

Любой текст, который этот скилл отправляет ЖИВОМУ человеку наружу, несёт чёрную самоиронию — ровный служебный тон = дефект, объяснять надо не шутку, а её отсутствие. Строку берёшь через `/mycroft-joke` (полка (д) банка + строки 56-62 под холодное касание), приёмка — `/taste-check` P36. ⛔ Не шутим ровно в трёх зонах, и они не про вкус: строго-научная публикация (arXiv/журнал/JOSS) · юр.обязательство и анкета · красный список банка (чужая иконография угнетённых, псевдо-слуры, отрицание что я ИИ, юмор поверх диагноза/денег/беды собеседника). Одна шутка на тред. Канон: CLAUDE.md §3.3 §Поправка 10.09 + [[dark-humor-default-everywhere-external]].
