Skip to content

fix(decision-records): carry a promotion through the whole tree - #9

Merged
trungfinity merged 1 commit into
mainfrom
fix/decision-records-promotion-rewrites
Aug 8, 2026
Merged

trungfinity merged 1 commit into
mainfrom
fix/decision-records-promotion-rewrites

Conversation

@trungfinity

Copy link
Copy Markdown
Contributor

promote changes a draft's ID and its directory, but only ever acted on the ID. The gap was five corrections made by hand on every promotion — the five most recent ones downstream (AUTH→0035, MEET→0036, VIEW→0037, CORR→0038, TMPL→0039) each needed all of them.

What the tool now does

  • The H1 takes the counter. # CONF — <title># 0016 — <title>, the shape every accepted record already has. Handles a backticked ID, an H1 that doesn't lead with the mnemonic (prefixes and warns), and a missing one (builds it from the front-matter title, rendering the title's own em-dash as a colon). rename-draft-id does the same.
  • Every relative link follows the move. A record lands one directory deeper, so hand-authored paths were left short. They're now re-pathed by path math against the new location, which covers the glossary, research memos, sibling docs and repo-root scripts uniformly rather than the subset check_links happened to flag. A link labelled with its own path moves label and target together. Anchors, URLs and absolute paths are untouched.
  • The mnemonic stops reading anywhere in docs/. Previously only linked mnemonics converted, leaving bare prose beside a fresh counter link in the same sentence, and the promoted record still calling itself CONF. The sweep now covers bare prose in the body and in front-matter prose such as summary, plus a spelled-out path to the draft file (docs/decisions/drafts/CONF-x.md).
  • promote exits non-zero if a link is still broken, rather than printing and returning 0.

Two deliberate limits

A four-letter mnemonic doubles as a plausible identifier — AUTH = "auth" in an enum, MEET = "https://…" as a scope constant, VIEW = "view" in a vendored, lint-excluded file where a bad rename is a syntax error that passes lint silently. So the sweep stays out of fenced blocks, inline code spans and link targets, and edits nothing above docs/. What it leaves behind is listed after the run for a human to work through.

The --deref bug

--deref inverted the front-matter edge correctly but detected body references with a pattern whose lookbehind is blind to [CONF](…). Since build --relink turns every bare ref into a link, the blocking check went blind after the first build and --deref promoted straight into a breach that check reported afterwards. Both call sites now share one body_ref_ids helper, so it refuses up front. Also fixes rewrite_reference writing supersedes: with a trailing space onto every record it round-trips — tens of unrelated files dirtied per promotion, which the trailing-whitespace hook then stripped back.

Verification

Replayed all five real promotions from the commit before each, ran mdformat, and diffed against the hand-corrected commits: VIEW, CORR and TMPL are byte-for-byte identical; AUTH differs only by a factual content edit the author made by hand; MEET differs only where this is more correct — it fixes the self-reference that had needed a follow-up commit downstream.

16 new tests (82 total, up from 66), covering each fix and both exclusions. validate_skills.py, the skill's pytest suite, and pre-commit run --all-files are clean.

Promoting a draft changes its ID and its directory, and the tool only
ever acted on the ID. Five corrections were then made by hand on every
promotion; the last five in a downstream repo each needed all of them.

The H1 kept the mnemonic while every accepted sibling reads
`# NNNN — <title>`, so promote now rewrites it (and rename-draft-id
does the same). A record moves one directory deeper, so every
hand-authored relative link was left short: they are re-pathed by path
math against the new location, which covers the glossary, research
memos and repo-root scripts alike rather than the subset check_links
happened to flag. A link labelled with its own path moves label and
target together, and promote now exits non-zero if anything is still
broken instead of reporting and returning 0.

Only linked mnemonics were converted in referring records, leaving
bare prose beside a fresh counter link in the same sentence, and the
promoted record still called itself by its mnemonic. The sweep now
covers bare prose in the body and in front-matter prose such as
summary, plus a spelled-out path to the draft file. It stays out of
fenced blocks, inline code spans and link targets, and out of
everything above docs/ — a four-letter mnemonic doubles as a plausible
identifier, and renaming one is not what promoting a record means.
What it leaves behind is listed for a human instead.

rewrite_reference wrote `supersedes: ` with a trailing space onto every
record it round-tripped, dirtying tens of unrelated files that the
trailing-whitespace hook then stripped back.

--deref inverted the front-matter edge but read body references with a
pattern blind to markdown links, so after the first `build --relink`
it saw none and promoted into a breach that check caught afterwards.
Both call sites now share one helper and it refuses up front.

Verified by replaying five real promotions from the commit before
each: the output matches the hand-corrected result byte for byte
after mdformat, except one self-reference this fixes that had needed
a follow-up commit.
@trungfinity
trungfinity merged commit a3cd93c into main Aug 8, 2026
10 checks passed
@trungfinity
trungfinity deleted the fix/decision-records-promotion-rewrites branch August 8, 2026 21:30
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