a6f05ab2d5
- Phase 0: AGENTS.md cleanup (dedup quotes, renumber sections, merge qmd) - Phase 1: typed relations (manage-relations.py, graph-search.py, check-staleness.py, detect-conflicts.py) - Phase 2: frontmatter validator, weekly lint, knowledge promotion, git hooks - Fix .gitignore to track tools/ and .githooks/ - Fix git remote URL (remove plaintext token) - New wiki pages: 504 pages, 34 raw sources
133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
#!/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()
|