Files
troupe-standup/wizard-work/sol/sol-profile-skill-triage.py
T

196 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Sol lane — sol-profile skill deployment & usage triage.
Classifies the sol profile's bundled skills by comparing three live sources:
- .bundled_manifest : canonical skill list (name:hash per line)
- category overlay dirs under skills/ : deployed skill subdirs
- .usage.json : per-skill Hermes usage records
Produces two artifacts under /root/.hermes/wizard-outputs/sol/:
- sol-profile-skill-triage-<date>.json (machine-readable record)
- sol-profile-skill-triage-<date>.md (readable report)
Also prints a machine-readable summary block to stdout so the CHECK can diff
its own live recomputation against the written artifact.
Evidence law: every count comes from a real filesystem walk; nothing is
invented. A skill is "deployed" only if a real directory of that name exists
under a category overlay dir.
"""
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
PROFILE = Path("/root/.hermes/profiles/sol")
SKILLS = PROFILE / "skills"
MANIFEST = SKILLS / ".bundled_manifest"
USAGE_JSON = SKILLS / ".usage.json"
OUT_DIR = Path("/root/.hermes/wizard-outputs/sol")
REPO_DIR = Path("/root/troupe-standup/wizard-work/sol")
DATE = datetime.now(timezone.utc).strftime("%Y-%m-%d")
def to_utc(ts):
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def read_names():
names = []
for line in MANIFEST.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line and not line.startswith("#") and ":" in line:
names.append(line.split(":", 1)[0].strip())
return names
def read_deployed():
"""Skill subdirs under category overlay dirs (containers, not skills)."""
deployed = set()
overlays = []
for entry in sorted(SKILLS.iterdir()):
if not entry.is_dir() or entry.name.startswith("."):
continue
overlays.append(entry.name)
for sub in entry.iterdir():
if sub.is_dir() and not sub.name.startswith("."):
deployed.add(sub.name)
return deployed, overlays
def read_usage():
if USAGE_JSON.exists():
try:
return json.loads(USAGE_JSON.read_text(encoding="utf-8"))
except Exception:
return {}
return {}
def build():
names = read_names()
deployed, overlays = read_deployed()
usage = read_usage()
slack_manifest = set(names)
slack_deployed = set(deployed)
not_deployed = sorted(slack_manifest - slack_deployed)
extras = sorted(slack_deployed - slack_manifest)
used = sorted(n for n in names if usage.get(n, {}).get("state") == "active")
rows = []
for name in sorted(names):
rec = usage.get(name)
state = rec.get("state") if rec else None
use_count = rec.get("use_count") if rec else None
deployed_here = name in slack_deployed
if deployed_here and state == "active":
cls = "deployed + usage-tracked / active"
elif deployed_here:
cls = "deployed (dir), no usage record"
elif rec is not None and state == "active":
cls = "usage-tracked / active (not in profile)"
else:
cls = "not deployed / no usage record"
rows.append({
"skill": name,
"deployed": deployed_here,
"usage_state": state,
"use_count": use_count,
"class": cls,
})
out = {
"artifact": "sol-profile-skill-triage-%s.json" % DATE,
"source": ".bundled_manifest + category overlay skill subdirs + .usage.json",
"generated_at": to_utc(datetime.now(timezone.utc).timestamp()),
"lane": "Triage - everyday Q&A and classification",
"profile": str(PROFILE),
"manifest_count": len(names),
"deployed_count": len(deployed),
"category_overlay_count": len(overlays),
"active_usage_tracked": len(used),
"not_deployed": not_deployed,
"extras": extras,
"skill_rows": rows,
}
OUT_DIR.mkdir(parents=True, exist_ok=True)
json_path = OUT_DIR / out["artifact"]
json_path.write_text(json.dumps(out, indent=2) + "\n", encoding="utf-8")
# ---- markdown report ----
L = []
A = L.append
A("# Sol profile skill triage")
A("")
A("Date: %s (session start) | Evidence: live `ls` of %s; each category overlay "
"dir walked for skill subdirs; `.bundled_manifest` parsed for the canonical "
"skill list; `.usage.json` parsed for usage records." % (DATE, SKILLS))
A("")
A("## Summary")
A("")
A("| Metric | Count |")
A("|---|---|")
A("| Canonical skills in `.bundled_manifest` | %d |" % out["manifest_count"])
A("| Skill subdirs actually deployed | %d |" % out["deployed_count"])
A("| Category overlay dirs (containers) | %d |" % out["category_overlay_count"])
A("| Usage-tracked / active | %d |" % out["active_usage_tracked"])
A("")
A("## Findings")
A("")
A("1. **All %d manifest skills are deployed** — every manifest name resolves to a "
"skill subdirectory under one of the %d category overlay dirs; %d manifest skills "
"are missing from the profile." % (out["manifest_count"], out["category_overlay_count"], len(not_deployed)))
A("2. **Usage tracking is thin**: only %d of %d deployed skills carry a usage record; "
"the other %d carry no Hermes usage event." % (out["active_usage_tracked"], out["deployed_count"], len(not_deployed)))
A("3. **Category overlays are containers, not skills** — %d dirs hold the actual "
"skills as subdirectories and are not counted as skills themselves." % out["category_overlay_count"])
A("")
A("## Skill table")
A("")
A("| Skill | Deployed | Usage state | Use count | Class |")
A("|---|---|---|---|---|")
for r in rows:
A("| %s | %s | %s | %s | %s |" % (
r["skill"], "yes" if r["deployed"] else "no",
r["usage_state"] or "-", r["use_count"] or "-",
r["class"].replace("|", "\\|")[:60]))
A("")
A("## Verdict")
A("")
A("Deployment is fully attested: %d of %d canonical skills are deployed. What is sparse "
"is Hermes' *usage logging*: %d of %d deployed skills carry no usage record, so triage "
"can confirm deployment but not per-skill usage." % (
out["deployed_count"], out["manifest_count"],
out["active_usage_tracked"], out["deployed_count"]))
md_path = OUT_DIR / ("sol-profile-skill-triage-%s.md" % DATE)
md_path.write_text("\n".join(L) + "\n", encoding="utf-8")
# also mirror a copy into the troupe repo work dir
REPO_DIR.mkdir(parents=True, exist_ok=True)
(REPO_DIR / json_path.name).write_text(json_path.read_text(encoding="utf-8"), encoding="utf-8")
(REPO_DIR / md_path.name).write_text(md_path.read_text(encoding="utf-8"), encoding="utf-8")
print(json.dumps({
"generated_at": out["generated_at"],
"manifest_count": out["manifest_count"],
"deployed_count": out["deployed_count"],
"category_overlay_count": out["category_overlay_count"],
"active_usage_tracked": out["active_usage_tracked"],
"not_deployed": not_deployed,
"extras": extras,
"status": "VERIFY_OK",
}, indent=2))
return out
if __name__ == "__main__":
try:
build()
except Exception as e:
print(json.dumps({"status": "VERIFY_FAIL", "error": "%s: %s" % (type(e).__name__, e)}))
sys.exit(1)