Enforce Architecture Decision Records
Require every ADR to carry a single status, real context, a decision, and consequences.
A decision record with an empty Consequences section is a decision nobody finished thinking about.
Architecture Decision Records are only useful when they're complete and honest: one clear status, the forces that drove the call, the decision itself, and what it costs. A contract beside the template keeps half-written ADRs out of the log.
The contract
<!-- mdv: document strict -->
<!-- mdv: section required -->
## Status
<!-- mdv: choice required oneChecked -->
- [ ] Proposed
- [ ] Accepted
- [ ] Superseded
<!-- mdv: endchoice -->
<!-- mdv: endsection -->
<!-- mdv: section required minWords=15 noPlaceholder -->
## Context
What forces a decision, and what constraints bound it.
<!-- mdv: endsection -->
<!-- mdv: section required minWords=10 noPlaceholder -->
## Decision
What we are doing.
<!-- mdv: endsection -->
<!-- mdv: section required minWords=10 noPlaceholder -->
## Consequences
What becomes easier or harder as a result.
<!-- mdv: endsection -->document strict locks the section set and their order — exactly Status, Context, Decision, Consequences, nothing else. The choice oneChecked makes Status a single-select: precisely one of Proposed / Accepted / Superseded. Each prose section needs real substance (minWords) and no leftover template text (noPlaceholder).
What it catches
An ADR that's half-filled and out of shape:
## Status
- [ ] Proposed
- [ ] Accepted
- [ ] Superseded
## Context
We need to pick a logging approach soon.
## Decision
We will adopt structured JSON logging.
## Consequences
Dashboards get simpler over time.
## Notes
randomForm — edit the document
adr-0007.md
19 error Unexpected section unexpected-section
The "Notes" section is not one the schema declares here.
fix: With `only`, the schema declares the complete set of allowed sections in this scope. Move the content into an allowed section or have the schema author declare this section.
State — do the work, then record it
adr-0007.md
3 error Pick exactly one need-exactly-one
The "Status" section needs exactly one box checked (found 0).
fix: This list records one selected option. Determine which single option is actually true first, then record only that one as checked `[x]` and leave the rest unchecked.
9 warning Too short too-few-words
The "Context" section is too short (8 words).
fix: Add more detail - aim for at least 15 words.
13 warning Too short too-few-words
The "Decision" section is too short (6 words).
fix: Add more detail - aim for at least 10 words.
17 warning Too short too-few-words
The "Consequences" section is too short (5 words).
fix: Add more detail - aim for at least 10 words.
contract: template.mdv.md
Restructuring on purpose? Update the contract in the same change so document and contract move together. One-off exception: <!-- mdv: disable-next-line <rule> -- reason -->
✖ 5 problems (2 errors, 3 warnings)No status chosen, three thin sections, and a stray Notes heading — five errors, exit 1. Fill it in honestly and it passes.
Run it in CI
Keep the contract as docs/adr/template.mdv.md and validate new records explicitly, or let the zero-config scan pair each NNNN-title.md with a sibling contract:
npx mediva check docs/adr/0007-structured-logging.md --schema docs/adr/template.mdv.mdVariations
- If your team writes ADRs in a fixed order but sometimes adds an optional
## Alternatives Considered, swapdocument strictfordocument noExtraSections(no order constraint) and declare the optional section withsection optional. - Add
frontrules for machine-readable metadata —front date date=isoandfront id pattern=/^ADR-\d{4}$/— when an index generator reads the records. See Frontmatter. - To require the decision to cite a driving issue, add
issueKeyword=Refsto the Decision section — it checks the prose carries aRefs #NNreference and needs no--context. AddissueState=open(and pass--context) only if you also want CI to verify the issue is actually open.