sylvan 1.0.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.
Files changed (239) hide show
  1. sylvan-1.0.0/PKG-INFO +105 -0
  2. sylvan-1.0.0/README.md +75 -0
  3. sylvan-1.0.0/pyproject.toml +70 -0
  4. sylvan-1.0.0/src/sylvan/__init__.py +8 -0
  5. sylvan-1.0.0/src/sylvan/__main__.py +6 -0
  6. sylvan-1.0.0/src/sylvan/analysis/__init__.py +0 -0
  7. sylvan-1.0.0/src/sylvan/analysis/impact/__init__.py +0 -0
  8. sylvan-1.0.0/src/sylvan/analysis/impact/blast_radius.py +135 -0
  9. sylvan-1.0.0/src/sylvan/analysis/impact/cross_repo.py +223 -0
  10. sylvan-1.0.0/src/sylvan/analysis/quality/__init__.py +0 -0
  11. sylvan-1.0.0/src/sylvan/analysis/quality/code_smells.py +162 -0
  12. sylvan-1.0.0/src/sylvan/analysis/quality/dead_code.py +90 -0
  13. sylvan-1.0.0/src/sylvan/analysis/quality/duplication.py +132 -0
  14. sylvan-1.0.0/src/sylvan/analysis/quality/quality_metrics.py +207 -0
  15. sylvan-1.0.0/src/sylvan/analysis/quality/security_scanner.py +156 -0
  16. sylvan-1.0.0/src/sylvan/analysis/quality/test_coverage.py +124 -0
  17. sylvan-1.0.0/src/sylvan/analysis/structure/__init__.py +0 -0
  18. sylvan-1.0.0/src/sylvan/analysis/structure/class_hierarchy.py +160 -0
  19. sylvan-1.0.0/src/sylvan/analysis/structure/reference_graph.py +209 -0
  20. sylvan-1.0.0/src/sylvan/cli.py +839 -0
  21. sylvan-1.0.0/src/sylvan/cluster/__init__.py +1 -0
  22. sylvan-1.0.0/src/sylvan/cluster/api.py +114 -0
  23. sylvan-1.0.0/src/sylvan/cluster/discovery.py +153 -0
  24. sylvan-1.0.0/src/sylvan/cluster/heartbeat.py +246 -0
  25. sylvan-1.0.0/src/sylvan/cluster/proxy.py +65 -0
  26. sylvan-1.0.0/src/sylvan/cluster/state.py +63 -0
  27. sylvan-1.0.0/src/sylvan/config.py +599 -0
  28. sylvan-1.0.0/src/sylvan/context.py +146 -0
  29. sylvan-1.0.0/src/sylvan/dashboard/__init__.py +1 -0
  30. sylvan-1.0.0/src/sylvan/dashboard/app.py +874 -0
  31. sylvan-1.0.0/src/sylvan/dashboard/server.py +73 -0
  32. sylvan-1.0.0/src/sylvan/dashboard/templates/base.html +584 -0
  33. sylvan-1.0.0/src/sylvan/dashboard/templates/blast_radius.html +85 -0
  34. sylvan-1.0.0/src/sylvan/dashboard/templates/libraries.html +41 -0
  35. sylvan-1.0.0/src/sylvan/dashboard/templates/overview.html +8 -0
  36. sylvan-1.0.0/src/sylvan/dashboard/templates/partials/blast_radius_result.html +92 -0
  37. sylvan-1.0.0/src/sylvan/dashboard/templates/partials/quality_report.html +128 -0
  38. sylvan-1.0.0/src/sylvan/dashboard/templates/partials/search_results.html +37 -0
  39. sylvan-1.0.0/src/sylvan/dashboard/templates/partials/session_stats.html +197 -0
  40. sylvan-1.0.0/src/sylvan/dashboard/templates/partials/stats.html +172 -0
  41. sylvan-1.0.0/src/sylvan/dashboard/templates/quality.html +35 -0
  42. sylvan-1.0.0/src/sylvan/dashboard/templates/search.html +43 -0
  43. sylvan-1.0.0/src/sylvan/dashboard/templates/session.html +8 -0
  44. sylvan-1.0.0/src/sylvan/database/__init__.py +0 -0
  45. sylvan-1.0.0/src/sylvan/database/backends/__init__.py +1 -0
  46. sylvan-1.0.0/src/sylvan/database/backends/base.py +318 -0
  47. sylvan-1.0.0/src/sylvan/database/backends/postgres/__init__.py +6 -0
  48. sylvan-1.0.0/src/sylvan/database/backends/postgres/backend.py +214 -0
  49. sylvan-1.0.0/src/sylvan/database/backends/postgres/dialect.py +185 -0
  50. sylvan-1.0.0/src/sylvan/database/backends/sqlite/__init__.py +6 -0
  51. sylvan-1.0.0/src/sylvan/database/backends/sqlite/backend.py +206 -0
  52. sylvan-1.0.0/src/sylvan/database/backends/sqlite/dialect.py +164 -0
  53. sylvan-1.0.0/src/sylvan/database/builder/__init__.py +30 -0
  54. sylvan-1.0.0/src/sylvan/database/builder/blueprint.py +401 -0
  55. sylvan-1.0.0/src/sylvan/database/builder/schema.py +448 -0
  56. sylvan-1.0.0/src/sylvan/database/connection.py +40 -0
  57. sylvan-1.0.0/src/sylvan/database/migrations/001_initial_schema.py +243 -0
  58. sylvan-1.0.0/src/sylvan/database/migrations/__init__.py +27 -0
  59. sylvan-1.0.0/src/sylvan/database/migrations/runner.py +234 -0
  60. sylvan-1.0.0/src/sylvan/database/orm/__init__.py +94 -0
  61. sylvan-1.0.0/src/sylvan/database/orm/exceptions.py +17 -0
  62. sylvan-1.0.0/src/sylvan/database/orm/mixins/__init__.py +0 -0
  63. sylvan-1.0.0/src/sylvan/database/orm/mixins/hooks.py +5 -0
  64. sylvan-1.0.0/src/sylvan/database/orm/mixins/soft_deletes.py +6 -0
  65. sylvan-1.0.0/src/sylvan/database/orm/model/__init__.py +0 -0
  66. sylvan-1.0.0/src/sylvan/database/orm/model/base.py +346 -0
  67. sylvan-1.0.0/src/sylvan/database/orm/model/bulk.py +305 -0
  68. sylvan-1.0.0/src/sylvan/database/orm/model/finders.py +184 -0
  69. sylvan-1.0.0/src/sylvan/database/orm/model/metaclass.py +89 -0
  70. sylvan-1.0.0/src/sylvan/database/orm/model/persistence.py +153 -0
  71. sylvan-1.0.0/src/sylvan/database/orm/models/__init__.py +34 -0
  72. sylvan-1.0.0/src/sylvan/database/orm/models/blob.py +61 -0
  73. sylvan-1.0.0/src/sylvan/database/orm/models/coding_session.py +58 -0
  74. sylvan-1.0.0/src/sylvan/database/orm/models/file_import.py +36 -0
  75. sylvan-1.0.0/src/sylvan/database/orm/models/file_record.py +63 -0
  76. sylvan-1.0.0/src/sylvan/database/orm/models/instance.py +73 -0
  77. sylvan-1.0.0/src/sylvan/database/orm/models/quality.py +35 -0
  78. sylvan-1.0.0/src/sylvan/database/orm/models/reference.py +29 -0
  79. sylvan-1.0.0/src/sylvan/database/orm/models/repo.py +66 -0
  80. sylvan-1.0.0/src/sylvan/database/orm/models/section.py +153 -0
  81. sylvan-1.0.0/src/sylvan/database/orm/models/symbol.py +224 -0
  82. sylvan-1.0.0/src/sylvan/database/orm/models/usage_stats.py +46 -0
  83. sylvan-1.0.0/src/sylvan/database/orm/models/workspace.py +35 -0
  84. sylvan-1.0.0/src/sylvan/database/orm/primitives/__init__.py +0 -0
  85. sylvan-1.0.0/src/sylvan/database/orm/primitives/fields.py +157 -0
  86. sylvan-1.0.0/src/sylvan/database/orm/primitives/relations.py +257 -0
  87. sylvan-1.0.0/src/sylvan/database/orm/primitives/scopes.py +86 -0
  88. sylvan-1.0.0/src/sylvan/database/orm/query/__init__.py +0 -0
  89. sylvan-1.0.0/src/sylvan/database/orm/query/builder.py +360 -0
  90. sylvan-1.0.0/src/sylvan/database/orm/query/eager_loading.py +174 -0
  91. sylvan-1.0.0/src/sylvan/database/orm/query/execution.py +450 -0
  92. sylvan-1.0.0/src/sylvan/database/orm/query/relations.py +150 -0
  93. sylvan-1.0.0/src/sylvan/database/orm/query/search.py +158 -0
  94. sylvan-1.0.0/src/sylvan/database/orm/query/sql.py +193 -0
  95. sylvan-1.0.0/src/sylvan/database/orm/query/where.py +329 -0
  96. sylvan-1.0.0/src/sylvan/database/orm/runtime/__init__.py +0 -0
  97. sylvan-1.0.0/src/sylvan/database/orm/runtime/connection_manager.py +28 -0
  98. sylvan-1.0.0/src/sylvan/database/orm/runtime/identity_map.py +65 -0
  99. sylvan-1.0.0/src/sylvan/database/orm/runtime/model_registry.py +30 -0
  100. sylvan-1.0.0/src/sylvan/database/orm/runtime/query_cache.py +119 -0
  101. sylvan-1.0.0/src/sylvan/database/orm/runtime/search_helpers.py +105 -0
  102. sylvan-1.0.0/src/sylvan/database/validation.py +178 -0
  103. sylvan-1.0.0/src/sylvan/database/workspace.py +149 -0
  104. sylvan-1.0.0/src/sylvan/error_codes.py +375 -0
  105. sylvan-1.0.0/src/sylvan/git/__init__.py +40 -0
  106. sylvan-1.0.0/src/sylvan/git/blame.py +122 -0
  107. sylvan-1.0.0/src/sylvan/git/dependency_files.py +180 -0
  108. sylvan-1.0.0/src/sylvan/git/diff.py +92 -0
  109. sylvan-1.0.0/src/sylvan/hooks.py +109 -0
  110. sylvan-1.0.0/src/sylvan/indexing/__init__.py +0 -0
  111. sylvan-1.0.0/src/sylvan/indexing/discovery/__init__.py +0 -0
  112. sylvan-1.0.0/src/sylvan/indexing/discovery/file_discovery.py +286 -0
  113. sylvan-1.0.0/src/sylvan/indexing/discovery/incremental.py +54 -0
  114. sylvan-1.0.0/src/sylvan/indexing/documents/__init__.py +0 -0
  115. sylvan-1.0.0/src/sylvan/indexing/documents/formats/__init__.py +0 -0
  116. sylvan-1.0.0/src/sylvan/indexing/documents/formats/asciidoc.py +98 -0
  117. sylvan-1.0.0/src/sylvan/indexing/documents/formats/html.py +200 -0
  118. sylvan-1.0.0/src/sylvan/indexing/documents/formats/json_parser.py +186 -0
  119. sylvan-1.0.0/src/sylvan/indexing/documents/formats/markdown.py +152 -0
  120. sylvan-1.0.0/src/sylvan/indexing/documents/formats/notebook.py +160 -0
  121. sylvan-1.0.0/src/sylvan/indexing/documents/formats/openapi.py +226 -0
  122. sylvan-1.0.0/src/sylvan/indexing/documents/formats/rst.py +156 -0
  123. sylvan-1.0.0/src/sylvan/indexing/documents/formats/text.py +83 -0
  124. sylvan-1.0.0/src/sylvan/indexing/documents/formats/xml_parser.py +137 -0
  125. sylvan-1.0.0/src/sylvan/indexing/documents/formats/yaml_parser.py +37 -0
  126. sylvan-1.0.0/src/sylvan/indexing/documents/parser.py +40 -0
  127. sylvan-1.0.0/src/sylvan/indexing/documents/registry.py +101 -0
  128. sylvan-1.0.0/src/sylvan/indexing/documents/section_builder.py +178 -0
  129. sylvan-1.0.0/src/sylvan/indexing/pipeline/__init__.py +0 -0
  130. sylvan-1.0.0/src/sylvan/indexing/pipeline/file_processor.py +236 -0
  131. sylvan-1.0.0/src/sylvan/indexing/pipeline/import_resolver.py +558 -0
  132. sylvan-1.0.0/src/sylvan/indexing/pipeline/orchestrator.py +286 -0
  133. sylvan-1.0.0/src/sylvan/indexing/post_processing/__init__.py +0 -0
  134. sylvan-1.0.0/src/sylvan/indexing/post_processing/background_tasks.py +92 -0
  135. sylvan-1.0.0/src/sylvan/indexing/post_processing/file_watcher.py +114 -0
  136. sylvan-1.0.0/src/sylvan/indexing/post_processing/summarizer.py +301 -0
  137. sylvan-1.0.0/src/sylvan/indexing/source_code/__init__.py +0 -0
  138. sylvan-1.0.0/src/sylvan/indexing/source_code/extractor.py +308 -0
  139. sylvan-1.0.0/src/sylvan/indexing/source_code/import_extraction.py +299 -0
  140. sylvan-1.0.0/src/sylvan/indexing/source_code/language_registry.py +63 -0
  141. sylvan-1.0.0/src/sylvan/indexing/source_code/language_specs.py +744 -0
  142. sylvan-1.0.0/src/sylvan/indexing/source_code/parse_orchestration.py +56 -0
  143. sylvan-1.0.0/src/sylvan/indexing/source_code/symbol_details.py +244 -0
  144. sylvan-1.0.0/src/sylvan/indexing/source_code/symbol_enrichment.py +267 -0
  145. sylvan-1.0.0/src/sylvan/libraries/__init__.py +5 -0
  146. sylvan-1.0.0/src/sylvan/libraries/manager.py +373 -0
  147. sylvan-1.0.0/src/sylvan/libraries/resolution/__init__.py +0 -0
  148. sylvan-1.0.0/src/sylvan/libraries/resolution/package_registry.py +185 -0
  149. sylvan-1.0.0/src/sylvan/libraries/resolution/package_resolvers.py +284 -0
  150. sylvan-1.0.0/src/sylvan/libraries/resolution/url_overrides.py +57 -0
  151. sylvan-1.0.0/src/sylvan/libraries/source_fetcher.py +293 -0
  152. sylvan-1.0.0/src/sylvan/logging.py +179 -0
  153. sylvan-1.0.0/src/sylvan/providers/__init__.py +53 -0
  154. sylvan-1.0.0/src/sylvan/providers/base.py +422 -0
  155. sylvan-1.0.0/src/sylvan/providers/builtin/__init__.py +0 -0
  156. sylvan-1.0.0/src/sylvan/providers/builtin/heuristic.py +98 -0
  157. sylvan-1.0.0/src/sylvan/providers/builtin/sentence_transformers.py +85 -0
  158. sylvan-1.0.0/src/sylvan/providers/ecosystem_context/__init__.py +2 -0
  159. sylvan-1.0.0/src/sylvan/providers/ecosystem_context/base.py +227 -0
  160. sylvan-1.0.0/src/sylvan/providers/ecosystem_context/dbt.py +161 -0
  161. sylvan-1.0.0/src/sylvan/providers/external/__init__.py +0 -0
  162. sylvan-1.0.0/src/sylvan/providers/external/claude_code.py +87 -0
  163. sylvan-1.0.0/src/sylvan/providers/external/codex.py +48 -0
  164. sylvan-1.0.0/src/sylvan/providers/external/ollama/__init__.py +0 -0
  165. sylvan-1.0.0/src/sylvan/providers/external/ollama/provider.py +125 -0
  166. sylvan-1.0.0/src/sylvan/providers/external/ollama/setup.py +122 -0
  167. sylvan-1.0.0/src/sylvan/providers/registry.py +74 -0
  168. sylvan-1.0.0/src/sylvan/py.typed +0 -0
  169. sylvan-1.0.0/src/sylvan/scaffold/__init__.py +6 -0
  170. sylvan-1.0.0/src/sylvan/scaffold/agent_config.py +230 -0
  171. sylvan-1.0.0/src/sylvan/scaffold/auto_docs.py +179 -0
  172. sylvan-1.0.0/src/sylvan/scaffold/auto_reports.py +214 -0
  173. sylvan-1.0.0/src/sylvan/scaffold/directory_structure.py +95 -0
  174. sylvan-1.0.0/src/sylvan/scaffold/generator.py +203 -0
  175. sylvan-1.0.0/src/sylvan/search/__init__.py +0 -0
  176. sylvan-1.0.0/src/sylvan/search/embeddings.py +209 -0
  177. sylvan-1.0.0/src/sylvan/security/__init__.py +0 -0
  178. sylvan-1.0.0/src/sylvan/security/filters.py +125 -0
  179. sylvan-1.0.0/src/sylvan/security/patterns.py +136 -0
  180. sylvan-1.0.0/src/sylvan/server/__init__.py +713 -0
  181. sylvan-1.0.0/src/sylvan/server/startup.py +167 -0
  182. sylvan-1.0.0/src/sylvan/server/transports.py +121 -0
  183. sylvan-1.0.0/src/sylvan/session/__init__.py +0 -0
  184. sylvan-1.0.0/src/sylvan/session/tracker.py +286 -0
  185. sylvan-1.0.0/src/sylvan/session/usage_stats.py +451 -0
  186. sylvan-1.0.0/src/sylvan/tools/__init__.py +0 -0
  187. sylvan-1.0.0/src/sylvan/tools/analysis/__init__.py +0 -0
  188. sylvan-1.0.0/src/sylvan/tools/analysis/find_importers.py +145 -0
  189. sylvan-1.0.0/src/sylvan/tools/analysis/get_blast_radius.py +67 -0
  190. sylvan-1.0.0/src/sylvan/tools/analysis/get_class_hierarchy.py +25 -0
  191. sylvan-1.0.0/src/sylvan/tools/analysis/get_dependency_graph.py +185 -0
  192. sylvan-1.0.0/src/sylvan/tools/analysis/get_git_context.py +102 -0
  193. sylvan-1.0.0/src/sylvan/tools/analysis/get_quality.py +57 -0
  194. sylvan-1.0.0/src/sylvan/tools/analysis/get_quality_report.py +202 -0
  195. sylvan-1.0.0/src/sylvan/tools/analysis/get_recent_changes.py +100 -0
  196. sylvan-1.0.0/src/sylvan/tools/analysis/get_references.py +30 -0
  197. sylvan-1.0.0/src/sylvan/tools/analysis/get_related.py +123 -0
  198. sylvan-1.0.0/src/sylvan/tools/analysis/get_symbol_diff.py +238 -0
  199. sylvan-1.0.0/src/sylvan/tools/analysis/rename_symbol.py +129 -0
  200. sylvan-1.0.0/src/sylvan/tools/analysis/search_columns.py +156 -0
  201. sylvan-1.0.0/src/sylvan/tools/browsing/__init__.py +0 -0
  202. sylvan-1.0.0/src/sylvan/tools/browsing/get_context_bundle.py +114 -0
  203. sylvan-1.0.0/src/sylvan/tools/browsing/get_file_outline.py +176 -0
  204. sylvan-1.0.0/src/sylvan/tools/browsing/get_file_tree.py +131 -0
  205. sylvan-1.0.0/src/sylvan/tools/browsing/get_repo_outline.py +73 -0
  206. sylvan-1.0.0/src/sylvan/tools/browsing/get_section.py +108 -0
  207. sylvan-1.0.0/src/sylvan/tools/browsing/get_symbol.py +125 -0
  208. sylvan-1.0.0/src/sylvan/tools/browsing/get_toc.py +100 -0
  209. sylvan-1.0.0/src/sylvan/tools/definitions/__init__.py +0 -0
  210. sylvan-1.0.0/src/sylvan/tools/definitions/analysis.py +358 -0
  211. sylvan-1.0.0/src/sylvan/tools/definitions/core.py +308 -0
  212. sylvan-1.0.0/src/sylvan/tools/definitions/support.py +384 -0
  213. sylvan-1.0.0/src/sylvan/tools/indexing/__init__.py +0 -0
  214. sylvan-1.0.0/src/sylvan/tools/indexing/index_file.py +299 -0
  215. sylvan-1.0.0/src/sylvan/tools/indexing/index_folder.py +35 -0
  216. sylvan-1.0.0/src/sylvan/tools/library/__init__.py +0 -0
  217. sylvan-1.0.0/src/sylvan/tools/library/add.py +30 -0
  218. sylvan-1.0.0/src/sylvan/tools/library/check.py +102 -0
  219. sylvan-1.0.0/src/sylvan/tools/library/compare.py +124 -0
  220. sylvan-1.0.0/src/sylvan/tools/library/list.py +18 -0
  221. sylvan-1.0.0/src/sylvan/tools/library/remove.py +21 -0
  222. sylvan-1.0.0/src/sylvan/tools/meta/__init__.py +0 -0
  223. sylvan-1.0.0/src/sylvan/tools/meta/get_logs.py +64 -0
  224. sylvan-1.0.0/src/sylvan/tools/meta/get_server_config.py +57 -0
  225. sylvan-1.0.0/src/sylvan/tools/meta/get_workflow_guide.py +296 -0
  226. sylvan-1.0.0/src/sylvan/tools/meta/list_repos.py +36 -0
  227. sylvan-1.0.0/src/sylvan/tools/meta/remove_repo.py +71 -0
  228. sylvan-1.0.0/src/sylvan/tools/meta/scaffold.py +41 -0
  229. sylvan-1.0.0/src/sylvan/tools/meta/suggest_queries.py +190 -0
  230. sylvan-1.0.0/src/sylvan/tools/search/__init__.py +0 -0
  231. sylvan-1.0.0/src/sylvan/tools/search/search_sections.py +78 -0
  232. sylvan-1.0.0/src/sylvan/tools/search/search_similar.py +96 -0
  233. sylvan-1.0.0/src/sylvan/tools/search/search_symbols.py +260 -0
  234. sylvan-1.0.0/src/sylvan/tools/search/search_text.py +127 -0
  235. sylvan-1.0.0/src/sylvan/tools/support/__init__.py +0 -0
  236. sylvan-1.0.0/src/sylvan/tools/support/response.py +380 -0
  237. sylvan-1.0.0/src/sylvan/tools/support/token_counting.py +73 -0
  238. sylvan-1.0.0/src/sylvan/tools/workspace/__init__.py +194 -0
  239. sylvan-1.0.0/src/sylvan/tools/workspace/pin_library.py +56 -0
sylvan-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.3
2
+ Name: sylvan
3
+ Version: 1.0.0
4
+ Summary: Unified code + documentation retrieval MCP server
5
+ Author: darki73
6
+ Author-email: darki73 <apple.zhivolupov@gmail.com>
7
+ Requires-Dist: mcp>=1.10.0,<2.0.0
8
+ Requires-Dist: tree-sitter-language-pack>=0.7.0,<1.0.0
9
+ Requires-Dist: sqlite-vec>=0.1.0,<1.0.0
10
+ Requires-Dist: pathspec>=0.12.0,<1.0.0
11
+ Requires-Dist: pyyaml>=6.0,<7.0
12
+ Requires-Dist: tiktoken>=0.12.0,<1.0.0
13
+ Requires-Dist: fastembed>=0.4.0,<1.0.0
14
+ Requires-Dist: ollama>=0.6.1,<1.0.0
15
+ Requires-Dist: structlog>=25.5.0,<27.0.0
16
+ Requires-Dist: claude-agent-sdk==0.1.48
17
+ Requires-Dist: aiosqlite>=0.21.0,<1.0.0
18
+ Requires-Dist: jinja2>=3.1,<4.0
19
+ Requires-Dist: uvicorn>=0.30.0,<1.0.0
20
+ Requires-Dist: starlette>=0.38.0,<1.0.0
21
+ Requires-Dist: watchfiles>=1.0.0 ; extra == 'all'
22
+ Requires-Dist: asyncpg>=0.30.0 ; extra == 'all'
23
+ Requires-Dist: asyncpg>=0.30.0 ; extra == 'postgres'
24
+ Requires-Dist: watchfiles>=1.0.0 ; extra == 'watch'
25
+ Requires-Python: >=3.12
26
+ Provides-Extra: all
27
+ Provides-Extra: postgres
28
+ Provides-Extra: watch
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Sylvan
32
+
33
+ Code intelligence platform for AI agents. Search, analyze, and navigate codebases through MCP tools — returning exactly the code your agent needs at a fraction of the token cost.
34
+
35
+ ## Why
36
+
37
+ AI agents burn tokens reading entire files when they need one function. They grep across directories to trace a dependency. They piece together call chains one file at a time. Every wasted read costs money and context window space.
38
+
39
+ Sylvan indexes your codebase into a structured database of symbols, sections, and import relationships, then exposes it through 52 MCP tools. Your agent asks for what it needs and gets exactly that — function signatures, blast radius, dependency graphs, semantic search results. Typical token savings exceed 80%.
40
+
41
+ ## Dashboard
42
+
43
+ ![Overview](docs/overview.png)
44
+
45
+ <details>
46
+ <summary>More screenshots</summary>
47
+
48
+ **Search** — find code by name, signature, or keywords with syntax-highlighted source
49
+ ![Search](docs/search.png)
50
+
51
+ **Session** — live token efficiency tracking per session and all-time
52
+ ![Session](docs/session.png)
53
+
54
+ **Quality Report** — code smells, security findings, test/doc coverage
55
+ ![Quality](docs/quality.png)
56
+
57
+ **Blast Radius** — visualize impact before changing a symbol
58
+ ![Blast Radius](docs/blast-radius.png)
59
+
60
+ **Libraries** — indexed third-party packages with symbol counts
61
+ ![Libraries](docs/libraries.png)
62
+
63
+ </details>
64
+
65
+ ## Features
66
+
67
+ - 52 MCP tools for search, browsing, analysis, and refactoring
68
+ - 34 programming languages via tree-sitter
69
+ - Hybrid search — full-text (FTS5) + vector similarity with ranked fusion
70
+ - Blast radius analysis before any refactor
71
+ - Dependency graphs, call chains, class hierarchies
72
+ - Third-party library indexing (pip, npm, cargo, go)
73
+ - Multi-repo workspaces with cross-repo analysis
74
+ - Code quality reports — smells, security, duplication, dead code
75
+ - Web dashboard with live token efficiency tracking
76
+ - Multi-instance cluster support
77
+
78
+ ## Quick start
79
+
80
+ ```bash
81
+ pip install sylvan
82
+ ```
83
+
84
+ Add to your MCP client config:
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "sylvan": {
90
+ "command": "uv",
91
+ "args": ["run", "--directory", "/path/to/sylvan", "sylvan", "serve"]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ Your agent handles the rest — index a project, search for code, navigate with precision.
98
+
99
+ ## Documentation
100
+
101
+ Full docs at [darki73.github.io/sylvan](https://darki73.github.io/sylvan/)
102
+
103
+ ## License
104
+
105
+ Non-commercial open source. Free to use, modify, and distribute with attribution. See [LICENSE](LICENSE) for details.
sylvan-1.0.0/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Sylvan
2
+
3
+ Code intelligence platform for AI agents. Search, analyze, and navigate codebases through MCP tools — returning exactly the code your agent needs at a fraction of the token cost.
4
+
5
+ ## Why
6
+
7
+ AI agents burn tokens reading entire files when they need one function. They grep across directories to trace a dependency. They piece together call chains one file at a time. Every wasted read costs money and context window space.
8
+
9
+ Sylvan indexes your codebase into a structured database of symbols, sections, and import relationships, then exposes it through 52 MCP tools. Your agent asks for what it needs and gets exactly that — function signatures, blast radius, dependency graphs, semantic search results. Typical token savings exceed 80%.
10
+
11
+ ## Dashboard
12
+
13
+ ![Overview](docs/overview.png)
14
+
15
+ <details>
16
+ <summary>More screenshots</summary>
17
+
18
+ **Search** — find code by name, signature, or keywords with syntax-highlighted source
19
+ ![Search](docs/search.png)
20
+
21
+ **Session** — live token efficiency tracking per session and all-time
22
+ ![Session](docs/session.png)
23
+
24
+ **Quality Report** — code smells, security findings, test/doc coverage
25
+ ![Quality](docs/quality.png)
26
+
27
+ **Blast Radius** — visualize impact before changing a symbol
28
+ ![Blast Radius](docs/blast-radius.png)
29
+
30
+ **Libraries** — indexed third-party packages with symbol counts
31
+ ![Libraries](docs/libraries.png)
32
+
33
+ </details>
34
+
35
+ ## Features
36
+
37
+ - 52 MCP tools for search, browsing, analysis, and refactoring
38
+ - 34 programming languages via tree-sitter
39
+ - Hybrid search — full-text (FTS5) + vector similarity with ranked fusion
40
+ - Blast radius analysis before any refactor
41
+ - Dependency graphs, call chains, class hierarchies
42
+ - Third-party library indexing (pip, npm, cargo, go)
43
+ - Multi-repo workspaces with cross-repo analysis
44
+ - Code quality reports — smells, security, duplication, dead code
45
+ - Web dashboard with live token efficiency tracking
46
+ - Multi-instance cluster support
47
+
48
+ ## Quick start
49
+
50
+ ```bash
51
+ pip install sylvan
52
+ ```
53
+
54
+ Add to your MCP client config:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "sylvan": {
60
+ "command": "uv",
61
+ "args": ["run", "--directory", "/path/to/sylvan", "sylvan", "serve"]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Your agent handles the rest — index a project, search for code, navigate with precision.
68
+
69
+ ## Documentation
70
+
71
+ Full docs at [darki73.github.io/sylvan](https://darki73.github.io/sylvan/)
72
+
73
+ ## License
74
+
75
+ Non-commercial open source. Free to use, modify, and distribute with attribution. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,70 @@
1
+ [project]
2
+ name = "sylvan"
3
+ version = "1.0.0"
4
+ description = "Unified code + documentation retrieval MCP server"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "darki73", email = "apple.zhivolupov@gmail.com" },
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "mcp>=1.10.0,<2.0.0",
12
+ "tree-sitter-language-pack>=0.7.0,<1.0.0",
13
+ "sqlite-vec>=0.1.0,<1.0.0",
14
+ "pathspec>=0.12.0,<1.0.0",
15
+ "pyyaml>=6.0,<7.0",
16
+ "tiktoken>=0.12.0,<1.0.0",
17
+ "fastembed>=0.4.0,<1.0.0",
18
+ "ollama>=0.6.1,<1.0.0",
19
+ "structlog>=25.5.0,<27.0.0",
20
+ "claude-agent-sdk==0.1.48",
21
+ "aiosqlite>=0.21.0,<1.0.0",
22
+ "jinja2>=3.1,<4.0",
23
+ "uvicorn>=0.30.0,<1.0.0",
24
+ "starlette>=0.38.0,<1.0.0",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ watch = ["watchfiles>=1.0.0"]
29
+ postgres = ["asyncpg>=0.30.0"]
30
+ all = ["watchfiles>=1.0.0", "asyncpg>=0.30.0"]
31
+
32
+ [project.scripts]
33
+ sylvan = "sylvan.cli:main"
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.10.6,<0.11.0"]
37
+ build-backend = "uv_build"
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
41
+ python_files = "test_*.py"
42
+ python_classes = "Test*"
43
+ python_functions = "test_*"
44
+ asyncio_mode = "auto"
45
+ filterwarnings = [
46
+ "ignore::pytest.PytestUnraisableExceptionWarning",
47
+ "ignore::pytest.PytestUnhandledThreadExceptionWarning",
48
+ "ignore:coroutine 'watch_folder' was never awaited:RuntimeWarning",
49
+ ]
50
+
51
+ [tool.mypy]
52
+ python_version = "3.12"
53
+ warn_return_any = true
54
+ warn_unused_configs = true
55
+ disallow_untyped_defs = false
56
+ check_untyped_defs = true
57
+ ignore_missing_imports = true
58
+
59
+ [dependency-groups]
60
+ dev = [
61
+ "pytest>=9.0",
62
+ "pytest-asyncio>=1.3",
63
+ "pytest-cov>=7.0",
64
+ "ruff>=0.9.0",
65
+ "mypy>=1.14",
66
+ "pre-commit>=4.0",
67
+ "mkdocs>=1.6",
68
+ "mkdocs-material>=9.5",
69
+ "mkdocstrings[python]>=0.27",
70
+ ]
@@ -0,0 +1,8 @@
1
+ """Sylvan — Unified code + documentation retrieval MCP server."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("sylvan")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0-dev"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m sylvan`."""
2
+
3
+ from sylvan.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
File without changes
File without changes
@@ -0,0 +1,135 @@
1
+ """Blast radius analysis -- estimate impact of changing a symbol."""
2
+
3
+ import re
4
+ from collections import deque
5
+
6
+ from sylvan.database.orm import Symbol
7
+ from sylvan.database.orm.models.blob import Blob
8
+ from sylvan.database.orm.runtime.connection_manager import get_backend
9
+
10
+
11
+ async def get_blast_radius(
12
+ symbol_id: str,
13
+ max_depth: int = 3,
14
+ ) -> dict:
15
+ """Estimate the blast radius of changing a symbol.
16
+
17
+ Uses BFS through the reference graph + text confirmation.
18
+
19
+ Args:
20
+ symbol_id: Unique identifier of the target symbol.
21
+ max_depth: Maximum BFS traversal depth.
22
+
23
+ Returns:
24
+ Dictionary with "symbol", "confirmed", "potential",
25
+ "depth_reached", and "total_affected" keys. Returns an error
26
+ dict if the symbol is not found.
27
+ """
28
+ backend = get_backend()
29
+
30
+ target = await (
31
+ Symbol.query()
32
+ .select("symbols.*", "f.path as file_path", "f.content_hash", "f.repo_id")
33
+ .join("files f", "f.id = symbols.file_id")
34
+ .where("symbols.symbol_id", symbol_id)
35
+ .first()
36
+ )
37
+
38
+ if target is None:
39
+ return {"error": "symbol_not_found", "symbol_id": symbol_id}
40
+
41
+ target_name = target.name
42
+ target_file_id = target.file_id
43
+ target_file_path = getattr(target, "file_path", "")
44
+ repo_id = getattr(target, "repo_id", None)
45
+
46
+ visited_files: set[int] = {target_file_id}
47
+ queue: deque[tuple[int, int]] = deque() # (file_id, depth)
48
+
49
+ importers = await backend.fetch_all(
50
+ """SELECT DISTINCT fi.file_id
51
+ FROM file_imports fi
52
+ JOIN files f ON f.id = fi.file_id
53
+ WHERE f.repo_id = ?
54
+ AND (fi.resolved_file_id = ?
55
+ OR fi.specifier LIKE ?)""",
56
+ [repo_id, target_file_id, f"%{target_file_path.rsplit('/', 1)[-1].rsplit('.', 1)[0]}%"],
57
+ )
58
+
59
+ for imp in importers:
60
+ fid = imp["file_id"]
61
+ if fid not in visited_files:
62
+ queue.append((fid, 1))
63
+ visited_files.add(fid)
64
+
65
+ confirmed = []
66
+ potential = []
67
+ depth_reached = 0
68
+
69
+ while queue:
70
+ file_id, depth = queue.popleft()
71
+ depth_reached = max(depth_reached, depth)
72
+
73
+ if depth > max_depth:
74
+ continue
75
+
76
+ file_row = await backend.fetch_one(
77
+ "SELECT path, content_hash FROM files WHERE id = ?", [file_id]
78
+ )
79
+ if file_row is None:
80
+ continue
81
+
82
+ content = await Blob.get(file_row["content_hash"])
83
+ if content is None:
84
+ continue
85
+
86
+ text = content.decode("utf-8", errors="replace")
87
+ occurrences = len(re.findall(r'\b' + re.escape(target_name) + r'\b', text))
88
+
89
+ entry = {
90
+ "file": file_row["path"],
91
+ "depth": depth,
92
+ "occurrences": occurrences,
93
+ }
94
+
95
+ file_symbols = await (
96
+ Symbol.where(file_id=file_id)
97
+ .select("symbol_id", "name", "kind", "line_start")
98
+ .limit(10)
99
+ .get()
100
+ )
101
+ entry["symbols"] = [
102
+ {"symbol_id": s.symbol_id, "name": s.name, "kind": s.kind, "line_start": s.line_start}
103
+ for s in file_symbols
104
+ ]
105
+
106
+ if occurrences > 0:
107
+ confirmed.append(entry)
108
+ else:
109
+ potential.append(entry)
110
+
111
+ if depth < max_depth:
112
+ next_importers = await backend.fetch_all(
113
+ """SELECT DISTINCT fi.file_id FROM file_imports fi
114
+ JOIN files f ON f.id = fi.file_id
115
+ WHERE f.repo_id = ? AND fi.resolved_file_id = ?""",
116
+ [repo_id, file_id],
117
+ )
118
+ for ni in next_importers:
119
+ nfid = ni["file_id"]
120
+ if nfid not in visited_files:
121
+ queue.append((nfid, depth + 1))
122
+ visited_files.add(nfid)
123
+
124
+ return {
125
+ "symbol": {
126
+ "symbol_id": symbol_id,
127
+ "name": target_name,
128
+ "kind": target.kind,
129
+ "file": target_file_path,
130
+ },
131
+ "confirmed": confirmed,
132
+ "potential": potential,
133
+ "depth_reached": depth_reached,
134
+ "total_affected": len(confirmed) + len(potential),
135
+ }
@@ -0,0 +1,223 @@
1
+ """Cross-repo analysis -- resolve imports and blast radius across repo boundaries."""
2
+
3
+ import re
4
+ from collections import deque
5
+
6
+ from sylvan.database.orm.models.blob import Blob
7
+ from sylvan.database.orm.runtime.connection_manager import get_backend
8
+
9
+
10
+ async def resolve_cross_repo_imports(
11
+ repo_ids: list[int],
12
+ ) -> int:
13
+ """Resolve file imports that cross repo boundaries.
14
+
15
+ For each unresolved import in any of the given repos, try to match the
16
+ specifier against files in the other repos. Updates resolved_file_id
17
+ in the file_imports table.
18
+
19
+ Args:
20
+ repo_ids: List of repository database IDs to scan.
21
+
22
+ Returns:
23
+ Number of cross-repo imports resolved.
24
+ """
25
+ backend = get_backend()
26
+
27
+ file_lookup: dict[str, int] = {}
28
+ for row in await backend.fetch_all(
29
+ f"""SELECT id, path, repo_id FROM files
30
+ WHERE repo_id IN ({",".join("?" * len(repo_ids))})""",
31
+ repo_ids,
32
+ ):
33
+ fid = row["id"]
34
+ path = row["path"]
35
+ file_lookup[path] = fid
36
+ filename = path.rsplit("/", 1)[-1]
37
+ file_lookup.setdefault(filename, fid)
38
+ stem = filename.rsplit(".", 1)[0]
39
+ file_lookup.setdefault(stem, fid)
40
+ dotpath = path.replace("/", ".").rsplit(".", 1)[0]
41
+ file_lookup.setdefault(dotpath, fid)
42
+
43
+ unresolved = await backend.fetch_all(
44
+ f"""SELECT fi.id, fi.specifier, fi.file_id, f.repo_id
45
+ FROM file_imports fi
46
+ JOIN files f ON f.id = fi.file_id
47
+ WHERE f.repo_id IN ({",".join("?" * len(repo_ids))})
48
+ AND fi.resolved_file_id IS NULL""",
49
+ repo_ids,
50
+ )
51
+
52
+ resolved_count = 0
53
+ for imp in unresolved:
54
+ spec = imp["specifier"]
55
+ source_repo_id = imp["repo_id"]
56
+
57
+ candidates = []
58
+
59
+ if spec in file_lookup:
60
+ target_fid = file_lookup[spec]
61
+ target_repo = await backend.fetch_one(
62
+ "SELECT repo_id FROM files WHERE id = ?", [target_fid]
63
+ )
64
+ if target_repo and target_repo["repo_id"] != source_repo_id:
65
+ candidates.append(target_fid)
66
+
67
+ stem = spec.rsplit("/", 1)[-1].rsplit(".", 1)[0]
68
+ if stem in file_lookup and not candidates:
69
+ target_fid = file_lookup[stem]
70
+ target_repo = await backend.fetch_one(
71
+ "SELECT repo_id FROM files WHERE id = ?", [target_fid]
72
+ )
73
+ if target_repo and target_repo["repo_id"] != source_repo_id:
74
+ candidates.append(target_fid)
75
+
76
+ dotpath = spec.replace(".", "/")
77
+ for suffix in ("", ".py", ".ts", ".js", ".go"):
78
+ key = dotpath + suffix
79
+ if key in file_lookup and not candidates:
80
+ target_fid = file_lookup[key]
81
+ target_repo = await backend.fetch_one(
82
+ "SELECT repo_id FROM files WHERE id = ?", [target_fid]
83
+ )
84
+ if target_repo and target_repo["repo_id"] != source_repo_id:
85
+ candidates.append(target_fid)
86
+
87
+ if candidates:
88
+ await backend.execute(
89
+ "UPDATE file_imports SET resolved_file_id = ? WHERE id = ?",
90
+ [candidates[0], imp["id"]],
91
+ )
92
+ resolved_count += 1
93
+
94
+ if resolved_count:
95
+ await backend.commit()
96
+ return resolved_count
97
+
98
+
99
+ async def cross_repo_blast_radius(
100
+ symbol_id: str,
101
+ repo_ids: list[int],
102
+ max_depth: int = 3,
103
+ ) -> dict:
104
+ """Blast radius analysis that crosses repo boundaries.
105
+
106
+ Like get_blast_radius but follows imports across all repos in the workspace.
107
+
108
+ Args:
109
+ symbol_id: Unique identifier of the target symbol.
110
+ repo_ids: List of repository database IDs to scan.
111
+ max_depth: Maximum BFS traversal depth.
112
+
113
+ Returns:
114
+ Dictionary with "symbol", "confirmed", "potential",
115
+ "depth_reached", "total_affected", "cross_repo_affected",
116
+ and "repos_scanned" keys. Returns an error dict if the
117
+ symbol is not found.
118
+ """
119
+ backend = get_backend()
120
+
121
+ target = await backend.fetch_one(
122
+ """SELECT s.*, f.path as file_path, f.repo_id, f.content_hash,
123
+ r.name as repo_name
124
+ FROM symbols s
125
+ JOIN files f ON f.id = s.file_id
126
+ JOIN repos r ON r.id = f.repo_id
127
+ WHERE s.symbol_id = ?""",
128
+ [symbol_id],
129
+ )
130
+
131
+ if target is None:
132
+ return {"error": "symbol_not_found", "symbol_id": symbol_id}
133
+
134
+ target_dict = dict(target)
135
+ target_name = target_dict["name"]
136
+ target_file_id = target_dict["file_id"]
137
+
138
+ visited_files: set[int] = {target_file_id}
139
+ queue: deque[tuple[int, int]] = deque()
140
+
141
+ repo_filter = ",".join("?" * len(repo_ids))
142
+ importers = await backend.fetch_all(
143
+ f"""SELECT DISTINCT fi.file_id
144
+ FROM file_imports fi
145
+ JOIN files f ON f.id = fi.file_id
146
+ WHERE f.repo_id IN ({repo_filter})
147
+ AND (fi.resolved_file_id = ? OR fi.specifier LIKE ?)""",
148
+ [*repo_ids, target_file_id, f"%{target_dict['file_path'].rsplit('/', 1)[-1].rsplit('.', 1)[0]}%"],
149
+ )
150
+
151
+ for imp in importers:
152
+ fid = imp["file_id"]
153
+ if fid not in visited_files:
154
+ queue.append((fid, 1))
155
+ visited_files.add(fid)
156
+
157
+ confirmed = []
158
+ potential = []
159
+ depth_reached = 0
160
+
161
+ while queue:
162
+ file_id, depth = queue.popleft()
163
+ depth_reached = max(depth_reached, depth)
164
+ if depth > max_depth:
165
+ continue
166
+
167
+ file_row = await backend.fetch_one(
168
+ """SELECT f.path, f.content_hash, r.name as repo_name
169
+ FROM files f JOIN repos r ON r.id = f.repo_id
170
+ WHERE f.id = ?""",
171
+ [file_id],
172
+ )
173
+ if file_row is None:
174
+ continue
175
+
176
+ content = await Blob.get(file_row["content_hash"])
177
+ if content is None:
178
+ continue
179
+
180
+ text = content.decode("utf-8", errors="replace")
181
+ occurrences = len(re.findall(r"\b" + re.escape(target_name) + r"\b", text))
182
+
183
+ entry = {
184
+ "file": file_row["path"],
185
+ "repo": file_row["repo_name"],
186
+ "depth": depth,
187
+ "occurrences": occurrences,
188
+ "cross_repo": file_row["repo_name"] != target_dict["repo_name"],
189
+ }
190
+
191
+ if occurrences > 0:
192
+ confirmed.append(entry)
193
+ else:
194
+ potential.append(entry)
195
+
196
+ if depth < max_depth:
197
+ next_importers = await backend.fetch_all(
198
+ f"""SELECT DISTINCT fi2.file_id FROM file_imports fi2
199
+ JOIN files f2 ON f2.id = fi2.file_id
200
+ WHERE f2.repo_id IN ({repo_filter})
201
+ AND fi2.resolved_file_id = ?""",
202
+ [*repo_ids, file_id],
203
+ )
204
+ for ni in next_importers:
205
+ if ni["file_id"] not in visited_files:
206
+ queue.append((ni["file_id"], depth + 1))
207
+ visited_files.add(ni["file_id"])
208
+
209
+ return {
210
+ "symbol": {
211
+ "symbol_id": target_dict["symbol_id"],
212
+ "name": target_name,
213
+ "kind": target_dict["kind"],
214
+ "file": target_dict["file_path"],
215
+ "repo": target_dict["repo_name"],
216
+ },
217
+ "confirmed": confirmed,
218
+ "potential": potential,
219
+ "depth_reached": depth_reached,
220
+ "total_affected": len(confirmed) + len(potential),
221
+ "cross_repo_affected": sum(1 for c in confirmed + potential if c.get("cross_repo")),
222
+ "repos_scanned": len(repo_ids),
223
+ }
File without changes