mcp-code-indexer 2.3.0__py3-none-any.whl → 3.0.0__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_code_indexer/ask_handler.py +5 -7
- mcp_code_indexer/claude_api_handler.py +2 -2
- mcp_code_indexer/cleanup_manager.py +255 -0
- mcp_code_indexer/database/database.py +82 -90
- mcp_code_indexer/database/models.py +3 -5
- mcp_code_indexer/deepask_handler.py +5 -9
- mcp_code_indexer/git_hook_handler.py +2 -9
- mcp_code_indexer/main.py +1 -0
- mcp_code_indexer/server/mcp_server.py +107 -209
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/METADATA +3 -3
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/RECORD +15 -14
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-2.3.0.dist-info → mcp_code_indexer-3.0.0.dist-info}/top_level.txt +0 -0
@@ -31,6 +31,7 @@ from mcp_code_indexer.database.connection_health import (
|
|
31
31
|
ConnectionHealthMonitor, DatabaseMetricsCollector
|
32
32
|
)
|
33
33
|
from mcp_code_indexer.query_preprocessor import preprocess_search_query
|
34
|
+
from mcp_code_indexer.cleanup_manager import CleanupManager
|
34
35
|
|
35
36
|
logger = logging.getLogger(__name__)
|
36
37
|
|
@@ -79,6 +80,9 @@ class DatabaseManager:
|
|
79
80
|
self._health_monitor = None # Initialized in async context
|
80
81
|
self._metrics_collector = DatabaseMetricsCollector()
|
81
82
|
|
83
|
+
# Cleanup manager for retention policies
|
84
|
+
self._cleanup_manager = None # Initialized in async context
|
85
|
+
|
82
86
|
async def initialize(self) -> None:
|
83
87
|
"""Initialize database schema and configuration."""
|
84
88
|
import asyncio
|
@@ -97,6 +101,9 @@ class DatabaseManager:
|
|
97
101
|
)
|
98
102
|
await self._health_monitor.start_monitoring()
|
99
103
|
|
104
|
+
# Initialize cleanup manager
|
105
|
+
self._cleanup_manager = CleanupManager(self, retention_months=6)
|
106
|
+
|
100
107
|
# Ensure database directory exists
|
101
108
|
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
102
109
|
|
@@ -703,20 +710,7 @@ class DatabaseManager:
|
|
703
710
|
|
704
711
|
return projects
|
705
712
|
|
706
|
-
|
707
|
-
"""Get file counts per branch for a project."""
|
708
|
-
async with self.get_connection() as db:
|
709
|
-
cursor = await db.execute(
|
710
|
-
"""
|
711
|
-
SELECT branch, COUNT(*) as file_count
|
712
|
-
FROM file_descriptions
|
713
|
-
WHERE project_id = ?
|
714
|
-
GROUP BY branch
|
715
|
-
""",
|
716
|
-
(project_id,)
|
717
|
-
)
|
718
|
-
rows = await cursor.fetchall()
|
719
|
-
return {row[0]: row[1] for row in rows}
|
713
|
+
|
720
714
|
|
721
715
|
# File description operations
|
722
716
|
|
@@ -726,18 +720,18 @@ class DatabaseManager:
|
|
726
720
|
await db.execute(
|
727
721
|
"""
|
728
722
|
INSERT OR REPLACE INTO file_descriptions
|
729
|
-
(project_id,
|
723
|
+
(project_id, file_path, description, file_hash, last_modified, version, source_project_id, to_be_cleaned)
|
730
724
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
731
725
|
""",
|
732
726
|
(
|
733
727
|
file_desc.project_id,
|
734
|
-
file_desc.branch,
|
735
728
|
file_desc.file_path,
|
736
729
|
file_desc.description,
|
737
730
|
file_desc.file_hash,
|
738
731
|
file_desc.last_modified,
|
739
732
|
file_desc.version,
|
740
|
-
file_desc.source_project_id
|
733
|
+
file_desc.source_project_id,
|
734
|
+
file_desc.to_be_cleaned
|
741
735
|
)
|
742
736
|
)
|
743
737
|
await db.commit()
|
@@ -746,60 +740,60 @@ class DatabaseManager:
|
|
746
740
|
async def get_file_description(
|
747
741
|
self,
|
748
742
|
project_id: str,
|
749
|
-
branch: str,
|
750
743
|
file_path: str
|
751
744
|
) -> Optional[FileDescription]:
|
752
|
-
"""Get file description by project
|
745
|
+
"""Get file description by project and path."""
|
753
746
|
async with self.get_connection() as db:
|
754
747
|
cursor = await db.execute(
|
755
748
|
"""
|
756
749
|
SELECT * FROM file_descriptions
|
757
|
-
WHERE project_id = ? AND
|
750
|
+
WHERE project_id = ? AND file_path = ? AND to_be_cleaned IS NULL
|
758
751
|
""",
|
759
|
-
(project_id,
|
752
|
+
(project_id, file_path)
|
760
753
|
)
|
761
754
|
row = await cursor.fetchone()
|
762
755
|
|
763
756
|
if row:
|
764
757
|
return FileDescription(
|
758
|
+
id=row['id'],
|
765
759
|
project_id=row['project_id'],
|
766
|
-
branch=row['branch'],
|
767
760
|
file_path=row['file_path'],
|
768
761
|
description=row['description'],
|
769
762
|
file_hash=row['file_hash'],
|
770
763
|
last_modified=datetime.fromisoformat(row['last_modified']),
|
771
764
|
version=row['version'],
|
772
|
-
source_project_id=row['source_project_id']
|
765
|
+
source_project_id=row['source_project_id'],
|
766
|
+
to_be_cleaned=row['to_be_cleaned']
|
773
767
|
)
|
774
768
|
return None
|
775
769
|
|
776
770
|
async def get_all_file_descriptions(
|
777
771
|
self,
|
778
|
-
project_id: str
|
779
|
-
branch: str
|
772
|
+
project_id: str
|
780
773
|
) -> List[FileDescription]:
|
781
|
-
"""Get all file descriptions for a project
|
774
|
+
"""Get all file descriptions for a project."""
|
782
775
|
async with self.get_connection() as db:
|
783
776
|
cursor = await db.execute(
|
784
777
|
"""
|
785
778
|
SELECT * FROM file_descriptions
|
786
|
-
WHERE project_id = ? AND
|
779
|
+
WHERE project_id = ? AND to_be_cleaned IS NULL
|
787
780
|
ORDER BY file_path
|
788
781
|
""",
|
789
|
-
(project_id,
|
782
|
+
(project_id,)
|
790
783
|
)
|
791
784
|
rows = await cursor.fetchall()
|
792
785
|
|
793
786
|
return [
|
794
787
|
FileDescription(
|
788
|
+
id=row['id'],
|
795
789
|
project_id=row['project_id'],
|
796
|
-
branch=row['branch'],
|
797
790
|
file_path=row['file_path'],
|
798
791
|
description=row['description'],
|
799
792
|
file_hash=row['file_hash'],
|
800
793
|
last_modified=datetime.fromisoformat(row['last_modified']),
|
801
794
|
version=row['version'],
|
802
|
-
source_project_id=row['source_project_id']
|
795
|
+
source_project_id=row['source_project_id'],
|
796
|
+
to_be_cleaned=row['to_be_cleaned']
|
803
797
|
)
|
804
798
|
for row in rows
|
805
799
|
]
|
@@ -813,13 +807,13 @@ class DatabaseManager:
|
|
813
807
|
data = [
|
814
808
|
(
|
815
809
|
fd.project_id,
|
816
|
-
fd.branch,
|
817
810
|
fd.file_path,
|
818
811
|
fd.description,
|
819
812
|
fd.file_hash,
|
820
813
|
fd.last_modified,
|
821
814
|
fd.version,
|
822
|
-
fd.source_project_id
|
815
|
+
fd.source_project_id,
|
816
|
+
fd.to_be_cleaned
|
823
817
|
)
|
824
818
|
for fd in file_descriptions
|
825
819
|
]
|
@@ -827,7 +821,7 @@ class DatabaseManager:
|
|
827
821
|
await conn.executemany(
|
828
822
|
"""
|
829
823
|
INSERT OR REPLACE INTO file_descriptions
|
830
|
-
(project_id,
|
824
|
+
(project_id, file_path, description, file_hash, last_modified, version, source_project_id, to_be_cleaned)
|
831
825
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
832
826
|
""",
|
833
827
|
data
|
@@ -845,7 +839,6 @@ class DatabaseManager:
|
|
845
839
|
async def search_file_descriptions(
|
846
840
|
self,
|
847
841
|
project_id: str,
|
848
|
-
branch: str,
|
849
842
|
query: str,
|
850
843
|
max_results: int = 20
|
851
844
|
) -> List[SearchResult]:
|
@@ -864,26 +857,24 @@ class DatabaseManager:
|
|
864
857
|
"""
|
865
858
|
SELECT
|
866
859
|
fd.project_id,
|
867
|
-
fd.branch,
|
868
860
|
fd.file_path,
|
869
861
|
fd.description,
|
870
862
|
bm25(file_descriptions_fts) as rank
|
871
863
|
FROM file_descriptions_fts
|
872
|
-
JOIN file_descriptions fd ON fd.
|
864
|
+
JOIN file_descriptions fd ON fd.id = file_descriptions_fts.rowid
|
873
865
|
WHERE file_descriptions_fts MATCH ?
|
874
866
|
AND fd.project_id = ?
|
875
|
-
AND fd.
|
867
|
+
AND fd.to_be_cleaned IS NULL
|
876
868
|
ORDER BY bm25(file_descriptions_fts)
|
877
869
|
LIMIT ?
|
878
870
|
""",
|
879
|
-
(preprocessed_query, project_id,
|
871
|
+
(preprocessed_query, project_id, max_results)
|
880
872
|
)
|
881
873
|
rows = await cursor.fetchall()
|
882
874
|
|
883
875
|
return [
|
884
876
|
SearchResult(
|
885
877
|
project_id=row['project_id'],
|
886
|
-
branch=row['branch'],
|
887
878
|
file_path=row['file_path'],
|
888
879
|
description=row['description'],
|
889
880
|
relevance_score=row['rank']
|
@@ -936,12 +927,12 @@ class DatabaseManager:
|
|
936
927
|
|
937
928
|
# Utility operations
|
938
929
|
|
939
|
-
async def get_file_count(self, project_id: str
|
940
|
-
"""Get count of files in a project
|
930
|
+
async def get_file_count(self, project_id: str) -> int:
|
931
|
+
"""Get count of files in a project."""
|
941
932
|
async with self.get_connection() as db:
|
942
933
|
cursor = await db.execute(
|
943
|
-
"SELECT COUNT(*) as count FROM file_descriptions WHERE project_id = ? AND
|
944
|
-
(project_id,
|
934
|
+
"SELECT COUNT(*) as count FROM file_descriptions WHERE project_id = ? AND to_be_cleaned IS NULL",
|
935
|
+
(project_id,)
|
945
936
|
)
|
946
937
|
row = await cursor.fetchone()
|
947
938
|
return row['count'] if row else 0
|
@@ -1030,12 +1021,11 @@ class DatabaseManager:
|
|
1030
1021
|
await db.execute(
|
1031
1022
|
"""
|
1032
1023
|
INSERT OR REPLACE INTO project_overviews
|
1033
|
-
(project_id,
|
1034
|
-
VALUES (?, ?, ?, ?,
|
1024
|
+
(project_id, overview, last_modified, total_files, total_tokens)
|
1025
|
+
VALUES (?, ?, ?, ?, ?)
|
1035
1026
|
""",
|
1036
1027
|
(
|
1037
1028
|
overview.project_id,
|
1038
|
-
overview.branch,
|
1039
1029
|
overview.overview,
|
1040
1030
|
overview.last_modified,
|
1041
1031
|
overview.total_files,
|
@@ -1043,21 +1033,20 @@ class DatabaseManager:
|
|
1043
1033
|
)
|
1044
1034
|
)
|
1045
1035
|
await db.commit()
|
1046
|
-
logger.debug(f"Created/updated overview for project {overview.project_id}
|
1036
|
+
logger.debug(f"Created/updated overview for project {overview.project_id}")
|
1047
1037
|
|
1048
|
-
async def get_project_overview(self, project_id: str
|
1049
|
-
"""Get project overview by ID
|
1038
|
+
async def get_project_overview(self, project_id: str) -> Optional[ProjectOverview]:
|
1039
|
+
"""Get project overview by ID."""
|
1050
1040
|
async with self.get_connection() as db:
|
1051
1041
|
cursor = await db.execute(
|
1052
|
-
"SELECT * FROM project_overviews WHERE project_id = ?
|
1053
|
-
(project_id,
|
1042
|
+
"SELECT * FROM project_overviews WHERE project_id = ?",
|
1043
|
+
(project_id,)
|
1054
1044
|
)
|
1055
1045
|
row = await cursor.fetchone()
|
1056
1046
|
|
1057
1047
|
if row:
|
1058
1048
|
return ProjectOverview(
|
1059
1049
|
project_id=row['project_id'],
|
1060
|
-
branch=row['branch'],
|
1061
1050
|
overview=row['overview'],
|
1062
1051
|
last_modified=datetime.fromisoformat(row['last_modified']),
|
1063
1052
|
total_files=row['total_files'],
|
@@ -1065,25 +1054,24 @@ class DatabaseManager:
|
|
1065
1054
|
)
|
1066
1055
|
return None
|
1067
1056
|
|
1068
|
-
async def cleanup_missing_files(self, project_id: str,
|
1057
|
+
async def cleanup_missing_files(self, project_id: str, project_root: Path) -> List[str]:
|
1069
1058
|
"""
|
1070
|
-
|
1059
|
+
Mark descriptions for cleanup for files that no longer exist on disk.
|
1071
1060
|
|
1072
1061
|
Args:
|
1073
1062
|
project_id: Project identifier
|
1074
|
-
branch: Branch name
|
1075
1063
|
project_root: Path to project root directory
|
1076
1064
|
|
1077
1065
|
Returns:
|
1078
|
-
List of file paths that were
|
1066
|
+
List of file paths that were marked for cleanup
|
1079
1067
|
"""
|
1080
1068
|
removed_files = []
|
1081
1069
|
|
1082
1070
|
async def cleanup_operation(conn: aiosqlite.Connection) -> List[str]:
|
1083
|
-
# Get all file descriptions for this project
|
1071
|
+
# Get all active file descriptions for this project
|
1084
1072
|
cursor = await conn.execute(
|
1085
|
-
"SELECT file_path FROM file_descriptions WHERE project_id = ? AND
|
1086
|
-
(project_id,
|
1073
|
+
"SELECT file_path FROM file_descriptions WHERE project_id = ? AND to_be_cleaned IS NULL",
|
1074
|
+
(project_id,)
|
1087
1075
|
)
|
1088
1076
|
|
1089
1077
|
rows = await cursor.fetchall()
|
@@ -1097,31 +1085,32 @@ class DatabaseManager:
|
|
1097
1085
|
if not full_path.exists():
|
1098
1086
|
to_remove.append(file_path)
|
1099
1087
|
|
1100
|
-
#
|
1088
|
+
# Mark descriptions for cleanup instead of deleting
|
1101
1089
|
if to_remove:
|
1090
|
+
import time
|
1091
|
+
cleanup_timestamp = int(time.time())
|
1102
1092
|
await conn.executemany(
|
1103
|
-
"
|
1104
|
-
[(
|
1093
|
+
"UPDATE file_descriptions SET to_be_cleaned = ? WHERE project_id = ? AND file_path = ?",
|
1094
|
+
[(cleanup_timestamp, project_id, path) for path in to_remove]
|
1105
1095
|
)
|
1106
|
-
logger.info(f"
|
1096
|
+
logger.info(f"Marked {len(to_remove)} missing files for cleanup from {project_id}")
|
1107
1097
|
|
1108
1098
|
return to_remove
|
1109
1099
|
|
1110
1100
|
removed_files = await self.execute_transaction_with_retry(
|
1111
1101
|
cleanup_operation,
|
1112
|
-
f"cleanup_missing_files_{project_id}
|
1102
|
+
f"cleanup_missing_files_{project_id}",
|
1113
1103
|
timeout_seconds=60.0 # Longer timeout for file system operations
|
1114
1104
|
)
|
1115
1105
|
|
1116
1106
|
return removed_files
|
1117
1107
|
|
1118
|
-
async def analyze_word_frequency(self, project_id: str,
|
1108
|
+
async def analyze_word_frequency(self, project_id: str, limit: int = 200) -> WordFrequencyResult:
|
1119
1109
|
"""
|
1120
|
-
Analyze word frequency across all file descriptions for a project
|
1110
|
+
Analyze word frequency across all file descriptions for a project.
|
1121
1111
|
|
1122
1112
|
Args:
|
1123
1113
|
project_id: Project identifier
|
1124
|
-
branch: Branch name
|
1125
1114
|
limit: Maximum number of top terms to return
|
1126
1115
|
|
1127
1116
|
Returns:
|
@@ -1152,10 +1141,10 @@ class DatabaseManager:
|
|
1152
1141
|
stop_words.update(programming_keywords)
|
1153
1142
|
|
1154
1143
|
async with self.get_connection() as db:
|
1155
|
-
# Get all descriptions for this project
|
1144
|
+
# Get all descriptions for this project
|
1156
1145
|
cursor = await db.execute(
|
1157
|
-
"SELECT description FROM file_descriptions WHERE project_id = ? AND
|
1158
|
-
(project_id,
|
1146
|
+
"SELECT description FROM file_descriptions WHERE project_id = ? AND to_be_cleaned IS NULL",
|
1147
|
+
(project_id,)
|
1159
1148
|
)
|
1160
1149
|
|
1161
1150
|
rows = await cursor.fetchall()
|
@@ -1218,13 +1207,12 @@ class DatabaseManager:
|
|
1218
1207
|
await db.commit()
|
1219
1208
|
return removed_count
|
1220
1209
|
|
1221
|
-
async def get_project_map_data(self, project_identifier: str
|
1210
|
+
async def get_project_map_data(self, project_identifier: str) -> dict:
|
1222
1211
|
"""
|
1223
1212
|
Get all data needed to generate a project map.
|
1224
1213
|
|
1225
1214
|
Args:
|
1226
1215
|
project_identifier: Project name or ID
|
1227
|
-
branch: Branch name (optional, will use first available if not specified)
|
1228
1216
|
|
1229
1217
|
Returns:
|
1230
1218
|
Dictionary containing project info, overview, and file descriptions
|
@@ -1256,39 +1244,43 @@ class DatabaseManager:
|
|
1256
1244
|
|
1257
1245
|
project = Project(**project_dict)
|
1258
1246
|
|
1259
|
-
# If no branch specified, find the first available branch
|
1260
|
-
if not branch:
|
1261
|
-
cursor = await db.execute(
|
1262
|
-
"SELECT DISTINCT branch FROM file_descriptions WHERE project_id = ? LIMIT 1",
|
1263
|
-
(project.id,)
|
1264
|
-
)
|
1265
|
-
branch_row = await cursor.fetchone()
|
1266
|
-
if branch_row:
|
1267
|
-
branch = branch_row['branch']
|
1268
|
-
else:
|
1269
|
-
branch = 'main' # Default fallback
|
1270
|
-
|
1271
1247
|
# Get project overview
|
1272
1248
|
cursor = await db.execute(
|
1273
|
-
"SELECT * FROM project_overviews WHERE project_id = ?
|
1274
|
-
(project.id,
|
1249
|
+
"SELECT * FROM project_overviews WHERE project_id = ?",
|
1250
|
+
(project.id,)
|
1275
1251
|
)
|
1276
1252
|
overview_row = await cursor.fetchone()
|
1277
1253
|
project_overview = ProjectOverview(**overview_row) if overview_row else None
|
1278
1254
|
|
1279
|
-
# Get all file descriptions for this project
|
1255
|
+
# Get all file descriptions for this project
|
1280
1256
|
cursor = await db.execute(
|
1281
1257
|
"""SELECT * FROM file_descriptions
|
1282
|
-
WHERE project_id = ? AND
|
1258
|
+
WHERE project_id = ? AND to_be_cleaned IS NULL
|
1283
1259
|
ORDER BY file_path""",
|
1284
|
-
(project.id,
|
1260
|
+
(project.id,)
|
1285
1261
|
)
|
1286
1262
|
file_rows = await cursor.fetchall()
|
1287
1263
|
file_descriptions = [FileDescription(**row) for row in file_rows]
|
1288
1264
|
|
1289
1265
|
return {
|
1290
1266
|
'project': project,
|
1291
|
-
'branch': branch,
|
1292
1267
|
'overview': project_overview,
|
1293
1268
|
'files': file_descriptions
|
1294
1269
|
}
|
1270
|
+
|
1271
|
+
# Cleanup operations
|
1272
|
+
|
1273
|
+
@property
|
1274
|
+
def cleanup_manager(self) -> CleanupManager:
|
1275
|
+
"""Get the cleanup manager instance."""
|
1276
|
+
if self._cleanup_manager is None:
|
1277
|
+
self._cleanup_manager = CleanupManager(self, retention_months=6)
|
1278
|
+
return self._cleanup_manager
|
1279
|
+
|
1280
|
+
async def mark_file_for_cleanup(self, project_id: str, file_path: str) -> bool:
|
1281
|
+
"""Mark a file for cleanup. Convenience method."""
|
1282
|
+
return await self.cleanup_manager.mark_file_for_cleanup(project_id, file_path)
|
1283
|
+
|
1284
|
+
async def perform_cleanup(self, project_id: Optional[str] = None) -> int:
|
1285
|
+
"""Perform cleanup of old records. Convenience method."""
|
1286
|
+
return await self.cleanup_manager.perform_cleanup(project_id)
|
@@ -29,19 +29,20 @@ class Project(BaseModel):
|
|
29
29
|
|
30
30
|
class FileDescription(BaseModel):
|
31
31
|
"""
|
32
|
-
Represents a file description within a project
|
32
|
+
Represents a file description within a project.
|
33
33
|
|
34
34
|
Stores detailed summaries of file contents including purpose, components,
|
35
35
|
and relationships to enable efficient codebase navigation.
|
36
36
|
"""
|
37
|
+
id: Optional[int] = Field(None, description="Database ID")
|
37
38
|
project_id: str = Field(..., description="Reference to project")
|
38
|
-
branch: str = Field(..., description="Git branch name")
|
39
39
|
file_path: str = Field(..., description="Relative path from project root")
|
40
40
|
description: str = Field(..., description="Detailed content description")
|
41
41
|
file_hash: Optional[str] = Field(None, description="SHA-256 of file contents")
|
42
42
|
last_modified: datetime = Field(default_factory=datetime.utcnow, description="Last update timestamp")
|
43
43
|
version: int = Field(default=1, description="For optimistic concurrency control")
|
44
44
|
source_project_id: Optional[str] = Field(None, description="Source project if copied from upstream")
|
45
|
+
to_be_cleaned: Optional[int] = Field(None, description="UNIX timestamp for cleanup, NULL = active")
|
45
46
|
|
46
47
|
|
47
48
|
class MergeConflict(BaseModel):
|
@@ -71,7 +72,6 @@ class ProjectOverview(BaseModel):
|
|
71
72
|
individual file descriptions.
|
72
73
|
"""
|
73
74
|
project_id: str = Field(..., description="Reference to project")
|
74
|
-
branch: str = Field(..., description="Git branch name")
|
75
75
|
overview: str = Field(..., description="Comprehensive codebase narrative")
|
76
76
|
last_modified: datetime = Field(default_factory=datetime.utcnow, description="Last update timestamp")
|
77
77
|
total_files: int = Field(..., description="Number of files in codebase")
|
@@ -86,7 +86,6 @@ class CodebaseOverview(BaseModel):
|
|
86
86
|
to help determine whether to use full overview or search-based approach.
|
87
87
|
"""
|
88
88
|
project_name: str = Field(..., description="Project name")
|
89
|
-
branch: str = Field(..., description="Git branch")
|
90
89
|
total_files: int = Field(..., description="Total number of tracked files")
|
91
90
|
total_tokens: int = Field(..., description="Total token count for all descriptions")
|
92
91
|
is_large: bool = Field(..., description="True if exceeds configured token limit")
|
@@ -121,7 +120,6 @@ class SearchResult(BaseModel):
|
|
121
120
|
description: str = Field(..., description="File description")
|
122
121
|
relevance_score: float = Field(..., description="Search relevance score")
|
123
122
|
project_id: str = Field(..., description="Project identifier")
|
124
|
-
branch: str = Field(..., description="Git branch")
|
125
123
|
|
126
124
|
|
127
125
|
class CodebaseSizeInfo(BaseModel):
|
@@ -75,7 +75,7 @@ class DeepAskHandler(ClaudeAPIHandler):
|
|
75
75
|
Ask an enhanced question about the project using two-stage Claude API processing.
|
76
76
|
|
77
77
|
Args:
|
78
|
-
project_info: Project information dict with projectName, folderPath,
|
78
|
+
project_info: Project information dict with projectName, folderPath, etc.
|
79
79
|
question: User's question about the project
|
80
80
|
max_file_results: Maximum number of file descriptions to include
|
81
81
|
|
@@ -118,8 +118,7 @@ class DeepAskHandler(ClaudeAPIHandler):
|
|
118
118
|
"stage1_tokens": stage1_result["token_usage"],
|
119
119
|
"stage2_tokens": stage2_result["token_usage"],
|
120
120
|
"total_files_found": stage2_result["total_files_found"],
|
121
|
-
"files_included": len(stage2_result["relevant_files"])
|
122
|
-
"branch": project_info.get("branch", "unknown")
|
121
|
+
"files_included": len(stage2_result["relevant_files"])
|
123
122
|
}
|
124
123
|
}
|
125
124
|
|
@@ -237,7 +236,6 @@ class DeepAskHandler(ClaudeAPIHandler):
|
|
237
236
|
try:
|
238
237
|
search_results = await self.db_manager.search_file_descriptions(
|
239
238
|
project_id=project.id,
|
240
|
-
branch=project_info["branch"],
|
241
239
|
query=search_term,
|
242
240
|
max_results=max_file_results
|
243
241
|
)
|
@@ -322,9 +320,8 @@ class DeepAskHandler(ClaudeAPIHandler):
|
|
322
320
|
) -> str:
|
323
321
|
"""Build stage 1 prompt for extracting search terms."""
|
324
322
|
project_name = project_info["projectName"]
|
325
|
-
branch = project_info.get("branch", "unknown")
|
326
323
|
|
327
|
-
return f"""I need to answer a question about the codebase "{project_name}"
|
324
|
+
return f"""I need to answer a question about the codebase "{project_name}". To provide the best answer, I need to search for relevant files and then answer the question.
|
328
325
|
|
329
326
|
PROJECT OVERVIEW:
|
330
327
|
{overview}
|
@@ -352,7 +349,6 @@ Respond with valid JSON in this format:
|
|
352
349
|
) -> str:
|
353
350
|
"""Build stage 2 prompt for enhanced answer."""
|
354
351
|
project_name = project_info["projectName"]
|
355
|
-
branch = project_info.get("branch", "unknown")
|
356
352
|
|
357
353
|
# Format file descriptions
|
358
354
|
file_context = ""
|
@@ -365,7 +361,7 @@ Respond with valid JSON in this format:
|
|
365
361
|
else:
|
366
362
|
file_context = "\n\nNo relevant files found in the search."
|
367
363
|
|
368
|
-
return f"""Please answer the following question about the codebase "{project_name}"
|
364
|
+
return f"""Please answer the following question about the codebase "{project_name}".
|
369
365
|
|
370
366
|
PROJECT OVERVIEW (COMPRESSED):
|
371
367
|
{compressed_overview}
|
@@ -432,7 +428,7 @@ Your answer should be comprehensive but focused on the specific question asked."
|
|
432
428
|
|
433
429
|
output = []
|
434
430
|
output.append(f"Question: {result['question']}")
|
435
|
-
output.append(f"Project: {result['project_name']}
|
431
|
+
output.append(f"Project: {result['project_name']}")
|
436
432
|
output.append("")
|
437
433
|
output.append("Answer:")
|
438
434
|
output.append(answer)
|
@@ -307,17 +307,12 @@ Return ONLY a JSON object:
|
|
307
307
|
except subprocess.CalledProcessError:
|
308
308
|
pass # No upstream remote
|
309
309
|
|
310
|
-
# Get current branch
|
311
|
-
branch_result = await self._run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
|
312
|
-
branch = branch_result.strip() if branch_result else "main"
|
313
|
-
|
314
310
|
# Extract project name from remote URL or use directory name
|
315
311
|
project_name = self._extract_project_name(remote_origin, project_root)
|
316
312
|
|
317
313
|
return {
|
318
314
|
"projectName": project_name,
|
319
315
|
"folderPath": str(project_root),
|
320
|
-
"branch": branch,
|
321
316
|
"remoteOrigin": remote_origin,
|
322
317
|
"upstreamOrigin": upstream_origin
|
323
318
|
}
|
@@ -515,7 +510,7 @@ Return ONLY a JSON object:
|
|
515
510
|
|
516
511
|
if project:
|
517
512
|
overview = await self.db_manager.get_project_overview(
|
518
|
-
project.id
|
513
|
+
project.id
|
519
514
|
)
|
520
515
|
return overview.overview if overview else ""
|
521
516
|
|
@@ -538,7 +533,7 @@ Return ONLY a JSON object:
|
|
538
533
|
|
539
534
|
if project:
|
540
535
|
descriptions = await self.db_manager.get_all_file_descriptions(
|
541
|
-
project.id
|
536
|
+
project.id
|
542
537
|
)
|
543
538
|
return {desc.file_path: desc.description for desc in descriptions}
|
544
539
|
|
@@ -883,7 +878,6 @@ Return ONLY a JSON object:
|
|
883
878
|
|
884
879
|
file_desc = FileDescription(
|
885
880
|
project_id=project.id,
|
886
|
-
branch=project_info["branch"],
|
887
881
|
file_path=file_path,
|
888
882
|
description=description,
|
889
883
|
file_hash=None,
|
@@ -901,7 +895,6 @@ Return ONLY a JSON object:
|
|
901
895
|
|
902
896
|
overview = ProjectOverview(
|
903
897
|
project_id=project.id,
|
904
|
-
branch=project_info["branch"],
|
905
898
|
overview=overview_update,
|
906
899
|
last_modified=datetime.utcnow(),
|
907
900
|
total_files=len(file_updates),
|
mcp_code_indexer/main.py
CHANGED
@@ -294,6 +294,7 @@ async def handle_runcommand(args: argparse.Namespace) -> None:
|
|
294
294
|
"update_codebase_overview": server._handle_update_codebase_overview,
|
295
295
|
"get_word_frequency": server._handle_get_word_frequency,
|
296
296
|
"merge_branch_descriptions": server._handle_merge_branch_descriptions,
|
297
|
+
"search_codebase_overview": server._handle_search_codebase_overview,
|
297
298
|
}
|
298
299
|
|
299
300
|
if tool_name not in tool_handlers:
|