跳到正文
cccskills
免费在 GitHub 公开

pr-reply

Answer reviewers on your own pull requests in other people's repos: find threads where the ball is on your side, tell humans from bots, do exactly what was asked and reply with evidence. Use at the start of any GitHub session or on a new-review alert. Triggers: /pr-reply, /prr, answer the reviewers, who is waiting on us.

中文简介正在准备中。当前显示原文说明。

集成工具TelegramGitHub
查看安装方法

包含的文件(1)

  • SKILL.md52.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

/pr-reply — ответить тем, кто уже согласился с нами разговаривать

Закон

Молчание в живом треде = провал, а не осторожность (CLAUDE.md §4.7). Отвечаю САМ: GitHub выведен из-под гейта Наташи, «+» Антона не жду. Порог — 24 часа.

Живой ревьюер дороже нового PR. Человек, который написал на нашем PR, уже потратил своё время — это самая тёплая дверь, какая у нас есть, и она закрывается от молчания, а не от несогласия. Спорить с ним можно и нужно ([[objection-sparring-to-consensus]]), молчать — нет.

Шаг 1 — снять очередь (0 токенов, 0 своей сети)

python ~/.claude/scripts/pr_watch.py --digest

Секция 🔴 МЯЧ У НАС — это и есть очередь. Дальше глазами раздели её на три кучи:

Кто говорил последнимЧто делаем
живой человекработаем сегодня, начиная с самого старого
бот (codecov[bot], cubic-dev-ai[bot], review-notebook-app[bot], chatgpt-codex-connector)чиним, если по делу; отвечать текстом не нужно
⏳ STALE/CLOSE-бот (github-actions[bot]: «This PR is stale…», «will be closed in N days»)ИСКЛЮЧЕНИЕ ИЗ СТРОКИ ВЫШЕ: текст ОБЯЗАТЕЛЕН и он же есть лекарство. Коммент — единственное, что снимает ярлык и обнуляет счётчик (actions/stale по умолчанию держит remove-stale-when-updated: true). Молчание = удаление артефакта в объявленный срок. Писать СОДЕРЖАТЕЛЬНО: что закрыто по ревью · чем дешево ревьюить (что идёт в CI без ключа) · предложение ребейзнуть или подвинуть. Голый «bump» ⛔ (§1.5 спам ≠ громкость). Дату смерти брать фактом: gh api repos/<o>/<r>/contents/.github/workflows/stale.yaml → days-before-pr-stale + days-before-pr-close; конфига нет = «бота нет» (это claim, бессрочность не выдумывать). Живой случай 30.09: openai/openai-cookbook#2880, 74k звёзд, до удаления оставалось 7 дней, три прогона вахты промолчали — все три двери (pr_watch.py · вахта тредов · эта таблица) считали бота шумом.
🔧 INTAKE-бот с МЕХАНИЧЕСКИМ флагом (graphiti-intake-bot и родня: «нужен linked issue Fixes #<N>», «нужна метка», «отметьте пункт чек-листа», «заполните шаблон»)ЛЕЧИТСЯ ПРАВКОЙ АРТЕФАКТА, А НЕ КОММЕНТОМ. Флаг снимает gh pr edit <N> --body-file / --add-label / правка заголовка — бот перечитывает PR на своём прогоне и гасит метку сам, в треде НОЛЬ шума. Коммент сюда = бамп (§1.5). Проверка, что сработало: метка-флаг ИСЧЕЗЛА (gh api repos/<o>/<r>/issues/<N> --jq '[.labels[].name]') — это независимый сигнал, а не наш claim. ⚠️ Ключевое слово Fixes #N ЗАКРЫВАЕТ issue на мерже: наш PR = часть issue → ставим слово (бот требует буквально его) И рядом scope-строку «это подмножество, снимите ключевое слово на squash или переоткройте», выбор отдаём мейнтейнеру, молча чужой issue не хороним. Живой случай 02.10: getzep/graphiti#1810, часы до 15.10, флаг needs-issue снят правкой тела за один вызов.
🤖 СИНТЕТИЧЕСКИЙ ПИР БЕЗ СУФФИКСА [bot] (<имя><цифры>-coder / -code / -lang и родня: qaisermehdi3-coder, juancarlosrial76-code, eddyflores100-lang)ЭТО НЕ ЖИВОЙ ЧЕЛОВЕК, И В ЧУЖОМ ТРЕДЕ ОТВЕТ ЕМУ = ШУМ МЕЙНТЕЙНЕРУ. Строка выше («боту отвечают коммитом») ловит только логин с [bot]; эти аккаунты проходят фильтр как люди и приносят полированный LLM-текст через часы после нашего коммента. Дешёвая проба ОДНИМ вызовом перед ответом: gh api users/<login> --jq '[.created_at,.public_repos,.followers,(.bio//"-")]' — молодой аккаунт + public_repos<=2 + followers<=1 + пустой bio = синтетический пир (живой мейнтейнер для сравнения: dsfaccini 2020 год, 70 репо, 46 фолловеров, bio). В ЧУЖОМ треде такой коммент долгом НЕ является, отвечаем только когда обращается мейнтейнер или автор. Живой случай 03.10: pydantic/pydantic-ai#8704 — мы ответили qaisermehdi3-coder коммент-в-коммент, и через 2 минуты assignee dsfaccini написал «bots: please refrain from replying to each other, we've got enough input here»; четвёртый коммент за 13 часов в треде, который мейнтейнер держит чистым. Журнал: класс reply-to-synthetic-peer-in-foreign-thread, 1-й датированный случай.
«нет снимка»почти всегда позиция, добавленная СЕГОДНЯ нами же — см. блок ниже ДО любых сетевых вызовов

⚠️ «Нет снимка» ≠ долг: сперва реестр, потом сеть (замер 27.09)

Позиция попадает в 🔴 без снимка просто потому, что сторож её ещё не видел, а --add был сегодня. Дешёвый разбор — 0 сетевых вызовов, по самому реестру _outreach/outbound_github.json:

python3 -c "import json;d=json.load(open('$OBSIDIAN_VAULT/_outreach/outbound_github.json'));\
print('\n'.join('%s#%s kind=%s added=%s :: %s'%(i['repo'],i['number'],i.get('kind','issue'),i.get('added'),i.get('note','')[:70]) for i in d['prs'] if i.get('added')=='<сегодня>'))"

Две вещи, которые эта строка отдаёт даром:

  • added = сегодня + note «ответ … ждём реакции» → мяч у НИХ, это наш собственный свежий ответ. Не долг, сеть не нужна.
  • kind: discussion → у дискуссий НЕТ repos/<o>/<r>/issues/N, REST вернёт 404 на всё подряд. Дверь только GraphQL: gh api graphql -f query='{repository(owner:"O",name:"R"){discussion(number:N){isAnswered updatedAt comments(last:2){nodes{author{login} createdAt}}}}}'. ⚠️ В zsh не собирай такой цикл через set -- $spec — $3 приезжает пустым и GraphQL падает на discussion(number:); заведи check() { … "$1" "$2" "$3" … } и вызывай явно.

Замер 27.09: шесть позиций «нет снимка» → три дискуссии (REST дал 404, я списал это на мусор в реестре — неверно) и три треда с нашим же ответом того же дня. Настоящий долг среди шести был один (наш PR с упавшим CI-ботом), и он виден по kind + автору без единого запроса к GitHub.

⚠️ Поправка 05.10: note бывает ПУСТОЙ, и тогда реестр отдаёт только added (замер: 17 позиций разом)

Эвристика выше опирается на note со словами «ответ … ждём реакции». В тот день она не сработала: из 17 красных «нет снимка» 13 имели note: None, потому что веер другого лейна узла добавил их без заметки. Голого added = сегодня для вердикта мало — он не отличает «мы ответили» от «нас спросили». Добирается ОДНИМ пакетным циклом (1 вызов модели на все позиции, не по одной):

for p in "owner/repo 123" "owner/repo2 456"; do R="${p% *}"; N="${p##* }"
  echo "$R#$N by=$(gh api repos/$R/issues/$N --jq '[.user.login,(.pull_request!=null|tostring)]|join("|")') \
last=$(gh api repos/$R/issues/$N/comments?per_page=100 --jq '.[-1]|[.user.login,.created_at]|join("|")')"
done

Вердикт читается механически: last = tonydzi сегодня → не долг (наш свежий коммент) · last пустой → наш PR без комментов, не долг · last = *[bot] → не долг · last = живой человек → в разбор. В тот заход так закрылись 17 позиций, и настоящие долги пришли не отсюда, а из mentions/involves (блок ниже). ⚠️ Цикл писать на bash с массивом (см. предупреждение про set -- $spec в zsh выше).

⚠️ Digest видит только реестр — добери упоминания (замер 15.09)

gh api -X GET search/issues -f q='mentions:tonydzi updated:>=<вчера>' --jq '.items[].html_url'

По каждой ссылке: последний писал не мы и обращался к нам → в очередь; нет в реестре → pr_watch.py --add <url>. 15.09 так нашлись agno#10086 и langgraph#8764: ответ нам ждал 15–40ч, а в outbound_github.json тредов не было. Ссылка даёт 404 через REST /issues/N → сперва GraphQL discussion(number:N): 15.09 все 6 «404» оказались discussions.

⚠️⚠️ Дайджест показывает НОЧНОЙ снимок — «🟢 активность от tonydzi» НЕ значит, что мяч у них (замер 20.09)

--digest делает 0 сетевых вызовов, поэтому дневной заход видит картину прошлой ночи. Тред, где последним писали МЫ ночью, стоит в 🟢 с подписью «активность от tonydzi» — и остаётся там, даже если инженер ответил нам сегодня утром: красным он станет только на следующем ночном тике, через сутки. Замер 20.09: дайджест дал 1 настоящий долг (litellm#32642), а добор mentions:tonydzi дал ещё ДВА, и оба были ответами живых инженеров на наши же комментарии того же дня — agno#10366 (18:26Z) и langgraph#8039 (14:31Z); в дайджесте оба стояли зелёными «активность от tonydzi». Правило: для КАЖДОГО треда из добора, обновлённого за 24ч, смотри последний коммент ФАКТОМ, а не поле author из снимка. Зелёная строка с нашим именем — довод НЕ проверять только если добор её не поднял.

⭐ КАК ДЕЛАТЬ ДОБОР ОДНИМ ВЫЗОВОМ (замер 22.09, 5-й случай класса подряд). Проверять последнего комментатора по 40–60 позициям REST-парами — это ~110 сетевых вызовов; один GraphQL с алиасами даёт то же за один. Шаблон: на каждую позицию aN: repository(owner:"o", name:"r"){ issueOrPublicRequest…}, внутри issueOrPullRequest(number:N) с двумя инлайн-фрагментами (... on Issue / ... on PullRequest), в каждом comments(last:1){ nodes{ author{login} createdAt } }. comments(last:1) снимает всю возню с пагинацией: GraphQL сам отдаёт ХВОСТ, грабля .[-1] первой страницы к нему не относится. Два условия, иначе прибор соврёт: (1) gh api graphql возвращает ненулевой код при ЧАСТИЧНЫХ ошибках (позиция оказалась discussion → «Could not resolve to an issue or pull request»), но data в stdout при этом ЕСТЬ — парсь stdout, не выходи по returncode, иначе одна discussion гасит все 55 позиций; (2) сравнивай логин с tonydzi явно — «наш последний» и «их последний» это и есть весь вердикт. Замер 22.09: 4 красных дали 2 настоящих долга, а ещё 4 (claude-agent-sdk-python#1276, #1271, mem0#6859, hermes-agent#88371) стояли зелёными или в «чужих тредах».

⛔ ВТОРАЯ ГРАБЛЯ ТОГО ЖЕ ПРИБОРА (замер 21.09): .[-1] БЕЗ ПАГИНАЦИИ ВРЁТ НА ДЛИННОМ ТРЕДЕ. Эндпоинт GET /repos/{o}/{r}/issues/{N}/comments игнорирует sort и direction (в его контракте их просто нет — только since, per_page, page), поэтому --jq '.[-1]...' отдаёт последний коммент ПЕРВОЙ СТРАНИЦЫ, то есть №100, а не последний в треде. 21.09 так соврал gastownhall/beads#5877 (101 коммент): прибор показал «последним писал tonydzi», хотя реальный последний был от @The1nk в тот же день, и тред чуть не уехал в «не долг». Чем людней тред — тем вероятнее ложь, а людные треды мы берём ПЕРВЫМИ (§4.7 «самый людный живой тред домена первым»), так что грабля лежит ровно на приоритетной дорожке. Лечение — брать последнюю страницу явно:

cnt=$(gh api "repos/$repo/issues/$num" --jq '.comments')
page=$(( (cnt + 99) / 100 ))
gh api "repos/$repo/issues/$num/comments?per_page=100&page=$page" \
  --jq '.[-1] | .user.login + " @ " + .created_at'

Готовый вариант на список тредов (считает страницу сам, печатает 2 последних коммента): ~/.claude/skills/pr-reply/gh_last_comment.sh <owner/repo#N> [...] (живёт рядом со скиллом — единственным потребителем; в ~/.claude/scripts не кладём, там 21.09 висела чужая EXCLUSIVE-табличка ON AIR на зоне fleet-code, и дверь onair_fleet_guard.py запись справедливо отбила).

⚠️ «Последний не мы» ≠ «ответ нам» (замер 14.09)

Digest ставит «ОТВЕТ ЗА НАМИ», если последним писал не tonydzi. В НАШЕМ PR это правда. В чужом большом issue, где мы — один из 40 участников, это чаще посторонний, который пишет не нам. Замер 14.09: из 53 позиций адресованы нам были 15; ещё ~25 — посторонние (anthropics/claude-code ×9 и др.), 3 уже отвечены соседом, 6 ссылок 404. Прежде чем писать, прочитай последние 2–3 реплики: есть @tonydzi, ответ на наш аргумент или просьба к автору/ревьюеру, которым были мы? Нет → не отвечаем, ответ без новой ценности = спам (§1.5). Боты digest уже не считает (is_bot в pr_watch.py). Leak-scan тела ответа: паттерны repo_release_leakscan.py (FAIL+WARN) по файлу --body-file, плюс последняя строка обязана нести github.com/tonydzi.

⚠️ СОБЕСЕДНИК САМ ЗАКРЫЛ РАЗГОВОР — это не долг и не 🪦 (замер 24.09)

Третий вид ложного красного, отдельный от «пишут не нам». Мяч формально у нас, реплика адресована НАМ и по существу — но её содержание есть явный выход из разговора: «letting this one rest» · «I'm going to stop here» · «I'd rather it sit finished» · «leaving it open for a maintainer» · «@maintainer PTAL» (пинг третьему лицу, а не нам). Ответ туда не несёт новой ценности и читается как «мне нужно последнее слово». Замер 24.09: из 8 красных ТРИ были такими (BerriAI/litellm#34140 и #41395 — DanBrima закрыл оба одной парой реплик; google/adk-go#1543 — wolo-lab пингует @wojtas000). Ещё одна — осуждённый ранее fieldkit#77. Итого половина очереди дня. Вердикт пишется в журнал В ТЕХ ЖЕ СЛОВАХ, что и осуждённые позиции Шага 1-бис, и работает так же: позиция остаётся красной навсегда (у pr_watch нет mute — проверено --help), следующий заход смотрит только, появился ли комментарий НОВЕЕ даты вердикта. ⭐ Граница: это НЕ повод молчать там, где человек закончил репликой к нам — благодарность с техническим содержанием, поправка нашего аргумента, вопрос «а как бы вы сделали». Тест один: после моей реплики у собеседника появится работа или знание? Нет — не пишем.

⚠️ Сначала спроси: чей это PR

Реестр держит оба направления, и работа в них разная. Проверяется одной командой:

gh pr view <N> --repo <owner/repo> --json author --jq .author.login
Автор PRКто мыЧто от нас ждут
мыавторсделать правку, которую попросил ревьюер → шаги 3–4
постороннийревьюерперепроверить их фикс и вынести вердикт → шаг 3-бис

Замер 06.08: из трёх живых людей в очереди двое ждали нас как РЕВЬЮЕРОВ, а не как авторов. Спутать эти роли — значит прийти в чужой тред с «поправил, посмотрите», где от нас ждали проверки. Роль определяется автором PR, а не тем, в чьём репозитории он открыт.

⚠️ Очередь ровно так же свежа, как снимки сторожа. Дата снимка печатается в шапке digest — если она вчерашняя, очередь тоже вчерашняя. Не выдавай старый список за сегодняшний.

🪦 Правило трёх дней (anton «+» 07.08)

Секцию 🪦 могилы digest выделяет сам (GRAVE_DAYS=3 в pr_watch.py): мяч у НИХ и тишина >3 дней после нашего последнего хода. Замер по 54 нашим PR: все 8 мержей случились за 0–3 дня, дальше — никогда. Поэтому: могилу не бампаем и не воскрешаем (один вежливый бамп на PR разрешён только ПОКА тред живой, второй запрещён всегда — §seed 778). Энергия из могил уходит живым тредам и ревью чужих PR. Могила ≠ провал: это нормальная цена присутствия (§1.5), закрывать PR самим не нужно — пусть висит, вдруг проснутся сами.

⭐ Мяч у НИХ, но протух НАШ собственный claim — это новая ценность, а не бамп (замер 19.09)

Могила и «ждём их» запрещают бамп без новой ценности. Но наше опубликованное утверждение — такой же скоропортящийся продукт, как и цифра ([[prichina-kak-claim]]): версия вышла, тег переехал, баг уехал в релиз. Проверка дешёвая и делается ДО решения «молчим»: то, что мы в этом треде утверждали про версии/релизы/состояние — всё ещё правда СЕГОДНЯ?

Протухло → пишем, даже если формально отвечают они, и первым абзацем правим себя, а не повторяем просьбу. Замер, родивший правило: в google/adk-python#6941 мы 05.09 написали «ни один опубликованный релиз не несёт bfeb04c» — 10.09 вышел v2.9.0, который его несёт, и баг из «живёт на main» стал «стоит у пользователей». Проверка = две команды (gh api repos/<repo>/compare/<tag>...<sha> --jq .status → behind значит содержится; gh api repos/<repo>/releases --jq '.[0:5][] | "\(.tag_name) \(.published_at)"').

⚠️ Граница: это НЕ лазейка для бампа. Новая ценность = факт, который изменился в мире и меняет приоритет для мейнтейнера; «прошла неделя» и «напоминаю» ценностью не являются. Перепроверять на опубликованном артефакте, а не на чекауте: pip install <pkg>==<version> + контрольный кейс, который ОБЯЗАН пройти — иначе красная строка ничего не доказывает.

Шаг 2 — прочитать, чего именно хотят

gh pr view <N> --repo <owner/repo> --json reviews,comments
gh api repos/<owner/repo>/pulls/<N>/comments --jq '.[] | "\(.user.login) | \(.path):\(.line) | \(.body)"'

Второй вызов обязателен: инлайн-комменты к строкам кода не видны в первом. Именно в них обычно и лежит просьба (замер 06.08: go-sdk#1148, «let's remove this comment» — только там).

Прочитай просьбу буквально. «Убери этот комментарий» — значит убрать, а не сократить и не переписать красивее. Мейнтейнер знает стиль своего репо лучше нас.

Шаг 3 — сделать то, что просили

Правка → прогон того, что репо просит прогнать (тесты/линт/генератор доков) → пуш в ту же ветку. Если доки генерируются (//go:generate, weave, nbconvert), правь и исходник, и сгенерированный файл — иначе ревьюер увидит рассинхрон и вернёт PR второй раз.

Не согласен — не молчи и не уступай молча: возражения по пунктам + «переубеди меня» ([[objection-sparring-to-consensus]]). Уступка без аргумента — такой же провал, как спор без него.

Шаг 3-бис — если ревьюеры МЫ: проверять доказательство, а не поведение

Автор написал «поправил всё по вашим замечаниям». Прогнать его тесты и увидеть зелёное — недостаточно: зелёный тест доказывает, что тест проходит, а не что защита работает.

Порядок:

  1. Забрать ветку: git fetch upstream pull/<N>/head:pr<N> && git checkout pr<N>.
  2. Прогнать так, как автор говорит, и назвать точный способ прогона в ответе (какие ячейки, с сетью или без, какой python) — иначе вердикт невоспроизводим.
  3. Сломать по одной защите в РАБОЧЕМ коде, не трогая тесты, и убедиться, что тест краснеет. Не поймал — защита есть только на бумаге, и это находка ценнее любого стилевого замечания.

⚠️ Грабли метода (наступил 06.08): мутация, которая переименовывает строку и в проверке, и в тесте разом, «проходит» всегда — тест сравнивает переименованное с переименованным. Ломать надо ПОВЕДЕНИЕ: убрать return refused, а не переименовать код ошибки. Первый прогон дал три ложных «ПРОПУЩЕНА» именно из-за этого; сначала я усомнился в их коде, и был неправ.

⚠️ Вторые грабли: изолированный прогон пары ячеек не тянет импорты из ранних. NameError — это скорее дефект твоей выборки, чем их notebook. Проверь, откуда символ, прежде чем писать человеку, что у него сломано.

⚠️ Чужой РЕПРО (не PR) проверяй так же: у каждой строки вывода нужен контроль, который МОГ провалиться — негатив для «гейт вернул True» (гейт вообще умеет False?) и позитив для «операций 0» (у заведомо правильного случая они есть?). Замер 16.09 litellm#41395: оба вывода верны, но без контролей не доказаны; ContextVar-stash в цикле давал 0 и у родителя.

Вердикт формулируй как факт с цифрой («47 outcomes, exit 0, три мутации пойманы»), а не как «выглядит хорошо». Закончил — так и скажи: «nothing further from me on this», чтобы автор не ждал следующего круга.

Шаг 3-тер — ВОЗРАСТ ИСТОЧНИКА ЦИФРЫ (пережито 13.09.2026, класс claim-judged-against-current-file-not-run-date)

Цифра уезжает в ЧУЖОЙ тред → перед отправкой ответь на один вопрос: когда появилось то, по чему я сужу? Судишь прогоны/логи/историю по ТЕКУЩЕМУ содержимому файла, настройки или поля — проверь, что оно существовало на момент тех событий (ls бэкапов, mtime, git log, дата правки). Не проверил — цифра не уезжает, уезжает формулировка «замерено с такой-то даты».

Замер, родивший правило: в anthropics/claude-code#66402 мы опубликовали «26 из 43 безлюдных прогонов (60%) шли не на объявленной модели». Поле model: дописали в определения задач 10.09, а под нож пошли прогоны 08–10.09 — объявления тогда просто не было. Честный замер дал 0 дрейфа и обратный вывод. Отзыв: issuecomment-5656756608, строка в 00-System/Mycroft-Correction-Log.md. Прибор с этим гвардом: ~/.claude/scripts/task_model_drift.py (+ регресс _test_task_model_drift.py).

⚠️ Усиление: чем ВЫГОДНЕЕ цифра нашему аргументу в чужом треде, тем строже проверка ([[self-serving-conclusion-needs-stronger-proof]]). Нашу ошибку поймало требование чужого инженера сделать репро, а не наша собственная проверка — значит проверку надо делать ДО отправки.

⚠️ Шаг 3-тер-бис — РАЗБОР ИСТОЧНИКА, А НЕ ТОЛЬКО ЕГО ВОЗРАСТ (пережито 10.10.2026, стоило публичного отзыва)

Второй вопрос к той же цифре: на каком СЛОЕ я её мерил? Транскрипты (~/.claude/projects/*/*.jsonl), дампы API, логи с JSON внутри — это ЭКРАНИРОВАННЫЙ текст: каждый перевод строки лежит двумя символами, каждый не-ASCII символ — шириной своей escape-последовательности. grep/regex по СЫРОЙ строке мерит экранирование, а не содержимое, и длины приезжают завышенными. Меряй после json.loads, не до.

Замер, родивший правило: в anthropics/claude-code#70555 мы опубликовали «превью 5812 символов при обещанных 2KB, значит ярлык не является верхней границей». С разбором строки как JSON потолок оказался ЖЁСТКИМ: ровно 2000 символов, 109 блоков из 594 стоят точно на нём, в 2001..2047 — ноль. Выброс был дефектом нашего прибора. Отзыв: issuecomment-6103021040. ⭐ Но перемер нашёл дефект КРУПНЕЕ, и это типично: ярлык считает байты, а срез — символы, поэтому у не-ASCII вывода реально приезжает до 1.72× обещанного (430 блоков из 594, 72%). Отзыв своей цифры — не потеря лица, а способ найти настоящий баг; отзывай ПЕРВЫМ абзацем и назови дефект своего прибора поимённо, иначе собеседник решит, что виноват корпус. ⛔ Два разных извлечения на одну цифру, прежде чем она уедет: regex по разобранным строкам И проход без regex (индексная арифметика), который НЕ может быть обрезан твоим же шаблоном. Совпали — цифра едет; разошлись — едет диапазон и причина.

Шаг 4 — ответить коротко

Форма, которая работает (замер: единственная, дававшая ответ живого человека):

  • что сделал — фактом, без «спасибо за ревью, очень ценно»
  • ссылка на коммит, если правка не однострочная
  • ⭐ подпись-CTA (anton 10.09, supersedes прежнее «никакой подписи»): ОДНА строка со ссылкой github.com/tonydzi, источник $OBSIDIAN_VAULT/08-Templates/github-touch-signature.md. Carve-out против спама: на НАШЕМ PR в идущем диалоге достаточно ОДНОЙ подписи на тред — уже подписывался в этом треде → в следующих репликах можно без неё. Первое касание чужого треда (ревью/коммент) — подпись всегда. Канон: память cta-signature-every-github-touch; сторож cta_signature_watch.py.

Голос Майкрофта (§3.3): пишу я → называюсь. Короткий технический ответ на чужом PR — цель ≤5 слов на само раскрытие, тон по каналу.

gh pr comment <N> --repo <owner/repo> --body "Done — <что именно>. <ссылка/деталь>."

⭐ НА СВОЁМ PR РЕВЬЮ ТОЖЕ РАЗРЕШЕНО (замер 28.09, SK#14371): GitHub запрещает автору только --approve и --request-changes, а gh pr review <N> -R <owner/repo> --comment --body-file <файл> на СВОЁМ PR проходит и создаёт объект Review (проверено: review 5342194561 от tonydzi на своём же PR). Ответ мейнтейнеру на его блокирующее ревью подаётся именно так, а не gh pr comment — тогда переписка видна в фильтре ревью, а не только в ленте комментов.

⭐ ТРИАЖ PR ЧИТАЕТ pulls/N/reviews, А НЕ ТОЛЬКО issues/N/comments (тот же замер 28.09): блокирующее ревью мейнтейнера НЕ появляется в issues/N/comments — там было пусто, и позиция читалась как «активности нет». Ревью eavanvalkenburg висело в pulls/14371/reviews четыре часа. Для позиции-PR смотри обе ручки, иначе самый дорогой долг захода выглядит как тишина.

⭐ ПОЧЕМУ ЗДЕСЬ КОММЕНТ, А НЕ РЕВЬЮ (граница правила, замер 05.09): это РЕПЛИКА в уже идущем диалоге — она комментом и подаётся. Но ПЕРВОЕ касание по ЧУЖОМУ PR идёт только через gh pr review <N> -R <owner/repo> --comment --body-file <файл>: gh pr comment не создаёт объект Review, и работа не видна ни в графе вкладов, ни в фильтре reviewed-by:tonydzi (в чужих PR за 30 дн: 127 комментов против 7 ревью = 5.2% видимых). У ISSUE ревью НЕ СУЩЕСТВУЕТ — там коммент нормален. Канон: Библия reglament-v-chuzhom-pr-pishem-revyu-a-ne-kommentariy + файл-константа $OBSIDIAN_VAULT/08-Templates/github-review-not-comment.md.

Шаг 5 — записать кредит автору совета

Совет постороннего человека — это вклад, и он именной (приказ Антона 06.08, скилл /alpha-credit):

python ~/.claude/scripts/alpha_credit.py add --source gh --author "<login>" --handle "<login>" --url "<url PR>" --advice "<что подсказал>" --kind critique --consent public --ref "gh:<owner/repo>#<N>:<id>"

Шаг 4-тер — «FIX YOUR CLA» В GOOGLE-РЕПО: СНАЧАЛА ПОСМОТРИ НА ТРЕЙЛЕРЫ КОММИТА (замер 29.09.2026)

Мейнтейнер пишет «please fix CLA so we can get this merged», cla/google = FAILURE. ⛔ НЕ беги подписывать: подпись может быть уже на месте, а валит её ВТОРОЙ контрибьютор.

Корень (доказан на ChromeDevTools/chrome-devtools-mcp#2852): бот читает ТРЕЙЛЕРЫ, а не людей. Строка Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> — для бота полноценный контрибьютор, у которого CLA нет и быть не может. Текст ошибки при этом отправляет подписывать то, что уже подписано: тот же коммит-email 194927794+tonydzi@users.noreply.github.com даёт cla/google = SUCCESS на google/adk-go#1543.

Порядок разбора (2 вызова, не поход в браузер):

  1. gh api repos/<o>/<r>/commits/<sha> --jq '.commit.message' — есть Co-Authored-By? есть ли чужой email?
  2. Сверь с нашим PR в другом репо того же вендора: gh pr view <N> -R google/<repo> --json statusCheckRollup — если там cla/google SUCCESS с тем же email, подпись есть, и дело в трейлере.

Лечение (провенанс §5.11 НЕ страдает): снять ТОЛЬКО Co-Authored-By, оставить Assisted-by: (называет инструмент, авторства не заявляет) и Signed-off-by: (DCO). code_provenance_lint.py требует Assisted-by / Machine / Account / Operator и Co-Authored-By НЕ требует — проверено 29.09, свой гейт этим не ломается. Амендим, пушим --force-with-lease, и ДОКАЗЫВАЕМ, что ревью не обнулилось: git diff --stat <old>..<new> обязан быть ПУСТ (контент байт-в-байт), иначе мейнтейнеру придётся перечитывать диф. Потом ждём перепрогона чека и смотрим ФАКТ: gh api repos/<o>/<r>/commits/<new-sha>/check-runs --jq '.check_runs[]|select(.name|test("cla";"i"))|[.name,.conclusion]|@tsv'.

⚠️ Подписание CLA само по себе — НЕ Tier-2 и не повод для аска: мандат Антона 30.08 ([[cla-sign-for-anton-autonomous]], рецидив 14–15.09 Kong#14999) покрывает ЛЮБОЙ способ подписи. Аск остаётся только на деньги, equity, права сверх стандартного контрибьюторского гранта и NDA. 🤔 Обобщение не проверено: замер один (Google CLA). Для EasyCLA/cla-assistant других вендоров правило пока гипотеза — но трейлеры смотреть дешевле, чем подписывать заново. ⛔ macOS: timeout не существует. Ждать перепрогон чека — bash-циклом с seq и sleep, не timeout.

Шаг 5-бис — ответ инженера = контент-момент (anton 24.08: «сразу в контент-воронку»)

Живой инженер (мейнтейнер / посторонний контрибьютор, не бот) ответил в нашем треде или на нашу лечилку → в ТОМ ЖЕ заходе черновик в воронку:

python3 $IMPORTS_ROOT/content-factory/content_miner.py capture --title "<хук: кто ответил и что это значит>" --note "<суть + ссылка на тред>" --tier teaser

Draft-first: это черновик в triage, публикацию решает гейт воронки — здесь только захват. ⛔ Не захватываем: ответы ботов · чисто механические «done/merged» без истории · приватное. Тир по весу: реплика = teaser; мерж нашего фикса или разговор с мейнтейнером Anthropic/OpenAI = medium. Канон: [[everything-becomes-content]] + [[content-miner-reflex]] + реглумент reglament-pochinil-u-sebya-srazu-razday-miru (реакция мира на раздачу — вторая половина той же трубы).

Шаг 6 — закрыть петлю

Ответил → позиция уходит из «мяч у нас» сама на следующем тике сторожа. Проверять не надо; надо проверить обратное: если через сутки она всё ещё там — ответ не доехал (не та ветка, не тот PR, коммент ушёл в issue).

⭐ Шаг 6-прим — ВЫЧИТАЙ ОТПРАВЛЕННОЕ ОБРАТНО С СЕРВЕРА, И СВЕРЯЙ ПО md5, А НЕ ПО ДЛИНЕ (пережито 04.10.2026)

gh issue comment печатает URL и выходит с 0 — это доказывает ПРИЁМ запроса, не целость тела. Второй заход (§4.3-бис «другим способом, чем в первый раз») для исходящего коммента выглядит так: взять id из URL и прочитать тело ОБРАТНО, а не пересказывать свой файл.

ID=<цифры после issuecomment->
gh api repos/<o>/<r>/issues/comments/$ID --jq '.body' > /tmp/srv.txt
python3 -c "import hashlib,io;l=io.open('$F',encoding='utf-8').read().rstrip(chr(10));s=io.open('/tmp/srv.txt',encoding='utf-8').read().rstrip(chr(10));print('IDENTICAL' if l==s else 'DIFF', hashlib.md5(l.encode()).hexdigest(), hashlib.md5(s.encode()).hexdigest())"

⚠️ ЛОВУШКА, КОТОРАЯ ВЫГЛЯДИТ КАК ОБРЕЗАННЫЙ КОММЕНТ: сверка по ДЛИНЕ даёт ложное расхождение. jq '.body|length' считает кодпоинты, wc -c — байты; каждый em-dash (—) в нашем тексте — 3 байта и 1 кодпоинт, то есть +2 байта расхождения на штуку. Замер 04.10 (jerryfane/herdr#16): len=2139 с сервера против 2154 локально выглядело как потеря 15 символов, хотя md5 совпал байт в байт — 7 em-dash'ей (14) плюс перевод строки. Длина как прибор здесь СЛЕПА; судит только md5 (или прямое сравнение строк).

Тело уехало битым — чиним, а не оправдываемся: gh api -X PATCH repos/<o>/<r>/issues/comments/$ID -f body="$(cat "$F")".

⚡ Шаг 6-бис — НАШ PR ЗАКРЫЛИ: ВЫНЬ УКАЗАТЕЛЬ ПРЕЖДЕ, ЧЕМ ЗАКРЫТЬ ТРЕД (30.09.2026)

Закрытый PR уходит из очереди этого скилла — и вместе с ним уходит подарок: мейнтейнер, отказывая, почти всегда говорит, куда идти вместо этого (доска, лейбл, CONTRIBUTING, чужой PR).

Поэтому позиция «наш PR стал CLOSED и не MERGED» закрывается не молчанием, а тремя действиями:

  1. python3 ~/.claude/scripts/gh_pr_discipline.py doors --scan — указатель попадает в очередь /issue-match;
  2. поблагодарить закрывшего ОДНОЙ строкой, если он дал указатель (кредит вперёд, §OUTBOUND_GATE п.3); глухое «closing, not a fit» благодарности не требует — молчание тут законно;
  3. ⛔ НЕ переоткрывать и НЕ пере-пушивать отклонённое: дверь теперь в другом месте.

Цена урока: haystack#12142 → их доска → haystack-integrations#566 смержен через 7 дней. Единственный мерж кампании, полученный из ОТКАЗА.

⚠️ Шаг 6-тер — РЕБЕЙЗ ЗАКОНЕН НЕ ВЕЗДЕ: перед force-push проверь запрет репо (01.10.2026)

Наш PR в конфликте — рефлекс «ребейз + git push --force-with-lease». В части репо этот ход уничтожает уже полученное ревью, а ревью мейнтейнера дороже самого PR.

ЗАМЕР, который это оплатил: 01.10 ребейз спас anthropics/claude-cookbooks#787 (одобрение @Droptops висело 46 дней в конфликте) — и в тот же час на QwenLM/qwen-code#12875 бот pr-force-push-reminder написал: «do not rebase or force-push to an active PR as it invalidates existing review comments… the bots always squash all changes into a single commit automatically». Один и тот же ход: там лечит, тут калечит.

ПОРЯДОК (30 секунд, до любого push):

  1. gh pr view <N> -R <owner/repo> --json comments --jq '.comments[].body' | grep -i "force-push\|rebase" — бот или мейнтейнер уже просили не делать?
  2. gh api repos/<o>/<r>/contents/CONTRIBUTING.md --jq .content | base64 -d | grep -i -A3 "force.push\|rebase"
  3. Нашёл запрет → ⛔ ребейз. Вливай обычным мержем: git merge upstream/main в ветку PR, он конфликт закрывает и ревью-комменты сохраняет. В треде одной строкой: «merged main instead of rebasing, to keep your review comments anchored».
  4. Запрета не нашёл → ребейз разрешён, но пиши в тред, ЧТО изменилось и что одобренный контент не двигался (иначе ревьюеру придётся перечитывать всё).

⛔ Не ослабляется: «нет запрета» = «я искал в двух местах и не нашёл» (§5.4), а не «можно всё». Класс в журнале: rebase-wipes-the-review-we-waited-for.

Границы

  • Мерж чужого кода, юридическое (CLA/DCO), деньги, удаление чужого — Tier-2, к Антону.
  • Массовые одинаковые комменты веером — запрещено: громкость ≠ спам (§1.5).
  • Ответ боту текстом — мусор в чужом треде. Боту отвечают коммитом.

Как понять, что скилл работает

Считаем не число ответов, а возраст самого старого живого долга в digest. Работает — если он держится ниже суток. Растёт — прибор есть, а рук нет, и это отдельный разговор, а не повод отвечать быстрее.

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

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

Шаг 3-кватер — REST отдал 403 «rate limit exceeded» при пустом счётчике → пиши через GraphQL (замер 07.10.2026, pr-reply-daily Mac16)

Симптом: каждый gh api repos/... отвечает API rate limit exceeded for user ID …, а gh api rate_limit при этом показывает core.remaining=5000, used=0. ✅ Причина доказана 08.10.2026: это ПЕРВИЧНЫЙ часовой бак аккаунта (текст вторичного лимита другой — «secondary rate limit»), съеденный тиком pr_watch.py 15:15 (658 тредов × 5 REST = ~3300/час) плюс соседями. Тело /rate_limit на этом токене СЛЕПО: каждый вызов отдаёт фантомное окно (used=0, reset = сейчас+1ч и ползёт). Настоящий бак — только по заголовкам живого ответа: python3 ~/.claude/scripts/_shared/gh_budget.py. Корень закрыт: pr_watch сперва спрашивает GraphQL-пачкой updatedAt и платит REST только за сдвинувшиеся треды (замер: 676 вместо ~3300), а на первом 403 останавливает тик; сторож _test_gh_rest_limit.py. Дверь: GraphQL считается ОТДЕЛЬНЫМ баком (graphql.remaining живой) и умеет и читать хвосты (issueOrPullRequest(number){ comments(last:4){…} }, семь тредов одним запросом), и писать: python3 ~/.claude/skills/pr-reply/gh_gql_comment.py <owner> <repo> <N> <body_file> = mutation addComment + md5-сверка тела по readback (exit 0 только при MATCH). Инструмент лежит рядом со скиллом (единственный потребитель; ~/.claude/scripts в тот вечер держал чужой ON AIR). Работает для issue и PR одинаково; гейты paragraph_lint && leak_scan && ставятся перед ним той же цепочкой &&. ⛔ Не замена ревью: первое касание в чужой PR остаётся gh pr review --comment (REST); 403 на нём → ждать окно либо писать с другого узла, не подменять ревью комментом молча.

<!--kit-footer-->

Like this skill? It is one of 100 in second-brain-starter-kit: the second brain we built for ourselves and run every day at Palo Alto AI Research Lab. Install the whole set with npx skills add tonydzi/second-brain-starter-kit. Everything is open source and free, so take what you need.

Flagships worth a look on their own: secondop-panel (a second opinion from a panel of external models), claude-memory-tidy (stop your agent's memory from rotting), telegram-mcp-kit (your own Telegram over MCP in about 15 minutes).

Author: Anton Dziatkovskii, Palo Alto AI Research Lab. Telegram @tonydzi - WhatsApp +1 341 222 9178 - X @Tony_Stef_

Engineers: want to test-drive this setup? Message me. I hand out free starter seeds to engineers who test and report back, and custom skill requests are welcome.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

03

免费

Run an autonomous consensus round between agent peers on several machines: they negotiate a decision over a shared channel and execute it without a human relaying messages. Use when multiple machines must agree on a change. Triggers: "/03", "work it out among yourselves", "find consensus". Wakes the human only for irreversible actions or a deadlock.

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

1

免费

Recover a session after a hard crash (app died, machine rebooted, context lost mid-work): replay the turn-state ledger for the last requests, files and decisions, run a green/red health ping over the system map, file sync and MCP connectors, then rebuild the previous session history for pickup. Triggers: "/1", "resume after crash", "wake".

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

agenda

免费

Show the operator's day: today's calendar plus what needs them personally (leads due, people awaiting a reply, deadlines), pulled from Calendar and Telegram and ranked. Read-only: sends and commits nothing. Triggers: "/agenda", "what's on today", "my agenda".

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

ai-slop

免费

Rewrite machine-written text so it reads like a busy human wrote it: cut 50-70%, break the even rhythm, drop AI-tell words and filler, keep facts and protected terms. Use before publishing any post, reply or email drafted by a model. Triggers: /ai-slop, /slop, humanize, anti-slop, make it shorter and alive.

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

Decision protocol for strategic work: recall the knowledge vault, run a gap analysis, emit a deep-research prompt for external tools, then synthesize the returned reports into a decision memo. Use before any new product, feature, market, pricing, GTM or architecture call, where local recall alone is not enough. Triggers: "/alfa-search-recall-deepresearch", "alpha protocol", "R+DR".

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

- КРЕДИТ АВТОРУ ИДЕИ: совет из комментария (TG @ClawRus / @ClawInga, комменты под постами Антона в FB, треды и issue на… Триггеры: “/alpha-credit“, “/credit“, “кто нам это посоветовал“, “запиши совет“, “поблагодари советчиков“, “кому мы должны спасибо“, “кредит автору“, “чей это был совет“, “credit the author“

中文简介正在准备中。当前显示原文说明。

tonydzi/second-brain-starter-kit92026年10月20日 更新

查看 tonydzi 的全部技能

报告此技能的问题