---
name: log-miner
description: "Analyse von Claude JSONL Session-Logs zur Erkennung von Fehlermustern, User-Frustration und wiederkehrenden Problemen. Trigger: 'logs analysieren', 'log mining', 'session logs auswerten', 'mine logs', 'find error patterns', 'was laeuft schief', 'welche fehler wiederholen sich'."
---

# Log-Miner Skill

> Analyse von Claude JSONL Session-Logs zur Erkennung von Fehlermustern, User-Frustration und wiederkehrenden Problemen.

## Trigger-Phrases
- "logs analysieren", "log mining", "session logs auswerten"
- "mine logs", "analyze session logs", "find error patterns in logs"
- "was laeuft schief", "welche fehler wiederholen sich"

## Assumptions
- Claude Code JSONL-Logs liegen unter `~/.claude/projects/` (Windows: `%USERPROFILE%\.claude\projects\`)
- Jede Zeile ist ein valides JSON-Objekt (JSONL-Format)
- Log-Dateien koennen mehrere hundert MB gross sein -- NIEMALS komplett in den Context laden
- `jq` ist installiert (wird via setup.sh geprueft)
- Python 3 ist als Fallback verfuegbar

## Konfiguration

Alle Parameter werden aus `.agent-memory/config.json` gelesen (Fallback-Defaults in Klammern):

```json
{
  "log_miner": {
    "log_base_path": "~/.claude/projects",
    "lookback_days": 7,
    "max_results_per_category": 50,
    "frustration_keywords": ["nein", "falsch", "nicht das", "nochmal", "wrong", "no", "stop", "undo", "revert"],
    "output_dir": ".agent-memory/log-analysis"
  }
}
```

## Ausfuehrungsmodi

Der Log-Miner kann in zwei Modi betrieben werden:

| Modus | Wann | Wie |
|---|---|---|
| **Sequentiell** | <= 3 Log-Dateien, oder als Standalone-Aufruf | Dieser Skill wird direkt ausgefuehrt |
| **Parallel** | > 3 Log-Dateien, aufgerufen vom Orchestrator | Orchestrator spawnt je 1 `claude -p` Mining-Agent pro Datei, dann Merge |

Im parallelen Modus fuehrt jeder Mining-Agent NUR Schritt 1 + 2 aus und schreibt Partial-Results nach `.agent-memory/log-analysis/partials/`. Der Planner-Agent (Orchestrator) uebernimmt dann Schritt 3-5 zum Merge.

### Parallel-Modus: Partials-Format

Jeder Mining-Agent schreibt eine JSONL-Datei:
`.agent-memory/log-analysis/partials/{LOG_FILENAME}.findings.jsonl`

Jede Zeile:
```json
{"source": "session_abc123.jsonl", "category": "user_frustration", "timestamp": "2026-03-24T10:01:00Z", "content": "nein, das ist falsch..."}
```

### Parallel-Modus: Merge-Schritt

Nach Abschluss aller Mining-Agenten fuehrt der Planner den Merge aus:

```bash
# Alle Partials zusammenfuehren
cat .agent-memory/log-analysis/partials/*.findings.jsonl 2>/dev/null | \
    python3 log_miner_cluster.py > .agent-memory/log-analysis/report_${TIMESTAMP}.json

# Partials aufraeumen
rm -f .agent-memory/log-analysis/partials/*.findings.jsonl
```

Danach weiter mit Schritt 4 (Report generieren) und Schritt 5 (Symlink).

---

## Prozedur (Sequentieller Modus)

### Schritt 1: Log-Dateien identifizieren

Finde alle JSONL-Dateien im konfigurierten Zeitraum:

```bash
# Windows-kompatibel: Pfad normalisieren
LOG_BASE="${USERPROFILE}/.claude/projects"
LOOKBACK_DAYS=7

# Finde relevante Log-Dateien (modifiziert innerhalb von LOOKBACK_DAYS)
find "$LOG_BASE" -name "*.jsonl" -mtime -${LOOKBACK_DAYS} -type f 2>/dev/null | head -100
```

Falls `find` auf Windows/Git Bash nicht funktioniert, Fallback auf Python:

```python
import os, time, pathlib

log_base = pathlib.Path.home() / ".claude" / "projects"
cutoff = time.time() - (7 * 86400)
jsonl_files = [
    str(p) for p in log_base.rglob("*.jsonl")
    if p.stat().st_mtime > cutoff
]
print("\n".join(jsonl_files))
```

### Schritt 2: Fehler extrahieren (jq-Pipeline)

**KRITISCH: Immer `jq` mit Streaming/Filter nutzen, NICHT die ganze Datei laden.**

#### 2a. Tool-Fehler und Exceptions

```bash
# Extrahiere Fehlermeldungen aus Tool-Results
jq -c 'select(.type == "tool_result" and .is_error == true) | {
  timestamp: .timestamp,
  tool: .tool_name,
  error: .content[0:500]
}' "$LOG_FILE" 2>/dev/null

# Alternative: Suche nach "error" in allen Message-Typen
jq -c 'select(.message.content[]?.text? // "" | test("error|Error|ERROR|exception|Exception|traceback|Traceback"; "i")) | {
  timestamp: .timestamp,
  type: .type,
  snippet: (.message.content[]?.text? // "" | .[0:300])
}' "$LOG_FILE" 2>/dev/null
```

#### 2b. User-Frustration erkennen

```bash
# Frustrations-Keywords in User-Messages
FRUSTRATION_PATTERN="nein|falsch|nicht das|nochmal|wrong|no,|stop|undo|revert|das stimmt nicht|immer noch|schon wieder"

jq -c --arg pattern "$FRUSTRATION_PATTERN" '
  select(.type == "human" or .role == "user") |
  select(.message.content[]?.text? // .content // "" | test($pattern; "i")) | {
    timestamp: .timestamp,
    text: (.message.content[]?.text? // .content // "" | .[0:300])
  }
' "$LOG_FILE" 2>/dev/null
```

#### 2c. Wiederholte Korrekturen (gleicher Kontext, mehrfache Aenderung)

```bash
# Finde Dateien die in einer Session >3x editiert wurden
jq -c 'select(.type == "tool_use" and (.tool_name == "edit" or .tool_name == "write")) |
  .input.file_path // .input.path // "unknown"
' "$LOG_FILE" 2>/dev/null | sort | uniq -c | sort -rn | head -20
```

#### 2d. Abgebrochene Ansaetze (Tool-Aufrufe gefolgt von Revert/Undo)

```bash
# Suche nach Revert-Patterns: git checkout, git stash, Datei-Wiederherstellung
jq -c 'select(
  (.type == "tool_use" and .tool_name == "bash") and
  (.input.command // "" | test("git checkout|git stash|git reset|rm |revert"; "i"))
) | {
  timestamp: .timestamp,
  command: .input.command[0:200]
}' "$LOG_FILE" 2>/dev/null
```

### Schritt 3: Clustering

Gruppiere die Funde in Kategorien. Verwende dafuer ein Python-Script:

```python
#!/usr/bin/env python3
"""
log_miner_cluster.py
Clustert Log-Mining-Ergebnisse nach Kategorie.
Input: JSON-Lines auf stdin (Output von Schritt 2)
Output: Strukturierter Report als JSON
"""

import json
import sys
from collections import defaultdict
from datetime import datetime

CATEGORIES = {
    "code_errors": {
        "patterns": ["error", "exception", "traceback", "syntax", "type error", "import"],
        "label": "Code-Fehler"
    },
    "architecture_misunderstandings": {
        "patterns": ["wrong file", "nicht die richtige", "falsche datei", "wrong approach", "anderer ansatz"],
        "label": "Architektur-Missverstaendnisse"
    },
    "tool_misuse": {
        "patterns": ["tool_result.*is_error", "command failed", "permission denied", "not found"],
        "label": "Tool-Fehlbenutzung"
    },
    "repeated_questions": {
        "patterns": ["nochmal", "wie war das", "erklaer nochmal", "again", "repeat"],
        "label": "Wiederholte Fragen"
    },
    "user_frustration": {
        "patterns": ["nein", "falsch", "nicht das", "wrong", "stop", "undo"],
        "label": "User-Frustration"
    },
    "reverted_changes": {
        "patterns": ["git checkout", "git stash", "git reset", "revert", "rollback"],
        "label": "Verworfene Aenderungen"
    }
}

def classify(entry: dict) -> str:
    """Classify a log entry into a category."""
    text = json.dumps(entry).lower()
    scores = {}
    for cat_id, cat_def in CATEGORIES.items():
        score = sum(1 for p in cat_def["patterns"] if p in text)
        if score > 0:
            scores[cat_id] = score
    if not scores:
        return "uncategorized"
    return max(scores, key=scores.get)

def main():
    clusters = defaultdict(list)
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        try:
            entry = json.loads(line)
        except json.JSONDecodeError:
            continue
        category = classify(entry)
        clusters[category].append(entry)

    # Build report
    report = {
        "generated_at": datetime.now().isoformat(),
        "total_findings": sum(len(v) for v in clusters.values()),
        "categories": {}
    }

    for cat_id, entries in sorted(clusters.items(), key=lambda x: -len(x[1])):
        label = CATEGORIES.get(cat_id, {}).get("label", cat_id)
        report["categories"][cat_id] = {
            "label": label,
            "count": len(entries),
            "entries": entries[:50]  # Cap per category
        }

    json.dump(report, sys.stdout, indent=2, ensure_ascii=False)

if __name__ == "__main__":
    main()
```

### Schritt 4: Report generieren

Speichere die Ergebnisse in `.agent-memory/log-analysis/`:

```bash
# Verzeichnis sicherstellen
mkdir -p .agent-memory/log-analysis

# Timestamp fuer Report-Dateinamen
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
```

#### 4a. JSON-Report

Speichere als `.agent-memory/log-analysis/report_${TIMESTAMP}.json`

#### 4b. Markdown-Report

Generiere `.agent-memory/log-analysis/report_${TIMESTAMP}.md` mit folgendem Format:

```markdown
# Log-Mining Report — {DATUM}

## Zusammenfassung
- Analysierter Zeitraum: {START} bis {ENDE}
- Anzahl analysierter Dateien: {N}
- Gesamtfunde: {TOTAL}

## Funde nach Kategorie

### Code-Fehler ({COUNT})
| # | Zeitpunkt | Kontext | Snippet |
|---|-----------|---------|---------|
| 1 | ...       | ...     | ...     |

### User-Frustration ({COUNT})
...

## Top-3 Handlungsempfehlungen
1. ...
2. ...
3. ...

## Rohe Evidenz
<details>
<summary>Alle Funde als JSON</summary>

\`\`\`json
{RAW_JSON}
\`\`\`

</details>
```

### Schritt 5: Symlink auf Latest

```bash
# Aktuellen Report als "latest" verlinken
ln -sf "report_${TIMESTAMP}.json" .agent-memory/log-analysis/latest.json
ln -sf "report_${TIMESTAMP}.md" .agent-memory/log-analysis/latest.md
```

## Error-Handling

| Fehlerfall | Reaktion |
|---|---|
| `jq` nicht installiert | Python-Fallback nutzen, Warnung ausgeben |
| Keine JSONL-Dateien gefunden | Report mit 0 Funden generieren, Hinweis auf Konfiguration |
| Korrupte JSON-Zeilen | Ueberspringen, Anzahl korrupter Zeilen im Report vermerken |
| Log-Dateien > 500MB | Nur die letzten 100.000 Zeilen pro Datei verarbeiten (`tail -n 100000`) |
| Leeres Ergebnis | Explizit "Keine Auffaelligkeiten gefunden" reporten |
| Pfad existiert nicht | Fehlermeldung mit Hinweis auf Konfiguration in config.json |

## Output-Vertrag

Der Skill produziert IMMER:
1. `.agent-memory/log-analysis/report_YYYYMMDD_HHMMSS.json` — Maschinenlesbar
2. `.agent-memory/log-analysis/report_YYYYMMDD_HHMMSS.md` — Menschenlesbar
3. `.agent-memory/log-analysis/latest.json` — Symlink auf aktuellsten Report
4. `.agent-memory/log-analysis/latest.md` — Symlink auf aktuellsten Report

## Konfiguration: Parallel-Modus

Zusaetzliche Einstellungen in `.agent-memory/config.json`:

```json
{
  "log_miner": {
    "parallel_threshold": 3,
    "max_parallel_agents": 5,
    "agent_timeout_seconds": 300,
    "partials_dir": ".agent-memory/log-analysis/partials"
  }
}
```

- `parallel_threshold`: Ab dieser Anzahl Log-Dateien wird parallel gearbeitet
- `max_parallel_agents`: Maximale Anzahl gleichzeitiger Mining-Agenten (begrenzt durch `xargs -P`)
- `agent_timeout_seconds`: Timeout pro Mining-Agent (via `timeout` Befehl)

## Abgrenzung zu bestehenden Skills

- **iteration-logger**: Dokumentiert einzelne Iterations-Zyklen WAEHREND der Arbeit. Log-Miner analysiert NACHTRAEGLICH die gesamte Session-Historie.
- **pattern-extractor**: Arbeitet auf `errors.json` (manuell erfasst). Log-Miner arbeitet auf JSONL-Rohdaten (automatisch generiert).
- Log-Miner ist der INPUT-Lieferant fuer pattern-extractor und claude-md-optimizer.
