xref: numbered cross-references to stable names, safe to rerun¶
The problem¶
Documents that cite each other by number ("as Paper 12 showed") break when one is added, dropped or reordered, and a reader holding one file cannot tell what "Paper 12" was. Titles do not drift. The usual fix, a regex and a dictionary, is not idempotent: a replacement that keeps the number ("Goal 1" to "Goal 1 (direct democracy)") still matches, so each rerun adds another "(direct democracy)". It also rewrites code blocks and URLs, and turns "Papers 2-4" into nonsense.
The method¶
Prove it before you write it. The file is rewritten in memory, then the same rewrite is run over the result. If the second pass would change anything, the file is refused and the first offending change named. Nothing is written.
- Already expanded is left alone: a match whose text already starts with its replacement is skipped, so keep-the-number templates are safe.
- Protected: fenced and inline code, URLs, link targets, "Paper No. 5"
labels;
Paper 1never matches insidePaper 120orWallpaper 1. - Reported, never guessed: plurals, ranges, lists and numbers missing from the map, each with file and line.
- A document citing itself gets "this paper";
# Paper 2: Titleheadings lose the number. --checklists every numbered reference left and every(X) (X)double, exit 1 if any. Given the map and template, a reference already in its expanded form counts as done.--undoublerepairs doubles an older script left, and needs no map on its own.
Sample¶
new, changes, unhandled = x.once(text, slug)
again, more, _ = x.once(new, slug)
if again != new: # the rewrite feeds itself
refuse(path, more[0]) # nothing written
else:
write_atomic(path, new)
# inside once(): never re-expand
if text.startswith(rep, m.start()):
continue
Use it:
xref -m map.tsv docs/*/manuscript.md # what would change
xref -m map.tsv --as '(Author, 2026, "*{title}*")' --write docs/*/manuscript.md
xref -m goals.tsv -w Goal --as 'Goal {num} ({title})' --write notes.md
xref --check -w Section docs/*.md # exit 1 if anything is still numbered
xref --check -m goals.tsv -w Goal --as 'Goal {num} ({title})' notes.md # expanded refs count as done
xref --undouble --write old.md # only fold "(X) (X)"
Limits¶
- One word per run, case as given.
- A bare number after a comma or "and" counts as a list and is reported, so "Paper 12, 2026 data" is left for a person.
- Code spans across lines and HTML
<code>are not protected.
Code¶
methods/xref/ in the methods repository: the script, a README and a
50-check test. Tried live: an older one-pass script, run twice on the same
file, wrote "Goal 1 (direct democracy) (direct democracy)"; xref run three
times leaves "Goal 1 (direct democracy)".