diff --git a/src/services/user-prompt/user-prompt-manager.ts b/src/services/user-prompt/user-prompt-manager.ts index 101f548b..b9e78814 100644 --- a/src/services/user-prompt/user-prompt-manager.ts +++ b/src/services/user-prompt/user-prompt-manager.ts @@ -335,8 +335,10 @@ export class UserPromptManager { const db = await this.ready(); const rows = projectPath ? await db.all( - `SELECT * FROM user_prompts WHERE captured = 1 AND project_path = ? ORDER BY created_at DESC`, - [projectPath] + `SELECT * FROM user_prompts + WHERE captured = 1 AND REPLACE(project_path, '\\', '/') = ? + ORDER BY created_at DESC`, + [projectPath.replace(/\\/g, "/")] ) : await db.all(`SELECT * FROM user_prompts WHERE captured = 1 ORDER BY created_at DESC`); return rows.map((row) => this.rowToPrompt(row)); @@ -351,8 +353,8 @@ export class UserPromptManager { const params: InValue[] = [`%${query}%`]; let sql = `SELECT * FROM user_prompts WHERE content LIKE ? AND captured = 1`; if (projectPath) { - sql += ` AND project_path = ?`; - params.push(projectPath); + sql += ` AND REPLACE(project_path, '\\', '/') = ?`; + params.push(projectPath.replace(/\\/g, "/")); } sql += ` ORDER BY created_at DESC LIMIT ?`; params.push(limit); diff --git a/tests/user-prompt-path-normalization.test.ts b/tests/user-prompt-path-normalization.test.ts new file mode 100644 index 00000000..0b11bcc9 --- /dev/null +++ b/tests/user-prompt-path-normalization.test.ts @@ -0,0 +1,79 @@ +import { afterAll, beforeEach, afterEach, describe, expect, it } from "bun:test"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { TursoDb } from "../src/services/turso/turso-db.js"; + +const sandbox = mkdtempSync(join(tmpdir(), "opencode-mem-path-home-")); +const originalHome = process.env.HOME; +const originalUserProfile = process.env.USERPROFILE; +process.env.HOME = sandbox; +process.env.USERPROFILE = sandbox; + +const { UserPromptManager } = await import("../src/services/user-prompt/user-prompt-manager.js"); + +type TestableManager = InstanceType & { + ready(): Promise; +}; + +afterAll(() => { + process.env.HOME = originalHome; + process.env.USERPROFILE = originalUserProfile; + try { + rmSync(sandbox, { recursive: true, force: true }); + } catch { + // ignore + } +}); + +describe("UserPromptManager project path normalization", () => { + let mgr: TestableManager; + let activeIds: string[]; + + beforeEach(() => { + mgr = new UserPromptManager() as TestableManager; + activeIds = []; + }); + + afterEach(async () => { + for (const id of activeIds) { + try { + await mgr.deletePrompt(id); + } catch { + // ignore + } + } + }); + + async function saveCapturedPrompt(projectPath: string) { + const id = await mgr.savePrompt( + "session-path-test", + `msg-${Date.now()}-${Math.random()}`, + projectPath, + "hello" + ); + await mgr.markAsCaptured(id); + activeIds.push(id); + return id; + } + + it("getCapturedPrompts matches paths regardless of separator style", async () => { + const storedPath = "C:\\workspace\\proj"; + await saveCapturedPrompt(storedPath); + + const forwardSlashQuery = await mgr.getCapturedPrompts("C:/workspace/proj"); + expect(forwardSlashQuery).toHaveLength(1); + + const backslashQuery = await mgr.getCapturedPrompts("C:\\workspace\\proj"); + expect(backslashQuery).toHaveLength(1); + }); + + it("searchPrompts matches paths regardless of separator style", async () => { + const storedPath = "D:\\repos\\app"; + await saveCapturedPrompt(storedPath); + + const results = await mgr.searchPrompts("hello", "D:/repos/app"); + expect(results).toHaveLength(1); + expect(results[0]?.content).toBe("hello"); + }); +});