aboutsummaryrefslogtreecommitdiffstats
path: root/internal/walker/walker.go
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-03-05 07:29:00 -0800
committerClawd <ai@clawd.bot>2026-03-05 07:29:00 -0800
commitf1ff85c7acad6b2ae7ec10720619ef2023cb7dc9 (patch)
tree5e694f4a2e864c9fcdfcbb1ab869c3bae05b50e3 /internal/walker/walker.go
parent03d8f49479b3446cf7f8ab9b6fdb2401584e3f12 (diff)
Implement core: walker, chunker, embedder, index, CLI
Diffstat (limited to 'internal/walker/walker.go')
-rw-r--r--internal/walker/walker.go109
1 files changed, 109 insertions, 0 deletions
diff --git a/internal/walker/walker.go b/internal/walker/walker.go
new file mode 100644
index 0000000..0ac470d
--- /dev/null
+++ b/internal/walker/walker.go
@@ -0,0 +1,109 @@
1package walker
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7
8 ignore "github.com/sabhiram/go-gitignore"
9)
10
11// DefaultIgnore patterns applied to all walks
12var DefaultIgnore = []string{
13 "vendor/",
14 "node_modules/",
15 ".git/",
16 ".codevec/",
17}
18
19// Walker walks a directory tree finding files to index
20type Walker struct {
21 root string
22 extensions []string // e.g., [".go"]
23 gitignore *ignore.GitIgnore
24}
25
26// New creates a walker for the given root directory
27func New(root string, extensions []string) (*Walker, error) {
28 root, err := filepath.Abs(root)
29 if err != nil {
30 return nil, err
31 }
32
33 w := &Walker{
34 root: root,
35 extensions: extensions,
36 }
37
38 // Load .gitignore if present
39 gitignorePath := filepath.Join(root, ".gitignore")
40 if _, err := os.Stat(gitignorePath); err == nil {
41 gi, err := ignore.CompileIgnoreFile(gitignorePath)
42 if err == nil {
43 w.gitignore = gi
44 }
45 }
46
47 return w, nil
48}
49
50// Walk returns all matching files in the directory tree
51func (w *Walker) Walk() ([]string, error) {
52 var files []string
53
54 err := filepath.WalkDir(w.root, func(path string, d os.DirEntry, err error) error {
55 if err != nil {
56 return err
57 }
58
59 // Get path relative to root for ignore matching
60 relPath, err := filepath.Rel(w.root, path)
61 if err != nil {
62 return err
63 }
64
65 // Skip default ignored directories
66 if d.IsDir() {
67 for _, pattern := range DefaultIgnore {
68 if strings.HasPrefix(relPath+"/", pattern) || relPath+"/" == pattern {
69 return filepath.SkipDir
70 }
71 }
72 }
73
74 // Skip if matched by .gitignore
75 if w.gitignore != nil && w.gitignore.MatchesPath(relPath) {
76 if d.IsDir() {
77 return filepath.SkipDir
78 }
79 return nil
80 }
81
82 // Skip directories and non-matching extensions
83 if d.IsDir() {
84 return nil
85 }
86
87 if !w.matchesExtension(path) {
88 return nil
89 }
90
91 files = append(files, path)
92 return nil
93 })
94
95 return files, err
96}
97
98func (w *Walker) matchesExtension(path string) bool {
99 if len(w.extensions) == 0 {
100 return true
101 }
102 ext := filepath.Ext(path)
103 for _, e := range w.extensions {
104 if ext == e {
105 return true
106 }
107 }
108 return false
109}