feat(docs): add LLM Handbook + Google Whitepaper integration v3.3.0

Advanced Guardrails:
- prompt-injection-detector.sh (PreToolUse)
- output-validator.sh (PostToolUse heuristics)
- claudemd-scanner.sh (SessionStart injection detection)
- output-secrets-scanner.sh (PostToolUse secrets leak prevention)

Observability & Monitoring:
- session-logger.sh (JSONL activity logging)
- session-stats.sh (cost tracking & analysis)
- guide/observability.md (full documentation)

LLM-as-a-Judge Evaluation:
- output-evaluator.md agent (Haiku)
- /validate-changes command
- pre-commit-evaluator.sh (opt-in git hook)

Google Agent Whitepaper Integration:
- Context Triage Guide (Section 2.2.4)
- CLAUDE.md Injection Warning (Section 3.1.3)
- Agent Validation Checklist (Section 4.2.4)
- MCP Security: Tool Shadowing & Confused Deputy (Section 8.6)
- Session vs Memory patterns (Section 3.3.3)

Stats: 10 new files, 8 modified, 5 new guide sections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Florian BRUNIAUX 2026-01-14 21:00:49 +01:00
parent 19110eba22
commit 8a4d116e2e
17 changed files with 2188 additions and 3 deletions

View file

@ -46,6 +46,7 @@ Ready-to-use templates for Claude Code configuration.
| [test-writer.md](./agents/test-writer.md) | TDD/BDD test generation | Sonnet |
| [security-auditor.md](./agents/security-auditor.md) | Security vulnerability detection | Sonnet |
| [refactoring-specialist.md](./agents/refactoring-specialist.md) | Clean code refactoring | Sonnet |
| [output-evaluator.md](./agents/output-evaluator.md) | LLM-as-a-Judge quality gate | Haiku |
### Skills
| File | Purpose |
@ -64,6 +65,7 @@ Ready-to-use templates for Claude Code configuration.
| [generate-tests.md](./commands/generate-tests.md) | `/generate-tests` | Test generation |
| [git-worktree.md](./commands/git-worktree.md) | `/git-worktree` | Isolated git worktree setup |
| [diagnose.md](./commands/diagnose.md) | `/diagnose` | Interactive troubleshooting assistant (FR/EN) |
| [validate-changes.md](./commands/validate-changes.md) | `/validate-changes` | LLM-as-a-Judge pre-commit validation |
### Hooks
| File | Event | Purpose |
@ -72,6 +74,10 @@ Ready-to-use templates for Claude Code configuration.
| [security-check.*](./hooks/) | PreToolUse | Block secrets in commands |
| [auto-format.*](./hooks/) | PostToolUse | Auto-format after edits |
| [notification.sh](./hooks/bash/notification.sh) | Notification | Contextual macOS sound alerts |
| [prompt-injection-detector.sh](./hooks/bash/prompt-injection-detector.sh) | PreToolUse | Detect prompt injection attempts |
| [output-validator.sh](./hooks/bash/output-validator.sh) | PostToolUse | Heuristic output validation |
| [session-logger.sh](./hooks/bash/session-logger.sh) | PostToolUse | Log operations for monitoring |
| [pre-commit-evaluator.sh](./hooks/bash/pre-commit-evaluator.sh) | Git hook | LLM-as-a-Judge pre-commit |
> **See [hooks/README.md](./hooks/README.md) for complete documentation and examples**
@ -96,6 +102,7 @@ Ready-to-use templates for Claude Code configuration.
| [check-claude.ps1](./scripts/check-claude.ps1) | Health check diagnostics (Windows) | Human |
| [clean-reinstall-claude.sh](./scripts/clean-reinstall-claude.sh) | Clean reinstall procedure (macOS/Linux) | Human |
| [clean-reinstall-claude.ps1](./scripts/clean-reinstall-claude.ps1) | Clean reinstall procedure (Windows) | Human |
| [session-stats.sh](./scripts/session-stats.sh) | Analyze session logs & costs | JSON / Human |
> **Usage**: `./audit-scan.sh` for human output, `./audit-scan.sh --json` for JSON output

View file

@ -0,0 +1,143 @@
---
name: output-evaluator
description: Evaluate Claude Code outputs for quality before commit/action (LLM-as-a-Judge pattern)
model: haiku
tools: Read, Grep, Glob
---
# Output Evaluator Agent
You evaluate code changes proposed by Claude for quality, correctness, and safety before they are committed or applied.
## Purpose
This agent implements the **LLM-as-a-Judge** pattern: using a language model to evaluate outputs from another LLM (or the same model in a different context). This provides an automated quality gate before irreversible actions like commits.
## When to Use
- Before committing staged changes
- After significant code generation
- Before applying bulk edits
- When reviewing unfamiliar code modifications
## Evaluation Criteria
Score each criterion from 0-10:
### Correctness (0-10)
- [ ] Code compiles/parses without errors
- [ ] Logic is sound and handles expected cases
- [ ] No obvious bugs or regressions introduced
- [ ] Type safety maintained (if applicable)
- [ ] No undefined variables or missing imports
### Completeness (0-10)
- [ ] All TODOs are resolved (not left as placeholders)
- [ ] Error handling is present where needed
- [ ] Edge cases are considered
- [ ] No stub implementations or mock data
- [ ] Tests included if appropriate for the change
### Safety (0-10)
- [ ] No hardcoded secrets or credentials
- [ ] No destructive operations without safeguards
- [ ] No SQL injection, XSS, or command injection vectors
- [ ] No overly permissive file/network access
- [ ] Sensitive data not logged or exposed
## Evaluation Process
1. **Read the changes**: Examine all modified files
2. **Check context**: Understand what the changes are trying to accomplish
3. **Score each criterion**: Apply the checklist above
4. **Identify issues**: List specific problems found
5. **Render verdict**: Based on scores and severity
## Output Format
Always respond with this JSON structure:
```json
{
"verdict": "APPROVE|NEEDS_REVIEW|REJECT",
"scores": {
"correctness": 8,
"completeness": 7,
"safety": 9
},
"overall_score": 8.0,
"issues": [
{
"severity": "high|medium|low",
"file": "path/to/file.ts",
"line": 42,
"description": "Description of the issue"
}
],
"summary": "Brief 1-2 sentence assessment",
"suggestion": "What to do next (if not APPROVE)"
}
```
## Verdict Rules
| Verdict | Condition |
|---------|-----------|
| **APPROVE** | All scores >= 7, no high-severity issues |
| **NEEDS_REVIEW** | Any score 5-6, or medium-severity issues present |
| **REJECT** | Any score < 5, or any high-severity security issue |
## Issue Severity Guide
- **High**: Security vulnerabilities, data loss risk, breaking changes, secrets exposure
- **Medium**: Missing error handling, incomplete implementation, poor patterns
- **Low**: Style issues, naming, minor optimizations, documentation gaps
## Example Evaluation
Given a diff that adds a new API endpoint:
```json
{
"verdict": "NEEDS_REVIEW",
"scores": {
"correctness": 8,
"completeness": 6,
"safety": 7
},
"overall_score": 7.0,
"issues": [
{
"severity": "medium",
"file": "src/api/users.ts",
"line": 45,
"description": "Missing error handling for database connection failures"
},
{
"severity": "low",
"file": "src/api/users.ts",
"line": 52,
"description": "Consider adding rate limiting for this endpoint"
}
],
"summary": "Endpoint implementation is correct but lacks error handling for edge cases.",
"suggestion": "Add try-catch around database operations and handle connection errors gracefully."
}
```
## Limitations
- **Not a replacement for human review**: This is a first-pass automated check
- **No runtime testing**: Evaluation is static analysis only
- **Model limitations**: May miss subtle bugs or domain-specific issues
- **Cost**: Each evaluation uses API tokens (~$0.01-0.05 with Haiku)
## Integration
Use with:
- `/validate-changes` command - Invoke before commits
- `pre-commit-evaluator.sh` hook - Automatic git integration
- Manual invocation for significant changes

View file

@ -0,0 +1,115 @@
---
name: validate-changes
description: Evaluate staged changes using LLM-as-a-Judge before committing
allowed-tools: Bash, Read, Grep, Glob, Task
---
# Validate Changes Before Commit
Evaluate staged git changes using the output-evaluator agent to catch issues before committing.
## Process
### Step 1: Check for Staged Changes
Run `git diff --cached --stat` to see what's staged. If nothing is staged, inform the user and exit.
### Step 2: Get the Full Diff
Run `git diff --cached` to get the complete diff of all staged changes.
### Step 3: Invoke the Evaluator
Use the Task tool to launch the `output-evaluator` agent with the diff:
```
Evaluate these staged changes for correctness, completeness, and safety.
Return a JSON verdict with scores and issues.
Changes:
[paste the git diff here]
```
### Step 4: Parse and Act on Verdict
Based on the evaluation result:
**If APPROVE:**
- Tell the user the changes passed evaluation
- Show the summary and scores
- Ask if they want to proceed with commit
**If NEEDS_REVIEW:**
- Show all issues found (grouped by severity)
- Show the suggestion from the evaluator
- Ask the user how to proceed:
- Fix issues and re-evaluate
- Commit anyway (acknowledge risks)
- Abort
**If REJECT:**
- Clearly state the changes were rejected
- Show critical issues that caused rejection
- Do NOT offer to commit anyway
- Suggest specific fixes
### Step 5: Commit (if approved)
If user confirms, create the commit using the standard commit flow.
## Usage Examples
```
/validate-changes
```
Output:
```
Evaluating 3 staged files...
VERDICT: NEEDS_REVIEW
Scores:
Correctness: 8/10
Completeness: 6/10
Safety: 9/10
Issues Found:
[MEDIUM] src/api/handler.ts:45
Missing error handling for network failures
[LOW] src/utils/format.ts:12
Consider adding input validation
Suggestion: Add try-catch around the fetch call in handler.ts
How would you like to proceed?
1. Fix issues and re-evaluate
2. Commit anyway (1 medium issue)
3. Abort
```
## Cost Awareness
This command invokes an LLM evaluation, which uses API tokens:
- **Typical cost**: $0.01-0.05 per evaluation (using Haiku)
- **Larger diffs**: May cost more due to increased token usage
## When to Use
- After significant code changes before committing
- When working on unfamiliar parts of the codebase
- For changes that affect security-sensitive code
- Before pushing to shared branches
## When to Skip
- Trivial changes (typos, formatting)
- Documentation-only changes
- When you've already manually reviewed thoroughly
- When iterating quickly on a feature branch
## Integration with Git Hooks
For automatic evaluation on every commit, see `pre-commit-evaluator.sh` hook.
This command is the manual alternative when you want control over when evaluation runs.

View file

@ -8,6 +8,8 @@ Hooks are scripts that execute automatically on Claude Code events. They enable
|------|-------|---------|----------|
| [dangerous-actions-blocker.sh](./bash/dangerous-actions-blocker.sh) | PreToolUse | Block dangerous commands/edits | Bash |
| [security-check.sh](./bash/security-check.sh) | PreToolUse | Block secrets in commands | Bash |
| [claudemd-scanner.sh](./bash/claudemd-scanner.sh) | SessionStart | Detect CLAUDE.md injection attacks | Bash |
| [output-secrets-scanner.sh](./bash/output-secrets-scanner.sh) | PostToolUse | Detect secrets in tool outputs | Bash |
| [auto-format.sh](./bash/auto-format.sh) | PostToolUse | Auto-format after edits | Bash |
| [notification.sh](./bash/notification.sh) | Notification | Contextual macOS sound alerts | Bash (macOS) |
| [security-check.ps1](./powershell/security-check.ps1) | PreToolUse | Block secrets in commands | PowerShell |
@ -25,6 +27,99 @@ Hooks are scripts that execute automatically on Claude Code events. They enable
| `SessionEnd` | At session end | Cleanup, session summary |
| `Stop` | User interrupts operation | State saving, graceful shutdown |
## Advanced Guardrails (NEW in v3.3.0)
Advanced protection patterns inspired by production LLM systems.
### prompt-injection-detector.sh
**Event**: `PreToolUse`
Detects and blocks prompt injection attempts before they reach Claude:
**Detected Patterns**:
- Role override: "ignore previous instructions", "you are now", "pretend to be"
- Jailbreak attempts: "DAN mode", "developer mode", "no restrictions"
- Delimiter injection: `</system>`, `[INST]`, `<<SYS>>`
- Authority impersonation: "anthropic employee", "authorized to bypass"
- Base64-encoded payloads (decoded and scanned)
- Context manipulation: false claims about previous messages
**Configuration**:
```json
{
"hooks": {
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/prompt-injection-detector.sh",
"timeout": 5000
}]
}]
}
}
```
### output-validator.sh
**Event**: `PostToolUse`
Heuristic validation of Claude's outputs (no LLM call, pure bash):
**Validation Checks**:
- Placeholder paths: `/path/to/`, `/your/project/`
- Placeholder content: `TODO:`, `your-api-key`, `example.com`
- Potential secrets in output (regex patterns)
- Uncertainty indicators (multiple "I'm not sure", "probably")
- Incomplete implementations: `NotImplementedError`, `throw new Error`
- Unverified reference claims
**Behavior**: Warns via `systemMessage`, does not block. For deeper validation, use the `output-evaluator` agent.
### session-logger.sh
**Event**: `PostToolUse`
Logs all Claude operations to JSONL files for monitoring and cost tracking:
**Log Location**: `~/.claude/logs/activity-YYYY-MM-DD.jsonl`
**Logged Data**:
- Timestamp, session ID, tool name
- File paths and commands (truncated)
- Project name
- Token estimates (input/output)
**Analysis**: Use `session-stats.sh` script to analyze logs.
**Environment Variables**:
| Variable | Default | Description |
|----------|---------|-------------|
| `CLAUDE_LOG_DIR` | `~/.claude/logs` | Log directory |
| `CLAUDE_LOG_TOKENS` | `true` | Enable token estimation |
| `CLAUDE_SESSION_ID` | auto | Custom session ID |
See [Observability Guide](../../guide/observability.md) for full documentation.
### pre-commit-evaluator.sh
**Type**: Git pre-commit hook (not Claude hook)
LLM-as-a-Judge evaluation before every commit. **Opt-in only** due to API costs.
**Installation**:
```bash
cp pre-commit-evaluator.sh .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
export CLAUDE_PRECOMMIT_EVAL=1 # Enable evaluation
```
**Cost**: ~$0.01-0.05 per commit (Haiku model)
**Bypass**: `git commit --no-verify` or `CLAUDE_SKIP_EVAL=1 git commit`
---
## Security Hooks
### dangerous-actions-blocker.sh
@ -77,6 +172,72 @@ Focused on detecting secrets in commands:
- Private keys
- Hardcoded tokens
### claudemd-scanner.sh
**Event**: `SessionStart`
Scans CLAUDE.md files at session start for potential prompt injection attacks:
**Detected Patterns**:
- "ignore previous instructions" variants
- Shell injection: `curl | bash`, `wget | sh`, `eval(`
- Base64 encoded content (potential obfuscation)
- Hidden instructions in HTML comments
- Suspicious long lines (>500 chars)
- Non-ASCII characters near sensitive keywords (homoglyph attacks)
**Files Scanned**:
- `CLAUDE.md` (project root)
- `.claude/CLAUDE.md` (local override)
- Any `.md` files in `.claude/` directory
**Why This Matters**: When you clone an unfamiliar repository, a malicious CLAUDE.md could inject instructions that compromise your system. This hook warns you before Claude processes potentially dangerous instructions.
**Configuration**:
```json
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": ".claude/hooks/claudemd-scanner.sh",
"timeout": 5000
}]
}]
}
}
```
### output-secrets-scanner.sh
**Event**: `PostToolUse`
Complements `security-check.sh` by scanning tool **outputs** (not inputs) for leaked secrets.
**Detected Patterns**:
- API Keys: OpenAI, Anthropic, AWS, GCP, Azure, Stripe, Twilio, SendGrid
- Tokens: GitHub, GitLab, NPM, PyPI, JWT
- Private Keys: RSA, EC, DSA, OpenSSH, PGP
- Database URLs with embedded passwords
- Generic `api_key=`, `secret=`, `password=` patterns
**Why This Matters**: Claude might read a `.env` file and include credentials in its response or a commit. This hook catches secrets before they leak.
**Configuration**:
```json
{
"hooks": {
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": ".claude/hooks/output-secrets-scanner.sh",
"timeout": 5000
}]
}]
}
}
```
## Productivity Hooks
### auto-format.sh / auto-format.ps1

View file

@ -0,0 +1,98 @@
#!/bin/bash
# =============================================================================
# CLAUDE.md Injection Scanner Hook
# =============================================================================
# Event: SessionStart (runs when Claude Code session begins)
# Purpose: Detect potential prompt injection attacks in CLAUDE.md files
#
# Installation:
# Add to .claude/settings.json:
# {
# "hooks": {
# "SessionStart": [{
# "matcher": "",
# "hooks": ["bash examples/hooks/bash/claudemd-scanner.sh"]
# }]
# }
# }
#
# What it detects:
# - "ignore previous instructions" patterns (common injection technique)
# - Shell command execution attempts (curl|bash, wget|sh, eval)
# - Base64 encoded content (potential obfuscation)
# - Suspicious HTML comments that might hide instructions
# =============================================================================
set -euo pipefail
# Define suspicious patterns (case-insensitive)
SUSPICIOUS_PATTERNS=(
"ignore.*previous.*instruction"
"ignore.*all.*instruction"
"disregard.*instruction"
"forget.*instruction"
"new.*instruction.*follow"
"curl.*\|.*bash"
"curl.*\|.*sh"
"wget.*\|.*bash"
"wget.*\|.*sh"
"eval\s*\("
"base64.*decode"
"\$\(.*curl"
"\$\(.*wget"
"<!--.*ignore"
"<!--.*instruction"
)
WARNINGS=()
# Function to scan a file for suspicious patterns
scan_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
return 0
fi
for pattern in "${SUSPICIOUS_PATTERNS[@]}"; do
if grep -qiE "$pattern" "$file" 2>/dev/null; then
WARNINGS+=("Suspicious pattern in $file: matches '$pattern'")
fi
done
# Check for very long single lines (potential obfuscation)
if awk 'length > 500' "$file" | grep -q .; then
WARNINGS+=("Warning: $file contains very long lines (potential obfuscation)")
fi
# Check for uncommon Unicode characters (potential homoglyph attack)
if grep -P '[^\x00-\x7F]' "$file" 2>/dev/null | grep -qiE "instruction|ignore|run|execute"; then
WARNINGS+=("Warning: $file contains non-ASCII characters near sensitive keywords")
fi
}
# Scan all potential CLAUDE.md locations
scan_file "CLAUDE.md"
scan_file ".claude/CLAUDE.md"
# Also scan any .md files in .claude/ directory that might be loaded
if [[ -d ".claude" ]]; then
for md_file in .claude/*.md; do
[[ -f "$md_file" ]] && scan_file "$md_file"
done
fi
# Output warnings if any found
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
# Construct JSON response with system message
WARNING_TEXT="SECURITY WARNING - Suspicious content detected:\\n"
for warning in "${WARNINGS[@]}"; do
WARNING_TEXT+="- $warning\\n"
done
WARNING_TEXT+="\\nReview these files before proceeding. See: https://github.com/FlorianBruniaux/claude-code-ultimate-guide/guide/ultimate-guide.md#security-warning-claudemd-injection"
echo "{\"systemMessage\": \"$WARNING_TEXT\"}"
fi
# Always exit 0 to not block session (just warn)
exit 0

View file

@ -0,0 +1,97 @@
#!/bin/bash
# =============================================================================
# Output Secrets Scanner Hook
# =============================================================================
# Event: PostToolUse (runs after each tool execution)
# Purpose: Detect secrets that might leak in tool outputs
#
# This complements security-check.sh (which scans inputs). This hook scans
# outputs to catch secrets that Claude might inadvertently expose.
#
# Installation:
# Add to .claude/settings.json:
# {
# "hooks": {
# "PostToolUse": [{
# "matcher": "",
# "hooks": ["bash examples/hooks/bash/output-secrets-scanner.sh"]
# }]
# }
# }
#
# What it detects:
# - API keys (OpenAI, Anthropic, AWS, GCP, Azure, Stripe, etc.)
# - Private keys and certificates
# - Database connection strings with passwords
# - GitHub/GitLab tokens
# - JWT tokens
# =============================================================================
set -euo pipefail
# Read the hook input from stdin
INPUT=$(cat)
# Extract tool output from JSON (handle both formats)
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // .output // ""' 2>/dev/null || echo "")
# If no output or empty, exit cleanly
if [[ -z "$TOOL_OUTPUT" ]]; then
exit 0
fi
# Secret patterns to detect
declare -A SECRET_PATTERNS=(
# API Keys
["OpenAI API Key"]="sk-[a-zA-Z0-9]{20,}"
["Anthropic API Key"]="sk-ant-[a-zA-Z0-9]{20,}"
["AWS Access Key"]="AKIA[0-9A-Z]{16}"
["AWS Secret Key"]="[0-9a-zA-Z/+]{40}"
["GCP API Key"]="AIza[0-9A-Za-z_-]{35}"
["Azure Key"]="[a-zA-Z0-9]{32,}"
["Stripe Key"]="(sk|pk)_(live|test)_[0-9a-zA-Z]{24,}"
["Twilio Key"]="SK[a-f0-9]{32}"
["SendGrid Key"]="SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}"
# Tokens
["GitHub Token"]="(ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}"
["GitLab Token"]="glpat-[a-zA-Z0-9_-]{20,}"
["NPM Token"]="npm_[a-zA-Z0-9]{36}"
["PyPI Token"]="pypi-[a-zA-Z0-9_-]{50,}"
["JWT Token"]="eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*"
# Private Keys
["Private Key"]="-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
["PGP Private Key"]="-----BEGIN PGP PRIVATE KEY BLOCK-----"
# Database
["Database URL with Password"]="(postgres|mysql|mongodb)://[^:]+:[^@]+@"
["Redis URL with Password"]="redis://:[^@]+@"
# Generic
["Generic API Key"]="(api[_-]?key|apikey|api[_-]?secret)['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_-]{20,}"
["Generic Secret"]="(secret|password|passwd|pwd)['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}"
)
DETECTED_SECRETS=()
# Check each pattern
for secret_type in "${!SECRET_PATTERNS[@]}"; do
pattern="${SECRET_PATTERNS[$secret_type]}"
if echo "$TOOL_OUTPUT" | grep -qiE "$pattern" 2>/dev/null; then
DETECTED_SECRETS+=("$secret_type")
fi
done
# If secrets detected, warn via systemMessage
if [[ ${#DETECTED_SECRETS[@]} -gt 0 ]]; then
SECRETS_LIST=$(printf ", %s" "${DETECTED_SECRETS[@]}")
SECRETS_LIST=${SECRETS_LIST:2} # Remove leading ", "
WARNING_MSG="SECRET LEAK WARNING: Potential secrets detected in output: $SECRETS_LIST. Do NOT commit or share this output. Consider using environment variables or a secrets manager."
echo "{\"systemMessage\": \"$WARNING_MSG\"}"
fi
# Always exit 0 (warn, don't block)
exit 0

View file

@ -0,0 +1,184 @@
#!/bin/bash
# Hook: PostToolUse - Validate Claude's outputs for quality issues
# Exit 0 = allow (always), but emit systemMessage warnings
#
# This hook performs heuristic validation of Claude's outputs to detect:
# - Potential hallucinations (fabricated paths, functions)
# - Sensitive data leakage in outputs
# - High uncertainty indicators
#
# This is a lightweight heuristic check, not a full LLM evaluation.
# For deeper validation, use the output-evaluator agent.
#
# Place in: .claude/hooks/output-validator.sh
# Register in: .claude/settings.json under PostToolUse event
set -e
# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // empty')
# Only validate tools that produce code/content outputs
case "$TOOL_NAME" in
Edit|Write|Bash)
;;
*)
exit 0
;;
esac
WARNINGS=()
# === FABRICATED FILE PATHS ===
# Detect paths that look suspicious (common hallucination patterns)
SUSPICIOUS_PATHS=(
"/path/to/"
"/your/project/"
"/example/"
"/foo/bar/"
"/my/app/"
"/user/project/"
"C:\\Users\\User\\"
"C:\\path\\to\\"
)
for pattern in "${SUSPICIOUS_PATHS[@]}"; do
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
WARNINGS+=("Suspicious placeholder path detected: '$pattern'")
fi
done
# === PLACEHOLDER CONTENT ===
# Detect common placeholder patterns that shouldn't be in production code
PLACEHOLDER_PATTERNS=(
"TODO:"
"FIXME:"
"XXX:"
"HACK:"
"your-api-key"
"your_api_key"
"YOUR_API_KEY"
"sk-..."
"pk_test_"
"pk_live_"
"api_key_here"
"replace_with"
"insert_your"
"placeholder"
"example.com"
"foo@bar.com"
"test@test.com"
)
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
WARNINGS+=("Placeholder content detected: '$pattern'")
fi
done
# === SENSITIVE DATA LEAKAGE ===
# Detect potential secrets in output (could indicate data exposure)
SECRET_PATTERNS=(
# AWS
'AKIA[0-9A-Z]{16}'
# Generic API keys (long hex strings)
'[a-f0-9]{32,}'
# Private keys
'-----BEGIN.*PRIVATE KEY-----'
'-----BEGIN RSA'
'-----BEGIN EC'
# JWT tokens
'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.'
# Password patterns
'password["\x27]?\s*[=:]\s*["\x27][^"\x27]{8,}'
)
for pattern in "${SECRET_PATTERNS[@]}"; do
if echo "$TOOL_OUTPUT" | grep -qE "$pattern" 2>/dev/null; then
WARNINGS+=("Potential sensitive data in output (pattern: ${pattern:0:20}...)")
fi
done
# === UNCERTAINTY INDICATORS ===
# Detect high uncertainty language that might indicate guessing
UNCERTAINTY_PATTERNS=(
"I'm not sure"
"I think it might"
"probably"
"possibly"
"might be"
"could be"
"I believe"
"I assume"
"I guess"
"if I recall"
"from memory"
"I don't have access"
"I cannot verify"
)
UNCERTAINTY_COUNT=0
TOOL_OUTPUT_LOWER=$(echo "$TOOL_OUTPUT" | tr '[:upper:]' '[:lower:]')
for pattern in "${UNCERTAINTY_PATTERNS[@]}"; do
pattern_lower=$(echo "$pattern" | tr '[:upper:]' '[:lower:]')
if [[ "$TOOL_OUTPUT_LOWER" == *"$pattern_lower"* ]]; then
((UNCERTAINTY_COUNT++))
fi
done
if [[ $UNCERTAINTY_COUNT -ge 3 ]]; then
WARNINGS+=("High uncertainty detected ($UNCERTAINTY_COUNT indicators) - verify output accuracy")
fi
# === INCOMPLETE IMPLEMENTATIONS ===
# Detect code that looks incomplete
INCOMPLETE_PATTERNS=(
"not implemented"
"NotImplementedError"
"throw new Error.*implement"
"// TODO"
"# TODO"
"pass # "
"raise NotImplemented"
"undefined"
)
for pattern in "${INCOMPLETE_PATTERNS[@]}"; do
if echo "$TOOL_OUTPUT" | grep -qiE "$pattern" 2>/dev/null; then
WARNINGS+=("Incomplete implementation detected: '$pattern'")
fi
done
# === HALLUCINATION INDICATORS ===
# Detect patterns that often indicate hallucinated content
HALLUCINATION_PATTERNS=(
"According to the documentation"
"As stated in"
"The official guide says"
"Based on the API reference"
)
for pattern in "${HALLUCINATION_PATTERNS[@]}"; do
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
WARNINGS+=("Unverified reference claim: '$pattern' - verify source")
fi
done
# === OUTPUT WARNINGS ===
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
WARNING_MSG="Output validation warnings:\\n"
for warn in "${WARNINGS[@]}"; do
WARNING_MSG+=" - $warn\\n"
done
WARNING_MSG+="\\nReview output carefully before accepting."
# Emit as systemMessage (warning, not blocking)
echo "{\"systemMessage\": \"$WARNING_MSG\"}"
fi
# Always allow (this hook warns, doesn't block)
exit 0

View file

@ -0,0 +1,207 @@
#!/bin/bash
# Git pre-commit hook: LLM-as-a-Judge evaluation before commit
#
# This hook uses Claude to evaluate staged changes before allowing a commit.
# It's an OPT-IN feature due to API costs and latency.
#
# COST WARNING: Each commit evaluation costs ~$0.01-0.05 (Haiku model)
#
# Installation:
# 1. Copy to your repo: cp pre-commit-evaluator.sh .git/hooks/pre-commit
# 2. Make executable: chmod +x .git/hooks/pre-commit
# 3. Set required env var: export CLAUDE_PRECOMMIT_EVAL=1
#
# Environment Variables:
# CLAUDE_PRECOMMIT_EVAL - Set to "1" to enable (default: disabled)
# CLAUDE_EVAL_MODEL - Model to use (default: haiku)
# CLAUDE_EVAL_THRESHOLD - Minimum score to pass (default: 7)
# CLAUDE_EVAL_SKIP_PATHS - Colon-separated paths to skip (e.g., "docs:*.md")
#
# Bypass for single commit:
# CLAUDE_SKIP_EVAL=1 git commit -m "message"
# or
# git commit --no-verify -m "message"
set -e
# Check if evaluation is enabled
if [[ "${CLAUDE_PRECOMMIT_EVAL:-0}" != "1" ]]; then
exit 0
fi
# Check for bypass
if [[ "${CLAUDE_SKIP_EVAL:-0}" == "1" ]]; then
echo "Skipping LLM evaluation (CLAUDE_SKIP_EVAL=1)"
exit 0
fi
# Configuration
MODEL="${CLAUDE_EVAL_MODEL:-haiku}"
THRESHOLD="${CLAUDE_EVAL_THRESHOLD:-7}"
SKIP_PATHS="${CLAUDE_EVAL_SKIP_PATHS:-}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# Check for staged changes
STAGED_FILES=$(git diff --cached --name-only)
if [[ -z "$STAGED_FILES" ]]; then
exit 0
fi
# Filter out skipped paths
if [[ -n "$SKIP_PATHS" ]]; then
IFS=':' read -ra SKIP_ARRAY <<< "$SKIP_PATHS"
FILTERED_FILES=""
for file in $STAGED_FILES; do
skip=false
for pattern in "${SKIP_ARRAY[@]}"; do
if [[ "$file" == $pattern ]]; then
skip=true
break
fi
done
if [[ "$skip" == "false" ]]; then
FILTERED_FILES="$FILTERED_FILES $file"
fi
done
STAGED_FILES=$(echo "$FILTERED_FILES" | xargs)
fi
# Exit if all files were filtered
if [[ -z "$STAGED_FILES" ]]; then
exit 0
fi
# Count files
FILE_COUNT=$(echo "$STAGED_FILES" | wc -w | tr -d ' ')
echo -e "${CYAN}Evaluating $FILE_COUNT staged file(s) with Claude ($MODEL)...${NC}"
echo -e "${YELLOW}Cost: ~\$0.01-0.05 per evaluation${NC}"
echo ""
# Get the diff
DIFF=$(git diff --cached)
# Truncate diff if too large (to control costs)
MAX_CHARS=50000
if [[ ${#DIFF} -gt $MAX_CHARS ]]; then
echo -e "${YELLOW}Warning: Diff truncated to ${MAX_CHARS} chars for cost control${NC}"
DIFF="${DIFF:0:$MAX_CHARS}
[TRUNCATED - diff exceeded ${MAX_CHARS} characters]"
fi
# Prepare the prompt
PROMPT="You are a code quality evaluator. Analyze this git diff and provide a JSON evaluation.
Score each criterion from 0-10:
- correctness: Does the code work correctly?
- completeness: Is the implementation complete (no TODOs, stubs)?
- safety: No secrets, no security issues?
Respond ONLY with valid JSON in this format:
{
\"verdict\": \"APPROVE\" or \"NEEDS_REVIEW\" or \"REJECT\",
\"scores\": {\"correctness\": N, \"completeness\": N, \"safety\": N},
\"issues\": [{\"severity\": \"high/medium/low\", \"description\": \"...\"}],
\"summary\": \"One sentence summary\"
}
Rules:
- APPROVE if all scores >= $THRESHOLD and no high-severity issues
- NEEDS_REVIEW if any score is 5-$((THRESHOLD-1)) or medium issues exist
- REJECT if any score < 5 or high-severity security issues
Git diff to evaluate:
$DIFF"
# Call Claude (requires claude CLI to be installed and authenticated)
if ! command -v claude &> /dev/null; then
echo -e "${RED}Error: 'claude' CLI not found. Install Claude Code first.${NC}"
exit 1
fi
# Run evaluation
RESULT=$(echo "$PROMPT" | claude --model "$MODEL" --print 2>/dev/null) || {
echo -e "${RED}Error: Claude evaluation failed${NC}"
echo "You can bypass with: CLAUDE_SKIP_EVAL=1 git commit"
exit 1
}
# Extract JSON from response (handle potential markdown wrapping)
JSON_RESULT=$(echo "$RESULT" | grep -o '{.*}' | head -1)
if [[ -z "$JSON_RESULT" ]]; then
echo -e "${YELLOW}Warning: Could not parse evaluation result${NC}"
echo "Raw response: $RESULT"
echo ""
echo "Proceeding with commit (evaluation inconclusive)"
exit 0
fi
# Parse result
VERDICT=$(echo "$JSON_RESULT" | jq -r '.verdict // "UNKNOWN"')
CORRECTNESS=$(echo "$JSON_RESULT" | jq -r '.scores.correctness // 0')
COMPLETENESS=$(echo "$JSON_RESULT" | jq -r '.scores.completeness // 0')
SAFETY=$(echo "$JSON_RESULT" | jq -r '.scores.safety // 0')
SUMMARY=$(echo "$JSON_RESULT" | jq -r '.summary // "No summary"')
ISSUES=$(echo "$JSON_RESULT" | jq -r '.issues // []')
# Display results
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Evaluation Results${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo ""
echo " Correctness: $CORRECTNESS/10"
echo " Completeness: $COMPLETENESS/10"
echo " Safety: $SAFETY/10"
echo ""
echo " Summary: $SUMMARY"
echo ""
# Show issues if any
ISSUE_COUNT=$(echo "$ISSUES" | jq 'length')
if [[ "$ISSUE_COUNT" -gt 0 ]]; then
echo " Issues found:"
echo "$ISSUES" | jq -r '.[] | " [\(.severity | ascii_upcase)] \(.description)"'
echo ""
fi
# Handle verdict
case "$VERDICT" in
APPROVE)
echo -e "${GREEN}✓ APPROVED - Proceeding with commit${NC}"
echo ""
exit 0
;;
NEEDS_REVIEW)
echo -e "${YELLOW}⚠ NEEDS_REVIEW - Issues detected${NC}"
echo ""
echo "Options:"
echo " 1. Fix issues and try again"
echo " 2. Bypass: CLAUDE_SKIP_EVAL=1 git commit"
echo " 3. Skip hook: git commit --no-verify"
echo ""
exit 1
;;
REJECT)
echo -e "${RED}✗ REJECTED - Critical issues found${NC}"
echo ""
echo "Please fix the issues before committing."
echo "To force commit anyway: git commit --no-verify"
echo ""
exit 1
;;
*)
echo -e "${YELLOW}? Unknown verdict: $VERDICT${NC}"
echo "Proceeding with commit (evaluation inconclusive)"
exit 0
;;
esac

View file

@ -0,0 +1,182 @@
#!/bin/bash
# Hook: PreToolUse - Detect prompt injection attempts
# Exit 0 = allow, Exit 2 = block (stderr message shown to Claude)
#
# This hook detects common prompt injection patterns that attempt to
# manipulate Claude's behavior through malicious instructions.
#
# Place in: .claude/hooks/prompt-injection-detector.sh
# Register in: .claude/settings.json under PreToolUse event
set -e
# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty')
# Only check tools that handle user-provided text content
case "$TOOL_NAME" in
Bash|Write|Edit|WebFetch)
;;
*)
exit 0
;;
esac
# Extract content to analyze based on tool type
CONTENT=""
case "$TOOL_NAME" in
Bash)
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
;;
Write|Edit)
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.content // .new_string // empty')
;;
WebFetch)
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.url // empty')
;;
esac
# Skip if no content to analyze
[[ -z "$CONTENT" ]] && exit 0
# Convert to lowercase for case-insensitive matching
CONTENT_LOWER=$(echo "$CONTENT" | tr '[:upper:]' '[:lower:]')
# === ROLE OVERRIDE PATTERNS ===
# Attempts to override Claude's instructions or identity
ROLE_OVERRIDE_PATTERNS=(
"ignore previous instructions"
"ignore all previous"
"ignore your instructions"
"disregard previous"
"disregard your instructions"
"forget your instructions"
"forget everything"
"you are now"
"act as if"
"pretend you are"
"pretend to be"
"from now on you"
"new instructions:"
"override:"
"system prompt:"
)
for pattern in "${ROLE_OVERRIDE_PATTERNS[@]}"; do
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
echo "BLOCKED: Prompt injection detected - role override attempt: '$pattern'" >&2
exit 2
fi
done
# === JAILBREAK PATTERNS ===
# Known jailbreak techniques and bypass attempts
JAILBREAK_PATTERNS=(
"dan mode"
"developer mode"
"jailbreak"
"do anything now"
"bypass"
"no restrictions"
"without restrictions"
"unrestricted mode"
"god mode"
"sudo mode"
"admin mode"
"debug mode"
"maintenance mode"
)
for pattern in "${JAILBREAK_PATTERNS[@]}"; do
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
echo "BLOCKED: Prompt injection detected - jailbreak attempt: '$pattern'" >&2
exit 2
fi
done
# === DELIMITER INJECTION ===
# Attempts to inject fake system messages or close prompt sections
DELIMITER_PATTERNS=(
"</system>"
"<|endoftext|>"
"<|im_end|>"
"[/INST]"
"[INST]"
"<<SYS>>"
"<</SYS>>"
"### System:"
"### Human:"
"### Assistant:"
"```system"
"SYSTEM:"
)
for pattern in "${DELIMITER_PATTERNS[@]}"; do
if [[ "$CONTENT" == *"$pattern"* ]]; then
echo "BLOCKED: Prompt injection detected - delimiter injection: '$pattern'" >&2
exit 2
fi
done
# === AUTHORITY IMPERSONATION ===
# Claims of special authority or permissions
AUTHORITY_PATTERNS=(
"anthropic employee"
"anthropic staff"
"i am your creator"
"i am your developer"
"i have admin access"
"authorized to bypass"
"emergency override"
"security exception"
"this is a test"
"testing mode"
)
for pattern in "${AUTHORITY_PATTERNS[@]}"; do
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
echo "BLOCKED: Prompt injection detected - authority impersonation: '$pattern'" >&2
exit 2
fi
done
# === BASE64 ENCODED INSTRUCTIONS ===
# Detect potential base64-encoded payloads (heuristic)
# Look for long base64-like strings that might contain instructions
if echo "$CONTENT" | grep -qE '[A-Za-z0-9+/]{50,}={0,2}'; then
# Try to decode and check for injection patterns
DECODED=$(echo "$CONTENT" | grep -oE '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d 2>/dev/null || true)
DECODED_LOWER=$(echo "$DECODED" | tr '[:upper:]' '[:lower:]')
for pattern in "ignore" "override" "system" "jailbreak" "dan mode"; do
if [[ "$DECODED_LOWER" == *"$pattern"* ]]; then
echo "BLOCKED: Prompt injection detected - encoded payload containing: '$pattern'" >&2
exit 2
fi
done
fi
# === CONTEXT MANIPULATION ===
# Attempts to manipulate the conversation context
CONTEXT_PATTERNS=(
"in the previous message"
"as i mentioned earlier"
"you agreed to"
"you already said"
"you promised"
"remember when you"
"our agreement was"
)
for pattern in "${CONTEXT_PATTERNS[@]}"; do
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
# Warning only - these could be legitimate
echo '{"systemMessage": "Warning: Detected potential context manipulation pattern. Verify legitimacy."}'
fi
done
# Allow by default
exit 0

View file

@ -0,0 +1,102 @@
#!/bin/bash
# Hook: PostToolUse - Log all Claude Code operations for monitoring
# Exit 0 = allow (always)
#
# This hook logs all tool operations to JSONL files for later analysis.
# Use session-stats.sh to analyze the logs.
#
# Logs are stored in: ~/.claude/logs/activity-YYYY-MM-DD.jsonl
#
# Environment variables:
# CLAUDE_LOG_DIR - Override log directory (default: ~/.claude/logs)
# CLAUDE_LOG_TOKENS - Enable token estimation (default: true)
# CLAUDE_SESSION_ID - Session identifier (auto-generated if not set)
#
# Place in: .claude/hooks/session-logger.sh
# Register in: .claude/settings.json under PostToolUse event
set -e
# Configuration
LOG_DIR="${CLAUDE_LOG_DIR:-$HOME/.claude/logs}"
ENABLE_TOKENS="${CLAUDE_LOG_TOKENS:-true}"
SESSION_ID="${CLAUDE_SESSION_ID:-$(date +%s)-$$}"
# Ensure log directory exists
mkdir -p "$LOG_DIR"
# Log file for today
LOG_FILE="$LOG_DIR/activity-$(date +%Y-%m-%d).jsonl"
# Read JSON from stdin
INPUT=$(cat)
# Extract tool information
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // ""')
# Get timestamp
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Extract relevant details based on tool type
FILE_PATH=""
COMMAND=""
case "$TOOL_NAME" in
Read|Write|Edit)
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // .path // ""')
;;
Bash)
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // ""' | head -c 200)
;;
Grep|Glob)
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.path // .pattern // ""')
;;
esac
# Estimate tokens (rough heuristic: ~4 chars per token)
TOKENS_INPUT=0
TOKENS_OUTPUT=0
if [[ "$ENABLE_TOKENS" == "true" ]]; then
INPUT_LEN=${#TOOL_INPUT}
OUTPUT_LEN=${#TOOL_OUTPUT}
TOKENS_INPUT=$((INPUT_LEN / 4))
TOKENS_OUTPUT=$((OUTPUT_LEN / 4))
fi
# Get project directory (if available)
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
PROJECT_NAME=$(basename "$PROJECT_DIR")
# Build log entry
LOG_ENTRY=$(jq -n \
--arg timestamp "$TIMESTAMP" \
--arg session_id "$SESSION_ID" \
--arg tool "$TOOL_NAME" \
--arg file "$FILE_PATH" \
--arg command "$COMMAND" \
--arg project "$PROJECT_NAME" \
--argjson tokens_in "$TOKENS_INPUT" \
--argjson tokens_out "$TOKENS_OUTPUT" \
'{
timestamp: $timestamp,
session_id: $session_id,
tool: $tool,
file: (if $file != "" then $file else null end),
command: (if $command != "" then $command else null end),
project: $project,
tokens: {
input: $tokens_in,
output: $tokens_out,
total: ($tokens_in + $tokens_out)
}
} | with_entries(select(.value != null))'
)
# Append to log file
echo "$LOG_ENTRY" >> "$LOG_FILE"
# Always allow
exit 0

235
examples/scripts/session-stats.sh Executable file
View file

@ -0,0 +1,235 @@
#!/bin/bash
# session-stats.sh - Analyze Claude Code session logs
#
# Analyzes logs created by session-logger.sh hook and outputs statistics
# about tool usage, estimated costs, and session patterns.
#
# Usage:
# ./session-stats.sh # Today's summary
# ./session-stats.sh --range week # Last 7 days
# ./session-stats.sh --range month # Last 30 days
# ./session-stats.sh --date 2026-01-14 # Specific date
# ./session-stats.sh --json # Machine-readable output
# ./session-stats.sh --project myapp # Filter by project
#
# Environment:
# CLAUDE_LOG_DIR - Log directory (default: ~/.claude/logs)
#
# Cost rates (per 1K tokens, configurable):
# CLAUDE_RATE_INPUT - Input token rate (default: 0.003 for Sonnet)
# CLAUDE_RATE_OUTPUT - Output token rate (default: 0.015 for Sonnet)
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Configuration
LOG_DIR="${CLAUDE_LOG_DIR:-$HOME/.claude/logs}"
RATE_INPUT="${CLAUDE_RATE_INPUT:-0.003}"
RATE_OUTPUT="${CLAUDE_RATE_OUTPUT:-0.015}"
# Defaults
OUTPUT_MODE="human"
DATE_RANGE="today"
SPECIFIC_DATE=""
PROJECT_FILTER=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--json)
OUTPUT_MODE="json"
shift
;;
--range)
DATE_RANGE="$2"
shift 2
;;
--date)
SPECIFIC_DATE="$2"
DATE_RANGE="specific"
shift 2
;;
--project)
PROJECT_FILTER="$2"
shift 2
;;
--help|-h)
grep '^#' "$0" | sed 's/^# \?//'
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Check if log directory exists
if [[ ! -d "$LOG_DIR" ]]; then
if [[ "$OUTPUT_MODE" == "json" ]]; then
echo '{"error": "No logs found", "log_dir": "'"$LOG_DIR"'"}'
else
echo -e "${RED}No logs found in $LOG_DIR${NC}"
echo "Enable session-logger.sh hook to start collecting logs."
fi
exit 1
fi
# Determine which log files to analyze
LOG_FILES=()
case "$DATE_RANGE" in
today)
TODAY=$(date +%Y-%m-%d)
[[ -f "$LOG_DIR/activity-$TODAY.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$TODAY.jsonl")
;;
week)
for i in {0..6}; do
DATE=$(date -v-${i}d +%Y-%m-%d 2>/dev/null || date -d "-$i days" +%Y-%m-%d)
[[ -f "$LOG_DIR/activity-$DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$DATE.jsonl")
done
;;
month)
for i in {0..29}; do
DATE=$(date -v-${i}d +%Y-%m-%d 2>/dev/null || date -d "-$i days" +%Y-%m-%d)
[[ -f "$LOG_DIR/activity-$DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$DATE.jsonl")
done
;;
specific)
[[ -f "$LOG_DIR/activity-$SPECIFIC_DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$SPECIFIC_DATE.jsonl")
;;
esac
if [[ ${#LOG_FILES[@]} -eq 0 ]]; then
if [[ "$OUTPUT_MODE" == "json" ]]; then
echo '{"error": "No logs found for specified range", "range": "'"$DATE_RANGE"'"}'
else
echo -e "${YELLOW}No logs found for range: $DATE_RANGE${NC}"
fi
exit 0
fi
# Combine and filter logs
COMBINED_LOGS=$(cat "${LOG_FILES[@]}" 2>/dev/null || true)
if [[ -n "$PROJECT_FILTER" ]]; then
COMBINED_LOGS=$(echo "$COMBINED_LOGS" | jq -c "select(.project == \"$PROJECT_FILTER\")" 2>/dev/null || true)
fi
if [[ -z "$COMBINED_LOGS" ]]; then
if [[ "$OUTPUT_MODE" == "json" ]]; then
echo '{"error": "No matching logs found"}'
else
echo -e "${YELLOW}No matching logs found${NC}"
fi
exit 0
fi
# Calculate statistics
TOTAL_OPS=$(echo "$COMBINED_LOGS" | wc -l | tr -d ' ')
TOTAL_TOKENS_IN=$(echo "$COMBINED_LOGS" | jq -s '[.[].tokens.input // 0] | add')
TOTAL_TOKENS_OUT=$(echo "$COMBINED_LOGS" | jq -s '[.[].tokens.output // 0] | add')
TOTAL_TOKENS=$((TOTAL_TOKENS_IN + TOTAL_TOKENS_OUT))
# Cost calculation
COST_INPUT=$(echo "scale=4; $TOTAL_TOKENS_IN * $RATE_INPUT / 1000" | bc)
COST_OUTPUT=$(echo "scale=4; $TOTAL_TOKENS_OUT * $RATE_OUTPUT / 1000" | bc)
COST_TOTAL=$(echo "scale=4; $COST_INPUT + $COST_OUTPUT" | bc)
# Tool breakdown
TOOL_STATS=$(echo "$COMBINED_LOGS" | jq -s 'group_by(.tool) | map({tool: .[0].tool, count: length}) | sort_by(-.count)')
# Session count
SESSION_COUNT=$(echo "$COMBINED_LOGS" | jq -s '[.[].session_id] | unique | length')
# Project breakdown
PROJECT_STATS=$(echo "$COMBINED_LOGS" | jq -s 'group_by(.project) | map({project: .[0].project, count: length}) | sort_by(-.count)')
# Most edited files
FILE_STATS=$(echo "$COMBINED_LOGS" | jq -s '[.[] | select(.file != null)] | group_by(.file) | map({file: .[0].file, count: length}) | sort_by(-.count) | .[0:10]')
# Output
if [[ "$OUTPUT_MODE" == "json" ]]; then
jq -n \
--arg range "$DATE_RANGE" \
--argjson total_ops "$TOTAL_OPS" \
--argjson sessions "$SESSION_COUNT" \
--argjson tokens_in "$TOTAL_TOKENS_IN" \
--argjson tokens_out "$TOTAL_TOKENS_OUT" \
--argjson tokens_total "$TOTAL_TOKENS" \
--arg cost_in "$COST_INPUT" \
--arg cost_out "$COST_OUTPUT" \
--arg cost_total "$COST_TOTAL" \
--argjson tools "$TOOL_STATS" \
--argjson projects "$PROJECT_STATS" \
--argjson files "$FILE_STATS" \
'{
range: $range,
summary: {
total_operations: $total_ops,
sessions: $sessions,
tokens: {
input: $tokens_in,
output: $tokens_out,
total: $tokens_total
},
estimated_cost: {
input: ($cost_in | tonumber),
output: ($cost_out | tonumber),
total: ($cost_total | tonumber),
currency: "USD"
}
},
tools: $tools,
projects: $projects,
top_files: $files
}'
else
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Claude Code Session Statistics - $DATE_RANGE${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Summary${NC}"
echo " Total operations: $TOTAL_OPS"
echo " Sessions: $SESSION_COUNT"
echo ""
echo -e "${BLUE}Token Usage${NC}"
printf " Input tokens: %'d\n" "$TOTAL_TOKENS_IN"
printf " Output tokens: %'d\n" "$TOTAL_TOKENS_OUT"
printf " Total tokens: %'d\n" "$TOTAL_TOKENS"
echo ""
echo -e "${BLUE}Estimated Cost (Sonnet rates)${NC}"
printf " Input: \$%.4f\n" "$COST_INPUT"
printf " Output: \$%.4f\n" "$COST_OUTPUT"
printf " ${GREEN}Total: \$%.4f${NC}\n" "$COST_TOTAL"
echo ""
echo -e "${BLUE}Tools Used${NC}"
echo "$TOOL_STATS" | jq -r '.[] | " \(.tool): \(.count)"'
echo ""
echo -e "${BLUE}Projects${NC}"
echo "$PROJECT_STATS" | jq -r '.[] | " \(.project): \(.count)"'
echo ""
echo -e "${BLUE}Most Edited Files${NC}"
echo "$FILE_STATS" | jq -r '.[] | " \(.file | split("/")[-1]): \(.count)"' | head -5
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "Logs: $LOG_DIR"
echo -e "Rate config: \$${RATE_INPUT}/1K in, \$${RATE_OUTPUT}/1K out"
echo ""
fi