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
@@ -32,7 +32,7 @@ from mcp_code_indexer.database.models import (
|
|
32
32
|
from mcp_code_indexer.error_handler import setup_error_handling, ErrorHandler
|
33
33
|
from mcp_code_indexer.middleware.error_middleware import create_tool_middleware, AsyncTaskManager
|
34
34
|
from mcp_code_indexer.logging_config import get_logger
|
35
|
-
|
35
|
+
|
36
36
|
|
37
37
|
logger = logging.getLogger(__name__)
|
38
38
|
|
@@ -104,7 +104,6 @@ class MCPCodeIndexServer:
|
|
104
104
|
retry_jitter=retry_jitter
|
105
105
|
)
|
106
106
|
self.token_counter = TokenCounter(token_limit)
|
107
|
-
self.merge_handler = MergeHandler(self.db_manager)
|
108
107
|
|
109
108
|
# Setup error handling
|
110
109
|
self.logger = get_logger(__name__)
|
@@ -248,10 +247,7 @@ class MCPCodeIndexServer:
|
|
248
247
|
"type": "string",
|
249
248
|
"description": "Absolute path to the project folder on disk"
|
250
249
|
},
|
251
|
-
|
252
|
-
"type": "string",
|
253
|
-
"description": "Git branch name (e.g., 'main', 'develop')"
|
254
|
-
},
|
250
|
+
|
255
251
|
"remoteOrigin": {
|
256
252
|
"type": "string",
|
257
253
|
"description": "Git remote origin URL if available"
|
@@ -265,7 +261,7 @@ class MCPCodeIndexServer:
|
|
265
261
|
"description": "Relative path to the file from project root"
|
266
262
|
}
|
267
263
|
},
|
268
|
-
"required": ["projectName", "folderPath", "
|
264
|
+
"required": ["projectName", "folderPath", "filePath"],
|
269
265
|
"additionalProperties": False
|
270
266
|
}
|
271
267
|
),
|
@@ -277,31 +273,29 @@ class MCPCodeIndexServer:
|
|
277
273
|
"properties": {
|
278
274
|
"projectName": {"type": "string", "description": "The name of the project"},
|
279
275
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
280
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
281
276
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
282
277
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
283
278
|
"filePath": {"type": "string", "description": "Relative path to the file from project root"},
|
284
279
|
"description": {"type": "string", "description": "Detailed description of the file's contents"},
|
285
280
|
"fileHash": {"type": "string", "description": "SHA-256 hash of the file contents (optional)"}
|
286
281
|
},
|
287
|
-
"required": ["projectName", "folderPath", "
|
282
|
+
"required": ["projectName", "folderPath", "filePath", "description"],
|
288
283
|
"additionalProperties": False
|
289
284
|
}
|
290
285
|
),
|
291
286
|
types.Tool(
|
292
287
|
name="check_codebase_size",
|
293
|
-
description="Checks the total token count of a codebase's file structure and descriptions
|
288
|
+
description="Checks the total token count of a codebase's file structure and descriptions. Returns whether the codebase is 'large' and recommends using search instead of the full overview.",
|
294
289
|
inputSchema={
|
295
290
|
"type": "object",
|
296
291
|
"properties": {
|
297
292
|
"projectName": {"type": "string", "description": "The name of the project"},
|
298
293
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
299
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
300
294
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
301
295
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
302
296
|
"tokenLimit": {"type": "integer", "description": "Optional token limit override (defaults to server configuration)"}
|
303
297
|
},
|
304
|
-
"required": ["projectName", "folderPath"
|
298
|
+
"required": ["projectName", "folderPath"],
|
305
299
|
"additionalProperties": False
|
306
300
|
}
|
307
301
|
),
|
@@ -313,12 +307,11 @@ class MCPCodeIndexServer:
|
|
313
307
|
"properties": {
|
314
308
|
"projectName": {"type": "string", "description": "The name of the project"},
|
315
309
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
316
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
317
310
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
318
311
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
319
312
|
"limit": {"type": "integer", "description": "Maximum number of missing files to return (optional)"}
|
320
313
|
},
|
321
|
-
"required": ["projectName", "folderPath"
|
314
|
+
"required": ["projectName", "folderPath"],
|
322
315
|
"additionalProperties": False
|
323
316
|
}
|
324
317
|
),
|
@@ -330,13 +323,12 @@ class MCPCodeIndexServer:
|
|
330
323
|
"properties": {
|
331
324
|
"projectName": {"type": "string", "description": "The name of the project"},
|
332
325
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
333
|
-
"branch": {"type": "string", "description": "Git branch to search in"},
|
334
326
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
335
327
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
336
328
|
"query": {"type": "string", "description": "Search query (e.g., 'authentication middleware', 'database models')"},
|
337
329
|
"maxResults": {"type": "integer", "default": 20, "description": "Maximum number of results to return"}
|
338
330
|
},
|
339
|
-
"required": ["projectName", "folderPath", "
|
331
|
+
"required": ["projectName", "folderPath", "query"],
|
340
332
|
"additionalProperties": False
|
341
333
|
}
|
342
334
|
),
|
@@ -348,57 +340,25 @@ class MCPCodeIndexServer:
|
|
348
340
|
"properties": {
|
349
341
|
"projectName": {"type": "string", "description": "The name of the project"},
|
350
342
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
351
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
352
343
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
353
344
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"}
|
354
345
|
},
|
355
|
-
"required": ["projectName", "folderPath"
|
356
|
-
"additionalProperties": False
|
357
|
-
}
|
358
|
-
),
|
359
|
-
types.Tool(
|
360
|
-
name="merge_branch_descriptions",
|
361
|
-
description="Merges file descriptions from one branch to another. This is a two-stage process: first call without resolutions returns conflicts where the same file has different descriptions in each branch. Second call with resolutions completes the merge.",
|
362
|
-
inputSchema={
|
363
|
-
"type": "object",
|
364
|
-
"properties": {
|
365
|
-
"projectName": {"type": "string", "description": "The name of the project"},
|
366
|
-
"folderPath": {"type": "string", "description": "Absolute path to the project folder"},
|
367
|
-
"remoteOrigin": {"type": "string", "description": "Git remote origin URL"},
|
368
|
-
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
369
|
-
"sourceBranch": {"type": "string", "description": "Branch to merge from (e.g., 'feature/new-ui')"},
|
370
|
-
"targetBranch": {"type": "string", "description": "Branch to merge into (e.g., 'main')"},
|
371
|
-
"conflictResolutions": {
|
372
|
-
"type": ["array", "null"],
|
373
|
-
"description": "Array of resolved conflicts (only for second stage)",
|
374
|
-
"items": {
|
375
|
-
"type": "object",
|
376
|
-
"properties": {
|
377
|
-
"conflictId": {"type": "string", "description": "ID of the conflict to resolve"},
|
378
|
-
"resolvedDescription": {"type": "string", "description": "Final description to use after merge"}
|
379
|
-
},
|
380
|
-
"required": ["conflictId", "resolvedDescription"],
|
381
|
-
"additionalProperties": False
|
382
|
-
}
|
383
|
-
}
|
384
|
-
},
|
385
|
-
"required": ["projectName", "folderPath", "sourceBranch", "targetBranch"],
|
346
|
+
"required": ["projectName", "folderPath"],
|
386
347
|
"additionalProperties": False
|
387
348
|
}
|
388
349
|
),
|
389
350
|
types.Tool(
|
390
351
|
name="get_codebase_overview",
|
391
|
-
description="Returns a condensed, interpretive overview of the entire codebase
|
352
|
+
description="Returns a condensed, interpretive overview of the entire codebase. This is a single comprehensive narrative that captures the architecture, key components, relationships, and design patterns. Unlike get_all_descriptions which lists every file, this provides a holistic view suitable for understanding the codebase's structure and purpose. If no overview exists, returns empty string.",
|
392
353
|
inputSchema={
|
393
354
|
"type": "object",
|
394
355
|
"properties": {
|
395
356
|
"projectName": {"type": "string", "description": "The name of the project"},
|
396
357
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
397
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
398
358
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
399
359
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"}
|
400
360
|
},
|
401
|
-
"required": ["projectName", "folderPath"
|
361
|
+
"required": ["projectName", "folderPath"],
|
402
362
|
"additionalProperties": False
|
403
363
|
}
|
404
364
|
),
|
@@ -444,29 +404,27 @@ src/
|
|
444
404
|
"properties": {
|
445
405
|
"projectName": {"type": "string", "description": "The name of the project"},
|
446
406
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
447
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
448
407
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
449
408
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
450
409
|
"overview": {"type": "string", "description": "Comprehensive narrative overview of the codebase (10-30k tokens recommended)"}
|
451
410
|
},
|
452
|
-
"required": ["projectName", "folderPath", "
|
411
|
+
"required": ["projectName", "folderPath", "overview"],
|
453
412
|
"additionalProperties": False
|
454
413
|
}
|
455
414
|
),
|
456
415
|
types.Tool(
|
457
416
|
name="get_word_frequency",
|
458
|
-
description="Analyzes all file descriptions to find the most frequently used technical terms
|
417
|
+
description="Analyzes all file descriptions to find the most frequently used technical terms. Filters out common English stop words and symbols, returning the top 200 meaningful terms. Useful for understanding the codebase's domain vocabulary and finding all functions/files related to specific concepts.",
|
459
418
|
inputSchema={
|
460
419
|
"type": "object",
|
461
420
|
"properties": {
|
462
421
|
"projectName": {"type": "string", "description": "The name of the project"},
|
463
422
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
464
|
-
"branch": {"type": "string", "description": "Git branch name"},
|
465
423
|
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
466
424
|
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
467
425
|
"limit": {"type": "integer", "default": 200, "description": "Number of top terms to return"}
|
468
426
|
},
|
469
|
-
"required": ["projectName", "folderPath"
|
427
|
+
"required": ["projectName", "folderPath"],
|
470
428
|
"additionalProperties": False
|
471
429
|
}
|
472
430
|
),
|
@@ -478,6 +436,22 @@ src/
|
|
478
436
|
"properties": {},
|
479
437
|
"additionalProperties": False
|
480
438
|
}
|
439
|
+
),
|
440
|
+
types.Tool(
|
441
|
+
name="search_codebase_overview",
|
442
|
+
description="Search for a single word in the codebase overview and return 2 sentences before and after where the word is found. Useful for quickly finding specific information in large overviews.",
|
443
|
+
inputSchema={
|
444
|
+
"type": "object",
|
445
|
+
"properties": {
|
446
|
+
"projectName": {"type": "string", "description": "The name of the project"},
|
447
|
+
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
448
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
449
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
450
|
+
"searchWord": {"type": "string", "description": "Single word to search for in the overview"}
|
451
|
+
},
|
452
|
+
"required": ["projectName", "folderPath", "searchWord"],
|
453
|
+
"additionalProperties": False
|
454
|
+
}
|
481
455
|
)
|
482
456
|
]
|
483
457
|
|
@@ -501,8 +475,9 @@ src/
|
|
501
475
|
"get_codebase_overview": self._handle_get_condensed_overview,
|
502
476
|
"update_codebase_overview": self._handle_update_codebase_overview,
|
503
477
|
"get_word_frequency": self._handle_get_word_frequency,
|
504
|
-
|
478
|
+
|
505
479
|
"check_database_health": self._handle_check_database_health,
|
480
|
+
"search_codebase_overview": self._handle_search_codebase_overview,
|
506
481
|
}
|
507
482
|
|
508
483
|
if name not in tool_handlers:
|
@@ -556,7 +531,7 @@ src/
|
|
556
531
|
remote_origin = arguments.get("remoteOrigin")
|
557
532
|
upstream_origin = arguments.get("upstreamOrigin")
|
558
533
|
folder_path = arguments["folderPath"]
|
559
|
-
|
534
|
+
|
560
535
|
|
561
536
|
# Normalize project name for case-insensitive matching
|
562
537
|
normalized_name = project_name.lower()
|
@@ -572,7 +547,7 @@ src/
|
|
572
547
|
# Check if upstream inheritance is needed
|
573
548
|
if upstream_origin and await self.db_manager.check_upstream_inheritance_needed(project):
|
574
549
|
try:
|
575
|
-
inherited_count = await self.db_manager.inherit_from_upstream(project
|
550
|
+
inherited_count = await self.db_manager.inherit_from_upstream(project)
|
576
551
|
if inherited_count > 0:
|
577
552
|
logger.info(f"Auto-inherited {inherited_count} descriptions from upstream for {normalized_name}")
|
578
553
|
except Exception as e:
|
@@ -595,7 +570,7 @@ src/
|
|
595
570
|
# Auto-inherit from upstream if needed
|
596
571
|
if upstream_origin:
|
597
572
|
try:
|
598
|
-
inherited_count = await self.db_manager.inherit_from_upstream(project
|
573
|
+
inherited_count = await self.db_manager.inherit_from_upstream(project)
|
599
574
|
if inherited_count > 0:
|
600
575
|
logger.info(f"Auto-inherited {inherited_count} descriptions from upstream for {normalized_name}")
|
601
576
|
except Exception as e:
|
@@ -689,7 +664,7 @@ src/
|
|
689
664
|
return False
|
690
665
|
|
691
666
|
# Get files already indexed for this project
|
692
|
-
indexed_files = await self.db_manager.get_all_file_descriptions(project.id
|
667
|
+
indexed_files = await self.db_manager.get_all_file_descriptions(project.id)
|
693
668
|
indexed_basenames = {Path(fd.file_path).name for fd in indexed_files}
|
694
669
|
|
695
670
|
if not indexed_basenames:
|
@@ -746,65 +721,14 @@ src/
|
|
746
721
|
await self.db_manager.update_project(project)
|
747
722
|
logger.debug(f"Updated project metadata for {project.name}")
|
748
723
|
|
749
|
-
|
750
|
-
"""
|
751
|
-
Resolve the actual branch to use for operations.
|
752
|
-
|
753
|
-
For new projects or branches with no existing files, uses the requested branch.
|
754
|
-
For existing projects, tries to find a consistent branch to avoid data fragmentation.
|
755
|
-
|
756
|
-
Returns the resolved branch name.
|
757
|
-
"""
|
758
|
-
try:
|
759
|
-
# Get all branches and their file counts for this project
|
760
|
-
branch_counts = await self.db_manager.get_branch_file_counts(project_id)
|
761
|
-
|
762
|
-
# If no existing data, use the requested branch
|
763
|
-
if not branch_counts:
|
764
|
-
return requested_branch
|
765
|
-
|
766
|
-
# If requested branch has files, use it
|
767
|
-
if requested_branch in branch_counts and branch_counts[requested_branch] > 0:
|
768
|
-
return requested_branch
|
769
|
-
|
770
|
-
# Try common branch name variations to find existing data
|
771
|
-
common_variations = {
|
772
|
-
'main': ['master', 'develop', 'development', 'dev'],
|
773
|
-
'master': ['main', 'develop', 'development', 'dev'],
|
774
|
-
'develop': ['development', 'main', 'master', 'dev'],
|
775
|
-
'development': ['develop', 'main', 'master', 'dev'],
|
776
|
-
'dev': ['develop', 'development', 'main', 'master']
|
777
|
-
}
|
778
|
-
|
779
|
-
# Try variations of the requested branch
|
780
|
-
if requested_branch.lower() in common_variations:
|
781
|
-
for variation in common_variations[requested_branch.lower()]:
|
782
|
-
if variation in branch_counts and branch_counts[variation] > 0:
|
783
|
-
logger.info(f"Resolved branch '{requested_branch}' to existing branch '{variation}' with {branch_counts[variation]} files")
|
784
|
-
return variation
|
785
|
-
|
786
|
-
# If no variations found, check if we should use the main data branch
|
787
|
-
# (to avoid fragmenting data across multiple branches)
|
788
|
-
best_branch = max(branch_counts.items(), key=lambda x: x[1])
|
789
|
-
if best_branch[1] > 10: # Only if there's substantial existing data
|
790
|
-
logger.info(f"Using primary branch '{best_branch[0]}' instead of '{requested_branch}' to avoid data fragmentation")
|
791
|
-
return best_branch[0]
|
792
|
-
|
793
|
-
# Fall back to requested branch for new/small projects
|
794
|
-
return requested_branch
|
795
|
-
|
796
|
-
except Exception as e:
|
797
|
-
logger.warning(f"Error resolving branch: {e}")
|
798
|
-
return requested_branch
|
724
|
+
|
799
725
|
|
800
726
|
async def _handle_get_file_description(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
801
727
|
"""Handle get_file_description tool calls."""
|
802
728
|
project_id = await self._get_or_create_project_id(arguments)
|
803
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
804
729
|
|
805
730
|
file_desc = await self.db_manager.get_file_description(
|
806
731
|
project_id=project_id,
|
807
|
-
branch=resolved_branch,
|
808
732
|
file_path=arguments["filePath"]
|
809
733
|
)
|
810
734
|
|
@@ -826,19 +750,16 @@ src/
|
|
826
750
|
"""Handle update_file_description tool calls."""
|
827
751
|
logger.info(f"Updating file description for: {arguments['filePath']}")
|
828
752
|
logger.info(f"Project: {arguments.get('projectName', 'Unknown')}")
|
829
|
-
logger.info(f"Branch: {arguments.get('branch', 'Unknown')}")
|
830
753
|
|
831
754
|
description_length = len(arguments.get("description", ""))
|
832
755
|
logger.info(f"Description length: {description_length} characters")
|
833
756
|
|
834
757
|
project_id = await self._get_or_create_project_id(arguments)
|
835
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
836
758
|
|
837
|
-
logger.info(f"Resolved project_id: {project_id}
|
759
|
+
logger.info(f"Resolved project_id: {project_id}")
|
838
760
|
|
839
761
|
file_desc = FileDescription(
|
840
762
|
project_id=project_id,
|
841
|
-
branch=resolved_branch,
|
842
763
|
file_path=arguments["filePath"],
|
843
764
|
description=arguments["description"],
|
844
765
|
file_hash=arguments.get("fileHash"),
|
@@ -861,46 +782,52 @@ src/
|
|
861
782
|
"""Handle check_codebase_size tool calls."""
|
862
783
|
logger.info(f"Checking codebase size for: {arguments.get('projectName', 'Unknown')}")
|
863
784
|
logger.info(f"Folder path: {arguments.get('folderPath', 'Unknown')}")
|
864
|
-
logger.info(f"Branch: {arguments.get('branch', 'Unknown')}")
|
865
785
|
|
866
786
|
project_id = await self._get_or_create_project_id(arguments)
|
867
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
868
787
|
folder_path = Path(arguments["folderPath"])
|
869
788
|
|
870
|
-
logger.info(f"Resolved project_id: {project_id}
|
789
|
+
logger.info(f"Resolved project_id: {project_id}")
|
871
790
|
|
872
791
|
# Clean up descriptions for files that no longer exist
|
873
792
|
logger.info("Cleaning up descriptions for missing files...")
|
874
793
|
cleaned_up_files = await self.db_manager.cleanup_missing_files(
|
875
794
|
project_id=project_id,
|
876
|
-
branch=resolved_branch,
|
877
795
|
project_root=folder_path
|
878
796
|
)
|
879
797
|
logger.info(f"Cleaned up {len(cleaned_up_files)} missing files")
|
880
798
|
|
881
|
-
# Get file descriptions for this project
|
799
|
+
# Get file descriptions for this project (after cleanup)
|
882
800
|
logger.info("Retrieving file descriptions...")
|
883
801
|
file_descriptions = await self.db_manager.get_all_file_descriptions(
|
884
|
-
project_id=project_id
|
885
|
-
branch=resolved_branch
|
802
|
+
project_id=project_id
|
886
803
|
)
|
887
804
|
logger.info(f"Found {len(file_descriptions)} file descriptions")
|
888
805
|
|
889
806
|
# Use provided token limit or fall back to server default
|
890
807
|
token_limit = arguments.get("tokenLimit", self.token_limit)
|
891
808
|
|
892
|
-
# Calculate total tokens
|
809
|
+
# Calculate total tokens for descriptions
|
893
810
|
logger.info("Calculating total token count...")
|
894
|
-
|
811
|
+
descriptions_tokens = self.token_counter.calculate_codebase_tokens(file_descriptions)
|
812
|
+
|
813
|
+
# Get overview tokens if available
|
814
|
+
overview = await self.db_manager.get_project_overview(project_id)
|
815
|
+
overview_tokens = 0
|
816
|
+
if overview and overview.overview:
|
817
|
+
overview_tokens = self.token_counter.count_tokens(overview.overview)
|
818
|
+
|
819
|
+
total_tokens = descriptions_tokens + overview_tokens
|
895
820
|
is_large = total_tokens > token_limit
|
896
821
|
recommendation = "use_search" if is_large else "use_overview"
|
897
822
|
|
898
|
-
logger.info(f"Codebase analysis complete: {total_tokens} tokens, {len(file_descriptions)} files")
|
823
|
+
logger.info(f"Codebase analysis complete: {total_tokens} tokens total ({descriptions_tokens} descriptions + {overview_tokens} overview), {len(file_descriptions)} files")
|
899
824
|
logger.info(f"Size assessment: {'LARGE' if is_large else 'SMALL'} (limit: {token_limit})")
|
900
825
|
logger.info(f"Recommendation: {recommendation}")
|
901
826
|
|
902
827
|
return {
|
903
828
|
"totalTokens": total_tokens,
|
829
|
+
"descriptionsTokens": descriptions_tokens,
|
830
|
+
"overviewTokens": overview_tokens,
|
904
831
|
"isLarge": is_large,
|
905
832
|
"recommendation": recommendation,
|
906
833
|
"tokenLimit": token_limit,
|
@@ -915,16 +842,14 @@ src/
|
|
915
842
|
logger.info(f"Folder path: {arguments.get('folderPath', 'Unknown')}")
|
916
843
|
|
917
844
|
project_id = await self._get_or_create_project_id(arguments)
|
918
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
919
845
|
folder_path = Path(arguments["folderPath"])
|
920
846
|
|
921
|
-
logger.info(f"Resolved project_id: {project_id}
|
847
|
+
logger.info(f"Resolved project_id: {project_id}")
|
922
848
|
|
923
849
|
# Get existing file descriptions
|
924
850
|
logger.info("Retrieving existing file descriptions...")
|
925
851
|
existing_descriptions = await self.db_manager.get_all_file_descriptions(
|
926
|
-
project_id=project_id
|
927
|
-
branch=resolved_branch
|
852
|
+
project_id=project_id
|
928
853
|
)
|
929
854
|
existing_paths = {desc.file_path for desc in existing_descriptions}
|
930
855
|
logger.info(f"Found {len(existing_paths)} existing descriptions")
|
@@ -965,13 +890,11 @@ src/
|
|
965
890
|
async def _handle_search_descriptions(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
966
891
|
"""Handle search_descriptions tool calls."""
|
967
892
|
project_id = await self._get_or_create_project_id(arguments)
|
968
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
969
893
|
max_results = arguments.get("maxResults", 20)
|
970
894
|
|
971
895
|
# Perform search
|
972
896
|
search_results = await self.db_manager.search_file_descriptions(
|
973
897
|
project_id=project_id,
|
974
|
-
branch=resolved_branch,
|
975
898
|
query=arguments["query"],
|
976
899
|
max_results=max_results
|
977
900
|
)
|
@@ -995,12 +918,10 @@ src/
|
|
995
918
|
async def _handle_get_codebase_overview(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
996
919
|
"""Handle get_codebase_overview tool calls."""
|
997
920
|
project_id = await self._get_or_create_project_id(arguments)
|
998
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
999
921
|
|
1000
922
|
# Get all file descriptions
|
1001
923
|
file_descriptions = await self.db_manager.get_all_file_descriptions(
|
1002
|
-
project_id=project_id
|
1003
|
-
branch=resolved_branch
|
924
|
+
project_id=project_id
|
1004
925
|
)
|
1005
926
|
|
1006
927
|
# Calculate total tokens
|
@@ -1012,8 +933,6 @@ src/
|
|
1012
933
|
|
1013
934
|
return {
|
1014
935
|
"projectName": arguments["projectName"],
|
1015
|
-
"branch": resolved_branch,
|
1016
|
-
"requestedBranch": arguments["branch"] if arguments["branch"] != resolved_branch else None,
|
1017
936
|
"totalFiles": len(file_descriptions),
|
1018
937
|
"totalTokens": total_tokens,
|
1019
938
|
"isLarge": is_large,
|
@@ -1064,77 +983,14 @@ src/
|
|
1064
983
|
|
1065
984
|
return convert_structure(root)
|
1066
985
|
|
1067
|
-
|
1068
|
-
"""Handle merge_branch_descriptions tool calls."""
|
1069
|
-
project_id = await self._get_or_create_project_id(arguments)
|
1070
|
-
source_branch = arguments["sourceBranch"]
|
1071
|
-
target_branch = arguments["targetBranch"]
|
1072
|
-
conflict_resolutions = arguments.get("conflictResolutions")
|
1073
|
-
|
1074
|
-
if conflict_resolutions is None:
|
1075
|
-
# Phase 1: Detect conflicts
|
1076
|
-
session = await self.merge_handler.start_merge_phase1(
|
1077
|
-
project_id, source_branch, target_branch
|
1078
|
-
)
|
1079
|
-
|
1080
|
-
if session.get_conflict_count() == 0:
|
1081
|
-
# No conflicts, can merge immediately
|
1082
|
-
return {
|
1083
|
-
"phase": "completed",
|
1084
|
-
"conflicts": [],
|
1085
|
-
"message": f"No conflicts detected. Merge from {source_branch} to {target_branch} can proceed automatically.",
|
1086
|
-
"sourceBranch": source_branch,
|
1087
|
-
"targetBranch": target_branch,
|
1088
|
-
"conflictCount": 0
|
1089
|
-
}
|
1090
|
-
else:
|
1091
|
-
# Return conflicts for resolution
|
1092
|
-
return {
|
1093
|
-
"phase": "conflicts_detected",
|
1094
|
-
"sessionId": session.session_id,
|
1095
|
-
"conflicts": [conflict.to_dict() for conflict in session.conflicts],
|
1096
|
-
"conflictCount": session.get_conflict_count(),
|
1097
|
-
"sourceBranch": source_branch,
|
1098
|
-
"targetBranch": target_branch,
|
1099
|
-
"message": f"Found {session.get_conflict_count()} conflicts that need resolution."
|
1100
|
-
}
|
1101
|
-
else:
|
1102
|
-
# Phase 2: Apply resolutions
|
1103
|
-
# Find the session ID from conflict resolutions
|
1104
|
-
if not conflict_resolutions:
|
1105
|
-
from ..error_handler import ValidationError
|
1106
|
-
raise ValidationError("Conflict resolutions required for phase 2")
|
1107
|
-
|
1108
|
-
# For simplicity, create a new session and resolve immediately
|
1109
|
-
# In a production system, you'd want to track session IDs properly
|
1110
|
-
session = await self.merge_handler.start_merge_phase1(
|
1111
|
-
project_id, source_branch, target_branch
|
1112
|
-
)
|
1113
|
-
|
1114
|
-
if session.get_conflict_count() == 0:
|
1115
|
-
return {
|
1116
|
-
"phase": "completed",
|
1117
|
-
"message": "No conflicts to resolve",
|
1118
|
-
"sourceBranch": source_branch,
|
1119
|
-
"targetBranch": target_branch
|
1120
|
-
}
|
1121
|
-
|
1122
|
-
result = await self.merge_handler.complete_merge_phase2(
|
1123
|
-
session.session_id, conflict_resolutions
|
1124
|
-
)
|
1125
|
-
|
1126
|
-
return {
|
1127
|
-
"phase": "completed",
|
1128
|
-
**result
|
1129
|
-
}
|
986
|
+
|
1130
987
|
|
1131
988
|
async def _handle_get_condensed_overview(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
1132
989
|
"""Handle get_codebase_overview tool calls for condensed overviews."""
|
1133
990
|
project_id = await self._get_or_create_project_id(arguments)
|
1134
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
1135
991
|
|
1136
992
|
# Try to get existing overview
|
1137
|
-
overview = await self.db_manager.get_project_overview(project_id
|
993
|
+
overview = await self.db_manager.get_project_overview(project_id)
|
1138
994
|
|
1139
995
|
if overview:
|
1140
996
|
return {
|
@@ -1154,13 +1010,11 @@ src/
|
|
1154
1010
|
async def _handle_update_codebase_overview(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
1155
1011
|
"""Handle update_codebase_overview tool calls."""
|
1156
1012
|
project_id = await self._get_or_create_project_id(arguments)
|
1157
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
1158
1013
|
folder_path = Path(arguments["folderPath"])
|
1159
1014
|
|
1160
1015
|
# Get current file count and total tokens for context
|
1161
1016
|
file_descriptions = await self.db_manager.get_all_file_descriptions(
|
1162
|
-
project_id=project_id
|
1163
|
-
branch=resolved_branch
|
1017
|
+
project_id=project_id
|
1164
1018
|
)
|
1165
1019
|
|
1166
1020
|
total_files = len(file_descriptions)
|
@@ -1169,7 +1023,6 @@ src/
|
|
1169
1023
|
# Create overview record
|
1170
1024
|
overview = ProjectOverview(
|
1171
1025
|
project_id=project_id,
|
1172
|
-
branch=resolved_branch,
|
1173
1026
|
overview=arguments["overview"],
|
1174
1027
|
last_modified=datetime.utcnow(),
|
1175
1028
|
total_files=total_files,
|
@@ -1189,13 +1042,11 @@ src/
|
|
1189
1042
|
async def _handle_get_word_frequency(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
1190
1043
|
"""Handle get_word_frequency tool calls."""
|
1191
1044
|
project_id = await self._get_or_create_project_id(arguments)
|
1192
|
-
resolved_branch = await self._resolve_branch(project_id, arguments["branch"])
|
1193
1045
|
limit = arguments.get("limit", 200)
|
1194
1046
|
|
1195
1047
|
# Analyze word frequency
|
1196
1048
|
result = await self.db_manager.analyze_word_frequency(
|
1197
1049
|
project_id=project_id,
|
1198
|
-
branch=resolved_branch,
|
1199
1050
|
limit=limit
|
1200
1051
|
)
|
1201
1052
|
|
@@ -1205,6 +1056,53 @@ src/
|
|
1205
1056
|
"totalUniqueTerms": result.total_unique_terms
|
1206
1057
|
}
|
1207
1058
|
|
1059
|
+
async def _handle_search_codebase_overview(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
1060
|
+
"""Handle search_codebase_overview tool calls."""
|
1061
|
+
project_id = await self._get_or_create_project_id(arguments)
|
1062
|
+
search_word = arguments["searchWord"].lower()
|
1063
|
+
|
1064
|
+
# Get the overview
|
1065
|
+
overview = await self.db_manager.get_project_overview(project_id)
|
1066
|
+
|
1067
|
+
if not overview or not overview.overview:
|
1068
|
+
return {
|
1069
|
+
"found": False,
|
1070
|
+
"message": "No overview found for this project",
|
1071
|
+
"searchWord": arguments["searchWord"]
|
1072
|
+
}
|
1073
|
+
|
1074
|
+
# Split overview into sentences
|
1075
|
+
import re
|
1076
|
+
sentences = re.split(r'[.!?]+', overview.overview)
|
1077
|
+
sentences = [s.strip() for s in sentences if s.strip()]
|
1078
|
+
|
1079
|
+
# Find matches
|
1080
|
+
matches = []
|
1081
|
+
for i, sentence in enumerate(sentences):
|
1082
|
+
if search_word in sentence.lower():
|
1083
|
+
# Get context: 2 sentences before and after
|
1084
|
+
start_idx = max(0, i - 2)
|
1085
|
+
end_idx = min(len(sentences), i + 3)
|
1086
|
+
|
1087
|
+
context_sentences = sentences[start_idx:end_idx]
|
1088
|
+
context = '. '.join(context_sentences) + '.'
|
1089
|
+
|
1090
|
+
matches.append({
|
1091
|
+
"matchIndex": i,
|
1092
|
+
"matchSentence": sentence,
|
1093
|
+
"context": context,
|
1094
|
+
"contextStartIndex": start_idx,
|
1095
|
+
"contextEndIndex": end_idx - 1
|
1096
|
+
})
|
1097
|
+
|
1098
|
+
return {
|
1099
|
+
"found": len(matches) > 0,
|
1100
|
+
"searchWord": arguments["searchWord"],
|
1101
|
+
"matches": matches,
|
1102
|
+
"totalMatches": len(matches),
|
1103
|
+
"totalSentences": len(sentences)
|
1104
|
+
}
|
1105
|
+
|
1208
1106
|
async def _handle_check_database_health(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
1209
1107
|
"""
|
1210
1108
|
Handle check_database_health tool calls with comprehensive diagnostics.
|