skill-seekers 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skill_seekers/__init__.py +22 -0
- skill_seekers/cli/__init__.py +39 -0
- skill_seekers/cli/adaptors/__init__.py +120 -0
- skill_seekers/cli/adaptors/base.py +221 -0
- skill_seekers/cli/adaptors/claude.py +485 -0
- skill_seekers/cli/adaptors/gemini.py +453 -0
- skill_seekers/cli/adaptors/markdown.py +269 -0
- skill_seekers/cli/adaptors/openai.py +503 -0
- skill_seekers/cli/ai_enhancer.py +310 -0
- skill_seekers/cli/api_reference_builder.py +373 -0
- skill_seekers/cli/architectural_pattern_detector.py +525 -0
- skill_seekers/cli/code_analyzer.py +1462 -0
- skill_seekers/cli/codebase_scraper.py +1225 -0
- skill_seekers/cli/config_command.py +563 -0
- skill_seekers/cli/config_enhancer.py +431 -0
- skill_seekers/cli/config_extractor.py +871 -0
- skill_seekers/cli/config_manager.py +452 -0
- skill_seekers/cli/config_validator.py +394 -0
- skill_seekers/cli/conflict_detector.py +528 -0
- skill_seekers/cli/constants.py +72 -0
- skill_seekers/cli/dependency_analyzer.py +757 -0
- skill_seekers/cli/doc_scraper.py +2332 -0
- skill_seekers/cli/enhance_skill.py +488 -0
- skill_seekers/cli/enhance_skill_local.py +1096 -0
- skill_seekers/cli/enhance_status.py +194 -0
- skill_seekers/cli/estimate_pages.py +433 -0
- skill_seekers/cli/generate_router.py +1209 -0
- skill_seekers/cli/github_fetcher.py +534 -0
- skill_seekers/cli/github_scraper.py +1466 -0
- skill_seekers/cli/guide_enhancer.py +723 -0
- skill_seekers/cli/how_to_guide_builder.py +1267 -0
- skill_seekers/cli/install_agent.py +461 -0
- skill_seekers/cli/install_skill.py +178 -0
- skill_seekers/cli/language_detector.py +614 -0
- skill_seekers/cli/llms_txt_detector.py +60 -0
- skill_seekers/cli/llms_txt_downloader.py +104 -0
- skill_seekers/cli/llms_txt_parser.py +150 -0
- skill_seekers/cli/main.py +558 -0
- skill_seekers/cli/markdown_cleaner.py +132 -0
- skill_seekers/cli/merge_sources.py +806 -0
- skill_seekers/cli/package_multi.py +77 -0
- skill_seekers/cli/package_skill.py +241 -0
- skill_seekers/cli/pattern_recognizer.py +1825 -0
- skill_seekers/cli/pdf_extractor_poc.py +1166 -0
- skill_seekers/cli/pdf_scraper.py +617 -0
- skill_seekers/cli/quality_checker.py +519 -0
- skill_seekers/cli/rate_limit_handler.py +438 -0
- skill_seekers/cli/resume_command.py +160 -0
- skill_seekers/cli/run_tests.py +230 -0
- skill_seekers/cli/setup_wizard.py +93 -0
- skill_seekers/cli/split_config.py +390 -0
- skill_seekers/cli/swift_patterns.py +560 -0
- skill_seekers/cli/test_example_extractor.py +1081 -0
- skill_seekers/cli/test_unified_simple.py +179 -0
- skill_seekers/cli/unified_codebase_analyzer.py +572 -0
- skill_seekers/cli/unified_scraper.py +932 -0
- skill_seekers/cli/unified_skill_builder.py +1605 -0
- skill_seekers/cli/upload_skill.py +162 -0
- skill_seekers/cli/utils.py +432 -0
- skill_seekers/mcp/__init__.py +33 -0
- skill_seekers/mcp/agent_detector.py +316 -0
- skill_seekers/mcp/git_repo.py +273 -0
- skill_seekers/mcp/server.py +231 -0
- skill_seekers/mcp/server_fastmcp.py +1249 -0
- skill_seekers/mcp/server_legacy.py +2302 -0
- skill_seekers/mcp/source_manager.py +285 -0
- skill_seekers/mcp/tools/__init__.py +115 -0
- skill_seekers/mcp/tools/config_tools.py +251 -0
- skill_seekers/mcp/tools/packaging_tools.py +826 -0
- skill_seekers/mcp/tools/scraping_tools.py +842 -0
- skill_seekers/mcp/tools/source_tools.py +828 -0
- skill_seekers/mcp/tools/splitting_tools.py +212 -0
- skill_seekers/py.typed +0 -0
- skill_seekers-2.7.3.dist-info/METADATA +2027 -0
- skill_seekers-2.7.3.dist-info/RECORD +79 -0
- skill_seekers-2.7.3.dist-info/WHEEL +5 -0
- skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
- skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
- skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1825 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Design Pattern Recognition Module
|
|
4
|
+
|
|
5
|
+
Detects common design patterns in codebases across multiple languages.
|
|
6
|
+
|
|
7
|
+
Supported Patterns:
|
|
8
|
+
- Creational: Singleton, Factory, Builder, Prototype
|
|
9
|
+
- Structural: Adapter, Decorator, Facade, Proxy
|
|
10
|
+
- Behavioral: Observer, Strategy, Command, Template Method, Chain of Responsibility
|
|
11
|
+
|
|
12
|
+
Detection Levels:
|
|
13
|
+
- Surface: Naming conventions (e.g., "Factory", "Singleton")
|
|
14
|
+
- Deep: Structural analysis (class relationships, method signatures)
|
|
15
|
+
- Full: Behavioral analysis (method interactions, state management)
|
|
16
|
+
|
|
17
|
+
Credits:
|
|
18
|
+
- Design pattern definitions: Gang of Four (GoF) Design Patterns
|
|
19
|
+
- Detection heuristics: Inspired by academic research on pattern mining
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
import sys
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PatternInstance:
|
|
34
|
+
"""Single detected pattern instance"""
|
|
35
|
+
|
|
36
|
+
pattern_type: str # e.g., 'Singleton', 'Factory'
|
|
37
|
+
category: str # 'Creational', 'Structural', 'Behavioral'
|
|
38
|
+
confidence: float # 0.0-1.0
|
|
39
|
+
location: str # File path
|
|
40
|
+
class_name: str | None = None
|
|
41
|
+
method_name: str | None = None
|
|
42
|
+
line_number: int | None = None
|
|
43
|
+
evidence: list[str] = field(default_factory=list) # Evidence for detection
|
|
44
|
+
related_classes: list[str] = field(default_factory=list) # Related pattern classes
|
|
45
|
+
ai_analysis: dict | None = None # AI-generated analysis (C3.6)
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> dict:
|
|
48
|
+
"""Export to dictionary"""
|
|
49
|
+
result = {
|
|
50
|
+
"pattern_type": self.pattern_type,
|
|
51
|
+
"category": self.category,
|
|
52
|
+
"confidence": self.confidence,
|
|
53
|
+
"location": self.location,
|
|
54
|
+
"class_name": self.class_name,
|
|
55
|
+
"method_name": self.method_name,
|
|
56
|
+
"line_number": self.line_number,
|
|
57
|
+
"evidence": self.evidence,
|
|
58
|
+
"related_classes": self.related_classes,
|
|
59
|
+
}
|
|
60
|
+
if self.ai_analysis:
|
|
61
|
+
result["ai_analysis"] = self.ai_analysis
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class PatternReport:
|
|
67
|
+
"""Complete pattern detection report"""
|
|
68
|
+
|
|
69
|
+
file_path: str
|
|
70
|
+
language: str
|
|
71
|
+
patterns: list[PatternInstance]
|
|
72
|
+
total_classes: int
|
|
73
|
+
total_functions: int
|
|
74
|
+
analysis_depth: str # 'surface', 'deep', 'full'
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict:
|
|
77
|
+
"""Export to dictionary"""
|
|
78
|
+
return {
|
|
79
|
+
"file_path": self.file_path,
|
|
80
|
+
"language": self.language,
|
|
81
|
+
"patterns": [p.to_dict() for p in self.patterns],
|
|
82
|
+
"total_classes": self.total_classes,
|
|
83
|
+
"total_functions": self.total_functions,
|
|
84
|
+
"analysis_depth": self.analysis_depth,
|
|
85
|
+
"pattern_summary": self.get_summary(),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def get_summary(self) -> dict[str, int]:
|
|
89
|
+
"""Get pattern count summary"""
|
|
90
|
+
summary = {}
|
|
91
|
+
for pattern in self.patterns:
|
|
92
|
+
summary[pattern.pattern_type] = summary.get(pattern.pattern_type, 0) + 1
|
|
93
|
+
return summary
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class BasePatternDetector:
|
|
97
|
+
"""Base class for all pattern detectors"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, depth: str = "deep"):
|
|
100
|
+
"""
|
|
101
|
+
Initialize detector.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
depth: Detection depth ('surface', 'deep', 'full')
|
|
105
|
+
"""
|
|
106
|
+
self.depth = depth
|
|
107
|
+
self.pattern_type = "BasePattern"
|
|
108
|
+
self.category = "Unknown"
|
|
109
|
+
|
|
110
|
+
def detect_surface(self, _class_sig, _all_classes: list) -> PatternInstance | None:
|
|
111
|
+
"""
|
|
112
|
+
Surface-level detection using naming conventions.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
class_sig: Class signature to analyze
|
|
116
|
+
all_classes: All classes in the file for context
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
PatternInstance if pattern detected, None otherwise
|
|
120
|
+
"""
|
|
121
|
+
# Default: no surface detection
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
def detect_deep(self, _class_sig, _all_classes: list) -> PatternInstance | None:
|
|
125
|
+
"""
|
|
126
|
+
Deep detection using structural analysis.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
class_sig: Class signature to analyze
|
|
130
|
+
all_classes: All classes in the file for context
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
PatternInstance if pattern detected, None otherwise
|
|
134
|
+
"""
|
|
135
|
+
# Default: no deep detection
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def detect_full(
|
|
139
|
+
self, _class_sig, _all_classes: list, _file_content: str
|
|
140
|
+
) -> PatternInstance | None:
|
|
141
|
+
"""
|
|
142
|
+
Full detection using behavioral analysis.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
class_sig: Class signature to analyze
|
|
146
|
+
all_classes: All classes in the file for context
|
|
147
|
+
file_content: Full file content for advanced analysis
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
PatternInstance if pattern detected, None otherwise
|
|
151
|
+
"""
|
|
152
|
+
# Default: no full detection
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
def detect(
|
|
156
|
+
self, class_sig, all_classes: list, file_content: str | None = None
|
|
157
|
+
) -> PatternInstance | None:
|
|
158
|
+
"""
|
|
159
|
+
Detect pattern based on configured depth.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
class_sig: Class signature to analyze
|
|
163
|
+
all_classes: All classes in the file for context
|
|
164
|
+
file_content: Full file content (needed for 'full' depth)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
PatternInstance if pattern detected, None otherwise
|
|
168
|
+
"""
|
|
169
|
+
if self.depth == "surface":
|
|
170
|
+
return self.detect_surface(class_sig, all_classes)
|
|
171
|
+
elif self.depth == "deep":
|
|
172
|
+
# Try deep first, fallback to surface
|
|
173
|
+
result = self.detect_deep(class_sig, all_classes)
|
|
174
|
+
if result:
|
|
175
|
+
return result
|
|
176
|
+
return self.detect_surface(class_sig, all_classes)
|
|
177
|
+
elif self.depth == "full":
|
|
178
|
+
# Try full, fallback to deep, then surface
|
|
179
|
+
if file_content:
|
|
180
|
+
result = self.detect_full(class_sig, all_classes, file_content)
|
|
181
|
+
if result:
|
|
182
|
+
return result
|
|
183
|
+
result = self.detect_deep(class_sig, all_classes)
|
|
184
|
+
if result:
|
|
185
|
+
return result
|
|
186
|
+
return self.detect_surface(class_sig, all_classes)
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError(f"Invalid depth: {self.depth}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class PatternRecognizer:
|
|
192
|
+
"""
|
|
193
|
+
Main pattern recognition orchestrator.
|
|
194
|
+
|
|
195
|
+
Coordinates multiple pattern detectors to analyze code.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(self, depth: str = "deep", enhance_with_ai: bool = True):
|
|
199
|
+
"""
|
|
200
|
+
Initialize pattern recognizer.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
depth: Detection depth ('surface', 'deep', 'full')
|
|
204
|
+
enhance_with_ai: Enable AI enhancement of detected patterns (default: True, C3.6)
|
|
205
|
+
"""
|
|
206
|
+
self.depth = depth
|
|
207
|
+
self.enhance_with_ai = enhance_with_ai
|
|
208
|
+
self.detectors: list[BasePatternDetector] = []
|
|
209
|
+
self._register_detectors()
|
|
210
|
+
|
|
211
|
+
# Initialize AI enhancer if enabled (C3.6)
|
|
212
|
+
self.ai_enhancer = None
|
|
213
|
+
if self.enhance_with_ai:
|
|
214
|
+
try:
|
|
215
|
+
from skill_seekers.cli.ai_enhancer import PatternEnhancer
|
|
216
|
+
|
|
217
|
+
self.ai_enhancer = PatternEnhancer()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.warning(f"⚠️ Failed to initialize AI enhancer: {e}")
|
|
220
|
+
self.enhance_with_ai = False
|
|
221
|
+
|
|
222
|
+
def _register_detectors(self):
|
|
223
|
+
"""Register all available pattern detectors"""
|
|
224
|
+
# Creational patterns (3)
|
|
225
|
+
self.detectors.append(SingletonDetector(self.depth))
|
|
226
|
+
self.detectors.append(FactoryDetector(self.depth))
|
|
227
|
+
self.detectors.append(BuilderDetector(self.depth))
|
|
228
|
+
|
|
229
|
+
# Structural patterns (2)
|
|
230
|
+
self.detectors.append(DecoratorDetector(self.depth))
|
|
231
|
+
self.detectors.append(AdapterDetector(self.depth))
|
|
232
|
+
|
|
233
|
+
# Behavioral patterns (5)
|
|
234
|
+
self.detectors.append(ObserverDetector(self.depth))
|
|
235
|
+
self.detectors.append(StrategyDetector(self.depth))
|
|
236
|
+
self.detectors.append(CommandDetector(self.depth))
|
|
237
|
+
self.detectors.append(TemplateMethodDetector(self.depth))
|
|
238
|
+
self.detectors.append(ChainOfResponsibilityDetector(self.depth))
|
|
239
|
+
|
|
240
|
+
def analyze_file(self, file_path: str, content: str, language: str) -> PatternReport:
|
|
241
|
+
"""
|
|
242
|
+
Analyze a single file for design patterns.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
file_path: Path to source file
|
|
246
|
+
content: File content
|
|
247
|
+
language: Programming language
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
PatternReport with detected patterns
|
|
251
|
+
"""
|
|
252
|
+
# Step 1: Analyze code structure using CodeAnalyzer
|
|
253
|
+
from skill_seekers.cli.code_analyzer import CodeAnalyzer
|
|
254
|
+
|
|
255
|
+
analyzer = CodeAnalyzer(depth="deep")
|
|
256
|
+
analysis = analyzer.analyze_file(file_path, content, language)
|
|
257
|
+
|
|
258
|
+
if not analysis:
|
|
259
|
+
return PatternReport(
|
|
260
|
+
file_path=file_path,
|
|
261
|
+
language=language,
|
|
262
|
+
patterns=[],
|
|
263
|
+
total_classes=0,
|
|
264
|
+
total_functions=0,
|
|
265
|
+
analysis_depth=self.depth,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
classes = analysis.get("classes", [])
|
|
269
|
+
functions = analysis.get("functions", [])
|
|
270
|
+
|
|
271
|
+
# Convert to class signature objects
|
|
272
|
+
class_sigs = self._convert_to_signatures(classes)
|
|
273
|
+
|
|
274
|
+
# Step 2: Run pattern detection
|
|
275
|
+
detected_patterns = []
|
|
276
|
+
|
|
277
|
+
for class_sig in class_sigs:
|
|
278
|
+
for detector in self.detectors:
|
|
279
|
+
pattern = detector.detect(
|
|
280
|
+
class_sig=class_sig,
|
|
281
|
+
all_classes=class_sigs,
|
|
282
|
+
file_content=content if self.depth == "full" else None,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if pattern:
|
|
286
|
+
# Add file path to pattern
|
|
287
|
+
pattern.location = file_path
|
|
288
|
+
|
|
289
|
+
# Apply language-specific adaptations
|
|
290
|
+
pattern = LanguageAdapter.adapt_for_language(pattern, language)
|
|
291
|
+
|
|
292
|
+
detected_patterns.append(pattern)
|
|
293
|
+
|
|
294
|
+
# Step 3: Enhance patterns with AI analysis (C3.6)
|
|
295
|
+
if self.enhance_with_ai and self.ai_enhancer and detected_patterns:
|
|
296
|
+
# Convert patterns to dict format for AI processing
|
|
297
|
+
pattern_dicts = [p.to_dict() for p in detected_patterns]
|
|
298
|
+
enhanced_dicts = self.ai_enhancer.enhance_patterns(pattern_dicts)
|
|
299
|
+
|
|
300
|
+
# Update patterns with AI analysis
|
|
301
|
+
for i, pattern in enumerate(detected_patterns):
|
|
302
|
+
if i < len(enhanced_dicts) and "ai_analysis" in enhanced_dicts[i]:
|
|
303
|
+
pattern.ai_analysis = enhanced_dicts[i]["ai_analysis"]
|
|
304
|
+
# Apply confidence boost if provided
|
|
305
|
+
if "confidence" in enhanced_dicts[i]:
|
|
306
|
+
pattern.confidence = enhanced_dicts[i]["confidence"]
|
|
307
|
+
|
|
308
|
+
return PatternReport(
|
|
309
|
+
file_path=file_path,
|
|
310
|
+
language=language,
|
|
311
|
+
patterns=detected_patterns,
|
|
312
|
+
total_classes=len(classes),
|
|
313
|
+
total_functions=len(functions),
|
|
314
|
+
analysis_depth=self.depth,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def _convert_to_signatures(self, classes: list[dict]):
|
|
318
|
+
"""
|
|
319
|
+
Convert dict-based class analysis to signature objects.
|
|
320
|
+
|
|
321
|
+
Note: Returns simple namespace objects that mimic ClassSignature structure
|
|
322
|
+
but work with dict-based input from CodeAnalyzer.
|
|
323
|
+
"""
|
|
324
|
+
from types import SimpleNamespace
|
|
325
|
+
|
|
326
|
+
signatures = []
|
|
327
|
+
|
|
328
|
+
for cls in classes:
|
|
329
|
+
# Convert methods
|
|
330
|
+
methods = []
|
|
331
|
+
for method in cls.get("methods", []):
|
|
332
|
+
# Convert parameters
|
|
333
|
+
params = []
|
|
334
|
+
for param in method.get("parameters", []):
|
|
335
|
+
param_obj = SimpleNamespace(
|
|
336
|
+
name=param.get("name", ""),
|
|
337
|
+
type_hint=param.get("type_hint"),
|
|
338
|
+
default=param.get("default"),
|
|
339
|
+
)
|
|
340
|
+
params.append(param_obj)
|
|
341
|
+
|
|
342
|
+
method_obj = SimpleNamespace(
|
|
343
|
+
name=method.get("name", ""),
|
|
344
|
+
parameters=params,
|
|
345
|
+
return_type=method.get("return_type"),
|
|
346
|
+
docstring=method.get("docstring"),
|
|
347
|
+
line_number=method.get("line_number"),
|
|
348
|
+
is_async=method.get("is_async", False),
|
|
349
|
+
is_method=True,
|
|
350
|
+
decorators=method.get("decorators", []),
|
|
351
|
+
)
|
|
352
|
+
methods.append(method_obj)
|
|
353
|
+
|
|
354
|
+
class_obj = SimpleNamespace(
|
|
355
|
+
name=cls.get("name", ""),
|
|
356
|
+
base_classes=cls.get("base_classes", []),
|
|
357
|
+
methods=methods,
|
|
358
|
+
docstring=cls.get("docstring"),
|
|
359
|
+
line_number=cls.get("line_number"),
|
|
360
|
+
)
|
|
361
|
+
signatures.append(class_obj)
|
|
362
|
+
|
|
363
|
+
return signatures
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class SingletonDetector(BasePatternDetector):
|
|
367
|
+
"""
|
|
368
|
+
Detect Singleton pattern.
|
|
369
|
+
|
|
370
|
+
Singleton ensures a class has only one instance and provides global access.
|
|
371
|
+
|
|
372
|
+
Detection Heuristics:
|
|
373
|
+
- Surface: Class name contains 'Singleton'
|
|
374
|
+
- Deep: Private constructor + static instance method
|
|
375
|
+
- Full: Instance caching + thread safety checks
|
|
376
|
+
|
|
377
|
+
Examples:
|
|
378
|
+
- Python: __new__ override with instance caching
|
|
379
|
+
- JavaScript: Module pattern or class with getInstance()
|
|
380
|
+
- Java: Private constructor + synchronized getInstance()
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
def __init__(self, depth: str = "deep"):
|
|
384
|
+
super().__init__(depth)
|
|
385
|
+
self.pattern_type = "Singleton"
|
|
386
|
+
self.category = "Creational"
|
|
387
|
+
|
|
388
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
389
|
+
"""Check if class name suggests Singleton"""
|
|
390
|
+
if "singleton" in class_sig.name.lower():
|
|
391
|
+
return PatternInstance(
|
|
392
|
+
pattern_type=self.pattern_type,
|
|
393
|
+
category=self.category,
|
|
394
|
+
confidence=0.6,
|
|
395
|
+
location="",
|
|
396
|
+
class_name=class_sig.name,
|
|
397
|
+
line_number=class_sig.line_number,
|
|
398
|
+
evidence=['Class name contains "Singleton"'],
|
|
399
|
+
)
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
403
|
+
"""Check structural characteristics of Singleton"""
|
|
404
|
+
evidence = []
|
|
405
|
+
confidence = 0.0
|
|
406
|
+
|
|
407
|
+
# Check for instance method (getInstance, instance, get_instance, etc.)
|
|
408
|
+
instance_methods = [
|
|
409
|
+
"getInstance",
|
|
410
|
+
"instance",
|
|
411
|
+
"get_instance",
|
|
412
|
+
"Instance",
|
|
413
|
+
"GetInstance",
|
|
414
|
+
"INSTANCE",
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
has_instance_method = False
|
|
418
|
+
for method in class_sig.methods:
|
|
419
|
+
if method.name in instance_methods:
|
|
420
|
+
evidence.append(f"Has instance method: {method.name}")
|
|
421
|
+
confidence += 0.4
|
|
422
|
+
has_instance_method = True
|
|
423
|
+
break
|
|
424
|
+
|
|
425
|
+
# Check for private/protected constructor-like methods
|
|
426
|
+
has_init_control = False
|
|
427
|
+
for method in class_sig.methods:
|
|
428
|
+
# Python: __init__ or __new__
|
|
429
|
+
# Java/C#: private constructor (detected by naming)
|
|
430
|
+
# Check if it has logic (not just pass)
|
|
431
|
+
if method.name in ["__new__", "__init__", "constructor"] and (
|
|
432
|
+
method.docstring or len(method.parameters) > 1
|
|
433
|
+
):
|
|
434
|
+
evidence.append(f"Controlled initialization: {method.name}")
|
|
435
|
+
confidence += 0.3
|
|
436
|
+
has_init_control = True
|
|
437
|
+
break
|
|
438
|
+
|
|
439
|
+
# Check for class-level instance storage
|
|
440
|
+
# This would require checking class attributes (future enhancement)
|
|
441
|
+
|
|
442
|
+
if has_instance_method or has_init_control and confidence >= 0.5:
|
|
443
|
+
return PatternInstance(
|
|
444
|
+
pattern_type=self.pattern_type,
|
|
445
|
+
category=self.category,
|
|
446
|
+
confidence=min(confidence, 0.9),
|
|
447
|
+
location="",
|
|
448
|
+
class_name=class_sig.name,
|
|
449
|
+
line_number=class_sig.line_number,
|
|
450
|
+
evidence=evidence,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Fallback to surface detection
|
|
454
|
+
return self.detect_surface(class_sig, all_classes)
|
|
455
|
+
|
|
456
|
+
def detect_full(
|
|
457
|
+
self, class_sig, all_classes: list, file_content: str
|
|
458
|
+
) -> PatternInstance | None:
|
|
459
|
+
"""
|
|
460
|
+
Full behavioral analysis for Singleton.
|
|
461
|
+
|
|
462
|
+
Checks:
|
|
463
|
+
- Instance caching in method body
|
|
464
|
+
- Thread safety (locks, synchronized)
|
|
465
|
+
- Lazy vs eager initialization
|
|
466
|
+
"""
|
|
467
|
+
# Start with deep detection
|
|
468
|
+
result = self.detect_deep(class_sig, all_classes)
|
|
469
|
+
if not result:
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
evidence = result.evidence.copy()
|
|
473
|
+
confidence = result.confidence
|
|
474
|
+
|
|
475
|
+
# Check for instance caching patterns in code
|
|
476
|
+
caching_patterns = [
|
|
477
|
+
"_instance",
|
|
478
|
+
"__instance",
|
|
479
|
+
"instance",
|
|
480
|
+
"if not",
|
|
481
|
+
"if self._instance is None",
|
|
482
|
+
"synchronized",
|
|
483
|
+
"Lock()",
|
|
484
|
+
"threading",
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
for pattern in caching_patterns:
|
|
488
|
+
if pattern in file_content and pattern not in " ".join(evidence):
|
|
489
|
+
evidence.append(f"Instance caching detected: {pattern}")
|
|
490
|
+
confidence += 0.1
|
|
491
|
+
|
|
492
|
+
# Cap confidence at 0.95 (never 100% certain without runtime analysis)
|
|
493
|
+
result.confidence = min(confidence, 0.95)
|
|
494
|
+
result.evidence = evidence
|
|
495
|
+
|
|
496
|
+
return result
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class FactoryDetector(BasePatternDetector):
|
|
500
|
+
"""
|
|
501
|
+
Detect Factory pattern (Factory Method and Abstract Factory).
|
|
502
|
+
|
|
503
|
+
Factory defines an interface for creating objects, letting subclasses decide
|
|
504
|
+
which class to instantiate.
|
|
505
|
+
|
|
506
|
+
Detection Heuristics:
|
|
507
|
+
- Surface: Class/method name contains 'Factory', 'create', 'make'
|
|
508
|
+
- Deep: Method returns different object types based on parameters
|
|
509
|
+
- Full: Polymorphic object creation with inheritance hierarchy
|
|
510
|
+
|
|
511
|
+
Examples:
|
|
512
|
+
- createProduct(type) -> Product
|
|
513
|
+
- ProductFactory with createProductA(), createProductB()
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
def __init__(self, depth: str = "deep"):
|
|
517
|
+
super().__init__(depth)
|
|
518
|
+
self.pattern_type = "Factory"
|
|
519
|
+
self.category = "Creational"
|
|
520
|
+
|
|
521
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
522
|
+
"""Check naming conventions for Factory"""
|
|
523
|
+
# Check class name
|
|
524
|
+
if "factory" in class_sig.name.lower():
|
|
525
|
+
return PatternInstance(
|
|
526
|
+
pattern_type=self.pattern_type,
|
|
527
|
+
category=self.category,
|
|
528
|
+
confidence=0.7,
|
|
529
|
+
location="",
|
|
530
|
+
class_name=class_sig.name,
|
|
531
|
+
line_number=class_sig.line_number,
|
|
532
|
+
evidence=['Class name contains "Factory"'],
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# Check for factory methods
|
|
536
|
+
factory_method_names = ["create", "make", "build", "new", "get"]
|
|
537
|
+
for method in class_sig.methods:
|
|
538
|
+
method_lower = method.name.lower()
|
|
539
|
+
# Check if method returns something (has return type or is not void)
|
|
540
|
+
if any(name in method_lower for name in factory_method_names) and (
|
|
541
|
+
method.return_type or "create" in method_lower
|
|
542
|
+
):
|
|
543
|
+
return PatternInstance(
|
|
544
|
+
pattern_type=self.pattern_type,
|
|
545
|
+
category=self.category,
|
|
546
|
+
confidence=0.6,
|
|
547
|
+
location="",
|
|
548
|
+
class_name=class_sig.name,
|
|
549
|
+
method_name=method.name,
|
|
550
|
+
line_number=method.line_number,
|
|
551
|
+
evidence=[f"Factory method detected: {method.name}"],
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
557
|
+
"""Structural analysis for Factory"""
|
|
558
|
+
evidence = []
|
|
559
|
+
confidence = 0.0
|
|
560
|
+
factory_methods = []
|
|
561
|
+
|
|
562
|
+
# Look for methods that create objects
|
|
563
|
+
creation_keywords = ["create", "make", "build", "new", "construct", "get"]
|
|
564
|
+
|
|
565
|
+
for method in class_sig.methods:
|
|
566
|
+
method_lower = method.name.lower()
|
|
567
|
+
|
|
568
|
+
# Check if method name suggests object creation
|
|
569
|
+
if any(keyword in method_lower for keyword in creation_keywords):
|
|
570
|
+
factory_methods.append(method.name)
|
|
571
|
+
confidence += 0.3
|
|
572
|
+
|
|
573
|
+
# Check if it takes parameters (suggests different object types)
|
|
574
|
+
if len(method.parameters) > 1: # >1 because 'self' counts
|
|
575
|
+
evidence.append(f"Parameterized factory method: {method.name}")
|
|
576
|
+
confidence += 0.2
|
|
577
|
+
else:
|
|
578
|
+
evidence.append(f"Factory method: {method.name}")
|
|
579
|
+
|
|
580
|
+
# Check if multiple factory methods exist (Abstract Factory pattern)
|
|
581
|
+
if len(factory_methods) >= 2:
|
|
582
|
+
evidence.append(f"Multiple factory methods: {', '.join(factory_methods[:3])}")
|
|
583
|
+
confidence += 0.2
|
|
584
|
+
|
|
585
|
+
# Check for inheritance (factory hierarchy)
|
|
586
|
+
if class_sig.base_classes:
|
|
587
|
+
evidence.append(f"Inherits from: {', '.join(class_sig.base_classes)}")
|
|
588
|
+
confidence += 0.1
|
|
589
|
+
|
|
590
|
+
if confidence >= 0.5:
|
|
591
|
+
return PatternInstance(
|
|
592
|
+
pattern_type=self.pattern_type,
|
|
593
|
+
category=self.category,
|
|
594
|
+
confidence=min(confidence, 0.9),
|
|
595
|
+
location="",
|
|
596
|
+
class_name=class_sig.name,
|
|
597
|
+
line_number=class_sig.line_number,
|
|
598
|
+
evidence=evidence,
|
|
599
|
+
related_classes=class_sig.base_classes,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Fallback to surface
|
|
603
|
+
return self.detect_surface(class_sig, all_classes)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class ObserverDetector(BasePatternDetector):
|
|
607
|
+
"""
|
|
608
|
+
Detect Observer pattern (Pub/Sub).
|
|
609
|
+
|
|
610
|
+
Observer defines one-to-many dependency where multiple objects
|
|
611
|
+
observe and react to state changes.
|
|
612
|
+
|
|
613
|
+
Detection Heuristics:
|
|
614
|
+
- Surface: Class/method names with 'Observer', 'Listener', 'Subscribe'
|
|
615
|
+
- Deep: attach/detach + notify methods
|
|
616
|
+
- Full: Collection of observers + iteration pattern
|
|
617
|
+
|
|
618
|
+
Examples:
|
|
619
|
+
- addObserver(), removeObserver(), notifyObservers()
|
|
620
|
+
- addEventListener(), removeEventListener(), emit()
|
|
621
|
+
- subscribe(), unsubscribe(), publish()
|
|
622
|
+
"""
|
|
623
|
+
|
|
624
|
+
def __init__(self, depth: str = "deep"):
|
|
625
|
+
super().__init__(depth)
|
|
626
|
+
self.pattern_type = "Observer"
|
|
627
|
+
self.category = "Behavioral"
|
|
628
|
+
|
|
629
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
630
|
+
"""Check naming for Observer pattern"""
|
|
631
|
+
observer_keywords = ["observer", "listener", "subscriber", "watcher"]
|
|
632
|
+
|
|
633
|
+
# Check class name
|
|
634
|
+
class_lower = class_sig.name.lower()
|
|
635
|
+
if any(keyword in class_lower for keyword in observer_keywords):
|
|
636
|
+
return PatternInstance(
|
|
637
|
+
pattern_type=self.pattern_type,
|
|
638
|
+
category=self.category,
|
|
639
|
+
confidence=0.6,
|
|
640
|
+
location="",
|
|
641
|
+
class_name=class_sig.name,
|
|
642
|
+
line_number=class_sig.line_number,
|
|
643
|
+
evidence=[f"Class name suggests Observer: {class_sig.name}"],
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Check method names
|
|
647
|
+
observer_methods = [
|
|
648
|
+
"subscribe",
|
|
649
|
+
"unsubscribe",
|
|
650
|
+
"publish",
|
|
651
|
+
"addobserver",
|
|
652
|
+
"removeobserver",
|
|
653
|
+
"notify",
|
|
654
|
+
"addeventlistener",
|
|
655
|
+
"removeeventlistener",
|
|
656
|
+
"emit",
|
|
657
|
+
"attach",
|
|
658
|
+
"detach",
|
|
659
|
+
"update",
|
|
660
|
+
]
|
|
661
|
+
|
|
662
|
+
for method in class_sig.methods:
|
|
663
|
+
method_lower = method.name.lower().replace("_", "")
|
|
664
|
+
if any(obs_method in method_lower for obs_method in observer_methods):
|
|
665
|
+
return PatternInstance(
|
|
666
|
+
pattern_type=self.pattern_type,
|
|
667
|
+
category=self.category,
|
|
668
|
+
confidence=0.65,
|
|
669
|
+
location="",
|
|
670
|
+
class_name=class_sig.name,
|
|
671
|
+
method_name=method.name,
|
|
672
|
+
line_number=method.line_number,
|
|
673
|
+
evidence=[f"Observer method detected: {method.name}"],
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
return None
|
|
677
|
+
|
|
678
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
679
|
+
"""Structural analysis for Observer"""
|
|
680
|
+
evidence = []
|
|
681
|
+
confidence = 0.0
|
|
682
|
+
|
|
683
|
+
# Look for characteristic method triplet: attach/detach/notify
|
|
684
|
+
has_attach = False
|
|
685
|
+
has_detach = False
|
|
686
|
+
has_notify = False
|
|
687
|
+
|
|
688
|
+
attach_names = ["attach", "add", "subscribe", "register", "addeventlistener"]
|
|
689
|
+
detach_names = [
|
|
690
|
+
"detach",
|
|
691
|
+
"remove",
|
|
692
|
+
"unsubscribe",
|
|
693
|
+
"unregister",
|
|
694
|
+
"removeeventlistener",
|
|
695
|
+
]
|
|
696
|
+
notify_names = ["notify", "update", "emit", "publish", "fire", "trigger"]
|
|
697
|
+
|
|
698
|
+
for method in class_sig.methods:
|
|
699
|
+
method_lower = method.name.lower().replace("_", "")
|
|
700
|
+
|
|
701
|
+
if any(name in method_lower for name in attach_names):
|
|
702
|
+
has_attach = True
|
|
703
|
+
evidence.append(f"Attach method: {method.name}")
|
|
704
|
+
confidence += 0.3
|
|
705
|
+
|
|
706
|
+
if any(name in method_lower for name in detach_names):
|
|
707
|
+
has_detach = True
|
|
708
|
+
evidence.append(f"Detach method: {method.name}")
|
|
709
|
+
confidence += 0.3
|
|
710
|
+
|
|
711
|
+
if any(name in method_lower for name in notify_names):
|
|
712
|
+
has_notify = True
|
|
713
|
+
evidence.append(f"Notify method: {method.name}")
|
|
714
|
+
confidence += 0.3
|
|
715
|
+
|
|
716
|
+
# Strong signal if has all three
|
|
717
|
+
if has_attach and has_detach and has_notify:
|
|
718
|
+
confidence = min(confidence, 0.95)
|
|
719
|
+
|
|
720
|
+
if confidence >= 0.5:
|
|
721
|
+
return PatternInstance(
|
|
722
|
+
pattern_type=self.pattern_type,
|
|
723
|
+
category=self.category,
|
|
724
|
+
confidence=min(confidence, 0.95),
|
|
725
|
+
location="",
|
|
726
|
+
class_name=class_sig.name,
|
|
727
|
+
line_number=class_sig.line_number,
|
|
728
|
+
evidence=evidence,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
# Fallback to surface
|
|
732
|
+
return self.detect_surface(class_sig, all_classes)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class StrategyDetector(BasePatternDetector):
|
|
736
|
+
"""
|
|
737
|
+
Detect Strategy pattern.
|
|
738
|
+
|
|
739
|
+
Strategy defines a family of algorithms, encapsulates each one,
|
|
740
|
+
and makes them interchangeable.
|
|
741
|
+
|
|
742
|
+
Detection Heuristics:
|
|
743
|
+
- Surface: Class/method names with 'Strategy', 'Policy', 'Algorithm'
|
|
744
|
+
- Deep: Interface with single key method + multiple implementations
|
|
745
|
+
- Full: Composition with interchangeable strategy objects
|
|
746
|
+
|
|
747
|
+
Examples:
|
|
748
|
+
- SortStrategy with sort() method
|
|
749
|
+
- PaymentStrategy with pay() method
|
|
750
|
+
- CompressionStrategy with compress() method
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
def __init__(self, depth: str = "deep"):
|
|
754
|
+
super().__init__(depth)
|
|
755
|
+
self.pattern_type = "Strategy"
|
|
756
|
+
self.category = "Behavioral"
|
|
757
|
+
|
|
758
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
759
|
+
"""Check naming for Strategy"""
|
|
760
|
+
strategy_keywords = ["strategy", "policy", "algorithm"]
|
|
761
|
+
|
|
762
|
+
class_lower = class_sig.name.lower()
|
|
763
|
+
if any(keyword in class_lower for keyword in strategy_keywords):
|
|
764
|
+
return PatternInstance(
|
|
765
|
+
pattern_type=self.pattern_type,
|
|
766
|
+
category=self.category,
|
|
767
|
+
confidence=0.7,
|
|
768
|
+
location="",
|
|
769
|
+
class_name=class_sig.name,
|
|
770
|
+
line_number=class_sig.line_number,
|
|
771
|
+
evidence=[f"Class name suggests Strategy: {class_sig.name}"],
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
return None
|
|
775
|
+
|
|
776
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
777
|
+
"""Structural analysis for Strategy"""
|
|
778
|
+
evidence = []
|
|
779
|
+
confidence = 0.0
|
|
780
|
+
|
|
781
|
+
# Strategy pattern often involves:
|
|
782
|
+
# 1. Base class/interface with key method
|
|
783
|
+
# 2. Multiple subclasses implementing same interface
|
|
784
|
+
|
|
785
|
+
# Check if this class is a concrete strategy
|
|
786
|
+
if class_sig.base_classes:
|
|
787
|
+
base_class = class_sig.base_classes[0] if class_sig.base_classes else None
|
|
788
|
+
|
|
789
|
+
# Look for siblings (other strategies with same base)
|
|
790
|
+
siblings = [
|
|
791
|
+
cls.name
|
|
792
|
+
for cls in all_classes
|
|
793
|
+
if cls.base_classes
|
|
794
|
+
and base_class in cls.base_classes
|
|
795
|
+
and cls.name != class_sig.name
|
|
796
|
+
]
|
|
797
|
+
|
|
798
|
+
if siblings:
|
|
799
|
+
evidence.append(f"Part of strategy family with: {', '.join(siblings[:3])}")
|
|
800
|
+
confidence += 0.5
|
|
801
|
+
|
|
802
|
+
if base_class and ("strategy" in base_class.lower() or "policy" in base_class.lower()):
|
|
803
|
+
evidence.append(f"Inherits from strategy base: {base_class}")
|
|
804
|
+
confidence += 0.3
|
|
805
|
+
|
|
806
|
+
# Check if this is a strategy base class
|
|
807
|
+
# (has subclasses in same file)
|
|
808
|
+
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
|
809
|
+
|
|
810
|
+
if len(subclasses) >= 2:
|
|
811
|
+
evidence.append(f"Strategy base with implementations: {', '.join(subclasses[:3])}")
|
|
812
|
+
confidence += 0.6
|
|
813
|
+
|
|
814
|
+
# Check for single dominant method (strategy interface)
|
|
815
|
+
if len(class_sig.methods) == 1 or len(class_sig.methods) == 2:
|
|
816
|
+
# Single method or method + __init__
|
|
817
|
+
main_method = [m for m in class_sig.methods if m.name not in ["__init__", "__new__"]]
|
|
818
|
+
if main_method:
|
|
819
|
+
evidence.append(f"Strategy interface method: {main_method[0].name}")
|
|
820
|
+
confidence += 0.2
|
|
821
|
+
|
|
822
|
+
if confidence >= 0.5:
|
|
823
|
+
return PatternInstance(
|
|
824
|
+
pattern_type=self.pattern_type,
|
|
825
|
+
category=self.category,
|
|
826
|
+
confidence=min(confidence, 0.9),
|
|
827
|
+
location="",
|
|
828
|
+
class_name=class_sig.name,
|
|
829
|
+
line_number=class_sig.line_number,
|
|
830
|
+
evidence=evidence,
|
|
831
|
+
related_classes=class_sig.base_classes + subclasses,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
# Fallback to surface
|
|
835
|
+
return self.detect_surface(class_sig, all_classes)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class DecoratorDetector(BasePatternDetector):
|
|
839
|
+
"""
|
|
840
|
+
Detect Decorator pattern.
|
|
841
|
+
|
|
842
|
+
Decorator attaches additional responsibilities to an object dynamically,
|
|
843
|
+
providing flexible alternative to subclassing.
|
|
844
|
+
|
|
845
|
+
Detection Heuristics:
|
|
846
|
+
- Surface: Class name contains 'Decorator', 'Wrapper'
|
|
847
|
+
- Deep: Wraps same interface, delegates to wrapped object
|
|
848
|
+
- Full: Composition + delegation + interface matching
|
|
849
|
+
|
|
850
|
+
Examples:
|
|
851
|
+
- LoggingDecorator wraps Service
|
|
852
|
+
- CachingDecorator wraps DataFetcher
|
|
853
|
+
- Python @decorator syntax
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
def __init__(self, depth: str = "deep"):
|
|
857
|
+
super().__init__(depth)
|
|
858
|
+
self.pattern_type = "Decorator"
|
|
859
|
+
self.category = "Structural"
|
|
860
|
+
|
|
861
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
862
|
+
"""Check naming for Decorator"""
|
|
863
|
+
decorator_keywords = ["decorator", "wrapper", "proxy"]
|
|
864
|
+
|
|
865
|
+
class_lower = class_sig.name.lower()
|
|
866
|
+
if any(keyword in class_lower for keyword in decorator_keywords):
|
|
867
|
+
return PatternInstance(
|
|
868
|
+
pattern_type=self.pattern_type,
|
|
869
|
+
category=self.category,
|
|
870
|
+
confidence=0.65,
|
|
871
|
+
location="",
|
|
872
|
+
class_name=class_sig.name,
|
|
873
|
+
line_number=class_sig.line_number,
|
|
874
|
+
evidence=[f"Class name suggests Decorator: {class_sig.name}"],
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
# Check for Python decorator syntax
|
|
878
|
+
for method in class_sig.methods:
|
|
879
|
+
if method.decorators:
|
|
880
|
+
# Has decorators - might be using decorator pattern
|
|
881
|
+
# But this is too common, so low confidence
|
|
882
|
+
return PatternInstance(
|
|
883
|
+
pattern_type=self.pattern_type,
|
|
884
|
+
category=self.category,
|
|
885
|
+
confidence=0.3,
|
|
886
|
+
location="",
|
|
887
|
+
class_name=class_sig.name,
|
|
888
|
+
method_name=method.name,
|
|
889
|
+
line_number=method.line_number,
|
|
890
|
+
evidence=[f"Method uses decorators: {method.decorators}"],
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
return None
|
|
894
|
+
|
|
895
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
896
|
+
"""Structural analysis for Decorator"""
|
|
897
|
+
evidence = []
|
|
898
|
+
confidence = 0.0
|
|
899
|
+
|
|
900
|
+
# Decorator pattern characteristics:
|
|
901
|
+
# 1. Has same base class as wrapped object
|
|
902
|
+
# 2. Takes wrapped object in constructor
|
|
903
|
+
# 3. Delegates calls to wrapped object
|
|
904
|
+
|
|
905
|
+
# Check if shares base class with other classes
|
|
906
|
+
if class_sig.base_classes:
|
|
907
|
+
base_class = class_sig.base_classes[0]
|
|
908
|
+
|
|
909
|
+
# Find other classes with same base
|
|
910
|
+
siblings = [
|
|
911
|
+
cls.name
|
|
912
|
+
for cls in all_classes
|
|
913
|
+
if cls.base_classes
|
|
914
|
+
and base_class in cls.base_classes
|
|
915
|
+
and cls.name != class_sig.name
|
|
916
|
+
]
|
|
917
|
+
|
|
918
|
+
if siblings:
|
|
919
|
+
evidence.append(f"Shares interface with: {', '.join(siblings[:2])}")
|
|
920
|
+
confidence += 0.3
|
|
921
|
+
|
|
922
|
+
# Check __init__ for composition (takes object parameter)
|
|
923
|
+
init_method = next((m for m in class_sig.methods if m.name == "__init__"), None)
|
|
924
|
+
# Check if takes object parameter (not just self)
|
|
925
|
+
if init_method and len(init_method.parameters) > 1: # More than just 'self'
|
|
926
|
+
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
|
927
|
+
if any(
|
|
928
|
+
name in ["wrapped", "component", "inner", "obj", "target"] for name in param_names
|
|
929
|
+
):
|
|
930
|
+
evidence.append(f"Takes wrapped object in constructor: {param_names}")
|
|
931
|
+
confidence += 0.4
|
|
932
|
+
|
|
933
|
+
if confidence >= 0.5:
|
|
934
|
+
return PatternInstance(
|
|
935
|
+
pattern_type=self.pattern_type,
|
|
936
|
+
category=self.category,
|
|
937
|
+
confidence=min(confidence, 0.85),
|
|
938
|
+
location="",
|
|
939
|
+
class_name=class_sig.name,
|
|
940
|
+
line_number=class_sig.line_number,
|
|
941
|
+
evidence=evidence,
|
|
942
|
+
related_classes=class_sig.base_classes,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
# Fallback to surface
|
|
946
|
+
return self.detect_surface(class_sig, all_classes)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
class BuilderDetector(BasePatternDetector):
|
|
950
|
+
"""
|
|
951
|
+
Detect Builder pattern.
|
|
952
|
+
|
|
953
|
+
Builder separates construction of complex object from its representation,
|
|
954
|
+
allowing same construction process to create different representations.
|
|
955
|
+
|
|
956
|
+
Detection Heuristics:
|
|
957
|
+
- Surface: Class name contains 'Builder'
|
|
958
|
+
- Deep: Fluent interface (methods return self), build()/create() terminal method
|
|
959
|
+
- Full: Multiple configuration methods + final build step
|
|
960
|
+
|
|
961
|
+
Examples:
|
|
962
|
+
- QueryBuilder with where(), orderBy(), build()
|
|
963
|
+
- RequestBuilder with setHeader(), setBody(), execute()
|
|
964
|
+
- StringBuilder pattern
|
|
965
|
+
"""
|
|
966
|
+
|
|
967
|
+
def __init__(self, depth: str = "deep"):
|
|
968
|
+
super().__init__(depth)
|
|
969
|
+
self.pattern_type = "Builder"
|
|
970
|
+
self.category = "Creational"
|
|
971
|
+
|
|
972
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
973
|
+
"""Check naming for Builder"""
|
|
974
|
+
if "builder" in class_sig.name.lower():
|
|
975
|
+
return PatternInstance(
|
|
976
|
+
pattern_type=self.pattern_type,
|
|
977
|
+
category=self.category,
|
|
978
|
+
confidence=0.7,
|
|
979
|
+
location="",
|
|
980
|
+
class_name=class_sig.name,
|
|
981
|
+
line_number=class_sig.line_number,
|
|
982
|
+
evidence=[f'Class name contains "Builder": {class_sig.name}'],
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
return None
|
|
986
|
+
|
|
987
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
988
|
+
"""Structural analysis for Builder"""
|
|
989
|
+
evidence = []
|
|
990
|
+
confidence = 0.0
|
|
991
|
+
|
|
992
|
+
# Builder characteristics:
|
|
993
|
+
# 1. Multiple setter/configuration methods
|
|
994
|
+
# 2. Terminal build()/create()/execute() method
|
|
995
|
+
# 3. Fluent interface (methods return self/this)
|
|
996
|
+
|
|
997
|
+
# Check for build/create terminal method
|
|
998
|
+
terminal_methods = ["build", "create", "execute", "construct", "make"]
|
|
999
|
+
has_terminal = any(
|
|
1000
|
+
m.name.lower() in terminal_methods or m.name.lower().startswith("build")
|
|
1001
|
+
for m in class_sig.methods
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
if has_terminal:
|
|
1005
|
+
evidence.append("Has terminal build/create method")
|
|
1006
|
+
confidence += 0.4
|
|
1007
|
+
|
|
1008
|
+
# Check for setter methods (with_, set_, add_)
|
|
1009
|
+
setter_prefixes = ["with", "set", "add", "configure"]
|
|
1010
|
+
setter_count = sum(
|
|
1011
|
+
1
|
|
1012
|
+
for m in class_sig.methods
|
|
1013
|
+
if any(m.name.lower().startswith(prefix) for prefix in setter_prefixes)
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
if setter_count >= 3:
|
|
1017
|
+
evidence.append(f"Has {setter_count} configuration methods")
|
|
1018
|
+
confidence += 0.4
|
|
1019
|
+
elif setter_count >= 1:
|
|
1020
|
+
confidence += 0.2
|
|
1021
|
+
|
|
1022
|
+
# Check method count (builders typically have many methods)
|
|
1023
|
+
if len(class_sig.methods) >= 5:
|
|
1024
|
+
confidence += 0.1
|
|
1025
|
+
|
|
1026
|
+
if confidence >= 0.5:
|
|
1027
|
+
return PatternInstance(
|
|
1028
|
+
pattern_type=self.pattern_type,
|
|
1029
|
+
category=self.category,
|
|
1030
|
+
confidence=min(confidence, 0.9),
|
|
1031
|
+
location="",
|
|
1032
|
+
class_name=class_sig.name,
|
|
1033
|
+
line_number=class_sig.line_number,
|
|
1034
|
+
evidence=evidence,
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
# Fallback to surface
|
|
1038
|
+
return self.detect_surface(class_sig, all_classes)
|
|
1039
|
+
|
|
1040
|
+
def detect_full(
|
|
1041
|
+
self, class_sig, all_classes: list, file_content: str
|
|
1042
|
+
) -> PatternInstance | None:
|
|
1043
|
+
"""Full behavioral analysis for Builder"""
|
|
1044
|
+
# Start with deep detection
|
|
1045
|
+
pattern = self.detect_deep(class_sig, all_classes)
|
|
1046
|
+
if not pattern:
|
|
1047
|
+
return None
|
|
1048
|
+
|
|
1049
|
+
evidence = list(pattern.evidence)
|
|
1050
|
+
confidence = pattern.confidence
|
|
1051
|
+
|
|
1052
|
+
# Look for fluent interface pattern (return self/this)
|
|
1053
|
+
class_content = file_content.lower()
|
|
1054
|
+
fluent_indicators = ["return self", "return this"]
|
|
1055
|
+
|
|
1056
|
+
if any(indicator in class_content for indicator in fluent_indicators):
|
|
1057
|
+
evidence.append("Uses fluent interface (return self)")
|
|
1058
|
+
confidence += 0.1
|
|
1059
|
+
|
|
1060
|
+
# Check for complex object construction (multiple fields)
|
|
1061
|
+
if "self." in class_content and class_content.count("self.") >= 5:
|
|
1062
|
+
evidence.append("Builds complex object with multiple fields")
|
|
1063
|
+
confidence += 0.05
|
|
1064
|
+
|
|
1065
|
+
if confidence >= 0.5:
|
|
1066
|
+
return PatternInstance(
|
|
1067
|
+
pattern_type=self.pattern_type,
|
|
1068
|
+
category=self.category,
|
|
1069
|
+
confidence=min(confidence, 0.95),
|
|
1070
|
+
location="",
|
|
1071
|
+
class_name=class_sig.name,
|
|
1072
|
+
line_number=class_sig.line_number,
|
|
1073
|
+
evidence=evidence,
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
# Fallback to deep
|
|
1077
|
+
return self.detect_deep(class_sig, all_classes)
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
class AdapterDetector(BasePatternDetector):
|
|
1081
|
+
"""
|
|
1082
|
+
Detect Adapter pattern.
|
|
1083
|
+
|
|
1084
|
+
Adapter converts interface of a class into another interface clients expect,
|
|
1085
|
+
allowing incompatible interfaces to work together.
|
|
1086
|
+
|
|
1087
|
+
Detection Heuristics:
|
|
1088
|
+
- Surface: Class name contains 'Adapter', 'Wrapper'
|
|
1089
|
+
- Deep: Wraps external/incompatible class, translates method calls
|
|
1090
|
+
- Full: Composition + delegation with interface translation
|
|
1091
|
+
|
|
1092
|
+
Examples:
|
|
1093
|
+
- DatabaseAdapter wraps external DB library
|
|
1094
|
+
- ApiAdapter translates REST to internal interface
|
|
1095
|
+
- FileSystemAdapter wraps OS file operations
|
|
1096
|
+
"""
|
|
1097
|
+
|
|
1098
|
+
def __init__(self, depth: str = "deep"):
|
|
1099
|
+
super().__init__(depth)
|
|
1100
|
+
self.pattern_type = "Adapter"
|
|
1101
|
+
self.category = "Structural"
|
|
1102
|
+
|
|
1103
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
1104
|
+
"""Check naming for Adapter"""
|
|
1105
|
+
adapter_keywords = ["adapter", "wrapper", "bridge"]
|
|
1106
|
+
|
|
1107
|
+
class_lower = class_sig.name.lower()
|
|
1108
|
+
if any(keyword in class_lower for keyword in adapter_keywords):
|
|
1109
|
+
return PatternInstance(
|
|
1110
|
+
pattern_type=self.pattern_type,
|
|
1111
|
+
category=self.category,
|
|
1112
|
+
confidence=0.7,
|
|
1113
|
+
location="",
|
|
1114
|
+
class_name=class_sig.name,
|
|
1115
|
+
line_number=class_sig.line_number,
|
|
1116
|
+
evidence=[f"Class name suggests Adapter: {class_sig.name}"],
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
return None
|
|
1120
|
+
|
|
1121
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
1122
|
+
"""Structural analysis for Adapter"""
|
|
1123
|
+
evidence = []
|
|
1124
|
+
confidence = 0.0
|
|
1125
|
+
|
|
1126
|
+
# Adapter characteristics:
|
|
1127
|
+
# 1. Takes adaptee in constructor
|
|
1128
|
+
# 2. Implements target interface
|
|
1129
|
+
# 3. Delegates to adaptee with translation
|
|
1130
|
+
|
|
1131
|
+
# Check __init__ for composition (takes adaptee)
|
|
1132
|
+
init_method = next((m for m in class_sig.methods if m.name == "__init__"), None)
|
|
1133
|
+
if init_method and len(init_method.parameters) > 1:
|
|
1134
|
+
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
|
1135
|
+
adaptee_names = ["adaptee", "wrapped", "client", "service", "api", "source"]
|
|
1136
|
+
if any(name in param_names for name in adaptee_names):
|
|
1137
|
+
evidence.append(f"Takes adaptee in constructor: {param_names}")
|
|
1138
|
+
confidence += 0.4
|
|
1139
|
+
|
|
1140
|
+
# Check if implements interface (has base class)
|
|
1141
|
+
if class_sig.base_classes:
|
|
1142
|
+
evidence.append(f"Implements interface: {class_sig.base_classes[0]}")
|
|
1143
|
+
confidence += 0.3
|
|
1144
|
+
|
|
1145
|
+
# Check for delegation methods (methods that likely call adaptee)
|
|
1146
|
+
if len(class_sig.methods) >= 3: # Multiple interface methods
|
|
1147
|
+
evidence.append(f"Has {len(class_sig.methods)} interface methods")
|
|
1148
|
+
confidence += 0.2
|
|
1149
|
+
|
|
1150
|
+
if confidence >= 0.5:
|
|
1151
|
+
return PatternInstance(
|
|
1152
|
+
pattern_type=self.pattern_type,
|
|
1153
|
+
category=self.category,
|
|
1154
|
+
confidence=min(confidence, 0.85),
|
|
1155
|
+
location="",
|
|
1156
|
+
class_name=class_sig.name,
|
|
1157
|
+
line_number=class_sig.line_number,
|
|
1158
|
+
evidence=evidence,
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
# Fallback to surface
|
|
1162
|
+
return self.detect_surface(class_sig, all_classes)
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
class CommandDetector(BasePatternDetector):
|
|
1166
|
+
"""
|
|
1167
|
+
Detect Command pattern.
|
|
1168
|
+
|
|
1169
|
+
Command encapsulates a request as an object, allowing parameterization
|
|
1170
|
+
of clients with different requests, queuing, logging, and undo operations.
|
|
1171
|
+
|
|
1172
|
+
Detection Heuristics:
|
|
1173
|
+
- Surface: Class name contains 'Command', 'Action', 'Task'
|
|
1174
|
+
- Deep: Has execute()/run() method, encapsulates action
|
|
1175
|
+
- Full: Receiver composition + undo support
|
|
1176
|
+
|
|
1177
|
+
Examples:
|
|
1178
|
+
- SaveCommand with execute() method
|
|
1179
|
+
- UndoableCommand with undo() and redo()
|
|
1180
|
+
- TaskCommand in task queue
|
|
1181
|
+
"""
|
|
1182
|
+
|
|
1183
|
+
def __init__(self, depth: str = "deep"):
|
|
1184
|
+
super().__init__(depth)
|
|
1185
|
+
self.pattern_type = "Command"
|
|
1186
|
+
self.category = "Behavioral"
|
|
1187
|
+
|
|
1188
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
1189
|
+
"""Check naming for Command"""
|
|
1190
|
+
command_keywords = ["command", "action", "task", "operation"]
|
|
1191
|
+
|
|
1192
|
+
class_lower = class_sig.name.lower()
|
|
1193
|
+
if any(keyword in class_lower for keyword in command_keywords):
|
|
1194
|
+
return PatternInstance(
|
|
1195
|
+
pattern_type=self.pattern_type,
|
|
1196
|
+
category=self.category,
|
|
1197
|
+
confidence=0.65,
|
|
1198
|
+
location="",
|
|
1199
|
+
class_name=class_sig.name,
|
|
1200
|
+
line_number=class_sig.line_number,
|
|
1201
|
+
evidence=[f"Class name suggests Command: {class_sig.name}"],
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
return None
|
|
1205
|
+
|
|
1206
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
1207
|
+
"""Structural analysis for Command"""
|
|
1208
|
+
evidence = []
|
|
1209
|
+
confidence = 0.0
|
|
1210
|
+
|
|
1211
|
+
# Command characteristics:
|
|
1212
|
+
# 1. Has execute()/run()/call() method
|
|
1213
|
+
# 2. May have undo()/redo() methods
|
|
1214
|
+
# 3. Encapsulates receiver and parameters
|
|
1215
|
+
|
|
1216
|
+
# Check for execute/run method
|
|
1217
|
+
execute_methods = ["execute", "run", "call", "do", "perform", "__call__"]
|
|
1218
|
+
has_execute = any(m.name.lower() in execute_methods for m in class_sig.methods)
|
|
1219
|
+
|
|
1220
|
+
if has_execute:
|
|
1221
|
+
method_name = next(
|
|
1222
|
+
m.name for m in class_sig.methods if m.name.lower() in execute_methods
|
|
1223
|
+
)
|
|
1224
|
+
evidence.append(f"Has execute method: {method_name}()")
|
|
1225
|
+
confidence += 0.5
|
|
1226
|
+
|
|
1227
|
+
# Check for undo/redo support
|
|
1228
|
+
undo_methods = ["undo", "rollback", "revert", "redo"]
|
|
1229
|
+
has_undo = any(m.name.lower() in undo_methods for m in class_sig.methods)
|
|
1230
|
+
|
|
1231
|
+
if has_undo:
|
|
1232
|
+
evidence.append("Supports undo/redo operations")
|
|
1233
|
+
confidence += 0.3
|
|
1234
|
+
|
|
1235
|
+
# Check for receiver (takes object in __init__)
|
|
1236
|
+
init_method = next((m for m in class_sig.methods if m.name == "__init__"), None)
|
|
1237
|
+
if init_method and len(init_method.parameters) > 1:
|
|
1238
|
+
evidence.append("Encapsulates receiver/parameters")
|
|
1239
|
+
confidence += 0.2
|
|
1240
|
+
|
|
1241
|
+
if confidence >= 0.5:
|
|
1242
|
+
return PatternInstance(
|
|
1243
|
+
pattern_type=self.pattern_type,
|
|
1244
|
+
category=self.category,
|
|
1245
|
+
confidence=min(confidence, 0.9),
|
|
1246
|
+
location="",
|
|
1247
|
+
class_name=class_sig.name,
|
|
1248
|
+
line_number=class_sig.line_number,
|
|
1249
|
+
evidence=evidence,
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
# Fallback to surface
|
|
1253
|
+
return self.detect_surface(class_sig, all_classes)
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
class TemplateMethodDetector(BasePatternDetector):
|
|
1257
|
+
"""
|
|
1258
|
+
Detect Template Method pattern.
|
|
1259
|
+
|
|
1260
|
+
Template Method defines skeleton of algorithm in base class,
|
|
1261
|
+
letting subclasses override specific steps without changing structure.
|
|
1262
|
+
|
|
1263
|
+
Detection Heuristics:
|
|
1264
|
+
- Surface: Abstract/Base class with template-like names
|
|
1265
|
+
- Deep: Abstract base with hook methods, concrete subclasses override
|
|
1266
|
+
- Full: Template method calls abstract/hook methods
|
|
1267
|
+
|
|
1268
|
+
Examples:
|
|
1269
|
+
- AbstractProcessor with process() calling abstract steps
|
|
1270
|
+
- BaseParser with parse() template method
|
|
1271
|
+
- Framework base classes with lifecycle hooks
|
|
1272
|
+
"""
|
|
1273
|
+
|
|
1274
|
+
def __init__(self, depth: str = "deep"):
|
|
1275
|
+
super().__init__(depth)
|
|
1276
|
+
self.pattern_type = "TemplateMethod"
|
|
1277
|
+
self.category = "Behavioral"
|
|
1278
|
+
|
|
1279
|
+
def detect_surface(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
1280
|
+
"""Check naming for Template Method"""
|
|
1281
|
+
template_keywords = ["abstract", "base", "template"]
|
|
1282
|
+
|
|
1283
|
+
class_lower = class_sig.name.lower()
|
|
1284
|
+
if any(keyword in class_lower for keyword in template_keywords):
|
|
1285
|
+
# Check if has subclasses
|
|
1286
|
+
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
|
1287
|
+
|
|
1288
|
+
if subclasses:
|
|
1289
|
+
return PatternInstance(
|
|
1290
|
+
pattern_type=self.pattern_type,
|
|
1291
|
+
category=self.category,
|
|
1292
|
+
confidence=0.6,
|
|
1293
|
+
location="",
|
|
1294
|
+
class_name=class_sig.name,
|
|
1295
|
+
line_number=class_sig.line_number,
|
|
1296
|
+
evidence=[f"Abstract base with subclasses: {', '.join(subclasses[:2])}"],
|
|
1297
|
+
related_classes=subclasses,
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
return None
|
|
1301
|
+
|
|
1302
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
1303
|
+
"""Structural analysis for Template Method"""
|
|
1304
|
+
evidence = []
|
|
1305
|
+
confidence = 0.0
|
|
1306
|
+
|
|
1307
|
+
# Template Method characteristics:
|
|
1308
|
+
# 1. Has subclasses (is base class)
|
|
1309
|
+
# 2. Has methods that look like hooks (prepare, validate, cleanup, etc.)
|
|
1310
|
+
# 3. Has template method that orchestrates
|
|
1311
|
+
|
|
1312
|
+
# Check for subclasses
|
|
1313
|
+
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
|
1314
|
+
|
|
1315
|
+
if len(subclasses) >= 1:
|
|
1316
|
+
evidence.append(f"Base class with {len(subclasses)} implementations")
|
|
1317
|
+
confidence += 0.4
|
|
1318
|
+
|
|
1319
|
+
# Check for hook-like method names
|
|
1320
|
+
hook_keywords = [
|
|
1321
|
+
"prepare",
|
|
1322
|
+
"initialize",
|
|
1323
|
+
"validate",
|
|
1324
|
+
"process",
|
|
1325
|
+
"finalize",
|
|
1326
|
+
"setup",
|
|
1327
|
+
"teardown",
|
|
1328
|
+
"before",
|
|
1329
|
+
"after",
|
|
1330
|
+
"pre",
|
|
1331
|
+
"post",
|
|
1332
|
+
"hook",
|
|
1333
|
+
]
|
|
1334
|
+
|
|
1335
|
+
hook_methods = [
|
|
1336
|
+
m.name
|
|
1337
|
+
for m in class_sig.methods
|
|
1338
|
+
if any(keyword in m.name.lower() for keyword in hook_keywords)
|
|
1339
|
+
]
|
|
1340
|
+
|
|
1341
|
+
if len(hook_methods) >= 2:
|
|
1342
|
+
evidence.append(f"Has hook methods: {', '.join(hook_methods[:3])}")
|
|
1343
|
+
confidence += 0.3
|
|
1344
|
+
|
|
1345
|
+
# Check for abstract methods (no implementation or pass/raise)
|
|
1346
|
+
abstract_methods = [
|
|
1347
|
+
m.name
|
|
1348
|
+
for m in class_sig.methods
|
|
1349
|
+
if m.name.startswith("_") or "abstract" in m.name.lower()
|
|
1350
|
+
]
|
|
1351
|
+
|
|
1352
|
+
if abstract_methods:
|
|
1353
|
+
evidence.append(f"Has abstract methods: {', '.join(abstract_methods[:2])}")
|
|
1354
|
+
confidence += 0.2
|
|
1355
|
+
|
|
1356
|
+
if confidence >= 0.5:
|
|
1357
|
+
return PatternInstance(
|
|
1358
|
+
pattern_type=self.pattern_type,
|
|
1359
|
+
category=self.category,
|
|
1360
|
+
confidence=min(confidence, 0.85),
|
|
1361
|
+
location="",
|
|
1362
|
+
class_name=class_sig.name,
|
|
1363
|
+
line_number=class_sig.line_number,
|
|
1364
|
+
evidence=evidence,
|
|
1365
|
+
related_classes=subclasses,
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
# Fallback to surface
|
|
1369
|
+
return self.detect_surface(class_sig, all_classes)
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
class ChainOfResponsibilityDetector(BasePatternDetector):
|
|
1373
|
+
"""
|
|
1374
|
+
Detect Chain of Responsibility pattern.
|
|
1375
|
+
|
|
1376
|
+
Chain of Responsibility passes request along chain of handlers until
|
|
1377
|
+
one handles it, avoiding coupling sender to receiver.
|
|
1378
|
+
|
|
1379
|
+
Detection Heuristics:
|
|
1380
|
+
- Surface: Class name contains 'Handler', 'Chain', 'Middleware'
|
|
1381
|
+
- Deep: Has next/successor reference, handle() method
|
|
1382
|
+
- Full: Chain traversal logic, request passing
|
|
1383
|
+
|
|
1384
|
+
Examples:
|
|
1385
|
+
- LogHandler with next handler
|
|
1386
|
+
- AuthMiddleware chain
|
|
1387
|
+
- EventHandler chain
|
|
1388
|
+
"""
|
|
1389
|
+
|
|
1390
|
+
def __init__(self, depth: str = "deep"):
|
|
1391
|
+
super().__init__(depth)
|
|
1392
|
+
self.pattern_type = "ChainOfResponsibility"
|
|
1393
|
+
self.category = "Behavioral"
|
|
1394
|
+
|
|
1395
|
+
def detect_surface(self, class_sig, _all_classes: list) -> PatternInstance | None:
|
|
1396
|
+
"""Check naming for Chain of Responsibility"""
|
|
1397
|
+
chain_keywords = ["handler", "chain", "middleware", "filter", "processor"]
|
|
1398
|
+
|
|
1399
|
+
class_lower = class_sig.name.lower()
|
|
1400
|
+
if any(keyword in class_lower for keyword in chain_keywords):
|
|
1401
|
+
return PatternInstance(
|
|
1402
|
+
pattern_type=self.pattern_type,
|
|
1403
|
+
category=self.category,
|
|
1404
|
+
confidence=0.6,
|
|
1405
|
+
location="",
|
|
1406
|
+
class_name=class_sig.name,
|
|
1407
|
+
line_number=class_sig.line_number,
|
|
1408
|
+
evidence=[f"Class name suggests handler chain: {class_sig.name}"],
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
return None
|
|
1412
|
+
|
|
1413
|
+
def detect_deep(self, class_sig, all_classes: list) -> PatternInstance | None:
|
|
1414
|
+
"""Structural analysis for Chain of Responsibility"""
|
|
1415
|
+
evidence = []
|
|
1416
|
+
confidence = 0.0
|
|
1417
|
+
|
|
1418
|
+
# Chain of Responsibility characteristics:
|
|
1419
|
+
# 1. Has handle()/process() method
|
|
1420
|
+
# 2. Has next/successor reference
|
|
1421
|
+
# 3. May have set_next() method
|
|
1422
|
+
|
|
1423
|
+
# Check for handle/process method
|
|
1424
|
+
handle_methods = ["handle", "process", "execute", "filter", "middleware"]
|
|
1425
|
+
has_handle = any(
|
|
1426
|
+
m.name.lower() in handle_methods or m.name.lower().startswith("handle")
|
|
1427
|
+
for m in class_sig.methods
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
if has_handle:
|
|
1431
|
+
evidence.append("Has handle/process method")
|
|
1432
|
+
confidence += 0.4
|
|
1433
|
+
|
|
1434
|
+
# Check for next/successor methods or parameters
|
|
1435
|
+
init_method = next((m for m in class_sig.methods if m.name == "__init__"), None)
|
|
1436
|
+
has_next_ref = False
|
|
1437
|
+
|
|
1438
|
+
if init_method:
|
|
1439
|
+
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
|
1440
|
+
next_names = ["next", "successor", "next_handler", "next_middleware"]
|
|
1441
|
+
|
|
1442
|
+
if any(name in param_names for name in next_names):
|
|
1443
|
+
evidence.append("Takes next handler in chain")
|
|
1444
|
+
confidence += 0.3
|
|
1445
|
+
has_next_ref = True
|
|
1446
|
+
|
|
1447
|
+
# Check for set_next() method
|
|
1448
|
+
has_set_next = any(
|
|
1449
|
+
"next" in m.name.lower() and ("set" in m.name.lower() or "add" in m.name.lower())
|
|
1450
|
+
for m in class_sig.methods
|
|
1451
|
+
)
|
|
1452
|
+
|
|
1453
|
+
if has_set_next:
|
|
1454
|
+
evidence.append("Has set_next() method")
|
|
1455
|
+
confidence += 0.3
|
|
1456
|
+
has_next_ref = True
|
|
1457
|
+
|
|
1458
|
+
# Check if part of handler family (shares base class)
|
|
1459
|
+
if class_sig.base_classes:
|
|
1460
|
+
base_class = class_sig.base_classes[0]
|
|
1461
|
+
siblings = [
|
|
1462
|
+
cls.name
|
|
1463
|
+
for cls in all_classes
|
|
1464
|
+
if cls.base_classes
|
|
1465
|
+
and base_class in cls.base_classes
|
|
1466
|
+
and cls.name != class_sig.name
|
|
1467
|
+
]
|
|
1468
|
+
|
|
1469
|
+
if siblings and has_next_ref:
|
|
1470
|
+
evidence.append(f"Part of handler chain with: {', '.join(siblings[:2])}")
|
|
1471
|
+
confidence += 0.2
|
|
1472
|
+
|
|
1473
|
+
if confidence >= 0.5:
|
|
1474
|
+
return PatternInstance(
|
|
1475
|
+
pattern_type=self.pattern_type,
|
|
1476
|
+
category=self.category,
|
|
1477
|
+
confidence=min(confidence, 0.9),
|
|
1478
|
+
location="",
|
|
1479
|
+
class_name=class_sig.name,
|
|
1480
|
+
line_number=class_sig.line_number,
|
|
1481
|
+
evidence=evidence,
|
|
1482
|
+
)
|
|
1483
|
+
|
|
1484
|
+
# Fallback to surface
|
|
1485
|
+
return self.detect_surface(class_sig, all_classes)
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
class LanguageAdapter:
|
|
1489
|
+
"""
|
|
1490
|
+
Language-specific pattern detection adaptations.
|
|
1491
|
+
|
|
1492
|
+
Adjusts pattern confidence based on language idioms and conventions.
|
|
1493
|
+
Different languages have different ways of implementing patterns.
|
|
1494
|
+
"""
|
|
1495
|
+
|
|
1496
|
+
@staticmethod
|
|
1497
|
+
def adapt_for_language(pattern: PatternInstance, language: str) -> PatternInstance:
|
|
1498
|
+
"""
|
|
1499
|
+
Adjust confidence based on language-specific idioms.
|
|
1500
|
+
|
|
1501
|
+
Args:
|
|
1502
|
+
pattern: Detected pattern instance
|
|
1503
|
+
language: Programming language
|
|
1504
|
+
|
|
1505
|
+
Returns:
|
|
1506
|
+
Adjusted pattern instance with language-specific confidence
|
|
1507
|
+
"""
|
|
1508
|
+
if not pattern:
|
|
1509
|
+
return pattern
|
|
1510
|
+
|
|
1511
|
+
evidence_str = " ".join(pattern.evidence).lower()
|
|
1512
|
+
|
|
1513
|
+
# Python-specific adaptations
|
|
1514
|
+
if language == "Python":
|
|
1515
|
+
# Decorator pattern: Python has native @ syntax
|
|
1516
|
+
if pattern.pattern_type == "Decorator":
|
|
1517
|
+
if "@" in " ".join(pattern.evidence):
|
|
1518
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1519
|
+
pattern.evidence.append("Python @decorator syntax detected")
|
|
1520
|
+
|
|
1521
|
+
# Singleton: __new__ method is Python idiom
|
|
1522
|
+
elif pattern.pattern_type == "Singleton":
|
|
1523
|
+
if "__new__" in evidence_str:
|
|
1524
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1525
|
+
|
|
1526
|
+
# Strategy: Duck typing common in Python
|
|
1527
|
+
elif (
|
|
1528
|
+
pattern.pattern_type == "Strategy"
|
|
1529
|
+
and "duck typing" in evidence_str
|
|
1530
|
+
or "protocol" in evidence_str
|
|
1531
|
+
):
|
|
1532
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1533
|
+
|
|
1534
|
+
# JavaScript/TypeScript adaptations
|
|
1535
|
+
elif language in ["JavaScript", "TypeScript"]:
|
|
1536
|
+
# Singleton: Module pattern is common
|
|
1537
|
+
if pattern.pattern_type == "Singleton":
|
|
1538
|
+
if "module" in evidence_str or "export default" in evidence_str:
|
|
1539
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1540
|
+
pattern.evidence.append("JavaScript module pattern")
|
|
1541
|
+
|
|
1542
|
+
# Factory: Factory functions are idiomatic
|
|
1543
|
+
elif pattern.pattern_type == "Factory":
|
|
1544
|
+
if "create" in evidence_str or "make" in evidence_str:
|
|
1545
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1546
|
+
|
|
1547
|
+
# Observer: Event emitters are built-in
|
|
1548
|
+
elif (
|
|
1549
|
+
pattern.pattern_type == "Observer"
|
|
1550
|
+
and "eventemitter" in evidence_str
|
|
1551
|
+
or "event" in evidence_str
|
|
1552
|
+
):
|
|
1553
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1554
|
+
pattern.evidence.append("EventEmitter pattern detected")
|
|
1555
|
+
|
|
1556
|
+
# Java/C# adaptations (interface-heavy languages)
|
|
1557
|
+
elif language in ["Java", "C#"]:
|
|
1558
|
+
# All patterns: Interfaces are explicit
|
|
1559
|
+
if "interface" in evidence_str:
|
|
1560
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1561
|
+
|
|
1562
|
+
# Factory: Abstract Factory common
|
|
1563
|
+
if pattern.pattern_type == "Factory":
|
|
1564
|
+
if "abstract" in evidence_str:
|
|
1565
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1566
|
+
pattern.evidence.append("Abstract Factory pattern")
|
|
1567
|
+
|
|
1568
|
+
# Template Method: Abstract classes common
|
|
1569
|
+
elif pattern.pattern_type == "TemplateMethod" and "abstract" in evidence_str:
|
|
1570
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1571
|
+
|
|
1572
|
+
# Go adaptations
|
|
1573
|
+
elif language == "Go":
|
|
1574
|
+
# Singleton: sync.Once is idiomatic
|
|
1575
|
+
if pattern.pattern_type == "Singleton":
|
|
1576
|
+
if "sync.once" in evidence_str or "once.do" in evidence_str:
|
|
1577
|
+
pattern.confidence = min(pattern.confidence + 0.15, 1.0)
|
|
1578
|
+
pattern.evidence.append("Go sync.Once idiom")
|
|
1579
|
+
|
|
1580
|
+
# Strategy: Interfaces are implicit
|
|
1581
|
+
elif pattern.pattern_type == "Strategy" and "interface{}" in evidence_str:
|
|
1582
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1583
|
+
|
|
1584
|
+
# Rust adaptations
|
|
1585
|
+
elif language == "Rust":
|
|
1586
|
+
# Singleton: Lazy static is common
|
|
1587
|
+
if pattern.pattern_type == "Singleton":
|
|
1588
|
+
if "lazy_static" in evidence_str or "oncecell" in evidence_str:
|
|
1589
|
+
pattern.confidence = min(pattern.confidence + 0.15, 1.0)
|
|
1590
|
+
pattern.evidence.append("Rust lazy_static/OnceCell")
|
|
1591
|
+
|
|
1592
|
+
# Builder: Derive builder is idiomatic
|
|
1593
|
+
elif pattern.pattern_type == "Builder":
|
|
1594
|
+
if "derive" in evidence_str and "builder" in evidence_str:
|
|
1595
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1596
|
+
|
|
1597
|
+
# Adapter: Trait adapters are common
|
|
1598
|
+
elif pattern.pattern_type == "Adapter" and "trait" in evidence_str:
|
|
1599
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1600
|
+
|
|
1601
|
+
# C++ adaptations
|
|
1602
|
+
elif language == "C++":
|
|
1603
|
+
# Singleton: Meyer's Singleton is idiomatic
|
|
1604
|
+
if pattern.pattern_type == "Singleton":
|
|
1605
|
+
if "static" in evidence_str and "local" in evidence_str:
|
|
1606
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1607
|
+
pattern.evidence.append("Meyer's Singleton (static local)")
|
|
1608
|
+
|
|
1609
|
+
# Factory: Template-based factories
|
|
1610
|
+
elif pattern.pattern_type == "Factory" and "template" in evidence_str:
|
|
1611
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1612
|
+
|
|
1613
|
+
# Ruby adaptations
|
|
1614
|
+
elif language == "Ruby":
|
|
1615
|
+
# Singleton: Ruby has Singleton module
|
|
1616
|
+
if pattern.pattern_type == "Singleton":
|
|
1617
|
+
if "include singleton" in evidence_str:
|
|
1618
|
+
pattern.confidence = min(pattern.confidence + 0.2, 1.0)
|
|
1619
|
+
pattern.evidence.append("Ruby Singleton module")
|
|
1620
|
+
|
|
1621
|
+
# Builder: Method chaining is idiomatic
|
|
1622
|
+
elif pattern.pattern_type == "Builder" and "method chaining" in evidence_str:
|
|
1623
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1624
|
+
|
|
1625
|
+
# PHP adaptations
|
|
1626
|
+
elif language == "PHP":
|
|
1627
|
+
# Singleton: Private constructor is common
|
|
1628
|
+
if pattern.pattern_type == "Singleton":
|
|
1629
|
+
if "private" in evidence_str and "__construct" in evidence_str:
|
|
1630
|
+
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
|
1631
|
+
|
|
1632
|
+
# Factory: Static factory methods
|
|
1633
|
+
elif pattern.pattern_type == "Factory" and "static" in evidence_str:
|
|
1634
|
+
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
|
1635
|
+
|
|
1636
|
+
return pattern
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
def main():
|
|
1640
|
+
"""
|
|
1641
|
+
CLI entry point for pattern detection.
|
|
1642
|
+
|
|
1643
|
+
Usage:
|
|
1644
|
+
skill-seekers-patterns --file src/database.py
|
|
1645
|
+
skill-seekers-patterns --directory src/ --output patterns/
|
|
1646
|
+
skill-seekers-patterns --file app.py --depth full --json
|
|
1647
|
+
"""
|
|
1648
|
+
import sys
|
|
1649
|
+
|
|
1650
|
+
parser = argparse.ArgumentParser(
|
|
1651
|
+
description="Detect design patterns in source code",
|
|
1652
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1653
|
+
epilog="""
|
|
1654
|
+
Examples:
|
|
1655
|
+
# Analyze single file
|
|
1656
|
+
skill-seekers-patterns --file src/database.py
|
|
1657
|
+
|
|
1658
|
+
# Analyze directory
|
|
1659
|
+
skill-seekers-patterns --directory src/ --output patterns/
|
|
1660
|
+
|
|
1661
|
+
# Full analysis with JSON output
|
|
1662
|
+
skill-seekers-patterns --file app.py --depth full --json
|
|
1663
|
+
|
|
1664
|
+
# Multiple files
|
|
1665
|
+
skill-seekers-patterns --file src/db.py --file src/api.py
|
|
1666
|
+
|
|
1667
|
+
Supported Languages:
|
|
1668
|
+
Python, JavaScript, TypeScript, C++, C, C#, Go, Rust, Java, Ruby, PHP
|
|
1669
|
+
""",
|
|
1670
|
+
)
|
|
1671
|
+
|
|
1672
|
+
parser.add_argument(
|
|
1673
|
+
"--file",
|
|
1674
|
+
action="append",
|
|
1675
|
+
help="Source file to analyze (can be specified multiple times)",
|
|
1676
|
+
)
|
|
1677
|
+
parser.add_argument("--directory", help="Directory to analyze (analyzes all source files)")
|
|
1678
|
+
parser.add_argument(
|
|
1679
|
+
"--output", help="Output directory for results (default: current directory)"
|
|
1680
|
+
)
|
|
1681
|
+
parser.add_argument(
|
|
1682
|
+
"--depth",
|
|
1683
|
+
choices=["surface", "deep", "full"],
|
|
1684
|
+
default="deep",
|
|
1685
|
+
help="Detection depth: surface (fast), deep (default), full (thorough)",
|
|
1686
|
+
)
|
|
1687
|
+
parser.add_argument(
|
|
1688
|
+
"--json",
|
|
1689
|
+
action="store_true",
|
|
1690
|
+
help="Output JSON format instead of human-readable",
|
|
1691
|
+
)
|
|
1692
|
+
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
|
|
1693
|
+
|
|
1694
|
+
args = parser.parse_args()
|
|
1695
|
+
|
|
1696
|
+
# Validate inputs
|
|
1697
|
+
if not args.file and not args.directory:
|
|
1698
|
+
parser.error("Must specify either --file or --directory")
|
|
1699
|
+
|
|
1700
|
+
# Create recognizer
|
|
1701
|
+
recognizer = PatternRecognizer(depth=args.depth)
|
|
1702
|
+
|
|
1703
|
+
# Collect files to analyze
|
|
1704
|
+
files_to_analyze = []
|
|
1705
|
+
|
|
1706
|
+
if args.file:
|
|
1707
|
+
for file_path in args.file:
|
|
1708
|
+
path = Path(file_path)
|
|
1709
|
+
if not path.exists():
|
|
1710
|
+
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
|
1711
|
+
return 1
|
|
1712
|
+
files_to_analyze.append(path)
|
|
1713
|
+
|
|
1714
|
+
if args.directory:
|
|
1715
|
+
from skill_seekers.cli.codebase_scraper import detect_language, walk_directory
|
|
1716
|
+
|
|
1717
|
+
directory = Path(args.directory)
|
|
1718
|
+
if not directory.exists():
|
|
1719
|
+
print(f"Error: Directory not found: {args.directory}", file=sys.stderr)
|
|
1720
|
+
return 1
|
|
1721
|
+
|
|
1722
|
+
# Walk directory for source files
|
|
1723
|
+
files_to_analyze.extend(walk_directory(directory))
|
|
1724
|
+
|
|
1725
|
+
if not files_to_analyze:
|
|
1726
|
+
print("No source files found to analyze", file=sys.stderr)
|
|
1727
|
+
return 1
|
|
1728
|
+
|
|
1729
|
+
# Analyze files
|
|
1730
|
+
all_reports = []
|
|
1731
|
+
total_patterns = 0
|
|
1732
|
+
|
|
1733
|
+
for file_path in files_to_analyze:
|
|
1734
|
+
try:
|
|
1735
|
+
from skill_seekers.cli.codebase_scraper import detect_language
|
|
1736
|
+
|
|
1737
|
+
content = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
1738
|
+
language = detect_language(file_path)
|
|
1739
|
+
|
|
1740
|
+
if language == "Unknown":
|
|
1741
|
+
if args.verbose:
|
|
1742
|
+
print(f"Skipping {file_path}: Unknown language")
|
|
1743
|
+
continue
|
|
1744
|
+
|
|
1745
|
+
report = recognizer.analyze_file(str(file_path), content, language)
|
|
1746
|
+
|
|
1747
|
+
if report.patterns:
|
|
1748
|
+
all_reports.append(report)
|
|
1749
|
+
total_patterns += len(report.patterns)
|
|
1750
|
+
|
|
1751
|
+
if not args.json and args.verbose:
|
|
1752
|
+
print(f"\n{file_path}:")
|
|
1753
|
+
for pattern in report.patterns:
|
|
1754
|
+
print(
|
|
1755
|
+
f" [{pattern.pattern_type}] {pattern.class_name} (confidence: {pattern.confidence:.2f})"
|
|
1756
|
+
)
|
|
1757
|
+
|
|
1758
|
+
except Exception as e:
|
|
1759
|
+
if args.verbose:
|
|
1760
|
+
print(f"Error analyzing {file_path}: {e}", file=sys.stderr)
|
|
1761
|
+
continue
|
|
1762
|
+
|
|
1763
|
+
# Output results
|
|
1764
|
+
if args.json:
|
|
1765
|
+
# JSON output
|
|
1766
|
+
output_data = {
|
|
1767
|
+
"total_files_analyzed": len(files_to_analyze),
|
|
1768
|
+
"files_with_patterns": len(all_reports),
|
|
1769
|
+
"total_patterns_detected": total_patterns,
|
|
1770
|
+
"reports": [report.to_dict() for report in all_reports],
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
if args.output:
|
|
1774
|
+
output_path = Path(args.output) / "detected_patterns.json"
|
|
1775
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1776
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
1777
|
+
json.dump(output_data, f, indent=2)
|
|
1778
|
+
print(f"Results saved to: {output_path}")
|
|
1779
|
+
else:
|
|
1780
|
+
print(json.dumps(output_data, indent=2))
|
|
1781
|
+
|
|
1782
|
+
else:
|
|
1783
|
+
# Human-readable output
|
|
1784
|
+
print(f"\n{'=' * 60}")
|
|
1785
|
+
print("PATTERN DETECTION RESULTS")
|
|
1786
|
+
print(f"{'=' * 60}")
|
|
1787
|
+
print(f"Files analyzed: {len(files_to_analyze)}")
|
|
1788
|
+
print(f"Files with patterns: {len(all_reports)}")
|
|
1789
|
+
print(f"Total patterns detected: {total_patterns}")
|
|
1790
|
+
print(f"{'=' * 60}\n")
|
|
1791
|
+
|
|
1792
|
+
# Pattern summary by type
|
|
1793
|
+
pattern_counts = {}
|
|
1794
|
+
for report in all_reports:
|
|
1795
|
+
for pattern in report.patterns:
|
|
1796
|
+
pattern_counts[pattern.pattern_type] = (
|
|
1797
|
+
pattern_counts.get(pattern.pattern_type, 0) + 1
|
|
1798
|
+
)
|
|
1799
|
+
|
|
1800
|
+
if pattern_counts:
|
|
1801
|
+
print("Pattern Summary:")
|
|
1802
|
+
for pattern_type, count in sorted(
|
|
1803
|
+
pattern_counts.items(), key=lambda x: x[1], reverse=True
|
|
1804
|
+
):
|
|
1805
|
+
print(f" {pattern_type}: {count}")
|
|
1806
|
+
print()
|
|
1807
|
+
|
|
1808
|
+
# Detailed results
|
|
1809
|
+
if all_reports:
|
|
1810
|
+
print("Detected Patterns:\n")
|
|
1811
|
+
for report in all_reports:
|
|
1812
|
+
print(f"{report.file_path}:")
|
|
1813
|
+
for pattern in report.patterns:
|
|
1814
|
+
print(f" • {pattern.pattern_type} - {pattern.class_name}")
|
|
1815
|
+
print(f" Confidence: {pattern.confidence:.2f}")
|
|
1816
|
+
print(f" Category: {pattern.category}")
|
|
1817
|
+
if pattern.evidence:
|
|
1818
|
+
print(f" Evidence: {pattern.evidence[0]}")
|
|
1819
|
+
print()
|
|
1820
|
+
|
|
1821
|
+
return 0
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
if __name__ == "__main__":
|
|
1825
|
+
sys.exit(main())
|