Skip to content

chore(docs): clear the markdownlint backlog - #260

Merged
StefanSteiner merged 7 commits into
tableau:mainfrom
StefanSteiner:chore/markdownlint-backlog
Sep 6, 2026
Merged

chore(docs): clear the markdownlint backlog#260
StefanSteiner merged 7 commits into
tableau:mainfrom
StefanSteiner:chore/markdownlint-backlog

Conversation

@StefanSteiner

Copy link
Copy Markdown
Contributor

No issue — this clears accumulated lint debt.

Takes the repo from 126 markdownlint findings to 0:

  • 98 genuinely fixed
  • 21 scope-relaxed
  • 7 excluded

What is excluded, and why

The root CHANGELOG.md is excluded rather than fixed: it is release-please-generated, so hand edits get clobbered on the next release.

docs/superpowers/**

Gets a nested .markdownlint.json that disables only MD013 and MD046. Specs and plans still get fence and heading feedback, while settled prose isn't reflowed.

extends is load-bearing there, because a nested config replaces ancestor configuration rather than merging into it. Omitting extends takes the repo from 5 findings to 92, including 70 MD060 — the rule this project deliberately disabled after a Markdown formatter stripped the README's badge links.

AGENTS.md gains a fourth trap in its markdownlint reminder recording that replace-not-merge behaviour, so the next person doesn't have to rediscover it.

Verification

  • All 554 code blocks across 69 files verified unchanged by content hash.
  • The MD040 commit is 63 insertions / 63 deletions, where every changed line is a fence line.
  • Re-linted after the rebase below: 0 issues in 0 files across 68 linted files.

Rebase note

This branch was based on fix/watchdog-flake-and-mb-units, which has since merged as #256. It has been rebased onto main, and the diff is now its own seven commits only — the two commits inherited from #256 are gone. The rebase was clean, and the replayed content is byte-identical to the pre-rebase diff (only hunk offsets moved).

Review status

Important

This branch has not had adversarial code review either, unlike #257 and #258 — though it carries unusually strong self-verification, as above.

…wnlint

Two path exclusions, dropping 33 of the 126 findings without touching a
document. Both configs get the entry because markdownlint-cli2 reads only
.markdownlint-cli2.jsonc and the editor extension reads only
.markdownlintignore; letting them drift means the editor flags what CI-adjacent
tooling ignores, or worse, the reverse.

Root CHANGELOG.md (7 findings) is release-please output -- it is the sole
changelog-path in release-please-config.json, so AGENTS.md forbids hand-editing
it and the next release would clobber any fix anyway. Its findings are exactly
that generator's shape: four MD012 double-blank-lines between release sections
and three MD013 over-long generated commit lines. The exclusion is deliberately
the bare path, not **/CHANGELOG.md, because the nine per-crate changelogs are
hand-maintained and must stay linted. In .markdownlintignore the leading slash
is what buys that, since it reads gitignore patterns where an unanchored
CHANGELOG.md matches at every level; the cli2 list is globs, so it needs none.
Verified with the `ignore` package the extension uses: root ignored, all nine
per-crate changelogs still linted.

docs/superpowers/** (26 findings) are point-in-time planning artifacts.
AGENTS.md describes them as a spec and plan written before a feature and
reviewed alongside the change that implements it; nothing maintains them after.
The findings are dominated by prose shape rather than defects -- 13 MD013 lines
of 500-1549 characters and 8 MD046 indented blocks inside a 1100-line plan --
so fixing them means a large reflow diff through settled records, which is also
where the risk of mangling a code block is highest and the payoff lowest.
Excluded specs alongside plans: same artifact class, and it keeps the question
from being relitigated the first time a spec trips a rule.

Not excluded for lack of a reader: excluded for lack of a maintainer.
Clears MD040 to zero across 20 files. 61 blocks take `text`, per the repo
convention for command output, ASCII diagrams, error messages and templates:
architecture and data-flow diagrams, source-tree listings, hyperd/CLI --help
output, stress-test result summaries, rustc error text, commit-message
templates, and the MCP tool-call transcripts in the hyperdb-mcp README,
SMOKE_TESTS and ROADMAP. Those transcripts read like a function call but match
no real grammar -- `kv_set store=smoke key=k value=v  -> {...}` is neither
Python nor JS -- so `text` is the honest tag rather than borrowing a highlighter
that would mis-colour it. Two blocks got a real language: a `cargo run`
invocation in ROW_MAPPING (bash) and a table schema with an SQL comment in
BENCHMARK_GUIDE (sql).

Fixed by line number, never by pattern. A naive bulk-fixer corrupted 176 fences
here once, because tagging an opener stops it matching a bare-fence test, so the
CLOSER matches instead and becomes a spurious opener. The tagger is instead
handed opener line numbers from a CommonMark state machine and asserts, per
fence, that the line is both a bare fence and classified as an opener; anything
else aborts. The state machine independently reproduced markdownlint exactly --
68/68 MD040 line numbers and 9/9 MD046 -- before it was trusted.

Structural proof, all 69 in-scope files: block count and a SHA of every block's
content are unchanged; only info strings differ. Independently, the diff is
63 insertions and 63 deletions with every changed line a fence line, so no
content line entered or left a block. The one 3-space-indented fence kept its
indent. Nothing was added or removed, which also leaves hyperdb-mcp's
README-coupled tests intact: doctor_readme_contract keys off a line window
around exact `hyperdb-mcp doctor` lines (still at 286/287) and doctor_tests
splits on `## CLI Reference` then does substring checks, none of which a fence
info string can perturb.
…24/046)

Clears every remaining non-MD013 rule: 11 MD025, 2 MD036, 2 MD024, and one
each of MD001, MD041 and MD046. 93 findings down to 12.

CONTRIBUTING.md had 12 H1s, one per top-level section, so every section
claimed to be the document title. Demoted the 11 that are not the title to H2
and their H2 children to H3. The three H3s under "Issues, requests & ideas"
deliberately stayed H3: they were the MD001 finding, an H1 -> H3 skip, and
bringing their parent down to H2 makes them correctly incremented where they
already are. Anchors are unaffected, which is worth stating because re-levelling
is usually where anchors break: a GitHub slug derives from heading text only --
the hash count picks the h1..h6 tag, not the id. Verified rather than assumed,
by running github-slugger over the file before and after: 22 anchors, identical
sequence, and all six inbound links still resolve (#release-process,
#commit-message-format, #contribution-checklist from AGENTS.md,
hyperdb-api-node/DEVELOPMENT.md and docs/GITHUB_OPERATIONS.md; #what-contributors-do;
plus intra-document #creating-a-pull-request and #commit-types-and-version-impact).
No link needed updating.

hyperdb-mcp/CHANGELOG.md carried "### Changed" and "### Fixed" twice under one
"## [Unreleased]", the classic case of appending a second section instead of
merging. Merged them and restored Keep a Changelog order (Added, Changed,
Fixed), keeping each source block's internal order. That order is load-bearing
here because two bullets cross-reference their own position: "supersedes the
shorthand in the Added notes above" stays true since Added now sorts first, and
"supersedes the older Unreleased note below" is intra-section -- both it and
the note it supersedes are in the same Fixed block, untouched. Proved lossless
by comparing the multiset of non-heading lines in the section: identical, with
the two duplicate headings the only lines removed.

BENCHMARK_GUIDE.md used a bold "**Hardware / software**" label as a heading in
all four platform sections. Two were flagged; the other two escaped only
because trailing italic placeholder text meant the line was not pure emphasis.
Converted all four to the "#### Hardware / software" they were imitating, so
the platform sections render alike. The placeholder notes became italic
sentences ending in a period, matching the file's existing placeholder style --
a bare "*(placeholder)*" on its own line would have been a fresh MD036, since
that rule exempts emphasis ending in punctuation and ")" does not qualify.

SECURITY.md opened at H2 with no H1. Promoted it; same text, so the slug is
unchanged, and nothing links to the file anyway. README.md's RHEL install
snippet was indented rather than fenced -- now a bash fence.

Structural proof over all 69 in-scope files: block count and per-block content
SHAs unchanged. The README conversion shows as "1 indented->fenced, content
identical", since the auditor hashes indented and fenced blocks alike, so the
dedent is provably lossless. hyperdb-mcp/CHANGELOG.md holds no code blocks, so
the section merge could not disturb one.
Takes the count to zero. All 12 were single unwrapped paragraphs or changelog
bullets of 529-1104 characters, in files whose median prose line is 68-79, so
each was an outlier against its own file rather than something inherently
unwrappable. Wrapped at 79. No inline disable was needed anywhere, and MD013
stays enabled globally.

The rule's own configuration is why the set is this small and this clean:
line_length is 500 with tables and code_blocks exempt, so long table rows and
code never appear, and none of the 12 lines contains a link or a bare URL --
checked before wrapping, since a break inside a link destination is exactly the
corruption that MD060 was disabled over.

Wrapped by a tokenizer that treats code spans as atomic, so no break can land
inside one, and that indents list continuations to the bullet's content column
so the text stays in its list item. Every line is asserted to round trip:
stripping the continuation indent and rejoining with single spaces has to
reproduce the original line exactly, or the wrap aborts. Verified again
independently afterwards, whole-file this time -- whitespace-normalized text is
identical to the previous commit for all five files, so no word, code span or
punctuation mark moved.

hyperdb-mcp/README.md is the one file whose tests key off line positions, and
the wrap shifted them by seven. Re-checked by re-implementing
doctor_readme_contract's window logic against the edited file: the exact
`hyperdb-mcp doctor` and `doctor --json` lines are still found, and the -12/+21
window around them still contains the "side-effect-free" and "does not start"
phrases the test requires. The doctor_tests `## CLI Reference` extraction and
its four substring checks also still pass, as does the `daemon ` foreground
line. Static simulation, not an executed cargo run -- this is a docs-only
change and no test was run.
Replaces the blanket docs/superpowers/** exclusion from 0b442c5 with a
directory-scoped relaxation of the two rules that would force a reflow. The
argument against excluding won: that tree is not a static archive. AGENTS.md
mandates a spec and plan for every non-trivial feature, so new files land there
regularly, and an exclusion leaves the editor silent exactly while one is being
authored. The rules worth having then -- untagged fences, heading structure --
are precisely the ones an exclusion would suppress. A relaxation keeps them and
drops only MD013 (plan prose runs past 1500 characters per line) and MD046
(plans mix indented and fenced blocks), which are the two that would demand
rewriting a settled record. The root CHANGELOG.md exclusion is untouched; that
one is generated, which is a different argument entirely.

The mechanism is a nested docs/superpowers/.markdownlint.json, so one file
governs both tools: markdownlint-cli2 resolves configuration per directory, and
the extension's documented precedence is a ".markdownlint.{jsonc,json,...} file
in the same or parent directory", so it walks up from whatever file is open.
Confirmed both rather than assumed. The CLI now lints 68 files instead of 56,
reports 0 issues, and the 1_88_uplift subdirectory two levels below the config
went from 8 baseline findings to 0, which shows the config governs recursively
and not just its own directory. The extension side is documented behaviour plus
its own guidance that a CLI-first setup needs no further change to behave the
same in the editor. .markdownlintignore loses its docs/superpowers/ entry, and
the `ignore` package the extension uses agrees: the tree is linted at every
depth, root CHANGELOG.md still ignored, all nine per-crate changelogs still in
scope.

The "extends" line is load-bearing and nearly went in as decoration. A nested
config REPLACES ancestor configuration rather than merging with it -- measured,
after a first attempt to measure it drew the opposite conclusion off a bad
probe (a no-spaces long line, which MD013 never flags in its default
non-strict mode). Dropping just the extends from this file takes the repo from
5 findings to 92: 70 MD060 and 17 MD010. MD060 is the rule this project
disabled because a formatter satisfying it stripped the README's badge links,
so a naive nested config would have quietly reintroduced it across the plan
files. Extending the root config inherits all of it and overrides only the two
rules named above.
Takes the repo to zero findings with docs/superpowers now linted. These are the
5 that 5185ad7 left behind because the tree was excluded at the time.

Two blocks in the kv-store-m2 plan take `markdown`: both are Markdown intended
to be pasted elsewhere -- a "### Key-value store (scratchpad)" section destined
for readme.rs, and two changelog bullets for hyperdb-mcp's Unreleased section.
The three in the LLM-ergonomics plan take `text`: each is a prose fragment to
be appended to a #[tool(description = ...)] string, and two of them open with a
deliberate leading space for that concatenation, which is preserved untouched
since only the fence line is rewritten.

Same tagger and same proof as 5185ad7, which matters more here than anywhere
else: these are the longest files in the repo and the ones nobody re-reads, so
a cascade would sit undetected. Checked first that no target block contains a
fence line of its own -- the markdown block was the real risk, since a snippet
documenting Markdown is how nesting shows up -- and the tagger still asserts
per fence that the line is both a bare fence and classified as an opener by the
state machine.

Structural proof over all 69 files, against the branch base: 554 code blocks
before and 554 after, every block's content SHA identical, no unclosed fence.
Untagged fenced blocks are now 0 repo-wide, down from 68 at baseline.
Independently, the diff is 5 insertions and 5 deletions with every changed line
a fence line, so no content line entered or left a block.
…merges

Adds a fourth trap to the markdownlint reminder. It belongs with the other
three for the reason the section exists: markdownlint is not a CI gate, so a
headless agent gets no feedback at all, and an agent adding a nested config
elsewhere in the repo would have no reason to open
docs/superpowers/.markdownlint.json and discover this. The full rationale stays
in that file's comments; the reminder just names the trap and the fix.

The failure mode is what makes it worth a bullet. Omitting `extends` does not
make the document wrong, it makes the linter wrong -- it reports fewer or
different findings while looking like it works, and here it would silently
re-enable MD060, the one rule this repo has already been burned by when a
formatter satisfying it stripped the README's badge links. Placed last so it
sits directly above the paragraph explaining why MD060 is disabled.

Mirrored the neighbouring bullets exactly rather than trusting the rendered
view, which normalizes whitespace: on this list the `-` marker sits at column 0
and continuations at one space, so the bullets are a sibling top-level list
rather than nested under item 3. Indenting a new bullet even one space makes it
a badly-indented nested list and trips MD007, which has happened here before.
Verified on both engines -- `markdownlint-cli2` latest and the 0.23.2 the
installed extension (DavidAnson.vscode-markdownlint 0.62.1) declares -- 68
files, 0 issues, exit 0 on each. Latest currently resolves to 0.23.2, so there
is no version skew between editor and CLI at all right now.
@StefanSteiner
StefanSteiner force-pushed the chore/markdownlint-backlog branch from b339b31 to a388db8 Compare September 6, 2026 07:09
@StefanSteiner
StefanSteiner merged commit 5b62831 into tableau:main Sep 6, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant