Files
llm_wiki/tools/scripts/check-staleness.py
hehaiguang1123 a6f05ab2d5 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
2026-07-01 08:05:43 +08:00

119 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()