From c4ac851187308128bb2ea7960c4f301f06b15d71 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sat, 2 May 2026 15:24:14 +0200 Subject: [PATCH] fix(sf): isolate sift warmup cache per project --- .../extensions/sf/code-intelligence.ts | 35 ++++++++++++++++-- .../sf/tests/code-intelligence.test.ts | 36 +++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/resources/extensions/sf/code-intelligence.ts b/src/resources/extensions/sf/code-intelligence.ts index d37c18ecb..aab552625 100644 --- a/src/resources/extensions/sf/code-intelligence.ts +++ b/src/resources/extensions/sf/code-intelligence.ts @@ -144,6 +144,29 @@ const DEFAULT_SIFT_WARMUP_RETRIEVER_TIMEOUT_MS = 30_000; const DEFAULT_SIFT_WARMUP_HARD_TIMEOUT_SEC = 30; const SIFT_WARMUP_KILL_GRACE_SEC = 10; +function resolveSiftWarmupRuntimeDirs(projectRoot: string): { + cacheHome: string; + tmpDir: string; +} { + const runtimeRoot = join(projectRoot, ".sf", "runtime", "sift"); + return { + cacheHome: join(runtimeRoot, "xdg-cache"), + tmpDir: join(runtimeRoot, "tmp"), + }; +} + +function buildSiftWarmupEnv( + projectRoot: string, + env: NodeJS.ProcessEnv, +): NodeJS.ProcessEnv { + const dirs = resolveSiftWarmupRuntimeDirs(projectRoot); + return { + ...env, + XDG_CACHE_HOME: dirs.cacheHome, + TMPDIR: dirs.tmpDir, + }; +} + function readJsonConfig(configPath: string): McpConfigFile { if (!existsSync(configPath)) return {}; const raw = readFileSync(configPath, "utf-8"); @@ -488,7 +511,11 @@ export function ensureSiftIndexWarmup( : "sift page-index-hybrid warmup started (no timeout(1)/gtimeout on PATH; running unbounded)"; try { + const runtimeDirs = resolveSiftWarmupRuntimeDirs(projectRoot); mkdirSync(join(projectRoot, ".sf", "runtime"), { recursive: true }); + mkdirSync(runtimeDirs.cacheHome, { recursive: true }); + mkdirSync(runtimeDirs.tmpDir, { recursive: true }); + const childEnv = buildSiftWarmupEnv(projectRoot, env); writeFileSync( markerPath, `${JSON.stringify( @@ -500,6 +527,8 @@ export function ensureSiftIndexWarmup( args, siftBinary: detection.binaryPath, hardTimeoutSec: wrapper?.timeoutSec ?? null, + cacheHome: runtimeDirs.cacheHome, + tmpDir: runtimeDirs.tmpDir, }, null, 2, @@ -509,7 +538,7 @@ export function ensureSiftIndexWarmup( const child = (options.spawnFn ?? spawn)(command, args, { cwd: projectRoot, - env, + env: childEnv, stdio: "ignore", detached: true, }); @@ -791,7 +820,7 @@ function buildSiftContextLines( '`--strategy path-hybrid` for path-heavy queries, and `sift search --agent ""` for bounded planner-driven exploration.', ); lines.push( - "- Sift uses a sector-aware cache in the platform cache directory, typically `~/.cache/sift`; " + + "- SF runs Sift warmup with project-scoped cache/temp directories under `.sf/runtime/sift/`; " + "if the CLI is missing or fails, continue with `.sf/CODEBASE.md`, native `grep`/`find`/`ls`, `lsp`, and scout.", ); } else { @@ -960,7 +989,7 @@ export function formatSiftStatus( 'When configured, agents should use `sift search --json ""`; `page-index-hybrid` is the strongest direct-search preset and `path-hybrid` is best for path-heavy queries.', ); lines.push( - "Sift reuses a sector-aware cache in the platform cache directory, typically ~/.cache/sift.", + "SF runs Sift warmup with project-scoped cache/temp directories under .sf/runtime/sift/.", ); return lines.join("\n"); } diff --git a/src/resources/extensions/sf/tests/code-intelligence.test.ts b/src/resources/extensions/sf/tests/code-intelligence.test.ts index abc31c052..6e64a6cdb 100644 --- a/src/resources/extensions/sf/tests/code-intelligence.test.ts +++ b/src/resources/extensions/sf/tests/code-intelligence.test.ts @@ -499,9 +499,17 @@ test("ensureSiftIndexWarmup defaults optional warmup hard cap to 30 seconds", () const fakeTimeout = join(projectRoot, "bin", "timeout"); writeFileSync(fakeTimeout, "", "utf-8"); - const calls: Array<{ command: string; args: string[] }> = []; - const fakeSpawn = ((command: string, args: string[]) => { - calls.push({ command, args }); + const calls: Array<{ + command: string; + args: string[]; + options?: { env?: NodeJS.ProcessEnv }; + }> = []; + const fakeSpawn = ((command: string, args: string[], options?: unknown) => { + calls.push({ + command, + args, + options: options as { env?: NodeJS.ProcessEnv }, + }); return { unref() {} }; }) as unknown as typeof import("node:child_process").spawn; @@ -520,6 +528,28 @@ test("ensureSiftIndexWarmup defaults optional warmup hard cap to 30 seconds", () fakeSift, ]); assert.match(result.reason, /hard cap 30s/); + assert.equal( + calls[0].options?.env?.XDG_CACHE_HOME, + join(projectRoot, ".sf", "runtime", "sift", "xdg-cache"), + ); + assert.equal( + calls[0].options?.env?.TMPDIR, + join(projectRoot, ".sf", "runtime", "sift", "tmp"), + ); + const marker = JSON.parse( + readFileSync( + join(projectRoot, ".sf", "runtime", "sift-index-warmup.json"), + "utf-8", + ), + ); + assert.equal( + marker.cacheHome, + join(projectRoot, ".sf", "runtime", "sift", "xdg-cache"), + ); + assert.equal( + marker.tmpDir, + join(projectRoot, ".sf", "runtime", "sift", "tmp"), + ); } finally { cleanup(projectRoot); }