madr-lint
v0.4.0
Published
Linter for MADR (Markdown Architectural Decision Records) — supports v2 bold-list and v3/v4 frontmatter. Validates filename pattern, required sections, status enum, ISO 8601 dates, numbering uniqueness/gaps, supersedes bidirectionality, and inter-ADR link
Maintainers
Readme
Documentation · 日本語ドキュメント · Getting started · Rules
For AI agents: llms.txt indexes the docs; llms-full.txt has the full text in one fetch; the AI agents guide covers the ready-made adopt-madr-lint / new-adr skills.
A fast, configurable linter for MADR (Markdown Architectural Decision Records). It validates the things a plain Markdown linter can't: required sections, a valid status, ISO-8601 dates, filename convention, and cross-file integrity like unique numbering and non-broken links — across MADR v2, v3, and v4.
Why madr-lint
MADR ships no official linter, and the general-purpose tools each cover only part of what an ADR collection needs:
| Tool | Markdown style | Inter-doc links | ADR numbering | Status enum | Date format | Supersedes graph | v2 bold-list | |---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | markdownlint-cli2 | ✅ | — | — | — | — | — | n/a | | lychee | — | ✅ | — | — | — | — | n/a | | adrs (rust) | — | — | ✅ (init) | — | — | ~ | — | | madr-lint | —¹ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
¹ Deliberately not Markdown style — pair it with markdownlint-cli2 if you want both. madr-lint owns the ADR-specific semantics.
- MADR v2 / v3 / v4 aware — reads YAML frontmatter and v2 body-list metadata (both
- **Status**:and canonical* Status:), or auto-detects per file. - ESLint-style rules — named
madr/*rules witherror/warn/offand per-rule options validated by a JSON Schema. - Per-file & cross-file — fast single-pass checks plus project rules for unique numbering, the supersedes graph, and link rot.
- CLI, library & CI —
text/json/sarif/githubreporters, a programmatic API, and a drop-in GitHub Actions step. - Gradual adoption — inline suppression comments for one-off exceptions and a baseline file to snapshot legacy debt so only new violations fail the build.
Install
npm install --save-dev madr-lint # or: pnpm add -D madr-lint / yarn add -D madr-lintNode.js 22+. ESM-only. Ships TypeScript types.
Quick start
# Lint the configured adrDir (default: docs/adr)
npx madr-lint
# Explicit paths (files or directories; directories are searched recursively)
npx madr-lint docs/adr libs/x/adr
# Machine-readable output for CI
npx madr-lint --format sarif > madr-lint.sarifExit code: 0 clean · 1 on error-level diagnostics or an exceeded --max-warnings threshold · 2 on a config problem.
Configure
A madr-lint.config.ts (or .madrlintrc.json) at your project root:
import { defineConfig } from 'madr-lint';
export default defineConfig({
extends: ['madr-lint:recommended'],
madrVersion: 'auto',
adrDir: 'docs/adr',
ignorePatterns: ['**/template.md', '9999-*'],
rules: {
'madr/filename-format': ['error', { pattern: '^[0-9]{4}-.+\\.md$' }],
'madr/no-numbering-gap': 'off',
},
});Rule values are a severity ('error' | 'warn' | 'off') or a [severity, options] tuple. Options are validated — an invalid option fails fast with exit code 2. Full reference: Configuration.
Rules
8 rules — 7 enabled by recommended, 1 opt-in. Each page documents its options, examples, and MADR-version compatibility.
| Rule | Type | Default | Checks |
|---|---|:---:|---|
| madr/required-sections | per-file | error | Required heading sections are present |
| madr/status-enum | per-file | error | status is one of the allowed values |
| madr/date-iso8601 | per-file | error | date is a valid ISO-8601 calendar date |
| madr/filename-format | per-file | error | Filename matches the ADR convention |
| madr/no-broken-links | project | error | Relative links resolve to existing files |
| madr/no-duplicate-numbering | project | error | ADR numbers are unique |
| madr/supersedes-bidirectional | project | error | supersedes / superseded-by agree |
| madr/no-numbering-gap | project | off | ADR numbers are contiguous (opt-in) |
Use in CI
madr-lint ships a composite GitHub Action that annotates the PR diff directly:
# .github/workflows/adr-lint.yml
jobs:
madr-lint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 } # madr-lint requires Node ≥22
- uses: knktkc/madr-lint@v0
with: { path: docs/adr }The floating
v0tag tracks the latest v0.x release; for stricter reproducibility, pin an exact tag like@v0.3.0.
Or run it via npx in any CI provider:
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx madr-lintThe sarif reporter integrates with GitHub code scanning — see GitHub Action.
Programmatic API
import { runRulesOnFile, buildProjectFile, rules } from 'madr-lint';
const diagnostics = runRulesOnFile(
[rules.requiredSections, rules.statusEnum],
{ path: '0001-x.md', content },
{ severity: 'error' },
);Full surface: Programmatic API.
Pairs well with
madr-lint intentionally skips Markdown style — compose it:
markdownlint-cli2 'docs/adr/**/*.md' # Markdown style
lychee 'docs/adr/**/*.md' # external link rot
madr-lint docs/adr # ADR structure + inter-ADR linksStatus
Alpha. Self-dogfooded (this repo lints its own ADRs) and validated against an external repository. Until 1.0, treat each minor bump as potentially breaking; the 1.0 gate is adoption feedback.
Contributing
Issues and PRs welcome. See CONTRIBUTING.md for the dev setup, rule-shape guide, and TDD/changeset workflow. The project dogfoods its own linter against its own ADRs in docs/adr/.
License
MIT © t.kaneko
