Thanks for your interest in Gate, a self-hosted AI-powered PR review system. This guide covers everything a new contributor needs to build, test, and ship a change.
For deeper technical context and AI-coding-agent-specific guidance, see CLAUDE.md.
- Python 3.11 or 3.12 (CI runs both)
tmux-- required for agent stage executiongh-- GitHub CLI, used for every API callclaude-- Claude Code CLI, authenticated (OAuth orCLAUDE_CODE_OAUTH_TOKEN)codexCLI -- optional, only needed for the auto-fix pipeline
git clone https://github.com/openlawteam/gate.git
cd gate
pip install -e ".[dev]"
make ci # format + lint + testmake install runs pip install -e ".[dev]". make ci chains format + lint + test; run it before every push.
GHA thin trigger -> `gate review` CLI -> Unix socket -> GateServer
-> ReviewQueue (ThreadPoolExecutor)
-> ReviewOrchestrator
-> stage runners (tmux / inline)
-> optional FixPipeline
See CLAUDE.md for data flow, path conventions, shared-state hazards, and the rationale behind tmux as an execution substrate. See README.md for operator-facing documentation (installation, CLI, config).
Gate splits paths into two roles (all exposed as functions, never constants, so tests can monkeypatch them):
gate.config.gate_dir()-- package install root (read-only at runtime:prompts/,config/,workflows/)gate.config.data_dir()-- OS-native runtime root viaplatformdirs(state, logs, socket, caches)
When adding a new runtime path, add a helper function in gate/config.py (or a module that already exposes one), not a module-level FOO = data_dir() / "foo". Module-level evaluation freezes the path at import time and breaks test isolation.
make test # full suite, ~2 min
pytest tests/test_queue.py -q # one file
pytest tests/test_orchestrator.py::TestCancel -v # one classThe autouse isolate_paths fixture in tests/conftest.py redirects both GATE_DIR and GATE_DATA_DIR to a per-test temp directory. Every test gets a clean fake filesystem -- no test ever writes to the real ~/Library/Application Support/gate (macOS) or ~/.local/share/gate (Linux). The complementary clean_gate_env autouse fixture strips GATE_PAT, GATE_NTFY_TOPIC, and similar env vars so tests default to an unconfigured shell.
If a test genuinely needs the real package directory (e.g. to validate a shipped prompt), request the real_gate_dir fixture.
- Write the prompt template as
prompts/<stage>.md(include the "Untrusted Content" preamble). - Register the stage name in
gate/schemas.py:- Add to
ALLOWED_STAGES. - Add to
AGENT_STAGESorSTRUCTURED_STAGES. - If structured, add a JSON schema entry to
STAGE_SCHEMAS.
- Add to
- Wire the stage into
ReviewOrchestrator.run()ingate/orchestrator.py. - Add a test in
tests/test_orchestrator.pythat exercises the new stage's happy path and at least one failure path (fail-open is important).
Profiles in gate/profiles.py drive build verification. To add a new language:
- Add an entry to
PROFILESwithtypecheck_cmd,lint_cmd,test_cmd,dep_install_cmd,dep_file, and the language metadata used by prompts. - Add a marker-file check to
detect_project_type()(e.g.Package.swiftfor Swift). - If the test runner's output format is parseable, add a parser to
gate/builder.pysimilar to_parse_tscor_parse_pytest; otherwise the generic exit-code parser is used. - Add tests in
tests/test_profiles.pyandtests/test_builder.py.
When adding a subprocess.run call:
- Always use list form (or
shlex.split(cmd)for user-supplied strings). Nevershell=True. - Always pass
timeout=. - Always use
capture_output=True, text=Truewhen parsing output. - Never interpolate untrusted input into the command.
Use gate.io.atomic_write / atomic_write_bytes for any full-file rewrite (JSON blobs, counters, trimmed JSONL). Append-only JSONL (reviews.jsonl, live logs) may continue to use open("a").
- Run
make cilocally before pushing. - Add tests for new behavior. Every new module must have a corresponding
test_<module>.py. - Keep prompt changes small and focused. Prompts affect model behavior across every review.
- Preserve the fail-open guarantee in
ReviewOrchestrator.run(): an unhandled exception must approve the PR with an error message, never block it. - Don't update
config/gate.toml,config/cursor-rules.md, orconfig/fix-blocklist.txtin a PR; those are user-local and gitignored. Ship example counterparts (*.example.*) instead.
.github/workflows/test.yml runs ruff check gate/ tests/ and pytest -q on Ubuntu with Python 3.11 and 3.12 on every PR. Both Python versions must pass. The .github/workflows/gate-review.yml workflow is the self-hosted Gate review trigger and is not part of CI.
Please use the .github/ISSUE_TEMPLATE/ templates so we have enough context to reproduce.