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
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
validate-frontmatter.py — Pre-commit hook: 验证被修改的 wiki 页面 frontmatter
检查项:
- 必须包含 categories(含 [[LLM Wiki]]
- 必须包含 tags(含 wiki
- 必须包含 type(合法值列表)
- 必须包含 source
- 如果 status=superseded,必须包含 superseded_by
- relations 的 target 必须指向存在的页面
用法:
python tools/scripts/validate-frontmatter.py # 所有 wiki 页面
python tools/scripts/validate-frontmatter.py --files file1.md file2.md # 指定文件
python tools/scripts/validate-frontmatter.py --git-hook # 从 git diff 读取
"""
import argparse
import re
import subprocess
import sys
from pathlib import Path
try:
import yaml
except ImportError:
yaml = None
WIKI = Path(r"D:\Applications\app\kepano-obsidian-main\wiki")
VALID_TYPES = [
"concept", "entity", "tool", "reference", "place",
"institution", "method", "knowledge-card", "synthesis",
"index", "log", "research-report", "lesson"
]
def validate_one(fp: Path) -> list[str]:
"""验证单个文件,返回错误列表"""
errors = []
content = fp.read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not m:
return ["No frontmatter found"]
if not yaml:
return ["PyYAML not installed"]
try:
front = yaml.safe_load(m.group(1)) or {}
except yaml.YAMLError as e:
return [f"YAML parse error: {e}"]
# categories must contain [[LLM Wiki]]
cats = front.get("categories", [])
if isinstance(cats, str):
cats = [cats]
if not any(str(c).strip("[]") == "LLM Wiki" for c in cats):
errors.append("Missing [[LLM Wiki]] in categories")
# tags must contain wiki
tags = front.get("tags", [])
if isinstance(tags, str):
tags = [tags]
if "wiki" not in tags:
errors.append("Missing 'wiki' in tags")
# type must exist and be valid
ptype = front.get("type")
if not ptype:
errors.append("Missing type")
elif isinstance(ptype, str) and ptype not in VALID_TYPES:
errors.append(f"Invalid type: '{ptype}' (valid: {', '.join(VALID_TYPES)})")
elif isinstance(ptype, list):
for t in ptype:
if t not in VALID_TYPES:
errors.append(f"Invalid type in list: '{t}'")
# source must exist
if not front.get("source"):
errors.append("Missing source")
# status=superseded must have superseded_by
if front.get("status") == "superseded" and not front.get("superseded_by"):
errors.append("status=superseded but missing superseded_by")
# relations targets must exist
for rel in front.get("relations", []):
target = str(rel.get("target", "")).strip("[]")
if target and not (WIKI / f"{target}.md").exists():
errors.append(f"Relation target [[{target}]] not found")
return errors
def get_git_changed_wiki_files() -> list[Path]:
"""获取 git 暂存区中被修改的 wiki .md 文件"""
try:
result = subprocess.run(
["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
capture_output=True, text=True, check=True, cwd=WIKI.parent
)
except (subprocess.CalledProcessError, FileNotFoundError):
return []
files = []
for line in result.stdout.splitlines():
line = line.strip()
if line.startswith("wiki/") and line.endswith(".md"):
fp = WIKI.parent / line
if fp.exists():
files.append(fp)
return files
def main():
parser = argparse.ArgumentParser(description="Validate wiki page frontmatter")
parser.add_argument("--files", nargs="+", help="Specific files to check")
parser.add_argument("--git-hook", action="store_true", help="Read from git diff")
args = parser.parse_args()
if args.git_hook:
files = get_git_changed_wiki_files()
if not files:
sys.exit(0)
elif args.files:
files = [Path(f) if Path(f).is_absolute() else WIKI / f for f in args.files]
else:
files = sorted(WIKI.glob("*.md"))
all_errors = {}
for fp in files:
if fp.name in ("index.md", "log.md"):
continue
errors = validate_one(fp)
if errors:
all_errors[fp.stem] = errors
if all_errors:
print(f"=== Frontmatter Validation Errors ({len(all_errors)} files) ===")
for name, errs in sorted(all_errors.items()):
print(f"\n [[{name}]]:")
for e in errs:
print(f"{e}")
sys.exit(1)
else:
print("✅ All frontmatter valid")
sys.exit(0)
if __name__ == "__main__":
main()