Merge branch 'fix/perf-d03' into fix/perf-omo-in-tree
This commit is contained in:
@@ -3,6 +3,7 @@ import { createDynamicTruncator } from "../../shared/dynamic-truncator";
|
|||||||
import { getRuleInjectionFilePath } from "./output-path";
|
import { getRuleInjectionFilePath } from "./output-path";
|
||||||
import { createSessionCacheStore, createSessionRuleScanCacheStore } from "./cache";
|
import { createSessionCacheStore, createSessionRuleScanCacheStore } from "./cache";
|
||||||
import { createRuleInjectionProcessor } from "./injector";
|
import { createRuleInjectionProcessor } from "./injector";
|
||||||
|
import { clearProjectRootCache } from "./project-root-finder";
|
||||||
|
|
||||||
interface ToolExecuteInput {
|
interface ToolExecuteInput {
|
||||||
tool: string;
|
tool: string;
|
||||||
@@ -83,6 +84,7 @@ export function createRulesInjectorHook(
|
|||||||
if (sessionInfo?.id) {
|
if (sessionInfo?.id) {
|
||||||
clearSessionState(sessionInfo.id);
|
clearSessionState(sessionInfo.id);
|
||||||
}
|
}
|
||||||
|
clearProjectRootCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "session.compacted") {
|
if (event.type === "session.compacted") {
|
||||||
@@ -91,6 +93,7 @@ export function createRulesInjectorHook(
|
|||||||
if (sessionID) {
|
if (sessionID) {
|
||||||
clearSessionState(sessionID);
|
clearSessionState(sessionID);
|
||||||
}
|
}
|
||||||
|
clearProjectRootCache();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { afterEach, describe, expect, it, mock } from "bun:test";
|
||||||
|
|
||||||
|
describe("findProjectRoot", () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
const actualFileSystem = await import("node:fs");
|
||||||
|
mock.module("node:fs", () => actualFileSystem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("memoizes repeated lookups for the same start path and resets on cache clear", async () => {
|
||||||
|
// given
|
||||||
|
const actualFileSystem = await import("node:fs");
|
||||||
|
const projectRoot = "/workspace/project";
|
||||||
|
const startPath = `${projectRoot}/src/file.ts`;
|
||||||
|
const packageJsonPath = `${projectRoot}/package.json`;
|
||||||
|
|
||||||
|
const existsSyncSpy = mock((path: string) => path === packageJsonPath);
|
||||||
|
const statSyncSpy = mock(() => ({ isDirectory: () => false }));
|
||||||
|
|
||||||
|
mock.module("node:fs", () => ({
|
||||||
|
...actualFileSystem,
|
||||||
|
existsSync: existsSyncSpy,
|
||||||
|
statSync: statSyncSpy,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { clearProjectRootCache, findProjectRoot } = await import(
|
||||||
|
`./project-root-finder.ts?memoization=${Date.now()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
const firstResult = findProjectRoot(startPath);
|
||||||
|
const firstExistsSyncCallCount = existsSyncSpy.mock.calls.length;
|
||||||
|
|
||||||
|
const secondResult = findProjectRoot(startPath);
|
||||||
|
const secondExistsSyncCallCount = existsSyncSpy.mock.calls.length;
|
||||||
|
|
||||||
|
clearProjectRootCache();
|
||||||
|
const thirdResult = findProjectRoot(startPath);
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(firstResult).toBe(projectRoot);
|
||||||
|
expect(secondResult).toBe(projectRoot);
|
||||||
|
expect(thirdResult).toBe(projectRoot);
|
||||||
|
expect(firstExistsSyncCallCount).toBeGreaterThan(0);
|
||||||
|
expect(secondExistsSyncCallCount).toBe(firstExistsSyncCallCount);
|
||||||
|
expect(existsSyncSpy).toHaveBeenCalledTimes(firstExistsSyncCallCount * 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,12 @@ import { existsSync, statSync } from "node:fs";
|
|||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { PROJECT_MARKERS } from "./constants";
|
import { PROJECT_MARKERS } from "./constants";
|
||||||
|
|
||||||
|
const projectRootCache = new Map<string, string | null>();
|
||||||
|
|
||||||
|
export function clearProjectRootCache(): void {
|
||||||
|
projectRootCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find project root by walking up from startPath.
|
* Find project root by walking up from startPath.
|
||||||
* Checks for PROJECT_MARKERS (.git, pyproject.toml, package.json, etc.)
|
* Checks for PROJECT_MARKERS (.git, pyproject.toml, package.json, etc.)
|
||||||
@@ -10,6 +16,16 @@ import { PROJECT_MARKERS } from "./constants";
|
|||||||
* @returns Project root path or null if not found
|
* @returns Project root path or null if not found
|
||||||
*/
|
*/
|
||||||
export function findProjectRoot(startPath: string): string | null {
|
export function findProjectRoot(startPath: string): string | null {
|
||||||
|
if (projectRootCache.has(startPath)) {
|
||||||
|
return projectRootCache.get(startPath) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectRoot = findProjectRootWithoutCache(startPath);
|
||||||
|
projectRootCache.set(startPath, projectRoot);
|
||||||
|
return projectRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findProjectRootWithoutCache(startPath: string): string | null {
|
||||||
let current: string;
|
let current: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user