diff --git a/src/session-cache.ts b/src/session-cache.ts index 5534ad6..948ed38 100644 --- a/src/session-cache.ts +++ b/src/session-cache.ts @@ -239,6 +239,21 @@ export async function fingerprintFile(filePath: string): Promise#cursor-ws=` (workspace-aware routing) + // - OpenCode: `:` (session scoping) + // These compound paths don't exist on disk; strip the suffix to stat the + // underlying file. Try `#` first (rare in real paths), then `:` (must use + // lastIndexOf to tolerate Windows drive letters like C:\...). + const hashIdx = filePath.indexOf('#') + if (hashIdx > 0) { + try { + const s = await stat(filePath.slice(0, hashIdx)) + return { dev: s.dev, ino: s.ino, mtimeMs: s.mtimeMs, sizeBytes: s.size } + } catch { + // fall through to colon check + } + } const colonIdx = filePath.lastIndexOf(':') if (colonIdx > 0) { try { diff --git a/tests/session-cache.test.ts b/tests/session-cache.test.ts index 8da5153..e919d0b 100644 --- a/tests/session-cache.test.ts +++ b/tests/session-cache.test.ts @@ -185,6 +185,42 @@ describe('fingerprintFile', () => { const fp = await fingerprintFile('/no/such/file') expect(fp).toBeNull() }) + + it('resolves compound path with # separator (Cursor workspace)', async () => { + await mkdir(TMP_DIR, { recursive: true }) + const filePath = join(TMP_DIR, 'state.vscdb') + await writeFile(filePath, 'cursor-data') + + const fp = await fingerprintFile(`${filePath}#cursor-ws=__orphan__`) + expect(fp).not.toBeNull() + expect(fp!.sizeBytes).toBe(11) + }) + + it('resolves compound path with : separator (OpenCode session)', async () => { + await mkdir(TMP_DIR, { recursive: true }) + const filePath = join(TMP_DIR, 'opencode.db') + await writeFile(filePath, 'opencode-data') + + const fp = await fingerprintFile(`${filePath}:ses_abc123`) + expect(fp).not.toBeNull() + expect(fp!.sizeBytes).toBe(13) + }) + + it('returns null when base file does not exist for compound path', async () => { + const fp = await fingerprintFile('/no/such/file.db#cursor-ws=workspace') + expect(fp).toBeNull() + }) + + it('prefers # separator over : when both present', async () => { + await mkdir(TMP_DIR, { recursive: true }) + const filePath = join(TMP_DIR, 'state.vscdb') + await writeFile(filePath, 'both-seps') + + // Path has both # and : — should strip at # first and find the base file + const fp = await fingerprintFile(`${filePath}#cursor-ws=ws:extra-colon`) + expect(fp).not.toBeNull() + expect(fp!.sizeBytes).toBe(9) + }) }) // ── reconcileFile ──────────────────────────────────────────────────────