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.
Files changed (79) hide show
  1. skill_seekers/__init__.py +22 -0
  2. skill_seekers/cli/__init__.py +39 -0
  3. skill_seekers/cli/adaptors/__init__.py +120 -0
  4. skill_seekers/cli/adaptors/base.py +221 -0
  5. skill_seekers/cli/adaptors/claude.py +485 -0
  6. skill_seekers/cli/adaptors/gemini.py +453 -0
  7. skill_seekers/cli/adaptors/markdown.py +269 -0
  8. skill_seekers/cli/adaptors/openai.py +503 -0
  9. skill_seekers/cli/ai_enhancer.py +310 -0
  10. skill_seekers/cli/api_reference_builder.py +373 -0
  11. skill_seekers/cli/architectural_pattern_detector.py +525 -0
  12. skill_seekers/cli/code_analyzer.py +1462 -0
  13. skill_seekers/cli/codebase_scraper.py +1225 -0
  14. skill_seekers/cli/config_command.py +563 -0
  15. skill_seekers/cli/config_enhancer.py +431 -0
  16. skill_seekers/cli/config_extractor.py +871 -0
  17. skill_seekers/cli/config_manager.py +452 -0
  18. skill_seekers/cli/config_validator.py +394 -0
  19. skill_seekers/cli/conflict_detector.py +528 -0
  20. skill_seekers/cli/constants.py +72 -0
  21. skill_seekers/cli/dependency_analyzer.py +757 -0
  22. skill_seekers/cli/doc_scraper.py +2332 -0
  23. skill_seekers/cli/enhance_skill.py +488 -0
  24. skill_seekers/cli/enhance_skill_local.py +1096 -0
  25. skill_seekers/cli/enhance_status.py +194 -0
  26. skill_seekers/cli/estimate_pages.py +433 -0
  27. skill_seekers/cli/generate_router.py +1209 -0
  28. skill_seekers/cli/github_fetcher.py +534 -0
  29. skill_seekers/cli/github_scraper.py +1466 -0
  30. skill_seekers/cli/guide_enhancer.py +723 -0
  31. skill_seekers/cli/how_to_guide_builder.py +1267 -0
  32. skill_seekers/cli/install_agent.py +461 -0
  33. skill_seekers/cli/install_skill.py +178 -0
  34. skill_seekers/cli/language_detector.py +614 -0
  35. skill_seekers/cli/llms_txt_detector.py +60 -0
  36. skill_seekers/cli/llms_txt_downloader.py +104 -0
  37. skill_seekers/cli/llms_txt_parser.py +150 -0
  38. skill_seekers/cli/main.py +558 -0
  39. skill_seekers/cli/markdown_cleaner.py +132 -0
  40. skill_seekers/cli/merge_sources.py +806 -0
  41. skill_seekers/cli/package_multi.py +77 -0
  42. skill_seekers/cli/package_skill.py +241 -0
  43. skill_seekers/cli/pattern_recognizer.py +1825 -0
  44. skill_seekers/cli/pdf_extractor_poc.py +1166 -0
  45. skill_seekers/cli/pdf_scraper.py +617 -0
  46. skill_seekers/cli/quality_checker.py +519 -0
  47. skill_seekers/cli/rate_limit_handler.py +438 -0
  48. skill_seekers/cli/resume_command.py +160 -0
  49. skill_seekers/cli/run_tests.py +230 -0
  50. skill_seekers/cli/setup_wizard.py +93 -0
  51. skill_seekers/cli/split_config.py +390 -0
  52. skill_seekers/cli/swift_patterns.py +560 -0
  53. skill_seekers/cli/test_example_extractor.py +1081 -0
  54. skill_seekers/cli/test_unified_simple.py +179 -0
  55. skill_seekers/cli/unified_codebase_analyzer.py +572 -0
  56. skill_seekers/cli/unified_scraper.py +932 -0
  57. skill_seekers/cli/unified_skill_builder.py +1605 -0
  58. skill_seekers/cli/upload_skill.py +162 -0
  59. skill_seekers/cli/utils.py +432 -0
  60. skill_seekers/mcp/__init__.py +33 -0
  61. skill_seekers/mcp/agent_detector.py +316 -0
  62. skill_seekers/mcp/git_repo.py +273 -0
  63. skill_seekers/mcp/server.py +231 -0
  64. skill_seekers/mcp/server_fastmcp.py +1249 -0
  65. skill_seekers/mcp/server_legacy.py +2302 -0
  66. skill_seekers/mcp/source_manager.py +285 -0
  67. skill_seekers/mcp/tools/__init__.py +115 -0
  68. skill_seekers/mcp/tools/config_tools.py +251 -0
  69. skill_seekers/mcp/tools/packaging_tools.py +826 -0
  70. skill_seekers/mcp/tools/scraping_tools.py +842 -0
  71. skill_seekers/mcp/tools/source_tools.py +828 -0
  72. skill_seekers/mcp/tools/splitting_tools.py +212 -0
  73. skill_seekers/py.typed +0 -0
  74. skill_seekers-2.7.3.dist-info/METADATA +2027 -0
  75. skill_seekers-2.7.3.dist-info/RECORD +79 -0
  76. skill_seekers-2.7.3.dist-info/WHEEL +5 -0
  77. skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
  78. skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
  79. 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())