kodit 0.1.6__tar.gz → 0.1.7__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.
- {kodit-0.1.6 → kodit-0.1.7}/PKG-INFO +1 -1
- {kodit-0.1.6 → kodit-0.1.7}/docs/_index.md +3 -4
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/_version.py +2 -2
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/cli.py +45 -96
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/indexing/repository.py +5 -3
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/indexing/service.py +3 -1
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/cli_test.py +4 -8
- {kodit-0.1.6 → kodit-0.1.7}/tests/smoke.sh +3 -8
- {kodit-0.1.6 → kodit-0.1.7}/.cursor/rules/kodit.mdc +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/CODE_OF_CONDUCT.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/CONTRIBUTING.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/workflows/docker.yaml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/workflows/docs.yaml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/workflows/pypi-test.yaml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/workflows/pypi.yaml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.github/workflows/test.yaml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.gitignore +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.python-version +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.vscode/launch.json +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/.vscode/settings.json +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/Dockerfile +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/LICENSE +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/README.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/alembic.ini +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/docs/developer/index.md +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/pyproject.toml +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/.gitignore +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/README +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/env.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/script.py.mako +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/versions/85155663351e_initial.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/alembic/versions/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/app.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/bm25/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/bm25/bm25.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/config.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/database.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/indexing/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/indexing/models.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/logging.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/mcp.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/middleware.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/retreival/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/retreival/repository.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/retreival/service.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/languages/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/languages/csharp.scm +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/languages/python.scm +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/method_snippets.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/snippets/snippets.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/sources/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/sources/models.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/sources/repository.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/src/kodit/sources/service.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/conftest.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/e2e.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/indexing/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/indexing/test_service.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/mcp_test.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/retreival/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/retreival/test_service.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/snippets/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/snippets/csharp.cs +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/snippets/detect_language_test.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/snippets/method_extraction_test.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/snippets/python.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/sources/__init__.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/tests/kodit/sources/test_service.py +0 -0
- {kodit-0.1.6 → kodit-0.1.7}/uv.lock +0 -0
|
@@ -57,10 +57,9 @@ pip install kodit
|
|
|
57
57
|
Kodit has two key parts. A configuration CLI to manage what gets indexed and an MCP
|
|
58
58
|
server to expose your code to an AI coding assistant.
|
|
59
59
|
|
|
60
|
-
1. Add
|
|
61
|
-
2.
|
|
62
|
-
3.
|
|
63
|
-
4. Start an MCP server: `kodit serve`
|
|
60
|
+
1. Add an index: `kodit index /path/to/your/code`
|
|
61
|
+
2. Test retrieval on your index: `kodit retrieve "test"`
|
|
62
|
+
3. Start an MCP server: `kodit serve`
|
|
64
63
|
|
|
65
64
|
Now browse to your AI coding assistant and add the MCP server. You will also need to
|
|
66
65
|
tell your assistant to use this server in coding tasks, otherwise it won't get called!
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
import click
|
|
9
9
|
import structlog
|
|
10
10
|
import uvicorn
|
|
11
|
-
from pytable_formatter import Table
|
|
11
|
+
from pytable_formatter import Cell, Table
|
|
12
12
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
13
|
|
|
14
14
|
from kodit.config import (
|
|
@@ -84,110 +84,59 @@ def cli( # noqa: PLR0913
|
|
|
84
84
|
ctx.obj = config
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
@cli.
|
|
88
|
-
|
|
89
|
-
"""Manage code sources."""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@sources.command(name="list")
|
|
93
|
-
@with_app_context
|
|
94
|
-
@with_session
|
|
95
|
-
async def list_sources(session: AsyncSession, app_context: AppContext) -> None:
|
|
96
|
-
"""List all code sources."""
|
|
97
|
-
repository = SourceRepository(session)
|
|
98
|
-
service = SourceService(app_context.get_clone_dir(), repository)
|
|
99
|
-
sources = await service.list_sources()
|
|
100
|
-
|
|
101
|
-
# Define headers and data
|
|
102
|
-
headers = ["ID", "Created At", "URI"]
|
|
103
|
-
data = [[source.id, source.created_at, source.uri] for source in sources]
|
|
104
|
-
|
|
105
|
-
# Create and display the table
|
|
106
|
-
table = Table(headers=headers, data=data)
|
|
107
|
-
click.echo(table)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@sources.command(name="create")
|
|
111
|
-
@click.argument("uri")
|
|
112
|
-
@with_app_context
|
|
113
|
-
@with_session
|
|
114
|
-
async def create_source(
|
|
115
|
-
session: AsyncSession, app_context: AppContext, uri: str
|
|
116
|
-
) -> None:
|
|
117
|
-
"""Add a new code source."""
|
|
118
|
-
repository = SourceRepository(session)
|
|
119
|
-
service = SourceService(app_context.get_clone_dir(), repository)
|
|
120
|
-
source = await service.create(uri)
|
|
121
|
-
click.echo(f"Source created: {source.id}")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@cli.group()
|
|
125
|
-
def indexes() -> None:
|
|
126
|
-
"""Manage indexes."""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@indexes.command(name="create")
|
|
130
|
-
@click.argument("source_id")
|
|
87
|
+
@cli.command()
|
|
88
|
+
@click.argument("sources", nargs=-1)
|
|
131
89
|
@with_app_context
|
|
132
90
|
@with_session
|
|
133
|
-
async def
|
|
134
|
-
session: AsyncSession,
|
|
91
|
+
async def index(
|
|
92
|
+
session: AsyncSession,
|
|
93
|
+
app_context: AppContext,
|
|
94
|
+
sources: list[str],
|
|
135
95
|
) -> None:
|
|
136
|
-
"""
|
|
96
|
+
"""List indexes, or index data sources."""
|
|
137
97
|
source_repository = SourceRepository(session)
|
|
138
98
|
source_service = SourceService(app_context.get_clone_dir(), source_repository)
|
|
139
99
|
repository = IndexRepository(session)
|
|
140
100
|
service = IndexService(repository, source_service, app_context.get_data_dir())
|
|
141
|
-
index = await service.create(source_id)
|
|
142
|
-
click.echo(f"Index created: {index.id}")
|
|
143
101
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
service = IndexService(repository, source_service, app_context.get_data_dir())
|
|
154
|
-
indexes = await service.list_indexes()
|
|
155
|
-
|
|
156
|
-
# Define headers and data
|
|
157
|
-
headers = [
|
|
158
|
-
"ID",
|
|
159
|
-
"Created At",
|
|
160
|
-
"Updated At",
|
|
161
|
-
"Num Snippets",
|
|
162
|
-
]
|
|
163
|
-
data = [
|
|
164
|
-
[
|
|
165
|
-
index.id,
|
|
166
|
-
index.created_at,
|
|
167
|
-
index.updated_at,
|
|
168
|
-
index.num_snippets,
|
|
102
|
+
if not sources:
|
|
103
|
+
# No source specified, list all indexes
|
|
104
|
+
indexes = await service.list_indexes()
|
|
105
|
+
headers: list[str | Cell] = [
|
|
106
|
+
"ID",
|
|
107
|
+
"Created At",
|
|
108
|
+
"Updated At",
|
|
109
|
+
"Source",
|
|
110
|
+
"Num Snippets",
|
|
169
111
|
]
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
112
|
+
data = [
|
|
113
|
+
[
|
|
114
|
+
index.id,
|
|
115
|
+
index.created_at,
|
|
116
|
+
index.updated_at,
|
|
117
|
+
index.source,
|
|
118
|
+
index.num_snippets,
|
|
119
|
+
]
|
|
120
|
+
for index in indexes
|
|
121
|
+
]
|
|
122
|
+
click.echo(Table(headers=headers, data=data))
|
|
123
|
+
return
|
|
124
|
+
# Handle source indexing
|
|
125
|
+
for source in sources:
|
|
126
|
+
if source.startswith("https://"):
|
|
127
|
+
msg = "Web or git indexing is not implemented yet"
|
|
128
|
+
raise click.UsageError(msg)
|
|
129
|
+
if source.startswith("git"):
|
|
130
|
+
msg = "Git indexing is not implemented yet"
|
|
131
|
+
raise click.UsageError(msg)
|
|
132
|
+
if Path(source).is_file():
|
|
133
|
+
msg = "File indexing is not implemented yet"
|
|
134
|
+
raise click.UsageError(msg)
|
|
135
|
+
|
|
136
|
+
# Index directory
|
|
137
|
+
s = await source_service.create(source)
|
|
138
|
+
index = await service.create(s.id)
|
|
139
|
+
await service.run(index.id)
|
|
191
140
|
|
|
192
141
|
|
|
193
142
|
@cli.command()
|
|
@@ -82,7 +82,7 @@ class IndexRepository:
|
|
|
82
82
|
result = await self.session.execute(query)
|
|
83
83
|
return list(result.scalars())
|
|
84
84
|
|
|
85
|
-
async def list_indexes(self) -> list[Index]:
|
|
85
|
+
async def list_indexes(self) -> list[tuple[Index, Source]]:
|
|
86
86
|
"""List all indexes.
|
|
87
87
|
|
|
88
88
|
Returns:
|
|
@@ -90,9 +90,11 @@ class IndexRepository:
|
|
|
90
90
|
and counts of files and snippets.
|
|
91
91
|
|
|
92
92
|
"""
|
|
93
|
-
query = select(Index).
|
|
93
|
+
query = select(Index, Source).join(
|
|
94
|
+
Source, Index.source_id == Source.id, full=True
|
|
95
|
+
)
|
|
94
96
|
result = await self.session.execute(query)
|
|
95
|
-
return list(result.
|
|
97
|
+
return list(result.tuples())
|
|
96
98
|
|
|
97
99
|
async def num_snippets_for_index(self, index_id: int) -> int:
|
|
98
100
|
"""Get the number of snippets for an index."""
|
|
@@ -33,6 +33,7 @@ class IndexView(pydantic.BaseModel):
|
|
|
33
33
|
id: int
|
|
34
34
|
created_at: datetime
|
|
35
35
|
updated_at: datetime | None = None
|
|
36
|
+
source: str | None = None
|
|
36
37
|
num_snippets: int | None = None
|
|
37
38
|
|
|
38
39
|
|
|
@@ -105,8 +106,9 @@ class IndexService:
|
|
|
105
106
|
created_at=index.created_at,
|
|
106
107
|
updated_at=index.updated_at,
|
|
107
108
|
num_snippets=await self.repository.num_snippets_for_index(index.id),
|
|
109
|
+
source=source.uri,
|
|
108
110
|
)
|
|
109
|
-
for index in indexes
|
|
111
|
+
for index, source in indexes
|
|
110
112
|
]
|
|
111
113
|
|
|
112
114
|
async def run(self, index_id: int) -> None:
|
|
@@ -37,9 +37,7 @@ def test_version_command(runner: CliRunner, default_cli_args: list[str]) -> None
|
|
|
37
37
|
def test_cli_vars_work(runner: CliRunner, default_cli_args: list[str]) -> None:
|
|
38
38
|
"""Test that cli args override env vars."""
|
|
39
39
|
runner.env = {"LOG_LEVEL": "INFO"}
|
|
40
|
-
result = runner.invoke(
|
|
41
|
-
cli, [*default_cli_args, "--log-level", "DEBUG", "sources", "list"]
|
|
42
|
-
)
|
|
40
|
+
result = runner.invoke(cli, [*default_cli_args, "--log-level", "DEBUG", "index"])
|
|
43
41
|
assert result.exit_code == 0
|
|
44
42
|
assert result.output.count("debug") > 10 # The db spits out lots of debug messages
|
|
45
43
|
|
|
@@ -47,7 +45,7 @@ def test_cli_vars_work(runner: CliRunner, default_cli_args: list[str]) -> None:
|
|
|
47
45
|
def test_env_vars_work(runner: CliRunner, default_cli_args: list[str]) -> None:
|
|
48
46
|
"""Test that env vars work."""
|
|
49
47
|
runner.env = {"LOG_LEVEL": "DEBUG"}
|
|
50
|
-
result = runner.invoke(cli, [*default_cli_args, "
|
|
48
|
+
result = runner.invoke(cli, [*default_cli_args, "index"])
|
|
51
49
|
assert result.exit_code == 0
|
|
52
50
|
assert result.output.count("debug") > 10 # The db spits out lots of debug messages
|
|
53
51
|
|
|
@@ -57,9 +55,7 @@ def test_dotenv_file_works(runner: CliRunner, default_cli_args: list[str]) -> No
|
|
|
57
55
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
58
56
|
f.write(b"LOG_LEVEL=DEBUG")
|
|
59
57
|
f.flush()
|
|
60
|
-
result = runner.invoke(
|
|
61
|
-
cli, [*default_cli_args, "--env-file", f.name, "sources", "list"]
|
|
62
|
-
)
|
|
58
|
+
result = runner.invoke(cli, [*default_cli_args, "--env-file", f.name, "index"])
|
|
63
59
|
assert result.exit_code == 0
|
|
64
60
|
assert (
|
|
65
61
|
result.output.count("debug") > 10
|
|
@@ -69,7 +65,7 @@ def test_dotenv_file_works(runner: CliRunner, default_cli_args: list[str]) -> No
|
|
|
69
65
|
def test_dotenv_file_not_found(runner: CliRunner, default_cli_args: list[str]) -> None:
|
|
70
66
|
"""Test that the .env file not found error is raised."""
|
|
71
67
|
result = runner.invoke(
|
|
72
|
-
cli, [*default_cli_args, "--env-file", "nonexistent.env", "
|
|
68
|
+
cli, [*default_cli_args, "--env-file", "nonexistent.env", "index"]
|
|
73
69
|
)
|
|
74
70
|
assert result.exit_code == 2
|
|
75
71
|
assert "does not exist" in result.output
|
|
@@ -24,14 +24,9 @@ echo "print('Hello, world!')" > $tmp_dir/test.py
|
|
|
24
24
|
# Test version command
|
|
25
25
|
$prefix kodit version
|
|
26
26
|
|
|
27
|
-
# Test
|
|
28
|
-
$prefix kodit
|
|
29
|
-
$prefix kodit
|
|
30
|
-
|
|
31
|
-
# Test indexes commands
|
|
32
|
-
$prefix kodit indexes list
|
|
33
|
-
$prefix kodit indexes create 1
|
|
34
|
-
$prefix kodit indexes run 1
|
|
27
|
+
# Test index command
|
|
28
|
+
$prefix kodit index $tmp_dir
|
|
29
|
+
$prefix kodit index
|
|
35
30
|
|
|
36
31
|
# Test retrieve command
|
|
37
32
|
$prefix kodit retrieve "Hello"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|