From c65710ca54f99ccef0772b55903b6a11859eaf87 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Mon, 5 May 2025 21:09:01 -0400 Subject: [PATCH 1/2] Update [ghstack-poisoned] --- codemcp/agno.py | 5 ++-- codemcp/main.py | 8 ++---- codemcp/testing.py | 38 ++++++++++++++++--------- codemcp/tools/glob.py | 6 +--- codemcp/tools/grep.py | 2 +- codemcp/tools/mv.py | 5 +--- codemcp/tools/rm.py | 2 +- tests/test_edit_file_string_matching.py | 1 + 8 files changed, 35 insertions(+), 32 deletions(-) diff --git a/codemcp/agno.py b/codemcp/agno.py index b6cb905..bc20606 100644 --- a/codemcp/agno.py +++ b/codemcp/agno.py @@ -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( diff --git a/codemcp/main.py b/codemcp/main.py index 8b7322e..b4b2f47 100644 --- a/codemcp/main.py +++ b/codemcp/main.py @@ -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 @@ -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 @@ -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( @@ -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): diff --git a/codemcp/testing.py b/codemcp/testing.py index 8c3726d..28037ff 100644 --- a/codemcp/testing.py +++ b/codemcp/testing.py @@ -241,13 +241,22 @@ async def _dispatch_to_subtool(self, subtool: str, kwargs: Dict[str, Any]) -> An elif subtool == "InitProject": from codemcp.tools.init_project import init_project + # Make a copy to avoid modifying the original kwargs + kwargs_copy = kwargs.copy() + # 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) + if "path" in kwargs_copy: + directory = kwargs_copy.pop("path") + kwargs_copy["directory"] = directory + + # Ensure reuse_head_chat_id is provided with a default value if not present + if ( + "reuse_head_chat_id" not in kwargs_copy + or kwargs_copy["reuse_head_chat_id"] is None + ): + kwargs_copy["reuse_head_chat_id"] = False + + return await init_project(**kwargs_copy) elif subtool == "RunCommand": from codemcp.tools.run_command import run_command @@ -424,14 +433,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 diff --git a/codemcp/tools/glob.py b/codemcp/tools/glob.py index 7f97cd8..201f355 100644 --- a/codemcp/tools/glob.py +++ b/codemcp/tools/glob.py @@ -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 @@ -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: diff --git a/codemcp/tools/grep.py b/codemcp/tools/grep.py index 4b0830d..a759e24 100644 --- a/codemcp/tools/grep.py +++ b/codemcp/tools/grep.py @@ -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 diff --git a/codemcp/tools/mv.py b/codemcp/tools/mv.py index fe5da7d..58c8807 100644 --- a/codemcp/tools/mv.py +++ b/codemcp/tools/mv.py @@ -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 diff --git a/codemcp/tools/rm.py b/codemcp/tools/rm.py index 4fb3272..e8f6d4b 100644 --- a/codemcp/tools/rm.py +++ b/codemcp/tools/rm.py @@ -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)) diff --git a/tests/test_edit_file_string_matching.py b/tests/test_edit_file_string_matching.py index 0769753..73ab973 100644 --- a/tests/test_edit_file_string_matching.py +++ b/tests/test_edit_file_string_matching.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import unittest + from expecttest import TestCase from codemcp.tools.edit_file import ( From 45c5edd463e135b0946431ac7857ecd81fcf30ce Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Mon, 5 May 2025 21:18:08 -0400 Subject: [PATCH 2/2] Update [ghstack-poisoned] --- codemcp/testing.py | 27 ++++----------------------- codemcp/tools/init_project.py | 21 ++++++++++++++------- codemcp/tools/run_command.py | 27 +++++++++++++++++++-------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/codemcp/testing.py b/codemcp/testing.py index 28037ff..3bf5231 100644 --- a/codemcp/testing.py +++ b/codemcp/testing.py @@ -241,33 +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 - # Make a copy to avoid modifying the original kwargs - kwargs_copy = kwargs.copy() - - # Convert 'path' parameter to 'directory' as expected by init_project - if "path" in kwargs_copy: - directory = kwargs_copy.pop("path") - kwargs_copy["directory"] = directory - - # Ensure reuse_head_chat_id is provided with a default value if not present - if ( - "reuse_head_chat_id" not in kwargs_copy - or kwargs_copy["reuse_head_chat_id"] is None - ): - kwargs_copy["reuse_head_chat_id"] = False - - return await init_project(**kwargs_copy) + # 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 diff --git a/codemcp/tools/init_project.py b/codemcp/tools/init_project.py index 89d8865..0c25f6e 100644 --- a/codemcp/tools/init_project.py +++ b/codemcp/tools/init_project.py @@ -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 @@ -135,6 +136,7 @@ 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, @@ -142,15 +144,20 @@ async def init_project( """ 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) diff --git a/codemcp/tools/run_command.py b/codemcp/tools/run_command.py index 3127206..ec68619 100644 --- a/codemcp/tools/run_command.py +++ b/codemcp/tools/run_command.py @@ -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. @@ -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 = ( @@ -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: @@ -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