Skip to content

CLI Commands

Complete reference for rumdl command-line interface.

Commands

check [PATHS...]

Lint Markdown files and report issues.

rumdl check .                    # Lint current directory
rumdl check README.md docs/      # Lint specific files/directories
rumdl check --fix .              # Lint and auto-fix issues

Options:

Option Description
--fix Auto-fix issues (exits 1 if unfixable issues remain)
--config <PATH> Path to configuration file
--disable <RULES> Disable specific rules (e.g., MD013,MD033), or all
--enable <RULES> Enable only specific rules
--exclude <PATTERNS> Exclude files matching patterns
--include <PATTERNS> Include only files matching patterns
--no-code-block-tools Skip configured tools; keep checking the outer Markdown
--only-code-block-tools Run configured tools; skip the outer Markdown
--stdin-batch Read NUL-delimited path/content pairs from stdin
--stdin-batch-closed-world Resolve batch links only within the supplied document set
--stdin-batch-targets Read NUL-terminated link target paths that are not linted
--watch Watch for changes and re-lint
--verbose Show detailed output
--quiet Print diagnostics, but suppress summaries
--silent Suppress diagnostics and summaries
--no-exclude Disable exclude patterns defined in config
--stderr Write diagnostics to stderr instead of stdout
--deny-config-warnings Treat configuration warnings as errors (exit code 2)

Findings go to stdout, whether the document came from a path or from --stdin, so --output-format json redirects the same way in both. --stderr moves them; config warnings and errors are always on stderr. The exception is a document rewritten on stdout - check --fix --stdin and fmt --stdin - where stdout belongs to the document and diagnostics go to stderr. fmt --check, fmt --diff and check --diff never write the document: they print its diff where findings go, named by --stdin-filename (or <stdin>), and exit as they do for a file.

A file the run leaves untouched, for conflict markers (MD092) or invalid UTF-8 (MD094), is reported on that same stream. The one place it moves is fmt --check and fmt --diff, whose stdout is a patch and nothing else: there the notice joins the config warnings on stderr.

The closing summary is written for a person, so a machine-readable format never carries one and needs no --quiet to keep its output parseable.

Batch stdin

--stdin-batch checks many caller-supplied snapshots in one process. The byte stream is a repeated path NUL content NUL sequence and must end in NUL:

path\0content\0path\0content\0

Paths must be UTF-8, non-empty and unique after path normalization. Content may be empty; content that is not valid UTF-8 is linted and reported as MD094 findings, as a file on disk would be. Each path is both the diagnostic name and the filesystem context used for configuration and relative links. A relative path is resolved against the working directory, and the file need not exist.

A document whose path matches an exclude pattern is not linted, exactly as rumdl check <file> skips it, but it still counts as a link target for the rest of the batch. --no-exclude lints it. A batch in which every document is excluded reports an empty run.

By default, supplied documents take precedence and links to documents omitted from the batch fall back to the on-disk workspace. Add --stdin-batch-closed-world to prohibit that fallback. In both modes a link to a directory resolves when any supplied path lies under it, so [docs](docs/) passes when the batch supplies docs/guide.md. Batch input never reads or writes the persistent workspace-index cache, because supplied content may differ from the file saved at the same path.

Links usually point at files that are not documents: PDFs, images, data files. Supplying each as an empty document makes rumdl configure and lint it. Instead, list such paths in a file and pass it with --stdin-batch-targets FILE. The file holds UTF-8 paths, each terminated by a NUL byte, like the batch itself, so any path spelling is expressible:

git ls-files -z > targets
printf 'docs/a.md\0[spec](files/spec.pdf)\n\0' | rumdl check --stdin-batch --stdin-batch-targets targets
  • A listed path exists for relative links in both open and closed world, and its parent directories are implied as for supplied documents. An entry with a trailing / declares a directory, which may be empty. The list answers before the disk: with guide.markdown listed, a link to guide names it, even when a guide.md exists on disk.
  • Paths resolve like batch document paths: relative to the working directory, and .. and absolute paths are accepted.
  • Listed paths are not linted, not counted, and never read from disk. Their headings are unknown, so a link with a #fragment into a listed Markdown file is not reported by MD051. The exception is a file that is also reached through another, unlisted path (a symlink, for example): fragments in links written with that path are checked against the file's headings, and once it has been read, links written with the listed path may be checked too.
  • A path both listed and supplied in the batch is an ordinary batch document.
  • An empty file means no targets. A missing or unreadable file, - (stdin carries the batch), invalid UTF-8, an empty entry, or a final entry without its NUL is a tool error (exit code 2). The option requires --stdin-batch.

Batch mode is check-only and cannot be combined with paths, --stdin, --stdin-filename, --fix, --diff, --check, or --watch.

Markdown embedded in other files

rumdl lints Markdown files and the doc comments of Rust files (Rust Doc Comments). Markdown that lives inside another language, such as doc comments or docstrings, can still be linted by a tool that extracts it with that language's own parser and pipes it to rumdl. rumdl does not ship such extractors.

Name each extracted snippet after its source file with a Markdown suffix, for example src/Foo.java.L42.md for the comment starting on line 42. A source-code name such as src/Foo.java is refused with exit code 2, because rumdl would otherwise lint the whole source file as Markdown. The .md name keeps the snippet in its source directory, so it picks up that directory's config, and a per-file-ignores glob can tune the rules for snippets only:

[per-file-ignores]
# A doc comment rarely opens with a heading.
"**/*.java.*.md" = ["MD041"]

Send many snippets in one process with --stdin-batch, giving each a unique path, and read the results with --output-format json. Positions in the output refer to the snippet as supplied (see Output Formats), so the extractor maps them back: add the snippet's starting line minus one to line, and add the number of characters it stripped from the start of that line, such as the indentation and ///, to column. Fix ranges address the snippet's bytes, so applying them to the source needs the extractor's own per-line byte map. Linting without fixing avoids that.

fmt [PATHS...]

Format Markdown files (applies fixes like rumdl check --fix, but keeps formatter-style exit codes).

rumdl fmt .                      # Format all files
rumdl fmt README.md              # Format specific file
rumdl fmt --silent -             # Format stdin to stdout without diagnostics

Files with unsuppressed MD092 conflict markers are left byte-for-byte unchanged. fmt reports an MD092 diagnostic and exits 0; check and check --fix report it as an error and exit 1 (unless --fail-on never is set). Other files are still processed. fmt --check and fmt --diff also skip conflicted files. Use scoped rumdl-disable merge-conflict / rumdl-enable merge-conflict comments around literal conflict examples to check and format the surrounding documentation.

Files that are not valid UTF-8 are reported by MD094 and are also left byte-for-byte unchanged.

fmt --diff, fmt --check and check --diff print a unified diff per changed file, line endings included, and leave the files alone. The fmt previews print that diff alone, so it is a patch: rumdl fmt --diff . > fmt.patch followed by git apply -p0 fmt.patch (or patch -p0 < fmt.patch) writes exactly what rumdl fmt . would. check --diff is the report of the same preview and lists the findings it would leave unfixed beside the diff.

Detection looks for opening or closing markers at the start of a line, with at least seven < or > characters followed by whitespace or the end of the line. It applies even inside code fences and to partially resolved conflicts; literal conflict examples therefore also prevent formatting. A Setext underline (=======) alone does not trigger detection. Editor formatting and fix actions also leave conflicted documents unchanged.

The safeguard is a rule, so a selection that drops MD092 - disable in the config, --disable MD092, an --enable list without it, or a scoped rumdl-disable merge-conflict comment - drops the protection with it, and the document is checked and formatted like any other.

Options:

Option Description
--config <PATH> Path to configuration file
--diff Show a diff of what would change instead of rewriting files
--check Exit 1 if formatting changes would be needed
--stdin Read from stdin
--stdin-filename <NAME> The file stdin holds, for diagnostics and per-file config
--no-code-block-tools Skip configured tools; format the outer Markdown
--only-code-block-tools Format configured fenced blocks only
--output-format <FMT> Output format for any remaining diagnostics
--watch Re-run formatting when files change
--quiet Print diagnostics, but suppress summaries
--silent Suppress diagnostics and summaries
--deny-config-warnings Treat configuration warnings as errors (exit code 2)

Use --silent whenever stdout should contain only formatted Markdown. Plain rumdl fmt - may also emit remaining diagnostics.

--stdin-filename names the file the piped document is, so the settings that apply to that file apply to it: the nearest per-directory config, exclude patterns, per-file-ignores, per-file-flavor, and .editorconfig. A relative name is resolved against the working directory, and the file need not exist yet. When the name matches an exclude pattern, nothing is linted: fmt and check --fix write the document back unchanged, and check reports an empty run. --no-exclude lints it anyway.

The code-block-tool mode flags are mutually exclusive and cannot be combined with --stdin, --stdin-batch, or the - stdin path. --only-code-block-tools drops the outer document's rules and leaves the tool phases as they are, so fmt still runs configured lint tools and reports what a formatter could not fix.

init [OPTIONS]

Create a configuration file.

rumdl init                       # Create .rumdl.toml
rumdl init --preset google       # Use Google style preset
rumdl init --output custom.toml  # Custom output path

Options:

Option Description
--pyproject Generate configuration for pyproject.toml
--preset <NAME> Use a style preset (default, google, relaxed)
--output <PATH> Output file path (default: .rumdl.toml)

import <FILE>

Import configuration from markdownlint.

rumdl import .markdownlint.json      # Import from markdownlint config
rumdl import .markdownlint.jsonc     # JSONC comments are supported
rumdl import .markdownlint.yaml      # YAML also works
rumdl import --dry-run .markdownlint.json
rumdl import --format json .markdownlint.yaml --output rumdl-config.json

Options:

Option Description
--dry-run Show the converted config without writing
--format <FMT> Output format: toml or json
--output <PATH> Output file path (default: .rumdl.toml)

rule [<RULE>]

Show rule documentation.

rumdl rule                       # List all rules
rumdl rule MD013                 # Show details for specific rule
rumdl rule line-length           # Use rule alias
rumdl rule --list-categories     # Discover rule categories
rumdl rule MD013 --output-format json
rumdl rule MD013 --output-format json --explain

Options:

Option Description
--list-categories List rule categories and exit
--category <NAME> Filter listed rules by category
--fixable Show only fixable rules
--output-format <FMT> Structured output such as json or json-lines
--explain Include full documentation in JSON-based output

config [OPTIONS]

Show effective configuration.

rumdl config                     # Show merged configuration
rumdl config --defaults          # Show default values only
rumdl config --no-defaults       # Show non-default values only

server

Start the LSP server.

rumdl server                     # Start Language Server Protocol server

See LSP Integration for details.

vscode

Install VS Code extension.

rumdl vscode                     # Install extension
rumdl vscode --status            # Check installation
rumdl vscode --update            # Update the installed extension
rumdl vscode --force             # Force reinstall

version

Show version information.

rumdl --version                  # Short version
rumdl version                    # Detailed version info

Global Options

These options are commonly used with check and fmt:

Option Description
--help, -h Show help
--version, -V Show version
--verbose, -v Verbose output
--quiet, -q Print diagnostics, but suppress summaries
--color <WHEN> Color output (auto, always, never)
--no-config Ignore discovered configuration and use built-in defaults
--output-format <FMT> Output format (see Output Formats)

Exit Codes

Code Meaning
0 Success
1 Lint violations found
2 Configuration or runtime error

fmt vs check --fix

  • rumdl fmt exits 0 whether or not violations remain (formatter mode). It exits 2 only when the run could not be completed: a file it could not read, or a code-block tool that could not run under a fail setting.
  • rumdl check --fix exits 1 if unfixable issues remain

Failing on configuration problems

Configuration problems (an unknown rule or option in a config file or a CLI flag, an unknown rule in an inline rumdl-disable-line comment, a shadowed config file, a subdirectory config that could not be loaded, an .editorconfig property rumdl cannot apply, or a run in which every Markdown file found was filtered out) are non-fatal warnings by default and do not affect the exit code. Pass --deny-config-warnings to make any of them exit with code 2, so CI catches a typo'd rule name. This is distinct from --fail-on, which governs the severity of Markdown violations (exit 1); a config problem exits 2 and takes precedence over Markdown violations.

When nothing gets checked

Checking zero files and checking every file cleanly both exit 0 with no findings, so rumdl reports which one happened on stderr. A directory holding no Markdown says so plainly; a run whose files were all filtered out instead reports how many were found and which setting removed them:

No markdown files left to check: 12 files found were filtered out.
  12 by ignore files (.gitignore, .ignore, git excludes); pass --respect-gitignore=false to keep them

Files hidden by .markdownlintignore get a line of their own, because --respect-gitignore=false does not turn that file off: the notice says to remove them from it or name them on the command line instead.

The notice never shares a stream with the selected output, so it stays out of --output-format json and the other machine-readable formats: it goes to stderr, or to stdout when --stderr routes diagnostics the other way. It survives --quiet; use --silent to suppress it, or --deny-config-warnings to fail the run instead.

Usage Examples

Basic Linting

# Lint all Markdown files
rumdl check .

# Lint specific directory
rumdl check docs/

# Lint with custom config
rumdl check --config my-config.toml .

Selective Rules

# Disable specific rules
rumdl check --disable MD013,MD033 .

# Enable only specific rules
rumdl check --enable MD001,MD003 .

# Disable every rule; an --enable list still survives
rumdl check --disable all .

Code block tools

Code block tools run external linters and formatters over fenced code blocks. They are configured separately from the Markdown rules, so either side can be run without the other while the rest of the configuration keeps applying:

# Markdown rules only, configured tools skipped
rumdl check --no-code-block-tools .
rumdl fmt --no-code-block-tools .

# Configured tools only, outer Markdown left alone
rumdl check --only-code-block-tools .
rumdl fmt --only-code-block-tools .

Reach for --only-code-block-tools rather than --disable all here. The latter empties the rule set that fenced Markdown configured with lint = ["rumdl"] is linted with, so the built-in tool reports nothing while external tools carry on.

--no-code-block-tools is the named form of --config 'code-block-tools.enabled = false', and the mode flags win over an inline --config that sets the master switch the other way.

Overriding configuration for one run

--config also takes an inline KEY = VALUE snippet, which overrides that setting at the highest precedence while everything else the config files set still applies. --no-config is the blunt alternative: it discards the whole configuration.

# One rule option
rumdl check --config 'MD013.line-length = 20' .

# A global option
rumdl check --config 'line-length = 20' .

# A non-rule section: code-block-tools, per-file-ignores, per-file-flavor
rumdl check --config 'code-block-tools.enabled = false' .
rumdl check --config 'per-file-flavor."docs/**/*.md" = "mkdocs"' .

An override sets the settings it names to the values given, and settings it does not name keep what they were configured with. code-block-tools holds several settings, so --config 'code-block-tools.timeout = 60000' leaves the configured languages and tools alone. per-file-ignores and per-file-flavor are each a single setting whose value is a map of patterns, so naming one pattern replaces the map - the same as writing that section in a higher-precedence config file, and the same as ruff's --config for lint.per-file-ignores. To keep the project's other patterns for a run, name them too.

A value the setting cannot hold is reported as a [config warning] naming the key it came from; that value is skipped and the rest of the run proceeds. See CLI --config overrides for what is and is not checked.

File Filtering

# Exclude directories
rumdl check --exclude "node_modules,dist" .

# Include only specific patterns
rumdl check --include "docs/**/*.md" .

# Combine patterns
rumdl check --include "docs/**/*.md" --exclude "docs/drafts" .

Watch Mode

# Watch for changes
rumdl check --watch docs/

Stdin/Stdout

# Format from stdin
cat README.md | rumdl fmt --silent -

# With filename context
cat README.md | rumdl check - --stdin-filename README.md

# Format clipboard (macOS)
pbpaste | rumdl fmt --silent - | pbcopy

Output Formats

Control how warnings are displayed with --output-format:

rumdl check --output-format full .
rumdl check --output-format json .
RUMDL_OUTPUT_FORMAT=github rumdl check .

Human-readable formats:

Format Description
text One line per warning: file:line:col: [RULE] message (default)
full Source lines with caret underlines highlighting the violation
concise Minimal: file:line:col rule message
grouped Warnings grouped by file with a header per file

Machine-readable formats:

Format Description
json JSON array of all warnings (collected)
json-lines One JSON object per warning (streaming)
sarif SARIF 2.1.0 for static analysis tools
junit JUnit XML for CI test reporters

See Output Formats for the field-level reference for each machine-readable format.

CI/CD formats:

Format Description
github GitHub Actions annotations (::warning/::error)
gitlab GitLab Code Quality report (JSON)
azure Azure Pipelines logging commands
pylint Pylint-compatible format

Example: full format output:

MD013 Line length 95 exceeds 80 characters
 --> README.md:42:81
   |
42 | This is a long line that exceeds the configured maximum line length ...
   |                                                                     ^^^
   |

Example: text format output (default):

README.md:42:81: [MD013] Line length 95 exceeds 80 characters

Preflight formatting

Use rumdl fmt --preflight PATH... or rumdl check --fix --preflight PATH... to plan the entire selected batch before writing any file. rumdl first reads all selected inputs and requires valid UTF-8, even if MD094 is disabled. It then computes document fixes and external formatter output and checks for fatal processing errors, non-converging document fixes, read-only output files, and changes to input contents, targets, or permissions during planning. A failure in this phase exits with code 2 and discards every planned write.

Successful plans are applied with atomic replacement for each file, preserving its permissions and original line endings. Discovery resolves symlinks and deduplicates target paths. Plans apply to those resolved targets; changing a symlink afterwards does not redirect a planned write. External tools run during planning and are not run again to apply the plan. Ordinary remaining lint findings do not abort preflight formatting; configured fatal tool policies do. Configuration and coverage warnings abort the batch when --deny-config-warnings is used. Execution warnings under on-error = "warn" retain that warning policy.

This is a preflight guarantee, not a multi-file transaction. An I/O failure or concurrent edit during the later apply phase can leave earlier files formatted; rumdl reports how many writes completed and exits with code 2. Changes made by external commands themselves are outside this guarantee. An edit between the last per-file validation and its atomic replacement can still race with that replacement. --preflight does not support stdin, stdin batches, watch mode, --diff, or --check previews. Omit it to retain the usual streaming write behavior.