diff --git a/codetide/agents/tide/agent.py b/codetide/agents/tide/agent.py index a30afe8..c6621cd 100644 --- a/codetide/agents/tide/agent.py +++ b/codetide/agents/tide/agent.py @@ -62,7 +62,7 @@ def pass_custom_logger_fn(self)->Self: def approve(self): self._has_patch = False if os.path.exists(self.patch_path): - changed_paths = process_patch(self.patch_path, open_file, write_file, remove_file, file_exists) + changed_paths = process_patch(self.patch_path, open_file, write_file, remove_file, file_exists, root_path=self.tide.rootpath) self.changed_paths.extend(changed_paths) previous_response = self.history[-1] @@ -81,10 +81,10 @@ def reject(self, feedback :str): @property def patch_path(self)->Path: - if not os.path.exists(DEFAULT_STORAGE_PATH): - os.makedirs(DEFAULT_STORAGE_PATH, exist_ok=True) + if not os.path.exists(self.tide.rootpath / DEFAULT_STORAGE_PATH): + os.makedirs(self.tide.rootpath / DEFAULT_STORAGE_PATH, exist_ok=True) - return DEFAULT_STORAGE_PATH / f"{self.session_id}.bash" + return self.tide.rootpath / DEFAULT_STORAGE_PATH / f"{self.session_id}.bash" @staticmethod def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): @@ -220,12 +220,12 @@ def commit(self, message :str): """ try: # Open the repository - repo = self.repo + repo = self.tide.repo # Get author and committer information config = repo.config - author_name = config.get('user.name', 'Unknown Author') - author_email = config.get('user.email', 'unknown@example.com') + author_name = config._get('user.name')[1].value or 'Unknown Author' + author_email = config._get('user.email')[1].value or 'unknown@example.com' author = pygit2.Signature(author_name, author_email) committer = author # Typically same as author diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py index 68945e4..b44d7ac 100644 --- a/codetide/agents/tide/prompts.py +++ b/codetide/agents/tide/prompts.py @@ -327,7 +327,8 @@ """ CMD_TRIGGER_PLANNING_STEPS = """ - +You must operate in a multi-step planning and execution mode: first outline the plan step by step in a sequential way, then ask for my revision. +Do not start implementing the steps without my approval. """ CMD_WRITE_TESTS_PROMPT = """ diff --git a/codetide/core/defaults.py b/codetide/core/defaults.py index 49bc901..9e4b7ca 100644 --- a/codetide/core/defaults.py +++ b/codetide/core/defaults.py @@ -89,6 +89,8 @@ BREAKLINE = "\n" +DEFAULT_BYTES_CONTENT_PLACEHOLDERS = "< suppressed bytes content >" + CODETIDE_ASCII_ART = """ ███████╗ ██████╗ ██████╗ ███████╗████████╗██╗██████╗ ███████╗ diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index 59f96b4..b933065 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -3,7 +3,7 @@ from .parser import Parser, patch_to_commit # from ....core.common import writeFile -from typing import Dict, Tuple, List, Callable +from typing import Dict, Optional, Tuple, List, Callable, Union import pathlib import os @@ -108,18 +108,24 @@ def process_patch( open_fn: Callable[[str], str], write_fn: Callable[[str, str], None], remove_fn: Callable[[str], None], - exists_fn: Callable[[str], bool] + exists_fn: Callable[[str], bool], + root_path: Optional[Union[str, pathlib.Path]]=None ) -> List[str]: """The main entrypoint function to process a patch from text to filesystem.""" if not os.path.exists(patch_path): raise DiffError("Patch path {patch_path} does not exist.") + if root_path is not None: + root_path = pathlib.Path(root_path) + # Normalize line endings before processing text = open_fn(patch_path) # FIX: Check for existence of files to be added before parsing. paths_to_add = identify_files_added(text) for p in paths_to_add: + if root_path is not None: + p = str(root_path / p) if exists_fn(p): raise DiffError(f"Add File Error - file already exists: {p}") @@ -128,6 +134,8 @@ def process_patch( # Load files with normalized line endings orig_files = {} for path in paths_needed: + if root_path is not None: + path = str(root_path / path) orig_files[path] = open_fn(path) patch, _fuzz = text_to_patch(text, orig_files) diff --git a/codetide/parsers/generic_parser.py b/codetide/parsers/generic_parser.py index 6a75aec..a1aa125 100644 --- a/codetide/parsers/generic_parser.py +++ b/codetide/parsers/generic_parser.py @@ -1,4 +1,5 @@ from ..core.models import CodeBase, CodeFileModel, ImportStatement +from ..core.defaults import DEFAULT_BYTES_CONTENT_PLACEHOLDERS from ..parsers.base_parser import BaseParser from ..core.common import readFile @@ -54,10 +55,10 @@ async def parse_file(self, file_path: Union[str, Path], root_path: Optional[Unio return codeFile - def parse_code(self, file_path :Path, code :str): + def parse_code(self, file_path :Path, code :Optional[str]=None): codeFile = CodeFileModel( file_path=str(file_path), - raw=code + raw=code if not isinstance(code, bytes) else DEFAULT_BYTES_CONTENT_PLACEHOLDERS ) return codeFile diff --git a/examples/hf_demo_space/app.py b/examples/hf_demo_space/app.py index 4993f8f..788dc24 100644 --- a/examples/hf_demo_space/app.py +++ b/examples/hf_demo_space/app.py @@ -18,7 +18,7 @@ from aicore.llm import Llm, LlmConfig from aicore.config import Config -from git_utils import push_new_branch, validate_git_url, checkout_new_branch +from git_utils import commit_and_push_changes, validate_git_url, checkout_new_branch from chainlit.cli import run_chainlit from typing import Optional from pathlib import Path @@ -153,6 +153,7 @@ async def start_chatr(): agent_tide_ui.agent_tide.llm = Llm.from_config(config.llm) agent_tide_ui.agent_tide.llm.provider.session_id = agent_tide_ui.agent_tide.session_id + agent_tide_ui.agent_tide.pass_custom_logger_fn() session_dir_path = DEFAULT_SESSIONS_WORKSPACE / session_id if not os.path.exists(session_dir_path): @@ -260,10 +261,9 @@ async def on_stop_steps(action :cl.Action): @cl.action_callback("checkout_commit_push") async def on_checkout_commit_push(action :cl.Action): agent_tide_ui: AgentTideUi = cl.user_session.get("AgentTideUi") - await agent_tide_ui.agent_tide.prepare_commit() - agent_tide_ui.agent_tide.commit("AgentTide - add all and push") - - push_new_branch(agent_tide_ui.agent_tide.tide.repo, branch_name=cl.user_session.get("current_branch_name")) + # await agent_tide_ui.agent_tide.prepare_commit() + # agent_tide_ui.agent_tide.commit("AgentTide - add all and push") + await commit_and_push_changes(agent_tide_ui.agent_tide.tide.rootpath, branch_name=cl.user_session.get("current_branch_name"), commit_message="AgentTide - add all and push", checkout=False) @cl.action_callback("inspect_code_context") async def on_inspect_context(action :cl.Action): diff --git a/examples/hf_demo_space/git_utils.py b/examples/hf_demo_space/git_utils.py index 0ad4686..b9b12fc 100644 --- a/examples/hf_demo_space/git_utils.py +++ b/examples/hf_demo_space/git_utils.py @@ -41,7 +41,7 @@ async def validate_git_url(url) -> None: except subprocess.CalledProcessError as e: raise ValueError(f"Invalid Git repository URL: {url}. Error: {e.stderr}") from e -async def commit_and_push_changes(repo_path: Path, branch_name: str = None, commit_message: str = "Auto-commit: Save changes") -> None: +async def commit_and_push_changes(repo_path: Path, branch_name: str = None, commit_message: str = "Auto-commit: Save changes", checkout :bool=True) -> None: """Add all changes, commit with default message, and push to remote.""" repo_path_str = str(repo_path) @@ -51,27 +51,26 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm if not branch_name: branch_name = f"agent-tide-{ulid()}" - # Create and checkout new branch - process = await asyncio.create_subprocess_exec( - "git", "checkout", "-b", branch_name, - cwd=repo_path_str, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - text=True - ) - - stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=10) - - if process.returncode != 0: - raise subprocess.CalledProcessError(process.returncode, ["git", "checkout", "-b", branch_name], stdout, stderr) - + if checkout: + # Create and checkout new branch + process = await asyncio.create_subprocess_exec( + "git", "checkout", "-b", branch_name, + cwd=repo_path_str, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + + stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=10) + + if process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, ["git", "checkout", "-b", branch_name], stdout, stderr) + # Add all changes process = await asyncio.create_subprocess_exec( "git", "add", ".", cwd=repo_path_str, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - text=True + stderr=asyncio.subprocess.PIPE ) stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30) @@ -84,8 +83,7 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm "git", "commit", "-m", commit_message, cwd=repo_path_str, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - text=True + stderr=asyncio.subprocess.PIPE ) stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30) @@ -101,8 +99,7 @@ async def commit_and_push_changes(repo_path: Path, branch_name: str = None, comm "git", "push", "origin", branch_name, cwd=repo_path_str, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - text=True + stderr=asyncio.subprocess.PIPE ) stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60)