UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

312 lines 12.8 kB
/** * Prompt Execution Utilities * * This module provides utilities for executing prompts with AI when enabled, * or falling back to returning prompts for external execution. */ import { getAIExecutor } from './ai-executor.js'; import { isAIExecutionEnabled, loadAIConfig } from '../config/ai-config.js'; /** * Execute a prompt with AI if enabled, otherwise return formatted prompt */ export async function executePromptWithFallback(prompt, instructions, options = {}) { const config = loadAIConfig(); const shouldUseAI = !options.forcePromptOnly && isAIExecutionEnabled(config); if (shouldUseAI) { try { const executor = getAIExecutor(); if (options.responseFormat === 'json') { const execOptions = {}; if (options.model !== undefined) execOptions.model = options.model; if (options.temperature !== undefined) execOptions.temperature = options.temperature; if (options.maxTokens !== undefined) execOptions.maxTokens = options.maxTokens; if (options.systemPrompt !== undefined) execOptions.systemPrompt = options.systemPrompt; // Apply defensive programming for executeStructuredPrompt if (executor && typeof executor.executeStructuredPrompt === 'function') { const result = await executor.executeStructuredPrompt(prompt, options.schema, execOptions); return { content: typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2), isAIGenerated: true, aiMetadata: result.raw.metadata, usage: result.raw.usage, model: result.raw.model, }; } else { console.warn('⚠️ AI Executor executeStructuredPrompt not available, falling back to prompt-only mode'); throw new Error('AI Executor not available'); } } else { const execOptions = {}; if (options.model !== undefined) execOptions.model = options.model; if (options.temperature !== undefined) execOptions.temperature = options.temperature; if (options.maxTokens !== undefined) execOptions.maxTokens = options.maxTokens; if (options.systemPrompt !== undefined) execOptions.systemPrompt = options.systemPrompt; // Apply defensive programming for executePrompt if (executor && typeof executor.executePrompt === 'function') { const result = await executor.executePrompt(prompt, execOptions); return { content: result.content, isAIGenerated: true, aiMetadata: result.metadata, usage: result.usage, model: result.model, }; } else { console.warn('⚠️ AI Executor executePrompt not available, falling back to prompt-only mode'); throw new Error('AI Executor not available'); } } } catch (error) { console.error('AI execution failed, falling back to prompt-only mode:', error); // Fall through to prompt-only mode } } // Fallback to prompt-only mode const formattedPrompt = formatPromptForExternal(prompt, instructions); return { content: formattedPrompt, isAIGenerated: false, }; } /** * Standard context placeholders used across tools */ const STANDARD_CONTEXT_PLACEHOLDERS = [ { placeholder: '{{userGoals}}', description: "User's primary objectives from the conversation", example: 'microservices migration for better scalability', }, { placeholder: '{{focusAreas}}', description: 'Specific areas of concern or interest', example: 'security, performance, maintainability', }, { placeholder: '{{constraints}}', description: 'Limitations, compliance requirements, or restrictions', example: 'GDPR compliance required, budget under $50k, minimal downtime', }, { placeholder: '{{projectPhase}}', description: 'Current project phase or stage', example: 'planning, development, migration, production', }, { placeholder: '{{userRole}}', description: "User's role or expertise level", example: 'senior architect, developer, project manager', }, ]; /** * Format a prompt for external execution with enhanced LLM instructions */ function formatPromptForExternal(prompt, instructions, contextPlaceholders = STANDARD_CONTEXT_PLACEHOLDERS) { // Check if prompt contains context placeholders const hasPlaceholders = contextPlaceholders.some(cp => prompt.includes(cp.placeholder)); const response = { executionMode: 'prompt-only', prompt: prompt, instructions: { howToUse: 'Execute this prompt within your current conversation context to preserve user goals and discussion history', contextIntegration: hasPlaceholders ? 'This prompt includes placeholders for conversation context. Replace them with relevant information from your discussion with the user before executing.' : "This prompt will work better if you provide context about the user's goals and constraints when executing it.", expectedOutput: instructions, bestPractices: [ "Maintain the user's stated objectives when executing this prompt", 'Include any constraints or preferences mentioned in the conversation', "Consider the project phase and user's role when interpreting results", "Tailor the analysis to address the user's specific concerns", ], }, ...(hasPlaceholders && { contextPlaceholders: contextPlaceholders.reduce((acc, cp) => ({ ...acc, [cp.placeholder]: { description: cp.description, example: cp.example, }, }), {}), exampleUsage: `Before executing, customize the prompt. For example, replace:\n${contextPlaceholders .slice(0, 2) .map(cp => ` ${cp.placeholder} → "${cp.example}"`) .join('\n')}`, }), tips: [ 'The more context you provide, the more tailored and useful the analysis will be', 'Consider what the user has already discussed when executing this prompt', "Focus the analysis on areas that matter most to the user's goals", ], }; return JSON.stringify(response, null, 2); } /** * Execute a prompt that expects structured ADR suggestions */ export async function executeADRSuggestionPrompt(prompt, instructions, options = {}) { const systemPrompt = options.systemPrompt || ` You are an expert software architect specializing in Architectural Decision Records (ADRs). Analyze the provided context and generate specific, actionable ADR suggestions. Focus on identifying architectural decisions that need to be documented based on the project context. Provide clear reasoning for each suggestion and prioritize them by importance. `; return executePromptWithFallback(prompt, instructions, { ...options, systemPrompt, temperature: options.temperature ?? 0.1, responseFormat: 'text', }); } /** * Execute a prompt that expects to generate actual ADRs */ export async function executeADRGenerationPrompt(prompt, instructions, options = {}) { const systemPrompt = options.systemPrompt || ` You are an expert software architect who creates comprehensive Architectural Decision Records (ADRs). Generate well-structured ADRs that follow best practices and include all necessary sections. Ensure each ADR is complete, actionable, and provides clear guidance for the development team. Use the standard ADR format with Status, Context, Decision, and Consequences sections. `; return executePromptWithFallback(prompt, instructions, { ...options, systemPrompt, temperature: options.temperature ?? 0.1, responseFormat: 'text', }); } /** * Execute a prompt for project ecosystem analysis */ export async function executeEcosystemAnalysisPrompt(prompt, instructions, options = {}) { const systemPrompt = options.systemPrompt || ` You are a senior software architect specializing in technology ecosystem analysis. Analyze the provided project context to identify technologies, patterns, and architectural decisions. Provide comprehensive insights about the project's technical landscape and recommendations for improvement. Focus on practical, actionable insights that can guide architectural decisions. `; return executePromptWithFallback(prompt, instructions, { ...options, systemPrompt, temperature: options.temperature ?? 0.1, responseFormat: 'text', }); } /** * Execute a prompt for research question generation */ export async function executeResearchPrompt(prompt, instructions, options = {}) { const systemPrompt = options.systemPrompt || ` You are a research specialist who generates comprehensive research questions and methodologies. Create detailed research plans that help teams investigate architectural decisions and technologies. Focus on practical research approaches that can be executed by development teams. Provide clear guidance on research methods, success criteria, and expected outcomes. `; return executePromptWithFallback(prompt, instructions, { ...options, systemPrompt, temperature: options.temperature ?? 0.2, // Slightly higher for creativity responseFormat: 'text', }); } /** * Check if AI execution is currently available */ export function isAIExecutionAvailable() { try { const executor = getAIExecutor(); return executor.isAvailable(); } catch { return false; } } /** * Get AI execution status for debugging */ export function getAIExecutionStatus() { try { const config = loadAIConfig(); const hasApiKey = !!config.apiKey; const isEnabled = isAIExecutionEnabled(config); let reason = undefined; if (!hasApiKey) { reason = 'Missing OPENROUTER_API_KEY environment variable'; } else if (config.executionMode !== 'full') { reason = `EXECUTION_MODE is '${config.executionMode}', should be 'full'`; } return { isEnabled, hasApiKey, executionMode: config.executionMode, model: config.defaultModel, reason, }; } catch (error) { return { isEnabled: false, hasApiKey: false, executionMode: 'unknown', model: 'unknown', reason: `Configuration error: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Get AI execution status and configuration info (legacy) */ export function getAIExecutionInfo() { const config = loadAIConfig(); const available = isAIExecutionAvailable(); const result = { available, mode: config.executionMode, }; if (available) { result.model = config.defaultModel; result.cacheEnabled = config.cacheEnabled; } return result; } /** * Format execution result for MCP tool response */ export function formatMCPResponse(result) { let responseText = result.content; // Add metadata footer if AI was used if (result.isAIGenerated && result.aiMetadata) { const metadata = [ `\n\n---`, `**AI Generated Response**`, `- Model: ${result.model}`, `- Execution Time: ${result.aiMetadata.executionTime}ms`, `- Cached: ${result.aiMetadata.cached ? 'Yes' : 'No'}`, ]; if (result.usage) { metadata.push(`- Tokens Used: ${result.usage.totalTokens} (${result.usage.promptTokens} prompt + ${result.usage.completionTokens} completion)`); } responseText += metadata.join('\n'); } return { content: [{ type: 'text', text: responseText }], }; } //# sourceMappingURL=prompt-execution.js.map