Автоматизируем код-ревью в GitLab CI/CD: Суровый AI-ревьюер

26.06.2026 21:00

Мы все знаем этот паттерн. Пулл-реквест на 800 строк кода висит в трекере. Ревьюер открывает вкладку изменений, скроллит вниз со скоростью чтения пользовательского соглашения и нажимает Approve. Комментарий: LGTM (Looks Good To Me). Иллюзия контроля соблюдена, галочка в Jira поставлена, потенциальный баг отправляется в продакшен.

Контекст-свитчинг убивает продуктивность. Разработчик пишет сложный микросервис, находится в состоянии потока, и тут ему прилетает просьба посмотреть соседний Merge Request. Мозг пытается выгрузить один контекст и загрузить другой. В результате ревью проводится поверхностно. Проблема не в том, что инженеры глупые или безответственные. Проблема в том, что человеческий мозг плохо приспособлен к парсингу сотен строк чужого контекста в конце тяжелого рабочего дня. Мы платим за это инцидентами на проде и бессонными ночами дежурных инженеров.

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

Архитектура и выбор инструментов

Выбор пал на Claude 3.5 Sonnet. На данный момент это одна из лучших моделей для анализа кода, способная удерживать огромный контекст без потери фокуса и галлюцинаций на пустом месте.

Почему RouterAPI, а не прямой вызов Anthropic API? В корпоративной среде важна отказоустойчивость и жесткий контроль расходов. RouterAPI дает нам единую точку входа. Если завтра мы решим протестировать новую модель от OpenAI или Google, нам не придется переписывать bash-скрипт и менять переменные окружения в GitLab. Достаточно изменить строку model в JSON-пейлоаде. Кроме того, встроенные механизмы нормализации стоимости позволяют нам точно считать, сколько центов уходит на ревью каждого конкретного пулл-реквеста, не собирая биллинги по разным провайдерам.

Инфраструктура максимально аскетична: GitLab CI/CD. Инструментарий: bash, curl, jq. Никаких тяжеловесных Docker-контейнеров с Python-скриптами и десятком зависимостей, только суровые POSIX-утилиты, которые отрабатывают за секунды и не требуют поддержки.

Подготовка пайплайна

Начинаем с конфигурации пайплайна. Нам нужно, чтобы джоба запускалась исключительно при создании или обновлении Merge Request.

ai_code_review:
 stage: test
 image: alpine:latest
 before_script:
 - apk add --no-cache curl jq
 script:
 - ./scripts/ai_reviewer.sh
 rules:
 - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Извлечение контекста

Внутри ai_reviewer.sh первая задача — получить изменения. Использовать git diff напрямую внутри раннера бывает больно из-за shallow clones (по умолчанию GitLab клонирует только последние 50 коммитов). Гораздо надежнее запросить diff через GitLab API.

Для работы скрипта потребуется создать Project Access Token с правами api. Использовать дефолтный $CI_JOB_TOKEN для публикации комментариев в тред Merge Request не получится — его прав недостаточно для полноценной работы с Notes API. Токен нужно сохранить в CI/CD Variables проекта под именем GITLAB_API_TOKEN, обязательно отметив флаги Masked и Protected.

Здесь кроется первый подводный камень. Если MR содержит изменения в package-lock.json, сгенерированных файлах или минифицированной статике, diff может раздуться до мегабайтов. Claude 3.5 Sonnet имеет огромное окно контекста, но скармливать ему мусор — значит жечь деньги компании впустую и снижать качество ответов. Поэтому перед отправкой мы жестко фильтруем diff, исключая вендорные директории и лок-файлы с помощью jq.

#!/bin/sh
set -e

# Получаем изменения через GitLab API
CHANGES_URL="$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/changes"
CHANGES_RESPONSE=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" "$CHANGES_URL")

# Извлекаем diff, фильтруя ненужные файлы
DIFF_TEXT=$(echo "$CHANGES_RESPONSE" | jq -r '.changes[] | select(.new_path | test("vendor/|package-lock.json|composer.lock") | not) | "File: \(.new_path)\n\n\(.diff)\n---"' | sed 's/"/\\"/g')

if [ -z "$DIFF_TEXT" ]; then
 echo "Нет релевантных изменений для анализа."
 exit 0
fi

Промпт-инжиниринг

Промпт — это 80% успеха всей затеи. Если просто попросить модель "проверить код", она начнет докапываться до нейминга переменных, отсутствия пустой строки в конце файла или кавычек. Это мусор, который генерирует информационный шум и раздражает команду. Нам нужен строгий архитектор, а не душнила-линтер.

SYSTEM_PROMPT="Ты — Staff Engineer. Твоя задача — провести жесткое код-ревью предоставленного diff.
Игнорируй: форматирование, стиль кода, отсутствие комментариев (для этого настроены линтеры).
Ищи: логические ошибки, race conditions, SQL-инъекции, неэффективные алгоритмы (N+1 запросы), утечки памяти, нарушение принципов SOLID.
Формат ответа: используй Markdown. Указывай конкретный файл и суть проблемы. Если критичных архитектурных или логических проблем нет, верни ровно одну фразу: 'CRITICAL ISSUES NOT FOUND'."

Интеграция с RouterAPI

Формируем JSON-payload и отправляем его в RouterAPI. Поскольку сервис поддерживает OpenAI-совместимый формат, интеграция выглядит тривиально. Мы выставляем temperature: 0.2, чтобы минимизировать креативность модели и заставить ее фокусироваться исключительно на фактах и коде.

PAYLOAD=$(cat <<EOF
{
 "model": "anthropic/claude-3.5-sonnet",
 "messages": [
 {"role": "system", "content": "$SYSTEM_PROMPT"},
 {"role": "user", "content": "Проведи ревью следующих изменений:\n\n$DIFF_TEXT"}
 ],
 "temperature": 0.2
}
EOF
)

REVIEW_RESPONSE=$(curl -s -X POST "https://api.routerapi.com/v1/chat/completions" \
 -H "Authorization: Bearer $ROUTERAPI_TOKEN" \
 -H "Content-Type: application/json" \
 -d "$PAYLOAD")

REVIEW_COMMENT=$(echo "$REVIEW_RESPONSE" | jq -r '.choices[0].message.content')

Возврат результатов в GitLab

Полученный ответ нужно вернуть разработчику туда, где он его гарантированно увидит — в обсуждение Merge Request. Снова используем GitLab API. В идеале можно заморочиться с GitLab Inline Comments API, чтобы бот оставлял комментарии прямо к конкретным строкам кода, но для первой итерации достаточно отправки общего сводного отчета в корень MR.

if [ "$REVIEW_COMMENT" != "CRITICAL ISSUES NOT FOUND" ] && [ -n "$REVIEW_COMMENT" ]; then
 # Экранируем спецсимволы для JSON
 COMMENT_PAYLOAD=$(jq -n --arg body "🤖 **AI Reviewer (Claude 3.5 Sonnet):**\n\n$REVIEW_COMMENT" '{body: $body}')

 curl -s -X POST "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
 -H "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
 -H "Content-Type: application/json" \
 -d "$COMMENT_PAYLOAD"

 echo "Найдены проблемы. Комментарий успешно отправлен в MR."
 # Опционально: можно завершить джобу с ошибкой, чтобы заблокировать мерж
 # exit 1 
else
 echo "Критичных проблем не найдено. Ревью пройдено."
fi

Инсайт и результаты

Внедрение этого скрипта вызвало предсказуемое сопротивление на старте. "Бездушная машина будет учить меня писать код?" — возмущались сеньоры. Но через две недели тональность кардинально изменилась.

AI-ревьюер не заменил живое обсуждение архитектуры. Он взял на себя самую неблагодарную работу: поиск забытых проверок на null, неоптимизированных циклов, N+1 запросов к базе данных и потенциальных дедлоков. Когда Claude нашел уязвимость в логике авторизации, которую пропустили два живых ревьюера (поставивших свои дежурные LGTM), скепсис испарился окончательно.

Бот не испытывает эмпатии. Ему не стыдно указать тимлиду на глупую опечатку. И, что парадоксально, разработчикам психологически комфортнее получать замечания от скрипта. Это не воспринимается как личная критика или попытка самоутвердиться за чужой счет. Ошибку можно исправить тихим пушем до того, как код посмотрит живой коллега.

Мы перестали тратить время на механический поиск багов глазами. Живое ревью теперь фокусируется на бизнес-логике и соответствии требованиям продукта. А суровый AI-ревьюер методично парсит каждый коммит, не прося прибавки к зарплате.

Теги

Ещё по теме