#!/usr/bin/env python # -*- coding: utf-8 -*- """ detect-conflicts.py — 检测 wiki 页面间的矛盾 通过扫描 relations 中的 conflicts_with 关系, 检查双方是否都引用了对方,并输出矛盾报告。 用法: python tools/scripts/detect-conflicts.py # 标准输出 python tools/scripts/detect-conflicts.py --json # JSON 格式 python tools/scripts/detect-conflicts.py --auto-callout # 自动添加 callout """ import argparse import json import re import sys from pathlib import Path try: import yaml except ImportError: yaml = None WIKI = Path(r"D:\Applications\app\kepano-obsidian-main\wiki") def read_frontmatter(fp: Path) -> dict | None: """读取 frontmatter,返回 dict 或 None""" content = fp.read_text(encoding="utf-8") m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) if not m or not yaml: return None try: return yaml.safe_load(m.group(1)) or {} except: return None def detect(): """检测所有 conflicts_with 关系,返回冲突报告列表""" conflicts = [] pages = {} for fp in WIKI.glob("*.md"): if fp.name in ("index.md", "log.md"): continue front = read_frontmatter(fp) if front is None: continue pages[fp.stem] = front for rel in front.get("relations", []): if rel["type"] == "conflicts_with": target = rel.get("target", "").strip("[]") conf_source = rel.get("confidence", 3) if target and target != fp.stem: conflicts.append({ "source": fp.stem, "target": target, "confidence": conf_source, "bidirectional": False }) # 检查双向性 for c in conflicts: target_front = pages.get(c["target"]) if target_front: for rel in target_front.get("relations", []): if rel["type"] == "conflicts_with" and rel.get("target", "").strip("[]") == c["source"]: c["bidirectional"] = True break return conflicts def generate_callout(source: str, target: str) -> str: return ( f"> [!WARNING] 可能矛盾\n" f"> 本页的声明与 [[{target}]] 存在冲突。\n" f"> 需要人工复核并解决矛盾。\n" ) def main(): parser = argparse.ArgumentParser(description="Detect conflicts between wiki pages") parser.add_argument("--json", action="store_true", help="Output JSON") parser.add_argument("--auto-callout", action="store_true", help="Auto-add callout to pages") args = parser.parse_args() conflicts = detect() if args.json: print(json.dumps(conflicts, ensure_ascii=False, indent=2)) return bidirectional = [c for c in conflicts if c["bidirectional"]] unidirectional = [c for c in conflicts if not c["bidirectional"]] print(f"=== Conflict Detection ===") print(f" Total conflict declarations: {len(conflicts)}") print(f" Bidirectional (both confirm): {len(bidirectional)}") print(f" Unidirectional (check needed): {len(unidirectional)}") if bidirectional: print(f"\n Bidirectional conflicts:") for c in bidirectional: print(f" [[{c['source']}]] <--conflicts_with--> [[{c['target']}]]") if unidirectional: print(f"\n Unidirectional conflicts (may need callout):") for c in unidirectional: print(f" [[{c['source']}]] --conflicts_with--> [[{c['target']}]] (unconfirmed)") if args.auto_callout: added = 0 for c in unidirectional: fp = WIKI / f"{c['source']}.md" if not fp.exists(): continue content = fp.read_text(encoding="utf-8") callout = generate_callout(c["source"], c["target"]) # Only add if not already present if callout.strip() not in content: content += f"\n\n{callout}" fp.write_text(content, encoding="utf-8") added += 1 print(f" Added callout to [[{c['source']}]]") print(f" Callouts added: {added}") if __name__ == "__main__": main()