Initial commit
diff --git a/claudetool/edit_regression_test.go b/claudetool/edit_regression_test.go
new file mode 100644
index 0000000..cb859fe
--- /dev/null
+++ b/claudetool/edit_regression_test.go
@@ -0,0 +1,152 @@
+package claudetool
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+ "testing"
+)
+
+// TestEmptyContentHandling tests handling of empty content in str_replace and related operations
+// This test specifically reproduces conditions that might lead to "index out of range [0]" panic
+func TestEmptyContentHandling(t *testing.T) {
+ // Create a file with empty content
+ emptyFile := setupTestFile(t, "")
+
+ // Test running EditRun directly with empty content
+ // This more closely simulates the actual call flow that led to the panic
+ input := map[string]any{
+ "command": "str_replace",
+ "path": emptyFile,
+ "old_str": "nonexistent text",
+ "new_str": "new content",
+ }
+
+ inputJSON, err := json.Marshal(input)
+ if err != nil {
+ t.Fatalf("Failed to marshal input: %v", err)
+ }
+
+ // This should not panic but return an error
+ _, err = EditRun(context.Background(), inputJSON)
+ if err == nil {
+ t.Fatalf("Expected error for empty file with str_replace but got none")
+ }
+
+ // Make sure the error message is as expected
+ if !strings.Contains(err.Error(), "did not appear verbatim") {
+ t.Errorf("Expected error message to indicate missing string, got: %s", err.Error())
+ }
+}
+
+// TestNilParameterHandling tests error cases with nil parameters
+// This test validates proper error handling when nil or invalid parameters are provided
+func TestNilParameterHandling(t *testing.T) {
+ // Create a test file
+ testFile := setupTestFile(t, "test content")
+
+ // Test case 1: nil old_str in str_replace
+ input1 := map[string]any{
+ "command": "str_replace",
+ "path": testFile,
+ // old_str is deliberately missing
+ "new_str": "replacement",
+ }
+
+ inputJSON1, err := json.Marshal(input1)
+ if err != nil {
+ t.Fatalf("Failed to marshal input: %v", err)
+ }
+
+ _, err = EditRun(context.Background(), inputJSON1)
+ if err == nil {
+ t.Fatalf("Expected error for missing old_str but got none")
+ }
+ if !strings.Contains(err.Error(), "parameter old_str is required") {
+ t.Errorf("Expected error message to indicate missing old_str, got: %s", err.Error())
+ }
+
+ // Test case 2: nil new_str in insert
+ input2 := map[string]any{
+ "command": "insert",
+ "path": testFile,
+ "insert_line": 1,
+ // new_str is deliberately missing
+ }
+
+ inputJSON2, err := json.Marshal(input2)
+ if err != nil {
+ t.Fatalf("Failed to marshal input: %v", err)
+ }
+
+ _, err = EditRun(context.Background(), inputJSON2)
+ if err == nil {
+ t.Fatalf("Expected error for missing new_str but got none")
+ }
+ if !strings.Contains(err.Error(), "parameter new_str is required") {
+ t.Errorf("Expected error message to indicate missing new_str, got: %s", err.Error())
+ }
+
+ // Test case 3: nil view_range in view
+ // This doesn't cause an error, but tests the code path
+ input3 := map[string]any{
+ "command": "view",
+ "path": testFile,
+ // No view_range
+ }
+
+ inputJSON3, err := json.Marshal(input3)
+ if err != nil {
+ t.Fatalf("Failed to marshal input: %v", err)
+ }
+
+ // This should not result in an error
+ _, err = EditRun(context.Background(), inputJSON3)
+ if err != nil {
+ t.Fatalf("Unexpected error for nil view_range: %v", err)
+ }
+}
+
+// TestEmptySplitResult tests the specific scenario where strings.Split might return empty results
+// This directly reproduces conditions that might have led to the "index out of range [0]" panic
+func TestEmptySplitResult(t *testing.T) {
+ // Direct test of strings.Split behavior and our handling of it
+ emptyCases := []struct {
+ content string
+ oldStr string
+ }{
+ {"", "any string"},
+ {"content", "not in string"},
+ {"\n\n", "also not here"},
+ }
+
+ for _, tc := range emptyCases {
+ parts := strings.Split(tc.content, tc.oldStr)
+
+ // Verify that strings.Split with non-matching separator returns a slice with original content
+ if len(parts) != 1 {
+ t.Errorf("Expected strings.Split to return a slice with 1 element when separator isn't found, got %d elements", len(parts))
+ }
+
+ // Double check the content
+ if len(parts) > 0 && parts[0] != tc.content {
+ t.Errorf("Expected parts[0] to be original content %q, got %q", tc.content, parts[0])
+ }
+ }
+
+ // Test the actual unsafe scenario with empty content
+ emptyFile := setupTestFile(t, "")
+
+ // Get the content and simulate the internal string splitting
+ content, _ := readFile(emptyFile)
+ oldStr := "nonexistent"
+ parts := strings.Split(content, oldStr)
+
+ // Validate that the defensive code would work
+ if len(parts) == 0 {
+ parts = []string{""} // This is the fix
+ }
+
+ // This would have panicked without the fix
+ _ = strings.Count(parts[0], "\n")
+}