cmd/filewatch-mcp/main.go
Ref: Size: 2.1 KiB
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/google/uuid"
"github.com/xanderle/claudealong/internal/ignore"
"github.com/xanderle/claudealong/internal/mcp"
"github.com/xanderle/claudealong/internal/rewriter"
"github.com/xanderle/claudealong/internal/scanner"
"github.com/xanderle/claudealong/internal/watcher"
)
func main() {
root := flag.String("root", ".", "project root to watch")
debounce := flag.Duration("debounce", 500*time.Millisecond, "per-file debounce window")
flag.Parse()
log.SetOutput(os.Stderr)
log.SetFlags(log.Ltime | log.Lshortfile)
ig, err := ignore.New(*root)
if err != nil {
log.Fatalf("ignore: %v", err)
}
w, err := watcher.New(*root, ig, *debounce)
if err != nil {
log.Fatalf("watcher: %v", err)
}
defer w.Close()
srv := mcp.New()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() { <-sigCh; cancel() }()
if err := w.Start(ctx); err != nil {
log.Fatalf("watcher start: %v", err)
}
go consume(ctx, w, srv)
if err := srv.Run(ctx); err != nil {
log.Fatalf("mcp run: %v", err)
}
}
func consume(ctx context.Context, w *watcher.Watcher, srv *mcp.Server) {
for {
select {
case <-ctx.Done():
return
case ev, ok := <-w.Events():
if !ok {
return
}
handle(ctx, ev.Path, srv)
}
}
}
func handle(ctx context.Context, path string, srv *mcp.Server) {
markers, err := scanner.Scan(path)
if err != nil {
log.Printf("scan %s: %v", path, err)
return
}
stamps := make(map[int]string, len(markers))
for _, m := range markers {
if m.Tagged {
continue
}
id := "fw-" + uuid.NewString()[:8]
stamps[m.Line] = id
if err := srv.SendChannel(ctx, mcp.ChannelMeta{
Source: "filewatch",
File: path,
Line: strconv.Itoa(m.Line),
ReplyTo: id,
}, m.Text); err != nil {
log.Printf("send: %v", err)
}
}
if len(stamps) == 0 {
return
}
if err := rewriter.StampUUIDs(path, stamps); err != nil {
log.Printf("stamp %s: %v", path, err)
}
}