kodit 0.3.8__tar.gz → 0.3.10__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 kodit might be problematic. Click here for more details.

Files changed (241) hide show
  1. {kodit-0.3.8 → kodit-0.3.10}/PKG-INFO +1 -1
  2. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/_version.py +2 -2
  3. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/index_service.py +19 -9
  4. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/slicing/slicer.py +46 -21
  5. kodit-0.3.10/tests/kodit/domain/services/index_service_test.py +320 -0
  6. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/slicer_test.py +250 -126
  7. kodit-0.3.8/tests/kodit/domain/services/index_service_test.py +0 -162
  8. {kodit-0.3.8 → kodit-0.3.10}/.claude/commands/debug.md +0 -0
  9. {kodit-0.3.8 → kodit-0.3.10}/.claude/commands/new-requirement.md +0 -0
  10. {kodit-0.3.8 → kodit-0.3.10}/.claude/commands/refactor.md +0 -0
  11. {kodit-0.3.8 → kodit-0.3.10}/.claude/commands/update-docs.md +0 -0
  12. {kodit-0.3.8 → kodit-0.3.10}/.claude/settings.json +0 -0
  13. {kodit-0.3.8 → kodit-0.3.10}/.cursor/rules/kodit.mdc +0 -0
  14. {kodit-0.3.8 → kodit-0.3.10}/.cursor/rules/style.mdc +0 -0
  15. {kodit-0.3.8 → kodit-0.3.10}/.dockerignore +0 -0
  16. {kodit-0.3.8 → kodit-0.3.10}/.github/CODE_OF_CONDUCT.md +0 -0
  17. {kodit-0.3.8 → kodit-0.3.10}/.github/CONTRIBUTING.md +0 -0
  18. {kodit-0.3.8 → kodit-0.3.10}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {kodit-0.3.8 → kodit-0.3.10}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {kodit-0.3.8 → kodit-0.3.10}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {kodit-0.3.8 → kodit-0.3.10}/.github/dependabot.yml +0 -0
  22. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/docker.yaml +0 -0
  23. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/docs.yaml +0 -0
  24. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/pull_request.yaml +0 -0
  25. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/pypi-test.yaml +0 -0
  26. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/pypi.yaml +0 -0
  27. {kodit-0.3.8 → kodit-0.3.10}/.github/workflows/test.yaml +0 -0
  28. {kodit-0.3.8 → kodit-0.3.10}/.gitignore +0 -0
  29. {kodit-0.3.8 → kodit-0.3.10}/.python-version +0 -0
  30. {kodit-0.3.8 → kodit-0.3.10}/.vscode/launch.json +0 -0
  31. {kodit-0.3.8 → kodit-0.3.10}/.vscode/settings.json +0 -0
  32. {kodit-0.3.8 → kodit-0.3.10}/CLAUDE.md +0 -0
  33. {kodit-0.3.8 → kodit-0.3.10}/Dockerfile +0 -0
  34. {kodit-0.3.8 → kodit-0.3.10}/LICENSE +0 -0
  35. {kodit-0.3.8 → kodit-0.3.10}/README.md +0 -0
  36. {kodit-0.3.8 → kodit-0.3.10}/alembic.ini +0 -0
  37. {kodit-0.3.8 → kodit-0.3.10}/docs/_index.md +0 -0
  38. {kodit-0.3.8 → kodit-0.3.10}/docs/demos/_index.md +0 -0
  39. {kodit-0.3.8 → kodit-0.3.10}/docs/demos/go-simple-microservice/index.md +0 -0
  40. {kodit-0.3.8 → kodit-0.3.10}/docs/demos/knock-knock-auth/index.md +0 -0
  41. {kodit-0.3.8 → kodit-0.3.10}/docs/developer/index.md +0 -0
  42. {kodit-0.3.8 → kodit-0.3.10}/docs/getting-started/_index.md +0 -0
  43. {kodit-0.3.8 → kodit-0.3.10}/docs/getting-started/installation/index.md +0 -0
  44. {kodit-0.3.8 → kodit-0.3.10}/docs/getting-started/integration/index.md +0 -0
  45. {kodit-0.3.8 → kodit-0.3.10}/docs/getting-started/quick-start/index.md +0 -0
  46. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/_index.md +0 -0
  47. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/configuration/index.md +0 -0
  48. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/deployment/docker-compose.yaml +0 -0
  49. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/deployment/index.md +0 -0
  50. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/deployment/kubernetes.yaml +0 -0
  51. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/indexing/index.md +0 -0
  52. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/mcp/index.md +0 -0
  53. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/sync/index.md +0 -0
  54. {kodit-0.3.8 → kodit-0.3.10}/docs/reference/telemetry/index.md +0 -0
  55. {kodit-0.3.8 → kodit-0.3.10}/pyproject.toml +0 -0
  56. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/.gitignore +0 -0
  57. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/__init__.py +0 -0
  58. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/app.py +0 -0
  59. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/__init__.py +0 -0
  60. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/factories/__init__.py +0 -0
  61. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/factories/code_indexing_factory.py +0 -0
  62. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/services/__init__.py +0 -0
  63. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/services/code_indexing_application_service.py +0 -0
  64. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/application/services/sync_scheduler.py +0 -0
  65. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/cli.py +0 -0
  66. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/config.py +0 -0
  67. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/database.py +0 -0
  68. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/__init__.py +0 -0
  69. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/entities.py +0 -0
  70. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/errors.py +0 -0
  71. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/interfaces.py +0 -0
  72. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/protocols.py +0 -0
  73. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/__init__.py +0 -0
  74. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/bm25_service.py +0 -0
  75. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/embedding_service.py +0 -0
  76. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/enrichment_service.py +0 -0
  77. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/services/index_query_service.py +0 -0
  78. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/domain/value_objects.py +0 -0
  79. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/__init__.py +0 -0
  80. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/bm25/__init__.py +0 -0
  81. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/bm25/bm25_factory.py +0 -0
  82. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/bm25/local_bm25_repository.py +0 -0
  83. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/bm25/vectorchord_bm25_repository.py +0 -0
  84. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/cloning/__init__.py +0 -0
  85. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/cloning/git/__init__.py +0 -0
  86. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/cloning/git/working_copy.py +0 -0
  87. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/cloning/metadata.py +0 -0
  88. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/__init__.py +0 -0
  89. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_factory.py +0 -0
  90. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_providers/__init__.py +0 -0
  91. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_providers/batching.py +0 -0
  92. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_providers/hash_embedding_provider.py +0 -0
  93. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_providers/local_embedding_provider.py +0 -0
  94. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/embedding_providers/openai_embedding_provider.py +0 -0
  95. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/local_vector_search_repository.py +0 -0
  96. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/embedding/vectorchord_vector_search_repository.py +0 -0
  97. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/enrichment/__init__.py +0 -0
  98. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/enrichment/enrichment_factory.py +0 -0
  99. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/enrichment/local_enrichment_provider.py +0 -0
  100. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -0
  101. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/enrichment/openai_enrichment_provider.py +0 -0
  102. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/git/__init__.py +0 -0
  103. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/git/git_utils.py +0 -0
  104. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/ignore/__init__.py +0 -0
  105. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/ignore/ignore_pattern_provider.py +0 -0
  106. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/indexing/__init__.py +0 -0
  107. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/indexing/auto_indexing_service.py +0 -0
  108. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/indexing/fusion_service.py +0 -0
  109. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/mappers/__init__.py +0 -0
  110. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/mappers/index_mapper.py +0 -0
  111. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/slicing/__init__.py +0 -0
  112. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/slicing/language_detection_service.py +0 -0
  113. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/sqlalchemy/__init__.py +0 -0
  114. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/sqlalchemy/embedding_repository.py +0 -0
  115. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/sqlalchemy/entities.py +0 -0
  116. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/sqlalchemy/index_repository.py +0 -0
  117. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/ui/__init__.py +0 -0
  118. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/ui/progress.py +0 -0
  119. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/infrastructure/ui/spinner.py +0 -0
  120. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/log.py +0 -0
  121. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/mcp.py +0 -0
  122. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/middleware.py +0 -0
  123. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/README +0 -0
  124. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/__init__.py +0 -0
  125. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/env.py +0 -0
  126. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/script.py.mako +0 -0
  127. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/4073b33f9436_add_file_processing_flag.py +0 -0
  128. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/4552eb3f23ce_add_summary.py +0 -0
  129. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/7c3bbc2ab32b_add_embeddings_table.py +0 -0
  130. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/85155663351e_initial.py +0 -0
  131. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/9e53ea8bb3b0_add_authors.py +0 -0
  132. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/__init__.py +0 -0
  133. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/migrations/versions/c3f5137d30f5_index_all_the_things.py +0 -0
  134. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/reporting.py +0 -0
  135. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/utils/__init__.py +0 -0
  136. {kodit-0.3.8 → kodit-0.3.10}/src/kodit/utils/path_utils.py +0 -0
  137. {kodit-0.3.8 → kodit-0.3.10}/tests/__init__.py +0 -0
  138. {kodit-0.3.8 → kodit-0.3.10}/tests/conftest.py +0 -0
  139. {kodit-0.3.8 → kodit-0.3.10}/tests/docker-smoke.sh +0 -0
  140. {kodit-0.3.8 → kodit-0.3.10}/tests/experiments/__init__.py +0 -0
  141. {kodit-0.3.8 → kodit-0.3.10}/tests/experiments/cline_prompt_tests/__init__.py +0 -0
  142. {kodit-0.3.8 → kodit-0.3.10}/tests/experiments/cline_prompt_tests/cline_prompt.txt +0 -0
  143. {kodit-0.3.8 → kodit-0.3.10}/tests/experiments/cline_prompt_tests/cline_prompt_test.py +0 -0
  144. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/__init__.py +0 -0
  145. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/application/__init__.py +0 -0
  146. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/application/services/__init__.py +0 -0
  147. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/application/services/test_sync_scheduler.py +0 -0
  148. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/application/test_code_indexing_application_service.py +0 -0
  149. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/cli_test.py +0 -0
  150. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/config_test.py +0 -0
  151. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/__init__.py +0 -0
  152. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/bm25_domain_service_test.py +0 -0
  153. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/enrichment_domain_service_test.py +0 -0
  154. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/entities_test.py +0 -0
  155. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/services/__init__.py +0 -0
  156. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/test_embedding_service.py +0 -0
  157. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/test_language_mapping.py +0 -0
  158. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/domain/test_multi_search_result.py +0 -0
  159. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/e2e.py +0 -0
  160. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/__init__.py +0 -0
  161. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/bm25/__init__.py +0 -0
  162. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/bm25/local_bm25_repository_test.py +0 -0
  163. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/bm25/vectorchord_bm25_repository_test.py +0 -0
  164. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/cloning/git_cloning/__init__.py +0 -0
  165. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/cloning/git_cloning/working_copy_test.py +0 -0
  166. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/__init__.py +0 -0
  167. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/embedding_factory_test.py +0 -0
  168. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/embedding_provider/__init__.py +0 -0
  169. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/embedding_provider/test_hash_embedding_provider.py +0 -0
  170. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/embedding_provider/test_local_embedding_provider.py +0 -0
  171. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/embedding_provider/test_openai_embedding_provider.py +0 -0
  172. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/test_batching.py +0 -0
  173. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/test_embedding_integration.py +0 -0
  174. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/test_local_vector_search_repository.py +0 -0
  175. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/embedding/test_vectorchord_vector_search_repository.py +0 -0
  176. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/__init__.py +0 -0
  177. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/enrichment_provider/__init__.py +0 -0
  178. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/enrichment_provider/test_local_enrichment_provider.py +0 -0
  179. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/enrichment_provider/test_null_enrichment_provider.py +0 -0
  180. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/enrichment_provider/test_openai_enrichment_provider.py +0 -0
  181. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/enrichment/test_enrichment_factory.py +0 -0
  182. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/indexing/__init__.py +0 -0
  183. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/indexing/test_auto_indexing_service.py +0 -0
  184. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/mappers/__init__.py +0 -0
  185. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/mappers/test_index_mapper.py +0 -0
  186. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/__init__.py +0 -0
  187. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/__init__.py +0 -0
  188. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/c/main.c +0 -0
  189. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/c/models.c +0 -0
  190. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/c/models.h +0 -0
  191. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/c/utils.c +0 -0
  192. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/c/utils.h +0 -0
  193. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/cpp/main.cpp +0 -0
  194. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/cpp/models.cpp +0 -0
  195. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/cpp/models.hpp +0 -0
  196. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/cpp/utils.cpp +0 -0
  197. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/cpp/utils.hpp +0 -0
  198. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/csharp/Main.cs +0 -0
  199. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/csharp/Models.cs +0 -0
  200. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/csharp/Utils.cs +0 -0
  201. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/css/components.css +0 -0
  202. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/css/main.css +0 -0
  203. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/css/utilities.css +0 -0
  204. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/go/main.go +0 -0
  205. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/go/models.go +0 -0
  206. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/go/utils.go +0 -0
  207. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/html/components.html +0 -0
  208. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/html/forms.html +0 -0
  209. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/html/main.html +0 -0
  210. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/java/Main.java +0 -0
  211. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/java/Models.java +0 -0
  212. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/java/Utils.java +0 -0
  213. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/javascript/main.js +0 -0
  214. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/javascript/models.js +0 -0
  215. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/javascript/utils.js +0 -0
  216. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/python/__init__.py +0 -0
  217. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/python/main.py +0 -0
  218. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/python/models.py +0 -0
  219. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/python/utils.py +0 -0
  220. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/rust/main.rs +0 -0
  221. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/rust/models.rs +0 -0
  222. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/slicing/data/rust/utils.rs +0 -0
  223. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/__init__.py +0 -0
  224. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/csharp.cs +0 -0
  225. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/golang.go +0 -0
  226. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/javascript.js +0 -0
  227. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/knock_knock_server.py +0 -0
  228. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/python.py +0 -0
  229. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/snippets/typescript.tsx +0 -0
  230. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/sqlalchemy/__init__.py +0 -0
  231. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/infrastructure/sqlalchemy/test_embedding_repository.py +0 -0
  232. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/log_test.py +0 -0
  233. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/mcp_stdio_test.py +0 -0
  234. {kodit-0.3.8 → kodit-0.3.10}/tests/kodit/mcp_test.py +0 -0
  235. {kodit-0.3.8 → kodit-0.3.10}/tests/performance/__init__.py +0 -0
  236. {kodit-0.3.8 → kodit-0.3.10}/tests/performance/similarity.py +0 -0
  237. {kodit-0.3.8 → kodit-0.3.10}/tests/smoke.sh +0 -0
  238. {kodit-0.3.8 → kodit-0.3.10}/tests/utils/__init__.py +0 -0
  239. {kodit-0.3.8 → kodit-0.3.10}/tests/utils/test_path_utils.py +0 -0
  240. {kodit-0.3.8 → kodit-0.3.10}/tests/vectorchord-smoke.sh +0 -0
  241. {kodit-0.3.8 → kodit-0.3.10}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kodit
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: Code indexing for better AI code generation
5
5
  Project-URL: Homepage, https://docs.helixml.tech/kodit/
6
6
  Project-URL: Documentation, https://docs.helixml.tech/kodit/
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.8'
21
- __version_tuple__ = version_tuple = (0, 3, 8)
20
+ __version__ = version = '0.3.10'
21
+ __version_tuple__ = version_tuple = (0, 3, 10)
@@ -1,6 +1,7 @@
1
1
  """Pure domain service for Index aggregate operations."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
+ from collections import defaultdict
4
5
  from pathlib import Path
5
6
 
6
7
  import structlog
@@ -104,30 +105,39 @@ class IndexDomainService:
104
105
 
105
106
  # Create a set of languages to extract snippets for
106
107
  extensions = {file.extension() for file in files}
107
- languages = []
108
+ lang_files_map: dict[str, list[domain_entities.File]] = defaultdict(list)
108
109
  for ext in extensions:
109
110
  try:
110
- languages.append(LanguageMapping.get_language_for_extension(ext))
111
+ lang = LanguageMapping.get_language_for_extension(ext)
112
+ lang_files_map[lang].extend(
113
+ file for file in files if file.extension() == ext
114
+ )
111
115
  except ValueError as e:
112
- self.log.info("Skipping", error=str(e))
116
+ self.log.debug("Skipping", error=str(e))
113
117
  continue
114
118
 
119
+ self.log.info(
120
+ "Languages to process",
121
+ languages=lang_files_map.keys(),
122
+ )
123
+
115
124
  reporter = Reporter(self.log, progress_callback)
116
125
  await reporter.start(
117
126
  "extract_snippets",
118
- len(files) * len(languages),
127
+ len(lang_files_map.keys()),
119
128
  "Extracting code snippets...",
120
129
  )
130
+
121
131
  # Calculate snippets for each language
122
132
  slicer = Slicer()
123
- for i, language in enumerate(languages):
133
+ for i, (lang, lang_files) in enumerate(lang_files_map.items()):
124
134
  await reporter.step(
125
135
  "extract_snippets",
126
- len(files) * (i + 1),
127
- len(files) * len(languages),
128
- "Extracting code snippets...",
136
+ i,
137
+ len(lang_files_map.keys()),
138
+ f"Extracting code snippets for {lang}...",
129
139
  )
130
- s = slicer.extract_snippets(files, language=language)
140
+ s = slicer.extract_snippets(lang_files, language=lang)
131
141
  index.snippets.extend(s)
132
142
 
133
143
  await reporter.done("extract_snippets")
@@ -10,10 +10,12 @@ from dataclasses import dataclass, field
10
10
  from pathlib import Path
11
11
  from typing import Any, ClassVar
12
12
 
13
+ import structlog
13
14
  from tree_sitter import Node, Parser, Tree
14
15
  from tree_sitter_language_pack import get_language
15
16
 
16
17
  from kodit.domain.entities import File, Snippet
18
+ from kodit.domain.value_objects import LanguageMapping
17
19
 
18
20
 
19
21
  @dataclass
@@ -145,8 +147,9 @@ class Slicer:
145
147
 
146
148
  def __init__(self) -> None:
147
149
  """Initialize an empty slicer."""
150
+ self.log = structlog.get_logger(__name__)
148
151
 
149
- def extract_snippets(
152
+ def extract_snippets( # noqa: C901
150
153
  self, files: list[File], language: str = "python"
151
154
  ) -> list[Snippet]:
152
155
  """Extract code snippets from a list of files.
@@ -170,6 +173,7 @@ class Slicer:
170
173
 
171
174
  # Get language configuration
172
175
  if language not in LanguageConfig.CONFIGS:
176
+ self.log.debug("Skipping", language=language)
173
177
  return []
174
178
 
175
179
  config = LanguageConfig.CONFIGS[language]
@@ -185,16 +189,20 @@ class Slicer:
185
189
  # Create mapping from Paths to File objects and extract paths
186
190
  path_to_file_map: dict[Path, File] = {}
187
191
  file_paths: list[Path] = []
188
-
189
192
  for file in files:
190
193
  file_path = file.as_path()
191
- path_to_file_map[file_path] = file
192
- file_paths.append(file_path)
194
+
195
+ # Validate file matches language
196
+ if not self._file_matches_language(file_path.suffix, language):
197
+ raise ValueError(f"File {file_path} does not match language {language}")
193
198
 
194
199
  # Validate file exists
195
200
  if not file_path.exists():
196
201
  raise FileNotFoundError(f"File not found: {file_path}")
197
202
 
203
+ path_to_file_map[file_path] = file
204
+ file_paths.append(file_path)
205
+
198
206
  # Initialize state
199
207
  state = AnalyzerState(parser=parser)
200
208
  state.files = file_paths
@@ -209,7 +217,7 @@ class Slicer:
209
217
  state.asts[file_path] = tree
210
218
  except OSError:
211
219
  # Skip files that can't be parsed
212
- pass
220
+ continue
213
221
 
214
222
  # Build indexes
215
223
  self._build_definition_and_import_indexes(state, config, language)
@@ -233,6 +241,19 @@ class Slicer:
233
241
 
234
242
  return snippets
235
243
 
244
+ def _file_matches_language(self, file_extension: str, language: str) -> bool:
245
+ """Check if a file extension matches the current language."""
246
+ if language not in LanguageConfig.CONFIGS:
247
+ return False
248
+
249
+ try:
250
+ return (
251
+ language == LanguageMapping.get_language_for_extension(file_extension)
252
+ )
253
+ except ValueError:
254
+ # Extension not supported, so it doesn't match any language
255
+ return False
256
+
236
257
  def _get_tree_sitter_language_name(self, language: str) -> str:
237
258
  """Map user language names to tree-sitter language names."""
238
259
  mapping = {
@@ -247,9 +268,9 @@ class Slicer:
247
268
  "typescript": "typescript",
248
269
  "js": "javascript",
249
270
  "ts": "typescript",
250
- "csharp": "c_sharp",
251
- "c#": "c_sharp",
252
- "cs": "c_sharp",
271
+ "csharp": "csharp",
272
+ "c#": "csharp",
273
+ "cs": "csharp",
253
274
  "html": "html",
254
275
  "css": "css",
255
276
  }
@@ -299,19 +320,23 @@ class Slicer:
299
320
 
300
321
  def _walk_tree(self, node: Node) -> Generator[Node, None, None]:
301
322
  """Walk the AST tree, yielding all nodes."""
302
- cursor = node.walk()
303
-
304
- def _walk_recursive() -> Generator[Node, None, None]:
305
- current_node = cursor.node
306
- if current_node is not None:
307
- yield current_node
308
- if cursor.goto_first_child():
309
- yield from _walk_recursive()
310
- while cursor.goto_next_sibling():
311
- yield from _walk_recursive()
312
- cursor.goto_parent()
313
-
314
- yield from _walk_recursive()
323
+ # Use a simple queue-based approach to avoid recursion issues
324
+ queue = [node]
325
+ visited: set[int] = set() # Track by node id (memory address)
326
+
327
+ while queue:
328
+ current = queue.pop(0)
329
+
330
+ # Use node id (memory address) as unique identifier to avoid infinite loops
331
+ node_id = id(current)
332
+ if node_id in visited:
333
+ continue
334
+ visited.add(node_id)
335
+
336
+ yield current
337
+
338
+ # Add children to queue
339
+ queue.extend(current.children)
315
340
 
316
341
  def _is_function_definition(self, node: Node, config: dict[str, Any]) -> bool:
317
342
  """Check if node is a function definition."""
@@ -0,0 +1,320 @@
1
+ """Unit tests for IndexDomainService."""
2
+
3
+ from pathlib import Path
4
+ from unittest.mock import AsyncMock, Mock
5
+
6
+ import pytest
7
+ from pydantic import AnyUrl
8
+
9
+ from kodit.domain.entities import Index, Snippet, Source, SourceType, WorkingCopy
10
+ from kodit.domain.services.enrichment_service import EnrichmentDomainService
11
+ from kodit.domain.services.index_service import (
12
+ IndexDomainService,
13
+ LanguageDetectionService,
14
+ )
15
+
16
+
17
+ class MockLanguageDetectionService(LanguageDetectionService):
18
+ """Mock language detection service."""
19
+
20
+ async def detect_language(self, file_path: Path) -> str:
21
+ """Return a mock language based on file extension."""
22
+ if file_path.suffix == ".py":
23
+ return "python"
24
+ return "unknown"
25
+
26
+
27
+ @pytest.fixture
28
+ def mock_enrichment_service() -> EnrichmentDomainService:
29
+ """Create a mock enrichment service."""
30
+ service = Mock(spec=EnrichmentDomainService)
31
+ service.enrich_documents = AsyncMock()
32
+ return service
33
+
34
+
35
+ @pytest.fixture
36
+ def index_domain_service(
37
+ mock_enrichment_service: EnrichmentDomainService,
38
+ tmp_path: Path,
39
+ ) -> IndexDomainService:
40
+ """Create an IndexDomainService for testing."""
41
+ return IndexDomainService(
42
+ language_detector=MockLanguageDetectionService(),
43
+ enrichment_service=mock_enrichment_service,
44
+ clone_dir=tmp_path / "clones",
45
+ )
46
+
47
+
48
+ @pytest.mark.asyncio
49
+ async def test_prepare_index_creates_working_copy(
50
+ index_domain_service: IndexDomainService, tmp_path: Path
51
+ ) -> None:
52
+ """Test that prepare_index creates a working copy without repository calls."""
53
+ # Create a test file
54
+ test_file = tmp_path / "test.py"
55
+ test_file.write_text("def hello(): pass")
56
+
57
+ # Prepare index (this only creates the working copy structure, no files scanned yet)
58
+ working_copy = await index_domain_service.prepare_index(str(tmp_path))
59
+
60
+ # Verify the working copy was created
61
+ assert isinstance(working_copy, WorkingCopy)
62
+ assert isinstance(working_copy.remote_uri, AnyUrl)
63
+ assert working_copy.source_type == SourceType.FOLDER
64
+
65
+ # In the new flow, files are not scanned during prepare_index
66
+ # They are scanned during refresh_working_copy
67
+ assert len(working_copy.files) == 0
68
+
69
+ # Now refresh to actually scan the files
70
+ refreshed_working_copy = await index_domain_service.refresh_working_copy(
71
+ working_copy
72
+ )
73
+ assert len(refreshed_working_copy.files) == 1
74
+ assert refreshed_working_copy.files[0].uri.path.endswith("test.py") # type: ignore[union-attr]
75
+
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_extract_snippets_from_index_returns_snippets(
79
+ index_domain_service: IndexDomainService, tmp_path: Path
80
+ ) -> None:
81
+ """Test that extract_snippets_from_index returns snippets without persistence."""
82
+ # Create a mock index with files
83
+ test_file = tmp_path / "test.py"
84
+ test_file.write_text("def hello(): pass")
85
+
86
+ # Prepare working copy first
87
+ working_copy = await index_domain_service.prepare_index(str(tmp_path))
88
+
89
+ # Now refresh to scan the files
90
+ working_copy = await index_domain_service.refresh_working_copy(working_copy)
91
+
92
+ from datetime import UTC, datetime
93
+
94
+ # Create a mock index
95
+ source = Source(
96
+ id=1,
97
+ working_copy=working_copy,
98
+ )
99
+ index = Index(
100
+ id=1,
101
+ source=source,
102
+ snippets=[],
103
+ created_at=datetime.now(UTC),
104
+ updated_at=datetime.now(UTC),
105
+ )
106
+
107
+ # Extract snippets - this method returns the updated Index, not just snippets
108
+ updated_index = await index_domain_service.extract_snippets_from_index(
109
+ index=index,
110
+ )
111
+
112
+ # Verify snippets were extracted
113
+ assert len(updated_index.snippets) > 0
114
+ assert isinstance(updated_index.snippets[0], Snippet)
115
+ # The actual Slicer is used now, so we should check for the actual function
116
+ assert "def hello" in updated_index.snippets[0].original_text()
117
+ assert "pass" in updated_index.snippets[0].original_text()
118
+
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_enrich_snippets_in_index_returns_enriched_snippets(
122
+ tmp_path: Path,
123
+ ) -> None:
124
+ """Test enrich_snippets_in_index returns enriched snippets without persistence."""
125
+ from kodit.domain.services.enrichment_service import EnrichmentDomainService
126
+ from kodit.infrastructure.enrichment.null_enrichment_provider import (
127
+ NullEnrichmentProvider,
128
+ )
129
+
130
+ # Create real enrichment service with null provider (fast)
131
+ enrichment_service = EnrichmentDomainService(
132
+ enrichment_provider=NullEnrichmentProvider()
133
+ )
134
+
135
+ # Create domain service with real enrichment service
136
+ domain_service = IndexDomainService(
137
+ language_detector=MockLanguageDetectionService(),
138
+ enrichment_service=enrichment_service,
139
+ clone_dir=tmp_path / "clones",
140
+ )
141
+
142
+ # Create mock snippets
143
+ snippet = Snippet(derives_from=[])
144
+ snippet.id = 1
145
+ snippet.add_original_content("def test(): pass", "python")
146
+ snippets = [snippet]
147
+
148
+ # Enrich snippets
149
+ enriched_snippets = await domain_service.enrich_snippets_in_index(snippets=snippets)
150
+
151
+ # Verify snippets were returned (null provider doesn't actually enrich)
152
+ assert len(enriched_snippets) == 1
153
+ assert enriched_snippets[0].id == 1
154
+
155
+
156
+ @pytest.mark.asyncio
157
+ async def test_enrich_snippets_with_empty_list_returns_empty_list(
158
+ index_domain_service: IndexDomainService,
159
+ ) -> None:
160
+ """Test that enriching an empty list returns an empty list."""
161
+ enriched_snippets = await index_domain_service.enrich_snippets_in_index(snippets=[])
162
+ assert enriched_snippets == []
163
+
164
+
165
+ @pytest.mark.asyncio
166
+ async def test_extract_snippets_only_processes_relevant_files(
167
+ index_domain_service: IndexDomainService, tmp_path: Path
168
+ ) -> None:
169
+ """Test that extract_snippets only processes files relevant to their language."""
170
+ # Create files of different types
171
+ py_file = tmp_path / "test.py"
172
+ py_file.write_text("def hello(): pass")
173
+
174
+ js_file = tmp_path / "test.js"
175
+ js_file.write_text("function hello() {}")
176
+
177
+ png_file = tmp_path / "image.png"
178
+ png_file.write_bytes(b"fake png data")
179
+
180
+ txt_file = tmp_path / "readme.txt"
181
+ txt_file.write_text("This is just text")
182
+
183
+ # Prepare working copy
184
+ working_copy = await index_domain_service.prepare_index(str(tmp_path))
185
+ working_copy = await index_domain_service.refresh_working_copy(working_copy)
186
+
187
+ # Verify all files were discovered
188
+ assert len(working_copy.files) == 4
189
+
190
+ from datetime import UTC, datetime
191
+
192
+ from kodit.domain.entities import Index, Source
193
+
194
+ # Create a mock index
195
+ source = Source(id=1, working_copy=working_copy)
196
+ index = Index(
197
+ id=1,
198
+ source=source,
199
+ snippets=[],
200
+ created_at=datetime.now(UTC),
201
+ updated_at=datetime.now(UTC),
202
+ )
203
+
204
+ # Extract snippets - should process all supported languages found in files
205
+ updated_index = await index_domain_service.extract_snippets_from_index(index)
206
+
207
+ # Should have snippets from both Python and JavaScript files (2 supported languages)
208
+ # PNG and TXT files should be ignored as they're not supported
209
+ assert len(updated_index.snippets) == 2 # Python and JavaScript files
210
+
211
+ # Get snippet texts
212
+ snippet_texts = [s.original_text() for s in updated_index.snippets]
213
+
214
+ # Should contain content from both supported languages
215
+ assert any("def hello" in text for text in snippet_texts) # Python file
216
+ assert any("function hello" in text for text in snippet_texts) # JavaScript file
217
+
218
+ # Verify the snippets derive from the correct files
219
+ source_files = [s.derives_from[0].uri.path for s in updated_index.snippets]
220
+ assert any(path.endswith("test.py") for path in source_files) # type: ignore[union-attr]
221
+ assert any(path.endswith("test.js") for path in source_files) # type: ignore[union-attr]
222
+
223
+
224
+ @pytest.mark.asyncio
225
+ async def test_extract_snippets_filters_by_language_mapping(
226
+ index_domain_service: IndexDomainService, tmp_path: Path
227
+ ) -> None:
228
+ """Test that extract_snippets correctly filters files by language mapping."""
229
+ # Create files for different languages
230
+ py_file = tmp_path / "script.py"
231
+ py_file.write_text("def python_func(): return 'python'")
232
+
233
+ js_file = tmp_path / "script.js"
234
+ js_file.write_text("function jsFunc() { return 'javascript'; }")
235
+
236
+ java_file = tmp_path / "Script.java"
237
+ java_file.write_text("public class Script { public void javaMethod() {} }")
238
+
239
+ # Create unsupported file type
240
+ unknown_file = tmp_path / "script.unknown"
241
+ unknown_file.write_text("some unknown content")
242
+
243
+ # Prepare working copy
244
+ working_copy = await index_domain_service.prepare_index(str(tmp_path))
245
+ working_copy = await index_domain_service.refresh_working_copy(working_copy)
246
+
247
+ # Should discover all files
248
+ assert len(working_copy.files) == 4
249
+
250
+ from datetime import UTC, datetime
251
+
252
+ from kodit.domain.entities import Index, Source
253
+
254
+ # Create a mock index
255
+ source = Source(id=1, working_copy=working_copy)
256
+ index = Index(
257
+ id=1,
258
+ source=source,
259
+ snippets=[],
260
+ created_at=datetime.now(UTC),
261
+ updated_at=datetime.now(UTC),
262
+ )
263
+
264
+ # Extract snippets - should process supported languages only
265
+ updated_index = await index_domain_service.extract_snippets_from_index(index)
266
+
267
+ # Should have snippets from Python, JavaScript, and Java files
268
+ # (unknown file should be ignored)
269
+ assert len(updated_index.snippets) == 3
270
+
271
+ # Get the snippet texts
272
+ snippet_texts = [s.original_text() for s in updated_index.snippets]
273
+
274
+ # Should contain content from all supported languages
275
+ assert any("def python_func" in text for text in snippet_texts)
276
+ assert any("function jsFunc" in text for text in snippet_texts)
277
+ assert any("javaMethod" in text for text in snippet_texts)
278
+
279
+ # Should not contain content from unsupported file
280
+ assert not any("unknown content" in text for text in snippet_texts)
281
+
282
+
283
+ @pytest.mark.asyncio
284
+ async def test_extract_snippets_handles_no_matching_files(
285
+ index_domain_service: IndexDomainService, tmp_path: Path
286
+ ) -> None:
287
+ """Test extract_snippets handles case where no files match supported languages."""
288
+ # Create only unsupported file types
289
+ img_file = tmp_path / "image.png"
290
+ img_file.write_bytes(b"fake image data")
291
+
292
+ doc_file = tmp_path / "document.pdf"
293
+ doc_file.write_bytes(b"fake pdf data")
294
+
295
+ # Prepare working copy
296
+ working_copy = await index_domain_service.prepare_index(str(tmp_path))
297
+ working_copy = await index_domain_service.refresh_working_copy(working_copy)
298
+
299
+ # Should discover files
300
+ assert len(working_copy.files) == 2
301
+
302
+ from datetime import UTC, datetime
303
+
304
+ from kodit.domain.entities import Index, Source
305
+
306
+ # Create a mock index
307
+ source = Source(id=1, working_copy=working_copy)
308
+ index = Index(
309
+ id=1,
310
+ source=source,
311
+ snippets=[],
312
+ created_at=datetime.now(UTC),
313
+ updated_at=datetime.now(UTC),
314
+ )
315
+
316
+ # Extract snippets - should return empty list since no supported files
317
+ updated_index = await index_domain_service.extract_snippets_from_index(index)
318
+
319
+ # Should have no snippets
320
+ assert len(updated_index.snippets) == 0