a73x

23114045

feat: wire watcher → scanner → mcp → rewriter in main

a73x   2026-04-29 05:59

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

diff --git a/cmd/filewatch-mcp/main.go b/cmd/filewatch-mcp/main.go
index ba241f0..f22dbc9 100644
--- a/cmd/filewatch-mcp/main.go
+++ b/cmd/filewatch-mcp/main.go
@@ -1,7 +1,101 @@
package main

import "fmt"
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() {
	fmt.Println("filewatch-mcp")
	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)
	}
}
diff --git a/go.mod b/go.mod
index 47a7d14..c0aeb67 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.26.2

require (
	github.com/fsnotify/fsnotify v1.9.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect
	golang.org/x/sys v0.13.0 // indirect
)
diff --git a/go.sum b/go.sum
index ced19ef..e3d1022 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=