Files
troupe-standup/wizard-work/sol/build_manifest.py
T

97 lines
3.7 KiB
Python

#!/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()