返回反向链接图:{被链接的页面 path: {链接它的源页 path, ...}} 命中策略:完整路径 > basename 自引用排除:页面 [[自己]] 不计入入链——与 web/lib/kb-service.ts 的 getBacklinks 口径保持一致;否则"健康度报告说没问题但点进去看才发现实际是孤儿"会出现。
(pages)
| 323 | |
| 324 | |
| 325 | def build_link_graph(pages): |
| 326 | """ |
| 327 | 返回反向链接图:{被链接的页面 path: {链接它的源页 path, ...}} |
| 328 | 命中策略:完整路径 > basename |
| 329 | |
| 330 | 自引用排除:页面 [[自己]] 不计入入链——与 web/lib/kb-service.ts 的 getBacklinks |
| 331 | 口径保持一致;否则"健康度报告说没问题但点进去看才发现实际是孤儿"会出现。 |
| 332 | """ |
| 333 | backlinks = {} |
| 334 | page_paths = {p.path for p in pages} |
| 335 | page_basenames = {Path(p).stem: p for p in page_paths} |
| 336 | |
| 337 | for page in pages: |
| 338 | # 剥离代码块 / 行内代码——代码示例里的 [[link]] 不是真实引用 |
| 339 | for link in parse_wikilinks(mask_code_spans(page.raw_content)): |
| 340 | target_norm = normalize_link_target(link.target) |
| 341 | if target_norm in page_paths: |
| 342 | if target_norm == page.path: |
| 343 | continue # self-link 不算入链 |
| 344 | backlinks.setdefault(target_norm, set()).add(page.path) |
| 345 | continue |
| 346 | stem = Path(target_norm).stem |
| 347 | if stem in page_basenames: |
| 348 | resolved = page_basenames[stem] |
| 349 | if resolved == page.path: |
| 350 | continue |
| 351 | backlinks.setdefault(resolved, set()).add(page.path) |
| 352 | |
| 353 | return backlinks |
| 354 | |
| 355 | |
| 356 | def _is_exempt(page) -> bool: |
no test coverage detected