kodit 0.3.6__py3-none-any.whl → 0.3.7__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.
- kodit/_version.py +2 -2
- kodit/app.py +12 -5
- kodit/cli.py +20 -10
- kodit/infrastructure/bm25/local_bm25_repository.py +3 -3
- kodit/infrastructure/embedding/embedding_factory.py +8 -3
- kodit/infrastructure/enrichment/enrichment_factory.py +5 -1
- kodit/infrastructure/enrichment/local_enrichment_provider.py +5 -5
- kodit/infrastructure/ui/progress.py +1 -1
- kodit/log.py +1 -1
- kodit/mcp.py +159 -131
- kodit/middleware.py +2 -2
- {kodit-0.3.6.dist-info → kodit-0.3.7.dist-info}/METADATA +3 -2
- {kodit-0.3.6.dist-info → kodit-0.3.7.dist-info}/RECORD +16 -17
- kodit/infrastructure/indexing/indexing_factory.py +0 -30
- {kodit-0.3.6.dist-info → kodit-0.3.7.dist-info}/WHEEL +0 -0
- {kodit-0.3.6.dist-info → kodit-0.3.7.dist-info}/entry_points.txt +0 -0
- {kodit-0.3.6.dist-info → kodit-0.3.7.dist-info}/licenses/LICENSE +0 -0
kodit/_version.py
CHANGED
kodit/app.py
CHANGED
|
@@ -51,14 +51,19 @@ async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
|
|
51
51
|
await _auto_indexing_service.stop()
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
# See https://gofastmcp.com/
|
|
55
|
-
|
|
54
|
+
# See https://gofastmcp.com/integrations/fastapi#mounting-an-mcp-server
|
|
55
|
+
mcp_sse_app = mcp.http_app(transport="sse", path="/")
|
|
56
|
+
mcp_http_app = mcp.http_app(transport="http", path="/")
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
@asynccontextmanager
|
|
59
60
|
async def combined_lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
60
61
|
"""Combine app and MCP lifespans."""
|
|
61
|
-
async with
|
|
62
|
+
async with (
|
|
63
|
+
app_lifespan(app),
|
|
64
|
+
mcp_sse_app.router.lifespan_context(app),
|
|
65
|
+
mcp_http_app.router.lifespan_context(app),
|
|
66
|
+
):
|
|
62
67
|
yield
|
|
63
68
|
|
|
64
69
|
|
|
@@ -82,8 +87,10 @@ async def healthz() -> dict[str, str]:
|
|
|
82
87
|
|
|
83
88
|
|
|
84
89
|
# Add mcp routes last, otherwise previous routes aren't added
|
|
85
|
-
|
|
90
|
+
# Mount both apps at root - they have different internal paths
|
|
91
|
+
app.mount("/sse", mcp_sse_app)
|
|
92
|
+
app.mount("/mcp", mcp_http_app)
|
|
86
93
|
|
|
87
94
|
# Wrap the entire app with ASGI middleware after all routes are added to suppress
|
|
88
95
|
# CancelledError at the ASGI level
|
|
89
|
-
app = ASGICancelledErrorMiddleware(app)
|
|
96
|
+
app = ASGICancelledErrorMiddleware(app) # type: ignore[assignment]
|
kodit/cli.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Command line interface for kodit."""
|
|
2
2
|
|
|
3
3
|
import signal
|
|
4
|
+
import warnings
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
9
|
import structlog
|
|
9
10
|
import uvicorn
|
|
10
|
-
from pytable_formatter import Cell, Table
|
|
11
|
+
from pytable_formatter import Cell, Table # type: ignore[import-untyped]
|
|
11
12
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
13
|
|
|
13
14
|
from kodit.application.factories.code_indexing_factory import (
|
|
@@ -32,6 +33,7 @@ from kodit.infrastructure.ui.progress import (
|
|
|
32
33
|
create_multi_stage_progress_callback,
|
|
33
34
|
)
|
|
34
35
|
from kodit.log import configure_logging, configure_telemetry, log_event
|
|
36
|
+
from kodit.mcp import create_stdio_mcp_server
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
@click.group(context_settings={"max_content_width": 100})
|
|
@@ -54,7 +56,7 @@ def cli(
|
|
|
54
56
|
config = AppContext()
|
|
55
57
|
# First check if env-file is set and reload config if it is
|
|
56
58
|
if env_file:
|
|
57
|
-
config = AppContext(_env_file=env_file) # type: ignore[
|
|
59
|
+
config = AppContext(_env_file=env_file) # type: ignore[call-arg]
|
|
58
60
|
|
|
59
61
|
configure_logging(config)
|
|
60
62
|
configure_telemetry(config)
|
|
@@ -100,7 +102,8 @@ async def _handle_sync(
|
|
|
100
102
|
# Filter indexes that match the provided sources
|
|
101
103
|
source_uris = set(sources)
|
|
102
104
|
indexes_to_sync = [
|
|
103
|
-
index
|
|
105
|
+
index
|
|
106
|
+
for index in all_indexes
|
|
104
107
|
if str(index.source.working_copy.remote_uri) in source_uris
|
|
105
108
|
]
|
|
106
109
|
|
|
@@ -124,9 +127,7 @@ async def _handle_sync(
|
|
|
124
127
|
click.echo(f"✓ Sync completed: {index.source.working_copy.remote_uri}")
|
|
125
128
|
except Exception as e:
|
|
126
129
|
log.exception("Sync failed", index_id=index.id, error=e)
|
|
127
|
-
click.echo(
|
|
128
|
-
f"✗ Sync failed: {index.source.working_copy.remote_uri} - {e}"
|
|
129
|
-
)
|
|
130
|
+
click.echo(f"✗ Sync failed: {index.source.working_copy.remote_uri} - {e}")
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
async def _handle_list_indexes(index_query_service: IndexQueryService) -> None:
|
|
@@ -159,9 +160,7 @@ async def _handle_list_indexes(index_query_service: IndexQueryService) -> None:
|
|
|
159
160
|
@click.option(
|
|
160
161
|
"--auto-index", is_flag=True, help="Index all configured auto-index sources"
|
|
161
162
|
)
|
|
162
|
-
@click.option(
|
|
163
|
-
"--sync", is_flag=True, help="Sync existing indexes with their remotes"
|
|
164
|
-
)
|
|
163
|
+
@click.option("--sync", is_flag=True, help="Sync existing indexes with their remotes")
|
|
165
164
|
@with_app_context
|
|
166
165
|
@with_session
|
|
167
166
|
async def index(
|
|
@@ -561,11 +560,15 @@ def serve(
|
|
|
561
560
|
host: str,
|
|
562
561
|
port: int,
|
|
563
562
|
) -> None:
|
|
564
|
-
"""Start the kodit
|
|
563
|
+
"""Start the kodit HTTP/SSE server with FastAPI integration."""
|
|
565
564
|
log = structlog.get_logger(__name__)
|
|
566
565
|
log.info("Starting kodit server", host=host, port=port)
|
|
567
566
|
log_event("kodit.cli.serve")
|
|
568
567
|
|
|
568
|
+
# Disable uvicorn's websockets deprecation warnings
|
|
569
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
|
|
570
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="uvicorn")
|
|
571
|
+
|
|
569
572
|
# Configure uvicorn with graceful shutdown
|
|
570
573
|
config = uvicorn.Config(
|
|
571
574
|
"kodit.app:app",
|
|
@@ -587,6 +590,13 @@ def serve(
|
|
|
587
590
|
server.run()
|
|
588
591
|
|
|
589
592
|
|
|
593
|
+
@cli.command()
|
|
594
|
+
def stdio() -> None:
|
|
595
|
+
"""Start the kodit MCP server in STDIO mode."""
|
|
596
|
+
log_event("kodit.cli.stdio")
|
|
597
|
+
create_stdio_mcp_server()
|
|
598
|
+
|
|
599
|
+
|
|
590
600
|
@cli.command()
|
|
591
601
|
def version() -> None:
|
|
592
602
|
"""Show the version of kodit."""
|
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
import aiofiles
|
|
10
|
-
import Stemmer
|
|
10
|
+
import Stemmer # type: ignore[import-not-found]
|
|
11
11
|
import structlog
|
|
12
12
|
|
|
13
13
|
from kodit.domain.services.bm25_service import BM25Repository
|
|
@@ -19,8 +19,8 @@ from kodit.domain.value_objects import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
|
-
import bm25s
|
|
23
|
-
from bm25s.tokenization import Tokenized
|
|
22
|
+
import bm25s # type: ignore[import-untyped]
|
|
23
|
+
from bm25s.tokenization import Tokenized # type: ignore[import-untyped]
|
|
24
24
|
|
|
25
25
|
SNIPPET_IDS_FILE = "snippet_ids.jsonl"
|
|
26
26
|
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"""Factory for creating embedding services with DDD architecture."""
|
|
2
2
|
|
|
3
|
+
from openai import AsyncOpenAI
|
|
3
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
5
|
|
|
5
6
|
from kodit.config import AppContext, Endpoint
|
|
6
|
-
from kodit.domain.services.embedding_service import
|
|
7
|
+
from kodit.domain.services.embedding_service import (
|
|
8
|
+
EmbeddingDomainService,
|
|
9
|
+
EmbeddingProvider,
|
|
10
|
+
VectorSearchRepository,
|
|
11
|
+
)
|
|
7
12
|
from kodit.infrastructure.embedding.embedding_providers.local_embedding_provider import ( # noqa: E501
|
|
8
13
|
CODE,
|
|
9
14
|
LocalEmbeddingProvider,
|
|
@@ -38,11 +43,10 @@ def embedding_domain_service_factory(
|
|
|
38
43
|
embedding_repository = SqlAlchemyEmbeddingRepository(session=session)
|
|
39
44
|
|
|
40
45
|
# Create embedding provider
|
|
46
|
+
embedding_provider: EmbeddingProvider | None = None
|
|
41
47
|
endpoint = _get_endpoint_configuration(app_context)
|
|
42
48
|
if endpoint and endpoint.type == "openai":
|
|
43
49
|
log_event("kodit.embedding", {"provider": "openai"})
|
|
44
|
-
from openai import AsyncOpenAI
|
|
45
|
-
|
|
46
50
|
embedding_provider = OpenAIEmbeddingProvider(
|
|
47
51
|
openai_client=AsyncOpenAI(
|
|
48
52
|
api_key=endpoint.api_key or "default",
|
|
@@ -57,6 +61,7 @@ def embedding_domain_service_factory(
|
|
|
57
61
|
embedding_provider = LocalEmbeddingProvider(CODE)
|
|
58
62
|
|
|
59
63
|
# Create vector search repository based on configuration
|
|
64
|
+
vector_search_repository: VectorSearchRepository | None = None
|
|
60
65
|
if app_context.default_search.provider == "vectorchord":
|
|
61
66
|
log_event("kodit.database", {"provider": "vectorchord"})
|
|
62
67
|
vector_search_repository = VectorChordVectorSearchRepository(
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""Enrichment factory for creating enrichment domain services."""
|
|
2
2
|
|
|
3
3
|
from kodit.config import AppContext, Endpoint
|
|
4
|
-
from kodit.domain.services.enrichment_service import
|
|
4
|
+
from kodit.domain.services.enrichment_service import (
|
|
5
|
+
EnrichmentDomainService,
|
|
6
|
+
EnrichmentProvider,
|
|
7
|
+
)
|
|
5
8
|
from kodit.infrastructure.enrichment.local_enrichment_provider import (
|
|
6
9
|
LocalEnrichmentProvider,
|
|
7
10
|
)
|
|
@@ -38,6 +41,7 @@ def enrichment_domain_service_factory(
|
|
|
38
41
|
"""
|
|
39
42
|
endpoint = _get_endpoint_configuration(app_context)
|
|
40
43
|
|
|
44
|
+
enrichment_provider: EnrichmentProvider | None = None
|
|
41
45
|
if endpoint and endpoint.type == "openai":
|
|
42
46
|
log_event("kodit.enrichment", {"provider": "openai"})
|
|
43
47
|
from openai import AsyncOpenAI
|
|
@@ -81,7 +81,7 @@ class LocalEnrichmentProvider(EnrichmentProvider):
|
|
|
81
81
|
prompts = [
|
|
82
82
|
{
|
|
83
83
|
"id": req.snippet_id,
|
|
84
|
-
"text": self.tokenizer.apply_chat_template(
|
|
84
|
+
"text": self.tokenizer.apply_chat_template( # type: ignore[attr-defined]
|
|
85
85
|
[
|
|
86
86
|
{"role": "system", "content": ENRICHMENT_SYSTEM_PROMPT},
|
|
87
87
|
{"role": "user", "content": req.text},
|
|
@@ -95,18 +95,18 @@ class LocalEnrichmentProvider(EnrichmentProvider):
|
|
|
95
95
|
]
|
|
96
96
|
|
|
97
97
|
for prompt in prompts:
|
|
98
|
-
model_inputs = self.tokenizer(
|
|
98
|
+
model_inputs = self.tokenizer( # type: ignore[misc]
|
|
99
99
|
prompt["text"],
|
|
100
100
|
return_tensors="pt",
|
|
101
101
|
padding=True,
|
|
102
102
|
truncation=True,
|
|
103
|
-
).to(self.model.device)
|
|
104
|
-
generated_ids = self.model.generate(
|
|
103
|
+
).to(self.model.device) # type: ignore[attr-defined]
|
|
104
|
+
generated_ids = self.model.generate( # type: ignore[attr-defined]
|
|
105
105
|
**model_inputs, max_new_tokens=self.context_window
|
|
106
106
|
)
|
|
107
107
|
input_ids = model_inputs["input_ids"][0]
|
|
108
108
|
output_ids = generated_ids[0][len(input_ids) :].tolist()
|
|
109
|
-
content = self.tokenizer.decode(output_ids, skip_special_tokens=True).strip(
|
|
109
|
+
content = self.tokenizer.decode(output_ids, skip_special_tokens=True).strip( # type: ignore[attr-defined]
|
|
110
110
|
"\n"
|
|
111
111
|
)
|
|
112
112
|
yield EnrichmentResponse(
|
kodit/log.py
CHANGED
|
@@ -12,7 +12,7 @@ from functools import lru_cache
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
|
-
import rudderstack.analytics as rudder_analytics
|
|
15
|
+
import rudderstack.analytics as rudder_analytics # type: ignore[import-untyped]
|
|
16
16
|
import structlog
|
|
17
17
|
from structlog.types import EventDict
|
|
18
18
|
|
kodit/mcp.py
CHANGED
|
@@ -58,10 +58,160 @@ async def mcp_lifespan(_: FastMCP) -> AsyncIterator[MCPContext]:
|
|
|
58
58
|
yield MCPContext(session=session, app_context=app_context)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
def create_mcp_server(name: str, instructions: str | None = None) -> FastMCP:
|
|
62
|
+
"""Create a FastMCP server with common configuration."""
|
|
63
|
+
return FastMCP(
|
|
64
|
+
name,
|
|
65
|
+
lifespan=mcp_lifespan,
|
|
66
|
+
instructions=instructions,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def register_mcp_tools(mcp_server: FastMCP) -> None:
|
|
71
|
+
"""Register MCP tools on the provided FastMCP instance."""
|
|
72
|
+
|
|
73
|
+
@mcp_server.tool()
|
|
74
|
+
async def search( # noqa: PLR0913
|
|
75
|
+
ctx: Context,
|
|
76
|
+
user_intent: Annotated[
|
|
77
|
+
str,
|
|
78
|
+
Field(
|
|
79
|
+
description="Think about what the user wants to achieve. Describe the "
|
|
80
|
+
"user's intent in one sentence."
|
|
81
|
+
),
|
|
82
|
+
],
|
|
83
|
+
related_file_paths: Annotated[
|
|
84
|
+
list[Path],
|
|
85
|
+
Field(
|
|
86
|
+
description=(
|
|
87
|
+
"A list of absolute paths to files that are relevant to the "
|
|
88
|
+
"user's intent."
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
related_file_contents: Annotated[
|
|
93
|
+
list[str],
|
|
94
|
+
Field(
|
|
95
|
+
description=(
|
|
96
|
+
"A list of the contents of the files that are relevant to the "
|
|
97
|
+
"user's intent."
|
|
98
|
+
)
|
|
99
|
+
),
|
|
100
|
+
],
|
|
101
|
+
keywords: Annotated[
|
|
102
|
+
list[str],
|
|
103
|
+
Field(
|
|
104
|
+
description=(
|
|
105
|
+
"A list of keywords that are relevant to the desired outcome."
|
|
106
|
+
)
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
language: Annotated[
|
|
110
|
+
str | None,
|
|
111
|
+
Field(
|
|
112
|
+
description="Filter by language (e.g., 'python', 'go', 'javascript')."
|
|
113
|
+
),
|
|
114
|
+
] = None,
|
|
115
|
+
author: Annotated[
|
|
116
|
+
str | None,
|
|
117
|
+
Field(description=("Filter to search for snippets by a specific author.")),
|
|
118
|
+
] = None,
|
|
119
|
+
created_after: Annotated[
|
|
120
|
+
str | None,
|
|
121
|
+
Field(
|
|
122
|
+
description=(
|
|
123
|
+
"Filter for snippets created after this date "
|
|
124
|
+
"(ISO format: YYYY-MM-DD)."
|
|
125
|
+
)
|
|
126
|
+
),
|
|
127
|
+
] = None,
|
|
128
|
+
created_before: Annotated[
|
|
129
|
+
str | None,
|
|
130
|
+
Field(
|
|
131
|
+
description=(
|
|
132
|
+
"Filter for snippets created before this date "
|
|
133
|
+
"(ISO format: YYYY-MM-DD)."
|
|
134
|
+
)
|
|
135
|
+
),
|
|
136
|
+
] = None,
|
|
137
|
+
source_repo: Annotated[
|
|
138
|
+
str | None,
|
|
139
|
+
Field(
|
|
140
|
+
description=(
|
|
141
|
+
"Filter results by project source repository (e.g., "
|
|
142
|
+
"github.com/example/repo)"
|
|
143
|
+
)
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
) -> str:
|
|
147
|
+
"""Search for pre-existing examples of relevant code.
|
|
148
|
+
|
|
149
|
+
⚠️ **ENFORCEMENT:**
|
|
150
|
+
For *every* user request that involves writing or modifying code (of any
|
|
151
|
+
language or domain), the assistant's *first* action **must** be to call the
|
|
152
|
+
kodit.search MCP tool. You may only produce or edit code *after* that tool
|
|
153
|
+
call and its successful result.
|
|
154
|
+
"""
|
|
155
|
+
# This docstring is used by the AI assistant to decide when to call the tool.
|
|
156
|
+
# If you want to update it, please make sure you thoroughly test the
|
|
157
|
+
# assistant's response to the updated tool call. See:
|
|
158
|
+
# tests/experiments/cline-prompt-regression-tests/cline_prompt_test.py
|
|
159
|
+
|
|
160
|
+
log = structlog.get_logger(__name__)
|
|
161
|
+
|
|
162
|
+
log.debug(
|
|
163
|
+
"Searching for relevant snippets",
|
|
164
|
+
user_intent=user_intent,
|
|
165
|
+
keywords=keywords,
|
|
166
|
+
file_count=len(related_file_paths),
|
|
167
|
+
file_paths=related_file_paths,
|
|
168
|
+
file_contents=related_file_contents,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
mcp_context: MCPContext = ctx.request_context.lifespan_context
|
|
172
|
+
|
|
173
|
+
# Use the unified application service
|
|
174
|
+
service = create_code_indexing_application_service(
|
|
175
|
+
app_context=mcp_context.app_context,
|
|
176
|
+
session=mcp_context.session,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
log.debug("Searching for snippets")
|
|
180
|
+
|
|
181
|
+
# Create filters if any filter parameters are provided
|
|
182
|
+
filters = SnippetSearchFilters.from_cli_params(
|
|
183
|
+
language=language,
|
|
184
|
+
author=author,
|
|
185
|
+
created_after=created_after,
|
|
186
|
+
created_before=created_before,
|
|
187
|
+
source_repo=source_repo,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
search_request = MultiSearchRequest(
|
|
191
|
+
keywords=keywords,
|
|
192
|
+
code_query="\n".join(related_file_contents),
|
|
193
|
+
text_query=user_intent,
|
|
194
|
+
filters=filters,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
log.debug("Searching for snippets")
|
|
198
|
+
snippets = await service.search(request=search_request)
|
|
199
|
+
|
|
200
|
+
log.debug("Fusing output")
|
|
201
|
+
output = MultiSearchResult.to_jsonlines(results=snippets)
|
|
202
|
+
|
|
203
|
+
log.debug("Output", output=output)
|
|
204
|
+
return output
|
|
205
|
+
|
|
206
|
+
@mcp_server.tool()
|
|
207
|
+
async def get_version() -> str:
|
|
208
|
+
"""Get the version of the kodit project."""
|
|
209
|
+
return version
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# FastAPI-integrated MCP server
|
|
213
|
+
mcp = create_mcp_server(
|
|
214
|
+
name="Kodit",
|
|
65
215
|
instructions=(
|
|
66
216
|
"This server is used to assist with code generation by retrieving "
|
|
67
217
|
"code examples related to the user's intent."
|
|
@@ -69,132 +219,10 @@ mcp = FastMCP(
|
|
|
69
219
|
),
|
|
70
220
|
)
|
|
71
221
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
async def search( # noqa: PLR0913
|
|
75
|
-
ctx: Context,
|
|
76
|
-
user_intent: Annotated[
|
|
77
|
-
str,
|
|
78
|
-
Field(
|
|
79
|
-
description="Think about what the user wants to achieve. Describe the "
|
|
80
|
-
"user's intent in one sentence."
|
|
81
|
-
),
|
|
82
|
-
],
|
|
83
|
-
related_file_paths: Annotated[
|
|
84
|
-
list[Path],
|
|
85
|
-
Field(
|
|
86
|
-
description="A list of absolute paths to files that are relevant to the "
|
|
87
|
-
"user's intent."
|
|
88
|
-
),
|
|
89
|
-
],
|
|
90
|
-
related_file_contents: Annotated[
|
|
91
|
-
list[str],
|
|
92
|
-
Field(
|
|
93
|
-
description="A list of the contents of the files that are relevant to the "
|
|
94
|
-
"user's intent."
|
|
95
|
-
),
|
|
96
|
-
],
|
|
97
|
-
keywords: Annotated[
|
|
98
|
-
list[str],
|
|
99
|
-
Field(
|
|
100
|
-
description="A list of keywords that are relevant to the desired outcome."
|
|
101
|
-
),
|
|
102
|
-
],
|
|
103
|
-
language: Annotated[
|
|
104
|
-
str | None,
|
|
105
|
-
Field(description="Filter by language (e.g., 'python', 'go', 'javascript')."),
|
|
106
|
-
] = None,
|
|
107
|
-
author: Annotated[
|
|
108
|
-
str | None,
|
|
109
|
-
Field(description=("Filter to search for snippets by a specific author.")),
|
|
110
|
-
] = None,
|
|
111
|
-
created_after: Annotated[
|
|
112
|
-
str | None,
|
|
113
|
-
Field(
|
|
114
|
-
description=(
|
|
115
|
-
"Filter for snippets created after this date (ISO format: YYYY-MM-DD)."
|
|
116
|
-
)
|
|
117
|
-
),
|
|
118
|
-
] = None,
|
|
119
|
-
created_before: Annotated[
|
|
120
|
-
str | None,
|
|
121
|
-
Field(
|
|
122
|
-
description=(
|
|
123
|
-
"Filter for snippets created before this date (ISO format: YYYY-MM-DD)."
|
|
124
|
-
)
|
|
125
|
-
),
|
|
126
|
-
] = None,
|
|
127
|
-
source_repo: Annotated[
|
|
128
|
-
str | None,
|
|
129
|
-
Field(
|
|
130
|
-
description=(
|
|
131
|
-
"Filter results by project source repository (e.g., "
|
|
132
|
-
"github.com/example/repo)"
|
|
133
|
-
)
|
|
134
|
-
),
|
|
135
|
-
] = None,
|
|
136
|
-
) -> str:
|
|
137
|
-
"""Search for pre-existing examples of relevant code.
|
|
138
|
-
|
|
139
|
-
⚠️ **ENFORCEMENT:**
|
|
140
|
-
For *every* user request that involves writing or modifying code (of any language or
|
|
141
|
-
domain), the assistant's *first* action **must** be to call the kodit.search MCP
|
|
142
|
-
tool. You may only produce or edit code *after* that tool call and its successful
|
|
143
|
-
result.
|
|
144
|
-
"""
|
|
145
|
-
# This docstring is used by the AI assistant to decide when to call the tool. If you
|
|
146
|
-
# want to update it, please make sure you thoroughly test the assistant's response
|
|
147
|
-
# to the updated tool call. See:
|
|
148
|
-
# tests/experiments/cline-prompt-regression-tests/cline_prompt_test.py
|
|
149
|
-
|
|
150
|
-
log = structlog.get_logger(__name__)
|
|
151
|
-
|
|
152
|
-
log.debug(
|
|
153
|
-
"Searching for relevant snippets",
|
|
154
|
-
user_intent=user_intent,
|
|
155
|
-
keywords=keywords,
|
|
156
|
-
file_count=len(related_file_paths),
|
|
157
|
-
file_paths=related_file_paths,
|
|
158
|
-
file_contents=related_file_contents,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
mcp_context: MCPContext = ctx.request_context.lifespan_context
|
|
162
|
-
|
|
163
|
-
# Use the unified application service
|
|
164
|
-
service = create_code_indexing_application_service(
|
|
165
|
-
app_context=mcp_context.app_context,
|
|
166
|
-
session=mcp_context.session,
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
log.debug("Searching for snippets")
|
|
170
|
-
|
|
171
|
-
# Create filters if any filter parameters are provided
|
|
172
|
-
filters = SnippetSearchFilters.from_cli_params(
|
|
173
|
-
language=language,
|
|
174
|
-
author=author,
|
|
175
|
-
created_after=created_after,
|
|
176
|
-
created_before=created_before,
|
|
177
|
-
source_repo=source_repo,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
search_request = MultiSearchRequest(
|
|
181
|
-
keywords=keywords,
|
|
182
|
-
code_query="\n".join(related_file_contents),
|
|
183
|
-
text_query=user_intent,
|
|
184
|
-
filters=filters,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
log.debug("Searching for snippets")
|
|
188
|
-
snippets = await service.search(request=search_request)
|
|
189
|
-
|
|
190
|
-
log.debug("Fusing output")
|
|
191
|
-
output = MultiSearchResult.to_jsonlines(results=snippets)
|
|
192
|
-
|
|
193
|
-
log.debug("Output", output=output)
|
|
194
|
-
return output
|
|
222
|
+
# Register the MCP tools
|
|
223
|
+
register_mcp_tools(mcp)
|
|
195
224
|
|
|
196
225
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
""
|
|
200
|
-
return version
|
|
226
|
+
def create_stdio_mcp_server() -> None:
|
|
227
|
+
"""Create and run a STDIO MCP server for kodit."""
|
|
228
|
+
mcp.run(transport="stdio", show_banner=False)
|
kodit/middleware.py
CHANGED
|
@@ -36,8 +36,8 @@ async def logging_middleware(request: Request, call_next: Callable) -> Response:
|
|
|
36
36
|
finally:
|
|
37
37
|
process_time = time.perf_counter_ns() - start_time
|
|
38
38
|
status_code = response.status_code
|
|
39
|
-
client_host = request.client.host
|
|
40
|
-
client_port = request.client.port
|
|
39
|
+
client_host = request.client.host if request.client else None
|
|
40
|
+
client_port = request.client.port if request.client else None
|
|
41
41
|
http_method = request.method
|
|
42
42
|
http_version = request.scope["http_version"]
|
|
43
43
|
# Recreate the Uvicorn access log format, but add all parameters as
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kodit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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/
|
|
@@ -30,7 +30,7 @@ Requires-Dist: click>=8.1.8
|
|
|
30
30
|
Requires-Dist: colorama>=0.4.6
|
|
31
31
|
Requires-Dist: dotenv>=0.9.9
|
|
32
32
|
Requires-Dist: fastapi[standard]>=0.115.12
|
|
33
|
-
Requires-Dist: fastmcp>=2.
|
|
33
|
+
Requires-Dist: fastmcp>=2.10.4
|
|
34
34
|
Requires-Dist: gitpython>=3.1.44
|
|
35
35
|
Requires-Dist: hf-xet>=1.1.2
|
|
36
36
|
Requires-Dist: httpx-retries>=0.3.2
|
|
@@ -38,6 +38,7 @@ Requires-Dist: httpx>=0.28.1
|
|
|
38
38
|
Requires-Dist: openai>=1.82.0
|
|
39
39
|
Requires-Dist: pathspec>=0.12.1
|
|
40
40
|
Requires-Dist: pydantic-settings>=2.9.1
|
|
41
|
+
Requires-Dist: pystemmer>=3.0.0
|
|
41
42
|
Requires-Dist: pytable-formatter>=0.1.1
|
|
42
43
|
Requires-Dist: rudder-sdk-python>=2.1.4
|
|
43
44
|
Requires-Dist: sentence-transformers>=4.1.0
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
|
|
2
2
|
kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
|
|
3
|
-
kodit/_version.py,sha256=
|
|
4
|
-
kodit/app.py,sha256=
|
|
5
|
-
kodit/cli.py,sha256=
|
|
3
|
+
kodit/_version.py,sha256=Mzy3NhAqfY25ymly5Ji2ZQDBbOVx_D1-5ADbqwHbtak,511
|
|
4
|
+
kodit/app.py,sha256=3_smkoioIQEYtRLIGHDtgGkmkP6Movd5CygQEMOStP8,3043
|
|
5
|
+
kodit/cli.py,sha256=ZOS_VzCHGjJRZzZpaVR00QXSPIwRXPYu-pTrbEtlyR0,19328
|
|
6
6
|
kodit/config.py,sha256=Dh1ybowJyOqZIoQnvF3DykaX2ze1MC0Rjs9oK1iZ7cs,8060
|
|
7
7
|
kodit/database.py,sha256=kI9yBm4uunsgV4-QeVoCBL0wLzU4kYmYv5qZilGnbPE,1740
|
|
8
|
-
kodit/log.py,sha256=
|
|
9
|
-
kodit/mcp.py,sha256=
|
|
10
|
-
kodit/middleware.py,sha256=
|
|
8
|
+
kodit/log.py,sha256=r0o7IpNvV-dNW-cTNWu1ouJF71vD9wHYzvqDPzeDYfw,8768
|
|
9
|
+
kodit/mcp.py,sha256=aEcPc8dQiZaR0AswCZZNxcm_rhhUZNsEBimYti0ibSI,7221
|
|
10
|
+
kodit/middleware.py,sha256=xBmC6keFeNsS0y8XUcIKzJzuefkE9bq2UhW1fR5cqxg,2770
|
|
11
11
|
kodit/reporting.py,sha256=icce1ZyiADsA_Qz-mSjgn2H4SSqKuGfLKnw-yrl9nsg,2722
|
|
12
12
|
kodit/application/__init__.py,sha256=mH50wTpgP9dhbKztFsL8Dda9Hi18TSnMVxXtpp4aGOA,35
|
|
13
13
|
kodit/application/factories/__init__.py,sha256=bU5CvEnaBePZ7JbkCOp1MGTNP752bnU2uEqmfy5FdRk,37
|
|
@@ -30,14 +30,14 @@ kodit/domain/services/index_service.py,sha256=ezVGbWdII25adri4_yyvsAF2eJOt4xmoHR
|
|
|
30
30
|
kodit/infrastructure/__init__.py,sha256=HzEYIjoXnkz_i_MHO2e0sIVYweUcRnl2RpyBiTbMObU,28
|
|
31
31
|
kodit/infrastructure/bm25/__init__.py,sha256=DmGbrEO34FOJy4e685BbyxLA7gPW1eqs2gAxsp6JOuM,34
|
|
32
32
|
kodit/infrastructure/bm25/bm25_factory.py,sha256=I4eo7qRslnyXIRkBf-StZ5ga2Evrr5J5YFocTChFD3g,884
|
|
33
|
-
kodit/infrastructure/bm25/local_bm25_repository.py,sha256=
|
|
33
|
+
kodit/infrastructure/bm25/local_bm25_repository.py,sha256=rDx8orGhg38n0zSpybEt5QLOWHY2Yt5IxIf9UtlhXXU,4629
|
|
34
34
|
kodit/infrastructure/bm25/vectorchord_bm25_repository.py,sha256=Jyic55V-38XeTad462Ge751iKyc0X8RNVBM9pr_DVJk,7439
|
|
35
35
|
kodit/infrastructure/cloning/__init__.py,sha256=IzIvX-yeRRFZ-lfvPVSEe_qXszO6DGQdjKwwDigexyQ,30
|
|
36
36
|
kodit/infrastructure/cloning/metadata.py,sha256=GD2UnCC1oR82RD0SVUqk9CJOqzXPxhOAHVOp7jqN6Qc,3571
|
|
37
37
|
kodit/infrastructure/cloning/git/__init__.py,sha256=20ePcp0qE6BuLsjsv4KYB1DzKhMIMsPXwEqIEZtjTJs,34
|
|
38
38
|
kodit/infrastructure/cloning/git/working_copy.py,sha256=SvaLAJa7FRsLuWjJz-xUeTJ-EkpPi_rIOP-gyuHLIdM,1835
|
|
39
39
|
kodit/infrastructure/embedding/__init__.py,sha256=F-8nLlWAerYJ0MOIA4tbXHLan8bW5rRR84vzxx6tRKI,39
|
|
40
|
-
kodit/infrastructure/embedding/embedding_factory.py,sha256=
|
|
40
|
+
kodit/infrastructure/embedding/embedding_factory.py,sha256=SOVX3wGxW8Qo2lJmMZ0kRssXKn6A4W7DmSpgo0zWTfM,3625
|
|
41
41
|
kodit/infrastructure/embedding/local_vector_search_repository.py,sha256=ExweyNEL5cP-g3eDhGqZSih7zhdOrop2WdFPPJL-tB4,3505
|
|
42
42
|
kodit/infrastructure/embedding/vectorchord_vector_search_repository.py,sha256=PIoU0HsDlaoXDXnGjOR0LAkAcW4JiE3ymJy_SBhEopc,8030
|
|
43
43
|
kodit/infrastructure/embedding/embedding_providers/__init__.py,sha256=qeZ-oAIAxMl5QqebGtO1lq-tHjl_ucAwOXePklcwwGk,34
|
|
@@ -46,8 +46,8 @@ kodit/infrastructure/embedding/embedding_providers/hash_embedding_provider.py,sh
|
|
|
46
46
|
kodit/infrastructure/embedding/embedding_providers/local_embedding_provider.py,sha256=U5fc8jUP8wF-nq1zo-CfSbJbLQyE-3muKmRCaYGtytk,4387
|
|
47
47
|
kodit/infrastructure/embedding/embedding_providers/openai_embedding_provider.py,sha256=LIK9Iir7geraZoqiaNbeHv3hXrghZRDpYGJDEjZaqzQ,4086
|
|
48
48
|
kodit/infrastructure/enrichment/__init__.py,sha256=8acZKNzql8Fs0lceFu9U3KoUrOptRBtVIxr_Iw6lz3Y,40
|
|
49
|
-
kodit/infrastructure/enrichment/enrichment_factory.py,sha256=
|
|
50
|
-
kodit/infrastructure/enrichment/local_enrichment_provider.py,sha256=
|
|
49
|
+
kodit/infrastructure/enrichment/enrichment_factory.py,sha256=2h-c96FWgSi0UG6IeL9Yjae80KYdzJqes5HIJQrkpc8,1919
|
|
50
|
+
kodit/infrastructure/enrichment/local_enrichment_provider.py,sha256=7Vlwu1jPJ5KNUn1a51M1P-laUd5YVFJA8EeH6KO-95k,3960
|
|
51
51
|
kodit/infrastructure/enrichment/null_enrichment_provider.py,sha256=DhZkJBnkvXg_XSAs-oKiFnKqYFPnmTl3ikdxrqeEfbc,713
|
|
52
52
|
kodit/infrastructure/enrichment/openai_enrichment_provider.py,sha256=fenq4HiJ2UkrzsE2D0A0qpmro38z9mKaIzKKU5v7hnY,3189
|
|
53
53
|
kodit/infrastructure/git/__init__.py,sha256=0iMosFzudj4_xNIMe2SRbV6l5bWqkjnUsZoFsoZFuM8,33
|
|
@@ -57,7 +57,6 @@ kodit/infrastructure/ignore/ignore_pattern_provider.py,sha256=zdxun3GodLfXxyssBK
|
|
|
57
57
|
kodit/infrastructure/indexing/__init__.py,sha256=7UPRa2jwCAsa0Orsp6PqXSF8iIXJVzXHMFmrKkI9yH8,38
|
|
58
58
|
kodit/infrastructure/indexing/auto_indexing_service.py,sha256=PgAyrmR8jNkAOlGnhQjFkqoE22oh-IwYTCg_v4o45Fo,2764
|
|
59
59
|
kodit/infrastructure/indexing/fusion_service.py,sha256=2B0guBsuKz19uWcs18sIJpUJPzXoRvULgl7UNWQGysA,1809
|
|
60
|
-
kodit/infrastructure/indexing/indexing_factory.py,sha256=LPjPCps_wJ9M_fZGRP02bfc2pvYc50ZSTYI99XwRRPg,918
|
|
61
60
|
kodit/infrastructure/mappers/__init__.py,sha256=QPHOjNreXmBPPovZ6elnYFS0vD-IsmrGl4TT01FCKro,77
|
|
62
61
|
kodit/infrastructure/mappers/index_mapper.py,sha256=ZSfu8kjTaa8_UY0nTqr4b02NS3VrjqZYkduCN71AL2g,12743
|
|
63
62
|
kodit/infrastructure/slicing/__init__.py,sha256=x7cjvHA9Ay2weUYE_dpdAaPaStp20M-4U2b5MLgT5KM,37
|
|
@@ -68,7 +67,7 @@ kodit/infrastructure/sqlalchemy/embedding_repository.py,sha256=dC2Wzj_zQiWExwfSc
|
|
|
68
67
|
kodit/infrastructure/sqlalchemy/entities.py,sha256=Dmh0z-dMI0wfMAPpf62kxU4md6NUH9P5Nx1QSTITOfg,5961
|
|
69
68
|
kodit/infrastructure/sqlalchemy/index_repository.py,sha256=fMnR3OxZN37dtp1M2Menf0xy31GjK1iv_0zn7EvRKYs,22575
|
|
70
69
|
kodit/infrastructure/ui/__init__.py,sha256=CzbLOBwIZ6B6iAHEd1L8cIBydCj-n_kobxJAhz2I9_Y,32
|
|
71
|
-
kodit/infrastructure/ui/progress.py,sha256=
|
|
70
|
+
kodit/infrastructure/ui/progress.py,sha256=LmEAQKWWSspqb0fOwruyxBfzBG7gmHd6z1iBco1d7_4,4823
|
|
72
71
|
kodit/infrastructure/ui/spinner.py,sha256=GcP115qtR0VEnGfMEtsGoAUpRzVGUSfiUXfoJJERngA,2357
|
|
73
72
|
kodit/migrations/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58
|
|
74
73
|
kodit/migrations/__init__.py,sha256=lP5MuwlyWRMO6UcDWnQcQ3G-GYHcFb6rl9gYPHJ1sjo,40
|
|
@@ -83,8 +82,8 @@ kodit/migrations/versions/__init__.py,sha256=9-lHzptItTzq_fomdIRBegQNm4Znx6pVjwD
|
|
|
83
82
|
kodit/migrations/versions/c3f5137d30f5_index_all_the_things.py,sha256=r7ukmJ_axXLAWewYx-F1fEmZ4JbtFd37i7cSb0tq3y0,1722
|
|
84
83
|
kodit/utils/__init__.py,sha256=DPEB1i8evnLF4Ns3huuAYg-0pKBFKUFuiDzOKG9r-sw,33
|
|
85
84
|
kodit/utils/path_utils.py,sha256=thK6YGGNvQThdBaCYCCeCvS1L8x-lwl3AoGht2jnjGw,1645
|
|
86
|
-
kodit-0.3.
|
|
87
|
-
kodit-0.3.
|
|
88
|
-
kodit-0.3.
|
|
89
|
-
kodit-0.3.
|
|
90
|
-
kodit-0.3.
|
|
85
|
+
kodit-0.3.7.dist-info/METADATA,sha256=2uWFTgaZtUWs3e1n6b04UHuELp7FCs24P_sxGOz4g7g,6973
|
|
86
|
+
kodit-0.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
87
|
+
kodit-0.3.7.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
|
|
88
|
+
kodit-0.3.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
89
|
+
kodit-0.3.7.dist-info/RECORD,,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""Factory for creating indexing services."""
|
|
2
|
-
|
|
3
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
|
-
|
|
5
|
-
from kodit.domain.services.indexing_service import IndexingDomainService
|
|
6
|
-
from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
|
|
7
|
-
from kodit.infrastructure.indexing.index_repository import SQLAlchemyIndexRepository
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def indexing_domain_service_factory(session: AsyncSession) -> IndexingDomainService:
|
|
11
|
-
"""Create an indexing domain service with all dependencies.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
session: SQLAlchemy session
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
Configured indexing domain service
|
|
18
|
-
|
|
19
|
-
"""
|
|
20
|
-
# Create repositories
|
|
21
|
-
index_repository = SQLAlchemyIndexRepository(session)
|
|
22
|
-
|
|
23
|
-
# Create fusion service
|
|
24
|
-
fusion_service = ReciprocalRankFusionService()
|
|
25
|
-
|
|
26
|
-
# Create domain service
|
|
27
|
-
return IndexingDomainService(
|
|
28
|
-
index_repository=index_repository,
|
|
29
|
-
fusion_service=fusion_service,
|
|
30
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|