23114045
feat: wire watcher → scanner → mcp → rewriter in main
a73x 2026-04-29 05:59
Commit message
cmd/filewatch-mcp/main.go
| Old | New | ||
|---|---|---|---|
| @@ -1,7 +1,101 @@ | |||
| 1 | package main | 1 | package main |
| 2 | 2 | ||
| 3 | import "fmt" | 3 | import ( |
| 4 | "context" | ||
| 5 | "flag" | ||
| 6 | "log" | ||
| 7 | "os" | ||
| 8 | "os/signal" | ||
| 9 | "strconv" | ||
| 10 | "syscall" | ||
| 11 | "time" | ||
| 12 | |||
| 13 | "github.com/google/uuid" | ||
| 14 | |||
| 15 | "github.com/xanderle/claudealong/internal/ignore" | ||
| 16 | "github.com/xanderle/claudealong/internal/mcp" | ||
| 17 | "github.com/xanderle/claudealong/internal/rewriter" | ||
| 18 | "github.com/xanderle/claudealong/internal/scanner" | ||
| 19 | "github.com/xanderle/claudealong/internal/watcher" | ||
| 20 | ) | ||
| 4 | 21 | ||
| 5 | func main() { | 22 | func main() { |
| 6 | fmt.Println("filewatch-mcp") | 23 | root := flag.String("root", ".", "project root to watch") |
| 24 | debounce := flag.Duration("debounce", 500*time.Millisecond, "per-file debounce window") | ||
| 25 | flag.Parse() | ||
| 26 | |||
| 27 | log.SetOutput(os.Stderr) | ||
| 28 | log.SetFlags(log.Ltime | log.Lshortfile) | ||
| 29 | |||
| 30 | ig, err := ignore.New(*root) | ||
| 31 | if err != nil { | ||
| 32 | log.Fatalf("ignore: %v", err) | ||
| 33 | } | ||
| 34 | w, err := watcher.New(*root, ig, *debounce) | ||
| 35 | if err != nil { | ||
| 36 | log.Fatalf("watcher: %v", err) | ||
| 37 | } | ||
| 38 | defer w.Close() | ||
| 39 | |||
| 40 | srv := mcp.New() | ||
| 41 | |||
| 42 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 43 | defer cancel() | ||
| 44 | |||
| 45 | sigCh := make(chan os.Signal, 1) | ||
| 46 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) | ||
| 47 | go func() { <-sigCh; cancel() }() | ||
| 48 | |||
| 49 | if err := w.Start(ctx); err != nil { | ||
| 50 | log.Fatalf("watcher start: %v", err) | ||
| 51 | } | ||
| 52 | go consume(ctx, w, srv) | ||
| 53 | |||
| 54 | if err := srv.Run(ctx); err != nil { | ||
| 55 | log.Fatalf("mcp run: %v", err) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | func consume(ctx context.Context, w *watcher.Watcher, srv *mcp.Server) { | ||
| 60 | for { | ||
| 61 | select { | ||
| 62 | case <-ctx.Done(): | ||
| 63 | return | ||
| 64 | case ev, ok := <-w.Events(): | ||
| 65 | if !ok { | ||
| 66 | return | ||
| 67 | } | ||
| 68 | handle(ctx, ev.Path, srv) | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | func handle(ctx context.Context, path string, srv *mcp.Server) { | ||
| 74 | markers, err := scanner.Scan(path) | ||
| 75 | if err != nil { | ||
| 76 | log.Printf("scan %s: %v", path, err) | ||
| 77 | return | ||
| 78 | } | ||
| 79 | stamps := make(map[int]string, len(markers)) | ||
| 80 | for _, m := range markers { | ||
| 81 | if m.Tagged { | ||
| 82 | continue | ||
| 83 | } | ||
| 84 | id := "fw-" + uuid.NewString()[:8] | ||
| 85 | stamps[m.Line] = id | ||
| 86 | if err := srv.SendChannel(ctx, mcp.ChannelMeta{ | ||
| 87 | Source: "filewatch", | ||
| 88 | File: path, | ||
| 89 | Line: strconv.Itoa(m.Line), | ||
| 90 | ReplyTo: id, | ||
| 91 | }, m.Text); err != nil { | ||
| 92 | log.Printf("send: %v", err) | ||
| 93 | } | ||
| 94 | } | ||
| 95 | if len(stamps) == 0 { | ||
| 96 | return | ||
| 97 | } | ||
| 98 | if err := rewriter.StampUUIDs(path, stamps); err != nil { | ||
| 99 | log.Printf("stamp %s: %v", path, err) | ||
| 100 | } | ||
| 7 | } | 101 | } |
go.mod
| Old | New | ||
|---|---|---|---|
| @@ -4,6 +4,7 @@ go 1.26.2 | |||
| 4 | 4 | ||
| 5 | require ( | 5 | require ( |
| 6 | github.com/fsnotify/fsnotify v1.9.0 // indirect | 6 | github.com/fsnotify/fsnotify v1.9.0 // indirect |
| 7 | github.com/google/uuid v1.6.0 // indirect | ||
| 7 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect | 8 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect |
| 8 | golang.org/x/sys v0.13.0 // indirect | 9 | golang.org/x/sys v0.13.0 // indirect |
| 9 | ) | 10 | ) |
go.sum
| Old | New | ||
|---|---|---|---|
| @@ -1,6 +1,8 @@ | |||
| 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 2 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= | 2 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= |
| 3 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= | 3 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= |
| 4 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||
| 5 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
| 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
| 5 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= | 7 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= |
| 6 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= | 8 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= |