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.
# 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,.adocby default, plusgate.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.