Loading...
Collects and reports detailed metrics about the coding session when Claude stops
{
"hookConfig": {
"hooks": {
"stop": {
"script": "./.claude/hooks/session-metrics-collector.sh",
"matchers": [
"*"
]
}
}
},
"scriptContent": "#!/bin/bash\n\necho \"📊 Session Metrics Collector - Gathering session analytics...\"\necho \"⏱️ Session ended: $(date)\"\n\n# Create metrics directory\nmkdir -p .metrics\n\n# Session metadata\nSESSION_ID=\"$(date +%Y%m%d_%H%M%S)\"\nMETRICS_FILE=\".metrics/session-${SESSION_ID}.json\"\nEND_TIME=$(date)\nEND_TIMESTAMP=$(date +%s)\n\necho \"💾 Collecting session metrics...\"\n\n# Get session start time (approximate from oldest .claude file or fallback)\nif [ -d \".claude\" ] && [ \"$(find .claude -type f 2>/dev/null | wc -l)\" -gt 0 ]; then\n START_TIMESTAMP=$(stat -f %B .claude/*.log 2>/dev/null | head -1 || echo $((END_TIMESTAMP - 3600)))\nelse\n # Fallback: assume 1 hour session\n START_TIMESTAMP=$((END_TIMESTAMP - 3600))\nfi\n\nSESSION_DURATION=$((END_TIMESTAMP - START_TIMESTAMP))\nSESSION_HOURS=$((SESSION_DURATION / 3600))\nSESSION_MINUTES=$(((SESSION_DURATION % 3600) / 60))\n\necho \"⏱️ Session Duration: ${SESSION_HOURS}h ${SESSION_MINUTES}m\"\n\n# File statistics\necho \"📁 Analyzing file changes...\"\n\n# Git-based analysis (preferred)\nif git rev-parse --git-dir >/dev/null 2>&1; then\n FILES_MODIFIED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')\n FILES_ADDED=$(git diff --name-status 2>/dev/null | grep '^A' | wc -l | tr -d ' ')\n FILES_DELETED=$(git diff --name-status 2>/dev/null | grep '^D' | wc -l | tr -d ' ')\n \n # Line statistics\n LINE_STATS=$(git diff --shortstat 2>/dev/null || echo \"0 files changed\")\n LINES_ADDED=$(echo \"$LINE_STATS\" | grep -o '[0-9]\\+ insertion' | grep -o '[0-9]\\+' || echo 0)\n LINES_DELETED=$(echo \"$LINE_STATS\" | grep -o '[0-9]\\+ deletion' | grep -o '[0-9]\\+' || echo 0)\n \n # Languages used\n LANGUAGES=$(git diff --name-only 2>/dev/null | sed 's/.*\\.//' | sort | uniq -c | sort -nr | head -5)\nelse\n # Fallback: recent file changes\n FILES_MODIFIED=$(find . -type f -newer .metrics 2>/dev/null | wc -l | tr -d ' ' || echo \"0\")\n FILES_ADDED=\"unknown\"\n FILES_DELETED=\"unknown\"\n LINES_ADDED=\"unknown\"\n LINES_DELETED=\"unknown\"\n LANGUAGES=\"unknown\"\nfi\n\necho \"📊 File Statistics:\"\necho \" • Files Modified: $FILES_MODIFIED\"\necho \" • Files Added: $FILES_ADDED\"\necho \" • Files Deleted: $FILES_DELETED\"\necho \" • Lines Added: $LINES_ADDED\"\necho \" • Lines Deleted: $LINES_DELETED\"\n\n# Repository analysis\nif git rev-parse --git-dir >/dev/null 2>&1; then\n REPO_NAME=$(basename \"$(git rev-parse --show-toplevel)\" 2>/dev/null || echo \"unknown\")\n CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo \"unknown\")\n COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo \"unknown\")\n \n echo \"📂 Repository Info:\"\n echo \" • Repository: $REPO_NAME\"\n echo \" • Branch: $CURRENT_BRANCH\"\n echo \" • Total Commits: $COMMIT_COUNT\"\nfi\n\n# System information\nCWD=$(pwd)\nUSER=$(whoami 2>/dev/null || echo \"unknown\")\nHOST=$(hostname 2>/dev/null || echo \"unknown\")\n\necho \"🖥️ Environment:\"\necho \" • User: $USER\"\necho \" • Host: $HOST\"\necho \" • Directory: $(basename \"$CWD\")\"\n\n# Activity analysis from .claude logs\nif [ -d \".claude\" ]; then\n ACTIVITY_COUNT=$(find .claude -name \"*.log\" -exec cat {} \\; 2>/dev/null | wc -l | tr -d ' ')\n echo \"📈 Activity: $ACTIVITY_COUNT logged actions\"\nfi\n\n# Generate JSON metrics\necho \"💾 Saving detailed metrics to: $METRICS_FILE\"\n\ncat > \"$METRICS_FILE\" << EOF\n{\n \"session\": {\n \"id\": \"$SESSION_ID\",\n \"start_time\": \"$(date -r $START_TIMESTAMP 2>/dev/null || echo 'unknown')\",\n \"end_time\": \"$END_TIME\",\n \"duration_seconds\": $SESSION_DURATION,\n \"duration_formatted\": \"${SESSION_HOURS}h ${SESSION_MINUTES}m\"\n },\n \"files\": {\n \"modified\": $FILES_MODIFIED,\n \"added\": $FILES_ADDED,\n \"deleted\": $FILES_DELETED\n },\n \"lines\": {\n \"added\": $LINES_ADDED,\n \"deleted\": $LINES_DELETED\n },\n \"repository\": {\n \"name\": \"$REPO_NAME\",\n \"branch\": \"$CURRENT_BRANCH\",\n \"total_commits\": $COMMIT_COUNT\n },\n \"environment\": {\n \"user\": \"$USER\",\n \"host\": \"$HOST\",\n \"working_directory\": \"$CWD\"\n },\n \"productivity\": {\n \"files_per_hour\": $(echo \"scale=2; $FILES_MODIFIED * 3600 / $SESSION_DURATION\" | bc -l 2>/dev/null || echo 0),\n \"lines_per_hour\": $(echo \"scale=2; ($LINES_ADDED + $LINES_DELETED) * 3600 / $SESSION_DURATION\" | bc -l 2>/dev/null || echo 0)\n }\n}\nEOF\n\n# Summary statistics\necho \"\"\necho \"📊 Session Summary:\"\necho \" • Duration: ${SESSION_HOURS}h ${SESSION_MINUTES}m\"\necho \" • Productivity: $(echo \"scale=1; $FILES_MODIFIED * 3600 / $SESSION_DURATION\" | bc -l 2>/dev/null || echo '0') files/hour\"\necho \" • Total Changes: $((LINES_ADDED + LINES_DELETED)) lines\"\n\n# Historical comparison\nif [ \"$(find .metrics -name 'session-*.json' 2>/dev/null | wc -l)\" -gt 1 ]; then\n PREV_SESSION=$(find .metrics -name 'session-*.json' | sort | tail -2 | head -1)\n if [ -f \"$PREV_SESSION\" ]; then\n echo \"📈 Compared to last session:\"\n echo \" • Previous session available for comparison\"\n fi\nfi\n\n# Cleanup old metrics (keep last 30 sessions)\nfind .metrics -name 'session-*.json' | sort | head -n -30 | xargs rm -f 2>/dev/null\n\necho \"\"\necho \"💡 Productivity Tips:\"\necho \" • Review metrics regularly to identify patterns\"\necho \" • Set productivity goals for future sessions\"\necho \" • Use metrics to optimize development workflow\"\necho \" • Compare sessions to track improvement\"\n\necho \"\"\necho \"🎯 Session metrics collection complete!\"\necho \"📄 Full report saved to: $METRICS_FILE\"\n\nexit 0"
}.claude/hooks/~/.claude/hooks/{
"hooks": {
"stop": {
"script": "./.claude/hooks/session-metrics-collector.sh",
"matchers": [
"*"
]
}
}
}#!/bin/bash
echo "📊 Session Metrics Collector - Gathering session analytics..."
echo "⏱️ Session ended: $(date)"
# Create metrics directory
mkdir -p .metrics
# Session metadata
SESSION_ID="$(date +%Y%m%d_%H%M%S)"
METRICS_FILE=".metrics/session-${SESSION_ID}.json"
END_TIME=$(date)
END_TIMESTAMP=$(date +%s)
echo "💾 Collecting session metrics..."
# Get session start time (approximate from oldest .claude file or fallback)
if [ -d ".claude" ] && [ "$(find .claude -type f 2>/dev/null | wc -l)" -gt 0 ]; then
START_TIMESTAMP=$(stat -f %B .claude/*.log 2>/dev/null | head -1 || echo $((END_TIMESTAMP - 3600)))
else
# Fallback: assume 1 hour session
START_TIMESTAMP=$((END_TIMESTAMP - 3600))
fi
SESSION_DURATION=$((END_TIMESTAMP - START_TIMESTAMP))
SESSION_HOURS=$((SESSION_DURATION / 3600))
SESSION_MINUTES=$(((SESSION_DURATION % 3600) / 60))
echo "⏱️ Session Duration: ${SESSION_HOURS}h ${SESSION_MINUTES}m"
# File statistics
echo "📁 Analyzing file changes..."
# Git-based analysis (preferred)
if git rev-parse --git-dir >/dev/null 2>&1; then
FILES_MODIFIED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
FILES_ADDED=$(git diff --name-status 2>/dev/null | grep '^A' | wc -l | tr -d ' ')
FILES_DELETED=$(git diff --name-status 2>/dev/null | grep '^D' | wc -l | tr -d ' ')
# Line statistics
LINE_STATS=$(git diff --shortstat 2>/dev/null || echo "0 files changed")
LINES_ADDED=$(echo "$LINE_STATS" | grep -o '[0-9]\+ insertion' | grep -o '[0-9]\+' || echo 0)
LINES_DELETED=$(echo "$LINE_STATS" | grep -o '[0-9]\+ deletion' | grep -o '[0-9]\+' || echo 0)
# Languages used
LANGUAGES=$(git diff --name-only 2>/dev/null | sed 's/.*\.//' | sort | uniq -c | sort -nr | head -5)
else
# Fallback: recent file changes
FILES_MODIFIED=$(find . -type f -newer .metrics 2>/dev/null | wc -l | tr -d ' ' || echo "0")
FILES_ADDED="unknown"
FILES_DELETED="unknown"
LINES_ADDED="unknown"
LINES_DELETED="unknown"
LANGUAGES="unknown"
fi
echo "📊 File Statistics:"
echo " • Files Modified: $FILES_MODIFIED"
echo " • Files Added: $FILES_ADDED"
echo " • Files Deleted: $FILES_DELETED"
echo " • Lines Added: $LINES_ADDED"
echo " • Lines Deleted: $LINES_DELETED"
# Repository analysis
if git rev-parse --git-dir >/dev/null 2>&1; then
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)" 2>/dev/null || echo "unknown")
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo "unknown")
echo "📂 Repository Info:"
echo " • Repository: $REPO_NAME"
echo " • Branch: $CURRENT_BRANCH"
echo " • Total Commits: $COMMIT_COUNT"
fi
# System information
CWD=$(pwd)
USER=$(whoami 2>/dev/null || echo "unknown")
HOST=$(hostname 2>/dev/null || echo "unknown")
echo "🖥️ Environment:"
echo " • User: $USER"
echo " • Host: $HOST"
echo " • Directory: $(basename "$CWD")"
# Activity analysis from .claude logs
if [ -d ".claude" ]; then
ACTIVITY_COUNT=$(find .claude -name "*.log" -exec cat {} \; 2>/dev/null | wc -l | tr -d ' ')
echo "📈 Activity: $ACTIVITY_COUNT logged actions"
fi
# Generate JSON metrics
echo "💾 Saving detailed metrics to: $METRICS_FILE"
cat > "$METRICS_FILE" << EOF
{
"session": {
"id": "$SESSION_ID",
"start_time": "$(date -r $START_TIMESTAMP 2>/dev/null || echo 'unknown')",
"end_time": "$END_TIME",
"duration_seconds": $SESSION_DURATION,
"duration_formatted": "${SESSION_HOURS}h ${SESSION_MINUTES}m"
},
"files": {
"modified": $FILES_MODIFIED,
"added": $FILES_ADDED,
"deleted": $FILES_DELETED
},
"lines": {
"added": $LINES_ADDED,
"deleted": $LINES_DELETED
},
"repository": {
"name": "$REPO_NAME",
"branch": "$CURRENT_BRANCH",
"total_commits": $COMMIT_COUNT
},
"environment": {
"user": "$USER",
"host": "$HOST",
"working_directory": "$CWD"
},
"productivity": {
"files_per_hour": $(echo "scale=2; $FILES_MODIFIED * 3600 / $SESSION_DURATION" | bc -l 2>/dev/null || echo 0),
"lines_per_hour": $(echo "scale=2; ($LINES_ADDED + $LINES_DELETED) * 3600 / $SESSION_DURATION" | bc -l 2>/dev/null || echo 0)
}
}
EOF
# Summary statistics
echo ""
echo "📊 Session Summary:"
echo " • Duration: ${SESSION_HOURS}h ${SESSION_MINUTES}m"
echo " • Productivity: $(echo "scale=1; $FILES_MODIFIED * 3600 / $SESSION_DURATION" | bc -l 2>/dev/null || echo '0') files/hour"
echo " • Total Changes: $((LINES_ADDED + LINES_DELETED)) lines"
# Historical comparison
if [ "$(find .metrics -name 'session-*.json' 2>/dev/null | wc -l)" -gt 1 ]; then
PREV_SESSION=$(find .metrics -name 'session-*.json' | sort | tail -2 | head -1)
if [ -f "$PREV_SESSION" ]; then
echo "📈 Compared to last session:"
echo " • Previous session available for comparison"
fi
fi
# Cleanup old metrics (keep last 30 sessions)
find .metrics -name 'session-*.json' | sort | head -n -30 | xargs rm -f 2>/dev/null
echo ""
echo "💡 Productivity Tips:"
echo " • Review metrics regularly to identify patterns"
echo " • Set productivity goals for future sessions"
echo " • Use metrics to optimize development workflow"
echo " • Compare sessions to track improvement"
echo ""
echo "🎯 Session metrics collection complete!"
echo "📄 Full report saved to: $METRICS_FILE"
exit 0Session metrics show 'unknown' duration or incorrect timing data
Hook relies on .claude directory timestamps. Ensure .claude exists before sessions. On Linux use 'stat -c %Y' instead of macOS 'stat -f %B' for timestamp extraction compatibility.
Git-based statistics return zero despite file modifications
Run 'git add .' before stopping to stage changes. Hook uses 'git diff' showing only unstaged. Modify script to use 'git diff HEAD' for all uncommitted changes instead.
bc command errors: 'command not found' during metrics calculation
Install bc: 'brew install bc' (macOS) or 'apt-get install bc' (Linux). Or replace with awk: 'awk "BEGIN {printf \"%.2f\", $FILES_MODIFIED * 3600 / $SESSION_DURATION}"'.
JSON metrics file has invalid format breaking parsers
Script appends without validation. Variables with special chars break JSON. Escape values: use jq for generation: 'jq -n --arg id "$SESSION_ID" '{session: {id: $id}}' > "$METRICS_FILE"'.
Productivity calculations show zero or negative values
Division by zero when SESSION_DURATION=0. Add check: 'if [ "$SESSION_DURATION" -lt 60 ]; then SESSION_DURATION=60; fi' setting minimum 1-minute before calculations.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters