mcp-vector-search 0.9.2__tar.gz → 0.12.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 (151) hide show
  1. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/.gitignore +12 -0
  2. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/PKG-INFO +1 -1
  3. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/CHANGELOG.md +46 -0
  4. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/pyproject.toml +3 -7
  5. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/__init__.py +2 -2
  6. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/index.py +31 -0
  7. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/visualize.py +358 -20
  8. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/database.py +71 -8
  9. mcp_vector_search-0.12.0/src/mcp_vector_search/core/directory_index.py +303 -0
  10. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/indexer.py +67 -0
  11. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/models.py +58 -0
  12. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/utils/gitignore.py +14 -4
  13. mcp_vector_search-0.12.0/src/mcp_vector_search/visualization/index.html +658 -0
  14. mcp_vector_search-0.9.2/.changesets/20251009-204754-feat-add-comprehensive-changeset-and-documentation.md +0 -29
  15. mcp_vector_search-0.9.2/.changesets/20251009-205435-fix-update-readme-version-badge-to-0-7-1.md +0 -27
  16. mcp_vector_search-0.9.2/.changesets/20251009-205439-feat-add-comprehensive-changeset-support-system.md +0 -27
  17. mcp_vector_search-0.9.2/.changesets/EXAMPLE.md +0 -272
  18. mcp_vector_search-0.9.2/.changesets/IMPLEMENTATION_SUMMARY.md +0 -317
  19. mcp_vector_search-0.9.2/.changesets/README.md +0 -172
  20. mcp_vector_search-0.9.2/.changesets/template.md +0 -27
  21. mcp_vector_search-0.9.2/.editorconfig +0 -53
  22. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/.github/workflows/ci.yml +0 -0
  23. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/.pre-commit-config.yaml +0 -0
  24. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/CLAUDE.md +0 -0
  25. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/LICENSE +0 -0
  26. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/Makefile +0 -0
  27. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/PERFORMANCE_OPTIMIZATION_SUMMARY.md +0 -0
  28. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/README.md +0 -0
  29. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/CLI_FEATURES.md +0 -0
  30. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/DEPLOY.md +0 -0
  31. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/DEVELOPMENT.md +0 -0
  32. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/FEATURES.md +0 -0
  33. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/IMPROVEMENTS_SUMMARY.md +0 -0
  34. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/MCP_FILE_WATCHING.md +0 -0
  35. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/RELEASES.md +0 -0
  36. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/STRUCTURE.md +0 -0
  37. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/VERSIONING.md +0 -0
  38. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/VERSIONING_WORKFLOW.md +0 -0
  39. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/_archive/CLAUDE_20251009_pre_mpm_init.md +0 -0
  40. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/_archive/CLAUDE_MPM_INIT_SUMMARY_20251009.md +0 -0
  41. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/_archive/MPM_INIT_EXECUTIVE_SUMMARY.md +0 -0
  42. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/analysis/SEARCH_ANALYSIS_REPORT.md +0 -0
  43. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/analysis/SEARCH_IMPROVEMENT_PLAN.md +0 -0
  44. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/architecture/REINDEXING_WORKFLOW.md +0 -0
  45. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/debugging/SEARCH_BUG_ANALYSIS.md +0 -0
  46. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/API.md +0 -0
  47. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/CONTRIBUTING.md +0 -0
  48. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/DEVELOPER.md +0 -0
  49. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/LINTING.md +0 -0
  50. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/REFACTORING_ANALYSIS.md +0 -0
  51. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/TESTING.md +0 -0
  52. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/TESTING_STRATEGY.md +0 -0
  53. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/developer/TEST_SUITE_SUMMARY.md +0 -0
  54. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/mcp-integration.md +0 -0
  55. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/optimizations/database-stats-chunked-processing.md +0 -0
  56. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/performance/CONNECTION_POOLING.md +0 -0
  57. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/performance/SEARCH_TIMING_ANALYSIS.md +0 -0
  58. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/prd/mcp_vector_search_prd_updated.md +0 -0
  59. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/reference/ENGINEER_TASK.md +0 -0
  60. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/reference/INSTALL.md +0 -0
  61. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/reference/INSTALL_COMMAND_ENHANCEMENTS.md +0 -0
  62. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/reference/MCP_SETUP.md +0 -0
  63. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/reference/PROJECT_ORGANIZATION.md +0 -0
  64. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/docs/technical/SIMILARITY_CALCULATION_FIX.md +0 -0
  65. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/examples/connection_pooling_example.py +0 -0
  66. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/examples/semi_automatic_reindexing_demo.py +0 -0
  67. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/mcp-vector-search-wrapper +0 -0
  68. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/pytest.ini +0 -0
  69. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/README.md +0 -0
  70. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/analyze_search_bottlenecks.py +0 -0
  71. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/build.sh +0 -0
  72. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/changeset.py +0 -0
  73. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/comprehensive_build.py +0 -0
  74. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/deploy-test.sh +0 -0
  75. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/dev-build.py +0 -0
  76. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/dev-setup.py +0 -0
  77. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/dev-test.sh +0 -0
  78. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/fix_linting.py +0 -0
  79. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/mcp-dev +0 -0
  80. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/monitor_search_performance.py +0 -0
  81. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/publish.sh +0 -0
  82. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/quick_search_timing.py +0 -0
  83. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/run_search_timing_tests.py +0 -0
  84. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/run_tests.py +0 -0
  85. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/search_performance_monitor.py +0 -0
  86. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/search_quality_analyzer.py +0 -0
  87. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/setup/mcp-vector-search.sh +0 -0
  88. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/setup/setup-alias.sh +0 -0
  89. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/setup-dev-mcp.sh +0 -0
  90. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/update_docs.py +0 -0
  91. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/version_manager.py +0 -0
  92. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/scripts/workflow.sh +0 -0
  93. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/__init__.py +0 -0
  94. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/__init__.py +0 -0
  95. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/auto_index.py +0 -0
  96. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/config.py +0 -0
  97. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/demo.py +0 -0
  98. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/init.py +0 -0
  99. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/install.py +0 -0
  100. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/mcp.py +0 -0
  101. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/reset.py +0 -0
  102. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/search.py +0 -0
  103. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/status.py +0 -0
  104. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/commands/watch.py +0 -0
  105. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/didyoumean.py +0 -0
  106. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/export.py +0 -0
  107. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/history.py +0 -0
  108. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/interactive.py +0 -0
  109. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/main.py +0 -0
  110. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/output.py +0 -0
  111. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/cli/suggestions.py +0 -0
  112. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/config/__init__.py +0 -0
  113. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/config/constants.py +0 -0
  114. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/config/defaults.py +0 -0
  115. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/config/settings.py +0 -0
  116. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/__init__.py +0 -0
  117. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/auto_indexer.py +0 -0
  118. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/connection_pool.py +0 -0
  119. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/embeddings.py +0 -0
  120. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/exceptions.py +0 -0
  121. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/factory.py +0 -0
  122. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/git_hooks.py +0 -0
  123. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/project.py +0 -0
  124. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/scheduler.py +0 -0
  125. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/search.py +0 -0
  126. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/core/watcher.py +0 -0
  127. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/mcp/__init__.py +0 -0
  128. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/mcp/__main__.py +0 -0
  129. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/mcp/server.py +0 -0
  130. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/__init__.py +0 -0
  131. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/base.py +0 -0
  132. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/dart.py +0 -0
  133. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/html.py +0 -0
  134. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/javascript.py +0 -0
  135. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/php.py +0 -0
  136. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/python.py +0 -0
  137. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/registry.py +0 -0
  138. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/ruby.py +0 -0
  139. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/text.py +0 -0
  140. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/parsers/utils.py +0 -0
  141. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/py.typed +0 -0
  142. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/utils/__init__.py +0 -0
  143. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/utils/monorepo.py +0 -0
  144. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/utils/timing.py +0 -0
  145. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/src/mcp_vector_search/utils/version.py +0 -0
  146. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/tests/__init__.py +0 -0
  147. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/tests/conftest.py +0 -0
  148. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/tests/sample_code/ast_test_javascript.js +0 -0
  149. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/tests/sample_code/ast_test_python.py +0 -0
  150. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/tests/sample_code/ast_test_typescript.ts +0 -0
  151. {mcp_vector_search-0.9.2 → mcp_vector_search-0.12.0}/uv.lock +0 -0
@@ -1,3 +1,13 @@
1
+ # All dotfiles and dot-directories (catch-all)
2
+ .*
3
+ !.gitignore
4
+ !.github/
5
+ !.pre-commit-config.yaml
6
+
7
+ # Explicitly exclude common dotfiles/directories
8
+ .claude/
9
+ .changesets/
10
+
1
11
  # MCP Vector Search specific
2
12
  .mcp-vector-search/
3
13
  *.db
@@ -179,6 +189,8 @@ test_*.py
179
189
  *_test.py
180
190
  debug_*.py
181
191
  test_js_project/
192
+ test-screenshots/
193
+ chunk-graph.json
182
194
 
183
195
  # UV specific
184
196
  .uv/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-vector-search
3
- Version: 0.9.2
3
+ Version: 0.12.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
@@ -56,6 +56,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
56
56
  - Created parser utilities module to reduce code duplication (potential -800 to -1000 LOC)
57
57
  - Proper LRU cache implementation with statistics and configurable size
58
58
 
59
+ ## [0.12.0] - 2025-10-25
60
+
61
+ ### Added
62
+ - **Hierarchical Graph Visualization**: Complete directory, file, and chunk-based visualization system
63
+ - Interactive D3.js force-directed graph with expand/collapse functionality
64
+ - Directory nodes expand to show all files and subdirectories
65
+ - File nodes expand to show individual code chunks with metadata
66
+ - Visual hierarchy with optimized 35px folder/file icons
67
+ - Cache-busting URL parameters and meta tags for fresh data loading
68
+
69
+ ### Enhanced
70
+ - **Graph Link Visibility**: Improved visual clarity of relationships
71
+ - Enhanced link colors with better opacity (0.4 for directories, 0.3 for files)
72
+ - Dynamic link distances based on node types (150px directories, 80px files/chunks)
73
+ - Optimized collision detection with dynamic radius calculations
74
+ - Better auto-spacing with enhanced force simulation parameters
75
+
76
+ - **Expand/Collapse Functionality**: Robust node interaction system
77
+ - Working directory expansion showing all child files and subdirectories
78
+ - File expansion revealing all contained code chunks
79
+ - Preserves original link structure before D3 modifications
80
+ - Proper state management for expanded/collapsed nodes
81
+ - Visual feedback with node color changes
82
+
83
+ ### Fixed
84
+ - **Parent Directory Linking**: Corrected absolute to relative path conversion
85
+ - Fixed lookup of parent directory IDs during graph generation
86
+ - Proper handling of nested directory structures
87
+ - Consistent path normalization across all node types
88
+
89
+ - **Stale Data Prevention**: Enhanced data freshness mechanisms
90
+ - Added `Cache-Control: no-cache, no-store, must-revalidate` meta tags
91
+ - Implemented timestamp-based cache-busting for JSON files
92
+ - Ensured visualization directory contains latest graph data
93
+ - Prevents browser caching of outdated graph structures
94
+
95
+ ### Files Modified
96
+ - `src/mcp_vector_search/visualization/index.html` - UI improvements, expand/collapse fixes, cache prevention
97
+ - `src/mcp_vector_search/cli/commands/visualize.py` - Parent directory path normalization and linking fixes
98
+
99
+ ### Technical Details
100
+ - Zero new dependencies (uses existing D3.js v7)
101
+ - Enhanced force simulation with configurable parameters
102
+ - Improved node interaction state management
103
+ - Better visual design with optimized spacing and colors
104
+
59
105
  ## [0.5.0] - 2025-10-02
60
106
 
61
107
  ### Added
@@ -47,6 +47,8 @@ dev = [
47
47
  "pytest-benchmark>=4.0.0",
48
48
  "pytest-cov>=4.1.0",
49
49
  "pytest-mock>=3.11.0",
50
+ "pytest-xdist>=3.3.0",
51
+ "pytest-watch>=4.2.0",
50
52
  "hypothesis>=6.88.0",
51
53
  "black>=23.0.0",
52
54
  "ruff>=0.1.0",
@@ -80,13 +82,7 @@ path = "src/mcp_vector_search/__init__.py"
80
82
  packages = ["src/mcp_vector_search"]
81
83
 
82
84
  # UV-specific configuration
83
- [tool.uv]
84
- dev-dependencies = [
85
- "pytest-xdist>=3.3.0",
86
- "pytest-watch>=4.2.0",
87
- ]
88
-
89
- # UV sources configuration removed - using standard dependencies
85
+ # Note: dev-dependencies moved to [dependency-groups] section above
90
86
 
91
87
  # Development tools configuration
92
88
  [tool.black]
@@ -1,7 +1,7 @@
1
1
  """MCP Vector Search - CLI-first semantic code search with MCP integration."""
2
2
 
3
- __version__ = "0.9.2"
4
- __build__ = "39"
3
+ __version__ = "0.12.0"
4
+ __build__ = "50"
5
5
  __author__ = "Robert Matsuoka"
6
6
  __email__ = "bobmatnyc@gmail.com"
7
7
 
@@ -67,6 +67,13 @@ def main(
67
67
  max=128,
68
68
  rich_help_panel="⚡ Performance",
69
69
  ),
70
+ debug: bool = typer.Option(
71
+ False,
72
+ "--debug",
73
+ "-d",
74
+ help="Enable debug output (shows hierarchy building details)",
75
+ rich_help_panel="🔍 Debugging",
76
+ ),
70
77
  ) -> None:
71
78
  """📑 Index your codebase for semantic search.
72
79
 
@@ -114,6 +121,7 @@ def main(
114
121
  force_reindex=force,
115
122
  batch_size=batch_size,
116
123
  show_progress=True,
124
+ debug=debug,
117
125
  )
118
126
  )
119
127
 
@@ -134,6 +142,7 @@ async def run_indexing(
134
142
  force_reindex: bool = False,
135
143
  batch_size: int = 32,
136
144
  show_progress: bool = True,
145
+ debug: bool = False,
137
146
  ) -> None:
138
147
  """Run the indexing process."""
139
148
  # Load project configuration
@@ -179,6 +188,7 @@ async def run_indexing(
179
188
  database=database,
180
189
  project_root=project_root,
181
190
  file_extensions=file_extensions,
191
+ debug=debug,
182
192
  )
183
193
 
184
194
  try:
@@ -324,6 +334,27 @@ async def _run_batch_indexing(
324
334
  )
325
335
  )
326
336
 
337
+ # Rebuild directory index after indexing completes
338
+ try:
339
+ import os
340
+ chunk_stats = {}
341
+ for file_path in files_to_index:
342
+ try:
343
+ mtime = os.path.getmtime(file_path)
344
+ chunk_stats[str(file_path)] = {
345
+ 'modified': mtime,
346
+ 'chunks': 1, # Placeholder - real counts are in database
347
+ }
348
+ except OSError:
349
+ pass
350
+
351
+ indexer.directory_index.rebuild_from_files(
352
+ files_to_index, indexer.project_root, chunk_stats=chunk_stats
353
+ )
354
+ indexer.directory_index.save()
355
+ except Exception as e:
356
+ logger.error(f"Failed to update directory index: {e}")
357
+
327
358
  # Final progress summary
328
359
  console.print()
329
360
  if failed_count > 0:
@@ -100,6 +100,8 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
100
100
  nodes = []
101
101
  links = []
102
102
  chunk_id_map = {} # Map chunk IDs to array indices
103
+ file_nodes = {} # Track file nodes by path
104
+ dir_nodes = {} # Track directory nodes by path
103
105
 
104
106
  # Add subproject root nodes for monorepos
105
107
  if subprojects:
@@ -118,6 +120,80 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
118
120
  }
119
121
  nodes.append(node)
120
122
 
123
+ # Load directory index for enhanced directory metadata
124
+ console.print("[cyan]Loading directory index...[/cyan]")
125
+ from ...core.directory_index import DirectoryIndex
126
+ dir_index_path = project_manager.project_root / ".mcp-vector-search" / "directory_index.json"
127
+ dir_index = DirectoryIndex(dir_index_path)
128
+ dir_index.load()
129
+
130
+ # Create directory nodes from directory index
131
+ console.print(f"[green]✓[/green] Loaded {len(dir_index.directories)} directories")
132
+ for dir_path_str, directory in dir_index.directories.items():
133
+ dir_id = f"dir_{hash(dir_path_str) & 0xffffffff:08x}"
134
+ dir_nodes[dir_path_str] = {
135
+ "id": dir_id,
136
+ "name": directory.name,
137
+ "type": "directory",
138
+ "file_path": dir_path_str,
139
+ "start_line": 0,
140
+ "end_line": 0,
141
+ "complexity": 0,
142
+ "depth": directory.depth,
143
+ "dir_path": dir_path_str,
144
+ "file_count": directory.file_count,
145
+ "subdirectory_count": directory.subdirectory_count,
146
+ "total_chunks": directory.total_chunks,
147
+ "languages": directory.languages or {},
148
+ "is_package": directory.is_package,
149
+ "last_modified": directory.last_modified,
150
+ }
151
+
152
+ # Create file nodes from chunks
153
+ for chunk in chunks:
154
+ file_path_str = str(chunk.file_path)
155
+ file_path = Path(file_path_str)
156
+
157
+ # Create file node with parent directory reference
158
+ if file_path_str not in file_nodes:
159
+ file_id = f"file_{hash(file_path_str) & 0xffffffff:08x}"
160
+
161
+ # Convert absolute path to relative path for parent directory lookup
162
+ try:
163
+ relative_file_path = file_path.relative_to(project_manager.project_root)
164
+ parent_dir = relative_file_path.parent
165
+ # Use relative path for parent directory (matches directory_index)
166
+ parent_dir_str = str(parent_dir) if parent_dir != Path(".") else None
167
+ except ValueError:
168
+ # File is outside project root
169
+ parent_dir_str = None
170
+
171
+ # Look up parent directory ID from dir_nodes (must match exactly)
172
+ parent_dir_id = None
173
+ if parent_dir_str and parent_dir_str in dir_nodes:
174
+ parent_dir_id = dir_nodes[parent_dir_str]["id"]
175
+
176
+ file_nodes[file_path_str] = {
177
+ "id": file_id,
178
+ "name": file_path.name,
179
+ "type": "file",
180
+ "file_path": file_path_str,
181
+ "start_line": 0,
182
+ "end_line": 0,
183
+ "complexity": 0,
184
+ "depth": len(file_path.parts) - 1,
185
+ "parent_dir_id": parent_dir_id,
186
+ "parent_dir_path": parent_dir_str,
187
+ }
188
+
189
+ # Add directory nodes to graph
190
+ for dir_node in dir_nodes.values():
191
+ nodes.append(dir_node)
192
+
193
+ # Add file nodes to graph
194
+ for file_node in file_nodes.values():
195
+ nodes.append(file_node)
196
+
121
197
  # Add chunk nodes
122
198
  for chunk in chunks:
123
199
  node = {
@@ -130,6 +206,9 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
130
206
  "complexity": chunk.complexity_score,
131
207
  "parent_id": chunk.parent_chunk_id,
132
208
  "depth": chunk.chunk_depth,
209
+ "content": chunk.content, # Add content for code viewer
210
+ "docstring": chunk.docstring,
211
+ "language": chunk.language,
133
212
  }
134
213
 
135
214
  # Add subproject info for monorepos
@@ -140,9 +219,52 @@ async def _export_chunks(output: Path, file_filter: str | None) -> None:
140
219
  nodes.append(node)
141
220
  chunk_id_map[node["id"]] = len(nodes) - 1
142
221
 
222
+ # Link directories to their parent directories (hierarchical structure)
223
+ for dir_path_str, dir_info in dir_index.directories.items():
224
+ if dir_info.parent_path:
225
+ parent_path_str = str(dir_info.parent_path)
226
+ if parent_path_str in dir_nodes:
227
+ parent_dir_id = f"dir_{hash(parent_path_str) & 0xffffffff:08x}"
228
+ child_dir_id = f"dir_{hash(dir_path_str) & 0xffffffff:08x}"
229
+ links.append({
230
+ "source": parent_dir_id,
231
+ "target": child_dir_id,
232
+ "type": "dir_hierarchy",
233
+ })
234
+
235
+ # Link directories to subprojects in monorepos (simple flat structure)
236
+ if subprojects:
237
+ for dir_path_str, dir_node in dir_nodes.items():
238
+ for sp_name, sp_data in subprojects.items():
239
+ if dir_path_str.startswith(sp_data.get("path", "")):
240
+ links.append({
241
+ "source": f"subproject_{sp_name}",
242
+ "target": dir_node["id"],
243
+ "type": "dir_containment",
244
+ })
245
+ break
246
+
247
+ # Link files to their parent directories
248
+ for file_path_str, file_node in file_nodes.items():
249
+ if file_node.get("parent_dir_id"):
250
+ links.append({
251
+ "source": file_node["parent_dir_id"],
252
+ "target": file_node["id"],
253
+ "type": "dir_containment",
254
+ })
255
+
143
256
  # Build hierarchical links from parent-child relationships
144
257
  for chunk in chunks:
145
258
  chunk_id = chunk.chunk_id or chunk.id
259
+ file_path = str(chunk.file_path)
260
+
261
+ # Link chunk to its file node if it has no parent (top-level chunks)
262
+ if not chunk.parent_chunk_id and file_path in file_nodes:
263
+ links.append({
264
+ "source": file_nodes[file_path]["id"],
265
+ "target": chunk_id,
266
+ "type": "file_containment",
267
+ })
146
268
 
147
269
  # Link to subproject root if in monorepo
148
270
  if chunk.subproject_name and not chunk.parent_chunk_id:
@@ -476,8 +598,30 @@ def _create_visualization_html(html_file: Path) -> None:
476
598
  .node.function circle { fill: #d29922; }
477
599
  .node.method circle { fill: #8957e5; }
478
600
  .node.code circle { fill: #6e7681; }
601
+ .node.file circle {
602
+ fill: none;
603
+ stroke: #58a6ff;
604
+ stroke-width: 2px;
605
+ stroke-dasharray: 5,3;
606
+ opacity: 0.6;
607
+ }
608
+ .node.directory circle {
609
+ fill: none;
610
+ stroke: #79c0ff;
611
+ stroke-width: 2px;
612
+ stroke-dasharray: 3,3;
613
+ opacity: 0.5;
614
+ }
479
615
  .node.subproject circle { fill: #da3633; stroke-width: 3px; }
480
616
 
617
+ /* Non-code document nodes - squares */
618
+ .node.docstring rect { fill: #8b949e; }
619
+ .node.comment rect { fill: #6e7681; }
620
+ .node rect {
621
+ stroke: #c9d1d9;
622
+ stroke-width: 1.5px;
623
+ }
624
+
481
625
  .node text {
482
626
  font-size: 11px;
483
627
  fill: #c9d1d9;
@@ -519,6 +663,79 @@ def _create_visualization_html(html_file: Path) -> None:
519
663
  font-size: 12px;
520
664
  color: #8b949e;
521
665
  }
666
+
667
+ #code-viewer {
668
+ position: absolute;
669
+ top: 20px;
670
+ right: 20px;
671
+ width: 500px;
672
+ max-height: 80vh;
673
+ background: rgba(13, 17, 23, 0.95);
674
+ border: 1px solid #30363d;
675
+ border-radius: 6px;
676
+ padding: 16px;
677
+ overflow-y: auto;
678
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
679
+ display: none;
680
+ }
681
+
682
+ #code-viewer.visible {
683
+ display: block;
684
+ }
685
+
686
+ #code-viewer .header {
687
+ margin-bottom: 12px;
688
+ padding-bottom: 12px;
689
+ border-bottom: 1px solid #30363d;
690
+ }
691
+
692
+ #code-viewer .title {
693
+ font-size: 14px;
694
+ font-weight: bold;
695
+ color: #58a6ff;
696
+ margin-bottom: 4px;
697
+ }
698
+
699
+ #code-viewer .meta {
700
+ font-size: 11px;
701
+ color: #8b949e;
702
+ }
703
+
704
+ #code-viewer .close-btn {
705
+ float: right;
706
+ cursor: pointer;
707
+ color: #8b949e;
708
+ font-size: 20px;
709
+ line-height: 1;
710
+ margin-top: -4px;
711
+ }
712
+
713
+ #code-viewer .close-btn:hover {
714
+ color: #c9d1d9;
715
+ }
716
+
717
+ #code-viewer pre {
718
+ margin: 0;
719
+ padding: 12px;
720
+ background: #0d1117;
721
+ border: 1px solid #30363d;
722
+ border-radius: 6px;
723
+ overflow-x: auto;
724
+ font-size: 11px;
725
+ line-height: 1.5;
726
+ }
727
+
728
+ #code-viewer code {
729
+ color: #c9d1d9;
730
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
731
+ }
732
+
733
+ .node.highlighted circle,
734
+ .node.highlighted rect {
735
+ stroke: #f0e68c;
736
+ stroke-width: 3px;
737
+ filter: drop-shadow(0 0 8px #f0e68c);
738
+ }
522
739
  </style>
523
740
  </head>
524
741
  <body>
@@ -534,6 +751,12 @@ def _create_visualization_html(html_file: Path) -> None:
534
751
  <div class="legend-item">
535
752
  <span class="legend-color" style="background: #da3633;"></span> Subproject
536
753
  </div>
754
+ <div class="legend-item">
755
+ <span class="legend-color" style="border: 2px dashed #79c0ff; border-radius: 50%; background: transparent;"></span> Directory
756
+ </div>
757
+ <div class="legend-item">
758
+ <span class="legend-color" style="border: 2px dashed #58a6ff; border-radius: 50%; background: transparent;"></span> File
759
+ </div>
537
760
  <div class="legend-item">
538
761
  <span class="legend-color" style="background: #238636;"></span> Module
539
762
  </div>
@@ -549,6 +772,12 @@ def _create_visualization_html(html_file: Path) -> None:
549
772
  <div class="legend-item">
550
773
  <span class="legend-color" style="background: #6e7681;"></span> Code
551
774
  </div>
775
+ <div class="legend-item">
776
+ <span class="legend-color" style="background: #8b949e; border-radius: 2px;"></span> Docstring ▢
777
+ </div>
778
+ <div class="legend-item">
779
+ <span class="legend-color" style="background: #6e7681; border-radius: 2px;"></span> Comment ▢
780
+ </div>
552
781
  </div>
553
782
 
554
783
  <div id="subprojects-legend" style="display: none;">
@@ -562,6 +791,15 @@ def _create_visualization_html(html_file: Path) -> None:
562
791
  <svg id="graph"></svg>
563
792
  <div id="tooltip" class="tooltip"></div>
564
793
 
794
+ <div id="code-viewer">
795
+ <div class="header">
796
+ <span class="close-btn" onclick="closeCodeViewer()">×</span>
797
+ <div class="title" id="viewer-title"></div>
798
+ <div class="meta" id="viewer-meta"></div>
799
+ </div>
800
+ <pre><code id="viewer-code"></code></pre>
801
+ </div>
802
+
565
803
  <script>
566
804
  const width = window.innerWidth;
567
805
  const height = window.innerHeight;
@@ -580,6 +818,7 @@ def _create_visualization_html(html_file: Path) -> None:
580
818
  let allLinks = [];
581
819
  let visibleNodes = new Set();
582
820
  let collapsedNodes = new Set();
821
+ let highlightedNode = null;
583
822
 
584
823
  function visualizeGraph(data) {
585
824
  g.selectAll("*").remove();
@@ -587,19 +826,37 @@ def _create_visualization_html(html_file: Path) -> None:
587
826
  allNodes = data.nodes;
588
827
  allLinks = data.links;
589
828
 
590
- // Find root nodes
829
+ // Find root nodes - start with only top-level nodes
591
830
  let rootNodes;
592
831
  if (data.metadata && data.metadata.is_monorepo) {
593
832
  // In monorepos, subproject nodes are roots
594
833
  rootNodes = allNodes.filter(n => n.type === 'subproject');
595
834
  } 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
- );
835
+ // Regular projects: show root-level directories AND files
836
+ const dirNodes = allNodes.filter(n => n.type === 'directory');
837
+ const fileNodes = allNodes.filter(n => n.type === 'file');
838
+
839
+ // Find minimum depth for directories and files
840
+ const minDirDepth = dirNodes.length > 0
841
+ ? Math.min(...dirNodes.map(n => n.depth))
842
+ : Infinity;
843
+ const minFileDepth = fileNodes.length > 0
844
+ ? Math.min(...fileNodes.map(n => n.depth))
845
+ : Infinity;
846
+
847
+ // Include both root-level directories and root-level files
848
+ rootNodes = [
849
+ ...dirNodes.filter(n => n.depth === minDirDepth),
850
+ ...fileNodes.filter(n => n.depth === minFileDepth)
851
+ ];
852
+
853
+ // Fallback to all files if nothing found
854
+ if (rootNodes.length === 0) {
855
+ rootNodes = fileNodes;
856
+ }
600
857
  }
601
858
 
602
- // Start with only root nodes visible
859
+ // Start with only root nodes visible, all collapsed
603
860
  visibleNodes = new Set(rootNodes.map(n => n.id));
604
861
  collapsedNodes = new Set(rootNodes.map(n => n.id));
605
862
 
@@ -631,22 +888,59 @@ def _create_visualization_html(html_file: Path) -> None:
631
888
  .selectAll("g")
632
889
  .data(visibleNodesList)
633
890
  .join("g")
634
- .attr("class", d => `node ${d.type}`)
891
+ .attr("class", d => {
892
+ let classes = `node ${d.type}`;
893
+ if (highlightedNode && d.id === highlightedNode.id) {
894
+ classes += ' highlighted';
895
+ }
896
+ return classes;
897
+ })
635
898
  .call(drag(simulation))
636
- .on("click", toggleNode)
899
+ .on("click", handleNodeClick)
637
900
  .on("mouseover", showTooltip)
638
901
  .on("mouseout", hideTooltip);
639
902
 
640
- // Add circles with expand indicator
641
- node.append("circle")
903
+ // Add shapes based on node type (circles for code, squares for docs)
904
+ const isDocNode = d => ['docstring', 'comment'].includes(d.type);
905
+
906
+ // Add circles for code nodes
907
+ node.filter(d => !isDocNode(d))
908
+ .append("circle")
642
909
  .attr("r", d => {
643
910
  if (d.type === 'subproject') return 20;
911
+ if (d.type === 'directory') return 40; // Largest for directory containers
912
+ if (d.type === 'file') return 30; // Larger transparent circle for files
644
913
  return d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
645
914
  })
646
915
  .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
647
916
  .attr("stroke-width", d => hasChildren(d) ? 2 : 0)
648
917
  .style("fill", d => d.color || null); // Use custom color if available
649
918
 
919
+ // Add rectangles for document nodes
920
+ node.filter(d => isDocNode(d))
921
+ .append("rect")
922
+ .attr("width", d => {
923
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
924
+ return size * 2;
925
+ })
926
+ .attr("height", d => {
927
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
928
+ return size * 2;
929
+ })
930
+ .attr("x", d => {
931
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
932
+ return -size;
933
+ })
934
+ .attr("y", d => {
935
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
936
+ return -size;
937
+ })
938
+ .attr("rx", 2) // Rounded corners
939
+ .attr("ry", 2)
940
+ .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
941
+ .attr("stroke-width", d => hasChildren(d) ? 2 : 0)
942
+ .style("fill", d => d.color || null);
943
+
650
944
  // Add expand/collapse indicator
651
945
  node.filter(d => hasChildren(d))
652
946
  .append("text")
@@ -681,20 +975,21 @@ def _create_visualization_html(html_file: Path) -> None:
681
975
  return allLinks.some(l => (l.source.id || l.source) === node.id);
682
976
  }
683
977
 
684
- function toggleNode(event, d) {
978
+ function handleNodeClick(event, d) {
685
979
  event.stopPropagation();
686
980
 
687
- if (!hasChildren(d)) return;
688
-
689
- if (collapsedNodes.has(d.id)) {
690
- // Expand: show children
691
- expandNode(d);
981
+ // If node has children, toggle expansion
982
+ if (hasChildren(d)) {
983
+ if (collapsedNodes.has(d.id)) {
984
+ expandNode(d);
985
+ } else {
986
+ collapseNode(d);
987
+ }
988
+ renderGraph();
692
989
  } else {
693
- // Collapse: hide children
694
- collapseNode(d);
990
+ // Leaf node - show code viewer
991
+ showCodeViewer(d);
695
992
  }
696
-
697
- renderGraph();
698
993
  }
699
994
 
700
995
  function expandNode(node) {
@@ -802,6 +1097,49 @@ def _create_visualization_html(html_file: Path) -> None:
802
1097
  }
803
1098
  }
804
1099
 
1100
+ function showCodeViewer(node) {
1101
+ // Highlight the node
1102
+ highlightedNode = node;
1103
+ renderGraph();
1104
+
1105
+ // Populate code viewer
1106
+ const viewer = document.getElementById('code-viewer');
1107
+ const title = document.getElementById('viewer-title');
1108
+ const meta = document.getElementById('viewer-meta');
1109
+ const code = document.getElementById('viewer-code');
1110
+
1111
+ title.textContent = node.name;
1112
+
1113
+ let metaText = `${node.type} • ${node.file_path}`;
1114
+ if (node.start_line) {
1115
+ metaText += ` • Lines ${node.start_line}-${node.end_line}`;
1116
+ }
1117
+ if (node.language) {
1118
+ metaText += ` • ${node.language}`;
1119
+ }
1120
+ meta.textContent = metaText;
1121
+
1122
+ // Show content if available
1123
+ if (node.content) {
1124
+ code.textContent = node.content;
1125
+ } else if (node.docstring) {
1126
+ code.textContent = `// Docstring:\n${node.docstring}`;
1127
+ } else {
1128
+ code.textContent = '// No content available';
1129
+ }
1130
+
1131
+ viewer.classList.add('visible');
1132
+ }
1133
+
1134
+ function closeCodeViewer() {
1135
+ const viewer = document.getElementById('code-viewer');
1136
+ viewer.classList.remove('visible');
1137
+
1138
+ // Remove highlight
1139
+ highlightedNode = null;
1140
+ renderGraph();
1141
+ }
1142
+
805
1143
  // Auto-load graph data on page load
806
1144
  window.addEventListener('DOMContentLoaded', () => {
807
1145
  const loadingEl = document.getElementById('loading');