Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions codemcp/agno.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ async def serve_playground_app_async(
prefix="/v1",
**kwargs,
):
import uvicorn
import signal
import os
import signal

import uvicorn

try:
create_playground_endpoint(
Expand Down
8 changes: 3 additions & 5 deletions codemcp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
import re
from pathlib import Path
from typing import List, Optional, Tuple
from typing import List, Optional

import click
import pathspec
Expand All @@ -14,8 +14,6 @@
from starlette.applications import Starlette
from starlette.routing import Mount

from .common import normalize_file_path
from .git_query import get_current_commit_hash
from .tools.chmod import chmod
from .tools.edit_file import edit_file
from .tools.glob import glob
Expand Down Expand Up @@ -839,8 +837,8 @@ def run() -> None:
configure_logging()

# Set up a signal handler to exit immediately on Ctrl+C
import signal
import os
import signal

def handle_exit(sig, frame):
logging.info(
Expand Down Expand Up @@ -887,8 +885,8 @@ def serve(host: str, port: int, cors_origin: List[str]) -> None:

app = create_sse_app(allowed_origins)

import signal
import os
import signal

# Register a custom signal handler that will take precedence and exit immediately
def handle_exit(sig, frame):
Expand Down
35 changes: 13 additions & 22 deletions codemcp/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,24 +241,14 @@ async def _dispatch_to_subtool(self, subtool: str, kwargs: Dict[str, Any]) -> An
elif subtool == "InitProject":
from codemcp.tools.init_project import init_project

# Convert 'path' parameter to 'directory' as expected by init_project
if "path" in kwargs:
kwargs = kwargs.copy() # Make a copy to avoid modifying the original
directory = kwargs.pop("path")
return await init_project(directory=directory, **kwargs)
else:
return await init_project(**kwargs)
# No need for parameter conversion anymore - init_project accepts both path and directory
return await init_project(**kwargs)

elif subtool == "RunCommand":
from codemcp.tools.run_command import run_command

# Convert 'path' parameter to 'project_dir' as expected by run_command
if "path" in kwargs:
kwargs = kwargs.copy() # Make a copy to avoid modifying the original
project_dir = kwargs.pop("path")
return await run_command(project_dir=project_dir, **kwargs)
else:
return await run_command(**kwargs)
# No need for parameter conversion anymore - run_command accepts both path and project_dir
return await run_command(**kwargs)

elif subtool == "Grep":
from codemcp.tools.grep import grep
Expand Down Expand Up @@ -424,14 +414,15 @@ async def get_chat_id(self, session: Optional[ClientSession]) -> str:
Returns:
str: The chat_id
"""
from codemcp.tools.init_project import init_project

# First initialize project to get chat_id using init_project directly
init_result_text = await init_project(
directory=self.temp_dir.name,
user_prompt="Test initialization for get_chat_id",
subject_line="test: initialize for e2e testing",
reuse_head_chat_id=False,
# Use the _dispatch_to_subtool for consistency with other test methods
init_result_text = await self._dispatch_to_subtool(
"InitProject",
{
"path": self.temp_dir.name,
"user_prompt": "Test initialization for get_chat_id",
"subject_line": "test: initialize for e2e testing",
"reuse_head_chat_id": False,
},
)

# Extract chat_id from the init result
Expand Down
6 changes: 1 addition & 5 deletions codemcp/tools/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import fnmatch
import logging
import os
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List

from ..common import normalize_file_path
from ..git import is_git_repository
Expand Down Expand Up @@ -106,10 +106,6 @@ async def glob(
matches = matches[offset_val : offset_val + limit_val]

# Create the result dictionary
result_dict = {
"files": matches,
"total": total_matches,
}

# Format the results
if not matches:
Expand Down
2 changes: 1 addition & 1 deletion codemcp/tools/grep.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import os
import subprocess
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict

from ..common import normalize_file_path
from ..git import is_git_repository
Expand Down
21 changes: 14 additions & 7 deletions codemcp/tools/init_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,11 @@ async def _generate_chat_id(directory: str, description: Optional[str] = None) -


async def init_project(
directory: str,
user_prompt: str,
subject_line: str,
reuse_head_chat_id: bool,
directory: Optional[str] = None,
user_prompt: str = "",
subject_line: str = "",
reuse_head_chat_id: bool = False,
path: Optional[str] = None,
) -> str:
"""Initialize a project by reading the codemcp.toml TOML file and returning
a combined system prompt. Creates an empty commit with the user's prompt as the body
Expand All @@ -135,22 +136,28 @@ async def init_project(
user_prompt: The user's original prompt verbatim
subject_line: A short subject line in Git conventional commit format
reuse_head_chat_id: Whether to reuse the chat ID from the HEAD commit
path: Alias for directory parameter (for backward compatibility)

Returns:
A string containing the system prompt plus any project_prompt from the config,
or an error message if validation fails

"""
try:
# Use path as an alias for directory if directory is not provided
effective_directory = directory if directory is not None else path
if effective_directory is None:
raise ValueError("Either directory or path must be provided")

# Normalize the directory path
full_dir_path = normalize_file_path(directory)
full_dir_path = normalize_file_path(effective_directory)

# Validate the directory path
if not os.path.exists(full_dir_path):
raise FileNotFoundError(f"Directory does not exist: {directory}")
raise FileNotFoundError(f"Directory does not exist: {effective_directory}")

if not os.path.isdir(full_dir_path):
raise NotADirectoryError(f"Path is not a directory: {directory}")
raise NotADirectoryError(f"Path is not a directory: {effective_directory}")

# Check if the directory is a Git repository
is_git_repo = await is_git_repository(full_dir_path)
Expand Down
5 changes: 1 addition & 4 deletions codemcp/tools/mv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
import logging
import os
import pathlib
import shutil
from typing import Optional

from ..access import check_edit_permission
from ..common import normalize_file_path
from ..git import commit_changes, get_repository_root, is_git_repository
from ..git import commit_changes, get_repository_root
from ..shell import run_command
from .commit_utils import append_commit_hash

Expand Down
2 changes: 1 addition & 1 deletion codemcp/tools/rm.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def rm(
raise ValueError(permission_message)

# Determine if it's a file or directory
is_dir = os.path.isdir(full_path)
os.path.isdir(full_path)

# Get git repository root
git_root = await get_repository_root(os.path.dirname(full_path))
Expand Down
27 changes: 19 additions & 8 deletions codemcp/tools/run_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@


async def run_command(
project_dir: str,
command: str,
project_dir: Optional[str] = None,
command: str = "",
arguments: Optional[str | list[str]] = None,
chat_id: str | None = None,
commit_hash: str | None = None,
chat_id: Optional[str] = None,
commit_hash: Optional[str] = None,
path: Optional[str] = None,
) -> str:
"""Run a command that is configured in codemcp.toml.

Expand All @@ -30,15 +31,21 @@ async def run_command(
containing spaces, etc.). If a list, it will be used directly.
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking
path: Alias for project_dir parameter (for backward compatibility)

Returns:
A string containing the result of the command operation
"""
# Use path as an alias for project_dir if project_dir is not provided
effective_project_dir = project_dir if project_dir is not None else path
if effective_project_dir is None:
raise ValueError("Either project_dir or path must be provided")

# Set default values
chat_id = "" if chat_id is None else chat_id

# Normalize the project directory path
project_dir = normalize_file_path(project_dir)
effective_project_dir = normalize_file_path(effective_project_dir)

# Ensure arguments is a string for run_command
args_str = (
Expand All @@ -47,7 +54,7 @@ async def run_command(
else " ".join(arguments)
)

command_list = get_command_from_config(project_dir, command)
command_list = get_command_from_config(effective_project_dir, command)

# If arguments are provided, extend the command with them
if args_str and command_list:
Expand All @@ -59,9 +66,13 @@ async def run_command(
actual_command = command_list if command_list is not None else []

result = await run_code_command(
project_dir, command, actual_command, f"Auto-commit {command} changes", chat_id
effective_project_dir,
command,
actual_command,
f"Auto-commit {command} changes",
chat_id,
)

# Append commit hash
result, _ = await append_commit_hash(result, project_dir, commit_hash)
result, _ = await append_commit_hash(result, effective_project_dir, commit_hash)
return result
1 change: 1 addition & 0 deletions tests/test_edit_file_string_matching.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3

import unittest

from expecttest import TestCase

from codemcp.tools.edit_file import (
Expand Down