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
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
promote-knowledge.py — Consolidation tiers: 检查 wiki/working/ 中的页面
是否可以提升到 wiki/semantic/(即主 wiki 目录)。
条件:
- 有完整的 frontmattercategories, tags, type, source
- 正文 > 100 字
- 创建时间 > 7 天(通过 created 字段判断)
用法:
python tools/scripts/promote-knowledge.py # 检查可提升页面
python tools/scripts/promote-knowledge.py --apply # 执行提升
python tools/scripts/promote-knowledge.py --dry-run # 预览
"""
import argparse
import re
import shutil
import sys
from datetime import date, timedelta
from pathlib import Path
try:
import yaml
except ImportError:
yaml = None
WIKI = Path(r"D:\Applications\app\kepano-obsidian-main\wiki")
WORKING = WIKI / "working"
SEMANTIC = WIKI # semantic 层就是当前 wiki/
ARCHIVE = WIKI / "archive"
PROCEDURAL = WIKI / "procedural"
def ensure_dirs():
"""确保层目录存在"""
for d in [WORKING, ARCHIVE, PROCEDURAL]:
d.mkdir(exist_ok=True)
def check_promotable(fp: Path) -> tuple[bool, list[str]]:
"""检查文件是否可提升"""
content = fp.read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not m:
return False, ["No frontmatter"]
if not yaml:
return False, ["PyYAML not installed"]
try:
front = yaml.safe_load(m.group(1)) or {}
except:
return False, ["YAML parse error"]
body = content[m.end():].strip()
reasons = []
if not front.get("categories"):
reasons.append("Missing categories")
tags = front.get("tags", [])
if isinstance(tags, str): tags = [tags]
if "wiki" not in tags:
reasons.append("Missing 'wiki' in tags")
if not front.get("type"):
reasons.append("Missing type")
if not front.get("source"):
reasons.append("Missing source")
if len(body) < 100:
reasons.append(f"Body too short ({len(body)} chars, need 100+)")
created = front.get("created")
if created:
try:
cdate = date.fromisoformat(str(created))
if (date.today() - cdate).days < 7:
reasons.append(f"Created < 7 days ago ({created})")
except:
pass # ignore invalid dates
return len(reasons) == 0, reasons
def main():
parser = argparse.ArgumentParser(description="Knowledge consolidation tiers")
parser.add_argument("--apply", action="store_true", help="Execute promotion")
parser.add_argument("--dry-run", action="store_true", help="Preview only")
args = parser.parse_args()
ensure_dirs()
if not WORKING.exists():
print("No working/ directory found. Nothing to promote.")
return
working_files = sorted(WORKING.glob("*.md"))
if not working_files:
print("No files in working/")
return
promotable = []
not_ready = []
for fp in working_files:
ready, reasons = check_promotable(fp)
if ready:
promotable.append(fp)
else:
not_ready.append((fp, reasons))
print(f"=== Knowledge Promotion Check ===")
print(f" Working files: {len(working_files)}")
print(f" Promotable: {len(promotable)}")
print(f" Not ready: {len(not_ready)}")
if promotable:
print(f"\n Promotable to semantic/:")
for fp in promotable:
dest = SEMANTIC / fp.name
if dest.exists():
print(f" [[{fp.stem}]] → WARNING: target exists")
else:
print(f" [[{fp.stem}]]")
if args.apply and not args.dry_run:
for fp in promotable:
dest = SEMANTIC / fp.name
if not dest.exists():
shutil.move(str(fp), str(dest))
print(f" Moved: working/{fp.name}{fp.name}")
print(f"\n Promoted: {len(promotable)}")
elif args.dry_run:
print(f"\n (--dry-run: no files moved)")
if not_ready:
print(f"\n Not ready for promotion:")
for fp, reasons in not_ready:
for r in reasons:
print(f" [[{fp.stem}]] — {r}")
if __name__ == "__main__":
main()