.mergejudge.yml reference
The file is read from the base branch tip (falling back to the PR's base commit), never from the PR head. Every key is optional. Values shown are defaults unless marked example.
provider:
type: none # anthropic | openai | zai | openai-compatible | workers-ai | none
base_url: https://api.openai.com/v1 # example
model: gpt-4o-mini # example
api_key_env: OPENAI_API_KEY # name of the env var holding the key
api: chat # openai only: chat (Chat Completions) | responses (Responses API)
endpoint: paygo # zai only: paygo | coding (coding-plan endpoint)
lanes:
auto: ["docs/**", "**/*.md", "**/*.css", "public/**"]
protected: [".github/**", "deploy/**", "**/auth/**", "**/migrations/**", "wrangler.toml", "_headers", "**/.env*"]
max_auto_lines: 200
code_auto: # opt-in: small, clean code changes the AI may mark auto (off = code always needs review)
enabled: false
paths: [] # globs a code file must match; [] = every code path that is not protected
max_lines: 30 # changed lines per file
max_total_lines: 100 # changed lines over all candidate files in the PR
trusted_authors: # opt-in: the auto lane is only for listed GitHub logins (off = no author check)
enabled: false
users: [] # e.g. ["gauravsharmaGG"] (case-insensitive)
applies_to: code_auto # code_auto | all_auto (all_auto also covers the docs/style auto paths)
agents: as_author # as_author (an agent PR counts as its author) | untrusted (agent PRs never get auto)
labels:
block: [do-not-merge]
review: [needs-review]
pr_template: # opt-in: the PR description must really answer the configured sections
enabled: false
sections: [Testing, Risk, "How to revert"] # headings or `**Testing:**`-style labels; the three defaults
# accept aliases ("Tests", "Test plan", "Rollback", ...)
min_chars: 15 # real text under each heading; HTML comments, placeholders and
# checkbox-only lines do not count
apply_to: agents # agents = only PRs with the agent marker (label or body trailer) | all
missing_lane: review # review | block; only ever raises a lane
review:
mode: diff # diff | context | deep (deep: hosted only)
context: # used when review.mode: context
max_bytes: 40000 # total context sent, in priority order (see below)
max_files: 25 # max files read per review (bounds API calls)
max_symbols: 3 # changed symbols searched for callers
max_callers: 3 # callers kept per symbol
max_file_bytes: 12000 # larger files are sent as excerpts around the changed lines / call sites
deep: # used when review.mode: deep
max_steps: 6 # tool rounds (up to 4 calls each), then one tool-less answer
max_tokens: 60000 # input+output over all calls
max_seconds: 120
max_tool_output_bytes: 6000 # per tool result; larger outputs are cut
index:
policy: stored # stored (R2 + Vectorize) | none (temporary, nothing persisted)
budget:
max_tokens_per_pr: 28000 # estimated prompt + reply size (~chars/4); patches are truncated to fit
# max_output_tokens: 2500 # unset = 8000 for reasoning models (glm-5.x, gpt-5*, o-series, ...), 2500 otherwise;
# set = always used (clamped to the PR budget). A cut-off reply is retried once at 2x.
diff_exclude: # kept out of the LLM prompt, still laned by rules
- "**/package-lock.json" # …plus other lockfiles, build output, minified files, images, fonts, generated/**
policy_files: [REVIEW_POLICY.md, APPROVAL_POLICY.md, AGENTS.md, CLAUDE.md] # [] disables
policy_exclude: [] # globs to skip
policy:
max_each_bytes: 8192
max_total_bytes: 8192
rubrics:
enabled: true
packs: [security, correctness] # also: maintainability
custom: [] # up to 20 extra criteria, see Rubric packs
notes: {} # criterion id -> extra guidance for the model (learning proposals add these)
# shorthand: rubric_packs: [security] (rubric_packs: [] turns rubrics off)
approval:
enabled: false # nothing happens unless true
mode: none # none | auto-merge-only | bot-approve
merge_method: squash # squash | merge | rebase (for native auto-merge)
human_approvers: { auto: 0, review: 1, block: 0 }
bot_approve: { auto: true, review: false, block: false }
learning:
enabled: true
propose_after: 10 # lessons before a proposal PR is opened
branch: mergejudge-learning # must not be your default branch
exclude_actors: [] # logins (case-insensitive) whose merges and label changes are never recorded as outcomes
proposal: # when the proposal PR may edit files (below these it is a report only)
edits: true # false = proposals never edit files
min_prs: 3 # "too strict" on a criterion: this many different PRs ...
min_actors: 2 # ... and this many different people
min_path_events: 2 # "too lenient" on a path: this many reverts / fix follow-ups of auto PRs
runner: # only for repos that review on a runner on your machine
engine: null # claude-code | codex | glm (null = the runner's default)
model: null # e.g. gpt-5 (null = the engine's default). The panel setting wins.
code_source: worker # deep mode: worker (Worker reads the repo via the GitHub API) | local (runner reads its own clone)Review modes
diff(default): the model sees the changed hunks with line numbers. Cheapest and fastest.context: the model also gets read-only context, in this order untilcontext.max_bytesis used up:- each changed file in full at the PR head,
- local files those files import (relative JS/TS and Python imports),
- files that use a changed or exported function, class or constant (GitHub code search).
diff_excludematches and binary files are never sent. Everything is inside the untrusted-data guard and labelled as context. Findings still point only at diff lines; context is used to justify them, for example "this new parameter breaks the call insrc/cart.js". Code search indexes the default branch, so callers added in the same PR are already covered as changed files, and very new files may be missed. Expect roughly 2–5× the tokens ofdiffon small PRs; the footer shows a separatecontextcount.deep(hosted):contextplus read-only tools (read_file,search_symbols,find_callers,semantic_search) over a repo index, used in a bounded loop. With a provider that can't call tools, it falls back tocontextand says so. See Review modes.
PR template and trusted authors
Two opt-in, raise-only rules that gate the light paths on who wrote the PR and how well it is described:
pr_templatechecks the PR description for the configured sections (defaultTesting,Risk,How to revert). A heading (## Testing) or a label line (**Testing:**/Testing:) opens a section, case-insensitively, and the three defaults also accept aliases (Tests,Test plan,How was this tested,Risks,Risk level,Rollback,Revert plan,Revert,How to roll back). Only real text counts: HTML comments, placeholders and checkbox-only lines are stripped before themin_charscheck. A missing section raises the PR tomissing_lanewith a label-style reason ("PR description is missing: Testing, How to revert (pr_template)"). Withapply_to: agents(default) only PRs carrying the agent marker are checked; editing the PR body re-runs the check without a new model call (the LLM result is cached per head SHA). The body is untrusted, but the rule can only raise a lane, so a forged body gains nothing.lanes.trusted_authorsmakes every auto path (withapplies_to: code_auto, onlylanes.code_autocandidates; withall_auto, docs/style auto paths too) available only to the listed GitHub logins. The author ispull_request.userfrom GitHub, so it cannot be faked in the body. Bots (a login ending in[bot], or user typeBot) are untrusted unless listed exactly. Withagents: as_author(default) an agent-marked PR is judged by the human who directed the agent; withuntrusted, agent-marked PRs never get the auto lane. An untrusted author sees the reason "author<login>is not on the trusted list (lanes.trusted_authors)". The Worker's server floor re-applies both rules to runner results, so an old or modified runner cannot skip them.
Agents and learning outcomes
MergeJudge learns from what people do with its lanes (merging over a review lane, reverting, labelling). Bots never count. A coding agent that acts through a human's login would look human, so there are three ways to exclude it:
learning.exclude_actors: logins whose merges and label changes are never recorded.- The PR label
mergejudge:agenton the PR when it is merged or closed. Adding or removing this label is never an outcome, and label changes on a PR that has it are ignored too. - A line
MergeJudge-Actor: agentin the PR body or in the merge commit message:gh pr merge 123 --merge --body "MergeJudge-Actor: agent".
If MergeJudge cannot read the merge commit message when it needs it, it records nothing for that merge.
Notes
- Token use. The comment footer shows a token breakdown (diff, policy, rubric). To save tokens: set
policy_files: [], trimrubrics.packs, lowerpolicy.max_total_bytes, or extenddiff_exclude. - Invalid config fails the run with a message naming the bad key, so mistakes are visible.