Sidecar-Workspace / Sidecar /multi_session.py
smlflg's picture
Initial public upload from Projekte/Sidecar-Workspace (part 4)
e6b2bd6 verified
Raw History Blame Contribute Delete
3.81 kB
#!/usr/bin/env python3
"""multi_session — Aggregate insights across ALL active sessions.
Sidecar v4 looks at individual sessions. This module looks across them:
- How many sessions today?
- How many projects touched?
- How many finished (recap_done)?
- Pattern: starting many, finishing few?
"""
import json
import os
import time
from pathlib import Path
STATE_DIR = "/tmp"
PROJECTS_DIR = Path.home() / ".claude" / "projects"
def _load_all_session_states() -> list[dict]:
"""Load all sidecar session state files from today."""
today_start = time.mktime(time.strptime(time.strftime("%Y-%m-%d"), "%Y-%m-%d"))
states = []
try:
for f in Path(STATE_DIR).glob("sidecar-*.json"):
if f.stat().st_mtime < today_start:
continue
try:
with open(f, "r") as fh:
state = json.load(fh)
state["_file"] = f.name
states.append(state)
except (json.JSONDecodeError, IOError):
continue
except Exception:
pass
return states
def aggregate_today() -> dict:
"""Build a cross-session summary for today."""
states = _load_all_session_states()
if not states:
return {"sessions": 0}
projects = set()
recapped = 0
total_calls = 0
total_errors = 0
total_edits = 0
big_sessions = 0 # >20 calls
tiny_sessions = 0 # <5 calls
for s in states:
proj = s.get("project_name", "")
if proj:
projects.add(proj)
if s.get("recap_done"):
recapped += 1
calls = s.get("total_calls", 0)
total_calls += calls
if calls > 20:
big_sessions += 1
elif calls < 5:
tiny_sessions += 1
for h in s.get("history", []):
if h.get("had_error"):
total_errors += 1
if h.get("tool") in ("Edit", "Write"):
total_edits += 1
return {
"sessions": len(states),
"projects": sorted(projects),
"project_count": len(projects),
"recapped": recapped,
"not_recapped": len(states) - recapped,
"total_calls": total_calls,
"total_errors": total_errors,
"total_edits": total_edits,
"big_sessions": big_sessions,
"tiny_sessions": tiny_sessions,
"avg_calls_per_session": round(total_calls / len(states), 1) if states else 0,
}
def format_summary(agg: dict) -> str:
"""Format aggregation into a human-readable coaching message."""
if agg.get("sessions", 0) == 0:
return None
lines = [f"[MULTI-SESSION] Heute: {agg['sessions']} Sessions"]
# Projects
if agg["project_count"] > 0:
lines.append(f" Projekte: {agg['project_count']} ({', '.join(agg['projects'][:5])})")
# Recap rate
if agg["sessions"] > 3:
recap_pct = round(agg["recapped"] / agg["sessions"] * 100)
if recap_pct < 30:
lines.append(
f" Recap: {agg['recapped']}/{agg['sessions']} ({recap_pct}%) — "
"Die meisten Sessions ohne Abschluss. Wissen geht verloren."
)
else:
lines.append(f" Recap: {agg['recapped']}/{agg['sessions']} ({recap_pct}%)")
# Tiny sessions (session sprawl indicator)
if agg["tiny_sessions"] > 5:
lines.append(
f" Mini-Sessions (<5 Calls): {agg['tiny_sessions']} — "
"Viele angefangen, wenig Substanz. Fokus-Problem?"
)
# Efficiency
if agg["total_calls"] > 0:
error_rate = round(agg["total_errors"] / agg["total_calls"] * 100)
if error_rate > 20:
lines.append(f" Error-Rate: {error_rate}% — Ueberdurchschnittlich viele Fehler heute.")
return "\n".join(lines)