Skip to main content

mdrepo

Portable links. Reachable docs. Exceptions that do not quietly live forever.

Python 3.11+ License: MIT Typing: strict Pyright Lint: Ruff pre-commit

Most Markdown linters stop at the file boundary. mdrepo starts at the repository boundary.

mdrepo is a focused Python 3.11+ CLI for policies that require cross-platform filesystem semantics or a view of the complete documentation set. It complements rumdl rather than replacing it.

It catches repository-level problems such as:

  • backslash links and machine-local absolute paths;
  • links that escape the repository root;
  • path spelling that differs from exact on-disk case;
  • Markdown pages unreachable from configured entry documents;
  • expired exceptions and exceptions that no longer suppress anything.

mdrepo is deterministic and offline. It performs no HTTP crawl, documentation-site build, Node.js or npm orchestration, subprocess wrapping, or public plugin discovery.

Quick start

From a checkout, install mdrepo as a persistent command and check a repository:

uv tool install .
mdrepo check /path/to/repository

For development inside this repository:

uv sync --extra dev
uv run python -m mdrepo check .

Preview the deliberately narrow safe-fix set before changing files:

mdrepo fix . --dry-run
mdrepo fix .

Diagnostics are stable and source-located:

docs/setup.md:24:15: error MDR001 local destination uses backslashes instead of POSIX '/' separators
  fix: normalize the local destination to a POSIX relative path
notes.md:1:1: error MDR101 Markdown document is unreachable from every configured graph root
  hint: Link the document into the graph or add a narrow structured exception.

Where mdrepo fits

rumdl validates Markdown as documents. mdrepo validates Markdown references as relationships inside a repository.

Question Authority
Is the Markdown valid, consistently formatted, and flavor-aware? rumdl
Does a relative target or heading fragment exist? rumdl MD057 and MD051
Are local paths portable, root-bounded, and cased exactly like the filesystem? mdrepo
Is each page reachable from a configured documentation root? mdrepo
Are policy exceptions justified, current, and still necessary? mdrepo
Does a generated documentation site build and route correctly? The site build
Does an external URL currently respond? A dedicated network checker

The tools are independent. mdrepo does not invoke rumdl, consume its diagnostics, interpret its inline suppressions, or forward its configuration. Each tool owns its own rules, configuration, output, and exit status.

See the complete responsibility boundary for overlap policy, MkDocs guidance, guarantees, and non-goals.

What the focused tool adds

Capability What it protects against
Portable destinations Backslashes, local absolute paths, and disallowed root-relative paths
Repository boundaries Local destinations that resolve outside the configured root
Exact path case Links that pass on case-insensitive filesystems and fail on Linux
Durable local targets Existing links to Git-ignored or mdrepo-excluded files
Rooted document graph Pages disconnected from configured roots
Structured exceptions Anonymous ignores, expired waivers, and stale policy debt
Conservative fixing Ambiguous edits, overlapping replacements, and changed CRLF line endings

Safe fixes are applied only when the source span is verified and the replacement is unambiguous. Writes are atomic, and existing CRLF line endings are preserved.

Orphan analysis is generic Markdown graph reachability. It is not the same as membership in an MkDocs nav tree. An optional mkdocs.yml file configures an MkDocs documentation site, and its nav section is the publication membership/order authority; rumdl MD074 owns that site-generator-specific check. Use MDR101 for repository-link reachability, or disable it when MkDocs navigation is the only discoverability policy.

Configuration

mdrepo searches upward for the nearest project root containing one of these markers:

  1. pyproject.toml with a [tool.mdrepo] table;
  2. mdrepo.toml;
  3. .mdrepo.toml;
  4. a .git entry, used only as a root fallback.

A small paired configuration is often enough:

# mdrepo-doc-example
[tool.rumdl]
flavor = "gfm"
extend-enable = ["MD029", "MD060"]

[tool.rumdl.MD029]
style = "ordered"

[tool.rumdl.MD060]
style = "aligned"

[tool.mdrepo.orphans]
enabled = true
roots = ["README.md", "docs/index.md"]

The link-policy defaults are strict and portable. In the normal paired workflow, links.check-missing-targets remains false so rumdl MD057 is the sole authority for ordinary missing targets. links.check-durable-targets remains enabled so existing links cannot silently point at transient or excluded files.

Structured exceptions

Exceptions are explicit policy records rather than casual ignore globs:

[tool.mdrepo.exception-policy]
report-expired = true
report-unused = true
expired-severity = "warning"
unused-severity = "warning"

[[tool.mdrepo.exceptions]]
id = "standalone-changelog"
rule = "MDR101"
path = "CHANGELOG.md"
reason = "Package metadata exposes this file independently of the documentation graph."
expires = 2027-01-01

MDR201 reports expired exceptions. MDR202 reports exceptions that no longer suppress a finding. Unknown configuration keys are errors. Repeated --config overlays and typed --set dotted.key=value overrides are applied last.

See the configuration reference for discovery patterns, link policy, graph resolution, rule selection, severity overrides, and configuration layering.

This repository is also a complete integration example: see its pyproject.toml for independent rumdl and mdrepo configuration, and the CI integration guide for dependency, hook, and CI setup patterns.

By default, discovery excludes Markdown files matched by the repository's .gitignore, along with common transient locations such as virtual environments, tool caches, build/, and dist/. Use top-level exclude patterns for project-specific generated or legacy trees. Set respect-gitignore = false only when ignored Markdown should be inspected explicitly; those files remain outside the orphan graph, and links to them can still receive MDR006.

Recommended workflow with rumdl

Run rumdl's configured document formatting first, then repository-policy fixes, and finish with read-only checks:

uvx rumdl check --fix .
mdrepo fix .
uvx rumdl check .
python -m mdrepo check .

In CI, keep the two authorities visible and independent:

rumdl check .
python -m mdrepo check . --format github

A green run means the Markdown layer and repository-policy layer are clean. It does not claim that remote websites respond or that a documentation generator can build the published site.

CI integration

See the CI integration guide for mdrepo's standalone installation, root and configuration contract, mutation ordering, exclusions, platform commands, exit codes, and a minimal GitHub Actions job.

MDR004 is available as a standalone missing-target fallback, but it remains disabled in the normal rumdl workflow to avoid duplicate ownership and duplicate diagnostics.

Commands

Command Purpose
mdrepo check [PATH ...] Check repository policy without modifying files.
mdrepo fix [PATH ...] Apply only source-verified safe fixes.
mdrepo fix --dry-run Print proposed diffs and return 1 when edits are available.
mdrepo fix --diff Apply fixes and also print unified diffs.
mdrepo graph --graph-format FORMAT Render the graph as text, json, or dot.
mdrepo rules List built-in rule metadata.
mdrepo config Print the fully resolved configuration as JSON.

Useful invocation overrides:

mdrepo check . --select MDR001,MDR005
mdrepo check . --ignore MDR101
mdrepo check . --format github
mdrepo check . --fail-on warning
mdrepo check . --set links.check-missing-targets=true

Exit statuses are stable:

  • 0: no visible finding meets fail-on;
  • 1: a visible finding meets fail-on, or fix --dry-run found applicable edits;
  • 2: configuration, invocation, discovery, parsing, or safe-fix failure.

Built-in rules

Rule Purpose Safe fix
MDR001 Backslash in a local destination Yes
MDR002 Machine-, protocol-, or repository-root-absolute destination Root-relative only
MDR003 Local destination escapes the repository root No
MDR004 Standalone missing-target fallback; disabled with rumdl No
MDR005 Path spelling differs from exact on-disk case Yes
MDR006 Existing local target is Git-ignored or mdrepo-excluded No
MDR100 No configured orphan-graph root exists No
MDR101 Markdown document is unreachable from all roots No
MDR201 Structured exception is expired No
MDR202 Structured exception is unused No

Rule IDs use the MDR namespace so they remain distinct from rumdl's MD rules.

Integration

See the CI integration guide for installation, repository-root requirements, configuration discovery, exclusions, mutation ordering, platform commands, exit codes, CI, and pre-commit setup. That guide is the canonical integration reference; this README keeps only the product overview and command summary.

Deliberate boundaries

mdrepo does not format Markdown, validate heading anchors, crawl external URLs, parse arbitrary raw HTML links, build documentation sites, or invoke other tools. The internal Rule protocol remains a small extension seam without committing the project to a public plugin-loading system.

Development

Run the same read-only gate used by hosted CI:

uv sync --extra dev
uv run python -m scripts.ci

During development, apply safe fixes first and then rerun the read-only gate:

uv run python -m scripts.ci --fix
uv run python -m scripts.ci

Hosted CI runs the supported operating-system and Python-version matrix defined in .github/workflows/ci.yml. The gate includes compilation, linting, tests, isolated package validation, type checking, documentation checks, pre-commit validation, and an mdrepo self-check.

Documentation

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mdrepo-0.0.1.post0.tar.gz (77.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mdrepo-0.0.1.post0-py3-none-any.whl (40.9 kB view details)

Uploaded Python 3

File details

Details for the file mdrepo-0.0.1.post0.tar.gz.

File metadata

  • Download URL: mdrepo-0.0.1.post0.tar.gz
  • Upload date:
  • Size: 77.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.9

File hashes

Hashes for mdrepo-0.0.1.post0.tar.gz
Algorithm Hash digest
SHA256 8ea8eb2e25ef4da7517cc7c835e08760eb85e7dfc27a7dfb36928589bff08305
MD5 81771eb4b37b11859b183b398cdcbe3d
BLAKE2b-256 176889d09b45e84af8bb852835c4691f36ddf4fe25add4b3a2f53b1d3b115974

See more details on using hashes here.

File details

Details for the file mdrepo-0.0.1.post0-py3-none-any.whl.

File metadata

  • Download URL: mdrepo-0.0.1.post0-py3-none-any.whl
  • Upload date:
  • Size: 40.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.9

File hashes

Hashes for mdrepo-0.0.1.post0-py3-none-any.whl
Algorithm Hash digest
SHA256 67a5a020854c6e9d57e8e364798efb489a3dfb25fd8595c49283cef619edce7a
MD5 5869f2f61a64b32f7c6ebd9511ab096f
BLAKE2b-256 3c580763e316244097b4cc28f894abbfeff3fff355a5965e0be8f58ead59bde5

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.1.post0 This release

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page