mcp-vector-search 0.8.6__tar.gz → 0.9.0__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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

Files changed (149) hide show
  1. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/PKG-INFO +1 -1
  2. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/__init__.py +2 -2
  3. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/index.py +1 -1
  4. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/visualize.py +215 -36
  5. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/database.py +113 -0
  6. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/indexer.py +17 -1
  7. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/models.py +8 -0
  8. mcp_vector_search-0.9.0/src/mcp_vector_search/utils/monorepo.py +277 -0
  9. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/20251009-204754-feat-add-comprehensive-changeset-and-documentation.md +0 -0
  10. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/20251009-205435-fix-update-readme-version-badge-to-0-7-1.md +0 -0
  11. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/20251009-205439-feat-add-comprehensive-changeset-support-system.md +0 -0
  12. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/EXAMPLE.md +0 -0
  13. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/IMPLEMENTATION_SUMMARY.md +0 -0
  14. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/README.md +0 -0
  15. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.changesets/template.md +0 -0
  16. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.editorconfig +0 -0
  17. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.github/workflows/ci.yml +0 -0
  18. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.gitignore +0 -0
  19. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/.pre-commit-config.yaml +0 -0
  20. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/CLAUDE.md +0 -0
  21. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/LICENSE +0 -0
  22. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/Makefile +0 -0
  23. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/PERFORMANCE_OPTIMIZATION_SUMMARY.md +0 -0
  24. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/README.md +0 -0
  25. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/CHANGELOG.md +0 -0
  26. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/CLI_FEATURES.md +0 -0
  27. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/DEPLOY.md +0 -0
  28. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/DEVELOPMENT.md +0 -0
  29. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/FEATURES.md +0 -0
  30. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/IMPROVEMENTS_SUMMARY.md +0 -0
  31. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/MCP_FILE_WATCHING.md +0 -0
  32. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/RELEASES.md +0 -0
  33. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/STRUCTURE.md +0 -0
  34. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/VERSIONING.md +0 -0
  35. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/VERSIONING_WORKFLOW.md +0 -0
  36. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/_archive/CLAUDE_20251009_pre_mpm_init.md +0 -0
  37. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/_archive/CLAUDE_MPM_INIT_SUMMARY_20251009.md +0 -0
  38. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/_archive/MPM_INIT_EXECUTIVE_SUMMARY.md +0 -0
  39. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/analysis/SEARCH_ANALYSIS_REPORT.md +0 -0
  40. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/analysis/SEARCH_IMPROVEMENT_PLAN.md +0 -0
  41. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/architecture/REINDEXING_WORKFLOW.md +0 -0
  42. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/debugging/SEARCH_BUG_ANALYSIS.md +0 -0
  43. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/API.md +0 -0
  44. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/CONTRIBUTING.md +0 -0
  45. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/DEVELOPER.md +0 -0
  46. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/LINTING.md +0 -0
  47. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/REFACTORING_ANALYSIS.md +0 -0
  48. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/TESTING.md +0 -0
  49. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/TESTING_STRATEGY.md +0 -0
  50. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/developer/TEST_SUITE_SUMMARY.md +0 -0
  51. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/mcp-integration.md +0 -0
  52. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/optimizations/database-stats-chunked-processing.md +0 -0
  53. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/performance/CONNECTION_POOLING.md +0 -0
  54. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/performance/SEARCH_TIMING_ANALYSIS.md +0 -0
  55. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/prd/mcp_vector_search_prd_updated.md +0 -0
  56. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/reference/ENGINEER_TASK.md +0 -0
  57. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/reference/INSTALL.md +0 -0
  58. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/reference/INSTALL_COMMAND_ENHANCEMENTS.md +0 -0
  59. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/reference/MCP_SETUP.md +0 -0
  60. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/reference/PROJECT_ORGANIZATION.md +0 -0
  61. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/docs/technical/SIMILARITY_CALCULATION_FIX.md +0 -0
  62. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/examples/connection_pooling_example.py +0 -0
  63. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/examples/semi_automatic_reindexing_demo.py +0 -0
  64. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/mcp-vector-search-wrapper +0 -0
  65. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/pyproject.toml +0 -0
  66. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/pytest.ini +0 -0
  67. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/README.md +0 -0
  68. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/analyze_search_bottlenecks.py +0 -0
  69. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/build.sh +0 -0
  70. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/changeset.py +0 -0
  71. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/comprehensive_build.py +0 -0
  72. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/deploy-test.sh +0 -0
  73. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/dev-build.py +0 -0
  74. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/dev-setup.py +0 -0
  75. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/dev-test.sh +0 -0
  76. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/fix_linting.py +0 -0
  77. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/mcp-dev +0 -0
  78. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/monitor_search_performance.py +0 -0
  79. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/publish.sh +0 -0
  80. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/quick_search_timing.py +0 -0
  81. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/run_search_timing_tests.py +0 -0
  82. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/run_tests.py +0 -0
  83. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/search_performance_monitor.py +0 -0
  84. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/search_quality_analyzer.py +0 -0
  85. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/setup/mcp-vector-search.sh +0 -0
  86. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/setup/setup-alias.sh +0 -0
  87. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/setup-dev-mcp.sh +0 -0
  88. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/update_docs.py +0 -0
  89. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/version_manager.py +0 -0
  90. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/scripts/workflow.sh +0 -0
  91. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/__init__.py +0 -0
  92. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/__init__.py +0 -0
  93. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/auto_index.py +0 -0
  94. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/config.py +0 -0
  95. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/demo.py +0 -0
  96. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/init.py +0 -0
  97. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/install.py +0 -0
  98. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/mcp.py +0 -0
  99. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/reset.py +0 -0
  100. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/search.py +0 -0
  101. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/status.py +0 -0
  102. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/commands/watch.py +0 -0
  103. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/didyoumean.py +0 -0
  104. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/export.py +0 -0
  105. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/history.py +0 -0
  106. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/interactive.py +0 -0
  107. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/main.py +0 -0
  108. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/output.py +0 -0
  109. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/cli/suggestions.py +0 -0
  110. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/config/__init__.py +0 -0
  111. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/config/constants.py +0 -0
  112. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/config/defaults.py +0 -0
  113. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/config/settings.py +0 -0
  114. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/__init__.py +0 -0
  115. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/auto_indexer.py +0 -0
  116. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/connection_pool.py +0 -0
  117. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/embeddings.py +0 -0
  118. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/exceptions.py +0 -0
  119. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/factory.py +0 -0
  120. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/git_hooks.py +0 -0
  121. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/project.py +0 -0
  122. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/scheduler.py +0 -0
  123. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/search.py +0 -0
  124. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/core/watcher.py +0 -0
  125. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/mcp/__init__.py +0 -0
  126. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/mcp/__main__.py +0 -0
  127. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/mcp/server.py +0 -0
  128. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/__init__.py +0 -0
  129. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/base.py +0 -0
  130. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/dart.py +0 -0
  131. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/html.py +0 -0
  132. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/javascript.py +0 -0
  133. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/php.py +0 -0
  134. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/python.py +0 -0
  135. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/registry.py +0 -0
  136. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/ruby.py +0 -0
  137. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/text.py +0 -0
  138. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/parsers/utils.py +0 -0
  139. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/py.typed +0 -0
  140. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/utils/__init__.py +0 -0
  141. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/utils/gitignore.py +0 -0
  142. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/utils/timing.py +0 -0
  143. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/src/mcp_vector_search/utils/version.py +0 -0
  144. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/tests/__init__.py +0 -0
  145. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/tests/conftest.py +0 -0
  146. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/tests/sample_code/ast_test_javascript.js +0 -0
  147. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/tests/sample_code/ast_test_python.py +0 -0
  148. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/tests/sample_code/ast_test_typescript.ts +0 -0
  149. {mcp_vector_search-0.8.6 → mcp_vector_search-0.9.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-vector-search
3
- Version: 0.8.6
3
+ Version: 0.9.0
4
4
  Summary: CLI-first semantic code search with MCP integration
5
5
  Project-URL: Homepage, https://github.com/bobmatnyc/mcp-vector-search
6
6
  Project-URL: Documentation, https://mcp-vector-search.readthedocs.io
@@ -1,7 +1,7 @@
1
1
  """MCP Vector Search - CLI-first semantic code search with MCP integration."""
2
2
 
3
- __version__ = "0.8.6"
4
- __build__ = "35"
3
+ __version__ = "0.9.0"
4
+ __build__ = "37"
5
5
  __author__ = "Robert Matsuoka"
6
6
  __email__ = "bobmatnyc@gmail.com"
7
7
 
@@ -330,7 +330,7 @@ async def _run_batch_indexing(
330
330
  console.print(
331
331
  f"[yellow]⚠ {failed_count} files failed to index[/yellow]"
332
332
  )
333
- error_log_path = project_root / ".mcp-vector-search" / "indexing_errors.log"
333
+ error_log_path = indexer.project_root / ".mcp-vector-search" / "indexing_errors.log"
334
334
  if error_log_path.exists():
335
335
  console.print(
336
336
  f"[dim] → See details in: {error_log_path}[/dim]"
@@ -72,48 +72,116 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
72
72
 
73
73
  # Get all chunks with metadata
74
74
  console.print("[cyan]Fetching chunks from database...[/cyan]")
75
+ chunks = await database.get_all_chunks()
75
76
 
76
- # Query all chunks (we'll use a dummy search to get all)
77
- stats = await database.get_stats()
78
-
79
- if stats.total_chunks == 0:
77
+ if len(chunks) == 0:
80
78
  console.print("[yellow]No chunks found in index. Run 'mcp-vector-search index' first.[/yellow]")
81
79
  raise typer.Exit(1)
82
80
 
81
+ console.print(f"[green]✓[/green] Retrieved {len(chunks)} chunks")
82
+
83
+ # Apply file filter if specified
84
+ if file_filter:
85
+ from fnmatch import fnmatch
86
+ chunks = [c for c in chunks if fnmatch(str(c.file_path), file_filter)]
87
+ console.print(f"[cyan]Filtered to {len(chunks)} chunks matching '{file_filter}'[/cyan]")
88
+
89
+ # Collect subprojects for monorepo support
90
+ subprojects = {}
91
+ for chunk in chunks:
92
+ if chunk.subproject_name and chunk.subproject_name not in subprojects:
93
+ subprojects[chunk.subproject_name] = {
94
+ "name": chunk.subproject_name,
95
+ "path": chunk.subproject_path,
96
+ "color": _get_subproject_color(chunk.subproject_name, len(subprojects)),
97
+ }
98
+
83
99
  # Build graph data structure
84
100
  nodes = []
85
101
  links = []
102
+ chunk_id_map = {} # Map chunk IDs to array indices
103
+
104
+ # Add subproject root nodes for monorepos
105
+ if subprojects:
106
+ console.print(f"[cyan]Detected monorepo with {len(subprojects)} subprojects[/cyan]")
107
+ for sp_name, sp_data in subprojects.items():
108
+ node = {
109
+ "id": f"subproject_{sp_name}",
110
+ "name": sp_name,
111
+ "type": "subproject",
112
+ "file_path": sp_data["path"] or "",
113
+ "start_line": 0,
114
+ "end_line": 0,
115
+ "complexity": 0,
116
+ "color": sp_data["color"],
117
+ "depth": 0,
118
+ }
119
+ nodes.append(node)
120
+
121
+ # Add chunk nodes
122
+ for chunk in chunks:
123
+ node = {
124
+ "id": chunk.chunk_id or chunk.id,
125
+ "name": chunk.function_name or chunk.class_name or f"L{chunk.start_line}",
126
+ "type": chunk.chunk_type,
127
+ "file_path": str(chunk.file_path),
128
+ "start_line": chunk.start_line,
129
+ "end_line": chunk.end_line,
130
+ "complexity": chunk.complexity_score,
131
+ "parent_id": chunk.parent_chunk_id,
132
+ "depth": chunk.chunk_depth,
133
+ }
86
134
 
87
- # We need to query the database to get actual chunk data
88
- # Since there's no "get all chunks" method, we'll work with the stats
89
- # In a real implementation, you would add a method to get all chunks
135
+ # Add subproject info for monorepos
136
+ if chunk.subproject_name:
137
+ node["subproject"] = chunk.subproject_name
138
+ node["color"] = subprojects[chunk.subproject_name]["color"]
90
139
 
91
- console.print(f"[yellow]Note: Full chunk export requires database enhancement.[/yellow]")
92
- console.print(f"[cyan]Creating placeholder graph with {stats.total_chunks} chunks...[/cyan]")
140
+ nodes.append(node)
141
+ chunk_id_map[node["id"]] = len(nodes) - 1
142
+
143
+ # Build hierarchical links from parent-child relationships
144
+ for chunk in chunks:
145
+ chunk_id = chunk.chunk_id or chunk.id
146
+
147
+ # Link to subproject root if in monorepo
148
+ if chunk.subproject_name and not chunk.parent_chunk_id:
149
+ links.append({
150
+ "source": f"subproject_{chunk.subproject_name}",
151
+ "target": chunk_id,
152
+ })
153
+
154
+ # Link to parent chunk
155
+ if chunk.parent_chunk_id and chunk.parent_chunk_id in chunk_id_map:
156
+ links.append({
157
+ "source": chunk.parent_chunk_id,
158
+ "target": chunk_id,
159
+ })
93
160
 
94
- # Create sample graph structure
161
+ # Parse inter-project dependencies for monorepos
162
+ if subprojects:
163
+ console.print("[cyan]Parsing inter-project dependencies...[/cyan]")
164
+ dep_links = _parse_project_dependencies(
165
+ project_manager.project_root,
166
+ subprojects
167
+ )
168
+ links.extend(dep_links)
169
+ if dep_links:
170
+ console.print(f"[green]✓[/green] Found {len(dep_links)} inter-project dependencies")
171
+
172
+ # Get stats
173
+ stats = await database.get_stats()
174
+
175
+ # Build final graph data
95
176
  graph_data = {
96
- "nodes": [
97
- {
98
- "id": f"chunk_{i}",
99
- "name": f"Chunk {i}",
100
- "type": "code",
101
- "file_path": "example.py",
102
- "start_line": i * 10,
103
- "end_line": (i + 1) * 10,
104
- "complexity": 1.0 + (i % 5),
105
- }
106
- for i in range(min(stats.total_chunks, 50)) # Limit to 50 for demo
107
- ],
108
- "links": [
109
- {"source": f"chunk_{i}", "target": f"chunk_{i+1}"}
110
- for i in range(min(stats.total_chunks - 1, 49))
111
- ],
177
+ "nodes": nodes,
178
+ "links": links,
112
179
  "metadata": {
113
- "total_chunks": stats.total_chunks,
180
+ "total_chunks": len(chunks),
114
181
  "total_files": stats.total_files,
115
182
  "languages": stats.languages,
116
- "export_note": "This is a placeholder. Full export requires database enhancement.",
183
+ "is_monorepo": len(subprojects) > 0,
184
+ "subprojects": list(subprojects.keys()) if subprojects else [],
117
185
  },
118
186
  }
119
187
 
@@ -129,7 +197,8 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
129
197
  Panel.fit(
130
198
  f"[green]✓[/green] Exported graph data to [cyan]{output}[/cyan]\n\n"
131
199
  f"Nodes: {len(graph_data['nodes'])}\n"
132
- f"Links: {len(graph_data['links'])}\n\n"
200
+ f"Links: {len(graph_data['links'])}\n"
201
+ f"{'Subprojects: ' + str(len(subprojects)) if subprojects else ''}\n\n"
133
202
  f"[dim]Next: Run 'mcp-vector-search visualize serve' to view[/dim]",
134
203
  title="Export Complete",
135
204
  border_style="green",
@@ -142,6 +211,69 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
142
211
  raise typer.Exit(1)
143
212
 
144
213
 
214
+ def _get_subproject_color(subproject_name: str, index: int) -> str:
215
+ """Get a consistent color for a subproject."""
216
+ # Color palette for subprojects (GitHub-style colors)
217
+ colors = [
218
+ "#238636", # Green
219
+ "#1f6feb", # Blue
220
+ "#d29922", # Yellow
221
+ "#8957e5", # Purple
222
+ "#da3633", # Red
223
+ "#bf8700", # Orange
224
+ "#1a7f37", # Dark green
225
+ "#0969da", # Dark blue
226
+ ]
227
+ return colors[index % len(colors)]
228
+
229
+
230
+ def _parse_project_dependencies(project_root: Path, subprojects: dict) -> list[dict]:
231
+ """Parse package.json files to find inter-project dependencies.
232
+
233
+ Args:
234
+ project_root: Root directory of the monorepo
235
+ subprojects: Dictionary of subproject information
236
+
237
+ Returns:
238
+ List of dependency links between subprojects
239
+ """
240
+ dependency_links = []
241
+
242
+ for sp_name, sp_data in subprojects.items():
243
+ package_json = project_root / sp_data["path"] / "package.json"
244
+
245
+ if not package_json.exists():
246
+ continue
247
+
248
+ try:
249
+ with open(package_json) as f:
250
+ package_data = json.load(f)
251
+
252
+ # Check all dependency types
253
+ all_deps = {}
254
+ for dep_type in ["dependencies", "devDependencies", "peerDependencies"]:
255
+ if dep_type in package_data:
256
+ all_deps.update(package_data[dep_type])
257
+
258
+ # Find dependencies on other subprojects
259
+ for dep_name in all_deps.keys():
260
+ # Check if this dependency is another subproject
261
+ for other_sp_name in subprojects.keys():
262
+ if other_sp_name != sp_name and dep_name == other_sp_name:
263
+ # Found inter-project dependency
264
+ dependency_links.append({
265
+ "source": f"subproject_{sp_name}",
266
+ "target": f"subproject_{other_sp_name}",
267
+ "type": "dependency",
268
+ })
269
+
270
+ except Exception as e:
271
+ logger.debug(f"Failed to parse {package_json}: {e}")
272
+ continue
273
+
274
+ return dependency_links
275
+
276
+
145
277
  @app.command()
146
278
  def serve(
147
279
  port: int = typer.Option(8080, "--port", "-p", help="Port for visualization server"),
@@ -344,6 +476,7 @@ def _create_visualization_html(html_file: Path) -> None:
344
476
  .node.function circle { fill: #d29922; }
345
477
  .node.method circle { fill: #8957e5; }
346
478
  .node.code circle { fill: #6e7681; }
479
+ .node.subproject circle { fill: #da3633; stroke-width: 3px; }
347
480
 
348
481
  .node text {
349
482
  font-size: 11px;
@@ -359,6 +492,13 @@ def _create_visualization_html(html_file: Path) -> None:
359
492
  stroke-width: 1.5px;
360
493
  }
361
494
 
495
+ .link.dependency {
496
+ stroke: #d29922;
497
+ stroke-opacity: 0.8;
498
+ stroke-width: 2px;
499
+ stroke-dasharray: 5,5;
500
+ }
501
+
362
502
  .tooltip {
363
503
  position: absolute;
364
504
  padding: 12px;
@@ -391,6 +531,9 @@ def _create_visualization_html(html_file: Path) -> None:
391
531
 
392
532
  <h3>Legend</h3>
393
533
  <div class="legend">
534
+ <div class="legend-item">
535
+ <span class="legend-color" style="background: #da3633;"></span> Subproject
536
+ </div>
394
537
  <div class="legend-item">
395
538
  <span class="legend-color" style="background: #238636;"></span> Module
396
539
  </div>
@@ -408,6 +551,11 @@ def _create_visualization_html(html_file: Path) -> None:
408
551
  </div>
409
552
  </div>
410
553
 
554
+ <div id="subprojects-legend" style="display: none;">
555
+ <h3>Subprojects</h3>
556
+ <div class="legend" id="subprojects-list"></div>
557
+ </div>
558
+
411
559
  <div class="stats" id="stats"></div>
412
560
  </div>
413
561
 
@@ -439,10 +587,17 @@ def _create_visualization_html(html_file: Path) -> None:
439
587
  allNodes = data.nodes;
440
588
  allLinks = data.links;
441
589
 
442
- // Find root nodes (nodes without parents or depth 0/1)
443
- const rootNodes = allNodes.filter(n =>
444
- !n.parent_id || n.depth === 0 || n.depth === 1 || n.type === 'module'
445
- );
590
+ // Find root nodes
591
+ let rootNodes;
592
+ if (data.metadata && data.metadata.is_monorepo) {
593
+ // In monorepos, subproject nodes are roots
594
+ rootNodes = allNodes.filter(n => n.type === 'subproject');
595
+ } else {
596
+ // Regular projects: nodes without parents or depth 0/1
597
+ rootNodes = allNodes.filter(n =>
598
+ !n.parent_id || n.depth === 0 || n.depth === 1 || n.type === 'module'
599
+ );
600
+ }
446
601
 
447
602
  // Start with only root nodes visible
448
603
  visibleNodes = new Set(rootNodes.map(n => n.id));
@@ -470,7 +625,7 @@ def _create_visualization_html(html_file: Path) -> None:
470
625
  .selectAll("line")
471
626
  .data(visibleLinks)
472
627
  .join("line")
473
- .attr("class", "link");
628
+ .attr("class", d => d.type === "dependency" ? "link dependency" : "link");
474
629
 
475
630
  const node = g.append("g")
476
631
  .selectAll("g")
@@ -484,9 +639,13 @@ def _create_visualization_html(html_file: Path) -> None:
484
639
 
485
640
  // Add circles with expand indicator
486
641
  node.append("circle")
487
- .attr("r", d => d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12)
642
+ .attr("r", d => {
643
+ if (d.type === 'subproject') return 20;
644
+ return d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
645
+ })
488
646
  .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
489
- .attr("stroke-width", d => hasChildren(d) ? 2 : 0);
647
+ .attr("stroke-width", d => hasChildren(d) ? 2 : 0)
648
+ .style("fill", d => d.color || null); // Use custom color if available
490
649
 
491
650
  // Add expand/collapse indicator
492
651
  node.filter(d => hasChildren(d))
@@ -620,7 +779,27 @@ def _create_visualization_html(html_file: Path) -> None:
620
779
  <div>Nodes: ${data.nodes.length}</div>
621
780
  <div>Links: ${data.links.length}</div>
622
781
  ${data.metadata ? `<div>Files: ${data.metadata.total_files || 'N/A'}</div>` : ''}
782
+ ${data.metadata && data.metadata.is_monorepo ? `<div>Monorepo: ${data.metadata.subprojects.length} subprojects</div>` : ''}
623
783
  `);
784
+
785
+ // Show subproject legend if monorepo
786
+ if (data.metadata && data.metadata.is_monorepo && data.metadata.subprojects.length > 0) {
787
+ const subprojectsLegend = d3.select("#subprojects-legend");
788
+ const subprojectsList = d3.select("#subprojects-list");
789
+
790
+ subprojectsLegend.style("display", "block");
791
+
792
+ // Get subproject nodes with colors
793
+ const subprojectNodes = allNodes.filter(n => n.type === 'subproject');
794
+
795
+ subprojectsList.html(
796
+ subprojectNodes.map(sp =>
797
+ `<div class="legend-item">
798
+ <span class="legend-color" style="background: ${sp.color};"></span> ${sp.name}
799
+ </div>`
800
+ ).join('')
801
+ );
802
+ }
624
803
  }
625
804
 
626
805
  // Auto-load graph data on page load
@@ -98,6 +98,15 @@ class VectorDatabase(ABC):
98
98
  """Reset the database (delete all data)."""
99
99
  ...
100
100
 
101
+ @abstractmethod
102
+ async def get_all_chunks(self) -> list[CodeChunk]:
103
+ """Get all chunks from the database.
104
+
105
+ Returns:
106
+ List of all code chunks with metadata
107
+ """
108
+ ...
109
+
101
110
  @abstractmethod
102
111
  async def health_check(self) -> bool:
103
112
  """Check database health and integrity.
@@ -467,6 +476,59 @@ class ChromaVectorDatabase(VectorDatabase):
467
476
  logger.error(f"Failed to reset database: {e}")
468
477
  raise DatabaseError(f"Failed to reset database: {e}") from e
469
478
 
479
+ async def get_all_chunks(self) -> list[CodeChunk]:
480
+ """Get all chunks from the database.
481
+
482
+ Returns:
483
+ List of all code chunks with metadata
484
+ """
485
+ if not self._collection:
486
+ raise DatabaseNotInitializedError("Database not initialized")
487
+
488
+ try:
489
+ # Get all documents from collection
490
+ results = self._collection.get(
491
+ include=["metadatas", "documents"]
492
+ )
493
+
494
+ chunks = []
495
+ if results and results.get("ids"):
496
+ for i, chunk_id in enumerate(results["ids"]):
497
+ metadata = results["metadatas"][i]
498
+ content = results["documents"][i]
499
+
500
+ chunk = CodeChunk(
501
+ content=content,
502
+ file_path=Path(metadata["file_path"]),
503
+ start_line=metadata["start_line"],
504
+ end_line=metadata["end_line"],
505
+ language=metadata["language"],
506
+ chunk_type=metadata.get("chunk_type", "code"),
507
+ function_name=metadata.get("function_name"),
508
+ class_name=metadata.get("class_name"),
509
+ docstring=metadata.get("docstring"),
510
+ imports=metadata.get("imports", []),
511
+ complexity_score=metadata.get("complexity_score", 0.0),
512
+ chunk_id=metadata.get("chunk_id"),
513
+ parent_chunk_id=metadata.get("parent_chunk_id"),
514
+ child_chunk_ids=metadata.get("child_chunk_ids", []),
515
+ chunk_depth=metadata.get("chunk_depth", 0),
516
+ decorators=metadata.get("decorators", []),
517
+ parameters=metadata.get("parameters", []),
518
+ return_type=metadata.get("return_type"),
519
+ type_annotations=metadata.get("type_annotations", {}),
520
+ subproject_name=metadata.get("subproject_name"),
521
+ subproject_path=metadata.get("subproject_path"),
522
+ )
523
+ chunks.append(chunk)
524
+
525
+ logger.debug(f"Retrieved {len(chunks)} chunks from database")
526
+ return chunks
527
+
528
+ except Exception as e:
529
+ logger.error(f"Failed to get all chunks: {e}")
530
+ raise DatabaseError(f"Failed to get all chunks: {e}") from e
531
+
470
532
  def _create_searchable_text(self, chunk: CodeChunk) -> str:
471
533
  """Create optimized searchable text from code chunk."""
472
534
  parts = [chunk.content]
@@ -914,6 +976,57 @@ class PooledChromaVectorDatabase(VectorDatabase):
914
976
  logger.error(f"Failed to reset database: {e}")
915
977
  raise DatabaseError(f"Failed to reset database: {e}") from e
916
978
 
979
+ async def get_all_chunks(self) -> list[CodeChunk]:
980
+ """Get all chunks from the database using pooled connection.
981
+
982
+ Returns:
983
+ List of all code chunks with metadata
984
+ """
985
+ try:
986
+ async with self._pool.get_connection() as conn:
987
+ # Get all documents from collection
988
+ results = conn.collection.get(
989
+ include=["metadatas", "documents"]
990
+ )
991
+
992
+ chunks = []
993
+ if results and results.get("ids"):
994
+ for i, chunk_id in enumerate(results["ids"]):
995
+ metadata = results["metadatas"][i]
996
+ content = results["documents"][i]
997
+
998
+ chunk = CodeChunk(
999
+ content=content,
1000
+ file_path=Path(metadata["file_path"]),
1001
+ start_line=metadata["start_line"],
1002
+ end_line=metadata["end_line"],
1003
+ language=metadata["language"],
1004
+ chunk_type=metadata.get("chunk_type", "code"),
1005
+ function_name=metadata.get("function_name"),
1006
+ class_name=metadata.get("class_name"),
1007
+ docstring=metadata.get("docstring"),
1008
+ imports=metadata.get("imports", []),
1009
+ complexity_score=metadata.get("complexity_score", 0.0),
1010
+ chunk_id=metadata.get("chunk_id"),
1011
+ parent_chunk_id=metadata.get("parent_chunk_id"),
1012
+ child_chunk_ids=metadata.get("child_chunk_ids", []),
1013
+ chunk_depth=metadata.get("chunk_depth", 0),
1014
+ decorators=metadata.get("decorators", []),
1015
+ parameters=metadata.get("parameters", []),
1016
+ return_type=metadata.get("return_type"),
1017
+ type_annotations=metadata.get("type_annotations", {}),
1018
+ subproject_name=metadata.get("subproject_name"),
1019
+ subproject_path=metadata.get("subproject_path"),
1020
+ )
1021
+ chunks.append(chunk)
1022
+
1023
+ logger.debug(f"Retrieved {len(chunks)} chunks from database")
1024
+ return chunks
1025
+
1026
+ except Exception as e:
1027
+ logger.error(f"Failed to get all chunks: {e}")
1028
+ raise DatabaseError(f"Failed to get all chunks: {e}") from e
1029
+
917
1030
  def _build_where_clause(self, filters: dict[str, Any]) -> dict[str, Any] | None:
918
1031
  """Build ChromaDB where clause from filters."""
919
1032
  if not filters:
@@ -13,6 +13,7 @@ from .. import __version__
13
13
  from ..config.defaults import DEFAULT_IGNORE_PATTERNS
14
14
  from ..parsers.registry import get_parser_registry
15
15
  from ..utils.gitignore import create_gitignore_parser
16
+ from ..utils.monorepo import MonorepoDetector
16
17
  from .database import VectorDatabase
17
18
  from .exceptions import ParsingError
18
19
  from .models import CodeChunk
@@ -72,6 +73,14 @@ class SemanticIndexer:
72
73
  logger.warning(f"Failed to load gitignore patterns: {e}")
73
74
  self.gitignore_parser = None
74
75
 
76
+ # Initialize monorepo detector
77
+ self.monorepo_detector = MonorepoDetector(project_root)
78
+ if self.monorepo_detector.is_monorepo():
79
+ subprojects = self.monorepo_detector.detect_subprojects()
80
+ logger.info(f"Detected monorepo with {len(subprojects)} subprojects")
81
+ for sp in subprojects:
82
+ logger.debug(f" - {sp.name} ({sp.relative_path})")
83
+
75
84
  async def index_project(
76
85
  self,
77
86
  force_reindex: bool = False,
@@ -519,7 +528,7 @@ class SemanticIndexer:
519
528
  file_path: Path to the file to parse
520
529
 
521
530
  Returns:
522
- List of code chunks
531
+ List of code chunks with subproject information
523
532
  """
524
533
  try:
525
534
  # Get appropriate parser
@@ -531,6 +540,13 @@ class SemanticIndexer:
531
540
  # Filter out empty chunks
532
541
  valid_chunks = [chunk for chunk in chunks if chunk.content.strip()]
533
542
 
543
+ # Assign subproject information for monorepos
544
+ subproject = self.monorepo_detector.get_subproject_for_file(file_path)
545
+ if subproject:
546
+ for chunk in valid_chunks:
547
+ chunk.subproject_name = subproject.name
548
+ chunk.subproject_path = subproject.relative_path
549
+
534
550
  return valid_chunks
535
551
 
536
552
  except Exception as e:
@@ -37,6 +37,10 @@ class CodeChunk:
37
37
  return_type: str | None = None
38
38
  type_annotations: dict[str, str] = None
39
39
 
40
+ # Enhancement 5: Monorepo support
41
+ subproject_name: str | None = None # "ewtn-plus-foundation"
42
+ subproject_path: str | None = None # Relative path from root
43
+
40
44
  def __post_init__(self) -> None:
41
45
  """Initialize default values and generate chunk ID."""
42
46
  if self.imports is None:
@@ -93,6 +97,8 @@ class CodeChunk:
93
97
  "parameters": self.parameters,
94
98
  "return_type": self.return_type,
95
99
  "type_annotations": self.type_annotations,
100
+ "subproject_name": self.subproject_name,
101
+ "subproject_path": self.subproject_path,
96
102
  }
97
103
 
98
104
  @classmethod
@@ -118,6 +124,8 @@ class CodeChunk:
118
124
  parameters=data.get("parameters", []),
119
125
  return_type=data.get("return_type"),
120
126
  type_annotations=data.get("type_annotations", {}),
127
+ subproject_name=data.get("subproject_name"),
128
+ subproject_path=data.get("subproject_path"),
121
129
  )
122
130
 
123
131