mcp-code-indexer 1.4.1__py3-none-any.whl → 1.6.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/__init__.py +1 -1
- mcp_code_indexer/database/database.py +120 -0
- mcp_code_indexer/git_hook_handler.py +165 -18
- mcp_code_indexer/main.py +36 -8
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/METADATA +25 -3
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/RECORD +10 -10
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-1.4.1.dist-info → mcp_code_indexer-1.6.0.dist-info}/top_level.txt +0 -0
mcp_code_indexer/__init__.py
CHANGED
@@ -6,7 +6,7 @@ intelligent codebase navigation through searchable file descriptions,
|
|
6
6
|
token-aware overviews, and advanced merge capabilities.
|
7
7
|
"""
|
8
8
|
|
9
|
-
__version__ = "1.
|
9
|
+
__version__ = "1.6.0"
|
10
10
|
__author__ = "MCP Code Indexer Contributors"
|
11
11
|
__email__ = ""
|
12
12
|
__license__ = "MIT"
|
@@ -179,6 +179,126 @@ class DatabaseManager:
|
|
179
179
|
last_accessed=datetime.fromisoformat(row['last_accessed'])
|
180
180
|
)
|
181
181
|
return None
|
182
|
+
|
183
|
+
async def find_matching_project(
|
184
|
+
self,
|
185
|
+
project_name: str,
|
186
|
+
remote_origin: Optional[str] = None,
|
187
|
+
upstream_origin: Optional[str] = None,
|
188
|
+
folder_path: Optional[str] = None
|
189
|
+
) -> Optional[Project]:
|
190
|
+
"""
|
191
|
+
Find project by matching criteria.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
project_name: Name of the project
|
195
|
+
remote_origin: Remote origin URL
|
196
|
+
upstream_origin: Upstream origin URL
|
197
|
+
folder_path: Project folder path
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
Matching project or None
|
201
|
+
"""
|
202
|
+
projects = await self.get_all_projects()
|
203
|
+
normalized_name = project_name.lower()
|
204
|
+
|
205
|
+
best_match = None
|
206
|
+
best_score = 0
|
207
|
+
|
208
|
+
for project in projects:
|
209
|
+
score = 0
|
210
|
+
match_factors = []
|
211
|
+
|
212
|
+
# Check name match (case-insensitive)
|
213
|
+
if project.name.lower() == normalized_name:
|
214
|
+
score += 1
|
215
|
+
match_factors.append("name")
|
216
|
+
|
217
|
+
# Check remote origin match
|
218
|
+
if remote_origin and project.remote_origin == remote_origin:
|
219
|
+
score += 1
|
220
|
+
match_factors.append("remote_origin")
|
221
|
+
|
222
|
+
# Check upstream origin match
|
223
|
+
if upstream_origin and project.upstream_origin == upstream_origin:
|
224
|
+
score += 1
|
225
|
+
match_factors.append("upstream_origin")
|
226
|
+
|
227
|
+
# Check folder path in aliases
|
228
|
+
if folder_path and folder_path in project.aliases:
|
229
|
+
score += 1
|
230
|
+
match_factors.append("folder_path")
|
231
|
+
|
232
|
+
# Enhanced matching: If name matches and no remote origins are provided,
|
233
|
+
# consider it a strong match to prevent duplicates
|
234
|
+
if (score == 1 and "name" in match_factors and
|
235
|
+
not remote_origin and not project.remote_origin and
|
236
|
+
not upstream_origin and not project.upstream_origin):
|
237
|
+
logger.info(f"Name-only match with no remotes for project {project.name} - treating as strong match")
|
238
|
+
score = 2 # Boost score to strong match level
|
239
|
+
match_factors.append("no_remotes_boost")
|
240
|
+
|
241
|
+
# If we have 2+ matches, this is a strong candidate
|
242
|
+
if score >= 2:
|
243
|
+
if score > best_score:
|
244
|
+
best_score = score
|
245
|
+
best_match = project
|
246
|
+
logger.info(f"Strong match for project {project.name} (score: {score}, factors: {match_factors})")
|
247
|
+
|
248
|
+
return best_match
|
249
|
+
|
250
|
+
async def get_or_create_project(
|
251
|
+
self,
|
252
|
+
project_name: str,
|
253
|
+
folder_path: str,
|
254
|
+
remote_origin: Optional[str] = None,
|
255
|
+
upstream_origin: Optional[str] = None
|
256
|
+
) -> Project:
|
257
|
+
"""
|
258
|
+
Get or create a project using intelligent matching.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
project_name: Name of the project
|
262
|
+
folder_path: Project folder path
|
263
|
+
remote_origin: Remote origin URL
|
264
|
+
upstream_origin: Upstream origin URL
|
265
|
+
|
266
|
+
Returns:
|
267
|
+
Existing or newly created project
|
268
|
+
"""
|
269
|
+
# Try to find existing project
|
270
|
+
project = await self.find_matching_project(
|
271
|
+
project_name, remote_origin, upstream_origin, folder_path
|
272
|
+
)
|
273
|
+
|
274
|
+
if project:
|
275
|
+
# Update aliases if folder path not already included
|
276
|
+
if folder_path not in project.aliases:
|
277
|
+
project.aliases.append(folder_path)
|
278
|
+
await self.update_project(project)
|
279
|
+
logger.info(f"Added folder path {folder_path} to project {project.name} aliases")
|
280
|
+
|
281
|
+
# Update access time
|
282
|
+
await self.update_project_access_time(project.id)
|
283
|
+
return project
|
284
|
+
|
285
|
+
# Create new project
|
286
|
+
from ..database.models import Project
|
287
|
+
import uuid
|
288
|
+
|
289
|
+
new_project = Project(
|
290
|
+
id=str(uuid.uuid4()),
|
291
|
+
name=project_name,
|
292
|
+
remote_origin=remote_origin,
|
293
|
+
upstream_origin=upstream_origin,
|
294
|
+
aliases=[folder_path],
|
295
|
+
created=datetime.utcnow(),
|
296
|
+
last_accessed=datetime.utcnow()
|
297
|
+
)
|
298
|
+
|
299
|
+
await self.create_project(new_project)
|
300
|
+
logger.info(f"Created new project: {new_project.name} ({new_project.id})")
|
301
|
+
return new_project
|
182
302
|
|
183
303
|
async def update_project_access_time(self, project_id: str) -> None:
|
184
304
|
"""Update the last accessed time for a project."""
|
@@ -73,18 +73,34 @@ class GitHookHandler:
|
|
73
73
|
if not self.api_key:
|
74
74
|
raise GitHookError("OPENROUTER_API_KEY environment variable is required for git hook mode")
|
75
75
|
|
76
|
-
async def run_githook_mode(
|
76
|
+
async def run_githook_mode(
|
77
|
+
self,
|
78
|
+
commit_hash: Optional[str] = None,
|
79
|
+
commit_range: Optional[Tuple[str, str]] = None
|
80
|
+
) -> None:
|
77
81
|
"""
|
78
82
|
Run in git hook mode - analyze changes and update descriptions.
|
79
83
|
|
84
|
+
Args:
|
85
|
+
commit_hash: Process a specific commit by hash
|
86
|
+
commit_range: Process commits in range (start_hash, end_hash)
|
87
|
+
|
80
88
|
This is the main entry point for git hook functionality.
|
81
89
|
"""
|
82
90
|
try:
|
83
91
|
# Get git info from current directory
|
84
92
|
project_info = await self._identify_project_from_git()
|
85
93
|
|
86
|
-
# Get git diff
|
87
|
-
|
94
|
+
# Get git diff and commit message based on mode
|
95
|
+
if commit_hash:
|
96
|
+
git_diff = await self._get_git_diff_for_commit(commit_hash)
|
97
|
+
commit_message = await self._get_commit_message_for_commit(commit_hash)
|
98
|
+
elif commit_range:
|
99
|
+
git_diff = await self._get_git_diff_for_range(commit_range[0], commit_range[1])
|
100
|
+
commit_message = await self._get_commit_messages_for_range(commit_range[0], commit_range[1])
|
101
|
+
else:
|
102
|
+
git_diff = await self._get_git_diff()
|
103
|
+
commit_message = await self._get_commit_message()
|
88
104
|
|
89
105
|
if not git_diff or len(git_diff) > self.config["max_diff_size"]:
|
90
106
|
self.logger.info(f"Skipping git hook update - diff too large or empty")
|
@@ -102,6 +118,7 @@ class GitHookHandler:
|
|
102
118
|
# Build prompt for OpenRouter
|
103
119
|
prompt = self._build_githook_prompt(
|
104
120
|
git_diff,
|
121
|
+
commit_message,
|
105
122
|
current_overview,
|
106
123
|
current_descriptions,
|
107
124
|
changed_files
|
@@ -204,6 +221,115 @@ class GitHookHandler:
|
|
204
221
|
return diff_result
|
205
222
|
except subprocess.CalledProcessError as e:
|
206
223
|
raise GitHookError(f"Failed to get git diff: {e}")
|
224
|
+
|
225
|
+
async def _get_commit_message(self) -> str:
|
226
|
+
"""
|
227
|
+
Get the commit message for context about what was changed.
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Commit message as string
|
231
|
+
"""
|
232
|
+
try:
|
233
|
+
# Get the commit message from the latest commit
|
234
|
+
message_result = await self._run_git_command([
|
235
|
+
"log", "-1", "--pretty=%B"
|
236
|
+
])
|
237
|
+
return message_result.strip()
|
238
|
+
|
239
|
+
except subprocess.CalledProcessError:
|
240
|
+
# If no commits exist yet, return empty string
|
241
|
+
return ""
|
242
|
+
|
243
|
+
async def _get_git_diff_for_commit(self, commit_hash: str) -> str:
|
244
|
+
"""
|
245
|
+
Get git diff for a specific commit.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
commit_hash: The commit hash to analyze
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
Git diff content as string
|
252
|
+
"""
|
253
|
+
try:
|
254
|
+
# Get diff for the specific commit compared to its parent
|
255
|
+
diff_result = await self._run_git_command([
|
256
|
+
"diff", "--no-color", "--no-ext-diff", f"{commit_hash}~1..{commit_hash}"
|
257
|
+
])
|
258
|
+
return diff_result
|
259
|
+
|
260
|
+
except subprocess.CalledProcessError:
|
261
|
+
# If parent doesn't exist (first commit), diff against empty tree
|
262
|
+
try:
|
263
|
+
diff_result = await self._run_git_command([
|
264
|
+
"diff", "--no-color", "--no-ext-diff", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", commit_hash
|
265
|
+
])
|
266
|
+
return diff_result
|
267
|
+
except subprocess.CalledProcessError as e:
|
268
|
+
raise GitHookError(f"Failed to get git diff for commit {commit_hash}: {e}")
|
269
|
+
|
270
|
+
async def _get_git_diff_for_range(self, start_hash: str, end_hash: str) -> str:
|
271
|
+
"""
|
272
|
+
Get git diff for a range of commits.
|
273
|
+
|
274
|
+
Args:
|
275
|
+
start_hash: Starting commit hash (exclusive)
|
276
|
+
end_hash: Ending commit hash (inclusive)
|
277
|
+
|
278
|
+
Returns:
|
279
|
+
Git diff content as string
|
280
|
+
"""
|
281
|
+
try:
|
282
|
+
diff_result = await self._run_git_command([
|
283
|
+
"diff", "--no-color", "--no-ext-diff", f"{start_hash}..{end_hash}"
|
284
|
+
])
|
285
|
+
return diff_result
|
286
|
+
except subprocess.CalledProcessError as e:
|
287
|
+
raise GitHookError(f"Failed to get git diff for range {start_hash}..{end_hash}: {e}")
|
288
|
+
|
289
|
+
async def _get_commit_message_for_commit(self, commit_hash: str) -> str:
|
290
|
+
"""
|
291
|
+
Get the commit message for a specific commit.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
commit_hash: The commit hash
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
Commit message as string
|
298
|
+
"""
|
299
|
+
try:
|
300
|
+
message_result = await self._run_git_command([
|
301
|
+
"log", "-1", "--pretty=%B", commit_hash
|
302
|
+
])
|
303
|
+
return message_result.strip()
|
304
|
+
except subprocess.CalledProcessError as e:
|
305
|
+
raise GitHookError(f"Failed to get commit message for {commit_hash}: {e}")
|
306
|
+
|
307
|
+
async def _get_commit_messages_for_range(self, start_hash: str, end_hash: str) -> str:
|
308
|
+
"""
|
309
|
+
Get commit messages for a range of commits.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
start_hash: Starting commit hash (exclusive)
|
313
|
+
end_hash: Ending commit hash (inclusive)
|
314
|
+
|
315
|
+
Returns:
|
316
|
+
Combined commit messages as string
|
317
|
+
"""
|
318
|
+
try:
|
319
|
+
# Get all commit messages in the range
|
320
|
+
message_result = await self._run_git_command([
|
321
|
+
"log", "--pretty=%B", f"{start_hash}..{end_hash}"
|
322
|
+
])
|
323
|
+
|
324
|
+
# Clean up and format the messages
|
325
|
+
messages = message_result.strip()
|
326
|
+
if messages:
|
327
|
+
return f"Combined commit messages for range {start_hash}..{end_hash}:\n\n{messages}"
|
328
|
+
else:
|
329
|
+
return f"No commits found in range {start_hash}..{end_hash}"
|
330
|
+
|
331
|
+
except subprocess.CalledProcessError as e:
|
332
|
+
raise GitHookError(f"Failed to get commit messages for range {start_hash}..{end_hash}: {e}")
|
207
333
|
|
208
334
|
def _extract_changed_files(self, git_diff: str) -> List[str]:
|
209
335
|
"""
|
@@ -232,12 +358,12 @@ class GitHookHandler:
|
|
232
358
|
async def _get_project_overview(self, project_info: Dict[str, Any]) -> str:
|
233
359
|
"""Get current project overview from database."""
|
234
360
|
try:
|
235
|
-
# Try to
|
236
|
-
project = await self.db_manager.
|
361
|
+
# Try to find existing project
|
362
|
+
project = await self.db_manager.find_matching_project(
|
237
363
|
project_info["projectName"],
|
238
|
-
project_info["folderPath"],
|
239
364
|
project_info.get("remoteOrigin"),
|
240
|
-
project_info.get("upstreamOrigin")
|
365
|
+
project_info.get("upstreamOrigin"),
|
366
|
+
project_info["folderPath"]
|
241
367
|
)
|
242
368
|
|
243
369
|
if project:
|
@@ -255,12 +381,12 @@ class GitHookHandler:
|
|
255
381
|
async def _get_all_descriptions(self, project_info: Dict[str, Any]) -> Dict[str, str]:
|
256
382
|
"""Get all current file descriptions from database."""
|
257
383
|
try:
|
258
|
-
# Try to
|
259
|
-
project = await self.db_manager.
|
384
|
+
# Try to find existing project
|
385
|
+
project = await self.db_manager.find_matching_project(
|
260
386
|
project_info["projectName"],
|
261
|
-
project_info["folderPath"],
|
262
387
|
project_info.get("remoteOrigin"),
|
263
|
-
project_info.get("upstreamOrigin")
|
388
|
+
project_info.get("upstreamOrigin"),
|
389
|
+
project_info["folderPath"]
|
264
390
|
)
|
265
391
|
|
266
392
|
if project:
|
@@ -278,6 +404,7 @@ class GitHookHandler:
|
|
278
404
|
def _build_githook_prompt(
|
279
405
|
self,
|
280
406
|
git_diff: str,
|
407
|
+
commit_message: str,
|
281
408
|
overview: str,
|
282
409
|
descriptions: Dict[str, str],
|
283
410
|
changed_files: List[str]
|
@@ -287,6 +414,7 @@ class GitHookHandler:
|
|
287
414
|
|
288
415
|
Args:
|
289
416
|
git_diff: Git diff content
|
417
|
+
commit_message: Commit message explaining the changes
|
290
418
|
overview: Current project overview
|
291
419
|
descriptions: Current file descriptions
|
292
420
|
changed_files: List of changed file paths
|
@@ -294,7 +422,10 @@ class GitHookHandler:
|
|
294
422
|
Returns:
|
295
423
|
Formatted prompt for the API
|
296
424
|
"""
|
297
|
-
return f"""Analyze this git
|
425
|
+
return f"""Analyze this git commit and update the file descriptions and project overview as needed.
|
426
|
+
|
427
|
+
COMMIT MESSAGE:
|
428
|
+
{commit_message or "No commit message available"}
|
298
429
|
|
299
430
|
CURRENT PROJECT OVERVIEW:
|
300
431
|
{overview or "No overview available"}
|
@@ -310,10 +441,12 @@ CHANGED FILES:
|
|
310
441
|
|
311
442
|
INSTRUCTIONS:
|
312
443
|
|
313
|
-
|
444
|
+
Use the COMMIT MESSAGE to understand the intent and context of the changes. The commit message explains what the developer was trying to accomplish.
|
445
|
+
|
446
|
+
1. **File Descriptions**: Update descriptions for any files that have changed significantly. Consider both the diff content and the commit message context. Only include files that need actual description updates.
|
314
447
|
|
315
448
|
2. **Project Overview**: Update ONLY if there are major structural changes like:
|
316
|
-
- New major features or components
|
449
|
+
- New major features or components (which may be indicated by commit message)
|
317
450
|
- Architectural changes (new patterns, frameworks, or approaches)
|
318
451
|
- Significant dependency additions
|
319
452
|
- New API endpoints or workflows
|
@@ -485,22 +618,36 @@ Return ONLY the JSON, no other text."""
|
|
485
618
|
# Update file descriptions
|
486
619
|
file_updates = updates.get("file_updates", {})
|
487
620
|
for file_path, description in file_updates.items():
|
488
|
-
|
621
|
+
from mcp_code_indexer.database.models import FileDescription
|
622
|
+
from datetime import datetime
|
623
|
+
|
624
|
+
file_desc = FileDescription(
|
489
625
|
project_id=project.id,
|
490
626
|
branch=project_info["branch"],
|
491
627
|
file_path=file_path,
|
492
|
-
description=description
|
628
|
+
description=description,
|
629
|
+
file_hash=None,
|
630
|
+
last_modified=datetime.utcnow(),
|
631
|
+
version=1
|
493
632
|
)
|
633
|
+
await self.db_manager.create_file_description(file_desc)
|
494
634
|
self.logger.info(f"Updated description for {file_path}")
|
495
635
|
|
496
636
|
# Update project overview if provided
|
497
637
|
overview_update = updates.get("overview_update")
|
498
638
|
if overview_update and overview_update.strip():
|
499
|
-
|
639
|
+
from mcp_code_indexer.database.models import ProjectOverview
|
640
|
+
from datetime import datetime
|
641
|
+
|
642
|
+
overview = ProjectOverview(
|
500
643
|
project_id=project.id,
|
501
644
|
branch=project_info["branch"],
|
502
|
-
overview=overview_update
|
645
|
+
overview=overview_update,
|
646
|
+
last_modified=datetime.utcnow(),
|
647
|
+
total_files=len(file_updates),
|
648
|
+
total_tokens=len(overview_update.split())
|
503
649
|
)
|
650
|
+
await self.db_manager.create_project_overview(overview)
|
504
651
|
self.logger.info("Updated project overview")
|
505
652
|
|
506
653
|
except Exception as e:
|
mcp_code_indexer/main.py
CHANGED
@@ -82,8 +82,11 @@ def parse_arguments() -> argparse.Namespace:
|
|
82
82
|
|
83
83
|
parser.add_argument(
|
84
84
|
"--githook",
|
85
|
-
|
86
|
-
|
85
|
+
nargs="*",
|
86
|
+
metavar="COMMIT_HASH",
|
87
|
+
help="Git hook mode: auto-update descriptions based on git diff using OpenRouter API. "
|
88
|
+
"Usage: --githook (current changes), --githook HASH (specific commit), "
|
89
|
+
"--githook HASH1 HASH2 (commit range from HASH1 to HASH2)"
|
87
90
|
)
|
88
91
|
|
89
92
|
parser.add_argument(
|
@@ -462,12 +465,16 @@ async def handle_githook(args: argparse.Namespace) -> None:
|
|
462
465
|
from .database.database import DatabaseManager
|
463
466
|
from .git_hook_handler import GitHookHandler
|
464
467
|
|
468
|
+
# Process commit hash arguments
|
469
|
+
commit_hashes = args.githook if args.githook else []
|
470
|
+
|
465
471
|
logger.info("Starting git hook execution", extra={
|
466
472
|
"structured_data": {
|
467
473
|
"args": {
|
468
474
|
"db_path": str(args.db_path),
|
469
475
|
"cache_dir": str(args.cache_dir),
|
470
|
-
"token_limit": args.token_limit
|
476
|
+
"token_limit": args.token_limit,
|
477
|
+
"commit_hashes": commit_hashes
|
471
478
|
}
|
472
479
|
}
|
473
480
|
})
|
@@ -497,7 +504,17 @@ async def handle_githook(args: argparse.Namespace) -> None:
|
|
497
504
|
|
498
505
|
# Run git hook analysis
|
499
506
|
logger.info("Starting git hook analysis")
|
500
|
-
|
507
|
+
if len(commit_hashes) == 0:
|
508
|
+
# Process current staged changes
|
509
|
+
await git_handler.run_githook_mode()
|
510
|
+
elif len(commit_hashes) == 1:
|
511
|
+
# Process specific commit
|
512
|
+
await git_handler.run_githook_mode(commit_hash=commit_hashes[0])
|
513
|
+
elif len(commit_hashes) == 2:
|
514
|
+
# Process commit range
|
515
|
+
await git_handler.run_githook_mode(commit_range=(commit_hashes[0], commit_hashes[1]))
|
516
|
+
else:
|
517
|
+
raise ValueError("--githook accepts 0, 1, or 2 commit hashes")
|
501
518
|
logger.info("Git hook analysis completed successfully")
|
502
519
|
|
503
520
|
except Exception as e:
|
@@ -510,6 +527,14 @@ async def handle_githook(args: argparse.Namespace) -> None:
|
|
510
527
|
print(f"Git hook error: {e}", file=sys.stderr)
|
511
528
|
sys.exit(1)
|
512
529
|
finally:
|
530
|
+
# Clean up database connections
|
531
|
+
if 'db_manager' in locals():
|
532
|
+
try:
|
533
|
+
await db_manager.close_pool()
|
534
|
+
logger.debug("Database connections closed")
|
535
|
+
except Exception as e:
|
536
|
+
logger.warning(f"Error closing database connections: {e}")
|
537
|
+
|
513
538
|
logger.info("=== GITHOOK SESSION ENDED ===")
|
514
539
|
|
515
540
|
# Close logger handlers to flush any remaining logs
|
@@ -674,15 +699,18 @@ def generate_project_markdown(project, branch, overview, files, logger):
|
|
674
699
|
|
675
700
|
markdown_lines = []
|
676
701
|
|
677
|
-
# Project header
|
678
|
-
|
702
|
+
# Project header with sentence case
|
703
|
+
project_name = project.name.title() if project.name.islower() else project.name
|
704
|
+
markdown_lines.append(f"# {project_name}")
|
679
705
|
markdown_lines.append("")
|
680
706
|
|
681
707
|
# Project metadata
|
682
708
|
if project.remote_origin:
|
683
709
|
markdown_lines.append(f"**Repository:** {project.remote_origin}")
|
710
|
+
markdown_lines.append("")
|
684
711
|
if project.upstream_origin:
|
685
712
|
markdown_lines.append(f"**Upstream:** {project.upstream_origin}")
|
713
|
+
markdown_lines.append("")
|
686
714
|
markdown_lines.append(f"**Branch:** {branch}")
|
687
715
|
markdown_lines.append("")
|
688
716
|
|
@@ -760,8 +788,8 @@ async def main() -> None:
|
|
760
788
|
"""Main entry point for the MCP server."""
|
761
789
|
args = parse_arguments()
|
762
790
|
|
763
|
-
# Handle git hook command
|
764
|
-
if args.githook:
|
791
|
+
# Handle git hook command
|
792
|
+
if args.githook is not None:
|
765
793
|
await handle_githook(args)
|
766
794
|
return
|
767
795
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-code-indexer
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.6.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
|
@@ -57,8 +57,8 @@ Dynamic: requires-python
|
|
57
57
|
|
58
58
|
# MCP Code Indexer 🚀
|
59
59
|
|
60
|
-
[](https://badge.fury.io/py/mcp-code-indexer)
|
61
|
+
[](https://pypi.org/project/mcp-code-indexer/)
|
62
62
|
[](https://opensource.org/licenses/MIT)
|
63
63
|
|
64
64
|
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.
|
@@ -246,6 +246,28 @@ The server provides **11 powerful MCP tools** for intelligent codebase managemen
|
|
246
246
|
|
247
247
|
💡 **Pro Tip**: Always start with `check_codebase_size` to get personalized recommendations for navigating your specific codebase.
|
248
248
|
|
249
|
+
## 🔗 Git Hook Integration
|
250
|
+
|
251
|
+
Keep your codebase documentation automatically synchronized with automated analysis on every commit, rebase, or merge:
|
252
|
+
|
253
|
+
```bash
|
254
|
+
# Analyze current staged changes
|
255
|
+
mcp-code-indexer --githook
|
256
|
+
|
257
|
+
# Analyze a specific commit
|
258
|
+
mcp-code-indexer --githook abc123def
|
259
|
+
|
260
|
+
# Analyze a commit range (perfect for rebases)
|
261
|
+
mcp-code-indexer --githook abc123 def456
|
262
|
+
```
|
263
|
+
|
264
|
+
**🎯 Perfect for**:
|
265
|
+
- **Automated documentation** that never goes stale
|
266
|
+
- **Rebase-aware analysis** that handles complex git operations
|
267
|
+
- **Zero-effort maintenance** with background processing
|
268
|
+
|
269
|
+
See the **[Git Hook Setup Guide](docs/git-hook-setup.md)** for complete installation instructions including post-commit, post-merge, and post-rewrite hooks.
|
270
|
+
|
249
271
|
## 🏗️ Architecture Highlights
|
250
272
|
|
251
273
|
### Performance Optimized
|
@@ -1,15 +1,15 @@
|
|
1
|
-
mcp_code_indexer/__init__.py,sha256=
|
1
|
+
mcp_code_indexer/__init__.py,sha256=DKmuPpw8URDyojC4T1bttK8kGMI9lv_-B9UYP8dqb90,473
|
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=
|
5
|
+
mcp_code_indexer/git_hook_handler.py,sha256=P3WxHH2BYotJqzhrbgEot6fiycevVsCkyMZLfvuJhMM,25475
|
6
6
|
mcp_code_indexer/logging_config.py,sha256=yCGQD-xx9oobS-YctOFcaE1Q3iiuOj2E6cTfKHbh_wc,7358
|
7
|
-
mcp_code_indexer/main.py,sha256=
|
7
|
+
mcp_code_indexer/main.py,sha256=7k00hj2C1CxTDDErbq2Ee072MbvqEAsRqrMHH-w1oM0,31538
|
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
|
11
11
|
mcp_code_indexer/database/__init__.py,sha256=aPq_aaRp0aSwOBIq9GkuMNjmLxA411zg2vhdrAuHm-w,38
|
12
|
-
mcp_code_indexer/database/database.py,sha256=
|
12
|
+
mcp_code_indexer/database/database.py,sha256=iVPdKD5HCjPv4AJeO4Uy-pBaYUgJ_a3KzysyCaA4D8s,35446
|
13
13
|
mcp_code_indexer/database/models.py,sha256=_vCmJnPXZSiInRzyvs4c7QUWuNNW8qsOoDlGX8J-Gnk,7124
|
14
14
|
mcp_code_indexer/middleware/__init__.py,sha256=p-mP0pMsfiU2yajCPvokCUxUEkh_lu4XJP1LyyMW2ug,220
|
15
15
|
mcp_code_indexer/middleware/error_middleware.py,sha256=v6jaHmPxf3qerYdb85X1tHIXLxgcbybpitKVakFLQTA,10109
|
@@ -17,9 +17,9 @@ mcp_code_indexer/server/__init__.py,sha256=16xMcuriUOBlawRqWNBk6niwrvtv_JD5xvI36
|
|
17
17
|
mcp_code_indexer/server/mcp_server.py,sha256=bRl5JTFWlQ0MrIulkts6fDws6kPqfy6kKoQdenMOk04,61290
|
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.6.0.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
21
|
+
mcp_code_indexer-1.6.0.dist-info/METADATA,sha256=0UMPFE12KQsdGMdGN5SCRFcY5LCOTfTH6gLt8V5RF5A,17453
|
22
|
+
mcp_code_indexer-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
mcp_code_indexer-1.6.0.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
|
24
|
+
mcp_code_indexer-1.6.0.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
|
25
|
+
mcp_code_indexer-1.6.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|