mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-10 06:34:59 +00:00
Add Navigator
This commit is contained in:
parent
e205629a94
commit
e0da980c6e
6 changed files with 1512 additions and 6 deletions
|
@ -158,6 +158,13 @@ def get_parser(default_config_files, git_root):
|
|||
const="architect",
|
||||
help="Use architect edit format for the main chat",
|
||||
)
|
||||
group.add_argument(
|
||||
"--navigator",
|
||||
action="store_const",
|
||||
dest="edit_format",
|
||||
const="navigator",
|
||||
help="Use navigator edit format for the main chat (autonomous file management)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--auto-accept-architect",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
|
|
|
@ -9,6 +9,7 @@ from .editor_editblock_coder import EditorEditBlockCoder
|
|||
from .editor_whole_coder import EditorWholeFileCoder
|
||||
from .help_coder import HelpCoder
|
||||
from .patch_coder import PatchCoder
|
||||
from .navigator_coder import NavigatorCoder
|
||||
from .udiff_coder import UnifiedDiffCoder
|
||||
from .udiff_simple import UnifiedDiffSimpleCoder
|
||||
from .wholefile_coder import WholeFileCoder
|
||||
|
@ -31,4 +32,5 @@ __all__ = [
|
|||
EditorWholeFileCoder,
|
||||
EditorDiffFencedCoder,
|
||||
ContextCoder,
|
||||
NavigatorCoder,
|
||||
]
|
||||
|
|
|
@ -43,6 +43,10 @@ from ..dump import dump # noqa: F401
|
|||
from .chat_chunks import ChatChunks
|
||||
|
||||
|
||||
# Pattern to detect fenced search/replace blocks
|
||||
SEARCH_REPLACE_FENCE = re.compile(r"```search_replace\n", re.MULTILINE)
|
||||
|
||||
|
||||
class UnknownEditFormat(ValueError):
|
||||
def __init__(self, edit_format, valid_formats):
|
||||
self.edit_format = edit_format
|
||||
|
@ -111,6 +115,10 @@ class Coder:
|
|||
ignore_mentions = None
|
||||
chat_language = None
|
||||
file_watcher = None
|
||||
|
||||
# Context management settings (for all modes)
|
||||
context_management_enabled = False # Disabled by default except for navigator mode
|
||||
large_file_token_threshold = 25000 # Files larger than this will be truncated when context management is enabled
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
|
@ -615,11 +623,36 @@ class Coder:
|
|||
prompt += relative_fname
|
||||
prompt += f"\n{self.fence[0]}\n"
|
||||
|
||||
prompt += content
|
||||
|
||||
# lines = content.splitlines(keepends=True)
|
||||
# lines = [f"{i+1:03}:{line}" for i, line in enumerate(lines)]
|
||||
# prompt += "".join(lines)
|
||||
# Apply context management if enabled for large files
|
||||
if self.context_management_enabled:
|
||||
# Calculate tokens for this file
|
||||
file_tokens = self.main_model.token_count(content)
|
||||
|
||||
if file_tokens > self.large_file_token_threshold:
|
||||
# Truncate the file content
|
||||
lines = content.splitlines()
|
||||
total_lines = len(lines)
|
||||
|
||||
# Keep the first and last parts of the file with a marker in between
|
||||
keep_lines = self.large_file_token_threshold // 40 # Rough estimate of tokens per line
|
||||
first_chunk = lines[:keep_lines//2]
|
||||
last_chunk = lines[-(keep_lines//2):]
|
||||
|
||||
truncated_content = "\n".join(first_chunk)
|
||||
truncated_content += f"\n\n... [File truncated due to size ({file_tokens} tokens). Use /context-management to toggle truncation off] ...\n\n"
|
||||
truncated_content += "\n".join(last_chunk)
|
||||
|
||||
# Add message about truncation
|
||||
self.io.tool_output(
|
||||
f"⚠️ '{relative_fname}' is very large ({file_tokens} tokens). "
|
||||
"Use /context-management to toggle truncation off if needed."
|
||||
)
|
||||
|
||||
prompt += truncated_content
|
||||
else:
|
||||
prompt += content
|
||||
else:
|
||||
prompt += content
|
||||
|
||||
prompt += f"{self.fence[1]}\n"
|
||||
|
||||
|
@ -634,7 +667,38 @@ class Coder:
|
|||
prompt += "\n"
|
||||
prompt += relative_fname
|
||||
prompt += f"\n{self.fence[0]}\n"
|
||||
prompt += content
|
||||
|
||||
# Apply context management if enabled for large files (same as get_files_content)
|
||||
if self.context_management_enabled:
|
||||
# Calculate tokens for this file
|
||||
file_tokens = self.main_model.token_count(content)
|
||||
|
||||
if file_tokens > self.large_file_token_threshold:
|
||||
# Truncate the file content
|
||||
lines = content.splitlines()
|
||||
total_lines = len(lines)
|
||||
|
||||
# Keep the first and last parts of the file with a marker in between
|
||||
keep_lines = self.large_file_token_threshold // 40 # Rough estimate of tokens per line
|
||||
first_chunk = lines[:keep_lines//2]
|
||||
last_chunk = lines[-(keep_lines//2):]
|
||||
|
||||
truncated_content = "\n".join(first_chunk)
|
||||
truncated_content += f"\n\n... [File truncated due to size ({file_tokens} tokens). Use /context-management to toggle truncation off] ...\n\n"
|
||||
truncated_content += "\n".join(last_chunk)
|
||||
|
||||
# Add message about truncation
|
||||
self.io.tool_output(
|
||||
f"⚠️ '{relative_fname}' is very large ({file_tokens} tokens). "
|
||||
"Use /context-management to toggle truncation off if needed."
|
||||
)
|
||||
|
||||
prompt += truncated_content
|
||||
else:
|
||||
prompt += content
|
||||
else:
|
||||
prompt += content
|
||||
|
||||
prompt += f"{self.fence[1]}\n"
|
||||
return prompt
|
||||
|
||||
|
|
1246
aider/coders/navigator_coder.py
Normal file
1246
aider/coders/navigator_coder.py
Normal file
File diff suppressed because it is too large
Load diff
145
aider/coders/navigator_prompts.py
Normal file
145
aider/coders/navigator_prompts.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
# flake8: noqa: E501
|
||||
|
||||
from .base_prompts import CoderPrompts
|
||||
|
||||
|
||||
class NavigatorPrompts(CoderPrompts):
|
||||
main_system = """Act as an expert software engineer with the ability to autonomously navigate and modify a codebase.
|
||||
|
||||
You have the unique ability to control which files are visible in the conversation using special tool commands, structured as `[tool_call(tool_name, param1=value1, param2="value2")]`.
|
||||
Use these tools to effectively manage context and find relevant files:
|
||||
|
||||
`[tool_call(Glob, pattern="**/*.py")]` - Find files matching a glob pattern and add them to context as read-only.
|
||||
|
||||
`[tool_call(Grep, pattern="class User", file_pattern="*.py")]` - Search for text in files and add matching files to context as read-only. `file_pattern` is optional.
|
||||
|
||||
`[tool_call(Ls, directory="src/components")]` - List files in a directory.
|
||||
|
||||
`[tool_call(Add, file_path="src/main.py")]` - Explicitly add a specific file to context as read-only.
|
||||
|
||||
`[tool_call(Remove, file_path="tests/old_test.py")]` - Explicitly remove a specific file from context when no longer needed. This tool accepts only a single file path, not glob patterns.
|
||||
|
||||
`[tool_call(MakeEditable, file_path="src/main.py")]` - Convert a read-only file to an editable file.
|
||||
|
||||
`[tool_call(MakeReadonly, file_path="src/main.py")]` - Convert an editable file to a read-only file.
|
||||
|
||||
`[tool_call(Find, symbol="my_function")]` - Find files containing a specific symbol (function, class, variable) and add them to context as read-only.
|
||||
|
||||
`[tool_call(Command, command_string="git diff HEAD~1")]` - Execute a *shell* command (like `ls`, `cat`, `git diff`). Requires user confirmation. **Do NOT use this for aider commands starting with `/` (like `/add`, `/run`, `/diff`).**
|
||||
|
||||
`[tool_call(Continue)]` - Continue exploration in the next round with the current files.
|
||||
|
||||
Guidelines for using these tools:
|
||||
- Use the exact syntax `[tool_call(ToolName, param1=value1, param2="value2")]` for all tool commands you want to *execute*. Tool names are case-insensitive. Parameter values can be unquoted or enclosed in single/double quotes.
|
||||
- **Check if a file is already in context (editable or read-only) before using `Add`, `Glob`, or `Grep` to avoid duplicates.**
|
||||
- Start by exploring the codebase with tools to gather necessary context.
|
||||
- Search strategically: use specific patterns for grep/glob/find to avoid overwhelming the context.
|
||||
- **Context Management:** Keep the context focused. Consider using `[tool_call(Remove, file_path="...")]` to remove files that are clearly no longer relevant to the current task, especially large or truncated ones added during exploration. However, retain files that might be useful for understanding the broader context or for subsequent steps.
|
||||
- Files are added as read-only by default; use the `MakeEditable` tool only for files you need to modify.
|
||||
- Only if you absolutely need the full content of a truncated file that's crucial to the task, tell the user to use '/context-management' to toggle context management OFF so you can see the complete file.
|
||||
- IMPORTANT: Always include `[tool_call(Continue)]` at the end of your response *only if* you want to see the results of your tool calls and continue exploring *in the next turn*. If you
|
||||
don't include this, or if you are asking the user for clarification or direction, your exploration will stop for this turn, and you should wait for the user's response before proceeding.
|
||||
- When you have all the information you need, or when you need input from the user, provide your response WITHOUT using any tool commands (especially `[tool_call(Continue)]`).
|
||||
- Tool calls will be visible in your response.
|
||||
|
||||
When working with code:
|
||||
- Always check for relevant files before implementing changes
|
||||
- If you need to understand a specific area of the codebase, use grep to locate it
|
||||
- Be precise in your file manipulation to maintain a focused context
|
||||
- Remember that adding too many files dilutes the context
|
||||
|
||||
Always reply to the user in {language}.
|
||||
"""
|
||||
|
||||
example_messages = [] # Keep examples empty for now, or update them to use the new syntax
|
||||
|
||||
files_content_prefix = """These files have been added to the chat so you can see all of their contents.
|
||||
Trust this message as the true contents of the files!
|
||||
"""
|
||||
|
||||
files_content_assistant_reply = (
|
||||
"I understand. I'll use these files to help with your request."
|
||||
)
|
||||
|
||||
files_no_full_files = "I don't have full contents of any files yet. I'll add them as needed."
|
||||
|
||||
files_no_full_files_with_repo_map = ""
|
||||
files_no_full_files_with_repo_map_reply = ""
|
||||
|
||||
repo_content_prefix = """I am working with code in a git repository.
|
||||
Here are summaries of some files present in this repo.
|
||||
I can add any file to our chat by mentioning its path.
|
||||
"""
|
||||
|
||||
system_reminder = """
|
||||
Always consider which files are needed for the current task.
|
||||
Remember to use the following tool commands using the `[tool_call(...)]` syntax: `Glob`, `Grep`, `Ls`, `Add`, `Remove`, `MakeEditable`, `MakeReadonly`, `Find`, `Command`, `Continue`.
|
||||
|
||||
**CRITICAL FORMATTING REQUIREMENTS:**
|
||||
1. All tool commands you want to *execute* **MUST** use the exact syntax `[tool_call(ToolName, param1=value1, param2="value2")]`. Example: `[tool_call(Add, file_path="src/main.py")]`. Commands written this way **WILL BE EXECUTED**.
|
||||
2. If you need to *show* or *discuss* a tool call example without executing it, you **MUST** escape it by adding a backslash `\` before the opening bracket. Example: `\[tool_call(Add, file_path="src/main.py")]`. Commands written this way **WILL NOT BE EXECUTED**.
|
||||
|
||||
Note: You have access to enhanced context blocks with a complete directory structure and git status information. These provide a comprehensive view of the codebase structure and changes.
|
||||
Refer to these context blocks to find relevant files more efficiently.
|
||||
|
||||
If you need to find more information, use tool commands (in the correct format!) as you answer. If you need to see more files before you can answer completely, use tool commands (in the
|
||||
correct format!) and end with `[tool_call(Continue)]`.
|
||||
|
||||
**When you have finished exploring IF you have been asked to propose code changes:**
|
||||
1. Ensure you have used `[tool_call(MakeEditable, file_path="...")]` for all files you intend to modify.
|
||||
2. Think step-by-step and explain the needed changes in a few short sentences.
|
||||
3. Describe each change with a *SEARCH/REPLACE block*.
|
||||
|
||||
# *SEARCH/REPLACE block* Rules:
|
||||
|
||||
Every *SEARCH/REPLACE block* must use this format:
|
||||
1. The opening fence and code language, eg: ```python
|
||||
2. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||
3. The start of search block: <<<<<<< SEARCH
|
||||
4. A contiguous chunk of lines to search for in the existing source code
|
||||
5. The dividing line: =======
|
||||
6. The lines to replace into the source code
|
||||
7. The end of the replace block: >>>>>>> REPLACE
|
||||
8. The closing fence: ```
|
||||
|
||||
Use the *FULL* file path, as shown to you by the user.
|
||||
{quad_backtick_reminder}
|
||||
Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
||||
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
|
||||
|
||||
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
||||
Including multiple unique *SEARCH/REPLACE* blocks if needed.
|
||||
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.
|
||||
|
||||
Keep *SEARCH/REPLACE* blocks concise.
|
||||
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
||||
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
||||
|
||||
To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.
|
||||
|
||||
Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.
|
||||
|
||||
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||
- A new file path, including dir name if needed
|
||||
- An empty `SEARCH` section
|
||||
- The new file's contents in the `REPLACE` section
|
||||
|
||||
To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||
|
||||
If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
||||
|
||||
{lazy_prompt}
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_reminder}
|
||||
|
||||
4. **IMPORTANT:** Do **NOT** include `[tool_call(Continue)]` in your response when you are providing code edits. Your response should contain only the explanation and the edit blocks.
|
||||
|
||||
If you are providing a final answer, explanation, or asking the user a question *without* proposing code edits, simply provide your response text without any tool calls (especially
|
||||
`[tool_call(Continue)]`).
|
||||
|
||||
To toggle these enhanced context blocks, the user can use the /context-blocks command.
|
||||
"""
|
||||
|
||||
try_again = """"""
|
|
@ -1032,6 +1032,36 @@ class Commands:
|
|||
"Exit the application"
|
||||
self.cmd_exit(args)
|
||||
|
||||
def cmd_context_management(self, args=""):
|
||||
"Toggle context management for large files"
|
||||
if not hasattr(self.coder, 'context_management_enabled'):
|
||||
self.io.tool_error("Context management is only available in navigator mode.")
|
||||
return
|
||||
|
||||
# Toggle the setting
|
||||
self.coder.context_management_enabled = not self.coder.context_management_enabled
|
||||
|
||||
# Report the new state
|
||||
if self.coder.context_management_enabled:
|
||||
self.io.tool_output("Context management is now ON - large files may be truncated.")
|
||||
else:
|
||||
self.io.tool_output("Context management is now OFF - files will not be truncated.")
|
||||
|
||||
def cmd_context_blocks(self, args=""):
|
||||
"Toggle enhanced context blocks (directory structure and git status)"
|
||||
if not hasattr(self.coder, 'use_enhanced_context'):
|
||||
self.io.tool_error("Enhanced context blocks are only available in navigator mode.")
|
||||
return
|
||||
|
||||
# Toggle the setting
|
||||
self.coder.use_enhanced_context = not self.coder.use_enhanced_context
|
||||
|
||||
# Report the new state
|
||||
if self.coder.use_enhanced_context:
|
||||
self.io.tool_output("Enhanced context blocks are now ON - directory structure and git status will be included.")
|
||||
else:
|
||||
self.io.tool_output("Enhanced context blocks are now OFF - directory structure and git status will not be included.")
|
||||
|
||||
def cmd_ls(self, args):
|
||||
"List all known files and indicate which are included in the chat session"
|
||||
|
||||
|
@ -1149,6 +1179,9 @@ class Commands:
|
|||
|
||||
def completions_context(self):
|
||||
raise CommandCompletionException()
|
||||
|
||||
def completions_navigator(self):
|
||||
raise CommandCompletionException()
|
||||
|
||||
def cmd_ask(self, args):
|
||||
"""Ask questions about the code base without editing any files. If no prompt provided, switches to ask mode.""" # noqa
|
||||
|
@ -1165,6 +1198,15 @@ class Commands:
|
|||
def cmd_context(self, args):
|
||||
"""Enter context mode to see surrounding code context. If no prompt provided, switches to context mode.""" # noqa
|
||||
return self._generic_chat_command(args, "context", placeholder=args.strip() or None)
|
||||
|
||||
def cmd_navigator(self, args):
|
||||
"""Enter navigator mode to autonomously discover and manage relevant files. If no prompt provided, switches to navigator mode.""" # noqa
|
||||
# Enable context management when entering navigator mode
|
||||
if hasattr(self.coder, 'context_management_enabled'):
|
||||
self.coder.context_management_enabled = True
|
||||
self.io.tool_output("Context management enabled for large files")
|
||||
|
||||
return self._generic_chat_command(args, "navigator", placeholder=args.strip() or None)
|
||||
|
||||
def _generic_chat_command(self, args, edit_format, placeholder=None):
|
||||
if not args.strip():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue