Skip to content

B-KP76-backlog-create-rumdl-wrap-heading-corruption

The rumdl@0.2.9 fmt step in apps/sdlc/cli/backlog_cli/create.ts (writeCommitPush) re-wraps body prose; when a token like #1146 lands at a line start after the wrap, it gets promoted to a ## 1146 ... heading — the sentence is split and a bogus H2 is injected. The file still passes schema validation and the CLI exits 0, so the corruption ships silently onto the capture PR. Observed on B-EANZ (captured via sdlc backlog create); repaired by hand on the backlog-capture branch. Recovery trap: re-running create with the same slug would mint a duplicate id, because the slug-reuse probe only scans the main checkout’s docs/planning/backlog/ (the file exists only on the capture branch until merge), and backlog update is frontmatter-only, so there is no CLI path to fix a corrupted body. Suggested fixes: escape or guard line-start ’#’ during the wrap, or run fmt before commit with a post-fmt structural diff check (fail if fmt introduced new headings); additionally make the slug-reuse probe scan the capture branch so re-capture is actually idempotent.

Resolved — the heading structure is compared across the format

Section titled “Resolved — the heading structure is compared across the format”

writeCommitPush now records the body’s ATX headings before rumdl fmt, re-reads the file after, and refuses to commit if they moved. Nothing is staged, committed or pushed on a mismatch, and the error names the heading that appeared plus the fix (put the token in a link or a code span, or reword). mintIdentity is a pure scan of the backlog directory, so an aborted run consumes no id — re-running after an edit is clean.

The comparison is structural, on (depth, text), so whitespace normalisation is not a change while an invented heading is. #1146 is deliberately not counted as a heading: CommonMark requires a space after the hashes, which is precisely what MD018 adds to manufacture one.

Two things ruled out on the way. Disabling MD018 for this call site does stop the promotion — verified — but leaves #1146 at line start, and the commit is not --no-verify, so lefthook’s own markdown-fmt applies the same rules moments later; it also leaves a landmine for the next fmt of that file. Escaping the # to \# is durable but has to avoid code spans, link text and URL fragments, and a bad regex mangling bodies is a worse failure than a loud refusal.

Still open, and deliberately not fixed here: the slug-reuse probe scans only the main checkout’s docs/planning/backlog/, so re-capturing a slug that exists solely on the capture branch mints a duplicate id. That was recorded above as the recovery trap for a corrupted body; with the guard in place no corrupted body ships, so the trap is unreachable by this route, but the idempotency gap itself is real and independent. It wants a separate change — the probe has to read origin/backlog-capture, and mintIdentity is a general entity helper shared with the other types.

Re-reviewed against the T-6XZZ-polish-bump-rumdl-formatter binary bump (0.2.65 → 0.2.77): the fix here is create.ts’s own structural-diff guard, not upstream rumdl behavior, so the guard holds regardless of the pinned version. Nothing in the 0.2.66–0.2.77 changelog touches MD018 or line-start #-token handling.


← Back to Backlog