diff --git a/internal/edit/edit.go b/internal/edit/edit.go index aabe663..8f45734 100644 --- a/internal/edit/edit.go +++ b/internal/edit/edit.go @@ -270,7 +270,11 @@ func (e *Engine) validateBaseEdit(edit *ASTEdit) error { switch edit.Operation { case EditReplace, EditInsertBefore, EditInsertAfter: if edit.NewContent == "" { - return errors.NewInvalidEditError(fmt.Sprintf("new_content is required for %s operation", edit.Operation)) + msg := fmt.Sprintf("new_content is required for %s operation", edit.Operation) + if edit.Operation == EditReplace { + msg += " (to remove content instead, use operation=delete)" + } + return errors.NewInvalidEditError(msg) } case EditDelete: // new_content not required diff --git a/internal/edit/edit_test.go b/internal/edit/edit_test.go index f7abcf1..cf400ca 100644 --- a/internal/edit/edit_test.go +++ b/internal/edit/edit_test.go @@ -101,6 +101,22 @@ func TestValidateEdit(t *testing.T) { } } +// A replace with empty new_content should point the caller at operation=delete. +func TestValidateReplaceEmptySuggestsDelete(t *testing.T) { + e := NewEngine(parser.NewRegistry()) + err := e.validateASTEdit(&ASTEdit{ + File: "test.go", + Operation: EditReplace, + Selector: ASTSelector{Kind: "function_declaration"}, + }) + if err == nil { + t.Fatal("expected error for replace with empty new_content") + } + if !strings.Contains(err.Error(), "operation=delete") { + t.Errorf("error should suggest operation=delete, got: %v", err) + } +} + func TestResolveSelector(t *testing.T) { registry := parser.NewRegistry() defer registry.Close()