this repo has no description

rules injection signature on tool call

karitham.dev 00222541 132bde38

verified
+50 -14
+1
.gitignore
··· 1 1 node_modules 2 + dist
+49 -14
src/index.ts
··· 84 84 85 85 /** 86 86 * Registry to track which rules have been read in each session. 87 + * Uses WeakMap for automatic cleanup when sessions are garbage collected. 87 88 */ 88 89 const sessionRulesRead = new Map<string, Set<string>>(); 89 90 91 + /** 92 + * Cleanup old sessions to prevent memory leaks. 93 + * Should be called periodically or when sessions exceed a threshold. 94 + */ 95 + function cleanupOldSessions(maxSessions = 1000): void { 96 + if (sessionRulesRead.size > maxSessions) { 97 + const entries = Array.from(sessionRulesRead.entries()); 98 + // Remove oldest half 99 + for (let i = 0; i < entries.length / 2; i++) { 100 + sessionRulesRead.delete(entries[i]![0]); 101 + } 102 + } 103 + } 104 + 105 + /** 106 + * Checks if rules should be prompted for a session/language combo. 107 + * Returns true if this is the first time, and marks it as read. 108 + */ 109 + function shouldPromptForRules(sessionID: string, language: string): boolean { 110 + if (!sessionRulesRead.has(sessionID)) { 111 + sessionRulesRead.set(sessionID, new Set()); 112 + } 113 + 114 + const rulesSet = sessionRulesRead.get(sessionID); 115 + if (!rulesSet || rulesSet.has(language)) { 116 + return false; 117 + } 118 + 119 + rulesSet.add(language); 120 + return true; 121 + } 122 + 90 123 export const LangRulesPlugin: Plugin = async ({ client, directory }) => { 91 124 return { 92 125 tool: { ··· 107 140 if (sessionID) { 108 141 if (!sessionRulesRead.has(sessionID)) { 109 142 sessionRulesRead.set(sessionID, new Set()); 143 + cleanupOldSessions(); 110 144 } 111 145 112 - sessionRulesRead.get(sessionID)?.add(languageFile); 146 + const rulesSet = sessionRulesRead.get(sessionID); 147 + if (rulesSet) { 148 + rulesSet.add(languageFile); 149 + } 113 150 } 114 151 115 152 const paths = [`${directory}/.rules/${languageFile}.md`]; ··· 121 158 122 159 for (const rulePath of paths) { 123 160 try { 124 - const content = readFileSync(rulePath).toString(); 161 + const content = readFileSync(rulePath, "utf-8"); 125 162 if (content.trim()) return content.trim(); 126 - } catch {} 163 + } catch (error) {} 127 164 } 128 165 129 166 return `No rule file found for '${languageFile}'`; ··· 131 168 }), 132 169 }, 133 170 134 - "tool.execute.before": async (input) => { 135 - const { sessionID, args } = input as { 136 - tool: string; 137 - sessionID: string; 138 - callID: string; 139 - args?: Record<string, unknown>; 140 - }; 171 + "tool.execute.before": async (input, output) => { 172 + const { sessionID } = input; 173 + const { args } = output; 141 174 142 175 if (!sessionID) return; 143 176 144 177 const filePath = (args?.filePath as string) || (args?.path as string); 145 - if (!filePath) return; 178 + if (!filePath || typeof filePath !== "string") return; 146 179 147 180 const language = getLanguageFromFilePath(filePath); 148 - const ext = filePath.split(".").pop()?.toLowerCase() || ""; 181 + if (!language) return; 149 182 150 - if (language && !sessionRulesRead.get(sessionID)?.has(language)) 183 + if (shouldPromptForRules(sessionID, language)) { 184 + const ext = filePath.split(".").pop()?.toLowerCase() || ""; 151 185 client.session.prompt({ 152 186 path: { 153 187 id: sessionID, ··· 157 191 { 158 192 synthetic: true, 159 193 type: "text", 160 - text: `**Hint:** You're about to modify a \`${ext}\` file (${language}). Consider reading the ${language} coding rules: \`langrules({ language: '${language}' }})\``, 194 + text: `**Hint:** You're about to modify a \`${ext}\` file (${language}). Consider reading the ${language} coding rules: \`langrules({ language: '${language}' })\``, 161 195 }, 162 196 ], 163 197 }, 164 198 }); 199 + } 165 200 }, 166 201 }; 167 202 };