diff --git a/CHANGELOG.md b/CHANGELOG.md index f905bfa..bcc5698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to SuperBased Observer are documented here. ## [Unreleased] +### Changed + +- **perf(watcher): reuse the live root map during file dispatch** — avoids + re-scanning every detected adapter on each poll/write re-dispatch, reducing + steady-state watcher CPU during high-churn session files. + ## [1.32.0] — 2026-08-26 ### Fixed diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 8ee832a..27b4583 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -760,6 +760,9 @@ func (w *Watcher) pollCursors(ctx context.Context) error { // The registry is still queried per call so dynamically-added // adapters appear without restarting Watch. func (w *Watcher) adapterFor(path string) adapter.Adapter { + if byRoot := w.snapshotByRoot(); len(byRoot) > 0 { + return adapterForPath(byRoot, path) + } byRoot := map[string]adapter.Adapter{} for _, a := range w.registry.Detected(w.allow) { for _, root := range a.WatchPaths() { diff --git a/internal/watcher/watcher_test.go b/internal/watcher/watcher_test.go index 5647ba2..7f2e540 100644 --- a/internal/watcher/watcher_test.go +++ b/internal/watcher/watcher_test.go @@ -396,6 +396,18 @@ func TestNewClampsNegativePollInterval(t *testing.T) { } } +func TestAdapterForUsesLiveRootIndex(t *testing.T) { + t.Parallel() + w, _, root := setup(t) + stub := &stubRetryAdapter{name: "stub", root: root} + w.byRoot = map[string]adapter.Adapter{root: stub} + + got := w.adapterFor(filepath.Join(root, "nested", "value.stub")) + if got != stub { + t.Fatalf("adapterFor used registry scan instead of live root index: got %T want %T", got, stub) + } +} + // TestCodexShortSessionMultiPassIngest reproduces the live failure mode // reported 2026-05-06: a short ChatGPT-auth Codex rollout grows in three // chunks (leading prompts, then function_call + token_count, then final