Markdown formatting tool — Obsidian-safe formatter choice
Status: open/accepted
Summary
Section titled “Summary”- rumdl (Rust linter-formatter, ruff model) is the markdown formatter — the only surveyed tool whose table formatting (MD060) is opt-in and off by default, satisfying the Standard’s hand-formatted-tables rule.
- One tool covers the whole formatter track:
MD013 reflow = truewraps prose at 100 columns and the MD040 fix tags bare fencestext— no custom wrap script needed. - Prettier and dprint stay disqualified (unavoidable table column-padding); the markdownlint-cli2 + wrap-script hybrid is the specified fallback; dprint a tertiary watch.
- Empirically verified: byte-identical fixture round-trip; forensic full-corpus pass (660 files) with wikilink/embed multiset, tables, and frontmatter byte-identical; idempotent second pass.
- Accepted caveats: two narrow reflow bugs (embed-glue — zero corpus instances; anchor displacement — three lines hand-fixed before the reformat), tracked for upstream filing via B-1ATA.
- T-0008 wired it (PR #356): pinned
bunx rumdl@0.2.9in the lefthookmarkdown-fmtstep, justfile recipes, and the/markdown-formatskill;check+ fixture-pin quality gates; and the one-time corpus reformat (~7,000 fixes across 433 files).
Context
Section titled “Context”D-0008-markdown-standard settled the rule set: prose wrapped at
100 columns, ATX headings, - bullets, explicit ordered lists,
code fences with language tags, tables hand-formatted and never
reflowed, frontmatter preserved byte-for-byte by the formatter
(schema validator owns frontmatter shape). Obsidian features —
wikilinks (…), block-ids (^anchor), transclusions
(file), frontmatter as Properties — MUST survive
the formatter without byte-level change to their syntax.
T-0007 tasked this ADR with selecting the tool that enforces those rules mechanically and slots into the lefthook pre-commit gate already used by the project. T-0008 wired the chosen tool; this ADR set the choice so T-0008 could act without re-deciding.
Evaluation axes
Section titled “Evaluation axes”| Axis | What it measures |
|---|---|
| Formats vs lints | Writes files (formatter) or only diagnoses (linter) |
| Frontmatter safety | Does it alter YAML frontmatter — quote style, list indentation, key order? |
| Wikilink safety | Does … text survive byte-for-byte? |
| Transclusion safety | Does file survive? Line merging is a dealbreaker. |
| Block-id safety | Does line-terminal ^anchor survive? |
| Table handling | Does it reflow or pad table columns? (dealbreaker: must leave tables untouched) |
| Bun/CI fit | Installable via Bun/npx without Node-isms; CI-clean |
| Pre-commit perf | Single-file ≤500ms; full corpus ≤30s |
| Maintenance | Active releases as of 2026 |
| Configurability | Can it express all formatter-track rules from D-0008-markdown-standard? |
Decision
Section titled “Decision”Recommendation: rumdl — a Rust linter-formatter (the ruff model) whose table rule (MD060) is opt-in and ships disabled, making it the only surveyed formatter that leaves tables untouched by default. Prettier and dprint remain disqualified: both column-pad tables on every write with no disable option (Prettier 3.8.3, dprint markdown plugin 0.22.1 — confirmed empirically; not a config gap).
rumdl covers the entire formatter track in one binary, including the
two rules markdownlint cannot auto-fix: prose wrap at 100 columns
(MD013 with reflow = true) and code-fence language tags (its
MD040 fix inserts text, the Standard’s fallback tag). No companion
script is needed. Config under Recommendation detail; empirical
evidence under Options considered.
Two reflow caveats are accepted and tracked (detail in the rumdl
section): an embed sharing a paragraph with over-100-column prose is
glued into the reflowed text (zero corpus instances), and a
line-terminal ^anchor on an over-100-column line is displaced to a
continuation line (three corpus instances — hand-fixed before the
one-time reformat). Both are upstream-reportable bugs in a project
with a fast fix record.
Runner-up: markdownlint-cli2 --fix plus a custom Bun
prose-wrap script as two sequential lefthook commands. Kept fully
specified under
Recommendation detail as the fallback — rumdl uses markdownlint’s
rule numbering and imports markdownlint configs, so the exit ramp is
cheap if rumdl’s single-maintainer pre-1.0 risk materializes.
dprint stays a tertiary watch: revisit if dprint/dprint-plugin-markdown#180 ships a table-preserve option.
The core constraint from D-0008-markdown-standard is that tables MUST NOT be reflowed. Both dominant formatters violate this:
- Prettier column-pads tables on every format pass. There is no config option to skip table alignment (issue #12074, open since 2022, no resolution).
- dprint pads columns identically.
"textWrap": "maintain"governs prose wrapping, not table formatting; no table-disable option exists (issue #180, open).
A tool with no table-untouched mode cannot be the primary formatter. rumdl inverts the default — table formatting is opt-in, off out of the box — so the dealbreaker axis passes without configuration tricks. One tool covering lint, format, and prose wrap also removes the custom-script maintenance surface the hybrid would have carried: the reason the Custom-script option is rejected below applies to the hybrid’s wrap script too, just at smaller scale.
Options considered
Section titled “Options considered”Cross-tool summary; per-tool score tables with evidence follow.
| Tool | Formats vs lints | Frontmatter | Wikilinks | Transclusions | Block-ids | Tables | Bun/CI fit | Maintenance | Verdict |
|---|---|---|---|---|---|---|---|---|---|
| rumdl 0.2.8 | Both (lint + fmt) | Pass | Pass | Pass* | Acceptable* | Strong (untouched) | Strong (bunx) | Acceptable (pre-1.0, fast cadence) | Recommended |
| Prettier 3.8.3 | Formatter | FAIL | Pass | FAIL | Pass | FAIL | Strong | Strong | Disqualified |
| dprint 0.54.0 | Formatter | Pass | Pass | Pass | Pass | FAIL | Acceptable | Strong | Disqualified (tertiary watch) |
| markdownlint-cli2 0.22.0 | Linter + limited fix | Strong | Strong | Strong | Strong | Strong (no reflow) | Strong | Strong | Fallback (hybrid component) |
| mdformat 1.0.0 | Formatter | Acceptable (plugin) | Acceptable (plugin) | Unknown | Unknown | Poor | Poor (Python) | Acceptable | Rejected |
| remark-cli 12 | Formatter (plugins) | Acceptable (plugin) | Acceptable (plugin) | Unknown | Unknown | Unknown | Acceptable | Acceptable | Rejected |
* rumdl reflow caveats (zero and three corpus instances respectively): see the rumdl section’s Transclusion and Block-id rows.
Prettier 3.8.3 (Node / Bun-compatible)
Section titled “Prettier 3.8.3 (Node / Bun-compatible)”| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Formatter | Writes files |
| Frontmatter safety | FAIL | Rewrites YAML quote style ('5' → "5"), re-indents block lists with 2-space indent prefix, moves list items. Verified empirically. |
| Wikilink safety | Pass | … preserved in prose; merged PR #9275 (2020) protects them from wrap-splits. Confirmed in round-trip test. |
| Transclusion safety | FAIL | Collapses adjacent … lines on separate lines into one space-separated line. Breaks Obsidian transclusion. Confirmed empirically. |
| Block-id safety | Pass | ^anchor at line-end preserved. |
| Table handling | FAIL | Column-pads and aligns all pipe tables. No option to disable. Issues #12074, #17148 both open. Confirmed empirically. |
| Bun/CI fit | Strong | bunx prettier works; no Node-isms needed. |
| Pre-commit perf | Strong | 5 files: ~200ms; 399-file corpus: ~3.9s. |
| Maintenance | Strong | v3.8.3 released April 2026; actively maintained. |
| Configurability | Poor | Cannot leave tables untouched; cannot skip frontmatter YAML re-serialization. |
Verdict: Disqualified. Three dealbreaker failures: table alignment, frontmatter mutation, transclusion line-collapse.
Empirical evidence — Prettier round-trip
Section titled “Empirical evidence — Prettier round-trip”Test file had YAML frontmatter with schema_version: '5' and
'M-0001' in a block list, two … transclusion
lines, and a pipe table. After bunx prettier --write with
proseWrap: always, printWidth: 100:
schema_version: '5'schema_version: "5"created: '2026-05-30'created: "2026-05-30" - test - test - '`M-0001`' - "`M-0001`"`D-0008-markdown-standard``S-0007-markdown-formatting``D-0008-markdown-standard` `S-0007-markdown-formatting`| Tool | Stack | Notes ||---|---|---|| Tool | Stack | Notes || ----- | ----- | -------------- |Frontmatter quote style changed, block-list indentation shifted, two transclusion lines collapsed to one, table column-padded.
dprint 0.54.0 + markdown plugin 0.22.1 (Rust)
Section titled “dprint 0.54.0 + markdown plugin 0.22.1 (Rust)”| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Formatter | Writes files |
| Frontmatter safety | Pass | YAML frontmatter passed through byte-for-byte; quote styles and block-list style preserved. Confirmed empirically. |
| Wikilink safety | Pass | … preserved in prose. textWrap: maintain does not split wikilinks. Confirmed empirically. |
| Transclusion safety | Pass | Adjacent … lines preserved on separate lines. Confirmed empirically. |
| Block-id safety | Pass | Line-terminal ^anchor preserved. |
| Table handling | FAIL | Column-pads and aligns pipe tables, identical behavior to Prettier. No disable option. Issue #180 open. Confirmed empirically. |
| Bun/CI fit | Acceptable | brew install dprint or download binary; not npm-native. Requires separate install step in CI. |
| Pre-commit perf | Strong | 5 files: ~35ms; 399-file corpus: ~104ms. 10–40× faster than Prettier. |
| Maintenance | Strong | v0.22.1 released May 2026; Rust-compiled, stable release cadence. |
| Configurability | Acceptable | textWrap: maintain preserves prose wrapping; lineWidth sets target. Cannot disable table formatting. |
Verdict: Disqualified on the table axis alone. All Obsidian
features (frontmatter, wikilinks, transclusions, block-ids) survive
intact — dprint is the closest runner-up. If issue #180 ships a
"tables": "preserve" option, dprint becomes the recommended tool
and this ADR should be revisited.
Empirical evidence — dprint round-trip
Section titled “Empirical evidence — dprint round-trip”Same test file as Prettier test. After dprint fmt with
textWrap: maintain, lineWidth: 100:
← blank line added after frontmatter (harmless)| Tool | Stack | Notes ||---|---|---|| Tool | Stack | Notes || ----- | ----- | -------------- |Only change beyond the blank line: table column-padding. Frontmatter untouched, wikilinks untouched, transclusion lines preserved on separate lines, block-ids preserved.
rumdl 0.2.8 (Rust / Bun-compatible)
Section titled “rumdl 0.2.8 (Rust / Bun-compatible)”A Rust linter-formatter (rumdl.dev) on the
ruff model — all 53 markdownlint rules
plus 22 more, most auto-fixable via rumdl fmt, TOML config,
markdownlint config import, and a built-in obsidian flavor.
| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Both | Linter with auto-fix (rumdl fmt / check --fix); one tool covers both roles of the hybrid. |
| Frontmatter safety | Pass | Byte-for-byte (quote styles, block-list indentation) with MD071 (blank-line-after-frontmatter) disabled. Confirmed empirically. |
| Wikilink safety | Pass | Wikilink/embed multiset across all 660 repo .md files byte-identical after 7,075 fixes. Reflow wraps around …, never splits; an unwrappable aliased-link line is flagged, not mangled. Confirmed empirically. |
| Transclusion safety | Pass | Adjacent standalone … lines preserved (the Prettier failure). Caveat: an embed in the same paragraph as over-100-column prose is glued into the reflowed text with no space — zero corpus instances; upstream-reportable. Confirmed empirically. |
| Block-id safety | Acceptable | Anchor syntax never altered. Reflow displaced 3/46 line-terminal ^summary anchors (on >100-column lines: C-0005, C-0007, P-0003) to continuation lines — Obsidian needs end-of-line placement for paragraphs. Hand-fix three lines before the corpus reformat. |
| Table handling | Strong | MD060 (table format) is opt-in, default-off — corpus tables byte-identical including |---| delimiters. Only formatter surveyed to pass this axis. Confirmed empirically. |
| Bun/CI fit | Strong | npm-native: bunx rumdl works; also brew / cargo / pip / static binary. (rumdl init is interactive — avoid in CI.) |
| Pre-commit perf | Strong | 660-file corpus: check 264ms cold / 58ms warm (incremental cache), full fmt 631ms — internal timings; ~1–1.7s wall through the bunx shim. Single file ~25ms. |
| Maintenance | Acceptable | Pre-1.0 (0.2.8; 0.2.9 shipped the same day), single maintainer, very fast cadence. The reflow-collapse bug family (#597–#600, #639) is all closed; #235 added wikilink-awareness to link rules. markdownlint rule numbering + rumdl import keep the fallback cheap. |
| Configurability | Strong | Expresses every formatter-track rule, including prose wrap (MD013 reflow) and the MD040 auto-fix (inserts text; 447 corpus instances). Bonus: MD057 relative-link validation (71 corpus hits) covers the broken-link capability the Standard deferred. Two default-config traps must be set — see the config sketch. |
Verdict: Recommended. Only formatter that passes the table axis; only tool covering the full formatter track without a companion script. Two narrow reflow wounds (embed-glue, anchor displacement) have zero and three corpus instances respectively.
Empirical evidence — rumdl round-trip
Section titled “Empirical evidence — rumdl round-trip”Same synthetic Obsidian-features fixture as the Prettier and dprint
tests. After rumdl fmt with the config sketch under
Recommendation detail, the round-trip is byte-identical —
frontmatter, wikilinks, adjacent transclusion lines, block-id, and
table all untouched. The only clean round-trip of the four formatters
tested.
Beyond the original methodology, a forensic full-corpus pass
(rumdl fmt over a git-baselined /tmp/ copy of all 660 tracked
.md files, 2026-06-06):
- Wikilink/embed multiset before vs after: byte-identical (7,075 fixes applied across ~500 files).
- Tables and frontmatter: byte-identical.
- Block anchors: 3 line-terminal
^summaryanchors displaced (the three >100-column lines named above); all others untouched. - Reflow stress fixture: wikilinks wrapped around, never split; embed-in-paragraph glue bug reproduced (absent from the corpus).
- Default-config traps caught and neutralized: MD025’s
front-matter-titledefault reads frontmattertitle:plus the required body H1 as two H1s and demoted headings in 116 files (front-matter-title = ""fixes it; markdownlint ships the same default — see the fallback config); MD046’sconsistentdefault converted one fence to an indented block (style = "fenced"fixes it). - A multi-line paragraph containing any over-100-column line is rewrapped as a unit, so deliberate one-field-per-line layouts (machine-written dedup-search blocks) merge into flowing prose. Renders identically; source-line layout is lost.
- Idempotent: a second
fmtpass produced zero changes.
markdownlint-cli2 0.22.0 (Node)
Section titled “markdownlint-cli2 0.22.0 (Node)”| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Linter + limited fix | --fix corrects auto-fixable rules; does NOT reflow prose or touch tables. |
| Frontmatter safety | Strong | Treats frontmatter as opaque; does not re-serialize. |
| Wikilink safety | Strong | Linter does not parse or transform wikilinks; they pass through. |
| Transclusion safety | Strong | Same — no AST transform of … syntax. |
| Block-id safety | Strong | No transformation of ^anchor. |
| Table handling | Strong | Does not reflow tables. Lint rules (MD055, MD056) flag structural issues without rewriting. |
| Bun/CI fit | Strong | bunx markdownlint-cli2; npm-native; no extra install. |
| Pre-commit perf | Strong | Lint-only is fast; ~100–200ms for a typical change set. |
| Maintenance | Strong | v0.22.0 on Arch Linux as of 2026-06; 126k weekly npm downloads; healthy release cadence. |
| Configurability | Acceptable | Covers: ATX headings (MD003), list markers (MD004), ordered list style (MD029), code fence language (MD040 — flag-only: cannot insert a tag, and the corpus has 447 bare fences), trailing whitespace (MD009), final newline (MD047). Cannot auto-wrap prose at 100 columns — that requires a formatter or separate script. Needs MD025.front_matter_title: "" — frontmatter title: plus the required body H1 otherwise flags every entity file (confirmed empirically via rumdl’s identical default). |
Verdict: Passes all Obsidian-feature safety axes. Cannot auto-format prose line wrapping without a companion formatter. Primary component of the fallback hybrid.
mdformat 1.0.0 (Python)
Section titled “mdformat 1.0.0 (Python)”| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Formatter | Writes files |
| Frontmatter safety | Acceptable (plugin) | Requires mdformat-frontmatter plugin (v2.1.2, May 2026); without it, frontmatter is parsed as body and mangled. |
| Wikilink safety | Acceptable (plugin) | Requires mdformat-wikilink plugin (Oct 2025); core mdformat escapes … to \[\[…\]\]. |
| Transclusion safety | Unknown | No confirmed plugin covers file transclusion form; mdformat_obsidian 0.1.0 exists but is alpha-quality. |
| Block-id safety | Unknown | No confirmed handling of line-terminal ^anchor. |
| Table handling | Poor | Requires mdformat-gfm for tables; behavior with mixed-width cells (wikilinks in cells) is not verified. |
| Bun/CI fit | Poor | Python/pip dependency in a Bun/TypeScript project. Adds a uv or pip step to CI. |
| Pre-commit perf | Acceptable | Python startup adds 300–600ms per run. |
| Maintenance | Acceptable | 1.0.0 released Oct 2025; plugin ecosystem fragmented across multiple PyPI packages. |
| Configurability | Poor | Plugin composition required for every Obsidian feature; each plugin is a separate maintenance surface. |
Verdict: Too many unknowns on Obsidian transclusion and block-id handling. Python dependency is a poor fit for this Bun/TypeScript project. Rejected.
remark-cli 12 / remark-stringify (Node / unified.js)
Section titled “remark-cli 12 / remark-stringify (Node / unified.js)”| Axis | Score | Notes |
|---|---|---|
| Formats vs lints | Formatter (with plugins) | remark --output writes files; remark-stringify serializes the AST. |
| Frontmatter safety | Acceptable (plugin) | Requires remark-frontmatter; without it, frontmatter is parsed as body. |
| Wikilink safety | Acceptable (plugin) | Requires remark-wiki-link or remark-obsidian; core remark parses … as text and may escape it. |
| Transclusion safety | Unknown | No well-maintained plugin covering file verified; community plugins are early-stage. |
| Block-id safety | Unknown | Not covered by any documented plugin. |
| Table handling | Unknown | remark-gfm adds table support; reflow behavior depends on stringify settings. |
| Bun/CI fit | Acceptable | npm-native but requires assembling a multi-plugin pipeline. |
| Pre-commit perf | Acceptable | 200–400ms for a typical change set. |
| Maintenance | Acceptable | remark-cli 12.0.0 (Sep 2023); ecosystem is large but plugin quality varies. Core is stable; Obsidian plugins are niche. |
| Configurability | High (in theory) | Can author custom plugins; high ceiling, high floor. Custom plugin cost is the maintenance surface of a custom solution. |
Verdict: Obsidian transclusion and block-id safety unconfirmed. Plugin assembly cost is high. Rejected in favor of markdownlint-cli2 whose Obsidian-safety profile is empirically verified.
Custom Python / Bun script
Section titled “Custom Python / Bun script”Verdict: Maximum control but maximum maintenance cost. Rejected outright: reinvents what linters already provide, and the “maintenance overhead” axis is the primary reason to use a tool rather than write one.
Recommendation detail
Section titled “Recommendation detail”rumdl (primary)
Section titled “rumdl (primary)”One lefthook pre-commit command; no custom script.
.rumdl.toml (live at the repo root; empirically tested against the full corpus — see the rumdl evidence under Options considered):
[global]flavor = "obsidian"# MD033 HTML: reviewer-enforced. MD041 first-line-H1: frontmatter precedes.# MD045 flags `embeds` as alt-less images. MD071 inserts a blank line# after frontmatter. MD060 (table format) is opt-in and stays off.# MD057/MD051/MD052 (broken links, fragments, labels) and MD024 (duplicate# sibling headings) stay disabled until a follow-up link-repair task.extend-disable = ["MD033", "MD041", "MD045", "MD071", "MD024", "MD051", "MD052", "MD057"]# Build artifacts (regenerated, never formatted), the generated Astro# site mirror, and the markdown-fixtures gate's fixture files.exclude = [ "docs/index.md", "docs/glossary.md", "docs/references.md", "sites/df-docs/src/content/docs", "plugin/lib/services/gate/tests/fixtures/markdown",]
[MD003]style = "atx"
[MD004]style = "dash"
[MD013]line-length = 100reflow = true# S-0007 exempts headings, fenced code, and tables from line length.headings = falsecode-blocks = falsetables = false
[MD025]front-matter-title = "" # body H1 + frontmatter title: is the house convention
[MD029]style = "ordered"
[MD046]style = "fenced" # default "consistent" converted a fence to an indented blockPer-file MD013/MD032 carve-outs exempt lines that are a single
irreducible inline-code span (a shell command, type signature, or
path in backticks); the live file enumerates them.
Lefthook integration (addition to lefthook.yml):
pre-commit: commands: markdown-fmt: run: bunx rumdl@0.2.9 fmt {staged_files} glob: "*.md" stage_fixed: truePin the version: pre-1.0 cadence is fast (0.2.8 and 0.2.9 shipped on consecutive days); bumps are deliberate, not implicit.
quality_checks additions (sdlc.yaml):
- bunx rumdl@0.2.9 check docs plugin- bun plugin/cli/sdlc.ts gate markdown-fixturescheck exits 0 over the corpus: the residual findings (71 broken
relative links via MD057, 26 duplicate sibling headings via MD024,
over-long unwrappable inline-code lines via MD013) are triaged in
.rumdl.toml — the link rules disabled pending a follow-up repair
sweep, the irreducible lines carved out per file. gate markdown-fixtures pins the formatter’s reflow behavior against this
decision’s stress fixtures, so a rumdl version or config bump that
changes reflow fails the gate instead of mangling the corpus.
One-time corpus reformat (shipped via T-0008, PR #356):
- Hand-fixed the three displaced-anchor lines (C-0005, C-0007, P-0003-pragmatic-architecture) before the reformat.
- Ran
bunx rumdl@0.2.9 fmt docs pluginonce: ~7,000 fixes across 433 files, wikilink/embed multiset preserved. - MD040 auto-tagged 447 bare fences with
text; upgrade to real language tags opportunistically.
markdownlint-cli2 + custom prose-wrap script (fallback hybrid)
Section titled “markdownlint-cli2 + custom prose-wrap script (fallback hybrid)”Two lefthook pre-commit commands run sequentially:
- markdownlint-cli2 —fix — auto-corrects list markers, heading style, trailing whitespace, final newline. MD040 (code-fence language) is flag-only; the 447 bare fences need a hand pass.
- bun run plugin/scripts/wrap_prose.ts — custom Bun script that
wraps prose paragraphs at 100 columns while skipping YAML
frontmatter blocks, table rows, fenced code blocks, headings,
lines containing wikilinks/transclusion targets, and lines ending
in a block-id (
^anchor) — the displacement hazard confirmed in the rumdl corpus test applies to any wrap implementation.
.markdownlint-cli2.yaml
Section titled “.markdownlint-cli2.yaml”config: MD003: style: atx # ATX headings only MD004: style: dash # - list markers only MD009: true # no trailing whitespace MD013: false # line length: prose-wrap script owns this MD025: front_matter_title: "" # body H1 + frontmatter title: is the house convention MD029: style: ordered # 1. 2. 3. explicit numbering MD033: false # HTML: reviewer-enforced, not formatter MD040: true # fenced code requires language tag (flag-only) MD041: false # first line need not be H1 (frontmatter precedes) MD047: true # files end with single newlineglobs: - "docs/**/*.md" - "plugin/skills/**/*.md" - "plugin/lib/**/*.md"fix: trueLefthook integration (additions to lefthook.yml):
pre-commit: commands: markdown-lint-fix: run: bunx markdownlint-cli2 --fix {staged_files} glob: "*.md" markdown-prose-wrap: run: bun run plugin/scripts/wrap_prose.ts {staged_files} glob: "*.md"Migration to dprint (tertiary watch)
Section titled “Migration to dprint (tertiary watch)”Watch
dprint/dprint-plugin-markdown#180. If
a "tables": "preserve" option ships, dprint’s Obsidian-feature safety is already confirmed — but
it would still need a prose-wrap answer (textWrap: maintain does not enforce the 100-column rule),
so it competes with the fallback hybrid, not with rumdl.
Consequences
Section titled “Consequences”- T-0008 authored
.rumdl.toml, wired the lefthook command, the quality_checks lines, and thejust fmt-markdown/check-markdownrecipes, hand-fixed the three anchor lines, and ran the one-time corpus reformat (~7,000 fixes across 433 files). The/markdown-formatskill is the on-demand, PR-producing surface. - No custom prose-wrap script. Prose wrap, list markers, heading
style, code-fence language tags, trailing whitespace, and final
newline are all rumdl auto-fixes; authors get them on commit and
can run
bunx rumdl fmt <file>manually. - Tables, wikilinks, transclusions, block-ids, and frontmatter are untouched by the toolchain (verified by full-corpus round-trip); they remain the author’s and the schema validator’s domain.
- MD057 relative-link validation comes free in
checkmode — it covers the broken-link capability D-0008-markdown-standard deferred — but stays disabled in.rumdl.tomluntil a follow-up task repairs the 71 existing broken links. - The rumdl version is pinned; bumps are deliberate. If the
single-maintainer risk materializes, fallback to the hybrid is
cheap (markdownlint rule numbering,
rumdl import). - Two upstream bug reports to file against rvben/rumdl (embed-glue, anchor displacement), tracked as B-1ATA; drop the reflow caveats when they land.
- Skill prose (
plugin/skills/**/*.md) stays in scope at full strictness per the Standard’sapplies_to.paths; irreducible inline-code lines get per-file carve-outs in.rumdl.toml. (rumdl processes.mdonly, so.etatemplate bodies are out of scope by construction.)
Out of scope
Section titled “Out of scope”- Implementation and one-time corpus reformat: T-0008.
- Broken-link checking as a gate — rumdl’s MD057 makes it available, but it stays disabled until a follow-up link-repair task; enabling it is not part of this decision.
- Spell-checking / SDLC-casing enforcement (reviewer-enforced per the Standard).
Open questions
Section titled “Open questions”- Upstream fixes: file the embed-glue and anchor-displacement reflow bugs against rvben/rumdl (tracked as B-1ATA); if reflow becomes Obsidian-aware, drop the displaced-anchor caveat.
- ADR filename follows D-0002-entity-identifier-shape:
D-0011-prefix plus slugmarkdown-formatting-tool. - Round-trip tests run 2026-06-05 in an isolated
/tmp/directory against five real corpus files plus a synthetic Obsidian-features fixture. No formatter output was committed to the repo. - Prettier version under test: 3.8.3 (via
bunx). dprint version: 0.54.0 (CLI) + markdown plugin 0.22.1 (WASM). rumdl version: 0.2.8 viabunx rumdl@latest; 0.2.9 released during testing — later corpus passes may have resolved it, with consistent results. - Performance numbers: dprint corpus pass 104ms vs Prettier 3.9s on 399 files; single-file dprint ~12ms vs Prettier ~100ms. Both well under the 30s / 500ms budgets.
- Corpus-size labels: the Prettier/dprint numbers were measured on the docs/ tree as of 2026-06-05 (399 files); the rumdl numbers cover every tracked .md in the repo (660 files as of 2026-06-06).
- rumdl tests run 2026-06-06 in an isolated
/tmp/directory: the synthetic-fixture round-trip, a reflow stress fixture, and a forensic full-corpusfmtpass over a git-baselined copy (multiset and placement diffs for wikilinks, embeds, anchors, tables, frontmatter; idempotence check). No formatter output was committed to the repo. - Registration: decisions surface via the generated
docs/index.md(sdlc docs generate); no D-ORMG roster row is needed.