# Review and merge The reviewer binds a GitLab merge request, source branch, and full commit SHA before it contacts a model. It runs from a disposable detached worktree at that SHA, so an uncommitted change in the caller's checkout cannot reach model tools or the permanent GitLab record. ```sh # Review the immutable merge-request base and head, then publish to that MR. gate review # Emit a stable, pipeable result. Progress remains on stderr. gate review --json | jq . # Inspect native GitLab thread state for the exact current head. gate findings gate findings --ack abcdef123456 --reason 'tracked in APPS-97' # Reply with the verified repair or rejection in GitLab, then resolve. gate findings --resolve abcdef123456 # Disposition a finding after the branch landed and was deleted: name the MR. gate findings --mr 44 --resolve abcdef123456 # Review, publish, inspect blocking discussion state, then land the reviewed SHA. gate merge agent/APPS-97 gate merge --explain ``` `gate merge` is blocking. It requires a complete GitLab read-back receipt and refuses a branch that moves after review. It selects a landing adapter once: `worktree` when the fleet lifecycle is available, otherwise corpus-free `plain` (`git` + `glab`). Use `--land-adapter` to select one explicitly; `--explain` reports the selected mechanism. Project configuration uses `gate.toml` with `[land]` and `adapter = "plain"` or `adapter = "worktree"`; explicit flags override `$GATE_LAND_ADAPTER`, which overrides that project setting. `review` and `findings` return **0** when their command result is published, cached or available, and **2** when it is unavailable. Only `gate merge` returns **1**, when a completed review blocks landing on current-head findings; an unavailable review or unverified landing returns **2**. A zero review exit does not resolve discussions or establish investigation completeness: inspect the JSON status, findings and durable receipt. By default, an eligible review failure can be published as `unreviewed` and still return zero; that record discloses a failed investigation. `GATE_FAIL_CLOSED=1` prevents that failure-publication path from admitting an unreviewed result. A fresh `gate review` retries a previously disclosed failure instead of treating it as a completed investigation. Within the Corpus fleet, use `agent-worktree merge` to land the reviewed branch. It calls Gate review and verifies the receipt; it does not invoke `gate merge`. The standalone `gate merge` examples above describe Gate's own landing command. ## What Gate reviews Every changed path classifies into exactly one of five classes (`gate/review/scope.py`), and only one of them is ever critiqued: - **Code** -- reviewable. Configuration stays code: `.py`, `.typ`, `.yaml`, `.toml`, `.json`, and anything else not claimed by the four classes below. - **Prose** -- `.md`, `.mdx`, `.rst`, `.txt`, `.adoc` by default, plus `gate.toml`'s `[review] prose = [...]` glob list. - **Content** -- archival or data text opted in per project through `[review] content = [...]`; empty by default. - **Generated** -- lockfiles, minified bundles, and snapshots (`*.lock`, `package-lock.json`, `*.min.js`, `*.snap`, `*.svg`, and similar). - **Binary** -- decided by Git's own classification, never by extension. Prose, content, generated, and binary paths are identified, never served: `read_file`, `git_show`, and `git_diff` return an identity stub instead of bytes, `grep` counts a match inside one in a notice rather than showing the line, an unnarrowed range diff excludes them by pathspec before Git ever assembles the patch text, `bundle`'s rung-1 fallback lists them by name, and the coverage contract owes no line inside them. Generated paths keep one deliberate, older exception: a `git_show` or `git_diff` narrowed to a generated path by name still returns its patch, because a complete lockfile change under `bundle`'s 4,000-byte threshold is evidence of what a dependency bump did, not churn, and rung 1 inlines exactly those through the same narrowed request. Prose and content earn no such exception, narrowed or not. That a path changed is signal; its contents are not evidence. When every changed path falls outside the code class, `gate review` never contacts a model at all: it returns `looks-solid` at rung 0 with a reason naming the count in each class, and zero gateway requests. The secret scan over the immutable patches still runs first and still blocks -- a prose-only range with a committed secret is not exempted by this policy. Every review's receipt -- the durable record, `--json`, and terminal output -- names every out-of-scope path and its class in `unreviewed_paths`, the way `binary_paths` already does. After a failed immutable preflight the same contract holds for whatever inventory the preflight classified before dying: nothing was served, so a prose or content path named there reads as *unreviewed*, never as reviewed-by-omission. A failure that struck before any path was classified carries no inventory, and that receipt's empty `unreviewed_paths` means *unknown*, not *none* -- treat the whole range as unexamined. `[review].prose` and `[review].content` in `gate.toml` are additive glob lists layered on top of the defaults above, matched against the full repo-relative path (so `content = ["archive/**"]` selects a whole directory, and a bare `*.rfc` still matches at any depth); a non-list value fails the review closed, naming the config path in the message, rather than silently reviewing (or silently skipping) a path a maintainer wrote wrong. Gate reads `gate.toml` from the merge request's immutable base revision, never from the head under review: a change cannot widen its own `[review]` globs and use that same change to exempt itself from review. Model input retains the full textual diff so committed-secret findings remain detectable. Git-classified binary paths have no text lines to scan: Gate records them in the review's `binary_paths` receipt (including durable state and `--json`) and names them in terminal output, while binary omission alone does not block an otherwise clean verdict. Classification follows Git rather than the extension or presumed human readability, so UTF-16 and other NUL-bearing files Git reports as binary follow this policy too. The path inventory remains bounded. An over-cap, timed-out, failed, NUL-bearing, or undecodable patch Git reports as nonbinary still fails closed before any provider call. Before model prose reaches GitLab, publication redacts credential-shaped material and blocks secret-bearing file anchors. The local cache is not a disposition ledger: acknowledgement and resolution live in GitLab discussion threads and are read back before a merge. `--ack` requires `--reason` and posts it as an acknowledgement without closing the thread. `--resolve` only changes native resolution state; a supplied `--reason` is ignored on that path. Post the repair or rejection as a discussion reply before resolving it. An emergency `--no-review` request is deliberately unavailable until a durable Plane-ready outbox is installed. No bypass can silently land a branch.