Skip to content

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 1 never matches inside Paper 120 or Wallpaper 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: Title headings lose the number.
  • --check lists 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. --undouble repairs 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)".