From 5119485d4e75c5de4a05aac042f321c8c6c80b8f Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Fri, 19 Sep 2025 13:20:47 +0100 Subject: [PATCH] feat(editor): add EDITOR_DISABLE_BACKUP option to skip backup file creation --- README.md | 1 + src/strands_tools/editor.py | 20 +++++++--- tests/test_editor.py | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 438b25c0..0d9a6da5 100644 --- a/README.md +++ b/README.md @@ -925,6 +925,7 @@ The Mem0 Memory Tool supports three different backend configurations: | EDITOR_DIR_TREE_MAX_DEPTH | Maximum depth for directory tree visualization | 2 | | EDITOR_DEFAULT_STYLE | Default style for output panels | default | | EDITOR_DEFAULT_LANGUAGE | Default language for syntax highlighting | python | +| EDITOR_DISABLE_BACKUP | Skip creating .bak backup files during edit operations | false | #### Environment Tool diff --git a/src/strands_tools/editor.py b/src/strands_tools/editor.py index 402d3f60..a70f86da 100644 --- a/src/strands_tools/editor.py +++ b/src/strands_tools/editor.py @@ -536,8 +536,10 @@ def editor( # Make replacements and backup new_content = content.replace(old_str, new_str) - backup_path = f"{path}.bak" - shutil.copy2(path, backup_path) + disable_backup = os.environ.get("EDITOR_DISABLE_BACKUP", "").lower() == "true" + if not disable_backup: + backup_path = f"{path}.bak" + shutil.copy2(path, backup_path) # Write new content and update cache with open(path, "w") as f: @@ -606,8 +608,12 @@ def editor( # Make replacements and backup new_content = regex.sub(new_str, content) - backup_path = f"{path}.bak" - shutil.copy2(path, backup_path) + disable_backup = os.environ.get("EDITOR_DISABLE_BACKUP", "").lower() == "true" + if not disable_backup: + backup_path = f"{path}.bak" + shutil.copy2(path, backup_path) + else: + backup_path = "Disabled" # Write new content and update cache with open(path, "w") as f: @@ -678,8 +684,10 @@ def editor( raise ValueError(f"insert_line {insert_line} is out of range") # Make backup - backup_path = f"{path}.bak" - shutil.copy2(path, backup_path) + disable_backup = os.environ.get("EDITOR_DISABLE_BACKUP", "").lower() == "true" + if not disable_backup: + backup_path = f"{path}.bak" + shutil.copy2(path, backup_path) # Insert and write lines.insert(insert_line, new_str) diff --git a/tests/test_editor.py b/tests/test_editor.py index c24db0c7..9d340d4b 100644 --- a/tests/test_editor.py +++ b/tests/test_editor.py @@ -253,6 +253,79 @@ def test_insert_command(self, mock_user_input, temp_file, clean_content_history) # changing the original file structure assert "Line 2\nINSERTED LINE\nLine 3\n" in content + @patch("strands_tools.editor.get_user_input") + @patch.dict("os.environ", {"EDITOR_DISABLE_BACKUP": "true"}) + def test_str_replace_no_backup(self, mock_user_input, temp_file, clean_content_history): + """Test str_replace without creating backup when EDITOR_DISABLE_BACKUP is set.""" + mock_user_input.return_value = "y" + + result = editor.editor( + command="str_replace", + path=temp_file, + old_str="Line 2", + new_str="Modified Line 2" + ) + + assert result["status"] == "success" + + # Verify backup was NOT created + backup_path = f"{temp_file}.bak" + assert not os.path.exists(backup_path) + + @patch("strands_tools.editor.get_user_input") + @patch.dict("os.environ", {"EDITOR_DISABLE_BACKUP": "true"}) + def test_pattern_replace_no_backup(self, mock_user_input, temp_file, clean_content_history): + """Test pattern_replace without creating backup.""" + mock_user_input.return_value = "y" + + result = editor.editor( + command="pattern_replace", + path=temp_file, + pattern="Line.*", + new_str="Updated Line" + ) + + assert result["status"] == "success" + backup_path = f"{temp_file}.bak" + assert not os.path.exists(backup_path) + + @patch("strands_tools.editor.get_user_input") + @patch.dict("os.environ", {"EDITOR_DISABLE_BACKUP": "true"}) + def test_insert_no_backup(self, mock_user_input, temp_file, clean_content_history): + """Test insert without creating backup.""" + mock_user_input.return_value = "y" + + result = editor.editor( + command="insert", + path=temp_file, + new_str="New line", + insert_line=2 + ) + + assert result["status"] == "success" + backup_path = f"{temp_file}.bak" + assert not os.path.exists(backup_path) + + @patch("strands_tools.editor.get_user_input") + def test_backup_created_by_default(self, mock_user_input, temp_file, clean_content_history): + """Test that backup is still created by default.""" + # Ensure env var is not set + if "EDITOR_DISABLE_BACKUP" in os.environ: + del os.environ["EDITOR_DISABLE_BACKUP"] + + mock_user_input.return_value = "y" + + result = editor.editor( + command="str_replace", + path=temp_file, + old_str="Line 2", + new_str="Modified Line 2" + ) + + assert result["status"] == "success" + backup_path = f"{temp_file}.bak" + assert os.path.exists(backup_path) + @patch("strands_tools.editor.get_user_input") def test_insert_with_search_text(self, mock_user_input, temp_file, clean_content_history): """Test inserting text after a line found by search."""