Перейти к содержимому
PD
Вайб-кодинг 8 мин чтения

Ревью стало узким местом: мой протокол для PR от AI-агентов

Агенты пишут больше кода, чем команда способна прочитать. Разбираю конкретный протокол ревью: лимит на диф, тиры по радиусу поражения, детект правок тестов и 12-минутное чтение, которое реально ловит баги.

PD

Pavel Duglas

AI Automation & MVP Architect

Писать код перестало быть бутылочным горлышком где-то год назад. За обычную неделю я выдаю в три-пять раз больше диффа, чем в 2023-м, и это не повод для гордости - это проблема. Потому что пропускная способность одного человеческого мозга на ревью не выросла в пять раз. Мозг тот же, работает на вьетнамском кофе со льдом и 40 минутах внимания за раз.

Эту поломку я вижу в каждой команде, которая всерьёз посадила агентов писать код: очередь просто переехала. Раньше было “нужны руки, чтобы построить”. Теперь - “у нас 14 открытых PR, половина трогает биллинг, и никто их по-настоящему не читал”. Мержить непрочитанный код агента - это самый быстрый способ получить инцидент в проде, который в команде никто не может отладить: никто это не писал и никто это не читал.

Поэтому я собрал протокол. Не философию, а набор механических правил, которые вшиты в репозиторий и в CI.

Правило 1: лимит на диф, и лимит проверяет машина

Агент с радостью принесёт вам PR на 2400 строк, где он переименовал три сущности, отрефакторил сервис, добавил фичу и “попутно почистил” конфиг. Такое не ревьюят. Такое пролистывают и апрувят.

Мои жёсткие рамки:

  • 400 изменённых строк на PR, не считая лок-файлов, генерённого кода и снапшотов
  • один смысл на PR: фича, рефакторинг или бамп зависимостей - но не всё вместе
  • никакого попутного форматирования в PR с логикой

Лимит живёт в CI, а не в вики, которую никто не открывает:

# .github/workflows/pr-size.yml (ключевой шаг)
CHANGED=$(git diff --numstat origin/main...HEAD \
  -- . ':(exclude)*.lock' ':(exclude)**/generated/**' ':(exclude)**/__snapshots__/**' \
  | awk '{a+=$1; d+=$2} END {print a+d}')

if [ "$CHANGED" -gt 400 ]; then
  echo "В PR $CHANGED строк. Разбей или поставь лейбл 'oversized-approved'."
  exit 1
fi

Лейбл-предохранитель обязателен. Большие механические миграции бывают. Но по умолчанию должно быть трение, иначе естественный размер вывода агента побеждает всегда.

Практическое следствие: я теперь сообщаю агенту ограничение заранее. “Реализуй только слой репозитория для этой фичи. HTTP-хендлеры не трогай. Цель - меньше 300 строк.” На том же усилии получаешь и код лучше, и диф, который реально можно прочитать.

Правило 2: ревьюить план, а не диф

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

Я читаю это три минуты. Девяносто процентов плохих PR умирают здесь, потому что неправильный план виден сразу, а неправильный код - нет. “Ты собираешься добавить таблицу users_meta” - нет, у нас для этого уже есть JSONB-колонка. “Ты вызываешь платёжного провайдера внутри хендлера запроса” - нет, это уходит в очередь задач.

После того как план согласован, ревью диффа превращается из следствия в сверку. Я не спрашиваю “что этот код делает?”, я спрашиваю “это то, о чём мы договорились?”. Это принципиально более быстрая операция для головы.

Правило 3: тиры по радиусу поражения, а не по числу строк

Изменение на 600 строк в лендинге опаснее, чем правка на 8 строк в функции обновления токена? Нет, наоборот. Поэтому я перестал сортировать PR по размеру и начал сортировать по тому, что сломается, если код неверный.

Карта лежит прямо в репозитории:

# review-tiers.yml
tier_1_читать_каждую_строку:
  - src/billing/**
  - src/auth/**
  - migrations/**
  - infra/**
  - src/**/webhooks/**
tier_2_читать_логику:
  - src/api/**
  - src/jobs/**
tier_3_доверять_тестам:
  - src/ui/**
  - content/**
  - scripts/one-off/**

Tier 1: читаю каждую строку, проговариваю про себя, запускаю локально. Ни один агент не мержит tier 1 без человека, который сможет объяснить этот код через месяц.

Tier 2: читаю логику и ветки ошибок, остальное просматриваю, дальше полагаюсь на интеграционные тесты.

Tier 3: если CI зелёный и preview-деплой выглядит нормально - в мерж.

Именно это изменение вернуло мне больше всего ресурса на ревью. Основная масса вывода агента - это tier 3. Пытаться читать его с строгостью tier 1 значит сжигать внимание, которое нужно на код, реально способный стоить денег.

Правило 4: тесты - это спека, а правки тестов подозрительны

Самое опасное, что делает агент, - это делает сьют зелёным, меняя сам сьют. Я видел, как агент “починил” падающий ассерт, ослабив его с конкретного значения до expect.anything(). Формально зелено. Фактически ложь.

Два контрмеры.

Первая: для всего в tier 1 и tier 2 тест пишу я, до того как агент напишет реализацию. Не религия TDD, а два-три ассерта, которые кодируют бизнес-правило. “Возврат больше суммы исходного платежа должен бросать исключение.” Этот тест - мой контракт, он лежит в файле, который агенту запрещено менять инструкцией.

Вторая: CI отдельно подсвечивает правки тестовых файлов, чтобы они не проскочили внутри большого диффа:

TEST_DELTA=$(git diff --numstat origin/main...HEAD -- '**/*.test.ts' | awk '{d+=$2} END {print d+0}')
if [ "$TEST_DELTA" -gt 0 ]; then
  echo "::warning::В этом PR удалено $TEST_DELTA строк тестов. Нужен ручной сайн-офф."
fi

Сигнал - именно удалённые строки тестов. Добавленные тесты почти всегда норм. Ловушка живёт в снятых ассертах. Каждую удалённую строку теста я читаю всегда, независимо от тира.

Правило 5: человеческое внимание не тратится на то, что проверяет машина

Если я поймал себя на комментарии про форматирование, нейминг, порядок импортов, отсутствующие типы, необработанный промис или забытый console.log - я слил слот в рабочей памяти. Всё это место тулинга, а не человека.

Минимальные ворота до того, как PR увидит человек:

  • форматтер и линтер, причём в CI автофикс выключен: пусть падает, а не молча перезаписывает
  • строгая типизация, никаких any в путях tier 1 и tier 2
  • юнит плюс интеграционные тесты, с живой базой, а не с моком
  • диф зависимостей: любой новый пакет в лок-файле требует одной строки обоснования в описании PR
  • скан на секреты, потому что агенты обожают “временно” заинлайнить ключ

Последний пункт - не паранойя. Инстинкт кодового агента, когда чтение конфига падает, - захардкодить значение, при котором тест проходит.

Правило 6: 12-минутное чтение

Когда PR доходит до меня, я делаю проход в фиксированном порядке и засекаю время. Если не уложился примерно в 12 минут - PR слишком большой и уезжает на разбиение.

  1. Читаю описание PR и сверяю с согласованным планом. Расхождение - стоп.
  2. Читаю изменения слоя данных: миграции, запросы, схемы. Здесь живёт необратимый ущерб.
  3. Читаю ветки ошибок. Агенты пишут прекрасный happy path и забывают, что существует сеть. Что будет на таймауте, на 429, на частичной записи?
  4. Читаю каждую удалённую строку. Удаления - это место, где поведение исчезает бесшумно.
  5. Гребу грепом по границам системы: новые сетевые вызовы, новые записи, новые env-переменные, новые фоновые задачи.
  6. Запускаю. Реально нажимаю кнопку. Две минуты пользования фичей выигрывают у двадцати минут чтения.

Обратите внимание: построчное чтение новой бизнес-логики - не первый шаг. Это как раз то, что лучше всего покрывают типы и тесты.

Метрики, которые показывают, что протокол работает

Я смотрю четыре числа в месяц, и все они дёшево достаются из Git и из журнала инцидентов:

  • медианный размер PR в читаемых строках - должен ползти вниз
  • медианное время от открытия до мержа - должно ползти вниз
  • доля реверта и доля пост-мерж хотфиксов - настоящий сигнал качества
  • доля смерженных PR, где ни один человек не оставил содержательного комментария - если она выше примерно трети, вы штампуете апрувы

Последняя метрика самая честная. Скорость апрувов выглядит как продуктивность ровно до той недели, когда вы три дня отлаживаете систему, которую в комнате не понимает никто.

Версия для соло-фаундера

Если вы один человек и катите MVP, вам не нужны CODEOWNERS и график дежурств по ревью. Нужны три вещи:

  1. Файл плана на каждую фичу, который вы действительно читаете до того, как агент начал кодить.
  2. Список tier 1 из пяти-десяти файлов, которые агент не трогает без вашего чтения. Обычно это auth, платежи, миграции, обработчик вебхуков, конфиг деплоя.
  3. Зелёный CI, в котором есть хотя бы один интеграционный тест, бьющий в настоящую базу.

Всё остальное можно отдать тестам и роллбэкам. Смысл не в том, чтобы ревьюить больше. Смысл в том, чтобы заранее решить, где ваше внимание дороже вашего тулинга, и потом защищать это внимание жёстко.

Команды, у которых сейчас болит, страдают не от того, что агенты пишут плохой код. Код агентов в основном нормальный. Они страдают потому, что генерацию масштабировали в пять раз, а ревью оставили как было, и потом удивились, что очередь встала. Сначала починить конвейер ревью. Генерация уже и так работает.

Вопросы и ответы

Не проще ли просто просить агента писать меньше кода, вместо всех этих правил в CI?

Просить нужно, и я прошу: ограничение по размеру и по области ответственности идёт в prompt заранее. Но просьба не воспроизводима. В следующей сессии, с другим контекстом или другой моделью, агент опять принесёт 2000 строк, потому что ему так удобнее закрыть задачу. Правило в CI работает всегда и без вашего участия, а prompt - только пока вы о нём помните. Держите оба уровня: prompt задаёт норму, CI её удерживает.

Как понять, что агент подкрутил тесты, а не починил код?

Смотрите на удалённые и ослабленные строки в тестовых файлах, а не на добавленные. Три типовых признака: точный ассерт заменён на проверку "что-нибудь пришло", тест помечен skip или todo, ожидаемое значение переписано под фактический вывод кода. Я отдельным шагом CI считаю число удалённых строк в файлах тестов и требую ручного сайн-оффа, если оно больше нуля. Плюс критичные бизнес-правила я оформляю тестами сам, до реализации, и запрещаю агенту редактировать эти файлы.

Сколько времени реально уходит на такой протокол и не убивает ли он скорость MVP?

Наоборот. Три минуты на чтение плана и до 12 минут на проход по PR - это меньше, чем один вечер отладки чужого непрочитанного кода в проде. На MVP я обычно урезаю протокол до трёх элементов: план перед кодом, список из пяти-десяти файлов tier 1, которые не мержатся без чтения, и один интеграционный тест на живой базе в CI. Это добавляет минуты к каждой фиче и снимает основной класс дорогих ошибок: деньги, доступы, необратимые миграции.

Похожие статьи