feat(610): generated where-did-it-go index on each stripped file

Live docs point into the corpus BY DATE -- "see `decisions.md` 2026-07-10" --
about 32 such references across 12 files, plus the same form in historical issue
comments. The split would dangle every one of them.

Each stripped file now ends with a generated "Records formerly in this file"
index: date, title, and a link to the record's new path. A reader following a
date pointer lands on the file it names and resolves from there. That is far
cheaper and less error-prone than rewriting 32 references by hand, and it also
covers the issue-comment breadcrumbs, which cannot be rewritten at all.

Caught while verifying it: the generated `## Records formerly in this file`
heading is itself an H2, so the record parser counted one legacy-unmigrated
record per stripped file -- the notice went 0 -> 6. Same treatment as the
existing `## Index` section: skip it by name. SKIP_HEADINGS moved to
decisions_lib as the single source of truth, since three modules now need it.

Found by reading the validator's notice output on a trial migration, not by
inspection -- the corpus still validated OK, so nothing else would have flagged it.
This commit is contained in:
2026-07-25 19:08:58 +02:00
parent 8ec1f527e4
commit 64b65fd2db
4 changed files with 38 additions and 4 deletions
+32 -2
View File
@@ -93,6 +93,34 @@ def _rationale_body(rec: dl.Record) -> str:
return "\n".join(lines).strip()
_MOVED_HEADING = "## Records formerly in this file"
def _moved_index(records: list[dl.Record]) -> str:
"""A generated 'where did it go' index appended to each stripped file.
Live docs point into the corpus BY DATE — "see `decisions.md` 2026-07-10" — roughly 32 such
references across 12 files. The split would dangle every one of them. This index keeps them
resolvable: the reader lands on the file the pointer names and finds the date, the title, and
the record's new path. Cheaper and far less error-prone than rewriting 32 references by hand,
and it degrades gracefully for the historical issue comments that use the same form.
"""
if not records:
return ""
lines = [
_MOVED_HEADING,
"",
"Each record below moved to its own file under `records/` (ersatztv#610); the rationale is",
"unchanged. Resolve by **key** — that is the stable identity. A date-based pointer from",
"another doc or an old issue comment should land here and then follow the link.",
"",
]
for r in sorted(records, key=lambda x: (x.heading, x.key or "")):
dest = record_path(r).relative_to(dl.TOPIC_DIR)
lines.append(f"- {r.heading} — [`{r.key}`]({dest})")
return "\n".join(lines)
def _preamble(path: Path, records: list[dl.Record]) -> str:
"""Everything in `path` that is not inside a record — kept, never deleted."""
text = path.read_text(encoding="utf-8")
@@ -117,13 +145,15 @@ def plan() -> tuple[list[tuple[dl.Record, Path]], dict[Path, str]]:
for src in sources:
if dl.RECORDS_DIR in src.parents or dl.has_frontmatter(src.read_text(encoding="utf-8")):
continue # already migrated
recs = [r for r in dl.parse_file(src) if r.heading not in ("Index", "Active catalog", "Contents")]
recs = [r for r in dl.parse_file(src) if r.heading not in dl.SKIP_HEADINGS]
keyed = [r for r in recs if r.key]
if len(keyed) != len(recs):
unkeyed = [r.heading for r in recs if not r.key]
raise SystemExit(f"{src}: {len(unkeyed)} record(s) without a key, cannot file by key: {unkeyed}")
moves += [(r, record_path(r)) for r in keyed]
preambles[src] = _preamble(src, recs)
body = _preamble(src, recs)
idx = _moved_index(keyed)
preambles[src] = f"{body.rstrip()}\n\n{idx}" if idx else body
return moves, preambles