From 1b427570c8cf2d7eed80000d1b2a44d6e0133852 Mon Sep 17 00:00:00 2001 From: Sisyphus Date: Fri, 26 Dec 2025 15:38:28 +0900 Subject: [PATCH] feat: add dynamic truncation to rules/readme/agents injectors (#257) - Apply dynamic truncation to rules-injector, directory-readme-injector, and directory-agents-injector - Add truncation notice encouraging users to read full content - Save context window space while maintaining awareness of complete documentation - Resolves #221 (part 1) Co-authored-by: sisyphus-dev-ai --- src/hooks/directory-agents-injector/index.ts | 16 +++++++++++----- src/hooks/directory-readme-injector/index.ts | 16 +++++++++++----- src/hooks/rules-injector/index.ts | 16 +++++++++++----- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/hooks/directory-agents-injector/index.ts b/src/hooks/directory-agents-injector/index.ts index c5f1f56..2b08877 100644 --- a/src/hooks/directory-agents-injector/index.ts +++ b/src/hooks/directory-agents-injector/index.ts @@ -7,6 +7,7 @@ import { clearInjectedPaths, } from "./storage"; import { AGENTS_FILENAME } from "./constants"; +import { createDynamicTruncator } from "../../shared/dynamic-truncator"; interface ToolExecuteInput { tool: string; @@ -39,6 +40,7 @@ interface EventInput { export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { const sessionCaches = new Map>(); const pendingBatchReads = new Map(); + const truncator = createDynamicTruncator(ctx); function getSessionCache(sessionID: string): Set { if (!sessionCaches.has(sessionID)) { @@ -73,11 +75,11 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { return found.reverse(); } - function processFilePathForInjection( + async function processFilePathForInjection( filePath: string, sessionID: string, output: ToolExecuteOutput, - ): void { + ): Promise { const resolved = resolveFilePath(filePath); if (!resolved) return; @@ -91,7 +93,11 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { try { const content = readFileSync(agentsPath, "utf-8"); - output.output += `\n\n[Directory Context: ${agentsPath}]\n${content}`; + const { result, truncated } = await truncator.truncate(sessionID, content); + const truncationNotice = truncated + ? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]` + : ""; + output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`; cache.add(agentsDir); } catch {} } @@ -127,7 +133,7 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { const toolName = input.tool.toLowerCase(); if (toolName === "read") { - processFilePathForInjection(output.title, input.sessionID, output); + await processFilePathForInjection(output.title, input.sessionID, output); return; } @@ -135,7 +141,7 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { const filePaths = pendingBatchReads.get(input.callID); if (filePaths) { for (const filePath of filePaths) { - processFilePathForInjection(filePath, input.sessionID, output); + await processFilePathForInjection(filePath, input.sessionID, output); } pendingBatchReads.delete(input.callID); } diff --git a/src/hooks/directory-readme-injector/index.ts b/src/hooks/directory-readme-injector/index.ts index 7f78b5e..a473644 100644 --- a/src/hooks/directory-readme-injector/index.ts +++ b/src/hooks/directory-readme-injector/index.ts @@ -7,6 +7,7 @@ import { clearInjectedPaths, } from "./storage"; import { README_FILENAME } from "./constants"; +import { createDynamicTruncator } from "../../shared/dynamic-truncator"; interface ToolExecuteInput { tool: string; @@ -39,6 +40,7 @@ interface EventInput { export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { const sessionCaches = new Map>(); const pendingBatchReads = new Map(); + const truncator = createDynamicTruncator(ctx); function getSessionCache(sessionID: string): Set { if (!sessionCaches.has(sessionID)) { @@ -73,11 +75,11 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { return found.reverse(); } - function processFilePathForInjection( + async function processFilePathForInjection( filePath: string, sessionID: string, output: ToolExecuteOutput, - ): void { + ): Promise { const resolved = resolveFilePath(filePath); if (!resolved) return; @@ -91,7 +93,11 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { try { const content = readFileSync(readmePath, "utf-8"); - output.output += `\n\n[Project README: ${readmePath}]\n${content}`; + const { result, truncated } = await truncator.truncate(sessionID, content); + const truncationNotice = truncated + ? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${readmePath}]` + : ""; + output.output += `\n\n[Project README: ${readmePath}]\n${result}${truncationNotice}`; cache.add(readmeDir); } catch {} } @@ -127,7 +133,7 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { const toolName = input.tool.toLowerCase(); if (toolName === "read") { - processFilePathForInjection(output.title, input.sessionID, output); + await processFilePathForInjection(output.title, input.sessionID, output); return; } @@ -135,7 +141,7 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { const filePaths = pendingBatchReads.get(input.callID); if (filePaths) { for (const filePath of filePaths) { - processFilePathForInjection(filePath, input.sessionID, output); + await processFilePathForInjection(filePath, input.sessionID, output); } pendingBatchReads.delete(input.callID); } diff --git a/src/hooks/rules-injector/index.ts b/src/hooks/rules-injector/index.ts index ced7c4d..4a7c5c0 100644 --- a/src/hooks/rules-injector/index.ts +++ b/src/hooks/rules-injector/index.ts @@ -15,6 +15,7 @@ import { loadInjectedRules, saveInjectedRules, } from "./storage"; +import { createDynamicTruncator } from "../../shared/dynamic-truncator"; interface ToolExecuteInput { tool: string; @@ -59,6 +60,7 @@ export function createRulesInjectorHook(ctx: PluginInput) { { contentHashes: Set; realPaths: Set } >(); const pendingBatchFiles = new Map(); + const truncator = createDynamicTruncator(ctx); function getSessionCache(sessionID: string): { contentHashes: Set; @@ -76,11 +78,11 @@ export function createRulesInjectorHook(ctx: PluginInput) { return resolve(ctx.directory, path); } - function processFilePathForInjection( + async function processFilePathForInjection( filePath: string, sessionID: string, output: ToolExecuteOutput - ): void { + ): Promise { const resolved = resolveFilePath(filePath); if (!resolved) return; @@ -125,7 +127,11 @@ export function createRulesInjectorHook(ctx: PluginInput) { toInject.sort((a, b) => a.distance - b.distance); for (const rule of toInject) { - output.output += `\n\n[Rule: ${rule.relativePath}]\n[Match: ${rule.matchReason}]\n${rule.content}`; + const { result, truncated } = await truncator.truncate(sessionID, rule.content); + const truncationNotice = truncated + ? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${rule.relativePath}]` + : ""; + output.output += `\n\n[Rule: ${rule.relativePath}]\n[Match: ${rule.matchReason}]\n${result}${truncationNotice}`; } saveInjectedRules(sessionID, cache); @@ -167,7 +173,7 @@ export function createRulesInjectorHook(ctx: PluginInput) { const toolName = input.tool.toLowerCase(); if (TRACKED_TOOLS.includes(toolName)) { - processFilePathForInjection(output.title, input.sessionID, output); + await processFilePathForInjection(output.title, input.sessionID, output); return; } @@ -175,7 +181,7 @@ export function createRulesInjectorHook(ctx: PluginInput) { const filePaths = pendingBatchFiles.get(input.callID); if (filePaths) { for (const filePath of filePaths) { - processFilePathForInjection(filePath, input.sessionID, output); + await processFilePathForInjection(filePath, input.sessionID, output); } pendingBatchFiles.delete(input.callID); }