#!/usr/bin/env python3 """Build the untracked-file triage manifest. Universe: every file `git ls-files --others --exclude-standard` reports in /root/troupe-standup (untracked, non-ignored). Each path is classified by the rule in `category()` into exactly one bucket, and the result is written as a strict TSV (path \t category \t rule_note) plus a JSON manifest. Rule notes are verbatim, so the category of any row can be re-derived from the path alone; the verifier does exactly that re-derivation. """ import json import os import subprocess from collections import Counter REPO = "/root/troupe-standup" OUT_DIR = os.path.join(REPO, "wizard-work", "sol") MANIFEST_TSV = os.path.join(OUT_DIR, "untracked-classify.tsv") MANIFEST_JSON = os.path.join(OUT_DIR, "untracked-classify.json") SUMMARY_MD = os.path.join(OUT_DIR, "untracked-classify-summary.md") # Bucket order matters: earlier buckets win on overlap. 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 rule_note(p): c = category(p) if c == "probe": return "wizard-work/null scratch probing output" if c == "deliverable": return "finished scripts + tests + output under wizard-work/spark" if c == "report": return "analysis / extraction / archive probe report" if c == "tool": return "standalone Python tool" if c == "data": return "data artifact (json/csv/tsv/db/yaml/toml/lock/html/css)" if c == "other": return "other file type" return "unclassified" def main(): out = subprocess.run( ["git", "-C", REPO, "ls-files", "--others", "--exclude-standard"], capture_output=True, text=True, check=True).stdout paths = sorted(set(out.splitlines())) paths = [p for p in paths if p] # drop empties rows = [(p, category(p), rule_note(p)) for p in paths] os.makedirs(OUT_DIR, exist_ok=True) with open(MANIFEST_TSV, "w") as f: f.write("path\tcategory\trule_note\n") for p, c, n in rows: f.write(f"{p}\t{c}\t{n}\n") with open(MANIFEST_JSON, "w") as f: json.dump({"count": len(rows), "snapshot_paths": paths, "rows": rows, "buckets": Counter(c for _, c, _ in rows)}, f, indent=2) with open(os.path.join(OUT_DIR, "untracked-classify-snapshot.txt"), "w") as f: f.write("\n".join(paths) + "\n") summary = ["# untracked-file triage manifest", "", "Universe: `git ls-files --others --exclude-standard` in the" " troupe repo, refreshed 2026-10-28.", "Vault (absolute path): " + MANIFEST_TSV, "Frozen universe (paths, one per line):" " " + os.path.join(OUT_DIR, "untracked-classify-snapshot.txt"), "Sum: %d files" % len(rows), ""] + ["%s: %d" % (k, v) for k, v in sorted(Counter(c for _, c, _ in rows).items())] + [""] open(SUMMARY_MD, "w").write("\n".join(summary)) print("count", len(rows)) for k, v in sorted(Counter(c for _, c, _ in rows).items()): print(k, v) if __name__ == "__main__": main()