mcp-code-indexer 1.5.0__py3-none-any.whl → 1.6.1__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.5.0"
9
+ __version__ = "1.6.0"
10
10
  __author__ = "MCP Code Indexer Contributors"
11
11
  __email__ = ""
12
12
  __license__ = "MIT"
@@ -22,6 +22,7 @@ from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_excep
22
22
  from .database.database import DatabaseManager
23
23
  from .database.models import Project, FileDescription
24
24
  from .error_handler import ValidationError
25
+ from .token_counter import TokenCounter
25
26
 
26
27
 
27
28
  class GitHookError(Exception):
@@ -59,11 +60,12 @@ class GitHookHandler:
59
60
  self.db_manager = db_manager
60
61
  self.cache_dir = cache_dir
61
62
  self.logger = logging.getLogger(__name__)
63
+ self.token_counter = TokenCounter()
62
64
 
63
65
  # Git hook specific settings
64
66
  self.config = {
65
67
  "model": os.getenv("MCP_GITHOOK_MODEL", self.OPENROUTER_MODEL),
66
- "max_diff_size": 100000, # Skip if diff larger than this
68
+ "max_diff_tokens": 136000, # Skip if diff larger than this (in tokens)
67
69
  "timeout": 30,
68
70
  "temperature": 0.3, # Lower temperature for consistent updates
69
71
  }
@@ -73,22 +75,43 @@ class GitHookHandler:
73
75
  if not self.api_key:
74
76
  raise GitHookError("OPENROUTER_API_KEY environment variable is required for git hook mode")
75
77
 
76
- async def run_githook_mode(self) -> None:
78
+ async def run_githook_mode(
79
+ self,
80
+ commit_hash: Optional[str] = None,
81
+ commit_range: Optional[Tuple[str, str]] = None
82
+ ) -> None:
77
83
  """
78
84
  Run in git hook mode - analyze changes and update descriptions.
79
85
 
86
+ Args:
87
+ commit_hash: Process a specific commit by hash
88
+ commit_range: Process commits in range (start_hash, end_hash)
89
+
80
90
  This is the main entry point for git hook functionality.
81
91
  """
82
92
  try:
83
93
  # Get git info from current directory
84
94
  project_info = await self._identify_project_from_git()
85
95
 
86
- # Get git diff and commit message
87
- git_diff = await self._get_git_diff()
88
- commit_message = await self._get_commit_message()
96
+ # Get git diff and commit message based on mode
97
+ if commit_hash:
98
+ git_diff = await self._get_git_diff_for_commit(commit_hash)
99
+ commit_message = await self._get_commit_message_for_commit(commit_hash)
100
+ elif commit_range:
101
+ git_diff = await self._get_git_diff_for_range(commit_range[0], commit_range[1])
102
+ commit_message = await self._get_commit_messages_for_range(commit_range[0], commit_range[1])
103
+ else:
104
+ git_diff = await self._get_git_diff()
105
+ commit_message = await self._get_commit_message()
89
106
 
90
- if not git_diff or len(git_diff) > self.config["max_diff_size"]:
91
- self.logger.info(f"Skipping git hook update - diff too large or empty")
107
+ if not git_diff:
108
+ self.logger.info(f"Skipping git hook update - no git diff")
109
+ return
110
+
111
+ # Check token count instead of character count
112
+ diff_tokens = self.token_counter.count_tokens(git_diff)
113
+ if diff_tokens > self.config["max_diff_tokens"]:
114
+ self.logger.info(f"Skipping git hook update - diff too large ({diff_tokens} tokens > {self.config['max_diff_tokens']} limit)")
92
115
  return
93
116
 
94
117
  # Fetch current state
@@ -224,6 +247,97 @@ class GitHookHandler:
224
247
  except subprocess.CalledProcessError:
225
248
  # If no commits exist yet, return empty string
226
249
  return ""
250
+
251
+ async def _get_git_diff_for_commit(self, commit_hash: str) -> str:
252
+ """
253
+ Get git diff for a specific commit.
254
+
255
+ Args:
256
+ commit_hash: The commit hash to analyze
257
+
258
+ Returns:
259
+ Git diff content as string
260
+ """
261
+ try:
262
+ # Get diff for the specific commit compared to its parent
263
+ diff_result = await self._run_git_command([
264
+ "diff", "--no-color", "--no-ext-diff", f"{commit_hash}~1..{commit_hash}"
265
+ ])
266
+ return diff_result
267
+
268
+ except subprocess.CalledProcessError:
269
+ # If parent doesn't exist (first commit), diff against empty tree
270
+ try:
271
+ diff_result = await self._run_git_command([
272
+ "diff", "--no-color", "--no-ext-diff", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", commit_hash
273
+ ])
274
+ return diff_result
275
+ except subprocess.CalledProcessError as e:
276
+ raise GitHookError(f"Failed to get git diff for commit {commit_hash}: {e}")
277
+
278
+ async def _get_git_diff_for_range(self, start_hash: str, end_hash: str) -> str:
279
+ """
280
+ Get git diff for a range of commits.
281
+
282
+ Args:
283
+ start_hash: Starting commit hash (exclusive)
284
+ end_hash: Ending commit hash (inclusive)
285
+
286
+ Returns:
287
+ Git diff content as string
288
+ """
289
+ try:
290
+ diff_result = await self._run_git_command([
291
+ "diff", "--no-color", "--no-ext-diff", f"{start_hash}..{end_hash}"
292
+ ])
293
+ return diff_result
294
+ except subprocess.CalledProcessError as e:
295
+ raise GitHookError(f"Failed to get git diff for range {start_hash}..{end_hash}: {e}")
296
+
297
+ async def _get_commit_message_for_commit(self, commit_hash: str) -> str:
298
+ """
299
+ Get the commit message for a specific commit.
300
+
301
+ Args:
302
+ commit_hash: The commit hash
303
+
304
+ Returns:
305
+ Commit message as string
306
+ """
307
+ try:
308
+ message_result = await self._run_git_command([
309
+ "log", "-1", "--pretty=%B", commit_hash
310
+ ])
311
+ return message_result.strip()
312
+ except subprocess.CalledProcessError as e:
313
+ raise GitHookError(f"Failed to get commit message for {commit_hash}: {e}")
314
+
315
+ async def _get_commit_messages_for_range(self, start_hash: str, end_hash: str) -> str:
316
+ """
317
+ Get commit messages for a range of commits.
318
+
319
+ Args:
320
+ start_hash: Starting commit hash (exclusive)
321
+ end_hash: Ending commit hash (inclusive)
322
+
323
+ Returns:
324
+ Combined commit messages as string
325
+ """
326
+ try:
327
+ # Get all commit messages in the range
328
+ message_result = await self._run_git_command([
329
+ "log", "--pretty=%B", f"{start_hash}..{end_hash}"
330
+ ])
331
+
332
+ # Clean up and format the messages
333
+ messages = message_result.strip()
334
+ if messages:
335
+ return f"Combined commit messages for range {start_hash}..{end_hash}:\n\n{messages}"
336
+ else:
337
+ return f"No commits found in range {start_hash}..{end_hash}"
338
+
339
+ except subprocess.CalledProcessError as e:
340
+ raise GitHookError(f"Failed to get commit messages for range {start_hash}..{end_hash}: {e}")
227
341
 
228
342
  def _extract_changed_files(self, git_diff: str) -> List[str]:
229
343
  """
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
- action="store_true",
86
- help="Git hook mode: auto-update descriptions based on git diff using OpenRouter API"
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
- await git_handler.run_githook_mode()
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:
@@ -682,15 +699,18 @@ def generate_project_markdown(project, branch, overview, files, logger):
682
699
 
683
700
  markdown_lines = []
684
701
 
685
- # Project header
686
- markdown_lines.append(f"# {project.name}")
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}")
687
705
  markdown_lines.append("")
688
706
 
689
707
  # Project metadata
690
708
  if project.remote_origin:
691
709
  markdown_lines.append(f"**Repository:** {project.remote_origin}")
710
+ markdown_lines.append("")
692
711
  if project.upstream_origin:
693
712
  markdown_lines.append(f"**Upstream:** {project.upstream_origin}")
713
+ markdown_lines.append("")
694
714
  markdown_lines.append(f"**Branch:** {branch}")
695
715
  markdown_lines.append("")
696
716
 
@@ -768,8 +788,8 @@ async def main() -> None:
768
788
  """Main entry point for the MCP server."""
769
789
  args = parse_arguments()
770
790
 
771
- # Handle git hook command
772
- if args.githook:
791
+ # Handle git hook command
792
+ if args.githook is not None:
773
793
  await handle_githook(args)
774
794
  return
775
795
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-code-indexer
3
- Version: 1.5.0
3
+ Version: 1.6.1
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?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/)
60
+ [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?5)](https://badge.fury.io/py/mcp-code-indexer)
61
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?5)](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.
@@ -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,10 +1,10 @@
1
- mcp_code_indexer/__init__.py,sha256=MkcEc96Vu59HUZk8IWIwS4psMwejpOuIvqik9_crg-w,473
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=u_C5zVSDFkagBufgqOPgZHh3ZgvUuDzfqm0BLmCE2Fo,21253
5
+ mcp_code_indexer/git_hook_handler.py,sha256=qd6y48PDnwnmErzkGyQGILuD3wQe3hbKEN0i6kxdIX4,25884
6
6
  mcp_code_indexer/logging_config.py,sha256=yCGQD-xx9oobS-YctOFcaE1Q3iiuOj2E6cTfKHbh_wc,7358
7
- mcp_code_indexer/main.py,sha256=e4Peuus4kZt0lc986pCtOz3Kk194rGN6cwsvxTqde28,30512
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
@@ -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.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,,
20
+ mcp_code_indexer-1.6.1.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
21
+ mcp_code_indexer-1.6.1.dist-info/METADATA,sha256=Fa4NYHH1gbZoF_aloOCDVWF7khgSqSU2ppYYZC_uOEI,17453
22
+ mcp_code_indexer-1.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ mcp_code_indexer-1.6.1.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
24
+ mcp_code_indexer-1.6.1.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
25
+ mcp_code_indexer-1.6.1.dist-info/RECORD,,