Skip to content

Rumdl: Markdown Linter

Tool: rumdl

rumdl is a high-performance Markdown linter and formatter written in Rust. It enforces strict CommonMark and GitHub Flavored Markdown (GFM) standards, and directly checks embedded code blocks.

What It Does

rumdl analyzes Markdown files across the codebase to enforce CommonMark and GitHub Flavored Markdown specifications. It checks heading hierarchies, list indentation, and table formatting.

Rumdl also integrates with Python linters like Ruff to check embedded Python code blocks (```python). This guarantees that examples never contain broken syntax, invalid imports, or outdated conventions.

Why It's in Zenith

Legacy Markdown linters require Node.js or Ruby runtimes. Zenith chooses rumdl to eliminate npm dependencies, keeping development environments strictly hermetic with Rust and Python binaries.

Automated Markdown formatting (rumdl fmt) ensures documentation authored by developers and AI agents adheres to identical formatting standards and maintains clean Git diffs.

Configuration in Zenith

Configured in rumdl.toml at the project root:

# ==============================================================================
# rumdl Configuration (rumdl.toml)
#
# Tool: rumdl (https://github.com/rvben/rumdl)
# Purpose: Blazing-fast markdown linter and formatter written in Rust.
# Why it's in Zenith: Enforces consistent, polished markdown style across READMEs
# and docs, and features integrated code-block tools that format and lint embedded
# Python code blocks with Ruff.
# ==============================================================================

enable = ["ALL"]
exclude = [
    ".git",
    ".github",
    ".agents",
    ".venv",
    ".ruff_cache",
    ".rumdl_cache",
    ".complexipy_cache",
    ".temp",
    "site",
    "dist",
]
extend-enable = ["MD063", "MD080", "MD083", "MD085", "MD090", "MD093"]
flavor = "gfm"
line-length = 88
respect-gitignore = true

[per-file-ignores]
    # GFM treats this Zensical admonition as an indented code block.
    "**/docs/getting-started.md" = ["MD046"]

    # Cookiecutter values change table cell widths after rendering.
    "**/SECURITY.md"       = ["MD060"]
    "**/docs/adr/index.md" = ["MD060"]

    # Keep standard Changelog headings and allow generated compare URLs to vary.
    "**/docs/changelog.md" = ["MD013", "MD082"]

# --- Code Block Tools (Format & Lint Python & JSON in Markdown) ---
[code-block-tools]
    enabled                = true
    on-error               = "warn"
    on-missing-tool-binary = "warn"

    [code-block-tools.language-aliases]
        "bash"     = "shell"
        "py"       = "python"
        "python3"  = "python"
        "{bash}"   = "shell"
        "{json}"   = "json"
        "{python}" = "python"
        "{py}"     = "python"

    [code-block-tools.languages.python]
        format = ["ruff:format"]
        lint   = ["ruff:check"]

    [code-block-tools.languages.json]
        format = ["jq"]

    [code-block-tools.tools."ruff:format"]
        command = ["ruff", "format", "-"]

    [code-block-tools.tools."ruff:check"]
        command = [
            "ruff",
            "check",
            "--preview",
            "--isolated",
            "--output-format=concise",
            "--ignore=D,T201,missing-required-import",
            "-",
        ]

# --- Rigid Markdown Rules ---
[MD001]
    front-matter-title = true

[MD003]
    style = "atx"

[MD004]
    style = "dash"

[MD007]
    indent = 2

[MD009]
    strict = true

[MD010]
    code-blocks = true

[MD012]
    maximum = 1

[MD013]
    code-block-line-length = 88
    code-blocks            = true
    headings               = false
    line-length            = 88
    math-blocks            = false
    reflow                 = true
    tables                 = false

[MD022]
    lines-above = 1
    lines-below = 1

[MD026]
    punctuation = ".,;:!"

[MD031]
    list-items = true

[MD033]
    allowed-elements = [
        "a",
        "audio",
        "br",
        "details",
        "div",
        "i",
        "iframe",
        "img",
        "kbd",
        "p",
        "span",
        "sub",
        "summary",
        "sup",
        "table",
        "tbody",
        "td",
        "th",
        "thead",
        "tr",
        "video",
    ]

[MD035]
    style = "---"

[MD046]
    style = "fenced"

[MD048]
    style = "backtick"

[MD049]
    style = "asterisk"

[MD050]
    style = "asterisk"

[MD058]
    minimum-after  = 1
    minimum-before = 1

[MD059]
    prohibited-texts = ["click here", "here", "link", "read more"]

[MD060]
    aligned-delimiter = false
    column-align      = "left"
    enabled           = true
    loose-last-column = false
    pad-width         = 1
    style             = "aligned"

Key Invocations

uv run rumdl check           # Check all Markdown files
uv run rumdl fmt             # Auto-format Markdown files
uv run poe lint-md           # Wired development task in Poe
uv run poe format-md         # Wired formatting task in Poe

Upstream Documentation