Topic: adr template github
ADR Template for GitHub Repos
A drop-in ADR setup for a GitHub repository: the folder layout, a PR template that asks "did this PR warrant an ADR?", and a GitHub Actions workflow that lints every new ADR. Copy-paste, no extra dependencies.
TL;DR
Three files, one folder, one PR. Add doc/decisions/0000-template.md, a .github/PULL_REQUEST_TEMPLATE.md with an ADR checkbox, and .github/workflows/adr-lint.yml that fails the build when an ADR is missing required sections. Bootstrap in 60 seconds:
mkdir -p doc/decisions .github/workflows
curl -o doc/decisions/0000-template.md https://whychose.com/assets/adr-template.md
The folder layout
Put ADRs at the repo root, not inside a service directory. They describe choices that affect the whole codebase; nesting them under one service hides them from people working in another.
repo-root/
├── doc/
│ └── decisions/
│ ├── 0000-template.md # the empty template
│ ├── 0001-use-postgres-over-mongodb.md
│ ├── 0002-monorepo-vs-polyrepo.md
│ └── README.md # one-paragraph index + link policy
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── adr-lint.yml
└── ... (your code)
Filename format: NNNN-short-slug.md, zero-padded so you don't rename files at #100. The README.md inside doc/decisions/ is a one-screen index that says "what these are, when to add one, who reviews them" — that's the difference between a folder people use and a folder people forget.
The PR template
The PR template is the forcing function. It nudges every PR author to ask the ADR question even when the answer is "no":
## What this PR does
<1-2 sentences>
## ADR check
- [ ] This PR does not introduce a durable architectural choice.
- [ ] This PR introduces a durable architectural choice and includes a new ADR under `doc/decisions/`.
- [ ] This PR supersedes a prior decision; the relevant ADR is updated to `Status: Superseded by NNNN`.
## Notes for the reviewer
<anything reviewers need to know>
One of the three boxes must be ticked. That's it — no policy doc, no team meeting. The template makes the right behaviour the path of least resistance.
The GitHub Actions lint
The lint job fires only on PRs that touch doc/decisions/, so it adds zero CI minutes to normal PRs. When it does fire, it checks that every changed ADR has the five required headings and a non-empty body under each.
# .github/workflows/adr-lint.yml
name: adr-lint
on:
pull_request:
paths:
- 'doc/decisions/**.md'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Lint changed ADRs
run: |
set -euo pipefail
changed=$(git diff --name-only --diff-filter=AM \
origin/${{ github.base_ref }}...HEAD \
-- 'doc/decisions/*.md' \
| grep -v '0000-template.md' || true)
if [ -z "$changed" ]; then
echo "No ADRs changed. Skipping."
exit 0
fi
fail=0
for f in $changed; do
echo "::group::Linting $f"
for section in '^# ' '^\*\*Status:\*\*' '^## Context' '^## Decision' '^## Consequences'; do
if ! grep -qE "$section" "$f"; then
echo "::error file=$f::Missing required section matching: $section"
fail=1
fi
done
# Empty section bodies = lint failure too
if grep -qE '^## (Context|Decision|Consequences)$' "$f" \
&& grep -BzoP '## (Context|Decision|Consequences)\n\s*\n\s*##' "$f"; then
echo "::error file=$f::Empty section body."
fail=1
fi
echo "::endgroup::"
done
exit $fail
This is intentionally simple. It catches the failure modes that actually show up in practice — a contributor copies the template, fills in the title, and forgets to write anything under Consequences. It does not try to enforce style, word count, or "quality." Those are reviewer judgement calls; CI is for things that are obviously wrong.
Cross-linking ADRs to the code
Two conventions, both cheap, both worth doing:
- The PR description links the ADR. "Implements ADR-0017." That makes
git blame+ GitHub's blame view a one-click path from any line of code to the decision behind it. - The ADR links the implementing PR. Under the
## Linkssection:- PR: #341. Future readers can read the ADR and see "what code did this actually become?" without spelunking.
You can also drop a one-line comment in code at non-obvious spots: // Why this shape: see doc/decisions/0017-event-replay.md. Keep it rare — most code shouldn't need a back-pointer. Use it for the surprising stuff.
How WhyChose helps
WhyChose reads your doc/decisions/ directory and your AI chat exports together. When you're about to write a new ADR, it surfaces the relevant prior conversations — the ChatGPT thread where you actually weighed Postgres vs MongoDB, the Claude session where you settled the auth question — and drafts the ADR for you in this exact format. You commit it like any other ADR; the lint above passes; the PR template checkbox is satisfied. The extractor is open-source if you want to run it locally against your repo.
Related questions
Should the ADR lint block merge, or just warn?
Block merge. A non-blocking lint trains the team to ignore it; within two months the warnings are background noise and the malformed ADRs land. The five-section check is so small that fixing a violation takes less than a minute, so the cost of blocking is low and the long-term payoff is high.
What about adr-tools by npryce — should I use that instead?
Use it if you like CLI workflow ergonomics. The adr new "..." command is genuinely useful for numbering. The folder layout and template above are compatible with adr-tools — it expects the same doc/adr/ (or doc/decisions/) shape. You can adopt the workflow here today and add adr-tools later without migrating.
Do I need a separate ADR repo for cross-team decisions?
Usually no. The decision still lives in the repo whose code it changes. For decisions that span multiple repos, put the ADR in the repo with the most upstream impact and link to it from the others' READMEs. A separate "central ADR repo" tends to drift out of sync within a quarter and becomes a graveyard.
Further reading
- ADR template in Markdown — copy-paste ready — the underlying template the lint expects.
- ADR example: Postgres vs MongoDB — a fully-filled-in record using this exact format.
- How to document architecture decisions (without your team revolting) — the practice, not the template.
- The full ADR GitHub Action (CI pipeline) — the four-job upgrade path beyond the basic lint stub on this page; adds number-collision detection, bidirectional supersession integrity, and an auto-generated index.
- ADR numbering scheme — padding, gaps, and collision recovery — the operational details that pair with the folder layout above: 4-digit padding choice, why gaps are correct, and the merge-base allocator that handles two PRs racing for the same number.
- ADR storage format comparison — the GitHub-repo-Markdown layer is the default this page assumes; the storage comparison covers when to migrate (cross-functional readers, Atlassian-native teams, M365-mandated enterprises) and the 4–16 hour migration path to each alternative.
- GitHub Discussions as an RFC surface — proposal-to-ADR workflow — the pre-ADR consultation layer that fits in front of the ADR PR merge step: how to configure a Proposals Discussion category, the lifecycle from idea to closed Decision comment, and how Discussion thread sections map to ADR sections. The Discussion produces the PR that adds the ADR file to this repository layout.