diff --git a/wizard-work/sol/check_sol_profile_skill_triage.py b/wizard-work/sol/check_sol_profile_skill_triage.py new file mode 100644 index 0000000..0c2f849 --- /dev/null +++ b/wizard-work/sol/check_sol_profile_skill_triage.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Independent re-check for sol-profile-skill-triage-2026-10-24. + +Recomputes the classification straight from the live filesystem (manifest, +category overlay skill subdirs, .usage.json) and diffs it against the written +JSON artifact. Exit 0 = artifact is correct; nonzero = artifact is wrong or +missing. No fallbacks: the exit code carries the verdict. +""" +import json +import os +import sys +from pathlib import Path + +SKILLS = Path("/root/.hermes/profiles/sol/skills") +ARTIFACT = Path("/root/.hermes/wizard-outputs/sol/sol-profile-skill-triage-2026-10-24.json") +REPO_ARTIFACT = Path("/root/troupe-standup/wizard-work/sol/sol-profile-skill-triage-2026-10-24.json") + +failures = [] + + +def read_names(): + names = [] + for line in SKILLS.joinpath(".bundled_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(): + deployed = set() + n = 0 + for entry in sorted(SKILLS.iterdir()): + if not entry.is_dir() or entry.name.startswith("."): + continue + n += 1 + for sub in entry.iterdir(): + if sub.is_dir() and not sub.name.startswith("."): + deployed.add(sub.name) + return deployed, n + + +def main(): + names = read_names() + deployed, overlays = read_deployed() + usage = json.loads(SKILLS.joinpath(".usage.json").read_text(encoding="utf-8")) + used = sorted(n for n in names if usage.get(n, {}).get("state") == "active") + + truth = { + "manifest_count": len(names), + "deployed_count": len(deployed), + "category_overlay_count": overlays, + "active_usage_tracked": len(used), + "not_deployed": sorted(set(names) - deployed), + "extras": sorted(deployed - set(names)), + "skill_rows": [ + { + "skill": n, + "deployed": n in deployed, + "usage_state": usage.get(n, {}).get("state"), + "use_count": usage.get(n, {}).get("use_count"), + "class": ( + "deployed + usage-tracked / active" if (n in deployed and usage.get(n, {}).get("state") == "active") + else ("deployed (dir), no usage record" if n in deployed else + ("usage-tracked / active (not in profile)" if usage.get(n, {}).get("state") == "active" else "not deployed / no usage record")) + ), + } + for n in sorted(names) + ], + } + + if not ARTIFACT.exists(): + print("FAIL: artifact missing " + str(ARTIFACT)) + sys.exit(1) + recorded = json.loads(ARTIFACT.read_text(encoding="utf-8")) + + for k in ("manifest_count", "deployed_count", "category_overlay_count", "active_usage_tracked", "not_deployed", "extras"): + if recorded.get(k) != truth[k]: + failures.append("%s artifact=%r truth=%r" % (k, recorded.get(k), truth[k])) + + rec_rows = {r["skill"]: r for r in recorded.get("skill_rows", [])} + if len(rec_rows) != len(truth["skill_rows"]): + failures.append("skill_rows count %d != truth %d" % (len(rec_rows), len(truth["skill_rows"]))) + for r in truth["skill_rows"]: + if rec_rows.get(r["skill"]) != r: + failures.append("row mismatch %s: %r != %r" % (r["skill"], rec_rows.get(r["skill"]), r)) + + if recorded.get("generated_at") == "" or recorded.get("generated_at") is None: + failures.append("generated_at missing/empty") + + if failures: + print("FAIL: sol-profile-skill-triage-2026-10-24 artifact incorrect") + for f in failures: + print(" - " + f) + sys.exit(1) + + print("PASS: sol-profile-skill-triage-2026-10-24 artifact verified against live filesystem") + print(" manifest=%d deployed=%d overlays=%d usage_tracked=%d not_deployed=%d extras=%d rows=%d" % ( + truth["manifest_count"], truth["deployed_count"], truth["category_overlay_count"], + truth["active_usage_tracked"], len(truth["not_deployed"]), len(truth["extras"]), len(truth["skill_rows"]))) + + +if __name__ == "__main__": + main() diff --git a/wizard-work/sol/sol-profile-skill-triage-2026-10-24.json b/wizard-work/sol/sol-profile-skill-triage-2026-10-24.json new file mode 100644 index 0000000..6bc0cf4 --- /dev/null +++ b/wizard-work/sol/sol-profile-skill-triage-2026-10-24.json @@ -0,0 +1,421 @@ +{ + "artifact": "sol-profile-skill-triage-2026-10-24.json", + "source": ".bundled_manifest + category overlay skill subdirs + .usage.json", + "generated_at": "2026-10-24T16:17:16Z", + "lane": "Triage - everyday Q&A and classification", + "profile": "/root/.hermes/profiles/sol", + "manifest_count": 58, + "deployed_count": 58, + "category_overlay_count": 12, + "active_usage_tracked": 3, + "not_deployed": [], + "extras": [], + "skill_rows": [ + { + "skill": "airtable", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "apple-notes", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "apple-reminders", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "architecture-diagram", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "arxiv", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "ascii-video", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "baoyu-infographic", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "blocked-page-recovery", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "box", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "claude-code", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "claude-design", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "codebase-inspection", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "codex", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "competitor-news-monitor", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "computer-use", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "design-md", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "document-to-action-items", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "docx", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "dogfood", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "email-inbox-triage", + "deployed": true, + "usage_state": "active", + "use_count": 1, + "class": "deployed + usage-tracked / active" + }, + { + "skill": "findmy", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "gif-search", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "github", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "google-workspace", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "grounded-citations", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "hermes-agent", + "deployed": true, + "usage_state": "active", + "use_count": 11, + "class": "deployed + usage-tracked / active" + }, + { + "skill": "hermes-agent-skill-authoring", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "himalaya", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "humanizer", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "imessage", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "inspecting-hermes-desktop-dom", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "llm-wiki", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "manim-video", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "maps", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "meeting-action-items", + "deployed": true, + "usage_state": "active", + "use_count": 1, + "class": "deployed + usage-tracked / active" + }, + { + "skill": "node-inspect-debugger", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "notion", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "obsidian", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "opencode", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "p5js", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "pdf", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "popular-web-designs", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "powerpoint", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "product-price-monitor", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "python-debugpy", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "requesting-code-review", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "sdlc-review", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "simplify-code", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "songsee", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "songwriting-and-ai-music", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "spike", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "systematic-debugging", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "teams-meeting-pipeline", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "test-driven-development", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "weekly-review-planning", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "xlsx", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "xurl", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + }, + { + "skill": "youtube-content", + "deployed": true, + "usage_state": null, + "use_count": null, + "class": "deployed (dir), no usage record" + } + ] +} diff --git a/wizard-work/sol/sol-profile-skill-triage-2026-10-24.md b/wizard-work/sol/sol-profile-skill-triage-2026-10-24.md new file mode 100644 index 0000000..4ac5bbc --- /dev/null +++ b/wizard-work/sol/sol-profile-skill-triage-2026-10-24.md @@ -0,0 +1,85 @@ +# Sol profile skill triage + +Date: 2026-10-24 (session start) | Evidence: live `ls` of /root/.hermes/profiles/sol/skills; each category overlay dir walked for skill subdirs; `.bundled_manifest` parsed for the canonical skill list; `.usage.json` parsed for usage records. + +## Summary + +| Metric | Count | +|---|---| +| Canonical skills in `.bundled_manifest` | 58 | +| Skill subdirs actually deployed | 58 | +| Category overlay dirs (containers) | 12 | +| Usage-tracked / active | 3 | + +## Findings + +1. **All 58 manifest skills are deployed** — every manifest name resolves to a skill subdirectory under one of the 12 category overlay dirs; 0 manifest skills are missing from the profile. +2. **Usage tracking is thin**: only 3 of 58 deployed skills carry a usage record; the other 0 carry no Hermes usage event. +3. **Category overlays are containers, not skills** — 12 dirs hold the actual skills as subdirectories and are not counted as skills themselves. + +## Skill table + +| Skill | Deployed | Usage state | Use count | Class | +|---|---|---|---|---| +| airtable | yes | - | - | deployed (dir), no usage record | +| apple-notes | yes | - | - | deployed (dir), no usage record | +| apple-reminders | yes | - | - | deployed (dir), no usage record | +| architecture-diagram | yes | - | - | deployed (dir), no usage record | +| arxiv | yes | - | - | deployed (dir), no usage record | +| ascii-video | yes | - | - | deployed (dir), no usage record | +| baoyu-infographic | yes | - | - | deployed (dir), no usage record | +| blocked-page-recovery | yes | - | - | deployed (dir), no usage record | +| box | yes | - | - | deployed (dir), no usage record | +| claude-code | yes | - | - | deployed (dir), no usage record | +| claude-design | yes | - | - | deployed (dir), no usage record | +| codebase-inspection | yes | - | - | deployed (dir), no usage record | +| codex | yes | - | - | deployed (dir), no usage record | +| competitor-news-monitor | yes | - | - | deployed (dir), no usage record | +| computer-use | yes | - | - | deployed (dir), no usage record | +| design-md | yes | - | - | deployed (dir), no usage record | +| document-to-action-items | yes | - | - | deployed (dir), no usage record | +| docx | yes | - | - | deployed (dir), no usage record | +| dogfood | yes | - | - | deployed (dir), no usage record | +| email-inbox-triage | yes | active | 1 | deployed + usage-tracked / active | +| findmy | yes | - | - | deployed (dir), no usage record | +| gif-search | yes | - | - | deployed (dir), no usage record | +| github | yes | - | - | deployed (dir), no usage record | +| google-workspace | yes | - | - | deployed (dir), no usage record | +| grounded-citations | yes | - | - | deployed (dir), no usage record | +| hermes-agent | yes | active | 11 | deployed + usage-tracked / active | +| hermes-agent-skill-authoring | yes | - | - | deployed (dir), no usage record | +| himalaya | yes | - | - | deployed (dir), no usage record | +| humanizer | yes | - | - | deployed (dir), no usage record | +| imessage | yes | - | - | deployed (dir), no usage record | +| inspecting-hermes-desktop-dom | yes | - | - | deployed (dir), no usage record | +| llm-wiki | yes | - | - | deployed (dir), no usage record | +| manim-video | yes | - | - | deployed (dir), no usage record | +| maps | yes | - | - | deployed (dir), no usage record | +| meeting-action-items | yes | active | 1 | deployed + usage-tracked / active | +| node-inspect-debugger | yes | - | - | deployed (dir), no usage record | +| notion | yes | - | - | deployed (dir), no usage record | +| obsidian | yes | - | - | deployed (dir), no usage record | +| opencode | yes | - | - | deployed (dir), no usage record | +| p5js | yes | - | - | deployed (dir), no usage record | +| pdf | yes | - | - | deployed (dir), no usage record | +| popular-web-designs | yes | - | - | deployed (dir), no usage record | +| powerpoint | yes | - | - | deployed (dir), no usage record | +| product-price-monitor | yes | - | - | deployed (dir), no usage record | +| python-debugpy | yes | - | - | deployed (dir), no usage record | +| requesting-code-review | yes | - | - | deployed (dir), no usage record | +| sdlc-review | yes | - | - | deployed (dir), no usage record | +| simplify-code | yes | - | - | deployed (dir), no usage record | +| songsee | yes | - | - | deployed (dir), no usage record | +| songwriting-and-ai-music | yes | - | - | deployed (dir), no usage record | +| spike | yes | - | - | deployed (dir), no usage record | +| systematic-debugging | yes | - | - | deployed (dir), no usage record | +| teams-meeting-pipeline | yes | - | - | deployed (dir), no usage record | +| test-driven-development | yes | - | - | deployed (dir), no usage record | +| weekly-review-planning | yes | - | - | deployed (dir), no usage record | +| xlsx | yes | - | - | deployed (dir), no usage record | +| xurl | yes | - | - | deployed (dir), no usage record | +| youtube-content | yes | - | - | deployed (dir), no usage record | + +## Verdict + +Deployment is fully attested: 58 of 58 canonical skills are deployed. What is sparse is Hermes' *usage logging*: 3 of 58 deployed skills carry no usage record, so triage can confirm deployment but not per-skill usage. diff --git a/wizard-work/sol/sol-profile-skill-triage.py b/wizard-work/sol/sol-profile-skill-triage.py new file mode 100644 index 0000000..00d0493 --- /dev/null +++ b/wizard-work/sol/sol-profile-skill-triage.py @@ -0,0 +1,195 @@ +#!/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-.json (machine-readable record) + - sol-profile-skill-triage-.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)