- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
9.4 KiB
Python
247 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Workspace Analyzer - Deep scan of project structure and code quality
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
import subprocess
|
|
import re
|
|
|
|
WORKSPACE = "/home/alex/clawd"
|
|
|
|
# File extensions to analyze
|
|
CODE_EXTS = {'.py', '.js', '.ts', '.sh', '.bash', '.zsh'}
|
|
CONFIG_EXTS = {'.json', '.yaml', '.yml', '.toml', '.ini', '.conf'}
|
|
DOC_EXTS = {'.md', '.txt', '.rst', '.adoc'}
|
|
|
|
# Patterns to ignore
|
|
IGNORE_PATTERNS = {
|
|
'node_modules', '.git', '__pycache__', '.venv', 'venv',
|
|
'dist', 'build', '.next', '.cache', 'coverage'
|
|
}
|
|
|
|
class WorkspaceAnalyzer:
|
|
def __init__(self, root_path=WORKSPACE):
|
|
self.root = Path(root_path)
|
|
self.stats = defaultdict(int)
|
|
self.issues = {'high': [], 'medium': [], 'low': []}
|
|
self.files = defaultdict(list)
|
|
|
|
def should_ignore(self, path):
|
|
"""Check if path should be ignored"""
|
|
parts = Path(path).parts
|
|
return any(ignore in parts for ignore in IGNORE_PATTERNS)
|
|
|
|
def scan_directory(self):
|
|
"""Scan entire workspace"""
|
|
print(f"🔍 Scanning workspace: {self.root}")
|
|
|
|
for root, dirs, files in os.walk(self.root):
|
|
# Filter ignored directories
|
|
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
|
|
|
|
for file in files:
|
|
filepath = Path(root) / file
|
|
|
|
if self.should_ignore(filepath):
|
|
continue
|
|
|
|
self.analyze_file(filepath)
|
|
|
|
print(f"✅ Scanned {self.stats['total_files']} files")
|
|
|
|
def analyze_file(self, filepath):
|
|
"""Analyze individual file"""
|
|
self.stats['total_files'] += 1
|
|
|
|
ext = filepath.suffix.lower()
|
|
|
|
# Categorize by extension
|
|
if ext in CODE_EXTS:
|
|
self.files['code'].append(filepath)
|
|
self.stats['code_files'] += 1
|
|
self.check_code_quality(filepath)
|
|
elif ext in CONFIG_EXTS:
|
|
self.files['config'].append(filepath)
|
|
self.stats['config_files'] += 1
|
|
elif ext in DOC_EXTS:
|
|
self.files['docs'].append(filepath)
|
|
self.stats['docs_files'] += 1
|
|
|
|
# Check file size
|
|
try:
|
|
size = filepath.stat().st_size
|
|
self.stats['total_size'] += size
|
|
|
|
if size == 0:
|
|
self.issues['low'].append(f"Empty file: {filepath.relative_to(self.root)}")
|
|
elif size > 10 * 1024 * 1024: # >10MB
|
|
self.issues['medium'].append(f"Large file ({size//1024//1024}MB): {filepath.relative_to(self.root)}")
|
|
except Exception as e:
|
|
self.issues['medium'].append(f"Cannot read file: {filepath.relative_to(self.root)} ({e})")
|
|
|
|
def check_code_quality(self, filepath):
|
|
"""Check Python/JS code quality"""
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
lines = content.split('\n')
|
|
|
|
# Check for TODOs
|
|
todos = [i+1 for i, line in enumerate(lines) if 'TODO' in line or 'FIXME' in line]
|
|
if todos:
|
|
self.issues['low'].append(f"TODO/FIXME found in {filepath.relative_to(self.root)} (lines: {todos[:3]})")
|
|
|
|
# Check for long functions (basic heuristic)
|
|
if filepath.suffix == '.py':
|
|
self.check_python_quality(filepath, lines)
|
|
|
|
# Check for potential secrets (skip test files)
|
|
if '.test.' not in str(filepath) and 'test/' not in str(filepath):
|
|
secret_patterns = [
|
|
r'api[_-]?key\s*=\s*["\'][a-zA-Z0-9]{20,}["\']', # Real API keys
|
|
r'password\s*=\s*["\'][^"\']{8,}["\']', # Real passwords
|
|
r'token\s*=\s*["\'][a-zA-Z0-9]{32,}["\']', # Real tokens
|
|
]
|
|
for pattern in secret_patterns:
|
|
if re.search(pattern, content):
|
|
self.issues['high'].append(f"Potential hardcoded secret in {filepath.relative_to(self.root)}")
|
|
break
|
|
|
|
except Exception as e:
|
|
pass # Ignore read errors
|
|
|
|
def check_python_quality(self, filepath, lines):
|
|
"""Python-specific checks"""
|
|
# Check syntax (skip if file is too large or binary)
|
|
if len(lines) > 10000:
|
|
return
|
|
|
|
try:
|
|
content = '\n'.join(lines)
|
|
compile(content, str(filepath), 'exec')
|
|
except SyntaxError as e:
|
|
self.issues['high'].append(f"Python syntax error in {filepath.relative_to(self.root)}: line {e.lineno}")
|
|
except Exception:
|
|
pass # Ignore other errors (encoding, etc.)
|
|
|
|
# Check for long functions (>100 lines)
|
|
in_function = False
|
|
func_start = 0
|
|
for i, line in enumerate(lines):
|
|
if line.strip().startswith('def '):
|
|
if in_function and i - func_start > 100:
|
|
self.issues['medium'].append(f"Long function ({i - func_start} lines) in {filepath.relative_to(self.root)} near line {func_start}")
|
|
in_function = True
|
|
func_start = i
|
|
|
|
def check_dependencies(self):
|
|
"""Check for missing dependencies"""
|
|
print("📦 Checking dependencies...")
|
|
|
|
# Check requirements.txt
|
|
req_file = self.root / 'requirements.txt'
|
|
if req_file.exists():
|
|
self.stats['has_requirements'] = True
|
|
|
|
# Check package.json
|
|
pkg_file = self.root / 'package.json'
|
|
if pkg_file.exists():
|
|
self.stats['has_package_json'] = True
|
|
|
|
def generate_report(self, detailed=False):
|
|
"""Generate markdown report"""
|
|
report = []
|
|
|
|
report.append("# 📊 Workspace Analysis Report\n")
|
|
report.append(f"**Workspace:** `{self.root}`\n")
|
|
report.append(f"**Scanned:** {self.stats['total_files']} files ({self.stats['total_size'] // 1024 // 1024}MB)\n")
|
|
|
|
# Summary
|
|
report.append("\n## 📁 File Summary\n")
|
|
report.append(f"- **Code files:** {self.stats.get('code_files', 0)}")
|
|
report.append(f"- **Config files:** {self.stats.get('config_files', 0)}")
|
|
report.append(f"- **Docs:** {self.stats.get('docs_files', 0)}")
|
|
|
|
# Issues
|
|
report.append("\n## 🚨 Issues Found\n")
|
|
|
|
if self.issues['high']:
|
|
report.append(f"\n### 🔴 High Priority ({len(self.issues['high'])})\n")
|
|
for issue in self.issues['high'][:10]:
|
|
report.append(f"- {issue}")
|
|
if len(self.issues['high']) > 10:
|
|
report.append(f"- _(+{len(self.issues['high']) - 10} more)_")
|
|
|
|
if self.issues['medium']:
|
|
report.append(f"\n### 🟠 Medium Priority ({len(self.issues['medium'])})\n")
|
|
for issue in self.issues['medium'][:10]:
|
|
report.append(f"- {issue}")
|
|
if len(self.issues['medium']) > 10:
|
|
report.append(f"- _(+{len(self.issues['medium']) - 10} more)_")
|
|
|
|
if self.issues['low']:
|
|
report.append(f"\n### 🟡 Low Priority ({len(self.issues['low'])})\n")
|
|
for issue in self.issues['low'][:5]:
|
|
report.append(f"- {issue}")
|
|
if len(self.issues['low']) > 5:
|
|
report.append(f"- _(+{len(self.issues['low']) - 5} more)_")
|
|
|
|
if not any(self.issues.values()):
|
|
report.append("✅ No issues found!")
|
|
|
|
# Recommendations
|
|
report.append("\n## 💡 Recommendations\n")
|
|
|
|
if not self.stats.get('has_requirements'):
|
|
report.append("- Consider adding `requirements.txt` for Python dependencies")
|
|
|
|
if self.issues['high']:
|
|
report.append("- **Address high-priority issues immediately** (security/syntax errors)")
|
|
|
|
if self.stats.get('code_files', 0) > 0:
|
|
report.append("- Run `pylint` or `flake8` for detailed Python linting")
|
|
|
|
return '\n'.join(report)
|
|
|
|
def to_json(self):
|
|
"""Export as JSON"""
|
|
return json.dumps({
|
|
'stats': dict(self.stats),
|
|
'issues': {
|
|
'high': self.issues['high'],
|
|
'medium': self.issues['medium'],
|
|
'low': self.issues['low']
|
|
},
|
|
'file_counts': {
|
|
'code': len(self.files['code']),
|
|
'config': len(self.files['config']),
|
|
'docs': len(self.files['docs'])
|
|
}
|
|
}, indent=2)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Analyze workspace structure and code quality')
|
|
parser.add_argument('path', nargs='?', default=WORKSPACE, help='Path to analyze')
|
|
parser.add_argument('--detailed', action='store_true', help='Show detailed report')
|
|
parser.add_argument('--json', action='store_true', help='Output JSON')
|
|
|
|
args = parser.parse_args()
|
|
|
|
analyzer = WorkspaceAnalyzer(args.path)
|
|
analyzer.scan_directory()
|
|
analyzer.check_dependencies()
|
|
|
|
if args.json:
|
|
print(analyzer.to_json())
|
|
else:
|
|
report = analyzer.generate_report(detailed=args.detailed)
|
|
print("\n" + report)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|