Overview
Decern is a CI gate that verifies every pull request — human or AI-generated — respects your team's architecture decisions. Decisions live as markdown files in your repo (docs/adr/*.md). An LLM compares each PR against the relevant decisions and blocks (or flags) violations. Everything stays yours: if you uninstall Decern, your ADRs remain in the repo as plain files.
Decern exists because the tech lead can't review every PR anymore — especially when half of them are written by AI. Copilot, Cursor, and Claude Code write code fast, but they don't know what your team decided last quarter about database access patterns, or why you deprecated that caching layer, or which dependencies are approved.
Decern turns those decisions into rules the CI enforces automatically, so you can stop being the architecture police.
Three phases
1. Bootstrap
Run `decern init` to analyze your codebase and generate ADR drafts. Review, approve, and commit to /docs/adr/.
2. CI Gate
Add `decern gate` to your CI. Every PR is evaluated against ADRs. Violations on blocking ADRs fail the build.
3. Evolution
Signals surface new patterns. The tech lead generates draft ADRs from the dashboard. The repo stays the source of truth.
Quick Start
Get from zero to enforced ADRs in 5 minutes.
1. Bootstrap ADRs
export DECERN_LLM_BASE_URL=https://api.anthropic.com export DECERN_LLM_API_KEY=sk-ant-... export DECERN_LLM_MODEL=claude-sonnet-4-6 npx decern init
Decern scans your codebase (directory tree, package.json, git history, README) and proposes 15-25 ADR drafts. You review each one interactively: [A]pprove / [S]kip / [Q]uit. Approved ADRs are written to docs/adr/.
2. Commit and push
git add docs/adr/ git commit -m "docs: bootstrap architecture decisions" git push
3. Add the gate to CI
name: Decern Gate
on: [pull_request]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Decern gate
run: npx decern gate
env:
DECERN_LLM_BASE_URL: ${{ secrets.DECERN_LLM_BASE_URL }}
DECERN_LLM_API_KEY: ${{ secrets.DECERN_LLM_API_KEY }}
DECERN_LLM_MODEL: ${{ secrets.DECERN_LLM_MODEL }}
DECERN_BASE_URL: ${{ secrets.DECERN_BASE_URL }}
DECERN_CI_TOKEN: ${{ secrets.DECERN_CI_TOKEN }}
CI_PR_URL: ${{ github.event.pull_request.html_url }}fetch-depth: 0 is required so git can compute the diff between base and head.4. Done
Every PR is now evaluated against your ADRs. Violations on blocking ADRs fail the build. Warnings are logged. Signals are reported to the dashboard.
ADR Format
ADRs are markdown files in docs/adr/ with YAML frontmatter and three sections.
--- id: ADR-007 title: Use PostgreSQL for persistence status: approved enforcement: blocking scope: - src/db/** - migrations/** supersedes: null date: 2026-04-10 --- ## Context The team evaluated PostgreSQL, MySQL, and MongoDB. PostgreSQL was chosen for its JSONB support and transactional integrity. ## Decision All persistent data storage uses PostgreSQL. No other database engines are allowed in production. ## Consequences - All team members must know SQL - MongoDB skills are not leveraged - JSONB provides flexibility without a separate document store
Frontmatter fields
| Field | Values | Description |
|---|---|---|
| id | ADR-NNN | Unique identifier |
| title | Free text | Concise, descriptive title |
| status | proposed | approved | superseded | rejected | Only approved ADRs are enforced by the gate |
| enforcement | blocking | warning | blocking fails CI on violation. warning logs but passes. |
| scope | Glob patterns | Files this ADR applies to. Empty = all files. Supports *, **, ? |
| supersedes | ADR-NNN | null | Which ADR this one replaces |
| date | YYYY-MM-DD | When the decision was made |
Writing ADRs That Work With the Gate
The quality of the gate depends directly on the quality of your ADRs. A vague ADR produces vague verdicts. A precise ADR produces precise verdicts. This section is the difference between a gate that catches real violations and one that produces false positives until your team disables it.
1. Decision must be specific and verifiable
The LLM needs to answer a binary question: does this diff respect this decision, yes or no? If the decision is fuzzy, the LLM guesses.
Bad
"Code should be readable and well-structured."
Good
"All database queries go through the service layer in src/services/. No direct DB access from route handlers."
2. Scope must be specific
Narrow scope = fewer files sent to the LLM = faster evaluation, lower cost, fewer false positives. An ADR with empty scope evaluates against every file in every PR.
Bad
scope: (empty)
Good
scope: [src/api/**, src/routes/**]
3. Context must explain the why
Context is not just for humans — the LLM reads it too. A good Context section helps the LLM understand intent, which reduces false positives on edge cases. "We chose X because of Y" is more useful than "We use X."
4. Consequences must be honest
Include trade-offs. This helps the LLM distinguish between "intentional deviation" (a known trade-off) and "violation" (something the team didn't consider). It also helps future team members understand what they're signing up for.
5. Blocking vs Warning — the most important decision
This is the choice that determines whether Decern helps or annoys your team.
| Use blocking when... | Use warning when... |
|---|---|
| The violation is clear-cut and binary (yes/no, not maybe) | The violation is a judgment call or has legitimate exceptions |
| A violation in production would be costly or hard to reverse | A violation is a style preference or soft convention |
| The team has strong consensus (everyone agrees this is non-negotiable) | The ADR is new and you're still calibrating whether the gate catches it correctly |
| The scope is narrow (less risk of false positives on unrelated code) | The scope is broad or the decision is philosophical |
warning. Observe the gate output for 1-2 weeks. When you're confident it catches real violations without false positives, promote to blocking. This is the safe path.6. Anti-patterns to avoid
- ADRs about process, not code: "Every PR must be reviewed by 2 people" — the gate can't see your GitHub review settings, only the diff.
- ADRs that are always true: "We use TypeScript" for a TypeScript repo — every diff will pass, the ADR adds no value.
- ADRs with overlapping scope: Two ADRs both covering
src/**with conflicting decisions confuse the LLM. - Too many blocking ADRs too early: Start with 2-3 blocking ADRs. Add more as confidence grows.
Bootstrap (decern init)
The bootstrap command analyzes your codebase and proposes ADR drafts for decisions the team has implicitly made but never written down. It looks at: directory structure, package manifests, git history, README, and key entry point files.
All proposed ADRs have status: approved and enforcement: warning. The tech lead promotes specific ADRs to blocking after review.
| Variable | Required | Description |
|---|---|---|
| DECERN_LLM_BASE_URL | Yes | LLM API base URL |
| DECERN_LLM_API_KEY | Yes | LLM API key |
| DECERN_LLM_MODEL | Yes | Model ID (e.g. claude-sonnet-4-6) |
| DECERN_ADR_DIR | No | ADR directory (default: docs/adr) |
decern adr sync to push the ADR index to the dashboard (if cloud is configured). Or just add the gate to CI — it syncs automatically on the next PR.CI Gate Setup
The gate runs on every pull request and evaluates the diff against your approved ADRs. It requires a BYO LLM (you provide the API key) and optionally reports results to the Decern cloud dashboard.
Environment variables
| Variable | Required | Description |
|---|---|---|
| DECERN_LLM_BASE_URL | Yes | LLM API base URL (e.g. https://api.anthropic.com or https://api.openai.com/v1) |
| DECERN_LLM_API_KEY | Yes | LLM API key |
| DECERN_LLM_MODEL | Yes | Model ID. See Recommended Models section. |
| DECERN_ADR_DIR | No | ADR directory (default: docs/adr) |
| DECERN_BASE_URL | No | Decern cloud URL. Enables evidence reporting and PR comments. |
| DECERN_CI_TOKEN | No | Workspace CI token. Required if DECERN_BASE_URL is set. |
| DECERN_CONFIDENCE_THRESHOLD | No | Minimum confidence to block (0-1, default: 0.75). Violations below this are degraded to warnings. |
| DECERN_EVAL_CONCURRENCY | No | Max parallel LLM calls (default: 3) |
| CI_BASE_SHA | No | Base commit SHA. Auto-detected from origin/main if not set. |
| CI_HEAD_SHA | No | Head commit SHA. Auto-detected as HEAD if not set. |
| CI_PR_URL | No | PR URL. Enables PR violation comments and nudges. |
| CI_PR_TITLE | No | PR title. Included in evidence record. |
GitHub Actions
name: Decern Gate
on: [pull_request]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Decern gate
run: npx decern gate
env:
DECERN_LLM_BASE_URL: ${{ secrets.DECERN_LLM_BASE_URL }}
DECERN_LLM_API_KEY: ${{ secrets.DECERN_LLM_API_KEY }}
DECERN_LLM_MODEL: ${{ secrets.DECERN_LLM_MODEL }}
DECERN_BASE_URL: ${{ secrets.DECERN_BASE_URL }}
DECERN_CI_TOKEN: ${{ secrets.DECERN_CI_TOKEN }}
CI_PR_URL: ${{ github.event.pull_request.html_url }}GitLab CI
decern-gate:
image: node:20
stage: test
script:
- npx decern gate
variables:
DECERN_LLM_BASE_URL: $DECERN_LLM_BASE_URL
DECERN_LLM_API_KEY: $DECERN_LLM_API_KEY
DECERN_LLM_MODEL: $DECERN_LLM_MODEL
DECERN_BASE_URL: $DECERN_BASE_URL
DECERN_CI_TOKEN: $DECERN_CI_TOKEN
only:
- merge_requestsBitbucket Pipelines
pipelines:
pull-requests:
'**':
- step:
name: Decern Gate
image: node:20
script:
- npx decern gateJenkins
pipeline {
agent { docker { image 'node:20' } }
stages {
stage('Decern Gate') {
steps {
sh 'npx decern gate'
}
}
}
}Azure DevOps
trigger: none
pr:
branches:
include: ['*']
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npx decern gate
displayName: Decern Gate
env:
DECERN_LLM_BASE_URL: $(DECERN_LLM_BASE_URL)
DECERN_LLM_API_KEY: $(DECERN_LLM_API_KEY)
DECERN_LLM_MODEL: $(DECERN_LLM_MODEL)How Evaluation Works
Step by step
For each PR, the gate:
- Reads approved ADRs from
docs/adr/*.md - Computes the diff (
git diff base...head) - Scope pre-filter: skips ADRs whose glob patterns don't match any changed file (zero LLM cost)
- Scope-filtered diff: for each relevant ADR, sends only the diff hunks for matching files (not the full diff)
- LLM evaluation: concurrent calls (configurable, default 3) asking the LLM: does this diff respect, violate, or is unrelated to this ADR? Plus a confidence score (0-1).
- Confidence threshold: violations with confidence below threshold (default 0.75) are degraded from blocking to warning
- Signal detection: in parallel, a separate LLM call scans for new patterns not covered by any ADR (1-3 signals max)
Verdict types
| Result | Blocks CI? | Description |
|---|---|---|
| pass | No | Diff respects the ADR |
| violation + blocking + high confidence | Yes | Clear violation, CI fails |
| violation + blocking + low confidence | No | Ambiguous, degraded to warning |
| violation + warning | No | Advisory only, logged |
| unrelated | No | ADR not relevant to this diff |
| skipped | No | Scope pre-filter, no LLM call |
| error | No | LLM failure, fail-open with warning |
LLM cost
N = ADRs that pass scope filter. Total LLM calls = N + 1 (N evaluations + 1 signal detection). Skipped ADRs cost nothing. With 15 ADRs and 4 matching scope, you make 5 LLM calls per PR.
Diff truncation
Diffs are capped at 2MB total (100K chars per ADR evaluation, 80K for signal detection). When truncated, the gate logs: "Note: the diff was truncated for analysis. Some changes may not have been evaluated."
Signal Detection
Every PR is scanned for new architectural patterns not covered by any existing ADR. This runs in parallel with ADR evaluation — a PR can pass ADR-007, violate ADR-012, AND generate a signal for a new caching pattern, all at the same time.
The LLM receives the diff, the list of existing ADR titles + decision summaries, and is asked: "does this diff introduce something architecturally significant not covered by the existing ADRs?" It returns 0-3 signals.
What counts as a signal
- New external dependency (library, framework, service)
- New structural pattern (layer, abstraction boundary, module system)
- New technology (first gRPC, first message queue, first IaC)
- New convention not covered by existing ADRs
What does NOT count
- Bug fixes, refactors, renames, style changes
- Patch version updates
- Tests, docs, comments
- Routine feature work following existing patterns
Dashboard flow
Signals appear in the Signals page grouped by repo. The tech lead can:
- Generate draft ADR (Enterprise) — cloud LLM creates a full ADR markdown from the signal(s)
- Create PR (GitHub) — commits the ADR file to the repo via a PR
- Copy markdown — for non-GitHub repos, copy and commit manually
- Dismiss — mark as not a real architectural decision
ADR Lifecycle
ADRs follow a status flow. Only approved ADRs are enforced by the gate.
proposed → approved → superseded
↘ rejectedLifecycle from the dashboard
The ADR detail drawer shows contextual action buttons. Each action generates a markdown preview that you can edit, then either copy or create a PR (GitHub).
| Current state | Actions available |
|---|---|
| proposed + warning | Approve, Reject |
| approved + warning | Promote to blocking, Supersede |
| approved + blocking | Demote to warning, Supersede |
| superseded / rejected | No actions (terminal states) |
Override & Escape Hatches
Sometimes a developer needs to bypass a blocking ADR intentionally — a production hotfix on Friday evening, a one-time migration that temporarily violates a pattern, or an edge case the ADR didn't anticipate. Decern supports this with a documented override workflow.
How override works
- The gate blocks the PR with a violation (exit code 1, CI fails)
- The developer or tech lead calls
POST /api/overridewith the evidence ID and a mandatory reason (minimum 20 characters) - Decern creates a new evidence record of type "override" linked to the original blocked record, containing who overrode, when, and why
- The original evidence record is updated with the override data
- The developer can re-run the gate or merge with a manual approval
Override via API
curl -X POST https://your-decern.app/api/override \
-H "Authorization: Bearer $DECERN_CI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"evidence_id": "01912345-...",
"override_reason": "Production hotfix for payment processing bug. ADR-012 violation is intentional and will be reverted in follow-up PR #234."
}'What gets recorded
The override is part of the evidence chain. An auditor sees:
- The original gate run (blocked, with ADR evaluation and confidence)
- The override record (who, when, reason)
- Both are hash-chained and signed — the override can't be silently inserted after the fact
Reducing the need for overrides
If your team overrides the same ADR frequently, it's a signal that the ADR needs updating — the decision may have changed, the scope may be too broad, or the enforcement should be warning instead of blocking. Check the drift report for patterns.
Multi-Repo
A workspace can host multiple repos. Each repo has its own docs/adr/ tree. ADRs are scoped per-repo — two repos can both have ADR-001 without collision.
Repos connect implicitly: any repo that presents the workspace's CI token is associated with that workspace. No explicit "add repo" UI — the first gate run or ADR sync from a repo makes it appear.
Repository identifier format: github.com/owner/repo, gitlab.com/group/project. Detected automatically from GITHUB_REPOSITORY (in CI) or git remote.origin.url (local).
Cross-repo ADRs
Decern does not support cross-repo ADRs today. Each ADR lives in one repo and is enforced only in that repo's PRs. For organization-wide policies (e.g. a security standard that applies to all repos), the recommended pattern is to replicate the ADR file in each repo's docs/adr/. This is intentionally explicit: each team owns their copy and can adapt it to their context. Cross-repo governance is on the roadmap.
Dashboard
The dashboard is the control plane for your architecture governance. It reads from the same data the gate writes — ADR cache, evidence records, signals — and lets the tech lead manage everything without touching the terminal.
ADRs
View all ADRs across repos. Collapsible accordion per repo with search bar. Each repo header shows counts (total, blocking, proposed) and a Sync button (GitHub repos). Click an ADR to open the detail drawer with full body, lifecycle actions, and raw markdown.
Signals
New architectural decisions detected in PRs. Grouped by repo. Generate draft ADR (Enterprise), create PR, or dismiss. Resolved signals (formalized or dismissed) shown at the bottom.
Gate Runs
Evidence records from CI. Stats (total, passed, warned, blocked) for the current month. Table with verdict badges, PR link, repo, and a detail modal showing commit SHA, CI provider, author, ADRs evaluated, and files changed.
Workspace
CI token management (generate, revoke). Evidence retention policy (days). Member management (invite, roles). Signing key warning if not configured.
Evidence Chain
Every gate run produces an immutable evidence record with:
- All ADRs evaluated, each verdict with confidence score, enforcement level, and reason
- Diff hash (SHA-256) and files touched
- Author identity from CI metadata
- Timestamp with microsecond precision
- Hash chain: each record's
previous_evidence_hashpoints to the preceding record - Ed25519 signature (algorithm, key_id, value)
Export
GET /api/evidence/export?from=...&to=... returns a JSON bundle containing records, access logs, chain tip, manifest, and public keys. The manifest includes signing_key_type: "persistent" | "ephemeral" so auditors can verify integrity.
Verification
npx decern verify-evidence evidence-export.json
Verifies hash chain continuity and Ed25519 signatures offline.
Signing key
Generate a persistent Ed25519 signing key:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Add to your environment:
DECERN_EVIDENCE_SIGNING_KEY=K7x2mFp3Q1nR8vYb...==
Recommended Models
The gate quality depends directly on the LLM. Tested and recommended for production:
claude-sonnet-4-6,claude-opus-4-6(Anthropic)gpt-4o,gpt-4.1,gpt-5(OpenAI)gemini-2.5-pro(Google)
Smaller models (gpt-4o-mini, claude-haiku) work but produce significantly more false negatives — violations that the model misses. A runtime warning is logged in CI when using a non-recommended model.
Troubleshooting & FAQ
The gate is slow (30+ seconds)
Each ADR that passes scope filter = one LLM call. With 10 matching ADRs at 3 concurrent (default), that's 4 rounds of calls. Solutions: (1) Tighten scope patterns so fewer ADRs match per PR. (2) Increase DECERN_EVAL_CONCURRENCY to 5 (watch for LLM rate limits). (3) Use a faster model.
I see "error" verdicts in gate runs
An error verdict means the LLM call failed (timeout, rate limit, malformed response). The gate treats errors as fail-open: the PR passes, but the error is logged. If you see many errors: check your LLM API key, rate limits, and model availability. The CI log shows the exact error message for each failed evaluation.
Too many false positives (blocking on things that aren't violations)
Ranked by impact:
- Check the ADR: is the Decision section specific and verifiable? Vague ADRs produce vague verdicts.
- Tighten the scope: a broad scope means the ADR evaluates diffs it shouldn't care about.
- Lower the confidence threshold: set
DECERN_CONFIDENCE_THRESHOLD=0.85to only block high-confidence violations. - Upgrade the model: smaller models produce more false positives. Check the Recommended Models section.
- Demote to warning: if the ADR is consistently problematic, switch from
blockingtowarningwhile you calibrate.
The LLM provider is down — does the gate block all PRs?
No. LLM failures are fail-open: the verdict is error (not violation), the PR passes, and the gate logs a warning. Your CI is never blocked by an LLM outage.
How do I handle very large diffs (1000+ lines)?
The gate sends a scope-filtered diff per ADR (only hunks matching the ADR's scope, not the full diff). For most PRs this keeps the payload small. If the total diff exceeds 2MB, it's truncated with a logged warning. For very large PRs, consider splitting into smaller PRs — this is good practice regardless of Decern.
Can I run the gate locally before pushing?
Yes. From your repo root:
export DECERN_LLM_BASE_URL=https://api.anthropic.com export DECERN_LLM_API_KEY=sk-ant-... export DECERN_LLM_MODEL=claude-sonnet-4-6 npx decern gate
It uses origin/main...HEAD as the diff range. Useful for testing before opening a PR.
What happens if I have no ADRs?
The gate passes immediately: "No approved ADRs found. Gate passes (nothing to enforce)." Signal detection also doesn't run. Run decern init to bootstrap.
Can two repos in the same workspace have the same ADR ID?
Yes. ADRs are scoped per-repo. ADR-001 in github.com/acme/api and ADR-001 in github.com/acme/web are independent records in the dashboard.
How do I remove an ADR?
Delete the file from docs/adr/ and commit. On the next sync (or gate run with cloud reporting), the ADR disappears from the dashboard. Alternatively, set status: rejected to keep the record but stop enforcement.
Does Decern store my code?
No. The gate runs in your CI and sends the LLM call directly to your BYO provider. The cloud receives only: verdict, confidence, ADR IDs evaluated, diff hash (not the diff itself), file paths, and author metadata. Your code never passes through Decern servers.
Self-Hosted
Self-hosted is for teams in regulated industries (finance, healthcare, defense, public sector) or with strict data residency requirements. If you can use the cloud, use the cloud — it's simpler and gets updates automatically. Choose self-hosted when your security policy requires that no data leaves your network, or when you need air-gapped deployment.
The gate CLI runs in your CI as always. The dashboard and cloud API run on your servers.
Requirements
- Node.js 20+
- PostgreSQL 15+ (Supabase or standalone)
- BYO LLM endpoint accessible from CI
Key environment variables
| Variable | Required | Description |
|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Yes | Supabase project URL |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Supabase anon key |
| SUPABASE_SERVICE_ROLE_KEY | Yes | Supabase service role key |
| DECERN_EVIDENCE_SIGNING_KEY | Yes | Ed25519 seed (32 bytes, base64) for persistent evidence signatures |
| CLOUD_LLM_API_KEY | No | Anthropic key for draft ADR generation (Enterprise feature) |
| CLOUD_LLM_MODEL | No | Model for draft generation (default: claude-sonnet-4-6) |
| CRON_SECRET | No | Bearer token for cron endpoints (evidence archival) |
Plans
The Free plan is free forever — no trial, no credit card, no expiration. It includes the full CI gate with blocking, signal detection, evidence chain, and export. The only limits are 1 workspace and 3 developers. Enterprise adds unlimited scale, draft ADR generation, PR creation from dashboard, self-hosted deployment, SSO, and dedicated support.
| Feature | Free | Enterprise |
|---|---|---|
| Workspaces | 1 | Unlimited |
| Developers | 3 | Unlimited |
| ADRs + gate runs | Unlimited | Unlimited |
| CI blocking | Yes | Yes |
| Signal detection | Yes | Yes |
| Evidence chain + export | Yes | Yes |
| Draft ADR from signals | No | Cloud LLM |
| Create PR from dashboard | No | GitHub |
| Self-hosted | No | VPC / air-gapped |
| SSO | No | SAML, OIDC |
| Support | Community | Dedicated with SLA |