Files
troupe-standup/wizard-work/ledger/verify_skill_catalog.py
T

210 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""Independent verifier for the ledger skill-catalog extraction artifact.
Re-derives every skill row from the live filesystem (/root/.hermes/profiles/
ledger/skills) and diffs row-for-row against the committed artifact files:
skill_metadata.csv, skill_metadata.jsonl, skill_metadata.md,
source_manifest.txt, summary.txt. Exits 0 iff all match.
This is a real test, not a tautology: the committed files are regenerated from
raw SKILL.md/DESCRIPTION.md frontmatter and compared to what was committed.
"""
import csv
import hashlib
import json
import os
import re
import sys
PROFILE = "/root/.hermes/profiles/ledger/skills"
OUT_DIR = "/root/troupe-standup/wizard-work/ledger"
CSV_PATH = os.path.join(OUT_DIR, "skill_metadata.csv")
JSONL_PATH = os.path.join(OUT_DIR, "skill_metadata.jsonl")
MD_PATH = os.path.join(OUT_DIR, "skill_metadata.md")
MAN_PATH = os.path.join(OUT_DIR, "source_manifest.txt")
SUM_PATH = os.path.join(OUT_DIR, "summary.txt")
CSV_HEADER = ["category", "name", "description", "version", "platforms", "file", "sha256"]
def sha256_of(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def parse_frontmatter(text):
if not text.startswith("---"):
return "", "", ""
m = re.match(r"---\s*\n(.*?)\n---|.*?$", text, re.DOTALL)
if not m or m.group(1) is None:
return "", "", ""
body = m.group(1)
desc = version = platforms = ""
for raw in body.splitlines():
line = raw.rstrip()
if not line.strip() or line.strip().startswith("#"):
continue
if ":" in line:
k, _, v = line.partition(":")
k, v = k.strip(), v.strip()
if k == "description":
desc = v.strip('"').strip("'")
elif k == "version":
version = v.strip('"').strip("'")
elif k == "platforms":
platforms = v
return desc, version, platforms
def re_derive_rows():
rows = []
for dirpath, dirnames, filenames in os.walk(PROFILE):
dirnames.sort()
for fn in sorted(filenames):
if fn not in ("SKILL.md", "DESCRIPTION.md"):
continue
full = os.path.join(dirpath, fn)
rel = os.path.relpath(full, PROFILE)
parts = rel.split(os.sep)
category = parts[0]
name = os.path.splitext(os.path.splitext(rel)[0])[0].replace(os.sep, "/")
text = open(full, encoding="utf-8").read()
h = sha256_of(full)
if rel.endswith("SKILL.md"):
desc, version, platforms = parse_frontmatter(text)
else:
desc, version, platforms = parse_frontmatter(text)
desc = desc.strip()
if not desc:
desc = text.splitlines()[0].strip()
version = "1.0.0"
platforms = "linux"
rows.append((category, name, desc, version, platforms, rel, h))
rows.sort(key=lambda r: r[5])
return rows
def derive_csv_text(rows):
import io
buf = io.StringIO()
w = csv.writer(buf, lineterminator="\n")
w.writerow(["category", "name", "description", "version", "platforms", "file", "sha256"])
for cat, name, desc, ver, plat, fn, h in rows:
w.writerow([cat, name, desc, ver, plat, fn, h])
return buf.getvalue()
def derive_jsonl_text(rows):
out = []
for cat, name, desc, ver, plat, fn, h in rows:
out.append(json.dumps({
"category": cat, "name": name, "description": desc, "version": ver,
"platforms": plat, "file": fn, "sha256": h}, ensure_ascii=False))
return "\n".join(out) + "\n"
def main():
rows = re_derive_rows()
failures = 0
def check(label, expected, actual):
nonlocal failures
if expected == actual:
print(f" OK {label}")
else:
failures += 1
print(f" FAIL {label}")
# 1) CSV
print("CSV skill_metadata.csv")
committed_csv = open(CSV_PATH, encoding="utf-8").read()
check("byte-identical", derive_csv_text(rows), committed_csv)
with open(CSV_PATH, newline="", encoding="utf-8") as f:
r = csv.reader(f)
header = next(r)
committed_rows = [tuple(x) for x in r]
check("header", CSV_HEADER, header)
check("row count", str(len(rows)), str(len(committed_rows)))
check("records", [tuple(x) for x in rows], committed_rows)
# 2) JSONL
print("JSONL skill_metadata.jsonl")
committed_jsonl = open(JSONL_PATH, encoding="utf-8").read()
check("byte-identical", derive_jsonl_text(rows), committed_jsonl)
# 3) Markdown
print("Markdown skill_metadata.md")
counts = {}
for cat, name, desc, ver, plat, fn, h in rows:
counts[cat] = counts.get(cat, 0) + 1
md_lines = []
md_lines.append("# Ledger Profile — Installed Skills Catalog")
md_lines.append("")
md_lines.append(f"Source: every `SKILL.md` and `DESCRIPTION.md` under `{PROFILE}` "
"(real filesystem, parsed 2026-10-30).")
md_lines.append("")
md_lines.append("## Counts by category")
md_lines.append("")
md_lines.append("| Category | Skills |")
md_lines.append("|---|---|")
for cat in sorted(counts):
md_lines.append(f"| {cat} | {counts[cat]} |")
md_lines.append("")
md_lines.append(f"**Total skills: {len(rows)}**")
md_lines.append("")
md_lines.append("## All skills")
md_lines.append("")
md_lines.append("| Category | Name | Version | Platforms | File | Description |")
md_lines.append("|---|---|---|---|---|---|")
for cat, name, desc, ver, plat, fn, h in rows:
d = desc.replace("|", "\\|")
md_lines.append(f"| {cat} | {name} | {ver} | {plat} | {fn} | {d} |")
committed_md = open(MD_PATH, encoding="utf-8").read()
check("byte-identical", "\n".join(md_lines) + "\n", committed_md)
# 4) source_manifest.txt
print("source_manifest.txt")
man_lines = [f"Source: {PROFILE}", f"Skill files found: {len(rows)}", "",
"rel_path\tbytes\tsha256"]
for cat, name, desc, ver, plat, fn, h in rows:
full = os.path.join(PROFILE, fn)
size = os.path.getsize(full)
man_lines.append(f"{fn}\t{size}\t{h}")
committed_man = open(MAN_PATH, encoding="utf-8").read()
check("byte-identical", "\n".join(man_lines) + "\n", committed_man)
# 5) summary.txt
print("summary.txt")
summ_lines = [
"Ledger Profile — Installed Skills Catalog — extraction summary",
"=" * 64,
"",
f"Source directory : {PROFILE}",
f"Files scanned : {len(rows)}",
f"Categories : {len(counts)}",
"Category counts : " + ", ".join(f"{c}={counts[c]}" for c in sorted(counts)),
f"SKILL.md entries : {sum(1 for r in rows if r[5].endswith('SKILL.md'))}",
f"DESCRIPTION.md entries : {sum(1 for r in rows if r[5].endswith('DESCRIPTION.md'))}",
f"Rows with version field : {sum(1 for r in rows if r[3])}",
"",
"Artifacts produced : skill_metadata.csv, skill_metadata.jsonl,",
" skill_metadata.md, source_manifest.txt,",
" summary.txt",
"",
]
committed_sum = open(SUM_PATH, encoding="utf-8").read()
check("byte-identical", "\n".join(summ_lines), committed_sum)
if failures:
print(f"FAIL: {failures} mismatch(es)")
sys.exit(1)
print("PASS: all skill-catalog artifacts re-derive from the filesystem.")
sys.exit(0)
if __name__ == "__main__":
main()