This commit is contained in:
Pablo Estevez
2026-01-17 17:29:21 +00:00
parent c89f059712
commit 5ed767ff9a
144 changed files with 14142 additions and 16488 deletions

View File

@@ -21,11 +21,9 @@ Credits:
"""
import logging
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Dict, Optional, Set
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
logger = logging.getLogger(__name__)
@@ -33,41 +31,43 @@ logger = logging.getLogger(__name__)
@dataclass
class ArchitecturalPattern:
"""Detected architectural pattern"""
pattern_name: str # e.g., "MVC", "MVVM", "Repository"
confidence: float # 0.0-1.0
evidence: List[str] # List of evidence supporting detection
components: Dict[str, List[str]] # Component type -> file paths
framework: Optional[str] = None # Detected framework (Django, Spring, etc.)
evidence: list[str] # List of evidence supporting detection
components: dict[str, list[str]] # Component type -> file paths
framework: str | None = None # Detected framework (Django, Spring, etc.)
description: str = "" # Human-readable description
@dataclass
class ArchitecturalReport:
"""Complete architectural analysis report"""
patterns: List[ArchitecturalPattern]
directory_structure: Dict[str, int] # Directory name -> file count
total_files_analyzed: int
frameworks_detected: List[str]
ai_analysis: Optional[Dict] = None # AI enhancement (C3.6 integration)
def to_dict(self) -> Dict:
patterns: list[ArchitecturalPattern]
directory_structure: dict[str, int] # Directory name -> file count
total_files_analyzed: int
frameworks_detected: list[str]
ai_analysis: dict | None = None # AI enhancement (C3.6 integration)
def to_dict(self) -> dict:
"""Export to dictionary"""
return {
'patterns': [
"patterns": [
{
'pattern_name': p.pattern_name,
'confidence': p.confidence,
'evidence': p.evidence,
'components': p.components,
'framework': p.framework,
'description': p.description
"pattern_name": p.pattern_name,
"confidence": p.confidence,
"evidence": p.evidence,
"components": p.components,
"framework": p.framework,
"description": p.description,
}
for p in self.patterns
],
'directory_structure': self.directory_structure,
'total_files_analyzed': self.total_files_analyzed,
'frameworks_detected': self.frameworks_detected,
'ai_analysis': self.ai_analysis
"directory_structure": self.directory_structure,
"total_files_analyzed": self.total_files_analyzed,
"frameworks_detected": self.frameworks_detected,
"ai_analysis": self.ai_analysis,
}
@@ -79,25 +79,25 @@ class ArchitecturalPatternDetector:
"""
# Common directory patterns for architectures
MVC_DIRS = {'models', 'views', 'controllers', 'model', 'view', 'controller'}
MVVM_DIRS = {'models', 'views', 'viewmodels', 'viewmodel'}
LAYERED_DIRS = {'presentation', 'business', 'data', 'dal', 'bll', 'ui'}
CLEAN_ARCH_DIRS = {'domain', 'application', 'infrastructure', 'presentation'}
REPO_DIRS = {'repositories', 'repository'}
SERVICE_DIRS = {'services', 'service'}
MVC_DIRS = {"models", "views", "controllers", "model", "view", "controller"}
MVVM_DIRS = {"models", "views", "viewmodels", "viewmodel"}
LAYERED_DIRS = {"presentation", "business", "data", "dal", "bll", "ui"}
CLEAN_ARCH_DIRS = {"domain", "application", "infrastructure", "presentation"}
REPO_DIRS = {"repositories", "repository"}
SERVICE_DIRS = {"services", "service"}
# Framework detection patterns
FRAMEWORK_MARKERS = {
'Django': ['django', 'manage.py', 'settings.py', 'urls.py'],
'Flask': ['flask', 'app.py', 'wsgi.py'],
'Spring': ['springframework', '@Controller', '@Service', '@Repository'],
'ASP.NET': ['Controllers', 'Models', 'Views', '.cshtml', 'Startup.cs'],
'Rails': ['app/models', 'app/views', 'app/controllers', 'config/routes.rb'],
'Angular': ['app.module.ts', '@Component', '@Injectable', 'angular.json'],
'React': ['package.json', 'react', 'components'],
'Vue.js': ['vue', '.vue', 'components'],
'Express': ['express', 'app.js', 'routes'],
'Laravel': ['artisan', 'app/Http/Controllers', 'app/Models']
"Django": ["django", "manage.py", "settings.py", "urls.py"],
"Flask": ["flask", "app.py", "wsgi.py"],
"Spring": ["springframework", "@Controller", "@Service", "@Repository"],
"ASP.NET": ["Controllers", "Models", "Views", ".cshtml", "Startup.cs"],
"Rails": ["app/models", "app/views", "app/controllers", "config/routes.rb"],
"Angular": ["app.module.ts", "@Component", "@Injectable", "angular.json"],
"React": ["package.json", "react", "components"],
"Vue.js": ["vue", ".vue", "components"],
"Express": ["express", "app.js", "routes"],
"Laravel": ["artisan", "app/Http/Controllers", "app/Models"],
}
def __init__(self, enhance_with_ai: bool = True):
@@ -113,12 +113,13 @@ class ArchitecturalPatternDetector:
if self.enhance_with_ai:
try:
from skill_seekers.cli.ai_enhancer import AIEnhancer
self.ai_enhancer = AIEnhancer()
except Exception as e:
logger.warning(f"⚠️ Failed to initialize AI enhancer: {e}")
self.enhance_with_ai = False
def analyze(self, directory: Path, files_analysis: List[Dict]) -> ArchitecturalReport:
def analyze(self, directory: Path, files_analysis: list[dict]) -> ArchitecturalReport:
"""
Analyze codebase for architectural patterns.
@@ -151,7 +152,7 @@ class ArchitecturalPatternDetector:
patterns=patterns,
directory_structure=dir_structure,
total_files_analyzed=len(files_analysis),
frameworks_detected=frameworks
frameworks_detected=frameworks,
)
# Enhance with AI if enabled (C3.6)
@@ -161,11 +162,11 @@ class ArchitecturalPatternDetector:
logger.info(f"✅ Detected {len(patterns)} architectural patterns")
return report
def _analyze_directory_structure(self, directory: Path) -> Dict[str, int]:
def _analyze_directory_structure(self, directory: Path) -> dict[str, int]:
"""Analyze directory structure and count files"""
structure = defaultdict(int)
for path in directory.rglob('*'):
for path in directory.rglob("*"):
if path.is_file():
# Get relative directory path
rel_dir = path.parent.relative_to(directory)
@@ -180,13 +181,13 @@ class ArchitecturalPatternDetector:
return dict(structure)
def _detect_frameworks(self, directory: Path, files: List[Dict]) -> List[str]:
def _detect_frameworks(self, directory: Path, files: list[dict]) -> list[str]:
"""Detect frameworks being used"""
detected = []
# Check file paths and content
all_paths = [str(f.get('file', '')) for f in files]
all_content = ' '.join(all_paths)
all_paths = [str(f.get("file", "")) for f in files]
all_content = " ".join(all_paths)
for framework, markers in self.FRAMEWORK_MARKERS.items():
matches = sum(1 for marker in markers if marker.lower() in all_content.lower())
@@ -196,7 +197,7 @@ class ArchitecturalPatternDetector:
return detected
def _detect_mvc(self, dirs: Dict[str, int], files: List[Dict], frameworks: List[str]) -> List[ArchitecturalPattern]:
def _detect_mvc(self, dirs: dict[str, int], files: list[dict], frameworks: list[str]) -> list[ArchitecturalPattern]:
"""Detect MVC pattern"""
patterns = []
@@ -213,58 +214,62 @@ class ArchitecturalPatternDetector:
# Find MVC files
for file in files:
file_path = str(file.get('file', '')).lower()
file_path = str(file.get("file", "")).lower()
if 'model' in file_path and ('models/' in file_path or '/model/' in file_path):
components['Models'].append(file.get('file', ''))
if len(components['Models']) == 1:
if "model" in file_path and ("models/" in file_path or "/model/" in file_path):
components["Models"].append(file.get("file", ""))
if len(components["Models"]) == 1:
evidence.append("Models directory with model classes")
if 'view' in file_path and ('views/' in file_path or '/view/' in file_path):
components['Views'].append(file.get('file', ''))
if len(components['Views']) == 1:
if "view" in file_path and ("views/" in file_path or "/view/" in file_path):
components["Views"].append(file.get("file", ""))
if len(components["Views"]) == 1:
evidence.append("Views directory with view files")
if 'controller' in file_path and ('controllers/' in file_path or '/controller/' in file_path):
components['Controllers'].append(file.get('file', ''))
if len(components['Controllers']) == 1:
if "controller" in file_path and ("controllers/" in file_path or "/controller/" in file_path):
components["Controllers"].append(file.get("file", ""))
if len(components["Controllers"]) == 1:
evidence.append("Controllers directory with controller classes")
# Calculate confidence
has_models = len(components['Models']) > 0
has_views = len(components['Views']) > 0
has_controllers = len(components['Controllers']) > 0
has_models = len(components["Models"]) > 0
has_views = len(components["Views"]) > 0
has_controllers = len(components["Controllers"]) > 0
if sum([has_models, has_views, has_controllers]) >= 2:
confidence = 0.6 + (sum([has_models, has_views, has_controllers]) * 0.15)
# Boost confidence if framework detected
framework = None
for fw in ['Django', 'Flask', 'Spring', 'ASP.NET', 'Rails', 'Laravel']:
for fw in ["Django", "Flask", "Spring", "ASP.NET", "Rails", "Laravel"]:
if fw in frameworks:
confidence = min(0.95, confidence + 0.1)
framework = fw
evidence.append(f"{fw} framework detected (uses MVC)")
break
patterns.append(ArchitecturalPattern(
pattern_name="MVC (Model-View-Controller)",
confidence=confidence,
evidence=evidence,
components=dict(components),
framework=framework,
description="Separates application into Models (data), Views (UI), and Controllers (logic)"
))
patterns.append(
ArchitecturalPattern(
pattern_name="MVC (Model-View-Controller)",
confidence=confidence,
evidence=evidence,
components=dict(components),
framework=framework,
description="Separates application into Models (data), Views (UI), and Controllers (logic)",
)
)
return patterns
def _detect_mvvm(self, dirs: Dict[str, int], files: List[Dict], frameworks: List[str]) -> List[ArchitecturalPattern]:
def _detect_mvvm(
self, dirs: dict[str, int], files: list[dict], frameworks: list[str]
) -> list[ArchitecturalPattern]:
"""Detect MVVM pattern"""
patterns = []
# Look for ViewModels directory or classes ending with ViewModel
has_viewmodel_dir = 'viewmodels' in dirs or 'viewmodel' in dirs
viewmodel_files = [f for f in files if 'viewmodel' in str(f.get('file', '')).lower()]
has_viewmodel_dir = "viewmodels" in dirs or "viewmodel" in dirs
viewmodel_files = [f for f in files if "viewmodel" in str(f.get("file", "")).lower()]
if not (has_viewmodel_dir or len(viewmodel_files) >= 2):
return patterns
@@ -274,63 +279,68 @@ class ArchitecturalPatternDetector:
# Find MVVM files
for file in files:
file_path = str(file.get('file', '')).lower()
classes = file.get('classes', [])
file_path = str(file.get("file", "")).lower()
classes = file.get("classes", [])
if 'model' in file_path and 'viewmodel' not in file_path:
components['Models'].append(file.get('file', ''))
if "model" in file_path and "viewmodel" not in file_path:
components["Models"].append(file.get("file", ""))
if 'view' in file_path:
components['Views'].append(file.get('file', ''))
if "view" in file_path:
components["Views"].append(file.get("file", ""))
if 'viewmodel' in file_path or any('viewmodel' in c.get('name', '').lower() for c in classes):
components['ViewModels'].append(file.get('file', ''))
if "viewmodel" in file_path or any("viewmodel" in c.get("name", "").lower() for c in classes):
components["ViewModels"].append(file.get("file", ""))
if len(components['ViewModels']) >= 2:
if len(components["ViewModels"]) >= 2:
evidence.append(f"ViewModels directory with {len(components['ViewModels'])} ViewModel classes")
if len(components['Views']) >= 2:
if len(components["Views"]) >= 2:
evidence.append(f"Views directory with {len(components['Views'])} view files")
if len(components['Models']) >= 1:
if len(components["Models"]) >= 1:
evidence.append(f"Models directory with {len(components['Models'])} model files")
# Calculate confidence
has_models = len(components['Models']) > 0
has_views = len(components['Views']) > 0
has_viewmodels = len(components['ViewModels']) >= 2
has_models = len(components["Models"]) > 0
has_views = len(components["Views"]) > 0
has_viewmodels = len(components["ViewModels"]) >= 2
if has_viewmodels and (has_models or has_views):
confidence = 0.7 if (has_models and has_views and has_viewmodels) else 0.6
framework = None
for fw in ['ASP.NET', 'Angular', 'Vue.js']:
for fw in ["ASP.NET", "Angular", "Vue.js"]:
if fw in frameworks:
confidence = min(0.95, confidence + 0.1)
framework = fw
evidence.append(f"{fw} framework detected (supports MVVM)")
break
patterns.append(ArchitecturalPattern(
pattern_name="MVVM (Model-View-ViewModel)",
confidence=confidence,
evidence=evidence,
components=dict(components),
framework=framework,
description="ViewModels provide data-binding between Views and Models"
))
patterns.append(
ArchitecturalPattern(
pattern_name="MVVM (Model-View-ViewModel)",
confidence=confidence,
evidence=evidence,
components=dict(components),
framework=framework,
description="ViewModels provide data-binding between Views and Models",
)
)
return patterns
def _detect_repository(self, dirs: Dict[str, int], files: List[Dict]) -> List[ArchitecturalPattern]:
def _detect_repository(self, dirs: dict[str, int], files: list[dict]) -> list[ArchitecturalPattern]:
"""Detect Repository pattern"""
patterns = []
# Look for repositories directory or classes ending with Repository
has_repo_dir = any(d in dirs for d in self.REPO_DIRS)
repo_files = [f for f in files
if 'repository' in str(f.get('file', '')).lower() or
any('repository' in c.get('name', '').lower() for c in f.get('classes', []))]
repo_files = [
f
for f in files
if "repository" in str(f.get("file", "")).lower()
or any("repository" in c.get("name", "").lower() for c in f.get("classes", []))
]
if not (has_repo_dir or len(repo_files) >= 2):
return patterns
@@ -339,30 +349,35 @@ class ArchitecturalPatternDetector:
components = defaultdict(list)
for file in repo_files:
components['Repositories'].append(file.get('file', ''))
components["Repositories"].append(file.get("file", ""))
if len(components['Repositories']) >= 2:
if len(components["Repositories"]) >= 2:
evidence.append(f"Repository pattern: {len(components['Repositories'])} repository classes")
evidence.append("Repositories abstract data access logic")
patterns.append(ArchitecturalPattern(
pattern_name="Repository Pattern",
confidence=0.75,
evidence=evidence,
components=dict(components),
description="Encapsulates data access logic in repository classes"
))
patterns.append(
ArchitecturalPattern(
pattern_name="Repository Pattern",
confidence=0.75,
evidence=evidence,
components=dict(components),
description="Encapsulates data access logic in repository classes",
)
)
return patterns
def _detect_service_layer(self, dirs: Dict[str, int], files: List[Dict]) -> List[ArchitecturalPattern]:
def _detect_service_layer(self, dirs: dict[str, int], files: list[dict]) -> list[ArchitecturalPattern]:
"""Detect Service Layer pattern"""
patterns = []
has_service_dir = any(d in dirs for d in self.SERVICE_DIRS)
service_files = [f for f in files
if 'service' in str(f.get('file', '')).lower() or
any('service' in c.get('name', '').lower() for c in f.get('classes', []))]
service_files = [
f
for f in files
if "service" in str(f.get("file", "")).lower()
or any("service" in c.get("name", "").lower() for c in f.get("classes", []))
]
if not (has_service_dir or len(service_files) >= 3):
return patterns
@@ -371,23 +386,25 @@ class ArchitecturalPatternDetector:
components = defaultdict(list)
for file in service_files:
components['Services'].append(file.get('file', ''))
components["Services"].append(file.get("file", ""))
if len(components['Services']) >= 3:
if len(components["Services"]) >= 3:
evidence.append(f"Service layer: {len(components['Services'])} service classes")
evidence.append("Services encapsulate business logic")
patterns.append(ArchitecturalPattern(
pattern_name="Service Layer Pattern",
confidence=0.75,
evidence=evidence,
components=dict(components),
description="Encapsulates business logic in service classes"
))
patterns.append(
ArchitecturalPattern(
pattern_name="Service Layer Pattern",
confidence=0.75,
evidence=evidence,
components=dict(components),
description="Encapsulates business logic in service classes",
)
)
return patterns
def _detect_layered_architecture(self, dirs: Dict[str, int], files: List[Dict]) -> List[ArchitecturalPattern]:
def _detect_layered_architecture(self, dirs: dict[str, int], files: list[dict]) -> list[ArchitecturalPattern]:
"""Detect Layered Architecture (3-tier, N-tier)"""
patterns = []
@@ -400,32 +417,34 @@ class ArchitecturalPatternDetector:
components = defaultdict(list)
layers_found = []
if 'presentation' in dirs or 'ui' in dirs:
if "presentation" in dirs or "ui" in dirs:
layers_found.append("Presentation Layer")
evidence.append("Presentation/UI layer detected")
if 'business' in dirs or 'bll' in dirs:
if "business" in dirs or "bll" in dirs:
layers_found.append("Business Logic Layer")
evidence.append("Business logic layer detected")
if 'data' in dirs or 'dal' in dirs:
if "data" in dirs or "dal" in dirs:
layers_found.append("Data Access Layer")
evidence.append("Data access layer detected")
if len(layers_found) >= 2:
confidence = 0.65 + (len(layers_found) * 0.1)
patterns.append(ArchitecturalPattern(
pattern_name=f"Layered Architecture ({len(layers_found)}-tier)",
confidence=min(confidence, 0.9),
evidence=evidence,
components={'Layers': layers_found},
description=f"Separates concerns into {len(layers_found)} distinct layers"
))
patterns.append(
ArchitecturalPattern(
pattern_name=f"Layered Architecture ({len(layers_found)}-tier)",
confidence=min(confidence, 0.9),
evidence=evidence,
components={"Layers": layers_found},
description=f"Separates concerns into {len(layers_found)} distinct layers",
)
)
return patterns
def _detect_clean_architecture(self, dirs: Dict[str, int], files: List[Dict]) -> List[ArchitecturalPattern]:
def _detect_clean_architecture(self, dirs: dict[str, int], files: list[dict]) -> list[ArchitecturalPattern]:
"""Detect Clean Architecture"""
patterns = []
@@ -437,50 +456,52 @@ class ArchitecturalPatternDetector:
evidence = []
components = defaultdict(list)
if 'domain' in dirs:
if "domain" in dirs:
evidence.append("Domain layer (core business logic)")
components['Domain'].append('domain/')
components["Domain"].append("domain/")
if 'application' in dirs:
if "application" in dirs:
evidence.append("Application layer (use cases)")
components['Application'].append('application/')
components["Application"].append("application/")
if 'infrastructure' in dirs:
if "infrastructure" in dirs:
evidence.append("Infrastructure layer (external dependencies)")
components['Infrastructure'].append('infrastructure/')
components["Infrastructure"].append("infrastructure/")
if 'presentation' in dirs:
if "presentation" in dirs:
evidence.append("Presentation layer (UI/API)")
components['Presentation'].append('presentation/')
components["Presentation"].append("presentation/")
if len(components) >= 3:
patterns.append(ArchitecturalPattern(
pattern_name="Clean Architecture",
confidence=0.85,
evidence=evidence,
components=dict(components),
description="Dependency inversion with domain at center, infrastructure at edges"
))
patterns.append(
ArchitecturalPattern(
pattern_name="Clean Architecture",
confidence=0.85,
evidence=evidence,
components=dict(components),
description="Dependency inversion with domain at center, infrastructure at edges",
)
)
return patterns
def _enhance_with_ai(self, report: ArchitecturalReport) -> Dict:
def _enhance_with_ai(self, report: ArchitecturalReport) -> dict:
"""Enhance architectural analysis with AI insights"""
if not self.ai_enhancer:
return {}
# Prepare summary for AI
summary = f"""Detected {len(report.patterns)} architectural patterns:
{chr(10).join(f'- {p.pattern_name} (confidence: {p.confidence:.2f})' for p in report.patterns)}
{chr(10).join(f"- {p.pattern_name} (confidence: {p.confidence:.2f})" for p in report.patterns)}
Frameworks: {', '.join(report.frameworks_detected) if report.frameworks_detected else 'None'}
Frameworks: {", ".join(report.frameworks_detected) if report.frameworks_detected else "None"}
Total files: {report.total_files_analyzed}
Provide brief architectural insights and recommendations."""
try:
response = self.ai_enhancer._call_claude(summary, max_tokens=500)
return {'insights': response} if response else {}
return {"insights": response} if response else {}
except Exception as e:
logger.warning(f"⚠️ AI enhancement failed: {e}")
return {}