117 lines
3.1 KiB
Python
117 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Find stale entries in .lore/.
|
|
|
|
Usage:
|
|
python find_stale.py # default: 90-day threshold
|
|
python find_stale.py --days=180
|
|
python find_stale.py --json
|
|
|
|
Reports two categories:
|
|
|
|
Stale : entry has not been `#verified` within the threshold
|
|
(or has no #verified at all, and was added > threshold
|
|
days ago).
|
|
Pending arch : entry already carries a `#stale:` tag and is waiting
|
|
to be moved into .lore/archive/.
|
|
|
|
Output is plain text by default, JSON with --json.
|
|
|
|
Used by:
|
|
- `audit` workflow (read-only)
|
|
- `compress` workflow (advisory)
|
|
- `lore mirror` (sanity check before regenerating)
|
|
"""
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from datetime import date, datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
|
|
def get_entries():
|
|
script = Path(__file__).parent / "list_entries.py"
|
|
r = subprocess.run(
|
|
[sys.executable, str(script), "--json"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if r.returncode != 0:
|
|
print(r.stderr.strip(), file=sys.stderr)
|
|
sys.exit(1)
|
|
try:
|
|
return json.loads(r.stdout)
|
|
except json.JSONDecodeError as exc:
|
|
print(f"error: list_entries.py returned invalid JSON: {exc}",
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def parse_date(s: str):
|
|
try:
|
|
return datetime.strptime(s, "%Y-%m-%d").date()
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def main():
|
|
days = 90
|
|
json_output = "--json" in sys.argv[1:]
|
|
|
|
for arg in sys.argv[1:]:
|
|
if arg.startswith("--days="):
|
|
days = int(arg.split("=", 1)[1])
|
|
|
|
today = date.today()
|
|
cutoff = today - timedelta(days=days)
|
|
|
|
entries = get_entries()
|
|
stale = []
|
|
pending_arch = []
|
|
|
|
for e in entries:
|
|
# Already marked stale → pending archive
|
|
if "stale" in e["tags"]:
|
|
pending_arch.append(e)
|
|
continue
|
|
|
|
# Determine the entry's freshness date
|
|
last_v = parse_date(e["last_verified"])
|
|
added = parse_date(e["tags"].get("added"))
|
|
ref_date = last_v or added
|
|
|
|
if ref_date is None:
|
|
continue # no date info, can't decide
|
|
|
|
if ref_date < cutoff:
|
|
stale.append(e)
|
|
|
|
if json_output:
|
|
out = {
|
|
"threshold_days": days,
|
|
"as_of": today.isoformat(),
|
|
"stale": stale,
|
|
"pending_archive": pending_arch,
|
|
}
|
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
return
|
|
|
|
print(f"=== Stale (unverified > {days} days, as of {today}) ===")
|
|
if not stale:
|
|
print(" (none)")
|
|
for e in stale:
|
|
ref = e["last_verified"] or e["tags"].get("added", "unknown")
|
|
print(f" [{e['file']}] {e['id']} {e['text']}")
|
|
print(f" ref date: {ref}")
|
|
|
|
print()
|
|
print("=== Pending archive (tagged #stale) ===")
|
|
if not pending_arch:
|
|
print(" (none)")
|
|
for e in pending_arch:
|
|
print(f" [{e['file']}] {e['id']} {e['text']}")
|
|
print(f" marked stale: {e['tags']['stale']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|