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
43 changes: 24 additions & 19 deletions codemcp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .git_query import get_current_commit_hash
from .tools.chmod import chmod
from .tools.edit_file import edit_file_content
from .tools.glob import MAX_RESULTS, glob_files
from .tools.glob import glob_files
from .tools.grep import grep_files
from .tools.init_project import init_project
from .tools.ls import ls_directory
Expand Down Expand Up @@ -182,7 +182,7 @@ async def codemcp(
if path is None:
raise ValueError("path is required for ReadFile subtool")

result = await read_file_content(path, offset, limit, chat_id)
result = await read_file_content(path, offset, limit, chat_id, commit_hash)
return result

if subtool == "WriteFile":
Expand All @@ -193,7 +193,9 @@ async def codemcp(
if chat_id is None:
raise ValueError("chat_id is required for WriteFile subtool")

result = await write_file_content(path, content, description, chat_id)
result = await write_file_content(
path, content, description, chat_id, commit_hash
)
return result

if subtool == "EditFile":
Expand All @@ -210,20 +212,20 @@ async def codemcp(
raise ValueError("chat_id is required for EditFile subtool")

# Accept either old_string or old_str (prefer old_string if both are provided)
old_content = old_string or old_str or ""
old_content = old_string or old_str
# Accept either new_string or new_str (prefer new_string if both are provided)
new_content = new_string or new_str or ""
new_content = new_string or new_str

result = await edit_file_content(
path, old_content, new_content, None, description, chat_id
path, old_content, new_content, None, description, chat_id, commit_hash
)
return result

if subtool == "LS":
if path is None:
raise ValueError("path is required for LS subtool")

result = await ls_directory(path, chat_id)
result = await ls_directory(path, chat_id, commit_hash)
return result

if subtool == "InitProject":
Expand Down Expand Up @@ -255,7 +257,7 @@ async def codemcp(
if chat_id is None:
raise ValueError("chat_id is required for RunCommand subtool")

result = await run_command(path, command, arguments, chat_id)
result = await run_command(path, command, arguments, chat_id, commit_hash)
return result

if subtool == "Grep":
Expand All @@ -265,7 +267,9 @@ async def codemcp(
raise ValueError("path is required for Grep subtool")

try:
grep_result = await grep_files(pattern, path, include, chat_id)
grep_result = await grep_files(
pattern, path, include, chat_id, commit_hash
)
result_string = grep_result.get(
"resultForAssistant",
f"Found {grep_result.get('numFiles', 0)} file(s)",
Expand All @@ -286,9 +290,10 @@ async def codemcp(
glob_result = await glob_files(
pattern,
path,
limit=limit if limit is not None else MAX_RESULTS,
offset=offset if offset is not None else 0,
chat_id=chat_id,
limit,
offset,
chat_id,
commit_hash,
)
result_string = glob_result.get(
"resultForAssistant",
Expand All @@ -308,7 +313,7 @@ async def codemcp(
if chat_id is None:
raise ValueError("chat_id is required for RM subtool")

result = await rm_file(path, description, chat_id)
result = await rm_file(path, description, chat_id, commit_hash)
return result

if subtool == "MV":
Expand All @@ -325,17 +330,16 @@ async def codemcp(
if chat_id is None:
raise ValueError("chat_id is required for MV subtool")

result = await mv_file(source_path, target_path, description, chat_id)
result = await mv_file(
source_path, target_path, description, chat_id, commit_hash
)
return result

if subtool == "Think":
if thought is None:
raise ValueError("thought is required for Think subtool")

result = await think(thought, chat_id)
# Think doesn't need a path, but we might have one in the provided parameters
if path:
return result
result = await think(thought, chat_id, commit_hash)
return result

if subtool == "Chmod":
Expand All @@ -346,11 +350,12 @@ async def codemcp(
if chat_id is None:
raise ValueError("chat_id is required for Chmod subtool")

chmod_result = await chmod(path, mode, chat_id)
chmod_result = await chmod(path, mode, chat_id, commit_hash)
result_string = chmod_result.get(
"resultForAssistant", "Chmod operation completed"
)
return result_string

except Exception:
logging.error("Exception", exc_info=True)
raise
Expand Down
7 changes: 6 additions & 1 deletion codemcp/tools/chmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,22 @@ async def chmod(
path: str,
mode: str, # Changed from Literal["a+x", "a-x"] to str to handle validation internally
chat_id: str | None = None,
commit_hash: str | None = None,
) -> dict[str, Any]:
"""Change file permissions using chmod.

Args:
path: The absolute path to the file to modify
mode: The chmod mode to apply, only "a+x" and "a-x" are supported
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking

Returns:
A dictionary with chmod output
"""
# Set default values
chat_id = "" if chat_id is None else chat_id

if not path:
raise ValueError("File path must be provided")

Expand Down Expand Up @@ -113,7 +118,7 @@ async def chmod(
success, commit_message = await commit_changes(
directory,
description,
chat_id if chat_id is not None else "",
chat_id,
)

if not success:
Expand Down
11 changes: 8 additions & 3 deletions codemcp/tools/commit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@
]


async def append_commit_hash(result: str, path: str | None) -> Tuple[str, str | None]:
async def append_commit_hash(
result: str, path: str | None, commit_hash: str | None = None
) -> Tuple[str, str | None]:
"""Get the current Git commit hash and append it to the result string.

Args:
result: The original result string to append to
path: Path to the Git repository (if available)
commit_hash: Optional Git commit hash to use instead of fetching the current one

Returns:
A tuple containing:
- The result string with the commit hash appended
- The current commit hash if available, None otherwise
"""
current_hash = None
# If commit_hash is provided, use it directly
if commit_hash:
return f"{result}\n\nCurrent commit hash: {commit_hash}", commit_hash

if path is None:
return result, None
Expand All @@ -38,4 +43,4 @@ async def append_commit_hash(result: str, path: str | None) -> Tuple[str, str |
except Exception as e:
logging.warning(f"Failed to get current commit hash: {e}", exc_info=True)

return result, current_hash
return result, None
22 changes: 15 additions & 7 deletions codemcp/tools/edit_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,11 +746,12 @@ def debug_string_comparison(

async def edit_file_content(
file_path: str,
old_string: str,
new_string: str,
old_string: str | None = None,
new_string: str | None = None,
read_file_timestamps: dict[str, float] | None = None,
description: str = "",
chat_id: str = "",
description: str | None = None,
chat_id: str | None = None,
commit_hash: str | None = None,
) -> str:
"""Edit a file by replacing old_string with new_string.

Expand All @@ -761,11 +762,12 @@ async def edit_file_content(

Args:
file_path: The absolute path to the file to edit
old_string: The text to replace
old_string: The text to replace (use empty string for new file creation)
new_string: The new text to replace old_string with
read_file_timestamps: Dictionary mapping file paths to timestamps when they were last read
description: Short description of the change
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking

Returns:
A success message
Expand All @@ -776,6 +778,12 @@ async def edit_file_content(
Files must be tracked in the git repository before they can be modified.

"""
# Set default values
old_string = "" if old_string is None else old_string
new_string = "" if new_string is None else new_string
description = "" if description is None else description
chat_id = "" if chat_id is None else chat_id

# Normalize the file path
full_file_path = normalize_file_path(file_path)

Expand Down Expand Up @@ -840,7 +848,7 @@ async def edit_file_content(

result = f"Successfully created {full_file_path}{git_message}"
# Append commit hash
result, _ = await append_commit_hash(result, full_file_path)
result, _ = await append_commit_hash(result, full_file_path, commit_hash)
return result

# Check if file exists
Expand Down Expand Up @@ -936,5 +944,5 @@ async def edit_file_content(
result = f"Successfully edited {full_file_path}\n\nHere's a snippet of the edited file:\n{snippet}{format_message}{git_message}"

# Append commit hash
result, _ = await append_commit_hash(result, full_file_path)
result, _ = await append_commit_hash(result, full_file_path, commit_hash)
return result
13 changes: 10 additions & 3 deletions codemcp/tools/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ def render_result_for_assistant(output: Dict[str, Any]) -> str:
async def glob_files(
pattern: str,
path: str | None = None,
limit: int = MAX_RESULTS,
offset: int = 0,
limit: int | None = None,
offset: int | None = None,
chat_id: str | None = None,
commit_hash: str | None = None,
) -> Dict[str, Any]:
"""Search for files matching a glob pattern.

Expand All @@ -158,15 +159,21 @@ async def glob_files(
limit: Maximum number of results to return
offset: Number of results to skip (for pagination)
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking

Returns:
A dictionary with matched files

"""
try:
# Use current directory if path is not provided
directory = path or os.getcwd()
normalized_path = normalize_file_path(directory)

# Set default values for limit and offset
limit = limit or MAX_RESULTS
offset = offset or 0

# Execute glob with options for pagination
options = {"limit": limit, "offset": offset}
result = await glob(pattern, normalized_path, options)
Expand All @@ -176,7 +183,7 @@ async def glob_files(

# Append commit hash
formatted_result, _ = await append_commit_hash(
formatted_result, normalized_path
formatted_result, normalized_path, commit_hash
)
result["resultForAssistant"] = formatted_result

Expand Down
12 changes: 10 additions & 2 deletions codemcp/tools/grep.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ async def grep_files(
path: str | None = None,
include: str | None = None,
chat_id: str | None = None,
commit_hash: str | None = None,
) -> Dict[str, Any]:
"""Search for a pattern in files within a directory or in a specific file.

Expand All @@ -169,14 +170,21 @@ async def grep_files(
path: The directory or file to search in (must be in a git repository)
include: Optional file pattern to filter the search
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking

Returns:
A dictionary with matched files

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

# Default to current directory if path is not provided
path = "." if path is None else path

# Normalize the path
normalized_path = normalize_file_path(path) if path else None
normalized_path = normalize_file_path(path)

# Execute git grep
matched_files = await git_grep(pattern, normalized_path, include)
Expand All @@ -201,7 +209,7 @@ async def grep_files(
# Append commit hash
if normalized_path:
result_for_assistant, _ = await append_commit_hash(
result_for_assistant, normalized_path
result_for_assistant, normalized_path, commit_hash
)

output["resultForAssistant"] = result_for_assistant
Expand Down
10 changes: 8 additions & 2 deletions codemcp/tools/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@
TRUNCATED_MESSAGE = f"There are more than {MAX_FILES} files in the directory. Use more specific paths to explore nested directories. The first {MAX_FILES} files and directories are included below:\n\n"


async def ls_directory(directory_path: str, chat_id: str | None = None) -> str:
async def ls_directory(
directory_path: str, chat_id: str | None = None, commit_hash: str | None = None
) -> str:
"""List the contents of a directory.

Args:
directory_path: The absolute path to the directory to list
chat_id: The unique ID of the current chat session
commit_hash: Optional Git commit hash for version tracking

Returns:
A formatted string representation of the directory contents

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

# Normalize the directory path
full_directory_path = normalize_file_path(directory_path)

Expand Down Expand Up @@ -69,7 +75,7 @@ async def ls_directory(directory_path: str, chat_id: str | None = None) -> str:
output = f"{TRUNCATED_MESSAGE}{tree_output}"

# Append commit hash
result, _ = await append_commit_hash(output, full_directory_path)
result, _ = await append_commit_hash(output, full_directory_path, commit_hash)
return result


Expand Down
Loading