Markdown linting

Updated Oct 17, 2026

make markdownlint lints every Markdown file it discovers under this checkout and reads this checkout's .markdownlint-cli2.jsonc. The walk prunes these directory names at any depth by default: .git, .hypothesis, .mypy_cache, .pytest_cache, .ruff_cache, .terraform, .tox, .uv-cache, .uv-tools, .venv, __pycache__, _build, build, dist, htmlcov, node_modules, site, and target. Passing --exclude (or setting GATE_RUNNER_EXCLUDE) replaces that default list rather than extending it; --empty-exclude clears it entirely. A directory the walk cannot read fails the gate instead of being skipped. Linting nothing is a failure: the gate stops with a diagnostic when discovery finds no Markdown file or when the linter is not installed. It is one of the gates make ci runs, in order: check-fmt, markdownlint, lint, typecheck, test, then spelling.

make nixie validates Mermaid diagrams with nixie over the same discovered files. It is deliberately not part of make ci, because it renders through an external Mermaid CLI (merman-cli, or mmdc with Chromium) that the CI runner does not provide. Run it locally when a change touches a diagram.

CI delegates the Markdown gate to the pinned DavidAnson/markdownlint-cli2-action and therefore runs make ci CI_SKIP_MARKDOWNLINT=1; that variable filters the Markdown gate out of the gate list, which keeps the workflow and the Makefile in step rather than restating the list.

get-markdown-tooling installs the markdownlint wrapper into consumer repositories. A bare invocation appends the **/*.md glob, so it cannot report a clean pass without having read a file. Explicit paths are honoured as given, and two argument forms are deliberately not treated as paths, so no glob is appended: a standalone -, which makes markdownlint-cli2 read its file list from standard input, and the operand of --config or --configPointer, which names a configuration file. The wrapper uses the repository's .markdownlint-cli2.jsonc when present, and otherwise supplies a bundled default, so it works unchanged in a consumer repository that has none. It resolves the linter from PATH, falling back to the bun global install ($HOME/.bun/bin/markdownlint-cli2); MDLINT_BIN overrides both.

Its rationale and rule set are in the developers' guide.