# ReviewGate — installing through an AI agent

> These are step-by-step instructions for an AI assistant (Claude Code, Cursor, a local model — any
> of them) helping to bring ReviewGate up inside a customer's own infrastructure. They are
> self-contained: follow the steps in order, and each one ends with a check. No guesswork needed.
>
> Playbook version: 6 · 2026-08-07 · canonical copy: https://reviewgate.dev/ai-setup.md

ReviewGate is an AI code review bot for self-hosted GitLab. It receives a webhook about a merge
request, reads the diff, runs the review through a model (cloud or local) and leaves inline comments
and a summary on the request. It runs inside the customer's own infrastructure: the code and the diff
never go out and are never stored.

The bot output follows the `language` key of `.reviewgate/config.yml` and is **English by default**.
The deployment logs an operator reads are in English too, and they do not follow `language`: that key
governs what the team sees in a merge request, not the operator log.

**This playbook is about GitLab.** The bot also supports a second hosting vendor — GitHub (reviewing
pull requests on github.com with the same engine and the same `.reviewgate/config.yml`). There is no
step-by-step playbook for GitHub yet. If the customer hosts on GitHub, do this:

- **step 1** — instead of a GitLab token, ask the human for GitHub App credentials (App ID plus the
  private key) or a personal access token, following https://reviewgate.dev/docs/github;
- **step 2** — do it as written, but in `.env` write `GITHUB_WEBHOOK_SECRET` and the pair
  `GITHUB_APP_ID` / `GITHUB_APP_PRIVATE_KEY_B64` (the key base64-encoded on ONE line), or
  `GITHUB_TOKEN`, instead of the `GITLAB_*` block;
- **step 3** (the bot is alive) — do it as written;
- **step 4** — skip it: the webhook is created in the GitHub App settings, not through the GitLab
  API. The URL is `<bot address>/api/webhooks/github`, Content type `application/json`, and the
  secret is the one from `.env`;
- **step 5** (`config.yml`), **step 7** (a test review) and **step 8** — do them as written;
- **step 6** — skip it: `diagnose.sh` can only check the GitLab chain. Look at the webhook deliveries
  instead (Advanced → Recent Deliveries) and at
  https://reviewgate.dev/docs/troubleshooting#github-events.

---

## Rules for you, the agent (follow them strictly)

1. **Do not invent secrets.** Only the human knows the GitLab token and the model key — ask for
   them, never substitute values yourself and never take them from other projects.
2. **Do not send the customer's code anywhere.** Never upload the diff, the sources or the contents
   of `.env`. Every command runs inside the customer's own infrastructure.
3. **Show what you are doing.** Before writing `.env` and before creating the webhook, show the
   human exactly what you will write or send, and wait for confirmation.
4. **Idempotence.** Every step is safe to repeat. Before creating a webhook, check whether one
   already exists (step 4).
5. **Verify every step.** Do not move on until the check of the current step has passed.

---

## What you need from the human

Ask for this at the start (step 1). The human enters the secrets themselves — you only place them
into `.env`.

| what | example | who provides it |
|---|---|---|
| The URL of your GitLab | `https://gitlab.acme.com` | the human |
| The project ID or path | `42` or `team/web` | the human |
| The bot token (scope `api`; **Maintainer** — see below) | `glpat-…` | the human (a secret) |
| Which model provider | Anthropic / any OpenAI-compatible endpoint / Yandex AI Studio / Ollama | the human decides |
| The model key | `sk-ant-…`, or the API key of a service account | the human (a secret) |
| A folder id, if the provider needs one (Yandex) | `b1g…` | the human |
| A proxy to the model, if the network needs one | `http://user:pass@host:8888` | the human |
| The review concurrency `REVIEW_CONCURRENCY` | `3` — the default, right for most · `1` — a local model (Ollama) · `5+` — a team of 10 or more on a cloud provider | the human decides |
| The bot address as GitLab sees it | `http://10.0.0.5:3000` | the human, or you work it out |

Generate the webhook secret (`GITLAB_WEBHOOK_SECRET`) **yourself** and use the same value in `.env`
and when creating the webhook (step 4) — that way both sides are guaranteed to match.

**About the token role.** For its work the bot needs **Developer** (reading the diff, writing
comments, setting a commit status). But **creating the webhook through the API (step 4) requires
Maintainer** — with Developer GitLab returns `403 Forbidden` in the middle of the installation. The
options: give the token Maintainer for the duration of the install and lower it to Developer
afterwards, keep Maintainer, or create the webhook by hand in the project UI (Settings → Webhooks) —
then Developer is enough from the start.

---

## Step 1 — collect the inputs

Ask the human the questions from the table above and write the answers down. If the human does not
know the project ID, it can be fetched later through the API (step 4 shows how).

**The review concurrency (`REVIEW_CONCURRENCY`, 1..64) — always ask, never leave the default
silently.** By default the bot runs up to three reviews at once. That is safe for a cloud provider
but already too much for a local model: the queue simply moves from Redis to the GPU. Ask how many
people actively open requests and what inference they run, then propose a value:

- a local model (Ollama) — `1`, and **it must be set explicitly**: the default of 3 would send three
  parallel requests into inference that serves them one at a time (more only if the human knows
  their vLLM or hardware can take it);
- a cloud provider, a team of up to about 10 — the default `3` is enough;
- a cloud provider, a team of 10 or more — `5` and above if the provider limits allow (token-heavy
  reviews usually hit the tokens-per-minute limit; the provider cheat sheet:
  https://reviewgate.dev/docs/llm#concurrency).

Write the agreed value down — it goes into `.env` in step 2.

---

## Step 2 — bring the containers up

Download the ready-made client `docker-compose.yml` (the bot plus Redis; Postgres is optional):

```
curl -O https://reviewgate.dev/docker-compose.yml
```

Create an `.env` file next to it. Generate the webhook secret and put in the human's secrets:

```
# --- GitLab ---
GITLAB_BASE_URL=<the GitLab URL, without /api/v4 and without a trailing slash>
GITLAB_TOKEN=<the bot token, scope api>
GITLAB_WEBHOOK_SECRET=<generate one: openssl rand -hex 16>

# --- Model: the "Anthropic cloud" option ---
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=<the human's key>
ANTHROPIC_MODEL=claude-opus-4-8
# LLM_PROXY=<http://user:pass@host:8888>   # only if the network cannot reach the provider

# --- Model: the "Yandex AI Studio" option (instead of the block above) ---
# LLM_PROVIDER=yandex
# LLM_API_KEY=<the API key of a YC service account>
# LLM_FOLDER_ID=<the YC folder, b1g...>
# the default model is Qwen3-235B and the endpoint is filled in automatically

# --- Model: the "local model" option ---
# LLM_PROVIDER=ollama
# LLM_BASE_URL=http://<host>:11434/v1
# LLM_MODEL=qwen3-coder:30b-a3b-q8_0

# --- Review concurrency (agreed in step 1 — do not leave the default silently) ---
# How many reviews the bot runs at once (1..64). The default is 3; a local model is usually 1,
# and a team of 10+ on a cloud provider 5 or more. The provider limits cheat sheet:
# https://reviewgate.dev/docs/llm#concurrency
REVIEW_CONCURRENCY=<the value from step 1>

# --- License (optional: only for previously issued keys) ---
# REVIEWGATE_LICENSE=<a previously issued key>
# ReviewGate is free and needs no key: the engine is NOT cut down — not by the number of
# developers and not by features. In summaries a run without a key is signed with the
# Community status; a previously issued key shows its plan instead.

# --- Diagnostics (RECOMMENDED during the installation) ---
# Every summary gets a folded 🔬 block: whether config.yml was picked up, the model calls with
# their timings, the judge decisions with reasons. The customer's code is not in the block.
# It can be switched off once things settle (or overridden per repository with
# diagnostics in config.yml).
DIAGNOSTICS=true
```

`REDIS_HOST` does not need to be set — it is already in the compose file (the `redis` service). The
full variable reference: https://reviewgate.dev/docs/install

Bring it up:

```
docker compose up -d
```

---

## Step 3 — check that the bot is alive

```
curl http://localhost:3000/api/health
```

The expected answer is `{"status":"ok","ts":<number>,"version":"<image version>"}` (the bot fills in
the values; there is no need to compare them with this example). If there is no answer, look at the
logs: `docker compose logs app`. Common causes and their analysis:
https://reviewgate.dev/docs/troubleshooting

The `version` field is the version of the running image; report it to the user and attach it to any
support request (the tag in compose will not tell you — with `latest` it never changes). `dev` means
a build from source. If the field is missing entirely, the image is an older build; upgrading is
covered by the canonical doc: https://reviewgate.dev/docs/install#obnovlenie (a `pull` alone is not
enough, the container has to be recreated). More about the version:
https://reviewgate.dev/docs/install#version

---

## Step 4 — create the webhook in GitLab (programmatically)

This removes the step people most often forget to do by hand. **Maintainer is required**: with
Developer GitLab answers `403` to the hook creation — then ask the human to create the webhook by
hand (Settings → Webhooks) with the same URL, secret and events, and move on to step 5. First, if
you need it, look up the project ID by its path:

```
curl -s --get "<GITLAB_BASE_URL>/api/v4/projects/<URL-encoded path, e.g. team%2Fweb>" \
  -H "PRIVATE-TOKEN: <GITLAB_TOKEN>" | grep -o '"id":[0-9]*' | head -1
```

Check whether a webhook pointing at the bot already exists (idempotence):

```
curl -s "<GITLAB_BASE_URL>/api/v4/projects/<PROJECT_ID>/hooks" \
  -H "PRIVATE-TOKEN: <GITLAB_TOKEN>"
```

If there is no hook whose `url` ends with `/api/webhooks/gitlab`, create one. `<BOT_URL>` is the bot
address as GitLab sees it (`http://10.0.0.5:3000`, for example). `note_events` is needed for reply
mode (the bot answering replies in the threads of its review); enable it straight away — until
`reply` is switched on in the config the bot simply ignores comment events:

```
curl -X POST "<GITLAB_BASE_URL>/api/v4/projects/<PROJECT_ID>/hooks" \
  -H "PRIVATE-TOKEN: <GITLAB_TOKEN>" \
  --data-urlencode "url=<BOT_URL>/api/webhooks/gitlab" \
  --data-urlencode "token=<the same GITLAB_WEBHOOK_SECRET as in .env>" \
  -d "merge_requests_events=true" \
  -d "note_events=true" \
  -d "enable_ssl_verification=true"
```

If the webhook ALREADY exists, check the flags `"merge_requests_events":true` and
`"note_events":true` in its JSON. Add a missing flag with a PUT (the hook id comes from the list
above; `url` is MANDATORY in a PUT per the GitLab API — pass the hook's current url, otherwise you
get a 400):

```
curl -X PUT "<GITLAB_BASE_URL>/api/v4/projects/<PROJECT_ID>/hooks/<HOOK_ID>" \
  -H "PRIVATE-TOKEN: <GITLAB_TOKEN>" \
  --data-urlencode "url=<BOT_URL>/api/webhooks/gitlab" \
  -d "merge_requests_events=true" \
  -d "note_events=true"
```

> **Important for self-hosted (a common cause of «the bot is silent»).** If the bot sits on an
> internal network, GitLab forbids webhooks there by default. Ask an administrator to enable it:
> Admin → Settings → Network → Outbound requests → «Allow requests to the local network from
> webhooks».

---

## Step 5 — set up the review rules (`.reviewgate/config.yml`)

The bot works without a config (on sensible defaults). But its value lies in the team's rules.
Create a `.reviewgate/config.yml` file at the root of the customer's repository. If the
`reviewgate` binary is installed, `reviewgate init` creates the skeleton for you (a preset
detected from the manifests, safe defaults, commented examples; existing files are never
touched; note it also creates a personal config skeleton in the home directory of whoever
runs it, if one is missing — harmless for the bot installation) — then fill `rules[]` as
described below.

**The heuristic — where the rules come from (do not invent them):**
1. Find the linter configs in the repository: `.eslintrc*`, `eslint.config.*`, `.stylelintrc*`,
   `phpcs.xml`, `pyproject.toml`/`ruff`, `analysis_options.yaml` and so on.
2. Find the team canon: `ADR/`, `CONTRIBUTING.md`, `CODESTYLE.md`, `docs/conventions*`,
   `docs/decisions*`, the wiki.
3. Put into `rules[]` **only what the linter does NOT catch automatically** and what is an agreement
   of the team (architectural boundaries, mandatory error handling, forbidden patterns). There is no
   need to duplicate what ESLint or Stylelint already checks — that is noise.
4. Cite the source inside the rule text (`(ADR-017)`, for instance) and choose the severity
   deliberately (`blocker` — an unconditional block: secrets, data destruction; `critical` — must
   not be violated; `major` — arguable; `minor` — a hint; `info` — an observation).
5. Write request hygiene rules (no description, no linked task) with request variables — the
   `{mr:field}` placeholders (author, title, description, source_branch, target_branch, url,
   project): the bot substitutes the actual value and mentions the author with an @. The bot sees
   the request metadata in every review even without placeholders.

**The key decisions (do not guess — choose deliberately):**
- **A preset or your own prompt.** The stack is among the presets (the full list is in the schema
  below) → take the preset (and sharpen it with `review_prompt: extend` if needed). The stack is not
  there OR the team has a strong guideline of its own → `preset: none` plus
  `review_prompt: replace` (you write the whole guideline; the bot adds the output format itself).
- **`integration_branches` — always set it, and carefully.** The bot reviews requests whose source
  branch is NOT in the list and whose target IS. List the real integration branches (`dev`,
  `master`, `release/**`). **The trap:** if `feature/*` is an ordinary working branch for a single
  task (merged into `dev`), do NOT add it, or a `feature/* → dev` request will be taken for an
  internal one and silently skipped. Work the flow out from `git log` (what is merged into what)
  rather than guessing.
- **The `llm` block — ALWAYS add it (do not skip it).** Vendor keys and addresses come from the
  deployment environment (the `LLM_BACKEND_*` catalog and the flat `LLM_*` variables) — do NOT
  duplicate them in `config.yml`. But set the role layout explicitly: `generators` (the reviewing
  models; the first is the main one) and, for precision, `judges` (the judge). Without the block the
  review quietly runs on the deployment's default model in a single pass — with no generate→verify to
  cut off false positives. This is a common integration mistake: «it works, but not the way it was
  configured». IMPORTANT (2.0): the 1.x keys `model`/`validate_model`/`extra_generators`/`provider`
  are REMOVED — the bot does not apply them and writes a migration notice with a ready replacement
  line into the summary; the migration table: https://reviewgate.dev/docs/config#migration.
- **`judges` (generate→verify).** If you need high precision and have the budget, set
  `judges: [{model: …}]` (a strong model judges the findings against whole files on a second pass and
  kills the false ones). For a stream of cheap reviews, one generator and no judges.
- **Search depth / recall.** To let the bot see not only the diff but whole changed files and their
  neighbours — `full_file_context: true` (it catches problems outside the hunk without inventing
  them). To have it judge against the stack versions from the manifests —
  `environment_context: true` (it stops confusing which APIs exist in the project's version of Node,
  TS or Angular). Both are opt-in and cost more tokens — for a high quality bar, preferably together
  with a judge.
- **Maximum quality (an ensemble).** For «perfect code by the team's rules» — several INDEPENDENT
  roles in `generators` (each model searches on its own, and different models see different things,
  so recall goes up); the judge weighs the merged findings, collapses duplicates and filters out the
  false ones, which holds precision. A second judge in `judges` (the cap is 2) turns judging into a
  panel: agreement decides, a split is resolved by the `arbiter` (the chairman; never called without
  judges). Every role is another paid pass — for the highest bar only. A role can be moved to
  ANOTHER vendor with `backend: name` from the deployment catalog (`LLM_BACKEND_<NAME>_*` in the bot
  environment; the keys stay with the operator; `backend: default` is the main provider) — see
  https://reviewgate.dev/docs/llm#backends.
- **Legacy code.** On a mature codebase strict rules (typing, DI) will produce noise on old code.
  Ask the team: highlight the technical debt (report on old code too) or suppress it (only propose
  in new code) — and put the decision into `review_prompt`.
- **Reply mode (`reply.enabled: true`).** The bot answers developers' replies in the threads of its
  own review (a question under a finding → an answer within a minute) and explicit @mentions in
  other threads of the request. It requires `note_events` on the webhook (step 4). The bot does not
  answer «fixed» or «ok» (it has the right to stay silent), and it gives at most
  `max_replies_per_thread` answers per thread. Ask the team whether to enable it.

**The schema is CLOSED — do not invent keys.** Write only the fields listed below (they are also
the JSON Schema). Assistants tend to add plausible-looking keys — `globs`, `prompt`, `when`,
`exclude` inside a rule — none of which exist: an unknown key is NOT applied, the review silently
runs without it (the bot does report it with a line in the summary, but that is after the fact).
A rule has exactly three fields: `id`, `description`, `severity`. Everything conditional — file
paths, «only new classes», exceptions — goes INSIDE `description` (or into `review_prompt`) in
plain language: the model reads it and applies it. Wrong (invented keys) vs right (same intent):

```yaml
# WRONG — globs and prompt do not exist; the rule runs WITHOUT them:
  - id: new-services-must-have-tests
    description: "A new service comes with unit tests"
    globs: ["common/services/*.php"]
    prompt: "If a new *Service class is added, require Codeception tests"

# RIGHT — the whole condition is prose inside description:
  - id: new-services-must-have-tests
    description: "A new service class in common/services (*Service.php) must come with Codeception unit tests"
    severity: critical
```

**Validate before committing.** The config schema is published as JSON Schema at
`https://reviewgate.dev/config.schema.json` — check the file you have written against it before the
commit (any JSON Schema validator works; editors with yaml-language-server pick it up from a
first-line comment `# yaml-language-server: $schema=https://reviewgate.dev/config.schema.json`).
The schema mirrors the parser: unknown keys anywhere (including inside `rules[]` and roles), the
closed rule schema (`id`, `description`, `severity` — nothing else), role shapes, caps.

**The file name** is `.reviewgate/config.yml` — and only it. A `.yaml` spelling is NOT read: the
bot reports such a file in the summary and asks to rename it. When no config exists at all, the
summary honestly says the review ran on default settings.

**The file schema** (the parser is defensive: unknown and invalid values are replaced by defaults —
the bot reports noticeable degradations with a line in the summary and a WARN in its log. Most fields
are optional, but set the `llm` block — `generators` and `judges` — EXPLICITLY, otherwise the review
runs on the default model without a judge; state `version: 1` explicitly):

```
version: 1                 # the schema version, currently 1
language: en               # the language of findings and of the summary frame (en · ru · any other)
preset: react              # JS/TS: angular|react|vue|svelte|nextjs|nestjs|express|typescript · Python: django|fastapi|flask|python · Go: gin|echo|fiber|go · Java: spring|java · C#: 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
severity_gate: off         # off (blocks no merges) | blocker | critical | major | minor | info
tests: required            # required | optional (optional does not flag missing tests)

rules:                     # the team rules in plain English
  - id: error-handling
    description: "Every HTTP call handles its errors (ADR-003)"
    severity: major
  - 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} {mr:target_branch} {mr:url} {mr:project}
    severity: minor

dont_flag:                 # optional: team assumptions — they silence classes of false positives
  - "Internal links without rel=noreferrer — our convention"

ignore:                    # glob masks of files not to review — for a CLASS of files
  - "**/*.spec.ts"        # do NOT hide under ignore anything a team rule talks about:
  - "**/*.generated.*"    # the rule "a migration must be in the request" and ignore migrations/** are incompatible

integration_branches:      # optional: review only working→integration requests (otherwise all of them)
  - dev
  - master

incremental_review: true   # a re-push reviews only the changed files (saves tokens)
review_drafts: false       # do not review drafts; the run happens when 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)
                                # the old values critical/warning/comment are accepted as synonyms

reply:                     # optional: reply mode — the bot answers replies in the threads of its review
  enabled: true            # the webhook needs note_events (step 4); the model is the review one (or set model)
  max_replies_per_thread: 3  # cap on the bot's answers per thread (an anti-loop guard)

review_prompt:             # optional: your review guideline
  mode: extend             # extend — on top of the default · replace — entirely instead of it
  text: |
    Pay particular attention to HTTP resilience and to leaked RxJS subscriptions.

llm:                       # ALWAYS SET THIS. Keys and addresses live in the environment
                           # (LLM_BACKEND_*/LLM_*); this is the role layout. Without the block:
                           # the default model and no judge.
  generators:                       # the reviewing models; the FIRST is the main one
    - model: claude-sonnet-4-6
      effort: high                  # reasoning depth of the role (optional)
    - model: claude-opus-4-8        # an ensemble (max quality): another pair of eyes, recall goes up
    - backend: deepseek             # multi-vendor: a role on a named backend from the bot env (optional)
  judges:                           # the judge: a second generate→verify pass (fewer false positives); cap 2 (a panel)
    - model: claude-opus-4-8
  arbiter:                          # the chairman: resolves splits of a two-judge panel (optional)
    model: claude-fable-5
  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
```

### Excluding a SINGLE file: a marker inside the file, not `ignore`

`ignore` is for a class of files. For a one-off file do NOT add a glob — put a marker in the
file itself, on its own line, behind a comment of that language:

```
// reviewgate-ignore-file: generated test cases, a review adds no signal
```

Rules that matter when you write one:

- the reason after the marker is MANDATORY — without it the marker is not applied, the file is
  reviewed as usual, and the summary says so;
- the marker works anywhere in the file, not only in the header;
- it is not looked for in documentation files (`.md`, `.rst`, `.txt` and similar);
- a file excluded this way does not reach the model at all — not its diff, not its contents, not
  even as a neighbouring import of another file's finding.

Prefer this over widening `ignore` with a glob: a list of one-off paths never shrinks, breaks
silently when a file is moved, and a broad glob added for one file swallows new files for years.

The fields, the presets and your own prompt in detail: https://reviewgate.dev/docs/config

Commit the file through a request, like any code — the bot reads it on every review.

---

## Step 6 — the self-check

The run checks the whole chain (the environment, the bot and its version, the token, webhook
acceptance, Redis, the model key and proxy, the webhook in the project):

```
curl -O https://reviewgate.dev/diagnose.sh
bash diagnose.sh --project <PROJECT_ID>
```

The script reads `.env` without executing it and sends only harmless requests (the model key is
checked with `GET /v1/models`, which spends no tokens). Fix whatever it marks with `✗` and run it
again.

---

## Step 7 — a test review

Open a test merge request (or add a commit to an existing one) and make sure the bot left comments
and a summary — this usually takes 1–5 minutes (with a judge there are two model passes, so closer
to the upper bound). If no review appears within about 10 minutes, walk the analysis:
https://reviewgate.dev/docs/troubleshooting

Expand the «🔬 Run diagnostics» block in the summary (enabled by the DIAGNOSTICS variable from step
2) and check with it that `.reviewgate/config.yml` was picked up (there is no «running on defaults»
note), that the model calls went through and that the judge did its work. Show the block to the
human as proof that the installation succeeded.

---

## Step 8 (optional) — install the review for yourself, in the development loop

Up to this point you have been installing the review on merge requests. But if you write code in
this repository, the same thing is available to you locally — by the same team rules and with the
same judge, before a request is even opened.

```bash
# 1. Download the binary for your platform. Platforms: darwin-arm64, darwin-x64,
#    linux-x64, linux-arm64, linux-x64-musl (Alpine), win-x64.exe
curl -fsSLO https://reviewgate.dev/dl/reviewgate-latest-linux-x64.xz
curl -fsSLO https://reviewgate.dev/dl/SHA256SUMS

# 2. Verify the checksum — you are running an executable from the network
sha256sum -c SHA256SUMS --ignore-missing

# 3. Unpack it and put it in PATH
xz -d reviewgate-latest-linux-x64.xz
chmod +x reviewgate-latest-linux-x64
sudo mv reviewgate-latest-linux-x64 /usr/local/bin/reviewgate

# 4. Check the environment (git must be in PATH; the model key comes from the process environment)
reviewgate doctor

# 5. Missing configs (the repo policy skeleton and/or the home config) can be created with
#    `reviewgate init` — it never touches existing files and asks nothing outside a terminal
```

From here there are three ways to use it — pick the one that fits your loop:

```bash
# One-off: check the uncommitted changes
reviewgate review

# In a script or in CI: the exit codes are deterministic
#   0 — clean · 2 — findings at or above the threshold · 1 — a failure (the error object goes to stdout)
reviewgate review --json --fail-on major
```

A hook before `git push` (Claude Code, `~/.claude/settings.json`) — it refuses to send changes that
carry blocking findings:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "reviewgate review --hook-stdin" }]
      }
    ]
  }
}
```

The MCP server offers two tools: `review_changes` (check the changes) and `get_team_rules` (learn
the team standards). Call the second one **before** writing code: writing to the rules straight away
is cheaper than learning them from a review afterwards.

```json
{ "mcpServers": { "reviewgate": { "command": "reviewgate", "args": ["mcp"] } } }
```

**Connecting the server is not enough — the agent must know the tools exist.** Many clients (Claude
Code among them) load MCP tool descriptions lazily: at the start of a session the agent sees the
server name but not its tools, and simply never thinks to call them. The cure is two lines in the
project instruction file (`CLAUDE.md`, `.cursorrules`, or whatever your agent uses):

```markdown
The standards of this project come from the `mcp__reviewgate__get_team_rules` tool —
call it BEFORE writing code, instead of reading the ADRs by hand.

Before finishing a task and before `git push`, check the changes with the
`mcp__reviewgate__review_changes` tool — it is the same judge and the same rules
that check the pull request.
```

In our measurement on a project with eight ADRs the difference was noticeable: without those lines
an agent asked to «work out the team standards» read the documents and the code by hand — about
three minutes of work; with them it called the tool at the sixteenth second and got the same answer
for half the cost.

**The short route for an agent: `reviewgate help agent-setup`.** The command prints instructions
written for an AI assistant: how the server is started, which lines to put into the instruction
file, at what level to configure it and how to verify the result. We do not guess the configuration
format — the agent knows it. The human only has to paste one line into the chat:

```
run reviewgate help agent-setup and do what it says
```

And a second one to confirm it worked:

```
call the get_team_rules tool and show me what it returned
```

**The second barrier is permission to call.** MCP tools are third-party code, so on the first call
the client asks a human whether they may run. In an ordinary session that is a dialog. In a
non-interactive run (`claude -p "…"`, a CI run, a script) there is nowhere to show the dialog — and
the agent, not getting access, **answers with its own analysis of the code instead of a review**:
the same tone, sometimes even a correct finding, but it is not your rules and not a judge's verdict.
There is one tell: **no tool call means there was no review**. The cure is either to list the tools
at startup or to grant the permission once in an ordinary session (the client remembers it for the
project):

```bash
claude -p "check the uncommitted changes" \
  --allowedTools mcp__reviewgate__review_changes mcp__reviewgate__get_team_rules
```

The flag shown is from Claude Code; in other agents look for «tool permissions» or «allowed tools».

What matters about these modes:

- the provider key comes **from the process environment or from the developer's home config**
  (`~/.config/reviewgate/config.yml`), never from the working copy. That is a security boundary: a
  clone of somebody else's repository cannot steer the diff to somebody else's endpoint. The
  environment beats the file;
- `--json` writes only the report to stdout; everything else goes to stderr;
- a full run with a judge takes 2–3 minutes and adds 35–47% to the cost compared with `--fast`.
  That is why the gate hangs on `git push` rather than on every step you take;
- none of this needs to be installed inside the customer's infrastructure — the binary runs on the
  developer's machine.

The details: https://reviewgate.dev/docs/agents

---

## Reference links

- Installation and environment: https://reviewgate.dev/docs/install
- Connecting GitLab: https://reviewgate.dev/docs/gitlab
- Review configuration: https://reviewgate.dev/docs/config
- Models and your own key (cloud, local, proxies): https://reviewgate.dev/docs/llm
- Logs and monitoring (the job tag, LOG_FORMAT=json, Graylog/ELK): https://reviewgate.dev/docs/logging
- Troubleshooting and diagnose.sh: https://reviewgate.dev/docs/troubleshooting
- Review in the agent's loop (CLI, hook, MCP): https://reviewgate.dev/docs/agents
- This playbook (for humans): https://reviewgate.dev/docs/ai-setup
