mcp-code-indexer 1.4.1__py3-none-any.whl → 1.5.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.
@@ -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.4.1"
9
+ __version__ = "1.5.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."""
@@ -83,8 +83,9 @@ class GitHookHandler:
83
83
  # Get git info from current directory
84
84
  project_info = await self._identify_project_from_git()
85
85
 
86
- # Get git diff
86
+ # Get git diff and commit message
87
87
  git_diff = await self._get_git_diff()
88
+ commit_message = await self._get_commit_message()
88
89
 
89
90
  if not git_diff or len(git_diff) > self.config["max_diff_size"]:
90
91
  self.logger.info(f"Skipping git hook update - diff too large or empty")
@@ -102,6 +103,7 @@ class GitHookHandler:
102
103
  # Build prompt for OpenRouter
103
104
  prompt = self._build_githook_prompt(
104
105
  git_diff,
106
+ commit_message,
105
107
  current_overview,
106
108
  current_descriptions,
107
109
  changed_files
@@ -204,6 +206,24 @@ class GitHookHandler:
204
206
  return diff_result
205
207
  except subprocess.CalledProcessError as e:
206
208
  raise GitHookError(f"Failed to get git diff: {e}")
209
+
210
+ async def _get_commit_message(self) -> str:
211
+ """
212
+ Get the commit message for context about what was changed.
213
+
214
+ Returns:
215
+ Commit message as string
216
+ """
217
+ try:
218
+ # Get the commit message from the latest commit
219
+ message_result = await self._run_git_command([
220
+ "log", "-1", "--pretty=%B"
221
+ ])
222
+ return message_result.strip()
223
+
224
+ except subprocess.CalledProcessError:
225
+ # If no commits exist yet, return empty string
226
+ return ""
207
227
 
208
228
  def _extract_changed_files(self, git_diff: str) -> List[str]:
209
229
  """
@@ -232,12 +252,12 @@ class GitHookHandler:
232
252
  async def _get_project_overview(self, project_info: Dict[str, Any]) -> str:
233
253
  """Get current project overview from database."""
234
254
  try:
235
- # Try to get existing project
236
- project = await self.db_manager.get_project(
255
+ # Try to find existing project
256
+ project = await self.db_manager.find_matching_project(
237
257
  project_info["projectName"],
238
- project_info["folderPath"],
239
258
  project_info.get("remoteOrigin"),
240
- project_info.get("upstreamOrigin")
259
+ project_info.get("upstreamOrigin"),
260
+ project_info["folderPath"]
241
261
  )
242
262
 
243
263
  if project:
@@ -255,12 +275,12 @@ class GitHookHandler:
255
275
  async def _get_all_descriptions(self, project_info: Dict[str, Any]) -> Dict[str, str]:
256
276
  """Get all current file descriptions from database."""
257
277
  try:
258
- # Try to get existing project
259
- project = await self.db_manager.get_project(
278
+ # Try to find existing project
279
+ project = await self.db_manager.find_matching_project(
260
280
  project_info["projectName"],
261
- project_info["folderPath"],
262
281
  project_info.get("remoteOrigin"),
263
- project_info.get("upstreamOrigin")
282
+ project_info.get("upstreamOrigin"),
283
+ project_info["folderPath"]
264
284
  )
265
285
 
266
286
  if project:
@@ -278,6 +298,7 @@ class GitHookHandler:
278
298
  def _build_githook_prompt(
279
299
  self,
280
300
  git_diff: str,
301
+ commit_message: str,
281
302
  overview: str,
282
303
  descriptions: Dict[str, str],
283
304
  changed_files: List[str]
@@ -287,6 +308,7 @@ class GitHookHandler:
287
308
 
288
309
  Args:
289
310
  git_diff: Git diff content
311
+ commit_message: Commit message explaining the changes
290
312
  overview: Current project overview
291
313
  descriptions: Current file descriptions
292
314
  changed_files: List of changed file paths
@@ -294,7 +316,10 @@ class GitHookHandler:
294
316
  Returns:
295
317
  Formatted prompt for the API
296
318
  """
297
- return f"""Analyze this git diff and update the file descriptions and project overview as needed.
319
+ return f"""Analyze this git commit and update the file descriptions and project overview as needed.
320
+
321
+ COMMIT MESSAGE:
322
+ {commit_message or "No commit message available"}
298
323
 
299
324
  CURRENT PROJECT OVERVIEW:
300
325
  {overview or "No overview available"}
@@ -310,10 +335,12 @@ CHANGED FILES:
310
335
 
311
336
  INSTRUCTIONS:
312
337
 
313
- 1. **File Descriptions**: Update descriptions for any files that have changed significantly. Only include files that need actual description updates.
338
+ Use the COMMIT MESSAGE to understand the intent and context of the changes. The commit message explains what the developer was trying to accomplish.
339
+
340
+ 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
341
 
315
342
  2. **Project Overview**: Update ONLY if there are major structural changes like:
316
- - New major features or components
343
+ - New major features or components (which may be indicated by commit message)
317
344
  - Architectural changes (new patterns, frameworks, or approaches)
318
345
  - Significant dependency additions
319
346
  - New API endpoints or workflows
@@ -485,22 +512,36 @@ Return ONLY the JSON, no other text."""
485
512
  # Update file descriptions
486
513
  file_updates = updates.get("file_updates", {})
487
514
  for file_path, description in file_updates.items():
488
- await self.db_manager.upsert_file_description(
515
+ from mcp_code_indexer.database.models import FileDescription
516
+ from datetime import datetime
517
+
518
+ file_desc = FileDescription(
489
519
  project_id=project.id,
490
520
  branch=project_info["branch"],
491
521
  file_path=file_path,
492
- description=description
522
+ description=description,
523
+ file_hash=None,
524
+ last_modified=datetime.utcnow(),
525
+ version=1
493
526
  )
527
+ await self.db_manager.create_file_description(file_desc)
494
528
  self.logger.info(f"Updated description for {file_path}")
495
529
 
496
530
  # Update project overview if provided
497
531
  overview_update = updates.get("overview_update")
498
532
  if overview_update and overview_update.strip():
499
- await self.db_manager.upsert_project_overview(
533
+ from mcp_code_indexer.database.models import ProjectOverview
534
+ from datetime import datetime
535
+
536
+ overview = ProjectOverview(
500
537
  project_id=project.id,
501
538
  branch=project_info["branch"],
502
- overview=overview_update
539
+ overview=overview_update,
540
+ last_modified=datetime.utcnow(),
541
+ total_files=len(file_updates),
542
+ total_tokens=len(overview_update.split())
503
543
  )
544
+ await self.db_manager.create_project_overview(overview)
504
545
  self.logger.info("Updated project overview")
505
546
 
506
547
  except Exception as e:
mcp_code_indexer/main.py CHANGED
@@ -510,6 +510,14 @@ async def handle_githook(args: argparse.Namespace) -> None:
510
510
  print(f"Git hook error: {e}", file=sys.stderr)
511
511
  sys.exit(1)
512
512
  finally:
513
+ # Clean up database connections
514
+ if 'db_manager' in locals():
515
+ try:
516
+ await db_manager.close_pool()
517
+ logger.debug("Database connections closed")
518
+ except Exception as e:
519
+ logger.warning(f"Error closing database connections: {e}")
520
+
513
521
  logger.info("=== GITHOOK SESSION ENDED ===")
514
522
 
515
523
  # Close logger handlers to flush any remaining logs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-code-indexer
3
- Version: 1.4.1
3
+ Version: 1.5.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
- [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?2)](https://badge.fury.io/py/mcp-code-indexer)
61
- [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?2)](https://pypi.org/project/mcp-code-indexer/)
60
+ [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?3)](https://badge.fury.io/py/mcp-code-indexer)
61
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?3)](https://pypi.org/project/mcp-code-indexer/)
62
62
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](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.
@@ -1,15 +1,15 @@
1
- mcp_code_indexer/__init__.py,sha256=NiKQWxFDFIhYEDn_q4vGaSEhBDwKtGiJlbQlxFpTxZo,473
1
+ mcp_code_indexer/__init__.py,sha256=MkcEc96Vu59HUZk8IWIwS4psMwejpOuIvqik9_crg-w,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=kp58PZUEU52H3ReqT84SC-BftwOo9WCjsAM6QHoj9rg,19448
5
+ mcp_code_indexer/git_hook_handler.py,sha256=u_C5zVSDFkagBufgqOPgZHh3ZgvUuDzfqm0BLmCE2Fo,21253
6
6
  mcp_code_indexer/logging_config.py,sha256=yCGQD-xx9oobS-YctOFcaE1Q3iiuOj2E6cTfKHbh_wc,7358
7
- mcp_code_indexer/main.py,sha256=F1Ym1bSyatLk3jr9D2utqYZvWF7zq-bgOWjcmMZkvb4,30193
7
+ mcp_code_indexer/main.py,sha256=e4Peuus4kZt0lc986pCtOz3Kk194rGN6cwsvxTqde28,30512
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=c7-rtj7ZM2w9HcKphSjNccygwnTwDCiret79xbP1Jtg,31000
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.4.1.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
21
- mcp_code_indexer-1.4.1.dist-info/METADATA,sha256=teVJB_nIJwLXbUEui2qlYSXmPaGKRO-B32xyHluZSh0,16721
22
- mcp_code_indexer-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- mcp_code_indexer-1.4.1.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
24
- mcp_code_indexer-1.4.1.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
25
- mcp_code_indexer-1.4.1.dist-info/RECORD,,
20
+ mcp_code_indexer-1.5.0.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
21
+ mcp_code_indexer-1.5.0.dist-info/METADATA,sha256=yTD1vbKGaJ8RGmFL4SsRYZj1mwB4Wls5_FHz2YwISTM,16721
22
+ mcp_code_indexer-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ mcp_code_indexer-1.5.0.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
24
+ mcp_code_indexer-1.5.0.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
25
+ mcp_code_indexer-1.5.0.dist-info/RECORD,,