Files
troupe-standup/wizard-work/sol/build_untracked_classify.py

157 lines
5.7 KiB
Python

#!/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 path<TAB>category 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`: `path<TAB>category` 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()