mcp-vector-search 1.0.3__py3-none-any.whl → 1.1.22__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.
- mcp_vector_search/__init__.py +3 -3
- mcp_vector_search/analysis/__init__.py +48 -1
- mcp_vector_search/analysis/baseline/__init__.py +68 -0
- mcp_vector_search/analysis/baseline/comparator.py +462 -0
- mcp_vector_search/analysis/baseline/manager.py +621 -0
- mcp_vector_search/analysis/collectors/__init__.py +35 -0
- mcp_vector_search/analysis/collectors/cohesion.py +463 -0
- mcp_vector_search/analysis/collectors/coupling.py +1162 -0
- mcp_vector_search/analysis/collectors/halstead.py +514 -0
- mcp_vector_search/analysis/collectors/smells.py +325 -0
- mcp_vector_search/analysis/debt.py +516 -0
- mcp_vector_search/analysis/interpretation.py +685 -0
- mcp_vector_search/analysis/metrics.py +74 -1
- mcp_vector_search/analysis/reporters/__init__.py +3 -1
- mcp_vector_search/analysis/reporters/console.py +424 -0
- mcp_vector_search/analysis/reporters/markdown.py +480 -0
- mcp_vector_search/analysis/reporters/sarif.py +377 -0
- mcp_vector_search/analysis/storage/__init__.py +93 -0
- mcp_vector_search/analysis/storage/metrics_store.py +762 -0
- mcp_vector_search/analysis/storage/schema.py +245 -0
- mcp_vector_search/analysis/storage/trend_tracker.py +560 -0
- mcp_vector_search/analysis/trends.py +308 -0
- mcp_vector_search/analysis/visualizer/__init__.py +90 -0
- mcp_vector_search/analysis/visualizer/d3_data.py +534 -0
- mcp_vector_search/analysis/visualizer/exporter.py +484 -0
- mcp_vector_search/analysis/visualizer/html_report.py +2895 -0
- mcp_vector_search/analysis/visualizer/schemas.py +525 -0
- mcp_vector_search/cli/commands/analyze.py +665 -11
- mcp_vector_search/cli/commands/chat.py +193 -0
- mcp_vector_search/cli/commands/index.py +600 -2
- mcp_vector_search/cli/commands/index_background.py +467 -0
- mcp_vector_search/cli/commands/search.py +194 -1
- mcp_vector_search/cli/commands/setup.py +64 -13
- mcp_vector_search/cli/commands/status.py +302 -3
- mcp_vector_search/cli/commands/visualize/cli.py +26 -10
- mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +8 -4
- mcp_vector_search/cli/commands/visualize/graph_builder.py +167 -234
- mcp_vector_search/cli/commands/visualize/server.py +304 -15
- mcp_vector_search/cli/commands/visualize/templates/base.py +60 -6
- mcp_vector_search/cli/commands/visualize/templates/scripts.py +2100 -65
- mcp_vector_search/cli/commands/visualize/templates/styles.py +1297 -88
- mcp_vector_search/cli/didyoumean.py +5 -0
- mcp_vector_search/cli/main.py +16 -5
- mcp_vector_search/cli/output.py +134 -5
- mcp_vector_search/config/thresholds.py +89 -1
- mcp_vector_search/core/__init__.py +16 -0
- mcp_vector_search/core/database.py +39 -2
- mcp_vector_search/core/embeddings.py +24 -0
- mcp_vector_search/core/git.py +380 -0
- mcp_vector_search/core/indexer.py +445 -84
- mcp_vector_search/core/llm_client.py +9 -4
- mcp_vector_search/core/models.py +88 -1
- mcp_vector_search/core/relationships.py +473 -0
- mcp_vector_search/core/search.py +1 -1
- mcp_vector_search/mcp/server.py +795 -4
- mcp_vector_search/parsers/python.py +285 -5
- mcp_vector_search/utils/gitignore.py +0 -3
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/METADATA +3 -2
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/RECORD +62 -39
- mcp_vector_search/cli/commands/visualize.py.original +0 -2536
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/WHEEL +0 -0
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""Markdown reporter for code analysis results.
|
|
2
|
+
|
|
3
|
+
Generates two markdown reports:
|
|
4
|
+
1. Full analysis report with metrics and visualizations
|
|
5
|
+
2. Agent-actionable fixes report for refactoring tasks
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..collectors.smells import CodeSmell
|
|
16
|
+
from ..metrics import ProjectMetrics
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MarkdownReporter:
|
|
20
|
+
"""Markdown reporter for generating analysis reports."""
|
|
21
|
+
|
|
22
|
+
def generate_analysis_report(
|
|
23
|
+
self,
|
|
24
|
+
metrics: ProjectMetrics,
|
|
25
|
+
smells: list[CodeSmell] | None = None,
|
|
26
|
+
output_path: Path | None = None,
|
|
27
|
+
) -> str:
|
|
28
|
+
"""Generate full analysis report in markdown format.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
metrics: Project metrics to report
|
|
32
|
+
smells: Optional list of detected code smells
|
|
33
|
+
output_path: Optional output file path (defaults to current directory)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Path to generated markdown file
|
|
37
|
+
"""
|
|
38
|
+
if output_path is None:
|
|
39
|
+
output_path = Path.cwd()
|
|
40
|
+
|
|
41
|
+
# Determine output directory
|
|
42
|
+
if output_path.is_dir():
|
|
43
|
+
output_dir = output_path
|
|
44
|
+
elif output_path.exists() and output_path.is_file():
|
|
45
|
+
# If file exists, use parent directory
|
|
46
|
+
output_dir = output_path.parent
|
|
47
|
+
else:
|
|
48
|
+
# If path doesn't exist, check if parent exists
|
|
49
|
+
if output_path.parent.exists() and output_path.parent.is_dir():
|
|
50
|
+
output_dir = output_path.parent
|
|
51
|
+
else:
|
|
52
|
+
# Default to current directory
|
|
53
|
+
output_dir = Path.cwd()
|
|
54
|
+
|
|
55
|
+
output_file = output_dir / "mcp-vector-search-analysis.md"
|
|
56
|
+
|
|
57
|
+
# Generate markdown content
|
|
58
|
+
content = self._build_analysis_markdown(metrics, smells or [])
|
|
59
|
+
|
|
60
|
+
# Write to file
|
|
61
|
+
output_file.write_text(content, encoding="utf-8")
|
|
62
|
+
|
|
63
|
+
return str(output_file)
|
|
64
|
+
|
|
65
|
+
def generate_fixes_report(
|
|
66
|
+
self,
|
|
67
|
+
metrics: ProjectMetrics,
|
|
68
|
+
smells: list[CodeSmell],
|
|
69
|
+
output_path: Path | None = None,
|
|
70
|
+
) -> str:
|
|
71
|
+
"""Generate agent-actionable fixes report.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
metrics: Project metrics to analyze
|
|
75
|
+
smells: List of detected code smells
|
|
76
|
+
output_path: Optional output file path (defaults to current directory)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Path to generated markdown file
|
|
80
|
+
"""
|
|
81
|
+
if output_path is None:
|
|
82
|
+
output_path = Path.cwd()
|
|
83
|
+
|
|
84
|
+
# Determine output directory
|
|
85
|
+
if output_path.is_dir():
|
|
86
|
+
output_dir = output_path
|
|
87
|
+
elif output_path.exists() and output_path.is_file():
|
|
88
|
+
# If file exists, use parent directory
|
|
89
|
+
output_dir = output_path.parent
|
|
90
|
+
else:
|
|
91
|
+
# If path doesn't exist, check if parent exists
|
|
92
|
+
if output_path.parent.exists() and output_path.parent.is_dir():
|
|
93
|
+
output_dir = output_path.parent
|
|
94
|
+
else:
|
|
95
|
+
# Default to current directory
|
|
96
|
+
output_dir = Path.cwd()
|
|
97
|
+
|
|
98
|
+
output_file = output_dir / "mcp-vector-search-analysis-fixes.md"
|
|
99
|
+
|
|
100
|
+
# Generate markdown content
|
|
101
|
+
content = self._build_fixes_markdown(metrics, smells)
|
|
102
|
+
|
|
103
|
+
# Write to file
|
|
104
|
+
output_file.write_text(content, encoding="utf-8")
|
|
105
|
+
|
|
106
|
+
return str(output_file)
|
|
107
|
+
|
|
108
|
+
def _build_analysis_markdown(
|
|
109
|
+
self, metrics: ProjectMetrics, smells: list[CodeSmell]
|
|
110
|
+
) -> str:
|
|
111
|
+
"""Build full analysis markdown content.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
metrics: Project metrics
|
|
115
|
+
smells: Detected code smells
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Markdown content as string
|
|
119
|
+
"""
|
|
120
|
+
lines: list[str] = []
|
|
121
|
+
|
|
122
|
+
# Header
|
|
123
|
+
lines.append("# MCP Vector Search - Code Analysis Report")
|
|
124
|
+
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
125
|
+
lines.append("")
|
|
126
|
+
|
|
127
|
+
# Summary section
|
|
128
|
+
lines.append("## Summary")
|
|
129
|
+
lines.append("")
|
|
130
|
+
lines.append(f"- **Files analyzed**: {metrics.total_files}")
|
|
131
|
+
lines.append(f"- **Total lines**: {metrics.total_lines:,}")
|
|
132
|
+
lines.append(f"- **Total functions**: {metrics.total_functions}")
|
|
133
|
+
lines.append(f"- **Total classes**: {metrics.total_classes}")
|
|
134
|
+
lines.append(f"- **Average complexity**: {metrics.avg_file_complexity:.1f}")
|
|
135
|
+
lines.append("")
|
|
136
|
+
|
|
137
|
+
# Complexity distribution
|
|
138
|
+
distribution = metrics._compute_grade_distribution()
|
|
139
|
+
total_chunks = sum(distribution.values())
|
|
140
|
+
|
|
141
|
+
if total_chunks > 0:
|
|
142
|
+
lines.append("## Complexity Distribution")
|
|
143
|
+
lines.append("")
|
|
144
|
+
lines.append("| Grade | Description | Count | Percentage |")
|
|
145
|
+
lines.append("|-------|------------|-------|------------|")
|
|
146
|
+
|
|
147
|
+
grade_info = {
|
|
148
|
+
"A": "Excellent (0-5)",
|
|
149
|
+
"B": "Good (6-10)",
|
|
150
|
+
"C": "Acceptable (11-20)",
|
|
151
|
+
"D": "Needs Improvement (21-30)",
|
|
152
|
+
"F": "Refactor Required (31+)",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for grade in ["A", "B", "C", "D", "F"]:
|
|
156
|
+
count = distribution.get(grade, 0)
|
|
157
|
+
percentage = (count / total_chunks * 100) if total_chunks > 0 else 0
|
|
158
|
+
description = grade_info[grade]
|
|
159
|
+
lines.append(
|
|
160
|
+
f"| {grade} | {description} | {count} | {percentage:.1f}% |"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
lines.append("")
|
|
164
|
+
|
|
165
|
+
# Code smells summary
|
|
166
|
+
if smells:
|
|
167
|
+
from ..collectors.smells import SmellSeverity
|
|
168
|
+
|
|
169
|
+
error_count = sum(1 for s in smells if s.severity == SmellSeverity.ERROR)
|
|
170
|
+
warning_count = sum(
|
|
171
|
+
1 for s in smells if s.severity == SmellSeverity.WARNING
|
|
172
|
+
)
|
|
173
|
+
info_count = sum(1 for s in smells if s.severity == SmellSeverity.INFO)
|
|
174
|
+
|
|
175
|
+
lines.append("## Code Smells Summary")
|
|
176
|
+
lines.append("")
|
|
177
|
+
lines.append("| Severity | Count |")
|
|
178
|
+
lines.append("|----------|-------|")
|
|
179
|
+
lines.append(f"| ERROR | {error_count} |")
|
|
180
|
+
lines.append(f"| WARNING | {warning_count} |")
|
|
181
|
+
lines.append(f"| INFO | {info_count} |")
|
|
182
|
+
lines.append(f"| **Total** | **{len(smells)}** |")
|
|
183
|
+
lines.append("")
|
|
184
|
+
|
|
185
|
+
# Count by smell type
|
|
186
|
+
smell_types: dict[str, int] = {}
|
|
187
|
+
for smell in smells:
|
|
188
|
+
smell_types[smell.name] = smell_types.get(smell.name, 0) + 1
|
|
189
|
+
|
|
190
|
+
lines.append("### By Type")
|
|
191
|
+
lines.append("")
|
|
192
|
+
lines.append("| Type | Count | Severity |")
|
|
193
|
+
lines.append("|------|-------|----------|")
|
|
194
|
+
|
|
195
|
+
# Sort by count descending
|
|
196
|
+
for smell_type, count in sorted(
|
|
197
|
+
smell_types.items(), key=lambda x: x[1], reverse=True
|
|
198
|
+
):
|
|
199
|
+
# Get most common severity for this type
|
|
200
|
+
type_smells = [s for s in smells if s.name == smell_type]
|
|
201
|
+
severities = [s.severity.value for s in type_smells]
|
|
202
|
+
common_severity = max(set(severities), key=severities.count)
|
|
203
|
+
lines.append(f"| {smell_type} | {count} | {common_severity} |")
|
|
204
|
+
|
|
205
|
+
lines.append("")
|
|
206
|
+
|
|
207
|
+
# Top complexity hotspots
|
|
208
|
+
hotspots = metrics.get_hotspots(limit=10)
|
|
209
|
+
|
|
210
|
+
if hotspots:
|
|
211
|
+
lines.append("## Top Complexity Hotspots")
|
|
212
|
+
lines.append("")
|
|
213
|
+
|
|
214
|
+
for rank, file_metrics in enumerate(hotspots, 1):
|
|
215
|
+
# Compute average grade
|
|
216
|
+
if file_metrics.chunks:
|
|
217
|
+
grades = [chunk.complexity_grade for chunk in file_metrics.chunks]
|
|
218
|
+
avg_grade = max(set(grades), key=grades.count)
|
|
219
|
+
else:
|
|
220
|
+
avg_grade = "N/A"
|
|
221
|
+
|
|
222
|
+
lines.append(
|
|
223
|
+
f"{rank}. **{file_metrics.file_path}** - "
|
|
224
|
+
f"Complexity: {file_metrics.avg_complexity:.1f}, "
|
|
225
|
+
f"Grade: {avg_grade}, "
|
|
226
|
+
f"Lines: {file_metrics.total_lines}, "
|
|
227
|
+
f"Functions: {len(file_metrics.chunks)}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
lines.append("")
|
|
231
|
+
|
|
232
|
+
# Detailed smells (top 20)
|
|
233
|
+
if smells:
|
|
234
|
+
from ..collectors.smells import SmellSeverity
|
|
235
|
+
|
|
236
|
+
lines.append("## Detailed Code Smells")
|
|
237
|
+
lines.append("")
|
|
238
|
+
lines.append("Showing top 20 most critical issues:")
|
|
239
|
+
lines.append("")
|
|
240
|
+
|
|
241
|
+
# Prioritize errors, then warnings, then info
|
|
242
|
+
sorted_smells = sorted(
|
|
243
|
+
smells,
|
|
244
|
+
key=lambda s: (
|
|
245
|
+
(
|
|
246
|
+
0
|
|
247
|
+
if s.severity == SmellSeverity.ERROR
|
|
248
|
+
else 1
|
|
249
|
+
if s.severity == SmellSeverity.WARNING
|
|
250
|
+
else 2
|
|
251
|
+
),
|
|
252
|
+
-s.metric_value,
|
|
253
|
+
),
|
|
254
|
+
)[:20]
|
|
255
|
+
|
|
256
|
+
for i, smell in enumerate(sorted_smells, 1):
|
|
257
|
+
lines.append(f"### {i}. {smell.name} ({smell.severity.value.upper()})")
|
|
258
|
+
lines.append("")
|
|
259
|
+
lines.append(f"- **Location**: `{smell.location}`")
|
|
260
|
+
lines.append(f"- **Description**: {smell.description}")
|
|
261
|
+
lines.append(
|
|
262
|
+
f"- **Metric**: {smell.metric_value} (threshold: {smell.threshold})"
|
|
263
|
+
)
|
|
264
|
+
if smell.suggestion:
|
|
265
|
+
lines.append(f"- **Suggestion**: {smell.suggestion}")
|
|
266
|
+
lines.append("")
|
|
267
|
+
|
|
268
|
+
# Health metrics
|
|
269
|
+
avg_health = metrics._compute_avg_health_score()
|
|
270
|
+
files_needing_attention = metrics._count_files_needing_attention()
|
|
271
|
+
|
|
272
|
+
lines.append("## Health Metrics")
|
|
273
|
+
lines.append("")
|
|
274
|
+
lines.append(f"- **Average health score**: {avg_health:.2f} / 1.00")
|
|
275
|
+
lines.append(
|
|
276
|
+
f"- **Files needing attention**: {files_needing_attention} "
|
|
277
|
+
f"({files_needing_attention / max(metrics.total_files, 1) * 100:.1f}%)"
|
|
278
|
+
)
|
|
279
|
+
lines.append("")
|
|
280
|
+
|
|
281
|
+
# Footer
|
|
282
|
+
lines.append("---")
|
|
283
|
+
lines.append("")
|
|
284
|
+
lines.append("*Generated by mcp-vector-search analyze command*")
|
|
285
|
+
lines.append("")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
def _build_fixes_markdown(
|
|
290
|
+
self, metrics: ProjectMetrics, smells: list[CodeSmell]
|
|
291
|
+
) -> str:
|
|
292
|
+
"""Build agent-actionable fixes markdown content.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
metrics: Project metrics
|
|
296
|
+
smells: Detected code smells
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Markdown content as string
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
lines: list[str] = []
|
|
303
|
+
|
|
304
|
+
# Header
|
|
305
|
+
lines.append("# MCP Vector Search - Refactoring Tasks")
|
|
306
|
+
lines.append("")
|
|
307
|
+
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
308
|
+
lines.append("")
|
|
309
|
+
lines.append(
|
|
310
|
+
"This report contains actionable refactoring tasks prioritized "
|
|
311
|
+
"by severity and impact."
|
|
312
|
+
)
|
|
313
|
+
lines.append("")
|
|
314
|
+
|
|
315
|
+
# Priority 1: God Classes
|
|
316
|
+
god_class_smells = [s for s in smells if s.name == "God Class"]
|
|
317
|
+
|
|
318
|
+
if god_class_smells:
|
|
319
|
+
lines.append("## Priority 1: God Classes")
|
|
320
|
+
lines.append("")
|
|
321
|
+
lines.append(
|
|
322
|
+
"Large classes with too many responsibilities. "
|
|
323
|
+
"These should be split into smaller, focused classes."
|
|
324
|
+
)
|
|
325
|
+
lines.append("")
|
|
326
|
+
|
|
327
|
+
for i, smell in enumerate(god_class_smells, 1):
|
|
328
|
+
# Extract file path and line info
|
|
329
|
+
location_parts = smell.location.split(":")
|
|
330
|
+
file_path = location_parts[0]
|
|
331
|
+
|
|
332
|
+
# Get file metrics for more context
|
|
333
|
+
file_metrics = metrics.files.get(file_path)
|
|
334
|
+
|
|
335
|
+
lines.append(f"### {i}. {Path(file_path).name}")
|
|
336
|
+
lines.append("")
|
|
337
|
+
lines.append(f"- **Location**: `{smell.location}`")
|
|
338
|
+
|
|
339
|
+
if file_metrics:
|
|
340
|
+
lines.append(f"- **Lines**: {file_metrics.total_lines}")
|
|
341
|
+
lines.append(f"- **Methods**: {file_metrics.method_count}")
|
|
342
|
+
lines.append(f"- **Complexity**: {file_metrics.avg_complexity:.1f}")
|
|
343
|
+
|
|
344
|
+
lines.append(f"- **Task**: {smell.suggestion}")
|
|
345
|
+
lines.append("")
|
|
346
|
+
lines.append("**Suggested refactoring:**")
|
|
347
|
+
lines.append("")
|
|
348
|
+
|
|
349
|
+
if file_metrics and file_metrics.method_count > 0:
|
|
350
|
+
# Suggest splitting based on method groups
|
|
351
|
+
suggested_classes = min(max(file_metrics.method_count // 10, 2), 5)
|
|
352
|
+
lines.append(f"- Extract into {suggested_classes} separate classes")
|
|
353
|
+
lines.append("- Group related methods by functionality")
|
|
354
|
+
lines.append("- Move configuration/setup to separate config class")
|
|
355
|
+
lines.append("- Consider using composition over inheritance")
|
|
356
|
+
|
|
357
|
+
lines.append("")
|
|
358
|
+
|
|
359
|
+
# Priority 2: Long Methods (>100 lines)
|
|
360
|
+
long_method_smells = [
|
|
361
|
+
s for s in smells if s.name == "Long Method" and s.metric_value > 100
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
if long_method_smells:
|
|
365
|
+
# Sort by metric value descending
|
|
366
|
+
long_method_smells.sort(key=lambda s: s.metric_value, reverse=True)
|
|
367
|
+
|
|
368
|
+
lines.append("## Priority 2: Long Methods (>100 lines)")
|
|
369
|
+
lines.append("")
|
|
370
|
+
lines.append(
|
|
371
|
+
"Methods that are too long and should be broken down into "
|
|
372
|
+
"smaller, focused functions."
|
|
373
|
+
)
|
|
374
|
+
lines.append("")
|
|
375
|
+
|
|
376
|
+
for i, smell in enumerate(long_method_smells[:10], 1):
|
|
377
|
+
lines.append(f"### {i}. Method at {smell.location}")
|
|
378
|
+
lines.append("")
|
|
379
|
+
lines.append(f"- **Metric**: {smell.metric_value:.0f} lines/complexity")
|
|
380
|
+
lines.append(f"- **Task**: {smell.suggestion}")
|
|
381
|
+
lines.append("")
|
|
382
|
+
|
|
383
|
+
# Priority 3: High Complexity (>20)
|
|
384
|
+
complex_method_smells = [
|
|
385
|
+
s for s in smells if s.name == "Complex Method" and s.metric_value > 20
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
if complex_method_smells:
|
|
389
|
+
# Sort by metric value descending
|
|
390
|
+
complex_method_smells.sort(key=lambda s: s.metric_value, reverse=True)
|
|
391
|
+
|
|
392
|
+
lines.append("## Priority 3: High Complexity (>20)")
|
|
393
|
+
lines.append("")
|
|
394
|
+
lines.append(
|
|
395
|
+
"Methods with high cyclomatic complexity that need simplification."
|
|
396
|
+
)
|
|
397
|
+
lines.append("")
|
|
398
|
+
|
|
399
|
+
for i, smell in enumerate(complex_method_smells[:10], 1):
|
|
400
|
+
lines.append(f"### {i}. Method at {smell.location}")
|
|
401
|
+
lines.append("")
|
|
402
|
+
lines.append(f"- **Cyclomatic Complexity**: {smell.metric_value:.0f}")
|
|
403
|
+
lines.append(f"- **Task**: {smell.suggestion}")
|
|
404
|
+
lines.append("")
|
|
405
|
+
|
|
406
|
+
# Priority 4: Deep Nesting
|
|
407
|
+
nesting_smells = [s for s in smells if s.name == "Deep Nesting"]
|
|
408
|
+
|
|
409
|
+
if nesting_smells:
|
|
410
|
+
# Sort by metric value descending
|
|
411
|
+
nesting_smells.sort(key=lambda s: s.metric_value, reverse=True)
|
|
412
|
+
|
|
413
|
+
lines.append("## Priority 4: Deep Nesting")
|
|
414
|
+
lines.append("")
|
|
415
|
+
lines.append("Methods with excessive nesting that reduce readability.")
|
|
416
|
+
lines.append("")
|
|
417
|
+
|
|
418
|
+
for i, smell in enumerate(nesting_smells[:10], 1):
|
|
419
|
+
lines.append(f"### {i}. Method at {smell.location}")
|
|
420
|
+
lines.append("")
|
|
421
|
+
lines.append(f"- **Nesting Depth**: {smell.metric_value:.0f}")
|
|
422
|
+
lines.append(f"- **Task**: {smell.suggestion}")
|
|
423
|
+
lines.append("")
|
|
424
|
+
|
|
425
|
+
# Priority 5: Long Parameter Lists
|
|
426
|
+
param_smells = [s for s in smells if s.name == "Long Parameter List"]
|
|
427
|
+
|
|
428
|
+
if param_smells:
|
|
429
|
+
# Sort by metric value descending
|
|
430
|
+
param_smells.sort(key=lambda s: s.metric_value, reverse=True)
|
|
431
|
+
|
|
432
|
+
lines.append("## Priority 5: Long Parameter Lists")
|
|
433
|
+
lines.append("")
|
|
434
|
+
lines.append(
|
|
435
|
+
"Functions with too many parameters that should use "
|
|
436
|
+
"parameter objects or builders."
|
|
437
|
+
)
|
|
438
|
+
lines.append("")
|
|
439
|
+
|
|
440
|
+
for i, smell in enumerate(param_smells[:10], 1):
|
|
441
|
+
lines.append(f"### {i}. Function at {smell.location}")
|
|
442
|
+
lines.append("")
|
|
443
|
+
lines.append(f"- **Parameter Count**: {smell.metric_value:.0f}")
|
|
444
|
+
lines.append(f"- **Task**: {smell.suggestion}")
|
|
445
|
+
lines.append("")
|
|
446
|
+
|
|
447
|
+
# Summary
|
|
448
|
+
lines.append("## Summary")
|
|
449
|
+
lines.append("")
|
|
450
|
+
lines.append(f"- **Total issues**: {len(smells)}")
|
|
451
|
+
lines.append(
|
|
452
|
+
f"- **God Classes**: {len(god_class_smells)} (Priority 1 - highest impact)"
|
|
453
|
+
)
|
|
454
|
+
lines.append(
|
|
455
|
+
f"- **Long Methods**: {len(long_method_smells)} "
|
|
456
|
+
"(Priority 2 - moderate impact)"
|
|
457
|
+
)
|
|
458
|
+
lines.append(
|
|
459
|
+
f"- **Complex Methods**: {len(complex_method_smells)} "
|
|
460
|
+
"(Priority 3 - moderate impact)"
|
|
461
|
+
)
|
|
462
|
+
lines.append(
|
|
463
|
+
f"- **Deep Nesting**: {len(nesting_smells)} (Priority 4 - readability)"
|
|
464
|
+
)
|
|
465
|
+
lines.append(
|
|
466
|
+
f"- **Long Parameters**: {len(param_smells)} (Priority 5 - API design)"
|
|
467
|
+
)
|
|
468
|
+
lines.append("")
|
|
469
|
+
|
|
470
|
+
# Footer
|
|
471
|
+
lines.append("---")
|
|
472
|
+
lines.append("")
|
|
473
|
+
lines.append("*Generated by mcp-vector-search analyze command*")
|
|
474
|
+
lines.append("")
|
|
475
|
+
lines.append(
|
|
476
|
+
"**Note**: Address Priority 1 and 2 items first for maximum impact."
|
|
477
|
+
)
|
|
478
|
+
lines.append("")
|
|
479
|
+
|
|
480
|
+
return "\n".join(lines)
|