stalemaid
v0.1.0
Published
CI gate that fails a PR when code a mermaid diagram models changes but the diagram does not. Your architecture diagrams can't silently lie.
Maintainers
Readme
stalemaid
Your architecture diagrams can't silently lie.
stalemaid is a CI gate that fails a pull request when code a mermaid diagram claims to model changes, but the diagram doesn't change with it. It reads provenance frontmatter on your diagram files and enforces that code and its diagram co-change in the same PR.
$ stalemaid check
stalemaid: 5 checks run, 1 failure(s)
FAIL [drift] docs/diagrams/billing.md: code file src/billing.ts changed but
referencing diagram docs/diagrams/billing.md (source_of_truth: code) was
not changed in the same PR/commit — update the diagram and bump last_verifiedWhy this exists
Diagrams-as-code rots the moment the code moves and the diagram doesn't. Existing tools
either fingerprint staleness after merge (Drift),
auto-fix instead of failing (Swimm), or only watch markdown specs —
none gate mermaid diagrams on a deterministic git co-change check in the PR itself.
That seam is what stalemaid fills. See
docs/DECISION-diagram-cochange-gate.md for the
full reasoning and the alternatives considered.
How it works
Each diagram is a markdown file with mandatory frontmatter:
---
title: "Billing flow"
models: "src/billing.ts, src/lib/charge.ts" # the code this diagram describes
source_of_truth: code # diagram | code | spec
last_verified: a1b2c3d 2026-06-15 # short SHA + date (or `bootstrap <date>`)
diagram_type: flowchart
---stalemaid check runs five checks over every *.md under --root (default
docs/diagrams):
| Check | Fails when |
|---|---|
| frontmatter | any of title, models, source_of_truth, last_verified is missing |
| freshness | last_verified SHA is outside the recent-commit window (bootstrap allowed only for diagram/spec) |
| mermaid | a ```mermaid block doesn't parse |
| forward-ref | a models: path on a source_of_truth: code diagram doesn't exist on disk |
| drift ⭐ | a models: code file changed in the PR but the diagram didn't |
The source_of_truth lifecycle: diagram (the contract, before code exists) → code
(derived view once built; now gated for drift) → spec (external authority; drift means
your code is wrong). Only code diagrams are drift-gated.
Install & use
# one-off, no install
npx stalemaid check --root docs/diagrams
# or install
npm i -D stalemaidstalemaid check [--root <dir>] [--strict] [--json] [--skip-mermaid]STALEMAID_BASE— git ref for PR-mode drift (e.g.origin/main); uses<base>...HEAD. Unset → the staged set (git diff --cached), ideal for a pre-commit hook.STALEMAID_MMDC— absolute path to anmmdcbinary (overrides the resolver chain).- Exit codes:
0pass,1findings,2usage error.
The mermaid-parse check shells out to mermaid-cli
(mmdc), resolved from $STALEMAID_MMDC → local node_modules/.bin → PATH →
npx @mermaid-js/mermaid-cli. It needs a headless browser; pass --skip-mermaid where one
isn't available.
GitHub Action
# .github/workflows/diagrams.yml
on: pull_request
jobs:
stalemaid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # drift + freshness need git history
- uses: your-org/stalemaid@v0
with:
root: docs/diagrams
skip-mermaid: "true" # mermaid parsing needs a browser; opt in if yours has onefetch-depth: 0 is required — the drift and freshness checks read commit history. In PR
runs the action defaults STALEMAID_BASE to origin/$GITHUB_BASE_REF.
Pre-commit hook
# .git/hooks/pre-commit
npx stalemaid check --root docs/diagrams || exit 1With no STALEMAID_BASE, stalemaid uses the staged set — so it catches drift before the
commit is even made.
Development
npm install
npm run build
npm test # vitest
bash tools/render-diagrams.sh # render docs/diagrams to gitignored PNGs, then eyeball themstalemaid governs its own diagrams (docs/diagrams/engine/) — drawn before the code and
gated by the tool itself.
License
Licensed under either of Apache License 2.0 or MIT license at your option.
