Skip to content

Commit 803e756

Browse files
authored
Merge pull request #39 from BrunoV21/expandable-reasoning
🧠 Implement expandable reasoning system with enhanced UI and agent capabilities
2 parents 12c2fdf + 2088263 commit 803e756

File tree

18 files changed

+3386
-537
lines changed

18 files changed

+3386
-537
lines changed

README.md

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ uvx --from codetide codetide-cli --help
4242
```
4343
## AgentTide
4444

45-
AgentTide consists of a demo, showing how CodeTide can integrate with LLMs and augment code generation and condebase related workflows. If you ask Tide to describe himself, he will say something like this: I'm the next-generation, precision-driven software engineering agent built on top of CodeTide. You can use it via the command-line interface (CLI) or a beautiful interactive UI.
45+
AgentTide is a next-generation, precision-driven software engineering agent built on top of CodeTide. It is ready to help you dig deep into your codebase, automate code changes, and provide intelligent, context-aware assistance. You can use it via the command-line interface (CLI) or a beautiful interactive UI.
4646

47-
> **Demo available:** Try AgentTide live on Hugging Face Spaces: [https://mclovinittt-agenttidedemo.hf.space/](https://mclovinittt-agenttidedemo.hf.space/)
47+
> **Try AgentTide live:** [https://mclovinittt-agenttidedemo.hf.space/](https://mclovinittt-agenttidedemo.hf.space/)
4848
4949
---
5050

@@ -57,46 +57,44 @@ AgentTide consists of a demo, showing how CodeTide can integrate with LLMs and a
5757

5858
**AgentTide CLI**
5959

60-
To use the AgentTide conversational CLI, you must install the `[agents]` extra and launch via:
60+
To use the AgentTide conversational CLI, install the `[agents]` extra and launch via:
6161

6262
```sh
6363
uvx --from codetide[agents] agent-tide
6464
```
6565

66-
This will start an interactive terminal session with AgentTide.
67-
68-
You can also pass the `--project_path` argument to start AgentTide on a specific path:
66+
This starts an interactive terminal session with AgentTide. You can specify a project path:
6967

7068
```sh
7169
uvx --from codetide[agents] agent-tide --project_path /path/to/your/project
7270
```
7371

74-
If you do not provide the `--project_path` argument, AgentTide will start in the current directory by default.
72+
If `--project_path` is not provided, AgentTide starts in the current directory.
7573

7674
**AgentTide UI**
7775

78-
To use the AgentTide web UI, you must install the `[agents-ui]` extra and launch via:
76+
To use the AgentTide web UI, install the `[agents-ui]` extra and launch:
7977

8078
```sh
8179
uvx --from codetide[agents-ui] agent-tide-ui
8280
```
8381

84-
This will start a web server for the AgentTide UI. Follow the on-screen instructions to interact with the agent in your browser at [http://localhost:9753](http://localhost:9753) (or the port you specified)
82+
This starts a web server for the AgentTide UI. Interact with the agent in your browser at [http://localhost:9753](http://localhost:9753) (or your specified port).
8583

86-
### Why Try AgentTide? ([Full Guide & Tips Here](codetide/agents/tide/ui/chainlit.md))
84+
### Why Use AgentTide? ([Full Guide & Tips Here](codetide/agents/tide/ui/chainlit.md))
8785

88-
**Local-First & Private:** All code analysis and patching is performed locally. Your code never leaves your machine.
86+
- **Local-First & Private:** All code analysis and patching is performed locally. Your code never leaves your machine.
8987
- **Transparent & Stepwise:** See every plan and patch before it's applied. Edit, reorder, or approve steps—you're always in control.
9088
- **Context-Aware:** AgentTide loads only the relevant code identifiers and dependencies for your request, making it fast and precise.
9189
- **Human-in-the-Loop:** After each step, review the patch, provide feedback, or continue—no black-box agent behavior.
9290
- **Patch-Based Editing:** All changes are atomic diffs, not full file rewrites, for maximum clarity and efficiency.
9391

9492
**Usage Tips:**
9593
- If you know the exact code context, specify identifiers directly in your request (e.g., `module.submodule.file_withoutextension.object`).
96-
- You can use the `plan` command to generate a step-by-step implementation plan for your request, review and edit the plan, and then proceed step-by-step.
97-
- The `commit` command allows you to review and finalize changes before they are applied.
94+
- Use the `plan` command to generate a step-by-step implementation plan for your request, review and edit the plan, and then proceed step-by-step.
95+
- Use the `commit` command to review and finalize changes before they are applied.
9896

99-
See the [chainlit.md](codetide/agents/tide/ui/chainlit.md) for full details and advanced workflows, including the latest specifications for these commands!
97+
See [chainlit.md](codetide/agents/tide/ui/chainlit.md) for full details and advanced workflows, including the latest specifications for these commands!
10098

10199
---
102100

@@ -156,7 +154,7 @@ CodeTide provides the following tools for agents:
156154
2. **`getRepoTree`**: Explore the repository structure.
157155

158156
#### Example: Initializing an LLM with CodeTide
159-
Heres a snippet from `agent_tide.py` demonstrating how to initialize an LLM with CodeTide as an MCP server:
157+
Here's a snippet from `agent_tide.py` demonstrating how to initialize an LLM with CodeTide as an MCP server:
160158

161159
```python
162160
from aicore.llm import Llm, LlmConfig
@@ -176,7 +174,7 @@ def init_llm() -> Llm:
176174
return llm
177175
```
178176

179-
This setup allows the LLM to leverage CodeTides tools for codebase interactions.
177+
This setup allows the LLM to leverage CodeTide's tools for codebase interactions.
180178

181179
CodeTide can now be used as an MCP Server! This allows seamless integration with AI tools and workflows. Below are the tools available:
182180
The available tools are:
@@ -517,7 +515,7 @@ if __name__ == "__main__":
517515
518516
## 🧠 Philosophy
519517
520-
CodeTide is about giving developers structure-aware tools that are **fast, predictable, and private**. Your code is parsed, navigated, and queried as a symbolic graph - not treated as a black box of tokens. Whether youre building, refactoring, or feeding context into an LLM - **you stay in control**.
518+
CodeTide is about giving developers structure-aware tools that are **fast, predictable, and private**. Your code is parsed, navigated, and queried as a symbolic graph - not treated as a black box of tokens. Whether you're building, refactoring, or feeding context into an LLM - **you stay in control**.
521519
522520
> Like a tide, your codebase evolves - and CodeTide helps you move with it, intelligently.
523521
@@ -539,7 +537,7 @@ Instead, it uses:
539537
540538
## 🗺️ Roadmap
541539
542-
Heres whats next for CodeTide:
540+
Here's what's next for CodeTide:
543541
544542
- 🧩 **Support more languages** already integrated with [Tree-sitter](https://tree-sitter.github.io/tree-sitter/)
545543
→ **TypeScript** is the top priority. **Now available in Beta**
@@ -554,11 +552,11 @@ Here’s what’s next for CodeTide:
554552
555553
## 🤖 Agents Module: AgentTide
556554
557-
> **Demo available:** Try AgentTide live on Hugging Face Spaces: [https://mclovinittt-agenttidedemo.hf.space/](https://mclovinittt-agenttidedemo.hf.space/)
555+
> **Try AgentTide live:** [https://mclovinittt-agenttidedemo.hf.space/](https://mclovinittt-agenttidedemo.hf.space/)
558556
559-
CodeTide now includes an `agents` module, featuring **AgentTide**—a precision-driven software engineering agent that connects directly to your codebase and executes your requests with full code context.
557+
CodeTide now includes an `agents` module, featuring **AgentTide**—a production-ready, precision-driven software engineering agent that connects directly to your codebase and executes your requests with full code context.
560558
561-
**AgentTide** leverages CodeTides symbolic code understanding to:
559+
**AgentTide** leverages CodeTide's symbolic code understanding to:
562560
- Retrieve and reason about relevant code context for any request
563561
- Generate atomic, high-precision patches using strict protocols
564562
- Apply changes directly to your codebase, with robust validation
@@ -567,14 +565,15 @@ CodeTide now includes an `agents` module, featuring **AgentTide**—a precision-
567565
- Source: [`codetide/agents/tide/agent.py`](codetide/agents/tide/agent.py)
568566
569567
### What It Does
570-
AgentTide acts as an autonomous agent that:
571-
- Connects to your codebase using CodeTide’s parsing and context tools
572-
- Interacts with users via a conversational interface
573-
- Identifies relevant files, classes, and functions for any request
574-
- Generates and applies diff-style patches, ensuring code quality and requirements fidelity
568+
AgentTide is an autonomous, precision-driven software engineering agent that:
569+
- Connects to your codebase using CodeTide's parsing and context tools
570+
- Interacts with users via a conversational interface (CLI or UI)
571+
- Identifies relevant files, classes, and functions for any request using advanced identifier resolution and code search
572+
- Generates and applies atomic, diff-style patches using a strict protocol, ensuring code quality and requirements fidelity
573+
- Supports stepwise planning, patch review, and human-in-the-loop approval for every change
575574
576575
### Example Usage
577-
To use AgentTide, ensure you have the `aicore` package installed (`pip install codetide[agents]`), then instantiate and run the agent:
576+
To use AgentTide programmatically, ensure you have the `aicore` package installed (`pip install codetide[agents]`), then instantiate and run the agent:
578577
579578
```python
580579
from codetide import CodeTide
@@ -599,10 +598,10 @@ if __name__ == "__main__":
599598
asyncio.run(main())
600599
```
601600
602-
AgentTide will prompt you for requests, retrieve the relevant code context, and generate precise patches to fulfill your requirements.
601+
AgentTide will prompt you for requests, retrieve the relevant code context, and generate precise, atomic patches to fulfill your requirements. All changes are patch-based and require explicit approval before being applied.
603602
604-
**Disclaimer:**
605-
AgentTide is designed for focused, context-aware code editing, not for generating entire applications from vague ideas. While CodeTide as a platform can support larger workflows, the current version of AgentTide is optimized for making precise, well-scoped changes. For best results, provide one clear request at a time. AgentTide does not yet have access to your terminal or the ability to execute commands, but support for test-based validation is planned in future updates.
603+
**Note:**
604+
AgentTide is designed for focused, context-aware code editing, not for generating entire applications from vague ideas. For best results, provide one clear request at a time. AgentTide does not execute code or shell commands, but support for test-based validation is planned in future updates.
606605
607606
For more details, see the [agents module source code](codetide/agents/tide/agent.py).
608607

codetide/__init__.py

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pydantic import BaseModel, ConfigDict, Field, field_validator
1515
from typing import Optional, List, Tuple, Union, Dict
1616
from datetime import datetime, timezone
17+
from collections import defaultdict
1718
from pathlib import Path
1819
import traceback
1920
import asyncio
@@ -97,10 +98,27 @@ def relative_filepaths(self)->List[str]:
9798
return [
9899
str(filepath.relative_to(self.rootpath)).replace("\\", "/") for filepath in self.files
99100
]
101+
102+
@property
103+
def relative_directories(self) -> List[str]:
104+
dirs = set()
105+
for filepath in self.files:
106+
p = filepath.resolve().parent
107+
while p != self.rootpath:
108+
dirs.add(p.relative_to(self.rootpath).as_posix())
109+
p = p.parent
110+
return sorted(dirs)
111+
112+
@property
113+
def filenames_mapped(self)->Dict[str, str]:
114+
return {
115+
filepath.name: str(filepath.relative_to(self.rootpath)).replace("\\", "/")
116+
for filepath in self.files
117+
}
100118

101119
@property
102120
def cached_ids(self)->List[str]:
103-
return self.codebase.non_import_unique_ids+self.relative_filepaths
121+
return self.codebase.non_import_unique_ids + self.relative_filepaths + self.relative_directories
104122

105123
@property
106124
def repo(self)->Optional[pygit2.Repository]:
@@ -412,6 +430,7 @@ def _get_changed_files(self) -> Tuple[List[Path], bool]:
412430
"""
413431
file_deletion_detected = False
414432
files = self._find_code_files() # Dict[Path, datetime]
433+
print("found code files")
415434

416435
changed_files = []
417436

@@ -528,6 +547,97 @@ def _is_file_content_valid(filepath :Path)->bool:
528547

529548
return True
530549

550+
@staticmethod
551+
def _is_subdirectory(identifier: str) -> bool:
552+
"""
553+
Check if an identifier represents a module/subdirectory.
554+
555+
Args:
556+
identifier: A string or Path object to check
557+
558+
Returns:
559+
True if the identifier ends with '/' (indicating a module), False otherwise
560+
"""
561+
if isinstance(identifier, Path):
562+
return False
563+
elif identifier.endswith("/"):
564+
return True
565+
else:
566+
return False
567+
568+
def get_module_identifiers(self, module_ids: List[str]) -> Dict[str, List[str]]:
569+
"""
570+
Get all file identifiers that belong to specified modules.
571+
572+
Args:
573+
module_ids: List of module identifier strings (directories)
574+
575+
Returns:
576+
Dictionary mapping module names to lists of relative file paths within each module
577+
"""
578+
module_paths = {
579+
self.rootpath / module_id
580+
for module_id in module_ids
581+
}
582+
modules_identifiers = defaultdict(list)
583+
for filepath in self.files:
584+
for module_path in module_paths:
585+
if filepath.is_relative_to(module_path):
586+
modules_identifiers[module_path.name].append(
587+
str(filepath.relative_to(self.rootpath))
588+
)
589+
break
590+
591+
# Log the results
592+
logger.info(f"Found {len(modules_identifiers)} modules")
593+
for module_name, identifiers in modules_identifiers.items():
594+
logger.info(f"Module '{module_name}' contains {len(identifiers)} identifiers")
595+
596+
return modules_identifiers
597+
598+
def inject_identifiers_from_modules(self, unique_ids: List[str]) -> List[str]:
599+
"""
600+
Expand module identifiers into their constituent file identifiers.
601+
602+
Takes a list of identifiers that may include module directories, finds all files
603+
within those modules, and replaces the module identifiers with individual file paths.
604+
605+
Args:
606+
unique_ids: List of identifiers, may include both files and modules (ending with '/')
607+
608+
Returns:
609+
Expanded list with module identifiers replaced by their constituent file identifiers
610+
"""
611+
modules_identifiers = [
612+
unique_id for unique_id in unique_ids if self._is_subdirectory(unique_id)
613+
]
614+
identifiers_per_module = self.get_module_identifiers(module_ids=modules_identifiers)
615+
616+
unique_ids = [
617+
unique_id for unique_id in unique_ids
618+
if unique_id not in modules_identifiers
619+
]
620+
for identifiers in identifiers_per_module.values():
621+
unique_ids.extend(identifiers)
622+
623+
return unique_ids
624+
625+
def precheck(self, unique_ids: List[str]) -> Dict[Path, str]:
626+
"""
627+
Preprocess and validate identifiers before further operations.
628+
629+
Expands any module identifiers into their constituent files and validates
630+
that all identifiers correspond to actual files.
631+
632+
Args:
633+
unique_ids: List of file or module identifiers to precheck
634+
635+
Returns:
636+
Dictionary mapping validated file paths to their identifier strings
637+
"""
638+
unique_ids = self.inject_identifiers_from_modules(unique_ids)
639+
return self._precheck_id_is_file(unique_ids)
640+
531641
def _precheck_id_is_file(self, unique_ids : List[str])->Dict[Path, str]:
532642
"""
533643
Preload file contents for the given IDs if they correspond to known files.
@@ -580,7 +690,7 @@ def get(
580690
f"Formats: string={as_string}, list={as_string_list}"
581691
)
582692

583-
requested_files = self._precheck_id_is_file(code_identifiers)
693+
requested_files = self.precheck(code_identifiers)
584694
return self.codebase.get(
585695
unique_id=code_identifiers,
586696
degree=context_depth,
@@ -600,8 +710,6 @@ def _as_file_paths(self, code_identifiers: Union[str, List[str]])->List[str]:
600710
as_file_paths.append(code_identifier)
601711
elif element := self.codebase.cached_elements.get(code_identifier):
602712
as_file_paths.append(element.file_path)
603-
else: ### covers new files
604-
as_file_paths.append(element)
605713

606714
return as_file_paths
607715

@@ -615,8 +723,11 @@ def get_unique_paths(path_list):
615723
unique_paths = []
616724

617725
for path in path_list:
618-
# Normalize the path to use OS-appropriate separators
619-
normalized = os.path.normpath(path)
726+
if isinstance(path, str) and path.endswith("/"):
727+
normalized = path
728+
else:
729+
# Normalize the path to use OS-appropriate separators
730+
normalized = os.path.normpath(path)
620731

621732
# Only add if we haven't seen this normalized path before
622733
if normalized not in seen:

0 commit comments

Comments
 (0)