124 lines
4.1 KiB
Python
124 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Classify every untracked file in the troupe repo into a persistent triage
|
|
bucket. Deterministic rule: the first matching prefix wins.
|
|
|
|
Output:
|
|
untracked-classify-2026-10-29.json (machine-readable: path, bucket, rule)
|
|
untracked-classify-summary.md (human readable, with total + per-bucket)
|
|
"""
|
|
import json
|
|
import subprocess
|
|
from collections import Counter, OrderedDict
|
|
|
|
REPO_ROOT = "/root/troupe-standup"
|
|
OUT_DIR = REPO_ROOT + "/wizard-work/sol"
|
|
|
|
# Ordered (bucket, canonical match). First match wins. The tooling directory
|
|
# "wizard-work/sol/" holds this classifer/checker/JSON/summary (the "tool" bucket),
|
|
# and the stale top-level "CHECK-sol-untracked-classify" is also tooling.
|
|
RULES = [
|
|
("tool", "wizard-work/sol/"),
|
|
("tool", "CHECK-sol-untracked-classify"),
|
|
("probe", "wizard-work/null/"),
|
|
("sphinx", "wizard-work/sphinx/"),
|
|
("wizard-work", "wizard-work/"),
|
|
("wizard-outputs", "wizard-outputs/"),
|
|
("replies", "replies/"),
|
|
("checkins", "checkins/"),
|
|
("docs", "docs/"),
|
|
("inbox", "inbox/"),
|
|
("recoveries", "recoveries/"),
|
|
("tools", "tools/"),
|
|
("dump", "d.jsonl"),
|
|
("dump", "s.json"),
|
|
("kanban", "kanban.db"),
|
|
("inventory", "repo_"),
|
|
("ci", "CHECK-"),
|
|
("ci", "."),
|
|
]
|
|
|
|
|
|
def classify(path):
|
|
for bucket, prefix in RULES:
|
|
if path == prefix or path.startswith(prefix):
|
|
return bucket
|
|
return "other"
|
|
|
|
|
|
def run_git(args):
|
|
return subprocess.run(
|
|
["git", "-C", REPO_ROOT] + args, check=True,
|
|
capture_output=True, text=True,
|
|
).stdout.splitlines()
|
|
|
|
|
|
def main():
|
|
# File set: independent enumeration via git LS.
|
|
# Subject = the untracked working-tree files that are NOT this tool's own
|
|
# output directory ("wizard-work/sol/") and not the stale top-level CI
|
|
# leftover; everything else is the classifiable triage subject.
|
|
files = run_git(["ls-files", "--others", "--exclude-standard"])
|
|
files = [f for f in files if f]
|
|
subject = [p for p in files
|
|
if not (p.startswith("wizard-work/sol/")
|
|
or p == "CHECK-sol-untracked-classify")]
|
|
|
|
classified = []
|
|
counts = Counter()
|
|
for path in sorted(subject):
|
|
bucket = classify(path)
|
|
counts[bucket] += 1
|
|
matched = next(p for b, p in RULES if b == bucket and
|
|
(path == p or path.startswith(p)))
|
|
classified.append({
|
|
"path": path,
|
|
"bucket": bucket,
|
|
"rule": matched,
|
|
})
|
|
|
|
assert len(classified) == len(subject), "dedup lost a file"
|
|
assert len(set(c["path"] for c in classified)) == len(subject), "duplicate path"
|
|
|
|
with open(OUT_DIR + "/untracked-classify-2026-10-29.json", "w") as f:
|
|
json.dump(classified, f, indent=2, sort_keys=True)
|
|
|
|
# Human summary
|
|
lines = []
|
|
lines.append("# Untraced-file triage: 2026-10-29")
|
|
lines.append("")
|
|
lines.append("Classified `%d` untracked files (enumerated via `git ls-files --others --exclude-standard`) "
|
|
"into %d bucket(s)." % (len(classified), len(counts)))
|
|
lines.append("")
|
|
lines.append("| Bucket | Files |")
|
|
lines.append("|---|---|")
|
|
for b in sorted(counts):
|
|
lines.append("| %s | %d |" % (b, counts[b]))
|
|
lines.append("| **total** | **%d** |" % len(classified))
|
|
lines.append("")
|
|
lines.append("## Sample")
|
|
lines.append("")
|
|
for c in classified[:8]:
|
|
lines.append("- `%s` -> `%s` (%s)" % (c["path"], c["bucket"], c["rule"]))
|
|
lines.append("")
|
|
lines.append("## Buckets (full)")
|
|
lines.append("")
|
|
for b in sorted(counts):
|
|
members = [c["path"] for c in classified if c["bucket"] == b]
|
|
lines.append("### %s (%d)" % (b, len(members)))
|
|
lines.append("")
|
|
for m in members:
|
|
lines.append("- `%s`" % m)
|
|
lines.append("")
|
|
|
|
with open(OUT_DIR + "/untracked-classify-summary.md", "w") as f:
|
|
f.write("\n".join(lines))
|
|
|
|
print("files=%d buckets=%d" % (len(classified), len(counts)))
|
|
for b in sorted(counts):
|
|
print(" %-12s %d" % (b, counts[b]))
|
|
print("wrote %s" % OUT_DIR)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|