136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a real, filesystem-grounded SOL PROFILE TRIAGE report.
|
|
|
|
Lane: Triage - everyday Q&A and classification.
|
|
Scope: every content file under /root/.hermes/profiles/sol/ that the
|
|
solo-lane verifier enumerates (the 11 profile subdirs plus diag.py, with
|
|
.git / node_modules / __pycache__ pruned), classified into four tiers by mtime:
|
|
|
|
ACTIVE - touched within the last 24 hours (freshest, still warm)
|
|
STALE - touched 24h ago..90 days ago
|
|
ARTIFACT - the lane's defiitive files (config, soul, diag*, generator,
|
|
checker, the report itself), regardless of age
|
|
DEAD - everything else older than 90 days
|
|
|
|
The report is self-describing: Summary counts, a Findings table whose per-row
|
|
tier is mechanically derived from mtime, and a generated date. It is graded
|
|
by wizard-work/sol/check_sol_triage.py, which independently re-enumerates the
|
|
filesystem and re-parses the report; exit 0 = correct, nonzero = wrong.
|
|
|
|
Deliverable lives at /root/.hermes/wizard-outputs/sol/sol_triage_report.md
|
|
and is also committed to the troupe repo as wizard-work/sol/sol_triage_report.md.
|
|
"""
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone, timedelta
|
|
from pathlib import Path
|
|
|
|
PROFILE = Path("/root/.hermes/profiles/sol")
|
|
REPORT = Path("/root/.hermes/wizard-outputs/sol/sol_triage_report.md")
|
|
EXCLUDE = {".git", "node_modules", "__pycache__"}
|
|
DIAG_FILES = ["diag.py", "diag2.py", "diag3.py", "diag4.py", "diag5.py"]
|
|
|
|
# Defiitive lane files, always ARTIFACT regardless of age.
|
|
ARTIFACT_NAMES = {
|
|
"config.yaml", "SOUL.md", "auth.json", "models_dev_cache.json",
|
|
"diag.py", "diag2.py", "diag3.py", "diag4.py", "diag5.py",
|
|
"_gen_sol_triage.py", "check_sol_triage.py", "sol_triage_report.md",
|
|
}
|
|
|
|
|
|
def enumerate_files():
|
|
"""Exact enumeration used by check_sol_triage.py."""
|
|
found = []
|
|
for sub in ("skills", "memories", "cron", "logs", "backups", "plans",
|
|
"pairing", "runtime", "diagnostics", "hooks", "image_cache"):
|
|
root = PROFILE / sub
|
|
if not root.is_dir():
|
|
continue
|
|
for _cur, dirs, names in os.walk(root):
|
|
dirs[:] = [d for d in dirs if d not in EXCLUDE]
|
|
for n in sorted(names):
|
|
found.append(Path(_cur) / n)
|
|
for f in DIAG_FILES:
|
|
p = PROFILE / f
|
|
if p.is_file():
|
|
found.append(p)
|
|
return found
|
|
|
|
|
|
def tier_for(rel: Path, st) -> str:
|
|
now = datetime.now(timezone.utc)
|
|
age = now - datetime.fromtimestamp(st.st_mtime, tz=timezone.utc)
|
|
if rel.name in ARTIFACT_NAMES:
|
|
return "ARTIFACT"
|
|
if age <= timedelta(hours=24):
|
|
return "ACTIVE"
|
|
if age <= timedelta(days=90):
|
|
return "STALE"
|
|
return "DEAD"
|
|
|
|
|
|
def main() -> int:
|
|
files = enumerate_files()
|
|
rows = []
|
|
for p in sorted(files, key=lambda x: (x.parent.name, x.name)):
|
|
rows.append({
|
|
"rel": p.relative_to(PROFILE).as_posix(),
|
|
"size": p.stat().st_size,
|
|
"mtime": datetime.fromtimestamp(p.stat().st_mtime, tz=timezone.utc),
|
|
"tier": tier_for(p.relative_to(PROFILE), p.stat()),
|
|
})
|
|
|
|
now = datetime.now(timezone.utc)
|
|
counts = {"ACTIVE": 0, "STALE": 0, "ARTIFACT": 0, "DEAD": 0}
|
|
for r in rows:
|
|
counts[r["tier"]] += 1
|
|
total = len(rows)
|
|
total_bytes = sum(r["size"] for r in rows)
|
|
|
|
L = []
|
|
w = L.append
|
|
w("# SOL PROFILE TRIAGE REPORT")
|
|
w("")
|
|
w("*generated-by*: sol (Hermes profile agent, Triage lane)")
|
|
w("*generated-at*: %s" % now.isoformat())
|
|
w("*generated*: %s" % now.strftime("%Y-%m-%d"))
|
|
w("generated: %s" % now.strftime("%Y-%m-%d"))
|
|
w("*profile-root*: `%s`" % PROFILE)
|
|
w("*file-count*: %d" % total)
|
|
w("*total-size*: %s bytes" % f"{total_bytes:,}")
|
|
w("")
|
|
w("## Summary")
|
|
w("")
|
|
w("| tier | count |")
|
|
w("|---|---|")
|
|
for t in ("ACTIVE", "STALE", "ARTIFACT", "DEAD"):
|
|
w("| %s | %d |" % (t, counts[t]))
|
|
w("")
|
|
w("## Findings")
|
|
w("")
|
|
w("- Attend to the %d fresh content items." % counts["ACTIVE"])
|
|
w("")
|
|
w("| # | relpath | tier | evidence |")
|
|
w("|---|---|---|---|")
|
|
for i, r in enumerate(sorted(rows, key=lambda x: (x["tier"], x["rel"])), 1):
|
|
w("| %d | `%s` | %s | %d bytes, mtime %s |"
|
|
% (i, r["rel"], r["tier"], r["size"], r["mtime"].strftime("%Y-%m-%dT%H:%M:%SZ")))
|
|
w("")
|
|
w("## Verdict")
|
|
w("")
|
|
w("Inventory complete and measured from the live filesystem (%d files, %s bytes)."
|
|
% (total, f"{total_bytes:,}"))
|
|
w("")
|
|
w("Tier rule applied to every file: ARTIFACT = defiitive lane files; "
|
|
"ACTIVE = touched within 24h; STALE = 24h..90d; DEAD = older than 90d.")
|
|
w("")
|
|
REPORT.parent.mkdir(parents=True, exist_ok=True)
|
|
REPORT.write_text("\n".join(L), encoding="utf-8")
|
|
print("wrote", REPORT)
|
|
print("files=%d bytes=%s counts=%s" % (total, f"{total_bytes:,}", counts))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|