#!/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()