kodit 0.4.3__py3-none-any.whl → 0.5.0__py3-none-any.whl

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 (95) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +53 -23
  3. kodit/application/factories/reporting_factory.py +6 -2
  4. kodit/application/factories/server_factory.py +311 -0
  5. kodit/application/services/code_search_application_service.py +144 -0
  6. kodit/application/services/commit_indexing_application_service.py +543 -0
  7. kodit/application/services/indexing_worker_service.py +13 -44
  8. kodit/application/services/queue_service.py +24 -3
  9. kodit/application/services/reporting.py +0 -2
  10. kodit/application/services/sync_scheduler.py +15 -31
  11. kodit/cli.py +2 -753
  12. kodit/cli_utils.py +2 -9
  13. kodit/config.py +1 -94
  14. kodit/database.py +38 -1
  15. kodit/domain/{entities.py → entities/__init__.py} +50 -195
  16. kodit/domain/entities/git.py +190 -0
  17. kodit/domain/factories/__init__.py +1 -0
  18. kodit/domain/factories/git_repo_factory.py +76 -0
  19. kodit/domain/protocols.py +263 -64
  20. kodit/domain/services/bm25_service.py +5 -1
  21. kodit/domain/services/embedding_service.py +3 -0
  22. kodit/domain/services/git_repository_service.py +429 -0
  23. kodit/domain/services/git_service.py +300 -0
  24. kodit/domain/services/task_status_query_service.py +2 -2
  25. kodit/domain/value_objects.py +83 -114
  26. kodit/infrastructure/api/client/__init__.py +0 -2
  27. kodit/infrastructure/api/v1/__init__.py +0 -4
  28. kodit/infrastructure/api/v1/dependencies.py +92 -46
  29. kodit/infrastructure/api/v1/routers/__init__.py +0 -6
  30. kodit/infrastructure/api/v1/routers/commits.py +271 -0
  31. kodit/infrastructure/api/v1/routers/queue.py +2 -2
  32. kodit/infrastructure/api/v1/routers/repositories.py +282 -0
  33. kodit/infrastructure/api/v1/routers/search.py +31 -14
  34. kodit/infrastructure/api/v1/schemas/__init__.py +0 -24
  35. kodit/infrastructure/api/v1/schemas/commit.py +96 -0
  36. kodit/infrastructure/api/v1/schemas/context.py +2 -0
  37. kodit/infrastructure/api/v1/schemas/repository.py +128 -0
  38. kodit/infrastructure/api/v1/schemas/search.py +12 -9
  39. kodit/infrastructure/api/v1/schemas/snippet.py +58 -0
  40. kodit/infrastructure/api/v1/schemas/tag.py +31 -0
  41. kodit/infrastructure/api/v1/schemas/task_status.py +2 -0
  42. kodit/infrastructure/bm25/local_bm25_repository.py +16 -4
  43. kodit/infrastructure/bm25/vectorchord_bm25_repository.py +68 -52
  44. kodit/infrastructure/cloning/git/git_python_adaptor.py +467 -0
  45. kodit/infrastructure/cloning/git/working_copy.py +1 -1
  46. kodit/infrastructure/embedding/embedding_factory.py +3 -2
  47. kodit/infrastructure/embedding/local_vector_search_repository.py +1 -1
  48. kodit/infrastructure/embedding/vectorchord_vector_search_repository.py +111 -84
  49. kodit/infrastructure/enrichment/litellm_enrichment_provider.py +19 -26
  50. kodit/infrastructure/indexing/fusion_service.py +1 -1
  51. kodit/infrastructure/mappers/git_mapper.py +193 -0
  52. kodit/infrastructure/mappers/snippet_mapper.py +106 -0
  53. kodit/infrastructure/mappers/task_mapper.py +5 -44
  54. kodit/infrastructure/reporting/log_progress.py +8 -5
  55. kodit/infrastructure/reporting/telemetry_progress.py +21 -0
  56. kodit/infrastructure/slicing/slicer.py +32 -31
  57. kodit/infrastructure/sqlalchemy/embedding_repository.py +43 -23
  58. kodit/infrastructure/sqlalchemy/entities.py +394 -158
  59. kodit/infrastructure/sqlalchemy/git_branch_repository.py +263 -0
  60. kodit/infrastructure/sqlalchemy/git_commit_repository.py +337 -0
  61. kodit/infrastructure/sqlalchemy/git_repository.py +252 -0
  62. kodit/infrastructure/sqlalchemy/git_tag_repository.py +257 -0
  63. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +484 -0
  64. kodit/infrastructure/sqlalchemy/task_repository.py +29 -23
  65. kodit/infrastructure/sqlalchemy/task_status_repository.py +24 -12
  66. kodit/infrastructure/sqlalchemy/unit_of_work.py +10 -14
  67. kodit/mcp.py +12 -30
  68. kodit/migrations/env.py +1 -0
  69. kodit/migrations/versions/04b80f802e0c_foreign_key_review.py +100 -0
  70. kodit/migrations/versions/7f15f878c3a1_add_new_git_entities.py +690 -0
  71. kodit/migrations/versions/f9e5ef5e688f_add_git_commits_number.py +43 -0
  72. kodit/py.typed +0 -0
  73. kodit/utils/dump_openapi.py +7 -4
  74. kodit/utils/path_utils.py +29 -0
  75. {kodit-0.4.3.dist-info → kodit-0.5.0.dist-info}/METADATA +3 -3
  76. kodit-0.5.0.dist-info/RECORD +137 -0
  77. kodit/application/factories/code_indexing_factory.py +0 -195
  78. kodit/application/services/auto_indexing_service.py +0 -99
  79. kodit/application/services/code_indexing_application_service.py +0 -410
  80. kodit/domain/services/index_query_service.py +0 -70
  81. kodit/domain/services/index_service.py +0 -269
  82. kodit/infrastructure/api/client/index_client.py +0 -57
  83. kodit/infrastructure/api/v1/routers/indexes.py +0 -164
  84. kodit/infrastructure/api/v1/schemas/index.py +0 -101
  85. kodit/infrastructure/bm25/bm25_factory.py +0 -28
  86. kodit/infrastructure/cloning/__init__.py +0 -1
  87. kodit/infrastructure/cloning/metadata.py +0 -98
  88. kodit/infrastructure/mappers/index_mapper.py +0 -345
  89. kodit/infrastructure/reporting/tdqm_progress.py +0 -38
  90. kodit/infrastructure/slicing/language_detection_service.py +0 -18
  91. kodit/infrastructure/sqlalchemy/index_repository.py +0 -646
  92. kodit-0.4.3.dist-info/RECORD +0 -125
  93. {kodit-0.4.3.dist-info → kodit-0.5.0.dist-info}/WHEEL +0 -0
  94. {kodit-0.4.3.dist-info → kodit-0.5.0.dist-info}/entry_points.txt +0 -0
  95. {kodit-0.4.3.dist-info → kodit-0.5.0.dist-info}/licenses/LICENSE +0 -0
kodit/cli.py CHANGED
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: ARG001
1
2
  """Command line interface for kodit."""
2
3
 
3
4
  import signal
@@ -8,26 +9,10 @@ from typing import Any
8
9
  import click
9
10
  import structlog
10
11
  import uvicorn
11
- from pytable_formatter import Cell, Table # type: ignore[import-untyped]
12
12
 
13
- from kodit.application.factories.code_indexing_factory import (
14
- create_cli_code_indexing_application_service,
15
- )
16
13
  from kodit.config import (
17
14
  AppContext,
18
- with_app_context,
19
- wrap_async,
20
- )
21
- from kodit.domain.errors import EmptySourceError
22
- from kodit.domain.services.index_query_service import IndexQueryService
23
- from kodit.domain.value_objects import (
24
- MultiSearchRequest,
25
- MultiSearchResult,
26
- SnippetSearchFilters,
27
15
  )
28
- from kodit.infrastructure.api.client import IndexClient, SearchClient
29
- from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
30
- from kodit.infrastructure.sqlalchemy.index_repository import create_index_repository
31
16
  from kodit.log import configure_logging, configure_telemetry, log_event
32
17
  from kodit.mcp import create_stdio_mcp_server
33
18
 
@@ -61,743 +46,6 @@ def cli(
61
46
  ctx.obj = config
62
47
 
63
48
 
64
- async def _handle_auto_index(
65
- app_context: AppContext,
66
- sources: list[str], # noqa: ARG001
67
- ) -> list[str]:
68
- """Handle auto-index option and return sources to process."""
69
- log = structlog.get_logger(__name__)
70
- log.info("Auto-indexing configuration", config=app_context.auto_indexing)
71
- if not app_context.auto_indexing or not app_context.auto_indexing.sources:
72
- click.echo("No auto-index sources configured.")
73
- return []
74
- auto_sources = app_context.auto_indexing.sources
75
- click.echo(f"Auto-indexing {len(auto_sources)} configured sources...")
76
- return [source.uri for source in auto_sources]
77
-
78
-
79
- async def _handle_sync(
80
- service: Any,
81
- index_query_service: IndexQueryService,
82
- sources: list[str],
83
- ) -> None:
84
- """Handle sync operation."""
85
- log = structlog.get_logger(__name__)
86
- log_event("kodit.cli.index.sync")
87
-
88
- # Get all existing indexes
89
- all_indexes = await index_query_service.list_indexes()
90
-
91
- if not all_indexes:
92
- click.echo("No existing indexes found to sync.")
93
- return
94
-
95
- # Filter indexes if specific sources are provided
96
- indexes_to_sync = all_indexes
97
- if sources:
98
- # Filter indexes that match the provided sources
99
- source_uris = set(sources)
100
- indexes_to_sync = [
101
- index
102
- for index in all_indexes
103
- if str(index.source.working_copy.remote_uri) in source_uris
104
- ]
105
-
106
- if not indexes_to_sync:
107
- click.echo(
108
- f"No indexes found for the specified sources: {', '.join(sources)}"
109
- )
110
- return
111
-
112
- click.echo(f"Syncing {len(indexes_to_sync)} indexes...")
113
-
114
- # Sync each index
115
- for index in indexes_to_sync:
116
- click.echo(f"Syncing: {index.source.working_copy.remote_uri}")
117
-
118
- try:
119
- await service.run_index(index)
120
- click.echo(f"✓ Sync completed: {index.source.working_copy.remote_uri}")
121
- except Exception as e:
122
- log.exception("Sync failed", index_id=index.id, error=e)
123
- click.echo(f"✗ Sync failed: {index.source.working_copy.remote_uri} - {e}")
124
-
125
-
126
- async def _handle_list_indexes(index_query_service: IndexQueryService) -> None:
127
- """Handle listing all indexes."""
128
- log_event("kodit.cli.index.list")
129
- # No source specified, list all indexes
130
- indexes = await index_query_service.list_indexes()
131
- headers: list[str | Cell] = [
132
- "ID",
133
- "Created At",
134
- "Updated At",
135
- "Source",
136
- "Num Snippets",
137
- ]
138
- data = [
139
- [
140
- index.id,
141
- index.created_at,
142
- index.updated_at,
143
- index.source.working_copy.remote_uri,
144
- len(index.source.working_copy.files),
145
- ]
146
- for index in indexes
147
- ]
148
- click.echo(Table(headers=headers, data=data))
149
-
150
-
151
- @cli.command()
152
- @click.argument("sources", nargs=-1)
153
- @click.option(
154
- "--auto-index", is_flag=True, help="Index all configured auto-index sources"
155
- )
156
- @click.option("--sync", is_flag=True, help="Sync existing indexes with their remotes")
157
- @with_app_context
158
- @wrap_async
159
- async def index(
160
- app_context: AppContext,
161
- sources: list[str],
162
- *, # Force keyword-only arguments
163
- auto_index: bool,
164
- sync: bool,
165
- ) -> None:
166
- """List indexes, index data sources, or sync existing indexes."""
167
- if not app_context.is_remote:
168
- # Local mode - use existing implementation
169
- await _index_local(app_context, sources, auto_index, sync)
170
- else:
171
- # Remote mode - use API clients
172
- await _index_remote(app_context, sources, auto_index, sync)
173
-
174
-
175
- async def _index_local(
176
- app_context: AppContext,
177
- sources: list[str],
178
- auto_index: bool, # noqa: FBT001
179
- sync: bool, # noqa: FBT001
180
- ) -> None:
181
- """Handle index operations in local mode."""
182
- log = structlog.get_logger(__name__)
183
-
184
- # Get database session
185
- db = await app_context.get_db()
186
- service = create_cli_code_indexing_application_service(
187
- app_context=app_context,
188
- session_factory=db.session_factory,
189
- )
190
- index_query_service = IndexQueryService(
191
- index_repository=create_index_repository(session_factory=db.session_factory),
192
- fusion_service=ReciprocalRankFusionService(),
193
- )
194
-
195
- if auto_index:
196
- sources = await _handle_auto_index(app_context, sources)
197
- if not sources:
198
- return
199
-
200
- if sync:
201
- await _handle_sync(service, index_query_service, sources)
202
- return
203
-
204
- if not sources:
205
- await _handle_list_indexes(index_query_service)
206
- return
207
-
208
- # Handle source indexing
209
- for source in sources:
210
- if Path(source).is_file():
211
- msg = "File indexing is not implemented yet"
212
- raise click.UsageError(msg)
213
-
214
- # Index source with progress
215
- log_event("kodit.cli.index.create")
216
-
217
- # Create a lazy progress callback that only shows progress when needed
218
- index = await service.create_index_from_uri(source)
219
-
220
- # Create a new progress callback for the indexing operations
221
- try:
222
- await service.run_index(index)
223
- except EmptySourceError as e:
224
- log.exception("Empty source error", error=e)
225
- msg = f"""{e}. This could mean:
226
- • The repository contains no supported file types
227
- • All files are excluded by ignore patterns
228
- • The files contain no extractable code snippets
229
- Please check the repository contents and try again.
230
- """
231
- click.echo(msg)
232
-
233
-
234
- async def _index_remote(
235
- app_context: AppContext,
236
- sources: list[str],
237
- auto_index: bool, # noqa: FBT001
238
- sync: bool, # noqa: FBT001
239
- ) -> None:
240
- """Handle index operations in remote mode."""
241
- if sync:
242
- # Sync operation not available in remote mode
243
- click.echo("⚠️ Warning: Index sync is not implemented in remote mode")
244
- click.echo("Please use the server's auto-sync functionality or sync locally")
245
- return
246
-
247
- if auto_index:
248
- click.echo("⚠️ Warning: Auto-index is not implemented in remote mode")
249
- click.echo("Please configure sources to be auto-indexed on the server")
250
- return
251
-
252
- # Create API client
253
- index_client = IndexClient(
254
- base_url=app_context.remote.server_url or "",
255
- api_key=app_context.remote.api_key,
256
- timeout=app_context.remote.timeout,
257
- max_retries=app_context.remote.max_retries,
258
- verify_ssl=app_context.remote.verify_ssl,
259
- )
260
-
261
- try:
262
- if not sources:
263
- # List indexes
264
- indexes = await index_client.list_indexes()
265
- _display_indexes_remote(indexes)
266
- return
267
-
268
- # Create new indexes
269
- for source in sources:
270
- click.echo(f"Creating index for: {source}")
271
- index = await index_client.create_index(source)
272
- click.echo(f"✓ Index created with ID: {index.id}")
273
-
274
- finally:
275
- await index_client.close()
276
-
277
-
278
- def _display_indexes_remote(indexes: list[Any]) -> None:
279
- """Display indexes for remote mode."""
280
- headers = [
281
- "ID",
282
- "Created At",
283
- "Updated At",
284
- "Source",
285
- ]
286
- data = [
287
- [
288
- index.id,
289
- index.attributes.created_at,
290
- index.attributes.updated_at,
291
- index.attributes.uri,
292
- ]
293
- for index in indexes
294
- ]
295
-
296
- click.echo(Table(headers=headers, data=data))
297
-
298
-
299
- async def _search_local( # noqa: PLR0913
300
- app_context: AppContext,
301
- keywords: list[str] | None = None,
302
- code_query: str | None = None,
303
- text_query: str | None = None,
304
- top_k: int = 10,
305
- language: str | None = None,
306
- author: str | None = None,
307
- created_after: str | None = None,
308
- created_before: str | None = None,
309
- source_repo: str | None = None,
310
- output_format: str = "text",
311
- event_name: str = "kodit.cli.search",
312
- ) -> None:
313
- """Handle search operations in local mode."""
314
- log_event(event_name)
315
-
316
- # Get database session
317
- db = await app_context.get_db()
318
- service = create_cli_code_indexing_application_service(
319
- app_context=app_context,
320
- session_factory=db.session_factory,
321
- )
322
-
323
- filters = _parse_filters(
324
- language, author, created_after, created_before, source_repo
325
- )
326
-
327
- snippets = await service.search(
328
- MultiSearchRequest(
329
- keywords=keywords,
330
- code_query=code_query,
331
- text_query=text_query,
332
- top_k=top_k,
333
- filters=filters,
334
- )
335
- )
336
-
337
- if len(snippets) == 0:
338
- click.echo("No snippets found")
339
- return
340
-
341
- if output_format == "text":
342
- click.echo(MultiSearchResult.to_string(snippets))
343
- elif output_format == "json":
344
- click.echo(MultiSearchResult.to_jsonlines(snippets))
345
-
346
-
347
- async def _search_remote( # noqa: PLR0913
348
- app_context: AppContext,
349
- keywords: list[str] | None = None,
350
- code_query: str | None = None,
351
- text_query: str | None = None,
352
- top_k: int = 10,
353
- language: str | None = None,
354
- author: str | None = None,
355
- created_after: str | None = None,
356
- created_before: str | None = None,
357
- source_repo: str | None = None,
358
- output_format: str = "text",
359
- ) -> None:
360
- """Handle search operations in remote mode."""
361
- from datetime import datetime
362
-
363
- # Parse date filters for API
364
- parsed_start_date = None
365
- if created_after:
366
- try:
367
- parsed_start_date = datetime.fromisoformat(created_after)
368
- except ValueError as err:
369
- raise ValueError(
370
- f"Invalid date format for --created-after: {created_after}. "
371
- "Expected ISO 8601 format (YYYY-MM-DD)"
372
- ) from err
373
-
374
- parsed_end_date = None
375
- if created_before:
376
- try:
377
- parsed_end_date = datetime.fromisoformat(created_before)
378
- except ValueError as err:
379
- raise ValueError(
380
- f"Invalid date format for --created-before: {created_before}. "
381
- "Expected ISO 8601 format (YYYY-MM-DD)"
382
- ) from err
383
-
384
- # Create API client
385
- search_client = SearchClient(
386
- base_url=app_context.remote.server_url or "",
387
- api_key=app_context.remote.api_key,
388
- timeout=app_context.remote.timeout,
389
- max_retries=app_context.remote.max_retries,
390
- verify_ssl=app_context.remote.verify_ssl,
391
- )
392
-
393
- try:
394
- # Perform search
395
- snippets = await search_client.search(
396
- keywords=keywords,
397
- code_query=code_query,
398
- text_query=text_query,
399
- limit=top_k,
400
- languages=[language] if language else None,
401
- authors=[author] if author else None,
402
- start_date=parsed_start_date,
403
- end_date=parsed_end_date,
404
- sources=[source_repo] if source_repo else None,
405
- )
406
-
407
- if len(snippets) == 0:
408
- click.echo("No snippets found")
409
- return
410
-
411
- if output_format == "text":
412
- _display_snippets_remote_text(snippets)
413
- elif output_format == "json":
414
- _display_snippets_remote_json(snippets)
415
-
416
- finally:
417
- await search_client.close()
418
-
419
-
420
- def _display_snippets_remote_text(snippets: list[Any]) -> None:
421
- """Display snippets in text format for remote mode."""
422
- for i, snippet in enumerate(snippets):
423
- click.echo(f"\n--- Snippet {i + 1} ---")
424
- click.echo(f"ID: {snippet.id}")
425
- click.echo(f"Language: {snippet.attributes.language}")
426
- click.echo(f"Source: {snippet.attributes.source_uri}")
427
- click.echo(f"Path: {snippet.attributes.relative_path}")
428
- click.echo(f"Authors: {', '.join(snippet.attributes.authors)}")
429
- click.echo(f"Summary: {snippet.attributes.summary}")
430
- click.echo("\nContent:")
431
- click.echo(snippet.attributes.content)
432
-
433
-
434
- def _display_snippets_remote_json(snippets: list[Any]) -> None:
435
- """Display snippets in JSON format for remote mode."""
436
- import json
437
-
438
- for snippet in snippets:
439
- snippet_data = {
440
- "id": snippet.id,
441
- "language": snippet.attributes.language,
442
- "source_uri": snippet.attributes.source_uri,
443
- "relative_path": snippet.attributes.relative_path,
444
- "authors": snippet.attributes.authors,
445
- "summary": snippet.attributes.summary,
446
- "content": snippet.attributes.content,
447
- "created_at": snippet.attributes.created_at.isoformat(),
448
- "updated_at": snippet.attributes.updated_at.isoformat(),
449
- }
450
- click.echo(json.dumps(snippet_data))
451
-
452
-
453
- @cli.group()
454
- def search() -> None:
455
- """Search for snippets in the database."""
456
-
457
-
458
- # Utility for robust filter parsing
459
- def _parse_filters(
460
- language: str | None,
461
- author: str | None,
462
- created_after: str | None,
463
- created_before: str | None,
464
- source_repo: str | None,
465
- ) -> SnippetSearchFilters | None:
466
- from datetime import datetime
467
-
468
- # Normalize language to lowercase if provided
469
- norm_language = language.lower() if language else None
470
- # Try to parse dates, raise error if invalid
471
- parsed_created_after = None
472
- if created_after:
473
- try:
474
- parsed_created_after = datetime.fromisoformat(created_after)
475
- except ValueError as err:
476
- raise ValueError(
477
- f"Invalid date format for --created-after: {created_after}. "
478
- "Expected ISO 8601 format (YYYY-MM-DD)"
479
- ) from err
480
- parsed_created_before = None
481
- if created_before:
482
- try:
483
- parsed_created_before = datetime.fromisoformat(created_before)
484
- except ValueError as err:
485
- raise ValueError(
486
- f"Invalid date format for --created-before: {created_before}. "
487
- "Expected ISO 8601 format (YYYY-MM-DD)"
488
- ) from err
489
- # Return None if no filters provided, otherwise return SnippetSearchFilters
490
- # Check if any original parameters were provided (not just the parsed values)
491
- if any(
492
- [
493
- language,
494
- author,
495
- created_after,
496
- created_before,
497
- source_repo,
498
- ]
499
- ):
500
- return SnippetSearchFilters(
501
- language=norm_language,
502
- author=author,
503
- created_after=parsed_created_after,
504
- created_before=parsed_created_before,
505
- source_repo=source_repo,
506
- )
507
- return None
508
-
509
-
510
- @search.command()
511
- @click.argument("query")
512
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
513
- @click.option(
514
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
515
- )
516
- @click.option("--author", help="Filter by author name")
517
- @click.option(
518
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
519
- )
520
- @click.option(
521
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
522
- )
523
- @click.option(
524
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
525
- )
526
- @click.option("--output-format", default="text", help="Format to display snippets in")
527
- @with_app_context
528
- @wrap_async
529
- async def code( # noqa: PLR0913
530
- app_context: AppContext,
531
- query: str,
532
- top_k: int,
533
- language: str | None,
534
- author: str | None,
535
- created_after: str | None,
536
- created_before: str | None,
537
- source_repo: str | None,
538
- output_format: str,
539
- ) -> None:
540
- """Search for snippets using semantic code search.
541
-
542
- This works best if your query is code.
543
- """
544
- if not app_context.is_remote:
545
- await _search_local(
546
- app_context,
547
- code_query=query,
548
- top_k=top_k,
549
- language=language,
550
- author=author,
551
- created_after=created_after,
552
- created_before=created_before,
553
- source_repo=source_repo,
554
- output_format=output_format,
555
- event_name="kodit.cli.search.code",
556
- )
557
- else:
558
- await _search_remote(
559
- app_context,
560
- code_query=query,
561
- top_k=top_k,
562
- language=language,
563
- author=author,
564
- created_after=created_after,
565
- created_before=created_before,
566
- source_repo=source_repo,
567
- output_format=output_format,
568
- )
569
-
570
-
571
- @search.command()
572
- @click.argument("keywords", nargs=-1)
573
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
574
- @click.option(
575
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
576
- )
577
- @click.option("--author", help="Filter by author name")
578
- @click.option(
579
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
580
- )
581
- @click.option(
582
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
583
- )
584
- @click.option(
585
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
586
- )
587
- @click.option("--output-format", default="text", help="Format to display snippets in")
588
- @with_app_context
589
- @wrap_async
590
- async def keyword( # noqa: PLR0913
591
- app_context: AppContext,
592
- keywords: list[str],
593
- top_k: int,
594
- language: str | None,
595
- author: str | None,
596
- created_after: str | None,
597
- created_before: str | None,
598
- source_repo: str | None,
599
- output_format: str,
600
- ) -> None:
601
- """Search for snippets using keyword search."""
602
- # Override remote configuration if provided via CLI
603
- if not app_context.is_remote:
604
- await _search_local(
605
- app_context,
606
- keywords=keywords,
607
- top_k=top_k,
608
- language=language,
609
- author=author,
610
- created_after=created_after,
611
- created_before=created_before,
612
- source_repo=source_repo,
613
- output_format=output_format,
614
- event_name="kodit.cli.search.keyword",
615
- )
616
- else:
617
- await _search_remote(
618
- app_context,
619
- keywords=keywords,
620
- top_k=top_k,
621
- language=language,
622
- author=author,
623
- created_after=created_after,
624
- created_before=created_before,
625
- source_repo=source_repo,
626
- output_format=output_format,
627
- )
628
-
629
-
630
- @search.command()
631
- @click.argument("query")
632
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
633
- @click.option(
634
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
635
- )
636
- @click.option("--author", help="Filter by author name")
637
- @click.option(
638
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
639
- )
640
- @click.option(
641
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
642
- )
643
- @click.option(
644
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
645
- )
646
- @click.option("--output-format", default="text", help="Format to display snippets in")
647
- @with_app_context
648
- @wrap_async
649
- async def text( # noqa: PLR0913
650
- app_context: AppContext,
651
- query: str,
652
- top_k: int,
653
- language: str | None,
654
- author: str | None,
655
- created_after: str | None,
656
- created_before: str | None,
657
- source_repo: str | None,
658
- output_format: str,
659
- ) -> None:
660
- """Search for snippets using semantic text search.
661
-
662
- This works best if your query is text.
663
- """
664
- if not app_context.is_remote:
665
- await _search_local(
666
- app_context,
667
- text_query=query,
668
- top_k=top_k,
669
- language=language,
670
- author=author,
671
- created_after=created_after,
672
- created_before=created_before,
673
- source_repo=source_repo,
674
- output_format=output_format,
675
- event_name="kodit.cli.search.text",
676
- )
677
- else:
678
- await _search_remote(
679
- app_context,
680
- text_query=query,
681
- top_k=top_k,
682
- language=language,
683
- author=author,
684
- created_after=created_after,
685
- created_before=created_before,
686
- source_repo=source_repo,
687
- output_format=output_format,
688
- )
689
-
690
-
691
- @search.command()
692
- @click.option("--top-k", default=10, help="Number of snippets to retrieve")
693
- @click.option("--keywords", required=True, help="Comma separated list of keywords")
694
- @click.option("--code", required=True, help="Semantic code search query")
695
- @click.option("--text", required=True, help="Semantic text search query")
696
- @click.option(
697
- "--language", help="Filter by programming language (e.g., python, go, javascript)"
698
- )
699
- @click.option("--author", help="Filter by author name")
700
- @click.option(
701
- "--created-after", help="Filter snippets created after this date (YYYY-MM-DD)"
702
- )
703
- @click.option(
704
- "--created-before", help="Filter snippets created before this date (YYYY-MM-DD)"
705
- )
706
- @click.option(
707
- "--source-repo", help="Filter by source repository (e.g., github.com/example/repo)"
708
- )
709
- @click.option("--output-format", default="text", help="Format to display snippets in")
710
- @with_app_context
711
- @wrap_async
712
- async def hybrid( # noqa: PLR0913
713
- app_context: AppContext,
714
- top_k: int,
715
- keywords: str,
716
- code: str,
717
- text: str,
718
- language: str | None,
719
- author: str | None,
720
- created_after: str | None,
721
- created_before: str | None,
722
- source_repo: str | None,
723
- output_format: str,
724
- ) -> None:
725
- """Search for snippets using hybrid search."""
726
- # Parse keywords into a list of strings
727
- keywords_list = [k.strip().lower() for k in keywords.split(",")]
728
-
729
- if not app_context.is_remote:
730
- await _search_local(
731
- app_context,
732
- keywords=keywords_list,
733
- code_query=code,
734
- text_query=text,
735
- top_k=top_k,
736
- language=language,
737
- author=author,
738
- created_after=created_after,
739
- created_before=created_before,
740
- source_repo=source_repo,
741
- output_format=output_format,
742
- event_name="kodit.cli.search.hybrid",
743
- )
744
- else:
745
- await _search_remote(
746
- app_context,
747
- keywords=keywords_list,
748
- code_query=code,
749
- text_query=text,
750
- top_k=top_k,
751
- language=language,
752
- author=author,
753
- created_after=created_after,
754
- created_before=created_before,
755
- source_repo=source_repo,
756
- output_format=output_format,
757
- )
758
-
759
-
760
- @cli.group()
761
- def show() -> None:
762
- """Show information about elements in the database."""
763
-
764
-
765
- @show.command()
766
- @click.option("--by-path", help="File or directory path to search for snippets")
767
- @click.option("--by-source", help="Source URI to filter snippets by")
768
- @click.option("--output-format", default="text", help="Format to display snippets in")
769
- @with_app_context
770
- @wrap_async
771
- async def snippets(
772
- app_context: AppContext,
773
- by_path: str | None,
774
- by_source: str | None,
775
- output_format: str,
776
- ) -> None:
777
- """Show snippets with optional filtering by path or source."""
778
- if not app_context.is_remote:
779
- # Local mode
780
- log_event("kodit.cli.show.snippets")
781
- db = await app_context.get_db()
782
- service = create_cli_code_indexing_application_service(
783
- app_context=app_context,
784
- session_factory=db.session_factory,
785
- )
786
- snippets = await service.list_snippets(file_path=by_path, source_uri=by_source)
787
- if output_format == "text":
788
- click.echo(MultiSearchResult.to_string(snippets))
789
- elif output_format == "json":
790
- click.echo(MultiSearchResult.to_jsonlines(snippets))
791
- else:
792
- # Remote mode - not supported
793
- click.echo("⚠️ Warning: 'show snippets' is not implemented in remote mode")
794
- click.echo(
795
- "This functionality is only available when connected directly "
796
- "to the database"
797
- )
798
- click.echo("Use 'kodit search' commands instead for remote snippet retrieval")
799
-
800
-
801
49
  @cli.command()
802
50
  @click.option("--host", default="127.0.0.1", help="Host to bind the server to")
803
51
  @click.option("--port", default=8080, help="Port to bind the server to")
@@ -813,6 +61,7 @@ def serve(
813
61
  # Disable uvicorn's websockets deprecation warnings
814
62
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
815
63
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="uvicorn")
64
+ warnings.filterwarnings("ignore", category=DeprecationWarning, module="httpx")
816
65
 
817
66
  # Configure uvicorn with graceful shutdown
818
67
  config = uvicorn.Config(