#!/usr/bin/env python3 """Build the untracked-file triage classification for the troupe repo. Universe (frozen, stable) -------------------------- Every untracked, non-ignored file under /root/troupe-standup, EXCLUDING this tool's own output directory ("wizard-work/sol/") and top-level CI CHECK files. The remainder is the classifiable triage subject. Rule ---- Single canonical rule `category(p)`; first match wins, then extension: probe wizard-work/null/ scratch probing output deliverable finished scripts + tests + output under wizard-work/spark report analysis / extraction / archive probe report tool standalone Python tool data data artifact (.json/.csv/.tsv/.db/.yaml/.yml/.toml/.lock/.html/.css) report .md/.txt analysis other everything else Outputs ------- untracked-classify-snapshot.txt frozen universe (paths, one/line) untracked-classify.tsv pathcategory per row untracked-classify.json machine-readable counts untracked-classify-summary.md human-readable report Run: python3 build_untracked_classify.py """ import json import os import subprocess from collections import Counter REPO_ROOT = "/root/troupe-standup" SOL = os.path.join(REPO_ROOT, "wizard-work", "sol") SNAPSHOT = os.path.join(SOL, "untracked-classify-snapshot.txt") MANIFEST = os.path.join(SOL, "untracked-classify.tsv") OUT_JSON = os.path.join(SOL, "untracked-classify.json") OUT_SUMMARY = os.path.join(SOL, "untracked-classify-summary.md") # ---- canonical rule (re-viewed verbatim in verify_untracked_classify.py) --- def category(p): base = os.path.basename(p) if p.startswith("wizard-work/null/"): return "probe" if p.startswith("wizard-work/spark/"): return "deliverable" if p.startswith("wizard-work/sphinx/"): return "report" if p.startswith("wizard-work/ledger/"): return "report" if p.startswith("wizard-work/"): return "other" if base.endswith(".py"): return "tool" if base.endswith((".json", ".csv", ".tsv", ".db", ".yaml", ".yml", ".toml", ".lock", ".html", ".css")): return "data" if base.endswith((".md", ".txt")): return "report" return "other" # --------------------------------------------------------------------------- def run_git(args): return subprocess.run( ["git", "-C", REPO_ROOT] + args, check=True, capture_output=True, text=True).stdout.splitlines() def is_excluded(p): if p.startswith("wizard-work/sol/"): return True if os.path.basename(p).startswith("CHECK-"): return True return False def main(): files = run_git(["ls-files", "--others", "--exclude-standard"]) files = sorted(p for p in files if p and not is_excluded(p)) assert files, "no classifiable untracked files found" counts = Counter() rows = [] for p in files: c = category(p) counts[c] += 1 rows.append((p, c)) rows.sort(key=lambda r: r[0]) with open(SNAPSHOT, "w", encoding="utf-8") as f: f.write("\n".join(p for p, _ in rows) + "\n") with open(MANIFEST, "w", encoding="utf-8") as f: f.write("path\tcategory\n") for p, c in rows: f.write("{}\t{}\n".format(p, c)) total = len(rows) buckets = ", ".join("{}={}".format(k, counts[k]) for k in sorted(counts)) print("wrote: snapshot={} rows={}".format(SNAPSHOT, total)) print("buckets: {}".format(buckets)) payload = { "count": total, "categories": {k: counts[k] for k in sorted(counts)}, "bucket_total": sum(counts.values()), } with open(OUT_JSON, "w", encoding="utf-8") as f: json.dump(payload, f, indent=2) f.write("\n") lines = [] lines.append("# Untracked-file triage classification") lines.append("") lines.append("Frozen at build time: {} untracked, non-ignored files under " "/root/troupe-standup (universe excludes this tool's output " "dir `wizard-work/sol/` and top-level `CHECK-*` CI files)." .format(total)) lines.append("") lines.append("## Rule") lines.append("") lines.append("Single canonical rule `category(p)` (see " "`verify_untracked_classify.py`):") lines.append("") lines.append("- `wizard-work/null/` -> `probe`") lines.append("- `wizard-work/spark/` -> `deliverable`") lines.append("- `wizard-work/sphinx/` -> `report`") lines.append("- `wizard-work/ledger/` -> `report`") lines.append("- `wizard-work/` (none of the above) -> `other`") lines.append("- `.py` -> `tool`") lines.append("- `.json/.csv/.tsv/.db/.yaml/.yml/.toml/.lock/.html/.css` -> " "`data`") lines.append("- `.md/.txt` -> `report`") lines.append("- else -> `other`") lines.append("") lines.append("## Result") lines.append("") lines.append("| bucket | count |") lines.append("|---|---|") for b in sorted(counts): lines.append("| {} | {} |".format(b, counts[b])) lines.append("| --- | --- |") lines.append("| **total** | **{}** |".format(total)) lines.append("") lines.append("## Artifacts") lines.append("") lines.append("- `untracked-classify-snapshot.txt`: frozen path universe " "({} paths)".format(total)) lines.append("- `untracked-classify.tsv`: `pathcategory` per row") lines.append("- `untracked-classify.json`: machine-readable counts") lines.append("- `verify_untracked_classify.py`: independent re-derivation " "gate (exit 0 = manifest matches the rule)") lines.append("") with open(OUT_SUMMARY, "w", encoding="utf-8") as f: f.write("\n".join(lines)) if __name__ == "__main__": main()