From d74d6e6e6bf52187520506911a1ae4a6f9e6af3e Mon Sep 17 00:00:00 2001 From: mohamedsedkiyouzbechi Date: Fri, 13 Mar 2026 14:34:18 +0100 Subject: [PATCH] fix: add instruction size safeguard to prevent Copilot context overflow & bump version to 1.5.6 --- CHANGELOG.md | 7 ++++ package.json | 2 +- src/constant.ts | 6 ++++ src/syncManager.ts | 65 ++++++++++++++++++++++++++++++++++++-- src/utils/fileSystem.ts | 4 +++ src/utils/notifications.ts | 23 ++++++++++++++ 6 files changed, 104 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 290c39a..4b02382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the "promptitude" extension will be documented in this file. +## [1.5.6] - 2026-03-23 + +### Added + +- Instructions size safeguard: warns when active instructions (`.instructions.md`) may overwhelm GitHub Copilot's context window. Checks total instructions file size (>500 KB), active instructions count (>50), and repository count (>10) after each sync. Only instructions are counted since they are auto-loaded into context, unlike `.prompt.md` files which are invoked on-demand. +- Disabled (inactive) prompts are now cleaned up from the active prompts directory during sync, preventing them from being loaded by Copilot. + ## [1.5.5] - 2026-03-18 ### Improved diff --git a/package.json b/package.json index dc9a4d9..0a6246c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "promptitude-extension", "displayName": "Promptitude", "description": "Sync GitHub Copilot prompts, chatmodes and instructions from git repositories", - "version": "1.5.5", + "version": "1.5.6", "publisher": "logientnventive", "icon": "resources/promptitude-icon.png", "repository": { diff --git a/src/constant.ts b/src/constant.ts index b5f9830..2173074 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -4,3 +4,9 @@ export const REPO_SYNC_CHAT_MODE_LEGACY_PATH = `chatmodes/`; export const REPO_SYNC_CHAT_MODE_LEGACY_SINGULAR_PATH = `chatmode/`; export const REPO_SYNC_INSTRUCTIONS_PATH = `instructions/`; export const REPO_SYNC_PROMPT_PATH = `prompts/`; + +// Instructions size safeguard thresholds +/** Maximum total size (in bytes) of all active instructions before warning the user */ +export const INSTRUCTION_SIZE_WARNING_THRESHOLD_BYTES = 500 * 1024; // 500 KB +/** Maximum number of active instructions before warning the user */ +export const INSTRUCTION_COUNT_WARNING_THRESHOLD = 50; diff --git a/src/syncManager.ts b/src/syncManager.ts index 345e6d2..f354571 100644 --- a/src/syncManager.ts +++ b/src/syncManager.ts @@ -10,7 +10,7 @@ import { GitProviderFactory } from './utils/gitProviderFactory'; import { FileSystemManager } from './utils/fileSystem'; import { AzureDevOpsApiManager } from './utils/azureDevOps'; import { PromptTreeDataProvider } from './ui/promptTreeProvider'; -import { REPO_SYNC_CHAT_MODE_PATH, REPO_SYNC_CHAT_MODE_LEGACY_PATH, REPO_SYNC_CHAT_MODE_LEGACY_SINGULAR_PATH, REPO_SYNC_INSTRUCTIONS_PATH, REPO_SYNC_PROMPT_PATH, } from './constant'; +import { REPO_SYNC_CHAT_MODE_PATH, REPO_SYNC_CHAT_MODE_LEGACY_PATH, REPO_SYNC_CHAT_MODE_LEGACY_SINGULAR_PATH, REPO_SYNC_INSTRUCTIONS_PATH, REPO_SYNC_PROMPT_PATH, INSTRUCTION_SIZE_WARNING_THRESHOLD_BYTES, INSTRUCTION_COUNT_WARNING_THRESHOLD, } from './constant'; export interface SyncResult { success: boolean; itemsUpdated: number; @@ -102,12 +102,18 @@ export class SyncManager { // Recreate symlinks for active prompts (in case they were manually deleted) await this.recreateActivePromptSymlinks(); + // Remove disabled prompts from the active prompts directory + await this.cleanupInactivePrompts(); + // Clean up orphaned regular files in prompts directory const cleanup = await this.cleanupOrphanedPrompts(); if (cleanup.removed > 0) { this.logger.info(`Cleaned up ${cleanup.removed} orphaned prompt files`); } + // Validate total instruction size to prevent Copilot context overflow + await this.validateInstructionSize(); + // Update status based on overall result if (result.overallSuccess) { this.statusBar.setStatus(SyncStatus.Success); @@ -289,7 +295,8 @@ export class SyncManager { } const filePath = this.fileSystem.joinPath(promptsDir, fileName); - const matchingPrompt = allPrompts.find(prompt => prompt.name === fileName); + // Match on both prompt.name and prompt.workspaceName to handle disambiguated files (e.g., prompt@org-repo.md) + const matchingPrompt = allPrompts.find(prompt => prompt.name === fileName || prompt.workspaceName === fileName); // Remove file if prompt exists but is not active if (matchingPrompt && !matchingPrompt.active) { @@ -1290,6 +1297,60 @@ export class SyncManager { } } + /** + * Validate total instructions size and count, warning the user if thresholds are exceeded. + * Instructions (.instructions.md) are automatically loaded into Copilot's context window, + * unlike .prompt.md files which are invoked on-demand. + */ + private async validateInstructionSize(): Promise { + try { + const promptsDir = this.config.getPromptsDirectory(); + + // Ensure the prompts directory exists + await this.fileSystem.ensureDirectoryExists(promptsDir); + + const entries = await this.fileSystem.readDirectory(promptsDir); + // Only count instructions files — they are auto-loaded into Copilot's context + const instructionFiles = entries.filter(f => f.endsWith('.instructions.md')); + const activeCount = instructionFiles.length; + + // Calculate total size of active instruction files + let totalSize = 0; + + for (const file of instructionFiles) { + try { + const filePath = this.fileSystem.joinPath(promptsDir, file); + totalSize += await this.fileSystem.getFileSize(filePath); + } catch { + this.logger.debug(`Skipped unreadable instruction file: ${file}`); + } + } + + const totalSizeKB = totalSize / 1024; + const reasons: string[] = []; + + if (totalSize > INSTRUCTION_SIZE_WARNING_THRESHOLD_BYTES) { + reasons.push(`total size (${totalSizeKB.toFixed(0)} KB) exceeds ${(INSTRUCTION_SIZE_WARNING_THRESHOLD_BYTES / 1024).toFixed(0)} KB`); + } + if (activeCount > INSTRUCTION_COUNT_WARNING_THRESHOLD) { + reasons.push(`${activeCount} active instructions exceeds limit of ${INSTRUCTION_COUNT_WARNING_THRESHOLD}`); + } + + if (reasons.length > 0) { + this.logger.warn(`Instructions size safeguard triggered: ${reasons.join('; ')}`); + await this.notifications.showPromptSizeWarning({ + totalSizeKB, + activeCount, + reasons + }); + } else { + this.logger.debug(`Instructions size check passed: ${activeCount} instructions, ${totalSizeKB.toFixed(1)} KB`); + } + } catch (error) { + this.logger.error('Failed to validate prompt size', error instanceof Error ? error : undefined); + } + } + dispose(): void { this.logger.info('Disposing SyncManager...'); diff --git a/src/utils/fileSystem.ts b/src/utils/fileSystem.ts index 47cbb18..0bf4884 100644 --- a/src/utils/fileSystem.ts +++ b/src/utils/fileSystem.ts @@ -58,6 +58,10 @@ export class FileSystemManager { } } + async getFileSize(filePath: string): Promise { + const stats = await stat(filePath); + return stats.size; + } joinPath(...paths: string[]): string { return path.join(...paths); diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts index c02f8e7..1bb4aca 100644 --- a/src/utils/notifications.ts +++ b/src/utils/notifications.ts @@ -182,4 +182,27 @@ export class NotificationManager { await this.showError(`Failed to setup Azure DevOps authentication: ${errorMessage}`); } } + + /** + * Show a warning when the total instructions size or count exceeds safe thresholds. + * Instructions are auto-loaded into Copilot's context, unlike prompts which are on-demand. + */ + async showPromptSizeWarning(details: { totalSizeKB: number; activeCount: number; reasons: string[] }): Promise { + const reasonText = details.reasons.join('; '); + const message = `⚠️ Promptitude: Your active instructions may be too large for GitHub Copilot's context window (${details.totalSizeKB.toFixed(0)} KB across ${details.activeCount} instructions). ${reasonText}. Consider disabling unused instructions.`; + + // Always show this warning regardless of notification settings — it's a safeguard + const result = await vscode.window.showWarningMessage( + message, + 'Manage Prompts', + 'Open Settings', + 'Dismiss' + ); + + if (result === 'Manage Prompts') { + vscode.commands.executeCommand('promptitude.cards.focus'); + } else if (result === 'Open Settings') { + vscode.commands.executeCommand('workbench.action.openSettings', 'promptitude'); + } + } }