Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:16:51 +08:00
commit 4e8a12140c
88 changed files with 17078 additions and 0 deletions

View File

@@ -0,0 +1,663 @@
#!/usr/bin/env python3
"""
CLAUDE.md Analyzer
Comprehensive validation engine for CLAUDE.md configuration files.
Validates against three categories:
1. Official Anthropic guidance (docs.claude.com)
2. Community best practices
3. Research-based optimizations
"""
import re
import os
from pathlib import Path
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass, field
from enum import Enum
class Severity(Enum):
"""Finding severity levels"""
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
class Category(Enum):
"""Finding categories"""
SECURITY = "security"
OFFICIAL_COMPLIANCE = "official_compliance"
BEST_PRACTICES = "best_practices"
RESEARCH_OPTIMIZATION = "research_optimization"
STRUCTURE = "structure"
MAINTENANCE = "maintenance"
@dataclass
class Finding:
"""Represents a single audit finding"""
severity: Severity
category: Category
title: str
description: str
line_number: Optional[int] = None
code_snippet: Optional[str] = None
impact: str = ""
remediation: str = ""
source: str = "" # "official", "community", or "research"
@dataclass
class AuditResults:
"""Container for all audit results"""
findings: List[Finding] = field(default_factory=list)
scores: Dict[str, int] = field(default_factory=dict)
metadata: Dict[str, any] = field(default_factory=dict)
def add_finding(self, finding: Finding):
"""Add a finding to results"""
self.findings.append(finding)
def calculate_scores(self):
"""Calculate health scores"""
# Count findings by severity
critical = sum(1 for f in self.findings if f.severity == Severity.CRITICAL)
high = sum(1 for f in self.findings if f.severity == Severity.HIGH)
medium = sum(1 for f in self.findings if f.severity == Severity.MEDIUM)
low = sum(1 for f in self.findings if f.severity == Severity.LOW)
# Calculate category scores (0-100)
total_issues = max(critical * 20 + high * 10 + medium * 5 + low * 2, 1)
base_score = max(0, 100 - total_issues)
# Category-specific scores
security_issues = [f for f in self.findings if f.category == Category.SECURITY]
official_issues = [f for f in self.findings if f.category == Category.OFFICIAL_COMPLIANCE]
best_practice_issues = [f for f in self.findings if f.category == Category.BEST_PRACTICES]
research_issues = [f for f in self.findings if f.category == Category.RESEARCH_OPTIMIZATION]
self.scores = {
"overall": base_score,
"security": max(0, 100 - len(security_issues) * 25),
"official_compliance": max(0, 100 - len(official_issues) * 10),
"best_practices": max(0, 100 - len(best_practice_issues) * 5),
"research_optimization": max(0, 100 - len(research_issues) * 3),
"critical_count": critical,
"high_count": high,
"medium_count": medium,
"low_count": low,
}
class CLAUDEMDAnalyzer:
"""Main analyzer for CLAUDE.md files"""
# Secret patterns (CRITICAL violations)
SECRET_PATTERNS = [
(r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\']?[a-zA-Z0-9_\-]{20,}', 'API Key'),
(r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\']?[^\s"\']{8,}', 'Password/Secret'),
(r'(?i)(token|auth[_-]?token)\s*[=:]\s*["\']?[a-zA-Z0-9_\-]{20,}', 'Auth Token'),
(r'(?i)sk-[a-zA-Z0-9]{20,}', 'OpenAI API Key'),
(r'(?i)AKIA[0-9A-Z]{16}', 'AWS Access Key'),
(r'(?i)(-----BEGIN.*PRIVATE KEY-----)', 'Private Key'),
(r'(?i)(postgres|mysql|mongodb)://[^:]+:[^@]+@', 'Database Connection String'),
]
# Generic content indicators (HIGH violations)
GENERIC_PATTERNS = [
r'(?i)React is a (JavaScript|JS) library',
r'(?i)TypeScript is a typed superset',
r'(?i)Git is a version control',
r'(?i)npm is a package manager',
r'(?i)What is a component\?',
]
def __init__(self, file_path: Path):
self.file_path = Path(file_path)
self.results = AuditResults()
self.content = ""
self.lines = []
self.line_count = 0
self.token_estimate = 0
def analyze(self) -> AuditResults:
"""Run comprehensive analysis"""
# Read file
if not self._read_file():
return self.results
# Calculate metadata
self._calculate_metadata()
# Run all validators
self._validate_security()
self._validate_official_compliance()
self._validate_best_practices()
self._validate_research_optimization()
self._validate_structure()
self._validate_maintenance()
# Calculate scores
self.results.calculate_scores()
return self.results
def _read_file(self) -> bool:
"""Read and parse the CLAUDE.md file"""
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
self.content = f.read()
self.lines = self.content.split('\n')
self.line_count = len(self.lines)
return True
except Exception as e:
self.results.add_finding(Finding(
severity=Severity.CRITICAL,
category=Category.OFFICIAL_COMPLIANCE,
title="Cannot Read File",
description=f"Failed to read {self.file_path}: {str(e)}",
impact="Unable to validate CLAUDE.md configuration",
remediation="Ensure file exists and is readable"
))
return False
def _calculate_metadata(self):
"""Calculate file metadata"""
# Estimate tokens (rough: 1 token ≈ 4 characters for English)
self.token_estimate = len(self.content) // 4
# Calculate percentages of context window
context_200k = (self.token_estimate / 200000) * 100
context_1m = (self.token_estimate / 1000000) * 100
self.results.metadata = {
"file_path": str(self.file_path),
"line_count": self.line_count,
"character_count": len(self.content),
"token_estimate": self.token_estimate,
"context_usage_200k": round(context_200k, 2),
"context_usage_1m": round(context_1m, 2),
"tier": self._detect_tier(),
}
def _detect_tier(self) -> str:
"""Detect which memory tier this file belongs to"""
path_str = str(self.file_path.absolute())
if '/Library/Application Support/ClaudeCode/' in path_str or \
'/etc/claude-code/' in path_str or \
'C:\\ProgramData\\ClaudeCode\\' in path_str:
return "Enterprise"
elif str(self.file_path.name) == 'CLAUDE.md' and \
(self.file_path.parent.name == '.claude' or \
self.file_path.parent.name != Path.home().name):
return "Project"
elif Path.home() in self.file_path.parents:
return "User"
else:
return "Unknown"
# ========== SECURITY VALIDATION ==========
def _validate_security(self):
"""CRITICAL: Check for secrets and sensitive information"""
# Check for secrets
for line_num, line in enumerate(self.lines, 1):
for pattern, secret_type in self.SECRET_PATTERNS:
if re.search(pattern, line):
self.results.add_finding(Finding(
severity=Severity.CRITICAL,
category=Category.SECURITY,
title=f"🚨 {secret_type} Detected",
description=f"Potential {secret_type.lower()} found in CLAUDE.md",
line_number=line_num,
code_snippet=self._redact_line(line),
impact="Security breach risk. Secrets may be exposed in git history, "
"logs, or backups. This violates security best practices.",
remediation=f"1. Remove the {secret_type.lower()} immediately\n"
"2. Rotate the compromised credential\n"
"3. Use environment variables or secret management\n"
"4. Add to .gitignore if in separate file\n"
"5. Clean git history if committed",
source="official"
))
# Check for internal URLs/IPs
internal_ip_pattern = r'\b(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b'
if re.search(internal_ip_pattern, self.content):
self.results.add_finding(Finding(
severity=Severity.CRITICAL,
category=Category.SECURITY,
title="Internal IP Address Exposed",
description="Internal IP addresses found in CLAUDE.md",
impact="Exposes internal infrastructure topology",
remediation="Remove internal IPs. Reference documentation instead.",
source="official"
))
def _redact_line(self, line: str) -> str:
"""Redact sensitive parts of line for display"""
for pattern, _ in self.SECRET_PATTERNS:
line = re.sub(pattern, '[REDACTED]', line)
return line[:100] + "..." if len(line) > 100 else line
# ========== OFFICIAL COMPLIANCE VALIDATION ==========
def _validate_official_compliance(self):
"""Validate against official Anthropic documentation"""
# Check for excessive verbosity (> 500 lines)
if self.line_count > 500:
self.results.add_finding(Finding(
severity=Severity.HIGH,
category=Category.OFFICIAL_COMPLIANCE,
title="File Exceeds Recommended Length",
description=f"CLAUDE.md has {self.line_count} lines (recommended: < 300)",
impact="Consumes excessive context window space. Official guidance: "
"'keep them lean as they take up context window space'",
remediation="Reduce to under 300 lines. Use @imports for detailed documentation:\n"
"Example: @docs/architecture.md",
source="official"
))
# Check for generic programming content
self._check_generic_content()
# Validate import syntax and depth
self._validate_imports()
# Check for vague instructions
self._check_vague_instructions()
# Validate structure and formatting
self._check_markdown_structure()
def _check_generic_content(self):
"""Check for generic programming tutorials/documentation"""
for line_num, line in enumerate(self.lines, 1):
for pattern in self.GENERIC_PATTERNS:
if re.search(pattern, line):
self.results.add_finding(Finding(
severity=Severity.HIGH,
category=Category.OFFICIAL_COMPLIANCE,
title="Generic Programming Content Detected",
description="File contains generic programming documentation",
line_number=line_num,
code_snippet=line[:100],
impact="Wastes context window. Official guidance: Don't include "
"'basic programming concepts Claude already understands'",
remediation="Remove generic content. Focus on project-specific standards.",
source="official"
))
break # One finding per line is enough
def _validate_imports(self):
"""Validate @import statements"""
import_pattern = r'^\s*@([^\s]+)'
imports = []
for line_num, line in enumerate(self.lines, 1):
match = re.match(import_pattern, line)
if match:
import_path = match.group(1)
imports.append((line_num, import_path))
# Check if import path exists (if it's not a URL)
if not import_path.startswith(('http://', 'https://')):
full_path = self.file_path.parent / import_path
if not full_path.exists():
self.results.add_finding(Finding(
severity=Severity.MEDIUM,
category=Category.MAINTENANCE,
title="Broken Import Path",
description=f"Import path does not exist: {import_path}",
line_number=line_num,
code_snippet=line,
impact="Imported documentation will not be loaded",
remediation=f"Fix import path or remove if no longer needed. "
f"Expected: {full_path}",
source="official"
))
# Check for excessive imports (> 10 might be excessive)
if len(imports) > 10:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.BEST_PRACTICES,
title="Excessive Imports",
description=f"Found {len(imports)} import statements",
impact="Many imports may indicate poor organization",
remediation="Consider consolidating related documentation",
source="community"
))
# TODO: Check for circular imports (requires traversing import graph)
# TODO: Check import depth (max 5 hops)
def _check_vague_instructions(self):
"""Detect vague or ambiguous instructions"""
vague_phrases = [
(r'\b(write|make|keep it|be)\s+(good|clean|simple|consistent|professional)\b', 'vague quality advice'),
(r'\bfollow\s+best\s+practices\b', 'undefined best practices'),
(r'\bdon\'t\s+be\s+clever\b', 'subjective advice'),
(r'\bkeep\s+it\s+simple\b', 'vague simplicity advice'),
]
for line_num, line in enumerate(self.lines, 1):
for pattern, issue_type in vague_phrases:
if re.search(pattern, line, re.IGNORECASE):
self.results.add_finding(Finding(
severity=Severity.HIGH,
category=Category.OFFICIAL_COMPLIANCE,
title="Vague or Ambiguous Instruction",
description=f"Line contains {issue_type}: not specific or measurable",
line_number=line_num,
code_snippet=line[:100],
impact="Not actionable. Claude won't know what this means in your context. "
"Official guidance: 'Be specific'",
remediation="Replace with measurable standards. Example:\n"
"'Write good code'\n"
"'Function length: max 50 lines, complexity: max 10'",
source="official"
))
def _check_markdown_structure(self):
"""Validate markdown structure and formatting"""
# Check for at least one H1 header
if not re.search(r'^#\s+', self.content, re.MULTILINE):
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.STRUCTURE,
title="Missing Top-Level Header",
description="No H1 header (#) found",
impact="Poor document structure",
remediation="Add H1 header with project name: # Project Name",
source="community"
))
# Check for consistent bullet style
dash_bullets = len(re.findall(r'^\s*-\s+', self.content, re.MULTILINE))
asterisk_bullets = len(re.findall(r'^\s*\*\s+', self.content, re.MULTILINE))
if dash_bullets > 5 and asterisk_bullets > 5:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.STRUCTURE,
title="Inconsistent Bullet Style",
description=f"Mix of dash (-) and asterisk (*) bullets",
impact="Inconsistent formatting reduces readability",
remediation="Use consistent bullet style (recommend: dashes)",
source="community"
))
# ========== BEST PRACTICES VALIDATION ==========
def _validate_best_practices(self):
"""Validate against community best practices"""
# Check recommended size range (100-300 lines)
if self.line_count < 50:
self.results.add_finding(Finding(
severity=Severity.INFO,
category=Category.BEST_PRACTICES,
title="File May Be Too Sparse",
description=f"Only {self.line_count} lines (recommended: 100-300)",
impact="May lack important project context",
remediation="Consider adding: project overview, standards, common commands",
source="community"
))
elif 300 < self.line_count <= 500:
self.results.add_finding(Finding(
severity=Severity.MEDIUM,
category=Category.BEST_PRACTICES,
title="File Exceeds Optimal Length",
description=f"{self.line_count} lines (recommended: 100-300)",
impact="Community best practice: 200-line sweet spot for balance",
remediation="Consider using imports for detailed documentation",
source="community"
))
# Check token usage percentage
if self.token_estimate > 10000: # > 5% of 200K context
self.results.add_finding(Finding(
severity=Severity.MEDIUM,
category=Category.BEST_PRACTICES,
title="High Token Usage",
description=f"Estimated {self.token_estimate} tokens "
f"({self.results.metadata['context_usage_200k']}% of 200K window)",
impact="Consumes significant context space (> 5%)",
remediation="Aim for < 3,000 tokens (≈200 lines). Use imports for details.",
source="community"
))
# Check for organizational patterns
self._check_organization()
# Check for maintenance indicators
self._check_update_dates()
def _check_organization(self):
"""Check for good organizational patterns"""
# Look for section markers
sections = re.findall(r'^##\s+(.+)$', self.content, re.MULTILINE)
if len(sections) < 3:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.STRUCTURE,
title="Minimal Organization",
description=f"Only {len(sections)} main sections found",
impact="May lack clear structure",
remediation="Organize into sections: Standards, Workflow, Commands, Reference",
source="community"
))
# Check for critical/important markers
has_critical = bool(re.search(r'(?i)(critical|must|required|mandatory)', self.content))
if not has_critical and self.line_count > 100:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.BEST_PRACTICES,
title="No Priority Markers",
description="No CRITICAL/MUST/REQUIRED emphasis found",
impact="Hard to distinguish must-follow vs. nice-to-have standards",
remediation="Add priority markers: CRITICAL, IMPORTANT, RECOMMENDED",
source="community"
))
def _check_update_dates(self):
"""Check for update dates/version information"""
date_pattern = r'\b(20\d{2}[/-]\d{1,2}[/-]\d{1,2}|updated?:?\s*20\d{2})\b'
has_date = bool(re.search(date_pattern, self.content, re.IGNORECASE))
if not has_date and self.line_count > 100:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.MAINTENANCE,
title="No Update Date",
description="No last-updated date found",
impact="Hard to know if information is current",
remediation="Add update date: Updated: 2025-10-26",
source="community"
))
# ========== RESEARCH OPTIMIZATION VALIDATION ==========
def _validate_research_optimization(self):
"""Validate against research-based optimizations"""
# Check for positioning strategy (critical info at top/bottom)
self._check_positioning_strategy()
# Check for effective chunking
self._check_chunking()
def _check_positioning_strategy(self):
"""Check if critical information is positioned optimally"""
# Analyze first 20% and last 20% for critical markers
top_20_idx = max(1, self.line_count // 5)
bottom_20_idx = self.line_count - top_20_idx
top_content = '\n'.join(self.lines[:top_20_idx])
bottom_content = '\n'.join(self.lines[bottom_20_idx:])
middle_content = '\n'.join(self.lines[top_20_idx:bottom_20_idx])
critical_markers = r'(?i)(critical|must|required|mandatory|never|always)'
top_critical = len(re.findall(critical_markers, top_content))
middle_critical = len(re.findall(critical_markers, middle_content))
bottom_critical = len(re.findall(critical_markers, bottom_content))
# If most critical content is in the middle, flag it
total_critical = top_critical + middle_critical + bottom_critical
if total_critical > 0 and middle_critical > (top_critical + bottom_critical):
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.RESEARCH_OPTIMIZATION,
title="Critical Content in Middle Position",
description="Most critical standards appear in middle section",
impact="Research shows 'lost in the middle' attention pattern. "
"Critical info at top/bottom gets more attention.",
remediation="Move must-follow standards to top section. "
"Move reference info to bottom. "
"Keep nice-to-have in middle.",
source="research"
))
def _check_chunking(self):
"""Check for effective information chunking"""
# Look for clear section boundaries
section_pattern = r'^#{1,3}\s+.+$'
sections = re.findall(section_pattern, self.content, re.MULTILINE)
if self.line_count > 100 and len(sections) < 5:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.RESEARCH_OPTIMIZATION,
title="Large Unchunked Content",
description=f"{self.line_count} lines with only {len(sections)} sections",
impact="Large blocks of text harder to process. "
"Research suggests chunking improves comprehension.",
remediation="Break into logical sections with clear headers",
source="research"
))
# ========== STRUCTURE & MAINTENANCE VALIDATION ==========
def _validate_structure(self):
"""Validate document structure"""
# Already covered in other validators
pass
def _validate_maintenance(self):
"""Validate maintenance indicators"""
# Check for broken links (basic check)
self._check_broken_links()
# Check for duplicate sections
self._check_duplicate_sections()
def _check_broken_links(self):
"""Check for potentially broken file paths"""
# Look for file path references
path_pattern = r'[/\\][a-zA-Z0-9_\-]+[/\\][^\s\)]*'
potential_paths = re.findall(path_pattern, self.content)
broken_count = 0
for path_str in potential_paths:
# Clean up the path
path_str = path_str.strip('`"\' ')
if path_str.startswith('/'):
# Check if path exists (relative to project root or absolute)
check_path = self.file_path.parent / path_str.lstrip('/')
if not check_path.exists() and not Path(path_str).exists():
broken_count += 1
if broken_count > 0:
self.results.add_finding(Finding(
severity=Severity.MEDIUM,
category=Category.MAINTENANCE,
title="Potentially Broken File Paths",
description=f"Found {broken_count} file paths that may not exist",
impact="Broken paths mislead developers and indicate stale documentation",
remediation="Verify all file paths and update or remove broken ones",
source="community"
))
def _check_duplicate_sections(self):
"""Check for duplicate section headers"""
headers = re.findall(r'^#{1,6}\s+(.+)$', self.content, re.MULTILINE)
header_counts = {}
for header in headers:
normalized = header.lower().strip()
header_counts[normalized] = header_counts.get(normalized, 0) + 1
duplicates = {h: c for h, c in header_counts.items() if c > 1}
if duplicates:
self.results.add_finding(Finding(
severity=Severity.LOW,
category=Category.STRUCTURE,
title="Duplicate Section Headers",
description=f"Found duplicate headers: {', '.join(duplicates.keys())}",
impact="May indicate poor organization or conflicting information",
remediation="Consolidate duplicate sections or rename for clarity",
source="community"
))
def analyze_file(file_path: str) -> AuditResults:
"""Convenience function to analyze a CLAUDE.md file"""
analyzer = CLAUDEMDAnalyzer(Path(file_path))
return analyzer.analyze()
if __name__ == "__main__":
import sys
import json
if len(sys.argv) < 2:
print("Usage: python analyzer.py <path-to-CLAUDE.md>")
sys.exit(1)
file_path = sys.argv[1]
results = analyze_file(file_path)
# Print summary
print(f"\n{'='*60}")
print(f"CLAUDE.md Audit Results: {file_path}")
print(f"{'='*60}\n")
print(f"Overall Health Score: {results.scores['overall']}/100")
print(f"Security Score: {results.scores['security']}/100")
print(f"Official Compliance Score: {results.scores['official_compliance']}/100")
print(f"Best Practices Score: {results.scores['best_practices']}/100")
print(f"Research Optimization Score: {results.scores['research_optimization']}/100")
print(f"\n{'='*60}")
print(f"Findings Summary:")
print(f" 🚨 Critical: {results.scores['critical_count']}")
print(f" ⚠️ High: {results.scores['high_count']}")
print(f" 📋 Medium: {results.scores['medium_count']}")
print(f" Low: {results.scores['low_count']}")
print(f"{'='*60}\n")
# Print findings
for finding in results.findings:
severity_emoji = {
Severity.CRITICAL: "🚨",
Severity.HIGH: "⚠️",
Severity.MEDIUM: "📋",
Severity.LOW: "",
Severity.INFO: "💡"
}
print(f"{severity_emoji.get(finding.severity, '')} {finding.title}")
print(f" Category: {finding.category.value}")
print(f" {finding.description}")
if finding.line_number:
print(f" Line: {finding.line_number}")
if finding.remediation:
print(f" Fix: {finding.remediation}")
print()