mcp-code-indexer 1.7.0__tar.gz → 1.8.1__tar.gz

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.
Files changed (41) hide show
  1. {mcp_code_indexer-1.7.0/src/mcp_code_indexer.egg-info → mcp_code_indexer-1.8.1}/PKG-INFO +3 -3
  2. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/README.md +2 -2
  3. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/pyproject.toml +1 -1
  4. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/git_hook_handler.py +187 -95
  5. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1/src/mcp_code_indexer.egg-info}/PKG-INFO +3 -3
  6. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/LICENSE +0 -0
  7. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/MANIFEST.in +0 -0
  8. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/docs/api-reference.md +0 -0
  9. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/docs/architecture.md +0 -0
  10. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/docs/configuration.md +0 -0
  11. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/docs/contributing.md +0 -0
  12. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/docs/git-hook-setup.md +0 -0
  13. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/migrations/001_initial.sql +0 -0
  14. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/migrations/002_performance_indexes.sql +0 -0
  15. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/migrations/003_project_overviews.sql +0 -0
  16. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/requirements.txt +0 -0
  17. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/setup.cfg +0 -0
  18. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/setup.py +0 -0
  19. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/__init__.py +0 -0
  20. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/__main__.py +0 -0
  21. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/data/stop_words_english.txt +0 -0
  22. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/database/__init__.py +0 -0
  23. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/database/database.py +0 -0
  24. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/database/models.py +0 -0
  25. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/error_handler.py +0 -0
  26. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/file_scanner.py +0 -0
  27. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/logging_config.py +0 -0
  28. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/main.py +0 -0
  29. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/merge_handler.py +0 -0
  30. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/middleware/__init__.py +0 -0
  31. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/middleware/error_middleware.py +0 -0
  32. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/server/__init__.py +0 -0
  33. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/server/mcp_server.py +0 -0
  34. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +0 -0
  35. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/token_counter.py +0 -0
  36. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer/tools/__init__.py +0 -0
  37. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer.egg-info/SOURCES.txt +0 -0
  38. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer.egg-info/dependency_links.txt +0 -0
  39. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer.egg-info/entry_points.txt +0 -0
  40. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer.egg-info/requires.txt +0 -0
  41. {mcp_code_indexer-1.7.0 → mcp_code_indexer-1.8.1}/src/mcp_code_indexer.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-code-indexer
3
- Version: 1.7.0
3
+ Version: 1.8.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
@@ -59,8 +59,8 @@ Dynamic: requires-python
59
59
 
60
60
  # MCP Code Indexer 🚀
61
61
 
62
- [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?9)](https://badge.fury.io/py/mcp-code-indexer)
63
- [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?9)](https://pypi.org/project/mcp-code-indexer/)
62
+ [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?10)](https://badge.fury.io/py/mcp-code-indexer)
63
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?10)](https://pypi.org/project/mcp-code-indexer/)
64
64
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](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.
@@ -1,7 +1,7 @@
1
1
  # MCP Code Indexer 🚀
2
2
 
3
- [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?9)](https://badge.fury.io/py/mcp-code-indexer)
4
- [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?9)](https://pypi.org/project/mcp-code-indexer/)
3
+ [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?10)](https://badge.fury.io/py/mcp-code-indexer)
4
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?10)](https://pypi.org/project/mcp-code-indexer/)
5
5
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mcp-code-indexer"
7
- version = "1.7.0"
7
+ version = "1.8.1"
8
8
  description = "MCP server that tracks file descriptions across codebases, enabling AI agents to efficiently navigate and understand code through searchable summaries and token-aware overviews."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -137,32 +137,26 @@ class GitHookHandler:
137
137
  self.logger.info(f"Current overview length: {len(current_overview) if current_overview else 0} characters")
138
138
  self.logger.info(f"Current descriptions count: {len(current_descriptions)}")
139
139
 
140
- # Build prompt for OpenRouter
141
- self.logger.info("Building analysis prompt...")
142
- prompt = self._build_githook_prompt(
143
- git_diff,
144
- commit_message,
145
- current_overview,
146
- current_descriptions,
147
- changed_files
148
- )
149
-
150
- # Log prompt details
151
- prompt_chars = len(prompt)
152
- prompt_tokens = self.token_counter.count_tokens(prompt)
153
- 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...")
154
142
 
155
- # Check total prompt token count
156
- if prompt_tokens > self.config["max_diff_tokens"]:
157
- self.logger.info(f"Skipping git hook update - prompt too large ({prompt_tokens} tokens > {self.config['max_diff_tokens']} limit)")
158
- return
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
+ )
159
147
 
160
- self.logger.info(f"Prompt size OK ({prompt_tokens} <= {self.config['max_diff_tokens']} tokens), calling OpenRouter...")
148
+ # Stage 2: Update file descriptions
149
+ file_updates = await self._analyze_file_updates(
150
+ git_diff, commit_message, current_descriptions, changed_files
151
+ )
161
152
 
162
- # Call OpenRouter API
163
- updates = await self._call_openrouter(prompt)
153
+ # Combine updates
154
+ updates = {
155
+ "file_updates": file_updates.get("file_updates", {}),
156
+ "overview_update": overview_updates.get("overview_update")
157
+ }
164
158
 
165
- self.logger.info(f"OpenRouter response received, processing updates...")
159
+ self.logger.info(f"Two-stage analysis completed, processing updates...")
166
160
 
167
161
  # Apply updates to database
168
162
  await self._apply_updates(project_info, updates)
@@ -441,101 +435,143 @@ class GitHookHandler:
441
435
  self.logger.warning(f"Failed to get file descriptions: {e}")
442
436
  return {}
443
437
 
444
- def _build_githook_prompt(
445
- self,
446
- git_diff: str,
447
- commit_message: str,
448
- overview: str,
449
- descriptions: Dict[str, str],
438
+ async def _analyze_overview_updates(
439
+ self,
440
+ git_diff: str,
441
+ commit_message: str,
442
+ current_overview: str,
450
443
  changed_files: List[str]
451
- ) -> str:
444
+ ) -> Dict[str, Any]:
452
445
  """
453
- Build prompt for OpenRouter API to analyze git changes.
446
+ Stage 1: Analyze if project overview needs updating.
454
447
 
455
448
  Args:
456
449
  git_diff: Git diff content
457
450
  commit_message: Commit message explaining the changes
458
- overview: Current project overview
459
- descriptions: Current file descriptions
451
+ current_overview: Current project overview
460
452
  changed_files: List of changed file paths
461
453
 
462
454
  Returns:
463
- Formatted prompt for the API
455
+ Dict with overview_update key
464
456
  """
465
- return f"""Analyze this git commit and update the file descriptions and project overview as needed.
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.
466
460
 
467
461
  COMMIT MESSAGE:
468
462
  {commit_message or "No commit message available"}
469
463
 
470
464
  CURRENT PROJECT OVERVIEW:
471
- {overview or "No overview available"}
465
+ {current_overview or "No overview available"}
472
466
 
473
- CURRENT FILE DESCRIPTIONS:
474
- {json.dumps(descriptions, indent=2)}
467
+ CHANGED FILES:
468
+ {', '.join(changed_files)}
475
469
 
476
470
  GIT DIFF:
477
471
  {git_diff}
478
472
 
479
- CHANGED FILES:
480
- {', '.join(changed_files)}
481
-
482
473
  INSTRUCTIONS:
483
474
 
484
- Use the COMMIT MESSAGE to understand the intent and context of the changes. The commit message explains what the developer was trying to accomplish.
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
485
481
 
486
- 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.
482
+ Do NOT update for: bug fixes, small refactors, documentation updates, version bumps.
487
483
 
488
- 2. **Project Overview**: Update ONLY if there are major structural changes like:
489
- - New major features or components (which may be indicated by commit message)
490
- - Architectural changes (new patterns, frameworks, or approaches)
491
- - Significant dependency additions
492
- - New API endpoints or workflows
493
- - Changes to build/deployment processes
494
-
495
- Do NOT update overview for minor changes like bug fixes, small refactors, or documentation updates.
484
+ If updating, provide comprehensive narrative (10-20 pages of text) with directory structure, architecture, components, and workflows.
496
485
 
497
- 3. **Overview Format**: If updating the overview, follow this structure with comprehensive narrative (10-20 pages of text):
486
+ Return ONLY a JSON object:
487
+ {{
488
+ "overview_update": "Updated overview text" or null
489
+ }}"""
498
490
 
499
- ````
500
- ## Directory Structure
501
- ```
502
- src/
503
- ├── api/ # REST API endpoints and middleware
504
- ├── models/ # Database models and business logic
505
- ├── services/ # External service integrations
506
- ├── utils/ # Shared utilities and helpers
507
- └── tests/ # Test suites
508
- ```
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
509
505
 
510
- ## Architecture Overview
511
- [Describe how components interact, data flow, key design decisions]
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.
512
534
 
513
- ## Core Components
514
- ### API Layer
515
- [Details about API structure, authentication, routing]
535
+ COMMIT MESSAGE:
536
+ {commit_message or "No commit message available"}
537
+
538
+ CURRENT FILE DESCRIPTIONS (for changed files only):
539
+ {json.dumps(relevant_descriptions, indent=2)}
540
+
541
+ CHANGED FILES:
542
+ {', '.join(changed_files)}
543
+
544
+ GIT DIFF:
545
+ {git_diff}
516
546
 
517
- ### Data Model
518
- [Key entities, relationships, database design]
547
+ INSTRUCTIONS:
519
548
 
520
- ## Key Workflows
521
- 1. User Authentication Flow
522
- [Step-by-step description]
523
- 2. Data Processing Pipeline
524
- [How data moves through the system]
549
+ Use the COMMIT MESSAGE to understand the intent and context of the changes.
525
550
 
526
- [Continue with other sections...]
527
- ````
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.
528
552
 
529
- Return ONLY a JSON object in this exact format:
553
+ Return ONLY a JSON object:
530
554
  {{
531
555
  "file_updates": {{
532
556
  "path/to/file1.py": "Updated description for file1",
533
557
  "path/to/file2.js": "Updated description for file2"
534
- }},
535
- "overview_update": "Updated project overview text (or null if no update needed)"
536
- }}
558
+ }}
559
+ }}"""
537
560
 
538
- Return ONLY the JSON, no other text."""
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
539
575
 
540
576
  @retry(
541
577
  wait=wait_exponential(multiplier=1, min=4, max=60),
@@ -631,28 +667,84 @@ Return ONLY the JSON, no other text."""
631
667
  Returns:
632
668
  Validated response data
633
669
  """
670
+ def extract_json_from_response(text: str) -> str:
671
+ """Extract JSON from response that might have extra text before/after."""
672
+ text = text.strip()
673
+
674
+ # Try to find JSON in the response
675
+ json_start = -1
676
+ json_end = -1
677
+
678
+ # Look for opening brace
679
+ for i, char in enumerate(text):
680
+ if char == '{':
681
+ json_start = i
682
+ break
683
+
684
+ if json_start == -1:
685
+ return text # No JSON found, return original
686
+
687
+ # Find matching closing brace
688
+ brace_count = 0
689
+ for i in range(json_start, len(text)):
690
+ if text[i] == '{':
691
+ brace_count += 1
692
+ elif text[i] == '}':
693
+ brace_count -= 1
694
+ if brace_count == 0:
695
+ json_end = i + 1
696
+ break
697
+
698
+ if json_end == -1:
699
+ return text # No matching brace found, return original
700
+
701
+ return text[json_start:json_end]
702
+
634
703
  try:
635
- data = json.loads(response_text.strip())
636
-
637
- # Validate structure
638
- if "file_updates" not in data:
639
- raise ValueError("Missing 'file_updates' field")
640
- if "overview_update" not in data:
641
- raise ValueError("Missing 'overview_update' field")
642
-
643
- if not isinstance(data["file_updates"], dict):
644
- raise ValueError("'file_updates' must be a dictionary")
645
-
646
- # Validate descriptions
647
- for path, desc in data["file_updates"].items():
648
- if not isinstance(desc, str) or not desc.strip():
649
- raise ValueError(f"Invalid description for {path}")
704
+ # First try parsing as-is
705
+ try:
706
+ data = json.loads(response_text.strip())
707
+ except json.JSONDecodeError:
708
+ # Try extracting JSON from response
709
+ extracted_json = extract_json_from_response(response_text)
710
+ if extracted_json != response_text.strip():
711
+ self.logger.debug(f"Extracted JSON from response: {extracted_json}")
712
+ data = json.loads(extracted_json)
713
+
714
+ # Handle both single-stage and two-stage responses
715
+ if "file_updates" in data and "overview_update" in data:
716
+ # Original single-stage format
717
+ if not isinstance(data["file_updates"], dict):
718
+ raise ValueError("'file_updates' must be a dictionary")
719
+
720
+ # Validate descriptions
721
+ for path, desc in data["file_updates"].items():
722
+ if not isinstance(desc, str) or not desc.strip():
723
+ raise ValueError(f"Invalid description for {path}")
724
+
725
+ elif "file_updates" in data:
726
+ # Stage 2 format (file updates only)
727
+ if not isinstance(data["file_updates"], dict):
728
+ raise ValueError("'file_updates' must be a dictionary")
729
+
730
+ # Validate descriptions
731
+ for path, desc in data["file_updates"].items():
732
+ if not isinstance(desc, str) or not desc.strip():
733
+ raise ValueError(f"Invalid description for {path}")
734
+
735
+ elif "overview_update" in data:
736
+ # Stage 1 format (overview only) - overview_update can be null
737
+ pass
738
+ else:
739
+ raise ValueError("Response must contain 'file_updates' and/or 'overview_update'")
650
740
 
651
741
  return data
652
742
 
653
743
  except json.JSONDecodeError as e:
744
+ self.logger.error(f"Raw response content: {repr(response_text)}")
654
745
  raise GitHookError(f"Invalid JSON response from API: {e}")
655
746
  except ValueError as e:
747
+ self.logger.error(f"Raw response content: {repr(response_text)}")
656
748
  raise GitHookError(f"Invalid response structure: {e}")
657
749
 
658
750
  async def _apply_updates(self, project_info: Dict[str, Any], updates: Dict[str, Any]) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-code-indexer
3
- Version: 1.7.0
3
+ Version: 1.8.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
@@ -59,8 +59,8 @@ Dynamic: requires-python
59
59
 
60
60
  # MCP Code Indexer 🚀
61
61
 
62
- [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?9)](https://badge.fury.io/py/mcp-code-indexer)
63
- [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?9)](https://pypi.org/project/mcp-code-indexer/)
62
+ [![PyPI version](https://badge.fury.io/py/mcp-code-indexer.svg?10)](https://badge.fury.io/py/mcp-code-indexer)
63
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-code-indexer.svg?10)](https://pypi.org/project/mcp-code-indexer/)
64
64
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](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.