mcp-code-indexer 1.6.5__py3-none-any.whl → 1.8.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/git_hook_handler.py +145 -95
- mcp_code_indexer/logging_config.py +53 -0
- mcp_code_indexer/main.py +1 -1
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/METADATA +3 -3
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/RECORD +9 -9
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-1.6.5.dist-info → mcp_code_indexer-1.8.0.dist-info}/top_level.txt +0 -0
@@ -49,17 +49,18 @@ class GitHookHandler:
|
|
49
49
|
OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
50
50
|
OPENROUTER_MODEL = "anthropic/claude-sonnet-4"
|
51
51
|
|
52
|
-
def __init__(self, db_manager: DatabaseManager, cache_dir: Path):
|
52
|
+
def __init__(self, db_manager: DatabaseManager, cache_dir: Path, logger: Optional[logging.Logger] = None):
|
53
53
|
"""
|
54
54
|
Initialize GitHookHandler.
|
55
55
|
|
56
56
|
Args:
|
57
57
|
db_manager: Database manager instance
|
58
58
|
cache_dir: Cache directory for temporary files
|
59
|
+
logger: Logger instance to use (optional, creates default if not provided)
|
59
60
|
"""
|
60
61
|
self.db_manager = db_manager
|
61
62
|
self.cache_dir = cache_dir
|
62
|
-
self.logger = logging.getLogger(__name__)
|
63
|
+
self.logger = logger if logger is not None else logging.getLogger(__name__)
|
63
64
|
self.token_counter = TokenCounter()
|
64
65
|
|
65
66
|
# Git hook specific settings
|
@@ -136,32 +137,26 @@ class GitHookHandler:
|
|
136
137
|
self.logger.info(f"Current overview length: {len(current_overview) if current_overview else 0} characters")
|
137
138
|
self.logger.info(f"Current descriptions count: {len(current_descriptions)}")
|
138
139
|
|
139
|
-
#
|
140
|
-
self.logger.info("
|
141
|
-
prompt = self._build_githook_prompt(
|
142
|
-
git_diff,
|
143
|
-
commit_message,
|
144
|
-
current_overview,
|
145
|
-
current_descriptions,
|
146
|
-
changed_files
|
147
|
-
)
|
148
|
-
|
149
|
-
# Log prompt details
|
150
|
-
prompt_chars = len(prompt)
|
151
|
-
prompt_tokens = self.token_counter.count_tokens(prompt)
|
152
|
-
self.logger.info(f"Analysis prompt: {prompt_chars} characters, {prompt_tokens} tokens")
|
140
|
+
# Use two-stage approach for large codebases
|
141
|
+
self.logger.info("Starting two-stage analysis approach...")
|
153
142
|
|
154
|
-
# Check
|
155
|
-
|
156
|
-
|
157
|
-
|
143
|
+
# Stage 1: Check if overview needs updating
|
144
|
+
overview_updates = await self._analyze_overview_updates(
|
145
|
+
git_diff, commit_message, current_overview, changed_files
|
146
|
+
)
|
158
147
|
|
159
|
-
|
148
|
+
# Stage 2: Update file descriptions
|
149
|
+
file_updates = await self._analyze_file_updates(
|
150
|
+
git_diff, commit_message, current_descriptions, changed_files
|
151
|
+
)
|
160
152
|
|
161
|
-
#
|
162
|
-
updates =
|
153
|
+
# Combine updates
|
154
|
+
updates = {
|
155
|
+
"file_updates": file_updates.get("file_updates", {}),
|
156
|
+
"overview_update": overview_updates.get("overview_update")
|
157
|
+
}
|
163
158
|
|
164
|
-
self.logger.info(f"
|
159
|
+
self.logger.info(f"Two-stage analysis completed, processing updates...")
|
165
160
|
|
166
161
|
# Apply updates to database
|
167
162
|
await self._apply_updates(project_info, updates)
|
@@ -440,101 +435,143 @@ class GitHookHandler:
|
|
440
435
|
self.logger.warning(f"Failed to get file descriptions: {e}")
|
441
436
|
return {}
|
442
437
|
|
443
|
-
def
|
444
|
-
self,
|
445
|
-
git_diff: str,
|
446
|
-
commit_message: str,
|
447
|
-
|
448
|
-
descriptions: Dict[str, str],
|
438
|
+
async def _analyze_overview_updates(
|
439
|
+
self,
|
440
|
+
git_diff: str,
|
441
|
+
commit_message: str,
|
442
|
+
current_overview: str,
|
449
443
|
changed_files: List[str]
|
450
|
-
) -> str:
|
444
|
+
) -> Dict[str, Any]:
|
451
445
|
"""
|
452
|
-
|
446
|
+
Stage 1: Analyze if project overview needs updating.
|
453
447
|
|
454
448
|
Args:
|
455
449
|
git_diff: Git diff content
|
456
450
|
commit_message: Commit message explaining the changes
|
457
|
-
|
458
|
-
descriptions: Current file descriptions
|
451
|
+
current_overview: Current project overview
|
459
452
|
changed_files: List of changed file paths
|
460
453
|
|
461
454
|
Returns:
|
462
|
-
|
455
|
+
Dict with overview_update key
|
463
456
|
"""
|
464
|
-
|
457
|
+
self.logger.info("Stage 1: Analyzing overview updates...")
|
458
|
+
|
459
|
+
prompt = f"""Analyze this git commit to determine if the project overview needs updating.
|
465
460
|
|
466
461
|
COMMIT MESSAGE:
|
467
462
|
{commit_message or "No commit message available"}
|
468
463
|
|
469
464
|
CURRENT PROJECT OVERVIEW:
|
470
|
-
{
|
465
|
+
{current_overview or "No overview available"}
|
471
466
|
|
472
|
-
|
473
|
-
{
|
467
|
+
CHANGED FILES:
|
468
|
+
{', '.join(changed_files)}
|
474
469
|
|
475
470
|
GIT DIFF:
|
476
471
|
{git_diff}
|
477
472
|
|
478
|
-
CHANGED FILES:
|
479
|
-
{', '.join(changed_files)}
|
480
|
-
|
481
473
|
INSTRUCTIONS:
|
482
474
|
|
483
|
-
|
475
|
+
Update project overview ONLY if there are major structural changes like:
|
476
|
+
- New major features or components (indicated by commit message or new directories)
|
477
|
+
- Architectural changes (new patterns, frameworks, or approaches)
|
478
|
+
- Significant dependency additions (Cargo.toml, package.json, requirements.txt changes)
|
479
|
+
- New API endpoints or workflows
|
480
|
+
- Changes to build/deployment processes
|
481
|
+
|
482
|
+
Do NOT update for: bug fixes, small refactors, documentation updates, version bumps.
|
483
|
+
|
484
|
+
If updating, provide comprehensive narrative (10-20 pages of text) with directory structure, architecture, components, and workflows.
|
484
485
|
|
485
|
-
|
486
|
+
Return ONLY a JSON object:
|
487
|
+
{{
|
488
|
+
"overview_update": "Updated overview text" or null
|
489
|
+
}}"""
|
490
|
+
|
491
|
+
# Log prompt details
|
492
|
+
prompt_chars = len(prompt)
|
493
|
+
prompt_tokens = self.token_counter.count_tokens(prompt)
|
494
|
+
self.logger.info(f"Stage 1 prompt: {prompt_chars} characters, {prompt_tokens} tokens")
|
495
|
+
|
496
|
+
if prompt_tokens > self.config["max_diff_tokens"]:
|
497
|
+
self.logger.warning(f"Stage 1 prompt too large ({prompt_tokens} tokens), skipping overview analysis")
|
498
|
+
return {"overview_update": None}
|
499
|
+
|
500
|
+
# Call OpenRouter API
|
501
|
+
result = await self._call_openrouter(prompt)
|
502
|
+
self.logger.info("Stage 1 completed: overview analysis")
|
503
|
+
|
504
|
+
return result
|
486
505
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
506
|
+
async def _analyze_file_updates(
|
507
|
+
self,
|
508
|
+
git_diff: str,
|
509
|
+
commit_message: str,
|
510
|
+
current_descriptions: Dict[str, str],
|
511
|
+
changed_files: List[str]
|
512
|
+
) -> Dict[str, Any]:
|
513
|
+
"""
|
514
|
+
Stage 2: Analyze file description updates.
|
515
|
+
|
516
|
+
Args:
|
517
|
+
git_diff: Git diff content
|
518
|
+
commit_message: Commit message explaining the changes
|
519
|
+
current_descriptions: Current file descriptions for changed files only
|
520
|
+
changed_files: List of changed file paths
|
521
|
+
|
522
|
+
Returns:
|
523
|
+
Dict with file_updates key
|
524
|
+
"""
|
525
|
+
self.logger.info("Stage 2: Analyzing file description updates...")
|
526
|
+
|
527
|
+
# Only include descriptions for changed files to reduce token usage
|
528
|
+
relevant_descriptions = {
|
529
|
+
path: desc for path, desc in current_descriptions.items()
|
530
|
+
if path in changed_files
|
531
|
+
}
|
532
|
+
|
533
|
+
prompt = f"""Analyze this git commit and update file descriptions for changed files.
|
495
534
|
|
496
|
-
|
535
|
+
COMMIT MESSAGE:
|
536
|
+
{commit_message or "No commit message available"}
|
497
537
|
|
498
|
-
|
499
|
-
|
500
|
-
```
|
501
|
-
src/
|
502
|
-
├── api/ # REST API endpoints and middleware
|
503
|
-
├── models/ # Database models and business logic
|
504
|
-
├── services/ # External service integrations
|
505
|
-
├── utils/ # Shared utilities and helpers
|
506
|
-
└── tests/ # Test suites
|
507
|
-
```
|
538
|
+
CURRENT FILE DESCRIPTIONS (for changed files only):
|
539
|
+
{json.dumps(relevant_descriptions, indent=2)}
|
508
540
|
|
509
|
-
|
510
|
-
|
541
|
+
CHANGED FILES:
|
542
|
+
{', '.join(changed_files)}
|
511
543
|
|
512
|
-
|
513
|
-
|
514
|
-
[Details about API structure, authentication, routing]
|
544
|
+
GIT DIFF:
|
545
|
+
{git_diff}
|
515
546
|
|
516
|
-
|
517
|
-
[Key entities, relationships, database design]
|
547
|
+
INSTRUCTIONS:
|
518
548
|
|
519
|
-
|
520
|
-
1. User Authentication Flow
|
521
|
-
[Step-by-step description]
|
522
|
-
2. Data Processing Pipeline
|
523
|
-
[How data moves through the system]
|
549
|
+
Use the COMMIT MESSAGE to understand the intent and context of the changes.
|
524
550
|
|
525
|
-
|
526
|
-
````
|
551
|
+
Update descriptions for files that have changed significantly. Consider both the diff content and commit message context. Only include files that need actual description updates.
|
527
552
|
|
528
|
-
Return ONLY a JSON object
|
553
|
+
Return ONLY a JSON object:
|
529
554
|
{{
|
530
555
|
"file_updates": {{
|
531
556
|
"path/to/file1.py": "Updated description for file1",
|
532
557
|
"path/to/file2.js": "Updated description for file2"
|
533
|
-
}}
|
534
|
-
|
535
|
-
}}
|
558
|
+
}}
|
559
|
+
}}"""
|
536
560
|
|
537
|
-
|
561
|
+
# Log prompt details
|
562
|
+
prompt_chars = len(prompt)
|
563
|
+
prompt_tokens = self.token_counter.count_tokens(prompt)
|
564
|
+
self.logger.info(f"Stage 2 prompt: {prompt_chars} characters, {prompt_tokens} tokens")
|
565
|
+
|
566
|
+
if prompt_tokens > self.config["max_diff_tokens"]:
|
567
|
+
self.logger.warning(f"Stage 2 prompt too large ({prompt_tokens} tokens), skipping file analysis")
|
568
|
+
return {"file_updates": {}}
|
569
|
+
|
570
|
+
# Call OpenRouter API
|
571
|
+
result = await self._call_openrouter(prompt)
|
572
|
+
self.logger.info("Stage 2 completed: file description analysis")
|
573
|
+
|
574
|
+
return result
|
538
575
|
|
539
576
|
@retry(
|
540
577
|
wait=wait_exponential(multiplier=1, min=4, max=60),
|
@@ -633,19 +670,32 @@ Return ONLY the JSON, no other text."""
|
|
633
670
|
try:
|
634
671
|
data = json.loads(response_text.strip())
|
635
672
|
|
636
|
-
#
|
637
|
-
if "file_updates"
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
673
|
+
# Handle both single-stage and two-stage responses
|
674
|
+
if "file_updates" in data and "overview_update" in data:
|
675
|
+
# Original single-stage format
|
676
|
+
if not isinstance(data["file_updates"], dict):
|
677
|
+
raise ValueError("'file_updates' must be a dictionary")
|
678
|
+
|
679
|
+
# Validate descriptions
|
680
|
+
for path, desc in data["file_updates"].items():
|
681
|
+
if not isinstance(desc, str) or not desc.strip():
|
682
|
+
raise ValueError(f"Invalid description for {path}")
|
683
|
+
|
684
|
+
elif "file_updates" in data:
|
685
|
+
# Stage 2 format (file updates only)
|
686
|
+
if not isinstance(data["file_updates"], dict):
|
687
|
+
raise ValueError("'file_updates' must be a dictionary")
|
688
|
+
|
689
|
+
# Validate descriptions
|
690
|
+
for path, desc in data["file_updates"].items():
|
691
|
+
if not isinstance(desc, str) or not desc.strip():
|
692
|
+
raise ValueError(f"Invalid description for {path}")
|
693
|
+
|
694
|
+
elif "overview_update" in data:
|
695
|
+
# Stage 1 format (overview only) - overview_update can be null
|
696
|
+
pass
|
697
|
+
else:
|
698
|
+
raise ValueError("Response must contain 'file_updates' and/or 'overview_update'")
|
649
699
|
|
650
700
|
return data
|
651
701
|
|
@@ -163,6 +163,9 @@ def setup_command_logger(
|
|
163
163
|
|
164
164
|
logger.addHandler(file_handler)
|
165
165
|
|
166
|
+
# Set up component loggers to also log to this command's log file
|
167
|
+
_setup_component_loggers_for_command(command_name, file_handler, structured_formatter)
|
168
|
+
|
166
169
|
logger.info(f"=== {command_name.upper()} SESSION STARTED ===")
|
167
170
|
|
168
171
|
except (OSError, PermissionError) as e:
|
@@ -175,6 +178,56 @@ def setup_command_logger(
|
|
175
178
|
return logger
|
176
179
|
|
177
180
|
|
181
|
+
def _setup_component_loggers_for_command(
|
182
|
+
command_name: str,
|
183
|
+
file_handler: logging.Handler,
|
184
|
+
formatter: logging.Formatter
|
185
|
+
) -> None:
|
186
|
+
"""
|
187
|
+
Set up component loggers to also send logs to the command's log file.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
command_name: Name of the command
|
191
|
+
file_handler: File handler to add to component loggers
|
192
|
+
formatter: Formatter to use for the handler
|
193
|
+
"""
|
194
|
+
# List of component logger names that should also log to command files
|
195
|
+
component_loggers = [
|
196
|
+
"mcp_code_indexer.database.database",
|
197
|
+
"mcp_code_indexer.server.mcp_server",
|
198
|
+
"mcp_code_indexer.token_counter",
|
199
|
+
"mcp_code_indexer.file_scanner",
|
200
|
+
"mcp_code_indexer.error_handler",
|
201
|
+
"mcp_code_indexer.merge_handler"
|
202
|
+
]
|
203
|
+
|
204
|
+
for component_logger_name in component_loggers:
|
205
|
+
component_logger = logging.getLogger(component_logger_name)
|
206
|
+
|
207
|
+
# Create a separate handler for this command to avoid interference
|
208
|
+
command_handler = logging.handlers.RotatingFileHandler(
|
209
|
+
file_handler.baseFilename,
|
210
|
+
maxBytes=file_handler.maxBytes,
|
211
|
+
backupCount=file_handler.backupCount,
|
212
|
+
encoding='utf-8'
|
213
|
+
)
|
214
|
+
command_handler.setLevel(logging.DEBUG)
|
215
|
+
command_handler.setFormatter(formatter)
|
216
|
+
|
217
|
+
# Add a marker to identify which command this handler belongs to
|
218
|
+
command_handler._command_name = command_name
|
219
|
+
|
220
|
+
# Remove any existing handlers for this command (in case of multiple calls)
|
221
|
+
existing_handlers = [h for h in component_logger.handlers if hasattr(h, '_command_name') and h._command_name == command_name]
|
222
|
+
for handler in existing_handlers:
|
223
|
+
component_logger.removeHandler(handler)
|
224
|
+
handler.close()
|
225
|
+
|
226
|
+
# Add the new handler
|
227
|
+
component_logger.addHandler(command_handler)
|
228
|
+
component_logger.setLevel(logging.DEBUG) # Ensure component loggers capture all levels
|
229
|
+
|
230
|
+
|
178
231
|
def log_performance_metrics(
|
179
232
|
logger: logging.Logger,
|
180
233
|
operation: str,
|
mcp_code_indexer/main.py
CHANGED
@@ -499,7 +499,7 @@ async def handle_githook(args: argparse.Namespace) -> None:
|
|
499
499
|
logger.debug("Database initialized successfully")
|
500
500
|
|
501
501
|
# Initialize git hook handler
|
502
|
-
git_handler = GitHookHandler(db_manager, cache_dir)
|
502
|
+
git_handler = GitHookHandler(db_manager, cache_dir, logger)
|
503
503
|
logger.debug("Git hook handler initialized")
|
504
504
|
|
505
505
|
# Run git hook analysis
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-code-indexer
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.8.0
|
4
4
|
Summary: MCP server that tracks file descriptions across codebases, enabling AI agents to efficiently navigate and understand code through searchable summaries and token-aware overviews.
|
5
5
|
Author: MCP Code Indexer Contributors
|
6
6
|
Maintainer: MCP Code Indexer Contributors
|
@@ -59,8 +59,8 @@ Dynamic: requires-python
|
|
59
59
|
|
60
60
|
# MCP Code Indexer 🚀
|
61
61
|
|
62
|
-
[](https://badge.fury.io/py/mcp-code-indexer)
|
63
|
+
[](https://pypi.org/project/mcp-code-indexer/)
|
64
64
|
[](https://opensource.org/licenses/MIT)
|
65
65
|
|
66
66
|
A production-ready **Model Context Protocol (MCP) server** that revolutionizes how AI agents navigate and understand codebases. Instead of repeatedly scanning files, agents get instant access to intelligent descriptions, semantic search, and context-aware recommendations.
|
@@ -2,9 +2,9 @@ mcp_code_indexer/__init__.py,sha256=GhY2NLQ6lH3n5mxqw0t8T1gmZGKhM6KvjhZH8xW5O-A,
|
|
2
2
|
mcp_code_indexer/__main__.py,sha256=4Edinoe0ug43hobuLYcjTmGp2YJnlFYN4_8iKvUBJ0Q,213
|
3
3
|
mcp_code_indexer/error_handler.py,sha256=cNSUFFrGBMLDv4qa78c7495L1wSl_dXCRbzCJOidx-Q,11590
|
4
4
|
mcp_code_indexer/file_scanner.py,sha256=ctXeZMROgDThEtjzsANTK9TbK-fhTScMBd4iyuleBT4,11734
|
5
|
-
mcp_code_indexer/git_hook_handler.py,sha256=
|
6
|
-
mcp_code_indexer/logging_config.py,sha256=
|
7
|
-
mcp_code_indexer/main.py,sha256=
|
5
|
+
mcp_code_indexer/git_hook_handler.py,sha256=_gM7TAcZ_H6tXVfh_gX0RwV0cJVdR_jhYFP9pQikLrc,30959
|
6
|
+
mcp_code_indexer/logging_config.py,sha256=_bd9XGCLQ2VHPViJitaxGyREyfOXDPiklRh17jXeV0U,9523
|
7
|
+
mcp_code_indexer/main.py,sha256=U-f3AJYdycWhjh-vLryj7aH8DGCs4d3x1yjA852HTxM,31546
|
8
8
|
mcp_code_indexer/merge_handler.py,sha256=lJR8eVq2qSrF6MW9mR3Fy8UzrNAaQ7RsI2FMNXne3vQ,14692
|
9
9
|
mcp_code_indexer/token_counter.py,sha256=WrifOkbF99nWWHlRlhCHAB2KN7qr83GOHl7apE-hJcE,8460
|
10
10
|
mcp_code_indexer/data/stop_words_english.txt,sha256=7Zdd9ameVgA6tN_zuXROvHXD4hkWeELVywPhb7FJEkw,6343
|
@@ -17,9 +17,9 @@ mcp_code_indexer/server/__init__.py,sha256=16xMcuriUOBlawRqWNBk6niwrvtv_JD5xvI36
|
|
17
17
|
mcp_code_indexer/server/mcp_server.py,sha256=4goDZmRmhPgipImgfhTVa6nYJM7L1p56h34ITO6JhSw,64431
|
18
18
|
mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
|
19
19
|
mcp_code_indexer/tools/__init__.py,sha256=m01mxML2UdD7y5rih_XNhNSCMzQTz7WQ_T1TeOcYlnE,49
|
20
|
-
mcp_code_indexer-1.
|
21
|
-
mcp_code_indexer-1.
|
22
|
-
mcp_code_indexer-1.
|
23
|
-
mcp_code_indexer-1.
|
24
|
-
mcp_code_indexer-1.
|
25
|
-
mcp_code_indexer-1.
|
20
|
+
mcp_code_indexer-1.8.0.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
21
|
+
mcp_code_indexer-1.8.0.dist-info/METADATA,sha256=GHDNwAAJ9Q-MBd3t9AMKkPDoHsz9ncyKzEkxf6Y_YIg,17573
|
22
|
+
mcp_code_indexer-1.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
mcp_code_indexer-1.8.0.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
|
24
|
+
mcp_code_indexer-1.8.0.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
|
25
|
+
mcp_code_indexer-1.8.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|