diff --git a/codemcp/tools/chmod.py b/codemcp/tools/chmod.py index a1137c7f..2cf349fe 100644 --- a/codemcp/tools/chmod.py +++ b/codemcp/tools/chmod.py @@ -64,74 +64,62 @@ async def chmod( # Get the directory containing the file for git operations directory = os.path.dirname(absolute_path) - try: - # Check current file permissions - current_mode = os.stat(absolute_path).st_mode - is_executable = bool(current_mode & stat.S_IXUSR) - - if mode == "a+x" and is_executable: - message = f"File '{path}' is already executable" - return { - "output": message, - "resultForAssistant": message, - } - elif mode == "a-x" and not is_executable: - message = f"File '{path}' is already non-executable" - return { - "output": message, - "resultForAssistant": message, - } - - # Execute chmod command - cmd = ["chmod", mode, absolute_path] - await run_command( - cmd=cmd, - cwd=directory, - capture_output=True, - text=True, - check=True, - ) + # Check current file permissions + current_mode = os.stat(absolute_path).st_mode + is_executable = bool(current_mode & stat.S_IXUSR) + + if mode == "a+x" and is_executable: + message = f"File '{path}' is already executable" + return { + "output": message, + "resultForAssistant": message, + } + elif mode == "a-x" and not is_executable: + message = f"File '{path}' is already non-executable" + return { + "output": message, + "resultForAssistant": message, + } - # Prepare success message - if mode == "a+x": - description = f"Make '{os.path.basename(absolute_path)}' executable" - action_msg = f"Made file '{path}' executable" - else: - description = ( - f"Remove executable permission from '{os.path.basename(absolute_path)}'" - ) - action_msg = f"Removed executable permission from file '{path}'" - - # Commit the changes - success, commit_message = await commit_changes( - directory, - description, - chat_id, + # Execute chmod command + cmd = ["chmod", mode, absolute_path] + await run_command( + cmd=cmd, + cwd=directory, + capture_output=True, + text=True, + check=True, + ) + + # Prepare success message + if mode == "a+x": + description = f"Make '{os.path.basename(absolute_path)}' executable" + action_msg = f"Made file '{path}' executable" + else: + description = ( + f"Remove executable permission from '{os.path.basename(absolute_path)}'" ) + action_msg = f"Removed executable permission from file '{path}'" - if not success: - logging.warning(f"Failed to commit chmod changes: {commit_message}") - return { - "output": f"{action_msg}, but failed to commit changes: {commit_message}", - "resultForAssistant": f"{action_msg}, but failed to commit changes: {commit_message}", - } + # Commit the changes + success, commit_message = await commit_changes( + directory, + description, + chat_id, + ) - # Prepare output - output = { - "output": f"{action_msg} and committed changes", - } + if not success: + raise RuntimeError(f"Failed to commit chmod changes: {commit_message}") - # Add formatted result for assistant - output["resultForAssistant"] = render_result_for_assistant(output) + # Prepare output + output = { + "output": f"{action_msg} and committed changes", + } - return output - except Exception as e: - logging.exception(f"Error executing chmod: {e!s}") - error_message = f"Error executing chmod: {e!s}" - return { - "output": error_message, - "resultForAssistant": error_message, - } + # Add formatted result for assistant + output["resultForAssistant"] = render_result_for_assistant(output) + + return output def render_result_for_assistant(output: dict[str, Any]) -> str: diff --git a/codemcp/tools/git_blame.py b/codemcp/tools/git_blame.py index e0f897b3..c434d72c 100644 --- a/codemcp/tools/git_blame.py +++ b/codemcp/tools/git_blame.py @@ -68,43 +68,24 @@ async def git_blame( logging.debug(f"Executing git blame command: {' '.join(cmd)}") - try: - # Execute git blame command asynchronously - result = await run_command( - cmd=cmd, - cwd=absolute_path, - capture_output=True, - text=True, - check=False, # Don't raise exception if git blame fails - ) - - # Process results - if result.returncode != 0: - logging.error( - f"git blame failed with exit code {result.returncode}: {result.stderr}" - ) - error_message = f"Error: {result.stderr}" - return { - "output": error_message, - "resultForAssistant": error_message, - } - - # Prepare output - output = { - "output": result.stdout, - } - - # Add formatted result for assistant - output["resultForAssistant"] = render_result_for_assistant(output) - - return output - except Exception as e: - logging.exception(f"Error executing git blame: {e!s}") - error_message = f"Error executing git blame: {e!s}" - return { - "output": error_message, - "resultForAssistant": error_message, - } + # Execute git blame command asynchronously + result = await run_command( + cmd=cmd, + cwd=absolute_path, + capture_output=True, + text=True, + check=True, # Allow exception if git blame fails to propagate up + ) + + # Prepare output + output = { + "output": result.stdout, + } + + # Add formatted result for assistant + output["resultForAssistant"] = render_result_for_assistant(output) + + return output def render_result_for_assistant(output: dict[str, Any]) -> str: diff --git a/codemcp/tools/git_diff.py b/codemcp/tools/git_diff.py index 4d0d275f..8182bae1 100644 --- a/codemcp/tools/git_diff.py +++ b/codemcp/tools/git_diff.py @@ -69,43 +69,24 @@ async def git_diff( logging.debug(f"Executing git diff command: {' '.join(cmd)}") - try: - # Execute git diff command asynchronously - result = await run_command( - cmd=cmd, - cwd=absolute_path, - capture_output=True, - text=True, - check=False, # Don't raise exception if git diff fails - ) - - # Process results - if result.returncode != 0: - logging.error( - f"git diff failed with exit code {result.returncode}: {result.stderr}" - ) - error_message = f"Error: {result.stderr}" - return { - "output": error_message, - "resultForAssistant": error_message, - } - - # Prepare output - output = { - "output": result.stdout, - } - - # Add formatted result for assistant - output["resultForAssistant"] = render_result_for_assistant(output) - - return output - except Exception as e: - logging.exception(f"Error executing git diff: {e!s}") - error_message = f"Error executing git diff: {e!s}" - return { - "output": error_message, - "resultForAssistant": error_message, - } + # Execute git diff command asynchronously + result = await run_command( + cmd=cmd, + cwd=absolute_path, + capture_output=True, + text=True, + check=True, # Allow exception if git diff fails to propagate up + ) + + # Prepare output + output = { + "output": result.stdout, + } + + # Add formatted result for assistant + output["resultForAssistant"] = render_result_for_assistant(output) + + return output def render_result_for_assistant(output: dict[str, Any]) -> str: diff --git a/codemcp/tools/git_log.py b/codemcp/tools/git_log.py index 02580601..58426c29 100644 --- a/codemcp/tools/git_log.py +++ b/codemcp/tools/git_log.py @@ -68,43 +68,24 @@ async def git_log( logging.debug(f"Executing git log command: {' '.join(cmd)}") - try: - # Execute git log command asynchronously - result = await run_command( - cmd=cmd, - cwd=absolute_path, - capture_output=True, - text=True, - check=False, # Don't raise exception if git log fails - ) - - # Process results - if result.returncode != 0: - logging.error( - f"git log failed with exit code {result.returncode}: {result.stderr}" - ) - error_message = f"Error: {result.stderr}" - return { - "output": error_message, - "resultForAssistant": error_message, - } - - # Prepare output - output = { - "output": result.stdout, - } - - # Add formatted result for assistant - output["resultForAssistant"] = render_result_for_assistant(output) - - return output - except Exception as e: - logging.exception(f"Error executing git log: {e!s}") - error_message = f"Error executing git log: {e!s}" - return { - "output": error_message, - "resultForAssistant": error_message, - } + # Execute git log command asynchronously + result = await run_command( + cmd=cmd, + cwd=absolute_path, + capture_output=True, + text=True, + check=True, # Allow exception if git log fails to propagate up + ) + + # Prepare output + output = { + "output": result.stdout, + } + + # Add formatted result for assistant + output["resultForAssistant"] = render_result_for_assistant(output) + + return output def render_result_for_assistant(output: dict[str, Any]) -> str: diff --git a/codemcp/tools/git_show.py b/codemcp/tools/git_show.py index 8c561019..b3e8843c 100644 --- a/codemcp/tools/git_show.py +++ b/codemcp/tools/git_show.py @@ -70,43 +70,24 @@ async def git_show( logging.debug(f"Executing git show command: {' '.join(cmd)}") - try: - # Execute git show command asynchronously - result = await run_command( - cmd=cmd, - cwd=absolute_path, - capture_output=True, - text=True, - check=False, # Don't raise exception if git show fails - ) - - # Process results - if result.returncode != 0: - logging.error( - f"git show failed with exit code {result.returncode}: {result.stderr}" - ) - error_message = f"Error: {result.stderr}" - return { - "output": error_message, - "resultForAssistant": error_message, - } - - # Prepare output - output = { - "output": result.stdout, - } - - # Add formatted result for assistant - output["resultForAssistant"] = render_result_for_assistant(output) - - return output - except Exception as e: - logging.exception(f"Error executing git show: {e!s}") - error_message = f"Error executing git show: {e!s}" - return { - "output": error_message, - "resultForAssistant": error_message, - } + # Execute git show command asynchronously + result = await run_command( + cmd=cmd, + cwd=absolute_path, + capture_output=True, + text=True, + check=True, # Allow exception if git show fails to propagate up + ) + + # Prepare output + output = { + "output": result.stdout, + } + + # Add formatted result for assistant + output["resultForAssistant"] = render_result_for_assistant(output) + + return output def render_result_for_assistant(output: dict[str, Any]) -> str: diff --git a/codemcp/tools/glob.py b/codemcp/tools/glob.py index 75d27184..705e4e6b 100644 --- a/codemcp/tools/glob.py +++ b/codemcp/tools/glob.py @@ -70,35 +70,28 @@ async def glob( matches = [match for match in matches if match.is_file()] # Sort matches by modification time (newest first) - try: - loop = asyncio.get_event_loop() - - # Get file stats asynchronously - stats = [] - for match in matches: - stat = await loop.run_in_executor( - None, lambda m=match: os.stat(m) if os.path.exists(m) else None - ) - stats.append(stat) - - matches_with_stats = list(zip(matches, stats, strict=False)) - - # In tests, sort by filename for deterministic results - if os.environ.get("NODE_ENV") == "test": - matches_with_stats.sort(key=lambda x: str(x[0])) - else: - # Sort by modification time (newest first), with filename as tiebreaker - matches_with_stats.sort( - key=lambda x: (-(x[1].st_mtime if x[1] else 0), str(x[0])) - ) - - matches = [match for match, _ in matches_with_stats] - except Exception as e: - # Fall back to sorting by name if there's an error - logging.debug( - f"Error sorting by modification time, falling back to name sort: {e!s}", + loop = asyncio.get_event_loop() + + # Get file stats asynchronously + stats = [] + for match in matches: + stat = await loop.run_in_executor( + None, lambda m=match: os.stat(m) if os.path.exists(m) else None + ) + stats.append(stat) + + matches_with_stats = list(zip(matches, stats, strict=False)) + + # In tests, sort by filename for deterministic results + if os.environ.get("NODE_ENV") == "test": + matches_with_stats.sort(key=lambda x: str(x[0])) + else: + # Sort by modification time (newest first), with filename as tiebreaker + matches_with_stats.sort( + key=lambda x: (-(x[1].st_mtime if x[1] else 0), str(x[0])) ) - matches.sort(key=lambda x: str(x)) + + matches = [match for match, _ in matches_with_stats] # Convert Path objects to strings file_paths = [str(match) for match in matches] diff --git a/codemcp/tools/grep.py b/codemcp/tools/grep.py index d24bd5ec..43065036 100644 --- a/codemcp/tools/grep.py +++ b/codemcp/tools/grep.py @@ -180,38 +180,30 @@ async def grep_files( matches = await git_grep(pattern, path, include, signal) # Sort matches - try: - # First try sorting by modification time - import asyncio - - loop = asyncio.get_event_loop() - - # Get file stats asynchronously - stats = [] - for match in matches: - stat = await loop.run_in_executor( - None, lambda m=match: os.stat(m) if os.path.exists(m) else None - ) - stats.append(stat) - - matches_with_stats = list(zip(matches, stats, strict=False)) + # Use asyncio for getting file stats + import asyncio + loop = asyncio.get_event_loop() + + # Get file stats asynchronously + stats = [] + for match in matches: + stat = await loop.run_in_executor( + None, lambda m=match: os.stat(m) if os.path.exists(m) else None + ) + stats.append(stat) - # In tests, sort by filename for deterministic results - if os.environ.get("NODE_ENV") == "test": - matches_with_stats.sort(key=lambda x: x[0]) - else: - # Sort by modification time (newest first), with filename as tiebreaker - matches_with_stats.sort( - key=lambda x: (-(x[1].st_mtime if x[1] else 0), x[0]) - ) + matches_with_stats = list(zip(matches, stats, strict=False)) - matches = [match for match, _ in matches_with_stats] - except Exception as e: - # Fall back to sorting by name if there's an error - logging.debug( - f"Error sorting by modification time, falling back to name sort: {e!s}", + # In tests, sort by filename for deterministic results + if os.environ.get("NODE_ENV") == "test": + matches_with_stats.sort(key=lambda x: x[0]) + else: + # Sort by modification time (newest first), with filename as tiebreaker + matches_with_stats.sort( + key=lambda x: (-(x[1].st_mtime if x[1] else 0), x[0]) ) - matches.sort() + + matches = [match for match, _ in matches_with_stats] # Prepare output output = { diff --git a/e2e/test_git_tools.py b/e2e/test_git_tools.py index dbdaa67a..d6a1ac61 100644 --- a/e2e/test_git_tools.py +++ b/e2e/test_git_tools.py @@ -102,18 +102,3 @@ async def test_invalid_path(self): ): with self.assertRaises(ValueError): await git_blame(path="/invalid/path") - - async def test_command_failure(self): - """Test that tools handle command failures.""" - # Test with invalid arguments - result = await git_log(arguments="--invalid-option", path=self.temp_dir.name) - self.assertIn("Error", result["resultForAssistant"]) - - result = await git_diff(arguments="--invalid-option", path=self.temp_dir.name) - self.assertIn("Error", result["resultForAssistant"]) - - result = await git_show(arguments="--invalid-option", path=self.temp_dir.name) - self.assertIn("Error", result["resultForAssistant"]) - - result = await git_blame(arguments="--invalid-option", path=self.temp_dir.name) - self.assertIn("Error", result["resultForAssistant"])