конфигурация

Конфигурация ревью

Стандарты команды живут в файле .reviewgate/config.yml в вашем репозитории — как у линтеров. Они версионируются вместе с кодом и меняются через MR/PR. Это ключевое отличие от generic-ревью: вы описываете соглашения обычным языком, а модель применяет их к коду.

.reviewgate/config.yml
version: 1
language: en                 # language of the findings and the summary frame (en · ru · any other)

preset: angular              # the stack preset (see the table) · none
angular:
  signals_naming: "camelCase, no $ prefix"
  prefer: combineLatest      # instead of forkJoin where applicable
  zone_less: false
  a11y: true                 # accessibility block: ARIA, keyboard, focus (off by default)

review_prompt:               # your review guideline on top of the default (optional)
  mode: extend               # extend — add to the default · replace — replace it entirely
  text: |
    Pay particular attention to the resilience of HTTP calls and to leaked RxJS subscriptions.
    Keep the tone short and to the point.

severity_gate: off           # off (the default) | blocker | critical | major | minor | info
tests: optional              # required | optional — optional does not flag missing tests
incremental_review: true     # a re-push reviews only the changed files (true by default)
review_drafts: false         # do not review drafts; the run happens when it is marked ready
committable_suggestions: false   # ready fixes (multi-line included) as a native suggestion (1 click); better with a judge
min_severity: info               # threshold for inline comments; below it they are folded into the summary (info = post everything)

rules:
  - id: commit-prefix
    description: "The pull request title and the commits start with TASK-<number>"
    severity: critical
  - id: no-any
    description: "The any type is forbidden, except in *.spec.ts files"
    severity: major
  - id: error-handling
    description: "Every HTTP call has a catchError with a typed error"
    severity: major

dont_flag:                   # explicit team assumptions — they silence whole classes of false positives
  - "Internal links do not get rel=noopener noreferrer — our convention, for analytics"
  - "The empty value of our UI kit inputs is '' by convention, not null"

ignore:
  - "**/*.generated.ts"
  - "**/migrations/**"

integration_branches:        # review only working-branch → integration pull requests (see below)
  - dev
  - master
  - "release/**"

# Vendor keys and addresses live in the deployment environment (the LLM_BACKEND_* catalog),
# not here. config.yml holds the role layout; set it explicitly.
llm:
  generators:                       # the reviewing models; the FIRST one is the main one
    - model: claude-sonnet-4-6
      effort: high                  # reasoning depth of the role (optional)
  judges:                           # the judge: a second generate→verify pass (fewer false positives)
    - model: claude-opus-4-8
  max_context_files: 10
  full_file_context: true           # recall: whole files plus neighbours for the generator, not only the diff
  environment_context: true         # recall: stack versions (Node/TS/Angular) from the manifests into the prompt
работает без конфигаФайла нет или YAML битый — бот не падает, а откатывается на разумные дефолты. Конфиг только уточняет поведение.

Имя файла — .reviewgate/config.yml, и только оно: написание .yaml не читается. Если в репозитории появляется config.yaml, бот сообщает об этом в сводке и просит переименовать файл — политика, записанная не туда, не лежит молча.

блок llm задавайте явноКлючи и адреса вендоров живут в окружении инсталляции (каталог LLM_BACKEND_* и плоские LLM_*) — в config.yml их указывать нельзя. А вот раскладку ролей стоит задать явно: generators (модели-ревьюеры; первый — основной) и judges (судья, подтверждающий находки по полным файлам). Без них ревью идёт на модели по умолчанию инсталляции и одним проходом — без generate→verify, который отсекает ложные срабатывания.

Поля

полечто задаёт
versionверсия схемы конфига (сейчас 1)
languageязык вывода бота: и тексты ревью (замечания, итог), и каркас сводки — вердикт гейта, счётчики, подсказки, — и нотисы о деградации прогона (судейства не было, расколы панели решены без арбитра, ответ упёрся в потолок выхода). Любая строка (например en · ru · de): замечания бот пишет на указанном языке, каркас есть на английском и русском, для остальных языков каркас английский. По умолчанию en
presetготовый шаблон стека (опц.). JS/TS: angular · react · vue · svelte · nextjs · nestjs · express · typescript. Python: django · fastapi · flask · python. Go: gin · echo · fiber · go. Java: spring · java. C#/.NET: aspnet · csharp. PHP: laravel · symfony · yii2 · php. Kotlin: android · ktor · kotlin. Ruby: rails · ruby. Rust: axum · actix · rust. Swift: ios · swift. Dart: flutter · dart. Также none
review_promptваш гайдлайн ревью: mode (extend — поверх дефолта · replace — вместо него) + text
severity_gateпорог блокировки merge: off (по умолчанию — не блокирует) · blocker · critical · major · minor · info. Значения старой шкалы принимаются бессрочно: у ПОРОГОВ warning = major, comment = info (нижний уровень — «блокировать всё»); у rules[].severity comment = minor. Конфиги, написанные до 2.0, работают без правок
testsполитика тестов: required (дефолт) · optional — не отмечать отсутствие тестов
rules[]правила команды свободным текстом: id, description, severity
dont_flag[]явные допущения команды («это не проблема») — гасят классы ложных срабатываний; см. раздел ниже
ignore[]glob-маски файлов, которые не ревьюить. Скрытые файлы бот явно объявляет модели как «есть в MR/PR, но вне ревью» — чтобы не рождались ложные «файл отсутствует». Не прячьте под ignore то, о чём есть правило (например, каталог миграций при правиле «миграция обязана быть в MR/PR»)
integration_branches[]ревьюить только MR/PR «рабочая-ветка → интеграционная» (см. раздел ниже); пусто — все MR/PR
incremental_reviewре-пуш ревьюит только изменённые файлы (по умолчанию true; false — всегда весь MR/PR)
review_draftsревьюить ли черновики (по умолчанию false — черновики пропускаются, ревью при переводе в Ready)
committable_suggestionsоднозначные механические фиксы (в том числе многострочные, до 20 строк) — нативным suggestion-блоком (кнопка «Apply suggestion»). По умолчанию false; имеет смысл с судьёй (llm.judges) — он проверяет сам fix
min_severityпорог строчных комментариев: info (по умолчанию — постить всё) | minor | major | critical | blocker; старые значения порога: warning = major, comment = info (постить всё). Находки ниже порога не постятся в строки кода — показываются свёрнутым списком в сводке (список переживает инкрементальные прогоны; закрытые вами замечания не поднимаются). На severity_gate и счётчики сводки не влияет
costстоимость ревью в сводке: show (по умолчанию false) включает строки расхода — текущий прогон, ответы в тредах и итог по MR/PR; models — цены за 1 млн токенов (input/output) по имени модели, currency — метка валюты. Деньги считаются, только если цена задана для всех использованных моделей; цены — в вашей валюте, курсы валют бот не запрашивает
diagnosticsблок 🔬: вызовы моделей с таймингами, контекст и решения судьи с причинами — включая дропнутые находки (см. раздел ниже). Бот сворачивает его в сводке, CLI печатает в конце отчёта (в JSON — поле diagnostics). Кода в блоке нет. По умолчанию false; не задано — действует env-дефолт инсталляции DIAGNOSTICS
questionsжанр ❓-вопросов (см. раздел ниже): отброшенные судьёй кандидаты проходят второй взгляд по другой планке — «уместно ли сомнение», а не «доказан ли дефект», — и уместные публикуются ❓-тредами вне severity gate. По умолчанию false; требует настроенного судейства (llm.judges либо судей env-дефолтов инсталляции)
replyreply mode — бот отвечает на реплики в тредах своего ревью и на @упоминания (см. раздел ниже): enabled (по умолчанию false; не задано — env-дефолт REPLY_ENABLED), model / effort / backend (по умолчанию — модель генератора), max_replies_per_thread (лимит ответов на тред, по умолчанию 3). Вебхуку нужен триггер Comments
llmроли одной формы {model, backend, effort}: generators[] (модели-ревьюеры, первый — основной), judges[] (панель: 0 — без валидации, 1 — generate→verify, 2 — согласие решает, спор уходит арбитру), arbiter (председатель расколов панели); плюс max_context_files, full_file_context (полные файлы + соседи генератору, recall), environment_context (версии стека из манифестов). Ключи и адреса — в env инсталляции (каталог LLM_BACKEND_*), роль ссылается на запись по имени: backend: deepseek; backend: default — основной провайдер

Ошибки в конфиге не проходят молча

Парсер защитный: непонятое значение не валит ревью, а откатывается к дефолту. Но об откате он сообщает — строкой в сводке и в поле run.config.notices отчёта CLI. Названы там: опечатка в ключе (sevirity_gate — гейт остался бы выключенным, пока команда считает блокировку настроенной), значение не той формы (rules мэппингом вместо списка, ignore строкой, review_prompt строкой вместо объекта), а также нераспознанные preset, severity_gate, tests, min_severity, rules[].severity и effort с backend у роли. Последние два важнее прочих: отброшенный backend отправляет запрос провайдеру по умолчанию — тому, которого команда не выбирала, — а правило с непонятым severity откатывается к major, и правило, задуманное блокирующим, перестаёт держать гейт.

Проверка идёт глубже верхнего уровня. Лишний ключ внутри известного блока тоже объявляется: правило с globs или prompt (схема правила закрыта — id, description, severity; scoping и условия живут в review_prompt), provider внутри роли, опечатка внутри reply или cost. Объявляются и булево, которое булевым не является (questions: enable), и блок review_prompt без text, и посторонний config.yaml — бот читает только config.yml и говорит об этом.

То же самое с файлом целиком. Если он есть, но бот не смог его прочитать — у токена нет прав на репозиторий, не поднялась сеть до репозитория, — ревью всё равно пройдёт, но по дефолтам: без правил команды, без ignore, без гейта. У этого случая теперь своя строка в сводке, и лечение в ней названо точно, потому что оно противоположно случаю битого файла: проверять надо права токена бота, а не YAML.

ключ провайдера в этом файле не работаетllm.api_key, base_url, proxy здесь не применяются: связь и секреты настраиваются окружением инсталляции — иначе клон чужого репозитория уводил бы ваш код на чужой адрес. Если ключ всё-таки попал в файл, мы скажем об этом прямо: файл лежит в git, значит ключ уже в истории и его надо отозвать.

Валидация до коммита — JSON Schema

Всё перечисленное выше происходит во время ревью. Те же ошибки можно поймать до коммита: схема конфига опубликована как JSON Schema по адресу reviewgate.dev/config.schema.json. Укажите её редактору комментарием из первой строки ниже — YAML-расширение VS Code (yaml-language-server) валидирует и подсказывает прямо при наборе. Тот же файл работает в CI и в руках ИИ-агента, который пишет конфиг за вас: неизвестные ключи, закрытая схема правил, форма ролей, капы — схема зеркалит парсер и держится с ним в синхроне тестами.

.reviewgate/config.yml — первая строка
# yaml-language-server: $schema=https://reviewgate.dev/config.schema.json
version: 1
# … the rest of the config

Полный пример — все опции

Все поля разом, для справки. Всё опционально (парсер защитный: неизвестное и невалидное заменяется дефолтами, о невалидном бот предупреждает в своих логах), кроме блока llm, который стоит задавать явно (см. выше).

.reviewgate/config.yml — полный референс
# The full .reviewgate/config.yml schema — every field, for reference.
# The parser is defensive: anything unknown or invalid is replaced by a default (and the bot
# warns about the invalid parts). Most fields are optional; set the llm block explicitly.

version: 1                   # schema version (currently 1)
language: en                 # language of the findings and the summary frame (en · ru · any other)

preset: angular              # the stack preset (see the table) · none — no preset
angular:                     # options of the chosen preset (the key is the preset name)
  signals_naming: "camelCase, no $ prefix"
  prefer: combineLatest
  zone_less: false
  a11y: true                 # optional: accessibility checks (ARIA / keyboard / focus)

review_prompt:               # your own review guideline (optional)
  mode: extend               # extend — on top of the default · replace — entirely instead of it
  text: |
    The guideline text in any language — what to check, the tone, the domain.

severity_gate: off           # off | blocker | critical | major | minor | info — the merge threshold
tests: required              # required | optional — optional does not flag missing tests
incremental_review: true     # a re-push reviews only the changed files (saves tokens)
review_drafts: false         # whether to review drafts (no by default)
committable_suggestions: false   # fixes as a native suggestion block (1 click); better with a judge (llm.judges)
min_severity: info           # blocker | critical | major | minor | info — threshold for inline comments

rules:                       # your team rules in plain English
  - id: error-handling
    description: "Every HTTP call handles its error (ADR-003)"
    severity: major           # blocker | critical | major | minor | info
  - id: mr-description       # request variables: {mr:author} {mr:title} {mr:description}
    description: "If the request has no description — ask {mr:author} to add one"   # {mr:source_branch}
    severity: minor                                                        # {mr:target_branch}
                                                                           # {mr:url} {mr:project}

dont_flag:                   # team assumptions — «not a problem, do not flag» (silences false positives)
  - "Internal links without rel=noreferrer — our convention"

ignore:                      # glob masks of files outside the review
  - "**/*.generated.ts"

integration_branches:        # review only working → integration (empty — every request)
  - dev
  - master

diagnostics: false           # the 🔬 block in the summary: calls, timings, judge decisions with reasons
                             #   (no code in the block; unset — the DIAGNOSTICS env default)

reply:                       # reply mode: the bot answers replies in threads (see the section)
  enabled: true              # unset — the REPLY_ENABLED env default; the webhook needs the Comments trigger
  model: claude-sonnet-4-6   # the model for replies (optional; the generator model by default)
  effort: high               # reasoning depth (optional)
  backend: deepseek          # a named backend from the deployment environment (optional, multi-vendor)
  max_replies_per_thread: 3  # cap on the bot's replies per thread (anti-loop)

cost:                        # the cost of the review in the summary (off by default)
  show: true                 # spend lines in the summary: this run, thread replies, the total
  currency: "$"              # the currency label ($ by default); prices are given in it — the bot fetches no rates
  models:                    # prices per 1 MILLION tokens; the key is the model name EXACTLY as in llm/env
    # prices are needed for EVERY model of the run: generators, judges and the arbiter.
    # The numbers below are an illustration; take the current ones from YOUR provider's price list.
    claude-sonnet-4-6: { input: 3, output: 15 }
    claude-opus-4-8: { input: 5, output: 25 }
    claude-fable-5: { input: 10, output: 50 }
    deepseek-chat: { input: 0.3, output: 1.2 }
    gpt-4o: { input: 2.5, output: 10 }

# Vendor keys and addresses live in the deployment environment (the LLM_BACKEND_* catalog),
# NOT in config.yml. Every role has the same shape {model, backend, effort}; backend is the name
# of a catalog entry (default = the deployment's main provider), and an unset effort falls back
# to the deployment default.
llm:
  generators:                       # the reviewing models; the FIRST is the main one (replies, fallbacks)
    - model: claude-sonnet-4-6
      effort: high
    - model: claude-opus-4-8        # another pair of eyes — recall goes up
    - backend: deepseek             # a role on a named backend from the environment (multi-vendor)
  judges:                           # the panel: 0 — no validation; 1 — generate→verify;
    - model: claude-opus-4-8        #   2 — agreement decides, a split goes to the arbiter (cap 2)
  arbiter:                          # the chairman: resolves splits of a two-judge panel
    model: claude-fable-5           #   (never called without judges)
  max_context_files: 10             # how many diff files to keep in context
  full_file_context: true           # recall: whole files plus imported neighbours for the generator
  environment_context: true         # recall: stack versions from the manifests into the prompt

Как составить конфиг с нуля

Порядок, который даёт рабочий конфиг без догадок:

  1. Стек → preset. Стек есть в таблице пресетов выше? Возьмите его как основу. Нет в списке — preset: none и опишите ревью в review_prompt.
  2. Правила — из того, что уже есть. Пройдитесь по линтерам (.eslintrc, phpcs.xml…) и канону команды (ADR, CONTRIBUTING, wiki). В rules[] выносите только то, что линтер НЕ ловит — договорённости команды; сошлитесь на источник в тексте правила. Дублировать линтер — шум.
  3. Ветки. Задайте integration_branches (см. раздел ниже) — иначе бот ревьюит все MR/PR подряд, включая служебные merge.
  4. Тон под домен. Нужен акцент — добавьте review_prompt (extend поверх пресета или replace целиком).
  5. Точность vs стоимость. Нужна высокая точность — добавьте судью: llm.judges: [{ model: … }] (2-й проход generate→verify, см. LLM). Поток дешёвых ревью — один генератор без судей.
  6. Максимальное качество (ансамбль, опц.). Для «идеального кода по вашим правилам» — несколько независимых генераторов: llm.generators перечисляет модели-ревьюеры (каждая ищет самостоятельно, разные модели видят разное — растёт recall), судья судит объединённые находки, схлопывает дубли и отсеивает ложные (держит точность). Второй судья превращает судейство в панель: согласие — решение, спор решает llm.arbiter — председатель. Каждая роль — дополнительный платный проход; включайте под самую высокую планку. Роль можно увести на другого вендораbackend: имя из каталога инсталляции (например, DeepSeek вторым генератором), см. LLM → мультивендор.
  7. Глубина поиска (recall). Чтобы бот видел не только дифф, а полные изменённые файлы и их импортируемых соседей — llm.full_file_context: true (ловит проблемы вне ханка, не домысливая). Чтобы судил по версиям стека (Node/TS/Angular из манифестов) — llm.environment_context: true (не путает доступность API в вашей версии). Оба opt-in и дороже по токенам — включайте под высокую планку качества, лучше вместе с судьёй.
  8. Фиксы в один клик (опц.). Хотите, чтобы бот предлагал однозначные правки нативным suggestion-блоком (кнопка «Apply suggestion») — committable_suggestions: true. Замена может покрывать и несколько соседних строк диффа (до 20). Применяет код прямо в ветку, поэтому держите вместе с судьёй (llm.judges — он проверяет сам фикс и его диапазон) и обкатайте сначала на доверенных репозиториях.
  9. Меньше шума (опц.). Если мелкие замечания отвлекают — min_severity: major: ниже порога бот не постит строчных комментариев, находки остаются свёрнутым списком в сводке (ничего не теряется). На блокировку (severity_gate) порог не влияет.
  10. Обкатайте без блокировки. Оставьте severity_gate: off на старте, посмотрите находки на нескольких MR/PR, и только потом включайте порог.

preset + extend или none + replace?

  • preset (+ опц. extend) — стек в списке, правила обычные. Готовая база, точечные акценты. Самый быстрый путь.
  • none + replace — стека нет в пресетах или у вас сильный собственный гайдлайн / ADR, который хотите контролировать дословно. Пишете промпт целиком — формат вывода бот добавит сам.

Правила команды

rules[] — это и есть отличие от линтера. Каждое правило: id (стабильный идентификатор), description (соглашение обычным текстом) и severity. Описание правила с тем же id может переопределить severity находки.

Находки по вашим правилам помечаются бейджем 📐 — и в строчном комментарии, и в сводке (строка «по правилам команды»). Так видно, что замечание опирается на ваш config.yml, а не на общие соображения модели; судья второго прохода дополнительно сверяет такие находки с текстом самого правила и отбрасывает те, что держатся только на ложной привязке. Правило с опечаткой в полях (id/description) не применяется — бот скажет об этом строкой в сводке и WARN в логе.

Исключить один файл: маркер внутри файла

Ключ ignore — про класс файлов: генерируемое, вендоренное, миграции; класс называется одним glob-выражением и не растёт. Одиночный файл — другая история. Запись в списке никогда не сокращается, молча ломается при переносе файла и провоцирует широкий glob, который годами глотает новые файлы. Для такого случая маркер ставится в сам файл:

любой файл — маркер действует в любом его месте
// reviewgate-ignore-file: generated test cases, a review adds no signal

Маркер обязан начинать строку и стоять за комментарием. Распознаются формы большинства языков: семейство слэшей вместе с doc-комментарием Rust, решётка, двойной дефис, звёздочка, блочные и JSX-комментарии, HTML, точка с запятой, процент, апостроф, скобки OCaml и Haskell, формы batch. Действует в любом месте файла, не только в шапке. Причина обязательна: маркер без причины не применяется, файл ревьюится как обычно, и сводка об этом говорит — иначе через год никто не восстановит, зачем повесили намордник. Если строка похожа на маркер, но форма не распознана, сводка скажет и об этом: маркер не отказывает молча.

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

документация не сканируетсяВ файлах .md, .rst, .txt и подобных маркер не ищется. Там решётка — заголовок, звёздочка — буллет, а пример маркера в блоке кода — законная иллюстрация: страница, объясняющая маркер вашей команде, иначе исключила бы саму себя. Цена принята осознанно: снять документацию с ревью этим способом нельзя — только через ignore.

Ложное срабатывание: как погасить класс

Если бот систематически флагает то, что для вашей команды норма — внутренняя конвенция, осознанный приём, — запишите допущение в dont_flag обычной фразой. Допущение видят все этапы — генератор находок, судья второго прохода и ответы в тредах, — поэтому класс гасится целиком, а не по одному замечанию. Формулируйте узко, про конкретный приём: слишком широкое допущение («не флагайте обработку ошибок») погасит и настоящие проблемы.

.reviewgate/config.yml
dont_flag:
  - "Internal links do not get rel=noopener noreferrer — our convention, for referrer analytics"
  - "The empty value of our UI kit inputs is '' by convention, not null/undefined"
  - "console.log is fine under scripts/ — those are CLI utilities"

Тот же эффект даёт исключение прямо в тексте правила («…; исключение: допустимо в тестах») — dont_flag нужен там, где отдельного правила нет. Встроенная дисциплина и без настройки не флагает вкусовщину вне ваших стандартов, микрооптимизации без измеримого эффекта, осознанные подавления с пояснением (eslint-disable, TODO с тикетом) и «заодно бы ещё» вне цели изменения.

Свой промпт ревью

Бот ревьюит по системному промпту — и этот промпт под вашим контролем. Из коробки работает разумный дефолт (что искать: баги, отказоустойчивость, безопасность, производительность, типизация), а через review_prompt вы пишете гайдлайн под свой проект, домен и стек — на любом языке, не только из списка пресетов.

слой промптакто им владеет
гайдлайн ревью — что искать, тон, доменвы — в review_prompt (или разумный дефолт)
формат вывода + дисциплина против ложных срабатыванийбот — машинный контракт, переопределить нельзя

mode: extend — ваш текст добавляется к дефолтному гайдлайну (точечно усилить акценты). mode: replace — дефолт не подмешивается, ревью идёт целиком по вашему промпту:

.reviewgate/config.yml — свой промпт целиком
review_prompt:
  mode: replace              # a guideline entirely your own — the default is not mixed in
  text: |
    You are reviewing an Angular project. What to look at:
    — Resilience: HTTP calls without catchError; subscriptions never unsubscribed.
    — Security: innerHTML without sanitising, secrets in the code.
    — List states: loading / error / empty result.
    — Typing: any where a concrete type can be inferred.
    Keep the tone short and to the point.

preset: none                 # your own prompt is self-sufficient — no preset needed
формат — не ваша заботаСтруктуру ответа (severity, номер строки, файл) и правило «не выдумывай проблемы, опирайся только на показанный дифф» бот дописывает сам поверх вашего текста. В своём промпте про формат и дисциплину писать не нужно — только что проверять.

Миграция со схемы 1.x

В 2.0 роли LLM записываются единообразно — списками ролей одной формы {model, backend, effort}. Старые ключи сняты: бот не применяет их и отвечает на каждый заметным notice в сводке с готовой строкой замены (ревью при этом выполняется — на канонических настройках и дефолтах инсталляции).

было (1.x)стало (2.0)
llm.model: Xllm.generators: [{model: X}]
llm.efforteffort в роли генератора
llm.validate_model: Xllm.judges: [{model: X}]
llm.validate_efforteffort в роли судьи
llm.extra_generatorsроли в llm.generators (основной — первым)
llm.providerвендор выбирается бэкендом роли (backend: имя)
llm.arbiter при ансамбле (1.x: судья объединённых находок)llm.judges: [{model: X}] — роль «судит и схлопывает union» в 2.0 играет судья; арбитр 2.0 — председатель расколов панели
арбитр «дорастал» из validate_modelнаследования нет: судит панель judges, арбитр — председатель расколов

Переносить руками не обязательно: reviewgate migrate делает это сам — по умолчанию показывает предпросмотр, --write применяет с бэкапом рядом. Команда понимает конфиг репозитория, личный конфиг CLI и .env-файлы инсталляции; секреты не печатает и не трогает, комментарии YAML сохраняет. Подробно: reviewgate help migrate и справочник CLI.

Поведение раскладки — «лестница»: один генератор без судей — законный эконом-режим; несколько генераторов без судей — union с механическим дедупом и notice с рецептом (семантические дубли никто не схлопнет); арбитр зовётся только при расколе панели из двух судей — без судей или при панели из одного он не зовётся, о чём прогон говорит notice. Явный judges: [] — осознанное «без валидации», он гасит и дефолт инсталляции. Рецепт «только основной провайдер инсталляции, без env-ансамбля»: generators: {backend: default}.

Расколы панели из двух судей решает арбитр-председатель; без него исход консервативный и зависит от вида спора: спор о правде находки — она отбрасывается (точность важнее полноты), спор о том, дубликат ли она другой находки, — сохраняется (лишний дубль дешевле потерянной находки), но committable-фикс у спорной находки снимается. Исключение — когда судьи указывают друг на друга (первый считает дублем одну находку, второй — другую): про сам факт дублирования они согласны и спорят лишь о том, кого оставить, поэтому остаётся одна. Все исходы видны в диагностике прогона.

Переменные MR/PR в промптах

В rules[].description и review_prompt можно ссылаться на метаданные конкретного MR/PR — плейсхолдерами вида {mr:поле}. Классика жанра — правило про гигиену MR/PR:

.reviewgate/config.yml — правило с переменной
rules:
  - id: mr-description-required
    description: "If the request has no description or no linked task — mention {mr:author} and ask for one"
    severity: minor

Бот подставит настоящий ник: в MR/PR без описания появится замечание вида «@ivanov, добавьте описание: что меняется и зачем». Автору придёт обычное уведомление платформы об упоминании.

переменнаязначение
{mr:author}ник автора MR/PR (бот упоминает через @)
{mr:title}заголовок MR/PR
{mr:description}описание MR/PR (пустое бот видит как «(пусто)»)
{mr:source_branch} / {mr:target_branch}ветки MR/PR
{mr:url}ссылка на MR/PR
{mr:project}путь проекта (группа/репозиторий)
метаданные бот видит всегдаАвтор, заголовок, описание и ссылка добавляются в контекст каждого ревью и без плейсхолдеров — правила вроде «в MR/PR нет описания — попроси добавить» работают, даже если переменная в тексте не написана. Плейсхолдер нужен, когда вы хотите явно управлять, где именно в замечании окажется значение.

Severity gate

По умолчанию severity_gate: off — бот ничего не блокирует, только оставляет комментарии. Ревью помогает изменению доехать, а не мешает.

Если команде нужен жёсткий порог, поставьте severity_gate: critical (или major). Тогда при находках этого уровня и выше бот выставляет неуспешный статус — check run на GitHub, commit status на GitLab, — и при настройке «pipeline must succeed» или обязательной проверки это блокирует merge.

Порог считается не только по последнему прогону. Если замечание уровня порога уже висит открытым тредом, а очередной прогон его файл не перепроверял (так бывает при инкрементальном ревью: изменился только другой файл) — оно продолжает держать статус красным, и в сводке это сказано прямо: «из них N — ранее открытые». Иначе посторонний коммит гасил бы блокировку, а замечание оставалось бы неисправленным.

Снять блокировку можно двумя способами: исправить — тогда следующий прогон перепроверит файл, не найдёт замечания и статус позеленеет сам; либо закрыть тред (Resolve) — это ваш сигнал «учтено или не относится к делу». Специально резолвить исправленное не нужно.

включайте осознанноБлокировка merge — сильный инструмент. Рекомендуем сначала обкатать ревью на нескольких MR/PR без gate и включать порог, когда команда доверяет находкам.

Какие MR/PR ревьюит бот

Без integration_branches бот ревьюит каждый MR/PR. Обычно это лишнее: служебные merge (доставка dev → release → master, back-merge, синки) ревью не требуют и создают шум. Список интеграционных веток включает фильтр:

правилоРевьюится MR/PR, у которого source-ветка ВНЕ списка, а target — В списке (рабочая ветка → интеграционная). Если обе ветки в списке (доставка / back-merge) — ревью пропускается.
.reviewgate/config.yml
integration_branches:
  - dev
  - master
  - "release/**"
# A review runs when the source branch is NOT in the list and the target IS (feature/X -> dev, say).
# A request between two branches from the list (dev -> master, a back-merge) is skipped as internal.
грабля: feature/Добавляйте в список только настоящие интеграционные ветки. Если feature/* у вас — обычная рабочая ветка под одну задачу (вливается в dev), не включайте её: иначе MR/PR feature/… → dev бот примет за служебный и молча пропустит. Добавляйте feature/*, только если это ветки-агрегаты, в которые вливаются другие.

Повторные ревью и Resolve

При новом пуше бот ревьюит заново, но не засыпает MR/PR дублями: уже открытые замечания не повторяются, а сводка одна и обновляется на месте. Если вы нажали Resolve на треде бота — это сигнал «учтено / не релевантно»: при следующих ревью бот не поднимет это замечание снова.

Повторное ревью инкрементальное: бот проверяет только файлы, изменившиеся с прошлого ревью, а не весь дифф — это экономит токены. Поведение по умолчанию; чтобы всегда ревьюить всё изменение целиком, поставьте incremental_review: false.

Черновики (Draft) бот по умолчанию не ревьюит — прогоняет их один раз, когда MR/PR переводят в Ready. Чтобы ревьюить и черновики, поставьте review_drafts: true.

как снять замечаниеНажмите Resolve на треде (или поправьте код). Ориентир для бота — именно статус Resolve, поэтому при итеративных правках ревью не превращается в повторяющуюся «стену» комментариев.

Стоимость ревью в сводке

С cost.show: true бот дописывает в сводку расход — сколько токенов ушло и, если задан прайс, во сколько это обошлось на вашем ключе. Строк до трёх: текущий прогон, ответы бота в тредах и итог по всему MR/PR:

строки в сводке
⚙️ This review: 46.2K tokens in · 3.8K out · ≈ 0.21 $
💬 Thread replies (3 replies): ≥ 12.1K tokens in · 0.9K out · ≥ 0.05 $
🧾 Total for this pull request (2 runs + 3 replies): ≥ 1.2M tokens in · 12.4K out · ≥ 1.32 $
язык и формат чиселПримеры на этой странице показаны для дефолтного language: en. С language: ru бот пишет тот же вывод по-русски, а разделителем дробной части становится запятая: ≈ 0,21 $ вместо ≈ 0.21 $. Это относится ко всему выводу: строкам стоимости, блоку «🔬 Диагностика прогона», предупреждениям о потолке трат и причинам, по которым судья снял кандидата.

Сводка обновляется на месте, поэтому строка прогона всегда про последний прогон — а итог копится по MR/PR и переживает как повторные ревью, так и упавшие прогоны. В скобках указан охват («2 прогона + 3 ответа»): учёт начинается с того прогона, когда бот впервые записал итог в этот MR/PR, — то, что было потрачено раньше, в сумму не попадёт.

.reviewgate/config.yml
cost:
  show: true                 # false by default
  currency: "$"              # the currency label (prices are given in it)
  models:                    # prices per 1 MILLION tokens; the key is the model name (llm block or env)
    claude-sonnet-4-6: { input: 3, output: 15 }
    claude-opus-4-8: { input: 5, output: 25 }
    # prompt cache prices are optional: without them the vendor multipliers are used
    # (writes 1.25x at a 5m TTL and 2x at an hour, reads 0.1x of input)
    # claude-sonnet-4-6: { input: 3, output: 15, cache_read: 0.3 }

Цены задаются за 1 млн токенов в вашей валюте — по тарифам вашего провайдера. Бот работает офлайн и курсы валют не запрашивает, поэтому конвертации нет: что записали, в том и посчитает. Считаются все вызовы прогона — генератор, дополнительные генераторы ансамбля, судья и арбитр, каждый по цене своей модели.

Как заполнить цены — три шага:

  1. Возьмите актуальный прайс своего провайдера.
  2. Приведите цены к «за 1 млн токенов»: Anthropic, OpenAI и DeepSeek публикуют их сразу за 1 млн — берите как есть; Yandex AI Studio — за 1 тысячу токенов, умножьте на 1000.
  3. Если провайдер биллит не в валюте currency — умножьте на курс, по которому вы реально платите (карта, посредник, договор), и запишите результат. Бот сам ничего не конвертирует.
cost.models — примеры для разных провайдеров
# The numbers are an illustration; take the current prices from your provider's price list.
# Keep ALL prices in the config in one currency — the one given in currency.

# Anthropic and the OpenAI-compatible vendors publish prices in $ per 1 MILLION tokens — copy them as is:
claude-sonnet-4-6: { input: 3, output: 15 }

# DeepSeek publishes prices in ¥ (CNY) and in $. If you pay in yuan, set currency: "¥"
# and give the prices in ¥ per 1 MILLION tokens:
deepseek-chat: { input: 2, output: 8 }

# Yandex AI Studio bills in ₽ but publishes the price per 1 THOUSAND tokens: multiply by 1000
# (and set currency: "₽"). The model name is the full URI, quoted because of the «://»:
"gpt://<folder>/qwen3-235b-a22b-fp8/latest": { input: 40, output: 120 }   # 0.04 / 0.12 ₽ per 1K × 1000

# If you pay in a different currency (a card, an intermediary) — convert at YOUR OWN rate.
ключ прайсаИмя модели в cost.models должно совпадать дословно с тем, что задано в ролях (llm.generators / judges / arbiter) либо в env (LLM_MODEL). Для Yandex AI Studio это полный URI вида gpt://<folder>/… — в YAML берите его в кавычки. Не совпало — бот честно напишет «нет цены для: …» вместо суммы.
prompt-кэш в счётеУ Anthropic запись и чтение кэша тарифицируются отдельно и идут сверх обычного входа, поэтому бот учитывает их в токенах и деньгах: запись — 1,25× цены входа при LLM_CACHE_TTL=5m и 2× при часе, чтение — 0,1×. Множители применяются к вашему input автоматически; переопределить их можно полями cache_write_5m, cache_write_1h, cache_read — они необязательны, и конфиги только с input/output продолжают работать. В строке прогона видно, сколько входа пришло из кэша. У OpenAI-совместимых провайдеров кэшированные токены уже входят в prompt_tokens, поэтому второй раз не считаются. После обновления бота итог по уже открытым MR/PR может сочетать прогоны, посчитанные ещё без кэша, с новыми — такие итоги подрастают по мере новых прогонов; задним числом прошлые прогоны не пересчитываются.
честный счётЕсли цена задана не для всех использованных моделей, бот покажет токены, а вместо суммы — пометку, для какой модели не хватает цены: заниженная цифра хуже её отсутствия. Если провайдер не вернул расход части вызовов, суммы помечаются знаком «≥»; не вернул ни по одному вызову — строки не будет. Расход в ответе передают Anthropic, Yandex AI Studio, Ollama и DeepSeek; некоторые строгие OpenAI-совместимые серверы не сообщают его в стриме.

Итог помечается «≥» и тогда, когда часть расхода заведомо не поддаётся учёту: прогон упал уже после ответа модели (токены списаны, объём неизвестен), либо включён режим ответов в тредах — отвечая, бот вправе промолчать, а молчаливый вызов оплачен и следа в MR/PR не оставляет. Знак «≥» значит «не меньше этого», и никогда — «примерно столько».

Диагностика прогона

С diagnostics: true бот дописывает в сводку свёрнутый блок 🔬 — объяснение своих решений прямо там, где живёт разработчик: не нужен доступ к серверу бота, логам или админу. Тот же блок печатает reviewgate review в конце отчёта (в JSON — поле diagnostics, его же видит агент через MCP). Внутри — вызовы моделей с расходом и таймингами, собранный контекст и, главное, решения судьи с причинами, включая дропнутые находки — то, что без этого блока не видно никому:

блок в сводке (свёрнут по умолчанию)
🔬 Run diagnostics

**Model context**: diff: 14 files · full files: 14 (191K chars) · environment: ✓ · from team standards (12 rules): 2 of 5 findings

**Calls**:
- generator `claude-sonnet-4-6` — 41.3K→2.9K tokens · 38 s · findings: 5
- validator `claude-opus-4-8` — 96K→1.1K tokens · 64 s · dropped: 1 · downgraded: 1

**The judge dropped / downgraded / removed a fix (✂️)**:
- ❌ `cart.service.ts:42` `error-handling` — «catchError is already one line above — the finding is false»
- ⬇️ `api.ts:90` `sql-injection` critical→major — «the input is already parameterised, the risk is indirect»

Частые вопросы, которые блок закрывает сам: «почему бот не ругнулся на X» — судья дропнул, причина в блоке; «правило написали, а оно молчит» — виден весь путь прогона; «почему ревью шло две минуты» — тайминги по вызовам. Если config.yml в репозитории не найден или невалиден, блок честно скажет «работаю на дефолтах» — ответ на самый частый вопрос при настройке.

Одного не нужно ждать от diagnostics: true: если сам прогон вышел мельче заказанного — ответ модели упёрся в потолок выхода и был повторён с пониженным усилием, модель вовсе не поддержала режим рассуждений, дополнительный генератор ансамбля упал или не был настроен, — сводка скажет об этом сама, свёрнутой строкой со счётчиком. Знать там стоит две вещи: анализ не тот, что вы просили, и обрезанный ответ оплачен дважды.

кода в блоке нетДиагностика — только метаданные: модели, числа, причины судьи. Дифф и содержимое файлов в блок не попадают. Включается и на всю инсталляцию env-переменной DIAGNOSTICS=true (удобно на время онбординга, пока config.yml ещё не создан); diagnostics: false в конфиге репозитория выключает её точечно.

❓ Вопросы к автору

Судья требует доказанного дефекта — именно это держит точность ревью. Но часть отброшенного — легитимное инженерное сомнение, у которого сегодня нет жертвы: клип на контейнере, прячущий симптом вместо причины, проглоченная ошибка на живом пути. С questions: true такие отказы проходят второй взгляд судьи прогона (первого из настроенной панели) по другой планке — «уместно ли сомнение», а не «доказан ли дефект», — и уместные возвращаются вопросами: сформулированы вопросом и называют, что снимет сомнение.

Вопрос — не замечание: он публикуется отдельным ❓-тредом, не входит в severity gate, порог min_severity и счётчики замечаний, а сводка считает его отдельной строкой. Ответ в треде работает как обычно (reply mode); после resolve автора повторные прогоны вопрос не переспрашивают. На проектах с обязательным resolve всех тредов платформа сама попросит ответить на ❓-тред до мержа — гейт вопрос не трогает, но ответа он ждёт. В отчёте CLI и MCP вопросы приходят массивом questions[] — для агента это прямое задание: проверить сомнение по коду и либо поправить правку, либо ответить. Дубликаты не переспрашиваются, линзе предписано не задавать вопросов по темам из dont_flag, а сбой линзы не трогает замечания — прогон честно говорит об этом, а не молчит. По умолчанию выключено; требует настроенного судейства (llm.judges либо судей env-дефолтов инсталляции) — без судьи нет отказов для переквалификации, и сводка об этом скажет.

Reply mode — ответы в тредах

С reply.enabled: true бот участвует в обсуждении: разработчик пишет реплику в тред замечания («а почему это утечка?») — и бот отвечает в том же треде, опираясь на код и стандарты команды из этого же конфига. Ответ приходит сам, обычно за десятки секунд — не нужен запуск джобы в CI или чья-то команда.

.reviewgate/config.yml
reply:
  enabled: true              # off by default (or the REPLY_ENABLED env default)
  max_replies_per_thread: 3  # past the cap the bot stays silent in that thread
  # model / effort / backend are optional; by default the generator model answers (llm.model)

Когда бот отвечает:

  • на реплики людей в тредах своего ревью — строчных замечаниях и под сводкой;
  • на явное @упоминание учётки бота в любом другом треде MR/PR;
  • в тредах черновика — да (реплика — явный вопрос); в закрытых (resolved) тредах — нет.

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

нужен триггер CommentsСобытие комментария приходит боту только если у вебхука проекта включён триггер Comments (note events) — см. подключение к GitLab. У вебхуков, созданных раньше, включите флаг в Settings → Webhooks → Edit; diagnose.sh --project проверяет это автоматически.
предсказуемая ценаОтвечает та же модель, что делает ревью (первый генератор), — без второго прохода и ансамбля; переопределяется полем reply.modelbackend — на другого вендора). Лимит max_replies_per_thread (по умолчанию 3) страхует от пинг-понга двух ботов и бесконечного спора: после лимита бот в треде молчит. Включается и на всю инсталляцию env-переменной REPLY_ENABLED=true; reply.enabled: false в конфиге репозитория выключает точечно.

Пресеты стека

Пресеты — готовые шаблоны гайдлайна для частых стеков: удобная отправная точка, чтобы не описывать всё в review_prompt с нуля. Они опциональны; с review_prompt: replace и preset: none вы целиком на своём промпте.

Для JS/TS пресеты слоёные: общее языковое ядро (типы, async/промисы, обработка ошибок, утечки, безопасность) + специфика фреймворка поверх. Поэтому react, vue, nestjs и т. д. уже включают TS-ядро — отдельно его указывать не нужно.

presetчто проверяет · опции
angularSignals (computed/effect+untracked, resource()/httpResource(), сигнальные queries, чтение за await), RxJS (switchMap, toSignal), control flow (@if/@for+track, @defer, @let), DI (inject() вне контекста, scope сервисов), маршрутизация (guard/резолверы/порядок маршрутов), формы (ngModel+name, markAllAsTouched), стили (::ng-deep, инкапсуляция), OnPush/zoneless, NgOptimizedImage, строгие формы/шаблоны, запрет any, версионная дисциплина (Angular N+). Опции: signals_naming, prefer, zone_less, a11y (проверки доступности: ARIA-состояние, клавиатура, фокус — по умолчанию выключены; true включает)
reactхуки (Rules of Hooks, deps/stale closure, бесконечный рендер), гонки и очистка эффектов (AbortController), производное состояние и key, мемоизация, dangerouslySetInnerHTML. Опция: style
nextjsApp Router поверх React: границы "use client", утечка серверных секретов в бандл, валидация и авторизация в server actions, кэш/revalidate, next/image
vueреактивность (потеря при деструктуризации, toRefs, ref/reactive), очистка watch и гонки async, computed vs метод, v-for key, v-html
svelteSvelte 5 runes ($state/$derived/$effect, очистка эффектов), потеря реактивности при деструктуризации, stores, ключи в {#each}, {@html}
nestjsDI/scope (request-scoped в singleton), DTO + ValidationPipe, exception filters вместо голого 500, транзакции и N+1, guards на изменяющих эндпоинтах, ClassSerializer
expressasync-роут без catch → unhandled rejection, порядок middleware, валидация входа + helmet/CORS/rate-limit, open redirect, стримы. Опция: framework: express | fastify
typescriptголое языковое ядро JS/TS без фреймворка (типы, async/промисы, ошибки, утечки, безопасность) — для Node/скриптов
djangoORM N+1 (select_related/prefetch_related), запросы без пагинации, transaction.atomic, валидация форм, SECRET_KEY/DEBUG/mark_safe, права на view, миграции с данными
fastapiPydantic-контракты и response_model, блокирующие вызовы в async-эндпоинте, Depends/yield для ресурсов, авторизация в зависимостях, секреты через Settings
flaskконтекст app/request/g, глобальное состояние между запросами, блокирующий обработчик, валидация входа, CSRF, debug/секреты в проде
pythonголое ядро Python без фреймворка (мутабельные дефолты, asyncio-ловушки, except/ресурсы, eval/pickle/SQL-инъекции, GIL)
ginбиндинг+валидация (ShouldBindJSON), c.Abort после ошибки, c.Copy для горутин, recovery-middleware, авторизация
echoc.Bind + валидатор, возврат error через HTTPErrorHandler, контекст не для горутин, middleware авторизации
fiberfasthttp: ctx/Locals невалидны после хендлера (не держать, не в горутины), BodyParser+валидатор, ErrorHandler
goголое ядро Go (goroutine leak/deadlock/гонки, context-отмена, error-wrapping %w/errors.Is, defer/ресурсы, typed nil, crypto/rand)
springSpring Boot/MVC/JPA: @Transactional через прокси (self-invocation, rollbackFor), JPA N+1/LazyInit, гонки в синглтон-бинах, @Valid/DTO, @PreAuthorize, секреты
javaголое ядро Java (equals/hashCode, == vs equals и Integer-кеш, Optional, try-with-resources, потокобезопасность, SecureRandom)
aspnetASP.NET Core + EF Core: captive dependency (Scoped в Singleton), DbContext не потокобезопасен, EF N+1/AsNoTracking, DTO/over-posting, [Authorize], секреты
csharpголое ядро C#/.NET (async void / .Result-deadlock / ConfigureAwait, IDisposable/HttpClient, nullable refs, LINQ multiple-enumeration, throw;)
phpядро PHP 8.3: type juggling (==/=== и 0e-хеши), unserialize/object injection, XSS/command injection/path traversal, random_bytes, strict_types/readonly, SQL-инъекции, N+1, RabbitMQ/Redis. Опция: framework
laravelEloquent N+1, $fillable/mass assignment, FormRequest-валидация, Policy/Gate, API Resource/DTO, Blade {!! !!} XSS, Jobs, config()/env
symfonyautowiring/приватные сервисы, Doctrine N+1 и flush в цикле, Constraints+DTO, Voters/#[IsGranted]/CSRF, Twig |raw, Messenger
yii2ActiveRecord сценарии/safe-атрибуты, RBAC, N+1 (with/joinWith), тонкие контроллеры, параметризация/Html::encode
kotlinголое ядро Kotlin (корутины: runBlocking/GlobalScope/Dispatchers/кооперативная отмена, null safety !!/lateinit, when-exhaustive/scope-функции/val, SecureRandom)
androidlifecycleScope/viewModelScope, утечки Context/View, Jetpack Compose (LaunchedEffect/remember/stable, ключи LazyColumn), main-thread/ANR
ktorструктурные корутины/таймауты, ContentNegotiation-валидация, StatusPages, переиспользование HttpClient, Authentication
rubyголое ядро Ruby (nil &. и 0-truthy, rescue StandardError vs Exception, мутабельность строк/proc-vs-lambda, eval/send/Marshal/YAML.load, command injection)
railsActiveRecord N+1 (includes/preload), SQL-инъекция (where-интерполяция), strong params/mass assignment, Pundit/before_action, html_safe/raw XSS + CSRF, callbacks-побочки, ActiveJob
rustголое ядро Rust (unwrap/expect-паники, ?/thiserror, lock через .await, clone-избыточность, unsafe, as-касты/overflow)
axumlock через await / spawn_blocking, extractors-валидация, IntoResponse для ошибок, без unwrap в хендлере, auth/таймауты (tower)
actixweb::block для блокирующего, web::Data (состояние), extractors-валидация, ResponseError, auth/лимиты payload
swiftголое ядро Swift (force unwrap !/try!, retain cycles [weak self], @MainActor/Sendable, struct vs class, try? проглатывание, Keychain)
iosSwiftUI @StateObject vs @ObservedObject, чистый body/.task, стабильные id в ForEach, main-thread/@MainActor, retain cycles, Keychain
flutteriOS+Android: dispose контроллеров/StreamSubscription, BuildContext через async gap (mounted), setState после dispose, const-виджеты/ListView.builder/Key, состояние (Provider/Riverpod/Bloc)
dartголое ядро Dart (null safety !/late, Future без await / Stream cancel / StreamController close, const/pattern matching, Random.secure)
любой язык, свой промптЯдро языконезависимо. Для стека без пресета опишите ревью в review_prompt или правилами rules: пресет не обязателен.