diff --git a/navi/profiles/developer/system_prompt.txt b/navi/profiles/developer/system_prompt.txt index c72fe45..85c26ba 100644 --- a/navi/profiles/developer/system_prompt.txt +++ b/navi/profiles/developer/system_prompt.txt @@ -44,6 +44,9 @@ 4. **Test** — run the code. Use `code_exec` or `terminal` to verify it works. 5. **Report** — what was done, what was tested, any caveats. +## Editing policy +When editing existing files with `filesystem`, default to `edit` (exact text replacement — read the file first and copy `old` verbatim) or `edit_lines` (by line numbers). Both are deterministic and cheap. Reserve `smart_edit` (AI) for changes that genuinely cannot be expressed as exact text or line numbers — e.g. rename a symbol everywhere, add type hints to every function. Reaching for `smart_edit` on every edit is wasteful and error-prone: it reads the whole file and makes an extra LLM call with less context than you already have. + --- ## Project knowledge diff --git a/navi/profiles/navi_code/system_prompt.txt b/navi/profiles/navi_code/system_prompt.txt index 0716667..7ca6a1a 100644 --- a/navi/profiles/navi_code/system_prompt.txt +++ b/navi/profiles/navi_code/system_prompt.txt @@ -44,6 +44,9 @@ 4. **Test** — run the code. Use `code_exec` or `terminal` to verify it works. 5. **Report** — what was done, what was tested, any caveats. +## Editing policy +When editing existing files with `filesystem`, default to `edit` (exact text replacement — read the file first and copy `old` verbatim) or `edit_lines` (by line numbers). Both are deterministic and cheap. Reserve `smart_edit` (AI) for changes that genuinely cannot be expressed as exact text or line numbers — e.g. rename a symbol everywhere, add type hints to every function. Reaching for `smart_edit` on every edit is wasteful and error-prone: it reads the whole file and makes an extra LLM call with less context than you already have. + --- diff --git a/navi/tools/filesystem.py b/navi/tools/filesystem.py index 8502d8a..576d24f 100644 --- a/navi/tools/filesystem.py +++ b/navi/tools/filesystem.py @@ -279,13 +279,22 @@ class FilesystemTool(Tool): name = "filesystem" description = ( - "Operate on the local filesystem. " - "Decision tree — pick the cheapest option for your task:\n" - "1. Need to extract info from a file — use 'query' (pass 'question').\n" - "2. Small exact edit — use 'edit' with 'old' and 'new' text, or 'edit_lines' with line numbers.\n" - "3. Semantic edit (intent known, exact text unknown) — use 'smart_edit' (pass 'instruction').\n" - "4. Create or fully rewrite — use 'write' (pass 'content').\n" - "5. Everything else — standard actions: read, append, list, find, find_up, grep, diff, " + "Operate the local filesystem. " + "Editing policy — prefer the cheapest deterministic method; reach for " + "smart_edit LAST:\n" + "1. Edit by exact text — 'edit' with 'old' (must occur exactly once) and " + "'new'. Read the file first and copy old text verbatim. This is the " + "default for almost all edits.\n" + "2. Edit by line numbers — 'edit_lines' with an operations array, when " + "you know the exact lines (e.g. 'change line 15'). Deterministic, no AI " + "call.\n" + "3. smart_edit (AI) — ONLY as a fallback when the change genuinely cannot " + "be expressed as exact text or line numbers (e.g. 'rename a symbol " + "everywhere', 'add type hints to every function'). It costs an LLM call " + "and reads the whole file, so try edit/edit_lines first.\n" + "4. Create or fully rewrite a file — 'write' (pass 'content').\n" + "5. Extract info from a file — 'query' (pass 'question').\n" + "6. Everything else — read, append, list, find, find_up, grep, diff, " "info, copy, move, delete, exists, mkdir.\n" "Tip: call 'info' before reading an unknown file to check size." ) @@ -596,8 +605,10 @@ return ToolResult( success=False, output=( - f"Exact text occurs {count} times. Use a larger unique old text block " - "or smart_edit for a semantic change." + f"Exact text occurs {count} times. Prefer 'edit' with a larger " + "unique 'old' block (include surrounding lines so it matches once) " + "or 'edit_lines' with line numbers. Fall back to 'smart_edit' only if " + "the change is genuinely semantic and cannot be expressed as exact text." ), error="old_not_unique", ) diff --git a/tests/unit/tools/test_filesystem.py b/tests/unit/tools/test_filesystem.py index 0d558a0..e8dde50 100644 --- a/tests/unit/tools/test_filesystem.py +++ b/tests/unit/tools/test_filesystem.py @@ -171,6 +171,10 @@ assert not result.success assert result.error == "old_not_unique" assert f.read_text() == "alpha beta beta" + # Hint steers back to edit/edit_lines first; smart_edit is a last resort. + assert "edit" in result.output + assert "edit_lines" in result.output + assert "smart_edit" in result.output async def test_copy_file(self, tool, tmp_path): src = tmp_path / "src.txt"