From self-improving-agent
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'.
How this skill is triggered — by the user, by Claude, or both
Slash command
/self-improving-agent:log-minerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Analyse von Claude JSONL Session-Logs zur Erkennung von Fehlermustern, User-Frustration und wiederkehrenden Problemen.
Analyse von Claude JSONL Session-Logs zur Erkennung von Fehlermustern, User-Frustration und wiederkehrenden Problemen.
~/.claude/projects/ (Windows: %USERPROFILE%\.claude\projects\)jq ist installiert (wird via setup.sh geprueft)Alle Parameter werden aus .agent-memory/config.json gelesen (Fallback-Defaults in Klammern):
{
"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"
}
}
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.
Jeder Mining-Agent schreibt eine JSONL-Datei:
.agent-memory/log-analysis/partials/{LOG_FILENAME}.findings.jsonl
Jede Zeile:
{"source": "session_abc123.jsonl", "category": "user_frustration", "timestamp": "2026-03-24T10:01:00Z", "content": "nein, das ist falsch..."}
Nach Abschluss aller Mining-Agenten fuehrt der Planner den Merge aus:
# 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).
Finde alle JSONL-Dateien im konfigurierten Zeitraum:
# 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:
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))
KRITISCH: Immer jq mit Streaming/Filter nutzen, NICHT die ganze Datei laden.
# 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
# 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
# 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
# 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
Gruppiere die Funde in Kategorien. Verwende dafuer ein Python-Script:
#!/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()
Speichere die Ergebnisse in .agent-memory/log-analysis/:
# Verzeichnis sicherstellen
mkdir -p .agent-memory/log-analysis
# Timestamp fuer Report-Dateinamen
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
Speichere als .agent-memory/log-analysis/report_${TIMESTAMP}.json
Generiere .agent-memory/log-analysis/report_${TIMESTAMP}.md mit folgendem Format:
# 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>
# 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
| 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 |
Der Skill produziert IMMER:
.agent-memory/log-analysis/report_YYYYMMDD_HHMMSS.json — Maschinenlesbar.agent-memory/log-analysis/report_YYYYMMDD_HHMMSS.md — Menschenlesbar.agent-memory/log-analysis/latest.json — Symlink auf aktuellsten Report.agent-memory/log-analysis/latest.md — Symlink auf aktuellsten ReportZusaetzliche Einstellungen in .agent-memory/config.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 gearbeitetmax_parallel_agents: Maximale Anzahl gleichzeitiger Mining-Agenten (begrenzt durch xargs -P)agent_timeout_seconds: Timeout pro Mining-Agent (via timeout Befehl)errors.json (manuell erfasst). Log-Miner arbeitet auf JSONL-Rohdaten (automatisch generiert).Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub dynamic-dome/self-improving-agent