knowledge-base/scripts/fix-wiki-links.py
Hector 0e02fdbd59 feat: people-pages for all TODO institutions markers
- Created 27 new people-pages for all TODO-marked persons
- Replaced all TODO: people-page markers with wiki-links in institutions
- Cross-referenced back to institutions from each people-page
- 0 TODO markers remaining

New people-pages:
  shayne-coplan, jim-zemlin, wang-changhu, yang-bingyang, demis-hassabis,
  clement-delangue, thomas-kurian, fei-fei-li, christopher-manning,
  sam-altman, ilya-sutskever, moritz-kaminski, elon-musk, nicolas-burtey,
  elizabeth-stark, olaoluwa-osuntokun, jan-leike, oren-etzioni,
  ali-farhadi, jaime-sevilla, max-tegmark, daniela-rus, jared-kaplan,
  alex-atallah, andrew-moore, tuomas-sandholm, mitchell-baker
2026-06-25 21:44:25 +02:00

175 lines
No EOL
5.7 KiB
Python

#!/usr/bin/env python3
"""
Fix broken wiki links in knowledge-base repo by rewriting them as proper
source-relative paths (the format Forgejo/Gitea Markdown rendering expects).
Bug classes handled:
A. [[wiki/...]] → strip 'wiki/' prefix, then resolve source-relative
B. [[concepts/...]] from wrong directory → resolve to ../concepts/...
C. [[concepts/...]] from same directory → resolve to basename
D. [[plain-name.md]] from wrong directory → resolve to ../.../plain-name.md
"""
import os
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
WIKI_DIR = REPO_ROOT / "wiki"
LINK_RE = re.compile(r'\[\[([^\]|]+)(?:\|([^\]]+))?\]\]')
def find_wiki_files():
files = []
for root, dirs, filenames in os.walk(WIKI_DIR):
for f in filenames:
if f.endswith('.md'):
files.append(Path(root) / f)
return sorted(files)
def find_target(link_path: str):
"""
Given a link path as written, find the actual target file.
Supports wiki/, raw/, and root-relative interpretations.
Returns absolute Path to the target file, or None if not found.
"""
basename = Path(link_path).name
if not basename.endswith('.md'):
basename_candidate = basename + '.md'
else:
basename_candidate = basename
candidates = []
# Interpretation 1: path starts with "wiki/" — strip and look in WIKI_DIR
if link_path.startswith('wiki/'):
stripped = link_path[5:]
if not stripped.endswith('.md'):
stripped += '.md'
candidates.append(WIKI_DIR / stripped)
# Interpretation 2: path starts with "raw/" — look in REPO_ROOT/raw/
if link_path.startswith('raw/'):
target = REPO_ROOT / link_path
if not target.exists() and not link_path.endswith('.md'):
target = REPO_ROOT / (link_path + '.md')
if target.exists():
candidates.append(target)
# Interpretation 3: path contains "/" — interpret as root-relative
if '/' in link_path:
# Try as wiki/-root-relative
if not link_path.endswith('.md'):
root_relative = WIKI_DIR / (link_path + '.md')
else:
root_relative = WIKI_DIR / link_path
candidates.append(root_relative)
# Also try as repo-root-relative
if not link_path.endswith('.md'):
candidates.append(REPO_ROOT / (link_path + '.md'))
else:
candidates.append(REPO_ROOT / link_path)
# Interpretation 4: plain basename — find by basename
if '/' not in link_path:
matches = [m for m in WIKI_DIR.rglob(basename_candidate) if 'test-fixtures' not in str(m)]
# Also check raw/
matches += [m for m in (REPO_ROOT / 'raw').rglob(basename_candidate) if 'test-fixtures' not in str(m)]
matches += [m for m in REPO_ROOT.rglob(basename_candidate) if 'test-fixtures' not in str(m)]
if len(matches) == 1:
candidates.append(matches[0])
elif len(matches) > 1:
non_fixture = [m for m in matches if 'test-fixtures' not in str(m)]
if len(non_fixture) == 1:
candidates.append(non_fixture[0])
# Try each candidate
for c in candidates:
if c.exists() and 'test-fixtures' not in str(c):
return c
return None
def make_source_relative(from_file: Path, to_file: Path) -> str:
"""Make a relative path from from_file's directory to to_file."""
rel = os.path.relpath(to_file, start=from_file.parent)
return rel
def fix_link(link_path: str, source_file: Path) -> str:
"""Fix a broken wiki link. Returns the corrected link path."""
if link_path.startswith('http'):
return link_path
# Strip anchor
anchor = ''
if '#' in link_path:
link_path, anchor = link_path.split('#', 1)
anchor = '#' + anchor
# Already valid? (source-relative resolves correctly)
target = (source_file.parent / link_path).resolve()
if target.exists():
return link_path + anchor
# Try with .md appended
if not link_path.endswith('.md'):
target = (source_file.parent / (link_path + '.md')).resolve()
if target.exists():
return link_path + '.md' + anchor
# Find the actual target file by interpreting the link in different ways
actual_target = find_target(link_path)
if actual_target and actual_target != source_file:
corrected = make_source_relative(source_file, actual_target)
return corrected + anchor
return link_path + anchor # no fix found
def process_file(source_file: Path, dry_run=False):
content = source_file.read_text(encoding='utf-8')
def repl(match):
link_path = match.group(1).strip()
label = match.group(2)
fixed = fix_link(link_path, source_file)
if fixed != link_path:
if label:
return f'[[{fixed}|{label}]]'
return f'[[{fixed}]]'
return match.group(0)
new_content = LINK_RE.sub(repl, content)
if new_content != content:
if not dry_run:
source_file.write_text(new_content, encoding='utf-8')
return True
return False
def main():
dry_run = '--dry-run' in sys.argv
files = find_wiki_files()
fixed_count = 0
fixed_files = []
for f in files:
if process_file(f, dry_run=dry_run):
fixed_count += 1
fixed_files.append(f.relative_to(REPO_ROOT))
print(f"{'Would fix' if dry_run else 'Fixed'}: {fixed_count} file(s)")
if fixed_files:
for rel in fixed_files:
print(f" - {rel}")
if dry_run and fixed_count > 0:
print("\nRun without --dry-run to apply changes.")
if __name__ == '__main__':
main()