This is page 8 of 18. Use http://codebase.md/minipuft/claude-prompts-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .actrc ├── .gitattributes ├── .github │ └── workflows │ ├── ci.yml │ ├── mcp-compliance.yml │ └── pr-validation.yml ├── .gitignore ├── agent.md ├── assets │ └── logo.png ├── CLAUDE.md ├── config │ └── framework-state.json ├── docs │ ├── architecture.md │ ├── chain-modification-examples.md │ ├── contributing.md │ ├── enhanced-gate-system.md │ ├── execution-architecture-guide.md │ ├── installation-guide.md │ ├── mcp-tool-usage-guide.md │ ├── mcp-tools-reference.md │ ├── prompt-format-guide.md │ ├── prompt-management.md │ ├── prompt-vs-template-guide.md │ ├── README.md │ ├── template-development-guide.md │ ├── TODO.md │ ├── troubleshooting.md │ └── version-history.md ├── LICENSE ├── local-test.sh ├── plans │ ├── nunjucks-dynamic-chain-orchestration.md │ ├── outputschema-realtime-progress-and-validation.md │ ├── parallel-conditional-execution-analysis.md │ ├── sqlite-storage-migration.md │ └── symbolic-command-language-implementation.md ├── README.md ├── scripts │ ├── setup-windows-testing.sh │ ├── test_server.js │ ├── test-all-platforms.sh │ └── windows-tests │ ├── test-windows-paths.js │ ├── test-windows-startup.sh │ └── windows-env.sh └── server ├── config │ ├── framework-state.json │ └── tool-descriptions.json ├── config.json ├── jest.config.cjs ├── LICENSE ├── package-lock.json ├── package.json ├── prompts │ ├── analysis │ │ ├── advanced_analysis_engine.md │ │ ├── content_analysis.md │ │ ├── deep_analysis.md │ │ ├── deep_research.md │ │ ├── markdown_notebook.md │ │ ├── note_integration.md │ │ ├── note_refinement.md │ │ ├── notes.md │ │ ├── progressive_research.md │ │ ├── prompts.json │ │ ├── query_refinement.md │ │ └── review.md │ ├── architecture │ │ ├── prompts.json │ │ └── strategic-system-alignment.md │ ├── content_processing │ │ ├── format_enhancement.md │ │ ├── noteIntegration.md │ │ ├── obsidian_metadata_optimizer.md │ │ ├── prompts.json │ │ ├── vault_related_notes_finder.md │ │ └── video_notes_enhanced.md │ ├── debugging │ │ ├── analyze_logs.md │ │ └── prompts.json │ ├── development │ │ ├── analyze_code_structure.md │ │ ├── analyze_file_structure.md │ │ ├── code_review_optimization_chain.md │ │ ├── component_flow_analysis.md │ │ ├── create_modularization_plan.md │ │ ├── detect_code_issues.md │ │ ├── detect_project_commands.md │ │ ├── expert_code_implementation.md │ │ ├── generate_comprehensive_claude_md.md │ │ ├── prompts.json │ │ ├── strategicImplement.md │ │ ├── suggest_code_improvements.md │ │ └── transform_code_to_modules.md │ ├── documentation │ │ ├── create_docs_chain.md │ │ ├── docs-content-creation.md │ │ ├── docs-content-planning.md │ │ ├── docs-final-assembly.md │ │ ├── docs-project-analysis.md │ │ ├── docs-review-refinement.md │ │ └── prompts.json │ ├── education │ │ ├── prompts.json │ │ └── vault_integrated_notes.md │ ├── general │ │ ├── diagnose.md │ │ └── prompts.json │ ├── promptsConfig.json │ └── testing │ ├── final_verification_test.md │ └── prompts.json ├── README.md ├── scripts │ └── validate-dependencies.js ├── src │ ├── api │ │ └── index.ts │ ├── chain-session │ │ └── manager.ts │ ├── config │ │ └── index.ts │ ├── Dockerfile │ ├── execution │ │ ├── context │ │ │ ├── context-resolver.ts │ │ │ ├── framework-injector.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── parsers │ │ │ ├── argument-parser.ts │ │ │ ├── index.ts │ │ │ └── unified-command-parser.ts │ │ └── types.ts │ ├── frameworks │ │ ├── framework-manager.ts │ │ ├── framework-state-manager.ts │ │ ├── index.ts │ │ ├── integration │ │ │ ├── framework-semantic-integration.ts │ │ │ └── index.ts │ │ ├── methodology │ │ │ ├── guides │ │ │ │ ├── 5w1h-guide.ts │ │ │ │ ├── cageerf-guide.ts │ │ │ │ ├── react-guide.ts │ │ │ │ └── scamper-guide.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ └── registry.ts │ │ ├── prompt-guidance │ │ │ ├── index.ts │ │ │ ├── methodology-tracker.ts │ │ │ ├── service.ts │ │ │ ├── system-prompt-injector.ts │ │ │ └── template-enhancer.ts │ │ └── types │ │ ├── index.ts │ │ ├── integration-types.ts │ │ ├── methodology-types.ts │ │ └── prompt-guidance-types.ts │ ├── gates │ │ ├── constants.ts │ │ ├── core │ │ │ ├── gate-definitions.ts │ │ │ ├── gate-loader.ts │ │ │ ├── gate-validator.ts │ │ │ ├── index.ts │ │ │ └── temporary-gate-registry.ts │ │ ├── definitions │ │ │ ├── code-quality.json │ │ │ ├── content-structure.json │ │ │ ├── educational-clarity.json │ │ │ ├── framework-compliance.json │ │ │ ├── research-quality.json │ │ │ ├── security-awareness.json │ │ │ └── technical-accuracy.json │ │ ├── gate-state-manager.ts │ │ ├── guidance │ │ │ ├── FrameworkGuidanceFilter.ts │ │ │ └── GateGuidanceRenderer.ts │ │ ├── index.ts │ │ ├── intelligence │ │ │ ├── GatePerformanceAnalyzer.ts │ │ │ └── GateSelectionEngine.ts │ │ ├── templates │ │ │ ├── code_quality_validation.md │ │ │ ├── educational_clarity_validation.md │ │ │ ├── framework_compliance_validation.md │ │ │ ├── research_self_validation.md │ │ │ ├── security_validation.md │ │ │ ├── structure_validation.md │ │ │ └── technical_accuracy_validation.md │ │ └── types.ts │ ├── index.ts │ ├── logging │ │ └── index.ts │ ├── mcp-tools │ │ ├── config-utils.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── prompt-engine │ │ │ ├── core │ │ │ │ ├── engine.ts │ │ │ │ ├── executor.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── processors │ │ │ │ ├── response-formatter.ts │ │ │ │ └── template-processor.ts │ │ │ └── utils │ │ │ ├── category-extractor.ts │ │ │ ├── classification.ts │ │ │ ├── context-builder.ts │ │ │ └── validation.ts │ │ ├── prompt-manager │ │ │ ├── analysis │ │ │ │ ├── comparison-engine.ts │ │ │ │ ├── gate-analyzer.ts │ │ │ │ └── prompt-analyzer.ts │ │ │ ├── core │ │ │ │ ├── index.ts │ │ │ │ ├── manager.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── operations │ │ │ │ └── file-operations.ts │ │ │ ├── search │ │ │ │ ├── filter-parser.ts │ │ │ │ └── prompt-matcher.ts │ │ │ └── utils │ │ │ ├── category-manager.ts │ │ │ └── validation.ts │ │ ├── shared │ │ │ └── structured-response-builder.ts │ │ ├── system-control.ts │ │ ├── tool-description-manager.ts │ │ └── types │ │ └── shared-types.ts │ ├── metrics │ │ ├── analytics-service.ts │ │ ├── index.ts │ │ └── types.ts │ ├── performance │ │ ├── index.ts │ │ └── monitor.ts │ ├── prompts │ │ ├── category-manager.ts │ │ ├── converter.ts │ │ ├── file-observer.ts │ │ ├── hot-reload-manager.ts │ │ ├── index.ts │ │ ├── loader.ts │ │ ├── promptUtils.ts │ │ ├── registry.ts │ │ └── types.ts │ ├── runtime │ │ ├── application.ts │ │ └── startup.ts │ ├── semantic │ │ ├── configurable-semantic-analyzer.ts │ │ └── integrations │ │ ├── index.ts │ │ └── llm-clients.ts │ ├── server │ │ ├── index.ts │ │ └── transport │ │ └── index.ts │ ├── smithery.yaml │ ├── text-references │ │ ├── conversation.ts │ │ └── index.ts │ ├── types │ │ └── index.ts │ ├── types.ts │ └── utils │ ├── chainUtils.ts │ ├── errorHandling.ts │ ├── global-resource-tracker.ts │ ├── index.ts │ └── jsonUtils.ts ├── tests │ ├── ci-startup-validation.js │ ├── enhanced-validation │ │ ├── contract-validation │ │ │ ├── contract-test-suite.js │ │ │ ├── interface-contracts.js │ │ │ └── interface-contracts.ts │ │ ├── environment-validation │ │ │ ├── environment-parity-checker.js │ │ │ └── environment-test-suite.js │ │ ├── lifecycle-validation │ │ │ ├── lifecycle-test-suite.js │ │ │ └── process-lifecycle-validator.js │ │ └── validation-orchestrator.js │ ├── helpers │ │ └── test-helpers.js │ ├── integration │ │ ├── mcp-tools.test.ts │ │ ├── server-startup.test.ts │ │ └── unified-parsing-integration.test.ts │ ├── performance │ │ ├── parsing-system-benchmark.test.ts │ │ └── server-performance.test.ts │ ├── scripts │ │ ├── consolidated-tools.js │ │ ├── establish-performance-baselines.js │ │ ├── functional-mcp-validation.js │ │ ├── integration-mcp-tools.js │ │ ├── integration-routing-system.js │ │ ├── integration-server-startup.js │ │ ├── integration-unified-parsing.js │ │ ├── methodology-guides.js │ │ ├── performance-memory.js │ │ ├── runtime-integration.js │ │ ├── unit-conversation-manager.js │ │ ├── unit-semantic-analyzer.js │ │ └── unit-unified-parsing.js │ ├── setup.ts │ ├── test-enhanced-parsing.js │ └── unit │ ├── conversation-manager.test.ts │ ├── semantic-analyzer-three-tier.test.ts │ └── unified-parsing-system.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /server/src/prompts/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Prompt Management System 3 | * Main module that orchestrates prompt loading, conversion, and registration 4 | */ 5 | 6 | // Import the individual modules 7 | export * from "./converter.js"; 8 | export * from "./loader.js"; 9 | export * from "./registry.js"; 10 | // Template processor moved to /execution/processor/template-processor.js for better organization 11 | export * from "./category-manager.js"; 12 | export * from "./file-observer.js"; 13 | export * from "./hot-reload-manager.js"; 14 | // Framework-aware components removed in Phase 3 simplification 15 | 16 | import { ConfigManager } from "../config/index.js"; 17 | import { Logger } from "../logging/index.js"; 18 | import { TextReferenceManager } from "../text-references/index.js"; 19 | import path from "path"; 20 | import { 21 | Category, 22 | CategoryPromptsResult, 23 | ConvertedPrompt, 24 | PromptData, 25 | } from "../types/index.js"; 26 | 27 | // Import the actual modules 28 | import { PromptConverter } from "./converter.js"; 29 | import { PromptLoader } from "./loader.js"; 30 | import { PromptRegistry } from "./registry.js"; 31 | // TemplateProcessor functionality consolidated into UnifiedPromptProcessor 32 | import { HotReloadManager, createHotReloadManager } from "./hot-reload-manager.js"; 33 | // Phase 1: Framework capabilities integrated into enhanced HotReloadManager 34 | 35 | /** 36 | * Main Prompt Manager class that coordinates all prompt operations 37 | */ 38 | export class PromptManager { 39 | private logger: Logger; 40 | private textReferenceManager: TextReferenceManager; 41 | private configManager: ConfigManager; 42 | private mcpServer: any; 43 | 44 | // Individual module instances 45 | private converter: PromptConverter; 46 | private loader: PromptLoader; 47 | private registry?: PromptRegistry; 48 | // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor 49 | private hotReloadManager?: HotReloadManager; 50 | // Framework-aware components removed in Phase 3 simplification 51 | 52 | constructor( 53 | logger: Logger, 54 | textReferenceManager: TextReferenceManager, 55 | configManager: ConfigManager, 56 | mcpServer?: any, 57 | // Framework parameters removed in Phase 3 simplification 58 | ) { 59 | this.logger = logger; 60 | this.textReferenceManager = textReferenceManager; 61 | this.configManager = configManager; 62 | this.mcpServer = mcpServer; 63 | // Framework initialization removed in Phase 3 simplification 64 | 65 | // Initialize individual modules 66 | this.loader = new PromptLoader(logger); 67 | // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor 68 | this.converter = new PromptConverter(logger, this.loader); 69 | 70 | if (mcpServer) { 71 | this.registry = new PromptRegistry( 72 | logger, 73 | mcpServer, 74 | configManager 75 | ); 76 | 77 | // Initialize HotReloadManager with CategoryManager integration 78 | const categoryManager = this.loader.getCategoryManager(); 79 | 80 | // Phase 1: Enhanced HotReloadManager with framework capabilities 81 | this.hotReloadManager = createHotReloadManager( 82 | logger, 83 | categoryManager, 84 | { 85 | enabled: true, 86 | autoReload: false, // Will be controlled by external triggers 87 | debounceMs: 500, 88 | batchChanges: true, 89 | batchTimeoutMs: 2000, 90 | frameworkCapabilities: { 91 | enabled: true, 92 | frameworkAnalysis: true, 93 | performanceMonitoring: true, 94 | preWarmAnalysis: true, 95 | invalidateFrameworkCaches: true 96 | } 97 | }, 98 | this.configManager 99 | ); 100 | 101 | this.logger.info('🔄 Hot reload manager initialized'); 102 | } 103 | } 104 | 105 | /** 106 | * Load prompts from category-specific configuration files 107 | */ 108 | async loadCategoryPrompts( 109 | configPath: string 110 | ): Promise<CategoryPromptsResult> { 111 | return this.loader.loadCategoryPrompts(configPath); 112 | } 113 | 114 | /** 115 | * Convert markdown prompts to JSON structure 116 | */ 117 | async convertMarkdownPromptsToJson( 118 | promptsData: PromptData[], 119 | basePath?: string 120 | ): Promise<ConvertedPrompt[]> { 121 | return this.converter.convertMarkdownPromptsToJson(promptsData, basePath); 122 | } 123 | 124 | // Removed: processTemplateAsync - deprecated method no longer needed 125 | // Template processing now handled directly using processTemplate from utils/jsonUtils.js 126 | 127 | /** 128 | * Register prompts with MCP server 129 | */ 130 | async registerAllPrompts(prompts: ConvertedPrompt[]): Promise<number> { 131 | if (!this.registry) { 132 | throw new Error("MCP server not provided - cannot register prompts"); 133 | } 134 | return this.registry.registerAllPrompts(prompts); 135 | } 136 | 137 | /** 138 | * Notify clients that prompt list has changed (for hot-reload) 139 | */ 140 | async notifyPromptsListChanged(): Promise<void> { 141 | if (!this.registry) { 142 | throw new Error("MCP server not provided - cannot send notifications"); 143 | } 144 | await this.registry.notifyPromptsListChanged(); 145 | } 146 | 147 | /** 148 | * Load and convert prompts in one operation 149 | */ 150 | async loadAndConvertPrompts( 151 | configPath: string, 152 | basePath?: string 153 | ): Promise<{ 154 | promptsData: PromptData[]; 155 | categories: Category[]; 156 | convertedPrompts: ConvertedPrompt[]; 157 | }> { 158 | try { 159 | this.logger.info(`📁 PromptManager: Loading prompts from: ${configPath}`); 160 | 161 | // Verify config path exists 162 | const fs = await import("fs/promises"); 163 | try { 164 | const stats = await fs.stat(configPath); 165 | this.logger.info( 166 | `✓ Config file found, size: ${ 167 | stats.size 168 | } bytes, modified: ${stats.mtime.toISOString()}` 169 | ); 170 | } catch (error) { 171 | this.logger.error(`✗ Config file access error:`, error); 172 | throw error; 173 | } 174 | 175 | this.logger.info("🔄 Calling PromptLoader.loadCategoryPrompts()..."); 176 | 177 | // Load the raw prompt data 178 | const { promptsData, categories } = await this.loadCategoryPrompts( 179 | configPath 180 | ); 181 | 182 | this.logger.info( 183 | "✅ PromptLoader.loadCategoryPrompts() completed successfully" 184 | ); 185 | this.logger.info( 186 | `📊 Raw data loaded: ${promptsData.length} prompts from ${categories.length} categories` 187 | ); 188 | 189 | // Log detailed breakdown by category 190 | if (categories.length > 0) { 191 | this.logger.info("📋 Category breakdown:"); 192 | categories.forEach((category) => { 193 | const categoryPrompts = promptsData.filter( 194 | (p) => p.category === category.id 195 | ); 196 | this.logger.info( 197 | ` ${category.name} (${category.id}): ${categoryPrompts.length} prompts` 198 | ); 199 | }); 200 | } else { 201 | this.logger.warn("⚠️ No categories found in loaded data!"); 202 | } 203 | 204 | if (promptsData.length === 0) { 205 | this.logger.warn("⚠️ No prompts found in loaded data!"); 206 | } 207 | 208 | this.logger.info("🔄 Converting prompts to JSON structure..."); 209 | 210 | // Convert to JSON structure 211 | const convertedPrompts = await this.convertMarkdownPromptsToJson( 212 | promptsData, 213 | basePath 214 | ); 215 | 216 | this.logger.info( 217 | `✅ Conversion completed: ${convertedPrompts.length} prompts converted` 218 | ); 219 | 220 | if (convertedPrompts.length !== promptsData.length) { 221 | this.logger.warn( 222 | `⚠️ Conversion count mismatch! Input: ${promptsData.length}, Output: ${convertedPrompts.length}` 223 | ); 224 | } 225 | 226 | this.logger.info( 227 | "🎉 PromptManager.loadAndConvertPrompts() completed successfully" 228 | ); 229 | return { promptsData, categories, convertedPrompts }; 230 | } catch (error) { 231 | this.logger.error("❌ PromptManager.loadAndConvertPrompts() FAILED:"); 232 | this.logger.error("Error type:", error?.constructor?.name); 233 | this.logger.error( 234 | "Error message:", 235 | error instanceof Error ? error.message : String(error) 236 | ); 237 | this.logger.error( 238 | "Stack trace:", 239 | error instanceof Error ? error.stack : "No stack trace available" 240 | ); 241 | throw error; 242 | } 243 | } 244 | 245 | /** 246 | * Complete prompt system initialization 247 | */ 248 | async initializePromptSystem( 249 | configPath: string, 250 | basePath?: string 251 | ): Promise<{ 252 | promptsData: PromptData[]; 253 | categories: Category[]; 254 | convertedPrompts: ConvertedPrompt[]; 255 | registeredCount: number; 256 | }> { 257 | try { 258 | // Load and convert prompts 259 | const result = await this.loadAndConvertPrompts(configPath, basePath); 260 | 261 | // Register with MCP server if available 262 | let registeredCount = 0; 263 | if (this.registry) { 264 | registeredCount = await this.registerAllPrompts( 265 | result.convertedPrompts 266 | ); 267 | } else { 268 | this.logger.warn( 269 | "MCP server not available - skipping prompt registration" 270 | ); 271 | } 272 | 273 | return { ...result, registeredCount }; 274 | } catch (error) { 275 | this.logger.error("Error initializing prompt system:", error); 276 | throw error; 277 | } 278 | } 279 | 280 | /** 281 | * Reload prompts (useful for hot-reloading) 282 | */ 283 | async reloadPrompts( 284 | configPath: string, 285 | basePath?: string 286 | ): Promise<{ 287 | promptsData: PromptData[]; 288 | categories: Category[]; 289 | convertedPrompts: ConvertedPrompt[]; 290 | registeredCount: number; 291 | }> { 292 | this.logger.info("Reloading prompt system..."); 293 | 294 | // Note: MCP protocol doesn't support unregistering prompts 295 | // Hot-reload will be handled via list_changed notifications 296 | 297 | // Reinitialize the system 298 | return this.initializePromptSystem(configPath, basePath); 299 | } 300 | 301 | /** 302 | * Start automatic file watching for hot reload 303 | */ 304 | async startHotReload(promptsConfigPath: string, onReloadCallback?: () => Promise<void>): Promise<void> { 305 | if (!this.hotReloadManager) { 306 | this.logger.warn("HotReloadManager not available - hot reload not started"); 307 | return; 308 | } 309 | 310 | // Set up reload callback 311 | if (onReloadCallback) { 312 | this.hotReloadManager.setReloadCallback(async (event) => { 313 | this.logger.info(`Hot reload triggered: ${event.reason}`); 314 | try { 315 | await onReloadCallback(); 316 | } catch (error) { 317 | this.logger.error("Hot reload callback failed:", error); 318 | } 319 | }); 320 | } 321 | 322 | // Start monitoring 323 | await this.hotReloadManager.start(); 324 | 325 | // Watch prompts directory and config files 326 | const promptsDir = path.dirname(promptsConfigPath); 327 | const promptsCategoryDirs = await this.discoverPromptDirectories(promptsDir); 328 | 329 | await this.hotReloadManager.watchDirectories([ 330 | { path: promptsDir }, // Watch main prompts directory 331 | ...promptsCategoryDirs.map(dir => ({ path: dir.path, category: dir.category })) 332 | ]); 333 | 334 | this.logger.info(`🔄 Hot reload monitoring started for ${promptsCategoryDirs.length + 1} directories`); 335 | } 336 | 337 | /** 338 | * Stop automatic file watching 339 | */ 340 | async stopHotReload(): Promise<void> { 341 | if (this.hotReloadManager) { 342 | await this.hotReloadManager.stop(); 343 | this.logger.info("Hot reload monitoring stopped"); 344 | } 345 | } 346 | 347 | /** 348 | * Discover prompt directories for watching 349 | */ 350 | private async discoverPromptDirectories(promptsDir: string): Promise<Array<{ path: string; category?: string }>> { 351 | const directories: Array<{ path: string; category?: string }> = []; 352 | 353 | try { 354 | const fs = await import("fs/promises"); 355 | const entries = await fs.readdir(promptsDir, { withFileTypes: true }); 356 | 357 | for (const entry of entries) { 358 | if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) { 359 | const fullPath = path.join(promptsDir, entry.name); 360 | 361 | // Check if this directory contains prompts.json (indicating it's a category directory) 362 | try { 363 | const categoryPromptsPath = path.join(fullPath, 'prompts.json'); 364 | await fs.access(categoryPromptsPath); 365 | directories.push({ path: fullPath, category: entry.name }); 366 | } catch { 367 | // Directory doesn't have prompts.json, but still watch it 368 | directories.push({ path: fullPath }); 369 | } 370 | } 371 | } 372 | } catch (error) { 373 | this.logger.error("Failed to discover prompt directories:", error); 374 | } 375 | 376 | return directories; 377 | } 378 | 379 | // Phase 1: Framework capabilities integrated into enhanced HotReloadManager 380 | 381 | // Framework-specific reload functionality removed in Phase 3 simplification 382 | 383 | // Framework statistics functionality removed in Phase 3 simplification 384 | 385 | /** 386 | * Get all individual module instances for external access 387 | */ 388 | getModules() { 389 | return { 390 | converter: this.converter, 391 | loader: this.loader, 392 | registry: this.registry, 393 | // templateProcessor: removed - functionality consolidated into UnifiedPromptProcessor 394 | categoryManager: this.loader.getCategoryManager(), 395 | hotReloadManager: this.hotReloadManager, 396 | // Framework-aware modules removed in Phase 3 simplification 397 | }; 398 | } 399 | 400 | /** 401 | * Get TextReferenceManager for direct access 402 | * Added for UnifiedPromptProcessor consolidation 403 | */ 404 | getTextReferenceManager(): TextReferenceManager { 405 | return this.textReferenceManager; 406 | } 407 | 408 | /** 409 | * Get system statistics 410 | */ 411 | getStats(prompts?: ConvertedPrompt[]) { 412 | const stats: any = { 413 | textReferences: this.textReferenceManager.getStats(), 414 | }; 415 | 416 | if (prompts && this.registry) { 417 | stats.registration = this.registry.getRegistrationStats(prompts); 418 | stats.conversation = this.registry.getConversationStats(); 419 | } 420 | 421 | if (prompts && this.converter) { 422 | stats.conversion = this.converter.getConversionStats( 423 | prompts.length, 424 | prompts 425 | ); 426 | } 427 | 428 | return stats; 429 | } 430 | } 431 | ``` -------------------------------------------------------------------------------- /server/src/execution/parsers/unified-command-parser.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Unified Command Parser 3 | * 4 | * Robust multi-strategy command parsing system that replaces fragile regex-based parsing 5 | * with intelligent format detection, fallback strategies, and comprehensive validation. 6 | * 7 | * Features: 8 | * - Multi-format detection (simple >>prompt, JSON objects, structured commands) 9 | * - Fallback parsing strategies with confidence scoring 10 | * - Comprehensive error messages with suggestions 11 | * - Command validation and sanitization 12 | */ 13 | 14 | import { Logger } from "../../logging/index.js"; 15 | import { PromptData } from "../../types/index.js"; 16 | import { ValidationError, PromptError, safeJsonParse } from "../../utils/index.js"; 17 | 18 | /** 19 | * Command parsing result with metadata 20 | */ 21 | export interface CommandParseResult { 22 | promptId: string; 23 | rawArgs: string; 24 | format: 'simple' | 'json' | 'structured' | 'legacy'; 25 | confidence: number; 26 | metadata: { 27 | originalCommand: string; 28 | parseStrategy: string; 29 | detectedFormat: string; 30 | warnings: string[]; 31 | }; 32 | } 33 | 34 | /** 35 | * Parsing strategy interface 36 | */ 37 | interface ParsingStrategy { 38 | name: string; 39 | canHandle: (command: string) => boolean; 40 | parse: (command: string) => CommandParseResult | null; 41 | confidence: number; 42 | } 43 | 44 | /** 45 | * Unified Command Parser Class 46 | */ 47 | export class UnifiedCommandParser { 48 | private logger: Logger; 49 | private strategies: ParsingStrategy[]; 50 | 51 | // Parsing statistics for monitoring 52 | private stats = { 53 | totalParses: 0, 54 | successfulParses: 0, 55 | failedParses: 0, 56 | strategyUsage: new Map<string, number>(), 57 | averageConfidence: 0 58 | }; 59 | 60 | constructor(logger: Logger) { 61 | this.logger = logger; 62 | this.strategies = this.initializeStrategies(); 63 | this.logger.debug(`UnifiedCommandParser initialized with ${this.strategies.length} parsing strategies`); 64 | } 65 | 66 | /** 67 | * Parse command string using multi-strategy approach 68 | */ 69 | async parseCommand(command: string, availablePrompts: PromptData[]): Promise<CommandParseResult> { 70 | this.stats.totalParses++; 71 | 72 | if (!command || command.trim().length === 0) { 73 | this.stats.failedParses++; 74 | throw new ValidationError("Command cannot be empty"); 75 | } 76 | 77 | const normalizedCommand = command.trim(); 78 | this.logger.debug(`Parsing command: "${normalizedCommand.substring(0, 100)}..."`); 79 | 80 | // Try each strategy in order of confidence 81 | const sortedStrategies = [...this.strategies].sort((a, b) => b.confidence - a.confidence); 82 | 83 | for (const strategy of sortedStrategies) { 84 | if (strategy.canHandle(normalizedCommand)) { 85 | try { 86 | const result = strategy.parse(normalizedCommand); 87 | if (result) { 88 | // Validate that the prompt ID exists 89 | await this.validatePromptExists(result.promptId, availablePrompts); 90 | 91 | // Update statistics 92 | this.stats.successfulParses++; 93 | this.updateStrategyStats(strategy.name); 94 | this.updateConfidenceStats(result.confidence); 95 | 96 | this.logger.debug(`Command parsed successfully using strategy: ${strategy.name} (confidence: ${result.confidence})`); 97 | return result; 98 | } 99 | } catch (error) { 100 | this.logger.debug(`Strategy ${strategy.name} failed: ${error instanceof Error ? error.message : 'Unknown error'}`); 101 | continue; 102 | } 103 | } 104 | } 105 | 106 | // If no strategy succeeded, provide helpful error message 107 | this.stats.failedParses++; 108 | const errorMessage = this.generateHelpfulError(normalizedCommand, availablePrompts); 109 | throw new ValidationError(errorMessage); 110 | } 111 | 112 | /** 113 | * Initialize parsing strategies (STREAMLINED: 2 core strategies) 114 | */ 115 | private initializeStrategies(): ParsingStrategy[] { 116 | return [ 117 | this.createSimpleCommandStrategy(), 118 | this.createJsonCommandStrategy() 119 | ]; 120 | } 121 | 122 | /** 123 | * Simple command strategy: >>prompt_name arguments (ENHANCED: More AI-friendly) 124 | */ 125 | private createSimpleCommandStrategy(): ParsingStrategy { 126 | return { 127 | name: 'simple', 128 | confidence: 0.95, // Increased confidence for primary strategy 129 | canHandle: (command: string) => { 130 | // More flexible pattern - handles spaces in prompt names via underscore conversion 131 | return /^(>>|\/)[a-zA-Z0-9_\-\s]+(\s|$)/.test(command.trim()); 132 | }, 133 | parse: (command: string): CommandParseResult | null => { 134 | // Enhanced regex to handle more natural command formats 135 | const match = command.trim().match(/^(>>|\/)([a-zA-Z0-9_\-\s]+?)(?:\s+([\s\S]*))?$/); 136 | if (!match) return null; 137 | 138 | const [, prefix, rawPromptId, rawArgs] = match; 139 | 140 | // Clean up prompt ID: convert spaces and hyphens to underscores, normalize 141 | const promptId = rawPromptId.trim() 142 | .toLowerCase() 143 | .replace(/[\s-]+/g, '_') // Spaces and hyphens to underscores 144 | .replace(/[^a-z0-9_]/g, '') // Remove invalid characters (except underscores) 145 | .replace(/_+/g, '_') // Collapse multiple underscores 146 | .replace(/^_|_$/g, ''); // Trim leading/trailing underscores 147 | 148 | const warnings: string[] = []; 149 | if (rawPromptId !== promptId) { 150 | warnings.push(`Normalized prompt name: "${rawPromptId}" → "${promptId}"`); 151 | } 152 | 153 | return { 154 | promptId: promptId, 155 | rawArgs: (rawArgs || '').trim(), 156 | format: 'simple', 157 | confidence: 0.98, // High confidence for enhanced parsing 158 | metadata: { 159 | originalCommand: command, 160 | parseStrategy: 'simple_enhanced', 161 | detectedFormat: `${prefix}prompt format (normalized)`, 162 | warnings 163 | } 164 | }; 165 | } 166 | }; 167 | } 168 | 169 | /** 170 | * JSON command strategy: {"command": ">>prompt", "args": {...}} 171 | */ 172 | private createJsonCommandStrategy(): ParsingStrategy { 173 | return { 174 | name: 'json', 175 | confidence: 0.85, 176 | canHandle: (command: string) => { 177 | const trimmed = command.trim(); 178 | return trimmed.startsWith('{') && trimmed.endsWith('}'); 179 | }, 180 | parse: (command: string): CommandParseResult | null => { 181 | const parseResult = safeJsonParse(command); 182 | if (!parseResult.success || !parseResult.data) { 183 | return null; 184 | } 185 | 186 | const data = parseResult.data; 187 | 188 | // Handle different JSON formats 189 | let actualCommand = ''; 190 | let confidence = 0.8; 191 | 192 | if (data.command) { 193 | actualCommand = data.command; 194 | confidence = 0.9; 195 | } else if (data.prompt) { 196 | actualCommand = data.prompt; 197 | confidence = 0.85; 198 | } else { 199 | return null; 200 | } 201 | 202 | // Recursively parse the inner command 203 | const simpleStrategy = this.createSimpleCommandStrategy(); 204 | if (!simpleStrategy.canHandle(actualCommand)) { 205 | return null; 206 | } 207 | 208 | const innerResult = simpleStrategy.parse(actualCommand); 209 | if (!innerResult) return null; 210 | 211 | return { 212 | promptId: innerResult.promptId, 213 | rawArgs: data.args ? JSON.stringify(data.args) : innerResult.rawArgs, 214 | format: 'json', 215 | confidence, 216 | metadata: { 217 | originalCommand: command, 218 | parseStrategy: 'json', 219 | detectedFormat: 'JSON wrapper with inner command', 220 | warnings: [] 221 | } 222 | }; 223 | } 224 | }; 225 | } 226 | 227 | 228 | /** 229 | * Validate that the prompt ID exists in available prompts 230 | */ 231 | private async validatePromptExists(promptId: string, availablePrompts: PromptData[]): Promise<void> { 232 | // Check if this is a built-in command that should be routed (handled by prompt engine) 233 | if (this.isBuiltinCommand(promptId)) { 234 | return; // Built-in commands are valid and will be routed by the prompt engine 235 | } 236 | 237 | // Use case-insensitive matching to find the prompt 238 | const found = availablePrompts.find(p => 239 | p.id.toLowerCase() === promptId.toLowerCase() || 240 | p.name?.toLowerCase() === promptId.toLowerCase() 241 | ); 242 | if (!found) { 243 | const suggestions = this.generatePromptSuggestions(promptId, availablePrompts); 244 | const builtinHint = this.getBuiltinCommandHint(promptId); 245 | throw new PromptError( 246 | `Unknown prompt: "${promptId}". ${suggestions}${builtinHint}\n\nTry: >>listprompts, >>help, >>status` 247 | ); 248 | } 249 | } 250 | 251 | /** 252 | * Check if command is a built-in system command 253 | */ 254 | private isBuiltinCommand(promptId: string): boolean { 255 | const builtinCommands = [ 256 | 'listprompts', 'listprompt', 'list_prompts', 257 | 'help', 'commands', 258 | 'status', 'health', 259 | 'analytics', 'metrics' 260 | ]; 261 | return builtinCommands.includes(promptId.toLowerCase()); 262 | } 263 | 264 | /** 265 | * Generate hint for built-in commands that might have been mistyped 266 | */ 267 | private getBuiltinCommandHint(promptId: string): string { 268 | const lower = promptId.toLowerCase(); 269 | 270 | // Check for common variations/typos of built-in commands 271 | if (lower.includes('list') && lower.includes('prompt')) { 272 | return '\n\nDid you mean >>listprompts?'; 273 | } 274 | if (lower === 'commands' || lower === 'help') { 275 | return '\n\nTry >>help for available commands.'; 276 | } 277 | if (lower === 'stat' || lower === 'status') { 278 | return '\n\nTry >>status for system status.'; 279 | } 280 | 281 | return ''; 282 | } 283 | 284 | /** 285 | * Generate helpful prompt suggestions for typos 286 | */ 287 | private generatePromptSuggestions(promptId: string, availablePrompts: PromptData[]): string { 288 | // Simple Levenshtein distance for suggestions 289 | const suggestions = availablePrompts 290 | .map(prompt => ({ 291 | prompt, 292 | distance: this.levenshteinDistance(promptId.toLowerCase(), prompt.id.toLowerCase()) 293 | })) 294 | .filter(item => item.distance <= 3) 295 | .sort((a, b) => a.distance - b.distance) 296 | .slice(0, 3) 297 | .map(item => item.prompt.id); 298 | 299 | if (suggestions.length > 0) { 300 | return `Did you mean: ${suggestions.join(', ')}?`; 301 | } 302 | 303 | return ''; 304 | } 305 | 306 | /** 307 | * Simple Levenshtein distance calculation 308 | */ 309 | private levenshteinDistance(a: string, b: string): number { 310 | const matrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null)); 311 | 312 | for (let i = 0; i <= a.length; i++) matrix[0][i] = i; 313 | for (let j = 0; j <= b.length; j++) matrix[j][0] = j; 314 | 315 | for (let j = 1; j <= b.length; j++) { 316 | for (let i = 1; i <= a.length; i++) { 317 | const indicator = a[i - 1] === b[j - 1] ? 0 : 1; 318 | matrix[j][i] = Math.min( 319 | matrix[j][i - 1] + 1, 320 | matrix[j - 1][i] + 1, 321 | matrix[j - 1][i - 1] + indicator 322 | ); 323 | } 324 | } 325 | 326 | return matrix[b.length][a.length]; 327 | } 328 | 329 | /** 330 | * Generate helpful error message for parsing failures 331 | */ 332 | private generateHelpfulError(command: string, availablePrompts: PromptData[]): string { 333 | let message = `Could not parse command: "${command}"\n\n`; 334 | 335 | message += 'Supported command formats:\n'; 336 | message += '• Simple: >>prompt_name arguments\n'; 337 | message += '• Simple: /prompt_name arguments\n'; 338 | message += '• JSON: {"command": ">>prompt_name", "args": "arguments"}\n'; 339 | message += '• JSON: {"command": ">>prompt_name", "args": {"key": "value"}}\n\n'; 340 | 341 | // Try to give specific suggestions based on command analysis 342 | if (command.includes('>>') || command.includes('/')) { 343 | const promptMatch = command.match(/^(>>|\/)([a-zA-Z0-9_\-]+)/); 344 | if (promptMatch) { 345 | const promptName = promptMatch[2]; 346 | message += `Prompt name "${promptName}" not found. `; 347 | message += 'Prompt names are case-insensitive.\n\n'; 348 | 349 | // Show some available prompts as examples 350 | const examplePrompts = availablePrompts.slice(0, 5).map(p => p.id).join(', '); 351 | message += `Available prompts include: ${examplePrompts}...\n\n`; 352 | } else { 353 | message += 'Invalid prompt name format. Use letters, numbers, underscores, and hyphens only.\n\n'; 354 | } 355 | } else if (command.startsWith('{')) { 356 | message += 'JSON format detected but could not parse. Check JSON syntax and structure.\n\n'; 357 | } else { 358 | message += 'Command format not recognized. Use >>prompt_name or /prompt_name format.\n\n'; 359 | } 360 | 361 | message += 'Use >>listprompts to see all available prompts, or >>help for assistance.'; 362 | 363 | return message; 364 | } 365 | 366 | /** 367 | * Update strategy usage statistics 368 | */ 369 | private updateStrategyStats(strategyName: string): void { 370 | const current = this.stats.strategyUsage.get(strategyName) || 0; 371 | this.stats.strategyUsage.set(strategyName, current + 1); 372 | } 373 | 374 | /** 375 | * Update confidence statistics 376 | */ 377 | private updateConfidenceStats(confidence: number): void { 378 | const totalSuccessful = this.stats.successfulParses; 379 | this.stats.averageConfidence = 380 | (this.stats.averageConfidence * (totalSuccessful - 1) + confidence) / totalSuccessful; 381 | } 382 | 383 | /** 384 | * Get parsing statistics for monitoring 385 | */ 386 | getStats(): typeof this.stats { 387 | return { 388 | ...this.stats, 389 | strategyUsage: new Map(this.stats.strategyUsage) 390 | }; 391 | } 392 | 393 | /** 394 | * Reset statistics (useful for testing or fresh starts) 395 | */ 396 | resetStats(): void { 397 | this.stats = { 398 | totalParses: 0, 399 | successfulParses: 0, 400 | failedParses: 0, 401 | strategyUsage: new Map(), 402 | averageConfidence: 0 403 | }; 404 | } 405 | } 406 | 407 | /** 408 | * Factory function to create unified command parser 409 | */ 410 | export function createUnifiedCommandParser(logger: Logger): UnifiedCommandParser { 411 | return new UnifiedCommandParser(logger); 412 | } 413 | ``` -------------------------------------------------------------------------------- /server/src/mcp-tools/types/shared-types.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared Type Definitions for MCP Tools 3 | * 4 | * This file contains proper TypeScript interfaces to replace 'any' types 5 | * throughout the MCP tools system, improving type safety and development experience. 6 | * 7 | * Updated: 2025-09-28 - Consolidated with unified error handling system 8 | */ 9 | 10 | // Import consolidated error handling types 11 | import { ErrorContext, ValidationResult } from '../../utils/errorHandling.js'; 12 | 13 | /** 14 | * MCP Tool callback extra parameter 15 | */ 16 | export interface ToolExtra { 17 | requestId?: string; 18 | clientInfo?: { 19 | name: string; 20 | version: string; 21 | }; 22 | metadata?: Record<string, unknown>; 23 | } 24 | 25 | /** 26 | * Structured response interface for all MCP tools (compatible with ToolResponse) 27 | */ 28 | export interface StructuredToolResponse { 29 | content: Array<{ 30 | type: "text"; 31 | text: string; 32 | }>; 33 | isError?: boolean; 34 | metadata?: { 35 | tool: string; 36 | action: string; 37 | timestamp: string; 38 | executionTime?: number; 39 | framework?: string; 40 | errorCode?: string; 41 | [key: string]: unknown; 42 | }; 43 | } 44 | 45 | /** 46 | * Analytics data structure for system metrics 47 | */ 48 | export interface AnalyticsData { 49 | executions: { 50 | total: number; 51 | successful: number; 52 | failed: number; 53 | byTool: Record<string, number>; 54 | byAction: Record<string, number>; 55 | }; 56 | performance: { 57 | averageExecutionTime: number; 58 | memoryUsage: { 59 | heapUsed: number; 60 | heapTotal: number; 61 | external: number; 62 | }; 63 | cacheHitRate?: number; 64 | }; 65 | frameworks: { 66 | activeFramework: string; 67 | switchCount: number; 68 | switchHistory: Array<{ 69 | from: string; 70 | to: string; 71 | timestamp: string; 72 | reason?: string; 73 | }>; 74 | }; 75 | timestamp: string; 76 | } 77 | 78 | /** 79 | * Response formatter interface with proper typing 80 | */ 81 | export interface TypedResponseFormatter { 82 | formatResponse<T>(content: T): StructuredToolResponse; 83 | formatErrorResponse(error: TypedError, context?: ResponseContext): StructuredToolResponse; 84 | setAnalyticsService(service: AnalyticsService): void; 85 | } 86 | 87 | /** 88 | * Response context for error formatting 89 | */ 90 | export interface ResponseContext { 91 | tool: string; 92 | action: string; 93 | requestId?: string; 94 | executionTime?: number; 95 | additionalInfo?: MetadataFields; 96 | } 97 | 98 | // ============================================================================ 99 | // PROPER ERROR TYPES (Phase 1: Replace error: any) 100 | // ============================================================================ 101 | 102 | /** 103 | * Base typed error interface 104 | */ 105 | export interface TypedError { 106 | name: string; 107 | message: string; 108 | code?: string; 109 | context?: ErrorContext; 110 | stack?: string; 111 | cause?: TypedError; 112 | } 113 | 114 | // Re-export ErrorContext from consolidated error handling 115 | export type { ErrorContext, ValidationResult }; 116 | 117 | /** 118 | * Validation error with detailed information 119 | */ 120 | export interface ValidationError extends TypedError { 121 | name: 'ValidationError'; 122 | field?: string; 123 | value?: unknown; 124 | constraint?: string; 125 | example?: string; 126 | } 127 | 128 | /** 129 | * Execution error with runtime context 130 | */ 131 | export interface ExecutionError extends TypedError { 132 | name: 'ExecutionError'; 133 | executionId?: string; 134 | step?: string; 135 | retryCount?: number; 136 | isRetryable?: boolean; 137 | } 138 | 139 | /** 140 | * Configuration error 141 | */ 142 | export interface ConfigurationError extends TypedError { 143 | name: 'ConfigurationError'; 144 | configKey?: string; 145 | configValue?: unknown; 146 | validValues?: unknown[]; 147 | } 148 | 149 | /** 150 | * Framework error 151 | */ 152 | export interface FrameworkError extends TypedError { 153 | name: 'FrameworkError'; 154 | framework?: string; 155 | operation?: string; 156 | supportedFrameworks?: string[]; 157 | } 158 | 159 | /** 160 | * Type guard functions for errors 161 | */ 162 | export function isTypedError(error: unknown): error is TypedError { 163 | return typeof error === 'object' && error !== null && 164 | 'name' in error && 'message' in error; 165 | } 166 | 167 | export function isValidationError(error: unknown): error is ValidationError { 168 | return isTypedError(error) && error.name === 'ValidationError'; 169 | } 170 | 171 | export function isExecutionError(error: unknown): error is ExecutionError { 172 | return isTypedError(error) && error.name === 'ExecutionError'; 173 | } 174 | 175 | export function isConfigurationError(error: unknown): error is ConfigurationError { 176 | return isTypedError(error) && error.name === 'ConfigurationError'; 177 | } 178 | 179 | export function isFrameworkError(error: unknown): error is FrameworkError { 180 | return isTypedError(error) && error.name === 'FrameworkError'; 181 | } 182 | 183 | /** 184 | * Analytics service interface (matches MetricsCollector) 185 | */ 186 | export interface AnalyticsService { 187 | recordExecution(executionData: ExecutionData): void; 188 | getAnalyticsSummary(options?: AnalyticsOptions): AnalyticsSummary; 189 | recordFrameworkSwitch?(data: FrameworkSwitchData): void; 190 | recordGateValidation?(data: GateValidationData): void; 191 | } 192 | 193 | /** 194 | * Execution data for analytics 195 | */ 196 | export interface ExecutionData { 197 | executionId: string; 198 | tool: string; 199 | action: string; 200 | success: boolean; 201 | duration: number; 202 | executionType: 'prompt' | 'template' | 'chain'; 203 | startTime: number; 204 | endTime: number; 205 | framework?: string; 206 | errorCode?: string; 207 | } 208 | 209 | /** 210 | * Analytics options 211 | */ 212 | export interface AnalyticsOptions { 213 | timeRange?: { 214 | start: Date; 215 | end: Date; 216 | }; 217 | includeDetails?: boolean; 218 | groupBy?: 'tool' | 'action' | 'framework'; 219 | } 220 | 221 | /** 222 | * Analytics summary result 223 | */ 224 | export interface AnalyticsSummary { 225 | totalExecutions: number; 226 | successRate: number; 227 | averageExecutionTime: number; 228 | breakdown: { 229 | byTool: Record<string, number>; 230 | byAction: Record<string, number>; 231 | byFramework: Record<string, number>; 232 | }; 233 | performance: { 234 | slowestExecutions: Array<{ 235 | tool: string; 236 | action: string; 237 | duration: number; 238 | timestamp: string; 239 | }>; 240 | errorRate: number; 241 | memoryUsage: { 242 | average: number; 243 | peak: number; 244 | }; 245 | }; 246 | } 247 | 248 | /** 249 | * Framework switch data 250 | */ 251 | export interface FrameworkSwitchData { 252 | from: string; 253 | to: string; 254 | timestamp: string; 255 | reason?: string; 256 | success: boolean; 257 | duration?: number; 258 | } 259 | 260 | /** 261 | * Gate validation data 262 | */ 263 | export interface GateValidationData { 264 | gateId: string; 265 | gateName: string; 266 | passed: boolean; 267 | score?: number; 268 | executionTime: number; 269 | requirements: string[]; 270 | context: { 271 | promptId: string; 272 | executionType: 'prompt' | 'template' | 'chain'; 273 | framework?: string; 274 | }; 275 | } 276 | 277 | /** 278 | * Configuration value types 279 | */ 280 | export type ConfigValue = string | number | boolean | null | ConfigObject | ConfigArray; 281 | export interface ConfigObject { 282 | [key: string]: ConfigValue; 283 | } 284 | export interface ConfigArray extends Array<ConfigValue> {} 285 | 286 | // ============================================================================ 287 | // SPECIFIC ARGUMENT TYPES (Phase 1: Replace any types) 288 | // ============================================================================ 289 | 290 | /** 291 | * Prompt argument definition with strict typing 292 | */ 293 | export interface PromptArgumentDefinition { 294 | name: string; 295 | description?: string; 296 | required: boolean; 297 | type?: 'string' | 'number' | 'boolean' | 'object' | 'array'; 298 | defaultValue?: ConfigValue; 299 | validation?: { 300 | pattern?: string; 301 | minLength?: number; 302 | maxLength?: number; 303 | allowedValues?: ConfigValue[]; 304 | }; 305 | } 306 | 307 | /** 308 | * Chain step definition with strict typing 309 | */ 310 | export interface ChainStepDefinition { 311 | promptId: string; 312 | stepName: string; 313 | inputMapping?: Record<string, string>; 314 | outputMapping?: Record<string, string>; 315 | conditions?: { 316 | skipIf?: string; 317 | stopIf?: string; 318 | }; 319 | } 320 | 321 | /** 322 | * Prompt Manager action argument types 323 | */ 324 | export interface BasePromptManagerArgs { 325 | id?: string; 326 | name?: string; 327 | category?: string; 328 | description?: string; 329 | system_message?: string; 330 | user_message_template?: string; 331 | arguments?: PromptArgumentDefinition[]; 332 | chain_steps?: ChainStepDefinition[]; 333 | filter?: string; 334 | detail?: 'overview' | 'steps' | 'structure' | 'gates' | 'flow' | 'analysis' | 'raw' | 'full'; 335 | format?: 'compact' | 'detailed' | 'json'; 336 | } 337 | 338 | export type PromptManagerAction = 339 | | { action: 'create'; id: string; name: string; user_message_template: string } & BasePromptManagerArgs 340 | | { action: 'create_prompt'; id: string; name: string; user_message_template: string } & BasePromptManagerArgs 341 | | { action: 'create_template'; id: string; name: string; user_message_template: string } & BasePromptManagerArgs 342 | | { action: 'update'; id: string } & BasePromptManagerArgs & { 343 | migrate_to?: 'prompt' | 'template'; 344 | section?: 'name' | 'description' | 'system_message' | 'user_message_template' | 'arguments' | 'chain_steps'; 345 | section_content?: string; 346 | } 347 | | { action: 'delete'; id: string } 348 | | { action: 'reload' } 349 | | { action: 'list' } & Pick<BasePromptManagerArgs, 'filter' | 'format'> 350 | | { action: 'inspect'; id: string } & Pick<BasePromptManagerArgs, 'detail' | 'format'>; 351 | 352 | /** 353 | * Prompt Engine argument types 354 | */ 355 | export interface PromptEngineArgs { 356 | command: string; 357 | execution_mode?: 'auto' | 'prompt' | 'template' | 'chain'; 358 | force_restart?: boolean; 359 | session_id?: string; 360 | options?: ExecutionOptions; 361 | } 362 | 363 | /** 364 | * Execution options with proper typing 365 | */ 366 | export interface ExecutionOptions { 367 | framework?: 'CAGEERF' | 'ReACT' | '5W1H' | 'SCAMPER'; 368 | gateValidation?: boolean; 369 | timeout?: number; 370 | retryCount?: number; 371 | debugMode?: boolean; 372 | stepConfirmation?: boolean; 373 | context?: ExecutionContext; 374 | } 375 | 376 | /** 377 | * Execution context 378 | */ 379 | export interface ExecutionContext { 380 | sessionId?: string; 381 | previousResults?: Record<string, unknown>; 382 | userPreferences?: { 383 | outputFormat?: 'compact' | 'detailed' | 'json' | 'markdown'; 384 | verboseMode?: boolean; 385 | autoExecution?: boolean; 386 | }; 387 | systemState?: { 388 | activeFramework?: string; 389 | memoryUsage?: { 390 | heapUsed: number; 391 | heapTotal: number; 392 | external: number; 393 | }; 394 | }; 395 | } 396 | 397 | /** 398 | * System Control argument types 399 | */ 400 | export interface BaseSystemControlArgs { 401 | include_history?: boolean; 402 | include_metrics?: boolean; 403 | show_details?: boolean; 404 | reason?: string; 405 | confirm?: boolean; 406 | limit?: number; 407 | reset_analytics?: boolean; 408 | operation?: string; 409 | } 410 | 411 | export type SystemControlAction = 412 | | { action: 'status' } & Pick<BaseSystemControlArgs, 'include_history' | 'include_metrics' | 'operation'> 413 | | { action: 'framework'; framework?: string } & Pick<BaseSystemControlArgs, 'reason' | 'show_details' | 'operation'> 414 | | { action: 'analytics' } & Pick<BaseSystemControlArgs, 'include_history' | 'reset_analytics' | 'limit' | 'confirm' | 'operation'> 415 | | { action: 'config'; config?: ConfigObject; backup_path?: string } & Pick<BaseSystemControlArgs, 'confirm' | 'operation'> 416 | | { action: 'maintenance' } & Pick<BaseSystemControlArgs, 'reason' | 'confirm' | 'operation'>; 417 | 418 | // ============================================================================ 419 | // REPLACEMENT TYPES FOR Record<string, any> 420 | // ============================================================================ 421 | 422 | /** 423 | * Prompt execution arguments (replaces Record<string, any>) 424 | */ 425 | export interface PromptExecutionArgs { 426 | [key: string]: string | number | boolean | null | PromptExecutionArgs | PromptExecutionArgs[]; 427 | } 428 | 429 | /** 430 | * Metadata fields (replaces Record<string, any>) 431 | */ 432 | export interface MetadataFields { 433 | tool: string; 434 | action: string; 435 | timestamp: string; 436 | executionTime?: number; 437 | framework?: string; 438 | errorCode?: string; 439 | [key: string]: string | number | boolean | null | undefined; 440 | } 441 | 442 | /** 443 | * Configuration update values 444 | */ 445 | export interface ConfigurationValues { 446 | [key: string]: ConfigValue; 447 | } 448 | 449 | /** 450 | * Chain execution results 451 | */ 452 | export interface ChainExecutionResults { 453 | [stepIndex: string]: { 454 | result: string; 455 | metadata: { 456 | startTime: number; 457 | endTime: number; 458 | status: 'completed' | 'failed' | 'skipped'; 459 | error?: string; 460 | }; 461 | }; 462 | } 463 | 464 | /** 465 | * Enhanced configuration management 466 | */ 467 | export interface ConfigChangeResult { 468 | valid: boolean; 469 | error?: string; 470 | convertedValue?: ConfigValue; 471 | suggestion?: string; 472 | } 473 | 474 | /** 475 | * Prompt data with proper typing 476 | */ 477 | export interface TypedPromptData { 478 | id: string; 479 | name: string; 480 | description: string; 481 | category: string; 482 | system_message?: string; 483 | user_message_template: string; 484 | arguments: Array<{ 485 | name: string; 486 | description?: string; 487 | required: boolean; 488 | type?: string; 489 | default?: ConfigValue; 490 | }>; 491 | chain_steps?: Array<{ 492 | promptId: string; 493 | stepName: string; 494 | inputMapping?: Record<string, string>; 495 | outputMapping?: Record<string, string>; 496 | }>; 497 | metadata?: { 498 | created: string; 499 | modified: string; 500 | version: string; 501 | tags?: string[]; 502 | }; 503 | } 504 | 505 | /** 506 | * Filter parsing result with structured data 507 | */ 508 | export interface FilterParseResult { 509 | textSearch?: string; 510 | structured: { 511 | type?: string[]; 512 | category?: string[]; 513 | gates?: boolean; 514 | confidence?: { 515 | operator: '>' | '<' | '>=' | '<=' | '='; 516 | value: number; 517 | }; 518 | created?: { 519 | operator: '>' | '<' | '>=' | '<=' | '='; 520 | value: Date; 521 | }; 522 | complexity?: string[]; 523 | }; 524 | operators?: { 525 | and?: FilterParseResult[]; 526 | or?: FilterParseResult[]; 527 | not?: FilterParseResult; 528 | }; 529 | } 530 | 531 | 532 | // ValidationResult now imported from execution/types.js - provides unified validation interface 533 | // Import at top of file if needed for MCP tool usage 534 | 535 | /** 536 | * Output formatting options 537 | */ 538 | export interface OutputOptions { 539 | format: "compact" | "detailed" | "json" | "markdown"; 540 | includeMetadata: boolean; 541 | maxResults?: number; 542 | sortBy?: string; 543 | sortOrder?: "asc" | "desc"; 544 | } 545 | 546 | /** 547 | * Enhanced tool response with formatting options 548 | */ 549 | export interface EnhancedToolResponse extends StructuredToolResponse { 550 | outputOptions?: OutputOptions; 551 | pagination?: { 552 | page: number; 553 | pageSize: number; 554 | total: number; 555 | hasNext: boolean; 556 | hasPrevious: boolean; 557 | }; 558 | } ``` -------------------------------------------------------------------------------- /server/src/text-references/conversation.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Conversation Management Module 3 | * Handles conversation history tracking and context management 4 | */ 5 | 6 | import { Logger } from "../logging/index.js"; 7 | 8 | /** 9 | * Conversation history item interface 10 | */ 11 | export interface ConversationHistoryItem { 12 | role: "user" | "assistant" | "system"; 13 | content: string; 14 | timestamp: number; 15 | isProcessedTemplate?: boolean; // Flag to indicate if this is a processed template rather than original user input 16 | } 17 | 18 | /** 19 | * Conversation Manager class 20 | */ 21 | export class ConversationManager { 22 | private logger: Logger; 23 | private conversationHistory: ConversationHistoryItem[] = []; 24 | private maxHistorySize: number; 25 | 26 | // NEW: Session manager integration for coordinated state management 27 | private chainSessionManager?: any; // ChainSessionManager (injected to avoid circular dependency) 28 | 29 | // NEW: Text reference manager for step storage delegation 30 | private textReferenceManager?: any; // TextReferenceManager (injected to avoid circular dependency) 31 | 32 | constructor(logger: Logger, maxHistorySize: number = 100) { 33 | this.logger = logger; 34 | this.maxHistorySize = maxHistorySize; 35 | } 36 | 37 | /** 38 | * NEW: Set chain session manager for coordinated state management 39 | */ 40 | setChainSessionManager(sessionManager: any): void { 41 | this.chainSessionManager = sessionManager; 42 | this.logger.debug("Chain session manager integrated with conversation manager"); 43 | } 44 | 45 | /** 46 | * NEW: Set text reference manager for step storage delegation 47 | */ 48 | setTextReferenceManager(textReferenceManager: any): void { 49 | this.textReferenceManager = textReferenceManager; 50 | this.logger.debug("Text reference manager integrated with conversation manager"); 51 | } 52 | 53 | /** 54 | * Add an item to conversation history with size management 55 | */ 56 | addToConversationHistory(item: ConversationHistoryItem): void { 57 | this.conversationHistory.push(item); 58 | 59 | // Trim history if it exceeds maximum size 60 | if (this.conversationHistory.length > this.maxHistorySize) { 61 | // Remove oldest entries, keeping recent ones 62 | this.conversationHistory.splice( 63 | 0, 64 | this.conversationHistory.length - this.maxHistorySize 65 | ); 66 | this.logger.debug( 67 | `Trimmed conversation history to ${this.maxHistorySize} entries to prevent memory leaks` 68 | ); 69 | } 70 | } 71 | 72 | /** 73 | * Get the previous message from conversation history 74 | */ 75 | getPreviousMessage(): string { 76 | // Try to find the last user message in conversation history 77 | if (this.conversationHistory.length > 0) { 78 | // Start from the end and find the first non-template user message 79 | for (let i = this.conversationHistory.length - 1; i >= 0; i--) { 80 | const historyItem = this.conversationHistory[i]; 81 | // Only consider user messages that aren't processed templates 82 | if (historyItem.role === "user" && !historyItem.isProcessedTemplate) { 83 | this.logger.debug( 84 | `Found previous user message for context: ${historyItem.content.substring( 85 | 0, 86 | 50 87 | )}...` 88 | ); 89 | return historyItem.content; 90 | } 91 | } 92 | } 93 | 94 | // Return a default prompt if no suitable history item is found 95 | return "[Please check previous messages in the conversation for context]"; 96 | } 97 | 98 | /** 99 | * Get conversation history 100 | */ 101 | getConversationHistory(): ConversationHistoryItem[] { 102 | return [...this.conversationHistory]; 103 | } 104 | 105 | /** 106 | * Get conversation statistics 107 | */ 108 | getConversationStats(): { 109 | totalMessages: number; 110 | userMessages: number; 111 | assistantMessages: number; 112 | systemMessages: number; 113 | templatedMessages: number; 114 | } { 115 | const stats = { 116 | totalMessages: this.conversationHistory.length, 117 | userMessages: 0, 118 | assistantMessages: 0, 119 | systemMessages: 0, 120 | templatedMessages: 0, 121 | }; 122 | 123 | this.conversationHistory.forEach((item) => { 124 | switch (item.role) { 125 | case "user": 126 | stats.userMessages++; 127 | break; 128 | case "assistant": 129 | stats.assistantMessages++; 130 | break; 131 | case "system": 132 | stats.systemMessages++; 133 | break; 134 | } 135 | 136 | if (item.isProcessedTemplate) { 137 | stats.templatedMessages++; 138 | } 139 | }); 140 | 141 | return stats; 142 | } 143 | 144 | /** 145 | * Clear conversation history 146 | */ 147 | clearHistory(): void { 148 | this.conversationHistory = []; 149 | this.logger.info("Conversation history cleared"); 150 | } 151 | 152 | /** 153 | * Get recent messages (useful for context) 154 | */ 155 | getRecentMessages(count: number = 5): ConversationHistoryItem[] { 156 | return this.conversationHistory.slice(-count); 157 | } 158 | 159 | // Enhanced: Chain context storage for LLM-driven iterative workflow with proper result capture 160 | private chainContext: Record<string, Record<number, string>> = {}; 161 | private chainStates: Record<string, { currentStep: number; totalSteps: number; lastUpdated: number }> = {}; 162 | 163 | // NEW: Track actual execution results vs placeholders 164 | private chainExecutionResults: Record<string, Record<number, { 165 | result: string; 166 | timestamp: number; 167 | isPlaceholder: boolean; 168 | executionMetadata?: any; 169 | }>> = {}; 170 | 171 | /** 172 | * Save step result for a chain execution with enhanced metadata 173 | * REFACTORED: Now delegates to TextReferenceManager for storage 174 | */ 175 | saveStepResult(chainId: string, step: number, result: string, isPlaceholder: boolean = false, metadata?: any): void { 176 | // Delegate to TextReferenceManager for actual storage (single source of truth) 177 | if (this.textReferenceManager) { 178 | this.textReferenceManager.storeChainStepResult(chainId, step, result, { 179 | ...metadata, 180 | isPlaceholder, 181 | executionMetadata: metadata 182 | }); 183 | } else { 184 | // Fallback to local storage if TextReferenceManager not available 185 | if (!this.chainContext[chainId]) { 186 | this.chainContext[chainId] = {}; 187 | } 188 | if (!this.chainExecutionResults[chainId]) { 189 | this.chainExecutionResults[chainId] = {}; 190 | } 191 | 192 | this.chainContext[chainId][step] = result; 193 | this.chainExecutionResults[chainId][step] = { 194 | result, 195 | timestamp: Date.now(), 196 | isPlaceholder, 197 | executionMetadata: metadata 198 | }; 199 | } 200 | 201 | this.logger.debug(`Saved step ${step} result for chain ${chainId} (placeholder: ${isPlaceholder})`); 202 | } 203 | 204 | /** 205 | * Get step result with metadata indicating if it's a placeholder 206 | */ 207 | getStepResultWithMetadata(chainId: string, step: number): { 208 | result: string; 209 | isPlaceholder: boolean; 210 | timestamp: number; 211 | metadata?: any; 212 | } | undefined { 213 | return this.chainExecutionResults[chainId]?.[step]; 214 | } 215 | 216 | /** 217 | * Get all step results for a chain 218 | * REFACTORED: Now delegates to TextReferenceManager for retrieval 219 | */ 220 | getStepResults(chainId: string): Record<number, string> { 221 | if (this.textReferenceManager) { 222 | return this.textReferenceManager.getChainStepResults(chainId); 223 | } 224 | 225 | // Fallback to local storage if TextReferenceManager not available 226 | return this.chainContext[chainId] || {}; 227 | } 228 | 229 | /** 230 | * Get specific step result for a chain 231 | * REFACTORED: Now delegates to TextReferenceManager for retrieval 232 | */ 233 | getStepResult(chainId: string, step: number): string | undefined { 234 | if (this.textReferenceManager) { 235 | return this.textReferenceManager.getChainStepResult(chainId, step) || undefined; 236 | } 237 | 238 | // Fallback to local storage if TextReferenceManager not available 239 | return this.chainContext[chainId]?.[step]; 240 | } 241 | 242 | /** 243 | * Set chain state (current step and total steps) with timestamp 244 | */ 245 | setChainState(chainId: string, currentStep: number, totalSteps: number): void { 246 | this.chainStates[chainId] = { currentStep, totalSteps, lastUpdated: Date.now() }; 247 | this.logger.debug(`Chain ${chainId}: step ${currentStep}/${totalSteps}`); 248 | } 249 | 250 | /** 251 | * Validate chain state integrity and recover if needed 252 | */ 253 | validateChainState(chainId: string): { valid: boolean; issues?: string[]; recovered?: boolean } { 254 | const state = this.chainStates[chainId]; 255 | const context = this.chainContext[chainId]; 256 | const results = this.chainExecutionResults[chainId]; 257 | 258 | const issues: string[] = []; 259 | let recovered = false; 260 | 261 | if (!state) { 262 | issues.push("No chain state found"); 263 | return { valid: false, issues }; 264 | } 265 | 266 | // Check for stale state (older than 1 hour) 267 | if (Date.now() - state.lastUpdated > 3600000) { 268 | issues.push("Chain state is stale (>1 hour old)"); 269 | } 270 | 271 | // Validate step consistency 272 | if (state.currentStep > state.totalSteps) { 273 | issues.push(`Current step ${state.currentStep} exceeds total steps ${state.totalSteps}`); 274 | // Auto-recover by resetting to final step 275 | this.setChainState(chainId, state.totalSteps, state.totalSteps); 276 | recovered = true; 277 | } 278 | 279 | // Check for missing results in expected steps 280 | if (context && results) { 281 | for (let i = 0; i < Math.min(state.currentStep, state.totalSteps); i++) { 282 | const hasResult = context[i] || results[i]; 283 | if (!hasResult) { 284 | issues.push(`Missing result for completed step ${i}`); 285 | } 286 | } 287 | } 288 | 289 | this.logger.debug(`Chain ${chainId} validation: ${issues.length} issues found${recovered ? ' (auto-recovered)' : ''}`); 290 | 291 | return { 292 | valid: issues.length === 0, 293 | issues: issues.length > 0 ? issues : undefined, 294 | recovered 295 | }; 296 | } 297 | 298 | /** 299 | * Get chain state 300 | */ 301 | getChainState(chainId: string): { currentStep: number; totalSteps: number; lastUpdated: number } | undefined { 302 | return this.chainStates[chainId]; 303 | } 304 | 305 | /** 306 | * Clear chain context and state with session manager coordination 307 | * REFACTORED: Now delegates step result clearing to TextReferenceManager 308 | */ 309 | clearChainContext(chainId: string): void { 310 | // Clear local state 311 | delete this.chainContext[chainId]; 312 | delete this.chainStates[chainId]; 313 | delete this.chainExecutionResults[chainId]; 314 | 315 | // Clear step results from TextReferenceManager 316 | if (this.textReferenceManager) { 317 | this.textReferenceManager.clearChainStepResults(chainId); 318 | } 319 | 320 | // Also clear session manager state if available 321 | if (this.chainSessionManager) { 322 | try { 323 | this.chainSessionManager.clearSessionsForChain(chainId); 324 | } catch (error) { 325 | this.logger.warn(`Failed to clear session manager state for ${chainId}:`, error); 326 | } 327 | } 328 | 329 | this.logger.info(`Cleared all chain state for ${chainId}`); 330 | } 331 | 332 | /** 333 | * Clear all chain contexts and states 334 | */ 335 | clearAllChainContexts(): void { 336 | this.chainContext = {}; 337 | this.chainStates = {}; 338 | this.chainExecutionResults = {}; 339 | this.logger.info("Cleared all chain contexts"); 340 | } 341 | 342 | /** 343 | * Get chain execution summary with metadata 344 | */ 345 | getChainSummary(chainId: string): { 346 | state?: { currentStep: number; totalSteps: number; lastUpdated: number }; 347 | completedSteps: number; 348 | placeholderSteps: number; 349 | realSteps: number; 350 | totalResults: number; 351 | } { 352 | const state = this.chainStates[chainId]; 353 | const results = this.chainExecutionResults[chainId] || {}; 354 | 355 | let placeholderSteps = 0; 356 | let realSteps = 0; 357 | 358 | Object.values(results).forEach(result => { 359 | if (result.isPlaceholder) { 360 | placeholderSteps++; 361 | } else { 362 | realSteps++; 363 | } 364 | }); 365 | 366 | return { 367 | state, 368 | completedSteps: Object.keys(results).length, 369 | placeholderSteps, 370 | realSteps, 371 | totalResults: Object.keys(results).length 372 | }; 373 | } 374 | 375 | /** 376 | * NEW: Comprehensive state validation with session manager coordination 377 | */ 378 | validateChainStateIntegrity(chainId: string): { 379 | conversationState: boolean; 380 | sessionState: boolean; 381 | synchronized: boolean; 382 | recommendations: string[]; 383 | sessionInfo?: any; 384 | } { 385 | const hasConversationState = !!this.chainStates[chainId]; 386 | let hasSessionState = false; 387 | let sessionInfo: any = undefined; 388 | 389 | // Check session manager state if available 390 | if (this.chainSessionManager) { 391 | try { 392 | hasSessionState = this.chainSessionManager.hasActiveSessionForChain(chainId); 393 | if (hasSessionState) { 394 | const session = this.chainSessionManager.getActiveSessionForChain(chainId); 395 | sessionInfo = { 396 | sessionId: session?.sessionId, 397 | state: session?.state, 398 | currentStepId: session?.currentStepId, 399 | executionOrder: session?.executionOrder 400 | }; 401 | } 402 | } catch (error) { 403 | this.logger.warn(`Failed to check session state for ${chainId}:`, error); 404 | } 405 | } 406 | 407 | const recommendations: string[] = []; 408 | 409 | if (hasConversationState && !hasSessionState) { 410 | recommendations.push("Orphaned conversation state - consider force restart to clear stale LLM context"); 411 | } 412 | if (!hasConversationState && hasSessionState) { 413 | recommendations.push("Missing conversation state - session may be from different execution type"); 414 | } 415 | if (hasConversationState && hasSessionState) { 416 | // Both exist - check for consistency 417 | const conversationState = this.chainStates[chainId]; 418 | if (conversationState && sessionInfo) { 419 | recommendations.push("States synchronized - both conversation and session managers have active state"); 420 | } 421 | } 422 | if (!hasConversationState && !hasSessionState) { 423 | recommendations.push("Clean state - no active chain execution detected"); 424 | } 425 | 426 | return { 427 | conversationState: hasConversationState, 428 | sessionState: hasSessionState, 429 | synchronized: hasConversationState === hasSessionState, 430 | recommendations, 431 | sessionInfo 432 | }; 433 | } 434 | } 435 | 436 | /** 437 | * Create and configure a conversation manager 438 | */ 439 | export function createConversationManager( 440 | logger: Logger, 441 | maxHistorySize?: number 442 | ): ConversationManager { 443 | return new ConversationManager(logger, maxHistorySize); 444 | } 445 | ``` -------------------------------------------------------------------------------- /server/tests/scripts/integration-mcp-tools.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | /** 3 | * MCP Tools Integration Tests - Node.js Script Version 4 | * Tests for the current 3 intelligent MCP tools with enhanced command routing 5 | */ 6 | 7 | async function runMcpToolsIntegrationTests() { 8 | try { 9 | console.log('🧪 Running MCP Tools Integration tests...'); 10 | console.log('📋 Testing intelligent MCP tool architecture with command routing functionality'); 11 | 12 | // Import global resource tracker for process cleanup 13 | const { globalResourceTracker } = await import('../../dist/utils/global-resource-tracker.js'); 14 | 15 | // Import modules - Updated to match current export structure 16 | const { createConsolidatedPromptEngine } = await import('../../dist/mcp-tools/prompt-engine/index.js'); 17 | const { createConsolidatedPromptManager } = await import('../../dist/mcp-tools/prompt-manager/index.js'); 18 | const { createConsolidatedSystemControl } = await import('../../dist/mcp-tools/system-control.js'); 19 | 20 | // Mock logger 21 | const mockLogger = { 22 | debug: () => {}, 23 | info: () => {}, 24 | warn: () => {}, 25 | error: () => {} 26 | }; 27 | 28 | // Mock MCP server 29 | const mockMcpServer = { 30 | registeredTools: [], 31 | registerTool: function(toolName, config) { 32 | this.registeredTools.push(toolName); 33 | return this; 34 | }, 35 | tool: function(toolName) { 36 | this.registeredTools.push(toolName); 37 | return this; 38 | }, 39 | getRegisteredToolNames: function() { 40 | return this.registeredTools.map(tool => typeof tool === 'string' ? tool : tool.name || 'unknown'); 41 | }, 42 | clear: function() { 43 | this.registeredTools = []; 44 | } 45 | }; 46 | 47 | // Test data 48 | const testPrompts = { 49 | simple: { 50 | id: 'test_simple', 51 | name: 'Simple Test', 52 | description: 'Simple test prompt', 53 | userMessageTemplate: 'Hello {{name}}', 54 | arguments: [{ name: 'name', required: true, description: 'Name' }], 55 | category: 'test' 56 | } 57 | }; 58 | 59 | let promptEngine, promptManager, systemControl; 60 | 61 | // Setup for each test 62 | function setupTest() { 63 | // Updated mock dependencies to match current architecture 64 | const mockPromptManager = { 65 | processTemplateAsync: () => Promise.resolve('mocked template result'), 66 | convertedPrompts: [testPrompts.simple], 67 | promptsData: [testPrompts.simple], 68 | loadAndConvertPrompts: () => Promise.resolve([testPrompts.simple]) 69 | }; 70 | 71 | const mockSemanticAnalyzer = { 72 | analyzePrompt: () => Promise.resolve({ 73 | executionType: 'template', 74 | requiresExecution: true, 75 | confidence: 0.8 76 | }), 77 | getConfig: () => ({ 78 | llmIntegration: { enabled: false } 79 | }) 80 | }; 81 | 82 | const mockFrameworkManager = { 83 | getCurrentFramework: () => ({ frameworkId: 'CAGEERF', frameworkName: 'CAGEERF' }), 84 | generateExecutionContext: () => ({ 85 | systemPrompt: 'test system prompt', 86 | framework: 'CAGEERF' 87 | }) 88 | }; 89 | 90 | // Complete mock parameters for ConsolidatedPromptEngine 91 | const mockConfigManager = { 92 | getConfig: () => ({ 93 | server: { name: 'test-server', version: '1.0.0' }, 94 | gates: { definitionsDirectory: "src/gates/definitions", templatesDirectory: "src/gates/templates" } 95 | }), 96 | getPromptsFilePath: () => '/test/prompts.json' 97 | }; 98 | 99 | const mockConversationManager = { 100 | addToConversationHistory: () => {}, 101 | getConversationHistory: () => [], 102 | saveStepResult: () => {}, 103 | getStepResult: () => null, 104 | setChainSessionManager: () => {} 105 | }; 106 | 107 | const mockTextReferenceManager = { 108 | extractReferences: () => [], 109 | resolveReferences: () => {}, 110 | addReference: () => {} 111 | }; 112 | 113 | const mockMcpToolsManager = { 114 | initialize: () => {}, 115 | getTools: () => [], 116 | promptManagerTool: { handleAction: () => Promise.resolve({ content: [], isError: false }) }, 117 | systemControl: { 118 | handleAction: () => Promise.resolve({ content: [], isError: false }), 119 | setAdvancedGateOrchestrator: () => {}, 120 | updateAnalytics: () => {} 121 | } 122 | }; 123 | 124 | // Clear mock server 125 | mockMcpServer.clear(); 126 | 127 | // Create consolidated tools with all required parameters (updated for current constructor signatures) 128 | promptEngine = createConsolidatedPromptEngine( 129 | mockLogger, 130 | mockMcpServer, 131 | mockPromptManager, 132 | mockConfigManager, 133 | mockSemanticAnalyzer, 134 | mockConversationManager, 135 | mockTextReferenceManager, 136 | mockMcpToolsManager 137 | ); 138 | 139 | // Check prompt manager constructor signature for proper parameters 140 | promptManager = createConsolidatedPromptManager( 141 | mockLogger, 142 | mockMcpServer, 143 | mockConfigManager, 144 | mockSemanticAnalyzer, 145 | undefined, // frameworkStateManager 146 | mockFrameworkManager, 147 | () => Promise.resolve(), // onRefresh 148 | () => Promise.resolve() // onRestart 149 | ); 150 | 151 | systemControl = createConsolidatedSystemControl( 152 | mockLogger, 153 | mockMcpServer, 154 | mockFrameworkManager, 155 | undefined, // frameworkStateManager 156 | mockMcpToolsManager 157 | ); 158 | 159 | // Note: Tools are no longer registered individually - they are registered by ConsolidatedMcpToolsManager 160 | // For testing, we'll simulate the registration that would normally happen in the manager 161 | mockMcpServer.registerTool('prompt_engine', { title: 'Prompt Engine', description: 'Test engine' }, async () => {}); 162 | mockMcpServer.registerTool('prompt_manager', { title: 'Prompt Manager', description: 'Test manager' }, async () => {}); 163 | mockMcpServer.registerTool('system_control', { title: 'System Control', description: 'Test control' }, async () => {}); 164 | } 165 | 166 | // Simple assertion helpers 167 | function assertEqual(actual, expected, testName) { 168 | if (actual === expected) { 169 | console.log(`✅ ${testName}: PASSED`); 170 | return true; 171 | } else { 172 | console.error(`❌ ${testName}: FAILED`); 173 | console.error(` Expected: ${expected}`); 174 | console.error(` Actual: ${actual}`); 175 | return false; 176 | } 177 | } 178 | 179 | function assertTruthy(value, testName) { 180 | if (value) { 181 | console.log(`✅ ${testName}: PASSED`); 182 | return true; 183 | } else { 184 | console.error(`❌ ${testName}: FAILED - Expected truthy value, got: ${value}`); 185 | return false; 186 | } 187 | } 188 | 189 | function assertType(value, expectedType, testName) { 190 | if (typeof value === expectedType) { 191 | console.log(`✅ ${testName}: PASSED`); 192 | return true; 193 | } else { 194 | console.error(`❌ ${testName}: FAILED - Expected type ${expectedType}, got: ${typeof value}`); 195 | return false; 196 | } 197 | } 198 | 199 | function assertContains(array, item, testName) { 200 | if (Array.isArray(array) && array.includes(item)) { 201 | console.log(`✅ ${testName}: PASSED`); 202 | return true; 203 | } else { 204 | console.error(`❌ ${testName}: FAILED - Array does not contain: ${item}`); 205 | console.error(` Array: ${JSON.stringify(array)}`); 206 | return false; 207 | } 208 | } 209 | 210 | function assertLessThan(actual, expected, testName) { 211 | if (actual < expected) { 212 | console.log(`✅ ${testName}: PASSED (${actual} < ${expected})`); 213 | return true; 214 | } else { 215 | console.error(`❌ ${testName}: FAILED (${actual} >= ${expected})`); 216 | return false; 217 | } 218 | } 219 | 220 | function assertGreaterThanOrEqual(actual, expected, testName) { 221 | if (actual >= expected) { 222 | console.log(`✅ ${testName}: PASSED (${actual} >= ${expected})`); 223 | return true; 224 | } else { 225 | console.error(`❌ ${testName}: FAILED (${actual} < ${expected})`); 226 | return false; 227 | } 228 | } 229 | 230 | let testResults = []; 231 | 232 | // Test 1: Consolidated Prompt Engine 233 | console.log('🔍 Test 1: Consolidated Prompt Engine'); 234 | 235 | setupTest(); 236 | testResults.push(assertTruthy(promptEngine, 'Prompt engine created')); 237 | 238 | const registeredTools1 = mockMcpServer.getRegisteredToolNames(); 239 | testResults.push(assertContains(registeredTools1, 'prompt_engine', 'Prompt engine tool registered')); 240 | testResults.push(assertType(promptEngine.executePromptCommand, 'function', 'Execute prompt command function exists')); 241 | 242 | // Test 2: Consolidated Prompt Manager 243 | console.log('🔍 Test 2: Consolidated Prompt Manager'); 244 | 245 | setupTest(); 246 | testResults.push(assertTruthy(promptManager, 'Prompt manager created')); 247 | 248 | const registeredTools2 = mockMcpServer.getRegisteredToolNames(); 249 | testResults.push(assertContains(registeredTools2, 'prompt_manager', 'Prompt manager tool registered')); 250 | testResults.push(assertType(promptManager.handleAction, 'function', 'Handle action function exists')); 251 | 252 | // Test 3: Consolidated System Control 253 | console.log('🔍 Test 3: Consolidated System Control'); 254 | 255 | setupTest(); 256 | testResults.push(assertTruthy(systemControl, 'System control created')); 257 | 258 | const registeredTools3 = mockMcpServer.getRegisteredToolNames(); 259 | testResults.push(assertContains(registeredTools3, 'system_control', 'System control tool registered')); 260 | testResults.push(assertType(systemControl.handleAction, 'function', 'Handle action function exists')); 261 | 262 | // Test 4: Consolidated Tools Integration 263 | console.log('🔍 Test 4: Consolidated Tools Integration'); 264 | 265 | setupTest(); 266 | const allRegisteredTools = mockMcpServer.getRegisteredToolNames(); 267 | const consolidatedTools = ['prompt_engine', 'prompt_manager', 'system_control']; 268 | 269 | for (const toolName of consolidatedTools) { 270 | testResults.push(assertContains(allRegisteredTools, toolName, `${toolName} registered`)); 271 | } 272 | 273 | const actualConsolidatedTools = allRegisteredTools.filter(name => 274 | consolidatedTools.includes(name) 275 | ); 276 | testResults.push(assertEqual(actualConsolidatedTools.length, 3, 'Exactly 3 consolidated tools registered')); 277 | 278 | // Test 5: Tool Consolidation Benefits 279 | console.log('🔍 Test 5: Tool Consolidation Benefits'); 280 | 281 | const totalRegisteredTools = mockMcpServer.registeredTools.length; 282 | testResults.push(assertLessThan(totalRegisteredTools, 10, 'Much fewer tools than legacy 24+ system')); 283 | testResults.push(assertGreaterThanOrEqual(totalRegisteredTools, 3, 'At least 3 consolidated tools')); 284 | 285 | // Test 6: Error Handling 286 | console.log('🔍 Test 6: Error Handling'); 287 | 288 | try { 289 | const invalidEngine = createConsolidatedPromptEngine(null, mockMcpServer, null, null, null, null, null); 290 | testResults.push(assertTruthy(true, 'Invalid tool creation handled gracefully')); 291 | } catch (error) { 292 | // It's actually acceptable for tools to throw with invalid parameters 293 | // This demonstrates proper parameter validation 294 | testResults.push(assertTruthy(true, 'Invalid tool creation properly validates parameters')); 295 | } 296 | 297 | testResults.push(assertTruthy(promptEngine, 'Prompt engine handles empty data gracefully')); 298 | testResults.push(assertTruthy(promptManager, 'Prompt manager handles empty data gracefully')); 299 | testResults.push(assertTruthy(systemControl, 'System control handles empty data gracefully')); 300 | 301 | // Test 7: Performance 302 | console.log('🔍 Test 7: Performance Validation'); 303 | 304 | const start = Date.now(); 305 | setupTest(); // Tools registered during setup 306 | const duration = Date.now() - start; 307 | 308 | testResults.push(assertLessThan(duration, 1000, 'Consolidated tools register efficiently')); 309 | 310 | const finalRegisteredTools = mockMcpServer.registeredTools.length; 311 | testResults.push(assertLessThan(finalRegisteredTools, 10, 'Performance benefits of consolidation maintained')); 312 | testResults.push(assertGreaterThanOrEqual(finalRegisteredTools, 3, 'Required consolidated tools present')); 313 | 314 | // Results Summary 315 | const passedTests = testResults.filter(result => result).length; 316 | const totalTests = testResults.length; 317 | 318 | console.log('\n📊 MCP Tools Integration Tests Summary:'); 319 | console.log(` ✅ Passed: ${passedTests}/${totalTests} tests`); 320 | console.log(` 📊 Success Rate: ${((passedTests/totalTests)*100).toFixed(1)}%`); 321 | 322 | // Check for remaining resources before exit 323 | console.log('\n🔍 Checking for remaining global resources...'); 324 | globalResourceTracker.logDiagnostics(); 325 | const cleared = globalResourceTracker.emergencyCleanup(); 326 | if (cleared > 0) { 327 | console.log(`💀 Emergency cleanup cleared ${cleared} additional resources`); 328 | } 329 | 330 | if (passedTests === totalTests) { 331 | console.log('🎉 All MCP Tools Integration tests passed!'); 332 | // Emergency process exit to prevent hanging due to global Node.js resources 333 | console.log('💀 Forcing process exit to prevent hanging from global timers...'); 334 | setTimeout(() => process.exit(0), 100); // Small delay to ensure log output 335 | return true; 336 | } else { 337 | console.error('❌ Some MCP Tools Integration tests failed'); 338 | // Emergency process exit for failure case as well 339 | console.log('💀 Forcing process exit to prevent hanging from global timers...'); 340 | setTimeout(() => process.exit(1), 100); // Small delay to ensure log output 341 | return false; 342 | } 343 | 344 | } catch (error) { 345 | console.error('❌ MCP Tools Integration tests failed with error:', error.message); 346 | if (error.stack) { 347 | console.error('Stack trace:', error.stack); 348 | } 349 | // Emergency process exit for error case as well 350 | console.log('💀 Forcing process exit due to test error to prevent hanging from global timers...'); 351 | setTimeout(() => process.exit(1), 100); // Small delay to ensure log output 352 | return false; 353 | } 354 | } 355 | 356 | // Run the tests 357 | if (import.meta.url === `file://${process.argv[1]}`) { 358 | runMcpToolsIntegrationTests().catch(error => { 359 | console.error('❌ Test execution failed:', error); 360 | process.exit(1); 361 | }); 362 | } 363 | 364 | export { runMcpToolsIntegrationTests }; ``` -------------------------------------------------------------------------------- /server/src/utils/errorHandling.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Consolidated Error Handling System 3 | * Combines basic error classes with MCP structured response capabilities 4 | */ 5 | 6 | // Define StructuredToolResponse locally to avoid circular dependency 7 | interface StructuredToolResponse { 8 | content: Array<{ 9 | type: "text"; 10 | text: string; 11 | }>; 12 | isError?: boolean; 13 | metadata?: { 14 | tool: string; 15 | action: string; 16 | timestamp: string; 17 | executionTime?: number; 18 | framework?: string; 19 | errorCode?: string; 20 | [key: string]: unknown; 21 | }; 22 | } 23 | 24 | // Error context interface for enhanced error handling 25 | export interface ErrorContext { 26 | tool?: string; 27 | action?: string; 28 | operation?: string; 29 | userInput?: unknown; 30 | suggestions?: string[]; 31 | recoveryOptions?: string[]; 32 | 33 | // Extended properties for MCP compatibility 34 | systemState?: { 35 | framework?: string; 36 | memoryUsage?: number; 37 | uptime?: number; 38 | }; 39 | errorType?: "validation" | "execution" | "system" | "client" | "configuration"; 40 | severity?: "low" | "medium" | "high" | "critical"; 41 | suggestedActions?: string[]; 42 | relatedComponents?: string[]; 43 | details?: any; 44 | } 45 | 46 | // Validation result types 47 | export interface ValidationResult { 48 | valid: boolean; 49 | errors?: Array<{ 50 | field: string; 51 | message: string; 52 | code: string; 53 | suggestion?: string; 54 | example?: string; 55 | }>; 56 | warnings?: Array<{ 57 | field: string; 58 | message: string; 59 | suggestion?: string; 60 | }>; 61 | } 62 | 63 | /** 64 | * Base error class with MCP structured response capability 65 | */ 66 | export abstract class BaseError extends Error { 67 | public readonly code: string; 68 | public readonly context: ErrorContext; 69 | public readonly timestamp: string; 70 | 71 | constructor(message: string, code: string, context: ErrorContext = {}) { 72 | super(message); 73 | this.name = this.constructor.name; 74 | this.code = code; 75 | this.context = context; 76 | this.timestamp = new Date().toISOString(); 77 | Error.captureStackTrace(this, this.constructor); 78 | } 79 | 80 | /** 81 | * Get structured error response for MCP protocol 82 | */ 83 | toStructuredResponse(): StructuredToolResponse { 84 | return { 85 | content: [{ 86 | type: "text", 87 | text: this.getEnhancedMessage() 88 | }], 89 | isError: true, 90 | metadata: { 91 | tool: this.context.tool || 'unknown', 92 | action: this.context.action || 'unknown', 93 | timestamp: this.timestamp, 94 | errorCode: this.code 95 | } 96 | }; 97 | } 98 | 99 | /** 100 | * Get enhanced error message with context and suggestions 101 | */ 102 | getEnhancedMessage(): string { 103 | let message = `❌ **${this.message}**\n\n`; 104 | 105 | // Show available context information 106 | if (this.context.action) { 107 | message += `**Action**: ${this.context.action}\n`; 108 | } 109 | if (this.context.operation) { 110 | message += `**Operation**: ${this.context.operation}\n`; 111 | } 112 | if (this.context.userInput !== undefined) { 113 | message += `**Input**: ${JSON.stringify(this.context.userInput)}\n\n`; 114 | } 115 | 116 | if (this.context.suggestions && this.context.suggestions.length > 0) { 117 | message += `💡 **Suggestions**:\n`; 118 | this.context.suggestions.forEach((suggestion, index) => { 119 | message += `${index + 1}. ${suggestion}\n`; 120 | }); 121 | message += '\n'; 122 | } 123 | 124 | if (this.context.recoveryOptions && this.context.recoveryOptions.length > 0) { 125 | message += `🔄 **Recovery Options**:\n`; 126 | this.context.recoveryOptions.forEach((option, index) => { 127 | message += `${index + 1}. ${option}\n`; 128 | }); 129 | } else { 130 | message += `⚠️ **Impact**: Please contact support if the issue persists.\n`; 131 | } 132 | 133 | return message; 134 | } 135 | } 136 | 137 | // Custom error classes extending BaseError 138 | export class PromptError extends BaseError { 139 | constructor(message: string, context: ErrorContext = {}) { 140 | super(message, 'PROMPT_ERROR', context); 141 | } 142 | } 143 | 144 | export class ArgumentError extends BaseError { 145 | constructor(message: string, context: ErrorContext = {}) { 146 | super(message, 'ARGUMENT_ERROR', context); 147 | } 148 | } 149 | 150 | export class ValidationError extends BaseError { 151 | public readonly validationResult?: ValidationResult; 152 | public readonly validationErrors?: string[]; // Keep for backwards compatibility 153 | 154 | constructor(message: string, validationErrorsOrContext?: string[] | ErrorContext, validationResult?: ValidationResult) { 155 | // Handle backwards compatibility with old constructor signature 156 | let context: ErrorContext = {}; 157 | let validationErrors: string[] | undefined; 158 | 159 | if (Array.isArray(validationErrorsOrContext)) { 160 | // Old signature: (message, validationErrors) 161 | validationErrors = validationErrorsOrContext; 162 | } else if (validationErrorsOrContext) { 163 | // New signature: (message, context, validationResult) 164 | context = validationErrorsOrContext; 165 | } 166 | 167 | super(message, 'VALIDATION_ERROR', context); 168 | this.validationErrors = validationErrors; 169 | this.validationResult = validationResult; 170 | } 171 | 172 | getEnhancedMessage(): string { 173 | let message = super.getEnhancedMessage(); 174 | 175 | // Enhanced validation error details 176 | if (this.validationResult?.errors) { 177 | message += `\n**Validation Errors**:\n`; 178 | this.validationResult.errors.forEach((error, index) => { 179 | message += `${index + 1}. **${error.field}**: ${error.message}\n`; 180 | if (error.suggestion) { 181 | message += ` 💡 ${error.suggestion}\n`; 182 | } 183 | if (error.example) { 184 | message += ` 📝 Example: ${error.example}\n`; 185 | } 186 | }); 187 | } 188 | 189 | if (this.validationResult?.warnings) { 190 | message += `\n**Warnings**:\n`; 191 | this.validationResult.warnings.forEach((warning, index) => { 192 | message += `${index + 1}. **${warning.field}**: ${warning.message}\n`; 193 | if (warning.suggestion) { 194 | message += ` 💡 ${warning.suggestion}\n`; 195 | } 196 | }); 197 | } 198 | 199 | // Backwards compatibility with old validationErrors array 200 | if (this.validationErrors && this.validationErrors.length > 0) { 201 | message += `\n**Legacy Validation Errors**: ${this.validationErrors.join(', ')}\n`; 202 | } 203 | 204 | return message; 205 | } 206 | } 207 | 208 | // Additional specialized error classes 209 | export class ConfigError extends BaseError { 210 | constructor(message: string, context: ErrorContext = {}) { 211 | super(message, 'CONFIG_ERROR', { 212 | ...context, 213 | suggestions: context.suggestions || ["Check your configuration file syntax and required fields", "See config documentation for valid options"] 214 | }); 215 | } 216 | } 217 | 218 | export class FrameworkError extends BaseError { 219 | constructor(message: string, context: ErrorContext = {}) { 220 | super(message, 'FRAMEWORK_ERROR', { 221 | ...context, 222 | suggestions: context.suggestions || ["Verify framework is enabled and properly configured", "See framework documentation for setup instructions"] 223 | }); 224 | } 225 | } 226 | 227 | export class ExecutionError extends BaseError { 228 | public readonly executionContext?: Record<string, unknown>; 229 | 230 | constructor(message: string, context: ErrorContext = {}, executionContext?: Record<string, unknown>) { 231 | super(message, 'EXECUTION_ERROR', context); 232 | this.executionContext = executionContext; 233 | } 234 | 235 | getEnhancedMessage(): string { 236 | let message = super.getEnhancedMessage(); 237 | 238 | if (this.executionContext) { 239 | message += `\n**Execution Context**:\n`; 240 | Object.entries(this.executionContext).forEach(([key, value]) => { 241 | message += `- **${key}**: ${JSON.stringify(value)}\n`; 242 | }); 243 | } 244 | 245 | return message; 246 | } 247 | } 248 | 249 | // Logger interface to ensure compatibility with the existing logger 250 | export interface Logger { 251 | info: (message: string, ...args: any[]) => void; 252 | error: (message: string, ...args: any[]) => void; 253 | warn: (message: string, ...args: any[]) => void; 254 | debug: (message: string, ...args: any[]) => void; 255 | } 256 | 257 | /** 258 | * Enhanced error handler with recovery strategies and MCP support 259 | */ 260 | export class ErrorHandler { 261 | private static instance: ErrorHandler; 262 | private retryStrategies = new Map<string, (error: BaseError) => boolean>(); 263 | 264 | private constructor() { 265 | this.setupDefaultRetryStrategies(); 266 | } 267 | 268 | public static getInstance(): ErrorHandler { 269 | if (!ErrorHandler.instance) { 270 | ErrorHandler.instance = new ErrorHandler(); 271 | } 272 | return ErrorHandler.instance; 273 | } 274 | 275 | /** 276 | * Handle error with context and return structured response 277 | */ 278 | handleError(error: unknown, context: ErrorContext): StructuredToolResponse { 279 | if (error instanceof BaseError) { 280 | return error.toStructuredResponse(); 281 | } 282 | 283 | // Convert unknown errors to BaseError 284 | const message = error instanceof Error ? error.message : String(error); 285 | const baseError = new (class extends BaseError { 286 | constructor(message: string, code: string, context: ErrorContext) { 287 | super(message, code, context); 288 | } 289 | })(message, 'UNKNOWN_ERROR', { 290 | ...context, 291 | suggestions: ["An unexpected error occurred. Please try again or contact support."], 292 | recoveryOptions: ["Try the operation again", "Check system status", "Contact support"] 293 | }); 294 | 295 | return baseError.toStructuredResponse(); 296 | } 297 | 298 | /** 299 | * Create validation error with enhanced context 300 | */ 301 | createValidationError( 302 | message: string, 303 | context: ErrorContext, 304 | validationResult?: ValidationResult 305 | ): ValidationError { 306 | return new ValidationError(message, context, validationResult); 307 | } 308 | 309 | /** 310 | * Add retry strategy for specific error patterns 311 | */ 312 | addRetryStrategy(errorCode: string, strategy: (error: BaseError) => boolean): void { 313 | this.retryStrategies.set(errorCode, strategy); 314 | } 315 | 316 | /** 317 | * Check if error is retryable 318 | */ 319 | isRetryable(error: BaseError): boolean { 320 | const strategy = this.retryStrategies.get(error.code); 321 | return strategy ? strategy(error) : Boolean(error.context.recoveryOptions && error.context.recoveryOptions.length > 0); 322 | } 323 | 324 | /** 325 | * Setup default retry strategies 326 | */ 327 | private setupDefaultRetryStrategies(): void { 328 | this.addRetryStrategy('VALIDATION_ERROR', () => false); // User must fix input 329 | this.addRetryStrategy('CONFIG_ERROR', () => false); // User must fix config 330 | this.addRetryStrategy('FRAMEWORK_ERROR', (error) => Boolean(error.context.recoveryOptions && error.context.recoveryOptions.length > 0)); 331 | this.addRetryStrategy('EXECUTION_ERROR', (error) => Boolean(error.context.recoveryOptions && error.context.recoveryOptions.length > 0)); 332 | } 333 | } 334 | 335 | /** 336 | * Validation helper functions 337 | */ 338 | export class ValidationHelpers { 339 | /** 340 | * Create validation result from errors 341 | */ 342 | static createValidationResult(errors: Array<{ 343 | field: string; 344 | message: string; 345 | code: string; 346 | suggestion?: string; 347 | example?: string; 348 | }>): ValidationResult { 349 | return { 350 | valid: errors.length === 0, 351 | errors: errors.length > 0 ? errors : undefined 352 | }; 353 | } 354 | 355 | /** 356 | * Validate required fields with enhanced messages 357 | */ 358 | static validateRequiredFields( 359 | data: Record<string, unknown>, 360 | requiredFields: string[] 361 | ): ValidationResult { 362 | const errors: ValidationResult['errors'] = []; 363 | 364 | requiredFields.forEach(field => { 365 | if (!(field in data) || data[field] === undefined || data[field] === null || data[field] === '') { 366 | errors!.push({ 367 | field, 368 | message: `Field '${field}' is required but was not provided`, 369 | code: 'REQUIRED_FIELD_MISSING', 370 | suggestion: `Please provide a value for '${field}'`, 371 | example: `"${field}": "example_value"` 372 | }); 373 | } 374 | }); 375 | 376 | return this.createValidationResult(errors || []); 377 | } 378 | 379 | /** 380 | * Create "did you mean" suggestions for typos 381 | */ 382 | static createDidYouMeanSuggestion(input: string, validOptions: string[]): string | undefined { 383 | const suggestions = validOptions.filter(option => 384 | this.levenshteinDistance(input.toLowerCase(), option.toLowerCase()) <= 2 385 | ); 386 | 387 | if (suggestions.length > 0) { 388 | return `Did you mean: ${suggestions.slice(0, 3).join(', ')}?`; 389 | } 390 | 391 | return undefined; 392 | } 393 | 394 | /** 395 | * Calculate Levenshtein distance for typo detection 396 | */ 397 | private static levenshteinDistance(str1: string, str2: string): number { 398 | const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null)); 399 | 400 | for (let i = 0; i <= str1.length; i += 1) { 401 | matrix[0][i] = i; 402 | } 403 | 404 | for (let j = 0; j <= str2.length; j += 1) { 405 | matrix[j][0] = j; 406 | } 407 | 408 | for (let j = 1; j <= str2.length; j += 1) { 409 | for (let i = 1; i <= str1.length; i += 1) { 410 | const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; 411 | matrix[j][i] = Math.min( 412 | matrix[j][i - 1] + 1, // deletion 413 | matrix[j - 1][i] + 1, // insertion 414 | matrix[j - 1][i - 1] + indicator // substitution 415 | ); 416 | } 417 | } 418 | 419 | return matrix[str2.length][str1.length]; 420 | } 421 | } 422 | 423 | // Export default error handler instance 424 | export const errorHandler = ErrorHandler.getInstance(); 425 | 426 | // Standardized error handling (backwards compatible) 427 | export function handleError( 428 | error: unknown, 429 | context: string, 430 | logger: Logger 431 | ): { message: string; isError: boolean } { 432 | // Enhanced handling with new error types 433 | if (error instanceof BaseError) { 434 | const logLevel = error.code === 'VALIDATION_ERROR' || error.code === 'ARGUMENT_ERROR' ? 'warn' : 'error'; 435 | logger[logLevel](`${context}: ${error.message}`); 436 | return { message: error.getEnhancedMessage(), isError: error.code !== 'ARGUMENT_ERROR' }; 437 | } else if (error instanceof PromptError) { 438 | logger.error(`${context}: ${error.message}`); 439 | return { message: error.message, isError: true }; 440 | } else if (error instanceof ArgumentError) { 441 | logger.warn(`${context}: ${error.message}`); 442 | return { message: error.message, isError: false }; 443 | } else if (error instanceof ValidationError) { 444 | logger.warn(`${context}: ${error.message}`); 445 | const errors = error.validationErrors ? `: ${error.validationErrors.join(', ')}` : ''; 446 | return { message: `${error.message}${errors}`, isError: false }; 447 | } else { 448 | const errorMessage = error instanceof Error ? error.message : String(error); 449 | logger.error(`${context}: ${errorMessage}`); 450 | return { message: `Unexpected error: ${errorMessage}`, isError: true }; 451 | } 452 | } ``` -------------------------------------------------------------------------------- /server/src/performance/monitor.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Performance Monitoring System 3 | * 4 | * Comprehensive performance tracking and optimization for the MCP server 5 | * Focuses on execution metrics, memory usage, and system health monitoring 6 | */ 7 | 8 | import * as os from "os"; 9 | import { Logger } from "../logging/index.js"; 10 | // REMOVED: ExecutionCoordinator - modular chain system removed 11 | 12 | export interface PerformanceMetrics { 13 | timestamp: number; 14 | 15 | // Memory metrics 16 | memory: { 17 | heapUsed: number; 18 | heapTotal: number; 19 | external: number; 20 | rss: number; 21 | }; 22 | 23 | // Execution metrics 24 | execution: { 25 | totalExecutions: number; 26 | averageExecutionTime: number; 27 | successRate: number; 28 | activeExecutions: number; 29 | }; 30 | 31 | // System metrics 32 | system: { 33 | uptime: number; 34 | cpuUsage: number[]; // [user percentage, system percentage] 35 | loadAverage: number[]; 36 | }; 37 | 38 | // Chain-specific metrics 39 | chains: { 40 | activeChains: number; 41 | averageChainLength: number; 42 | chainSuccessRate: number; 43 | averageChainExecutionTime: number; 44 | }; 45 | } 46 | 47 | export interface PerformanceThresholds { 48 | memoryThreshold: number; // MB 49 | executionTimeThreshold: number; // ms 50 | successRateThreshold: number; // percentage 51 | chainExecutionTimeThreshold: number; // ms 52 | } 53 | 54 | export interface PerformanceAlert { 55 | level: 'warning' | 'error' | 'critical'; 56 | category: 'memory' | 'execution' | 'chains' | 'system'; 57 | message: string; 58 | timestamp: number; 59 | metrics?: Partial<PerformanceMetrics>; 60 | recommendation?: string; 61 | } 62 | 63 | /** 64 | * Performance monitoring and optimization system 65 | */ 66 | export class PerformanceMonitor { 67 | private logger: Logger; 68 | // REMOVED: executionCoordinator - modular chain system removed 69 | 70 | // Performance tracking 71 | private metricsHistory: PerformanceMetrics[] = []; 72 | private maxHistorySize = 1000; // Keep last 1000 measurements 73 | private monitoringInterval?: NodeJS.Timeout; 74 | private alertingCallbacks: ((alert: PerformanceAlert) => void)[] = []; 75 | 76 | // Performance thresholds 77 | private thresholds: PerformanceThresholds = { 78 | memoryThreshold: 512, // MB 79 | executionTimeThreshold: 5000, // 5 seconds 80 | successRateThreshold: 95, // 95% 81 | chainExecutionTimeThreshold: 30000 // 30 seconds 82 | }; 83 | 84 | // CPU tracking for delta calculation 85 | private previousCpuUsage: NodeJS.CpuUsage | null = null; 86 | private previousCpuTime = 0; 87 | 88 | // Optimization state 89 | private optimizationScheduled = false; 90 | private lastOptimization = 0; 91 | private optimizationInterval = 300000; // 5 minutes 92 | 93 | constructor(logger: Logger, thresholds?: Partial<PerformanceThresholds>) { 94 | this.logger = logger; 95 | 96 | if (thresholds) { 97 | this.thresholds = { ...this.thresholds, ...thresholds }; 98 | } 99 | } 100 | 101 | // REMOVED: setExecutionCoordinator - ExecutionCoordinator removed 102 | 103 | /** 104 | * Check if we're running in a test environment 105 | */ 106 | private isTestEnvironment(): boolean { 107 | return ( 108 | process.env.NODE_ENV === 'test' || 109 | process.argv.includes('--suppress-debug') || 110 | process.argv.includes('--test-mode') || 111 | // Detect GitHub Actions CI environment 112 | process.env.GITHUB_ACTIONS === 'true' || 113 | process.env.CI === 'true' || 114 | // Detect common test runner patterns 115 | process.argv.some(arg => arg.includes('test') || arg.includes('jest') || arg.includes('mocha')) || 116 | // Detect if called from integration test scripts 117 | process.argv[1]?.includes('tests/scripts/') 118 | ); 119 | } 120 | 121 | /** 122 | * Start performance monitoring 123 | * SUPPRESSED in test environments to prevent hanging processes 124 | */ 125 | startMonitoring(intervalMs: number = 30000): void { // Default: 30 seconds 126 | if (this.monitoringInterval) { 127 | this.stopMonitoring(); 128 | } 129 | 130 | // Skip performance monitoring in test environments to prevent hanging processes 131 | if (this.isTestEnvironment()) { 132 | this.logger.debug("Performance monitoring suppressed in test environment"); 133 | return; 134 | } 135 | 136 | this.logger.info(`Starting performance monitoring (interval: ${intervalMs}ms)`); 137 | 138 | // Take initial measurement 139 | this.collectMetrics(); 140 | 141 | // Set up regular monitoring 142 | this.monitoringInterval = setInterval(() => { 143 | this.collectMetrics(); 144 | this.checkThresholds(); 145 | this.scheduleOptimization(); 146 | }, intervalMs); 147 | } 148 | 149 | /** 150 | * Stop performance monitoring 151 | */ 152 | stopMonitoring(): void { 153 | if (this.monitoringInterval) { 154 | clearInterval(this.monitoringInterval); 155 | this.monitoringInterval = undefined; 156 | this.logger.info("Performance monitoring stopped"); 157 | } 158 | } 159 | 160 | /** 161 | * Collect current performance metrics 162 | */ 163 | collectMetrics(): PerformanceMetrics { 164 | const timestamp = Date.now(); 165 | const memoryUsage = process.memoryUsage(); 166 | const currentCpuUsage = process.cpuUsage(); 167 | 168 | // Calculate CPU percentage from deltas 169 | let cpuPercentage = [0, 0]; // [user%, system%] 170 | if (this.previousCpuUsage && this.previousCpuTime) { 171 | const timeDelta = timestamp - this.previousCpuTime; // milliseconds 172 | const userDelta = currentCpuUsage.user - this.previousCpuUsage.user; // microseconds 173 | const systemDelta = currentCpuUsage.system - this.previousCpuUsage.system; // microseconds 174 | 175 | if (timeDelta > 0) { 176 | // Convert to percentages (microseconds to milliseconds, then percentage) 177 | const userPercent = (userDelta / 1000) / timeDelta * 100; 178 | const systemPercent = (systemDelta / 1000) / timeDelta * 100; 179 | cpuPercentage = [Math.min(userPercent, 100), Math.min(systemPercent, 100)]; 180 | } 181 | } 182 | 183 | // Store current values for next calculation 184 | this.previousCpuUsage = currentCpuUsage; 185 | this.previousCpuTime = timestamp; 186 | 187 | // Get execution metrics if available 188 | let executionMetrics = { 189 | totalExecutions: 0, 190 | averageExecutionTime: 0, 191 | successRate: 100, 192 | activeExecutions: 0 193 | }; 194 | 195 | // REMOVED: ExecutionCoordinator metrics - execution handled by ConsolidatedPromptEngine 196 | // Default execution metrics since ExecutionCoordinator removed 197 | 198 | const metrics: PerformanceMetrics = { 199 | timestamp, 200 | memory: { 201 | heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024), // MB 202 | heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024), // MB 203 | external: Math.round(memoryUsage.external / 1024 / 1024), // MB 204 | rss: Math.round(memoryUsage.rss / 1024 / 1024) // MB 205 | }, 206 | execution: executionMetrics, 207 | system: { 208 | uptime: Math.round(process.uptime()), 209 | cpuUsage: cpuPercentage, // CPU percentage [user%, system%] 210 | loadAverage: process.platform !== 'win32' ? os.loadavg() : [0, 0, 0] 211 | }, 212 | chains: this.calculateChainMetrics() 213 | }; 214 | 215 | // Store metrics with history management 216 | this.metricsHistory.push(metrics); 217 | if (this.metricsHistory.length > this.maxHistorySize) { 218 | this.metricsHistory.shift(); 219 | } 220 | 221 | this.logger.debug(`Performance metrics collected - Memory: ${metrics.memory.heapUsed}MB, Executions: ${metrics.execution.totalExecutions}`); 222 | 223 | return metrics; 224 | } 225 | 226 | /** 227 | * Get performance metrics history 228 | */ 229 | getMetricsHistory(count?: number): PerformanceMetrics[] { 230 | if (count && count < this.metricsHistory.length) { 231 | return this.metricsHistory.slice(-count); 232 | } 233 | return [...this.metricsHistory]; 234 | } 235 | 236 | /** 237 | * Get latest performance metrics 238 | */ 239 | getLatestMetrics(): PerformanceMetrics | undefined { 240 | return this.metricsHistory[this.metricsHistory.length - 1]; 241 | } 242 | 243 | /** 244 | * Register alerting callback 245 | */ 246 | onAlert(callback: (alert: PerformanceAlert) => void): void { 247 | this.alertingCallbacks.push(callback); 248 | } 249 | 250 | /** 251 | * Get performance summary over time period 252 | */ 253 | getPerformanceSummary(periodMs: number = 3600000): { // Default: 1 hour 254 | averageMemory: number; 255 | peakMemory: number; 256 | averageExecutionTime: number; 257 | totalExecutions: number; 258 | successRate: number; 259 | alertsGenerated: number; 260 | } | undefined { 261 | const cutoffTime = Date.now() - periodMs; 262 | const relevantMetrics = this.metricsHistory.filter(m => m.timestamp >= cutoffTime); 263 | 264 | if (relevantMetrics.length === 0) { 265 | return undefined; 266 | } 267 | 268 | const avgMemory = relevantMetrics.reduce((sum, m) => sum + m.memory.heapUsed, 0) / relevantMetrics.length; 269 | const peakMemory = Math.max(...relevantMetrics.map(m => m.memory.heapUsed)); 270 | 271 | const latestMetrics = relevantMetrics[relevantMetrics.length - 1]; 272 | const earliestMetrics = relevantMetrics[0]; 273 | 274 | const totalExecutions = latestMetrics.execution.totalExecutions - earliestMetrics.execution.totalExecutions; 275 | 276 | return { 277 | averageMemory: Math.round(avgMemory), 278 | peakMemory, 279 | averageExecutionTime: latestMetrics.execution.averageExecutionTime, 280 | totalExecutions, 281 | successRate: latestMetrics.execution.successRate, 282 | alertsGenerated: 0 // Could be implemented with alert history 283 | }; 284 | } 285 | 286 | /** 287 | * Force performance optimization 288 | */ 289 | async optimizePerformance(): Promise<{ 290 | memoryFreed: number; 291 | optimizationsApplied: string[]; 292 | }> { 293 | const beforeMemory = process.memoryUsage().heapUsed; 294 | const optimizations: string[] = []; 295 | 296 | this.logger.info("Starting performance optimization"); 297 | 298 | // 1. Trigger garbage collection if available 299 | if (global.gc) { 300 | global.gc(); 301 | optimizations.push("Garbage collection executed"); 302 | } 303 | 304 | // REMOVED: Execution history cleanup - ExecutionCoordinator removed 305 | 306 | // 3. Trim metrics history if needed 307 | if (this.metricsHistory.length > this.maxHistorySize * 0.8) { 308 | const trimCount = Math.floor(this.metricsHistory.length * 0.2); 309 | this.metricsHistory.splice(0, trimCount); 310 | optimizations.push(`Metrics history trimmed (${trimCount} entries)`); 311 | } 312 | 313 | const afterMemory = process.memoryUsage().heapUsed; 314 | const memoryFreed = Math.max(0, beforeMemory - afterMemory); 315 | 316 | this.lastOptimization = Date.now(); 317 | this.optimizationScheduled = false; 318 | 319 | this.logger.info(`Performance optimization completed - ${optimizations.length} optimizations applied, ${Math.round(memoryFreed / 1024 / 1024)}MB freed`); 320 | 321 | return { 322 | memoryFreed: Math.round(memoryFreed / 1024 / 1024), // MB 323 | optimizationsApplied: optimizations 324 | }; 325 | } 326 | 327 | /** 328 | * Calculate chain-specific metrics 329 | */ 330 | private calculateChainMetrics(): PerformanceMetrics['chains'] { 331 | // REMOVED: ExecutionCoordinator chain metrics - using defaults 332 | // Chain metrics now tracked by ConsolidatedPromptEngine if needed 333 | return { 334 | activeChains: 0, 335 | averageChainLength: 0, 336 | chainSuccessRate: 100, 337 | averageChainExecutionTime: 0 338 | }; 339 | } 340 | 341 | /** 342 | * Check performance thresholds and generate alerts 343 | */ 344 | private checkThresholds(): void { 345 | const latest = this.getLatestMetrics(); 346 | if (!latest) return; 347 | 348 | const alerts: PerformanceAlert[] = []; 349 | 350 | // Memory threshold check 351 | if (latest.memory.heapUsed > this.thresholds.memoryThreshold) { 352 | alerts.push({ 353 | level: latest.memory.heapUsed > this.thresholds.memoryThreshold * 1.5 ? 'critical' : 'warning', 354 | category: 'memory', 355 | message: `High memory usage: ${latest.memory.heapUsed}MB (threshold: ${this.thresholds.memoryThreshold}MB)`, 356 | timestamp: latest.timestamp, 357 | metrics: { memory: latest.memory }, 358 | recommendation: 'Consider running performance optimization or reducing execution concurrency' 359 | }); 360 | } 361 | 362 | // Execution time threshold check 363 | if (latest.execution.averageExecutionTime > this.thresholds.executionTimeThreshold) { 364 | alerts.push({ 365 | level: 'warning', 366 | category: 'execution', 367 | message: `Slow execution times: ${latest.execution.averageExecutionTime}ms average (threshold: ${this.thresholds.executionTimeThreshold}ms)`, 368 | timestamp: latest.timestamp, 369 | metrics: { execution: latest.execution }, 370 | recommendation: 'Review prompt complexity and chain configurations' 371 | }); 372 | } 373 | 374 | // Success rate threshold check 375 | if (latest.execution.successRate < this.thresholds.successRateThreshold) { 376 | alerts.push({ 377 | level: 'error', 378 | category: 'execution', 379 | message: `Low success rate: ${latest.execution.successRate.toFixed(1)}% (threshold: ${this.thresholds.successRateThreshold}%)`, 380 | timestamp: latest.timestamp, 381 | metrics: { execution: latest.execution }, 382 | recommendation: 'Investigate execution failures and improve error handling' 383 | }); 384 | } 385 | 386 | // Chain execution time threshold check 387 | if (latest.chains.averageChainExecutionTime > this.thresholds.chainExecutionTimeThreshold) { 388 | alerts.push({ 389 | level: 'warning', 390 | category: 'chains', 391 | message: `Slow chain execution: ${latest.chains.averageChainExecutionTime}ms average (threshold: ${this.thresholds.chainExecutionTimeThreshold}ms)`, 392 | timestamp: latest.timestamp, 393 | metrics: { chains: latest.chains }, 394 | recommendation: 'Optimize chain steps and reduce chain complexity' 395 | }); 396 | } 397 | 398 | // Send alerts 399 | alerts.forEach(alert => { 400 | this.logger.warn(`Performance alert [${alert.level}]: ${alert.message}`); 401 | this.alertingCallbacks.forEach(callback => callback(alert)); 402 | }); 403 | } 404 | 405 | /** 406 | * Schedule performance optimization if needed 407 | */ 408 | private scheduleOptimization(): void { 409 | if (this.optimizationScheduled) return; 410 | 411 | const timeSinceLastOptimization = Date.now() - this.lastOptimization; 412 | const latest = this.getLatestMetrics(); 413 | 414 | if (!latest) return; 415 | 416 | // Schedule optimization if: 417 | // 1. It's been long enough since last optimization 418 | // 2. Memory usage is high 419 | // 3. Execution history is large 420 | const shouldOptimize = ( 421 | timeSinceLastOptimization > this.optimizationInterval || 422 | latest.memory.heapUsed > this.thresholds.memoryThreshold * 0.8 423 | // REMOVED: Execution history check - ExecutionCoordinator removed 424 | ); 425 | 426 | if (shouldOptimize) { 427 | this.optimizationScheduled = true; 428 | 429 | // Schedule optimization in next tick to avoid blocking current operations 430 | setImmediate(() => { 431 | this.optimizePerformance().catch(error => { 432 | this.logger.error("Performance optimization failed:", error); 433 | this.optimizationScheduled = false; 434 | }); 435 | }); 436 | } 437 | } 438 | } 439 | 440 | /** 441 | * Factory function to create a performance monitor 442 | */ 443 | export function createPerformanceMonitor( 444 | logger: Logger, 445 | thresholds?: Partial<PerformanceThresholds> 446 | ): PerformanceMonitor { 447 | return new PerformanceMonitor(logger, thresholds); 448 | } ``` -------------------------------------------------------------------------------- /server/src/frameworks/prompt-guidance/service.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Prompt Guidance Service - Phase 3 Implementation 3 | * 4 | * Unified service that orchestrates all prompt guidance components. 5 | * Provides a single integration point for MCP tools to access methodology guidance. 6 | */ 7 | 8 | import { Logger } from "../../logging/index.js"; 9 | import { ConvertedPrompt } from "../../types/index.js"; 10 | import { ContentAnalysisResult } from "../../semantic/configurable-semantic-analyzer.js"; 11 | import { 12 | SystemPromptInjector, 13 | createSystemPromptInjector 14 | } from "./system-prompt-injector.js"; 15 | import { 16 | MethodologyTracker, 17 | createMethodologyTracker, 18 | type MethodologyTrackerConfig 19 | } from "./methodology-tracker.js"; 20 | import { 21 | TemplateEnhancer, 22 | createTemplateEnhancer 23 | } from "./template-enhancer.js"; 24 | import { 25 | SystemPromptInjectionResult, 26 | TemplateEnhancementResult, 27 | MethodologyState, 28 | MethodologySwitchRequest 29 | } from "../types/index.js"; 30 | import { 31 | FrameworkDefinition, 32 | IMethodologyGuide 33 | } from "../types/index.js"; 34 | import { FrameworkManager } from "../framework-manager.js"; 35 | 36 | /** 37 | * Prompt guidance service configuration 38 | */ 39 | type MethodologyTrackingServiceConfig = Partial<MethodologyTrackerConfig> & { 40 | enabled: boolean; 41 | }; 42 | 43 | export interface PromptGuidanceServiceConfig { 44 | systemPromptInjection: { 45 | enabled: boolean; 46 | injectionMethod: 'template' | 'append' | 'prepend' | 'smart'; 47 | enableTemplateVariables: boolean; 48 | enableContextualEnhancement: boolean; 49 | }; 50 | templateEnhancement: { 51 | enabled: boolean; 52 | enhancementLevel: 'minimal' | 'moderate' | 'comprehensive'; 53 | enableArgumentSuggestions: boolean; 54 | enableStructureOptimization: boolean; 55 | }; 56 | methodologyTracking: MethodologyTrackingServiceConfig; 57 | } 58 | 59 | /** 60 | * Comprehensive prompt guidance result 61 | */ 62 | export interface PromptGuidanceResult { 63 | originalPrompt: ConvertedPrompt; 64 | enhancedPrompt?: ConvertedPrompt; 65 | systemPromptInjection?: SystemPromptInjectionResult; 66 | templateEnhancement?: TemplateEnhancementResult; 67 | activeMethodology: string; 68 | guidanceApplied: boolean; 69 | processingTimeMs: number; 70 | metadata: { 71 | frameworkUsed: string; 72 | enhancementsApplied: string[]; 73 | confidenceScore: number; 74 | // Phase 4: Semantic analysis metadata 75 | semanticAware?: boolean; 76 | semanticComplexity?: 'low' | 'medium' | 'high'; 77 | semanticConfidence?: number; 78 | }; 79 | } 80 | 81 | /** 82 | * Prompt Guidance Service 83 | * 84 | * Orchestrates all prompt guidance components to provide intelligent 85 | * methodology-driven prompt enhancement for MCP tools. 86 | */ 87 | export class PromptGuidanceService { 88 | private logger: Logger; 89 | private config: PromptGuidanceServiceConfig; 90 | private systemPromptInjector: SystemPromptInjector; 91 | private methodologyTracker!: MethodologyTracker; 92 | private templateEnhancer: TemplateEnhancer; 93 | private frameworkManager?: FrameworkManager; 94 | private initialized: boolean = false; 95 | 96 | constructor(logger: Logger, config?: Partial<PromptGuidanceServiceConfig>) { 97 | this.logger = logger; 98 | this.config = { 99 | systemPromptInjection: { 100 | enabled: true, 101 | injectionMethod: 'smart', 102 | enableTemplateVariables: true, 103 | enableContextualEnhancement: true 104 | }, 105 | templateEnhancement: { 106 | enabled: true, 107 | enhancementLevel: 'moderate', 108 | enableArgumentSuggestions: true, 109 | enableStructureOptimization: true 110 | }, 111 | methodologyTracking: { 112 | enabled: true, 113 | persistStateToDisk: true, 114 | enableHealthMonitoring: true, 115 | healthCheckIntervalMs: 30000, 116 | maxSwitchHistory: 100, 117 | enableMetrics: true 118 | }, 119 | ...config 120 | }; 121 | 122 | // Initialize components 123 | this.systemPromptInjector = createSystemPromptInjector(logger, this.config.systemPromptInjection); 124 | this.templateEnhancer = createTemplateEnhancer(logger, this.config.templateEnhancement); 125 | } 126 | 127 | /** 128 | * Initialize the prompt guidance service 129 | */ 130 | async initialize(frameworkManager?: FrameworkManager): Promise<void> { 131 | if (this.initialized) { 132 | this.logger.debug("PromptGuidanceService already initialized"); 133 | return; 134 | } 135 | 136 | this.logger.info("Initializing PromptGuidanceService..."); 137 | 138 | try { 139 | // Initialize methodology tracker 140 | const { enabled: trackingEnabled, ...trackerConfig } = 141 | this.config.methodologyTracking; 142 | this.methodologyTracker = await createMethodologyTracker( 143 | this.logger, 144 | trackerConfig 145 | ); 146 | 147 | if (!trackingEnabled) { 148 | this.logger.info( 149 | "Prompt guidance methodology tracking initialized but marked disabled in config" 150 | ); 151 | } 152 | 153 | // Set framework manager if provided 154 | if (frameworkManager) { 155 | this.frameworkManager = frameworkManager; 156 | } 157 | 158 | this.initialized = true; 159 | this.logger.info("PromptGuidanceService initialized successfully"); 160 | 161 | } catch (error) { 162 | this.logger.error("Failed to initialize PromptGuidanceService:", error); 163 | throw error; 164 | } 165 | } 166 | 167 | /** 168 | * Apply comprehensive prompt guidance to a prompt 169 | * Phase 4: Enhanced with semantic analysis integration 170 | */ 171 | async applyGuidance( 172 | prompt: ConvertedPrompt, 173 | options: { 174 | includeSystemPromptInjection?: boolean; 175 | includeTemplateEnhancement?: boolean; 176 | frameworkOverride?: string; 177 | semanticAnalysis?: ContentAnalysisResult; 178 | } = {} 179 | ): Promise<PromptGuidanceResult> { 180 | const startTime = Date.now(); 181 | 182 | if (!this.initialized) { 183 | throw new Error("PromptGuidanceService not initialized. Call initialize() first."); 184 | } 185 | 186 | this.logger.debug(`Applying prompt guidance for prompt: ${prompt.name}${options.semanticAnalysis ? ' with semantic analysis' : ''}`); 187 | 188 | try { 189 | // Get current methodology state 190 | const methodologyState = this.methodologyTracker.getCurrentState(); 191 | const activeFramework = await this.getActiveFramework(options.frameworkOverride); 192 | const methodologyGuide = await this.getMethodologyGuide(activeFramework.methodology); 193 | 194 | const result: PromptGuidanceResult = { 195 | originalPrompt: prompt, 196 | activeMethodology: methodologyState.activeMethodology, 197 | guidanceApplied: false, 198 | processingTimeMs: 0, 199 | metadata: { 200 | frameworkUsed: activeFramework.methodology, 201 | enhancementsApplied: [], 202 | confidenceScore: 0, 203 | // Phase 4: Semantic analysis metadata 204 | semanticAware: options.semanticAnalysis !== undefined, 205 | semanticComplexity: options.semanticAnalysis?.complexity, 206 | semanticConfidence: options.semanticAnalysis?.confidence 207 | } 208 | }; 209 | 210 | let enhancedPrompt = { ...prompt }; 211 | let totalConfidence = 0; 212 | let enhancementCount = 0; 213 | 214 | // Apply system prompt injection if enabled 215 | if (this.config.systemPromptInjection.enabled && 216 | (options.includeSystemPromptInjection !== false)) { 217 | 218 | try { 219 | const injectionResult = this.systemPromptInjector.injectMethodologyGuidance( 220 | prompt, 221 | activeFramework, 222 | methodologyGuide, 223 | options.semanticAnalysis 224 | ); 225 | 226 | result.systemPromptInjection = injectionResult; 227 | 228 | // FIXED: Combine original system message with framework-injected guidance 229 | // This preserves the original prompt's system message while adding framework context 230 | const originalSystemMessage = prompt.systemMessage || ''; 231 | const frameworkGuidance = injectionResult.enhancedPrompt; 232 | 233 | // Combine both: framework guidance first (sets context), then original system message 234 | enhancedPrompt.systemMessage = originalSystemMessage 235 | ? `${frameworkGuidance}\n\n${originalSystemMessage}` 236 | : frameworkGuidance; 237 | 238 | result.metadata.enhancementsApplied.push('system_prompt_injection'); 239 | totalConfidence += injectionResult.metadata.confidence; 240 | enhancementCount++; 241 | result.guidanceApplied = true; 242 | 243 | this.logger.debug(`System prompt injection applied with confidence: ${injectionResult.metadata.confidence}`); 244 | 245 | } catch (error) { 246 | this.logger.warn("System prompt injection failed:", error); 247 | } 248 | } 249 | 250 | // Apply template enhancement if enabled 251 | if (this.config.templateEnhancement.enabled && 252 | (options.includeTemplateEnhancement !== false)) { 253 | 254 | try { 255 | const enhancementResult = await this.templateEnhancer.enhanceTemplate( 256 | enhancedPrompt.userMessageTemplate, 257 | enhancedPrompt, 258 | methodologyGuide, 259 | activeFramework, 260 | undefined, // context 261 | options.semanticAnalysis 262 | ); 263 | 264 | result.templateEnhancement = enhancementResult; 265 | 266 | // Update enhanced prompt with enhanced template 267 | enhancedPrompt.userMessageTemplate = enhancementResult.enhancedTemplate; 268 | 269 | result.metadata.enhancementsApplied.push('template_enhancement'); 270 | totalConfidence += enhancementResult.validation.score / 100; // Convert to 0-1 scale 271 | enhancementCount++; 272 | result.guidanceApplied = true; 273 | 274 | this.logger.debug(`Template enhancement applied with score: ${enhancementResult.validation.score}`); 275 | 276 | } catch (error) { 277 | this.logger.warn("Template enhancement failed:", error); 278 | } 279 | } 280 | 281 | // Set enhanced prompt and calculate metrics 282 | if (result.guidanceApplied) { 283 | result.enhancedPrompt = enhancedPrompt; 284 | result.metadata.confidenceScore = enhancementCount > 0 ? totalConfidence / enhancementCount : 0; 285 | } 286 | 287 | result.processingTimeMs = Date.now() - startTime; 288 | 289 | this.logger.debug(`Prompt guidance completed in ${result.processingTimeMs}ms with confidence: ${result.metadata.confidenceScore}`); 290 | return result; 291 | 292 | } catch (error) { 293 | this.logger.error("Failed to apply prompt guidance:", error); 294 | 295 | // Return minimal result on error 296 | return { 297 | originalPrompt: prompt, 298 | activeMethodology: this.methodologyTracker?.getCurrentState()?.activeMethodology || 'CAGEERF', 299 | guidanceApplied: false, 300 | processingTimeMs: Date.now() - startTime, 301 | metadata: { 302 | frameworkUsed: 'error', 303 | enhancementsApplied: [], 304 | confidenceScore: 0 305 | } 306 | }; 307 | } 308 | } 309 | 310 | /** 311 | * Switch methodology using the tracker 312 | */ 313 | async switchMethodology(request: MethodologySwitchRequest): Promise<boolean> { 314 | if (!this.initialized) { 315 | throw new Error("PromptGuidanceService not initialized"); 316 | } 317 | 318 | this.logger.info(`Switching methodology to: ${request.targetMethodology}`); 319 | return await this.methodologyTracker.switchMethodology(request); 320 | } 321 | 322 | /** 323 | * Get current methodology state 324 | */ 325 | getCurrentMethodologyState(): MethodologyState { 326 | if (!this.initialized) { 327 | throw new Error("PromptGuidanceService not initialized"); 328 | } 329 | 330 | return this.methodologyTracker.getCurrentState(); 331 | } 332 | 333 | /** 334 | * Get methodology system health 335 | */ 336 | getSystemHealth() { 337 | if (!this.initialized) { 338 | throw new Error("PromptGuidanceService not initialized"); 339 | } 340 | 341 | return this.methodologyTracker.getSystemHealth(); 342 | } 343 | 344 | /** 345 | * Enable or disable the entire guidance system 346 | */ 347 | setGuidanceEnabled(enabled: boolean): void { 348 | this.config.systemPromptInjection.enabled = enabled; 349 | this.config.templateEnhancement.enabled = enabled; 350 | this.config.methodologyTracking.enabled = enabled; 351 | 352 | this.logger.info(`Prompt guidance system ${enabled ? 'enabled' : 'disabled'}`); 353 | } 354 | 355 | /** 356 | * Update service configuration 357 | */ 358 | updateConfig(config: Partial<PromptGuidanceServiceConfig>): void { 359 | this.config = { ...this.config, ...config }; 360 | 361 | // Update component configurations 362 | this.systemPromptInjector.updateConfig(config.systemPromptInjection || {}); 363 | this.templateEnhancer.updateConfig(config.templateEnhancement || {}); 364 | 365 | if (config.methodologyTracking && this.methodologyTracker) { 366 | const { enabled: trackingEnabled, ...trackerConfig } = 367 | config.methodologyTracking; 368 | 369 | if (typeof trackingEnabled === 'boolean') { 370 | this.config.methodologyTracking.enabled = trackingEnabled; 371 | } 372 | 373 | this.methodologyTracker.updateConfig(trackerConfig); 374 | } 375 | 376 | this.logger.debug("PromptGuidanceService configuration updated"); 377 | } 378 | 379 | /** 380 | * Shutdown the service and cleanup resources 381 | */ 382 | async shutdown(): Promise<void> { 383 | if (!this.initialized) { 384 | return; 385 | } 386 | 387 | this.logger.info("Shutting down PromptGuidanceService..."); 388 | 389 | if (this.methodologyTracker) { 390 | await this.methodologyTracker.shutdown(); 391 | } 392 | 393 | this.initialized = false; 394 | this.logger.info("PromptGuidanceService shutdown complete"); 395 | } 396 | 397 | /** 398 | * Set framework manager for guidance operations 399 | */ 400 | setFrameworkManager(frameworkManager: FrameworkManager): void { 401 | this.frameworkManager = frameworkManager; 402 | this.logger.debug("FrameworkManager set for PromptGuidanceService"); 403 | } 404 | 405 | /** 406 | * Check if service is initialized and ready 407 | */ 408 | isInitialized(): boolean { 409 | return this.initialized; 410 | } 411 | 412 | /** 413 | * Get current service configuration 414 | */ 415 | getConfig(): PromptGuidanceServiceConfig { 416 | return { ...this.config }; 417 | } 418 | 419 | /** 420 | * Get active framework definition 421 | */ 422 | private async getActiveFramework(frameworkOverride?: string): Promise<FrameworkDefinition> { 423 | if (!this.frameworkManager) { 424 | throw new Error("FrameworkManager not set"); 425 | } 426 | 427 | const methodologyState = this.methodologyTracker.getCurrentState(); 428 | const targetMethodology = frameworkOverride || methodologyState.activeMethodology; 429 | 430 | const framework = this.frameworkManager.getFramework(targetMethodology); 431 | if (!framework) { 432 | throw new Error(`Framework ${targetMethodology} not found`); 433 | } 434 | 435 | return framework; 436 | } 437 | 438 | /** 439 | * Get methodology guide for framework 440 | */ 441 | private async getMethodologyGuide(methodology: string): Promise<IMethodologyGuide> { 442 | if (!this.frameworkManager) { 443 | throw new Error("FrameworkManager not set"); 444 | } 445 | 446 | const guide = this.frameworkManager.getMethodologyGuide(methodology); 447 | if (!guide) { 448 | throw new Error(`Methodology guide for ${methodology} not found`); 449 | } 450 | 451 | return guide; 452 | } 453 | } 454 | 455 | /** 456 | * Create and initialize a PromptGuidanceService instance 457 | */ 458 | export async function createPromptGuidanceService( 459 | logger: Logger, 460 | config?: Partial<PromptGuidanceServiceConfig>, 461 | frameworkManager?: FrameworkManager 462 | ): Promise<PromptGuidanceService> { 463 | const service = new PromptGuidanceService(logger, config); 464 | await service.initialize(frameworkManager); 465 | return service; 466 | } 467 | ``` -------------------------------------------------------------------------------- /server/src/frameworks/framework-manager.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Framework Manager - Phase 2 Implementation 3 | * Manages methodology selection and system prompt guidelines 4 | * Framework = Constitutional guidelines for HOW to execute prompts, not analysis tools 5 | */ 6 | 7 | import { Logger } from "../logging/index.js"; 8 | import { ConvertedPrompt } from "../types/index.js"; 9 | import { 10 | IMethodologyGuide, 11 | FrameworkMethodology, 12 | FrameworkDefinition, 13 | FrameworkExecutionContext, 14 | FrameworkSelectionCriteria 15 | } from "./types/index.js"; 16 | import { MethodologyRegistry, createMethodologyRegistry } from "./methodology/index.js"; 17 | import type { FrameworkStateManager } from "./framework-state-manager.js"; 18 | 19 | /** 20 | * Framework Manager Implementation 21 | * Provides methodology selection and system prompt generation 22 | */ 23 | export class FrameworkManager { 24 | private frameworks: Map<string, FrameworkDefinition> = new Map(); 25 | private methodologyRegistry: MethodologyRegistry | null = null; 26 | private defaultFramework: string = "CAGEERF"; 27 | private logger: Logger; 28 | private initialized: boolean = false; 29 | private frameworkStateManager?: FrameworkStateManager; 30 | 31 | constructor(logger: Logger) { 32 | this.logger = logger; 33 | } 34 | 35 | /** 36 | * Set the framework state manager for synchronization 37 | * FIXED: Allows Framework Manager to sync with active framework state 38 | */ 39 | setFrameworkStateManager(frameworkStateManager: FrameworkStateManager): void { 40 | this.frameworkStateManager = frameworkStateManager; 41 | this.logger.debug("Framework State Manager synchronized with Framework Manager"); 42 | } 43 | 44 | /** 45 | * Initialize framework definitions and system templates 46 | */ 47 | async initialize(): Promise<void> { 48 | if (this.initialized) { 49 | this.logger.debug("FrameworkManager already initialized"); 50 | return; 51 | } 52 | 53 | this.logger.info("Initializing FrameworkManager with methodology registry..."); 54 | 55 | // Initialize methodology registry (Phase 2: NEW) 56 | this.methodologyRegistry = await createMethodologyRegistry(this.logger); 57 | 58 | // Generate framework definitions from methodology guides 59 | await this.generateFrameworkDefinitions(); 60 | 61 | this.initialized = true; 62 | this.logger.info(`FrameworkManager initialized with ${this.frameworks.size} frameworks`); 63 | } 64 | 65 | /** 66 | * Select appropriate framework based on criteria 67 | * Simplified selection since frameworks are user-controlled via MCP tools 68 | */ 69 | selectFramework(criteria: FrameworkSelectionCriteria = {}): FrameworkDefinition { 70 | this.ensureInitialized(); 71 | 72 | // User preference takes priority (this is the primary selection mechanism) 73 | if (criteria.userPreference && criteria.userPreference !== "AUTO") { 74 | const preferred = this.getFramework(criteria.userPreference); 75 | if (preferred && preferred.enabled) { 76 | this.logger.debug(`Framework selected by user preference: ${preferred.name}`); 77 | return preferred; 78 | } else { 79 | this.logger.warn(`Requested framework ${criteria.userPreference} not found or disabled, using default`); 80 | } 81 | } 82 | 83 | // FIXED: Check Framework State Manager for active framework before using hardcoded default 84 | // This ensures all injection points get the same active framework 85 | if (this.frameworkStateManager?.isFrameworkSystemEnabled()) { 86 | const activeFramework = this.frameworkStateManager.getActiveFramework(); 87 | if (activeFramework) { 88 | const framework = this.getFramework(activeFramework.methodology); 89 | if (framework && framework.enabled) { 90 | this.logger.debug(`Framework selected: ${framework.name} (from active state manager)`); 91 | return framework; 92 | } 93 | } 94 | } 95 | 96 | // Fallback to default framework only if state manager is not available 97 | const defaultFramework = this.getFramework(this.defaultFramework); 98 | if (!defaultFramework) { 99 | throw new Error(`Default framework ${this.defaultFramework} not found`); 100 | } 101 | 102 | this.logger.debug(`Framework selected: ${defaultFramework.name} (default fallback)`); 103 | return defaultFramework; 104 | } 105 | 106 | /** 107 | * Generate execution context with system prompts and guidelines 108 | */ 109 | generateExecutionContext( 110 | prompt: ConvertedPrompt, 111 | criteria: FrameworkSelectionCriteria = {} 112 | ): FrameworkExecutionContext { 113 | const selectedFramework = this.selectFramework(criteria); 114 | 115 | // Generate framework-specific system prompt 116 | const systemPrompt = this.generateSystemPrompt(selectedFramework, prompt); 117 | 118 | return { 119 | selectedFramework, 120 | systemPrompt, 121 | executionGuidelines: [...selectedFramework.executionGuidelines], 122 | metadata: { 123 | selectionReason: this.getSelectionReason(selectedFramework, criteria), 124 | confidence: 1.0, // High confidence since frameworks are user-selected 125 | appliedAt: new Date() 126 | } 127 | }; 128 | } 129 | 130 | /** 131 | * Get framework by methodology type 132 | * Supports case-insensitive lookup for robust framework switching 133 | */ 134 | getFramework(methodology: string): FrameworkDefinition | undefined { 135 | this.ensureInitialized(); 136 | 137 | // Try exact match first (fastest path) 138 | let framework = this.frameworks.get(methodology); 139 | if (framework) { 140 | this.logger.debug(`Framework found via exact match: ${methodology} -> ${framework.name}`); 141 | return framework; 142 | } 143 | 144 | // Try uppercase match (most common conversion) 145 | const upperCaseId = methodology.toUpperCase(); 146 | framework = this.frameworks.get(upperCaseId); 147 | if (framework) { 148 | this.logger.debug(`Framework found via uppercase match: ${methodology} -> ${framework.name}`); 149 | return framework; 150 | } 151 | 152 | // Try case-insensitive search through all frameworks 153 | for (const [id, def] of this.frameworks) { 154 | if (id.toLowerCase() === methodology.toLowerCase()) { 155 | this.logger.debug(`Framework found via case-insensitive match: ${methodology} -> ${def.name}`); 156 | return def; 157 | } 158 | 159 | // Also check methodology field for additional matching 160 | if (def.methodology.toLowerCase() === methodology.toLowerCase()) { 161 | this.logger.debug(`Framework found via methodology match: ${methodology} -> ${def.name}`); 162 | return def; 163 | } 164 | } 165 | 166 | // Log available frameworks for debugging 167 | const availableIds = Array.from(this.frameworks.keys()); 168 | this.logger.warn(`Framework '${methodology}' not found. Available frameworks: [${availableIds.join(', ')}]`); 169 | return undefined; 170 | } 171 | 172 | /** 173 | * List available frameworks 174 | */ 175 | listFrameworks(enabledOnly: boolean = false): FrameworkDefinition[] { 176 | const frameworks = Array.from(this.frameworks.values()); 177 | return enabledOnly ? frameworks.filter(f => f.enabled) : frameworks; 178 | } 179 | 180 | /** 181 | * Get methodology guide by framework ID 182 | */ 183 | getMethodologyGuide(frameworkId: string): IMethodologyGuide | undefined { 184 | this.ensureInitialized(); 185 | return this.methodologyRegistry!.getGuide(frameworkId.toLowerCase()); 186 | } 187 | 188 | /** 189 | * List available methodology guides 190 | */ 191 | listMethodologyGuides(): IMethodologyGuide[] { 192 | this.ensureInitialized(); 193 | return this.methodologyRegistry!.getAllGuides(true); 194 | } 195 | 196 | /** 197 | * Check if framework is applicable for given criteria 198 | */ 199 | private isApplicable(framework: FrameworkDefinition, criteria: FrameworkSelectionCriteria): boolean { 200 | // Check execution type compatibility 201 | if (criteria.executionType && framework.applicableTypes.length > 0) { 202 | if (!framework.applicableTypes.includes(criteria.executionType)) { 203 | return false; 204 | } 205 | } 206 | 207 | // All enabled frameworks are generally applicable 208 | return framework.enabled; 209 | } 210 | 211 | /** 212 | * Calculate fit score for framework selection 213 | */ 214 | private calculateFitScore(framework: FrameworkDefinition, criteria: FrameworkSelectionCriteria): number { 215 | let score = framework.priority; 216 | 217 | // Execution type match bonus 218 | if (criteria.executionType && framework.applicableTypes.includes(criteria.executionType)) { 219 | score += 10; 220 | } 221 | 222 | // Complexity match bonus 223 | if (criteria.complexity) { 224 | switch (criteria.complexity) { 225 | case 'high': 226 | if (framework.methodology === 'CAGEERF') score += 15; 227 | break; 228 | case 'medium': 229 | if (framework.methodology === 'ReACT' || framework.methodology === '5W1H') score += 10; 230 | break; 231 | case 'low': 232 | if (framework.methodology === 'SCAMPER') score += 5; 233 | break; 234 | } 235 | } 236 | 237 | return score; 238 | } 239 | 240 | /** 241 | * Generate framework-specific system prompt 242 | */ 243 | private generateSystemPrompt(framework: FrameworkDefinition, prompt: ConvertedPrompt): string { 244 | let systemPrompt = framework.systemPromptTemplate; 245 | 246 | // Replace template variables 247 | systemPrompt = systemPrompt.replace(/\{PROMPT_NAME\}/g, prompt.name || 'Prompt'); 248 | systemPrompt = systemPrompt.replace(/\{PROMPT_CATEGORY\}/g, prompt.category || 'general'); 249 | systemPrompt = systemPrompt.replace(/\{FRAMEWORK_NAME\}/g, framework.name); 250 | 251 | return systemPrompt; 252 | } 253 | 254 | /** 255 | * Get selection reason for context metadata (simplified) 256 | */ 257 | private getSelectionReason(framework: FrameworkDefinition, criteria: FrameworkSelectionCriteria): string { 258 | if (criteria.userPreference && criteria.userPreference !== "AUTO") { 259 | return `User preference: ${criteria.userPreference}`; 260 | } 261 | 262 | return "Default framework selection"; 263 | } 264 | 265 | /** 266 | * Initialize methodology guides registry (REMOVED - Phase 2) 267 | * Functionality moved to MethodologyRegistry for better separation of concerns 268 | */ 269 | 270 | /** 271 | * Generate framework definitions from methodology guides 272 | */ 273 | private async generateFrameworkDefinitions(): Promise<void> { 274 | try { 275 | const guides = this.methodologyRegistry!.getAllGuides(true); 276 | 277 | for (const guide of guides) { 278 | // Generate system prompt template from methodology guide 279 | const systemPromptTemplate = this.generateSystemPromptTemplate(guide); 280 | 281 | // Create framework definition from methodology guide 282 | const frameworkDefinition: FrameworkDefinition = { 283 | id: guide.frameworkId.toUpperCase(), 284 | name: guide.frameworkName, 285 | description: this.getFrameworkDescription(guide), 286 | methodology: guide.methodology as FrameworkMethodology, 287 | systemPromptTemplate, 288 | executionGuidelines: this.getExecutionGuidelines(guide), 289 | applicableTypes: this.getApplicableTypes(guide), 290 | priority: this.getFrameworkPriority(guide), 291 | enabled: true 292 | }; 293 | 294 | this.frameworks.set(frameworkDefinition.id, frameworkDefinition); 295 | this.logger.debug(`Generated framework definition for ${guide.frameworkName}`); 296 | } 297 | 298 | this.logger.info(`Generated ${this.frameworks.size} framework definitions from methodology guides`); 299 | } catch (error) { 300 | this.logger.error("Failed to generate framework definitions:", error); 301 | throw error; 302 | } 303 | } 304 | 305 | /** 306 | * Generate system prompt template from methodology guide 307 | */ 308 | private generateSystemPromptTemplate(guide: IMethodologyGuide): string { 309 | const baseGuidance = guide.getSystemPromptGuidance({}); 310 | return `You are operating under the ${guide.frameworkName} methodology for {PROMPT_NAME}. 311 | 312 | ${baseGuidance} 313 | 314 | Apply this methodology systematically to ensure comprehensive and structured responses.`; 315 | } 316 | 317 | /** 318 | * Get framework description from methodology guide 319 | */ 320 | private getFrameworkDescription(guide: IMethodologyGuide): string { 321 | // Generate descriptions based on methodology type 322 | switch (guide.methodology) { 323 | case "CAGEERF": 324 | return "Comprehensive structured approach: Context, Analysis, Goals, Execution, Evaluation, Refinement, Framework"; 325 | case "ReACT": 326 | return "Reasoning and Acting pattern for systematic problem-solving"; 327 | case "5W1H": 328 | return "Who, What, When, Where, Why, How systematic analysis"; 329 | case "SCAMPER": 330 | return "Creative problem-solving: Substitute, Combine, Adapt, Modify, Put to other uses, Eliminate, Reverse"; 331 | default: 332 | return `${guide.methodology} methodology for systematic approach`; 333 | } 334 | } 335 | 336 | /** 337 | * Get execution guidelines from methodology guide 338 | */ 339 | private getExecutionGuidelines(guide: IMethodologyGuide): string[] { 340 | // Generate basic guidelines from methodology guide context 341 | const processingGuidance = guide.guideTemplateProcessing("", "template"); 342 | return processingGuidance.templateEnhancements.systemPromptAdditions; 343 | } 344 | 345 | /** 346 | * Get applicable types for framework based on methodology 347 | */ 348 | private getApplicableTypes(guide: IMethodologyGuide): string[] { 349 | switch (guide.methodology) { 350 | case "CAGEERF": 351 | return ["chain", "template"]; 352 | case "ReACT": 353 | return ["chain"]; 354 | case "5W1H": 355 | return ["template", "chain"]; 356 | case "SCAMPER": 357 | return ["template"]; 358 | default: 359 | return ["template"]; 360 | } 361 | } 362 | 363 | /** 364 | * Get framework priority based on methodology 365 | */ 366 | private getFrameworkPriority(guide: IMethodologyGuide): number { 367 | switch (guide.methodology) { 368 | case "CAGEERF": 369 | return 10; 370 | case "ReACT": 371 | return 8; 372 | case "5W1H": 373 | return 7; 374 | case "SCAMPER": 375 | return 6; 376 | default: 377 | return 5; 378 | } 379 | } 380 | 381 | /** 382 | * Ensure manager is initialized before use 383 | */ 384 | private ensureInitialized(): void { 385 | if (!this.initialized) { 386 | throw new Error("FrameworkManager not initialized. Call initialize() first."); 387 | } 388 | } 389 | 390 | /** 391 | * Get initialization status 392 | */ 393 | get isInitialized(): boolean { 394 | return this.initialized; 395 | } 396 | 397 | /** 398 | * Enable/disable specific framework 399 | */ 400 | setFrameworkEnabled(methodology: FrameworkMethodology, enabled: boolean): void { 401 | const framework = this.frameworks.get(methodology); 402 | if (framework) { 403 | framework.enabled = enabled; 404 | this.logger.info(`Framework ${methodology} ${enabled ? 'enabled' : 'disabled'}`); 405 | } 406 | } 407 | 408 | /** 409 | * Set default framework 410 | */ 411 | setDefaultFramework(methodology: FrameworkMethodology): void { 412 | if (this.frameworks.has(methodology)) { 413 | this.defaultFramework = methodology; 414 | this.logger.info(`Default framework set to: ${methodology}`); 415 | } else { 416 | throw new Error(`Framework ${methodology} not found`); 417 | } 418 | } 419 | } 420 | 421 | /** 422 | * Create and initialize a FrameworkManager instance 423 | */ 424 | export async function createFrameworkManager(logger: Logger): Promise<FrameworkManager> { 425 | const manager = new FrameworkManager(logger); 426 | await manager.initialize(); 427 | return manager; 428 | } 429 | 430 | // Export types that are used by other modules 431 | export type { FrameworkDefinition, FrameworkExecutionContext, FrameworkSelectionCriteria }; ``` -------------------------------------------------------------------------------- /server/src/mcp-tools/prompt-manager/analysis/gate-analyzer.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Gate Analyzer Module 3 | * 4 | * Analyzes prompt content to suggest appropriate gates and temporary gate definitions. 5 | * Integrates with the temporary gate system to provide intelligent gate recommendations. 6 | */ 7 | 8 | import { Logger } from '../../../logging/index.js'; 9 | import type { ConvertedPrompt } from '../../../execution/types.js'; 10 | import type { TemporaryGateDefinition } from '../../../execution/types.js'; 11 | import type { PromptManagerDependencies } from '../core/types.js'; 12 | 13 | /** 14 | * Gate analysis result 15 | */ 16 | export interface GateAnalysisResult { 17 | /** Recommended persistent gates */ 18 | recommendedGates: string[]; 19 | /** Suggested temporary gates */ 20 | suggestedTemporaryGates: TemporaryGateDefinition[]; 21 | /** Analysis reasoning */ 22 | reasoning: string[]; 23 | /** Confidence score (0.0-1.0) */ 24 | confidence: number; 25 | /** Gate configuration preview */ 26 | gateConfigurationPreview: { 27 | include?: string[]; 28 | exclude?: string[]; 29 | framework_gates?: boolean; 30 | temporary_gates?: TemporaryGateDefinition[]; 31 | }; 32 | } 33 | 34 | /** 35 | * Gate suggestion context 36 | */ 37 | export interface GateSuggestionContext { 38 | /** Execution context type */ 39 | executionType: 'prompt' | 'template' | 'chain'; 40 | /** Prompt category */ 41 | category: string; 42 | /** Framework context */ 43 | framework?: string; 44 | /** User intent keywords */ 45 | intentKeywords?: string[]; 46 | /** Complexity level */ 47 | complexity: 'low' | 'medium' | 'high'; 48 | } 49 | 50 | /** 51 | * Analyzes prompts for gate recommendations 52 | */ 53 | export class GateAnalyzer { 54 | private logger: Logger; 55 | private dependencies: PromptManagerDependencies; 56 | 57 | constructor(dependencies: PromptManagerDependencies) { 58 | this.logger = dependencies.logger; 59 | this.dependencies = dependencies; 60 | } 61 | 62 | /** 63 | * Analyze a prompt for gate recommendations 64 | */ 65 | async analyzePromptForGates(prompt: ConvertedPrompt): Promise<GateAnalysisResult> { 66 | this.logger.debug('[GATE ANALYZER] Analyzing prompt for gate recommendations:', { 67 | promptId: prompt.id, 68 | category: prompt.category, 69 | hasChainSteps: !!(prompt.chainSteps?.length), 70 | argumentsCount: prompt.arguments?.length || 0 71 | }); 72 | 73 | // Extract context from prompt 74 | const context = this.extractGateSuggestionContext(prompt); 75 | 76 | // Analyze content for gate requirements 77 | const contentAnalysis = this.analyzePromptContent(prompt); 78 | 79 | // Generate gate recommendations 80 | const recommendedGates = this.generateGateRecommendations(context, contentAnalysis); 81 | 82 | // Generate temporary gate suggestions 83 | const temporaryGates = this.generateTemporaryGateSuggestions(context, contentAnalysis); 84 | 85 | // Calculate confidence based on analysis depth 86 | const confidence = this.calculateConfidence(context, contentAnalysis, recommendedGates.length + temporaryGates.length); 87 | 88 | // Create reasoning 89 | const reasoning = this.generateReasoning(context, contentAnalysis, recommendedGates, temporaryGates); 90 | 91 | // Generate gate configuration preview 92 | const gateConfigurationPreview = this.generateGateConfigurationPreview(recommendedGates, temporaryGates); 93 | 94 | const result: GateAnalysisResult = { 95 | recommendedGates, 96 | suggestedTemporaryGates: temporaryGates, 97 | reasoning, 98 | confidence, 99 | gateConfigurationPreview 100 | }; 101 | 102 | this.logger.debug('[GATE ANALYZER] Analysis complete:', { 103 | promptId: prompt.id, 104 | recommendedGatesCount: recommendedGates.length, 105 | temporaryGatesCount: temporaryGates.length, 106 | confidence 107 | }); 108 | 109 | return result; 110 | } 111 | 112 | /** 113 | * Suggest gates for a specific execution context 114 | */ 115 | async suggestGatesForContext(context: GateSuggestionContext): Promise<string[]> { 116 | const gates: string[] = []; 117 | 118 | // Category-based recommendations 119 | const categoryGates = this.getCategoryGateMapping()[context.category] || []; 120 | gates.push(...categoryGates); 121 | 122 | // Execution type specific gates 123 | if (context.executionType === 'template') { 124 | gates.push('framework-compliance'); 125 | } 126 | if (context.executionType === 'chain') { 127 | gates.push('content-structure'); 128 | } 129 | 130 | // Framework-specific gates 131 | if (context.framework) { 132 | const frameworkGates = this.getFrameworkGates(context.framework); 133 | gates.push(...frameworkGates); 134 | } 135 | 136 | // Complexity-based gates 137 | if (context.complexity === 'high') { 138 | gates.push('technical-accuracy', 'research-quality'); 139 | } 140 | 141 | // Intent-based gates 142 | if (context.intentKeywords) { 143 | const intentGates = this.getIntentBasedGates(context.intentKeywords); 144 | gates.push(...intentGates); 145 | } 146 | 147 | // Remove duplicates and return 148 | return [...new Set(gates)]; 149 | } 150 | 151 | /** 152 | * Extract gate suggestion context from prompt 153 | */ 154 | private extractGateSuggestionContext(prompt: ConvertedPrompt): GateSuggestionContext { 155 | // Determine execution type 156 | let executionType: 'prompt' | 'template' | 'chain' = 'prompt'; 157 | if (prompt.chainSteps && prompt.chainSteps.length > 0) { 158 | executionType = 'chain'; 159 | } else if (prompt.systemMessage || (prompt.arguments && prompt.arguments.length > 2)) { 160 | executionType = 'template'; 161 | } 162 | 163 | // Determine complexity 164 | let complexity: 'low' | 'medium' | 'high' = 'low'; 165 | const complexityIndicators = [ 166 | prompt.arguments?.length || 0, 167 | prompt.chainSteps?.length || 0, 168 | prompt.userMessageTemplate.length / 100 169 | ]; 170 | const complexityScore = complexityIndicators.reduce((a, b) => a + b, 0); 171 | 172 | if (complexityScore > 10) { 173 | complexity = 'high'; 174 | } else if (complexityScore > 5) { 175 | complexity = 'medium'; 176 | } 177 | 178 | // Extract intent keywords 179 | const intentKeywords = this.extractIntentKeywords(prompt.userMessageTemplate); 180 | 181 | return { 182 | executionType, 183 | category: prompt.category, 184 | framework: this.dependencies.frameworkStateManager?.getActiveFramework()?.methodology, 185 | intentKeywords, 186 | complexity 187 | }; 188 | } 189 | 190 | /** 191 | * Analyze prompt content for gate indicators 192 | */ 193 | private analyzePromptContent(prompt: ConvertedPrompt): { 194 | hasDataRequirements: boolean; 195 | hasCodeRequirements: boolean; 196 | hasResearchRequirements: boolean; 197 | hasEducationalContent: boolean; 198 | hasTechnicalContent: boolean; 199 | requiresAccuracy: boolean; 200 | requiresStructure: boolean; 201 | } { 202 | const content = (prompt.userMessageTemplate + ' ' + (prompt.systemMessage || '')).toLowerCase(); 203 | 204 | return { 205 | hasDataRequirements: /data|statistics|numbers|metrics|analytics/.test(content), 206 | hasCodeRequirements: /code|programming|function|class|method|variable/.test(content), 207 | hasResearchRequirements: /research|analyze|investigate|study|examine/.test(content), 208 | hasEducationalContent: /learn|teach|explain|understand|clarify/.test(content), 209 | hasTechnicalContent: /technical|specification|implementation|architecture/.test(content), 210 | requiresAccuracy: /accurate|precise|correct|verify|validate/.test(content), 211 | requiresStructure: /structure|organize|format|outline|steps/.test(content) 212 | }; 213 | } 214 | 215 | /** 216 | * Generate gate recommendations based on analysis 217 | */ 218 | private generateGateRecommendations( 219 | context: GateSuggestionContext, 220 | contentAnalysis: any 221 | ): string[] { 222 | const gates: string[] = []; 223 | 224 | // Content-based recommendations 225 | if (contentAnalysis.hasCodeRequirements) { 226 | gates.push('code-quality'); 227 | } 228 | if (contentAnalysis.hasResearchRequirements) { 229 | gates.push('research-quality'); 230 | } 231 | if (contentAnalysis.hasEducationalContent) { 232 | gates.push('educational-clarity'); 233 | } 234 | if (contentAnalysis.hasTechnicalContent) { 235 | gates.push('technical-accuracy'); 236 | } 237 | if (contentAnalysis.requiresStructure) { 238 | gates.push('content-structure'); 239 | } 240 | 241 | // Category-based recommendations 242 | const categoryGates = this.getCategoryGateMapping()[context.category] || []; 243 | gates.push(...categoryGates); 244 | 245 | // Remove duplicates 246 | return [...new Set(gates)]; 247 | } 248 | 249 | /** 250 | * Generate temporary gate suggestions 251 | */ 252 | private generateTemporaryGateSuggestions( 253 | context: GateSuggestionContext, 254 | contentAnalysis: any 255 | ): TemporaryGateDefinition[] { 256 | const temporaryGates: TemporaryGateDefinition[] = []; 257 | 258 | // Data accuracy temporary gate 259 | if (contentAnalysis.hasDataRequirements) { 260 | temporaryGates.push({ 261 | name: 'Data Source Verification', 262 | type: 'validation', 263 | scope: 'execution', 264 | description: 'Verify all statistical claims and data sources', 265 | guidance: 'Ensure all numerical data includes proper citations and verification of accuracy', 266 | pass_criteria: [ 267 | { 268 | type: 'content_check', 269 | message: 'Data sources must be cited', 270 | passed: false 271 | } 272 | ], 273 | source: 'automatic' 274 | }); 275 | } 276 | 277 | // Code review temporary gate 278 | if (contentAnalysis.hasCodeRequirements && context.complexity === 'high') { 279 | temporaryGates.push({ 280 | name: 'Code Review Standards', 281 | type: 'validation', 282 | scope: 'execution', 283 | description: 'Enhanced code quality validation for complex implementations', 284 | guidance: 'Apply rigorous code review standards including performance, security, and maintainability', 285 | pass_criteria: [ 286 | { 287 | type: 'pattern_check', 288 | message: 'Code must include error handling', 289 | passed: false 290 | } 291 | ], 292 | source: 'automatic' 293 | }); 294 | } 295 | 296 | // Research depth temporary gate 297 | if (contentAnalysis.hasResearchRequirements && context.executionType === 'chain') { 298 | temporaryGates.push({ 299 | name: 'Research Depth Validation', 300 | type: 'quality', 301 | scope: 'chain', 302 | description: 'Ensure comprehensive research across all chain steps', 303 | guidance: 'Each research step must provide multiple perspectives and credible sources', 304 | source: 'automatic' 305 | }); 306 | } 307 | 308 | return temporaryGates; 309 | } 310 | 311 | /** 312 | * Calculate confidence score 313 | */ 314 | private calculateConfidence( 315 | context: GateSuggestionContext, 316 | contentAnalysis: any, 317 | totalGatesRecommended: number 318 | ): number { 319 | let confidence = 0.5; // Base confidence 320 | 321 | // Increase confidence for clear indicators 322 | const indicators = Object.values(contentAnalysis).filter(Boolean).length; 323 | confidence += (indicators * 0.05); 324 | 325 | // Adjust for context clarity 326 | if (context.category !== 'general') confidence += 0.1; 327 | if (context.framework) confidence += 0.1; 328 | if (context.intentKeywords && context.intentKeywords.length > 0) confidence += 0.1; 329 | 330 | // Adjust for recommendation count 331 | if (totalGatesRecommended > 0) confidence += 0.1; 332 | if (totalGatesRecommended > 3) confidence += 0.1; 333 | 334 | return Math.min(confidence, 1.0); 335 | } 336 | 337 | /** 338 | * Generate reasoning for recommendations 339 | */ 340 | private generateReasoning( 341 | context: GateSuggestionContext, 342 | contentAnalysis: any, 343 | recommendedGates: string[], 344 | temporaryGates: TemporaryGateDefinition[] 345 | ): string[] { 346 | const reasoning: string[] = []; 347 | 348 | reasoning.push(`Analyzed ${context.executionType} with ${context.complexity} complexity in ${context.category} category`); 349 | 350 | if (contentAnalysis.hasCodeRequirements) { 351 | reasoning.push('Detected code requirements - recommended code quality gates'); 352 | } 353 | if (contentAnalysis.hasResearchRequirements) { 354 | reasoning.push('Detected research requirements - recommended research quality gates'); 355 | } 356 | if (contentAnalysis.hasEducationalContent) { 357 | reasoning.push('Detected educational content - recommended clarity-focused gates'); 358 | } 359 | 360 | if (temporaryGates.length > 0) { 361 | reasoning.push(`Suggested ${temporaryGates.length} temporary gates for execution-specific quality control`); 362 | } 363 | 364 | if (recommendedGates.length === 0 && temporaryGates.length === 0) { 365 | reasoning.push('No specific gate requirements detected - default gates will apply'); 366 | } 367 | 368 | return reasoning; 369 | } 370 | 371 | /** 372 | * Generate gate configuration preview 373 | */ 374 | private generateGateConfigurationPreview( 375 | recommendedGates: string[], 376 | temporaryGates: TemporaryGateDefinition[] 377 | ): any { 378 | const preview: any = {}; 379 | 380 | if (recommendedGates.length > 0) { 381 | preview.include = recommendedGates; 382 | } 383 | 384 | if (temporaryGates.length > 0) { 385 | preview.temporary_gates = temporaryGates; 386 | } 387 | 388 | // Default to including framework gates unless specifically disabled 389 | preview.framework_gates = true; 390 | 391 | return preview; 392 | } 393 | 394 | /** 395 | * Extract intent keywords from content 396 | */ 397 | private extractIntentKeywords(content: string): string[] { 398 | const keywords: string[] = []; 399 | const intentPatterns = { 400 | 'analysis': /analyz|investigat|examin|study/gi, 401 | 'creation': /creat|generat|build|develop/gi, 402 | 'explanation': /explain|clarify|describe|detail/gi, 403 | 'validation': /validat|verify|check|confirm/gi, 404 | 'optimization': /optim|improv|enhanc|refin/gi 405 | }; 406 | 407 | for (const [intent, pattern] of Object.entries(intentPatterns)) { 408 | if (pattern.test(content)) { 409 | keywords.push(intent); 410 | } 411 | } 412 | 413 | return keywords; 414 | } 415 | 416 | /** 417 | * Get intent-based gate recommendations 418 | */ 419 | private getIntentBasedGates(intentKeywords: string[]): string[] { 420 | const intentGateMapping: Record<string, string[]> = { 421 | 'analysis': ['research-quality', 'technical-accuracy'], 422 | 'creation': ['content-structure', 'code-quality'], 423 | 'explanation': ['educational-clarity', 'content-structure'], 424 | 'validation': ['technical-accuracy'], 425 | 'optimization': ['code-quality', 'technical-accuracy'] 426 | }; 427 | 428 | const gates: string[] = []; 429 | for (const intent of intentKeywords) { 430 | const intentGates = intentGateMapping[intent] || []; 431 | gates.push(...intentGates); 432 | } 433 | 434 | return [...new Set(gates)]; 435 | } 436 | 437 | /** 438 | * Get category-based gate mapping 439 | */ 440 | private getCategoryGateMapping(): Record<string, string[]> { 441 | return { 442 | 'analysis': ['research-quality', 'technical-accuracy'], 443 | 'education': ['educational-clarity', 'content-structure'], 444 | 'development': ['code-quality', 'security-awareness'], 445 | 'research': ['research-quality', 'technical-accuracy'], 446 | 'debugging': ['technical-accuracy', 'code-quality'], 447 | 'documentation': ['content-structure', 'educational-clarity'], 448 | 'content_processing': ['content-structure'], 449 | 'general': ['content-structure'] 450 | }; 451 | } 452 | 453 | /** 454 | * Get framework-specific gates 455 | */ 456 | private getFrameworkGates(framework: string): string[] { 457 | const frameworkGateMapping: Record<string, string[]> = { 458 | 'CAGEERF': ['framework-compliance', 'content-structure'], 459 | 'ReACT': ['technical-accuracy', 'research-quality'], 460 | '5W1H': ['content-structure', 'research-quality'], 461 | 'SCAMPER': ['educational-clarity'] 462 | }; 463 | 464 | return frameworkGateMapping[framework] || ['framework-compliance']; 465 | } 466 | } ``` -------------------------------------------------------------------------------- /server/src/metrics/analytics-service.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Metrics Collector - Centralized Performance and Usage Metrics Collection 3 | * 4 | * Provides comprehensive metrics collection and reporting for all MCP tools 5 | * without coupling to execution logic. Uses event-driven architecture to 6 | * observe tool operations and provide detailed insights. 7 | */ 8 | 9 | import { EventEmitter } from 'events'; 10 | import { Logger } from '../logging/index.js'; 11 | import { 12 | ExecutionData, 13 | GateValidationData, 14 | FrameworkSwitchData, 15 | ExecutionStats, 16 | SystemMetrics, 17 | FrameworkUsage, 18 | AnalyticsEvent, 19 | AnalyticsQueryOptions, 20 | AnalyticsSummary, 21 | PerformanceTrend 22 | } from './types.js'; 23 | 24 | /** 25 | * Centralized Metrics Collector 26 | */ 27 | export class MetricsCollector extends EventEmitter { 28 | private logger: Logger; 29 | private startTime: number; 30 | 31 | // Analytics data storage 32 | private executionStats: ExecutionStats; 33 | private systemMetrics: SystemMetrics; 34 | private frameworkUsage: FrameworkUsage; 35 | private gateValidationStats = { 36 | totalValidations: 0, 37 | successfulValidations: 0, 38 | totalValidationTime: 0, 39 | validationHistory: [] as GateValidationData[] 40 | }; 41 | 42 | // Raw data storage for queries 43 | private executionHistory: ExecutionData[] = []; 44 | private frameworkSwitchHistory: FrameworkSwitchData[] = []; 45 | private performanceTrends: PerformanceTrend[] = []; 46 | 47 | // Performance monitoring 48 | private memoryCheckInterval?: NodeJS.Timeout; 49 | private readonly MAX_HISTORY_SIZE = 1000; 50 | 51 | constructor(logger: Logger) { 52 | super(); 53 | this.logger = logger; 54 | this.startTime = Date.now(); 55 | 56 | // Initialize analytics data 57 | this.executionStats = { 58 | totalExecutions: 0, 59 | successfulExecutions: 0, 60 | failedExecutions: 0, 61 | averageExecutionTime: 0, 62 | executionsByMode: { 63 | prompt: 0, 64 | template: 0, 65 | chain: 0 66 | }, 67 | executionsByTool: { 68 | prompt_engine: 0, 69 | prompt_manager: 0, 70 | system_control: 0 71 | }, 72 | lastUpdated: Date.now() 73 | }; 74 | 75 | this.systemMetrics = { 76 | uptime: 0, 77 | memoryUsage: { 78 | heapUsed: 0, 79 | heapTotal: 0, 80 | external: 0, 81 | rss: 0 82 | }, 83 | averageResponseTime: 0, 84 | requestsPerMinute: 0, 85 | errorRate: 0, 86 | performanceTrends: [] 87 | }; 88 | 89 | this.frameworkUsage = { 90 | currentFramework: 'CAGEERF', // Default framework 91 | frameworkSwitches: 0, 92 | frameworkUsageTime: {}, 93 | frameworkSwitchHistory: [], 94 | frameworkPerformance: {} 95 | }; 96 | 97 | this.setupEventListeners(); 98 | this.startPerformanceMonitoring(); 99 | 100 | this.logger.info('AnalyticsService initialized with event-driven collection'); 101 | } 102 | 103 | /** 104 | * Set up event listeners for analytics collection 105 | */ 106 | private setupEventListeners(): void { 107 | this.on('execution:complete', this.handleExecutionComplete.bind(this)); 108 | this.on('execution:error', this.handleExecutionError.bind(this)); 109 | this.on('gate:validation', this.handleGateValidation.bind(this)); 110 | this.on('framework:switch', this.handleFrameworkSwitch.bind(this)); 111 | this.on('system:performance', this.handlePerformanceTrend.bind(this)); 112 | } 113 | 114 | /** 115 | * Start performance monitoring 116 | */ 117 | private startPerformanceMonitoring(): void { 118 | // Monitor memory usage every 30 seconds 119 | this.memoryCheckInterval = setInterval(() => { 120 | this.recordMemoryUsage(); 121 | }, 30000); 122 | } 123 | 124 | /** 125 | * Record execution completion 126 | */ 127 | recordExecution(executionData: ExecutionData): void { 128 | this.emit('execution:complete', executionData); 129 | } 130 | 131 | /** 132 | * Record execution error 133 | */ 134 | recordExecutionError(executionData: ExecutionData): void { 135 | this.emit('execution:error', executionData); 136 | } 137 | 138 | /** 139 | * Record gate validation 140 | */ 141 | recordGateValidation(gateData: GateValidationData): void { 142 | this.emit('gate:validation', gateData); 143 | } 144 | 145 | /** 146 | * Record framework switch 147 | */ 148 | recordFrameworkSwitch(switchData: FrameworkSwitchData): void { 149 | this.emit('framework:switch', switchData); 150 | } 151 | 152 | /** 153 | * Handle execution completion event 154 | */ 155 | private handleExecutionComplete(executionData: ExecutionData): void { 156 | // Update execution statistics 157 | this.executionStats.totalExecutions++; 158 | 159 | if (executionData.success) { 160 | this.executionStats.successfulExecutions++; 161 | } else { 162 | this.executionStats.failedExecutions++; 163 | } 164 | 165 | // Update average execution time 166 | const totalTime = this.executionStats.averageExecutionTime * (this.executionStats.totalExecutions - 1); 167 | this.executionStats.averageExecutionTime = 168 | (totalTime + executionData.executionTime) / this.executionStats.totalExecutions; 169 | 170 | // Update execution by mode 171 | if (this.executionStats.executionsByMode[executionData.executionType] !== undefined) { 172 | this.executionStats.executionsByMode[executionData.executionType]++; 173 | } 174 | 175 | // Update execution by tool 176 | if (this.executionStats.executionsByTool[executionData.toolName as keyof typeof this.executionStats.executionsByTool] !== undefined) { 177 | this.executionStats.executionsByTool[executionData.toolName as keyof typeof this.executionStats.executionsByTool]++; 178 | } 179 | 180 | // Update framework performance 181 | if (executionData.frameworkUsed) { 182 | if (!this.frameworkUsage.frameworkPerformance[executionData.frameworkUsed]) { 183 | this.frameworkUsage.frameworkPerformance[executionData.frameworkUsed] = { 184 | averageExecutionTime: 0, 185 | successRate: 0, 186 | usageCount: 0 187 | }; 188 | } 189 | 190 | const perf = this.frameworkUsage.frameworkPerformance[executionData.frameworkUsed]; 191 | const totalTime = perf.averageExecutionTime * perf.usageCount; 192 | perf.usageCount++; 193 | perf.averageExecutionTime = (totalTime + executionData.executionTime) / perf.usageCount; 194 | 195 | // Update success rate 196 | const prevSuccesses = perf.successRate * (perf.usageCount - 1); 197 | const newSuccesses = prevSuccesses + (executionData.success ? 1 : 0); 198 | perf.successRate = newSuccesses / perf.usageCount; 199 | } 200 | 201 | // Store execution history 202 | this.executionHistory.push(executionData); 203 | this.trimHistory(this.executionHistory); 204 | 205 | // Record performance trend 206 | this.recordPerformanceTrend('execution_time', executionData.executionTime, executionData.toolName); 207 | 208 | this.executionStats.lastUpdated = Date.now(); 209 | 210 | this.logger.debug(`Analytics updated: Total executions: ${this.executionStats.totalExecutions}, Success rate: ${this.getSuccessRate()}%`); 211 | } 212 | 213 | /** 214 | * Handle execution error event 215 | */ 216 | private handleExecutionError(executionData: ExecutionData): void { 217 | // Error handling is included in handleExecutionComplete 218 | this.handleExecutionComplete(executionData); 219 | } 220 | 221 | /** 222 | * Handle gate validation event 223 | */ 224 | private handleGateValidation(gateData: GateValidationData): void { 225 | this.gateValidationStats.totalValidations++; 226 | 227 | if (gateData.passedGates === gateData.totalGates) { 228 | this.gateValidationStats.successfulValidations++; 229 | } 230 | 231 | this.gateValidationStats.totalValidationTime += gateData.validationTime; 232 | this.gateValidationStats.validationHistory.push(gateData); 233 | this.trimHistory(this.gateValidationStats.validationHistory); 234 | 235 | this.logger.debug(`Gate validation recorded: ${gateData.passedGates}/${gateData.totalGates} passed`); 236 | } 237 | 238 | /** 239 | * Handle framework switch event 240 | */ 241 | private handleFrameworkSwitch(switchData: FrameworkSwitchData): void { 242 | this.frameworkUsage.frameworkSwitches++; 243 | this.frameworkUsage.currentFramework = switchData.toFramework; 244 | 245 | this.frameworkUsage.frameworkSwitchHistory.push({ 246 | timestamp: switchData.switchTime, 247 | fromFramework: switchData.fromFramework, 248 | toFramework: switchData.toFramework, 249 | reason: switchData.reason 250 | }); 251 | 252 | this.frameworkSwitchHistory.push(switchData); 253 | this.trimHistory(this.frameworkSwitchHistory); 254 | 255 | this.logger.debug(`Framework switch recorded: ${switchData.fromFramework} → ${switchData.toFramework}`); 256 | } 257 | 258 | /** 259 | * Handle performance trend event 260 | */ 261 | private handlePerformanceTrend(trend: PerformanceTrend): void { 262 | this.performanceTrends.push(trend); 263 | this.systemMetrics.performanceTrends.push(trend); 264 | this.trimHistory(this.performanceTrends); 265 | this.trimHistory(this.systemMetrics.performanceTrends); 266 | } 267 | 268 | /** 269 | * Record memory usage 270 | */ 271 | private recordMemoryUsage(): void { 272 | const usage = process.memoryUsage(); 273 | this.systemMetrics.memoryUsage = { 274 | heapUsed: usage.heapUsed, 275 | heapTotal: usage.heapTotal, 276 | external: usage.external, 277 | rss: usage.rss 278 | }; 279 | 280 | this.recordPerformanceTrend('memory_usage', usage.heapUsed, 'system'); 281 | } 282 | 283 | /** 284 | * Record performance trend 285 | */ 286 | private recordPerformanceTrend( 287 | metric: PerformanceTrend['metric'], 288 | value: number, 289 | context?: string 290 | ): void { 291 | const trend: PerformanceTrend = { 292 | timestamp: Date.now(), 293 | metric, 294 | value, 295 | context 296 | }; 297 | 298 | this.emit('system:performance', trend); 299 | } 300 | 301 | /** 302 | * Trim history arrays to max size 303 | */ 304 | private trimHistory<T>(array: T[]): void { 305 | if (array.length > this.MAX_HISTORY_SIZE) { 306 | array.splice(0, array.length - this.MAX_HISTORY_SIZE); 307 | } 308 | } 309 | 310 | /** 311 | * Get execution statistics 312 | */ 313 | getExecutionStats(): ExecutionStats { 314 | return { ...this.executionStats }; 315 | } 316 | 317 | /** 318 | * Get system metrics 319 | */ 320 | getSystemMetrics(): SystemMetrics { 321 | this.systemMetrics.uptime = Date.now() - this.startTime; 322 | this.systemMetrics.errorRate = this.getErrorRate(); 323 | this.systemMetrics.averageResponseTime = this.executionStats.averageExecutionTime; 324 | 325 | return { ...this.systemMetrics }; 326 | } 327 | 328 | /** 329 | * Get framework usage 330 | */ 331 | getFrameworkUsage(): FrameworkUsage { 332 | return { ...this.frameworkUsage }; 333 | } 334 | 335 | /** 336 | * Get comprehensive analytics summary 337 | */ 338 | getAnalyticsSummary(options?: AnalyticsQueryOptions): AnalyticsSummary { 339 | const gateStats = { 340 | totalValidations: this.gateValidationStats.totalValidations, 341 | validationSuccessRate: this.getGateValidationSuccessRate(), 342 | averageValidationTime: this.getAverageGateValidationTime(), 343 | gateAdoptionRate: this.getGateAdoptionRate() 344 | }; 345 | 346 | const recommendations = this.generateRecommendations(); 347 | 348 | return { 349 | executionStats: this.getExecutionStats(), 350 | systemMetrics: this.getSystemMetrics(), 351 | frameworkUsage: this.getFrameworkUsage(), 352 | gateValidationStats: gateStats, 353 | recommendations 354 | }; 355 | } 356 | 357 | /** 358 | * Reset analytics data 359 | */ 360 | resetAnalytics(): void { 361 | this.executionStats = { 362 | totalExecutions: 0, 363 | successfulExecutions: 0, 364 | failedExecutions: 0, 365 | averageExecutionTime: 0, 366 | executionsByMode: { 367 | prompt: 0, 368 | template: 0, 369 | chain: 0 370 | }, 371 | executionsByTool: { 372 | prompt_engine: 0, 373 | prompt_manager: 0, 374 | system_control: 0 375 | }, 376 | lastUpdated: Date.now() 377 | }; 378 | 379 | this.gateValidationStats = { 380 | totalValidations: 0, 381 | successfulValidations: 0, 382 | totalValidationTime: 0, 383 | validationHistory: [] 384 | }; 385 | 386 | this.frameworkUsage.frameworkSwitches = 0; 387 | this.frameworkUsage.frameworkSwitchHistory = []; 388 | this.frameworkUsage.frameworkPerformance = {}; 389 | 390 | this.executionHistory = []; 391 | this.frameworkSwitchHistory = []; 392 | this.performanceTrends = []; 393 | this.systemMetrics.performanceTrends = []; 394 | 395 | this.logger.info('Analytics data reset'); 396 | } 397 | 398 | /** 399 | * Calculate success rate 400 | */ 401 | private getSuccessRate(): number { 402 | if (this.executionStats.totalExecutions === 0) return 100; 403 | return Math.round((this.executionStats.successfulExecutions / this.executionStats.totalExecutions) * 100); 404 | } 405 | 406 | /** 407 | * Calculate error rate 408 | */ 409 | private getErrorRate(): number { 410 | if (this.executionStats.totalExecutions === 0) return 0; 411 | return this.executionStats.failedExecutions / this.executionStats.totalExecutions; 412 | } 413 | 414 | /** 415 | * Calculate gate validation success rate 416 | */ 417 | private getGateValidationSuccessRate(): number { 418 | if (this.gateValidationStats.totalValidations === 0) return 100; 419 | return (this.gateValidationStats.successfulValidations / this.gateValidationStats.totalValidations) * 100; 420 | } 421 | 422 | /** 423 | * Calculate average gate validation time 424 | */ 425 | private getAverageGateValidationTime(): number { 426 | if (this.gateValidationStats.totalValidations === 0) return 0; 427 | return this.gateValidationStats.totalValidationTime / this.gateValidationStats.totalValidations; 428 | } 429 | 430 | /** 431 | * Calculate gate adoption rate 432 | */ 433 | private getGateAdoptionRate(): number { 434 | if (this.executionStats.totalExecutions === 0) return 0; 435 | return (this.gateValidationStats.totalValidations / this.executionStats.totalExecutions) * 100; 436 | } 437 | 438 | /** 439 | * Generate recommendations based on analytics 440 | */ 441 | private generateRecommendations(): string[] { 442 | const recommendations: string[] = []; 443 | 444 | // Performance recommendations 445 | if (this.executionStats.averageExecutionTime > 5000) { 446 | recommendations.push('Average execution time is high (>5s). Consider optimizing prompt complexity or system resources.'); 447 | } 448 | 449 | // Memory recommendations 450 | const memoryUtilization = this.systemMetrics.memoryUsage.heapUsed / this.systemMetrics.memoryUsage.heapTotal; 451 | if (memoryUtilization > 0.8) { 452 | recommendations.push('High memory utilization detected (>80%). Monitor for memory leaks.'); 453 | } 454 | 455 | // Gate adoption recommendations 456 | const gateAdoption = this.getGateAdoptionRate(); 457 | if (gateAdoption < 50) { 458 | recommendations.push('Low gate validation adoption (<50%). Consider enabling gates for better quality assurance.'); 459 | } 460 | 461 | // Error rate recommendations 462 | if (this.getErrorRate() > 0.1) { 463 | recommendations.push('High error rate detected (>10%). Review failed executions for patterns.'); 464 | } 465 | 466 | // Framework recommendations 467 | const frameworks = Object.keys(this.frameworkUsage.frameworkPerformance); 468 | if (frameworks.length > 1) { 469 | const bestFramework = frameworks.reduce((best, current) => { 470 | const bestPerf = this.frameworkUsage.frameworkPerformance[best]; 471 | const currentPerf = this.frameworkUsage.frameworkPerformance[current]; 472 | return currentPerf.successRate > bestPerf.successRate ? current : best; 473 | }); 474 | 475 | if (this.frameworkUsage.currentFramework !== bestFramework) { 476 | recommendations.push(`Consider switching to ${bestFramework} framework for better performance (${Math.round(this.frameworkUsage.frameworkPerformance[bestFramework].successRate * 100)}% success rate).`); 477 | } 478 | } 479 | 480 | return recommendations; 481 | } 482 | 483 | /** 484 | * Cleanup on shutdown 485 | */ 486 | shutdown(): void { 487 | if (this.memoryCheckInterval) { 488 | clearInterval(this.memoryCheckInterval); 489 | } 490 | this.removeAllListeners(); 491 | this.logger.info('AnalyticsService shutdown complete'); 492 | } 493 | } 494 | 495 | /** 496 | * Create analytics service instance 497 | */ 498 | export function createMetricsCollector(logger: Logger): MetricsCollector { 499 | return new MetricsCollector(logger); 500 | } ```