This guide covers installing merger, running your first scan, and standing up
the full control plane locally. If you only want to classify a diff and preview
its merge lane, you can stop after Install and First scan
— no services, database, or event bus required.
| Use case | Requirements |
|---|---|
| CLI / SDK | Go 1.25+ (only to build from source or go install; prebuilt binaries need nothing) |
| Local control plane | Docker + Docker Compose, Go 1.25.10 |
| GitHub Action | None — the action installs merger for you |
Recommended (Go):
go install github.com/devr-tools/merger/cmd/merger@latest
merger versionOther install paths:
- GitHub Releases — tagged archives for direct download
- Homebrew —
brew install devr-tools/tap/merger - GitHub Marketplace Action —
Devr Merger(see GitHub Action) - Source build —
make buildproduces./bin/merger
merger runs the same analysis pipeline the control plane uses, entirely
offline. Scaffold a config, validate it, then scan a diff:
merger init # scaffold .merger/ config + policy
merger validate # check config and policy resolve
merger scan -base-ref origin/main # analyze the diff vs a base refYou can also feed a diff file (or stdin) directly and choose an output format:
merger scan -diff change.diff -format json
git diff main...HEAD | merger scan -diff - -format textUse merger scan as a CI gate by failing on a lane threshold:
merger scan -base-ref origin/main -fail-on-lane REDThis exits non-zero when the assigned lane is at or above RED.
For an actionable explanation of the result, including policy rationale, risk
contributors, mitigations, affected services, and runtime notes, add
-explain:
merger scan -base-ref origin/main -explainmerger validate uses the same strict validators as the SDK, MCP server, and
long-running services. It rejects unknown YAML fields, invalid lane thresholds
or enum values, duplicate policy names, and rules with no condition or effect.
This catches misspelled safety controls before a scan or service starts.
merger auto-discovers configuration from, in order:
merger.yamlin the repository root.merger/merger.yaml
Point -config at an explicit file or a directory (such as .merger) to
override discovery. Policies default to the config's policy path; override with
-policy <file>.
A minimal, composable policy looks like:
policies:
- name: auth_requires_security_review
when:
mutations:
- auth_behavior_change
require:
reviewers:
- security
evidence:
- auth_integration_tests
github_checks:
- evidence: auth_integration_tests
name: CI / auth integration
app_id: 12345
deployment:
strategy: canary
requires_canary: true
action:
minimum_lane: REDgithub_checks optionally authorizes automatic evidence reconciliation from
GitHub check_run webhooks. A binding must reference an evidence item declared
in the same rule and includes both the exact check name and the numeric GitHub
App ID. Merger rejects a check/App pair bound to different evidence items and
rejects conflicting bindings for the same evidence across policies. Scalar
evidence entries remain manual unless explicitly bound.
Serve the same offline analysis as agent tools over the Model Context Protocol (stdio):
merger mcpPoint an MCP client at the merger mcp command as a stdio server. See
docs/mcp.md for the tool contract.
Add the Devr Merger action to gate pull requests on their assigned lane:
- uses: devr-tools/merger@v1
with:
base-ref: ${{ github.base_ref }}
fail-on-lane: RED| Input | Default | Description |
|---|---|---|
base-ref |
main |
Base ref to diff against (git diff <base-ref>...HEAD) |
config |
auto-discover | Path to a config file or directory |
repo-root |
. |
Repository root for content lookups and relative paths |
format |
text |
Output format (text or json) |
fail-on-lane |
report only | Fail when the assigned lane is at or above this lane |
version |
latest |
merger version to install |
The Action exports lane, risk-score, and change-packet-id outputs for use
by later workflow steps.
Merger does not execute a merge queue. It supplies a required gate that GitHub
Merge Queue reruns against the queue-generated merge_group commit. Use one
workflow and the same job name for both pull_request and merge_group, then
select Merger change control as a required status check in branch
protection/rulesets. The workflow must use the merge-group head_sha and
base_sha, not a pull-request head or moving branch name.
Copy the merge-queue workflow example
into .github/workflows/merger-change-control.yml, adjust the action version
and lane threshold, and enable GitHub Merge Queue for the target branch.
Optionally configure a repository-local graph manifest to resolve transitive
impact beyond changed-file topology. Traversal is bounded (default depth 3)
and supplements, rather than replaces, CODEOWNERS and manifest discovery.
runtime_graph:
enable_codeowners: true
graph_manifest_path: .merger/runtime-graph.yaml
max_traversal_depth: 3The graph manifest maps changed paths to node IDs and declares dependencies:
nodes:
- id: payments-api
name: payments-api
kind: service
criticality: high
paths: ["services/payments-api/**"]
- id: ledger
name: ledger
kind: service
edges:
- from: payments-api
to: ledger
type: callsExternal analyzers are opt-in JSON-over-stdio subprocesses. Each executable must be an absolute path explicitly repeated in its allowlist; Merger never uses a shell or passes configuration arguments. The analyzer receives one analysis input JSON object on stdin and emits a JSON mutation array on stdout.
mutation_analyzers:
- name: organization-rules
executable: /opt/merger/analyzers/org-rules
allowlist: [/opt/merger/analyzers/org-rules]
timeout: 5s
paths: ["services/**"]Every scan also checks the proposed diff for unresolved conflict markers,
duplicate normalized file entries, and policy-sensitive paths (such as
CODEOWNERS, deployment workflows, and schema or migration files). When the
caller supplies both the change's base SHA and the target branch's current SHA,
Merger additionally detects base drift. Findings are recorded in the Change
Packet's conflict field and reflected in the risk score.
Merger never resolves a conflict or rewrites the change. It routes ordinary
drift and policy-sensitive concurrent-edit risk to refresh_and_verify (RED),
and unresolved conflict markers to human_resolution (BLACK). The JSON output
includes the exact findings, affected paths, and mitigations so a merge queue
or agent can take the prescribed next step.
SDK callers can set ScanOptions.BaseSHA and ScanOptions.CurrentBaseSHA when
they have freshly read the target branch; webhook integrations set
current_base_sha packet metadata when that value is available.
The full control plane (webhook ingest, persistence, event bus) needs the local toolchain and the Compose stack for platform dependencies.
Merger requires Go 1.25.10 for local development and CI — this is part of the
security baseline, not an optional preference. Bootstrap it into your shell:
eval "$(./scripts/dev/use-go-1.25.10.sh)"
go versionThe helper installs the go1.25.10 launcher if needed, downloads the toolchain
into $HOME/sdk/go1.25.10, and exports GOROOT, PATH, and GO for the
current shell. After that one-time install, plain make targets prefer
$HOME/sdk/go1.25.10/bin/go when it exists, so make ci does not depend on your
shell defaulting to the right Go version.
make compose-up # Postgres, Redis, NATS via deployments/local/docker-compose.yml
make run-ingest # HTTP ingress for GitHub pull_request webhooks
make run-controlplane # downstream orchestration and subscriptionsDefault ports:
| Service | Port |
|---|---|
| Ingest HTTP | :8080 |
| Control-plane HTTP | :8081 |
| Control-plane gRPC | :9091 |
| PostgreSQL | :5432 |
| Redis | :6379 |
| NATS | :4222 |
Local development defaults to access.mode: disabled. For a deployed control
plane, either configure environment-backed bearer tokens or validate signed
JWTs from your auth gateway or identity provider.
Static token mode:
access:
mode: static_token
tokens:
- subject: ci
token_env: MERGER_CI_TOKEN
roles: [evidence_writer]
- subject: dashboard
token_env: MERGER_DASHBOARD_TOKEN
roles: [reader]Set the referenced environment variables on the control-plane process. Merger stores only token digests in memory. Production configuration validation rejects disabled access.
JWT mode:
access:
mode: jwt
jwt:
algorithm: HS256
issuer: https://auth.example.test
audience: merger-controlplane
secret_env: MERGER_CONTROLPLANE_JWT_SECRET
roles_claim: scope
role_bindings:
- claim_value: merger.read
roles: [reader]
- claim_value: merger.write
roles: [evidence_writer]For asymmetric signing, use algorithm: RS256 and public_key_path instead of
secret_env. Merger validates issuer, audience, expiry, and signature, then
maps claim values to Merger roles. subject_claim defaults to sub, and
roles_claim defaults to roles when omitted.
Every accepted evidence update retains the current execution snapshot and appends an immutable audit record containing the previous and new status, actor, timestamp, summary, details URL, and provenance metadata. Read a packet's history with:
curl -H "Authorization: Bearer $MERGER_DASHBOARD_TOKEN" \
'http://localhost:8081/api/v1/change-packets/cp_example/evidence/audit?limit=50'Audit records are append-only. GitHub check reconciliation records its trusted check-run, app, and commit provenance in the entry metadata.
Record a bounded deployment outcome after delivery; Merger stores only the
outcome kind (success, rollback, or incident), source, time, and the
packet's lane/risk-type snapshot—never logs or incident narratives. The
calibration report aggregates sample count and adverse rate by lane and risk
type, and provides recommendations without changing policy thresholds.
curl -X POST -H 'Content-Type: application/json' \
-d '{"outcome":"success","source":"deploy-controller"}' \
http://localhost:8081/api/v1/change-packets/cp_example/outcomes
curl http://localhost:8081/api/v1/risk-calibrationTear the stack down with make compose-down.
make verify # test-all + build
make ci # full local CI (fmt, vet, tests, coverage, lint, security)- SDK guide — embed the offline pipeline as a Go library
- MCP server — agent tool contract
- Extending merger — plug in your own SCM, topology, event, and persistence adapters
- GitHub webhook flow — detailed end-to-end flow
- Release automation — how releases are cut