Files
Pascal Michel 9e506ff588 Abgleich mit dem Vault-Stand 7f484f9
38 Datei(en) geändert.
Erzeugt von .vault-tools/auto-push/werkzeuge_spiegel.py.
2026-11-02 03:00:03 +01:00

96 lines
3.9 KiB
Python

"""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 <Ordner>] [--projekt <Ordner>] [--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())