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:
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
check-staleness.py — 检测 wiki 页面的时效性
|
||||
|
||||
检查每页的 last_reviewed 字段是否超过 review_interval_days,
|
||||
输出过时页面列表和从未审查的页面列表。
|
||||
|
||||
用法:
|
||||
python tools/scripts/check-staleness.py # 标准输出
|
||||
python tools/scripts/check-staleness.py --json # JSON 格式
|
||||
python tools/scripts/check-staleness.py --overdue-only # 仅过时页面
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
WIKI = Path(r"D:\Applications\app\kepano-obsidian-main\wiki")
|
||||
TODAY = date.today()
|
||||
DEFAULT_INTERVAL = 180
|
||||
|
||||
|
||||
def check_page(fp: Path) -> dict:
|
||||
"""检查单页,返回结果 dict"""
|
||||
result = {"page": fp.stem, "stale": False, "overdue_days": 0, "reason": ""}
|
||||
content = fp.read_text(encoding="utf-8")
|
||||
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
if not m:
|
||||
result["reason"] = "no_frontmatter"
|
||||
return result
|
||||
if not yaml:
|
||||
result["reason"] = "no_pyyaml"
|
||||
return result
|
||||
try:
|
||||
front = yaml.safe_load(m.group(1)) or {}
|
||||
except:
|
||||
result["reason"] = "yaml_parse_error"
|
||||
return result
|
||||
|
||||
last_str = front.get("last_reviewed", "")
|
||||
interval = front.get("review_interval_days", DEFAULT_INTERVAL)
|
||||
|
||||
if not last_str:
|
||||
result["reason"] = "never_reviewed"
|
||||
result["stale"] = True
|
||||
return result
|
||||
|
||||
try:
|
||||
last = date.fromisoformat(str(last_str))
|
||||
diff = (TODAY - last).days
|
||||
if diff > interval:
|
||||
result["stale"] = True
|
||||
result["overdue_days"] = diff - interval
|
||||
result["reason"] = f"overdue_by_{diff - interval}_days"
|
||||
except ValueError:
|
||||
result["reason"] = f"invalid_date_{last_str}"
|
||||
result["stale"] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Check wiki page staleness")
|
||||
parser.add_argument("--json", action="store_true", help="Output JSON")
|
||||
parser.add_argument("--overdue-only", action="store_true", help="Only stale pages")
|
||||
args = parser.parse_args()
|
||||
|
||||
all_pages = sorted(WIKI.glob("*.md"))
|
||||
results = []
|
||||
for fp in all_pages:
|
||||
if fp.name in ("index.md", "log.md"):
|
||||
continue
|
||||
results.append(check_page(fp))
|
||||
|
||||
stale = [r for r in results if r["stale"]]
|
||||
never = [r for r in stale if r["reason"] == "never_reviewed"]
|
||||
overdue = [r for r in stale if r["reason"].startswith("overdue")]
|
||||
|
||||
if args.json:
|
||||
output = {
|
||||
"total": len(results),
|
||||
"stale": len(stale),
|
||||
"never_reviewed": len(never),
|
||||
"overdue": len(overdue),
|
||||
"pages": stale if args.overdue_only else results
|
||||
}
|
||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
print(f"=== Staleness Check ({TODAY}) ===")
|
||||
print(f" Total: {len(results)} pages")
|
||||
print(f" Stale: {len(stale)}")
|
||||
print(f" Never reviewed: {len(never)}")
|
||||
print(f" Overdue: {len(overdue)}")
|
||||
|
||||
if overdue:
|
||||
print(f"\n Overdue pages (top 20):")
|
||||
for r in sorted(overdue, key=lambda x: -x["overdue_days"])[:20]:
|
||||
print(f" [[{r['page']}]] — overdue by {r['overdue_days']} days")
|
||||
|
||||
if never:
|
||||
print(f"\n Never reviewed (top 20):")
|
||||
for r in never[:20]:
|
||||
print(f" [[{r['page']}]]")
|
||||
if len(never) > 20:
|
||||
print(f" ... and {len(never)-20} more")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user