Add Navigator

This commit is contained in:
Amar Sood (tekacs) 2025-04-11 08:23:09 -04:00
parent e205629a94
commit e0da980c6e
6 changed files with 1512 additions and 6 deletions

View file

@ -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,

View file

@ -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,
]

View file

@ -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

File diff suppressed because it is too large Load diff

View 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 = """"""

View file

@ -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():