This is page 9 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/mcp-tools/tool-description-manager.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tool Description Manager 3 | * 4 | * Manages externalized tool descriptions with graceful fallback to defaults. 5 | * Follows established ConfigManager pattern for consistency with existing architecture. 6 | */ 7 | 8 | import { Logger } from '../logging/index.js'; 9 | import { ConfigManager } from '../config/index.js'; 10 | import { ToolDescription, ToolDescriptionsConfig } from '../types/index.js'; 11 | import * as fs from 'fs/promises'; 12 | import * as path from 'path'; 13 | import { watch, FSWatcher } from 'fs'; 14 | import { EventEmitter } from 'events'; 15 | import { 16 | CAGEERFMethodologyGuide, 17 | ReACTMethodologyGuide, 18 | FiveW1HMethodologyGuide, 19 | SCAMPERMethodologyGuide 20 | } from '../frameworks/methodology/index.js'; 21 | import { MethodologyToolDescriptions } from '../frameworks/types/index.js'; 22 | 23 | /** 24 | * Manages tool descriptions loaded from external configuration with hot-reload support 25 | */ 26 | export class ToolDescriptionManager extends EventEmitter { 27 | private logger: Logger; 28 | private configPath: string; 29 | private descriptions: Map<string, ToolDescription>; 30 | private defaults: Map<string, ToolDescription>; 31 | private methodologyDescriptions: Map<string, MethodologyToolDescriptions>; 32 | private isInitialized: boolean = false; 33 | private fileWatcher?: FSWatcher; 34 | private isWatching: boolean = false; 35 | private reloadDebounceTimer?: NodeJS.Timeout; 36 | 37 | constructor(logger: Logger, configManager: ConfigManager) { 38 | super(); 39 | this.logger = logger; 40 | this.configPath = path.join(configManager.getServerRoot(), 'config', 'tool-descriptions.json'); 41 | this.descriptions = new Map(); 42 | this.defaults = this.createDefaults(); 43 | this.methodologyDescriptions = new Map(); 44 | } 45 | 46 | /** 47 | * Normalize methodology keys for consistent lookup (case-insensitive) 48 | */ 49 | private normalizeMethodologyKey(methodology?: string): string | undefined { 50 | if (!methodology) return undefined; 51 | return methodology.trim().toUpperCase(); 52 | } 53 | 54 | /** 55 | * Create default descriptions as fallback 56 | */ 57 | private createDefaults(): Map<string, ToolDescription> { 58 | return new Map([ 59 | ['prompt_engine', { 60 | description: '🚀 PROMPT ENGINE [HOT-RELOAD]: Processes Nunjucks templates, returns executable instructions. WARNING: Output contains instructions YOU must execute (code gen, analysis, multi-step tasks) - not just information. IMPORTANT: Prompt names are case-insensitive and hyphens are converted to underscores. When your arguments include newlines or multi-line payloads, wrap the call in JSON so the parser receives a single-line command shell.', 61 | shortDescription: 'Execute prompts, templates, and chains', 62 | category: 'execution' 63 | }], 64 | ['prompt_manager', { 65 | description: '🧰 PROMPT MANAGER: Create, update, delete, list, and analyze prompts. Supports gate configuration, temporary gates, and prompt-type migration for full lifecycle control.', 66 | shortDescription: 'Manage prompt lifecycle, gates, and discovery', 67 | category: 'management' 68 | }], 69 | ['system_control', { 70 | description: '⚙️ SYSTEM CONTROL: Unified interface for status reporting, framework and gate controls, analytics, configuration management, and maintenance operations.', 71 | shortDescription: 'Manage framework state, metrics, and maintenance', 72 | category: 'system' 73 | }] 74 | ]); 75 | } 76 | 77 | /** 78 | * Pre-load all methodology descriptions for dynamic switching 79 | */ 80 | private preloadMethodologyDescriptions(): void { 81 | try { 82 | // Initialize all methodology guides 83 | const guides = [ 84 | new CAGEERFMethodologyGuide(), 85 | new ReACTMethodologyGuide(), 86 | new FiveW1HMethodologyGuide(), 87 | new SCAMPERMethodologyGuide() 88 | ]; 89 | 90 | // Pre-load tool descriptions for each methodology using normalized keys 91 | for (const guide of guides) { 92 | const descriptions = guide.getToolDescriptions?.() || {}; 93 | const methodologyKey = this.normalizeMethodologyKey(guide.methodology); 94 | const frameworkKey = this.normalizeMethodologyKey(guide.frameworkId); 95 | 96 | if (methodologyKey) { 97 | this.methodologyDescriptions.set(methodologyKey, descriptions); 98 | } 99 | 100 | if (frameworkKey) { 101 | this.methodologyDescriptions.set(frameworkKey, descriptions); 102 | } 103 | } 104 | 105 | this.logger.info(`✅ Pre-loaded tool descriptions for ${this.methodologyDescriptions.size} methodologies`); 106 | } catch (error) { 107 | this.logger.error(`Failed to pre-load methodology descriptions: ${error instanceof Error ? error.message : String(error)}`); 108 | } 109 | } 110 | 111 | /** 112 | * Initialize by loading descriptions from external config file 113 | */ 114 | async initialize(): Promise<void> { 115 | try { 116 | this.logger.info(`Loading tool descriptions from ${this.configPath}...`); 117 | const content = await fs.readFile(this.configPath, 'utf-8'); 118 | const config: ToolDescriptionsConfig = JSON.parse(content); 119 | 120 | // Validate config structure 121 | if (!config.tools || typeof config.tools !== 'object') { 122 | throw new Error('Invalid tool descriptions config: missing or invalid tools section'); 123 | } 124 | 125 | // Load descriptions 126 | this.descriptions.clear(); 127 | for (const [name, description] of Object.entries(config.tools)) { 128 | this.descriptions.set(name, description); 129 | } 130 | 131 | this.isInitialized = true; 132 | this.logger.info(`✅ Loaded ${this.descriptions.size} tool descriptions from external config (version: ${config.version})`); 133 | 134 | // Pre-load methodology descriptions for dynamic switching 135 | this.preloadMethodologyDescriptions(); 136 | 137 | } catch (error) { 138 | this.logger.warn(`⚠️ Failed to load tool descriptions from ${this.configPath}: ${error instanceof Error ? error.message : String(error)}`); 139 | this.logger.info('🔄 Using hardcoded default descriptions as fallback'); 140 | 141 | // Use defaults as fallback 142 | this.descriptions = new Map(this.defaults); 143 | this.isInitialized = true; 144 | 145 | // Pre-load methodology descriptions for dynamic switching 146 | this.preloadMethodologyDescriptions(); 147 | } 148 | } 149 | 150 | /** 151 | * Get description for a specific tool with corrected priority hierarchy 152 | */ 153 | getDescription( 154 | toolName: string, 155 | frameworkEnabled?: boolean, 156 | activeMethodology?: string, 157 | options?: { applyMethodologyOverride?: boolean } 158 | ): string { 159 | const applyMethodologyOverride = options?.applyMethodologyOverride ?? true; 160 | this.logger.debug(`Getting description for ${toolName} (framework: ${frameworkEnabled}, methodology: ${activeMethodology})`); 161 | const methodologyKey = this.normalizeMethodologyKey(activeMethodology); 162 | const methodologyLogName = activeMethodology ?? methodologyKey; 163 | 164 | // PRIORITY 1: Methodology-specific descriptions from guides (HIGHEST PRIORITY) 165 | if (applyMethodologyOverride && methodologyKey) { 166 | const methodologyDescs = this.methodologyDescriptions.get(methodologyKey); 167 | if (methodologyDescs?.[toolName as keyof MethodologyToolDescriptions]?.description) { 168 | const methodologyDesc = methodologyDescs[toolName as keyof MethodologyToolDescriptions]!.description!; 169 | this.logger.debug(`✅ Using methodology-specific description from ${methodologyLogName} guide for ${toolName}`); 170 | return methodologyDesc; 171 | } 172 | this.logger.debug(`⚠️ No methodology-specific description found for ${toolName} in ${methodologyLogName} guide`); 173 | } 174 | 175 | // Get config/default descriptions for fallback priority checks 176 | const toolDesc = this.descriptions.get(toolName) || this.defaults.get(toolName); 177 | 178 | if (!toolDesc) { 179 | this.logger.warn(`No description found for tool: ${toolName}`); 180 | return `Tool: ${toolName}`; 181 | } 182 | 183 | // PRIORITY 2: Framework-aware descriptions from config (if methodology desc not available) 184 | if (frameworkEnabled !== undefined && toolDesc.frameworkAware) { 185 | if (frameworkEnabled && toolDesc.frameworkAware.enabled) { 186 | this.logger.debug(`✅ Using framework-aware enabled description from config for ${toolName}`); 187 | return toolDesc.frameworkAware.enabled; 188 | } else if (!frameworkEnabled && toolDesc.frameworkAware.disabled) { 189 | this.logger.debug(`✅ Using framework-aware disabled description from config for ${toolName}`); 190 | return toolDesc.frameworkAware.disabled; 191 | } 192 | 193 | // Check for static methodology descriptions in config as fallback 194 | if (frameworkEnabled && methodologyKey && toolDesc.frameworkAware?.methodologies) { 195 | const configMethodologyDescription = 196 | toolDesc.frameworkAware.methodologies[methodologyKey] ?? 197 | (activeMethodology 198 | ? toolDesc.frameworkAware.methodologies[activeMethodology] 199 | : undefined); 200 | if (configMethodologyDescription) { 201 | this.logger.debug( 202 | `✅ Using static methodology description from config for ${toolName} (${methodologyLogName})` 203 | ); 204 | return configMethodologyDescription; 205 | } 206 | } 207 | } 208 | 209 | // PRIORITY 3: Basic config file descriptions (LOWER PRIORITY) 210 | this.logger.debug(`✅ Using basic config/default description for ${toolName}`); 211 | return toolDesc.description; 212 | } 213 | 214 | /** 215 | * Get parameter description for a specific tool parameter 216 | */ 217 | getParameterDescription( 218 | toolName: string, 219 | paramName: string, 220 | frameworkEnabled?: boolean, 221 | activeMethodology?: string, 222 | options?: { applyMethodologyOverride?: boolean } 223 | ): string | undefined { 224 | const applyMethodologyOverride = options?.applyMethodologyOverride ?? true; 225 | const toolDesc = this.descriptions.get(toolName) || this.defaults.get(toolName); 226 | if (!toolDesc?.parameters) return undefined; 227 | const methodologyKey = this.normalizeMethodologyKey(activeMethodology); 228 | 229 | // Check for methodology-specific parameter descriptions first (from pre-loaded cache) 230 | if (applyMethodologyOverride && methodologyKey) { 231 | const methodologyDescs = this.methodologyDescriptions.get(methodologyKey); 232 | const methodologyTool = methodologyDescs?.[toolName as keyof MethodologyToolDescriptions]; 233 | if (methodologyTool?.parameters?.[paramName]) { 234 | return methodologyTool.parameters[paramName]; 235 | } 236 | // Fallback to static config if available 237 | const methodologyParameters = toolDesc.frameworkAware?.methodologyParameters; 238 | const methodologyParamConfig = methodologyParameters?.[methodologyKey] ?? 239 | (activeMethodology ? methodologyParameters?.[activeMethodology] : undefined); 240 | if (methodologyParamConfig?.[paramName]) { 241 | const param = methodologyParamConfig[paramName]; 242 | return typeof param === 'string' ? param : param?.description; 243 | } 244 | } 245 | 246 | // Check for framework-aware parameter descriptions 247 | if (frameworkEnabled !== undefined && toolDesc.frameworkAware) { 248 | const frameworkParams = frameworkEnabled 249 | ? toolDesc.frameworkAware.parametersEnabled 250 | : toolDesc.frameworkAware.parametersDisabled; 251 | 252 | if (frameworkParams?.[paramName]) { 253 | const param = frameworkParams[paramName]; 254 | return typeof param === 'string' ? param : param?.description; 255 | } 256 | } 257 | 258 | // Fall back to default parameters 259 | const param = toolDesc.parameters[paramName]; 260 | return typeof param === 'string' ? param : param?.description; 261 | } 262 | 263 | /** 264 | * Get all available tool names 265 | */ 266 | getAvailableTools(): string[] { 267 | return Array.from(this.descriptions.keys()); 268 | } 269 | 270 | /** 271 | * Check if manager is properly initialized 272 | */ 273 | isReady(): boolean { 274 | return this.isInitialized; 275 | } 276 | 277 | /** 278 | * Get configuration path for debugging 279 | */ 280 | getConfigPath(): string { 281 | return this.configPath; 282 | } 283 | 284 | /** 285 | * Get statistics about loaded descriptions 286 | */ 287 | getStats(): { 288 | totalDescriptions: number; 289 | loadedFromFile: number; 290 | usingDefaults: number; 291 | configPath: string; 292 | isInitialized: boolean; 293 | } { 294 | const loadedFromFile = this.descriptions.size; 295 | const defaultCount = this.defaults.size; 296 | 297 | return { 298 | totalDescriptions: this.descriptions.size, 299 | loadedFromFile: loadedFromFile > defaultCount ? loadedFromFile : 0, 300 | usingDefaults: loadedFromFile <= defaultCount ? defaultCount : 0, 301 | configPath: this.configPath, 302 | isInitialized: this.isInitialized 303 | }; 304 | } 305 | 306 | /** 307 | * Start watching the tool descriptions file for changes 308 | */ 309 | startWatching(): void { 310 | if (this.isWatching) { 311 | this.logger.debug('Tool description file watcher already active'); 312 | return; 313 | } 314 | 315 | try { 316 | this.logger.info(`🔍 Starting file watcher for tool descriptions: ${this.configPath}`); 317 | 318 | this.fileWatcher = watch(this.configPath, (eventType) => { 319 | if (eventType === 'change') { 320 | this.handleFileChange(); 321 | } 322 | }); 323 | 324 | this.fileWatcher.on('error', (error) => { 325 | this.logger.error(`Tool description file watcher error: ${error.message}`); 326 | this.isWatching = false; 327 | }); 328 | 329 | this.isWatching = true; 330 | this.logger.info('✅ Tool description hot-reload watcher started successfully'); 331 | } catch (error) { 332 | this.logger.error(`Failed to start tool description file watcher: ${error instanceof Error ? error.message : String(error)}`); 333 | } 334 | } 335 | 336 | /** 337 | * Stop watching the tool descriptions file 338 | */ 339 | stopWatching(): void { 340 | if (this.fileWatcher) { 341 | this.logger.info('🛑 Stopping tool description file watcher...'); 342 | this.fileWatcher.close(); 343 | this.fileWatcher = undefined; 344 | } 345 | 346 | if (this.reloadDebounceTimer) { 347 | clearTimeout(this.reloadDebounceTimer); 348 | this.reloadDebounceTimer = undefined; 349 | } 350 | 351 | this.isWatching = false; 352 | this.logger.info('✅ Tool description file watcher stopped'); 353 | } 354 | 355 | /** 356 | * Handle file change event with debouncing 357 | */ 358 | private handleFileChange(): void { 359 | // Clear existing timer to debounce rapid file changes 360 | if (this.reloadDebounceTimer) { 361 | clearTimeout(this.reloadDebounceTimer); 362 | } 363 | 364 | // Debounce file changes (wait 500ms after last change) 365 | this.reloadDebounceTimer = setTimeout(async () => { 366 | try { 367 | this.logger.info('📝 Tool descriptions file changed, reloading...'); 368 | await this.reload(); 369 | this.emit('descriptions-changed', this.getStats()); 370 | this.logger.info('✅ Tool descriptions reloaded successfully'); 371 | } catch (error) { 372 | this.logger.error(`Failed to reload tool descriptions: ${error instanceof Error ? error.message : String(error)}`); 373 | this.emit('descriptions-error', error); 374 | } 375 | }, 500); 376 | } 377 | 378 | /** 379 | * Reload descriptions from file 380 | */ 381 | async reload(): Promise<void> { 382 | await this.initialize(); 383 | } 384 | 385 | /** 386 | * Check if file watching is active 387 | */ 388 | isWatchingFile(): boolean { 389 | return this.isWatching; 390 | } 391 | 392 | /** 393 | * Cleanup resources on shutdown 394 | */ 395 | shutdown(): void { 396 | this.stopWatching(); 397 | this.removeAllListeners(); 398 | } 399 | } 400 | 401 | /** 402 | * Factory function following established pattern 403 | */ 404 | export function createToolDescriptionManager( 405 | logger: Logger, 406 | configManager: ConfigManager 407 | ): ToolDescriptionManager { 408 | return new ToolDescriptionManager(logger, configManager); 409 | } 410 | ``` -------------------------------------------------------------------------------- /server/src/gates/core/gate-validator.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Core Gate Validator 3 | * Provides validation capabilities with practical checks for prompt execution 4 | */ 5 | 6 | import { Logger } from '../../logging/index.js'; 7 | import type { GateLoader } from './gate-loader.js'; 8 | import type { 9 | LightweightGateDefinition, 10 | ValidationCheck, 11 | ValidationContext, 12 | GatePassCriteria 13 | } from '../types.js'; 14 | import type { ValidationResult } from '../../execution/types.js'; 15 | import type { LLMIntegrationConfig } from '../../types.js'; 16 | 17 | /** 18 | * Gate validation statistics 19 | */ 20 | export interface GateValidationStatistics { 21 | totalValidations: number; 22 | successfulValidations: number; 23 | failedValidations: number; 24 | averageValidationTime: number; 25 | retryRequests: number; 26 | } 27 | 28 | /** 29 | * Core gate validator with pass/fail logic 30 | */ 31 | export class GateValidator { 32 | private logger: Logger; 33 | private gateLoader: GateLoader; 34 | private llmConfig?: LLMIntegrationConfig; 35 | private validationStats: GateValidationStatistics = { 36 | totalValidations: 0, 37 | successfulValidations: 0, 38 | failedValidations: 0, 39 | averageValidationTime: 0, 40 | retryRequests: 0 41 | }; 42 | private validationTimes: number[] = []; 43 | 44 | constructor(logger: Logger, gateLoader: GateLoader, llmConfig?: LLMIntegrationConfig) { 45 | this.logger = logger; 46 | this.gateLoader = gateLoader; 47 | this.llmConfig = llmConfig; 48 | } 49 | 50 | /** 51 | * Validate content against a gate 52 | */ 53 | async validateGate( 54 | gateId: string, 55 | context: ValidationContext 56 | ): Promise<ValidationResult | null> { 57 | const startTime = Date.now(); 58 | 59 | try { 60 | const gate = await this.gateLoader.loadGate(gateId); 61 | if (!gate) { 62 | this.logger.warn(`Gate not found for validation: ${gateId}`); 63 | return null; 64 | } 65 | 66 | if (gate.type !== 'validation') { 67 | this.logger.debug(`Gate ${gateId} is guidance-only, skipping validation`); 68 | return { 69 | valid: true, 70 | passed: true, 71 | gateId, 72 | checks: [], 73 | retryHints: [], 74 | metadata: { 75 | validationTime: Date.now() - startTime, 76 | checksPerformed: 0, 77 | llmValidationUsed: false 78 | } 79 | }; 80 | } 81 | 82 | this.logger.debug(`Validating content against gate: ${gateId}`); 83 | 84 | // Run validation checks 85 | const checks: ValidationCheck[] = []; 86 | let llmValidationUsed = false; 87 | 88 | if (gate.pass_criteria) { 89 | for (const criteria of gate.pass_criteria) { 90 | const check = await this.runValidationCheck(criteria, context); 91 | checks.push(check); 92 | 93 | if (criteria.type === 'llm_self_check') { 94 | llmValidationUsed = true; 95 | } 96 | } 97 | } 98 | 99 | // Determine overall pass/fail 100 | const passed = checks.length === 0 || checks.every(check => check.passed); 101 | 102 | // Generate retry hints for failures 103 | const retryHints = passed ? [] : this.generateRetryHints(gate, checks); 104 | 105 | const result: ValidationResult = { 106 | valid: passed, 107 | passed, 108 | gateId, 109 | checks, 110 | retryHints, 111 | metadata: { 112 | validationTime: Date.now() - startTime, 113 | checksPerformed: checks.length, 114 | llmValidationUsed 115 | } 116 | }; 117 | 118 | this.logger.debug( 119 | `Gate validation complete: ${gateId} - ${passed ? 'PASSED' : 'FAILED'} (${checks.length} checks)` 120 | ); 121 | 122 | return result; 123 | } catch (error) { 124 | this.logger.error(`Gate validation failed for ${gateId}:`, error); 125 | return { 126 | valid: false, 127 | passed: false, 128 | gateId, 129 | checks: [{ 130 | type: 'system_error', 131 | passed: false, 132 | message: `Validation error: ${error instanceof Error ? error.message : String(error)}` 133 | }], 134 | retryHints: [`Gate validation encountered an error. Please try again.`], 135 | metadata: { 136 | validationTime: Date.now() - startTime, 137 | checksPerformed: 0, 138 | llmValidationUsed: false 139 | } 140 | }; 141 | } 142 | } 143 | 144 | /** 145 | * Validate content against multiple gates 146 | */ 147 | async validateGates( 148 | gateIds: string[], 149 | context: ValidationContext 150 | ): Promise<ValidationResult[]> { 151 | const startTime = Date.now(); 152 | const results: ValidationResult[] = []; 153 | 154 | for (const gateId of gateIds) { 155 | const result = await this.validateGate(gateId, context); 156 | if (result) { 157 | results.push(result); 158 | 159 | // Update statistics based on result 160 | if (result.passed) { 161 | this.validationStats.successfulValidations++; 162 | } else { 163 | this.validationStats.failedValidations++; 164 | } 165 | } 166 | } 167 | 168 | // Update overall statistics 169 | const executionTime = Date.now() - startTime; 170 | this.validationTimes.push(executionTime); 171 | this.validationStats.totalValidations++; 172 | this.updateAverageValidationTime(); 173 | 174 | return results; 175 | } 176 | 177 | /** 178 | * Run a single validation check 179 | */ 180 | private async runValidationCheck( 181 | criteria: GatePassCriteria, 182 | context: ValidationContext 183 | ): Promise<ValidationCheck> { 184 | try { 185 | switch (criteria.type) { 186 | case 'content_check': 187 | return await this.runContentCheck(criteria, context); 188 | case 'pattern_check': 189 | return await this.runPatternCheck(criteria, context); 190 | case 'llm_self_check': 191 | return await this.runLLMSelfCheck(criteria, context); 192 | default: 193 | return { 194 | type: criteria.type, 195 | passed: false, 196 | message: `Unknown validation type: ${criteria.type}` 197 | }; 198 | } 199 | } catch (error) { 200 | this.logger.error(`Validation check failed for ${criteria.type}:`, error); 201 | return { 202 | type: criteria.type, 203 | passed: false, 204 | message: `Check failed: ${error instanceof Error ? error.message : String(error)}` 205 | }; 206 | } 207 | } 208 | 209 | /** 210 | * Run basic content checks (length, basic requirements) 211 | */ 212 | private async runContentCheck( 213 | criteria: GatePassCriteria, 214 | context: ValidationContext 215 | ): Promise<ValidationCheck> { 216 | const content = context.content; 217 | const issues: string[] = []; 218 | 219 | // Length checks 220 | if (criteria.min_length && content.length < criteria.min_length) { 221 | issues.push(`Content too short: ${content.length} < ${criteria.min_length} characters`); 222 | } 223 | 224 | if (criteria.max_length && content.length > criteria.max_length) { 225 | issues.push(`Content too long: ${content.length} > ${criteria.max_length} characters`); 226 | } 227 | 228 | // Required patterns (simple string matching) 229 | if (criteria.required_patterns) { 230 | for (const pattern of criteria.required_patterns) { 231 | if (!content.toLowerCase().includes(pattern.toLowerCase())) { 232 | issues.push(`Missing required content: "${pattern}"`); 233 | } 234 | } 235 | } 236 | 237 | // Forbidden patterns 238 | if (criteria.forbidden_patterns) { 239 | for (const pattern of criteria.forbidden_patterns) { 240 | if (content.toLowerCase().includes(pattern.toLowerCase())) { 241 | issues.push(`Contains forbidden content: "${pattern}"`); 242 | } 243 | } 244 | } 245 | 246 | const passed = issues.length === 0; 247 | 248 | return { 249 | type: 'content_check', 250 | passed, 251 | score: passed ? 1.0 : Math.max(0, 1 - (issues.length * 0.25)), 252 | message: passed ? 'Content checks passed' : issues.join('; '), 253 | details: { 254 | contentLength: content.length, 255 | issuesFound: issues.length 256 | } 257 | }; 258 | } 259 | 260 | /** 261 | * Run pattern matching checks 262 | */ 263 | private async runPatternCheck( 264 | criteria: GatePassCriteria, 265 | context: ValidationContext 266 | ): Promise<ValidationCheck> { 267 | const content = context.content; 268 | const issues: string[] = []; 269 | 270 | // Regex pattern matching 271 | if (criteria.regex_patterns) { 272 | for (const pattern of criteria.regex_patterns) { 273 | try { 274 | const regex = new RegExp(pattern, 'i'); 275 | if (!regex.test(content)) { 276 | issues.push(`Content doesn't match pattern: ${pattern}`); 277 | } 278 | } catch (error) { 279 | issues.push(`Invalid regex pattern: ${pattern}`); 280 | } 281 | } 282 | } 283 | 284 | // Keyword count checking 285 | if (criteria.keyword_count) { 286 | for (const [keyword, requiredCount] of Object.entries(criteria.keyword_count)) { 287 | const matches = (content.toLowerCase().match(new RegExp(keyword.toLowerCase(), 'g')) || []).length; 288 | if (matches < requiredCount) { 289 | issues.push(`Insufficient keyword "${keyword}": found ${matches}, required ${requiredCount}`); 290 | } 291 | } 292 | } 293 | 294 | const passed = issues.length === 0; 295 | 296 | return { 297 | type: 'pattern_check', 298 | passed, 299 | score: passed ? 1.0 : Math.max(0, 1 - (issues.length * 0.3)), 300 | message: passed ? 'Pattern checks passed' : issues.join('; '), 301 | details: { issuesFound: issues.length } 302 | }; 303 | } 304 | 305 | /** 306 | * Run LLM self-check validation 307 | * 308 | * TODO: IMPLEMENT LLM API INTEGRATION 309 | * This requires connecting to the LLM client configured at: 310 | * config.analysis.semanticAnalysis.llmIntegration 311 | * 312 | * Requirements for implementation: 313 | * - LLM client instance (from semantic analyzer) 314 | * - Validation prompt templates 315 | * - Quality assessment criteria 316 | * - Confidence threshold enforcement 317 | * 318 | * Current behavior: Gracefully skips when LLM not configured 319 | */ 320 | private async runLLMSelfCheck( 321 | criteria: GatePassCriteria, 322 | context: ValidationContext 323 | ): Promise<ValidationCheck> { 324 | // Check if LLM integration is configured and enabled 325 | if (!this.llmConfig?.enabled) { 326 | this.logger.debug('[LLM GATE] LLM self-check skipped - LLM integration disabled in config'); 327 | return { 328 | type: 'llm_self_check', 329 | passed: true, // Auto-pass when not configured 330 | score: 1.0, 331 | message: 'LLM validation skipped (not configured - set analysis.semanticAnalysis.llmIntegration.enabled=true)', 332 | details: { 333 | skipped: true, 334 | reason: 'LLM integration disabled in config', 335 | configPath: 'config.analysis.semanticAnalysis.llmIntegration.enabled' 336 | } 337 | }; 338 | } 339 | 340 | if (!this.llmConfig.endpoint) { 341 | this.logger.warn('[LLM GATE] LLM self-check skipped - no endpoint configured'); 342 | return { 343 | type: 'llm_self_check', 344 | passed: true, 345 | score: 1.0, 346 | message: 'LLM validation skipped (no endpoint configured)', 347 | details: { 348 | skipped: true, 349 | reason: 'No LLM endpoint configured', 350 | configPath: 'config.analysis.semanticAnalysis.llmIntegration.endpoint' 351 | } 352 | }; 353 | } 354 | 355 | // TODO: Once LLM API client is available, implement actual validation here 356 | // For now, log that it's not yet implemented even though config is enabled 357 | this.logger.warn('[LLM GATE] LLM self-check requested but API client not yet implemented'); 358 | this.logger.debug(`[LLM GATE] Would validate with template: ${criteria.prompt_template}`); 359 | 360 | return { 361 | type: 'llm_self_check', 362 | passed: true, // Auto-pass until implementation complete 363 | score: 1.0, 364 | message: 'LLM validation not yet implemented (API client integration pending)', 365 | details: { 366 | skipped: true, 367 | reason: 'LLM API client not yet implemented', 368 | configEnabled: this.llmConfig.enabled, 369 | endpoint: this.llmConfig.endpoint, 370 | templateRequested: criteria.prompt_template || 'default', 371 | implementation: 'TODO: Wire LLM client from semantic analyzer' 372 | } 373 | }; 374 | } 375 | 376 | /** 377 | * Generate retry hints based on failed checks 378 | */ 379 | private generateRetryHints( 380 | gate: LightweightGateDefinition, 381 | checks: ValidationCheck[] 382 | ): string[] { 383 | const hints: string[] = []; 384 | const failedChecks = checks.filter(check => !check.passed); 385 | 386 | if (failedChecks.length === 0) { 387 | return hints; 388 | } 389 | 390 | // Add gate-specific guidance as a hint 391 | if (gate.guidance) { 392 | hints.push(`Remember the ${gate.name} guidelines:\n${gate.guidance}`); 393 | } 394 | 395 | // Add specific failure hints 396 | for (const check of failedChecks) { 397 | switch (check.type) { 398 | case 'content_check': 399 | if (check.message.includes('too short')) { 400 | hints.push('Add more detail, examples, or explanations to meet length requirements'); 401 | } 402 | if (check.message.includes('too long')) { 403 | hints.push('Condense your response by removing redundant information'); 404 | } 405 | if (check.message.includes('Missing required content')) { 406 | hints.push(`Ensure your response includes: ${check.message.split(': ')[1]}`); 407 | } 408 | break; 409 | case 'pattern_check': 410 | hints.push('Review pattern matching requirements and adjust content structure'); 411 | break; 412 | case 'llm_self_check': 413 | hints.push('Review the quality criteria and improve content structure and depth'); 414 | break; 415 | } 416 | } 417 | 418 | // Ensure we have at least one helpful hint 419 | if (hints.length === 0) { 420 | hints.push(`${gate.name} validation failed. Please review the requirements and try again.`); 421 | } 422 | 423 | return hints; 424 | } 425 | 426 | /** 427 | * Check if content should be retried based on validation results 428 | * 429 | * @param validationResults - Results from gate validation 430 | * @param currentAttempt - Current attempt number 431 | * @param maxAttempts - Maximum allowed attempts 432 | * @returns true if retry should be attempted 433 | */ 434 | shouldRetry( 435 | validationResults: ValidationResult[], 436 | currentAttempt: number, 437 | maxAttempts: number = 3 438 | ): boolean { 439 | if (currentAttempt >= maxAttempts) { 440 | this.logger.debug('[GATE VALIDATOR] Max attempts reached, no retry'); 441 | return false; 442 | } 443 | 444 | // Retry if any validation gate failed 445 | const shouldRetry = validationResults.some(result => !result.valid); 446 | 447 | if (shouldRetry) { 448 | this.validationStats.retryRequests++; 449 | this.logger.debug('[GATE VALIDATOR] Retry recommended:', { 450 | currentAttempt, 451 | maxAttempts, 452 | failedGates: validationResults.filter(r => !r.valid).map(r => r.gateId) 453 | }); 454 | } 455 | 456 | return shouldRetry; 457 | } 458 | 459 | /** 460 | * Update average validation time 461 | */ 462 | private updateAverageValidationTime(): void { 463 | if (this.validationTimes.length > 0) { 464 | const sum = this.validationTimes.reduce((a, b) => a + b, 0); 465 | this.validationStats.averageValidationTime = sum / this.validationTimes.length; 466 | } 467 | 468 | // Keep only last 100 measurements for rolling average 469 | if (this.validationTimes.length > 100) { 470 | this.validationTimes = this.validationTimes.slice(-100); 471 | } 472 | } 473 | 474 | /** 475 | * Get validation statistics 476 | */ 477 | getStatistics(): GateValidationStatistics { 478 | return { ...this.validationStats }; 479 | } 480 | 481 | /** 482 | * Reset validation statistics 483 | */ 484 | resetStatistics(): void { 485 | this.validationStats = { 486 | totalValidations: 0, 487 | successfulValidations: 0, 488 | failedValidations: 0, 489 | averageValidationTime: 0, 490 | retryRequests: 0 491 | }; 492 | this.validationTimes = []; 493 | this.logger.debug('[GATE VALIDATOR] Statistics reset'); 494 | } 495 | } 496 | 497 | /** 498 | * Create a gate validator instance 499 | */ 500 | export function createGateValidator(logger: Logger, gateLoader: GateLoader, llmConfig?: LLMIntegrationConfig): GateValidator { 501 | return new GateValidator(logger, gateLoader, llmConfig); 502 | } ``` -------------------------------------------------------------------------------- /docs/prompt-vs-template-guide.md: -------------------------------------------------------------------------------- ```markdown 1 | # Execution Modes Guide - Prompt vs Template vs Chain 2 | 3 | ## Overview 4 | 5 | The Claude Prompts MCP Server provides **three intelligent execution modes** optimized for different use cases. All execution happens through the consolidated `prompt_engine` MCP tool with automatic mode detection and framework integration. 6 | 7 | > 📖 **For comprehensive details**, see the [Execution Architecture Guide](./execution-architecture-guide.md) 8 | 9 | ## Three Execution Modes 10 | 11 | - **Prompt**: Basic variable substitution (fastest, ~10-50ms) 12 | - **Template**: Framework-aware processing with methodology guidance (balanced, ~100-500ms) 13 | - **Chain**: LLM-driven iterative execution with instructions (most capable, variable timing) 14 | 15 | **Intelligent Detection**: The `prompt_engine` automatically detects the optimal execution mode based on your prompt's complexity and structure. 16 | 17 | ## Quick Decision Guide 18 | 19 | ### 🚀 Use **Prompt Mode** When: 20 | 21 | - Speed is critical (sub-100ms execution) 22 | - Simple variable substitution needed 23 | - Development/testing scenarios 24 | - Basic text formatting or generation 25 | - No framework methodology needed 26 | 27 | ### 🧠 Use **Template Mode** When: 28 | 29 | - Complex reasoning or analysis required 30 | - Framework methodology benefits (CAGEERF, ReACT, 5W1H, SCAMPER) 31 | - Professional-quality outputs needed 32 | - Quality gates and validation desired 33 | - System prompt enhancement helpful 34 | 35 | ### 🔗 Use **Chain Mode** When: 36 | 37 | - Multi-step processes requiring iterative execution 38 | - Building complex outputs progressively through LLM guidance 39 | - Context needs to flow between steps 40 | - Mission-critical processes requiring step-by-step validation 41 | - LLM should control execution flow and decision-making 42 | 43 | ## MCP Tool Usage 44 | 45 | All execution uses the consolidated `prompt_engine` MCP tool: 46 | 47 | ### Basic Execution 48 | 49 | ```bash 50 | # Automatic mode detection (recommended) 51 | prompt_engine >>my_prompt input="analyze this data" 52 | 53 | # Explicit mode specification 54 | prompt_engine >>my_prompt input="data" execution_mode="prompt" # Force basic mode 55 | prompt_engine >>my_prompt input="data" execution_mode="template" # Force framework mode 56 | prompt_engine >>my_prompt input="data" execution_mode="chain" # Force chain mode 57 | ``` 58 | 59 | ### JSON Command Format 60 | 61 | ```bash 62 | # Alternative JSON syntax for complex arguments 63 | prompt_engine command='{"command": ">>analysis_prompt", "args": {"data": "complex data", "focus": "security"}}' 64 | ``` 65 | 66 | ### Framework Integration 67 | 68 | Templates automatically use the active framework methodology: 69 | 70 | ```bash 71 | # Check current framework and system status 72 | system_control status 73 | 74 | # Switch framework for enhanced template processing 75 | system_control switch_framework framework="CAGEERF" reason="Need comprehensive analysis" 76 | system_control switch_framework framework="ReACT" reason="Problem-solving focus" 77 | ``` 78 | 79 | ## Execution Mode Details 80 | 81 | ### Prompt Mode (`execution_mode="prompt"`) 82 | 83 | **Purpose**: Lightning-fast variable substitution without framework overhead 84 | 85 | **Features**: 86 | - Simple Nunjucks template processing 87 | - Direct variable substitution 88 | - No system prompt enhancement 89 | - Minimal processing overhead 90 | - Optional quality gates 91 | 92 | **Output Format**: `⚡ **Basic Prompt Execution** | 🚀 Fast variable substitution` 93 | 94 | **Example**: 95 | ```bash 96 | prompt_engine >>format_code code="function test() {}" style="prettier" execution_mode="prompt" 97 | ``` 98 | 99 | ### Template Mode (`execution_mode="template"`) 100 | 101 | **Purpose**: Framework-enhanced execution with methodology guidance 102 | 103 | **Features**: 104 | - Full framework processing with active methodology 105 | - System prompt injection from framework guides 106 | - Quality gates integration (when enabled) 107 | - Framework-specific enhancement and validation 108 | - Professional-quality outputs 109 | 110 | **Output Format**: `🧠 **Framework Template Execution** | 🎯 [Active Framework] | ✅ Quality gates applied` 111 | 112 | **Example**: 113 | ```bash 114 | prompt_engine >>security_analysis code="{{codebase}}" execution_mode="template" gate_validation=true 115 | ``` 116 | 117 | ### Chain Mode (`execution_mode="chain"`) 118 | 119 | **Purpose**: LLM-driven iterative execution with step-by-step guidance 120 | 121 | **Revolutionary Architecture**: Instead of server-side orchestration, chains return **structured instructions** that guide the LLM through iterative execution. 122 | 123 | **How Chain Mode Works**: 124 | 1. Chain mode analyzes the chain definition and current state 125 | 2. Returns **LLM instruction template** for the next step to execute 126 | 3. LLM executes the step by calling `prompt_engine` again with step-specific arguments 127 | 4. Server tracks progress and provides instructions for subsequent steps 128 | 5. Process continues until chain completion 129 | 130 | **Features**: 131 | - LLM controls execution flow and decision-making 132 | - Natural conversation flow preservation 133 | - Automatic step progression with state tracking 134 | - Quality gates enabled by default (`gate_validation=true`) 135 | - Error recovery and retry capabilities 136 | - Context preservation between steps 137 | 138 | **Output Format**: `🔗 **Chain Execution**: [Chain Name] | Step [N/Total] | [Next Instructions]` 139 | 140 | **Example**: 141 | ```bash 142 | # Execute complete chain with automatic progression 143 | prompt_engine >>research_pipeline topic="AI Ethics" llm_driven_execution=true 144 | 145 | # Manual step-by-step execution 146 | prompt_engine >>research_pipeline topic="AI Ethics" execution_mode="chain" 147 | # Returns instructions for Step 1, then call again based on instructions 148 | ``` 149 | 150 | ## Performance Characteristics 151 | 152 | | Execution Mode | Speed | Memory | CPU | Framework | Quality Gates | Best Use Case | 153 | |----------------|---------------|--------|-----|-----------|---------------|---------------| 154 | | **Prompt** | ⚡ ~10-50ms | Low | Low | None | Optional | Variable substitution, formatting | 155 | | **Template** | 🚀 ~100-500ms | Medium | Med | Active | Optional | Analysis, reasoning, quality output | 156 | | **Chain** | 🔄 Variable | Medium | Med | Active | Default On | Multi-step processes, complex orchestration | 157 | 158 | ## Advanced Features 159 | 160 | ### Automatic Mode Detection 161 | 162 | When `execution_mode="auto"` (default): 163 | 164 | 1. **Semantic Analysis**: Analyzes prompt structure and complexity 165 | 2. **Chain Detection**: Automatically detects chains based on presence of `chainSteps` 166 | 3. **Template Detection**: Complex arguments, template variables, or analysis requirements 167 | 4. **Prompt Fallback**: Simple variable substitution (default) 168 | 169 | **Detection Logic**: 170 | ```typescript 171 | if (prompt.chainSteps?.length) return "chain" 172 | if (hasComplexArguments || requiresFramework) return "template" 173 | return "prompt" // Default for simple cases 174 | ``` 175 | 176 | ### Framework System Integration 177 | 178 | **Active Framework Impact**: 179 | - **Prompt Mode**: No framework enhancement 180 | - **Template Mode**: Full framework processing with methodology guidance 181 | - **Chain Mode**: Framework-aware step instructions and validation 182 | 183 | **Available Methodologies**: 184 | - **CAGEERF**: Comprehensive structured analysis (Context, Analysis, Goals, Execution, Evaluation, Refinement, Framework) 185 | - **ReACT**: Reasoning and Acting systematic problem-solving approach 186 | - **5W1H**: Who, What, When, Where, Why, How systematic analysis framework 187 | - **SCAMPER**: Creative problem-solving (Substitute, Combine, Adapt, Modify, Put to other uses, Eliminate, Reverse) 188 | 189 | ### Quality Gates Integration 190 | 191 | **Gate Validation Levels**: 192 | - **Prompts**: Optional gates (`gate_validation=false` by default) 193 | - **Templates**: Optional gates (`gate_validation=true` recommended) 194 | - **Chains**: Automatic gates (`gate_validation=true` by default) 195 | 196 | **Gate Types Available**: 197 | - Content analysis (length, readability, tone, grammar) 198 | - Structure validation (format, sections, hierarchy) 199 | - Pattern matching (keywords, patterns, links) 200 | - Custom logic (required fields, completeness, security) 201 | 202 | ## Common Usage Patterns 203 | 204 | ### Speed-Critical Operations 205 | 206 | ```bash 207 | # Use prompt mode for rapid iteration and simple tasks 208 | prompt_engine >>format_json data="{{raw_data}}" execution_mode="prompt" 209 | prompt_engine >>generate_title content="{{article}}" execution_mode="prompt" 210 | ``` 211 | 212 | ### Analysis and Reasoning Tasks 213 | 214 | ```bash 215 | # Templates provide framework methodology enhancement 216 | prompt_engine >>code_review code="{{source}}" execution_mode="template" 217 | prompt_engine >>market_analysis data="{{research}}" execution_mode="template" gate_validation=true 218 | ``` 219 | 220 | ### Complex Multi-Step Processes 221 | 222 | ```bash 223 | # Chains handle iterative LLM-driven execution 224 | prompt_engine >>content_creation_workflow topic="{{subject}}" length="comprehensive" llm_driven_execution=true 225 | prompt_engine >>research_and_analysis_pipeline query="{{research_question}}" depth="thorough" 226 | ``` 227 | 228 | ### Framework-Specific Execution 229 | 230 | ```bash 231 | # Switch framework and execute with methodology guidance 232 | system_control switch_framework framework="CAGEERF" reason="Comprehensive analysis needed" 233 | prompt_engine >>strategic_analysis situation="{{business_context}}" execution_mode="template" 234 | 235 | # Different methodology for creative tasks 236 | system_control switch_framework framework="SCAMPER" reason="Creative problem solving" 237 | prompt_engine >>innovation_workshop challenge="{{problem}}" execution_mode="template" 238 | ``` 239 | 240 | ## Troubleshooting 241 | 242 | ### Mode Detection Issues 243 | 244 | ```bash 245 | # Check what execution mode was detected and why 246 | system_control analytics 247 | 248 | # View detailed execution history and mode usage 249 | system_control status 250 | 251 | # Force specific mode if auto-detection is incorrect 252 | prompt_engine >>my_prompt input="data" execution_mode="template" 253 | ``` 254 | 255 | ### Performance Optimization 256 | 257 | **Speed Priority**: 258 | - Use `execution_mode="prompt"` for maximum speed 259 | - Disable quality gates: `gate_validation=false` 260 | - Use simple variable names and minimal template logic 261 | 262 | **Quality Priority**: 263 | - Use `execution_mode="template"` with appropriate framework 264 | - Enable quality gates: `gate_validation=true` 265 | - Switch to methodology that matches your task type 266 | 267 | **Complex Workflow Priority**: 268 | - Use `execution_mode="chain"` for multi-step processes 269 | - Enable `llm_driven_execution=true` for LLM-driven coordination 270 | - Let LLM control flow - don't force manual step control 271 | 272 | ### Chain Execution Issues 273 | 274 | **Chain Not Progressing**: 275 | ```bash 276 | # Check chain state and progress 277 | system_control status 278 | 279 | # Reset chain state if stuck 280 | prompt_engine >>your_chain_name execution_mode="chain" # Restarts from current step 281 | ``` 282 | 283 | **Chain Step Failures**: 284 | - Quality gates are enabled by default - check gate validation results 285 | - LLM instructions may need clarification - review step definitions 286 | - Framework methodology may not match chain requirements 287 | 288 | ### Framework Integration Issues 289 | 290 | ```bash 291 | # Verify active framework 292 | system_control status 293 | 294 | # Check framework switching history and performance 295 | system_control analytics include_history=true 296 | 297 | # Switch to appropriate framework for your task 298 | system_control switch_framework framework="ReACT" reason="Problem-solving task" 299 | ``` 300 | 301 | ## Migration Guide 302 | 303 | ### From Simple to Complex Execution 304 | 305 | **Development Phase**: 306 | 1. Start with **prompt mode** for rapid prototyping: `execution_mode="prompt"` 307 | 2. Test with various inputs and ensure variable substitution works correctly 308 | 309 | **Quality Phase**: 310 | 3. Upgrade to **template mode** when output quality matters: `execution_mode="template"` 311 | 4. Choose appropriate framework methodology for your use case 312 | 5. Enable quality gates for validation: `gate_validation=true` 313 | 314 | **Production Phase**: 315 | 6. Convert to **chain mode** for multi-step processes: `execution_mode="chain"` 316 | 7. Enable LLM-driven chain coordination: `llm_driven_execution=true` 317 | 8. Monitor execution analytics and optimize based on usage patterns 318 | 319 | ### Framework Selection Guide 320 | 321 | **Choose Framework Based on Task Type**: 322 | 323 | - **CAGEERF**: Complex analysis, strategic planning, comprehensive evaluation 324 | - **ReACT**: Problem-solving, debugging, systematic reasoning tasks 325 | - **5W1H**: Research, investigation, systematic information gathering 326 | - **SCAMPER**: Creative tasks, innovation, brainstorming, design thinking 327 | 328 | ## System Integration 329 | 330 | ### MCP Tool Coordination 331 | 332 | ```bash 333 | # Complete workflow using all consolidated tools 334 | system_control status # Check system and framework state 335 | system_control switch_framework framework="CAGEERF" # Set methodology 336 | prompt_engine >>analysis_task data="{{input}}" execution_mode="template" # Execute with framework 337 | system_control analytics # Monitor performance 338 | ``` 339 | 340 | ### Analytics and Monitoring 341 | 342 | **Execution Statistics**: 343 | - Mode usage distribution (prompt/template/chain percentages) 344 | - Framework usage patterns and switching frequency 345 | - Performance metrics per execution mode 346 | - Quality gate success rates and common failures 347 | 348 | **Access Analytics**: 349 | ```bash 350 | # View comprehensive execution analytics 351 | system_control analytics include_history=true 352 | 353 | # Monitor system health including execution performance 354 | system_control health 355 | 356 | # Reset metrics if needed for fresh tracking 357 | system_control reset_metrics confirm=true 358 | ``` 359 | 360 | ## Best Practices 361 | 362 | ### Execution Mode Selection 363 | 364 | - **Start Simple**: Begin with automatic mode detection, override only when necessary 365 | - **Performance Testing**: Benchmark your specific use cases to choose optimal modes 366 | - **Framework Alignment**: Match execution mode with framework - templates benefit most from methodology 367 | - **Quality Requirements**: Use quality gates in template/chain modes for production workloads 368 | 369 | ### Framework Integration 370 | 371 | - **Task-Appropriate Frameworks**: Switch frameworks based on task type, not randomly 372 | - **Consistent Methodology**: Stick with one framework per logical workflow or project phase 373 | - **Performance Monitoring**: Track framework effectiveness for your specific use cases 374 | - **Strategic Switching**: Plan framework switches, don't change mid-workflow unnecessarily 375 | 376 | ### Chain Development 377 | 378 | - **Step Design**: Design clear, discrete steps that can be executed independently 379 | - **State Management**: Let the LLM handle state and flow - avoid over-constraining steps 380 | - **Error Recovery**: Design steps to be retryable and recoverable from failures 381 | - **Progress Tracking**: Monitor chain execution through system analytics 382 | 383 | ### Quality Assurance 384 | 385 | - **Gate Strategy**: Enable gates for production, disable for development speed 386 | - **Framework Quality**: Use appropriate methodology - each framework has quality strengths 387 | - **Validation Testing**: Test execution modes with realistic data before production 388 | - **Monitoring Setup**: Implement analytics monitoring for production workloads 389 | 390 | ## Advanced Topics 391 | 392 | ### Custom Chain Development 393 | 394 | For creating sophisticated multi-step workflows: 395 | 396 | ```bash 397 | # Define chain with clear step progression 398 | prompt_manager create_template name="custom_analysis_chain" category="analysis" \ 399 | content="Multi-step analysis workflow with data collection and synthesis" \ 400 | chain_steps='[ 401 | {"promptId": "data_collection", "stepName": "Data Collection"}, 402 | {"promptId": "analysis_processing", "stepName": "Analysis"}, 403 | {"promptId": "synthesis_generation", "stepName": "Synthesis"} 404 | ]' 405 | ``` 406 | 407 | ### Performance Optimization Strategies 408 | 409 | **For High-Throughput Scenarios**: 410 | - Batch similar executions using prompt mode 411 | - Cache framework contexts when possible 412 | - Monitor memory usage during chain execution 413 | - Use analytics to identify bottlenecks 414 | 415 | **For Quality-Critical Scenarios**: 416 | - Always enable quality gates in production 417 | - Use appropriate framework methodology consistently 418 | - Implement comprehensive error handling and retry logic 419 | - Monitor quality gate success rates and adjust criteria 420 | 421 | --- 422 | 423 | **Quick Summary**: The MCP server provides three intelligent execution modes accessible through the `prompt_engine` tool. Use automatic mode detection for most cases, prompt mode for speed, template mode for quality with framework enhancement, and chain mode for complex LLM-driven iterative processes. All modes integrate seamlessly with the framework system and quality gates for professional-grade AI prompt execution. ``` -------------------------------------------------------------------------------- /docs/template-development-guide.md: -------------------------------------------------------------------------------- ```markdown 1 | # Template Development Guide 2 | 3 | ## Overview 4 | 5 | This guide covers creating **framework-aware templates** that leverage the Claude Prompts MCP Server's methodology system for enhanced, systematic prompt execution. Templates represent the middle tier of our three-tier execution model, providing structured guidance and quality assurance. 6 | 7 | ## Related Documentation 8 | 9 | - **[Three-Tier Execution System](execution-architecture-guide.md)** - Understanding template execution in the broader system 10 | - **[Prompt Format Guide](prompt-format-guide.md)** - Basic prompt formatting (foundation for templates) 11 | - **[Chain System Analysis](chain-system-analysis.md)** - Using templates within chain workflows 12 | - **[Enhanced Gate System](enhanced-gate-system.md)** - Quality validation for templates 13 | - **[MCP Tools Reference](mcp-tools-reference.md)** - Using tools with template execution 14 | 15 | ## What Are Framework-Aware Templates? 16 | 17 | Framework-aware templates are enhanced prompts that: 18 | - **Integrate with methodology guides** (CAGEERF, ReACT, 5W1H, SCAMPER) 19 | - **Include system prompt injection** for structured thinking 20 | - **Apply quality gates** for validation and consistency 21 | - **Provide structured guidance** to LLMs for better outcomes 22 | 23 | ## Template Structure 24 | 25 | ### Basic Template Format 26 | 27 | ```markdown 28 | # Template Name 29 | 30 | **🔄 TEMPLATE EXECUTION**: Framework-aware processing with {{framework}} methodology 31 | 32 | ## System Message 33 | You are an expert {{role}} who follows systematic approaches to {{task_type}}. 34 | Use the active framework methodology to ensure comprehensive analysis. 35 | 36 | ## User Message Template 37 | {{user_content_with_variables}} 38 | 39 | ## Arguments 40 | - role: The expert role for this template 41 | - task_type: Type of task being performed 42 | - user_content_with_variables: Main template content 43 | ``` 44 | 45 | ### Template Markers 46 | 47 | Templates are identified by specific markers: 48 | 49 | #### Execution Type Markers 50 | ```markdown 51 | **🔄 TEMPLATE EXECUTION**: Framework-aware processing 52 | **⚡ EXECUTION REQUIRED**: Uses template execution for framework integration 53 | ``` 54 | 55 | #### Framework Integration Markers 56 | ```markdown 57 | **📋 FRAMEWORK**: Uses active methodology ({{framework}}) for systematic approach 58 | **🎯 METHODOLOGY**: Applies {{framework}} principles throughout execution 59 | ``` 60 | 61 | ## Framework Integration 62 | 63 | ### Available Frameworks 64 | 65 | #### CAGEERF Framework 66 | **Use for**: Comprehensive structured analysis 67 | - **Context**: Understand the situation 68 | - **Analysis**: Break down the problem 69 | - **Goals**: Define clear objectives 70 | - **Execution**: Implement systematic approach 71 | - **Evaluation**: Assess results 72 | - **Refinement**: Improve based on feedback 73 | - **Framework**: Apply methodology consistently 74 | 75 | ```markdown 76 | # CAGEERF Analysis Template 77 | **🔄 TEMPLATE EXECUTION**: Uses CAGEERF methodology for systematic analysis 78 | 79 | ## System Message 80 | You are an expert analyst who follows the CAGEERF methodology for comprehensive evaluation. 81 | Apply each phase systematically: Context → Analysis → Goals → Execution → Evaluation → Refinement → Framework consistency. 82 | 83 | ## User Message Template 84 | Analyze the following using CAGEERF methodology: 85 | {{content}} 86 | 87 | Focus areas: {{focus_areas}} 88 | ``` 89 | 90 | #### ReACT Framework 91 | **Use for**: Reasoning and action-oriented tasks 92 | - **Reasoning**: Think through the problem systematically 93 | - **Acting**: Take concrete steps based on reasoning 94 | 95 | ```markdown 96 | # ReACT Problem Solving Template 97 | **🔄 TEMPLATE EXECUTION**: Uses ReACT methodology for reasoning and action 98 | 99 | ## System Message 100 | You are an expert problem solver who uses ReACT (Reasoning and Acting) methodology. 101 | For each step: 1) Reason about the situation, 2) Act based on that reasoning. 102 | 103 | ## User Message Template 104 | Apply ReACT methodology to solve: 105 | {{problem_statement}} 106 | 107 | Available actions: {{available_actions}} 108 | ``` 109 | 110 | #### 5W1H Framework 111 | **Use for**: Comprehensive information gathering 112 | - **Who**: Key stakeholders and people involved 113 | - **What**: Core facts and details 114 | - **When**: Timing and sequence 115 | - **Where**: Location and context 116 | - **Why**: Motivations and reasons 117 | - **How**: Methods and processes 118 | 119 | ```markdown 120 | # 5W1H Analysis Template 121 | **🔄 TEMPLATE EXECUTION**: Uses 5W1H methodology for comprehensive analysis 122 | 123 | ## System Message 124 | You are an expert investigator who uses the 5W1H framework for thorough analysis. 125 | Address each question systematically: Who, What, When, Where, Why, and How. 126 | 127 | ## User Message Template 128 | Analyze using 5W1H framework: 129 | {{subject}} 130 | 131 | Priority questions: {{priority_questions}} 132 | ``` 133 | 134 | #### SCAMPER Framework 135 | **Use for**: Creative problem-solving and innovation 136 | - **Substitute**: What can be substituted? 137 | - **Combine**: What can be combined? 138 | - **Adapt**: What can be adapted? 139 | - **Modify**: What can be modified? 140 | - **Put to other uses**: How else can this be used? 141 | - **Eliminate**: What can be eliminated? 142 | - **Reverse**: What can be reversed? 143 | 144 | ```markdown 145 | # SCAMPER Innovation Template 146 | **🔄 TEMPLATE EXECUTION**: Uses SCAMPER methodology for creative problem-solving 147 | 148 | ## System Message 149 | You are an innovation expert who applies SCAMPER methodology for creative solutions. 150 | Systematically explore: Substitute, Combine, Adapt, Modify, Put to other uses, Eliminate, Reverse. 151 | 152 | ## User Message Template 153 | Apply SCAMPER methodology to innovate on: 154 | {{concept}} 155 | 156 | Focus areas: {{focus_areas}} 157 | ``` 158 | 159 | ### Framework Selection 160 | 161 | #### Automatic Framework Application 162 | Templates automatically use the **active framework** set in the system: 163 | 164 | ```bash 165 | # Check current framework 166 | system_control status 167 | 168 | # Switch framework for template execution 169 | system_control switch_framework framework=CAGEERF reason="Need comprehensive analysis" 170 | ``` 171 | 172 | #### Framework-Specific Templates 173 | Create templates optimized for specific frameworks: 174 | 175 | ```markdown 176 | # CAGEERF-Optimized Template 177 | **📋 FRAMEWORK**: Optimized for CAGEERF methodology 178 | 179 | This template works best with CAGEERF framework active. 180 | To use: `system_control switch_framework framework=CAGEERF` 181 | ``` 182 | 183 | ## Template Development Process 184 | 185 | ### Step 1: Define Template Purpose 186 | 187 | ```markdown 188 | # Template Planning 189 | - **Purpose**: What specific task does this template address? 190 | - **Target Framework**: Which methodology best supports this task? 191 | - **Complexity Level**: Simple, moderate, or complex template? 192 | - **Quality Requirements**: What validation is needed? 193 | ``` 194 | 195 | ### Step 2: Choose Template Structure 196 | 197 | #### Simple Template (Basic framework integration) 198 | ```markdown 199 | # Simple Analysis Template 200 | **🔄 TEMPLATE EXECUTION**: Framework-aware analysis 201 | 202 | Analyze: {{content}} 203 | Focus: {{focus_area}} 204 | ``` 205 | 206 | #### Structured Template (Full framework integration) 207 | ```markdown 208 | # Comprehensive Analysis Template 209 | **🔄 TEMPLATE EXECUTION**: Uses {{framework}} for systematic analysis 210 | 211 | ## System Message 212 | You are an expert analyst following {{framework}} methodology. 213 | [Detailed system instructions...] 214 | 215 | ## User Message Template 216 | [Structured template with multiple variables...] 217 | 218 | ## Arguments 219 | - content: Content to analyze 220 | - framework: Active methodology framework 221 | - focus_area: Specific focus for analysis 222 | ``` 223 | 224 | #### Advanced Template (Custom framework guidance) 225 | ```markdown 226 | # Advanced Research Template 227 | **🔄 TEMPLATE EXECUTION**: Advanced framework integration with custom quality gates 228 | 229 | ## Framework-Specific Guidance 230 | {{#if framework == "CAGEERF"}} 231 | Apply CAGEERF phases systematically... 232 | {{elif framework == "ReACT"}} 233 | Use reasoning-action cycles... 234 | {{else}} 235 | Apply general systematic approach... 236 | {{/if}} 237 | 238 | [Rest of template...] 239 | ``` 240 | 241 | ### Step 3: Implement Template Logic 242 | 243 | #### Variable Definition 244 | ```markdown 245 | ## Arguments 246 | - content (required): Primary content for processing 247 | - focus_area (optional): Specific area to emphasize 248 | - depth_level (optional, default: "moderate"): Analysis depth 249 | - output_format (optional, default: "markdown"): Desired output format 250 | ``` 251 | 252 | #### Conditional Logic 253 | ```markdown 254 | ## User Message Template 255 | Analyze the following content: 256 | {{content}} 257 | 258 | {{#if focus_area}} 259 | Pay special attention to: {{focus_area}} 260 | {{/if}} 261 | 262 | {{#if depth_level == "deep"}} 263 | Provide comprehensive analysis including edge cases and implications. 264 | {{elif depth_level == "surface"}} 265 | Provide high-level overview focusing on key points. 266 | {{else}} 267 | Provide balanced analysis covering main points and key details. 268 | {{/if}} 269 | ``` 270 | 271 | ### Step 4: Add Quality Gates 272 | 273 | #### Template-Level Quality Requirements 274 | ```markdown 275 | # Template with Quality Gates 276 | **🔄 TEMPLATE EXECUTION**: Framework-aware with enhanced validation 277 | **🛡️ QUALITY GATES**: Enabled - Content analysis, structure validation, methodology compliance 278 | 279 | [Template content...] 280 | ``` 281 | 282 | #### Custom Quality Criteria 283 | ```markdown 284 | ## Quality Requirements 285 | - Minimum response length: 500 words 286 | - Must include framework methodology application 287 | - Structured output with clear sections 288 | - Evidence-based conclusions 289 | ``` 290 | 291 | ## Advanced Template Features 292 | 293 | ### Nunjucks Template Features 294 | 295 | #### Loops and Iteration 296 | ```markdown 297 | ## Analysis Points 298 | {{#each analysis_points}} 299 | ### {{@index + 1}}. {{this.title}} 300 | {{this.description}} 301 | 302 | {{/each}} 303 | ``` 304 | 305 | #### Filters and Functions 306 | ```markdown 307 | ## Processed Content 308 | Original length: {{content | length}} characters 309 | Summary: {{content | truncate(200)}} 310 | Formatted: {{content | upper | trim}} 311 | ``` 312 | 313 | #### Macros for Reusable Components 314 | ```markdown 315 | {{#macro section_header(title, framework)}} 316 | ## {{title}} 317 | *Using {{framework}} methodology* 318 | {{/macro}} 319 | 320 | {{section_header("Analysis", framework)}} 321 | Your analysis content here... 322 | ``` 323 | 324 | ### Framework Context Injection 325 | 326 | #### Accessing Framework Information 327 | ```markdown 328 | ## Current Framework Context 329 | - **Active Framework**: {{framework}} 330 | - **Methodology Guide**: {{framework_description}} 331 | - **Quality Criteria**: {{framework_quality_gates}} 332 | 333 | Apply {{framework}} principles throughout this analysis. 334 | ``` 335 | 336 | #### Framework-Specific Customization 337 | ```markdown 338 | {{#if framework == "CAGEERF"}} 339 | ## CAGEERF Analysis Structure 340 | 1. **Context**: {{context_analysis}} 341 | 2. **Analysis**: {{detailed_analysis}} 342 | 3. **Goals**: {{goal_definition}} 343 | 4. **Execution**: {{implementation_plan}} 344 | 5. **Evaluation**: {{results_assessment}} 345 | 6. **Refinement**: {{improvement_suggestions}} 346 | 7. **Framework**: {{methodology_consistency}} 347 | {{/if}} 348 | ``` 349 | 350 | ## Testing Templates 351 | 352 | ### Local Testing 353 | 354 | ```bash 355 | # Test template execution 356 | prompt_engine >>your_template content="test content" focus_area="key points" 357 | 358 | # Test with specific framework 359 | system_control switch_framework framework=CAGEERF 360 | prompt_engine >>your_template execution_mode=template 361 | ``` 362 | 363 | ### Quality Gate Testing 364 | 365 | ```bash 366 | # Test with quality gates enabled 367 | prompt_engine >>your_template gate_validation=true 368 | 369 | # Test quality gate compliance 370 | prompt_engine >>your_template execution_mode=template gate_validation=true 371 | ``` 372 | 373 | ### Framework Integration Testing 374 | 375 | ```bash 376 | # Test with different frameworks 377 | system_control switch_framework framework=ReACT 378 | prompt_engine >>your_template 379 | 380 | system_control switch_framework framework=5W1H 381 | prompt_engine >>your_template 382 | 383 | # Compare framework-specific outputs 384 | ``` 385 | 386 | ## Best Practices 387 | 388 | ### Template Design 389 | 390 | #### DO: 391 | - **Use clear template markers** for execution type identification 392 | - **Define all required arguments** with descriptions 393 | - **Include framework integration markers** for methodology awareness 394 | - **Structure templates logically** with clear sections 395 | - **Test across different frameworks** to ensure compatibility 396 | 397 | #### DON'T: 398 | - **Hard-code framework references** - use {{framework}} variables 399 | - **Create overly complex templates** - prefer clarity over cleverness 400 | - **Ignore quality gate requirements** - design for validation 401 | - **Mix execution tiers** - keep templates focused on their tier 402 | 403 | ### Performance Optimization 404 | 405 | #### Template Efficiency 406 | - **Minimize complex conditional logic** 407 | - **Use efficient Nunjucks operations** 408 | - **Cache reusable template components** 409 | - **Optimize variable substitution** 410 | 411 | #### Framework Integration 412 | - **Leverage active framework selection** rather than switching 413 | - **Use framework-appropriate templates** for best performance 414 | - **Cache framework context** when possible 415 | 416 | ### Quality Assurance 417 | 418 | #### Template Validation 419 | - **Test all argument combinations** 420 | - **Verify framework integration works correctly** 421 | - **Validate output structure and format** 422 | - **Check quality gate compliance** 423 | 424 | #### Documentation 425 | - **Document template purpose clearly** 426 | - **Provide usage examples** 427 | - **List framework compatibility** 428 | - **Include troubleshooting guidance** 429 | 430 | ## Migration from Basic Prompts 431 | 432 | ### Converting Existing Prompts 433 | 434 | #### Step 1: Add Template Markers 435 | ```markdown 436 | # Before (basic prompt) 437 | Analyze this content: {{content}} 438 | 439 | # After (template) 440 | **🔄 TEMPLATE EXECUTION**: Framework-aware analysis 441 | 442 | Analyze this content using systematic methodology: {{content}} 443 | ``` 444 | 445 | #### Step 2: Add Framework Integration 446 | ```markdown 447 | # Enhanced template 448 | **🔄 TEMPLATE EXECUTION**: Uses {{framework}} methodology 449 | 450 | ## System Message 451 | You are an expert analyst applying {{framework}} methodology for systematic analysis. 452 | 453 | ## User Message Template 454 | Apply {{framework}} principles to analyze: {{content}} 455 | ``` 456 | 457 | #### Step 3: Add Quality Requirements 458 | ```markdown 459 | # Final template with quality gates 460 | **🔄 TEMPLATE EXECUTION**: Framework-aware with quality validation 461 | **🛡️ QUALITY GATES**: Content analysis, methodology compliance 462 | 463 | [Template content with framework integration] 464 | ``` 465 | 466 | ## Troubleshooting 467 | 468 | ### Common Issues 469 | 470 | #### Template Not Using Framework 471 | **Problem**: Template executes as basic prompt instead of framework-aware template 472 | **Solution**: Add proper template execution markers and check framework is active 473 | 474 | #### Quality Gate Failures 475 | **Problem**: Template fails validation 476 | **Solution**: Review quality gate requirements and adjust template structure 477 | 478 | #### Variable Substitution Issues 479 | **Problem**: Variables not being replaced correctly 480 | **Solution**: Check variable names match argument definitions exactly 481 | 482 | ### Debug Information 483 | 484 | Enable verbose logging to debug template execution: 485 | ```bash 486 | npm run start:verbose 487 | ``` 488 | 489 | This shows: 490 | - Template execution tier detection 491 | - Framework integration process 492 | - Quality gate evaluation results 493 | - Variable substitution details 494 | 495 | ## Examples Library 496 | 497 | ### Content Analysis Template 498 | ```markdown 499 | # Content Analysis Template 500 | **🔄 TEMPLATE EXECUTION**: Framework-aware content analysis 501 | 502 | ## System Message 503 | You are an expert content analyst who uses systematic methodology for comprehensive analysis. 504 | Apply the active framework to ensure thorough and structured evaluation. 505 | 506 | ## User Message Template 507 | Analyze the following content using {{framework}} methodology: 508 | 509 | **Content:** 510 | {{content}} 511 | 512 | **Analysis Focus:** 513 | {{#if focus_areas}} 514 | Pay special attention to: {{focus_areas}} 515 | {{else}} 516 | Provide comprehensive analysis across all relevant dimensions. 517 | {{/if}} 518 | 519 | **Output Requirements:** 520 | - Use {{framework}} structured approach 521 | - Provide evidence-based insights 522 | - Include actionable recommendations 523 | - Maintain systematic methodology throughout 524 | 525 | ## Arguments 526 | - content: Content to analyze (required) 527 | - focus_areas: Specific areas to emphasize (optional) 528 | ``` 529 | 530 | ### Problem-Solving Template 531 | ```markdown 532 | # Systematic Problem Solving Template 533 | **🔄 TEMPLATE EXECUTION**: Framework-driven problem solving 534 | 535 | ## System Message 536 | You are an expert problem solver who applies systematic methodology to address complex challenges. 537 | Use the active framework to ensure comprehensive problem analysis and solution development. 538 | 539 | ## User Message Template 540 | Apply {{framework}} methodology to solve this problem: 541 | 542 | **Problem Statement:** 543 | {{problem}} 544 | 545 | **Constraints:** 546 | {{constraints}} 547 | 548 | **Available Resources:** 549 | {{resources}} 550 | 551 | **Success Criteria:** 552 | {{success_criteria}} 553 | 554 | Follow {{framework}} principles to: 555 | 1. Analyze the problem systematically 556 | 2. Develop structured solutions 557 | 3. Evaluate options methodically 558 | 4. Provide implementation guidance 559 | 560 | ## Arguments 561 | - problem: Problem description (required) 562 | - constraints: Known limitations (required) 563 | - resources: Available resources (optional) 564 | - success_criteria: Definition of success (required) 565 | ``` 566 | 567 | This guide provides the foundation for creating powerful, framework-aware templates that leverage the full capabilities of the Claude Prompts MCP Server's methodology system. ``` -------------------------------------------------------------------------------- /server/src/mcp-tools/prompt-manager/operations/file-operations.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * File system and category management operations 3 | */ 4 | 5 | import * as fs from "fs/promises"; 6 | import { readFile } from "fs/promises"; 7 | import path from "path"; 8 | import { Logger } from "../../../logging/index.js"; 9 | import { ConfigManager } from "../../../config/index.js"; 10 | import { PromptData, PromptsConfigFile } from "../../../types/index.js"; 11 | import { safeWriteFile } from "../../../prompts/promptUtils.js"; 12 | import { 13 | OperationResult, 14 | CategoryResult, 15 | FileOperationResult, 16 | PromptManagerDependencies 17 | } from "../core/types.js"; 18 | import { CategoryManager } from "../utils/category-manager.js"; 19 | 20 | /** 21 | * File system operations for prompt management 22 | */ 23 | export class FileOperations { 24 | private logger: Logger; 25 | private configManager: ConfigManager; 26 | private categoryManager: CategoryManager; 27 | 28 | constructor(dependencies: Pick<PromptManagerDependencies, 'logger' | 'configManager'>) { 29 | this.logger = dependencies.logger; 30 | this.configManager = dependencies.configManager; 31 | this.categoryManager = new CategoryManager(this.logger); 32 | } 33 | 34 | /** 35 | * Update prompt implementation (shared by create/update) 36 | */ 37 | async updatePromptImplementation(promptData: any): Promise<OperationResult> { 38 | const PROMPTS_FILE = this.configManager.getPromptsFilePath(); 39 | const messages: string[] = []; 40 | 41 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 42 | const promptsConfig = JSON.parse(fileContent) as PromptsConfigFile; 43 | 44 | if (!promptsConfig.categories) promptsConfig.categories = []; 45 | if (!promptsConfig.imports) promptsConfig.imports = []; 46 | 47 | // Ensure category exists 48 | const { effectiveCategory, created: categoryCreated } = 49 | await this.categoryManager.ensureCategoryExists(promptData.category, promptsConfig, PROMPTS_FILE); 50 | 51 | if (categoryCreated) { 52 | messages.push(`✅ Created category: '${effectiveCategory}'`); 53 | } 54 | 55 | // Create/update prompt file 56 | const { exists: promptExists } = await this.createOrUpdatePromptFile( 57 | promptData, 58 | effectiveCategory, 59 | PROMPTS_FILE 60 | ); 61 | 62 | messages.push(`✅ ${promptExists ? 'Updated' : 'Created'} prompt file and registry entry`); 63 | 64 | return { 65 | message: messages.join('\n'), 66 | affectedFiles: [`${promptData.id}.md`] 67 | }; 68 | } 69 | 70 | /** 71 | * Delete prompt implementation 72 | */ 73 | async deletePromptImplementation(id: string): Promise<OperationResult> { 74 | const PROMPTS_FILE = this.configManager.getPromptsFilePath(); 75 | const promptsConfigDir = path.dirname(PROMPTS_FILE); 76 | const messages: string[] = []; 77 | const affectedFiles: string[] = []; 78 | 79 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 80 | const promptsConfig = JSON.parse(fileContent) as PromptsConfigFile; 81 | 82 | let promptFound = false; 83 | 84 | // Search through category imports 85 | for (const categoryImport of promptsConfig.imports || []) { 86 | const categoryPath = path.join(promptsConfigDir, categoryImport); 87 | 88 | try { 89 | const categoryContent = await readFile(categoryPath, "utf8"); 90 | const categoryData = JSON.parse(categoryContent); 91 | 92 | const promptIndex = categoryData.prompts.findIndex((p: PromptData) => p.id === id); 93 | 94 | if (promptIndex > -1) { 95 | const promptEntry = categoryData.prompts[promptIndex]; 96 | 97 | // Remove from category 98 | categoryData.prompts.splice(promptIndex, 1); 99 | await safeWriteFile(categoryPath, JSON.stringify(categoryData, null, 2), "utf8"); 100 | 101 | // Delete markdown file 102 | const markdownPath = path.join(path.dirname(categoryPath), promptEntry.file); 103 | try { 104 | await fs.unlink(markdownPath); 105 | messages.push(`✅ Deleted prompt file: ${promptEntry.file}`); 106 | affectedFiles.push(promptEntry.file); 107 | } catch (unlinkError: any) { 108 | if (unlinkError.code !== "ENOENT") { 109 | messages.push(`⚠️ Could not delete file: ${unlinkError.message}`); 110 | } 111 | } 112 | 113 | messages.push(`✅ Removed from category: ${categoryImport}`); 114 | promptFound = true; 115 | 116 | // Automatically clean up empty category 117 | if (categoryData.prompts.length === 0) { 118 | this.logger.info(`Category ${categoryImport} is now empty, performing automatic cleanup`); 119 | const cleanupResult = await this.categoryManager.cleanupEmptyCategory(categoryImport, promptsConfig, PROMPTS_FILE); 120 | messages.push(`🧹 **Automatic Category Cleanup**:\n${cleanupResult.message}`); 121 | } 122 | 123 | break; 124 | } 125 | } catch (error) { 126 | this.logger.warn(`Could not process category file: ${categoryPath}`, error); 127 | } 128 | } 129 | 130 | if (!promptFound) { 131 | throw new Error(`Prompt not found: ${id}`); 132 | } 133 | 134 | return { 135 | message: messages.join('\n'), 136 | affectedFiles 137 | }; 138 | } 139 | 140 | /** 141 | * Create or update prompt file 142 | */ 143 | async createOrUpdatePromptFile( 144 | promptData: any, 145 | effectiveCategory: string, 146 | promptsFile: string 147 | ): Promise<FileOperationResult> { 148 | const promptFilename = `${promptData.id}.md`; 149 | const categoryDir = path.join(path.dirname(promptsFile), effectiveCategory); 150 | const promptPath = path.join(categoryDir, promptFilename); 151 | 152 | // Create markdown content 153 | let content = `# ${promptData.name}\n\n`; 154 | content += `## Description\n${promptData.description}\n\n`; 155 | 156 | if (promptData.systemMessage) { 157 | content += `## System Message\n${promptData.systemMessage}\n\n`; 158 | } 159 | 160 | content += `## User Message Template\n${promptData.userMessageTemplate}\n`; 161 | 162 | // Build gate configuration section separately 163 | let gateConfigSection = ''; 164 | this.logger.error(`[GATE-TRACE] 💾 FILE-OPS Gate Configuration Check for ${promptData.id}:`, { 165 | hasGateConfiguration: !!promptData.gateConfiguration, 166 | gateConfigType: typeof promptData.gateConfiguration, 167 | gateConfigContent: promptData.gateConfiguration, 168 | promptId: promptData.id 169 | }); 170 | 171 | if (promptData.gateConfiguration) { 172 | this.logger.error(`[GATE-TRACE] ✅ Building gate configuration section for prompt ${promptData.id}`); 173 | gateConfigSection = `\n## Gate Configuration\n\n`; 174 | gateConfigSection += `\`\`\`json\n`; 175 | const gateConfigJson = JSON.stringify(promptData.gateConfiguration, null, 2); 176 | gateConfigSection += gateConfigJson; 177 | gateConfigSection += `\n\`\`\`\n`; 178 | this.logger.error(`[GATE-TRACE] 📝 Gate configuration JSON content:`, gateConfigJson); 179 | } else { 180 | this.logger.error(`[GATE-TRACE] ❌ NO GATE CONFIGURATION FOUND for prompt ${promptData.id}`); 181 | } 182 | 183 | // Build chain steps section 184 | let chainStepsSection = ''; 185 | if ((promptData.chainSteps?.length ?? 0) > 0) { 186 | chainStepsSection = `\n## Chain Steps\n\n`; 187 | promptData.chainSteps.forEach((step: any, index: number) => { 188 | chainStepsSection += `${index + 1}. **${step.stepName}** (${step.promptId})\n`; 189 | if (step.inputMapping) { 190 | chainStepsSection += ` - Input Mapping: ${JSON.stringify(step.inputMapping)}\n`; 191 | } 192 | if (step.outputMapping) { 193 | chainStepsSection += ` - Output Mapping: ${JSON.stringify(step.outputMapping)}\n`; 194 | } 195 | chainStepsSection += `\n`; 196 | }); 197 | } 198 | 199 | // Check if file exists and handle Gate Configuration replacement 200 | const existsBefore = await fs.access(promptPath).then(() => true).catch(() => false); 201 | 202 | if (existsBefore && gateConfigSection) { 203 | try { 204 | // Read existing file to preserve structure and replace Gate Configuration section 205 | const existingContent = await readFile(promptPath, "utf8"); 206 | 207 | // Remove ALL existing Gate Configuration sections (handles multiple duplicates) 208 | const gateConfigRegex = /## Gate Configuration\s*\n```json\s*\n[\s\S]*?\n```\s*/g; 209 | let cleanedContent = existingContent.replace(gateConfigRegex, ''); 210 | 211 | // Find insertion point - after User Message Template, before Chain Steps or end 212 | const chainStepsIndex = cleanedContent.indexOf('## Chain Steps'); 213 | 214 | if (chainStepsIndex > 0) { 215 | // Insert gate config before Chain Steps 216 | content = cleanedContent.slice(0, chainStepsIndex).trimEnd() + '\n' + 217 | gateConfigSection + '\n' + 218 | cleanedContent.slice(chainStepsIndex); 219 | this.logger.error(`[GATE-TRACE] ✅ Replaced Gate Configuration section (inserted before Chain Steps)`); 220 | } else { 221 | // No Chain Steps - append at end 222 | content = cleanedContent.trimEnd() + '\n' + gateConfigSection; 223 | this.logger.error(`[GATE-TRACE] ✅ Replaced Gate Configuration section (appended at end)`); 224 | } 225 | } catch (readError) { 226 | // If read fails, fall back to full regeneration 227 | this.logger.warn(`[GATE-TRACE] ⚠️ Failed to read existing file for section replacement, using full regeneration`, readError); 228 | content += gateConfigSection + chainStepsSection; 229 | } 230 | } else { 231 | // New file or no gate configuration - use simple append 232 | content += gateConfigSection + chainStepsSection; 233 | if (gateConfigSection) { 234 | this.logger.error(`[GATE-TRACE] ✅ Added Gate Configuration section to new file`); 235 | } 236 | } 237 | 238 | // Write markdown file 239 | await safeWriteFile(promptPath, content, "utf8"); 240 | 241 | // Update category prompts.json 242 | const categoryPromptsPath = path.join(categoryDir, "prompts.json"); 243 | let categoryData: { prompts: PromptData[] }; 244 | 245 | try { 246 | const categoryContent = await readFile(categoryPromptsPath, "utf8"); 247 | categoryData = JSON.parse(categoryContent); 248 | } catch { 249 | categoryData = { prompts: [] }; 250 | } 251 | 252 | const promptEntry: PromptData = { 253 | id: promptData.id, 254 | name: promptData.name, 255 | category: effectiveCategory, 256 | description: promptData.description, 257 | file: promptFilename, 258 | arguments: promptData.arguments || [] 259 | }; 260 | 261 | const existingIndex = categoryData.prompts.findIndex(p => p.id === promptData.id); 262 | if (existingIndex > -1) { 263 | categoryData.prompts[existingIndex] = promptEntry; 264 | } else { 265 | categoryData.prompts.push(promptEntry); 266 | } 267 | 268 | await safeWriteFile(categoryPromptsPath, JSON.stringify(categoryData, null, 2), "utf8"); 269 | 270 | return { 271 | exists: existsBefore, 272 | path: promptPath 273 | }; 274 | } 275 | 276 | /** 277 | * Ensure category exists in the configuration 278 | */ 279 | async ensureCategoryExists( 280 | category: string, 281 | promptsConfig: PromptsConfigFile, 282 | promptsFile: string 283 | ): Promise<CategoryResult> { 284 | return this.categoryManager.ensureCategoryExists(category, promptsConfig, promptsFile); 285 | } 286 | 287 | /** 288 | * Clean up empty category 289 | */ 290 | async cleanupEmptyCategory( 291 | categoryImport: string, 292 | promptsConfig: PromptsConfigFile, 293 | promptsFile: string 294 | ): Promise<OperationResult> { 295 | return this.categoryManager.cleanupEmptyCategory(categoryImport, promptsConfig, promptsFile); 296 | } 297 | 298 | /** 299 | * Validate file system state 300 | */ 301 | async validateFileSystemState(): Promise<{ 302 | valid: boolean; 303 | issues: string[]; 304 | stats: Record<string, number>; 305 | }> { 306 | const issues: string[] = []; 307 | const PROMPTS_FILE = this.configManager.getPromptsFilePath(); 308 | 309 | try { 310 | // Check main config file 311 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 312 | const promptsConfig = JSON.parse(fileContent) as PromptsConfigFile; 313 | 314 | // Validate categories and imports 315 | const stats = await this.categoryManager.getCategoryStats(promptsConfig.imports || [], PROMPTS_FILE); 316 | 317 | // Check each category structure 318 | for (const categoryImport of promptsConfig.imports || []) { 319 | const validation = await this.categoryManager.validateCategoryStructure(categoryImport, PROMPTS_FILE); 320 | if (!validation.valid) { 321 | issues.push(`Category ${categoryImport}: ${validation.issues.join(', ')}`); 322 | } 323 | } 324 | 325 | return { 326 | valid: issues.length === 0, 327 | issues, 328 | stats 329 | }; 330 | 331 | } catch (error) { 332 | issues.push(`Failed to validate file system: ${error instanceof Error ? error.message : String(error)}`); 333 | return { 334 | valid: false, 335 | issues, 336 | stats: {} 337 | }; 338 | } 339 | } 340 | 341 | /** 342 | * Backup prompt files 343 | */ 344 | async backupPrompts(): Promise<{ 345 | backupPath: string; 346 | fileCount: number; 347 | }> { 348 | const PROMPTS_FILE = this.configManager.getPromptsFilePath(); 349 | const promptsDir = path.dirname(PROMPTS_FILE); 350 | const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); 351 | const backupDir = path.join(promptsDir, `backup-${timestamp}`); 352 | 353 | await fs.mkdir(backupDir, { recursive: true }); 354 | 355 | // Copy main config 356 | await fs.copyFile(PROMPTS_FILE, path.join(backupDir, 'promptsConfig.json')); 357 | let fileCount = 1; 358 | 359 | // Copy all categories and their prompts 360 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 361 | const promptsConfig = JSON.parse(fileContent) as PromptsConfigFile; 362 | 363 | for (const categoryImport of promptsConfig.imports || []) { 364 | const sourcePath = path.join(promptsDir, categoryImport); 365 | const targetPath = path.join(backupDir, categoryImport); 366 | 367 | // Create category directory in backup 368 | await fs.mkdir(path.dirname(targetPath), { recursive: true }); 369 | 370 | try { 371 | // Copy category config 372 | await fs.copyFile(sourcePath, targetPath); 373 | fileCount++; 374 | 375 | // Copy all markdown files in category 376 | const categoryDir = path.dirname(sourcePath); 377 | const files = await fs.readdir(categoryDir); 378 | 379 | for (const file of files) { 380 | if (file.endsWith('.md')) { 381 | const sourceFile = path.join(categoryDir, file); 382 | const targetFile = path.join(path.dirname(targetPath), file); 383 | await fs.copyFile(sourceFile, targetFile); 384 | fileCount++; 385 | } 386 | } 387 | } catch (error) { 388 | this.logger.warn(`Failed to backup category ${categoryImport}:`, error); 389 | } 390 | } 391 | 392 | this.logger.info(`Created backup with ${fileCount} files at ${backupDir}`); 393 | 394 | return { 395 | backupPath: backupDir, 396 | fileCount 397 | }; 398 | } 399 | 400 | /** 401 | * Get file system statistics 402 | */ 403 | async getFileSystemStats(): Promise<{ 404 | totalCategories: number; 405 | totalPrompts: number; 406 | totalFiles: number; 407 | diskUsage: number; 408 | }> { 409 | const PROMPTS_FILE = this.configManager.getPromptsFilePath(); 410 | const promptsDir = path.dirname(PROMPTS_FILE); 411 | 412 | let totalCategories = 0; 413 | let totalPrompts = 0; 414 | let totalFiles = 0; 415 | let diskUsage = 0; 416 | 417 | try { 418 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 419 | const promptsConfig = JSON.parse(fileContent) as PromptsConfigFile; 420 | 421 | totalCategories = promptsConfig.categories?.length || 0; 422 | 423 | const stats = await this.categoryManager.getCategoryStats(promptsConfig.imports || [], PROMPTS_FILE); 424 | totalPrompts = Object.values(stats).reduce((sum, count) => sum + count, 0); 425 | 426 | // Count files and calculate disk usage 427 | const calculateDirSize = async (dirPath: string): Promise<{ files: number; size: number }> => { 428 | let files = 0; 429 | let size = 0; 430 | 431 | try { 432 | const items = await fs.readdir(dirPath, { withFileTypes: true }); 433 | 434 | for (const item of items) { 435 | const itemPath = path.join(dirPath, item.name); 436 | 437 | if (item.isDirectory()) { 438 | const subResult = await calculateDirSize(itemPath); 439 | files += subResult.files; 440 | size += subResult.size; 441 | } else { 442 | files++; 443 | const stat = await fs.stat(itemPath); 444 | size += stat.size; 445 | } 446 | } 447 | } catch (error) { 448 | // Ignore errors for inaccessible directories 449 | } 450 | 451 | return { files, size }; 452 | }; 453 | 454 | const dirStats = await calculateDirSize(promptsDir); 455 | totalFiles = dirStats.files; 456 | diskUsage = dirStats.size; 457 | 458 | } catch (error) { 459 | this.logger.warn('Failed to calculate file system stats:', error); 460 | } 461 | 462 | return { 463 | totalCategories, 464 | totalPrompts, 465 | totalFiles, 466 | diskUsage 467 | }; 468 | } 469 | } ``` -------------------------------------------------------------------------------- /server/src/frameworks/prompt-guidance/methodology-tracker.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Methodology Tracker - Phase 3 Implementation 3 | * 4 | * Tracks active methodology state and handles framework switching. 5 | * Consolidated from framework-state-manager for better separation of concerns. 6 | */ 7 | 8 | import { EventEmitter } from "events"; 9 | import { Logger } from "../../logging/index.js"; 10 | import { 11 | FrameworkDefinition, 12 | MethodologyState, 13 | MethodologySwitchRequest, 14 | MethodologyHealth, 15 | PersistedMethodologyState 16 | } from "../types/index.js"; 17 | import * as fs from 'fs/promises'; 18 | import * as path from 'path'; 19 | 20 | /** 21 | * Methodology tracking configuration 22 | */ 23 | export interface MethodologyTrackerConfig { 24 | persistStateToDisk: boolean; 25 | stateFilePath: string; 26 | enableHealthMonitoring: boolean; 27 | healthCheckIntervalMs: number; 28 | maxSwitchHistory: number; 29 | enableMetrics: boolean; 30 | } 31 | 32 | /** 33 | * Methodology Tracker Events 34 | */ 35 | export interface MethodologyTrackerEvents { 36 | 'methodology-switched': (previous: string, current: string, reason: string) => void; 37 | 'methodology-error': (methodology: string, error: Error) => void; 38 | 'health-changed': (health: MethodologyHealth) => void; 39 | 'state-persisted': (state: PersistedMethodologyState) => void; 40 | } 41 | 42 | /** 43 | * Methodology Tracker 44 | * 45 | * Maintains methodology state across operations, handles switching, 46 | * and provides health monitoring for the methodology system. 47 | */ 48 | export class MethodologyTracker extends EventEmitter { 49 | private logger: Logger; 50 | private config: MethodologyTrackerConfig; 51 | private currentState: MethodologyState; 52 | private readonly rootPath: string; 53 | private switchHistory: Array<{ 54 | from: string; 55 | to: string; 56 | timestamp: Date; 57 | reason: string; 58 | success: boolean; 59 | }> = []; 60 | private healthCheckTimer: NodeJS.Timeout | null = null; 61 | private switchingMetrics = { 62 | totalSwitches: 0, 63 | successfulSwitches: 0, 64 | failedSwitches: 0, 65 | averageResponseTime: 0, 66 | responseTimes: [] as number[] 67 | }; 68 | 69 | constructor(logger: Logger, config?: Partial<MethodologyTrackerConfig>) { 70 | super(); 71 | this.logger = logger; 72 | const rootPath = path.resolve(process.env.MCP_SERVER_ROOT || process.cwd()); 73 | this.rootPath = rootPath; 74 | const runtimeStatePath = path.join(rootPath, 'runtime-state', 'framework-state.json'); 75 | 76 | const defaultConfig: MethodologyTrackerConfig = { 77 | persistStateToDisk: true, 78 | stateFilePath: runtimeStatePath, 79 | enableHealthMonitoring: true, 80 | healthCheckIntervalMs: 30000, // 30 seconds 81 | maxSwitchHistory: 100, 82 | enableMetrics: true 83 | }; 84 | 85 | this.config = { 86 | ...defaultConfig, 87 | ...config, 88 | stateFilePath: config?.stateFilePath 89 | ? path.isAbsolute(config.stateFilePath) 90 | ? config.stateFilePath 91 | : path.resolve(rootPath, config.stateFilePath) 92 | : defaultConfig.stateFilePath 93 | }; 94 | 95 | // Initialize default state 96 | this.currentState = { 97 | activeMethodology: "CAGEERF", // Default methodology 98 | previousMethodology: null, 99 | switchedAt: new Date(), 100 | switchReason: "Initial state", 101 | isHealthy: true, 102 | methodologySystemEnabled: true, 103 | switchingMetrics: { 104 | switchCount: 0, 105 | averageResponseTime: 0, 106 | errorCount: 0 107 | } 108 | }; 109 | } 110 | 111 | /** 112 | * Initialize methodology tracker with state restoration 113 | */ 114 | async initialize(): Promise<void> { 115 | this.logger.info("Initializing MethodologyTracker..."); 116 | 117 | try { 118 | // Restore state from disk if enabled 119 | if (this.config.persistStateToDisk) { 120 | await this.restoreState(); 121 | } 122 | 123 | // Start health monitoring if enabled 124 | if (this.config.enableHealthMonitoring) { 125 | this.startHealthMonitoring(); 126 | } 127 | 128 | this.logger.info(`MethodologyTracker initialized with ${this.currentState.activeMethodology} methodology`); 129 | } catch (error) { 130 | this.logger.error("Failed to initialize MethodologyTracker:", error); 131 | throw error; 132 | } 133 | } 134 | 135 | /** 136 | * Switch to a different methodology 137 | * Consolidated from framework-state-manager.switchFramework() 138 | */ 139 | async switchMethodology(request: MethodologySwitchRequest): Promise<boolean> { 140 | const startTime = Date.now(); 141 | const previousMethodology = this.currentState.activeMethodology; 142 | const targetMethodology = request.targetMethodology; 143 | const reason = request.reason || "Manual switch"; 144 | 145 | this.logger.info(`Switching methodology: ${previousMethodology} -> ${targetMethodology} (${reason})`); 146 | 147 | try { 148 | // Validate switch request 149 | if (!this.validateSwitchRequest(request)) { 150 | throw new Error(`Invalid switch request: ${targetMethodology}`); 151 | } 152 | 153 | // Update state 154 | this.currentState = { 155 | ...this.currentState, 156 | previousMethodology, 157 | activeMethodology: targetMethodology, 158 | switchedAt: new Date(), 159 | switchReason: reason, 160 | switchingMetrics: { 161 | ...this.currentState.switchingMetrics, 162 | switchCount: this.currentState.switchingMetrics.switchCount + 1 163 | } 164 | }; 165 | 166 | // Record switch in history 167 | const switchRecord = { 168 | from: previousMethodology, 169 | to: targetMethodology, 170 | timestamp: new Date(), 171 | reason, 172 | success: true 173 | }; 174 | this.addToSwitchHistory(switchRecord); 175 | 176 | // Update metrics 177 | if (this.config.enableMetrics) { 178 | this.updateSwitchingMetrics(Date.now() - startTime, true); 179 | } 180 | 181 | // Persist state if enabled 182 | if (this.config.persistStateToDisk) { 183 | await this.persistState(); 184 | } 185 | 186 | // Emit event 187 | this.emit('methodology-switched', previousMethodology, targetMethodology, reason); 188 | 189 | this.logger.info(`Methodology switch completed: ${previousMethodology} -> ${targetMethodology} in ${Date.now() - startTime}ms`); 190 | return true; 191 | 192 | } catch (error) { 193 | this.logger.error(`Failed to switch methodology to ${targetMethodology}:`, error); 194 | 195 | // Record failed switch 196 | this.addToSwitchHistory({ 197 | from: previousMethodology, 198 | to: targetMethodology, 199 | timestamp: new Date(), 200 | reason, 201 | success: false 202 | }); 203 | 204 | // Update failure metrics 205 | if (this.config.enableMetrics) { 206 | this.updateSwitchingMetrics(Date.now() - startTime, false); 207 | } 208 | 209 | // Emit error event 210 | this.emit('methodology-error', targetMethodology, error instanceof Error ? error : new Error(String(error))); 211 | 212 | return false; 213 | } 214 | } 215 | 216 | /** 217 | * Get current methodology state 218 | */ 219 | getCurrentState(): MethodologyState { 220 | return { ...this.currentState }; 221 | } 222 | 223 | /** 224 | * Get methodology system health 225 | */ 226 | getSystemHealth(): MethodologyHealth { 227 | return { 228 | status: this.currentState.isHealthy ? "healthy" : "error", 229 | activeMethodology: this.currentState.activeMethodology, 230 | methodologySystemEnabled: this.currentState.methodologySystemEnabled, 231 | lastSwitchTime: this.currentState.switchedAt, 232 | switchingMetrics: { 233 | totalSwitches: this.switchingMetrics.totalSwitches, 234 | successfulSwitches: this.switchingMetrics.successfulSwitches, 235 | failedSwitches: this.switchingMetrics.failedSwitches, 236 | averageResponseTime: this.switchingMetrics.averageResponseTime 237 | }, 238 | issues: this.detectHealthIssues() 239 | }; 240 | } 241 | 242 | /** 243 | * Enable or disable the methodology system 244 | */ 245 | async setMethodologySystemEnabled(enabled: boolean, reason: string = "Manual toggle"): Promise<void> { 246 | const previousState = this.currentState.methodologySystemEnabled; 247 | 248 | this.currentState.methodologySystemEnabled = enabled; 249 | this.currentState.switchReason = `System ${enabled ? 'enabled' : 'disabled'}: ${reason}`; 250 | 251 | this.logger.info(`Methodology system ${enabled ? 'enabled' : 'disabled'}: ${reason}`); 252 | 253 | // Persist state change 254 | if (this.config.persistStateToDisk) { 255 | await this.persistState(); 256 | } 257 | 258 | // Emit event if state changed 259 | if (previousState !== enabled) { 260 | this.emit('methodology-system-toggled' as any, enabled, reason); 261 | } 262 | } 263 | 264 | /** 265 | * Get switch history 266 | */ 267 | getSwitchHistory(): Array<{ from: string; to: string; timestamp: Date; reason: string; success: boolean }> { 268 | return [...this.switchHistory]; 269 | } 270 | 271 | /** 272 | * Clear switch history 273 | */ 274 | clearSwitchHistory(): void { 275 | this.switchHistory = []; 276 | this.logger.debug("Switch history cleared"); 277 | } 278 | 279 | /** 280 | * Shutdown methodology tracker 281 | */ 282 | async shutdown(): Promise<void> { 283 | this.logger.info("Shutting down MethodologyTracker..."); 284 | 285 | // Stop health monitoring 286 | if (this.healthCheckTimer) { 287 | clearInterval(this.healthCheckTimer); 288 | this.healthCheckTimer = null; 289 | } 290 | 291 | // Persist final state 292 | if (this.config.persistStateToDisk) { 293 | await this.persistState(); 294 | } 295 | 296 | this.logger.info("MethodologyTracker shutdown complete"); 297 | } 298 | 299 | /** 300 | * Validate switch request 301 | */ 302 | private validateSwitchRequest(request: MethodologySwitchRequest): boolean { 303 | // Check if methodology system is enabled 304 | if (!this.currentState.methodologySystemEnabled) { 305 | this.logger.warn("Methodology switch rejected - system disabled"); 306 | return false; 307 | } 308 | 309 | // Check if switching to same methodology 310 | if (request.targetMethodology === this.currentState.activeMethodology) { 311 | this.logger.debug(`Already using ${request.targetMethodology} methodology`); 312 | return true; // Not an error, but no switch needed 313 | } 314 | 315 | // Validate methodology exists (basic validation) 316 | const validMethodologies = ["CAGEERF", "ReACT", "5W1H", "SCAMPER"]; 317 | if (!validMethodologies.includes(request.targetMethodology)) { 318 | this.logger.error(`Invalid methodology: ${request.targetMethodology}`); 319 | return false; 320 | } 321 | 322 | return true; 323 | } 324 | 325 | /** 326 | * Add switch record to history 327 | */ 328 | private addToSwitchHistory(record: { 329 | from: string; 330 | to: string; 331 | timestamp: Date; 332 | reason: string; 333 | success: boolean; 334 | }): void { 335 | this.switchHistory.push(record); 336 | 337 | // Trim history if it exceeds maximum 338 | if (this.switchHistory.length > this.config.maxSwitchHistory) { 339 | this.switchHistory = this.switchHistory.slice(-this.config.maxSwitchHistory); 340 | } 341 | } 342 | 343 | /** 344 | * Update switching metrics 345 | */ 346 | private updateSwitchingMetrics(responseTime: number, success: boolean): void { 347 | this.switchingMetrics.totalSwitches++; 348 | 349 | if (success) { 350 | this.switchingMetrics.successfulSwitches++; 351 | } else { 352 | this.switchingMetrics.failedSwitches++; 353 | } 354 | 355 | // Update response time metrics 356 | this.switchingMetrics.responseTimes.push(responseTime); 357 | if (this.switchingMetrics.responseTimes.length > 100) { 358 | this.switchingMetrics.responseTimes = this.switchingMetrics.responseTimes.slice(-100); 359 | } 360 | 361 | this.switchingMetrics.averageResponseTime = 362 | this.switchingMetrics.responseTimes.reduce((sum, time) => sum + time, 0) / 363 | this.switchingMetrics.responseTimes.length; 364 | 365 | // Update current state metrics 366 | this.currentState.switchingMetrics = { 367 | switchCount: this.switchingMetrics.totalSwitches, 368 | averageResponseTime: this.switchingMetrics.averageResponseTime, 369 | errorCount: this.switchingMetrics.failedSwitches 370 | }; 371 | } 372 | 373 | /** 374 | * Start health monitoring 375 | */ 376 | private startHealthMonitoring(): void { 377 | this.healthCheckTimer = setInterval(() => { 378 | this.performHealthCheck(); 379 | }, this.config.healthCheckIntervalMs); 380 | 381 | this.logger.debug(`Health monitoring started (interval: ${this.config.healthCheckIntervalMs}ms)`); 382 | } 383 | 384 | /** 385 | * Perform health check 386 | */ 387 | private performHealthCheck(): void { 388 | const wasHealthy = this.currentState.isHealthy; 389 | const health = this.getSystemHealth(); 390 | 391 | // Update health status 392 | this.currentState.isHealthy = health.status === "healthy"; 393 | 394 | // Emit health change event if status changed 395 | if (wasHealthy !== this.currentState.isHealthy) { 396 | this.emit('health-changed', health); 397 | this.logger.info(`Methodology system health changed: ${health.status}`); 398 | } 399 | } 400 | 401 | /** 402 | * Detect health issues 403 | */ 404 | private detectHealthIssues(): string[] { 405 | const issues: string[] = []; 406 | 407 | // Check error rate 408 | const errorRate = this.switchingMetrics.totalSwitches > 0 409 | ? this.switchingMetrics.failedSwitches / this.switchingMetrics.totalSwitches 410 | : 0; 411 | 412 | if (errorRate > 0.1) { // More than 10% error rate 413 | issues.push(`High error rate: ${(errorRate * 100).toFixed(1)}%`); 414 | } 415 | 416 | // Check response time 417 | if (this.switchingMetrics.averageResponseTime > 1000) { // More than 1 second 418 | issues.push(`Slow switching: ${this.switchingMetrics.averageResponseTime.toFixed(0)}ms average`); 419 | } 420 | 421 | // Check if system is disabled 422 | if (!this.currentState.methodologySystemEnabled) { 423 | issues.push("Methodology system is disabled"); 424 | } 425 | 426 | return issues; 427 | } 428 | 429 | /** 430 | * Persist state to disk 431 | */ 432 | private async persistState(): Promise<void> { 433 | try { 434 | const persistedState: PersistedMethodologyState = { 435 | version: "1.0.0", 436 | methodologySystemEnabled: this.currentState.methodologySystemEnabled, 437 | activeMethodology: this.currentState.activeMethodology, 438 | lastSwitchedAt: this.currentState.switchedAt.toISOString(), 439 | switchReason: this.currentState.switchReason 440 | }; 441 | 442 | await fs.mkdir(path.dirname(this.config.stateFilePath), { recursive: true }); 443 | await fs.writeFile( 444 | this.config.stateFilePath, 445 | JSON.stringify(persistedState, null, 2) 446 | ); 447 | this.emit('state-persisted', persistedState); 448 | this.logger.debug(`State persisted to ${this.config.stateFilePath}`); 449 | 450 | } catch (error) { 451 | this.logger.error("Failed to persist methodology state:", error); 452 | } 453 | } 454 | 455 | /** 456 | * Restore state from disk 457 | */ 458 | private async restoreState(): Promise<void> { 459 | const persistedState = await this.readPersistedState(); 460 | 461 | if (!persistedState) { 462 | this.logger.debug("Using default methodology state"); 463 | return; 464 | } 465 | 466 | this.currentState = { 467 | ...this.currentState, 468 | activeMethodology: persistedState.activeMethodology, 469 | methodologySystemEnabled: persistedState.methodologySystemEnabled, 470 | switchedAt: new Date(persistedState.lastSwitchedAt), 471 | switchReason: persistedState.switchReason 472 | }; 473 | 474 | this.logger.info( 475 | `State restored from ${this.config.stateFilePath}: ${persistedState.activeMethodology}` 476 | ); 477 | } 478 | 479 | private async readPersistedState(): Promise<PersistedMethodologyState | null> { 480 | try { 481 | const stateData = await fs.readFile(this.config.stateFilePath, 'utf-8'); 482 | return JSON.parse(stateData); 483 | } catch (error: any) { 484 | if (error?.code !== 'ENOENT') { 485 | this.logger.warn( 486 | `Failed to read methodology state from ${this.config.stateFilePath}: ${ 487 | error instanceof Error ? error.message : String(error) 488 | }` 489 | ); 490 | } 491 | return null; 492 | } 493 | } 494 | 495 | /** 496 | * Update tracker configuration 497 | */ 498 | updateConfig(config: Partial<MethodologyTrackerConfig>): void { 499 | const oldConfig = { ...this.config }; 500 | this.config = { ...this.config, ...config }; 501 | 502 | if (config.stateFilePath) { 503 | this.config.stateFilePath = path.isAbsolute(config.stateFilePath) 504 | ? config.stateFilePath 505 | : path.resolve(this.rootPath, config.stateFilePath); 506 | } 507 | 508 | // Restart health monitoring if interval changed 509 | if (oldConfig.healthCheckIntervalMs !== this.config.healthCheckIntervalMs && this.config.enableHealthMonitoring) { 510 | if (this.healthCheckTimer) { 511 | clearInterval(this.healthCheckTimer); 512 | } 513 | this.startHealthMonitoring(); 514 | } 515 | 516 | this.logger.debug('MethodologyTracker configuration updated', config); 517 | } 518 | 519 | /** 520 | * Get current tracker configuration 521 | */ 522 | getConfig(): MethodologyTrackerConfig { 523 | return { ...this.config }; 524 | } 525 | } 526 | 527 | /** 528 | * Create and initialize a MethodologyTracker instance 529 | */ 530 | export async function createMethodologyTracker( 531 | logger: Logger, 532 | config?: Partial<MethodologyTrackerConfig> 533 | ): Promise<MethodologyTracker> { 534 | const tracker = new MethodologyTracker(logger, config); 535 | await tracker.initialize(); 536 | return tracker; 537 | } 538 | ``` -------------------------------------------------------------------------------- /server/src/api/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * API Management Module 3 | * Handles Express app setup, middleware, and REST API endpoints 4 | */ 5 | 6 | import express, { Request, Response } from "express"; 7 | import { mkdir, readFile, writeFile } from "fs/promises"; 8 | import path from "path"; 9 | import { ConfigManager } from "../config/index.js"; 10 | import { Logger } from "../logging/index.js"; 11 | import { McpToolsManager } from "../mcp-tools/index.js"; 12 | import { PromptManager } from "../prompts/index.js"; 13 | import { modifyPromptSection } from "../prompts/promptUtils.js"; 14 | import { Category, PromptData, PromptsFile } from "../types/index.js"; 15 | 16 | /** 17 | * API Manager class 18 | */ 19 | export class ApiManager { 20 | private logger: Logger; 21 | private configManager: ConfigManager; 22 | private promptManager?: PromptManager; 23 | private mcpToolsManager?: McpToolsManager; 24 | private promptsData: PromptData[] = []; 25 | private categories: Category[] = []; 26 | private convertedPrompts: any[] = []; 27 | 28 | constructor( 29 | logger: Logger, 30 | configManager: ConfigManager, 31 | promptManager?: PromptManager, 32 | mcpToolsManager?: McpToolsManager 33 | ) { 34 | this.logger = logger; 35 | this.configManager = configManager; 36 | this.promptManager = promptManager; 37 | this.mcpToolsManager = mcpToolsManager; 38 | } 39 | 40 | /** 41 | * Update data references 42 | */ 43 | updateData( 44 | promptsData: PromptData[], 45 | categories: Category[], 46 | convertedPrompts: any[] 47 | ): void { 48 | this.promptsData = promptsData; 49 | this.categories = categories; 50 | this.convertedPrompts = convertedPrompts; 51 | } 52 | 53 | /** 54 | * Create and configure Express application 55 | */ 56 | createApp(): express.Application { 57 | const app = express(); 58 | 59 | // Setup middleware 60 | this.setupMiddleware(app); 61 | 62 | // Setup routes 63 | this.setupRoutes(app); 64 | 65 | return app; 66 | } 67 | 68 | /** 69 | * Setup Express middleware 70 | */ 71 | private setupMiddleware(app: express.Application): void { 72 | // Enable CORS for Cursor integration 73 | app.use((req, res, next) => { 74 | res.header("Access-Control-Allow-Origin", "*"); 75 | res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE"); 76 | res.header( 77 | "Access-Control-Allow-Headers", 78 | "Origin, X-Requested-With, Content-Type, Accept" 79 | ); 80 | if (req.method === "OPTIONS") { 81 | return res.sendStatus(200); 82 | } 83 | next(); 84 | }); 85 | 86 | // Add JSON body parser middleware 87 | app.use(express.json()); 88 | 89 | // Add request logging middleware 90 | app.use((req, res, next) => { 91 | this.logger.debug( 92 | `${req.method} ${req.url} - Headers: ${JSON.stringify(req.headers)}` 93 | ); 94 | next(); 95 | }); 96 | } 97 | 98 | /** 99 | * Setup API routes 100 | */ 101 | private setupRoutes(app: express.Application): void { 102 | // Basic routes 103 | this.setupBasicRoutes(app); 104 | 105 | // Prompt and category routes 106 | this.setupPromptRoutes(app); 107 | 108 | // Tool API routes 109 | this.setupToolRoutes(app); 110 | } 111 | 112 | /** 113 | * Setup basic routes (home, health) 114 | */ 115 | private setupBasicRoutes(app: express.Application): void { 116 | app.get("/", (_req: Request, res: Response) => { 117 | res.send( 118 | "Claude Custom Prompts MCP Server - Use /mcp endpoint for MCP connections" 119 | ); 120 | }); 121 | 122 | // Health check endpoint 123 | app.get("/health", (_req: Request, res: Response) => { 124 | const config = this.configManager.getConfig(); 125 | res.json({ status: "ok", version: config.server.version }); 126 | }); 127 | } 128 | 129 | /** 130 | * Setup prompt and category routes 131 | */ 132 | private setupPromptRoutes(app: express.Application): void { 133 | // Get all categories and prompts 134 | app.get("/prompts", (_req: Request, res: Response) => { 135 | const result = { 136 | categories: this.categories, 137 | prompts: this.promptsData.map((prompt) => ({ 138 | id: prompt.id, 139 | name: prompt.name, 140 | category: prompt.category, 141 | description: prompt.description, 142 | arguments: prompt.arguments, 143 | })), 144 | }; 145 | res.json(result); 146 | }); 147 | 148 | // Get prompts by category 149 | app.get( 150 | "/categories/:categoryId/prompts", 151 | (req: Request, res: Response) => { 152 | const categoryId = req.params.categoryId; 153 | const categoryPrompts = this.promptsData.filter( 154 | (prompt) => prompt.category === categoryId 155 | ); 156 | 157 | if (categoryPrompts.length === 0) { 158 | return res 159 | .status(404) 160 | .json({ error: `No prompts found for category: ${categoryId}` }); 161 | } 162 | 163 | res.json(categoryPrompts); 164 | } 165 | ); 166 | } 167 | 168 | /** 169 | * Setup tool API routes 170 | */ 171 | private setupToolRoutes(app: express.Application): void { 172 | // Create category endpoint 173 | app.post( 174 | "/api/v1/tools/create_category", 175 | async (req: Request, res: Response) => { 176 | await this.handleCreateCategory(req, res); 177 | } 178 | ); 179 | 180 | // Update prompt endpoint 181 | app.post( 182 | "/api/v1/tools/update_prompt", 183 | async (req: Request, res: Response) => { 184 | await this.handleUpdatePrompt(req, res); 185 | } 186 | ); 187 | 188 | // Delete prompt endpoint 189 | app.delete( 190 | "/api/v1/tools/prompts/:id", 191 | async (req: Request, res: Response) => { 192 | await this.handleDeletePrompt(req, res); 193 | } 194 | ); 195 | 196 | // Modify prompt section endpoint 197 | app.post( 198 | "/api/v1/tools/modify_prompt_section", 199 | async (req: Request, res: Response) => { 200 | await this.handleModifyPromptSection(req, res); 201 | } 202 | ); 203 | 204 | // Reload prompts endpoint 205 | app.post( 206 | "/api/v1/tools/reload_prompts", 207 | async (req: Request, res: Response) => { 208 | await this.handleReloadPrompts(req, res); 209 | } 210 | ); 211 | } 212 | 213 | /** 214 | * Handle create category API endpoint 215 | */ 216 | private async handleCreateCategory( 217 | req: Request, 218 | res: Response 219 | ): Promise<void> { 220 | try { 221 | this.logger.info("API request to create category:", req.body); 222 | 223 | // Validate required fields 224 | if (!req.body.id || !req.body.name || !req.body.description) { 225 | res.status(400).json({ 226 | error: 227 | "Missing required fields. Please provide id, name, and description.", 228 | }); 229 | return; 230 | } 231 | 232 | const { id, name, description } = req.body; 233 | 234 | // Read the current prompts configuration file 235 | const PROMPTS_FILE = this.getPromptsFilePath(); 236 | const fileContent = await readFile(PROMPTS_FILE, "utf8"); 237 | const promptsFile = JSON.parse(fileContent) as PromptsFile; 238 | 239 | // Check if the category already exists 240 | const categoryExists = promptsFile.categories.some( 241 | (cat) => cat.id === id 242 | ); 243 | if (categoryExists) { 244 | res.status(400).json({ error: `Category '${id}' already exists.` }); 245 | return; 246 | } 247 | 248 | // Add the new category 249 | promptsFile.categories.push({ id, name, description }); 250 | 251 | // Write the updated file 252 | await writeFile( 253 | PROMPTS_FILE, 254 | JSON.stringify(promptsFile, null, 2), 255 | "utf8" 256 | ); 257 | 258 | // Create the category directory if it doesn't exist 259 | const categoryDirPath = path.join(path.dirname(PROMPTS_FILE), id); 260 | try { 261 | await mkdir(categoryDirPath, { recursive: true }); 262 | } catch (error) { 263 | this.logger.error( 264 | `Error creating directory ${categoryDirPath}:`, 265 | error 266 | ); 267 | // Continue even if directory creation fails 268 | } 269 | 270 | // Reload prompts and categories if prompt manager is available 271 | if (this.promptManager) { 272 | try { 273 | await this.reloadPromptData(); 274 | this.logger.info( 275 | `Reloaded ${this.promptsData.length} prompts and ${this.categories.length} categories after creating category: ${id}` 276 | ); 277 | } catch (error) { 278 | this.logger.error("Error reloading prompts data:", error); 279 | } 280 | } 281 | 282 | res.status(200).json({ 283 | success: true, 284 | message: `Category '${name}' created successfully`, 285 | }); 286 | } catch (error) { 287 | this.logger.error("Error handling create_category API request:", error); 288 | res.status(500).json({ 289 | error: "Internal server error", 290 | details: error instanceof Error ? error.message : String(error), 291 | }); 292 | } 293 | } 294 | 295 | /** 296 | * Handle update prompt API endpoint 297 | */ 298 | private async handleUpdatePrompt(req: Request, res: Response): Promise<void> { 299 | try { 300 | this.logger.info("API request to update prompt:", req.body); 301 | 302 | // Validate required fields 303 | if ( 304 | !req.body.id || 305 | !req.body.name || 306 | !req.body.category || 307 | !req.body.userMessageTemplate 308 | ) { 309 | res.status(400).json({ 310 | error: 311 | "Missing required fields. Please provide id, name, category, and userMessageTemplate.", 312 | }); 313 | return; 314 | } 315 | 316 | const { 317 | id, 318 | name, 319 | category, 320 | description, 321 | userMessageTemplate, 322 | arguments: promptArgs, 323 | systemMessage, 324 | isChain, 325 | chainSteps, 326 | } = req.body; 327 | 328 | // Implementation would include full update logic... 329 | // For brevity, this is a simplified version 330 | res.status(200).json({ 331 | success: true, 332 | message: `Prompt '${name}' updated successfully`, 333 | }); 334 | } catch (error) { 335 | this.logger.error("Error handling update_prompt API request:", error); 336 | res.status(500).json({ 337 | error: "Internal server error", 338 | details: error instanceof Error ? error.message : String(error), 339 | }); 340 | } 341 | } 342 | 343 | /** 344 | * Handle delete prompt API endpoint 345 | */ 346 | private async handleDeletePrompt(req: Request, res: Response): Promise<void> { 347 | try { 348 | const id = req.params.id; 349 | this.logger.info(`API request to delete prompt: ${id}`); 350 | 351 | if (!id) { 352 | res.status(400).json({ error: "Prompt ID is required" }); 353 | return; 354 | } 355 | 356 | // Implementation would include full delete logic... 357 | res.status(200).json({ 358 | success: true, 359 | message: `Prompt '${id}' deleted successfully`, 360 | }); 361 | } catch (error) { 362 | this.logger.error("Error handling delete_prompt API request:", error); 363 | res.status(500).json({ 364 | error: "Internal server error", 365 | details: error instanceof Error ? error.message : String(error), 366 | }); 367 | } 368 | } 369 | 370 | /** 371 | * Handle modify prompt section API endpoint 372 | */ 373 | private async handleModifyPromptSection( 374 | req: Request, 375 | res: Response 376 | ): Promise<void> { 377 | try { 378 | this.logger.info("Received request to modify prompt section:", req.body); 379 | 380 | const { id, section_name, new_content, restartServer } = req.body; 381 | 382 | if (!id || !section_name || !new_content) { 383 | res.status(400).json({ 384 | success: false, 385 | message: 386 | "Missing required fields: id, section_name, and new_content are required", 387 | }); 388 | return; 389 | } 390 | 391 | // Use the modifyPromptSection function from promptUtils 392 | const PROMPTS_FILE = this.getPromptsFilePath(); 393 | const result = await modifyPromptSection( 394 | id, 395 | section_name, 396 | new_content, 397 | PROMPTS_FILE 398 | ); 399 | 400 | if (!result.success) { 401 | res.status(404).json({ 402 | success: false, 403 | message: result.message, 404 | }); 405 | return; 406 | } 407 | 408 | // Reload prompt data if available 409 | if (this.promptManager) { 410 | try { 411 | await this.reloadPromptData(); 412 | this.logger.info( 413 | `Triggered server refresh${ 414 | restartServer ? " with restart" : "" 415 | } after modifying section: ${section_name}` 416 | ); 417 | } catch (refreshError) { 418 | this.logger.error( 419 | `Error refreshing server after modifying section: ${section_name}`, 420 | refreshError 421 | ); 422 | } 423 | } 424 | 425 | res.status(200).json({ 426 | success: true, 427 | message: result.message, 428 | restarting: restartServer || false, 429 | }); 430 | } catch (error) { 431 | this.logger.error( 432 | "Error handling modify_prompt_section API request:", 433 | error 434 | ); 435 | res.status(500).json({ 436 | success: false, 437 | message: "Internal server error", 438 | }); 439 | } 440 | } 441 | 442 | /** 443 | * Handle reload prompts API endpoint 444 | */ 445 | private async handleReloadPrompts( 446 | req: Request, 447 | res: Response 448 | ): Promise<void> { 449 | try { 450 | this.logger.info("API request to reload prompts"); 451 | 452 | const shouldRestart = req.body && req.body.restart === true; 453 | const reason = 454 | req.body && req.body.reason 455 | ? req.body.reason 456 | : "Manual reload requested"; 457 | 458 | try { 459 | // Reload prompt data if available 460 | if (this.promptManager) { 461 | await this.reloadPromptData(); 462 | } 463 | 464 | if (shouldRestart) { 465 | res.status(200).json({ 466 | success: true, 467 | message: `Successfully refreshed the server with ${this.promptsData.length} prompts and ${this.categories.length} categories. Server is now restarting.`, 468 | data: { 469 | promptsCount: this.promptsData.length, 470 | categoriesCount: this.categories.length, 471 | convertedPromptsCount: this.convertedPrompts.length, 472 | restarting: true, 473 | }, 474 | }); 475 | } else { 476 | res.status(200).json({ 477 | success: true, 478 | message: `Successfully refreshed the server with ${this.promptsData.length} prompts and ${this.categories.length} categories`, 479 | data: { 480 | promptsCount: this.promptsData.length, 481 | categoriesCount: this.categories.length, 482 | convertedPromptsCount: this.convertedPrompts.length, 483 | }, 484 | }); 485 | } 486 | } catch (refreshError) { 487 | this.logger.error("Error refreshing server:", refreshError); 488 | res.status(500).json({ 489 | success: false, 490 | message: `Failed to refresh server: ${ 491 | refreshError instanceof Error 492 | ? refreshError.message 493 | : String(refreshError) 494 | }`, 495 | }); 496 | } 497 | } catch (error) { 498 | this.logger.error("Error handling reload_prompts API request:", error); 499 | res.status(500).json({ 500 | success: false, 501 | message: "Internal server error", 502 | }); 503 | } 504 | } 505 | 506 | /** 507 | * Helper method to reload prompt data 508 | */ 509 | private async reloadPromptData(): Promise<void> { 510 | if (!this.promptManager) { 511 | throw new Error("PromptManager not available"); 512 | } 513 | 514 | const PROMPTS_FILE = this.getPromptsFilePath(); 515 | 516 | const result = await this.promptManager.loadAndConvertPrompts(PROMPTS_FILE); 517 | this.updateData( 518 | result.promptsData, 519 | result.categories, 520 | result.convertedPrompts 521 | ); 522 | 523 | // Update MCP tools manager if available 524 | if (this.mcpToolsManager) { 525 | this.mcpToolsManager.updateData( 526 | result.promptsData, 527 | result.convertedPrompts, 528 | result.categories 529 | ); 530 | } 531 | } 532 | 533 | /** 534 | * Get prompts file path using consistent resolution logic 535 | * This ensures all API operations use the same path resolution as the orchestration module 536 | */ 537 | private getPromptsFilePath(): string { 538 | // ENHANCED: Use same path resolution logic as orchestration module 539 | // This ensures API operations also respect MCP_PROMPTS_CONFIG_PATH environment variable 540 | let PROMPTS_FILE: string; 541 | 542 | if (process.env.MCP_PROMPTS_CONFIG_PATH) { 543 | PROMPTS_FILE = process.env.MCP_PROMPTS_CONFIG_PATH; 544 | this.logger.info( 545 | "🎯 API: Using MCP_PROMPTS_CONFIG_PATH environment variable override" 546 | ); 547 | } else { 548 | // Fallback to ConfigManager's getPromptsFilePath() method which handles server root properly 549 | PROMPTS_FILE = this.configManager.getPromptsFilePath(); 550 | this.logger.info( 551 | "📁 API: Using config-based prompts file path resolution" 552 | ); 553 | } 554 | 555 | // Ensure absolute path (critical for Claude Desktop) 556 | if (!path.isAbsolute(PROMPTS_FILE)) { 557 | PROMPTS_FILE = path.resolve(PROMPTS_FILE); 558 | this.logger.info(`🔧 API: Converted to absolute path: ${PROMPTS_FILE}`); 559 | } 560 | 561 | return PROMPTS_FILE; 562 | } 563 | } 564 | 565 | /** 566 | * Create and configure an API manager 567 | */ 568 | export function createApiManager( 569 | logger: Logger, 570 | configManager: ConfigManager, 571 | promptManager?: PromptManager, 572 | mcpToolsManager?: McpToolsManager 573 | ): ApiManager { 574 | return new ApiManager(logger, configManager, promptManager, mcpToolsManager); 575 | } 576 | ``` -------------------------------------------------------------------------------- /server/tests/integration/unified-parsing-integration.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Integration Tests for Unified Parsing System 3 | * 4 | * End-to-end integration tests that verify the complete parsing system 5 | * works correctly with the real MCP server infrastructure. 6 | */ 7 | 8 | import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals'; 9 | import { Logger } from '../../src/logging/index.js'; 10 | import { PromptManager } from '../../src/prompts/index.js'; 11 | import { ConsolidatedPromptEngine, createConsolidatedPromptEngine } from '../../src/mcp-tools/consolidated-prompt-engine.js'; 12 | import { SemanticAnalyzer } from '../../src/analysis/semantic-analyzer.js'; 13 | import { PromptData, ConvertedPrompt } from '../../src/types/index.js'; 14 | import { isChainPrompt } from '../../src/utils/chainUtils.js'; 15 | 16 | // Mock logger 17 | const mockLogger: Logger = { 18 | debug: jest.fn(), 19 | info: jest.fn(), 20 | warn: jest.fn(), 21 | error: jest.fn() 22 | } as any; 23 | 24 | // Mock MCP server 25 | const mockMcpServer = { 26 | tool: jest.fn() 27 | }; 28 | 29 | // Mock prompt manager 30 | const mockPromptManager = { 31 | processTemplateAsync: jest.fn().mockResolvedValue('Processed template content'), 32 | convertedPrompts: [] as ConvertedPrompt[], 33 | promptsData: [] as PromptData[] 34 | } as any; 35 | 36 | // Mock semantic analyzer 37 | const mockSemanticAnalyzer = { 38 | analyzePrompt: jest.fn().mockResolvedValue({ 39 | executionType: 'template', 40 | requiresExecution: true, 41 | confidence: 0.8, 42 | reasoning: ['Simple prompt detected'], 43 | suggestedGates: [] 44 | }) 45 | } as any; 46 | 47 | // Test data 48 | const testPromptsData: PromptData[] = [ 49 | { 50 | id: 'simple_test', 51 | name: 'simple_test', 52 | description: 'A simple test prompt', 53 | userMessageTemplate: 'Process this: {{content}}', 54 | arguments: [ 55 | { 56 | name: 'content', 57 | description: 'Content to process', 58 | required: true 59 | } 60 | ], 61 | category: 'test' 62 | }, 63 | { 64 | id: 'multi_arg_test', 65 | name: 'multi_arg_test', 66 | description: 'Multi-argument test prompt', 67 | userMessageTemplate: 'Transform {{text}} to {{format}} in {{language}}', 68 | arguments: [ 69 | { 70 | name: 'text', 71 | description: 'Text to transform', 72 | required: true 73 | }, 74 | { 75 | name: 'format', 76 | description: 'Output format (json, xml, csv)', 77 | required: false 78 | }, 79 | { 80 | name: 'language', 81 | description: 'Target language', 82 | required: false 83 | } 84 | ], 85 | category: 'test' 86 | }, 87 | { 88 | id: 'chain_test', 89 | name: 'chain_test', 90 | description: 'Chain execution test prompt', 91 | userMessageTemplate: 'Step result: {{result}}', 92 | arguments: [ 93 | { 94 | name: 'result', 95 | description: 'Result from previous step', 96 | required: false 97 | } 98 | ], 99 | category: 'test' 100 | } 101 | ]; 102 | 103 | const testConvertedPrompts: ConvertedPrompt[] = testPromptsData.map(prompt => ({ 104 | ...prompt, 105 | chainSteps: prompt.id === 'chain_test' ? [ 106 | { stepName: 'Step 1', promptId: 'simple_test' }, 107 | { stepName: 'Step 2', promptId: 'multi_arg_test' } 108 | ] : undefined 109 | })); 110 | 111 | describe('Unified Parsing Integration Tests', () => { 112 | let promptEngine: ConsolidatedPromptEngine; 113 | 114 | beforeEach(() => { 115 | // Reset mocks 116 | jest.clearAllMocks(); 117 | 118 | // Create prompt engine with enhanced parsing 119 | promptEngine = createConsolidatedPromptEngine( 120 | mockLogger, 121 | mockMcpServer, 122 | mockPromptManager, 123 | mockSemanticAnalyzer 124 | ); 125 | 126 | // Update test data 127 | promptEngine.updateData(testPromptsData, testConvertedPrompts); 128 | }); 129 | 130 | describe('End-to-End Command Processing', () => { 131 | test('should process simple command through entire pipeline', async () => { 132 | const mockExecutePrompt = jest.fn(); 133 | 134 | // Mock the internal executePrompt method 135 | (promptEngine as any).executePrompt = mockExecutePrompt.mockResolvedValue({ 136 | content: [{ type: 'text', text: 'Success: Processed content' }] 137 | }); 138 | 139 | // Simulate the command execution 140 | const result = await (promptEngine as any).executePrompt({ 141 | command: '>>simple_test Hello world', 142 | execution_mode: 'auto' 143 | }, {}); 144 | 145 | expect(mockExecutePrompt).toHaveBeenCalledWith( 146 | expect.objectContaining({ 147 | command: '>>simple_test Hello world', 148 | execution_mode: 'auto' 149 | }), 150 | {} 151 | ); 152 | }); 153 | 154 | test('should handle JSON command format', async () => { 155 | const mockExecutePrompt = jest.fn().mockResolvedValue({ 156 | content: [{ type: 'text', text: 'Success: JSON processed' }] 157 | }); 158 | 159 | (promptEngine as any).executePrompt = mockExecutePrompt; 160 | 161 | const jsonCommand = JSON.stringify({ 162 | command: '>>multi_arg_test', 163 | args: { 164 | text: 'Hello world', 165 | format: 'json', 166 | language: 'en' 167 | } 168 | }); 169 | 170 | await (promptEngine as any).executePrompt({ 171 | command: jsonCommand, 172 | execution_mode: 'auto' 173 | }, {}); 174 | 175 | expect(mockExecutePrompt).toHaveBeenCalled(); 176 | }); 177 | 178 | test('should handle structured command format', async () => { 179 | const mockExecutePrompt = jest.fn().mockResolvedValue({ 180 | content: [{ type: 'text', text: 'Success: Structured processed' }] 181 | }); 182 | 183 | (promptEngine as any).executePrompt = mockExecutePrompt; 184 | 185 | const structuredCommand = 'multi_arg_test {"text": "Hello", "format": "xml"}'; 186 | 187 | await (promptEngine as any).executePrompt({ 188 | command: structuredCommand, 189 | execution_mode: 'template' 190 | }, {}); 191 | 192 | expect(mockExecutePrompt).toHaveBeenCalled(); 193 | }); 194 | 195 | test('should parse multi-line arguments in simple command format', async () => { 196 | const multiLineCommand = [ 197 | '>>simple_test **Title**: Network Layers Model', 198 | '**Summary**:', 199 | 'Line one of details.', 200 | 'Line two of details.' 201 | ].join('\n'); 202 | 203 | const result = await (promptEngine as any).parseCommandUnified(multiLineCommand); 204 | 205 | expect(result.promptId).toBe('simple_test'); 206 | expect(result.arguments).toBeDefined(); 207 | expect(result.arguments.content).toContain('Line two of details.'); 208 | expect(result.arguments.content.split('\n').length).toBeGreaterThan(1); 209 | }); 210 | }); 211 | 212 | describe('Context-Aware Processing', () => { 213 | test('should use conversation history for context', async () => { 214 | // Mock conversation history in prompt manager 215 | const mockHistory = [ 216 | { role: 'user', content: 'Previous message content', timestamp: Date.now() - 1000 } 217 | ]; 218 | 219 | mockPromptManager.getHistory = jest.fn().mockReturnValue(mockHistory); 220 | 221 | const mockExecutePrompt = jest.fn().mockResolvedValue({ 222 | content: [{ type: 'text', text: 'Success: Context aware' }] 223 | }); 224 | 225 | (promptEngine as any).executePrompt = mockExecutePrompt; 226 | 227 | // Execute command that should use context 228 | await (promptEngine as any).executePrompt({ 229 | command: '>>simple_test', 230 | execution_mode: 'auto' 231 | }, {}); 232 | 233 | expect(mockExecutePrompt).toHaveBeenCalled(); 234 | }); 235 | 236 | test('should resolve environment variables for defaults', async () => { 237 | // Set environment variable 238 | process.env.PROMPT_FORMAT = 'json'; 239 | process.env.PROMPT_LANGUAGE = 'es'; 240 | 241 | const mockExecutePrompt = jest.fn().mockResolvedValue({ 242 | content: [{ type: 'text', text: 'Success: Environment resolved' }] 243 | }); 244 | 245 | (promptEngine as any).executePrompt = mockExecutePrompt; 246 | 247 | await (promptEngine as any).executePrompt({ 248 | command: '>>multi_arg_test Hello world', 249 | execution_mode: 'auto' 250 | }, {}); 251 | 252 | expect(mockExecutePrompt).toHaveBeenCalled(); 253 | 254 | // Clean up 255 | delete process.env.PROMPT_FORMAT; 256 | delete process.env.PROMPT_LANGUAGE; 257 | }); 258 | }); 259 | 260 | describe('Error Handling and Recovery', () => { 261 | test('should provide helpful error messages for unknown prompts', async () => { 262 | try { 263 | await (promptEngine as any).parseCommandUnified('>>unknown_prompt test'); 264 | expect(true).toBe(false); // Should not reach here 265 | } catch (error: any) { 266 | expect(error.message).toContain('Unknown prompt: unknown_prompt'); 267 | expect(error.message).toContain('>>listprompts'); 268 | } 269 | }); 270 | 271 | test('should suggest corrections for typos', async () => { 272 | try { 273 | await (promptEngine as any).parseCommandUnified('>>simple_tst test'); 274 | } catch (error: any) { 275 | expect(error.message).toContain('simple_test'); 276 | } 277 | }); 278 | 279 | test('should handle malformed JSON gracefully', async () => { 280 | try { 281 | await (promptEngine as any).parseCommandUnified('{"command": ">>simple_test", "malformed": json}'); 282 | } catch (error: any) { 283 | expect(error.message).toContain('Supported command formats'); 284 | } 285 | }); 286 | }); 287 | 288 | describe('Performance and Statistics', () => { 289 | test('should track parsing statistics', async () => { 290 | // Execute several commands 291 | const commands = [ 292 | '>>simple_test hello', 293 | '>>multi_arg_test world format=json', 294 | 'chain_test {"result": "test"}' 295 | ]; 296 | 297 | for (const command of commands) { 298 | try { 299 | await (promptEngine as any).parseCommandUnified(command); 300 | } catch (error) { 301 | // Expected for some test cases 302 | } 303 | } 304 | 305 | const stats = promptEngine.getParsingStats(); 306 | 307 | expect(stats.commandParser).toBeDefined(); 308 | expect(stats.argumentProcessor).toBeDefined(); 309 | expect(stats.contextResolver).toBeDefined(); 310 | 311 | expect(stats.commandParser.totalParses).toBeGreaterThan(0); 312 | expect(stats.argumentProcessor.totalProcessed).toBeGreaterThan(0); 313 | expect(stats.contextResolver.totalResolutions).toBeGreaterThan(0); 314 | }); 315 | 316 | test('should allow statistics reset', () => { 317 | promptEngine.resetParsingStats(); 318 | 319 | const stats = promptEngine.getParsingStats(); 320 | expect(stats.commandParser.totalParses).toBe(0); 321 | expect(stats.argumentProcessor.totalProcessed).toBe(0); 322 | expect(stats.contextResolver.totalResolutions).toBe(0); 323 | }); 324 | }); 325 | 326 | describe('Execution Mode Detection', () => { 327 | test('should detect template mode for simple prompts', async () => { 328 | mockSemanticAnalyzer.analyzePrompt.mockResolvedValue({ 329 | executionType: 'template', 330 | requiresExecution: false, 331 | confidence: 0.9, 332 | reasoning: ['Simple informational prompt'], 333 | suggestedGates: [] 334 | }); 335 | 336 | const mockExecuteTemplate = jest.fn().mockResolvedValue({ 337 | content: [{ type: 'text', text: 'Template result' }] 338 | }); 339 | 340 | (promptEngine as any).executeTemplate = mockExecuteTemplate; 341 | 342 | await (promptEngine as any).executePrompt({ 343 | command: '>>simple_test info request', 344 | execution_mode: 'auto' 345 | }, {}); 346 | 347 | // Would verify template mode was selected 348 | }); 349 | 350 | test('should detect chain mode for chain prompts', async () => { 351 | const chainPrompt = testConvertedPrompts.find(p => isChainPrompt(p)); 352 | expect(chainPrompt).toBeDefined(); 353 | 354 | const mockExecuteChain = jest.fn().mockResolvedValue({ 355 | content: [{ type: 'text', text: 'Chain result' }] 356 | }); 357 | 358 | (promptEngine as any).executeChain = mockExecuteChain; 359 | 360 | // Test would verify chain execution 361 | }); 362 | }); 363 | 364 | describe('Backward Compatibility', () => { 365 | test('should maintain compatibility with legacy parseCommand calls', async () => { 366 | const legacyResult = await (promptEngine as any).parseCommand('>>simple_test legacy test'); 367 | 368 | expect(legacyResult.promptId).toBe('simple_test'); 369 | expect(legacyResult.arguments).toBeDefined(); 370 | expect(legacyResult.convertedPrompt).toBeDefined(); 371 | }); 372 | 373 | test('should maintain compatibility with legacy parseArguments calls', async () => { 374 | const legacyResult = await (promptEngine as any).parseArguments( 375 | 'legacy argument test', 376 | testPromptsData[0] 377 | ); 378 | 379 | expect(legacyResult).toBeDefined(); 380 | expect(typeof legacyResult).toBe('object'); 381 | }); 382 | 383 | test('should log migration warnings for deprecated methods', async () => { 384 | await (promptEngine as any).parseCommand('>>simple_test legacy'); 385 | await (promptEngine as any).parseArguments('test', testPromptsData[0]); 386 | 387 | expect(mockLogger.warn).toHaveBeenCalledWith( 388 | expect.stringContaining('[MIGRATION]') 389 | ); 390 | }); 391 | }); 392 | 393 | describe('Real-World Scenarios', () => { 394 | test('should handle complex multi-step workflow', async () => { 395 | const workflow = [ 396 | '>>simple_test Extract key information from this document', 397 | '>>multi_arg_test format=json language=en', 398 | '>>chain_test' 399 | ]; 400 | 401 | for (const command of workflow) { 402 | try { 403 | await (promptEngine as any).parseCommandUnified(command); 404 | } catch (error) { 405 | // Some commands may fail in test environment 406 | } 407 | } 408 | 409 | // Verify the workflow was processed 410 | const stats = promptEngine.getParsingStats(); 411 | expect(stats.commandParser.totalParses).toBe(workflow.length); 412 | }); 413 | 414 | test('should handle concurrent command processing', async () => { 415 | const concurrentCommands = [ 416 | '>>simple_test concurrent test 1', 417 | '>>multi_arg_test concurrent test 2', 418 | '>>simple_test concurrent test 3' 419 | ]; 420 | 421 | const promises = concurrentCommands.map(command => 422 | (promptEngine as any).parseCommandUnified(command).catch(() => null) 423 | ); 424 | 425 | await Promise.all(promises); 426 | 427 | const stats = promptEngine.getParsingStats(); 428 | expect(stats.commandParser.totalParses).toBe(concurrentCommands.length); 429 | }); 430 | 431 | test('should maintain state consistency under load', async () => { 432 | const commands = Array(50).fill(null).map((_, i) => 433 | `>>simple_test load test ${i}` 434 | ); 435 | 436 | for (const command of commands) { 437 | try { 438 | await (promptEngine as any).parseCommandUnified(command); 439 | } catch (error) { 440 | // Expected in test environment 441 | } 442 | } 443 | 444 | const stats = promptEngine.getParsingStats(); 445 | expect(stats.commandParser.successfulParses + stats.commandParser.failedParses) 446 | .toBe(stats.commandParser.totalParses); 447 | }); 448 | }); 449 | }); 450 | 451 | describe('Migration Validation', () => { 452 | test('should demonstrate zero-breaking-changes migration', async () => { 453 | const promptEngine = createConsolidatedPromptEngine( 454 | mockLogger, 455 | mockMcpServer, 456 | mockPromptManager, 457 | mockSemanticAnalyzer 458 | ); 459 | 460 | promptEngine.updateData(testPromptsData, testConvertedPrompts); 461 | 462 | // All these legacy patterns should still work 463 | const legacyPatterns = [ 464 | '>>simple_test hello', 465 | '>>multi_arg_test text format=json', 466 | 'chain_test' 467 | ]; 468 | 469 | let allPassed = true; 470 | for (const pattern of legacyPatterns) { 471 | try { 472 | await (promptEngine as any).parseCommandUnified(pattern); 473 | } catch (error) { 474 | // Error handling is expected, but should be graceful 475 | expect((error as Error).message).toBeTruthy(); 476 | } 477 | } 478 | 479 | // Verify enhanced features are available 480 | expect(promptEngine.getParsingStats).toBeDefined(); 481 | expect(promptEngine.resetParsingStats).toBeDefined(); 482 | }); 483 | }); 484 | 485 | describe('System Health and Monitoring', () => { 486 | test('should provide comprehensive system health metrics', () => { 487 | const promptEngine = createConsolidatedPromptEngine( 488 | mockLogger, 489 | mockMcpServer, 490 | mockPromptManager, 491 | mockSemanticAnalyzer 492 | ); 493 | 494 | const executionStats = promptEngine.getAnalytics(); 495 | const parsingStats = promptEngine.getParsingStats(); 496 | 497 | expect(executionStats).toHaveProperty('totalExecutions'); 498 | expect(executionStats).toHaveProperty('successfulExecutions'); 499 | expect(executionStats).toHaveProperty('executionsByMode'); 500 | 501 | expect(parsingStats).toHaveProperty('commandParser'); 502 | expect(parsingStats).toHaveProperty('argumentProcessor'); 503 | expect(parsingStats).toHaveProperty('contextResolver'); 504 | }); 505 | 506 | test('should enable performance monitoring', async () => { 507 | const promptEngine = createConsolidatedPromptEngine( 508 | mockLogger, 509 | mockMcpServer, 510 | mockPromptManager, 511 | mockSemanticAnalyzer 512 | ); 513 | 514 | promptEngine.updateData(testPromptsData, testConvertedPrompts); 515 | 516 | // Perform operations 517 | try { 518 | await (promptEngine as any).parseCommandUnified('>>simple_test monitoring test'); 519 | } catch (error) { 520 | // Expected in test environment 521 | } 522 | 523 | const stats = promptEngine.getParsingStats(); 524 | 525 | // Verify metrics are being collected 526 | expect(stats.commandParser.totalParses).toBeGreaterThan(0); 527 | expect(stats.commandParser.averageConfidence).toBeGreaterThanOrEqual(0); 528 | }); 529 | }); 530 | ``` -------------------------------------------------------------------------------- /server/src/prompts/file-observer.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * File Observer Module 3 | * Handles file system watching for automatic change detection and hot reload triggers 4 | */ 5 | 6 | import * as fs from "fs"; 7 | import { FSWatcher } from "fs"; 8 | import path from "path"; 9 | import { Logger } from "../logging/index.js"; 10 | import { EventEmitter } from "events"; 11 | import { ConfigManager } from "../config/index.js"; 12 | 13 | /** 14 | * File change event types 15 | */ 16 | export type FileChangeType = 'added' | 'modified' | 'removed' | 'renamed'; 17 | 18 | /** 19 | * Framework analysis data for file changes 20 | */ 21 | export interface FrameworkAnalysisData { 22 | requiresFrameworkUpdate: boolean; 23 | affectedFrameworks: string[]; 24 | analysisInvalidated: boolean; 25 | performanceImpact: 'low' | 'medium' | 'high'; 26 | } 27 | 28 | /** 29 | * File change event data 30 | */ 31 | export interface FileChangeEvent { 32 | type: FileChangeType; 33 | filePath: string; 34 | filename: string; 35 | timestamp: number; 36 | isPromptFile: boolean; 37 | isConfigFile: boolean; 38 | category?: string; 39 | frameworkAnalysis?: FrameworkAnalysisData; 40 | } 41 | 42 | /** 43 | * Framework integration capabilities 44 | */ 45 | export interface FrameworkIntegration { 46 | enabled: boolean; 47 | analyzeChanges: boolean; 48 | cacheInvalidation: boolean; 49 | performanceTracking: boolean; 50 | } 51 | 52 | /** 53 | * File observer configuration 54 | */ 55 | export interface FileObserverConfig { 56 | enabled: boolean; 57 | debounceMs: number; 58 | watchPromptFiles: boolean; 59 | watchConfigFiles: boolean; 60 | recursive: boolean; 61 | ignoredPatterns: string[]; 62 | maxRetries: number; 63 | retryDelayMs: number; 64 | frameworkIntegration?: FrameworkIntegration; 65 | } 66 | 67 | /** 68 | * File observer statistics 69 | */ 70 | export interface FileObserverStats { 71 | watchersActive: number; 72 | eventsDetected: number; 73 | eventsDebounced: number; 74 | eventsTriggered: number; 75 | lastEventTime?: number; 76 | uptime: number; 77 | retryCount: number; 78 | frameworkEvents: number; 79 | frameworkCacheInvalidations: number; 80 | } 81 | 82 | /** 83 | * Default configuration for FileObserver 84 | */ 85 | const DEFAULT_CONFIG: FileObserverConfig = { 86 | enabled: true, 87 | debounceMs: 500, 88 | watchPromptFiles: true, 89 | watchConfigFiles: true, 90 | recursive: true, 91 | ignoredPatterns: [ 92 | '**/.git/**', 93 | '**/node_modules/**', 94 | '**/.DS_Store', 95 | '**/Thumbs.db', 96 | '**/*.tmp', 97 | '**/*.temp', 98 | '**/dist/**', 99 | '**/*.log' 100 | ], 101 | maxRetries: 3, 102 | retryDelayMs: 1000, 103 | frameworkIntegration: { 104 | enabled: false, 105 | analyzeChanges: false, 106 | cacheInvalidation: false, 107 | performanceTracking: false 108 | } 109 | }; 110 | 111 | /** 112 | * FileObserver class 113 | * Provides robust file system watching with event-driven architecture 114 | */ 115 | export class FileObserver extends EventEmitter { 116 | protected logger: Logger; 117 | private config: FileObserverConfig; 118 | private watchers: Map<string, FSWatcher> = new Map(); 119 | private debounceTimers: Map<string, NodeJS.Timeout> = new Map(); 120 | private stats: FileObserverStats; 121 | private isStarted: boolean = false; 122 | private startTime: number = 0; 123 | private retryCount: number = 0; 124 | private configManager?: ConfigManager; 125 | 126 | constructor(logger: Logger, config?: Partial<FileObserverConfig>, configManager?: ConfigManager) { 127 | super(); 128 | this.logger = logger; 129 | this.config = { ...DEFAULT_CONFIG, ...config }; 130 | this.configManager = configManager; 131 | this.stats = { 132 | watchersActive: 0, 133 | eventsDetected: 0, 134 | eventsDebounced: 0, 135 | eventsTriggered: 0, 136 | uptime: 0, 137 | retryCount: 0, 138 | frameworkEvents: 0, 139 | frameworkCacheInvalidations: 0 140 | }; 141 | 142 | // Set max listeners to prevent warning for multiple prompt directories 143 | this.setMaxListeners(50); 144 | } 145 | 146 | /** 147 | * Start file watching 148 | */ 149 | async start(): Promise<void> { 150 | if (this.isStarted) { 151 | this.logger.warn("FileObserver is already started"); 152 | return; 153 | } 154 | 155 | if (!this.config.enabled) { 156 | this.logger.info("FileObserver is disabled in configuration"); 157 | return; 158 | } 159 | 160 | this.logger.info("📁 FileObserver: Starting file system watching..."); 161 | this.startTime = Date.now(); 162 | this.isStarted = true; 163 | 164 | // Listen for process termination to clean up watchers 165 | process.on('SIGINT', () => this.stop()); 166 | process.on('SIGTERM', () => this.stop()); 167 | 168 | this.logger.info(`✅ FileObserver started with debounce: ${this.config.debounceMs}ms`); 169 | } 170 | 171 | /** 172 | * Stop file watching and clean up resources 173 | */ 174 | async stop(): Promise<void> { 175 | if (!this.isStarted) { 176 | return; 177 | } 178 | 179 | this.logger.info("🛑 FileObserver: Stopping file system watching..."); 180 | 181 | // Clear all debounce timers 182 | for (const timer of this.debounceTimers.values()) { 183 | clearTimeout(timer); 184 | } 185 | this.debounceTimers.clear(); 186 | 187 | // Close all watchers 188 | for (const [path, watcher] of this.watchers.entries()) { 189 | try { 190 | watcher.close(); 191 | this.logger.debug(`Closed watcher for: ${path}`); 192 | } catch (error) { 193 | this.logger.warn(`Failed to close watcher for ${path}:`, error); 194 | } 195 | } 196 | this.watchers.clear(); 197 | 198 | this.isStarted = false; 199 | this.stats.watchersActive = 0; 200 | 201 | this.logger.info("✅ FileObserver stopped and resources cleaned up"); 202 | } 203 | 204 | /** 205 | * Add a directory to watch 206 | */ 207 | async watchDirectory(directoryPath: string, category?: string): Promise<void> { 208 | if (!this.isStarted) { 209 | throw new Error("FileObserver must be started before adding watchers"); 210 | } 211 | 212 | if (this.watchers.has(directoryPath)) { 213 | this.logger.debug(`Directory already being watched: ${directoryPath}`); 214 | return; 215 | } 216 | 217 | try { 218 | // Verify directory exists 219 | const stats = await fs.promises.stat(directoryPath); 220 | if (!stats.isDirectory()) { 221 | throw new Error(`Path is not a directory: ${directoryPath}`); 222 | } 223 | 224 | const watcher = fs.watch(directoryPath, { recursive: this.config.recursive }, (eventType, filename) => { 225 | this.handleFileEvent(eventType, directoryPath, filename, category); 226 | }); 227 | 228 | watcher.on('error', (error) => { 229 | this.handleWatcherError(directoryPath, error); 230 | }); 231 | 232 | this.watchers.set(directoryPath, watcher); 233 | this.stats.watchersActive = this.watchers.size; 234 | 235 | this.logger.info(`👁️ FileObserver: Watching directory: ${directoryPath}${category ? ` (category: ${category})` : ''}`); 236 | 237 | } catch (error) { 238 | this.logger.error(`Failed to watch directory ${directoryPath}:`, error); 239 | if (this.retryCount < this.config.maxRetries) { 240 | this.retryCount++; 241 | this.stats.retryCount++; 242 | this.logger.info(`Retrying in ${this.config.retryDelayMs}ms (attempt ${this.retryCount}/${this.config.maxRetries})`); 243 | setTimeout(() => this.watchDirectory(directoryPath, category), this.config.retryDelayMs); 244 | } else { 245 | throw error; 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Remove a directory from watching 252 | */ 253 | async unwatchDirectory(directoryPath: string): Promise<void> { 254 | const watcher = this.watchers.get(directoryPath); 255 | if (!watcher) { 256 | this.logger.debug(`Directory not being watched: ${directoryPath}`); 257 | return; 258 | } 259 | 260 | try { 261 | watcher.close(); 262 | this.watchers.delete(directoryPath); 263 | this.stats.watchersActive = this.watchers.size; 264 | 265 | // Clear any pending debounce timers for this directory 266 | const timersToRemove: string[] = []; 267 | for (const [key, timer] of this.debounceTimers.entries()) { 268 | if (key.startsWith(directoryPath)) { 269 | clearTimeout(timer); 270 | timersToRemove.push(key); 271 | } 272 | } 273 | timersToRemove.forEach(key => this.debounceTimers.delete(key)); 274 | 275 | this.logger.info(`🚫 FileObserver: Stopped watching directory: ${directoryPath}`); 276 | 277 | } catch (error) { 278 | this.logger.error(`Failed to stop watching directory ${directoryPath}:`, error); 279 | throw error; 280 | } 281 | } 282 | 283 | /** 284 | * Handle file system events 285 | */ 286 | private handleFileEvent(eventType: string, directoryPath: string, filename: string | null, category?: string): void { 287 | if (!filename) { 288 | return; 289 | } 290 | 291 | const filePath = path.join(directoryPath, filename); 292 | this.stats.eventsDetected++; 293 | 294 | // Check if file should be ignored 295 | if (this.shouldIgnoreFile(filePath, filename)) { 296 | return; 297 | } 298 | 299 | // Determine file types 300 | const isPromptFile = this.isPromptFile(filename); 301 | const isConfigFile = this.isConfigFile(filename, filePath); 302 | 303 | // Skip if we're not watching this type 304 | if (!isPromptFile && !isConfigFile) { 305 | return; 306 | } 307 | 308 | if (!this.config.watchPromptFiles && isPromptFile) { 309 | return; 310 | } 311 | 312 | if (!this.config.watchConfigFiles && isConfigFile) { 313 | return; 314 | } 315 | 316 | const changeType = this.mapEventType(eventType); 317 | const event: FileChangeEvent = { 318 | type: changeType, 319 | filePath, 320 | filename, 321 | timestamp: Date.now(), 322 | isPromptFile, 323 | isConfigFile, 324 | category 325 | }; 326 | 327 | // Add framework analysis if enabled 328 | if (this.config.frameworkIntegration?.enabled) { 329 | event.frameworkAnalysis = this.analyzeFrameworkImpact(event); 330 | if (event.frameworkAnalysis.requiresFrameworkUpdate) { 331 | this.stats.frameworkEvents++; 332 | } 333 | } 334 | 335 | this.logger.debug(`File event detected: ${changeType} ${filename} in ${directoryPath}`); 336 | 337 | // Apply debouncing 338 | this.debounceEvent(event); 339 | } 340 | 341 | /** 342 | * Apply debouncing to prevent excessive event firing 343 | */ 344 | private debounceEvent(event: FileChangeEvent): void { 345 | const debounceKey = `${event.filePath}_${event.type}`; 346 | 347 | // Clear existing timer for this file+type 348 | const existingTimer = this.debounceTimers.get(debounceKey); 349 | if (existingTimer) { 350 | clearTimeout(existingTimer); 351 | this.stats.eventsDebounced++; 352 | } 353 | 354 | // Set new timer 355 | const timer = setTimeout(() => { 356 | this.debounceTimers.delete(debounceKey); 357 | this.emitFileChangeEvent(event); 358 | }, this.config.debounceMs); 359 | 360 | this.debounceTimers.set(debounceKey, timer); 361 | } 362 | 363 | /** 364 | * Emit the file change event 365 | */ 366 | private emitFileChangeEvent(event: FileChangeEvent): void { 367 | this.stats.eventsTriggered++; 368 | this.stats.lastEventTime = event.timestamp; 369 | 370 | this.logger.info(`🔄 FileObserver: File ${event.type}: ${event.filename}`); 371 | 372 | // Emit specific event types 373 | this.emit('fileChange', event); 374 | this.emit(`file:${event.type}`, event); 375 | 376 | if (event.isPromptFile) { 377 | this.emit('promptFileChange', event); 378 | } 379 | 380 | if (event.isConfigFile) { 381 | this.emit('configFileChange', event); 382 | } 383 | } 384 | 385 | /** 386 | * Handle watcher errors 387 | */ 388 | private handleWatcherError(directoryPath: string, error: Error): void { 389 | this.logger.error(`FileObserver: Watcher error for ${directoryPath}:`, error); 390 | 391 | // Remove failed watcher 392 | this.watchers.delete(directoryPath); 393 | this.stats.watchersActive = this.watchers.size; 394 | 395 | // Emit error event 396 | this.emit('watcherError', { directoryPath, error }); 397 | 398 | // Attempt to restart watcher 399 | if (this.retryCount < this.config.maxRetries) { 400 | this.retryCount++; 401 | this.stats.retryCount++; 402 | setTimeout(() => { 403 | this.logger.info(`Attempting to restart watcher for: ${directoryPath}`); 404 | this.watchDirectory(directoryPath).catch(retryError => { 405 | this.logger.error(`Failed to restart watcher for ${directoryPath}:`, retryError); 406 | }); 407 | }, this.config.retryDelayMs); 408 | } 409 | } 410 | 411 | /** 412 | * Check if file should be ignored 413 | */ 414 | private shouldIgnoreFile(filePath: string, filename: string): boolean { 415 | const normalizedPath = path.normalize(filePath).replace(/\\/g, '/'); 416 | 417 | return this.config.ignoredPatterns.some(pattern => { 418 | // Simple glob pattern matching 419 | const regexPattern = pattern 420 | .replace(/\*\*/g, '.*') 421 | .replace(/\*/g, '[^/]*') 422 | .replace(/\?/g, '[^/]'); 423 | 424 | const regex = new RegExp(`^${regexPattern}$`); 425 | return regex.test(normalizedPath) || regex.test(filename); 426 | }); 427 | } 428 | 429 | /** 430 | * Check if file is a prompt file 431 | */ 432 | private isPromptFile(filename: string): boolean { 433 | const ext = path.extname(filename).toLowerCase(); 434 | return ext === '.md' || ext === '.markdown'; 435 | } 436 | 437 | /** 438 | * Check if file is a configuration file 439 | */ 440 | private isConfigFile(filename: string, fullPath?: string): boolean { 441 | const basename = path.basename(filename); 442 | 443 | // Standard config files 444 | if (basename === 'prompts.json' || basename === 'config.json') { 445 | return true; 446 | } 447 | 448 | // Main prompts config - get filename from ConfigManager if available 449 | if (this.configManager && fullPath) { 450 | try { 451 | const mainConfigPath = this.configManager.getPromptsFilePath(); 452 | const mainConfigFilename = path.basename(mainConfigPath); 453 | return basename === mainConfigFilename; 454 | } catch (error) { 455 | // Fallback to default behavior if ConfigManager fails 456 | this.logger.debug(`Could not get prompts config path from ConfigManager: ${error}`); 457 | } 458 | } 459 | 460 | // Fallback for backward compatibility 461 | return basename === 'promptsConfig.json'; 462 | } 463 | 464 | /** 465 | * Map fs.watch event types to our event types 466 | */ 467 | private mapEventType(eventType: string): FileChangeType { 468 | switch (eventType) { 469 | case 'rename': 470 | return 'renamed'; 471 | case 'change': 472 | return 'modified'; 473 | default: 474 | return 'modified'; 475 | } 476 | } 477 | 478 | /** 479 | * Get current statistics 480 | */ 481 | getStats(): FileObserverStats { 482 | return { 483 | ...this.stats, 484 | uptime: this.isStarted ? Date.now() - this.startTime : 0 485 | }; 486 | } 487 | 488 | /** 489 | * Get current configuration 490 | */ 491 | getConfig(): FileObserverConfig { 492 | return { ...this.config }; 493 | } 494 | 495 | /** 496 | * Update configuration 497 | */ 498 | updateConfig(newConfig: Partial<FileObserverConfig>): void { 499 | this.config = { ...this.config, ...newConfig }; 500 | this.logger.info("FileObserver configuration updated"); 501 | } 502 | 503 | /** 504 | * Get list of watched directories 505 | */ 506 | getWatchedDirectories(): string[] { 507 | return Array.from(this.watchers.keys()); 508 | } 509 | 510 | /** 511 | * Check if FileObserver is running 512 | */ 513 | isRunning(): boolean { 514 | return this.isStarted; 515 | } 516 | 517 | /** 518 | * Analyze framework impact of file changes 519 | * Phase 1: Basic analysis without complex framework dependencies 520 | */ 521 | private analyzeFrameworkImpact(event: FileChangeEvent): FrameworkAnalysisData { 522 | // Basic framework analysis for Phase 1 compatibility 523 | const requiresFrameworkUpdate = event.isPromptFile || event.isConfigFile; 524 | const affectedFrameworks = requiresFrameworkUpdate ? ['basic'] : []; 525 | const analysisInvalidated = event.isPromptFile && (event.type === 'modified' || event.type === 'added'); 526 | const performanceImpact: 'low' | 'medium' | 'high' = event.isConfigFile ? 'high' : 'low'; 527 | 528 | if (requiresFrameworkUpdate && this.config.frameworkIntegration?.cacheInvalidation) { 529 | this.stats.frameworkCacheInvalidations++; 530 | this.logger.debug(`Framework cache invalidation triggered by ${event.filename}`); 531 | } 532 | 533 | return { 534 | requiresFrameworkUpdate, 535 | affectedFrameworks, 536 | analysisInvalidated, 537 | performanceImpact 538 | }; 539 | } 540 | 541 | /** 542 | * Enable framework integration 543 | */ 544 | enableFrameworkIntegration(options: Partial<FrameworkIntegration> = {}): void { 545 | this.config.frameworkIntegration = { 546 | enabled: true, 547 | analyzeChanges: true, 548 | cacheInvalidation: true, 549 | performanceTracking: true, 550 | ...options 551 | }; 552 | this.logger.info("Framework integration enabled for FileObserver"); 553 | } 554 | 555 | /** 556 | * Disable framework integration 557 | */ 558 | disableFrameworkIntegration(): void { 559 | this.config.frameworkIntegration = { 560 | enabled: false, 561 | analyzeChanges: false, 562 | cacheInvalidation: false, 563 | performanceTracking: false 564 | }; 565 | this.logger.info("Framework integration disabled for FileObserver"); 566 | } 567 | 568 | /** 569 | * Check if framework integration is enabled 570 | */ 571 | isFrameworkIntegrationEnabled(): boolean { 572 | return this.config.frameworkIntegration?.enabled ?? false; 573 | } 574 | 575 | /** 576 | * Get debug information 577 | */ 578 | getDebugInfo(): { 579 | isRunning: boolean; 580 | config: FileObserverConfig; 581 | stats: FileObserverStats; 582 | watchedDirectories: string[]; 583 | activeDebounceTimers: number; 584 | frameworkIntegration: FrameworkIntegration | undefined; 585 | } { 586 | return { 587 | isRunning: this.isRunning(), 588 | config: this.getConfig(), 589 | stats: this.getStats(), 590 | watchedDirectories: this.getWatchedDirectories(), 591 | activeDebounceTimers: this.debounceTimers.size, 592 | frameworkIntegration: this.config.frameworkIntegration 593 | }; 594 | } 595 | } 596 | 597 | /** 598 | * Factory function to create a FileObserver instance 599 | */ 600 | export function createFileObserver(logger: Logger, config?: Partial<FileObserverConfig>, configManager?: ConfigManager): FileObserver { 601 | return new FileObserver(logger, config, configManager); 602 | } ```