Merge branch 'perf/directory-injector-dirty-flag' into dev

This commit is contained in:
YeonGyu-Kim 2026-02-13 11:45:45 +09:00
commit e99088d70f
4 changed files with 238 additions and 2 deletions

View File

@ -0,0 +1,114 @@
import { beforeEach, describe, expect, it, mock } from "bun:test"
const readFileSyncMock = mock((_: string, __: string) => "# AGENTS")
const findAgentsMdUpMock = mock((_: { startDir: string; rootDir: string }) => [] as string[])
const resolveFilePathMock = mock((_: string, path: string) => path)
const loadInjectedPathsMock = mock((_: string) => new Set<string>())
const saveInjectedPathsMock = mock((_: string, __: Set<string>) => {})
mock.module("node:fs", () => ({
readFileSync: readFileSyncMock,
}))
mock.module("./finder", () => ({
findAgentsMdUp: findAgentsMdUpMock,
resolveFilePath: resolveFilePathMock,
}))
mock.module("./storage", () => ({
loadInjectedPaths: loadInjectedPathsMock,
saveInjectedPaths: saveInjectedPathsMock,
}))
const { processFilePathForAgentsInjection } = await import("./injector")
describe("processFilePathForAgentsInjection", () => {
beforeEach(() => {
readFileSyncMock.mockClear()
findAgentsMdUpMock.mockClear()
resolveFilePathMock.mockClear()
loadInjectedPathsMock.mockClear()
saveInjectedPathsMock.mockClear()
})
it("does not save when all discovered paths are already cached", async () => {
//#given
const sessionID = "session-1"
const cachedDirectory = "/repo/src"
loadInjectedPathsMock.mockReturnValueOnce(new Set([cachedDirectory]))
findAgentsMdUpMock.mockReturnValueOnce(["/repo/src/AGENTS.md"])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForAgentsInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/src/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).not.toHaveBeenCalled()
})
it("saves when a new path is injected", async () => {
//#given
const sessionID = "session-2"
loadInjectedPathsMock.mockReturnValueOnce(new Set())
findAgentsMdUpMock.mockReturnValueOnce(["/repo/src/AGENTS.md"])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForAgentsInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/src/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect(saveCall[0]).toBe(sessionID)
expect((saveCall[1] as Set<string>).has("/repo/src")).toBe(true)
})
it("saves once when cached and new paths are mixed", async () => {
//#given
const sessionID = "session-3"
loadInjectedPathsMock.mockReturnValueOnce(new Set(["/repo/already-cached"]))
findAgentsMdUpMock.mockReturnValueOnce([
"/repo/already-cached/AGENTS.md",
"/repo/new-dir/AGENTS.md",
])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForAgentsInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/new-dir/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect((saveCall[1] as Set<string>).has("/repo/new-dir")).toBe(true)
})
})

View File

@ -33,6 +33,7 @@ export async function processFilePathForAgentsInjection(input: {
const cache = getSessionCache(input.sessionCaches, input.sessionID);
const agentsPaths = findAgentsMdUp({ startDir: dir, rootDir: input.ctx.directory });
let dirty = false;
for (const agentsPath of agentsPaths) {
const agentsDir = dirname(agentsPath);
if (cache.has(agentsDir)) continue;
@ -48,8 +49,11 @@ export async function processFilePathForAgentsInjection(input: {
: "";
input.output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`;
cache.add(agentsDir);
dirty = true;
} catch {}
}
saveInjectedPaths(input.sessionID, cache);
if (dirty) {
saveInjectedPaths(input.sessionID, cache);
}
}

View File

@ -0,0 +1,114 @@
import { beforeEach, describe, expect, it, mock } from "bun:test"
const readFileSyncMock = mock((_: string, __: string) => "# README")
const findReadmeMdUpMock = mock((_: { startDir: string; rootDir: string }) => [] as string[])
const resolveFilePathMock = mock((_: string, path: string) => path)
const loadInjectedPathsMock = mock((_: string) => new Set<string>())
const saveInjectedPathsMock = mock((_: string, __: Set<string>) => {})
mock.module("node:fs", () => ({
readFileSync: readFileSyncMock,
}))
mock.module("./finder", () => ({
findReadmeMdUp: findReadmeMdUpMock,
resolveFilePath: resolveFilePathMock,
}))
mock.module("./storage", () => ({
loadInjectedPaths: loadInjectedPathsMock,
saveInjectedPaths: saveInjectedPathsMock,
}))
const { processFilePathForReadmeInjection } = await import("./injector")
describe("processFilePathForReadmeInjection", () => {
beforeEach(() => {
readFileSyncMock.mockClear()
findReadmeMdUpMock.mockClear()
resolveFilePathMock.mockClear()
loadInjectedPathsMock.mockClear()
saveInjectedPathsMock.mockClear()
})
it("does not save when all discovered paths are already cached", async () => {
//#given
const sessionID = "session-1"
const cachedDirectory = "/repo/src"
loadInjectedPathsMock.mockReturnValueOnce(new Set([cachedDirectory]))
findReadmeMdUpMock.mockReturnValueOnce(["/repo/src/README.md"])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForReadmeInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/src/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).not.toHaveBeenCalled()
})
it("saves when a new path is injected", async () => {
//#given
const sessionID = "session-2"
loadInjectedPathsMock.mockReturnValueOnce(new Set())
findReadmeMdUpMock.mockReturnValueOnce(["/repo/src/README.md"])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForReadmeInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/src/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect(saveCall[0]).toBe(sessionID)
expect((saveCall[1] as Set<string>).has("/repo/src")).toBe(true)
})
it("saves once when cached and new paths are mixed", async () => {
//#given
const sessionID = "session-3"
loadInjectedPathsMock.mockReturnValueOnce(new Set(["/repo/already-cached"]))
findReadmeMdUpMock.mockReturnValueOnce([
"/repo/already-cached/README.md",
"/repo/new-dir/README.md",
])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
//#when
await processFilePathForReadmeInjection({
ctx: { directory: "/repo" } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: "/repo/new-dir/file.ts",
sessionID,
output: { title: "Result", output: "", metadata: {} },
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect((saveCall[1] as Set<string>).has("/repo/new-dir")).toBe(true)
})
})

View File

@ -33,6 +33,7 @@ export async function processFilePathForReadmeInjection(input: {
const cache = getSessionCache(input.sessionCaches, input.sessionID);
const readmePaths = findReadmeMdUp({ startDir: dir, rootDir: input.ctx.directory });
let dirty = false;
for (const readmePath of readmePaths) {
const readmeDir = dirname(readmePath);
if (cache.has(readmeDir)) continue;
@ -48,8 +49,11 @@ export async function processFilePathForReadmeInjection(input: {
: "";
input.output.output += `\n\n[Project README: ${readmePath}]\n${result}${truncationNotice}`;
cache.add(readmeDir);
dirty = true;
} catch {}
}
saveInjectedPaths(input.sessionID, cache);
if (dirty) {
saveInjectedPaths(input.sessionID, cache);
}
}