Files
Clawd Bot ca9b510922 chore: align with upstream openclaw/openclaw and overlay local additions
- 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>
2026-03-03 07:40:46 +01:00

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()