#!/usr/bin/env python3 """ Design Pattern Recognition Module Detects common design patterns in codebases across multiple languages. Supported Patterns: - Creational: Singleton, Factory, Builder, Prototype - Structural: Adapter, Decorator, Facade, Proxy - Behavioral: Observer, Strategy, Command, Template Method, Chain of Responsibility Detection Levels: - Surface: Naming conventions (e.g., "Factory", "Singleton") - Deep: Structural analysis (class relationships, method signatures) - Full: Behavioral analysis (method interactions, state management) Credits: - Design pattern definitions: Gang of Four (GoF) Design Patterns - Detection heuristics: Inspired by academic research on pattern mining """ import argparse import json import logging import sys from dataclasses import dataclass, field from pathlib import Path logger = logging.getLogger(__name__) @dataclass class PatternInstance: """Single detected pattern instance""" pattern_type: str # e.g., 'Singleton', 'Factory' category: str # 'Creational', 'Structural', 'Behavioral' confidence: float # 0.0-1.0 location: str # File path class_name: str | None = None method_name: str | None = None line_number: int | None = None evidence: list[str] = field(default_factory=list) # Evidence for detection related_classes: list[str] = field(default_factory=list) # Related pattern classes ai_analysis: dict | None = None # AI-generated analysis (C3.6) def to_dict(self) -> dict: """Export to dictionary""" result = { "pattern_type": self.pattern_type, "category": self.category, "confidence": self.confidence, "location": self.location, "class_name": self.class_name, "method_name": self.method_name, "line_number": self.line_number, "evidence": self.evidence, "related_classes": self.related_classes, } if self.ai_analysis: result["ai_analysis"] = self.ai_analysis return result @dataclass class PatternReport: """Complete pattern detection report""" file_path: str language: str patterns: list[PatternInstance] total_classes: int total_functions: int analysis_depth: str # 'surface', 'deep', 'full' def to_dict(self) -> dict: """Export to dictionary""" return { "file_path": self.file_path, "language": self.language, "patterns": [p.to_dict() for p in self.patterns], "total_classes": self.total_classes, "total_functions": self.total_functions, "analysis_depth": self.analysis_depth, "pattern_summary": self.get_summary(), } def get_summary(self) -> dict[str, int]: """Get pattern count summary""" summary = {} for pattern in self.patterns: summary[pattern.pattern_type] = summary.get(pattern.pattern_type, 0) + 1 return summary class BasePatternDetector: """Base class for all pattern detectors""" def __init__(self, depth: str = "deep"): """ Initialize detector. Args: depth: Detection depth ('surface', 'deep', 'full') """ self.depth = depth self.pattern_type = "BasePattern" self.category = "Unknown" def detect_surface(self, _class_sig, _all_classes: list) -> PatternInstance | None: """ Surface-level detection using naming conventions. Args: class_sig: Class signature to analyze all_classes: All classes in the file for context Returns: PatternInstance if pattern detected, None otherwise """ # Default: no surface detection return None def detect_deep(self, _class_sig, _all_classes: list) -> PatternInstance | None: """ Deep detection using structural analysis. Args: class_sig: Class signature to analyze all_classes: All classes in the file for context Returns: PatternInstance if pattern detected, None otherwise """ # Default: no deep detection return None def detect_full( self, _class_sig, _all_classes: list, _file_content: str ) -> PatternInstance | None: """ Full detection using behavioral analysis. Args: class_sig: Class signature to analyze all_classes: All classes in the file for context file_content: Full file content for advanced analysis Returns: PatternInstance if pattern detected, None otherwise """ # Default: no full detection return None def detect( self, class_sig, all_classes: list, file_content: str | None = None ) -> PatternInstance | None: """ Detect pattern based on configured depth. Args: class_sig: Class signature to analyze all_classes: All classes in the file for context file_content: Full file content (needed for 'full' depth) Returns: PatternInstance if pattern detected, None otherwise """ if self.depth == "surface": return self.detect_surface(class_sig, all_classes) elif self.depth == "deep": # Try deep first, fallback to surface result = self.detect_deep(class_sig, all_classes) if result: return result return self.detect_surface(class_sig, all_classes) elif self.depth == "full": # Try full, fallback to deep, then surface if file_content: result = self.detect_full(class_sig, all_classes, file_content) if result: return result result = self.detect_deep(class_sig, all_classes) if result: return result return self.detect_surface(class_sig, all_classes) else: raise ValueError(f"Invalid depth: {self.depth}") class PatternRecognizer: """ Main pattern recognition orchestrator. Coordinates multiple pattern detectors to analyze code. """ def __init__(self, depth: str = "deep", enhance_with_ai: bool = True): """ Initialize pattern recognizer. Args: depth: Detection depth ('surface', 'deep', 'full') enhance_with_ai: Enable AI enhancement of detected patterns (default: True, C3.6) """ self.depth = depth self.enhance_with_ai = enhance_with_ai self.detectors: list[BasePatternDetector] = [] self._register_detectors() # Initialize AI enhancer if enabled (C3.6) self.ai_enhancer = None if self.enhance_with_ai: try: from skill_seekers.cli.ai_enhancer import PatternEnhancer self.ai_enhancer = PatternEnhancer() except Exception as e: logger.warning(f"⚠️ Failed to initialize AI enhancer: {e}") self.enhance_with_ai = False def _register_detectors(self): """Register all available pattern detectors""" # Creational patterns (3) self.detectors.append(SingletonDetector(self.depth)) self.detectors.append(FactoryDetector(self.depth)) self.detectors.append(BuilderDetector(self.depth)) # Structural patterns (2) self.detectors.append(DecoratorDetector(self.depth)) self.detectors.append(AdapterDetector(self.depth)) # Behavioral patterns (5) self.detectors.append(ObserverDetector(self.depth)) self.detectors.append(StrategyDetector(self.depth)) self.detectors.append(CommandDetector(self.depth)) self.detectors.append(TemplateMethodDetector(self.depth)) self.detectors.append(ChainOfResponsibilityDetector(self.depth)) def analyze_file(self, file_path: str, content: str, language: str) -> PatternReport: """ Analyze a single file for design patterns. Args: file_path: Path to source file content: File content language: Programming language Returns: PatternReport with detected patterns """ # Step 1: Analyze code structure using CodeAnalyzer from skill_seekers.cli.code_analyzer import CodeAnalyzer analyzer = CodeAnalyzer(depth="deep") analysis = analyzer.analyze_file(file_path, content, language) if not analysis: return PatternReport( file_path=file_path, language=language, patterns=[], total_classes=0, total_functions=0, analysis_depth=self.depth, ) classes = analysis.get("classes", []) functions = analysis.get("functions", []) # Convert to class signature objects class_sigs = self._convert_to_signatures(classes) # Step 2: Run pattern detection detected_patterns = [] for class_sig in class_sigs: for detector in self.detectors: pattern = detector.detect( class_sig=class_sig, all_classes=class_sigs, file_content=content if self.depth == "full" else None, ) if pattern: # Add file path to pattern pattern.location = file_path # Apply language-specific adaptations pattern = LanguageAdapter.adapt_for_language(pattern, language) detected_patterns.append(pattern) # Step 3: Enhance patterns with AI analysis (C3.6) if self.enhance_with_ai and self.ai_enhancer and detected_patterns: # Convert patterns to dict format for AI processing pattern_dicts = [p.to_dict() for p in detected_patterns] enhanced_dicts = self.ai_enhancer.enhance_patterns(pattern_dicts) # Update patterns with AI analysis for i, pattern in enumerate(detected_patterns): if i < len(enhanced_dicts) and "ai_analysis" in enhanced_dicts[i]: pattern.ai_analysis = enhanced_dicts[i]["ai_analysis"] # Apply confidence boost if provided if "confidence" in enhanced_dicts[i]: pattern.confidence = enhanced_dicts[i]["confidence"] return PatternReport( file_path=file_path, language=language, patterns=detected_patterns, total_classes=len(classes), total_functions=len(functions), analysis_depth=self.depth, ) def _convert_to_signatures(self, classes: list[dict]): """ Convert dict-based class analysis to signature objects. Note: Returns simple namespace objects that mimic ClassSignature structure but work with dict-based input from CodeAnalyzer. """ from types import SimpleNamespace signatures = [] for cls in classes: # Convert methods methods = [] for method in cls.get("methods", []): # Convert parameters params = [] for param in method.get("parameters", []): param_obj = SimpleNamespace( name=param.get("name", ""), type_hint=param.get("type_hint"), default=param.get("default"), ) params.append(param_obj) method_obj = SimpleNamespace( name=method.get("name", ""), parameters=params, return_type=method.get("return_type"), docstring=method.get("docstring"), line_number=method.get("line_number"), is_async=method.get("is_async", False), is_method=True, decorators=method.get("decorators", []), ) methods.append(method_obj) class_obj = SimpleNamespace( name=cls.get("name", ""), base_classes=cls.get("base_classes", []), methods=methods, docstring=cls.get("docstring"), line_number=cls.get("line_number"), ) signatures.append(class_obj) return signatures class SingletonDetector(BasePatternDetector): """ Detect Singleton pattern. Singleton ensures a class has only one instance and provides global access. Detection Heuristics: - Surface: Class name contains 'Singleton' - Deep: Private constructor + static instance method - Full: Instance caching + thread safety checks Examples: - Python: __new__ override with instance caching - JavaScript: Module pattern or class with getInstance() - Java: Private constructor + synchronized getInstance() """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Singleton" self.category = "Creational" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check if class name suggests Singleton""" if "singleton" in class_sig.name.lower(): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.6, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=['Class name contains "Singleton"'], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Check structural characteristics of Singleton""" evidence = [] confidence = 0.0 # Check for instance method (getInstance, instance, get_instance, etc.) instance_methods = [ "getInstance", "instance", "get_instance", "Instance", "GetInstance", "INSTANCE", ] has_instance_method = False for method in class_sig.methods: if method.name in instance_methods: evidence.append(f"Has instance method: {method.name}") confidence += 0.4 has_instance_method = True break # Check for private/protected constructor-like methods has_init_control = False for method in class_sig.methods: # Python: __init__ or __new__ # Java/C#: private constructor (detected by naming) if method.name in ["__new__", "__init__", "constructor"]: # Check if it has logic (not just pass) if method.docstring or len(method.parameters) > 1: evidence.append(f"Controlled initialization: {method.name}") confidence += 0.3 has_init_control = True break # Check for class-level instance storage # This would require checking class attributes (future enhancement) if has_instance_method or has_init_control: if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface detection return self.detect_surface(class_sig, all_classes) def detect_full( self, class_sig, all_classes: list, file_content: str ) -> PatternInstance | None: """ Full behavioral analysis for Singleton. Checks: - Instance caching in method body - Thread safety (locks, synchronized) - Lazy vs eager initialization """ # Start with deep detection result = self.detect_deep(class_sig, all_classes) if not result: return None evidence = result.evidence.copy() confidence = result.confidence # Check for instance caching patterns in code caching_patterns = [ "_instance", "__instance", "instance", "if not", "if self._instance is None", "synchronized", "Lock()", "threading", ] for pattern in caching_patterns: if pattern in file_content: if pattern not in " ".join(evidence): evidence.append(f"Instance caching detected: {pattern}") confidence += 0.1 # Cap confidence at 0.95 (never 100% certain without runtime analysis) result.confidence = min(confidence, 0.95) result.evidence = evidence return result class FactoryDetector(BasePatternDetector): """ Detect Factory pattern (Factory Method and Abstract Factory). Factory defines an interface for creating objects, letting subclasses decide which class to instantiate. Detection Heuristics: - Surface: Class/method name contains 'Factory', 'create', 'make' - Deep: Method returns different object types based on parameters - Full: Polymorphic object creation with inheritance hierarchy Examples: - createProduct(type) -> Product - ProductFactory with createProductA(), createProductB() """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Factory" self.category = "Creational" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming conventions for Factory""" # Check class name if "factory" in class_sig.name.lower(): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.7, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=['Class name contains "Factory"'], ) # Check for factory methods factory_method_names = ["create", "make", "build", "new", "get"] for method in class_sig.methods: method_lower = method.name.lower() if any(name in method_lower for name in factory_method_names): # Check if method returns something (has return type or is not void) if method.return_type or "create" in method_lower: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.6, location="", class_name=class_sig.name, method_name=method.name, line_number=method.line_number, evidence=[f"Factory method detected: {method.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Factory""" evidence = [] confidence = 0.0 factory_methods = [] # Look for methods that create objects creation_keywords = ["create", "make", "build", "new", "construct", "get"] for method in class_sig.methods: method_lower = method.name.lower() # Check if method name suggests object creation if any(keyword in method_lower for keyword in creation_keywords): factory_methods.append(method.name) confidence += 0.3 # Check if it takes parameters (suggests different object types) if len(method.parameters) > 1: # >1 because 'self' counts evidence.append(f"Parameterized factory method: {method.name}") confidence += 0.2 else: evidence.append(f"Factory method: {method.name}") # Check if multiple factory methods exist (Abstract Factory pattern) if len(factory_methods) >= 2: evidence.append(f"Multiple factory methods: {', '.join(factory_methods[:3])}") confidence += 0.2 # Check for inheritance (factory hierarchy) if class_sig.base_classes: evidence.append(f"Inherits from: {', '.join(class_sig.base_classes)}") confidence += 0.1 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, related_classes=class_sig.base_classes, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class ObserverDetector(BasePatternDetector): """ Detect Observer pattern (Pub/Sub). Observer defines one-to-many dependency where multiple objects observe and react to state changes. Detection Heuristics: - Surface: Class/method names with 'Observer', 'Listener', 'Subscribe' - Deep: attach/detach + notify methods - Full: Collection of observers + iteration pattern Examples: - addObserver(), removeObserver(), notifyObservers() - addEventListener(), removeEventListener(), emit() - subscribe(), unsubscribe(), publish() """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Observer" self.category = "Behavioral" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Observer pattern""" observer_keywords = ["observer", "listener", "subscriber", "watcher"] # Check class name class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in observer_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.6, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests Observer: {class_sig.name}"], ) # Check method names observer_methods = [ "subscribe", "unsubscribe", "publish", "addobserver", "removeobserver", "notify", "addeventlistener", "removeeventlistener", "emit", "attach", "detach", "update", ] for method in class_sig.methods: method_lower = method.name.lower().replace("_", "") if any(obs_method in method_lower for obs_method in observer_methods): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.65, location="", class_name=class_sig.name, method_name=method.name, line_number=method.line_number, evidence=[f"Observer method detected: {method.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Observer""" evidence = [] confidence = 0.0 # Look for characteristic method triplet: attach/detach/notify has_attach = False has_detach = False has_notify = False attach_names = ["attach", "add", "subscribe", "register", "addeventlistener"] detach_names = ["detach", "remove", "unsubscribe", "unregister", "removeeventlistener"] notify_names = ["notify", "update", "emit", "publish", "fire", "trigger"] for method in class_sig.methods: method_lower = method.name.lower().replace("_", "") if any(name in method_lower for name in attach_names): has_attach = True evidence.append(f"Attach method: {method.name}") confidence += 0.3 if any(name in method_lower for name in detach_names): has_detach = True evidence.append(f"Detach method: {method.name}") confidence += 0.3 if any(name in method_lower for name in notify_names): has_notify = True evidence.append(f"Notify method: {method.name}") confidence += 0.3 # Strong signal if has all three if has_attach and has_detach and has_notify: confidence = min(confidence, 0.95) if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.95), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class StrategyDetector(BasePatternDetector): """ Detect Strategy pattern. Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. Detection Heuristics: - Surface: Class/method names with 'Strategy', 'Policy', 'Algorithm' - Deep: Interface with single key method + multiple implementations - Full: Composition with interchangeable strategy objects Examples: - SortStrategy with sort() method - PaymentStrategy with pay() method - CompressionStrategy with compress() method """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Strategy" self.category = "Behavioral" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Strategy""" strategy_keywords = ["strategy", "policy", "algorithm"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in strategy_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.7, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests Strategy: {class_sig.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Strategy""" evidence = [] confidence = 0.0 # Strategy pattern often involves: # 1. Base class/interface with key method # 2. Multiple subclasses implementing same interface # Check if this class is a concrete strategy if class_sig.base_classes: base_class = class_sig.base_classes[0] if class_sig.base_classes else None # Look for siblings (other strategies with same base) siblings = [ cls.name for cls in all_classes if cls.base_classes and base_class in cls.base_classes and cls.name != class_sig.name ] if siblings: evidence.append(f"Part of strategy family with: {', '.join(siblings[:3])}") confidence += 0.5 if base_class and ("strategy" in base_class.lower() or "policy" in base_class.lower()): evidence.append(f"Inherits from strategy base: {base_class}") confidence += 0.3 # Check if this is a strategy base class # (has subclasses in same file) subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes] if len(subclasses) >= 2: evidence.append(f"Strategy base with implementations: {', '.join(subclasses[:3])}") confidence += 0.6 # Check for single dominant method (strategy interface) if len(class_sig.methods) == 1 or len(class_sig.methods) == 2: # Single method or method + __init__ main_method = [m for m in class_sig.methods if m.name not in ["__init__", "__new__"]] if main_method: evidence.append(f"Strategy interface method: {main_method[0].name}") confidence += 0.2 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, related_classes=class_sig.base_classes + subclasses, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class DecoratorDetector(BasePatternDetector): """ Detect Decorator pattern. Decorator attaches additional responsibilities to an object dynamically, providing flexible alternative to subclassing. Detection Heuristics: - Surface: Class name contains 'Decorator', 'Wrapper' - Deep: Wraps same interface, delegates to wrapped object - Full: Composition + delegation + interface matching Examples: - LoggingDecorator wraps Service - CachingDecorator wraps DataFetcher - Python @decorator syntax """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Decorator" self.category = "Structural" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Decorator""" decorator_keywords = ["decorator", "wrapper", "proxy"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in decorator_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.65, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests Decorator: {class_sig.name}"], ) # Check for Python decorator syntax for method in class_sig.methods: if method.decorators: # Has decorators - might be using decorator pattern # But this is too common, so low confidence return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.3, location="", class_name=class_sig.name, method_name=method.name, line_number=method.line_number, evidence=[f"Method uses decorators: {method.decorators}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Decorator""" evidence = [] confidence = 0.0 # Decorator pattern characteristics: # 1. Has same base class as wrapped object # 2. Takes wrapped object in constructor # 3. Delegates calls to wrapped object # Check if shares base class with other classes if class_sig.base_classes: base_class = class_sig.base_classes[0] # Find other classes with same base siblings = [ cls.name for cls in all_classes if cls.base_classes and base_class in cls.base_classes and cls.name != class_sig.name ] if siblings: evidence.append(f"Shares interface with: {', '.join(siblings[:2])}") confidence += 0.3 # Check __init__ for composition (takes object parameter) init_method = next((m for m in class_sig.methods if m.name == "__init__"), None) if init_method: # Check if takes object parameter (not just self) if len(init_method.parameters) > 1: # More than just 'self' param_names = [p.name for p in init_method.parameters if p.name != "self"] if any( name in ["wrapped", "component", "inner", "obj", "target"] for name in param_names ): evidence.append(f"Takes wrapped object in constructor: {param_names}") confidence += 0.4 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.85), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, related_classes=class_sig.base_classes, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class BuilderDetector(BasePatternDetector): """ Detect Builder pattern. Builder separates construction of complex object from its representation, allowing same construction process to create different representations. Detection Heuristics: - Surface: Class name contains 'Builder' - Deep: Fluent interface (methods return self), build()/create() terminal method - Full: Multiple configuration methods + final build step Examples: - QueryBuilder with where(), orderBy(), build() - RequestBuilder with setHeader(), setBody(), execute() - StringBuilder pattern """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Builder" self.category = "Creational" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Builder""" if "builder" in class_sig.name.lower(): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.7, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f'Class name contains "Builder": {class_sig.name}'], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Builder""" evidence = [] confidence = 0.0 # Builder characteristics: # 1. Multiple setter/configuration methods # 2. Terminal build()/create()/execute() method # 3. Fluent interface (methods return self/this) # Check for build/create terminal method terminal_methods = ["build", "create", "execute", "construct", "make"] has_terminal = any( m.name.lower() in terminal_methods or m.name.lower().startswith("build") for m in class_sig.methods ) if has_terminal: evidence.append("Has terminal build/create method") confidence += 0.4 # Check for setter methods (with_, set_, add_) setter_prefixes = ["with", "set", "add", "configure"] setter_count = sum( 1 for m in class_sig.methods if any(m.name.lower().startswith(prefix) for prefix in setter_prefixes) ) if setter_count >= 3: evidence.append(f"Has {setter_count} configuration methods") confidence += 0.4 elif setter_count >= 1: confidence += 0.2 # Check method count (builders typically have many methods) if len(class_sig.methods) >= 5: confidence += 0.1 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) def detect_full( self, class_sig, all_classes: list, file_content: str ) -> PatternInstance | None: """Full behavioral analysis for Builder""" # Start with deep detection pattern = self.detect_deep(class_sig, all_classes) if not pattern: return None evidence = list(pattern.evidence) confidence = pattern.confidence # Look for fluent interface pattern (return self/this) class_content = file_content.lower() fluent_indicators = ["return self", "return this"] if any(indicator in class_content for indicator in fluent_indicators): evidence.append("Uses fluent interface (return self)") confidence += 0.1 # Check for complex object construction (multiple fields) if "self." in class_content and class_content.count("self.") >= 5: evidence.append("Builds complex object with multiple fields") confidence += 0.05 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.95), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to deep return self.detect_deep(class_sig, all_classes) class AdapterDetector(BasePatternDetector): """ Detect Adapter pattern. Adapter converts interface of a class into another interface clients expect, allowing incompatible interfaces to work together. Detection Heuristics: - Surface: Class name contains 'Adapter', 'Wrapper' - Deep: Wraps external/incompatible class, translates method calls - Full: Composition + delegation with interface translation Examples: - DatabaseAdapter wraps external DB library - ApiAdapter translates REST to internal interface - FileSystemAdapter wraps OS file operations """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Adapter" self.category = "Structural" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Adapter""" adapter_keywords = ["adapter", "wrapper", "bridge"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in adapter_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.7, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests Adapter: {class_sig.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Adapter""" evidence = [] confidence = 0.0 # Adapter characteristics: # 1. Takes adaptee in constructor # 2. Implements target interface # 3. Delegates to adaptee with translation # Check __init__ for composition (takes adaptee) init_method = next((m for m in class_sig.methods if m.name == "__init__"), None) if init_method: if len(init_method.parameters) > 1: # More than just 'self' param_names = [p.name for p in init_method.parameters if p.name != "self"] adaptee_names = ["adaptee", "wrapped", "client", "service", "api", "source"] if any(name in param_names for name in adaptee_names): evidence.append(f"Takes adaptee in constructor: {param_names}") confidence += 0.4 # Check if implements interface (has base class) if class_sig.base_classes: evidence.append(f"Implements interface: {class_sig.base_classes[0]}") confidence += 0.3 # Check for delegation methods (methods that likely call adaptee) if len(class_sig.methods) >= 3: # Multiple interface methods evidence.append(f"Has {len(class_sig.methods)} interface methods") confidence += 0.2 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.85), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class CommandDetector(BasePatternDetector): """ Detect Command pattern. Command encapsulates a request as an object, allowing parameterization of clients with different requests, queuing, logging, and undo operations. Detection Heuristics: - Surface: Class name contains 'Command', 'Action', 'Task' - Deep: Has execute()/run() method, encapsulates action - Full: Receiver composition + undo support Examples: - SaveCommand with execute() method - UndoableCommand with undo() and redo() - TaskCommand in task queue """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "Command" self.category = "Behavioral" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Command""" command_keywords = ["command", "action", "task", "operation"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in command_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.65, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests Command: {class_sig.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Command""" evidence = [] confidence = 0.0 # Command characteristics: # 1. Has execute()/run()/call() method # 2. May have undo()/redo() methods # 3. Encapsulates receiver and parameters # Check for execute/run method execute_methods = ["execute", "run", "call", "do", "perform", "__call__"] has_execute = any(m.name.lower() in execute_methods for m in class_sig.methods) if has_execute: method_name = next( m.name for m in class_sig.methods if m.name.lower() in execute_methods ) evidence.append(f"Has execute method: {method_name}()") confidence += 0.5 # Check for undo/redo support undo_methods = ["undo", "rollback", "revert", "redo"] has_undo = any(m.name.lower() in undo_methods for m in class_sig.methods) if has_undo: evidence.append("Supports undo/redo operations") confidence += 0.3 # Check for receiver (takes object in __init__) init_method = next((m for m in class_sig.methods if m.name == "__init__"), None) if init_method and len(init_method.parameters) > 1: evidence.append("Encapsulates receiver/parameters") confidence += 0.2 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class TemplateMethodDetector(BasePatternDetector): """ Detect Template Method pattern. Template Method defines skeleton of algorithm in base class, letting subclasses override specific steps without changing structure. Detection Heuristics: - Surface: Abstract/Base class with template-like names - Deep: Abstract base with hook methods, concrete subclasses override - Full: Template method calls abstract/hook methods Examples: - AbstractProcessor with process() calling abstract steps - BaseParser with parse() template method - Framework base classes with lifecycle hooks """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "TemplateMethod" self.category = "Behavioral" def detect_surface(self, class_sig, all_classes: list) -> PatternInstance | None: """Check naming for Template Method""" template_keywords = ["abstract", "base", "template"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in template_keywords): # Check if has subclasses subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes] if subclasses: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.6, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Abstract base with subclasses: {', '.join(subclasses[:2])}"], related_classes=subclasses, ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Template Method""" evidence = [] confidence = 0.0 # Template Method characteristics: # 1. Has subclasses (is base class) # 2. Has methods that look like hooks (prepare, validate, cleanup, etc.) # 3. Has template method that orchestrates # Check for subclasses subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes] if len(subclasses) >= 1: evidence.append(f"Base class with {len(subclasses)} implementations") confidence += 0.4 # Check for hook-like method names hook_keywords = [ "prepare", "initialize", "validate", "process", "finalize", "setup", "teardown", "before", "after", "pre", "post", "hook", ] hook_methods = [ m.name for m in class_sig.methods if any(keyword in m.name.lower() for keyword in hook_keywords) ] if len(hook_methods) >= 2: evidence.append(f"Has hook methods: {', '.join(hook_methods[:3])}") confidence += 0.3 # Check for abstract methods (no implementation or pass/raise) abstract_methods = [ m.name for m in class_sig.methods if m.name.startswith("_") or "abstract" in m.name.lower() ] if abstract_methods: evidence.append(f"Has abstract methods: {', '.join(abstract_methods[:2])}") confidence += 0.2 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.85), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, related_classes=subclasses, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class ChainOfResponsibilityDetector(BasePatternDetector): """ Detect Chain of Responsibility pattern. Chain of Responsibility passes request along chain of handlers until one handles it, avoiding coupling sender to receiver. Detection Heuristics: - Surface: Class name contains 'Handler', 'Chain', 'Middleware' - Deep: Has next/successor reference, handle() method - Full: Chain traversal logic, request passing Examples: - LogHandler with next handler - AuthMiddleware chain - EventHandler chain """ def __init__(self, depth: str = "deep"): super().__init__(depth) self.pattern_type = "ChainOfResponsibility" self.category = "Behavioral" def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None: """Check naming for Chain of Responsibility""" chain_keywords = ["handler", "chain", "middleware", "filter", "processor"] class_lower = class_sig.name.lower() if any(keyword in class_lower for keyword in chain_keywords): return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=0.6, location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=[f"Class name suggests handler chain: {class_sig.name}"], ) return None def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None: """Structural analysis for Chain of Responsibility""" evidence = [] confidence = 0.0 # Chain of Responsibility characteristics: # 1. Has handle()/process() method # 2. Has next/successor reference # 3. May have set_next() method # Check for handle/process method handle_methods = ["handle", "process", "execute", "filter", "middleware"] has_handle = any( m.name.lower() in handle_methods or m.name.lower().startswith("handle") for m in class_sig.methods ) if has_handle: evidence.append("Has handle/process method") confidence += 0.4 # Check for next/successor methods or parameters init_method = next((m for m in class_sig.methods if m.name == "__init__"), None) has_next_ref = False if init_method: param_names = [p.name for p in init_method.parameters if p.name != "self"] next_names = ["next", "successor", "next_handler", "next_middleware"] if any(name in param_names for name in next_names): evidence.append("Takes next handler in chain") confidence += 0.3 has_next_ref = True # Check for set_next() method has_set_next = any( "next" in m.name.lower() and ("set" in m.name.lower() or "add" in m.name.lower()) for m in class_sig.methods ) if has_set_next: evidence.append("Has set_next() method") confidence += 0.3 has_next_ref = True # Check if part of handler family (shares base class) if class_sig.base_classes: base_class = class_sig.base_classes[0] siblings = [ cls.name for cls in all_classes if cls.base_classes and base_class in cls.base_classes and cls.name != class_sig.name ] if siblings and has_next_ref: evidence.append(f"Part of handler chain with: {', '.join(siblings[:2])}") confidence += 0.2 if confidence >= 0.5: return PatternInstance( pattern_type=self.pattern_type, category=self.category, confidence=min(confidence, 0.9), location="", class_name=class_sig.name, line_number=class_sig.line_number, evidence=evidence, ) # Fallback to surface return self.detect_surface(class_sig, all_classes) class LanguageAdapter: """ Language-specific pattern detection adaptations. Adjusts pattern confidence based on language idioms and conventions. Different languages have different ways of implementing patterns. """ @staticmethod def adapt_for_language(pattern: PatternInstance, language: str) -> PatternInstance: """ Adjust confidence based on language-specific idioms. Args: pattern: Detected pattern instance language: Programming language Returns: Adjusted pattern instance with language-specific confidence """ if not pattern: return pattern evidence_str = " ".join(pattern.evidence).lower() # Python-specific adaptations if language == "Python": # Decorator pattern: Python has native @ syntax if pattern.pattern_type == "Decorator": if "@" in " ".join(pattern.evidence): pattern.confidence = min(pattern.confidence + 0.1, 1.0) pattern.evidence.append("Python @decorator syntax detected") # Singleton: __new__ method is Python idiom elif pattern.pattern_type == "Singleton": if "__new__" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) # Strategy: Duck typing common in Python elif pattern.pattern_type == "Strategy": if "duck typing" in evidence_str or "protocol" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # JavaScript/TypeScript adaptations elif language in ["JavaScript", "TypeScript"]: # Singleton: Module pattern is common if pattern.pattern_type == "Singleton": if "module" in evidence_str or "export default" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) pattern.evidence.append("JavaScript module pattern") # Factory: Factory functions are idiomatic elif pattern.pattern_type == "Factory": if "create" in evidence_str or "make" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # Observer: Event emitters are built-in elif pattern.pattern_type == "Observer": if "eventemitter" in evidence_str or "event" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) pattern.evidence.append("EventEmitter pattern detected") # Java/C# adaptations (interface-heavy languages) elif language in ["Java", "C#"]: # All patterns: Interfaces are explicit if "interface" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # Factory: Abstract Factory common if pattern.pattern_type == "Factory": if "abstract" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) pattern.evidence.append("Abstract Factory pattern") # Template Method: Abstract classes common elif pattern.pattern_type == "TemplateMethod": if "abstract" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) # Go adaptations elif language == "Go": # Singleton: sync.Once is idiomatic if pattern.pattern_type == "Singleton": if "sync.once" in evidence_str or "once.do" in evidence_str: pattern.confidence = min(pattern.confidence + 0.15, 1.0) pattern.evidence.append("Go sync.Once idiom") # Strategy: Interfaces are implicit elif pattern.pattern_type == "Strategy": if "interface{}" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # Rust adaptations elif language == "Rust": # Singleton: Lazy static is common if pattern.pattern_type == "Singleton": if "lazy_static" in evidence_str or "oncecell" in evidence_str: pattern.confidence = min(pattern.confidence + 0.15, 1.0) pattern.evidence.append("Rust lazy_static/OnceCell") # Builder: Derive builder is idiomatic elif pattern.pattern_type == "Builder": if "derive" in evidence_str and "builder" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) # Adapter: Trait adapters are common elif pattern.pattern_type == "Adapter": if "trait" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) # C++ adaptations elif language == "C++": # Singleton: Meyer's Singleton is idiomatic if pattern.pattern_type == "Singleton": if "static" in evidence_str and "local" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) pattern.evidence.append("Meyer's Singleton (static local)") # Factory: Template-based factories elif pattern.pattern_type == "Factory": if "template" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # Ruby adaptations elif language == "Ruby": # Singleton: Ruby has Singleton module if pattern.pattern_type == "Singleton": if "include singleton" in evidence_str: pattern.confidence = min(pattern.confidence + 0.2, 1.0) pattern.evidence.append("Ruby Singleton module") # Builder: Method chaining is idiomatic elif pattern.pattern_type == "Builder": if "method chaining" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) # PHP adaptations elif language == "PHP": # Singleton: Private constructor is common if pattern.pattern_type == "Singleton": if "private" in evidence_str and "__construct" in evidence_str: pattern.confidence = min(pattern.confidence + 0.1, 1.0) # Factory: Static factory methods elif pattern.pattern_type == "Factory": if "static" in evidence_str: pattern.confidence = min(pattern.confidence + 0.05, 1.0) return pattern def main(): """ CLI entry point for pattern detection. Usage: skill-seekers-patterns --file src/database.py skill-seekers-patterns --directory src/ --output patterns/ skill-seekers-patterns --file app.py --depth full --json """ import sys parser = argparse.ArgumentParser( description="Detect design patterns in source code", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Analyze single file skill-seekers-patterns --file src/database.py # Analyze directory skill-seekers-patterns --directory src/ --output patterns/ # Full analysis with JSON output skill-seekers-patterns --file app.py --depth full --json # Multiple files skill-seekers-patterns --file src/db.py --file src/api.py Supported Languages: Python, JavaScript, TypeScript, C++, C, C#, Go, Rust, Java, Ruby, PHP """, ) parser.add_argument( "--file", action="append", help="Source file to analyze (can be specified multiple times)" ) parser.add_argument("--directory", help="Directory to analyze (analyzes all source files)") parser.add_argument( "--output", help="Output directory for results (default: current directory)" ) parser.add_argument( "--depth", choices=["surface", "deep", "full"], default="deep", help="Detection depth: surface (fast), deep (default), full (thorough)", ) parser.add_argument( "--json", action="store_true", help="Output JSON format instead of human-readable" ) parser.add_argument("--verbose", action="store_true", help="Enable verbose output") args = parser.parse_args() # Validate inputs if not args.file and not args.directory: parser.error("Must specify either --file or --directory") # Create recognizer recognizer = PatternRecognizer(depth=args.depth) # Collect files to analyze files_to_analyze = [] if args.file: for file_path in args.file: path = Path(file_path) if not path.exists(): print(f"Error: File not found: {file_path}", file=sys.stderr) return 1 files_to_analyze.append(path) if args.directory: from skill_seekers.cli.codebase_scraper import detect_language, walk_directory directory = Path(args.directory) if not directory.exists(): print(f"Error: Directory not found: {args.directory}", file=sys.stderr) return 1 # Walk directory for source files files_to_analyze.extend(walk_directory(directory)) if not files_to_analyze: print("No source files found to analyze", file=sys.stderr) return 1 # Analyze files all_reports = [] total_patterns = 0 for file_path in files_to_analyze: try: from skill_seekers.cli.codebase_scraper import detect_language content = file_path.read_text(encoding="utf-8", errors="ignore") language = detect_language(file_path) if language == "Unknown": if args.verbose: print(f"Skipping {file_path}: Unknown language") continue report = recognizer.analyze_file(str(file_path), content, language) if report.patterns: all_reports.append(report) total_patterns += len(report.patterns) if not args.json and args.verbose: print(f"\n{file_path}:") for pattern in report.patterns: print( f" [{pattern.pattern_type}] {pattern.class_name} (confidence: {pattern.confidence:.2f})" ) except Exception as e: if args.verbose: print(f"Error analyzing {file_path}: {e}", file=sys.stderr) continue # Output results if args.json: # JSON output output_data = { "total_files_analyzed": len(files_to_analyze), "files_with_patterns": len(all_reports), "total_patterns_detected": total_patterns, "reports": [report.to_dict() for report in all_reports], } if args.output: output_path = Path(args.output) / "detected_patterns.json" output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: json.dump(output_data, f, indent=2) print(f"Results saved to: {output_path}") else: print(json.dumps(output_data, indent=2)) else: # Human-readable output print(f"\n{'=' * 60}") print("PATTERN DETECTION RESULTS") print(f"{'=' * 60}") print(f"Files analyzed: {len(files_to_analyze)}") print(f"Files with patterns: {len(all_reports)}") print(f"Total patterns detected: {total_patterns}") print(f"{'=' * 60}\n") # Pattern summary by type pattern_counts = {} for report in all_reports: for pattern in report.patterns: pattern_counts[pattern.pattern_type] = ( pattern_counts.get(pattern.pattern_type, 0) + 1 ) if pattern_counts: print("Pattern Summary:") for pattern_type, count in sorted( pattern_counts.items(), key=lambda x: x[1], reverse=True ): print(f" {pattern_type}: {count}") print() # Detailed results if all_reports: print("Detected Patterns:\n") for report in all_reports: print(f"{report.file_path}:") for pattern in report.patterns: print(f" • {pattern.pattern_type} - {pattern.class_name}") print(f" Confidence: {pattern.confidence:.2f}") print(f" Category: {pattern.category}") if pattern.evidence: print(f" Evidence: {pattern.evidence[0]}") print() return 0 if __name__ == "__main__": sys.exit(main())