Skip to content

Commit 2204753

Browse files
fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly (#64711)
* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly * fix(memory-core): ignore non-markdown memory watch churn * fix(memory-core): allow multimodal watch events * test(memory-core): type watcher ignore callback --------- Co-authored-by: Vincent Koc <[email protected]>
1 parent 6437aa8 commit 2204753

2 files changed

Lines changed: 39 additions & 8 deletions

File tree

extensions/memory-core/src/memory/manager-sync-ops.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,29 @@ const IGNORED_MEMORY_WATCH_DIR_NAMES = new Set([
9090

9191
const log = createSubsystemLogger("memory");
9292

93-
function shouldIgnoreMemoryWatchPath(watchPath: string): boolean {
93+
function shouldIgnoreMemoryWatchPath(
94+
watchPath: string,
95+
stats?: { isDirectory?: () => boolean },
96+
multimodalSettings?: ResolvedMemorySearchConfig["multimodal"],
97+
): boolean {
9498
const normalized = path.normalize(watchPath);
9599
const parts = normalized
96100
.split(path.sep)
97101
.map((segment) => normalizeLowercaseStringOrEmpty(segment));
98-
return parts.some((segment) => IGNORED_MEMORY_WATCH_DIR_NAMES.has(segment));
102+
if (parts.some((segment) => IGNORED_MEMORY_WATCH_DIR_NAMES.has(segment))) {
103+
return true;
104+
}
105+
if (stats?.isDirectory?.()) {
106+
return false;
107+
}
108+
const extension = normalizeLowercaseStringOrEmpty(path.extname(normalized));
109+
if (extension.length === 0 || extension === ".md") {
110+
return false;
111+
}
112+
if (!multimodalSettings) {
113+
return true;
114+
}
115+
return classifyMemoryMultimodalPath(normalized, multimodalSettings) === null;
99116
}
100117

101118
export function runDetachedMemorySync(sync: () => Promise<void>, reason: "interval" | "watch") {
@@ -345,7 +362,7 @@ export abstract class MemoryManagerSyncOps {
345362
const watchPaths = new Set<string>([
346363
path.join(this.workspaceDir, "MEMORY.md"),
347364
path.join(this.workspaceDir, "memory.md"),
348-
path.join(this.workspaceDir, "memory", "**", "*.md"),
365+
path.join(this.workspaceDir, "memory"),
349366
]);
350367
const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths);
351368
for (const entry of additionalPaths) {
@@ -380,7 +397,8 @@ export abstract class MemoryManagerSyncOps {
380397
}
381398
this.watcher = chokidar.watch(Array.from(watchPaths), {
382399
ignoreInitial: true,
383-
ignored: (watchPath) => shouldIgnoreMemoryWatchPath(watchPath),
400+
ignored: (watchPath, stats) =>
401+
shouldIgnoreMemoryWatchPath(watchPath, stats, this.settings.multimodal),
384402
awaitWriteFinish: {
385403
stabilityThreshold: this.settings.sync.watchDebounceMs,
386404
pollInterval: 100,

extensions/memory-core/src/memory/manager.watcher-config.test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi }
99
import type { MemoryIndexManager } from "./index.js";
1010
import { registerBuiltInMemoryEmbeddingProviders } from "./provider-adapters.js";
1111

12+
type WatchIgnoredFn = (watchPath: string, stats?: { isDirectory?: () => boolean }) => boolean;
13+
1214
const { watchMock } = vi.hoisted(() => ({
1315
watchMock: vi.fn(() => ({
1416
on: vi.fn(),
@@ -123,7 +125,7 @@ describe("memory watcher config", () => {
123125
manager = result.manager as unknown as MemoryIndexManager;
124126
}
125127

126-
it("watches markdown globs and ignores dependency directories", async () => {
128+
it("watches the memory directory and ignores non-markdown churn", async () => {
127129
await setupWatcherWorkspace({ name: "notes.md", contents: "hello" });
128130
const cfg = createWatcherConfig();
129131

@@ -138,20 +140,25 @@ describe("memory watcher config", () => {
138140
expect.arrayContaining([
139141
path.join(workspaceDir, "MEMORY.md"),
140142
path.join(workspaceDir, "memory.md"),
141-
path.join(workspaceDir, "memory", "**", "*.md"),
143+
path.join(workspaceDir, "memory"),
142144
path.join(extraDir, "**", "*.md"),
143145
]),
144146
);
145147
expect(options.ignoreInitial).toBe(true);
146148
expect(options.awaitWriteFinish).toEqual({ stabilityThreshold: 25, pollInterval: 100 });
147149

148-
const ignored = options.ignored as ((watchPath: string) => boolean) | undefined;
150+
const ignored = options.ignored as WatchIgnoredFn | undefined;
149151
expect(ignored).toBeTypeOf("function");
150152
expect(ignored?.(path.join(workspaceDir, "memory", "node_modules", "pkg", "index.md"))).toBe(
151153
true,
152154
);
153155
expect(ignored?.(path.join(workspaceDir, "memory", ".venv", "lib", "python.md"))).toBe(true);
156+
expect(ignored?.(path.join(workspaceDir, "memory", "project", "notes.tmp"))).toBe(true);
157+
expect(ignored?.(path.join(workspaceDir, "memory", "project", "notes.json"))).toBe(true);
154158
expect(ignored?.(path.join(workspaceDir, "memory", "project", "notes.md"))).toBe(false);
159+
expect(
160+
ignored?.(path.join(workspaceDir, "memory", "project"), { isDirectory: () => true }),
161+
).toBe(false);
155162
});
156163

157164
it("watches multimodal extensions with case-insensitive globs", async () => {
@@ -166,7 +173,7 @@ describe("memory watcher config", () => {
166173
await expectWatcherManager(cfg);
167174

168175
expect(watchMock).toHaveBeenCalledTimes(1);
169-
const [watchedPaths] = watchMock.mock.calls[0] as unknown as [
176+
const [watchedPaths, options] = watchMock.mock.calls[0] as unknown as [
170177
string[],
171178
Record<string, unknown>,
172179
];
@@ -176,5 +183,11 @@ describe("memory watcher config", () => {
176183
path.join(extraDir, "**", "*.[wW][aA][vV]"),
177184
]),
178185
);
186+
187+
const ignored = options.ignored as WatchIgnoredFn | undefined;
188+
expect(ignored).toBeTypeOf("function");
189+
expect(ignored?.(path.join(extraDir, "nested", "PHOTO.PNG"))).toBe(false);
190+
expect(ignored?.(path.join(extraDir, "nested", "voice.WAV"))).toBe(false);
191+
expect(ignored?.(path.join(extraDir, "nested", "metadata.json"))).toBe(true);
179192
});
180193
});

0 commit comments

Comments
 (0)