Phase 0-2: Schema cleanup, typed relations, event-driven automation

- 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
This commit is contained in:
hehaiguang1123
2026-07-01 08:05:43 +08:00
parent e544d6e04a
commit a6f05ab2d5
1067 changed files with 522992 additions and 819 deletions
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
graph-search.py — 图感知搜索
流程:
1. qmd vsearch 获取语义匹配结果(Top 10)
2. 对每个结果,读取 relations 字段 → 获取相邻节点
3. 去重后返回(直接关联 + 关系扩展)
用法:
python tools/scripts/graph-search.py "查询词" [--count 15] [--depth 1]
依赖:
- qmd 已安装(通过 node
"""
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
try:
import yaml
except ImportError:
yaml = None
QMD = r'node "C:\Users\hhhh2024\AppData\Roaming\npm\node_modules\@tobilu\qmd\dist\cli\qmd.js"'
WIKI = Path(r"D:\Applications\app\kepano-obsidian-main\wiki")
def qmd_search(query: str, count: int = 10) -> list[str]:
"""调用 qmd vsearch 获取匹配的页面名列表"""
cmd = f'{QMD} vsearch "{query}" -c wiki -n {count}'
result = subprocess.run(cmd, capture_output=True, text=True, shell=True, timeout=30)
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
return lines
def get_relations(page_name: str) -> list[dict]:
"""读取页面的 relations 字段"""
fp = WIKI / f"{page_name}.md"
if not fp.exists():
return []
content = fp.read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not m:
return []
if not yaml:
return []
try:
front = yaml.safe_load(m.group(1)) or {}
except:
return []
return front.get("relations", [])
def expand_nodes(nodes: list[str], depth: int = 1) -> list[str]:
"""从起始节点出发,沿 relations 扩展相邻节点"""
expanded = list(nodes)
frontier = list(nodes)
for _ in range(depth):
next_frontier = []
for node in frontier:
rels = get_relations(node)
for r in rels:
target = r.get("target", "").strip("[]")
if target and target not in expanded:
expanded.append(target)
next_frontier.append(target)
frontier = next_frontier
if not frontier:
break
return expanded
def main():
parser = argparse.ArgumentParser(description="Graph-aware search for wiki pages")
parser.add_argument("query", help="Search query")
parser.add_argument("--count", type=int, default=15, help="Max results")
parser.add_argument("--depth", type=int, default=1, help="Graph expansion depth")
args = parser.parse_args()
print(f"=== Searching: {args.query} ===")
results = qmd_search(args.query, count=max(10, args.count))
if not results:
print(" No results from qmd")
return
print(f"\nDirect matches ({len(results)}):")
for r in results:
print(f" [[{r}]]")
expanded = expand_nodes(results, depth=args.depth)
new = [e for e in expanded if e not in results]
if new:
print(f"\nGraph-expanded (via relations, depth={args.depth}):")
for n in new[:args.count]:
print(f" [[{n}]]")
if len(new) > args.count:
print(f" ... and {len(new) - args.count} more")
print(f"\nTotal unique: {len(expanded)}")
if __name__ == "__main__":
main()