196 lines
7.4 KiB
Python
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)
|