Find pages that have no links to or from other pages. A page is orphaned if: - No other page links to it (no incoming links), AND - It has no outgoing wikilinks itself. index.md is excluded from orphan detection. Args: wiki: Path to the wiki root directory. Return
(wiki: Path)
| 290 | |
| 291 | |
| 292 | def find_orphans(wiki: Path) -> list[str]: |
| 293 | """Find pages that have no links to or from other pages. |
| 294 | |
| 295 | A page is orphaned if: |
| 296 | - No other page links to it (no incoming links), AND |
| 297 | - It has no outgoing wikilinks itself. |
| 298 | |
| 299 | index.md is excluded from orphan detection. |
| 300 | |
| 301 | Args: |
| 302 | wiki: Path to the wiki root directory. |
| 303 | |
| 304 | Returns: |
| 305 | List of relative page paths that are orphaned. |
| 306 | """ |
| 307 | # Exclude index, schema, log, and sources/ (sources are auto-generated, not expected to be linked) |
| 308 | all_mds = [ |
| 309 | p |
| 310 | for p in wiki.rglob("*.md") |
| 311 | if p.name not in {"index.md", *_EXCLUDED_FILES} |
| 312 | and "sources" not in p.relative_to(wiki).parts |
| 313 | ] |
| 314 | if not all_mds: |
| 315 | return [] |
| 316 | |
| 317 | # Build outgoing links per page |
| 318 | outgoing: dict[str, set[str]] = {} |
| 319 | for md in all_mds: |
| 320 | rel = str(md.relative_to(wiki).with_suffix("")).replace("\\", "/") |
| 321 | text = _read_md(md) |
| 322 | outgoing[rel] = set(_extract_wikilinks(text)) |
| 323 | |
| 324 | # Build incoming link set (which pages are linked to) |
| 325 | incoming: set[str] = set() |
| 326 | for links in outgoing.values(): |
| 327 | for lnk in links: |
| 328 | cleaned = lnk.strip().strip("/") |
| 329 | incoming.add(cleaned) |
| 330 | # Only treat a link as a bare-stem reference when it is itself a |
| 331 | # bare stem. A qualified link like ``concepts/foo`` must not mark a |
| 332 | # same-stem page in another directory (``summaries/foo``) as linked, |
| 333 | # which would hide a genuine orphan. |
| 334 | if "/" not in cleaned: |
| 335 | incoming.add(Path(cleaned).stem) |
| 336 | |
| 337 | orphans: list[str] = [] |
| 338 | for rel, links in outgoing.items(): |
| 339 | stem = Path(rel).stem |
| 340 | has_incoming = rel in incoming or stem in incoming |
| 341 | has_outgoing = bool(links) |
| 342 | if not has_incoming and not has_outgoing: |
| 343 | orphans.append(rel) |
| 344 | |
| 345 | return sorted(orphans) |
| 346 | |
| 347 | |
| 348 | def find_missing_entries( |