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,525 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Architectural Pattern Detection (C3.7)
|
|
4
|
+
|
|
5
|
+
Detects high-level architectural patterns by analyzing multi-file relationships,
|
|
6
|
+
directory structures, and framework conventions.
|
|
7
|
+
|
|
8
|
+
Detected Patterns:
|
|
9
|
+
- MVC (Model-View-Controller)
|
|
10
|
+
- MVVM (Model-View-ViewModel)
|
|
11
|
+
- MVP (Model-View-Presenter)
|
|
12
|
+
- Repository Pattern
|
|
13
|
+
- Service Layer Pattern
|
|
14
|
+
- Layered Architecture (3-tier, N-tier)
|
|
15
|
+
- Clean Architecture
|
|
16
|
+
- Hexagonal/Ports & Adapters
|
|
17
|
+
|
|
18
|
+
Credits:
|
|
19
|
+
- Architectural pattern definitions from industry standards
|
|
20
|
+
- Framework detection based on official documentation
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ArchitecturalPattern:
|
|
33
|
+
"""Detected architectural pattern"""
|
|
34
|
+
|
|
35
|
+
pattern_name: str # e.g., "MVC", "MVVM", "Repository"
|
|
36
|
+
confidence: float # 0.0-1.0
|
|
37
|
+
evidence: list[str] # List of evidence supporting detection
|
|
38
|
+
components: dict[str, list[str]] # Component type -> file paths
|
|
39
|
+
framework: str | None = None # Detected framework (Django, Spring, etc.)
|
|
40
|
+
description: str = "" # Human-readable description
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ArchitecturalReport:
|
|
45
|
+
"""Complete architectural analysis report"""
|
|
46
|
+
|
|
47
|
+
patterns: list[ArchitecturalPattern]
|
|
48
|
+
directory_structure: dict[str, int] # Directory name -> file count
|
|
49
|
+
total_files_analyzed: int
|
|
50
|
+
frameworks_detected: list[str]
|
|
51
|
+
ai_analysis: dict | None = None # AI enhancement (C3.6 integration)
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict:
|
|
54
|
+
"""Export to dictionary"""
|
|
55
|
+
return {
|
|
56
|
+
"patterns": [
|
|
57
|
+
{
|
|
58
|
+
"pattern_name": p.pattern_name,
|
|
59
|
+
"confidence": p.confidence,
|
|
60
|
+
"evidence": p.evidence,
|
|
61
|
+
"components": p.components,
|
|
62
|
+
"framework": p.framework,
|
|
63
|
+
"description": p.description,
|
|
64
|
+
}
|
|
65
|
+
for p in self.patterns
|
|
66
|
+
],
|
|
67
|
+
"directory_structure": self.directory_structure,
|
|
68
|
+
"total_files_analyzed": self.total_files_analyzed,
|
|
69
|
+
"frameworks_detected": self.frameworks_detected,
|
|
70
|
+
"ai_analysis": self.ai_analysis,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ArchitecturalPatternDetector:
|
|
75
|
+
"""
|
|
76
|
+
Detect high-level architectural patterns.
|
|
77
|
+
|
|
78
|
+
Analyzes entire codebase structure, not individual files.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Common directory patterns for architectures
|
|
82
|
+
MVC_DIRS = {"models", "views", "controllers", "model", "view", "controller"}
|
|
83
|
+
MVVM_DIRS = {"models", "views", "viewmodels", "viewmodel"}
|
|
84
|
+
LAYERED_DIRS = {"presentation", "business", "data", "dal", "bll", "ui"}
|
|
85
|
+
CLEAN_ARCH_DIRS = {"domain", "application", "infrastructure", "presentation"}
|
|
86
|
+
REPO_DIRS = {"repositories", "repository"}
|
|
87
|
+
SERVICE_DIRS = {"services", "service"}
|
|
88
|
+
|
|
89
|
+
# Framework detection patterns
|
|
90
|
+
FRAMEWORK_MARKERS = {
|
|
91
|
+
"Django": ["django", "manage.py", "settings.py", "urls.py"],
|
|
92
|
+
"Flask": ["flask", "app.py", "wsgi.py"],
|
|
93
|
+
"Spring": ["springframework", "@Controller", "@Service", "@Repository"],
|
|
94
|
+
"ASP.NET": ["Controllers", "Models", "Views", ".cshtml", "Startup.cs"],
|
|
95
|
+
"Rails": ["app/models", "app/views", "app/controllers", "config/routes.rb"],
|
|
96
|
+
"Angular": ["app.module.ts", "@Component", "@Injectable", "angular.json"],
|
|
97
|
+
"React": ["package.json", "react", "components"],
|
|
98
|
+
"Vue.js": ["vue", ".vue", "components"],
|
|
99
|
+
"Express": ["express", "app.js", "routes"],
|
|
100
|
+
"Laravel": ["artisan", "app/Http/Controllers", "app/Models"],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def __init__(self, enhance_with_ai: bool = True):
|
|
104
|
+
"""
|
|
105
|
+
Initialize detector.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
enhance_with_ai: Enable AI enhancement for detected patterns (C3.6)
|
|
109
|
+
"""
|
|
110
|
+
self.enhance_with_ai = enhance_with_ai
|
|
111
|
+
self.ai_enhancer = None
|
|
112
|
+
|
|
113
|
+
if self.enhance_with_ai:
|
|
114
|
+
try:
|
|
115
|
+
from skill_seekers.cli.ai_enhancer import AIEnhancer
|
|
116
|
+
|
|
117
|
+
self.ai_enhancer = AIEnhancer()
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f"⚠️ Failed to initialize AI enhancer: {e}")
|
|
120
|
+
self.enhance_with_ai = False
|
|
121
|
+
|
|
122
|
+
def analyze(self, directory: Path, files_analysis: list[dict]) -> ArchitecturalReport:
|
|
123
|
+
"""
|
|
124
|
+
Analyze codebase for architectural patterns.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
directory: Root directory of codebase
|
|
128
|
+
files_analysis: List of analyzed files from CodeAnalyzer
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
ArchitecturalReport with detected patterns
|
|
132
|
+
"""
|
|
133
|
+
logger.info(f"🏗️ Analyzing architectural patterns in {directory}")
|
|
134
|
+
|
|
135
|
+
# Build directory structure map
|
|
136
|
+
dir_structure = self._analyze_directory_structure(directory)
|
|
137
|
+
|
|
138
|
+
# Detect frameworks
|
|
139
|
+
frameworks = self._detect_frameworks(directory, files_analysis)
|
|
140
|
+
|
|
141
|
+
# Detect architectural patterns
|
|
142
|
+
patterns = []
|
|
143
|
+
|
|
144
|
+
patterns.extend(self._detect_mvc(dir_structure, files_analysis, frameworks))
|
|
145
|
+
patterns.extend(self._detect_mvvm(dir_structure, files_analysis, frameworks))
|
|
146
|
+
patterns.extend(self._detect_repository(dir_structure, files_analysis))
|
|
147
|
+
patterns.extend(self._detect_service_layer(dir_structure, files_analysis))
|
|
148
|
+
patterns.extend(self._detect_layered_architecture(dir_structure, files_analysis))
|
|
149
|
+
patterns.extend(self._detect_clean_architecture(dir_structure, files_analysis))
|
|
150
|
+
|
|
151
|
+
report = ArchitecturalReport(
|
|
152
|
+
patterns=patterns,
|
|
153
|
+
directory_structure=dir_structure,
|
|
154
|
+
total_files_analyzed=len(files_analysis),
|
|
155
|
+
frameworks_detected=frameworks,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Enhance with AI if enabled (C3.6)
|
|
159
|
+
if self.enhance_with_ai and self.ai_enhancer and patterns:
|
|
160
|
+
report.ai_analysis = self._enhance_with_ai(report)
|
|
161
|
+
|
|
162
|
+
logger.info(f"✅ Detected {len(patterns)} architectural patterns")
|
|
163
|
+
return report
|
|
164
|
+
|
|
165
|
+
def _analyze_directory_structure(self, directory: Path) -> dict[str, int]:
|
|
166
|
+
"""Analyze directory structure and count files"""
|
|
167
|
+
structure = defaultdict(int)
|
|
168
|
+
|
|
169
|
+
for path in directory.rglob("*"):
|
|
170
|
+
if path.is_file():
|
|
171
|
+
# Get relative directory path
|
|
172
|
+
rel_dir = path.parent.relative_to(directory)
|
|
173
|
+
dir_name = str(rel_dir).lower()
|
|
174
|
+
|
|
175
|
+
# Extract top-level and leaf directory names
|
|
176
|
+
parts = Path(dir_name).parts
|
|
177
|
+
if parts:
|
|
178
|
+
structure[parts[0]] += 1 # Top-level dir
|
|
179
|
+
if len(parts) > 1:
|
|
180
|
+
structure[parts[-1]] += 1 # Leaf dir
|
|
181
|
+
|
|
182
|
+
return dict(structure)
|
|
183
|
+
|
|
184
|
+
def _detect_frameworks(self, _directory: Path, files: list[dict]) -> list[str]:
|
|
185
|
+
"""Detect frameworks being used"""
|
|
186
|
+
detected = []
|
|
187
|
+
|
|
188
|
+
# Check file paths and content
|
|
189
|
+
all_paths = [str(f.get("file", "")) for f in files]
|
|
190
|
+
all_content = " ".join(all_paths)
|
|
191
|
+
|
|
192
|
+
for framework, markers in self.FRAMEWORK_MARKERS.items():
|
|
193
|
+
matches = sum(1 for marker in markers if marker.lower() in all_content.lower())
|
|
194
|
+
if matches >= 2: # Require at least 2 markers
|
|
195
|
+
detected.append(framework)
|
|
196
|
+
logger.info(f" 📦 Detected framework: {framework}")
|
|
197
|
+
|
|
198
|
+
return detected
|
|
199
|
+
|
|
200
|
+
def _detect_mvc(
|
|
201
|
+
self, dirs: dict[str, int], files: list[dict], frameworks: list[str]
|
|
202
|
+
) -> list[ArchitecturalPattern]:
|
|
203
|
+
"""Detect MVC pattern"""
|
|
204
|
+
patterns = []
|
|
205
|
+
|
|
206
|
+
# Check for MVC directory structure
|
|
207
|
+
mvc_dir_matches = sum(1 for d in self.MVC_DIRS if d in dirs)
|
|
208
|
+
has_mvc_structure = mvc_dir_matches >= 2
|
|
209
|
+
|
|
210
|
+
if not has_mvc_structure:
|
|
211
|
+
return patterns
|
|
212
|
+
|
|
213
|
+
# Build evidence
|
|
214
|
+
evidence = []
|
|
215
|
+
components = defaultdict(list)
|
|
216
|
+
|
|
217
|
+
# Find MVC files
|
|
218
|
+
for file in files:
|
|
219
|
+
file_path = str(file.get("file", "")).lower()
|
|
220
|
+
|
|
221
|
+
if "model" in file_path and ("models/" in file_path or "/model/" in file_path):
|
|
222
|
+
components["Models"].append(file.get("file", ""))
|
|
223
|
+
if len(components["Models"]) == 1:
|
|
224
|
+
evidence.append("Models directory with model classes")
|
|
225
|
+
|
|
226
|
+
if "view" in file_path and ("views/" in file_path or "/view/" in file_path):
|
|
227
|
+
components["Views"].append(file.get("file", ""))
|
|
228
|
+
if len(components["Views"]) == 1:
|
|
229
|
+
evidence.append("Views directory with view files")
|
|
230
|
+
|
|
231
|
+
if "controller" in file_path and (
|
|
232
|
+
"controllers/" in file_path or "/controller/" in file_path
|
|
233
|
+
):
|
|
234
|
+
components["Controllers"].append(file.get("file", ""))
|
|
235
|
+
if len(components["Controllers"]) == 1:
|
|
236
|
+
evidence.append("Controllers directory with controller classes")
|
|
237
|
+
|
|
238
|
+
# Calculate confidence
|
|
239
|
+
has_models = len(components["Models"]) > 0
|
|
240
|
+
has_views = len(components["Views"]) > 0
|
|
241
|
+
has_controllers = len(components["Controllers"]) > 0
|
|
242
|
+
|
|
243
|
+
if sum([has_models, has_views, has_controllers]) >= 2:
|
|
244
|
+
confidence = 0.6 + (sum([has_models, has_views, has_controllers]) * 0.15)
|
|
245
|
+
|
|
246
|
+
# Boost confidence if framework detected
|
|
247
|
+
framework = None
|
|
248
|
+
for fw in ["Django", "Flask", "Spring", "ASP.NET", "Rails", "Laravel"]:
|
|
249
|
+
if fw in frameworks:
|
|
250
|
+
confidence = min(0.95, confidence + 0.1)
|
|
251
|
+
framework = fw
|
|
252
|
+
evidence.append(f"{fw} framework detected (uses MVC)")
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
patterns.append(
|
|
256
|
+
ArchitecturalPattern(
|
|
257
|
+
pattern_name="MVC (Model-View-Controller)",
|
|
258
|
+
confidence=confidence,
|
|
259
|
+
evidence=evidence,
|
|
260
|
+
components=dict(components),
|
|
261
|
+
framework=framework,
|
|
262
|
+
description="Separates application into Models (data), Views (UI), and Controllers (logic)",
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return patterns
|
|
267
|
+
|
|
268
|
+
def _detect_mvvm(
|
|
269
|
+
self, dirs: dict[str, int], files: list[dict], frameworks: list[str]
|
|
270
|
+
) -> list[ArchitecturalPattern]:
|
|
271
|
+
"""Detect MVVM pattern"""
|
|
272
|
+
patterns = []
|
|
273
|
+
|
|
274
|
+
# Look for ViewModels directory or classes ending with ViewModel
|
|
275
|
+
has_viewmodel_dir = "viewmodels" in dirs or "viewmodel" in dirs
|
|
276
|
+
viewmodel_files = [f for f in files if "viewmodel" in str(f.get("file", "")).lower()]
|
|
277
|
+
|
|
278
|
+
if not (has_viewmodel_dir or len(viewmodel_files) >= 2):
|
|
279
|
+
return patterns
|
|
280
|
+
|
|
281
|
+
evidence = []
|
|
282
|
+
components = defaultdict(list)
|
|
283
|
+
|
|
284
|
+
# Find MVVM files
|
|
285
|
+
for file in files:
|
|
286
|
+
file_path = str(file.get("file", "")).lower()
|
|
287
|
+
classes = file.get("classes", [])
|
|
288
|
+
|
|
289
|
+
if "model" in file_path and "viewmodel" not in file_path:
|
|
290
|
+
components["Models"].append(file.get("file", ""))
|
|
291
|
+
|
|
292
|
+
if "view" in file_path:
|
|
293
|
+
components["Views"].append(file.get("file", ""))
|
|
294
|
+
|
|
295
|
+
if "viewmodel" in file_path or any(
|
|
296
|
+
"viewmodel" in c.get("name", "").lower() for c in classes
|
|
297
|
+
):
|
|
298
|
+
components["ViewModels"].append(file.get("file", ""))
|
|
299
|
+
|
|
300
|
+
if len(components["ViewModels"]) >= 2:
|
|
301
|
+
evidence.append(
|
|
302
|
+
f"ViewModels directory with {len(components['ViewModels'])} ViewModel classes"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if len(components["Views"]) >= 2:
|
|
306
|
+
evidence.append(f"Views directory with {len(components['Views'])} view files")
|
|
307
|
+
|
|
308
|
+
if len(components["Models"]) >= 1:
|
|
309
|
+
evidence.append(f"Models directory with {len(components['Models'])} model files")
|
|
310
|
+
|
|
311
|
+
# Calculate confidence
|
|
312
|
+
has_models = len(components["Models"]) > 0
|
|
313
|
+
has_views = len(components["Views"]) > 0
|
|
314
|
+
has_viewmodels = len(components["ViewModels"]) >= 2
|
|
315
|
+
|
|
316
|
+
if has_viewmodels and (has_models or has_views):
|
|
317
|
+
confidence = 0.7 if (has_models and has_views and has_viewmodels) else 0.6
|
|
318
|
+
|
|
319
|
+
framework = None
|
|
320
|
+
for fw in ["ASP.NET", "Angular", "Vue.js"]:
|
|
321
|
+
if fw in frameworks:
|
|
322
|
+
confidence = min(0.95, confidence + 0.1)
|
|
323
|
+
framework = fw
|
|
324
|
+
evidence.append(f"{fw} framework detected (supports MVVM)")
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
patterns.append(
|
|
328
|
+
ArchitecturalPattern(
|
|
329
|
+
pattern_name="MVVM (Model-View-ViewModel)",
|
|
330
|
+
confidence=confidence,
|
|
331
|
+
evidence=evidence,
|
|
332
|
+
components=dict(components),
|
|
333
|
+
framework=framework,
|
|
334
|
+
description="ViewModels provide data-binding between Views and Models",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return patterns
|
|
339
|
+
|
|
340
|
+
def _detect_repository(
|
|
341
|
+
self, dirs: dict[str, int], files: list[dict]
|
|
342
|
+
) -> list[ArchitecturalPattern]:
|
|
343
|
+
"""Detect Repository pattern"""
|
|
344
|
+
patterns = []
|
|
345
|
+
|
|
346
|
+
# Look for repositories directory or classes ending with Repository
|
|
347
|
+
has_repo_dir = any(d in dirs for d in self.REPO_DIRS)
|
|
348
|
+
repo_files = [
|
|
349
|
+
f
|
|
350
|
+
for f in files
|
|
351
|
+
if "repository" in str(f.get("file", "")).lower()
|
|
352
|
+
or any("repository" in c.get("name", "").lower() for c in f.get("classes", []))
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
if not (has_repo_dir or len(repo_files) >= 2):
|
|
356
|
+
return patterns
|
|
357
|
+
|
|
358
|
+
evidence = []
|
|
359
|
+
components = defaultdict(list)
|
|
360
|
+
|
|
361
|
+
for file in repo_files:
|
|
362
|
+
components["Repositories"].append(file.get("file", ""))
|
|
363
|
+
|
|
364
|
+
if len(components["Repositories"]) >= 2:
|
|
365
|
+
evidence.append(
|
|
366
|
+
f"Repository pattern: {len(components['Repositories'])} repository classes"
|
|
367
|
+
)
|
|
368
|
+
evidence.append("Repositories abstract data access logic")
|
|
369
|
+
|
|
370
|
+
patterns.append(
|
|
371
|
+
ArchitecturalPattern(
|
|
372
|
+
pattern_name="Repository Pattern",
|
|
373
|
+
confidence=0.75,
|
|
374
|
+
evidence=evidence,
|
|
375
|
+
components=dict(components),
|
|
376
|
+
description="Encapsulates data access logic in repository classes",
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return patterns
|
|
381
|
+
|
|
382
|
+
def _detect_service_layer(
|
|
383
|
+
self, dirs: dict[str, int], files: list[dict]
|
|
384
|
+
) -> list[ArchitecturalPattern]:
|
|
385
|
+
"""Detect Service Layer pattern"""
|
|
386
|
+
patterns = []
|
|
387
|
+
|
|
388
|
+
has_service_dir = any(d in dirs for d in self.SERVICE_DIRS)
|
|
389
|
+
service_files = [
|
|
390
|
+
f
|
|
391
|
+
for f in files
|
|
392
|
+
if "service" in str(f.get("file", "")).lower()
|
|
393
|
+
or any("service" in c.get("name", "").lower() for c in f.get("classes", []))
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
if not (has_service_dir or len(service_files) >= 3):
|
|
397
|
+
return patterns
|
|
398
|
+
|
|
399
|
+
evidence = []
|
|
400
|
+
components = defaultdict(list)
|
|
401
|
+
|
|
402
|
+
for file in service_files:
|
|
403
|
+
components["Services"].append(file.get("file", ""))
|
|
404
|
+
|
|
405
|
+
if len(components["Services"]) >= 3:
|
|
406
|
+
evidence.append(f"Service layer: {len(components['Services'])} service classes")
|
|
407
|
+
evidence.append("Services encapsulate business logic")
|
|
408
|
+
|
|
409
|
+
patterns.append(
|
|
410
|
+
ArchitecturalPattern(
|
|
411
|
+
pattern_name="Service Layer Pattern",
|
|
412
|
+
confidence=0.75,
|
|
413
|
+
evidence=evidence,
|
|
414
|
+
components=dict(components),
|
|
415
|
+
description="Encapsulates business logic in service classes",
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return patterns
|
|
420
|
+
|
|
421
|
+
def _detect_layered_architecture(
|
|
422
|
+
self, dirs: dict[str, int], _files: list[dict]
|
|
423
|
+
) -> list[ArchitecturalPattern]:
|
|
424
|
+
"""Detect Layered Architecture (3-tier, N-tier)"""
|
|
425
|
+
patterns = []
|
|
426
|
+
|
|
427
|
+
layered_matches = sum(1 for d in self.LAYERED_DIRS if d in dirs)
|
|
428
|
+
|
|
429
|
+
if layered_matches < 2:
|
|
430
|
+
return patterns
|
|
431
|
+
|
|
432
|
+
evidence = []
|
|
433
|
+
_components = defaultdict(list)
|
|
434
|
+
layers_found = []
|
|
435
|
+
|
|
436
|
+
if "presentation" in dirs or "ui" in dirs:
|
|
437
|
+
layers_found.append("Presentation Layer")
|
|
438
|
+
evidence.append("Presentation/UI layer detected")
|
|
439
|
+
|
|
440
|
+
if "business" in dirs or "bll" in dirs:
|
|
441
|
+
layers_found.append("Business Logic Layer")
|
|
442
|
+
evidence.append("Business logic layer detected")
|
|
443
|
+
|
|
444
|
+
if "data" in dirs or "dal" in dirs:
|
|
445
|
+
layers_found.append("Data Access Layer")
|
|
446
|
+
evidence.append("Data access layer detected")
|
|
447
|
+
|
|
448
|
+
if len(layers_found) >= 2:
|
|
449
|
+
confidence = 0.65 + (len(layers_found) * 0.1)
|
|
450
|
+
|
|
451
|
+
patterns.append(
|
|
452
|
+
ArchitecturalPattern(
|
|
453
|
+
pattern_name=f"Layered Architecture ({len(layers_found)}-tier)",
|
|
454
|
+
confidence=min(confidence, 0.9),
|
|
455
|
+
evidence=evidence,
|
|
456
|
+
components={"Layers": layers_found},
|
|
457
|
+
description=f"Separates concerns into {len(layers_found)} distinct layers",
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
return patterns
|
|
462
|
+
|
|
463
|
+
def _detect_clean_architecture(
|
|
464
|
+
self, dirs: dict[str, int], _files: list[dict]
|
|
465
|
+
) -> list[ArchitecturalPattern]:
|
|
466
|
+
"""Detect Clean Architecture"""
|
|
467
|
+
patterns = []
|
|
468
|
+
|
|
469
|
+
clean_matches = sum(1 for d in self.CLEAN_ARCH_DIRS if d in dirs)
|
|
470
|
+
|
|
471
|
+
if clean_matches < 3:
|
|
472
|
+
return patterns
|
|
473
|
+
|
|
474
|
+
evidence = []
|
|
475
|
+
components = defaultdict(list)
|
|
476
|
+
|
|
477
|
+
if "domain" in dirs:
|
|
478
|
+
evidence.append("Domain layer (core business logic)")
|
|
479
|
+
components["Domain"].append("domain/")
|
|
480
|
+
|
|
481
|
+
if "application" in dirs:
|
|
482
|
+
evidence.append("Application layer (use cases)")
|
|
483
|
+
components["Application"].append("application/")
|
|
484
|
+
|
|
485
|
+
if "infrastructure" in dirs:
|
|
486
|
+
evidence.append("Infrastructure layer (external dependencies)")
|
|
487
|
+
components["Infrastructure"].append("infrastructure/")
|
|
488
|
+
|
|
489
|
+
if "presentation" in dirs:
|
|
490
|
+
evidence.append("Presentation layer (UI/API)")
|
|
491
|
+
components["Presentation"].append("presentation/")
|
|
492
|
+
|
|
493
|
+
if len(components) >= 3:
|
|
494
|
+
patterns.append(
|
|
495
|
+
ArchitecturalPattern(
|
|
496
|
+
pattern_name="Clean Architecture",
|
|
497
|
+
confidence=0.85,
|
|
498
|
+
evidence=evidence,
|
|
499
|
+
components=dict(components),
|
|
500
|
+
description="Dependency inversion with domain at center, infrastructure at edges",
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return patterns
|
|
505
|
+
|
|
506
|
+
def _enhance_with_ai(self, report: ArchitecturalReport) -> dict:
|
|
507
|
+
"""Enhance architectural analysis with AI insights"""
|
|
508
|
+
if not self.ai_enhancer:
|
|
509
|
+
return {}
|
|
510
|
+
|
|
511
|
+
# Prepare summary for AI
|
|
512
|
+
summary = f"""Detected {len(report.patterns)} architectural patterns:
|
|
513
|
+
{chr(10).join(f"- {p.pattern_name} (confidence: {p.confidence:.2f})" for p in report.patterns)}
|
|
514
|
+
|
|
515
|
+
Frameworks: {", ".join(report.frameworks_detected) if report.frameworks_detected else "None"}
|
|
516
|
+
Total files: {report.total_files_analyzed}
|
|
517
|
+
|
|
518
|
+
Provide brief architectural insights and recommendations."""
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
response = self.ai_enhancer._call_claude(summary, max_tokens=500)
|
|
522
|
+
return {"insights": response} if response else {}
|
|
523
|
+
except Exception as e:
|
|
524
|
+
logger.warning(f"⚠️ AI enhancement failed: {e}")
|
|
525
|
+
return {}
|