package builtin import ( "context" "encoding/json" "fmt" "io/fs" "os" "sort" "github.com/bmatcuk/doublestar/v4" "nub/internal/tool" ) type GlobTool struct{} func (GlobTool) Name() string { return "glob" } func (GlobTool) Description() string { return "Findet Dateien per Glob-Pattern, .gitignore respektiert, nach mtime absteigend sortiert." } func (GlobTool) Schema() json.RawMessage { return json.RawMessage(`{ "type": "object", "properties": { "pattern": {"type": "string", "description": "doublestar-Pattern, z.B. **/*.go"} }, "required": ["pattern"] }`) } type globInput struct { Pattern string `json:"pattern"` } func (GlobTool) Run(ctx context.Context, input json.RawMessage, env tool.Env) (tool.Result, error) { var in globInput if err := json.Unmarshal(input, &in); err != nil { return tool.Result{ForModel: "invalid input: " + err.Error(), IsError: true}, nil } ignore := loadGitignore(env.RepoRoot) type match struct { path string mtime int64 } var matches []match err := doublestar.GlobWalk(os.DirFS(env.RepoRoot), in.Pattern, func(path string, d fs.DirEntry) error { if d.IsDir() || ignore.match(path) { return nil } info, err := d.Info() if err != nil { return nil } matches = append(matches, match{path: path, mtime: info.ModTime().Unix()}) return nil }) if err != nil { return tool.Result{ForModel: fmt.Sprintf("glob %s: %v", in.Pattern, err), IsError: true}, nil } sort.Slice(matches, func(i, j int) bool { return matches[i].mtime > matches[j].mtime }) paths := make([]string, len(matches)) out := "" for i, m := range matches { paths[i] = m.path out += m.path + "\n" } if len(matches) == 0 { out = fmt.Sprintf("no matches for %s", in.Pattern) } return tool.Result{ForModel: out, ForUI: GlobResult{Paths: paths}}, nil } type GlobResult struct { Paths []string }