608e53be
feat(scanner): detect untagged @claude markers in Go comments
a73x 2026-04-29 05:42
diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go new file mode 100644 index 0000000..dc41796 --- /dev/null +++ b/internal/scanner/scanner.go @@ -0,0 +1,60 @@ package scanner import ( "bufio" "os" "path/filepath" "regexp" "strings" ) type Marker struct { Line int Text string Tagged bool UUID string } // commentPrefixes maps file extension (lowercase, with dot) to the line-comment prefix. var commentPrefixes = map[string]string{ ".go": "//", } var ( untaggedRE = regexp.MustCompile(`^@claude\s+\S`) taggedRE = regexp.MustCompile(`^@claude\[(fw-[0-9a-f]{8})\]\s+\S`) ) func Scan(path string) ([]Marker, error) { ext := strings.ToLower(filepath.Ext(path)) prefix, ok := commentPrefixes[ext] if !ok { return nil, nil } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() var markers []Marker sc := bufio.NewScanner(f) sc.Buffer(make([]byte, 1024*1024), 1024*1024) line := 0 for sc.Scan() { line++ raw := strings.TrimSpace(sc.Text()) if !strings.HasPrefix(raw, prefix) { continue } body := strings.TrimSpace(strings.TrimPrefix(raw, prefix)) if m := taggedRE.FindStringSubmatch(body); m != nil { markers = append(markers, Marker{Line: line, Text: body, Tagged: true, UUID: m[1]}) continue } if untaggedRE.MatchString(body) { markers = append(markers, Marker{Line: line, Text: body}) } } return markers, sc.Err() } diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go new file mode 100644 index 0000000..a097963 --- /dev/null +++ b/internal/scanner/scanner_test.go @@ -0,0 +1,39 @@ package scanner import ( "os" "path/filepath" "testing" ) func writeFile(t *testing.T, dir, name, content string) string { t.Helper() path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } return path } func TestScanGoFileSingleUntaggedMarker(t *testing.T) { dir := t.TempDir() path := writeFile(t, dir, "foo.go", "package foo\n\n// @claude write a test for foo\nfunc Foo() {}\n") markers, err := Scan(path) if err != nil { t.Fatalf("Scan: %v", err) } if len(markers) != 1 { t.Fatalf("got %d markers, want 1: %#v", len(markers), markers) } m := markers[0] if m.Line != 3 { t.Errorf("Line = %d, want 3", m.Line) } if m.Text != "@claude write a test for foo" { t.Errorf("Text = %q, want %q", m.Text, "@claude write a test for foo") } if m.Tagged { t.Errorf("Tagged = true, want false") } }