fix(sf): isolate sift warmup cache per project

This commit is contained in:
Mikael Hugo 2026-05-02 15:24:14 +02:00
parent f21890addb
commit c4ac851187
2 changed files with 65 additions and 6 deletions

View file

@ -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 <path> --agent "<task>"` 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 <path> "<query>"`; `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");
}

View file

@ -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);
}