Подключите ревью на хук: быстрое на коммите, полное на пуше
Гонять полное ревью на каждом коммите — плохая мысль, и это видно по арифметике: на наших прогонах судья съедает около половины цены. Поэтому у двух хуков разные задачи. Хук коммита дешёвый и грубый — одна модель, без судьи, секунды — и останавливает он только за то, что вы вообще не захотели бы вносить в историю. Хук пуша строгий, с судьёй, и стоит там, где код уходит с вашей машины.
Ниже — как поставить обе ступени, какой код возврата блокирует, а какой пропускает, и почему без явного порога хук не блокирует вообще ничего.
Перед началом: нужен работающий в терминале reviewgate и доступ к модели (свой API-ключ, корпоративный шлюз или локальная модель). Если первого ревью ещё не было, начните с первого ревью на своей машине.
Что на самом деле выключает --fast
Это не «взгляд помельче». --fast меняет состав ролей: из всех генераторов остаётся только первый, панель судей обнуляется, арбитр снимается. Вместе с судьями выключаются и стадии, которые на них держатся: судейский проход, сбор полных файлов для судьи, перепроверка готовых фиксов и линза вопросов — та, что превращает часть снятых замечаний в вопросы к автору.
На наших прогонах это выглядело так: чистая проверка на коммите, одна правка в README, — 4 секунды и ≈ 0,01 $. Ветка целиком на пуше, два файла и 31 строка против main, остановленная судьёй, — около двух минут и ≈ 0,26 $. Сколько стоит сам судья, замерено отдельно, на одном и том же диффе: около половины счёта. Это соотношение и есть весь аргумент в пользу двух ступеней вместо одной.
Шаг 1. Хук коммита: дешёвый, грубый, редко мешает
# .husky/pre-commit
# free checks first — uncomment what you run:
# npx lint-staged
# npm test
# hooks should run on one agreed personal config, not on each developer's own.
# Applied only if the file exists, so a typo cannot disable the review silently:
# CFG="$HOME/.config/reviewgate/config.hooks.yml"
# [ -z "${REVIEWGATE_CONFIG:-}" ] && [ -f "$CFG" ] && export REVIEWGATE_CONFIG="$CFG"
command -v reviewgate >/dev/null 2>&1 || {
echo "reviewgate: not installed — commit allowed unchecked" >&2
exit 0
}
code=0
reviewgate review --staged --fast --fail-on blocker || code=$?
case $code in
0) ;;
2)
echo "reviewgate: blocking findings — commit stopped" \
"(bypass: git commit --no-verify)" >&2
exit 1
;;
*)
echo "reviewgate: the review did not run (code $code)" \
"— commit allowed unchecked" >&2
;;
esac
exit 0Здесь четыре решения. От них зависит, приживётся ли хук или вы снесёте его через неделю.
Важно:
--fail-on blocker, а неmajor. Черновой коммит не должен становиться заложником замечания о стиле. На этом пороге ревью по-прежнему показывает всё найденное — 🟠 major вы увидите, — но останавливает только за то, чего вы вообще не захотели бы иметь в истории: секрет или учётные данные в диффе и безвозвратное уничтожение данных. Это ровно те два класса, которые продукт считает безусловно блокирующими; всё остальное, включая сломанную миграцию и снятую проверку доступа, приходит уровнем 🔴 critical и коммит не останавливает.--fail-onвообще. Без него хук не блокирует никогда. Гейт по умолчанию выключен, поэтому ревью выходит с нулём при любом числе замечаний — а хук, увидев ноль, пропускает коммит. Так и получается хук, который замечания печатает, но ничего не останавливает.- Код возврата ловите сразу:
|| code=$?. husky запускает файл хука командойsh -e, то есть оболочка падает на первой же неуспешной команде. Значит, строка с ревью, вернувшая ненулевой код, обрывает хук целиком, и написанный следомcase $?не выполняется вовсе: вы не увидите ни своего сообщения, ни разбора кодов. - Сначала бесплатные проверки, потом ревью. Линтер, форматтер и тесты детерминированы и ничего не стоят, а
sh -eоборвёт хук на первом же падении, и модель не будет вызвана зря.|| code=$?нужен только строке с ревью — остальные команды могут просто упасть.
Шаг 2. Хук пуша: строгий
# .husky/pre-push
# free checks first — uncomment what you run:
# npm run lint
# npm test
# hooks should run on one agreed personal config, not on each developer's own.
# Applied only if the file exists, so a typo cannot disable the review silently:
# CFG="$HOME/.config/reviewgate/config.hooks.yml"
# [ -z "${REVIEWGATE_CONFIG:-}" ] && [ -f "$CFG" ] && export REVIEWGATE_CONFIG="$CFG"
command -v reviewgate >/dev/null 2>&1 || {
echo "reviewgate: not installed — push allowed unchecked" >&2
exit 0
}
code=0
reviewgate review --refs origin/main --fail-on major || code=$?
case $code in
0) ;;
2)
echo "reviewgate: blocking findings — push stopped" \
"(bypass: git push --no-verify)" >&2
exit 1
;;
*)
echo "reviewgate: the review did not run (code $code)" \
"— push allowed unchecked" >&2
;;
esac
exit 0Та же форма, три отличия: вся ветка вместо индекса, судья вместо одной модели и major вместо blocker — потому что здесь код перестаёт быть только вашим.
Как это выглядит в терминале
Обе ветки открыты в нашем учебном репозитории: та, где токен, — на GitLab и GitHub, та, где скидка, — на GitLab и GitHub.
Разработчик подключает письмо о счёте к почтовому провайдеру и кладёт токен прямо в исходник. Хук коммита не пускает это в историю:
⛔ src/notifications/notifications.service.ts:18 — В код закоммичен реальный (судя по
формату mrt_live_...) секретный токен провайдера доставки писем. Такой секрет
попадает в историю git и виден всем, у кого есть доступ к репозиторию — это утечка
учётных данных.
Немедленно отозвать/сменить этот токен у провайдера, вынести адрес и токен в
конфигурацию через ConfigService (NestJS) вместо хардкода в модуле.
❌ Гейт (blocker) НЕ ПРОЙДЕН. Замечаний: 2 (⛔ 1 · 🔴 0 · 🟠 1 · 🔵 0 · ⚪ 0).
⚙️ Стоимость прогона: ≈ 0,07 $
reviewgate: blocking findings — commit stopped (bypass: git commit --no-verify)
husky - pre-commit script failed (code 1)0,07 $ и около сорока секунд. Обратите внимание на счётчики: замечание 🟠 major было тоже показано — и ничего не остановило, потому что порог здесь blocker. В этом и смысл низкой планки на коммите: видно всё, а останавливают редко и только за блокирующее.
Разработчик выносит токен в окружение и коммитит снова:
✅ Гейт (blocker) пройден. Замечаний: 3 (⛔ 0 · 🔴 0 · 🟠 2 · 🔵 1 · ⚪ 0).
⚙️ Стоимость прогона: ≈ 0,07 $
[feat/email-delivery 6c5bf3e] fix: take the delivery credentials from the environmentБлокирующее замечание ушло вместе с токеном, и коммит проходит. Но замечаний стало не меньше, а больше: было одно 🟠 major, теперь два 🟠 major и одно 🔵 minor — дифф изменился, а модель недетерминирована, и второй прогон не повторяет первый. Ничего не замели под ковёр: все три на экране, просто это не повод блокировать черновик.
Тот же репозиторий, другая ветка — та, где скидку по купону посчитали руками вместо общего денежного хелпера:
❌ Гейт (major) НЕ ПРОЙДЕН. Замечаний: 3 (⛔ 0 · 🔴 2 · 🟠 1 · 🔵 0 · ⚪ 0).
⚙️ Стоимость прогона: ≈ 0,26 $
reviewgate: blocking findings — push stopped (bypass: git push --no-verify)
husky - pre-push script failed (code 1)
error: failed to push some refs0,26 $ — и ветка остаётся на машине, пока автор не поправит то, на что указали замечания 🔴 critical про деньги.
Во сколько обходится лесенка
Наши прогоны, тот же репозиторий, 7 сентября 2026; цены по прайс-листу Anthropic на 23 сентября 2026 года. Цена зависит и от кэша: промпт кэшируется на час, и первый прогон в течение часа дороже следующих.
| Ступень | Область | Время | Цена |
|---|---|---|---|
| коммит, чисто | индекс, правка в README | 4 с | ≈ 0,01 $ |
| коммит, есть замечания | индекс, один файл | ~40 с | ≈ 0,07 $ |
| пуш, чисто | ветка с правкой README против main | 3 с | ≈ 0,01 $ |
| пуш, остановлен | ветка со скидкой против main | ~120 с | ≈ 0,26 $ |
Четыре вещи, которые ломают лесенку
Lock-файл значительно увеличит ваш счёт. Наш самый первый прогон хука стоил 0,33 $ на диффе в 37 строк, потому что pnpm-lock.yaml уехал в контекст модели целиком: 143 962 байта из 146 179, которые модель прочитала, и это вылилось в 96 тысяч токенов входа ради четырёх изменённых файлов — файл из хешей упаковывается в токены куда плотнее прозы. С lock-файлом в ignore тот же коммит обошёлся в 0,01 $: более чем в двадцать раз дешевле и в десять раз быстрее. Кладите lock-файлы, генерированный код и фикстуры в ignore до того, как поставите хук на коммит, а не после.
git push --dry-run запускает хук. Мы проверили: сухой прогон вызывает pre-push ровно так же, как настоящий, поэтому «просто посмотреть, что будет» стоит полного ревью с судьёй. Подавляется это флагом --no-verify.
Личный конфиг разработчика побеждает командный. Домашняя схема заменяет роли команды, а не дополняет их: коллега, у которого в личном конфиге другой провайдер, прогонит командное ревью на своей модели — а если ключ не подойдёт, получит упавший прогон, который хук пропустит — со строкой в stderr, которую легко не заметить. Если хуки обязаны идти на командной схеме, задайте прямо в хуке переменную REVIEWGATE_CONFIG с путём к нужному файлу: она перебивает домашний конфиг. Так сделан и наш собственный хук — и путь там подставляется, только если файл существует: если файла нет, ReviewGate решит, что личного конфига нет вовсе, прогон упадёт на llm_unavailable, а хук пропустит вас дальше.
--no-verify — штатный выход, а не взлом. Обойти оба хука может кто угодно, и будут обходить: на хотфиксе в полночь, на откате, на ветке, которая и так сломана. Локальные хуки работают ровно до тех пор, пока их не обходят. Проверка, которую обойти нельзя, стоит на стороне площадки — это бот в merge request или pull request, и подключается он отдельно.
Когда что-то пошло не так
Хук ничего не блокирует. Почти всегда — забытый --fail-on: гейт по умолчанию выключен, ревью выходит с нулём при любых замечаниях, а хук получает нулевой код и пропускает коммит. Проверить: выполните ту же команду руками и посмотрите код возврата — заблокированный прогон даёт 2:
reviewgate review --staged --fast --fail-on blocker; echo $?Хук не запускается вовсе — ни вывода, ни задержки. Git про него не знает: путь к хукам прописывает husky при установке, и только там, где отработал prepare. На свежем клоне их ставит на место pnpm install. Проверить: git config core.hooksPath должен показать .husky/_.
Коммит думает минутами. В контекст модели уезжает что-то большое — чаще всего lock-файл, снапшот или генерируемый код. Посмотрите в выводе прогона строку Judge context: там названо число файлов и символов. Лечится это ignore.
В stderr написано «the review did not run (code N)». Сломалось само ревью — нет ключа, нет сети, конфиг не читается, — и хук намеренно вас пропустил. Ваш коммит или пуш ушёл непроверенным. Запустите команду руками, чтобы увидеть настоящую ошибку: код возврата назовёт класс сбоя.
Коммит, который делает ИИ-агент, обрывается по таймауту. Агенты запускают команды со своим лимитом времени, и полное ревью в него может не уложиться. Тогда коммита не будет вовсе: изменения останутся в индексе, а начатый прогон продолжится в фоне, и вы за него заплатите. Дальше агент, увидев ошибку, легко повторит коммит с --no-verify — вот тогда код уйдёт непроверенным. Поэтому на коммите и держат --fast: он укладывается в секунды.
Дальше
- Первое ревью на своей машине— если терминальная часть ещё в новинку
- Гейт перед push без husky— вариант на голых git-хуках
- Справочник CLI— флаги, коды возврата, что каждый означает
- Справочник конфига— ignore, severity_gate, роли моделей
- Ревью в Merge Request— проверка, которую нельзя обойти; у GitHub своя страница