mcp-vector-search 0.12.6__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.
Files changed (92) hide show
  1. mcp_vector_search/__init__.py +3 -3
  2. mcp_vector_search/analysis/__init__.py +111 -0
  3. mcp_vector_search/analysis/baseline/__init__.py +68 -0
  4. mcp_vector_search/analysis/baseline/comparator.py +462 -0
  5. mcp_vector_search/analysis/baseline/manager.py +621 -0
  6. mcp_vector_search/analysis/collectors/__init__.py +74 -0
  7. mcp_vector_search/analysis/collectors/base.py +164 -0
  8. mcp_vector_search/analysis/collectors/cohesion.py +463 -0
  9. mcp_vector_search/analysis/collectors/complexity.py +743 -0
  10. mcp_vector_search/analysis/collectors/coupling.py +1162 -0
  11. mcp_vector_search/analysis/collectors/halstead.py +514 -0
  12. mcp_vector_search/analysis/collectors/smells.py +325 -0
  13. mcp_vector_search/analysis/debt.py +516 -0
  14. mcp_vector_search/analysis/interpretation.py +685 -0
  15. mcp_vector_search/analysis/metrics.py +414 -0
  16. mcp_vector_search/analysis/reporters/__init__.py +7 -0
  17. mcp_vector_search/analysis/reporters/console.py +646 -0
  18. mcp_vector_search/analysis/reporters/markdown.py +480 -0
  19. mcp_vector_search/analysis/reporters/sarif.py +377 -0
  20. mcp_vector_search/analysis/storage/__init__.py +93 -0
  21. mcp_vector_search/analysis/storage/metrics_store.py +762 -0
  22. mcp_vector_search/analysis/storage/schema.py +245 -0
  23. mcp_vector_search/analysis/storage/trend_tracker.py +560 -0
  24. mcp_vector_search/analysis/trends.py +308 -0
  25. mcp_vector_search/analysis/visualizer/__init__.py +90 -0
  26. mcp_vector_search/analysis/visualizer/d3_data.py +534 -0
  27. mcp_vector_search/analysis/visualizer/exporter.py +484 -0
  28. mcp_vector_search/analysis/visualizer/html_report.py +2895 -0
  29. mcp_vector_search/analysis/visualizer/schemas.py +525 -0
  30. mcp_vector_search/cli/commands/analyze.py +1062 -0
  31. mcp_vector_search/cli/commands/chat.py +1455 -0
  32. mcp_vector_search/cli/commands/index.py +621 -5
  33. mcp_vector_search/cli/commands/index_background.py +467 -0
  34. mcp_vector_search/cli/commands/init.py +13 -0
  35. mcp_vector_search/cli/commands/install.py +597 -335
  36. mcp_vector_search/cli/commands/install_old.py +8 -4
  37. mcp_vector_search/cli/commands/mcp.py +78 -6
  38. mcp_vector_search/cli/commands/reset.py +68 -26
  39. mcp_vector_search/cli/commands/search.py +224 -8
  40. mcp_vector_search/cli/commands/setup.py +1184 -0
  41. mcp_vector_search/cli/commands/status.py +339 -5
  42. mcp_vector_search/cli/commands/uninstall.py +276 -357
  43. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  44. mcp_vector_search/cli/commands/visualize/cli.py +292 -0
  45. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  46. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  47. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +33 -0
  48. mcp_vector_search/cli/commands/visualize/graph_builder.py +647 -0
  49. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  50. mcp_vector_search/cli/commands/visualize/server.py +600 -0
  51. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  52. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  53. mcp_vector_search/cli/commands/visualize/templates/base.py +234 -0
  54. mcp_vector_search/cli/commands/visualize/templates/scripts.py +4542 -0
  55. mcp_vector_search/cli/commands/visualize/templates/styles.py +2522 -0
  56. mcp_vector_search/cli/didyoumean.py +27 -2
  57. mcp_vector_search/cli/main.py +127 -160
  58. mcp_vector_search/cli/output.py +158 -13
  59. mcp_vector_search/config/__init__.py +4 -0
  60. mcp_vector_search/config/default_thresholds.yaml +52 -0
  61. mcp_vector_search/config/settings.py +12 -0
  62. mcp_vector_search/config/thresholds.py +273 -0
  63. mcp_vector_search/core/__init__.py +16 -0
  64. mcp_vector_search/core/auto_indexer.py +3 -3
  65. mcp_vector_search/core/boilerplate.py +186 -0
  66. mcp_vector_search/core/config_utils.py +394 -0
  67. mcp_vector_search/core/database.py +406 -94
  68. mcp_vector_search/core/embeddings.py +24 -0
  69. mcp_vector_search/core/exceptions.py +11 -0
  70. mcp_vector_search/core/git.py +380 -0
  71. mcp_vector_search/core/git_hooks.py +4 -4
  72. mcp_vector_search/core/indexer.py +632 -54
  73. mcp_vector_search/core/llm_client.py +756 -0
  74. mcp_vector_search/core/models.py +91 -1
  75. mcp_vector_search/core/project.py +17 -0
  76. mcp_vector_search/core/relationships.py +473 -0
  77. mcp_vector_search/core/scheduler.py +11 -11
  78. mcp_vector_search/core/search.py +179 -29
  79. mcp_vector_search/mcp/server.py +819 -9
  80. mcp_vector_search/parsers/python.py +285 -5
  81. mcp_vector_search/utils/__init__.py +2 -0
  82. mcp_vector_search/utils/gitignore.py +0 -3
  83. mcp_vector_search/utils/gitignore_updater.py +212 -0
  84. mcp_vector_search/utils/monorepo.py +66 -4
  85. mcp_vector_search/utils/timing.py +10 -6
  86. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/METADATA +184 -53
  87. mcp_vector_search-1.1.22.dist-info/RECORD +120 -0
  88. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/WHEEL +1 -1
  89. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/entry_points.txt +1 -0
  90. mcp_vector_search/cli/commands/visualize.py +0 -1467
  91. mcp_vector_search-0.12.6.dist-info/RECORD +0 -68
  92. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,308 @@
1
+ """Historical trend tracking for code metrics over time.
2
+
3
+ Stores daily snapshots of key metrics to track codebase evolution.
4
+ At most one entry per day - updates existing entry if reindexed same day.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from dataclasses import dataclass, field
11
+ from datetime import UTC, datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from loguru import logger
16
+
17
+
18
+ @dataclass
19
+ class TrendEntry:
20
+ """Single trend snapshot for a specific date.
21
+
22
+ Attributes:
23
+ date: ISO date string (YYYY-MM-DD)
24
+ timestamp: Full ISO timestamp when captured
25
+ metrics: Dictionary of metrics captured at this point
26
+ """
27
+
28
+ date: str
29
+ timestamp: str
30
+ metrics: dict[str, Any] = field(default_factory=dict)
31
+
32
+ def to_dict(self) -> dict[str, Any]:
33
+ """Convert to dictionary for JSON serialization."""
34
+ return {
35
+ "date": self.date,
36
+ "timestamp": self.timestamp,
37
+ "metrics": self.metrics,
38
+ }
39
+
40
+ @classmethod
41
+ def from_dict(cls, data: dict[str, Any]) -> TrendEntry:
42
+ """Create from dictionary."""
43
+ return cls(
44
+ date=data["date"],
45
+ timestamp=data["timestamp"],
46
+ metrics=data.get("metrics", {}),
47
+ )
48
+
49
+
50
+ @dataclass
51
+ class TrendData:
52
+ """Container for all trend entries.
53
+
54
+ Attributes:
55
+ entries: List of trend snapshots (one per day)
56
+ last_updated: ISO timestamp of most recent update
57
+ """
58
+
59
+ entries: list[TrendEntry] = field(default_factory=list)
60
+ last_updated: str | None = None
61
+
62
+ def to_dict(self) -> dict[str, Any]:
63
+ """Convert to dictionary for JSON serialization."""
64
+ return {
65
+ "entries": [entry.to_dict() for entry in self.entries],
66
+ "last_updated": self.last_updated,
67
+ }
68
+
69
+ @classmethod
70
+ def from_dict(cls, data: dict[str, Any]) -> TrendData:
71
+ """Create from dictionary."""
72
+ return cls(
73
+ entries=[TrendEntry.from_dict(e) for e in data.get("entries", [])],
74
+ last_updated=data.get("last_updated"),
75
+ )
76
+
77
+
78
+ class TrendTracker:
79
+ """Track code metrics over time with daily snapshots.
80
+
81
+ Features:
82
+ - One entry per day (updates existing if reindexed same day)
83
+ - Stores key metrics: files, chunks, lines, complexity, health
84
+ - JSON file storage in .mcp-vector-search/trends.json
85
+ - History retrieval for time series analysis
86
+ """
87
+
88
+ def __init__(self, project_root: Path) -> None:
89
+ """Initialize trend tracker.
90
+
91
+ Args:
92
+ project_root: Root directory of the project
93
+ """
94
+ self.project_root = project_root
95
+ self.trends_file = project_root / ".mcp-vector-search" / "trends.json"
96
+ self._ensure_directory()
97
+
98
+ def _ensure_directory(self) -> None:
99
+ """Ensure .mcp-vector-search directory exists."""
100
+ self.trends_file.parent.mkdir(parents=True, exist_ok=True)
101
+
102
+ def load_trends(self) -> TrendData:
103
+ """Load existing trends from file.
104
+
105
+ Returns:
106
+ TrendData with existing entries, or empty if file doesn't exist
107
+ """
108
+ if not self.trends_file.exists():
109
+ logger.debug("No trends file found, returning empty TrendData")
110
+ return TrendData()
111
+
112
+ try:
113
+ with open(self.trends_file, encoding="utf-8") as f:
114
+ data = json.load(f)
115
+ return TrendData.from_dict(data)
116
+ except Exception as e:
117
+ logger.warning(f"Failed to load trends file: {e}")
118
+ return TrendData()
119
+
120
+ def save_trends(self, trends: TrendData) -> None:
121
+ """Save trends to file.
122
+
123
+ Args:
124
+ trends: TrendData to save
125
+ """
126
+ try:
127
+ self._ensure_directory()
128
+ with open(self.trends_file, "w", encoding="utf-8") as f:
129
+ json.dump(trends.to_dict(), f, indent=2)
130
+ logger.debug(f"Saved trends to {self.trends_file}")
131
+ except Exception as e:
132
+ logger.error(f"Failed to save trends file: {e}")
133
+
134
+ def save_snapshot(self, metrics: dict[str, Any]) -> None:
135
+ """Save or update today's metrics snapshot.
136
+
137
+ If an entry already exists for today, it's replaced (reindex case).
138
+ Otherwise, a new entry is appended.
139
+
140
+ Args:
141
+ metrics: Dictionary of metrics to store
142
+ """
143
+ trends = self.load_trends()
144
+
145
+ # Get current date and timestamp
146
+ now = datetime.now(UTC)
147
+ today_date = now.date().isoformat() # YYYY-MM-DD
148
+ timestamp = now.isoformat()
149
+
150
+ # Check if entry already exists for today
151
+ existing_index = None
152
+ for i, entry in enumerate(trends.entries):
153
+ if entry.date == today_date:
154
+ existing_index = i
155
+ break
156
+
157
+ # Create new entry
158
+ new_entry = TrendEntry(
159
+ date=today_date,
160
+ timestamp=timestamp,
161
+ metrics=metrics,
162
+ )
163
+
164
+ # Replace existing or append new
165
+ if existing_index is not None:
166
+ logger.info(f"Updating existing trend entry for {today_date}")
167
+ trends.entries[existing_index] = new_entry
168
+ else:
169
+ logger.info(f"Creating new trend entry for {today_date}")
170
+ trends.entries.append(new_entry)
171
+
172
+ # Sort entries by date (oldest first)
173
+ trends.entries.sort(key=lambda e: e.date)
174
+
175
+ # Update last_updated timestamp
176
+ trends.last_updated = timestamp
177
+
178
+ # Save to file
179
+ self.save_trends(trends)
180
+ logger.info(
181
+ f"Saved trend snapshot with {len(metrics)} metrics for {today_date}"
182
+ )
183
+
184
+ def get_history(self, days: int = 30) -> list[TrendEntry]:
185
+ """Get recent trend history.
186
+
187
+ Args:
188
+ days: Number of days to retrieve (default: 30)
189
+
190
+ Returns:
191
+ List of TrendEntry objects for the last N days
192
+ """
193
+ trends = self.load_trends()
194
+
195
+ # Return last N entries
196
+ return trends.entries[-days:] if days > 0 else trends.entries
197
+
198
+ def get_trend_summary(self, days: int = 30) -> dict[str, Any]:
199
+ """Get summary of trends for visualization.
200
+
201
+ Args:
202
+ days: Number of days to include
203
+
204
+ Returns:
205
+ Dictionary with trend summary data
206
+ """
207
+ history = self.get_history(days)
208
+
209
+ if not history:
210
+ return {
211
+ "days": days,
212
+ "entries_count": 0,
213
+ "date_range": None,
214
+ "entries": [],
215
+ }
216
+
217
+ return {
218
+ "days": days,
219
+ "entries_count": len(history),
220
+ "date_range": {
221
+ "start": history[0].date,
222
+ "end": history[-1].date,
223
+ },
224
+ "entries": [entry.to_dict() for entry in history],
225
+ }
226
+
227
+ def compute_metrics_from_stats(
228
+ self, stats: dict[str, Any], chunks: list[Any] | None = None
229
+ ) -> dict[str, Any]:
230
+ """Compute metrics dictionary from database stats and chunks.
231
+
232
+ Args:
233
+ stats: Database statistics (from database.get_stats())
234
+ chunks: Optional list of chunks for detailed metrics
235
+
236
+ Returns:
237
+ Dictionary of metrics suitable for save_snapshot()
238
+ """
239
+ metrics = {
240
+ "total_files": stats.get("total_files", 0),
241
+ "total_chunks": stats.get("total_chunks", 0),
242
+ "total_lines": 0, # Computed from chunks if available
243
+ "avg_complexity": 0.0,
244
+ "max_complexity": 0,
245
+ "health_score": 0,
246
+ "code_smells_count": 0,
247
+ "high_complexity_files": 0,
248
+ }
249
+
250
+ # Compute detailed metrics from chunks if provided
251
+ if chunks:
252
+ total_lines = 0
253
+ complexities = []
254
+ smell_counts = 0
255
+ high_complexity_count = 0
256
+
257
+ for chunk in chunks:
258
+ # Lines of code
259
+ if hasattr(chunk, "lines_of_code") and chunk.lines_of_code:
260
+ total_lines += chunk.lines_of_code
261
+ else:
262
+ # Fallback: estimate from line range
263
+ total_lines += chunk.end_line - chunk.start_line + 1
264
+
265
+ # Complexity
266
+ if (
267
+ hasattr(chunk, "cognitive_complexity")
268
+ and chunk.cognitive_complexity
269
+ ):
270
+ complexities.append(chunk.cognitive_complexity)
271
+ # High complexity = cognitive > 20
272
+ if chunk.cognitive_complexity > 20:
273
+ high_complexity_count += 1
274
+
275
+ # Code smells
276
+ if hasattr(chunk, "smell_count") and chunk.smell_count:
277
+ smell_counts += chunk.smell_count
278
+
279
+ metrics["total_lines"] = total_lines
280
+
281
+ if complexities:
282
+ metrics["avg_complexity"] = sum(complexities) / len(complexities)
283
+ metrics["max_complexity"] = max(complexities)
284
+
285
+ metrics["code_smells_count"] = smell_counts
286
+ metrics["high_complexity_files"] = high_complexity_count
287
+
288
+ # Compute health score (0-100)
289
+ # Formula: Base 100, penalty for complexity and smells
290
+ health = 100.0
291
+
292
+ # Penalty for average complexity
293
+ if metrics["avg_complexity"] > 30:
294
+ health -= 50
295
+ elif metrics["avg_complexity"] > 20:
296
+ health -= 30
297
+ elif metrics["avg_complexity"] > 10:
298
+ health -= 20
299
+ elif metrics["avg_complexity"] > 5:
300
+ health -= 10
301
+
302
+ # Penalty for code smells (5 points per smell, max 30 points)
303
+ smell_penalty = min(30, smell_counts * 5)
304
+ health -= smell_penalty
305
+
306
+ metrics["health_score"] = max(0, int(health))
307
+
308
+ return metrics
@@ -0,0 +1,90 @@
1
+ """Visualization and export schemas for code analysis results.
2
+
3
+ This module provides the JSON export format for structural code analysis,
4
+ enabling integration with visualization tools and external analysis platforms.
5
+
6
+ The export schema is versioned and designed to be stable across tool updates,
7
+ with support for:
8
+ - Complete metric snapshots (complexity, coupling, smells)
9
+ - Dependency graph visualization
10
+ - Historical trend tracking
11
+ - Git-aware context for time-series analysis
12
+
13
+ Example:
14
+ >>> from mcp_vector_search.analysis.visualizer.schemas import (
15
+ ... AnalysisExport,
16
+ ... ExportMetadata,
17
+ ... MetricsSummary,
18
+ ... generate_json_schema
19
+ ... )
20
+ >>> from datetime import datetime
21
+ >>>
22
+ >>> # Create export
23
+ >>> export = AnalysisExport(
24
+ ... metadata=ExportMetadata(
25
+ ... version="1.0.0",
26
+ ... generated_at=datetime.now(),
27
+ ... tool_version="0.19.0",
28
+ ... project_root="/path/to/project"
29
+ ... ),
30
+ ... summary=MetricsSummary(...),
31
+ ... files=[],
32
+ ... dependencies=DependencyGraph(edges=[], circular_dependencies=[])
33
+ ... )
34
+ >>>
35
+ >>> # Export to JSON
36
+ >>> json_data = export.model_dump_json(indent=2)
37
+ >>>
38
+ >>> # Generate schema for documentation
39
+ >>> schema = generate_json_schema()
40
+ """
41
+
42
+ from .d3_data import D3Edge, D3Node, transform_for_d3
43
+ from .exporter import JSONExporter
44
+ from .html_report import HTMLReportGenerator
45
+ from .schemas import (
46
+ AnalysisExport,
47
+ ClassMetrics,
48
+ CyclicDependency,
49
+ DependencyEdge,
50
+ DependencyGraph,
51
+ ExportMetadata,
52
+ FileDetail,
53
+ FunctionMetrics,
54
+ MetricsSummary,
55
+ MetricTrend,
56
+ SmellLocation,
57
+ TrendData,
58
+ TrendDataPoint,
59
+ generate_json_schema,
60
+ )
61
+
62
+ __all__ = [
63
+ # Exporters
64
+ "JSONExporter",
65
+ "HTMLReportGenerator",
66
+ # D3 visualization
67
+ "D3Node",
68
+ "D3Edge",
69
+ "transform_for_d3",
70
+ # Main export schema
71
+ "AnalysisExport",
72
+ # Metadata and summary
73
+ "ExportMetadata",
74
+ "MetricsSummary",
75
+ # File-level schemas
76
+ "FileDetail",
77
+ "FunctionMetrics",
78
+ "ClassMetrics",
79
+ "SmellLocation",
80
+ # Dependency analysis
81
+ "DependencyGraph",
82
+ "DependencyEdge",
83
+ "CyclicDependency",
84
+ # Trend tracking
85
+ "TrendData",
86
+ "MetricTrend",
87
+ "TrendDataPoint",
88
+ # Utilities
89
+ "generate_json_schema",
90
+ ]