1package spec23import (4 "io/fs"5 "path/filepath"6 "time"7)89type stamp struct {10 mod time.Time11 size int6412}1314// Fingerprint is a cheap stat-only view of every file under openspec/. It is15// used to poll for changes without holding a file descriptor per watched file.16type Fingerprint map[string]stamp1718// Scan stats every file and directory under dir, so a new empty change19// directory is noticed too. A missing dir yields an empty fingerprint.20func Scan(dir string) Fingerprint {21 fp := Fingerprint{}22 _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {23 if err != nil {24 return nil25 }26 if d.IsDir() {27 fp[path] = stamp{} // presence only; dir mtimes churn on temp files28 return nil29 }30 fi, err := d.Info()31 if err != nil {32 return nil33 }34 fp[path] = stamp{mod: fi.ModTime(), size: fi.Size()}35 return nil36 })37 return fp38}3940// Changed returns the paths that were added, removed, or modified between41// two fingerprints.42func (fp Fingerprint) Changed(next Fingerprint) []string {43 var paths []string44 for p, s := range next {45 if old, ok := fp[p]; !ok || old != s {46 paths = append(paths, p)47 }48 }49 for p := range fp {50 if _, ok := next[p]; !ok {51 paths = append(paths, p)52 }53 }54 return paths55}