mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-19 16:13:56 +00:00
fix: handle # compound-path separator in fingerprintFile (#358)
Some checks are pending
CI / semgrep (push) Waiting to run
Some checks are pending
CI / semgrep (push) Waiting to run
The Cursor provider encodes workspace context into source paths using a `#cursor-ws=<tag>` suffix (e.g. `state.vscdb#cursor-ws=__orphan__`). `fingerprintFile` only had a fallback for `:` separators (OpenCode sessions), so Cursor sources silently returned null on macOS/Linux where paths contain no colons, causing them to be skipped entirely. Add a `#` fallback before the existing `:` check. The first `stat()` on the full path still succeeds for real files containing `#`, so there is no regression for legitimate paths. Includes 4 new test cases covering both separators, the combined case, and the null case for non-existent base files.
This commit is contained in:
parent
c9487e7b0a
commit
3542407f8f
2 changed files with 51 additions and 0 deletions
|
|
@ -239,6 +239,21 @@ export async function fingerprintFile(filePath: string): Promise<FileFingerprint
|
|||
const s = await stat(filePath)
|
||||
return { dev: s.dev, ino: s.ino, mtimeMs: s.mtimeMs, sizeBytes: s.size }
|
||||
} catch {
|
||||
// Providers encode extra context into source paths using virtual suffixes:
|
||||
// - Cursor: `<dbPath>#cursor-ws=<workspace>` (workspace-aware routing)
|
||||
// - OpenCode: `<dbPath>:<sessionId>` (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 {
|
||||
|
|
|
|||
|
|
@ -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 ──────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue