From 71a63c20ec329a25272533dd61444f07645fca75 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 18:02:13 +0900 Subject: [PATCH] Revert "fix(hooks): dedupe native agent instructions" This reverts commit 78d52872f21f23d2b393781141c8d531674da087. --- .../injector.test.ts | 65 -------- .../directory-agents-injector/injector.ts | 139 +----------------- 2 files changed, 3 insertions(+), 201 deletions(-) diff --git a/src/hooks/directory-agents-injector/injector.test.ts b/src/hooks/directory-agents-injector/injector.test.ts index 23a75932f..822381a69 100644 --- a/src/hooks/directory-agents-injector/injector.test.ts +++ b/src/hooks/directory-agents-injector/injector.test.ts @@ -173,71 +173,6 @@ describe("processFilePathForAgentsInjection", () => { expect(output.output.split("[Directory Context:").length - 1).toBe(2) }) - it("dedupes native global Instructions from blocks across reads", async () => { - // given - const { processFilePathForAgentsInjection } = await import("./injector") - const sessionCaches = new Map>() - const filePath = join(testRoot, "file.ts") - const globalAgentsPath = "/Users/example/.config/opencode/AGENTS.md" - const globalAgentsContent = "# GLOBAL AGENTS\nglobal directives" - const nativeOutput = () => ({ - title: "Read result", - output: `base output\n\nAdditional project instructions matched for ${filePath}:\n\nInstructions from: ${globalAgentsPath}\n${globalAgentsContent}`, - metadata: {}, - }) - const firstOutput = nativeOutput() - const secondOutput = nativeOutput() - - // when - await processFilePathForAgentsInjection({ - ctx: { directory: testRoot } as PluginInput, - truncator, - sessionCaches, - filePath, - sessionID: "session-native-global-dedupe", - output: firstOutput, - }) - await processFilePathForAgentsInjection({ - ctx: { directory: testRoot } as PluginInput, - truncator, - sessionCaches, - filePath, - sessionID: "session-native-global-dedupe", - output: secondOutput, - }) - - // then - expect(firstOutput.output).toContain(`Instructions from: ${globalAgentsPath}`) - expect(secondOutput.output).toBe("base output") - }) - - it("does not add Directory Context when native output already included the same AGENTS.md", async () => { - // given - const { processFilePathForAgentsInjection } = await import("./injector") - const filePath = join(srcDirectory, "file.ts") - const srcAgentsPath = join(srcDirectory, "AGENTS.md") - const output = { - title: "Read result", - output: `base output\n\nAdditional project instructions matched for ${filePath}:\n\nInstructions from: ${srcAgentsPath}\n${srcAgentsContent}`, - metadata: {}, - } - - // when - await processFilePathForAgentsInjection({ - ctx: { directory: testRoot } as PluginInput, - truncator, - sessionCaches: new Map(), - filePath, - sessionID: "session-native-local-dedupe", - output, - }) - - // then - expect(output.output).toContain(`Instructions from: ${srcAgentsPath}`) - expect(output.output).not.toContain(`[Directory Context: ${srcAgentsPath}]`) - expect(output.output.split(srcAgentsContent).length - 1).toBe(1) - }) - it("shows truncation notice when content is truncated", async () => { // given const { processFilePathForAgentsInjection } = await import("./injector") diff --git a/src/hooks/directory-agents-injector/injector.ts b/src/hooks/directory-agents-injector/injector.ts index 184aa9e9e..f05dc276f 100644 --- a/src/hooks/directory-agents-injector/injector.ts +++ b/src/hooks/directory-agents-injector/injector.ts @@ -8,137 +8,6 @@ import { loadInjectedPaths, saveInjectedPaths } from "./storage"; type DynamicTruncator = ReturnType; -const ADDITIONAL_INSTRUCTIONS_MARKER = "Additional project instructions matched for "; -const DIRECTORY_CONTEXT_MARKER = "[Directory Context: "; -const INSTRUCTIONS_FROM_MARKER = "Instructions from: "; - -interface InstructionBlock { - path: string; - start: number; - end: number; - source: "directory-context" | "instructions-from"; -} - -function lineStartAt(output: string, index: number): number { - const previousNewline = output.lastIndexOf("\n", index - 1); - return previousNewline === -1 ? 0 : previousNewline + 1; -} - -function lineEndAt(output: string, index: number): number { - const nextNewline = output.indexOf("\n", index); - return nextNewline === -1 ? output.length : nextNewline; -} - -function findAdditionalInstructionsBlockStart(output: string, instructionsLineStart: number): number { - const headerStart = output.lastIndexOf(ADDITIONAL_INSTRUCTIONS_MARKER, instructionsLineStart); - if (headerStart === -1) return instructionsLineStart; - - const headerLineEnd = lineEndAt(output, headerStart); - if (headerLineEnd > instructionsLineStart) return instructionsLineStart; - - const gap = output.slice(headerLineEnd, instructionsLineStart); - return gap.trim() === "" ? headerStart : instructionsLineStart; -} - -function findNextInstructionBlockStart(output: string, from: number): number { - const markers = [ - `\n\n${ADDITIONAL_INSTRUCTIONS_MARKER}`, - `\n\n${DIRECTORY_CONTEXT_MARKER}`, - `\n\n${INSTRUCTIONS_FROM_MARKER}`, - ]; - const starts = markers - .map((marker) => output.indexOf(marker, from)) - .filter((index) => index !== -1); - return starts.length > 0 ? Math.min(...starts) : output.length; -} - -function collectInstructionBlocks(output: string): InstructionBlock[] { - const blocks: InstructionBlock[] = []; - - let searchIndex = 0; - while (true) { - const markerIndex = output.indexOf(INSTRUCTIONS_FROM_MARKER, searchIndex); - if (markerIndex === -1) break; - - const lineStart = lineStartAt(output, markerIndex); - const lineEnd = lineEndAt(output, markerIndex); - const instructionPath = output.slice(markerIndex + INSTRUCTIONS_FROM_MARKER.length, lineEnd).trim(); - if (instructionPath) { - blocks.push({ - path: instructionPath, - start: findAdditionalInstructionsBlockStart(output, lineStart), - end: findNextInstructionBlockStart(output, lineEnd), - source: "instructions-from", - }); - } - searchIndex = lineEnd; - } - - searchIndex = 0; - while (true) { - const markerIndex = output.indexOf(DIRECTORY_CONTEXT_MARKER, searchIndex); - if (markerIndex === -1) break; - - const pathStart = markerIndex + DIRECTORY_CONTEXT_MARKER.length; - const pathEnd = output.indexOf("]", pathStart); - if (pathEnd === -1) break; - - const instructionPath = output.slice(pathStart, pathEnd).trim(); - if (instructionPath) { - blocks.push({ - path: instructionPath, - start: lineStartAt(output, markerIndex), - end: findNextInstructionBlockStart(output, pathEnd), - source: "directory-context", - }); - } - searchIndex = pathEnd + 1; - } - - return blocks.sort((a, b) => a.start - b.start); -} - -function removeInstructionBlockRanges( - output: string, - ranges: Array<{ start: number; end: number }>, -): string { - let deduped = output; - for (const range of [...ranges].sort((a, b) => b.start - a.start)) { - deduped = deduped.slice(0, range.start) + deduped.slice(range.end); - } - return deduped.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, ""); -} - -function dedupeExistingInstructionBlocks( - output: string, - cache: Set, -): { output: string; dirty: boolean } { - const blocks = collectInstructionBlocks(output); - if (blocks.length === 0) return { output, dirty: false }; - - const seenInOutput = new Set(); - const rangesToRemove: Array<{ start: number; end: number }> = []; - let dirty = false; - - for (const block of blocks) { - const repeatedNativeInstruction = block.source === "instructions-from" && cache.has(block.path); - if (repeatedNativeInstruction || seenInOutput.has(block.path)) { - rangesToRemove.push({ start: block.start, end: block.end }); - dirty = true; - continue; - } - - cache.add(block.path); - seenInOutput.add(block.path); - dirty = true; - } - - return { - output: rangesToRemove.length > 0 ? removeInstructionBlockRanges(output, rangesToRemove) : output, - dirty, - }; -} - function getSessionCache( sessionCaches: Map>, sessionID: string, @@ -167,17 +36,15 @@ export async function processFilePathForAgentsInjection(input: { const dir = dirname(resolved); const cache = getSessionCache(input.sessionCaches, input.sessionID); const agentsPaths = await findAgentsMdUp({ startDir: dir, rootDir: input.ctx.directory }); - const dedupedExisting = dedupeExistingInstructionBlocks(input.output.output, cache); - input.output.output = dedupedExisting.output; - let dirty = dedupedExisting.dirty; + let dirty = false; for (const agentsPath of agentsPaths) { const agentsDir = dirname(agentsPath); - if (cache.has(agentsPath) || cache.has(agentsDir)) continue; + if (cache.has(agentsDir)) continue; try { const content = await fsPromises.readFile(agentsPath, "utf-8"); - cache.add(agentsPath); + cache.add(agentsDir); const { result, truncated } = await input.truncator.truncate( input.sessionID, content,