diff --git a/wizard-work/sol/CHECK-sol-profile-file-class-2026-11-02.sh b/wizard-work/sol/CHECK-sol-profile-file-class-2026-11-02.sh new file mode 100644 index 0000000..430a07c --- /dev/null +++ b/wizard-work/sol/CHECK-sol-profile-file-class-2026-11-02.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# CHECK -- sol: sol-profile content file-class census 2026-11-02 (forge: verify lane). +# Exit 0 iff the committed artifact re-derives from the LIVE sol profile by an +# independently written rule table (name suffix + directory name); every frozen +# path is a real top-level entry in the repo. +set -u + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)/.." +REPO_ROOT="$(cd "$REPO_ROOT" && pwd)" +cd "$REPO_ROOT" || exit 2 + +exec python3 wizard-work/sol/verify_sol_profile_file_class_2026_11_02.py diff --git a/wizard-work/sol/build_sol_profile_file_class_2026_11_02.py b/wizard-work/sol/build_sol_profile_file_class_2026_11_02.py new file mode 100644 index 0000000..444625d --- /dev/null +++ b/wizard-work/sol/build_sol_profile_file_class_2026_11_02.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Generator: class-classify the SOL profile's top-level entries by content suffix. + +Fresh, independent from any earlier sol-profile-triage artifact. Re-runnable: +re-run against the live profile and the artifact re-derives. + +Verifiable by re-running the same rule table against the live profile directory. + +Writes: + /root/.hermes/wizard-outputs/sol/sol-profile-file-class-2026-11-02.json +""" +import json +import os +import sys +from collections import Counter + +PROFILE = "/root/.hermes/profiles/sol" +OUT = "/root/.hermes/wizard-outputs/sol/sol-profile-file-class-2026-11-02.json" + +# Independent rule table: suffix based (dir-name exceptions below). +SUFFIX_RULES = [ + (".db", "STATE_DB::FILE"), + (".py", "SCRIPT_PY::FILE"), + (".sh", "SCRIPT_SH::FILE"), + (".yaml", "CONFIG_YAML::FILE"), + (".yml", "CONFIG_YAML::FILE"), + (".json", "CONFIG_JSON::FILE"), + (".lock", "LOCK::FILE"), + (".md", "DOC::FILE"), + (".txt", "LOG_TXT::FILE"), + (".etag", "CACHE_ETAG::FILE"), + (".mp3", "ARTIFACT::FILE"), + (".png", "ARTIFACT::FILE"), + (".jpg", "ARTIFACT::FILE"), +] +# Exact top-level name -> class for directories (also used for files that are +# directories by name but match no suffix rule). +DIR_RULES = { + "skills": "SKILL_CONTAINER::DIR", + "vault": "VAULT::DIR", + "memories": "MEMORY_DIR::DIR", + "logs": "LOG_DIR::DIR", + "cache": "CACHE_DIR::DIR", + "backups": "BACKUP_DIR::DIR", + "forge": "FORGE_DIR::DIR", + "bin": "BIN_DIR::DIR", + "lsp": "LSP_DIR::DIR", + "wizard-outputs": "OUTPUT_DIR::DIR", +} +UNCLASSIFIED_DIR = "DIR::UNCLASSIFIED" + + +def classify(name, full): + if os.path.isdir(full): + return DIR_RULES.get(name, UNCLASSIFIED_DIR) + low = name.lower() + for suffix, cls in SUFFIX_RULES: + if low.endswith(suffix): + return cls + return "FILE::UNCLASSIFIED" + + +def main(): + try: + names = os.listdir(PROFILE) + except OSError as exc: + print("FATAL: cannot list profile: %s" % exc) + return 1 + + entries = [] + counts = Counter() + for name in sorted(names): + full = os.path.join(PROFILE, name) + kind = "dir" if os.path.isdir(full) else "file" + cls = classify(name, full) + entries.append({"name": name, "kind": kind, "class": cls}) + counts[cls] += 1 + + artifact = { + "profile": PROFILE, + "generated_at": "2026-11-02T09:30:00Z", + "type": "sol-profile-file-class-2026-11-02", + "generated_for": "2026-11-02", + "total_entries": len(names), + "counts": {"file": sum(1 for e in entries if e["kind"] == "file"), + "dir": sum(1 for e in entries if e["kind"] == "dir")}, + "file_classes": dict(sorted(counts.items())), + "entries": entries, + } + + os.makedirs(os.path.dirname(OUT), exist_ok=True) + with open(OUT, "w", encoding="utf-8") as fh: + json.dump(artifact, fh, indent=2, sort_keys=True) + fh.write("\n") + print("written: %s" % OUT) + print("entries: %d" % len(entries)) + print("classes: %s" % json.dumps(artifact["file_classes"], sort_keys=True)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/wizard-work/sol/sol-profile-file-class-2026-11-02.json b/wizard-work/sol/sol-profile-file-class-2026-11-02.json new file mode 100644 index 0000000..97f6145 --- /dev/null +++ b/wizard-work/sol/sol-profile-file-class-2026-11-02.json @@ -0,0 +1,379 @@ +{ + "counts": { + "dir": 28, + "file": 41 + }, + "entries": [ + { + "class": "FILE::UNCLASSIFIED", + "kind": "file", + "name": ".env" + }, + { + "class": "FILE::UNCLASSIFIED", + "kind": "file", + "name": ".env.bak-20261014T165715Z" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": ".env.lock" + }, + { + "class": "CONFIG_JSON::FILE", + "kind": "file", + "name": ".skills_prompt_snapshot.json" + }, + { + "class": "DOC::FILE", + "kind": "file", + "name": "SOUL.md" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_check_repo_json.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_gen_daily_ledger_json.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_gen_daily_ledger_report.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_gen_daily_triage.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_gen_daily_triage_fresh.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "_verify_daily_triage.py" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "audio_cache" + }, + { + "class": "CONFIG_JSON::FILE", + "kind": "file", + "name": "auth.json" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "auth.lock" + }, + { + "class": "BACKUP_DIR::DIR", + "kind": "dir", + "name": "backups" + }, + { + "class": "BIN_DIR::DIR", + "kind": "dir", + "name": "bin" + }, + { + "class": "CACHE_DIR::DIR", + "kind": "dir", + "name": "cache" + }, + { + "class": "CONFIG_YAML::FILE", + "kind": "file", + "name": "config.yaml" + }, + { + "class": "FILE::UNCLASSIFIED", + "kind": "file", + "name": "config.yaml.bak-20261014T165715Z" + }, + { + "class": "CONFIG_YAML::FILE", + "kind": "file", + "name": "context_length_cache.yaml" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "context_length_cache.yaml.lock" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "cron" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "cron-fleet-triage-2026-10-17.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "cron-fleet-triage-2026-10-18.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "cron-fleet-verify.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "diag.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "diag2.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "diag3.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "diag4.py" + }, + { + "class": "FORGE_DIR::DIR", + "kind": "dir", + "name": "forge" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "gateway.lock" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "gen_sol_profile_triage.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "gen_sol_profile_triage_2026-10-31.py" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "home" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "hooks" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "image_cache" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "inventory_skills.py" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "keys" + }, + { + "class": "LOG_DIR::DIR", + "kind": "dir", + "name": "logs" + }, + { + "class": "LSP_DIR::DIR", + "kind": "dir", + "name": "lsp" + }, + { + "class": "MEMORY_DIR::DIR", + "kind": "dir", + "name": "memories" + }, + { + "class": "CACHE_ETAG::FILE", + "kind": "file", + "name": "models_dev_cache.etag" + }, + { + "class": "CONFIG_JSON::FILE", + "kind": "file", + "name": "models_dev_cache.json" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "pairing" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "plans" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "platforms" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "plugin-update-checks" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "plugins" + }, + { + "class": "STATE_DB::FILE", + "kind": "file", + "name": "projects.db" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "runtime" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "sandboxes" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "sessions" + }, + { + "class": "SKILL_CONTAINER::DIR", + "kind": "dir", + "name": "skills" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "skins" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "source-checks" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "state" + }, + { + "class": "STATE_DB::FILE", + "kind": "file", + "name": "state.db" + }, + { + "class": "FILE::UNCLASSIFIED", + "kind": "file", + "name": "state.db-shm" + }, + { + "class": "FILE::UNCLASSIFIED", + "kind": "file", + "name": "state.db-wal" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "state.db.auto-maintenance.lock" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "state.db.fts_rebuild.lock" + }, + { + "class": "LOCK::FILE", + "kind": "file", + "name": "state.db.quarantine.lock" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "triage_check.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "triage_profiles.py" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "triage_skill_report.py" + }, + { + "class": "VAULT::DIR", + "kind": "dir", + "name": "vault" + }, + { + "class": "SCRIPT_PY::FILE", + "kind": "file", + "name": "verify_skills.py" + }, + { + "class": "OUTPUT_DIR::DIR", + "kind": "dir", + "name": "wizard-outputs" + }, + { + "class": "DIR::UNCLASSIFIED", + "kind": "dir", + "name": "workspace" + } + ], + "file_classes": { + "BACKUP_DIR::DIR": 1, + "BIN_DIR::DIR": 1, + "CACHE_DIR::DIR": 1, + "CACHE_ETAG::FILE": 1, + "CONFIG_JSON::FILE": 3, + "CONFIG_YAML::FILE": 2, + "DIR::UNCLASSIFIED": 18, + "DOC::FILE": 1, + "FILE::UNCLASSIFIED": 5, + "FORGE_DIR::DIR": 1, + "LOCK::FILE": 7, + "LOG_DIR::DIR": 1, + "LSP_DIR::DIR": 1, + "MEMORY_DIR::DIR": 1, + "OUTPUT_DIR::DIR": 1, + "SCRIPT_PY::FILE": 20, + "SKILL_CONTAINER::DIR": 1, + "STATE_DB::FILE": 2, + "VAULT::DIR": 1 + }, + "generated_at": "2026-11-02T09:30:00Z", + "generated_for": "2026-11-02", + "profile": "/root/.hermes/profiles/sol", + "total_entries": 69, + "type": "sol-profile-file-class-2026-11-02" +} diff --git a/wizard-work/sol/verify_sol_profile_file_class_2026_11_02.py b/wizard-work/sol/verify_sol_profile_file_class_2026_11_02.py new file mode 100644 index 0000000..d00c635 --- /dev/null +++ b/wizard-work/sol/verify_sol_profile_file_class_2026_11_02.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Independent re-verification for sol-profile-file-class-2026-11-02.json. + +Re-derives the classification from the LIVE sol profile (NOT from the +artifact) using its own rule table written separately from the generator, then +checks the committed artifact entry-by-entry. Exit 0 = correct, nonzero = wrong. + +Checks: +(a) every live top-level entry appears exactly once in the artifact; +(b) every class label belongs to the vocabulary; +(c) every class count re-derives exactly from the disk. +""" +import json +import os +import sys + +PROFILE = "/root/.hermes/profiles/sol" +ARTIFACT = "/root/troupe-standup/wizard-work/sol/sol-profile-file-class-2026-11-02.json" + +# Independent vocabulary (written separately from the generator's rule table). +VOCAB = { + "BACKUP_DIR::DIR", "BIN_DIR::DIR", "CACHE_DIR::DIR", "CACHE_ETAG::FILE", + "CONFIG_JSON::FILE", "CONFIG_YAML::FILE", "DIR::UNCLASSIFIED", "DOC::FILE", + "FILE::UNCLASSIFIED", "FORGE_DIR::DIR", "LOCK::FILE", "LOG_DIR::DIR", + "LSP_DIR::DIR", "MEMORY_DIR::DIR", "OUTPUT_DIR::DIR", + "SCRIPT_PY::FILE", "SKILL_CONTAINER::DIR", "STATE_DB::FILE", "VAULT::DIR", + "BIN_DIR::DIR", +} + + +def suffix_rule(name): + low = name.lower() + if low.endswith(".db"): + return "STATE_DB::FILE" + if low.endswith(".py"): + return "SCRIPT_PY::FILE" + if low.endswith(".sh"): + return "SCRIPT_SH::FILE" + if low.endswith((".yaml", ".yml")): + return "CONFIG_YAML::FILE" + if low.endswith(".json"): + return "CONFIG_JSON::FILE" + if low.endswith(".lock"): + return "LOCK::FILE" + if low.endswith(".md"): + return "DOC::FILE" + if low.endswith(".txt"): + return "LOG_TXT::FILE" + if low.endswith(".etag"): + return "CACHE_ETAG::FILE" + if low.endswith((".mp3", ".png", ".jpg", ".jpeg")): + return "ARTIFACT::FILE" + return "FILE::UNCLASSIFIED" + + +def dir_rule(name): + # Exact-name directory specials, then generic directory. + if name in ("skills",): + return "SKILL_CONTAINER::DIR" + if name in ("vault",): + return "VAULT::DIR" + if name in ("memories",): + return "MEMORY_DIR::DIR" + if name in ("logs",): + return "LOG_DIR::DIR" + if name in ("cache",): + return "CACHE_DIR::DIR" + if name in ("backups",): + return "BACKUP_DIR::DIR" + if name in ("forge",): + return "FORGE_DIR::DIR" + if name in ("bin",): + return "BIN_DIR::DIR" + if name in ("lsp",): + return "LSP_DIR::DIR" + if name in ("wizard-outputs",): + return "OUTPUT_DIR::DIR" + return "DIR::UNCLASSIFIED" + + +def recompute(): + counts = {} + for name in os.listdir(PROFILE): + full = os.path.join(PROFILE, name) + if os.path.isdir(full): + cls = dir_rule(name) + else: + cls = suffix_rule(name) + counts[cls] = counts.get(cls, 0) + 1 + return counts + + +def main(): + errs = [] + try: + with open(ARTIFACT, encoding="utf-8") as fh: + art = json.load(fh) + except Exception as exc: + print("FAIL: cannot load artifact: %s" % exc) + return 1 + + try: + disk_names = sorted(os.listdir(PROFILE)) + except OSError as exc: + print("FAIL: cannot list profile: %s" % exc) + return 1 + + art_names = [e.get("name") for e in art.get("entries", [])] + if len(art_names) != len(set(art_names)): + errs.append("duplicate entry names in artifact") + if set(art_names) != set(disk_names): + missing = sorted(set(disk_names) - set(art_names)) + extra = sorted(set(art_names) - set(disk_names)) + errs.append("name set mismatch; missing=%s extra=%s" % (missing, extra)) + + live_counts = recompute() + disk_names = sorted(os.listdir(PROFILE)) + live_files = sum(1 for n in disk_names if not os.path.isdir(os.path.join(PROFILE, n))) + live_dirs = len(disk_names) - live_files + if art.get("counts") != {"file": live_files, "dir": live_dirs}: + errs.append("counts mismatch: artifact=%r live=%r" + % (art.get("counts"), {"file": live_files, "dir": live_dirs})) + if art.get("file_classes") != live_counts: + errs.append("file_classes mismatch: artifact=%r live=%r" % (art.get("file_classes"), live_counts)) + if art.get("total_entries") != len(disk_names): + errs.append("total_entries mismatch: %r" % art.get("total_entries")) + + for e in art.get("entries", []): + if e.get("class") not in VOCAB: + errs.append("unknown class label %r" % (e.get("class"),)) + want = dir_rule(e["name"]) if os.path.isdir(os.path.join(PROFILE, e["name"])) else suffix_rule(e["name"]) + if want != e.get("class"): + errs.append("class mismatch %s: artifact=%r recompute=%r" % (e["name"], e.get("class"), want)) + + if errs: + print("CHECK FAIL") + for e in errs: + print(" -", e) + return 1 + + files = sum(1 for n in disk_names if not os.path.isdir(os.path.join(PROFILE, n))) + dirs = len(disk_names) - files + print("CHECK OK: %d entries (%d files, %d dirs); artifact == independently " + "recomputed from live profile disk" % (len(disk_names), files, dirs)) + return 0 + + +if __name__ == "__main__": + sys.exit(main())