fixup! fixup! fixup! Add Docker usage instructions to README

This commit is contained in:
2026-03-12 10:42:39 +00:00
parent 34d4401280
commit db803c6950
8 changed files with 38 additions and 268 deletions
+6 -20
View File
@@ -22,18 +22,13 @@ func unescapeNewlines(s string) string {
return s
}
// handleEditPreview handles the edit_preview tool.
func (s *Server) handleEditPreview(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return s.handleEdit(ctx, request, false)
}
// handleEditApply handles the edit_apply tool.
func (s *Server) handleEditApply(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return s.handleEdit(ctx, request, true)
return s.handleEdit(ctx, request)
}
// handleEdit is the shared implementation for edit_preview and edit_apply.
func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, apply bool) (*mcp.CallToolResult, error) {
// handleEdit performs an edit operation (always applies changes).
func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
file, err := request.RequireString("file")
if err != nil {
return mcp.NewToolResultError("file is required"), nil
@@ -85,13 +80,8 @@ func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, ap
},
}
// Perform edit
var result *edit.EditResult
if apply {
result, err = s.editor.Apply(ctx, astEdit)
} else {
result, err = s.editor.Preview(ctx, astEdit)
}
// Perform edit (always apply)
result, err := s.editor.Apply(ctx, astEdit)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("edit failed: %s", errors.SanitizeError(err))), nil
@@ -103,11 +93,7 @@ func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, ap
// Format output
var output strings.Builder
if apply {
output.WriteString("**Edit Applied Successfully**\n\n")
} else {
output.WriteString("**Edit Preview**\n\n")
}
output.WriteString("**Edit Applied Successfully**\n\n")
output.WriteString("Diff:\n```diff\n")
output.WriteString(result.Diff)
+12 -34
View File
@@ -96,35 +96,17 @@ func Hello() string {
}
})
// Test 4: Edit preview and apply
// Test 4: Edit workflow
t.Run("edit_workflow", func(t *testing.T) {
// Preview edit
previewReq := mcp.CallToolRequest{}
previewReq.Params.Arguments = map[string]interface{}{
// Apply edit directly (preview removed to avoid confusing LLMs)
applyReq := mcp.CallToolRequest{}
applyReq.Params.Arguments = map[string]interface{}{
"file": testFile,
"operation": "replace",
"selector_kind": "function_declaration",
"selector_name": "Hello",
"new_content": "func Hello() string {\n\treturn \"goodbye\"\n}",
}
previewResult, err := srv.handleEditPreview(ctx, previewReq)
if err != nil {
t.Errorf("handleEditPreview() error = %v", err)
}
if previewResult == nil {
t.Fatal("handleEditPreview() returned nil")
return
}
// Verify file unchanged after preview
originalContent, _ := os.ReadFile(testFile)
if string(originalContent) != content {
t.Error("preview should not modify file")
}
// Apply edit
applyReq := mcp.CallToolRequest{}
applyReq.Params.Arguments = previewReq.Params.Arguments
applyResult, err := srv.handleEditApply(ctx, applyReq)
if err != nil {
t.Errorf("handleEditApply() error = %v", err)
@@ -267,10 +249,8 @@ func TestMCPErrorResponses(t *testing.T) {
expectError: true,
},
{
name: "edit_missing_file",
handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return srv.handleEdit(ctx, req, false)
},
name: "edit_missing_file",
handler: srv.handleEditApply,
setupReq: func() mcp.CallToolRequest {
req := mcp.CallToolRequest{}
req.Params.Arguments = map[string]interface{}{
@@ -281,10 +261,8 @@ func TestMCPErrorResponses(t *testing.T) {
expectError: true,
},
{
name: "edit_missing_operation",
handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return srv.handleEdit(ctx, req, false)
},
name: "edit_missing_operation",
handler: srv.handleEditApply,
setupReq: func() mcp.CallToolRequest {
req := mcp.CallToolRequest{}
req.Params.Arguments = map[string]interface{}{
@@ -376,7 +354,7 @@ func Add(a, b int) int {
return
}
// 3. Preview edit
// 3. Edit (no preview)
editReq := mcp.CallToolRequest{}
editReq.Params.Arguments = map[string]interface{}{
"file": testFile,
@@ -385,12 +363,12 @@ func Add(a, b int) int {
"selector_name": "Add",
"new_content": "func Add(a, b int) int {\n\treturn a + b + 1\n}",
}
editResult, err := srv.handleEditPreview(ctx, editReq)
editResult, err := srv.handleEditApply(ctx, editReq)
if err != nil {
t.Fatalf("handleEditPreview() error = %v", err)
t.Fatalf("handleEditApply() error = %v", err)
}
if editResult == nil {
t.Fatal("handleEditPreview() returned nil")
t.Fatal("handleEditApply() returned nil")
return
}
})
-48
View File
@@ -290,54 +290,6 @@ func (s *Server) registerTools() {
}
// Register edit tools
s.mcp.AddTool(
mcp.NewTool("edit_preview",
mcp.WithDescription("Preview an edit without applying it. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust), and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
"Returns: \"**Edit Preview**\" followed by a unified diff showing proposed changes. Does not modify the file. "+
"For code files: uses AST-aware mode with syntax validation. For other files: uses text-based mode.\n\n"+
"Examples:\n"+
" AST mode: {\"file\": \"main.go\", \"operation\": \"replace\", \"selector_kind\": \"function_declaration\", \"selector_name\": \"Hello\", \"new_content\": \"func Hello() {\\n\\treturn\\n}\"}\n"+
" Text mode: {\"file\": \"README.md\", \"operation\": \"replace\", \"selector_text\": \"## Old Header\", \"new_content\": \"## New Header\"}\n"+
" Line range: {\"file\": \"config.yaml\", \"operation\": \"replace\", \"selector_line\": 5, \"selector_line_end\": 10, \"new_content\": \"key: value\"}"),
mcp.WithString("file",
mcp.Required(),
mcp.Description("Path to the file to edit"),
),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("Edit operation: replace, insert_before, insert_after, delete"),
),
mcp.WithString("new_content",
mcp.Description("New content (required for replace/insert operations)"),
),
// AST-mode selectors (for code files)
mcp.WithString("selector_kind",
mcp.Description("AST node type to match (e.g., function_declaration, class_declaration). For code files only."),
),
mcp.WithString("selector_name",
mcp.Description("Name of the symbol to match. For code files only."),
),
// Shared selectors
mcp.WithNumber("selector_line",
mcp.Description("Line number (1-indexed). For AST mode: narrows search. For text mode: start of line range."),
),
mcp.WithNumber("selector_index",
mcp.Description("Index of the match to use if multiple matches found (default: 0)"),
),
// Text-mode selectors (for non-code files or explicit text matching)
mcp.WithNumber("selector_line_end",
mcp.Description("End line number for range selection (text mode). Used with selector_line."),
),
mcp.WithString("selector_text",
mcp.Description("Exact text to match (text mode). Must be unique or use selector_index."),
),
mcp.WithString("selector_pattern",
mcp.Description("Regex pattern to match (text mode). Must be unique or use selector_index."),
),
),
s.handleEditPreview,
)
s.mcp.AddTool(
mcp.NewTool("edit_apply",
mcp.WithDescription("Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
+4 -54
View File
@@ -283,56 +283,6 @@ func Goodbye() error {
}
}
func TestHandleEditPreview(t *testing.T) {
tmpDir := t.TempDir()
// Create a test file
testFile := filepath.Join(tmpDir, "test.go")
content := `package main
func Hello() {
println("Hello")
}
`
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("failed to write test file: %v", err)
}
cfg := &config.Config{WorkspaceRoot: tmpDir, EnableLSP: false}
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
srv, err := New(cfg, logger)
if err != nil {
t.Fatalf("New() error = %v", err)
}
ctx := context.Background()
req := mcp.CallToolRequest{}
req.Params.Arguments = map[string]interface{}{
"file": testFile,
"operation": "replace",
"selector_kind": "function_declaration",
"selector_name": "Hello",
"new_content": "func Hello() {\n\tprintln(\"Goodbye\")\n}",
}
result, err := srv.handleEdit(ctx, req, false)
if err != nil {
t.Errorf("handleEdit(preview) error = %v", err)
}
if result == nil {
t.Fatal("handleEdit(preview) returned nil result")
return
}
// Verify file was NOT modified (it's just a preview)
fileContent, _ := os.ReadFile(testFile)
if string(fileContent) != content {
t.Error("handleEdit(preview) should not modify the file")
}
}
func TestHandleEditApply(t *testing.T) {
tmpDir := t.TempDir()
@@ -366,20 +316,20 @@ func Hello() {
"new_content": "func Hello() {\n\tprintln(\"Goodbye\")\n}",
}
result, err := srv.handleEdit(ctx, req, true)
result, err := srv.handleEditApply(ctx, req)
if err != nil {
t.Errorf("handleEdit(apply) error = %v", err)
t.Errorf("handleEditApply error = %v", err)
}
if result == nil {
t.Fatal("handleEdit(apply) returned nil result")
t.Fatal("handleEditApply returned nil result")
return
}
// Verify file WAS modified
fileContent, _ := os.ReadFile(testFile)
if string(fileContent) == content {
t.Error("handleEdit(apply) should modify the file")
t.Error("handleEditApply should modify the file")
}
}