"""Listet die Sitzungs-Scratchpads eines Projekts mit Größe, Titel und letzter Aktivität des Transkripts. Ersteller: Pascal Michel Erstellt: 2026-10-30 Doku: LIESMICH.md in diesem Ordner Nur lesend. Gibt Sitzungskennung (acht Zeichen), Titel, Größe und Alter aus, keine Dateiinhalte. Was es nicht sieht: welche Sitzungen gerade laufen. Die nennst du mit --laufend (Kennungsanfänge der Claude-Code-Sitzung, nicht die local_-Kennung der App; Zuordnung nur über den Titel). Das Alter kommt aus der mtime des Transkripts, die nach einem App-Neustart für viele Sitzungen frisch ist (Harness-Erinnerung transkript-usage-groesster-wert-je-nachricht). Aufruf: python -I -X utf8 scratchpads.py [--scratch ] [--projekt ] [--laufend id8,id8] [--ab-mb 50] """ import argparse import collections import json import os import sys import time def groesse_kb(p): total = 0 for root, dirs, files in os.walk(p): # Junctions nicht betreten, sonst zählt ein verlinkter Ordner doppelt (os.path.isjunction ab Python 3.12). dirs[:] = [d for d in dirs if not getattr(os.path, "isjunction", lambda _: False)(os.path.join(root, d))] for f in files: try: total += os.lstat(os.path.join(root, f)).st_size except OSError: pass return total // 1024 def titel_aus(ct): try: with open(ct, encoding="utf-8") as f: d = json.load(f) except (OSError, ValueError): return None return (d.get("customTitle") or d.get("title")) if isinstance(d, dict) else None def main(argv=None): ap = argparse.ArgumentParser(description="Sitzungs-Scratchpads mit Größe und letzter Aktivität") ap.add_argument("--scratch", default=r"D:\ClaudeData\claude\D--Claude") ap.add_argument("--projekt", default=r"D:\ClaudeData\config\projects\D--Claude") ap.add_argument("--laufend", default="", help="kommagetrennte Kennungsanfänge laufender Sitzungen") ap.add_argument("--ab-mb", type=float, default=50, help="nur Scratchpads ab dieser Größe einzeln auflisten") a = ap.parse_args(argv) if not os.path.isdir(a.scratch) or not os.path.isdir(a.projekt): print(f"Ordner fehlt: {a.scratch if not os.path.isdir(a.scratch) else a.projekt}") return 2 laufend = {x.strip()[:8] for x in a.laufend.split(",") if x.strip()} jetzt = time.time() zeilen = [] for name in os.listdir(a.scratch): d = os.path.join(a.scratch, name) if not os.path.isdir(d): continue tr = os.path.join(a.projekt, name + ".jsonl") letzte = os.path.getmtime(tr) if os.path.exists(tr) else None titel = titel_aus(os.path.join(a.projekt, name, "custom-title.json")) tage = (jetzt - letzte) / 86400 if letzte else None zeilen.append((groesse_kb(d), name, titel, tage, letzte is not None)) zeilen.sort(reverse=True) print("| Sitzung | Titel | Scratchpad MB | letzte Transkript-Aktivität | Klasse |") print("|---|---|---|---|---|") summen = collections.Counter(); anzahl = collections.Counter() for kb, name, titel, tage, hat_tr in zeilen: if name[:8] in laufend: klasse = "läuft" elif not hat_tr: klasse = "kein Transkript" elif tage <= 7: klasse = "aktiv ≤ 7 Tage" elif tage <= 14: klasse = "ruhend 7-14 Tage" else: klasse = "ruhend > 14 Tage" summen[klasse] += kb; anzahl[klasse] += 1 if kb > a.ab_mb * 1024: print(f"| {name[:8]} | {(titel or '-')[:45]} | {kb/1024:.0f} | {('vor %.1f Tagen' % tage) if tage is not None else '-'} | {klasse} |") print() print("| Klasse | Sitzungen | GB |") print("|---|---|---|") for k in summen: print(f"| {k} | {anzahl[k]} | {summen[k]/1024/1024:.1f} |") print(f"| Summe | {sum(anzahl.values())} | {sum(summen.values())/1024/1024:.1f} |") return 0 if __name__ == "__main__": sys.exit(main())