wip

Ayman Bagabas created

Change summary

internal/fsext/fileutil.go | 24 ++++++++++++++++++------
internal/llm/tools/glob.go | 11 +++++++----
2 files changed, 25 insertions(+), 10 deletions(-)

Detailed changes

internal/fsext/fileutil.go 🔗

@@ -8,6 +8,7 @@ import (
 	"path/filepath"
 	"sort"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/bmatcuk/doublestar/v4"
@@ -95,14 +96,14 @@ func NewFastGlobWalker(searchPath string) *FastGlobWalker {
 	return walker
 }
 
-func (w *FastGlobWalker) shouldSkip(path string) bool {
+func shouldSkip(path, rootPath string, gitignore *ignore.GitIgnore) bool {
 	if SkipHidden(path) {
 		return true
 	}
 
-	if w.gitignore != nil {
-		relPath, err := filepath.Rel(w.rootPath, path)
-		if err == nil && w.gitignore.MatchesPath(relPath) {
+	if gitignore != nil {
+		relPath, err := filepath.Rel(rootPath, path)
+		if err == nil && gitignore.MatchesPath(relPath) {
 			return true
 		}
 	}
@@ -111,6 +112,7 @@ func (w *FastGlobWalker) shouldSkip(path string) bool {
 }
 
 func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool, error) {
+	var mu sync.Mutex
 	walker := NewFastGlobWalker(searchPath)
 	var matches []FileInfo
 	conf := fastwalk.Config{
@@ -119,21 +121,28 @@ func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool,
 		ToSlash: fastwalk.DefaultToSlash(),
 		Sort:    fastwalk.SortFilesFirst,
 	}
+	rootPath, gitignore := walker.rootPath, walker.gitignore
 	err := fastwalk.Walk(&conf, searchPath, func(path string, d os.DirEntry, err error) error {
 		if err != nil {
 			return nil // Skip files we can't access
 		}
 
 		if d.IsDir() {
-			if walker.shouldSkip(path) {
+			mu.Lock()
+			if shouldSkip(path, rootPath, gitignore) {
+				mu.Unlock()
 				return filepath.SkipDir
 			}
+			mu.Unlock()
 			return nil
 		}
 
-		if walker.shouldSkip(path) {
+		mu.Lock()
+		if shouldSkip(path, rootPath, gitignore) {
+			mu.Unlock()
 			return nil
 		}
+		mu.Unlock()
 
 		// Check if path matches the pattern
 		relPath, err := filepath.Rel(searchPath, path)
@@ -151,6 +160,9 @@ func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool,
 			return nil
 		}
 
+		mu.Lock()
+		defer mu.Unlock()
+
 		matches = append(matches, FileInfo{Path: path, ModTime: info.ModTime()})
 		if limit > 0 && len(matches) >= limit*2 {
 			return filepath.SkipAll

internal/llm/tools/glob.go 🔗

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"strings"
+	"sync"
 
 	"github.com/charmbracelet/crush/internal/fsext"
 )
@@ -64,6 +65,7 @@ type GlobResponseMetadata struct {
 
 type globTool struct {
 	workingDir string
+	mu         sync.Mutex
 }
 
 func NewGlobTool(workingDir string) BaseTool {
@@ -109,20 +111,21 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
 		searchPath = g.workingDir
 	}
 
+	g.mu.Lock()
 	files, truncated, err := globFiles(params.Pattern, searchPath, 100)
 	if err != nil {
+		g.mu.Unlock()
 		return ToolResponse{}, fmt.Errorf("error finding files: %w", err)
 	}
 
-	var output string
-	if len(files) == 0 {
-		output = "No files found"
-	} else {
+	output := "No files found"
+	if len(files) > 0 {
 		output = strings.Join(files, "\n")
 		if truncated {
 			output += "\n\n(Results are truncated. Consider using a more specific path or pattern.)"
 		}
 	}
+	g.mu.Unlock()
 
 	return WithResponseMetadata(
 		NewTextResponse(output),