157 lines
5.7 KiB
Python
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()
|