This is page 10 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/hot-reload-manager.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Hot Reload Manager Module 3 | * Orchestrates file system monitoring and reload workflows with event-driven architecture 4 | */ 5 | 6 | import { Logger } from "../logging/index.js"; 7 | import { FileObserver, FileChangeEvent, FileObserverConfig, createFileObserver } from "./file-observer.js"; 8 | import { CategoryManager } from "./category-manager.js"; 9 | import { ConfigManager } from "../config/index.js"; 10 | import path from "path"; 11 | import * as fs from "fs/promises"; 12 | 13 | /** 14 | * Hot reload event types 15 | */ 16 | export type HotReloadEventType = 'prompt_changed' | 'config_changed' | 'category_changed' | 'reload_required'; 17 | 18 | /** 19 | * Hot reload event data 20 | */ 21 | export interface HotReloadEvent { 22 | type: HotReloadEventType; 23 | reason: string; 24 | affectedFiles: string[]; 25 | category?: string; 26 | timestamp: number; 27 | requiresFullReload: boolean; 28 | } 29 | 30 | /** 31 | * Framework-aware hot reload capabilities 32 | */ 33 | export interface FrameworkHotReloadCapabilities { 34 | enabled: boolean; 35 | frameworkAnalysis: boolean; 36 | performanceMonitoring: boolean; 37 | preWarmAnalysis: boolean; 38 | invalidateFrameworkCaches: boolean; 39 | } 40 | 41 | /** 42 | * Hot reload configuration 43 | */ 44 | export interface HotReloadConfig extends Partial<FileObserverConfig> { 45 | enabled: boolean; 46 | autoReload: boolean; 47 | reloadDelayMs: number; 48 | batchChanges: boolean; 49 | batchTimeoutMs: number; 50 | frameworkCapabilities?: FrameworkHotReloadCapabilities; 51 | } 52 | 53 | /** 54 | * Hot reload statistics 55 | */ 56 | export interface HotReloadStats { 57 | reloadsTriggered: number; 58 | filesChanged: number; 59 | lastReloadTime?: number; 60 | autoReloadsEnabled: boolean; 61 | fileObserverStats: ReturnType<FileObserver['getStats']>; 62 | frameworkReloads: number; 63 | frameworkCacheClears: number; 64 | performanceOptimizations: number; 65 | } 66 | 67 | /** 68 | * Hot reload manager configuration 69 | */ 70 | const DEFAULT_HOT_RELOAD_CONFIG: HotReloadConfig = { 71 | enabled: true, 72 | autoReload: true, 73 | reloadDelayMs: 1000, 74 | batchChanges: true, 75 | batchTimeoutMs: 2000, 76 | debounceMs: 500, 77 | watchPromptFiles: true, 78 | watchConfigFiles: true, 79 | recursive: true, 80 | ignoredPatterns: [ 81 | '**/.git/**', 82 | '**/node_modules/**', 83 | '**/.DS_Store', 84 | '**/Thumbs.db', 85 | '**/*.tmp', 86 | '**/*.temp', 87 | '**/dist/**', 88 | '**/*.log' 89 | ], 90 | maxRetries: 3, 91 | retryDelayMs: 1000, 92 | frameworkCapabilities: { 93 | enabled: false, 94 | frameworkAnalysis: false, 95 | performanceMonitoring: false, 96 | preWarmAnalysis: false, 97 | invalidateFrameworkCaches: false 98 | } 99 | }; 100 | 101 | /** 102 | * HotReloadManager class 103 | * Coordinates file watching and reload operations 104 | */ 105 | export class HotReloadManager { 106 | protected logger: Logger; 107 | private config: HotReloadConfig; 108 | private fileObserver: FileObserver; 109 | private categoryManager?: CategoryManager; 110 | private onReloadCallback?: (event: HotReloadEvent) => Promise<void>; 111 | private stats: HotReloadStats; 112 | private isStarted: boolean = false; 113 | private batchTimer?: NodeJS.Timeout; 114 | private pendingChanges: FileChangeEvent[] = []; 115 | private watchedDirectories: Set<string> = new Set(); 116 | 117 | constructor( 118 | logger: Logger, 119 | categoryManager?: CategoryManager, 120 | config?: Partial<HotReloadConfig>, 121 | configManager?: ConfigManager 122 | ) { 123 | this.logger = logger; 124 | this.categoryManager = categoryManager; 125 | this.config = { ...DEFAULT_HOT_RELOAD_CONFIG, ...config }; 126 | 127 | // Create file observer with filtered config 128 | const observerConfig = { 129 | enabled: this.config.enabled, 130 | debounceMs: this.config.debounceMs, 131 | watchPromptFiles: this.config.watchPromptFiles, 132 | watchConfigFiles: this.config.watchConfigFiles, 133 | recursive: this.config.recursive, 134 | ignoredPatterns: this.config.ignoredPatterns, 135 | maxRetries: this.config.maxRetries, 136 | retryDelayMs: this.config.retryDelayMs 137 | }; 138 | 139 | this.fileObserver = createFileObserver(logger, observerConfig, configManager); 140 | 141 | this.stats = { 142 | reloadsTriggered: 0, 143 | filesChanged: 0, 144 | autoReloadsEnabled: this.config.autoReload, 145 | fileObserverStats: this.fileObserver.getStats(), 146 | frameworkReloads: 0, 147 | frameworkCacheClears: 0, 148 | performanceOptimizations: 0 149 | }; 150 | 151 | this.setupFileObserverEventHandlers(); 152 | } 153 | 154 | /** 155 | * Start hot reload monitoring 156 | */ 157 | async start(): Promise<void> { 158 | if (this.isStarted) { 159 | this.logger.warn("HotReloadManager is already started"); 160 | return; 161 | } 162 | 163 | if (!this.config.enabled) { 164 | this.logger.info("HotReloadManager is disabled in configuration"); 165 | return; 166 | } 167 | 168 | this.logger.info("🔥 HotReloadManager: Starting hot reload monitoring..."); 169 | 170 | await this.fileObserver.start(); 171 | this.isStarted = true; 172 | 173 | this.logger.info(`✅ HotReloadManager started - Auto reload: ${this.config.autoReload ? 'ON' : 'OFF'}`); 174 | } 175 | 176 | /** 177 | * Stop hot reload monitoring 178 | */ 179 | async stop(): Promise<void> { 180 | if (!this.isStarted) { 181 | return; 182 | } 183 | 184 | this.logger.info("🛑 HotReloadManager: Stopping hot reload monitoring..."); 185 | 186 | // Clear batch timer 187 | if (this.batchTimer) { 188 | clearTimeout(this.batchTimer); 189 | this.batchTimer = undefined; 190 | } 191 | 192 | await this.fileObserver.stop(); 193 | this.isStarted = false; 194 | 195 | this.logger.info("✅ HotReloadManager stopped"); 196 | } 197 | 198 | /** 199 | * Set the callback for reload events 200 | */ 201 | setReloadCallback(callback: (event: HotReloadEvent) => Promise<void>): void { 202 | this.onReloadCallback = callback; 203 | this.logger.debug("HotReloadManager: Reload callback registered"); 204 | } 205 | 206 | /** 207 | * Add directories to watch 208 | */ 209 | async watchDirectories(directories: Array<{ path: string; category?: string }>): Promise<void> { 210 | if (!this.isStarted) { 211 | throw new Error("HotReloadManager must be started before watching directories"); 212 | } 213 | 214 | for (const { path: dirPath, category } of directories) { 215 | try { 216 | await this.fileObserver.watchDirectory(dirPath, category); 217 | this.watchedDirectories.add(dirPath); 218 | this.logger.info(`📁 HotReloadManager: Watching directory: ${dirPath}${category ? ` (${category})` : ''}`); 219 | } catch (error) { 220 | this.logger.error(`Failed to watch directory ${dirPath}:`, error); 221 | } 222 | } 223 | } 224 | 225 | /** 226 | * Manually trigger a reload 227 | */ 228 | async triggerReload(reason: string = 'Manual trigger', requiresFullReload: boolean = true): Promise<void> { 229 | const event: HotReloadEvent = { 230 | type: 'reload_required', 231 | reason, 232 | affectedFiles: [], 233 | timestamp: Date.now(), 234 | requiresFullReload 235 | }; 236 | 237 | await this.processReloadEvent(event); 238 | } 239 | 240 | /** 241 | * Setup file observer event handlers 242 | */ 243 | private setupFileObserverEventHandlers(): void { 244 | this.fileObserver.on('fileChange', (event: FileChangeEvent) => { 245 | this.handleFileChange(event); 246 | }); 247 | 248 | this.fileObserver.on('watcherError', (error: { directoryPath: string; error: Error }) => { 249 | this.logger.error(`File watcher error for ${error.directoryPath}:`, error.error); 250 | }); 251 | 252 | this.logger.debug("HotReloadManager: File observer event handlers registered"); 253 | } 254 | 255 | /** 256 | * Handle file change events 257 | */ 258 | private handleFileChange(event: FileChangeEvent): void { 259 | this.stats.filesChanged++; 260 | this.logger.debug(`File change detected: ${event.type} - ${event.filename}`); 261 | 262 | if (this.config.batchChanges) { 263 | this.batchFileChange(event); 264 | } else { 265 | this.processFileChangeImmediate(event); 266 | } 267 | } 268 | 269 | /** 270 | * Batch file changes to prevent excessive reloads 271 | */ 272 | private batchFileChange(event: FileChangeEvent): void { 273 | this.pendingChanges.push(event); 274 | 275 | // Clear existing timer 276 | if (this.batchTimer) { 277 | clearTimeout(this.batchTimer); 278 | } 279 | 280 | // Set new timer 281 | this.batchTimer = setTimeout(() => { 282 | this.processBatchedChanges(); 283 | }, this.config.batchTimeoutMs); 284 | } 285 | 286 | /** 287 | * Process batched file changes 288 | */ 289 | private async processBatchedChanges(): Promise<void> { 290 | if (this.pendingChanges.length === 0) { 291 | return; 292 | } 293 | 294 | const changes = [...this.pendingChanges]; 295 | this.pendingChanges = []; 296 | this.batchTimer = undefined; 297 | 298 | this.logger.info(`Processing ${changes.length} batched file changes`); 299 | 300 | // Group changes by type 301 | const promptChanges = changes.filter(c => c.isPromptFile); 302 | const configChanges = changes.filter(c => c.isConfigFile); 303 | 304 | // Determine reload type 305 | const requiresFullReload = configChanges.length > 0 || 306 | promptChanges.some(c => c.type === 'added' || c.type === 'removed'); 307 | 308 | let reloadType: HotReloadEventType = 'prompt_changed'; 309 | let reason = `${promptChanges.length} prompt file(s) changed`; 310 | 311 | if (configChanges.length > 0) { 312 | reloadType = 'config_changed'; 313 | reason = `${configChanges.length} config file(s) changed`; 314 | } 315 | 316 | const hotReloadEvent: HotReloadEvent = { 317 | type: reloadType, 318 | reason, 319 | affectedFiles: changes.map(c => c.filePath), 320 | timestamp: Date.now(), 321 | requiresFullReload 322 | }; 323 | 324 | await this.processReloadEvent(hotReloadEvent); 325 | } 326 | 327 | /** 328 | * Process immediate file change (no batching) 329 | */ 330 | private async processFileChangeImmediate(event: FileChangeEvent): Promise<void> { 331 | let reloadType: HotReloadEventType = 'prompt_changed'; 332 | let requiresFullReload = false; 333 | 334 | if (event.isConfigFile) { 335 | reloadType = 'config_changed'; 336 | requiresFullReload = true; 337 | } else if (event.type === 'added' || event.type === 'removed') { 338 | requiresFullReload = true; 339 | } 340 | 341 | const hotReloadEvent: HotReloadEvent = { 342 | type: reloadType, 343 | reason: `File ${event.type}: ${event.filename}`, 344 | affectedFiles: [event.filePath], 345 | category: event.category, 346 | timestamp: event.timestamp, 347 | requiresFullReload 348 | }; 349 | 350 | await this.processReloadEvent(hotReloadEvent); 351 | } 352 | 353 | /** 354 | * Process reload event with framework integration 355 | */ 356 | protected async processReloadEvent(event: HotReloadEvent): Promise<void> { 357 | this.stats.reloadsTriggered++; 358 | this.stats.lastReloadTime = event.timestamp; 359 | 360 | this.logger.info(`🔄 Hot reload triggered: ${event.reason}`); 361 | 362 | // Framework-aware pre-processing 363 | if (this.config.frameworkCapabilities?.enabled) { 364 | await this.processFrameworkPreReload(event); 365 | } 366 | 367 | if (this.config.autoReload && this.onReloadCallback) { 368 | try { 369 | // Add delay if configured 370 | if (this.config.reloadDelayMs > 0) { 371 | this.logger.debug(`Delaying reload by ${this.config.reloadDelayMs}ms`); 372 | await new Promise(resolve => setTimeout(resolve, this.config.reloadDelayMs)); 373 | } 374 | 375 | await this.onReloadCallback(event); 376 | 377 | // Framework-aware post-processing 378 | if (this.config.frameworkCapabilities?.enabled) { 379 | await this.processFrameworkPostReload(event); 380 | } 381 | 382 | this.logger.info("✅ Hot reload completed successfully"); 383 | 384 | } catch (error) { 385 | this.logger.error("❌ Hot reload failed:", error); 386 | } 387 | } else { 388 | this.logger.info("⏭️ Auto reload is disabled - skipping automatic reload"); 389 | } 390 | } 391 | 392 | /** 393 | * Get current statistics 394 | */ 395 | getStats(): HotReloadStats { 396 | return { 397 | ...this.stats, 398 | fileObserverStats: this.fileObserver.getStats() 399 | }; 400 | } 401 | 402 | /** 403 | * Get current configuration 404 | */ 405 | getConfig(): HotReloadConfig { 406 | return { ...this.config }; 407 | } 408 | 409 | /** 410 | * Update configuration 411 | */ 412 | updateConfig(newConfig: Partial<HotReloadConfig>): void { 413 | const oldAutoReload = this.config.autoReload; 414 | this.config = { ...this.config, ...newConfig }; 415 | 416 | // Update file observer config if needed 417 | if (newConfig.debounceMs !== undefined || 418 | newConfig.watchPromptFiles !== undefined || 419 | newConfig.watchConfigFiles !== undefined) { 420 | this.fileObserver.updateConfig({ 421 | debounceMs: this.config.debounceMs, 422 | watchPromptFiles: this.config.watchPromptFiles, 423 | watchConfigFiles: this.config.watchConfigFiles 424 | }); 425 | } 426 | 427 | if (oldAutoReload !== this.config.autoReload) { 428 | this.stats.autoReloadsEnabled = this.config.autoReload; 429 | this.logger.info(`Auto reload ${this.config.autoReload ? 'enabled' : 'disabled'}`); 430 | } 431 | 432 | this.logger.info("HotReloadManager configuration updated"); 433 | } 434 | 435 | /** 436 | * Check if hot reload manager is running 437 | */ 438 | isRunning(): boolean { 439 | return this.isStarted; 440 | } 441 | 442 | /** 443 | * Get watched directories 444 | */ 445 | getWatchedDirectories(): string[] { 446 | return Array.from(this.watchedDirectories); 447 | } 448 | 449 | /** 450 | * Framework pre-reload processing 451 | * Phase 1: Basic framework cache invalidation and analysis 452 | */ 453 | private async processFrameworkPreReload(event: HotReloadEvent): Promise<void> { 454 | const startTime = performance.now(); 455 | 456 | this.logger.debug("Processing framework pre-reload analysis..."); 457 | 458 | if (this.config.frameworkCapabilities?.invalidateFrameworkCaches) { 459 | this.stats.frameworkCacheClears++; 460 | this.logger.debug("Framework caches invalidated for hot-reload"); 461 | } 462 | 463 | if (this.config.frameworkCapabilities?.frameworkAnalysis) { 464 | this.stats.frameworkReloads++; 465 | this.logger.debug(`Framework analysis prepared for ${event.affectedFiles.length} files`); 466 | } 467 | 468 | const processingTime = performance.now() - startTime; 469 | this.logger.debug(`Framework pre-reload completed in ${processingTime.toFixed(2)}ms`); 470 | } 471 | 472 | /** 473 | * Framework post-reload processing 474 | * Phase 1: Basic performance optimization and cache warming 475 | */ 476 | private async processFrameworkPostReload(event: HotReloadEvent): Promise<void> { 477 | const startTime = performance.now(); 478 | 479 | this.logger.debug("Processing framework post-reload optimizations..."); 480 | 481 | if (this.config.frameworkCapabilities?.preWarmAnalysis) { 482 | this.stats.performanceOptimizations++; 483 | this.logger.debug("Framework analysis cache pre-warmed"); 484 | } 485 | 486 | if (this.config.frameworkCapabilities?.performanceMonitoring) { 487 | const processingTime = performance.now() - startTime; 488 | this.logger.debug(`Framework post-reload monitoring: ${processingTime.toFixed(2)}ms`); 489 | } 490 | } 491 | 492 | /** 493 | * Enable framework capabilities 494 | */ 495 | enableFrameworkCapabilities(options: Partial<FrameworkHotReloadCapabilities> = {}): void { 496 | this.config.frameworkCapabilities = { 497 | enabled: true, 498 | frameworkAnalysis: true, 499 | performanceMonitoring: true, 500 | preWarmAnalysis: true, 501 | invalidateFrameworkCaches: true, 502 | ...options 503 | }; 504 | 505 | // Enable framework integration on file observer if available 506 | if ('enableFrameworkIntegration' in this.fileObserver) { 507 | (this.fileObserver as any).enableFrameworkIntegration({ 508 | enabled: true, 509 | analyzeChanges: this.config.frameworkCapabilities.frameworkAnalysis, 510 | cacheInvalidation: this.config.frameworkCapabilities.invalidateFrameworkCaches, 511 | performanceTracking: this.config.frameworkCapabilities.performanceMonitoring 512 | }); 513 | } 514 | 515 | this.logger.info("Framework capabilities enabled for HotReloadManager"); 516 | } 517 | 518 | /** 519 | * Disable framework capabilities 520 | */ 521 | disableFrameworkCapabilities(): void { 522 | this.config.frameworkCapabilities = { 523 | enabled: false, 524 | frameworkAnalysis: false, 525 | performanceMonitoring: false, 526 | preWarmAnalysis: false, 527 | invalidateFrameworkCaches: false 528 | }; 529 | 530 | // Disable framework integration on file observer if available 531 | if ('disableFrameworkIntegration' in this.fileObserver) { 532 | (this.fileObserver as any).disableFrameworkIntegration(); 533 | } 534 | 535 | this.logger.info("Framework capabilities disabled for HotReloadManager"); 536 | } 537 | 538 | /** 539 | * Check if framework capabilities are enabled 540 | */ 541 | isFrameworkCapabilitiesEnabled(): boolean { 542 | return this.config.frameworkCapabilities?.enabled ?? false; 543 | } 544 | 545 | /** 546 | * Get debug information 547 | */ 548 | getDebugInfo(): { 549 | isRunning: boolean; 550 | config: HotReloadConfig; 551 | stats: HotReloadStats; 552 | watchedDirectories: string[]; 553 | pendingChanges: number; 554 | fileObserverDebug: ReturnType<FileObserver['getDebugInfo']>; 555 | frameworkCapabilities: FrameworkHotReloadCapabilities | undefined; 556 | } { 557 | return { 558 | isRunning: this.isRunning(), 559 | config: this.getConfig(), 560 | stats: this.getStats(), 561 | watchedDirectories: this.getWatchedDirectories(), 562 | pendingChanges: this.pendingChanges.length, 563 | fileObserverDebug: this.fileObserver.getDebugInfo(), 564 | frameworkCapabilities: this.config.frameworkCapabilities 565 | }; 566 | } 567 | } 568 | 569 | /** 570 | * Factory function to create a HotReloadManager instance 571 | */ 572 | export function createHotReloadManager( 573 | logger: Logger, 574 | categoryManager?: CategoryManager, 575 | config?: Partial<HotReloadConfig>, 576 | configManager?: ConfigManager 577 | ): HotReloadManager { 578 | return new HotReloadManager(logger, categoryManager, config, configManager); 579 | } ``` -------------------------------------------------------------------------------- /server/src/prompts/registry.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Prompt Registry Module 3 | * Handles registering prompts with MCP server using proper MCP protocol and managing conversation history 4 | */ 5 | 6 | import { z } from "zod"; 7 | import { ConfigManager } from "../config/index.js"; 8 | import { Logger } from "../logging/index.js"; 9 | import { 10 | ConversationHistoryItem, 11 | ConvertedPrompt, 12 | } from "../types/index.js"; 13 | import { isChainPrompt } from "../utils/chainUtils.js"; 14 | // TemplateProcessor functionality consolidated into UnifiedPromptProcessor 15 | 16 | /** 17 | * Prompt Registry class 18 | */ 19 | export class PromptRegistry { 20 | private logger: Logger; 21 | private mcpServer: any; 22 | private configManager: ConfigManager; 23 | // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor 24 | private conversationHistory: ConversationHistoryItem[] = []; 25 | private readonly MAX_HISTORY_SIZE = 100; 26 | private registeredPromptNames = new Set<string>(); // Track registered prompts to prevent duplicates 27 | 28 | /** 29 | * Direct template processing method (minimal implementation) 30 | * Replaces templateProcessor calls for basic template processing 31 | */ 32 | private async processTemplateDirect( 33 | template: string, 34 | args: Record<string, string>, 35 | specialContext: Record<string, string> = {}, 36 | toolsEnabled: boolean = false 37 | ): Promise<string> { 38 | // Import jsonUtils for basic template processing 39 | const { processTemplate } = await import("../utils/jsonUtils.js"); 40 | const { getAvailableTools } = await import("../utils/index.js"); 41 | 42 | const enhancedSpecialContext = { ...specialContext }; 43 | if (toolsEnabled) { 44 | enhancedSpecialContext["tools_available"] = getAvailableTools(); 45 | } 46 | 47 | return processTemplate(template, args, enhancedSpecialContext); 48 | } 49 | 50 | constructor( 51 | logger: Logger, 52 | mcpServer: any, 53 | configManager: ConfigManager 54 | ) { 55 | this.logger = logger; 56 | this.mcpServer = mcpServer; 57 | this.configManager = configManager; 58 | // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor 59 | } 60 | 61 | /** 62 | * Register individual prompts using MCP SDK registerPrompt API 63 | * This implements the standard MCP prompts protocol using the high-level API 64 | */ 65 | private registerIndividualPrompts(prompts: ConvertedPrompt[]): void { 66 | try { 67 | this.logger.info('Registering individual prompts with MCP SDK...'); 68 | let registeredCount = 0; 69 | 70 | for (const prompt of prompts) { 71 | // Skip if already registered (deduplication guard) 72 | if (this.registeredPromptNames.has(prompt.name)) { 73 | this.logger.debug(`Skipping already registered prompt: ${prompt.name}`); 74 | continue; 75 | } 76 | 77 | // Create argument schema 78 | const argsSchema: Record<string, any> = {}; 79 | for (const arg of prompt.arguments) { 80 | argsSchema[arg.name] = z 81 | .string() 82 | .optional() 83 | .describe(arg.description || `Argument: ${arg.name}`); 84 | } 85 | 86 | // Register the prompt using the correct MCP SDK API with error recovery 87 | try { 88 | this.mcpServer.registerPrompt( 89 | prompt.name, 90 | { 91 | title: prompt.name, 92 | description: prompt.description || `Prompt: ${prompt.name}`, 93 | argsSchema 94 | }, 95 | async (args: any) => { 96 | this.logger.debug(`Executing prompt '${prompt.name}' with args:`, args); 97 | return await this.executePromptLogic(prompt, args || {}); 98 | } 99 | ); 100 | 101 | // Track the registered prompt 102 | this.registeredPromptNames.add(prompt.name); 103 | registeredCount++; 104 | this.logger.debug(`Registered prompt: ${prompt.name}`); 105 | } catch (error: any) { 106 | if (error.message && error.message.includes('already registered')) { 107 | // Handle MCP SDK's "already registered" error gracefully 108 | this.logger.warn(`Prompt '${prompt.name}' already registered in MCP SDK, skipping re-registration`); 109 | this.registeredPromptNames.add(prompt.name); // Track it anyway 110 | continue; 111 | } else { 112 | // Re-throw other errors 113 | this.logger.error(`Failed to register prompt '${prompt.name}':`, error.message || error); 114 | throw error; 115 | } 116 | } 117 | } 118 | 119 | this.logger.info(`Successfully registered ${registeredCount} of ${prompts.length} prompts with MCP SDK`); 120 | } catch (error) { 121 | this.logger.error('Error registering individual prompts:', error instanceof Error ? error.message : String(error)); 122 | throw error; 123 | } 124 | } 125 | 126 | /** 127 | * Execute prompt logic (extracted from createPromptHandler for MCP protocol) 128 | */ 129 | private async executePromptLogic(promptData: ConvertedPrompt, args: any): Promise<any> { 130 | try { 131 | this.logger.info(`Executing prompt '${promptData.name}'...`); 132 | 133 | // Check if arguments are effectively empty 134 | const effectivelyEmptyArgs = this.areArgumentsEffectivelyEmpty( 135 | promptData.arguments, 136 | args 137 | ); 138 | 139 | if ( 140 | effectivelyEmptyArgs && 141 | promptData.onEmptyInvocation === "return_template" 142 | ) { 143 | this.logger.info( 144 | `Prompt '${promptData.name}' invoked without arguments and onEmptyInvocation is 'return_template'. Returning template info.` 145 | ); 146 | 147 | let responseText = `Prompt: '${promptData.name}'\n`; 148 | responseText += `Description: ${promptData.description}\n`; 149 | 150 | if (promptData.arguments && promptData.arguments.length > 0) { 151 | responseText += `This prompt requires the following arguments:\n`; 152 | promptData.arguments.forEach((arg) => { 153 | responseText += ` - ${arg.name}${ 154 | arg.required ? " (required)" : " (optional)" 155 | }: ${arg.description || "No description"}\n`; 156 | }); 157 | responseText += `\nExample usage: >>${ 158 | promptData.id || promptData.name 159 | } ${promptData.arguments 160 | .map((arg) => `${arg.name}=\"value\"`) 161 | .join(" ")}`; 162 | } else { 163 | responseText += "This prompt does not require any arguments.\n"; 164 | } 165 | 166 | return { 167 | messages: [ 168 | { 169 | role: "assistant" as const, 170 | content: { type: "text" as const, text: responseText }, 171 | }, 172 | ], 173 | }; 174 | } 175 | 176 | // Check if this is a chain prompt 177 | if ( 178 | isChainPrompt(promptData) && 179 | promptData.chainSteps && 180 | promptData.chainSteps.length > 0 181 | ) { 182 | this.logger.info( 183 | `Prompt '${promptData.name}' is a chain with ${promptData.chainSteps.length} steps. NOT automatically executing the chain.` 184 | ); 185 | // Note: Chain execution is handled elsewhere 186 | } 187 | 188 | // Create messages array with only user and assistant roles 189 | const messages: { 190 | role: "user" | "assistant"; 191 | content: { type: "text"; text: string }; 192 | }[] = []; 193 | 194 | // Create user message with placeholders replaced 195 | let userMessageText = promptData.userMessageTemplate; 196 | 197 | // If there's a system message, prepend it to the user message 198 | if (promptData.systemMessage) { 199 | userMessageText = `[System Info: ${promptData.systemMessage}]\n\n${userMessageText}`; 200 | } 201 | 202 | // Process the template with special context 203 | // Using direct processing since TemplateProcessor was consolidated 204 | userMessageText = await this.processTemplateDirect( 205 | userMessageText, 206 | args, 207 | { previous_message: this.getPreviousMessage() }, 208 | promptData.tools || false 209 | ); 210 | 211 | // Store in conversation history for future reference 212 | this.addToConversationHistory({ 213 | role: "user", 214 | content: userMessageText, 215 | timestamp: Date.now(), 216 | isProcessedTemplate: true, // Mark as a processed template 217 | }); 218 | 219 | // Push the user message to the messages array 220 | messages.push({ 221 | role: "user", 222 | content: { 223 | type: "text", 224 | text: userMessageText, 225 | }, 226 | }); 227 | 228 | this.logger.debug( 229 | `Processed messages for prompt '${promptData.name}':`, 230 | messages 231 | ); 232 | return { messages }; 233 | } catch (error) { 234 | this.logger.error( 235 | `Error executing prompt '${promptData.name}':`, 236 | error 237 | ); 238 | throw error; // Re-throw to let the MCP framework handle it 239 | } 240 | } 241 | 242 | /** 243 | * Register all prompts with the MCP server using proper MCP protocol 244 | */ 245 | async registerAllPrompts(prompts: ConvertedPrompt[]): Promise<number> { 246 | try { 247 | this.logger.info( 248 | `Registering ${prompts.length} prompts with MCP SDK registerPrompt API...` 249 | ); 250 | 251 | // Register individual prompts using the correct MCP SDK API 252 | this.registerIndividualPrompts(prompts); 253 | 254 | this.logger.info( 255 | `Successfully registered ${prompts.length} prompts with MCP SDK` 256 | ); 257 | return prompts.length; 258 | } catch (error) { 259 | this.logger.error(`Error registering prompts:`, error); 260 | throw error; 261 | } 262 | } 263 | 264 | 265 | /** 266 | * Send list_changed notification to clients (for hot-reload) 267 | * This is the proper MCP way to notify clients about prompt updates 268 | */ 269 | async notifyPromptsListChanged(): Promise<void> { 270 | try { 271 | // Send MCP notification that prompt list has changed 272 | if (this.mcpServer && typeof this.mcpServer.notification === 'function') { 273 | this.mcpServer.notification({ 274 | method: "notifications/prompts/list_changed" 275 | }); 276 | this.logger.info("✅ Sent prompts/list_changed notification to clients"); 277 | } else { 278 | this.logger.debug("MCP server doesn't support notifications"); 279 | } 280 | } catch (error) { 281 | this.logger.warn("Could not send prompts/list_changed notification:", error); 282 | } 283 | } 284 | 285 | // Note: MCP SDK doesn't provide prompt unregistration 286 | // Hot-reload is handled through list_changed notifications to clients 287 | 288 | /** 289 | * Helper function to determine if provided arguments are effectively empty 290 | * for the given prompt definition. 291 | */ 292 | private areArgumentsEffectivelyEmpty( 293 | promptArgs: Array<{ name: string }>, 294 | providedArgs: any 295 | ): boolean { 296 | if ( 297 | !providedArgs || 298 | typeof providedArgs !== "object" || 299 | Object.keys(providedArgs).length === 0 300 | ) { 301 | return true; // No arguments provided at all 302 | } 303 | // Check if any of the defined arguments for the prompt have a meaningful value 304 | for (const definedArg of promptArgs) { 305 | const value = providedArgs[definedArg.name]; 306 | if ( 307 | value !== undefined && 308 | value !== null && 309 | String(value).trim() !== "" 310 | ) { 311 | return false; // Found at least one provided argument with a value 312 | } 313 | } 314 | return true; // All defined arguments are missing or have empty values 315 | } 316 | 317 | 318 | 319 | /** 320 | * Add item to conversation history with size management 321 | */ 322 | addToConversationHistory(item: ConversationHistoryItem): void { 323 | this.conversationHistory.push(item); 324 | 325 | // Trim history if it exceeds maximum size 326 | if (this.conversationHistory.length > this.MAX_HISTORY_SIZE) { 327 | // Remove oldest entries, keeping recent ones 328 | this.conversationHistory.splice( 329 | 0, 330 | this.conversationHistory.length - this.MAX_HISTORY_SIZE 331 | ); 332 | this.logger.debug( 333 | `Trimmed conversation history to ${this.MAX_HISTORY_SIZE} entries to prevent memory leaks` 334 | ); 335 | } 336 | } 337 | 338 | /** 339 | * Get the previous message from conversation history 340 | */ 341 | getPreviousMessage(): string { 342 | // Try to find the last user message in conversation history 343 | if (this.conversationHistory.length > 0) { 344 | // Start from the end and find the first non-template user message 345 | for (let i = this.conversationHistory.length - 1; i >= 0; i--) { 346 | const historyItem = this.conversationHistory[i]; 347 | // Only consider user messages that aren't processed templates 348 | if (historyItem.role === "user" && !historyItem.isProcessedTemplate) { 349 | this.logger.debug( 350 | `Found previous user message for context: ${historyItem.content.substring( 351 | 0, 352 | 50 353 | )}...` 354 | ); 355 | return historyItem.content; 356 | } 357 | } 358 | } 359 | 360 | // Return a default prompt if no suitable history item is found 361 | return "[Please check previous messages in the conversation for context]"; 362 | } 363 | 364 | /** 365 | * Get conversation history 366 | */ 367 | getConversationHistory(): ConversationHistoryItem[] { 368 | return [...this.conversationHistory]; 369 | } 370 | 371 | /** 372 | * Clear conversation history 373 | */ 374 | clearConversationHistory(): void { 375 | this.conversationHistory = []; 376 | this.logger.info("Conversation history cleared"); 377 | } 378 | 379 | /** 380 | * Get conversation history statistics 381 | */ 382 | getConversationStats(): { 383 | totalMessages: number; 384 | userMessages: number; 385 | assistantMessages: number; 386 | processedTemplates: number; 387 | oldestMessage?: number; 388 | newestMessage?: number; 389 | } { 390 | const userMessages = this.conversationHistory.filter( 391 | (item) => item.role === "user" 392 | ).length; 393 | const assistantMessages = this.conversationHistory.filter( 394 | (item) => item.role === "assistant" 395 | ).length; 396 | const processedTemplates = this.conversationHistory.filter( 397 | (item) => item.isProcessedTemplate 398 | ).length; 399 | 400 | const timestamps = this.conversationHistory.map((item) => item.timestamp); 401 | const oldestMessage = 402 | timestamps.length > 0 ? Math.min(...timestamps) : undefined; 403 | const newestMessage = 404 | timestamps.length > 0 ? Math.max(...timestamps) : undefined; 405 | 406 | return { 407 | totalMessages: this.conversationHistory.length, 408 | userMessages, 409 | assistantMessages, 410 | processedTemplates, 411 | oldestMessage, 412 | newestMessage, 413 | }; 414 | } 415 | 416 | /** 417 | * Execute a prompt directly (for testing or internal use) 418 | */ 419 | async executePromptDirectly( 420 | promptId: string, 421 | args: Record<string, string>, 422 | prompts: ConvertedPrompt[] 423 | ): Promise<string> { 424 | try { 425 | const convertedPrompt = prompts.find((cp) => cp.id === promptId); 426 | if (!convertedPrompt) { 427 | throw new Error(`Could not find prompt with ID: ${promptId}`); 428 | } 429 | 430 | this.logger.debug( 431 | `Running prompt directly: ${promptId} with arguments:`, 432 | args 433 | ); 434 | 435 | // Check for missing arguments but treat all as optional 436 | const missingArgs = convertedPrompt.arguments 437 | .filter((arg) => !args[arg.name]) 438 | .map((arg) => arg.name); 439 | 440 | if (missingArgs.length > 0) { 441 | this.logger.info( 442 | `Missing arguments for '${promptId}': ${missingArgs.join( 443 | ", " 444 | )}. Will attempt to use conversation context.` 445 | ); 446 | 447 | // Use previous_message for all missing arguments 448 | missingArgs.forEach((argName) => { 449 | args[argName] = `{{previous_message}}`; 450 | }); 451 | } 452 | 453 | // Process template with context 454 | // Using direct processing since TemplateProcessor was consolidated 455 | const userMessageText = await this.processTemplateDirect( 456 | convertedPrompt.userMessageTemplate, 457 | args, 458 | { previous_message: this.getPreviousMessage() }, 459 | convertedPrompt.tools || false 460 | ); 461 | 462 | // Add the message to conversation history 463 | this.addToConversationHistory({ 464 | role: "user", 465 | content: userMessageText, 466 | timestamp: Date.now(), 467 | isProcessedTemplate: true, 468 | }); 469 | 470 | // Generate a response (echo in this MCP implementation) 471 | const response = `Processed prompt: ${promptId}\nWith message: ${userMessageText}`; 472 | 473 | // Store the response in conversation history 474 | this.addToConversationHistory({ 475 | role: "assistant", 476 | content: response, 477 | timestamp: Date.now(), 478 | }); 479 | 480 | return response; 481 | } catch (error) { 482 | this.logger.error(`Error executing prompt '${promptId}':`, error); 483 | throw error; 484 | } 485 | } 486 | 487 | /** 488 | * Get registration statistics 489 | */ 490 | getRegistrationStats(prompts: ConvertedPrompt[]): { 491 | totalPrompts: number; 492 | chainPrompts: number; 493 | regularPrompts: number; 494 | toolEnabledPrompts: number; 495 | categoriesCount: number; 496 | averageArgumentsPerPrompt: number; 497 | } { 498 | const chainPrompts = prompts.filter((p) => isChainPrompt(p)).length; 499 | const toolEnabledPrompts = prompts.filter((p) => p.tools).length; 500 | const categoriesSet = new Set(prompts.map((p) => p.category)); 501 | const totalArguments = prompts.reduce( 502 | (sum, p) => sum + p.arguments.length, 503 | 0 504 | ); 505 | 506 | return { 507 | totalPrompts: prompts.length, 508 | chainPrompts, 509 | regularPrompts: prompts.length - chainPrompts, 510 | toolEnabledPrompts, 511 | categoriesCount: categoriesSet.size, 512 | averageArgumentsPerPrompt: 513 | prompts.length > 0 ? totalArguments / prompts.length : 0, 514 | }; 515 | } 516 | } 517 | ``` -------------------------------------------------------------------------------- /server/src/mcp-tools/prompt-engine/utils/category-extractor.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Category Extraction Utility 3 | * 4 | * Implements intelligent category detection from multiple sources: 5 | * 1. Prompt metadata (PromptData.category) 6 | * 2. File path structure (/prompts/analysis/ -> analysis) 7 | * 3. Pattern-based detection (fallback) 8 | * 9 | * Part of Gate System Intelligent Selection Upgrade - Phase 1 10 | */ 11 | 12 | import type { Logger } from '../../../logging/index.js'; 13 | import path from 'path'; 14 | 15 | /** 16 | * Template gate configuration 17 | */ 18 | export interface GateConfigurationInfo { 19 | include?: string[]; 20 | exclude?: string[]; 21 | framework_gates?: boolean; 22 | } 23 | 24 | /** 25 | * Extracted category and gate information with source tracking 26 | */ 27 | export interface CategoryExtractionResult { 28 | /** The determined category */ 29 | category: string; 30 | /** Source of the category determination */ 31 | source: 'metadata' | 'path' | 'pattern' | 'fallback'; 32 | /** Confidence level (0-100) */ 33 | confidence: number; 34 | /** Template-level gate configuration */ 35 | gateConfiguration?: GateConfigurationInfo; 36 | /** Original data used for extraction */ 37 | sourceData?: { 38 | metadata?: string; 39 | filePath?: string; 40 | promptId?: string; 41 | }; 42 | } 43 | 44 | /** 45 | * Category extractor with intelligent detection 46 | */ 47 | export class CategoryExtractor { 48 | private logger: Logger; 49 | 50 | constructor(logger: Logger) { 51 | this.logger = logger; 52 | } 53 | 54 | /** 55 | * Extract category from prompt using multiple detection strategies 56 | * 57 | * Priority order: 58 | * 1. Prompt metadata category (highest confidence) 59 | * 2. File path structure parsing 60 | * 3. Prompt ID pattern matching 61 | * 4. Default fallback 62 | */ 63 | extractCategory(prompt: any): CategoryExtractionResult { 64 | this.logger.debug('[CATEGORY EXTRACTOR] Extracting category from prompt:', { 65 | promptId: prompt?.id, 66 | promptCategory: prompt?.category, 67 | promptFile: prompt?.file, 68 | hasGateConfiguration: !!prompt?.gateConfiguration 69 | }); 70 | 71 | // Strategy 1: Use prompt metadata category (highest priority) 72 | if (prompt?.category && typeof prompt.category === 'string') { 73 | const metadataCategory = prompt.category.toLowerCase().trim(); 74 | if (this.isValidCategory(metadataCategory)) { 75 | return { 76 | category: metadataCategory, 77 | source: 'metadata', 78 | confidence: 95, 79 | gateConfiguration: prompt.gateConfiguration, 80 | sourceData: { 81 | metadata: prompt.category, 82 | filePath: prompt.file, 83 | promptId: prompt.id 84 | } 85 | }; 86 | } 87 | } 88 | 89 | // Strategy 2: Extract from file path structure 90 | if (prompt?.file && typeof prompt.file === 'string') { 91 | const pathCategory = this.extractCategoryFromPath(prompt.file); 92 | if (pathCategory) { 93 | return { 94 | category: pathCategory, 95 | source: 'path', 96 | confidence: 85, 97 | gateConfiguration: prompt.gateConfiguration, 98 | sourceData: { 99 | filePath: prompt.file, 100 | promptId: prompt.id 101 | } 102 | }; 103 | } 104 | } 105 | 106 | // Strategy 3: Pattern-based detection from prompt ID 107 | if (prompt?.id && typeof prompt.id === 'string') { 108 | const patternCategory = this.extractCategoryFromPattern(prompt.id); 109 | if (patternCategory) { 110 | return { 111 | category: patternCategory, 112 | source: 'pattern', 113 | confidence: 60, 114 | gateConfiguration: prompt.gateConfiguration, 115 | sourceData: { 116 | promptId: prompt.id, 117 | filePath: prompt.file 118 | } 119 | }; 120 | } 121 | } 122 | 123 | // Strategy 4: Default fallback 124 | this.logger.debug('[CATEGORY EXTRACTOR] No category detected, using fallback'); 125 | return { 126 | category: 'general', 127 | source: 'fallback', 128 | confidence: 30, 129 | gateConfiguration: prompt?.gateConfiguration, 130 | sourceData: { 131 | promptId: prompt?.id, 132 | filePath: prompt?.file 133 | } 134 | }; 135 | } 136 | 137 | /** 138 | * Extract category from file path structure 139 | * Examples: 140 | * - "/prompts/analysis/notes.md" -> "analysis" 141 | * - "/prompts/education/learning.md" -> "education" 142 | * - "analysis/query_refinement.md" -> "analysis" 143 | */ 144 | private extractCategoryFromPath(filePath: string): string | null { 145 | try { 146 | // Normalize path separators 147 | const normalizedPath = filePath.replace(/\\/g, '/'); 148 | 149 | // Split path and look for category indicators 150 | const pathSegments = normalizedPath.split('/').filter(segment => segment.length > 0); 151 | 152 | // Look for prompts directory structure: /prompts/{category}/ 153 | const promptsIndex = pathSegments.findIndex(segment => segment === 'prompts'); 154 | if (promptsIndex !== -1 && promptsIndex + 1 < pathSegments.length) { 155 | const categoryCandidate = pathSegments[promptsIndex + 1]; 156 | if (this.isValidCategory(categoryCandidate)) { 157 | return categoryCandidate; 158 | } 159 | } 160 | 161 | // Look for direct category directory structure: {category}/ 162 | for (const segment of pathSegments) { 163 | if (this.isValidCategory(segment)) { 164 | return segment; 165 | } 166 | } 167 | 168 | return null; 169 | } catch (error) { 170 | this.logger.warn('[CATEGORY EXTRACTOR] Error extracting category from path:', error); 171 | return null; 172 | } 173 | } 174 | 175 | /** 176 | * Extract category from prompt ID patterns 177 | * Examples: 178 | * - "analysis_notes" -> "analysis" 179 | * - "education_learning" -> "education" 180 | * - "debug_application" -> "debugging" 181 | */ 182 | private extractCategoryFromPattern(promptId: string): string | null { 183 | const patterns = [ 184 | { pattern: /^analysis_|_analysis$|analysis/i, category: 'analysis' }, 185 | { pattern: /^education_|_education$|learning|teach/i, category: 'education' }, 186 | { pattern: /^develop_|_develop$|code|programming/i, category: 'development' }, 187 | { pattern: /^research_|_research$|investigate/i, category: 'research' }, 188 | { pattern: /^debug_|_debug$|troubleshoot/i, category: 'debugging' }, 189 | { pattern: /^doc_|_doc$|documentation|readme/i, category: 'documentation' }, 190 | { pattern: /^content_|_content$|process|format/i, category: 'content_processing' } 191 | ]; 192 | 193 | for (const { pattern, category } of patterns) { 194 | if (pattern.test(promptId)) { 195 | return category; 196 | } 197 | } 198 | 199 | return null; 200 | } 201 | 202 | /** 203 | * Validate if a category is recognized 204 | */ 205 | private isValidCategory(category: string): boolean { 206 | const validCategories = [ 207 | 'analysis', 208 | 'education', 209 | 'development', 210 | 'research', 211 | 'debugging', 212 | 'documentation', 213 | 'content_processing', 214 | 'general' 215 | ]; 216 | 217 | return validCategories.includes(category.toLowerCase()); 218 | } 219 | 220 | /** 221 | * Intelligent gate selection with precedence logic 222 | * 223 | * Priority order: 224 | * 1. Explicit template gates (highest priority) 225 | * 2. Category-based gates 226 | * 3. Framework-based gates 227 | * 4. Default fallback gates (lowest priority) 228 | */ 229 | selectGatesWithPrecedence( 230 | categoryResult: CategoryExtractionResult, 231 | frameworkGates: string[] = [], 232 | fallbackGates: string[] = ['content-structure'] 233 | ): { 234 | selectedGates: string[]; 235 | precedenceUsed: string[]; 236 | reasoning: string; 237 | } { 238 | const { category, gateConfiguration } = categoryResult; 239 | const categoryGates = CategoryExtractor.getCategoryGateMapping()[category] || []; 240 | 241 | this.logger.debug('[GATE PRECEDENCE] Input for gate selection:', { 242 | category, 243 | templateGates: gateConfiguration, 244 | categoryGates, 245 | frameworkGates, 246 | fallbackGates 247 | }); 248 | 249 | let selectedGates: string[] = []; 250 | let precedenceUsed: string[] = []; 251 | let reasoning = ''; 252 | 253 | // Phase 1: Start with template gates if specified 254 | if (gateConfiguration) { 255 | if (gateConfiguration.include && gateConfiguration.include.length > 0) { 256 | selectedGates.push(...gateConfiguration.include); 257 | precedenceUsed.push('template-include'); 258 | reasoning += `Template includes: [${gateConfiguration.include.join(', ')}]. `; 259 | } 260 | 261 | // Phase 2: Add category gates if framework_gates is true (default) 262 | if (gateConfiguration.framework_gates !== false) { 263 | const additionalCategoryGates = categoryGates.filter(gate => !selectedGates.includes(gate)); 264 | selectedGates.push(...additionalCategoryGates); 265 | if (additionalCategoryGates.length > 0) { 266 | precedenceUsed.push('category-gates'); 267 | reasoning += `Category gates: [${additionalCategoryGates.join(', ')}]. `; 268 | } 269 | 270 | // Phase 3: Add framework gates 271 | const additionalFrameworkGates = frameworkGates.filter(gate => !selectedGates.includes(gate)); 272 | selectedGates.push(...additionalFrameworkGates); 273 | if (additionalFrameworkGates.length > 0) { 274 | precedenceUsed.push('framework-gates'); 275 | reasoning += `Framework gates: [${additionalFrameworkGates.join(', ')}]. `; 276 | } 277 | } 278 | 279 | // Phase 4: Apply exclusions 280 | if (gateConfiguration.exclude && gateConfiguration.exclude.length > 0) { 281 | const originalCount = selectedGates.length; 282 | selectedGates = selectedGates.filter(gate => !gateConfiguration.exclude!.includes(gate)); 283 | if (selectedGates.length < originalCount) { 284 | precedenceUsed.push('template-exclude'); 285 | reasoning += `Template excludes: [${gateConfiguration.exclude.join(', ')}]. `; 286 | } 287 | } 288 | } else { 289 | // No template configuration - use standard precedence 290 | 291 | // Phase 2: Category gates 292 | selectedGates.push(...categoryGates); 293 | if (categoryGates.length > 0) { 294 | precedenceUsed.push('category-gates'); 295 | reasoning += `Category gates: [${categoryGates.join(', ')}]. `; 296 | } 297 | 298 | // Phase 3: Framework gates 299 | const additionalFrameworkGates = frameworkGates.filter(gate => !selectedGates.includes(gate)); 300 | selectedGates.push(...additionalFrameworkGates); 301 | if (additionalFrameworkGates.length > 0) { 302 | precedenceUsed.push('framework-gates'); 303 | reasoning += `Framework gates: [${additionalFrameworkGates.join(', ')}]. `; 304 | } 305 | } 306 | 307 | // Phase 5: Fallback if no gates selected 308 | if (selectedGates.length === 0) { 309 | selectedGates.push(...fallbackGates); 310 | precedenceUsed.push('fallback'); 311 | reasoning += `Fallback gates: [${fallbackGates.join(', ')}]. `; 312 | } 313 | 314 | // Remove duplicates (shouldn't happen with our logic, but safety check) 315 | selectedGates = [...new Set(selectedGates)]; 316 | 317 | this.logger.info('[GATE PRECEDENCE] Final gate selection:', { 318 | category, 319 | selectedGates, 320 | precedenceUsed, 321 | reasoning: reasoning.trim() 322 | }); 323 | 324 | return { 325 | selectedGates, 326 | precedenceUsed, 327 | reasoning: reasoning.trim() 328 | }; 329 | } 330 | 331 | /** 332 | * Get fallback category mapping for gate selection 333 | */ 334 | public static getCategoryGateMapping(): Record<string, string[]> { 335 | return { 336 | 'analysis': ['research-quality', 'technical-accuracy'], 337 | 'education': ['educational-clarity', 'content-structure'], 338 | 'development': ['code-quality', 'security-awareness'], 339 | 'research': ['research-quality', 'fact-checking'], 340 | 'debugging': ['technical-accuracy', 'problem-solving'], 341 | 'documentation': ['content-structure', 'clarity'], 342 | 'content_processing': ['content-structure', 'format-consistency'], 343 | 'general': ['content-structure'] 344 | }; 345 | } 346 | 347 | /** 348 | * Enhanced gate selection with 5-level precedence including temporary gates 349 | * 350 | * Priority order (Phase 3): 351 | * 1. Temporary gates (highest priority - execution-specific) 352 | * 2. Explicit template gates 353 | * 3. Category-based gates 354 | * 4. Framework-based gates 355 | * 5. Default fallback gates (lowest priority) 356 | */ 357 | selectGatesWithEnhancedPrecedence( 358 | categoryResult: CategoryExtractionResult, 359 | frameworkGates: string[] = [], 360 | fallbackGates: string[] = ['content-structure'], 361 | temporaryGates: string[] = [], 362 | enhancedConfig?: any 363 | ): { 364 | selectedGates: string[]; 365 | precedenceUsed: string[]; 366 | reasoning: string; 367 | temporaryGatesApplied: string[]; 368 | } { 369 | const { category, gateConfiguration } = categoryResult; 370 | const categoryGates = CategoryExtractor.getCategoryGateMapping()[category] || []; 371 | 372 | this.logger.debug('[ENHANCED GATE PRECEDENCE] Input for enhanced gate selection:', { 373 | category, 374 | templateGates: gateConfiguration, 375 | categoryGates, 376 | frameworkGates, 377 | fallbackGates, 378 | temporaryGates, 379 | enhancedConfig 380 | }); 381 | 382 | let selectedGates: string[] = []; 383 | let precedenceUsed: string[] = []; 384 | let reasoning = ''; 385 | let temporaryGatesApplied: string[] = []; 386 | 387 | // Phase 1: Start with temporary gates (highest priority) 388 | if (temporaryGates.length > 0) { 389 | selectedGates.push(...temporaryGates); 390 | precedenceUsed.push('temporary-gates'); 391 | temporaryGatesApplied = [...temporaryGates]; 392 | reasoning += `Temporary gates: [${temporaryGates.join(', ')}]. `; 393 | } 394 | 395 | // Phase 2: Add template gates if specified 396 | if (gateConfiguration) { 397 | if (gateConfiguration.include && gateConfiguration.include.length > 0) { 398 | const additionalTemplateGates = gateConfiguration.include.filter(gate => !selectedGates.includes(gate)); 399 | selectedGates.push(...additionalTemplateGates); 400 | if (additionalTemplateGates.length > 0) { 401 | precedenceUsed.push('template-include'); 402 | reasoning += `Template includes: [${additionalTemplateGates.join(', ')}]. `; 403 | } 404 | } 405 | 406 | // Phase 3: Add category gates if framework_gates is true (default) 407 | if (gateConfiguration.framework_gates !== false) { 408 | const additionalCategoryGates = categoryGates.filter(gate => !selectedGates.includes(gate)); 409 | selectedGates.push(...additionalCategoryGates); 410 | if (additionalCategoryGates.length > 0) { 411 | precedenceUsed.push('category-gates'); 412 | reasoning += `Category gates: [${additionalCategoryGates.join(', ')}]. `; 413 | } 414 | 415 | // Phase 4: Add framework gates 416 | const additionalFrameworkGates = frameworkGates.filter(gate => !selectedGates.includes(gate)); 417 | selectedGates.push(...additionalFrameworkGates); 418 | if (additionalFrameworkGates.length > 0) { 419 | precedenceUsed.push('framework-gates'); 420 | reasoning += `Framework gates: [${additionalFrameworkGates.join(', ')}]. `; 421 | } 422 | } 423 | 424 | // Phase 5: Apply exclusions (can remove temporary, template, category, or framework gates) 425 | if (gateConfiguration.exclude && gateConfiguration.exclude.length > 0) { 426 | const originalCount = selectedGates.length; 427 | selectedGates = selectedGates.filter(gate => !gateConfiguration.exclude!.includes(gate)); 428 | 429 | // Update temporary gates applied if they were excluded 430 | temporaryGatesApplied = temporaryGatesApplied.filter(gate => !gateConfiguration.exclude!.includes(gate)); 431 | 432 | if (selectedGates.length < originalCount) { 433 | precedenceUsed.push('template-exclude'); 434 | reasoning += `Template excludes: [${gateConfiguration.exclude.join(', ')}]. `; 435 | } 436 | } 437 | } else { 438 | // No template configuration - use standard precedence (skip template level) 439 | 440 | // Phase 3: Category gates 441 | const additionalCategoryGates = categoryGates.filter(gate => !selectedGates.includes(gate)); 442 | selectedGates.push(...additionalCategoryGates); 443 | if (additionalCategoryGates.length > 0) { 444 | precedenceUsed.push('category-gates'); 445 | reasoning += `Category gates: [${additionalCategoryGates.join(', ')}]. `; 446 | } 447 | 448 | // Phase 4: Framework gates 449 | const additionalFrameworkGates = frameworkGates.filter(gate => !selectedGates.includes(gate)); 450 | selectedGates.push(...additionalFrameworkGates); 451 | if (additionalFrameworkGates.length > 0) { 452 | precedenceUsed.push('framework-gates'); 453 | reasoning += `Framework gates: [${additionalFrameworkGates.join(', ')}]. `; 454 | } 455 | } 456 | 457 | // Phase 6: Fallback if no gates selected 458 | if (selectedGates.length === 0) { 459 | selectedGates.push(...fallbackGates); 460 | precedenceUsed.push('fallback'); 461 | reasoning += `Fallback gates: [${fallbackGates.join(', ')}]. `; 462 | } 463 | 464 | // Remove duplicates (shouldn't happen with our logic, but safety check) 465 | selectedGates = [...new Set(selectedGates)]; 466 | temporaryGatesApplied = [...new Set(temporaryGatesApplied)]; 467 | 468 | this.logger.info('[ENHANCED GATE PRECEDENCE] Final enhanced gate selection:', { 469 | category, 470 | selectedGates, 471 | precedenceUsed, 472 | temporaryGatesApplied, 473 | reasoning: reasoning.trim() 474 | }); 475 | 476 | return { 477 | selectedGates, 478 | precedenceUsed, 479 | reasoning: reasoning.trim(), 480 | temporaryGatesApplied 481 | }; 482 | } 483 | } 484 | 485 | /** 486 | * Convenience function for quick category extraction 487 | */ 488 | export function extractPromptCategory(prompt: any, logger: Logger): CategoryExtractionResult { 489 | const extractor = new CategoryExtractor(logger); 490 | return extractor.extractCategory(prompt); 491 | } ``` -------------------------------------------------------------------------------- /server/src/prompts/promptUtils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import fs from "fs/promises"; 2 | import path from "path"; 3 | import { PromptData, PromptsConfigFile } from "../types.js"; 4 | 5 | // Create a simple logger since we can't import from index.ts 6 | const log = { 7 | info: (message: string, ...args: any[]) => { 8 | console.log(`[INFO] ${message}`, ...args); 9 | }, 10 | error: (message: string, ...args: any[]) => { 11 | console.error(`[ERROR] ${message}`, ...args); 12 | }, 13 | warn: (message: string, ...args: any[]) => { 14 | console.warn(`[WARN] ${message}`, ...args); 15 | }, 16 | }; 17 | 18 | /** 19 | * Resolves a prompt file path consistently across the application 20 | * @param promptFile The file path from the prompt data 21 | * @param configFilePath The path to the config file (used as reference for absolute paths) 22 | * @param categoryFolder The path to the category folder (used for relative paths) 23 | * @returns The fully resolved path to the prompt file 24 | */ 25 | export function resolvePromptFilePath( 26 | promptFile: string, 27 | configFilePath: string, 28 | categoryFolder: string 29 | ): string { 30 | if (promptFile.startsWith("/")) { 31 | // Absolute path (relative to config file location) 32 | return path.resolve(path.dirname(configFilePath), promptFile.slice(1)); 33 | } else if (promptFile.includes("/")) { 34 | // Path already includes category or sub-path 35 | return path.resolve(path.dirname(configFilePath), promptFile); 36 | } else { 37 | // Simple filename, relative to category folder 38 | return path.resolve(categoryFolder, promptFile); 39 | } 40 | } 41 | 42 | /** 43 | * Reads a prompt file and returns its content 44 | * @param promptFilePath Path to the prompt file 45 | * @returns The content of the prompt file 46 | */ 47 | export async function readPromptFile(promptFilePath: string): Promise<string> { 48 | try { 49 | return await fs.readFile(promptFilePath, "utf8"); 50 | } catch (error) { 51 | log.error(`Error reading prompt file ${promptFilePath}:`, error); 52 | throw new Error( 53 | `Failed to read prompt file: ${ 54 | error instanceof Error ? error.message : String(error) 55 | }` 56 | ); 57 | } 58 | } 59 | 60 | /** 61 | * Parses a prompt file content into sections 62 | * @param content The content of the prompt file 63 | * @returns An object containing the different sections of the prompt 64 | */ 65 | export function parsePromptSections(content: string): Record<string, string> { 66 | const sections: Record<string, string> = {}; 67 | 68 | // Extract the title and description (everything before the first ## heading) 69 | const titleMatch = content.match(/^# (.+?)(?=\n\n|\n##)/s); 70 | if (titleMatch) { 71 | sections.title = titleMatch[1].trim(); 72 | 73 | // Extract description (content between title and first ## heading) 74 | const descMatch = content.match(/^# .+?\n\n([\s\S]+?)(?=\n## )/s); 75 | if (descMatch) { 76 | sections.description = descMatch[1].trim(); 77 | } else { 78 | sections.description = ""; 79 | } 80 | } 81 | 82 | // Extract other sections (## headings) 83 | const sectionMatches = content.matchAll( 84 | /## ([^\n]+)\n\n([\s\S]+?)(?=\n## |\n# |\n$)/g 85 | ); 86 | for (const match of sectionMatches) { 87 | const sectionName = match[1].trim(); 88 | const sectionContent = match[2].trim(); 89 | sections[sectionName] = sectionContent; 90 | } 91 | 92 | return sections; 93 | } 94 | 95 | /** 96 | * Modifies a specific section of a prompt markdown file 97 | * @param promptId Unique identifier of the prompt to modify 98 | * @param sectionName Name of the section to modify (e.g., "title", "description", "System Message", "User Message Template") 99 | * @param newContent New content for the specified section 100 | * @param configPath Path to the promptsConfig.json file 101 | * @returns Object containing the result of the operation 102 | */ 103 | export async function modifyPromptSection( 104 | promptId: string, 105 | sectionName: string, 106 | newContent: string, 107 | configPath: string 108 | ): Promise<{ 109 | success: boolean; 110 | message: string; 111 | promptData?: PromptData; 112 | filePath?: string; 113 | }> { 114 | try { 115 | const messages: string[] = []; 116 | // Read the promptsConfig.json file 117 | const configFilePath = path.resolve(configPath); 118 | const configContent = await fs.readFile(configFilePath, "utf8"); 119 | const promptsConfig = JSON.parse(configContent) as PromptsConfigFile; 120 | 121 | // Find the prompt in all category files 122 | let prompt: PromptData | null = null; 123 | let categoryFilePath: string = ""; 124 | let promptIndex: number = -1; 125 | let promptsFile: any = null; 126 | 127 | // Search through each import path 128 | for (const importPath of promptsConfig.imports) { 129 | try { 130 | // Construct the full path to the import file 131 | const fullImportPath = path.resolve( 132 | path.dirname(configFilePath), 133 | importPath 134 | ); 135 | 136 | // Check if the file exists 137 | try { 138 | await fs.access(fullImportPath); 139 | } catch (error) { 140 | log.warn(`Import file not found: ${importPath}. Skipping.`); 141 | continue; 142 | } 143 | 144 | // Read the file 145 | const fileContent = await fs.readFile(fullImportPath, "utf8"); 146 | const categoryPromptsFile = JSON.parse(fileContent); 147 | 148 | if ( 149 | categoryPromptsFile.prompts && 150 | Array.isArray(categoryPromptsFile.prompts) 151 | ) { 152 | // Find the prompt in this category file 153 | const foundIndex = categoryPromptsFile.prompts.findIndex( 154 | (p: PromptData) => p.id === promptId 155 | ); 156 | 157 | if (foundIndex !== -1) { 158 | prompt = categoryPromptsFile.prompts[foundIndex]; 159 | categoryFilePath = fullImportPath; 160 | promptIndex = foundIndex; 161 | promptsFile = categoryPromptsFile; 162 | messages.push( 163 | `✅ Found prompt '${promptId}' in category file: ${path.basename( 164 | categoryFilePath 165 | )}` 166 | ); 167 | break; 168 | } 169 | } 170 | } catch (error) { 171 | log.error(`Error processing import file ${importPath}:`, error); 172 | } 173 | } 174 | 175 | // If prompt not found, throw an error 176 | if (!prompt) { 177 | return { 178 | success: false, 179 | message: `Prompt with ID '${promptId}' not found in any category file`, 180 | }; 181 | } 182 | 183 | // Determine the category folder path 184 | const categoryFolder = path.dirname(categoryFilePath); 185 | 186 | // Get the full path to the prompt file using the new utility function 187 | const promptFilePath = resolvePromptFilePath( 188 | prompt.file, 189 | configFilePath, 190 | categoryFolder 191 | ); 192 | 193 | // Read the prompt file 194 | const promptContent = await readPromptFile(promptFilePath); 195 | 196 | // Parse the prompt sections 197 | const sections = parsePromptSections(promptContent); 198 | 199 | // Check if the section exists 200 | if (!(sectionName in sections) && sectionName !== "description") { 201 | return { 202 | success: false, 203 | message: `Section '${sectionName}' not found in prompt '${promptId}'`, 204 | }; 205 | } 206 | 207 | // Store the original prompt data for potential rollback 208 | const originalPrompt = { ...prompt }; 209 | const originalContent = promptContent; 210 | 211 | // Modify the section 212 | if (sectionName === "title") { 213 | sections.title = newContent; 214 | } else if (sectionName === "description") { 215 | sections.description = newContent; 216 | } else { 217 | sections[sectionName] = newContent; 218 | } 219 | 220 | // Reconstruct the prompt content 221 | let newPromptContent = `# ${sections.title}\n\n${sections.description}\n\n`; 222 | 223 | // Add other sections 224 | for (const [name, content] of Object.entries(sections)) { 225 | if (name !== "title" && name !== "description") { 226 | newPromptContent += `## ${name}\n\n${content}\n\n`; 227 | } 228 | } 229 | 230 | // Create the updated prompt 231 | const updatedPrompt: PromptData = { 232 | ...originalPrompt, 233 | name: sectionName === "title" ? newContent : originalPrompt.name, 234 | }; 235 | 236 | // Create a copy of the prompts file with the prompt removed 237 | const updatedPromptsFile = { 238 | ...promptsFile, 239 | prompts: [...promptsFile.prompts], 240 | }; 241 | updatedPromptsFile.prompts.splice(promptIndex, 1); 242 | 243 | // Add the updated prompt to the new prompts array 244 | updatedPromptsFile.prompts.push(updatedPrompt); 245 | 246 | // Define the operations and rollbacks for the transaction 247 | const operations = [ 248 | // 1. Write the updated prompt content to the file 249 | async () => await safeWriteFile(promptFilePath, newPromptContent), 250 | 251 | // 2. Write the updated category file with the prompt removed and added back 252 | async () => 253 | await safeWriteFile( 254 | categoryFilePath, 255 | JSON.stringify(updatedPromptsFile, null, 2) 256 | ), 257 | ]; 258 | 259 | const rollbacks = [ 260 | // 1. Restore the original prompt content 261 | async () => await fs.writeFile(promptFilePath, originalContent, "utf8"), 262 | 263 | // 2. Restore the original category file 264 | async () => 265 | await fs.writeFile( 266 | categoryFilePath, 267 | JSON.stringify(promptsFile, null, 2), 268 | "utf8" 269 | ), 270 | ]; 271 | 272 | // Perform the operations as a transaction 273 | await performTransactionalFileOperations(operations, rollbacks); 274 | 275 | messages.push( 276 | `✅ Updated section '${sectionName}' in markdown file: ${prompt.file}` 277 | ); 278 | if (sectionName === "title") { 279 | messages.push( 280 | `✅ Updated prompt name in category file to '${newContent}'` 281 | ); 282 | } 283 | 284 | return { 285 | success: true, 286 | message: messages.join("\n"), 287 | promptData: updatedPrompt, 288 | filePath: promptFilePath, 289 | }; 290 | } catch (error) { 291 | log.error(`Error in modifyPromptSection:`, error); 292 | return { 293 | success: false, 294 | message: `Failed to modify prompt section: ${ 295 | error instanceof Error ? error.message : String(error) 296 | }`, 297 | }; 298 | } 299 | } 300 | 301 | /** 302 | * Helper function to perform a series of file operations as a transaction 303 | * Automatically rolls back all changes if any operation fails 304 | * @param operations Array of async functions that perform file operations 305 | * @param rollbacks Array of async functions that undo the operations 306 | * @returns Result of the last operation if successful 307 | */ 308 | export async function performTransactionalFileOperations<T>( 309 | operations: Array<() => Promise<any>>, 310 | rollbacks: Array<() => Promise<any>> 311 | ): Promise<T> { 312 | // Validate inputs 313 | if (!operations || !Array.isArray(operations) || operations.length === 0) { 314 | throw new Error("No operations provided for transaction"); 315 | } 316 | 317 | if (!rollbacks || !Array.isArray(rollbacks)) { 318 | log.warn( 319 | "No rollbacks provided for transaction - operations cannot be rolled back if they fail" 320 | ); 321 | rollbacks = []; 322 | } 323 | 324 | // Ensure rollbacks array matches operations array length 325 | if (rollbacks.length < operations.length) { 326 | log.warn( 327 | `Rollbacks array (${rollbacks.length}) is shorter than operations array (${operations.length}) - some operations cannot be rolled back` 328 | ); 329 | // Fill with dummy rollbacks 330 | for (let i = rollbacks.length; i < operations.length; i++) { 331 | rollbacks.push(async () => { 332 | log.warn(`No rollback defined for operation ${i}`); 333 | }); 334 | } 335 | } 336 | 337 | let lastSuccessfulIndex = -1; 338 | let result: any; 339 | 340 | try { 341 | // Perform operations 342 | for (let i = 0; i < operations.length; i++) { 343 | if (typeof operations[i] !== "function") { 344 | throw new Error(`Operation at index ${i} is not a function`); 345 | } 346 | result = await operations[i](); 347 | lastSuccessfulIndex = i; 348 | } 349 | return result as T; 350 | } catch (error) { 351 | log.error( 352 | `Transaction failed at operation ${lastSuccessfulIndex + 1}:`, 353 | error 354 | ); 355 | 356 | // Perform rollbacks in reverse order 357 | for (let i = lastSuccessfulIndex; i >= 0; i--) { 358 | try { 359 | if (typeof rollbacks[i] === "function") { 360 | await rollbacks[i](); 361 | } else { 362 | log.warn(`Skipping invalid rollback at index ${i} (not a function)`); 363 | } 364 | } catch (rollbackError) { 365 | log.error(`Error during rollback operation ${i}:`, rollbackError); 366 | // Continue with other rollbacks even if one fails 367 | } 368 | } 369 | throw error; 370 | } 371 | } 372 | 373 | /** 374 | * Safely writes content to a file by first writing to a temp file, then renaming 375 | * This ensures the file is either completely written or left unchanged 376 | * @param filePath Path to the file 377 | * @param content Content to write 378 | * @param encoding Optional encoding (defaults to 'utf8') 379 | */ 380 | export async function safeWriteFile( 381 | filePath: string, 382 | content: string, 383 | encoding: BufferEncoding = "utf8" 384 | ): Promise<void> { 385 | const tempPath = `${filePath}.tmp`; 386 | 387 | try { 388 | // Write to temp file 389 | await fs.writeFile(tempPath, content, encoding); 390 | 391 | // Check if the original file exists 392 | try { 393 | await fs.access(filePath); 394 | // If it exists, make a backup 395 | const backupPath = `${filePath}.bak`; 396 | await fs.copyFile(filePath, backupPath); 397 | 398 | // Replace the original with the temp file 399 | await fs.rename(tempPath, filePath); 400 | 401 | // Remove the backup 402 | await fs.unlink(backupPath); 403 | } catch (error) { 404 | // File doesn't exist, just rename the temp file 405 | await fs.rename(tempPath, filePath); 406 | } 407 | } catch (error) { 408 | // Clean up temp file if it exists 409 | try { 410 | await fs.unlink(tempPath); 411 | } catch (cleanupError) { 412 | // Ignore errors during cleanup 413 | } 414 | throw error; 415 | } 416 | } 417 | 418 | /** 419 | * Finds and deletes a prompt file 420 | * @param promptId Unique identifier of the prompt to delete 421 | * @param baseDir Base directory to search in (usually the prompts directory) 422 | * @returns Object containing information about the deletion 423 | */ 424 | export async function findAndDeletePromptFile( 425 | promptId: string, 426 | baseDir: string 427 | ): Promise<{ 428 | found: boolean; 429 | deleted: boolean; 430 | path?: string; 431 | error?: string; 432 | }> { 433 | try { 434 | // First, find the prompt file 435 | const findResult = await findPromptFile(promptId, baseDir); 436 | 437 | // If the file wasn't found, return the result 438 | if (!findResult.found) { 439 | return { found: false, deleted: false }; 440 | } 441 | 442 | // Try to delete the file 443 | try { 444 | await fs.unlink(findResult.path!); 445 | log.info(`Successfully deleted markdown file: ${findResult.path}`); 446 | return { found: true, deleted: true, path: findResult.path }; 447 | } catch (deleteError) { 448 | const errorMessage = `Error deleting file at ${findResult.path}: ${ 449 | deleteError instanceof Error ? deleteError.message : String(deleteError) 450 | }`; 451 | log.error(errorMessage); 452 | return { 453 | found: true, 454 | deleted: false, 455 | path: findResult.path, 456 | error: errorMessage, 457 | }; 458 | } 459 | } catch (error) { 460 | const errorMessage = `Error finding and deleting prompt file: ${ 461 | error instanceof Error ? error.message : String(error) 462 | }`; 463 | log.error(errorMessage); 464 | return { found: false, deleted: false, error: errorMessage }; 465 | } 466 | } 467 | 468 | /** 469 | * Checks if a prompt file exists and returns its path 470 | * @param promptId Unique identifier of the prompt to find 471 | * @param baseDir Base directory to search in (usually the prompts directory) 472 | * @returns Object containing information about the prompt file 473 | */ 474 | export async function findPromptFile( 475 | promptId: string, 476 | baseDir: string 477 | ): Promise<{ 478 | found: boolean; 479 | path?: string; 480 | category?: string; 481 | error?: string; 482 | }> { 483 | try { 484 | // Get all category directories 485 | const categoryDirs = await fs.readdir(baseDir, { withFileTypes: true }); 486 | 487 | // Filter for directories only 488 | const categories = categoryDirs 489 | .filter((dirent) => dirent.isDirectory()) 490 | .map((dirent) => dirent.name); 491 | 492 | log.info( 493 | `Searching for markdown file with ID '${promptId}' in ${categories.length} category folders` 494 | ); 495 | 496 | // Possible filenames to look for 497 | const possibleFilenames = [ 498 | `${promptId}.md`, // Simple ID.md 499 | `${promptId.replace(/-/g, "_")}.md`, // ID with underscores instead of hyphens 500 | `${promptId.replace(/_/g, "-")}.md`, // ID with hyphens instead of underscores 501 | ]; 502 | 503 | // Search each category directory for the file 504 | for (const category of categories) { 505 | const categoryPath = path.join(baseDir, category); 506 | 507 | try { 508 | const files = await fs.readdir(categoryPath); 509 | 510 | // Check each possible filename 511 | for (const filename of possibleFilenames) { 512 | if (files.includes(filename)) { 513 | const filePath = path.join(categoryPath, filename); 514 | log.info(`Found markdown file at: ${filePath}`); 515 | return { found: true, path: filePath, category }; 516 | } 517 | } 518 | } catch (readError) { 519 | log.warn( 520 | `Error reading directory ${categoryPath}: ${ 521 | readError instanceof Error ? readError.message : String(readError) 522 | }` 523 | ); 524 | // Continue to next category 525 | } 526 | } 527 | 528 | log.warn( 529 | `Could not find markdown file for prompt '${promptId}' in any category folder` 530 | ); 531 | return { found: false }; 532 | } catch (error) { 533 | const errorMessage = `Error searching for prompt file: ${ 534 | error instanceof Error ? error.message : String(error) 535 | }`; 536 | log.error(errorMessage); 537 | return { found: false, error: errorMessage }; 538 | } 539 | } 540 | ``` -------------------------------------------------------------------------------- /server/src/execution/context/context-resolver.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Context Resolution System 3 | * 4 | * Intelligent context resolution with priority-based fallbacks that replaces 5 | * the hardcoded {{previous_message}} pattern with flexible, multi-source context aggregation. 6 | * 7 | * Features: 8 | * - Priority-based context resolution strategies 9 | * - Multi-source context aggregation 10 | * - Context validation and sanitization 11 | * - Smart fallback context generation 12 | * - Context caching for performance 13 | */ 14 | 15 | import { Logger } from "../../logging/index.js"; 16 | import { PromptArgument } from "../../types/index.js"; 17 | 18 | /** 19 | * Context source types 20 | */ 21 | export type ContextSource = 22 | | 'user_provided' 23 | | 'conversation_history' 24 | | 'environment_variables' 25 | | 'prompt_defaults' 26 | | 'system_context' 27 | | 'cached_context' 28 | | 'generated_placeholder' 29 | | 'empty_fallback'; 30 | 31 | /** 32 | * Context resolution result 33 | */ 34 | export interface ContextResolution { 35 | value: any; 36 | source: ContextSource; 37 | confidence: number; 38 | metadata: { 39 | resolvedAt: number; 40 | strategy: string; 41 | alternativeValues?: Array<{ value: any; source: ContextSource; confidence: number }>; 42 | warnings: string[]; 43 | }; 44 | } 45 | 46 | /** 47 | * Context provider interface 48 | */ 49 | export interface ContextProvider { 50 | name: string; 51 | priority: number; 52 | isAvailable: () => boolean; 53 | resolve: (key: string, hint?: any) => Promise<ContextResolution | null>; 54 | } 55 | 56 | /** 57 | * Context aggregation options 58 | */ 59 | export interface ContextAggregationOptions { 60 | preferredSources?: ContextSource[]; 61 | excludedSources?: ContextSource[]; 62 | cacheResults?: boolean; 63 | includeAlternatives?: boolean; 64 | minimumConfidence?: number; 65 | } 66 | 67 | /** 68 | * Context resolver class 69 | */ 70 | export class ContextResolver { 71 | private logger: Logger; 72 | private providers: Map<string, ContextProvider> = new Map(); 73 | private cache: Map<string, ContextResolution> = new Map(); 74 | private cacheTimeout: number = 30000; // 30 seconds 75 | 76 | // Resolution statistics 77 | private stats = { 78 | totalResolutions: 0, 79 | successfulResolutions: 0, 80 | cacheHits: 0, 81 | cacheMisses: 0, 82 | providerUsage: new Map<string, number>(), 83 | averageConfidence: 0, 84 | averageResolutionTime: 0 85 | }; 86 | 87 | constructor(logger: Logger) { 88 | this.logger = logger; 89 | this.initializeDefaultProviders(); 90 | this.logger.debug(`ContextResolver initialized with ${this.providers.size} providers`); 91 | } 92 | 93 | /** 94 | * Resolve context value using priority-based strategy 95 | */ 96 | async resolveContext( 97 | key: string, 98 | hint?: any, 99 | options: ContextAggregationOptions = {} 100 | ): Promise<ContextResolution> { 101 | const startTime = Date.now(); 102 | this.stats.totalResolutions++; 103 | 104 | this.logger.debug(`Resolving context for key: "${key}"`); 105 | 106 | // Check cache first 107 | if (options.cacheResults !== false) { 108 | const cached = this.getCachedResolution(key); 109 | if (cached) { 110 | this.stats.cacheHits++; 111 | this.logger.debug(`Context resolved from cache: ${key} -> ${cached.source}`); 112 | return cached; 113 | } 114 | } 115 | 116 | this.stats.cacheMisses++; 117 | 118 | // Get available providers sorted by priority 119 | const availableProviders = this.getAvailableProviders(options); 120 | const alternatives: Array<{ value: any; source: ContextSource; confidence: number }> = []; 121 | 122 | // Try each provider in priority order 123 | for (const provider of availableProviders) { 124 | try { 125 | const resolution = await provider.resolve(key, hint); 126 | if (resolution && this.meetsMinimumConfidence(resolution, options)) { 127 | 128 | // Collect alternatives if requested 129 | if (options.includeAlternatives) { 130 | // Continue trying other providers for alternatives 131 | await this.collectAlternatives(key, hint, availableProviders, provider, alternatives); 132 | resolution.metadata.alternativeValues = alternatives; 133 | } 134 | 135 | // Cache the result 136 | if (options.cacheResults !== false) { 137 | this.cacheResolution(key, resolution); 138 | } 139 | 140 | // Update statistics 141 | this.updateStats(provider.name, resolution, startTime); 142 | 143 | this.logger.debug(`Context resolved: ${key} -> ${resolution.source} (confidence: ${resolution.confidence})`); 144 | return resolution; 145 | } else if (resolution) { 146 | alternatives.push({ 147 | value: resolution.value, 148 | source: resolution.source, 149 | confidence: resolution.confidence 150 | }); 151 | } 152 | } catch (error) { 153 | this.logger.debug(`Provider ${provider.name} failed for key ${key}:`, error); 154 | continue; 155 | } 156 | } 157 | 158 | // If no provider succeeded, create a fallback resolution 159 | const fallbackResolution = this.createFallbackResolution(key, hint, alternatives); 160 | 161 | // Cache fallback if enabled 162 | if (options.cacheResults !== false) { 163 | this.cacheResolution(key, fallbackResolution); 164 | } 165 | 166 | this.updateStats('fallback', fallbackResolution, startTime); 167 | this.logger.debug(`Context resolved using fallback: ${key} -> ${fallbackResolution.source}`); 168 | 169 | return fallbackResolution; 170 | } 171 | 172 | /** 173 | * Register a custom context provider 174 | */ 175 | registerProvider(provider: ContextProvider): void { 176 | this.providers.set(provider.name, provider); 177 | this.stats.providerUsage.set(provider.name, 0); 178 | this.logger.debug(`Registered context provider: ${provider.name} (priority: ${provider.priority})`); 179 | } 180 | 181 | /** 182 | * Unregister a context provider 183 | */ 184 | unregisterProvider(name: string): boolean { 185 | const removed = this.providers.delete(name); 186 | this.stats.providerUsage.delete(name); 187 | if (removed) { 188 | this.logger.debug(`Unregistered context provider: ${name}`); 189 | } 190 | return removed; 191 | } 192 | 193 | /** 194 | * Initialize default context providers 195 | */ 196 | private initializeDefaultProviders(): void { 197 | // Conversation history provider 198 | this.registerProvider({ 199 | name: 'conversation_history', 200 | priority: 80, 201 | isAvailable: () => true, 202 | resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => { 203 | if (hint?.conversationHistory && hint.conversationHistory.length > 0) { 204 | const lastMessage = hint.conversationHistory[hint.conversationHistory.length - 1]; 205 | if (lastMessage?.content) { 206 | return { 207 | value: lastMessage.content, 208 | source: 'conversation_history', 209 | confidence: 0.8, 210 | metadata: { 211 | resolvedAt: Date.now(), 212 | strategy: 'last_message', 213 | warnings: [] 214 | } 215 | }; 216 | } 217 | } 218 | return null; 219 | } 220 | }); 221 | 222 | // Environment variables provider 223 | this.registerProvider({ 224 | name: 'environment_variables', 225 | priority: 70, 226 | isAvailable: () => true, 227 | resolve: async (key: string): Promise<ContextResolution | null> => { 228 | const envKey = `PROMPT_${key.toUpperCase()}`; 229 | const value = process.env[envKey]; 230 | if (value) { 231 | return { 232 | value, 233 | source: 'environment_variables', 234 | confidence: 0.9, 235 | metadata: { 236 | resolvedAt: Date.now(), 237 | strategy: 'env_var', 238 | warnings: [] 239 | } 240 | }; 241 | } 242 | return null; 243 | } 244 | }); 245 | 246 | // Prompt defaults provider 247 | this.registerProvider({ 248 | name: 'prompt_defaults', 249 | priority: 60, 250 | isAvailable: () => true, 251 | resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => { 252 | if (hint?.promptDefaults && hint.promptDefaults[key] !== undefined) { 253 | return { 254 | value: hint.promptDefaults[key], 255 | source: 'prompt_defaults', 256 | confidence: 0.7, 257 | metadata: { 258 | resolvedAt: Date.now(), 259 | strategy: 'prompt_specific', 260 | warnings: [] 261 | } 262 | }; 263 | } 264 | return null; 265 | } 266 | }); 267 | 268 | // System context provider 269 | this.registerProvider({ 270 | name: 'system_context', 271 | priority: 50, 272 | isAvailable: () => true, 273 | resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => { 274 | if (hint?.systemContext && hint.systemContext[key] !== undefined) { 275 | return { 276 | value: hint.systemContext[key], 277 | source: 'system_context', 278 | confidence: 0.6, 279 | metadata: { 280 | resolvedAt: Date.now(), 281 | strategy: 'system_provided', 282 | warnings: [] 283 | } 284 | }; 285 | } 286 | return null; 287 | } 288 | }); 289 | 290 | // Smart placeholder generator 291 | this.registerProvider({ 292 | name: 'placeholder_generator', 293 | priority: 30, 294 | isAvailable: () => true, 295 | resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => { 296 | const placeholder = this.generateSmartPlaceholder(key, hint); 297 | return { 298 | value: placeholder.value, 299 | source: 'generated_placeholder', 300 | confidence: placeholder.confidence, 301 | metadata: { 302 | resolvedAt: Date.now(), 303 | strategy: 'smart_generation', 304 | warnings: placeholder.warnings 305 | } 306 | }; 307 | } 308 | }); 309 | } 310 | 311 | /** 312 | * Get available providers based on options 313 | */ 314 | private getAvailableProviders(options: ContextAggregationOptions): ContextProvider[] { 315 | return Array.from(this.providers.values()) 316 | .filter(provider => { 317 | // Check if provider is available 318 | if (!provider.isAvailable()) return false; 319 | 320 | // Check preferred sources 321 | if (options.preferredSources?.length) { 322 | // This is a simplistic check - in practice you'd map provider names to sources 323 | const providerSource = this.mapProviderToSource(provider.name); 324 | if (!options.preferredSources.includes(providerSource)) return false; 325 | } 326 | 327 | // Check excluded sources 328 | if (options.excludedSources?.length) { 329 | const providerSource = this.mapProviderToSource(provider.name); 330 | if (options.excludedSources.includes(providerSource)) return false; 331 | } 332 | 333 | return true; 334 | }) 335 | .sort((a, b) => b.priority - a.priority); // Higher priority first 336 | } 337 | 338 | /** 339 | * Map provider name to context source 340 | */ 341 | private mapProviderToSource(providerName: string): ContextSource { 342 | const mapping: Record<string, ContextSource> = { 343 | 'conversation_history': 'conversation_history', 344 | 'environment_variables': 'environment_variables', 345 | 'prompt_defaults': 'prompt_defaults', 346 | 'system_context': 'system_context', 347 | 'placeholder_generator': 'generated_placeholder' 348 | }; 349 | 350 | return mapping[providerName] || 'system_context'; 351 | } 352 | 353 | /** 354 | * Check if resolution meets minimum confidence 355 | */ 356 | private meetsMinimumConfidence(resolution: ContextResolution, options: ContextAggregationOptions): boolean { 357 | if (options.minimumConfidence === undefined) return true; 358 | return resolution.confidence >= options.minimumConfidence; 359 | } 360 | 361 | /** 362 | * Collect alternative values from other providers 363 | */ 364 | private async collectAlternatives( 365 | key: string, 366 | hint: any, 367 | allProviders: ContextProvider[], 368 | usedProvider: ContextProvider, 369 | alternatives: Array<{ value: any; source: ContextSource; confidence: number }> 370 | ): Promise<void> { 371 | const remainingProviders = allProviders.filter(p => p !== usedProvider); 372 | 373 | for (const provider of remainingProviders.slice(0, 3)) { // Limit to 3 alternatives 374 | try { 375 | const resolution = await provider.resolve(key, hint); 376 | if (resolution) { 377 | alternatives.push({ 378 | value: resolution.value, 379 | source: resolution.source, 380 | confidence: resolution.confidence 381 | }); 382 | } 383 | } catch (error) { 384 | // Ignore errors when collecting alternatives 385 | continue; 386 | } 387 | } 388 | } 389 | 390 | /** 391 | * Generate smart placeholder based on key characteristics 392 | */ 393 | private generateSmartPlaceholder( 394 | key: string, 395 | hint?: any 396 | ): { value: string; confidence: number; warnings: string[] } { 397 | const keyLower = key.toLowerCase(); 398 | const warnings: string[] = []; 399 | 400 | // Argument-specific placeholders 401 | if (hint?.argumentDef) { 402 | const arg = hint.argumentDef as PromptArgument; 403 | const description = (arg.description || '').toLowerCase(); 404 | 405 | if (description.includes('file') || keyLower.includes('file')) { 406 | return { value: '[File path required]', confidence: 0.4, warnings }; 407 | } 408 | 409 | if (description.includes('url') || keyLower.includes('url')) { 410 | return { value: '[URL required]', confidence: 0.4, warnings }; 411 | } 412 | 413 | if (description.includes('number') || keyLower.includes('count')) { 414 | return { value: '1', confidence: 0.5, warnings }; 415 | } 416 | } 417 | 418 | // Generic semantic placeholders 419 | if (keyLower.includes('content') || keyLower.includes('text') || keyLower.includes('input')) { 420 | return { 421 | value: '[Content to be provided]', 422 | confidence: 0.3, 423 | warnings: ['Generic content placeholder - consider providing specific content'] 424 | }; 425 | } 426 | 427 | if (keyLower.includes('name') || keyLower.includes('title')) { 428 | return { value: `[${key} required]`, confidence: 0.3, warnings }; 429 | } 430 | 431 | if (keyLower.includes('format') || keyLower.includes('style')) { 432 | return { value: 'default', confidence: 0.4, warnings }; 433 | } 434 | 435 | if (keyLower.includes('language') || keyLower.includes('lang')) { 436 | return { value: 'en', confidence: 0.4, warnings }; 437 | } 438 | 439 | // Ultra-generic fallback 440 | warnings.push(`No semantic match found for "${key}" - using generic placeholder`); 441 | return { 442 | value: `[${key.replace(/_/g, ' ')} to be specified]`, 443 | confidence: 0.2, 444 | warnings 445 | }; 446 | } 447 | 448 | /** 449 | * Create fallback resolution when no providers succeed 450 | */ 451 | private createFallbackResolution( 452 | key: string, 453 | hint: any, 454 | alternatives: Array<{ value: any; source: ContextSource; confidence: number }> 455 | ): ContextResolution { 456 | // If we have alternatives, use the best one 457 | if (alternatives.length > 0) { 458 | const best = alternatives.sort((a, b) => b.confidence - a.confidence)[0]; 459 | return { 460 | value: best.value, 461 | source: best.source, 462 | confidence: best.confidence, 463 | metadata: { 464 | resolvedAt: Date.now(), 465 | strategy: 'best_alternative', 466 | alternativeValues: alternatives, 467 | warnings: ['Used alternative resolution after primary strategies failed'] 468 | } 469 | }; 470 | } 471 | 472 | // Last resort: empty fallback 473 | return { 474 | value: '', 475 | source: 'empty_fallback', 476 | confidence: 0.1, 477 | metadata: { 478 | resolvedAt: Date.now(), 479 | strategy: 'empty_fallback', 480 | warnings: [`No context available for "${key}" - using empty value`] 481 | } 482 | }; 483 | } 484 | 485 | /** 486 | * Get cached resolution if available and not expired 487 | */ 488 | private getCachedResolution(key: string): ContextResolution | null { 489 | const cached = this.cache.get(key); 490 | if (cached && (Date.now() - cached.metadata.resolvedAt) < this.cacheTimeout) { 491 | return cached; 492 | } else if (cached) { 493 | // Remove expired cache entry 494 | this.cache.delete(key); 495 | } 496 | return null; 497 | } 498 | 499 | /** 500 | * Cache resolution result 501 | */ 502 | private cacheResolution(key: string, resolution: ContextResolution): void { 503 | this.cache.set(key, resolution); 504 | 505 | // Cleanup old entries periodically 506 | if (this.cache.size > 1000) { 507 | const cutoff = Date.now() - this.cacheTimeout; 508 | for (const [k, v] of this.cache.entries()) { 509 | if (v.metadata.resolvedAt < cutoff) { 510 | this.cache.delete(k); 511 | } 512 | } 513 | } 514 | } 515 | 516 | /** 517 | * Update resolution statistics 518 | */ 519 | private updateStats(providerName: string, resolution: ContextResolution, startTime: number): void { 520 | this.stats.successfulResolutions++; 521 | 522 | const current = this.stats.providerUsage.get(providerName) || 0; 523 | this.stats.providerUsage.set(providerName, current + 1); 524 | 525 | // Update average confidence 526 | const totalSuccessful = this.stats.successfulResolutions; 527 | this.stats.averageConfidence = 528 | (this.stats.averageConfidence * (totalSuccessful - 1) + resolution.confidence) / totalSuccessful; 529 | 530 | // Update average resolution time 531 | const resolutionTime = Date.now() - startTime; 532 | this.stats.averageResolutionTime = 533 | (this.stats.averageResolutionTime * (totalSuccessful - 1) + resolutionTime) / totalSuccessful; 534 | } 535 | 536 | /** 537 | * Clear cache 538 | */ 539 | clearCache(): void { 540 | this.cache.clear(); 541 | this.logger.debug('Context cache cleared'); 542 | } 543 | 544 | /** 545 | * Get context resolution statistics 546 | */ 547 | getStats(): typeof this.stats { 548 | return { 549 | ...this.stats, 550 | providerUsage: new Map(this.stats.providerUsage) 551 | }; 552 | } 553 | 554 | /** 555 | * Reset statistics 556 | */ 557 | resetStats(): void { 558 | this.stats = { 559 | totalResolutions: 0, 560 | successfulResolutions: 0, 561 | cacheHits: 0, 562 | cacheMisses: 0, 563 | providerUsage: new Map(Array.from(this.providers.keys()).map(name => [name, 0])), 564 | averageConfidence: 0, 565 | averageResolutionTime: 0 566 | }; 567 | } 568 | } 569 | 570 | /** 571 | * Factory function to create context resolver 572 | */ 573 | export function createContextResolver(logger: Logger): ContextResolver { 574 | return new ContextResolver(logger); 575 | } ``` -------------------------------------------------------------------------------- /server/prompts/analysis/advanced_analysis_engine.md: -------------------------------------------------------------------------------- ```markdown 1 | # Advanced Analysis Engine 2 | 3 | ## Description 4 | Complex template testing prompt with advanced Nunjucks features including conditionals, loops, inheritance, filters, and multi-format output generation. Designed to stress-test the template engine with maximum complexity. 5 | 6 | ## System Message 7 | You are an advanced analysis engine capable of processing complex requests with multiple data sources, analysis types, and output formats. Handle all template complexity gracefully and provide structured, comprehensive analysis. This prompt tests advanced template features including nested conditionals, complex loops, filters, and dynamic content generation. 8 | 9 | ## User Message Template 10 | # 🔍 Advanced Analysis: {{ topic | title if topic else "General Analysis" }} 11 | 12 | ## 🎯 Analysis Configuration 13 | {% if analysis_type %} 14 | **Analysis Type**: {{ analysis_type | upper | replace("_", " ") }} 15 | {% else %} 16 | **Analysis Type**: COMPREHENSIVE MULTI-DIMENSIONAL ANALYSIS 17 | {% endif %} 18 | 19 | {% set complexity_score = 0 %} 20 | {% if sources %}{% set complexity_score = complexity_score + sources|length %}{% endif %} 21 | {% if focus_areas %}{% set complexity_score = complexity_score + focus_areas|length %}{% endif %} 22 | {% if constraints %}{% set complexity_score = complexity_score + constraints|length %}{% endif %} 23 | 24 | **Complexity Score**: {{ complexity_score }} 25 | {% if complexity_score > 10 %}🔥 MAXIMUM COMPLEXITY{% elif complexity_score > 5 %}⚡ HIGH COMPLEXITY{% else %}📊 STANDARD COMPLEXITY{% endif %} 26 | 27 | {% if sources %} 28 | ## 📊 Data Sources Strategy 29 | {% for source in sources %} 30 | {% set loop_info = loop %} 31 | {{ loop.index }}. **{{ source | title | replace("_", " ") }}** 32 | {% if source == "web" %} 33 | - Current information and real-time trends 34 | - SEO-optimized content analysis 35 | - Social sentiment indicators 36 | {% elif source == "papers" %} 37 | - Peer-reviewed research and academic insights 38 | - Methodological rigor and scientific validation 39 | - Historical context and longitudinal studies 40 | {% elif source == "news" %} 41 | - Breaking developments and current events 42 | - Media bias analysis and fact-checking 43 | - Timeline construction and event correlation 44 | {% elif source == "social" %} 45 | - Public opinion and sentiment analysis 46 | - Viral trend identification 47 | - Demographic and geographic distribution 48 | {% elif source == "industry" %} 49 | - Market reports and industry intelligence 50 | - Competitive landscape analysis 51 | - Regulatory and compliance considerations 52 | {% elif source == "expert" %} 53 | - Professional opinions and expert interviews 54 | - Specialized knowledge and insider perspectives 55 | - Best practices and lessons learned 56 | {% else %} 57 | - {{ source | title }} data collection and analysis 58 | - Source-specific validation and verification 59 | - Integration with primary research methods 60 | {% endif %} 61 | {% if loop.first %}**PRIMARY SOURCE** - Foundation for analysis{% endif %} 62 | {% if loop.last %}**VALIDATION SOURCE** - Final verification and cross-reference{% endif %} 63 | {% if loop.length > 3 and loop.index == loop.length // 2 %}**PIVOT SOURCE** - Mid-analysis validation{% endif %} 64 | {% endfor %} 65 | 66 | ### Source Integration Matrix 67 | {% for source in sources %} 68 | {% for other_source in sources %} 69 | {% if source != other_source %} 70 | - **{{ source|title }} ↔ {{ other_source|title }}**: {% if source == "web" and other_source == "papers" %}Validate web claims with academic research{% elif source == "news" and other_source == "social" %}Cross-reference news with social sentiment{% else %}Comparative analysis and validation{% endif %} 71 | {% endif %} 72 | {% endfor %} 73 | {% endfor %} 74 | {% else %} 75 | ## 📊 Standard Data Sources 76 | 1. **Web Research** - Current information and trends 77 | 2. **Academic Papers** - Scholarly perspective and research 78 | 3. **News Articles** - Recent developments and context 79 | 4. **Industry Reports** - Market intelligence and analysis 80 | {% endif %} 81 | 82 | {% if depth %} 83 | ## 🎚️ Analysis Depth: {{ depth | title }} 84 | {% set depth_config = { 85 | "surface": { 86 | "description": "Quick overview and key highlights", 87 | "time_estimate": "15-30 minutes", 88 | "output_length": "2-3 pages", 89 | "detail_level": "High-level summary with actionable insights" 90 | }, 91 | "standard": { 92 | "description": "Detailed examination of core topics", 93 | "time_estimate": "1-2 hours", 94 | "output_length": "5-8 pages", 95 | "detail_level": "Comprehensive analysis with supporting evidence" 96 | }, 97 | "comprehensive": { 98 | "description": "Deep-dive analysis across all dimensions", 99 | "time_estimate": "3-5 hours", 100 | "output_length": "10-15 pages", 101 | "detail_level": "Multi-perspective evaluation with detailed frameworks" 102 | }, 103 | "expert": { 104 | "description": "Expert-level technical analysis", 105 | "time_estimate": "6+ hours", 106 | "output_length": "15+ pages", 107 | "detail_level": "Advanced methodology with strategic recommendations" 108 | } 109 | } %} 110 | 111 | {% set current_depth = depth_config[depth] if depth in depth_config else depth_config["standard"] %} 112 | 113 | - **Description**: {{ current_depth.description }} 114 | - **Estimated Time**: {{ current_depth.time_estimate }} 115 | - **Expected Output**: {{ current_depth.output_length }} 116 | - **Detail Level**: {{ current_depth.detail_level }} 117 | 118 | {% if depth == "expert" %} 119 | ### Expert Analysis Framework 120 | - Advanced statistical analysis and modeling 121 | - Multi-dimensional correlation mapping 122 | - Predictive analytics and scenario planning 123 | - Risk assessment matrices and mitigation strategies 124 | {% endif %} 125 | {% endif %} 126 | 127 | {% if constraints %} 128 | ## ⚙️ Analysis Constraints & Parameters 129 | {% for key, value in constraints.items() %} 130 | - **{{ key | replace("_", " ") | title }}**: {{ value }} 131 | {% if key == "time_limit" %}*Prioritizing high-impact insights within timeframe*{% endif %} 132 | {% if key == "budget" %}*Cost-effective research methods and resource allocation*{% endif %} 133 | {% if key == "scope" %}*Focused analysis boundaries and exclusion criteria*{% endif %} 134 | {% if key == "audience" %}*Content and presentation tailored for {{ value }}*{% endif %} 135 | {% endfor %} 136 | 137 | ### Constraint Impact Assessment 138 | {% if constraints.time_limit %} 139 | ⏱️ **Time Pressure**: Accelerated methodology with focus on critical insights 140 | {% endif %} 141 | {% if constraints.budget %} 142 | 💰 **Budget Optimization**: Prioritized research activities and cost-effective approaches 143 | {% endif %} 144 | {% if constraints.scope %} 145 | 🎯 **Scope Management**: Clear boundaries prevent scope creep and maintain focus 146 | {% endif %} 147 | {% endif %} 148 | 149 | {% if focus_areas %} 150 | ## 🔍 Multi-Dimensional Focus Areas 151 | {% for area in focus_areas %} 152 | ### {{ loop.index }}. {{ area | title }} Analysis Framework 153 | {% if area == "technical" %} 154 | #### Technical Deep-Dive 155 | - **Architecture & Design**: System specifications and technical requirements 156 | - **Implementation Details**: Development methodologies and best practices 157 | - **Performance Metrics**: Benchmarking, optimization, and scalability analysis 158 | - **Technology Stack**: Tools, frameworks, and platform considerations 159 | - **Risk Assessment**: Technical debt, security vulnerabilities, maintenance overhead 160 | {% elif area == "business" %} 161 | #### Business Impact Analysis 162 | - **Market Dynamics**: Competitive landscape and positioning strategies 163 | - **Financial Modeling**: Revenue projections, cost analysis, and ROI calculations 164 | - **Strategic Alignment**: Business objectives and organizational fit 165 | - **Stakeholder Impact**: Customer, partner, and internal team considerations 166 | - **Go-to-Market**: Launch strategies, marketing approaches, and sales enablement 167 | {% elif area == "ethical" %} 168 | #### Ethical Framework Evaluation 169 | - **Moral Implications**: Ethical principles and moral reasoning analysis 170 | - **Stakeholder Impact**: Effects on users, communities, and society 171 | - **Privacy & Rights**: Data protection, consent, and individual autonomy 172 | - **Fairness & Bias**: Algorithmic fairness and discrimination prevention 173 | - **Transparency**: Explainability, accountability, and public discourse 174 | {% elif area == "regulatory" %} 175 | #### Regulatory Compliance Analysis 176 | - **Legal Framework**: Applicable laws, regulations, and compliance requirements 177 | - **Risk Assessment**: Legal exposure, liability, and mitigation strategies 178 | - **Policy Implications**: Government relations and regulatory strategy 179 | - **International Considerations**: Cross-border regulations and harmonization 180 | - **Future Regulations**: Anticipated regulatory changes and preparation strategies 181 | {% elif area == "social" %} 182 | #### Social Impact Assessment 183 | - **Community Effects**: Local and global community implications 184 | - **Cultural Considerations**: Cross-cultural sensitivity and adaptation 185 | - **Public Opinion**: Social acceptance and public discourse analysis 186 | - **Digital Divide**: Accessibility and inclusion considerations 187 | - **Behavioral Change**: Individual and collective behavior modifications 188 | {% elif area == "environmental" %} 189 | #### Environmental Sustainability Analysis 190 | - **Carbon Footprint**: Environmental impact and sustainability metrics 191 | - **Resource Usage**: Energy consumption and resource optimization 192 | - **Lifecycle Assessment**: End-to-end environmental impact evaluation 193 | - **Green Alternatives**: Sustainable approaches and eco-friendly solutions 194 | - **Regulatory Environment**: Environmental regulations and compliance 195 | {% else %} 196 | #### {{ area | title }} Framework 197 | - **Core Principles**: Fundamental concepts and guiding principles 198 | - **Key Considerations**: Critical factors and decision points 199 | - **Impact Assessment**: Quantitative and qualitative impact evaluation 200 | - **Risk Factors**: Potential challenges and mitigation strategies 201 | - **Best Practices**: Industry standards and recommended approaches 202 | {% endif %} 203 | 204 | {% if not loop.last %} 205 | --- 206 | {% endif %} 207 | {% endfor %} 208 | 209 | ### Focus Area Integration Matrix 210 | {% for area1 in focus_areas %} 211 | {% for area2 in focus_areas %} 212 | {% if area1 != area2 and loop.index0 < loop.index %} 213 | - **{{ area1|title }} ↔ {{ area2|title }}**: Cross-dimensional analysis and synergy identification 214 | {% endif %} 215 | {% endfor %} 216 | {% endfor %} 217 | {% else %} 218 | ## 🔍 Standard Analysis Framework 219 | 1. **Contextual Overview** - Current state and background analysis 220 | 2. **Trend Identification** - Pattern recognition and trend analysis 221 | 3. **Impact Assessment** - Quantitative and qualitative impact evaluation 222 | 4. **Strategic Implications** - Long-term considerations and planning 223 | 5. **Actionable Recommendations** - Specific next steps and implementation guidance 224 | {% endif %} 225 | 226 | {% if format %} 227 | ## 📋 Output Format Specification: {{ format | title | replace("_", " ") }} 228 | {% set format_specs = { 229 | "executive_summary": { 230 | "structure": "Executive Summary → Key Findings → Strategic Recommendations → Appendices", 231 | "length": "2-4 pages + supporting materials", 232 | "audience": "C-level executives, board members, and senior decision makers", 233 | "style": "Concise, decision-focused, high-level strategic content", 234 | "sections": ["Executive Overview", "Critical Insights", "Strategic Options", "Resource Requirements", "Timeline & Milestones"] 235 | }, 236 | "technical_report": { 237 | "structure": "Technical Overview → Detailed Analysis → Implementation Guide → Technical Appendices", 238 | "length": "10-20 pages + technical documentation", 239 | "audience": "Technical teams, engineers, and implementation specialists", 240 | "style": "Detailed, implementation-focused, technical depth", 241 | "sections": ["Technical Architecture", "Detailed Specifications", "Implementation Roadmap", "Technical Risks", "Testing & Validation"] 242 | }, 243 | "presentation": { 244 | "structure": "Slide deck with executive summary, detailed findings, and action items", 245 | "length": "15-25 slides + speaker notes", 246 | "audience": "Mixed stakeholder groups and meeting presentations", 247 | "style": "Visual, presentation-ready, engaging narrative", 248 | "sections": ["Problem Statement", "Analysis Overview", "Key Findings", "Recommendations", "Next Steps"] 249 | }, 250 | "research_paper": { 251 | "structure": "Abstract → Introduction → Literature Review → Analysis → Discussion → Conclusions → References", 252 | "length": "15-30 pages + extensive bibliography", 253 | "audience": "Academic researchers, policy makers, and domain experts", 254 | "style": "Academic rigor, peer-review ready, comprehensive citations", 255 | "sections": ["Abstract", "Introduction", "Methodology", "Results & Analysis", "Discussion", "Conclusions", "Future Research"] 256 | } 257 | } %} 258 | 259 | {% set current_format = format_specs[format] if format in format_specs else format_specs["executive_summary"] %} 260 | 261 | ### Format Configuration 262 | - **Structure**: {{ current_format.structure }} 263 | - **Expected Length**: {{ current_format.length }} 264 | - **Target Audience**: {{ current_format.audience }} 265 | - **Writing Style**: {{ current_format.style }} 266 | 267 | ### Content Sections 268 | {% for section in current_format.sections %} 269 | {{ loop.index }}. **{{ section }}** 270 | {% endfor %} 271 | 272 | ### Presentation Guidelines 273 | {% if format == "executive_summary" %} 274 | - Lead with bottom-line impact and recommendations 275 | - Use bullet points and executive-friendly language 276 | - Include financial implications and resource requirements 277 | - Provide clear decision points and options 278 | {% elif format == "technical_report" %} 279 | - Include detailed technical specifications and diagrams 280 | - Provide step-by-step implementation guidance 281 | - Document technical dependencies and requirements 282 | - Include testing procedures and validation methods 283 | {% elif format == "presentation" %} 284 | - Design slide-ready content with clear headings 285 | - Include data visualizations and infographics 286 | - Provide speaker notes and presentation guidance 287 | - Structure for 30-45 minute presentation window 288 | {% elif format == "research_paper" %} 289 | - Follow academic formatting and citation standards 290 | - Include comprehensive literature review and methodology 291 | - Provide detailed statistical analysis and evidence 292 | - Structure for peer review and academic publication 293 | {% endif %} 294 | {% endif %} 295 | 296 | {% if previous_context %} 297 | ## 🔗 Building Upon Previous Analysis 298 | **Previous Context Integration:** 299 | {{ previous_context }} 300 | 301 | ### Continuity Framework 302 | - **Historical Baseline**: Previous findings serve as analytical foundation 303 | - **Evolutionary Analysis**: Changes and developments since last analysis 304 | - **Gap Identification**: Areas requiring additional investigation 305 | - **Trend Validation**: Confirmation or revision of previous predictions 306 | {% endif %} 307 | 308 | {% if requirements %} 309 | ## ✅ Specific Requirements & Success Criteria 310 | {% for req in requirements %} 311 | {% if req is string %} 312 | {{ loop.index }}. {{ req }} 313 | {% else %} 314 | {{ loop.index }}. **{{ req.category | default("General Requirement") | title }}**: {{ req.description }} 315 | {% if req.priority %} 316 | - **Priority Level**: {{ req.priority | upper }} 317 | {% if req.priority == "critical" %}🔴 CRITICAL - Must be addressed in core analysis 318 | {% elif req.priority == "high" %}🟡 HIGH - Primary focus area 319 | {% elif req.priority == "medium" %}🟢 MEDIUM - Important but not critical 320 | {% else %}⚪ LOW - Include if time/resources permit{% endif %} 321 | {% endif %} 322 | {% if req.examples %} 323 | - **Examples**: {% for ex in req.examples %}{{ ex }}{% if not loop.last %}, {% endif %}{% endfor %} 324 | {% endif %} 325 | {% if req.success_criteria %} 326 | - **Success Criteria**: {{ req.success_criteria }} 327 | {% endif %} 328 | {% endif %} 329 | {% endfor %} 330 | 331 | ### Requirements Validation Matrix 332 | {% for req in requirements %} 333 | {% if req is not string and req.priority %} 334 | - **{{ req.category | default("Requirement " + loop.index|string) }}** ({{ req.priority|upper }}): Validation method and acceptance criteria 335 | {% endif %} 336 | {% endfor %} 337 | {% endif %} 338 | 339 | --- 340 | 341 | ## 🎯 Analysis Execution Framework 342 | 343 | ### Phase 1: Foundation & Context 344 | - Establish analytical baseline and context 345 | - Validate scope and constraints alignment 346 | - Confirm data source accessibility and reliability 347 | 348 | ### Phase 2: Multi-Dimensional Investigation 349 | {% if focus_areas %} 350 | {% for area in focus_areas %} 351 | - **{{ area|title }} Analysis**: Deep-dive investigation and assessment 352 | {% endfor %} 353 | {% else %} 354 | - Comprehensive investigation across all relevant dimensions 355 | {% endif %} 356 | 357 | ### Phase 3: Integration & Synthesis 358 | - Cross-dimensional pattern identification 359 | - Insight synthesis and framework development 360 | - Validation against requirements and constraints 361 | 362 | ### Phase 4: Recommendations & Implementation 363 | - Strategic recommendation development 364 | - Implementation pathway design 365 | - Risk assessment and mitigation planning 366 | 367 | --- 368 | 369 | **🚀 Analysis Request Summary:** 370 | - **Topic**: {{ topic }} 371 | - **Complexity**: {% if sources and focus_areas and constraints %}Maximum ({{ complexity_score }} complexity points){% elif focus_areas or constraints %}High{% else %}Standard{% endif %} 372 | - **Expected Deliverable**: {{ format | default("Comprehensive Analysis") | title }} 373 | - **Timeline**: {% if constraints.time_limit %}{{ constraints.time_limit }}{% else %}Standard delivery window{% endif %} 374 | 375 | {% set template_features_used = [] %} 376 | {% if sources %}{% set _ = template_features_used.append("Dynamic source configuration") %}{% endif %} 377 | {% if focus_areas %}{% set _ = template_features_used.append("Multi-dimensional framework") %}{% endif %} 378 | {% if constraints %}{% set _ = template_features_used.append("Constraint optimization") %}{% endif %} 379 | {% if requirements %}{% set _ = template_features_used.append("Requirements validation") %}{% endif %} 380 | {% if format %}{% set _ = template_features_used.append("Format specialization") %}{% endif %} 381 | 382 | **🔧 Template Features Active**: {{ template_features_used | join(", ") | default("Standard template rendering") }} 383 | 384 | --- 385 | 386 | Please provide a comprehensive analysis following the above specifications. Ensure all requested dimensions are covered, the output matches the specified format requirements, and the analysis demonstrates the full complexity and capability of this advanced template system. 387 | ``` -------------------------------------------------------------------------------- /docs/prompt-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # Prompt Management 2 | 3 | This document describes how to manage prompts in the MCP server using the **consolidated prompt management system** through the `prompt_manager` tool and distributed prompts configuration. 4 | 5 | ## Consolidated Architecture Overview 6 | 7 | The MCP server uses **3 consolidated tools** for all prompt management operations: 8 | 9 | - **`prompt_manager`**: Complete lifecycle management with intelligent analysis and filtering 10 | - **`prompt_engine`**: Execute prompts with framework integration and gate validation 11 | - **`system_control`**: Framework switching, analytics, and system administration 12 | 13 | **Key Benefits:** 14 | 15 | - **Action-Based Interface**: Single tools with multiple actions instead of separate tools 16 | - **Intelligent Features**: Type analysis, framework integration, advanced filtering 17 | - **MCP Protocol Only**: No HTTP API - works through MCP-compatible clients 18 | 19 | ## Distributed Prompts Configuration System 20 | 21 | The server organizes prompts using a distributed configuration system where prompts are organized by category, with each category having its own configuration file. 22 | 23 | ### Key Components 24 | 25 | 1. **promptsConfig.json** - Main configuration file defining categories and imports 26 | 2. **Category-specific prompts.json files** - Each category has its own prompts.json file 27 | 3. **Prompt .md files** - Individual prompt templates using Nunjucks templating 28 | 29 | ## Main Configuration (promptsConfig.json) 30 | 31 | The main configuration file defines all available categories and specifies which category-specific prompts.json files to import: 32 | 33 | ```json 34 | { 35 | "categories": [ 36 | { 37 | "id": "general", 38 | "name": "General", 39 | "description": "General-purpose prompts for everyday tasks" 40 | }, 41 | { 42 | "id": "analysis", 43 | "name": "Analysis", 44 | "description": "Analytical and research-focused prompts" 45 | }, 46 | { 47 | "id": "development", 48 | "name": "Development", 49 | "description": "Software development and coding prompts" 50 | } 51 | // More categories... 52 | ], 53 | "imports": [ 54 | "prompts/general/prompts.json", 55 | "prompts/analysis/prompts.json", 56 | "prompts/development/prompts.json" 57 | // More imports... 58 | ] 59 | } 60 | ``` 61 | 62 | ### Categories 63 | 64 | Each category in the `categories` array has: 65 | 66 | - `id` (string) - Unique identifier for the category 67 | - `name` (string) - Display name for the category 68 | - `description` (string) - Description of the category's purpose 69 | 70 | ### Imports 71 | 72 | The `imports` array lists paths to category-specific prompts.json files, relative to the server's working directory. 73 | 74 | ## Category-Specific Prompts Files 75 | 76 | Each category has its own prompts.json file (e.g., `prompts/general/prompts.json`): 77 | 78 | ```json 79 | { 80 | "prompts": [ 81 | { 82 | "id": "content_analysis", 83 | "name": "Content Analysis", 84 | "category": "analysis", 85 | "description": "Systematic analysis of content using structured methodology", 86 | "file": "content_analysis.md", 87 | "arguments": [ 88 | { 89 | "name": "content", 90 | "description": "The content to analyze", 91 | "required": true 92 | }, 93 | { 94 | "name": "focus", 95 | "description": "Specific focus area for analysis", 96 | "required": false 97 | } 98 | ] 99 | } 100 | // More prompts... 101 | ] 102 | } 103 | ``` 104 | 105 | Each prompt has: 106 | 107 | - `id` (string) - Unique identifier 108 | - `name` (string) - Display name 109 | - `category` (string) - Category this prompt belongs to 110 | - `description` (string) - What the prompt does 111 | - `file` (string) - Path to .md file with template 112 | - `arguments` (array) - Arguments the prompt accepts 113 | - `isChain` (boolean, optional) - Whether this is a chain prompt 114 | - `chainSteps` (array, optional) - Steps for chain prompts 115 | - `onEmptyInvocation` (string, optional) - Behavior when invoked without arguments 116 | 117 | ## Consolidated Prompt Management 118 | 119 | ### prompt_manager Tool Actions 120 | 121 | The `prompt_manager` tool provides comprehensive prompt lifecycle management through **action-based commands**: 122 | 123 | #### Core Actions 124 | 125 | - `list` - List and filter prompts with intelligent search 126 | - `create` - Auto-detect type and create appropriate prompt 127 | - `create_prompt` - Create basic prompt (fast variable substitution) 128 | - `create_template` - Create framework-enhanced template 129 | - `update` - Update existing prompts 130 | - `delete` - Delete prompts with safety checks 131 | 132 | #### Advanced Actions 133 | 134 | - `analyze_type` - Analyze prompt and recommend execution type 135 | - `migrate_type` - Convert between prompt types (prompt ↔ template) 136 | - `modify` - Precision editing of specific sections 137 | - `reload` - Trigger hot-reload of prompt system 138 | 139 | ### Basic Prompt Management 140 | 141 | #### Listing Prompts 142 | 143 | ```bash 144 | # List all prompts 145 | prompt_manager list 146 | 147 | # List prompts in specific category 148 | prompt_manager list filter="category:analysis" 149 | 150 | # List by execution type 151 | prompt_manager list filter="type:template" 152 | 153 | # Combined filters 154 | prompt_manager list filter="category:development type:chain" 155 | 156 | # Intent-based search 157 | prompt_manager list filter="intent:debugging" 158 | ``` 159 | 160 | #### Creating Prompts 161 | 162 | ```bash 163 | # Auto-detect appropriate type 164 | prompt_manager create name="Data Processor" category="analysis" \ 165 | description="Process and analyze data systematically" \ 166 | content="Analyze {{data}} and provide insights on {{focus_area}}" 167 | 168 | # Create basic prompt (fast execution) 169 | prompt_manager create_prompt name="Simple Greeting" category="general" \ 170 | description="Basic personalized greeting" \ 171 | content="Hello {{name}}, welcome to {{service}}!" \ 172 | arguments='[{"name":"name","required":true},{"name":"service","required":false}]' 173 | 174 | # Create framework-enhanced template 175 | prompt_manager create_template name="Research Analysis" category="analysis" \ 176 | description="Comprehensive research analysis using active methodology" \ 177 | content="Research {{topic}} using systematic approach. Focus on {{aspects}}." \ 178 | arguments='[{"name":"topic","required":true},{"name":"aspects","required":false}]' 179 | ``` 180 | 181 | #### Updating Prompts 182 | 183 | ```bash 184 | # Update prompt content 185 | prompt_manager update id="data_processor" \ 186 | content="Enhanced analysis of {{data}} with focus on {{methodology}}" 187 | 188 | # Update prompt metadata 189 | prompt_manager update id="greeting_prompt" \ 190 | name="Enhanced Greeting" \ 191 | description="Improved greeting with personalization" 192 | 193 | # Precision section editing 194 | prompt_manager modify id="analysis_prompt" \ 195 | section="User Message Template" \ 196 | new_content="Analyze {{content}} using {{framework}} methodology" 197 | ``` 198 | 199 | #### Deleting Prompts 200 | 201 | ```bash 202 | # Delete prompt with safety checks 203 | prompt_manager delete id="old_prompt" 204 | 205 | # The system will warn if prompt is referenced by chains or other prompts 206 | ``` 207 | 208 | ### Advanced Features 209 | 210 | #### Type Analysis & Migration 211 | 212 | ```bash 213 | # Analyze existing prompt for optimization recommendations 214 | prompt_manager analyze_type id="basic_analysis" 215 | # Returns: execution type, framework suitability, improvement suggestions 216 | 217 | # Convert prompt to framework-enhanced template 218 | prompt_manager migrate_type id="simple_prompt" target_type="template" 219 | 220 | # Convert template back to basic prompt for speed 221 | prompt_manager migrate_type id="complex_template" target_type="prompt" 222 | ``` 223 | 224 | #### Framework Integration 225 | 226 | ```bash 227 | # Switch to desired framework before creating templates 228 | system_control switch_framework framework="CAGEERF" reason="Complex analysis needed" 229 | 230 | # Create framework-aware template 231 | prompt_manager create_template name="Strategic Analysis" category="business" \ 232 | description="CAGEERF-enhanced strategic analysis" \ 233 | content="Analyze {{situation}} using comprehensive structured approach" 234 | 235 | # Templates automatically use active framework methodology 236 | ``` 237 | 238 | #### Chain Prompt Creation 239 | 240 | ```bash 241 | # Create multi-step chain prompt 242 | prompt_manager create_template name="Research Workflow" category="research" \ 243 | description="Multi-step research and analysis workflow" \ 244 | content="Research workflow for {{topic}} with comprehensive analysis" \ 245 | chain_steps='[ 246 | { 247 | "promptId": "data_collection", 248 | "stepName": "Data Collection", 249 | "inputMapping": {"topic": "research_topic"}, 250 | "outputMapping": {"collected_data": "step1_output"} 251 | }, 252 | { 253 | "promptId": "data_analysis", 254 | "stepName": "Analysis", 255 | "inputMapping": {"data": "step1_output"}, 256 | "outputMapping": {"analysis_result": "final_output"} 257 | } 258 | ]' 259 | ``` 260 | 261 | ### Intelligent Filtering System 262 | 263 | The `prompt_manager list` command supports advanced filtering: 264 | 265 | #### Filter Syntax 266 | 267 | - **Category**: `category:analysis`, `category:development` 268 | - **Type**: `type:prompt`, `type:template`, `type:chain` 269 | - **Intent**: `intent:debugging`, `intent:analysis`, `intent:creation` 270 | - **Confidence**: `confidence:>80`, `confidence:70-90` 271 | - **Framework**: `framework:CAGEERF`, `framework:ReACT` 272 | 273 | #### Advanced Examples 274 | 275 | ```bash 276 | # Find high-confidence templates in analysis category 277 | prompt_manager list filter="category:analysis type:template confidence:>85" 278 | 279 | # Find debugging-related prompts 280 | prompt_manager list filter="intent:debugging" 281 | 282 | # Find prompts suitable for current framework 283 | system_control status # Check active framework 284 | prompt_manager list filter="framework:CAGEERF type:template" 285 | ``` 286 | 287 | ## Advanced Templating with Nunjucks 288 | 289 | The prompt templating system supports **Nunjucks** for dynamic prompt construction: 290 | 291 | ### Key Features 292 | 293 | - **Conditional Logic (`{% if %}`)**: Show/hide content based on arguments 294 | - **Loops (`{% for %}`)**: Iterate over arrays dynamically 295 | - **Standard Placeholders**: `{{variable}}` syntax continues to work 296 | - **Macros (`{% macro %}`)**: Reusable template components 297 | - **Filters (`|`)**: Transform data (upper, lower, default, etc.) 298 | 299 | ### Template Processing 300 | 301 | 1. **Nunjucks Rendering**: Process `{% %}` tags and `{{ }}` placeholders 302 | 2. **Text Reference Expansion**: Handle long text references (ref:xyz) 303 | 3. **Framework Enhancement**: Apply active methodology if template type 304 | 305 | ### Examples 306 | 307 | #### Conditional Logic 308 | 309 | ```nunjucks 310 | {% if user_name %} 311 | Hello, {{user_name}}! Thanks for providing your name. 312 | {% else %} 313 | Hello there! 314 | {% endif %} 315 | 316 | {% if analysis_type == "comprehensive" %} 317 | This requires detailed CAGEERF methodology analysis. 318 | {% elif analysis_type == "quick" %} 319 | Using streamlined ReACT approach. 320 | {% endif %} 321 | ``` 322 | 323 | #### Loops 324 | 325 | ```nunjucks 326 | Please analyze the following data points: 327 | {% for item in data_list %} 328 | - {{ loop.index }}. {{ item }} 329 | {% endfor %} 330 | ``` 331 | 332 | #### Macros for Reusability 333 | 334 | ```nunjucks 335 | {% macro analysis_section(title, content, methodology) %} 336 | ## {{ title }} 337 | **Methodology**: {{ methodology }} 338 | **Content**: {{ content }} 339 | {% endmacro %} 340 | 341 | {{ analysis_section("Market Analysis", market_data, "CAGEERF") }} 342 | {{ analysis_section("Risk Assessment", risk_data, "5W1H") }} 343 | ``` 344 | 345 | #### Filters 346 | 347 | ```nunjucks 348 | Topic: {{ topic_name | upper }} 349 | Priority: {{ priority_level | default("Medium") }} 350 | Items: {{ item_count | length }} total 351 | Summary: {{ long_text | truncate(100) }} 352 | ``` 353 | 354 | ## Integration with Consolidated Architecture 355 | 356 | ### MCP Tool Coordination 357 | 358 | ```bash 359 | # Complete workflow using all 3 tools 360 | system_control status # Check system state 361 | system_control switch_framework framework="CAGEERF" # Set methodology 362 | prompt_manager create_template name="..." category="..." # Create template 363 | prompt_engine >>template_name input="data" gate_validation=true # Execute with gates 364 | system_control analytics # Monitor performance 365 | ``` 366 | 367 | ### Framework-Aware Operations 368 | 369 | ```bash 370 | # Framework affects template creation and execution 371 | system_control list_frameworks # See available frameworks 372 | system_control switch_framework framework="ReACT" reason="Problem-solving focus" 373 | 374 | # Templates created after switching inherit framework 375 | prompt_manager create_template name="Problem Solver" category="analysis" 376 | 377 | # Execute with framework enhancement 378 | prompt_engine >>problem_solver issue="complex problem" execution_mode="template" 379 | ``` 380 | 381 | ### Performance Monitoring 382 | 383 | ```bash 384 | # Monitor prompt management operations 385 | system_control analytics include_history=true 386 | # Shows: prompt creation stats, execution statistics, framework usage 387 | 388 | # Check system health 389 | system_control health 390 | # Includes: prompt loading status, template processing health, framework integration 391 | ``` 392 | 393 | ## File Management 394 | 395 | ### Automatic File Operations 396 | 397 | When using `prompt_manager`, the system automatically: 398 | 399 | 1. **Creates .md files** in appropriate category directories 400 | 2. **Updates prompts.json** in category folders 401 | 3. **Maintains file consistency** across configuration and files 402 | 4. **Triggers hot-reload** to refresh the system 403 | 404 | ### File Structure 405 | 406 | ``` 407 | prompts/ 408 | ├── analysis/ 409 | │ ├── prompts.json # Category prompt registry 410 | │ ├── content_analysis.md # Individual prompt templates 411 | │ └── research_workflow.md 412 | ├── development/ 413 | │ ├── prompts.json 414 | │ ├── code_review.md 415 | │ └── debugging_guide.md 416 | └── promptsConfig.json # Main configuration 417 | ``` 418 | 419 | ### Generated .md File Structure 420 | 421 | ```markdown 422 | # Prompt Name 423 | 424 | ## Description 425 | 426 | Prompt description explaining purpose and usage 427 | 428 | ## System Message 429 | 430 | Optional system message for framework enhancement 431 | 432 | ## User Message Template 433 | 434 | Template content with {{variables}} and Nunjucks logic 435 | 436 | ## Arguments 437 | 438 | - name: Description (required/optional) 439 | - focus: Analysis focus area (optional) 440 | 441 | ## Chain Steps (for chain prompts) 442 | 443 | 1. Step 1: Data Collection 444 | 2. Step 2: Analysis 445 | 3. Step 3: Recommendations 446 | ``` 447 | 448 | ## Troubleshooting 449 | 450 | ### Common Issues 451 | 452 | #### Tool Not Found Errors 453 | 454 | - **Issue**: `create_category tool not found` 455 | - **Solution**: Use `prompt_manager` with action: `prompt_manager create_category` 456 | 457 | #### Legacy Tool References 458 | 459 | - **Issue**: Documentation mentions `update_prompt` standalone tool 460 | - **Solution**: Use consolidated tool: `prompt_manager update id="..." content="..."` 461 | 462 | #### HTTP API Errors 463 | 464 | - **Issue**: HTTP fetch examples don't work 465 | - **Solution**: MCP server uses MCP protocol only - use MCP-compatible clients 466 | 467 | #### Framework Integration Issues 468 | 469 | - **Issue**: Templates not getting framework enhancement 470 | - **Solution**: Verify active framework with `system_control status` and use `create_template` action 471 | 472 | ### Debug Commands 473 | 474 | ```bash 475 | # Check system health including prompt loading 476 | system_control health 477 | 478 | # Verify prompt registration 479 | prompt_manager list 480 | 481 | # Check framework integration 482 | system_control status 483 | 484 | # View comprehensive diagnostics 485 | system_control diagnostics 486 | ``` 487 | 488 | ## Best Practices 489 | 490 | ### Prompt Type Selection 491 | 492 | - **Basic Prompts**: Use `create_prompt` for simple variable substitution (fastest) 493 | - **Framework Templates**: Use `create_template` for analysis, reasoning, complex tasks 494 | - **Chains**: Provide `chain_steps` array for multi-step workflows - chain status detected automatically 495 | 496 | ### Framework Integration 497 | 498 | - Switch to appropriate framework before creating templates 499 | - Use `analyze_type` to get recommendations for existing prompts 500 | - Use `migrate_type` to upgrade prompts for framework enhancement 501 | 502 | ### Organization 503 | 504 | - Group related prompts into logical categories 505 | - Use descriptive names and comprehensive descriptions 506 | - Leverage Nunjucks for maintainable, reusable templates 507 | - Test prompts with various argument combinations 508 | 509 | ### Performance Optimization 510 | 511 | - Use basic prompts for simple operations (bypasses framework overhead) 512 | - Use templates when methodology enhancement adds value 513 | - Monitor performance with `system_control analytics` 514 | - Consider prompt complexity vs. execution speed trade-offs 515 | 516 | ## Advanced Workflows 517 | 518 | ### Template Development Workflow 519 | 520 | ```bash 521 | # 1. Analyze requirements 522 | prompt_manager analyze_type id="existing_prompt" # If converting existing 523 | 524 | # 2. Set appropriate framework 525 | system_control switch_framework framework="CAGEERF" 526 | 527 | # 3. Create framework-enhanced template 528 | prompt_manager create_template name="Advanced Analysis" category="research" 529 | 530 | # 4. Test execution with gates 531 | prompt_engine >>advanced_analysis input="test data" gate_validation=true 532 | 533 | # 5. Monitor performance 534 | system_control analytics 535 | ``` 536 | 537 | ### Chain Development Workflow 538 | 539 | ```bash 540 | # 1. Create individual step prompts 541 | prompt_manager create_template name="collect_data" category="research" 542 | prompt_manager create_template name="analyze_data" category="research" 543 | prompt_manager create_template name="generate_insights" category="research" 544 | 545 | # 2. Create chain prompt linking steps 546 | prompt_manager create_template name="research_pipeline" category="research" \ 547 | chain_steps='[{"promptId":"collect_data","stepName":"Collection"}, {"promptId":"analyze_data","stepName":"Analysis"}]' 548 | 549 | # 3. Execute complete chain with LLM coordination (requires semantic LLM integration) 550 | prompt_engine >>research_pipeline topic="market analysis" llm_driven_execution=true 551 | 552 | # 4. Monitor chain execution 553 | system_control status # Check execution state 554 | ``` 555 | 556 | ## Migration from Legacy Tools 557 | 558 | If you have references to old tool names: 559 | 560 | | Legacy Tool | Consolidated Usage | 561 | | ----------------------- | ----------------------------------- | 562 | | `create_category` | `prompt_manager create_category` | 563 | | `update_prompt` | `prompt_manager create` or `update` | 564 | | `delete_prompt` | `prompt_manager delete` | 565 | | `modify_prompt_section` | `prompt_manager modify` | 566 | | `reload_prompts` | `prompt_manager reload` | 567 | | `listprompts` | `prompt_manager list` | 568 | 569 | **Key Changes:** 570 | 571 | - **No HTTP API**: Use MCP protocol through compatible clients 572 | - **Action-Based**: Single tools with actions instead of separate tools 573 | - **Enhanced Features**: Type analysis, framework integration, intelligent filtering 574 | - **Consolidated**: 3 tools instead of 24+ legacy tools 575 | 576 | --- 577 | 578 | The consolidated prompt management system provides sophisticated prompt lifecycle management while maintaining simplicity and performance. The `prompt_manager` tool offers comprehensive capabilities from basic CRUD operations to advanced features like type analysis, framework integration, and intelligent filtering, all within the efficient 3-tool consolidated architecture. 579 | ``` -------------------------------------------------------------------------------- /plans/outputschema-realtime-progress-and-validation.md: -------------------------------------------------------------------------------- ```markdown 1 | # OutputSchema Real-Time Progress & Gate Validation Implementation Plan 2 | 3 | ## Overview 4 | 5 | This plan integrates real-time progress tracking with visual gate validation to create a comprehensive user experience for chain executions and quality gate monitoring. The implementation builds upon the current Option 2 (Minimal Compliance) foundation while adding progressive enhancement capabilities for modern MCP clients. 6 | 7 | ## Core Feature Integration 8 | 9 | ### 1. Real-Time Progress Tracking 10 | 11 | #### Chain Execution Progress 12 | - **Live Progress Bars**: Visual percentage indicators for overall chain completion 13 | - **Step-by-Step Status**: Individual step progress with state transitions (pending → running → completed/failed) 14 | - **Time Analytics**: Real-time ETA calculations based on historical execution patterns 15 | - **Performance Metrics**: Live memory usage, CPU utilization, and execution speed tracking 16 | - **Multi-Chain Monitoring**: Concurrent chain execution progress with session isolation 17 | 18 | #### Progress Data Structure 19 | ```typescript 20 | interface RealTimeChainProgress extends ChainProgress { 21 | // Enhanced progress tracking 22 | progressPercentage: number; // 0-100 overall completion 23 | estimatedTimeRemaining: number; // ETA in milliseconds 24 | currentStepProgress: number; // 0-100 current step completion 25 | 26 | // Performance metrics 27 | performanceMetrics: { 28 | memoryUsage: NodeJS.MemoryUsage; 29 | executionSpeed: number; // steps per second 30 | resourceUtilization: number; // 0-1 CPU/memory usage 31 | }; 32 | 33 | // Historical context 34 | averageStepTime: number; // milliseconds 35 | slowestStepId?: string; 36 | fastestStepId?: string; 37 | } 38 | ``` 39 | 40 | ### 2. Visual Gate Validation 41 | 42 | #### Interactive Validation Display 43 | - **Real-Time Gate Status**: Live indicators showing gate evaluation progress 44 | - **Quality Score Visualization**: Graphical representation of validation scores (0-1 range) 45 | - **Detailed Failure Analysis**: Structured explanations with actionable recommendations 46 | - **Retry Mechanism Integration**: Visual retry controls with attempt tracking 47 | - **Validation History**: Historical gate performance with trend analysis 48 | 49 | #### Gate Validation Data Structure 50 | ```typescript 51 | interface VisualGateValidation extends GateValidationResult { 52 | // Enhanced validation tracking 53 | validationProgress: number; // 0-100 validation completion 54 | currentGateIndex: number; // Which gate is being evaluated 55 | 56 | // Visual representation data 57 | gateStatusMap: Map<string, { 58 | status: 'pending' | 'running' | 'passed' | 'failed'; 59 | score?: number; 60 | progressPercentage: number; 61 | startTime: number; 62 | estimatedCompletion?: number; 63 | }>; 64 | 65 | // Retry mechanism 66 | retryAttempts: Array<{ 67 | attemptNumber: number; 68 | timestamp: number; 69 | result: 'passed' | 'failed' | 'timeout'; 70 | improvedGates: string[]; // Gates that passed after retry 71 | }>; 72 | 73 | // User actionable data 74 | recommendations: Array<{ 75 | gateId: string; 76 | priority: 'high' | 'medium' | 'low'; 77 | actionType: 'retry' | 'modify' | 'skip' | 'review'; 78 | description: string; 79 | estimatedFixTime?: number; 80 | }>; 81 | } 82 | ``` 83 | 84 | ## Technical Implementation Strategy 85 | 86 | ### Phase 1: Schema Enhancement (Week 1) 87 | 88 | #### Enhanced OutputSchema Definitions 89 | ```typescript 90 | // Extend existing chainProgressSchema 91 | export const enhancedChainProgressSchema = chainProgressSchema.extend({ 92 | progressPercentage: z.number().min(0).max(100), 93 | estimatedTimeRemaining: z.number().optional(), 94 | currentStepProgress: z.number().min(0).max(100), 95 | performanceMetrics: z.object({ 96 | memoryUsage: z.object({ 97 | heapUsed: z.number(), 98 | heapTotal: z.number(), 99 | external: z.number() 100 | }), 101 | executionSpeed: z.number(), 102 | resourceUtilization: z.number().min(0).max(1) 103 | }), 104 | averageStepTime: z.number(), 105 | slowestStepId: z.string().optional(), 106 | fastestStepId: z.string().optional() 107 | }); 108 | 109 | // Enhanced gate validation schema 110 | export const visualGateValidationSchema = gateValidationSchema.extend({ 111 | validationProgress: z.number().min(0).max(100), 112 | currentGateIndex: z.number(), 113 | gateStatusMap: z.record(z.object({ 114 | status: z.enum(['pending', 'running', 'passed', 'failed']), 115 | score: z.number().optional(), 116 | progressPercentage: z.number().min(0).max(100), 117 | startTime: z.number(), 118 | estimatedCompletion: z.number().optional() 119 | })), 120 | retryAttempts: z.array(z.object({ 121 | attemptNumber: z.number(), 122 | timestamp: z.number(), 123 | result: z.enum(['passed', 'failed', 'timeout']), 124 | improvedGates: z.array(z.string()) 125 | })), 126 | recommendations: z.array(z.object({ 127 | gateId: z.string(), 128 | priority: z.enum(['high', 'medium', 'low']), 129 | actionType: z.enum(['retry', 'modify', 'skip', 'review']), 130 | description: z.string(), 131 | estimatedFixTime: z.number().optional() 132 | })) 133 | }); 134 | ``` 135 | 136 | #### Integration Points 137 | - **ChainSessionManager**: Add progress tracking hooks to existing session management 138 | - **GateEvaluator**: Enhance gate evaluation with progress callbacks 139 | - **FrameworkStateManager**: Add performance metrics collection 140 | - **ResponseFormatter**: Extend formatters to handle enhanced schemas 141 | 142 | ### Phase 2: Real-Time Infrastructure (Week 2) 143 | 144 | #### SSE Event Streaming 145 | ```typescript 146 | // New progress event types 147 | export enum ProgressEventType { 148 | CHAIN_STARTED = 'chain:started', 149 | CHAIN_PROGRESS = 'chain:progress', 150 | STEP_STARTED = 'step:started', 151 | STEP_COMPLETED = 'step:completed', 152 | GATE_VALIDATION_STARTED = 'gate:validation:started', 153 | GATE_VALIDATION_PROGRESS = 'gate:validation:progress', 154 | GATE_VALIDATION_COMPLETED = 'gate:validation:completed', 155 | EXECUTION_METRICS = 'execution:metrics' 156 | } 157 | 158 | // Progress event streaming service 159 | export class ProgressEventStreamer { 160 | private sseConnections: Map<string, Response> = new Map(); 161 | 162 | broadcast(eventType: ProgressEventType, data: any): void { 163 | const event = { 164 | type: eventType, 165 | timestamp: Date.now(), 166 | data 167 | }; 168 | 169 | this.sseConnections.forEach((response, clientId) => { 170 | response.write(`data: ${JSON.stringify(event)}\n\n`); 171 | }); 172 | } 173 | 174 | addClient(clientId: string, response: Response): void { 175 | response.writeHead(200, { 176 | 'Content-Type': 'text/event-stream', 177 | 'Cache-Control': 'no-cache', 178 | 'Connection': 'keep-alive' 179 | }); 180 | 181 | this.sseConnections.set(clientId, response); 182 | } 183 | } 184 | ``` 185 | 186 | #### Chain Execution Integration 187 | ```typescript 188 | // Enhanced ChainOrchestrator with progress tracking 189 | export class EnhancedChainOrchestrator extends ChainOrchestrator { 190 | private progressStreamer: ProgressEventStreamer; 191 | 192 | async executeStep( 193 | session: ChainExecutionSession, 194 | stepId: string 195 | ): Promise<StepExecutionResult> { 196 | // Emit step start event 197 | this.progressStreamer.broadcast(ProgressEventType.STEP_STARTED, { 198 | sessionId: session.sessionId, 199 | stepId, 200 | stepName: session.chainDefinition.steps[stepId]?.name, 201 | progressPercentage: this.calculateOverallProgress(session, stepId) 202 | }); 203 | 204 | // Execute step with performance monitoring 205 | const startTime = Date.now(); 206 | const startMemory = process.memoryUsage(); 207 | 208 | const result = await super.executeStep(session, stepId); 209 | 210 | // Calculate performance metrics 211 | const executionTime = Date.now() - startTime; 212 | const endMemory = process.memoryUsage(); 213 | const memoryDelta = endMemory.heapUsed - startMemory.heapUsed; 214 | 215 | // Emit progress update 216 | this.progressStreamer.broadcast(ProgressEventType.CHAIN_PROGRESS, { 217 | sessionId: session.sessionId, 218 | currentStep: stepId, 219 | progressPercentage: this.calculateOverallProgress(session, stepId), 220 | estimatedTimeRemaining: this.calculateETA(session, stepId), 221 | performanceMetrics: { 222 | stepExecutionTime: executionTime, 223 | memoryDelta, 224 | currentMemoryUsage: endMemory 225 | } 226 | }); 227 | 228 | return result; 229 | } 230 | } 231 | ``` 232 | 233 | ### Phase 3: Gate Validation Enhancement (Week 3) 234 | 235 | #### Enhanced Gate Evaluation 236 | ```typescript 237 | export class VisualGateEvaluator extends BaseGateEvaluator { 238 | private progressStreamer: ProgressEventStreamer; 239 | 240 | async evaluateGates( 241 | content: string, 242 | gates: GateDefinition[], 243 | context: EvaluationContext 244 | ): Promise<VisualGateValidation> { 245 | const sessionId = context.sessionId || 'anonymous'; 246 | 247 | // Initialize gate status map 248 | const gateStatusMap = new Map(); 249 | gates.forEach((gate, index) => { 250 | gateStatusMap.set(gate.id, { 251 | status: 'pending', 252 | progressPercentage: 0, 253 | startTime: 0 254 | }); 255 | }); 256 | 257 | // Emit validation start event 258 | this.progressStreamer.broadcast(ProgressEventType.GATE_VALIDATION_STARTED, { 259 | sessionId, 260 | totalGates: gates.length, 261 | gateStatusMap: Object.fromEntries(gateStatusMap) 262 | }); 263 | 264 | const results: GateEvaluationResult[] = []; 265 | const retryAttempts: Array<any> = []; 266 | 267 | for (let i = 0; i < gates.length; i++) { 268 | const gate = gates[i]; 269 | 270 | // Update gate status to running 271 | gateStatusMap.set(gate.id, { 272 | status: 'running', 273 | progressPercentage: 0, 274 | startTime: Date.now() 275 | }); 276 | 277 | // Emit progress update 278 | this.progressStreamer.broadcast(ProgressEventType.GATE_VALIDATION_PROGRESS, { 279 | sessionId, 280 | currentGateIndex: i, 281 | currentGateId: gate.id, 282 | overallProgress: (i / gates.length) * 100, 283 | gateStatusMap: Object.fromEntries(gateStatusMap) 284 | }); 285 | 286 | // Evaluate gate with progress callbacks 287 | const result = await this.evaluateGateWithProgress(gate, content, context, (progress) => { 288 | gateStatusMap.set(gate.id, { 289 | status: 'running', 290 | progressPercentage: progress, 291 | startTime: gateStatusMap.get(gate.id)!.startTime 292 | }); 293 | 294 | this.progressStreamer.broadcast(ProgressEventType.GATE_VALIDATION_PROGRESS, { 295 | sessionId, 296 | currentGateIndex: i, 297 | currentGateId: gate.id, 298 | currentGateProgress: progress, 299 | overallProgress: ((i + progress/100) / gates.length) * 100, 300 | gateStatusMap: Object.fromEntries(gateStatusMap) 301 | }); 302 | }); 303 | 304 | // Update final status 305 | gateStatusMap.set(gate.id, { 306 | status: result.passed ? 'passed' : 'failed', 307 | progressPercentage: 100, 308 | startTime: gateStatusMap.get(gate.id)!.startTime, 309 | score: result.score 310 | }); 311 | 312 | results.push(result); 313 | 314 | // Handle retry logic if gate failed 315 | if (!result.passed && gate.allowRetry) { 316 | const retryResult = await this.handleGateRetry(gate, content, context, retryAttempts); 317 | if (retryResult) { 318 | gateStatusMap.set(gate.id, { 319 | status: 'passed', 320 | progressPercentage: 100, 321 | startTime: gateStatusMap.get(gate.id)!.startTime, 322 | score: retryResult.score 323 | }); 324 | results[results.length - 1] = retryResult; 325 | } 326 | } 327 | } 328 | 329 | // Generate recommendations 330 | const recommendations = this.generateActionableRecommendations(results, gates); 331 | 332 | // Emit final validation complete event 333 | this.progressStreamer.broadcast(ProgressEventType.GATE_VALIDATION_COMPLETED, { 334 | sessionId, 335 | overallResult: results.every(r => r.passed), 336 | totalTime: Date.now() - (gateStatusMap.values().next().value?.startTime || 0), 337 | recommendations 338 | }); 339 | 340 | return { 341 | ...this.consolidateResults(results), 342 | validationProgress: 100, 343 | currentGateIndex: gates.length - 1, 344 | gateStatusMap: Object.fromEntries(gateStatusMap), 345 | retryAttempts, 346 | recommendations 347 | }; 348 | } 349 | } 350 | ``` 351 | 352 | ### Phase 4: Client Integration (Week 4) 353 | 354 | #### WebUI Components 355 | ```typescript 356 | // React component for chain progress visualization 357 | export const ChainProgressTracker: React.FC<{ 358 | sessionId: string; 359 | onComplete?: (result: ChainExecutionResult) => void; 360 | }> = ({ sessionId, onComplete }) => { 361 | const [progress, setProgress] = useState<RealTimeChainProgress | null>(null); 362 | const [gateValidation, setGateValidation] = useState<VisualGateValidation | null>(null); 363 | 364 | useEffect(() => { 365 | // Connect to SSE stream 366 | const eventSource = new EventSource(`/api/progress/stream/${sessionId}`); 367 | 368 | eventSource.addEventListener('chain:progress', (event) => { 369 | const data = JSON.parse(event.data); 370 | setProgress(data); 371 | }); 372 | 373 | eventSource.addEventListener('gate:validation:progress', (event) => { 374 | const data = JSON.parse(event.data); 375 | setGateValidation(data); 376 | }); 377 | 378 | return () => eventSource.close(); 379 | }, [sessionId]); 380 | 381 | return ( 382 | <div className="chain-progress-tracker"> 383 | <ChainProgressBar progress={progress} /> 384 | <StepStatusIndicators steps={progress?.steps || []} /> 385 | <GateValidationDisplay validation={gateValidation} /> 386 | <PerformanceMetrics metrics={progress?.performanceMetrics} /> 387 | </div> 388 | ); 389 | }; 390 | ``` 391 | 392 | #### MCP Client Integration 393 | ```typescript 394 | // Enhanced MCP client with progress tracking 395 | export class ProgressAwareMCPClient extends MCPClient { 396 | private progressCallbacks: Map<string, (progress: any) => void> = new Map(); 397 | 398 | async executeChainWithProgress( 399 | chainId: string, 400 | args: any, 401 | onProgress?: (progress: RealTimeChainProgress) => void, 402 | onGateValidation?: (validation: VisualGateValidation) => void 403 | ): Promise<ToolResponse> { 404 | const sessionId = generateSessionId(); 405 | 406 | // Register progress callbacks 407 | if (onProgress) { 408 | this.progressCallbacks.set(`${sessionId}:progress`, onProgress); 409 | } 410 | if (onGateValidation) { 411 | this.progressCallbacks.set(`${sessionId}:gate`, onGateValidation); 412 | } 413 | 414 | // Execute chain with enhanced response parsing 415 | const response = await this.callTool('prompt_engine', { 416 | command: `>>${chainId}`, 417 | ...args, 418 | sessionOptions: { 419 | sessionId, 420 | trackProgress: true, 421 | enableGateVisualization: true 422 | } 423 | }); 424 | 425 | // Parse structured content for progress data 426 | if (response.structuredContent?.chainProgress) { 427 | onProgress?.(response.structuredContent.chainProgress); 428 | } 429 | 430 | if (response.structuredContent?.gateValidation) { 431 | onGateValidation?.(response.structuredContent.gateValidation); 432 | } 433 | 434 | return response; 435 | } 436 | } 437 | ``` 438 | 439 | ### Phase 5: Backward Compatibility & Progressive Enhancement 440 | 441 | #### Compatibility Strategy 442 | ```typescript 443 | // Feature detection and graceful degradation 444 | export class ProgressCompatibilityLayer { 445 | static detectClientCapabilities(request: Request): ClientCapabilities { 446 | const userAgent = request.headers['user-agent'] || ''; 447 | const acceptHeader = request.headers.accept || ''; 448 | 449 | return { 450 | supportsSSE: acceptHeader.includes('text/event-stream'), 451 | supportsStructuredOutput: request.headers['x-mcp-structured'] === 'true', 452 | supportsProgressTracking: request.headers['x-progress-tracking'] === 'true', 453 | clientType: this.identifyClientType(userAgent) 454 | }; 455 | } 456 | 457 | static adaptResponse( 458 | response: ToolResponse, 459 | capabilities: ClientCapabilities 460 | ): ToolResponse { 461 | if (!capabilities.supportsStructuredOutput) { 462 | // Return Option 2 minimal compliance response 463 | return { 464 | content: response.content, 465 | isError: response.isError 466 | }; 467 | } 468 | 469 | if (!capabilities.supportsProgressTracking) { 470 | // Remove progress-specific structured data 471 | const { chainProgress, gateValidation, ...otherData } = response.structuredContent || {}; 472 | return { 473 | ...response, 474 | structuredContent: otherData 475 | }; 476 | } 477 | 478 | return response; // Full enhanced response 479 | } 480 | } 481 | ``` 482 | 483 | ## Implementation Benefits 484 | 485 | ### User Experience Enhancement 486 | - **Visual Feedback**: Clear progress indicators reduce uncertainty during long-running operations 487 | - **Error Understanding**: Detailed gate validation results help users understand and fix issues 488 | - **Performance Insights**: Real-time metrics help users optimize their chains and prompts 489 | - **Professional Interface**: Modern UI components suitable for enterprise environments 490 | 491 | ### Developer Benefits 492 | - **Debugging Capabilities**: Detailed progress tracking aids in identifying bottlenecks 493 | - **Performance Optimization**: Real-time metrics enable performance tuning 494 | - **Quality Assurance**: Enhanced gate validation improves output quality 495 | - **Extensibility**: Modular design allows easy addition of new progress types 496 | 497 | ### System Integration 498 | - **Backward Compatible**: Maintains Option 2 minimal compliance as foundation 499 | - **Progressive Enhancement**: Advanced features only activate for capable clients 500 | - **Scalable Architecture**: Event-driven design supports multiple concurrent clients 501 | - **Future-Proof**: Extensible schema design accommodates future enhancements 502 | 503 | ## Success Metrics 504 | 505 | ### Technical Metrics 506 | - **Performance Impact**: < 5% overhead for progress tracking 507 | - **Memory Usage**: < 10MB additional memory for progress state 508 | - **Response Time**: < 100ms additional latency for enhanced responses 509 | - **Compatibility**: 100% backward compatibility maintained 510 | 511 | ### User Experience Metrics 512 | - **Progress Clarity**: Users report clear understanding of execution status 513 | - **Error Resolution**: Reduced time to fix gate validation failures 514 | - **Perceived Performance**: Improved user satisfaction with long-running operations 515 | - **Adoption Rate**: Percentage of clients utilizing enhanced features 516 | 517 | ## Risk Mitigation 518 | 519 | ### Performance Risks 520 | - **Mitigation**: Configurable progress granularity (can reduce update frequency) 521 | - **Monitoring**: Real-time performance impact measurement 522 | - **Fallback**: Automatic degradation to minimal compliance if performance degrades 523 | 524 | ### Compatibility Risks 525 | - **Mitigation**: Comprehensive client capability detection 526 | - **Testing**: Automated testing with various client types 527 | - **Documentation**: Clear migration guides for client implementations 528 | 529 | ### Implementation Complexity 530 | - **Mitigation**: Phased rollout with incremental feature addition 531 | - **Testing**: Extensive integration testing at each phase 532 | - **Documentation**: Detailed implementation guides and examples 533 | 534 | This implementation plan provides a comprehensive foundation for real-time progress tracking and visual gate validation while maintaining the stability and compatibility of the existing system. ``` -------------------------------------------------------------------------------- /server/tests/scripts/integration-server-startup.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | /** 3 | * Server Startup Integration Tests - Node.js Script Version 4 | * Tests extracted from GitHub Actions inline scripts 5 | */ 6 | 7 | async function runServerStartupIntegrationTests() { 8 | // Global timeout for entire test suite 9 | const globalTimeout = setTimeout(() => { 10 | console.error('❌ Integration tests timed out after 2 minutes - forcing exit'); 11 | console.log('💀 Global timeout triggered - forcing immediate process exit...'); 12 | process.exit(1); 13 | }, 120000); // 2 minutes 14 | 15 | try { 16 | console.log('🧪 Running Server Startup Integration tests...'); 17 | console.log('📋 Testing full server initialization sequence and error recovery'); 18 | 19 | // Import modules 20 | const { Application } = await import('../../dist/runtime/application.js'); 21 | const { createSimpleLogger } = await import('../../dist/logging/index.js'); 22 | const { globalResourceTracker } = await import('../../dist/utils/global-resource-tracker.js'); 23 | 24 | let logger, orchestrator; 25 | 26 | // Timeout wrapper for individual operations 27 | async function runWithTimeout(operation, timeoutMs = 10000, operationName = 'operation') { 28 | return Promise.race([ 29 | operation(), 30 | new Promise((_, reject) => 31 | setTimeout(() => reject(new Error(`${operationName} timed out after ${timeoutMs}ms`)), timeoutMs) 32 | ) 33 | ]); 34 | } 35 | 36 | // Setup for each test - reuse orchestrator to prevent multiple full startups 37 | function setupTest() { 38 | if (!logger) { 39 | logger = createSimpleLogger(); 40 | } 41 | if (!orchestrator) { 42 | orchestrator = new Application(logger); 43 | } 44 | return orchestrator; 45 | } 46 | 47 | // Simple assertion helpers 48 | function assertEqual(actual, expected, testName) { 49 | const actualStr = JSON.stringify(actual); 50 | const expectedStr = JSON.stringify(expected); 51 | if (actualStr === expectedStr) { 52 | console.log(`✅ ${testName}: PASSED`); 53 | return true; 54 | } else { 55 | console.error(`❌ ${testName}: FAILED`); 56 | console.error(` Expected: ${expectedStr}`); 57 | console.error(` Actual: ${actualStr}`); 58 | return false; 59 | } 60 | } 61 | 62 | function assertTruthy(value, testName) { 63 | if (value) { 64 | console.log(`✅ ${testName}: PASSED`); 65 | return true; 66 | } else { 67 | console.error(`❌ ${testName}: FAILED - Expected truthy value, got: ${value}`); 68 | return false; 69 | } 70 | } 71 | 72 | function assertType(value, expectedType, testName) { 73 | if (typeof value === expectedType) { 74 | console.log(`✅ ${testName}: PASSED`); 75 | return true; 76 | } else { 77 | console.error(`❌ ${testName}: FAILED - Expected type ${expectedType}, got: ${typeof value}`); 78 | return false; 79 | } 80 | } 81 | 82 | function assertHasProperty(obj, property, testName) { 83 | if (obj && typeof obj === 'object' && property in obj) { 84 | console.log(`✅ ${testName}: PASSED`); 85 | return true; 86 | } else { 87 | console.error(`❌ ${testName}: FAILED - Object does not have property: ${property}`); 88 | return false; 89 | } 90 | } 91 | 92 | function assertGreaterThan(actual, expected, testName) { 93 | if (actual > expected) { 94 | console.log(`✅ ${testName}: PASSED (${actual} > ${expected})`); 95 | return true; 96 | } else { 97 | console.error(`❌ ${testName}: FAILED (${actual} <= ${expected})`); 98 | return false; 99 | } 100 | } 101 | 102 | function assertGreaterThanOrEqual(actual, expected, testName) { 103 | if (actual >= expected) { 104 | console.log(`✅ ${testName}: PASSED (${actual} >= ${expected})`); 105 | return true; 106 | } else { 107 | console.error(`❌ ${testName}: FAILED (${actual} < ${expected})`); 108 | return false; 109 | } 110 | } 111 | 112 | function assertLessThan(actual, expected, testName) { 113 | if (actual < expected) { 114 | console.log(`✅ ${testName}: PASSED (${actual} < ${expected})`); 115 | return true; 116 | } else { 117 | console.error(`❌ ${testName}: FAILED (${actual} >= ${expected})`); 118 | return false; 119 | } 120 | } 121 | 122 | function assertIsArray(value, testName) { 123 | if (Array.isArray(value)) { 124 | console.log(`✅ ${testName}: PASSED`); 125 | return true; 126 | } else { 127 | console.error(`❌ ${testName}: FAILED - Expected array, got: ${typeof value}`); 128 | return false; 129 | } 130 | } 131 | 132 | let testResults = []; 133 | 134 | // Test 1: Full Server Initialization Sequence 135 | console.log('🔍 Test 1: Full Server Initialization Sequence'); 136 | 137 | setupTest(); 138 | 139 | try { 140 | // Step 1: Load Configuration 141 | await runWithTimeout(() => orchestrator.loadConfiguration(), 5000, 'Configuration loading'); 142 | testResults.push(assertTruthy(orchestrator.config, 'Configuration loaded')); 143 | testResults.push(assertTruthy(orchestrator.config !== null, 'Configuration not null')); 144 | 145 | // Step 2: Load Prompts Data 146 | await runWithTimeout(() => orchestrator.loadPromptsData(), 10000, 'Prompts data loading'); 147 | const promptsCount = orchestrator.promptsData ? orchestrator.promptsData.length : 0; 148 | testResults.push(assertGreaterThanOrEqual(promptsCount, 0, 'Prompts data loaded or initialized')); 149 | 150 | // Step 3: Initialize Modules 151 | await runWithTimeout(() => orchestrator.initializeModules(), 8000, 'Module initialization'); 152 | testResults.push(assertTruthy(orchestrator.mcpToolsManager, 'MCP tools manager initialized')); 153 | testResults.push(assertTruthy(orchestrator.mcpToolsManager !== null, 'MCP tools manager not null')); 154 | 155 | // Step 4: Get Diagnostic Info 156 | const healthInfo = await runWithTimeout(() => orchestrator.getDiagnosticInfo(), 3000, 'Diagnostic info retrieval'); 157 | testResults.push(assertTruthy(healthInfo, 'Health info retrieved')); 158 | testResults.push(assertType(healthInfo, 'object', 'Health info is object')); 159 | testResults.push(assertGreaterThan(Object.keys(healthInfo).length, 0, 'Health info has properties')); 160 | 161 | } catch (error) { 162 | console.error(`❌ Full initialization sequence failed: ${error.message}`); 163 | testResults.push(false); 164 | } 165 | 166 | // Test 2: Configuration Loading 167 | console.log('🔍 Test 2: Configuration Loading'); 168 | 169 | // Reuse the already configured orchestrator from Test 1 170 | try { 171 | // Configuration already loaded in Test 1, just verify it exists 172 | 173 | testResults.push(assertTruthy(orchestrator.config, 'Configuration object exists')); 174 | testResults.push(assertHasProperty(orchestrator.config, 'server', 'Config has server property')); 175 | 176 | if (orchestrator.config && orchestrator.config.server) { 177 | testResults.push(assertHasProperty(orchestrator.config.server, 'name', 'Server config has name')); 178 | testResults.push(assertHasProperty(orchestrator.config.server, 'version', 'Server config has version')); 179 | testResults.push(assertType(orchestrator.config.server.name, 'string', 'Server name is string')); 180 | testResults.push(assertGreaterThan(orchestrator.config.server.name.length, 0, 'Server name not empty')); 181 | } else { 182 | testResults.push(false); 183 | testResults.push(false); 184 | testResults.push(false); 185 | testResults.push(false); 186 | } 187 | } catch (error) { 188 | console.error(`❌ Configuration loading failed: ${error.message}`); 189 | testResults.push(false); 190 | testResults.push(false); 191 | testResults.push(false); 192 | testResults.push(false); 193 | testResults.push(false); 194 | testResults.push(false); 195 | } 196 | 197 | // Test 3: Prompts Data Loading 198 | console.log('🔍 Test 3: Prompts Data Loading'); 199 | 200 | // Reuse the already initialized orchestrator from Test 1 201 | try { 202 | // Prompts data already loaded in Test 1, just verify it exists 203 | 204 | testResults.push(assertTruthy(orchestrator.promptsData !== undefined, 'Prompts data property exists')); 205 | 206 | const promptsDataIsValid = Array.isArray(orchestrator.promptsData) || orchestrator.promptsData === null; 207 | testResults.push(assertTruthy(promptsDataIsValid, 'Prompts data is array or null')); 208 | 209 | if (orchestrator.promptsData && orchestrator.promptsData.length > 0) { 210 | const firstPrompt = orchestrator.promptsData[0]; 211 | testResults.push(assertHasProperty(firstPrompt, 'id', 'First prompt has id')); 212 | testResults.push(assertHasProperty(firstPrompt, 'name', 'First prompt has name')); 213 | } else { 214 | // If no prompts, that's still valid 215 | testResults.push(assertTruthy(true, 'Empty prompts handled gracefully')); 216 | testResults.push(assertTruthy(true, 'Empty prompts handled gracefully')); 217 | } 218 | } catch (error) { 219 | console.error(`❌ Prompts data loading failed: ${error.message}`); 220 | testResults.push(false); 221 | testResults.push(false); 222 | testResults.push(false); 223 | testResults.push(false); 224 | } 225 | 226 | // Test 4: Module Initialization 227 | console.log('🔍 Test 4: Module Initialization'); 228 | 229 | // Reuse the already initialized orchestrator from Test 1 230 | try { 231 | // Modules already initialized in Test 1, just verify they exist 232 | 233 | testResults.push(assertTruthy(orchestrator.mcpToolsManager, 'MCP tools manager initialized')); 234 | testResults.push(assertTruthy(orchestrator.mcpToolsManager !== null, 'MCP tools manager not null')); 235 | } catch (error) { 236 | console.error(`❌ Module initialization failed: ${error.message}`); 237 | testResults.push(false); 238 | testResults.push(false); 239 | } 240 | 241 | // Test 5: Health Diagnostics 242 | console.log('🔍 Test 5: Health Diagnostics'); 243 | 244 | // Reuse the already initialized orchestrator from Test 1 245 | try { 246 | // Get fresh diagnostic info from the already initialized orchestrator 247 | const healthInfo = await runWithTimeout(() => orchestrator.getDiagnosticInfo(), 3000, 'Health diagnostics'); 248 | 249 | testResults.push(assertTruthy(healthInfo, 'Health info exists')); 250 | testResults.push(assertType(healthInfo, 'object', 'Health info is object')); 251 | 252 | const diagnosticKeys = Object.keys(healthInfo); 253 | testResults.push(assertGreaterThan(diagnosticKeys.length, 0, 'Health info has diagnostic keys')); 254 | 255 | const hasRelevantKeys = diagnosticKeys.some(key => 256 | key.includes('status') || 257 | key.includes('config') || 258 | key.includes('prompts') || 259 | key.includes('tools') 260 | ); 261 | testResults.push(assertTruthy(hasRelevantKeys, 'Health info contains relevant diagnostic keys')); 262 | 263 | // Test partial initialization health info 264 | const partialOrchestrator = new Application(logger); 265 | await partialOrchestrator.loadConfiguration(); 266 | 267 | const partialHealthInfo = await partialOrchestrator.getDiagnosticInfo(); 268 | testResults.push(assertTruthy(partialHealthInfo, 'Partial health info exists')); 269 | testResults.push(assertType(partialHealthInfo, 'object', 'Partial health info is object')); 270 | 271 | // Clean up partial orchestrator 272 | try { 273 | await partialOrchestrator.shutdown(); 274 | partialOrchestrator.cleanup(); 275 | } catch (error) { 276 | console.warn('⚠️ Warning: Error cleaning up partialOrchestrator:', error.message); 277 | } 278 | 279 | } catch (error) { 280 | console.error(`❌ Health diagnostics failed: ${error.message}`); 281 | testResults.push(false); 282 | testResults.push(false); 283 | testResults.push(false); 284 | testResults.push(false); 285 | testResults.push(false); 286 | testResults.push(false); 287 | } 288 | 289 | // Test 6: Error Recovery 290 | console.log('🔍 Test 6: Error Recovery'); 291 | 292 | try { 293 | // Test configuration loading errors 294 | const failingOrchestrator = new Application(logger); 295 | 296 | const originalLoadConfig = failingOrchestrator.loadConfiguration; 297 | failingOrchestrator.loadConfiguration = async () => { 298 | throw new Error('Mock config error'); 299 | }; 300 | 301 | let configErrorThrown = false; 302 | try { 303 | await failingOrchestrator.loadConfiguration(); 304 | } catch (error) { 305 | if (error.message === 'Mock config error') { 306 | configErrorThrown = true; 307 | } 308 | } 309 | 310 | testResults.push(assertTruthy(configErrorThrown, 'Configuration loading error handled')); 311 | testResults.push(assertTruthy(failingOrchestrator.config === undefined, 'Config remains undefined after error')); 312 | 313 | // Test module initialization errors 314 | const moduleFailOrchestrator = new Application(logger); 315 | await moduleFailOrchestrator.loadConfiguration(); 316 | await moduleFailOrchestrator.loadPromptsData(); 317 | 318 | moduleFailOrchestrator.initializeModules = async () => { 319 | throw new Error('Mock module error'); 320 | }; 321 | 322 | let moduleErrorThrown = false; 323 | try { 324 | await moduleFailOrchestrator.initializeModules(); 325 | } catch (error) { 326 | if (error.message === 'Mock module error') { 327 | moduleErrorThrown = true; 328 | } 329 | } 330 | 331 | testResults.push(assertTruthy(moduleErrorThrown, 'Module initialization error handled')); 332 | 333 | // Clean up error recovery test instances 334 | try { 335 | await failingOrchestrator.shutdown(); 336 | failingOrchestrator.cleanup(); 337 | } catch (error) { 338 | console.warn('⚠️ Warning: Error cleaning up failingOrchestrator:', error.message); 339 | } 340 | 341 | try { 342 | await moduleFailOrchestrator.shutdown(); 343 | moduleFailOrchestrator.cleanup(); 344 | } catch (error) { 345 | console.warn('⚠️ Warning: Error cleaning up moduleFailOrchestrator:', error.message); 346 | } 347 | 348 | } catch (error) { 349 | console.error(`❌ Error recovery test failed: ${error.message}`); 350 | testResults.push(false); 351 | testResults.push(false); 352 | testResults.push(false); 353 | } 354 | 355 | // Test 7: Performance Validation 356 | console.log('🔍 Test 7: Performance Validation'); 357 | 358 | setupTest(); 359 | try { 360 | const start = Date.now(); 361 | 362 | await orchestrator.loadConfiguration(); 363 | await orchestrator.loadPromptsData(); 364 | await orchestrator.initializeModules(); 365 | 366 | const duration = Date.now() - start; 367 | 368 | testResults.push(assertLessThan(duration, 10000, 'Initialization completes within 10 seconds')); 369 | 370 | // Test step timing 371 | const timings = {}; 372 | 373 | const timingOrchestrator = new Application(logger); 374 | 375 | let stepStart = Date.now(); 376 | await timingOrchestrator.loadConfiguration(); 377 | timings.config = Date.now() - stepStart; 378 | 379 | stepStart = Date.now(); 380 | await timingOrchestrator.loadPromptsData(); 381 | timings.prompts = Date.now() - stepStart; 382 | 383 | stepStart = Date.now(); 384 | await timingOrchestrator.initializeModules(); 385 | timings.modules = Date.now() - stepStart; 386 | 387 | testResults.push(assertLessThan(timings.config, 5000, 'Configuration loading under 5 seconds')); 388 | testResults.push(assertLessThan(timings.prompts, 5000, 'Prompts loading under 5 seconds')); 389 | testResults.push(assertLessThan(timings.modules, 5000, 'Module initialization under 5 seconds')); 390 | 391 | console.log(`📊 Initialization timings - Config: ${timings.config}ms, Prompts: ${timings.prompts}ms, Modules: ${timings.modules}ms`); 392 | 393 | // Clean up timing orchestrator 394 | try { 395 | await timingOrchestrator.shutdown(); 396 | timingOrchestrator.cleanup(); 397 | } catch (error) { 398 | console.warn('⚠️ Warning: Error cleaning up timingOrchestrator:', error.message); 399 | } 400 | 401 | } catch (error) { 402 | console.error(`❌ Performance validation failed: ${error.message}`); 403 | testResults.push(false); 404 | testResults.push(false); 405 | testResults.push(false); 406 | testResults.push(false); 407 | } 408 | 409 | // Results Summary 410 | const passedTests = testResults.filter(result => result).length; 411 | const totalTests = testResults.length; 412 | 413 | console.log('\n📊 Server Startup Integration Tests Summary:'); 414 | console.log(` ✅ Passed: ${passedTests}/${totalTests} tests`); 415 | console.log(` 📊 Success Rate: ${((passedTests/totalTests)*100).toFixed(1)}%`); 416 | 417 | // Cleanup: Properly shutdown Application instances to prevent hanging processes 418 | if (orchestrator) { 419 | try { 420 | await orchestrator.shutdown(); 421 | orchestrator.cleanup(); 422 | } catch (error) { 423 | console.warn('⚠️ Warning: Error during orchestrator cleanup:', error.message); 424 | } 425 | } 426 | 427 | // Check for remaining resources before exit 428 | console.log('\n🔍 Checking for remaining global resources...'); 429 | globalResourceTracker.logDiagnostics(); 430 | const cleared = globalResourceTracker.emergencyCleanup(); 431 | if (cleared > 0) { 432 | console.log(`💀 Emergency cleanup cleared ${cleared} additional resources`); 433 | } 434 | 435 | if (passedTests === totalTests) { 436 | console.log('🎉 All Server Startup Integration tests passed!'); 437 | // Emergency process exit to prevent hanging due to global Node.js resources 438 | console.log('💀 Forcing process exit to prevent hanging from global timers...'); 439 | setTimeout(() => process.exit(0), 100); // Small delay to ensure log output 440 | return true; 441 | } else { 442 | console.error('❌ Some Server Startup Integration tests failed'); 443 | // Emergency process exit for failure case as well 444 | console.log('💀 Forcing process exit to prevent hanging from global timers...'); 445 | setTimeout(() => process.exit(1), 100); // Small delay to ensure log output 446 | return false; 447 | } 448 | 449 | } catch (error) { 450 | console.error('❌ Server Startup Integration tests failed with error:', error.message); 451 | if (error.stack) { 452 | console.error('Stack trace:', error.stack); 453 | } 454 | // Emergency process exit for error case as well 455 | console.log('💀 Forcing process exit due to test error to prevent hanging from global timers...'); 456 | setTimeout(() => process.exit(1), 100); // Small delay to ensure log output 457 | return false; 458 | } finally { 459 | // Clear the global timeout 460 | clearTimeout(globalTimeout); 461 | 462 | // Emergency cleanup: Ensure all Application instances are properly shut down 463 | if (typeof orchestrator !== 'undefined' && orchestrator) { 464 | try { 465 | await orchestrator.shutdown(); 466 | orchestrator.cleanup(); 467 | } catch (error) { 468 | console.warn('⚠️ Emergency cleanup warning:', error.message); 469 | } 470 | } 471 | } 472 | } 473 | 474 | // Run the tests 475 | if (import.meta.url === `file://${process.argv[1]}`) { 476 | runServerStartupIntegrationTests().catch(error => { 477 | console.error('❌ Test execution failed:', error); 478 | process.exit(1); 479 | }); 480 | } 481 | 482 | export { runServerStartupIntegrationTests }; ```