Модуль 2 · Качество · урок 6 из 13
Ворота качества: машина и человек
Когда поток изменений растёт, каждое правило, которое проверяет человек, становится дорогим. Ворота качества — это решение, какие проверки делает машина и блокирует мерж, какие делает машина и только советует, а какие остаются за человеком. Правильно поставленные ворота делают ревью короче и спокойнее. В этом уроке — как их выстроить в GitLab CI для проекта агентства.
Два вида машинных проверок
Биргитта Бёкелер в статье о harness engineering делит проверки на два типа — в оригинале computational и inferential (martinfowler.com):
- Детерминированные — тесты, линтеры, проверка типов, структурные тесты архитектуры. Быстрые, дешёвые, дают одинаковый результат при каждом запуске. Им можно доверить блокировку мержа.
- Вероятностные — ревью языковой моделью, оценка «LLM как судья». Видят смысл, но могут ошибаться в обе стороны. Их место — совет и подсветка, а не единственный барьер.
Отсюда первое правило: всё, что можно проверить детерминированно, проверяйте детерминированно. Правило «не конкатенировать SQL» в AGENTS.md агент может забыть, правило линтера — нет. Документация Codex прямо советует оставлять форматирование и линт CI, а не правилам ревью-агента (GitLab).
Сообщение об ошибке — тоже инструкция
Агент читает вывод проверок и исправляет код сам — если понимает, что от него хотят. Команда OpenAI пишет сообщения своих линтеров как инструкции по исправлению, чтобы они сразу попадали в контекст агента (Harness engineering). Сравните:
Error: forbidden dependencyКомпонент themes/site/partials/hero.htm обращается к модели напрямую.
Шаблоны получают данные только через компонент страницы.
Перенесите запрос в plugins/site/content/components/Hero.php
и передайте результат в шаблон через $this->page['hero'].Второе сообщение исправит и агент, и джуниор. Как писать такие проверки — в уроке «Архитектура через инварианты».
Слои проверок
Принцип «качество левее» — ловить проблему как можно раньше и дешевле:
| Когда | Что | Блокирует? |
|---|---|---|
| Локально, до коммита | Линтер, форматирование, быстрые юнит-тесты; агент запускает их сам по командам из AGENTS.md | Да, для автора |
| В MR | Линт, статанализ, тесты, лимит размера MR, сканирование зависимостей и SAST | Да |
| В MR | Ревью-агент | Нет, совещательно |
| В MR | Человеческое ревью по уровню риска | Да |
| После мержа | E2E-регресс на стенде, проверка релиза QA | Да, для релиза |
Агент в локальном цикле сильно экономит время: если в AGENTS.md записаны команды линта и тестов, он прогонит их сам и придёт в MR уже с зелёным результатом.
Пример конвейера
Ниже — рабочий минимум для PHP-проекта: статанализ с отчётом в виджет MR, юнит-тесты, лимит размера MR и совещательное ревью агентом.
stages: [check, test, review]
variables:
MAX_CHANGED_LINES: "600"
.mr_only:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
phpstan:
extends: .mr_only
stage: check
image: composer:2
script:
- composer install --no-interaction --prefer-dist
- vendor/bin/phpstan analyse --no-progress --error-format=gitlab > phpstan.json
artifacts:
reports:
codequality: phpstan.json
when: always
mr_size:
extends: .mr_only
stage: check
image:
name: alpine/git
entrypoint: [""]
variables:
GIT_DEPTH: "0"
script:
- >
CHANGED=$(git diff --numstat "$CI_MERGE_REQUEST_DIFF_BASE_SHA" HEAD --
. ':!composer.lock' ':!package-lock.json'
| awk '{s+=$1+$2} END {print s+0}')
- echo "Изменено строк $CHANGED, лимит $MAX_CHANGED_LINES"
- test "$CHANGED" -le "$MAX_CHANGED_LINES"
unit:
extends: .mr_only
stage: test
image: composer:2
script:
- composer install --no-interaction --prefer-dist
- vendor/bin/phpunit --log-junit junit.xml
artifacts:
reports:
junit: junit.xml
when: always
agent_review:
stage: review
image: node:22
rules:
# только MR из веток этого проекта, не из форков
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_PROJECT_ID == $CI_PROJECT_ID
allow_failure: true
environment:
name: agent-review # к этому окружению привязана переменная с ключом
action: prepare # без создания деплоя
variables:
GIT_DEPTH: "0"
script:
# версию закрепляем, чтобы поведение не менялось само
- npm install -g @openai/codex@0.163.0
- >
git diff "$CI_MERGE_REQUEST_DIFF_BASE_SHA" HEAD
| CODEX_API_KEY="$CODEX_REVIEW_KEY"
codex exec --sandbox read-only --ephemeral -o agent-review.md
"Это дифф merge request. Проведи ревью по разделу
Code Review Rules из AGENTS.md. Пиши только о серьёзных
проблемах: файл, строка, почему это ошибка, как исправить.
Если проблем нет, ответь одной строкой: замечаний нет."
artifacts:
paths: [agent-review.md]
when: alwaysЧто здесь важно:
allow_failure: trueу ревью агентом: его находки — совет, они не блокируют мерж и не заменяют человека.- Ключ хранится в маскированной переменной CI под отдельным именем (
CODEX_REVIEW_KEY) и подставляется вCODEX_API_KEYтолько для вызоваcodex exec. Задание с ключом не запускает код репозитория и не работает на MR из форков: в официальной документации прямо предупреждают не выставлять ключ на уровне задачи, где выгружается или выполняется код проекта (non-interactive mode). Переменные GitLab по умолчанию видны всем задачам пайплайна, включаяcomposer installи тесты, — ограничьте переменную областью окружения (environment scope) и укажите это окружение только у задачи ревью. - Дифф подаётся агенту через stdin, песочница только на чтение.
- Лимит в 600 строк — пример, подберите свой по истории проекта.
Полноценные варианты — с отчётом ревью прямо в виджете MR, разбором находок SAST и патчами — есть в кукбуке OpenAI Secure quality with GitLab и в уроке «Codex в GitLab CI». Вопросы безопасности таких заданий — в «Безопасность автоматизации».
Что остаётся человеку
Машина не ответит на вопросы, которые составляют суть ревью:
- Решает ли изменение ту задачу, которую ставил клиент, а не ту, что понял агент?
- Не стало ли решение сложнее, чем нужно? Нет ли в проекте уже готового способа?
- Проверяют ли тесты поведение, или они просто повторяют реализацию? Тесты, написанные тем же агентом, что и код, требуют отдельного внимания — см. «Как проверять тесты, написанные агентом».
- Как изменение выглядит и ощущается: вёрстка, анимации, тексты, поведение на телефоне.
- Безопасность в контексте: какие данные клиента затронуты, кто имеет доступ.
Чем больше рутины забрали ворота, тем больше внимания остаётся на эти вопросы.
Защита веток — не трогать
Ворота работают, только если их нельзя обойти. Защищённая ветка main, мерж только через MR, обязательный зелёный конвейер и одобрение человека — это база, которую не меняют ради скорости. Правила ревью-агента её не заменяют: об этом прямо написано в документации Codex.
Попробуйте сами
10–15 мин на рабочем месте- Выпишите правила из AGENTS.md и ревью-чек-листа команды. Отметьте, какие можно превратить в линтер или тест. Возьмите одно и сделайте.
- Добавьте в свой
.gitlab-ci.ymlзаданиеmr_sizeс лимитом, который не нарушали 80% MR за последний месяц. - Перепишите сообщение одной своей проверки так, чтобы из него было понятно, как исправить ошибку.
Коротко
- Детерминированные проверки блокируют мерж, вероятностные — советуют.
- Всё, что можно проверить линтером или тестом, не должно жить только в тексте правил.
- Сообщения об ошибках пишутся как инструкции по исправлению — для агента и для людей.
- Ревью агентом в CI — совещательный этап с закреплённой версией и без доступа форков к ключу.
- Человек проверяет смысл, сложность, тесты, ощущение и безопасность в контексте; защиту веток не ослабляют.