kodit 0.1.3__tar.gz → 0.1.4__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 (66) hide show
  1. kodit-0.1.4/.cursor/rules/kodit.mdc +6 -0
  2. {kodit-0.1.3 → kodit-0.1.4}/.github/workflows/pypi.yaml +20 -3
  3. {kodit-0.1.3 → kodit-0.1.4}/PKG-INFO +1 -1
  4. kodit-0.1.4/docs/_index.md +92 -0
  5. kodit-0.1.4/docs/developer/index.md +30 -0
  6. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/_version.py +2 -2
  7. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/cli.py +0 -2
  8. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/database.py +2 -0
  9. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/indexing/service.py +0 -1
  10. kodit-0.1.4/src/kodit/mcp.py +110 -0
  11. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/mcp_test.py +43 -0
  12. kodit-0.1.3/docs/_index.md +0 -53
  13. kodit-0.1.3/docs/developer/index.md +0 -17
  14. kodit-0.1.3/src/kodit/mcp.py +0 -51
  15. {kodit-0.1.3 → kodit-0.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
  16. {kodit-0.1.3 → kodit-0.1.4}/.github/CONTRIBUTING.md +0 -0
  17. {kodit-0.1.3 → kodit-0.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {kodit-0.1.3 → kodit-0.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  19. {kodit-0.1.3 → kodit-0.1.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {kodit-0.1.3 → kodit-0.1.4}/.github/workflows/docker.yaml +0 -0
  21. {kodit-0.1.3 → kodit-0.1.4}/.github/workflows/docs.yaml +0 -0
  22. {kodit-0.1.3 → kodit-0.1.4}/.github/workflows/pypi-test.yaml +0 -0
  23. {kodit-0.1.3 → kodit-0.1.4}/.github/workflows/test.yaml +0 -0
  24. {kodit-0.1.3 → kodit-0.1.4}/.gitignore +0 -0
  25. {kodit-0.1.3 → kodit-0.1.4}/.python-version +0 -0
  26. {kodit-0.1.3 → kodit-0.1.4}/.vscode/settings.json +0 -0
  27. {kodit-0.1.3 → kodit-0.1.4}/Dockerfile +0 -0
  28. {kodit-0.1.3 → kodit-0.1.4}/LICENSE +0 -0
  29. {kodit-0.1.3 → kodit-0.1.4}/README.md +0 -0
  30. {kodit-0.1.3 → kodit-0.1.4}/alembic.ini +0 -0
  31. {kodit-0.1.3 → kodit-0.1.4}/pyproject.toml +0 -0
  32. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/.gitignore +0 -0
  33. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/__init__.py +0 -0
  34. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/README +0 -0
  35. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/__init__.py +0 -0
  36. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/env.py +0 -0
  37. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/script.py.mako +0 -0
  38. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/versions/85155663351e_initial.py +0 -0
  39. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/alembic/versions/__init__.py +0 -0
  40. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/app.py +0 -0
  41. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/config.py +0 -0
  42. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/indexing/__init__.py +0 -0
  43. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/indexing/models.py +0 -0
  44. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/indexing/repository.py +0 -0
  45. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/logging.py +0 -0
  46. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/middleware.py +0 -0
  47. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/retreival/__init__.py +0 -0
  48. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/retreival/repository.py +0 -0
  49. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/retreival/service.py +0 -0
  50. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/sources/__init__.py +0 -0
  51. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/sources/models.py +0 -0
  52. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/sources/repository.py +0 -0
  53. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/sources/service.py +0 -0
  54. {kodit-0.1.3 → kodit-0.1.4}/src/kodit/sse.py +0 -0
  55. {kodit-0.1.3 → kodit-0.1.4}/tests/__init__.py +0 -0
  56. {kodit-0.1.3 → kodit-0.1.4}/tests/conftest.py +0 -0
  57. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/__init__.py +0 -0
  58. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/cli_test.py +0 -0
  59. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/indexing/__init__.py +0 -0
  60. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/indexing/test_service.py +0 -0
  61. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/retreival/__init__.py +0 -0
  62. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/retreival/test_service.py +0 -0
  63. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/sources/__init__.py +0 -0
  64. {kodit-0.1.3 → kodit-0.1.4}/tests/kodit/sources/test_service.py +0 -0
  65. {kodit-0.1.3 → kodit-0.1.4}/tests/smoke.sh +0 -0
  66. {kodit-0.1.3 → kodit-0.1.4}/uv.lock +0 -0
@@ -0,0 +1,6 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ You MUST use the code-search MCP tool and always include any file context the user has provided in the chat.
@@ -28,10 +28,27 @@ jobs:
28
28
  steps:
29
29
  - name: Wait for PyPI release to be available
30
30
  run: |
31
+ set -x
32
+
33
+ REPO_NAME=${{ github.event.repository.name }}
34
+ REPO_TAG=${{ github.event.release.tag_name }}
35
+ REPO_TAG=0.1.3
36
+
37
+ # Get the first letter of the repo name
38
+ REPO_NAME_FIRST_LETTER=${REPO_NAME:0:1}
39
+
40
+ # Initialize counter for 60 second timeout
41
+ count=0
31
42
  while true; do
32
- if curl -s https://pypi.io/packages/source/k/${{ github.event.repository.name }}/${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.tar.gz; then
33
- break
34
- fi
43
+ if curl -sfL https://pypi.io/packages/source/${REPO_NAME_FIRST_LETTER}/${REPO_NAME}/${REPO_NAME}-${REPO_TAG}.tar.gz > /dev/null; then
44
+ break
45
+ fi
46
+ sleep 1
47
+ ((count++))
48
+ if [ $count -ge 60 ]; then
49
+ echo "Timeout reached after 60 seconds"
50
+ exit 1
51
+ fi
35
52
  done
36
53
  - uses: mislav/bump-homebrew-formula-action@v3
37
54
  with:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kodit
3
- Version: 0.1.3
3
+ Version: 0.1.4
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/
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: "kodit: Code Indexing MCP Server"
3
+ linkTitle: kodit Docs
4
+ cascade:
5
+ type: docs
6
+ menu:
7
+ main:
8
+ name: kodit Docs
9
+ weight: 3
10
+ # next: /helix/getting-started
11
+ weight: 1
12
+ aliases:
13
+ - /coda
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ Please choose your preferred installation method. They all ultimately install the kodit
19
+ cli, which contains the kodit MCP server and other tools to manage your data sources.
20
+
21
+ ### Docker
22
+
23
+ ```sh
24
+ docker run -it --rm registry.helix.ml/helix/kodit:latest
25
+ ```
26
+
27
+ Always replace latest with a specific version.
28
+
29
+ ### pipx
30
+
31
+ ```sh
32
+ pipx install kodit
33
+ ```
34
+
35
+ ### homebrew
36
+
37
+ ```sh
38
+ brew install helixml/kodit/kodit
39
+ ```
40
+
41
+ ### uv
42
+
43
+ ```sh
44
+ uv tool install kodit
45
+ ```
46
+
47
+ ### pip
48
+
49
+ Use this if you want to use kodit as a python library:
50
+
51
+ ```sh
52
+ pip install kodit
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ Kodit has two key parts. A configuration CLI to manage what gets indexed and an MCP
58
+ server to expose your code to an AI coding assistant.
59
+
60
+ 1. Add a source: `kodit sources create /path/to/your/code`
61
+ 2. Create an index on that source: `kodit indexes create 1`
62
+ 3. Test retrieval on your index: `kodit retrieve "test"`
63
+ 4. Start an MCP server: `kodit serve`
64
+
65
+ Now browse to your AI coding assistant and add the MCP server. You will also need to
66
+ tell your assistant to use this server in coding tasks, otherwise it won't get called!
67
+
68
+ ### Integration with Cursor
69
+
70
+ Add the following to `.cursor/mcp.json`:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "code-search": {
76
+ "url": "http://localhost:8080/sse"
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Now add the following prompt to `.cursor/rules/kodit.mdc` in your project directory:
83
+
84
+ ```markdown
85
+ ---
86
+ description:
87
+ globs:
88
+ alwaysApply: true
89
+ ---
90
+ You MUST use the code-search MCP tool and always include any file context the user has provided in the chat.```
91
+
92
+ Alternatively, you can browse to the cursor settings and set this prompt globally.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: "Kodit Developer Documentation"
3
+ linkTitle: Developer Docs
4
+ # next: /helix/getting-started
5
+ weight: 99
6
+ ---
7
+
8
+ ## Database
9
+
10
+ All database operations are handled by SQLAlchemy and Alembic.
11
+
12
+ ### Creating a Database Migration
13
+
14
+ 1. Make changes to your models
15
+ 2. Ensure the model is referenced in [alembic's env.py](src/kodit/alembic/env.py)
16
+ 3. Run `alembic revision --autogenerate -m "your message"`
17
+ 4. The new migration will be applied when you next run a kodit command
18
+
19
+ ## Releasing
20
+
21
+ Performing a release is designed to be fully automated. If you spot opportunities to
22
+ improve the CI to help performing an automated release, please do so.
23
+
24
+ 1. Create a new release in GitHub.
25
+ 2. Set the version number. Use patch versions for bugfixes or minor small improvements.
26
+ Use minor versions when adding significant new functionality. Use major versions for
27
+ overhauls.
28
+ 3. Generate the release notes. <- this could be improved, because we use a strict
29
+ pr/commit naming structure.
30
+ 4. Wait for all jobs to succeed, then you should be able to brew install, pipx install, etc.
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.3'
21
- __version_tuple__ = version_tuple = (0, 1, 3)
20
+ __version__ = version = '0.1.4'
21
+ __version_tuple__ = version_tuple = (0, 1, 4)
@@ -104,7 +104,6 @@ async def list_indexes(session: AsyncSession) -> None:
104
104
  "ID",
105
105
  "Created At",
106
106
  "Updated At",
107
- "Source URI",
108
107
  "Num Snippets",
109
108
  ]
110
109
  data = [
@@ -112,7 +111,6 @@ async def list_indexes(session: AsyncSession) -> None:
112
111
  index.id,
113
112
  index.created_at,
114
113
  index.updated_at,
115
- index.source_uri,
116
114
  index.num_snippets,
117
115
  ]
118
116
  for index in indexes
@@ -2,6 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import AsyncGenerator, Callable
5
+ from contextlib import asynccontextmanager
5
6
  from datetime import UTC, datetime
6
7
  from functools import wraps
7
8
  from pathlib import Path
@@ -54,6 +55,7 @@ class CommonMixin:
54
55
  )
55
56
 
56
57
 
58
+ @asynccontextmanager
57
59
  async def get_session() -> AsyncGenerator[AsyncSession, None]:
58
60
  """Get a database session."""
59
61
  async with async_session_factory() as session:
@@ -37,7 +37,6 @@ class IndexView(pydantic.BaseModel):
37
37
  id: int
38
38
  created_at: datetime
39
39
  updated_at: datetime | None = None
40
- source_uri: str | None = None
41
40
  num_snippets: int | None = None
42
41
 
43
42
 
@@ -0,0 +1,110 @@
1
+ """MCP server implementation for kodit."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import structlog
7
+ from mcp.server.fastmcp import FastMCP
8
+ from pydantic import Field
9
+
10
+ from kodit.database import get_session
11
+ from kodit.retreival.repository import RetrievalRepository, RetrievalResult
12
+ from kodit.retreival.service import RetrievalRequest, RetrievalService
13
+
14
+ mcp = FastMCP("kodit MCP Server")
15
+
16
+
17
+ @mcp.tool()
18
+ async def retrieve_relevant_snippets(
19
+ user_intent: Annotated[
20
+ str,
21
+ Field(
22
+ description="Think about what the user wants to achieve. Describe the "
23
+ "user's intent in one sentence."
24
+ ),
25
+ ],
26
+ related_file_paths: Annotated[
27
+ list[Path],
28
+ Field(
29
+ description="A list of absolute paths to files that are relevant to the "
30
+ "user's intent."
31
+ ),
32
+ ],
33
+ related_file_contents: Annotated[
34
+ list[str],
35
+ Field(
36
+ description="A list of the contents of the files that are relevant to the "
37
+ "user's intent."
38
+ ),
39
+ ],
40
+ keywords: Annotated[
41
+ list[str],
42
+ Field(
43
+ description="A list of keywords that are relevant to the desired outcome."
44
+ ),
45
+ ],
46
+ ) -> str:
47
+ """Retrieve relevant snippets from various sources.
48
+
49
+ This tool retrieves relevant snippets from sources such as private codebases,
50
+ public codebases, and documentation. You can use this information to improve
51
+ the quality of your generated code. You must call this tool when you need to
52
+ write code.
53
+ """
54
+ # Log the search query and related files for debugging
55
+ log = structlog.get_logger(__name__)
56
+ log.debug(
57
+ "Retrieving relevant snippets",
58
+ user_intent=user_intent,
59
+ keywords=keywords,
60
+ file_count=len(related_file_paths),
61
+ file_paths=related_file_paths,
62
+ file_contents=related_file_contents,
63
+ )
64
+
65
+ async with get_session() as session:
66
+ log.debug("Creating retrieval repository")
67
+ retrieval_repository = RetrievalRepository(
68
+ session=session,
69
+ )
70
+
71
+ log.debug("Creating retrieval service")
72
+ retrieval_service = RetrievalService(
73
+ repository=retrieval_repository,
74
+ )
75
+
76
+ log.debug("Fusing input")
77
+ input_query = input_fusion(
78
+ user_intent=user_intent,
79
+ related_file_paths=related_file_paths,
80
+ related_file_contents=related_file_contents,
81
+ keywords=keywords,
82
+ )
83
+ log.debug("Input", input_query=input_query)
84
+ retrieval_request = RetrievalRequest(
85
+ query=input_query,
86
+ )
87
+ log.debug("Retrieving snippets")
88
+ snippets = await retrieval_service.retrieve(request=retrieval_request)
89
+
90
+ log.debug("Fusing output")
91
+ output = output_fusion(snippets=snippets)
92
+
93
+ log.debug("Output", output=output)
94
+ return output
95
+
96
+
97
+ def input_fusion(
98
+ user_intent: str, # noqa: ARG001
99
+ related_file_paths: list[Path], # noqa: ARG001
100
+ related_file_contents: list[str], # noqa: ARG001
101
+ keywords: list[str],
102
+ ) -> str:
103
+ """Fuse the search query and related file contents into a single query."""
104
+ # Since this is a dummy implementation, we just return the first keyword
105
+ return keywords[0] if len(keywords) > 0 else ""
106
+
107
+
108
+ def output_fusion(snippets: list[RetrievalResult]) -> str:
109
+ """Fuse the snippets into a single output."""
110
+ return "\n\n".join(f"{snippet.uri}\n{snippet.content}" for snippet in snippets)
@@ -64,3 +64,46 @@ async def test_mcp_client_connection() -> None:
64
64
  # doesn't shut down the server when requested. So we have to force kill it.
65
65
  server_process.kill()
66
66
  server_process.join(timeout=1)
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_mcp_code_retrieval() -> None:
71
+ """Test end-to-end flow of adding code and retrieving it via MCP."""
72
+ # Can't quite test this properly yet, because the MCP server runs in a separate
73
+ # process using the user's actual database. So this code is just testing that it
74
+ # doesn't crash or anything.
75
+ server_process = None
76
+ try:
77
+ # Start the server in a separate process
78
+ server_process = Process(target=run_server, daemon=True)
79
+ server_process.start()
80
+
81
+ # Wait for server to start
82
+ await wait_for_server()
83
+
84
+ # Connect to MCP server
85
+ async with (
86
+ sse_client("http://127.0.0.1:8000/sse/") as (
87
+ read_stream,
88
+ write_stream,
89
+ ),
90
+ ClientSession(read_stream, write_stream) as mcp_session,
91
+ ):
92
+ # Initialize the connection
93
+ await mcp_session.initialize()
94
+
95
+ # Call retrieve_relevant_snippets tool
96
+ await mcp_session.call_tool(
97
+ "retrieve_relevant_snippets",
98
+ {
99
+ "user_intent": "I want to find code that prints hello world",
100
+ "related_file_paths": [],
101
+ "related_file_contents": [],
102
+ "keywords": ["hello", "world", "print"],
103
+ },
104
+ )
105
+
106
+ finally:
107
+ if server_process:
108
+ server_process.kill()
109
+ server_process.join(timeout=1)
@@ -1,53 +0,0 @@
1
- ---
2
- title: "kodit: Code Indexing MCP Server"
3
- linkTitle: kodit Docs
4
- cascade:
5
- type: docs
6
- menu:
7
- main:
8
- name: kodit Docs
9
- weight: 3
10
- # next: /helix/getting-started
11
- weight: 1
12
- aliases:
13
- - /coda
14
- ---
15
-
16
- ## Installation
17
-
18
- Please choose your preferred installation method. They all ultimately install the kodit
19
- cli, which contains the kodit MCP server and other tools to manage your data sources.
20
-
21
- ### Docker
22
-
23
- ```sh
24
- docker run -it --rm registry.helix.ml/helix/kodit:latest
25
- ```
26
-
27
- Always replace latest with a specific version.
28
-
29
- ### pipx
30
-
31
- ```sh
32
- pipx install kodit
33
- ```
34
-
35
- ### homebrew
36
-
37
- ```sh
38
- brew install helixml/kodit/kodit
39
- ```
40
-
41
- ### uv
42
-
43
- ```sh
44
- uv tool install kodit
45
- ```
46
-
47
- ### pip
48
-
49
- Use this if you want to use kodit as a python library:
50
-
51
- ```sh
52
- pip install kodit
53
- ```
@@ -1,17 +0,0 @@
1
- ---
2
- title: "Kodit Developer Documentation"
3
- linkTitle: Developer Docs
4
- # next: /helix/getting-started
5
- weight: 99
6
- ---
7
-
8
- ## Database
9
-
10
- All database operations are handled by SQLAlchemy and Alembic.
11
-
12
- ### Creating a Database Migration
13
-
14
- 1. Make changes to your models
15
- 2. Ensure the model is referenced in [alembic's env.py](src/kodit/alembic/env.py)
16
- 3. Run `alembic revision --autogenerate -m "your message"`
17
- 4. The new migration will be applied when you next run a kodit command
@@ -1,51 +0,0 @@
1
- """MCP server implementation for kodit."""
2
-
3
- from pathlib import Path
4
- from typing import Annotated
5
-
6
- import structlog
7
- from mcp.server.fastmcp import FastMCP
8
- from pydantic import Field
9
-
10
- mcp = FastMCP("kodit MCP Server")
11
-
12
-
13
- @mcp.tool()
14
- async def retrieve_relevant_snippets(
15
- search_query: Annotated[
16
- str,
17
- Field(description="Describe the user's intent in a few sentences."),
18
- ],
19
- related_file_paths: Annotated[
20
- list[Path],
21
- Field(
22
- description="A list of absolute paths to files that are relevant to the "
23
- "user's intent."
24
- ),
25
- ],
26
- related_file_contents: Annotated[
27
- list[str],
28
- Field(
29
- description="A list of the contents of the files that are relevant to the "
30
- "user's intent."
31
- ),
32
- ],
33
- ) -> str:
34
- """Retrieve relevant snippets from various sources.
35
-
36
- This tool retrieves relevant snippets from sources such as private codebases,
37
- public codebases, and documentation. You can use this information to improve
38
- the quality of your generated code. You must call this tool when you need to
39
- write code.
40
- """
41
- # Log the search query and related files for debugging
42
- log = structlog.get_logger(__name__)
43
- log.debug(
44
- "Retrieving relevant snippets",
45
- search_query=search_query,
46
- file_count=len(related_file_paths),
47
- file_paths=related_file_paths,
48
- file_contents=related_file_contents,
49
- )
50
-
51
- return "Retrieved"
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