UNPKG

optivise

Version:

Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support

735 lines (727 loc) 29.3 kB
/** * Implementation Guide Tool (optidev_implementation_guide) * Analyzes Jira tickets and provides complete implementation guidance */ import { chromaDBService } from '../integrations/chromadb-client.js'; import { ProductDetectionService } from '../services/product-detection-service.js'; import { RuleIntelligenceService } from '../services/rule-intelligence-service.js'; import { RequestFormatter } from '../formatters/request-formatter.js'; import { FormatterTemplates } from '../formatters/templates.js'; import { z } from 'zod'; export const ImplementationGuideRequestSchema = z.object({ ticketContent: z.string().min(1, 'ticketContent is required'), projectContext: z.string().optional(), userPrompt: z.string().optional(), promptContext: z.any().optional(), projectPath: z.string().optional() }); export class ImplementationGuideTool { productDetection; logger; ruleService; constructor(logger) { this.logger = logger; this.productDetection = new ProductDetectionService(logger); this.ruleService = new RuleIntelligenceService(logger); } async initialize() { await this.productDetection.initialize(); await this.ruleService.initialize(); this.logger.info('Implementation Guide Tool initialized'); } /** * Analyze Jira ticket and generate comprehensive implementation guidance */ async analyzeTicket(request) { try { const parsed = ImplementationGuideRequestSchema.safeParse(request); if (!parsed.success) { throw parsed.error; } this.logger.info('Analyzing Jira ticket for implementation guidance'); // 1. Parse and analyze ticket content const ticketAnalysis = await this.parseTicketContent(request.ticketContent); // 2. Detect relevant Optimizely products const detectedProducts = await this.detectProducts(request.ticketContent, request.projectContext); // 3. Generate implementation plan const implementationPlan = await this.generateImplementationPlan(ticketAnalysis, detectedProducts, request.projectContext); // 4. Create code templates const codeTemplates = await this.generateCodeTemplates(ticketAnalysis, detectedProducts, implementationPlan); // 5. Define testing strategy const testingStrategy = this.generateTestingStrategy(ticketAnalysis, detectedProducts); // 6. Identify deployment considerations const deploymentConsiderations = this.generateDeploymentConsiderations(detectedProducts, implementationPlan); // 7. Create documentation plan const documentation = this.generateDocumentationPlan(ticketAnalysis, detectedProducts); // 8. Suggest project milestones const suggestedMilestones = this.generateMilestones(implementationPlan, ticketAnalysis.complexity); const base = { ticketAnalysis, detectedProducts, implementationPlan, codeTemplates, testingStrategy, deploymentConsiderations, documentation, suggestedMilestones }; const blocks = [ { type: 'analysis', title: 'Ticket Analysis', content: JSON.stringify(ticketAnalysis).slice(0, 4000), relevance: 0.9 }, { type: 'analysis', title: 'Implementation Plan', content: JSON.stringify(implementationPlan).slice(0, 4000), relevance: 0.95 } ]; if (request.projectPath) { try { const rules = await this.ruleService.analyzeIDERules(request.projectPath); blocks.push({ type: 'rules', title: 'IDE Rules Summary', content: JSON.stringify({ files: rules.foundFiles, lintWarnings: rules.lintWarnings, conflicts: rules.conflicts, proposed: rules.proposedCursorRules?.slice(0, 2000), diff: rules.proposedCursorRulesDiff?.slice(0, 2000) }).slice(0, 4000), source: request.projectPath, relevance: 0.65 }); } catch { } } if (codeTemplates?.length) { blocks.push({ type: 'code', title: 'Code Templates', content: JSON.stringify(codeTemplates).slice(0, 4000), relevance: 0.7 }); } base.llm_request = RequestFormatter.format({ toolName: 'optidev_implementation_guide', userPrompt: request.userPrompt || ticketAnalysis.summary, promptContext: request.promptContext, summary: 'Produce an actionable, product-aware implementation guide with risks and milestones.', products: detectedProducts, blocks, template: FormatterTemplates.optidev_implementation_guide }); return base; } catch (error) { this.logger.error('Failed to analyze ticket for implementation guidance', error); throw error; } } /** * Parse ticket content and extract key information */ async parseTicketContent(ticketContent) { // Extract summary const summary = this.extractSummary(ticketContent); // Extract requirements const requirements = this.extractRequirements(ticketContent); // Extract acceptance criteria const acceptanceCriteria = this.extractAcceptanceCriteria(ticketContent); // Assess complexity const complexity = this.assessComplexity(ticketContent, requirements); // Estimate story points const estimatedStoryPoints = this.estimateStoryPoints(complexity, requirements.length); return { summary, requirements, acceptanceCriteria, complexity, estimatedStoryPoints }; } /** * Extract ticket summary */ extractSummary(content) { // Try to find ticket title/summary patterns const titleMatch = content.match(/(?:title|summary|epic):\s*(.+)/i); if (titleMatch?.[1]) { return titleMatch[1].trim(); } // Extract first meaningful line const lines = content.split('\n').filter(line => line.trim().length > 0); const firstLine = lines[0]?.trim() || ''; // If first line is too long, truncate it return firstLine.length > 100 ? firstLine.substring(0, 100) + '...' : firstLine; } /** * Extract requirements from ticket content */ extractRequirements(content) { const requirements = []; // Look for bulleted requirements const bulletMatches = content.match(/^[\s]*[-*•]\s*(.+)$/gm); if (bulletMatches) { requirements.push(...bulletMatches.map(match => match.replace(/^[\s]*[-*•]\s*/, '').trim())); } // Look for numbered requirements const numberedMatches = content.match(/^\s*\d+\.\s*(.+)$/gm); if (numberedMatches) { requirements.push(...numberedMatches.map(match => match.replace(/^\s*\d+\.\s*/, '').trim())); } // Look for "should" statements const shouldMatches = content.match(/\b(?:should|must|shall|will)\s+(.+?)(?:\.|$)/gi); if (shouldMatches) { requirements.push(...shouldMatches.map(match => match.trim())); } // If no explicit requirements found, extract sentences that look like requirements if (requirements.length === 0) { const sentences = content.split(/[.!?]/).filter(s => s.trim().length > 20); requirements.push(...sentences.slice(0, 3).map(s => s.trim())); } return [...new Set(requirements)].slice(0, 10); // Remove duplicates and limit } /** * Extract acceptance criteria */ extractAcceptanceCriteria(content) { const criteria = []; // Look for "acceptance criteria" section const acMatch = content.match(/acceptance\s+criteria[:\s]*((?:.|\n)*?)(?:\n\s*\n|\n\s*[A-Z]|$)/i); if (acMatch?.[1]) { const acContent = acMatch[1]; const bulletMatches = acContent.match(/^[\s]*[-*•]\s*(.+)$/gm); if (bulletMatches) { criteria.push(...bulletMatches.map(match => match.replace(/^[\s]*[-*•]\s*/, '').trim())); } } // Look for "given/when/then" patterns (BDD style) const bddMatches = content.match(/(?:given|when|then)\s+(.+?)(?:\n|$)/gi); if (bddMatches) { criteria.push(...bddMatches.map(match => match.trim())); } // Fallback to requirements if no AC found if (criteria.length === 0) { const requirements = this.extractRequirements(content); criteria.push(...requirements.slice(0, 3)); } return [...new Set(criteria)].slice(0, 8); } /** * Assess ticket complexity */ assessComplexity(content, requirements) { let complexityScore = 0; // Length-based scoring if (content.length > 2000) complexityScore += 2; else if (content.length > 1000) complexityScore += 1; // Requirements-based scoring if (requirements.length > 8) complexityScore += 2; else if (requirements.length > 4) complexityScore += 1; // Keyword-based scoring const highComplexityKeywords = [ 'integration', 'api', 'database', 'migration', 'performance', 'security', 'authentication', 'architecture', 'microservice', 'enterprise', 'scalability', 'multi-tenant', 'real-time' ]; const mediumComplexityKeywords = [ 'workflow', 'automation', 'notification', 'reporting', 'configuration', 'customization', 'extension' ]; const contentLower = content.toLowerCase(); const highMatches = highComplexityKeywords.filter(keyword => contentLower.includes(keyword)).length; const mediumMatches = mediumComplexityKeywords.filter(keyword => contentLower.includes(keyword)).length; complexityScore += highMatches * 2 + mediumMatches; // Determine final complexity if (complexityScore >= 8) return 'enterprise'; if (complexityScore >= 5) return 'high'; if (complexityScore >= 2) return 'medium'; return 'low'; } /** * Estimate story points based on complexity and requirements */ estimateStoryPoints(complexity, requirementCount) { const basePoints = { low: 2, medium: 5, high: 8, enterprise: 13 }; let points = basePoints[complexity]; // Adjust based on requirement count if (requirementCount > 8) points += 3; else if (requirementCount > 5) points += 2; else if (requirementCount > 3) points += 1; return Math.min(points, 21); // Cap at 21 (Fibonacci sequence) } /** * Detect relevant Optimizely products */ async detectProducts(ticketContent, projectContext) { const context = `${ticketContent}\n\n${projectContext || ''}`; const detection = await this.productDetection.detectFromPrompt(context); return detection.products; } /** * Generate comprehensive implementation plan */ async generateImplementationPlan(ticketAnalysis, products, projectContext) { const primaryProduct = products[0] || 'platform'; // Get relevant documentation if AI is available let contextualInfo = ''; if (chromaDBService.isAvailable()) { const docs = await chromaDBService.searchDocuments(ticketAnalysis.summary, { product: primaryProduct, limit: 3 }); contextualInfo = docs.map(doc => doc.content).join('\n'); } // Generate architecture approach based on products const approach = this.generateApproach(ticketAnalysis, products, contextualInfo); const architecture = this.generateArchitecture(products, ticketAnalysis.complexity); const estimatedEffort = this.estimateEffort(ticketAnalysis.complexity, ticketAnalysis.estimatedStoryPoints); const keyComponents = this.identifyKeyComponents(ticketAnalysis, products); const risks = this.identifyRisks(ticketAnalysis, products); const dependencies = this.identifyDependencies(products, ticketAnalysis.requirements); return { approach, architecture, estimatedEffort, keyComponents, risks, dependencies }; } /** * Generate implementation approach */ generateApproach(ticketAnalysis, products, contextualInfo) { const primaryProduct = products[0] || 'platform'; const approaches = { 'configured-commerce': 'Commerce extension-based implementation with custom business logic', 'cms-paas': 'CMS content type and template-based solution with custom components', 'cms-saas': 'Headless CMS approach with API-driven content delivery', 'experimentation': 'A/B testing framework with statistical analysis and goal tracking', 'dxp': 'Digital experience orchestration with personalization engines', 'platform': 'Cross-platform integration with unified data layer' }; let baseApproach = approaches[primaryProduct] || approaches.platform; // Enhance with complexity considerations if (ticketAnalysis.complexity === 'enterprise') { baseApproach += ' with enterprise-grade security, scalability, and monitoring'; } else if (ticketAnalysis.complexity === 'high') { baseApproach += ' with comprehensive error handling and performance optimization'; } // Add multi-product considerations if (products.length > 1) { baseApproach += `. Multi-product integration involving ${products.join(', ')}`; } return baseApproach; } /** * Generate architecture recommendation */ generateArchitecture(products, complexity) { const architectures = { low: 'Simple layered architecture with direct integrations', medium: 'Modular architecture with service layer abstraction', high: 'Microservices architecture with event-driven communication', enterprise: 'Enterprise service bus with CQRS pattern and distributed caching' }; let baseArchitecture = architectures[complexity]; // Add product-specific architectural considerations if (products.includes('configured-commerce')) { baseArchitecture += '. Commerce-specific: Extension framework with dependency injection'; } if (products.includes('cms-paas') || products.includes('cms-saas')) { baseArchitecture += '. CMS integration: Content delivery pipeline with caching layer'; } if (products.includes('experimentation')) { baseArchitecture += '. Experimentation: Statistical engine with real-time analytics'; } return baseArchitecture; } /** * Estimate implementation effort */ estimateEffort(complexity, storyPoints) { const effortMap = { low: '1-2 weeks', medium: '3-4 weeks', high: '6-8 weeks', enterprise: '12+ weeks (multiple sprints)' }; let baseEffort = effortMap[complexity]; // Adjust based on story points if (storyPoints > 13) { baseEffort = '12+ weeks (epic-level implementation)'; } else if (storyPoints > 8) { baseEffort = '6-10 weeks'; } return baseEffort; } /** * Identify key components needed */ identifyKeyComponents(ticketAnalysis, products) { const components = []; // Product-specific components if (products.includes('configured-commerce')) { components.push('Commerce Extension', 'Business Logic Layer', 'Data Access Layer'); } if (products.includes('cms-paas') || products.includes('cms-saas')) { components.push('Content Types', 'Templates', 'Content API'); } if (products.includes('experimentation')) { components.push('Experiment Configuration', 'Variation Engine', 'Analytics Tracker'); } // Generic components based on requirements const requirementText = ticketAnalysis.requirements.join(' ').toLowerCase(); if (requirementText.includes('ui') || requirementText.includes('interface')) { components.push('User Interface Components'); } if (requirementText.includes('api') || requirementText.includes('service')) { components.push('API Endpoints', 'Service Layer'); } if (requirementText.includes('database') || requirementText.includes('data')) { components.push('Database Schema', 'Data Migration Scripts'); } if (requirementText.includes('auth') || requirementText.includes('security')) { components.push('Authentication Module', 'Authorization Layer'); } // Always include testing and configuration components.push('Unit Tests', 'Integration Tests', 'Configuration Management'); return [...new Set(components)]; } /** * Identify potential risks */ identifyRisks(ticketAnalysis, products) { const risks = []; // Complexity-based risks if (ticketAnalysis.complexity === 'enterprise') { risks.push('Integration complexity may lead to extended timeline'); risks.push('Performance bottlenecks under high load'); risks.push('Security vulnerabilities in complex data flows'); } else if (ticketAnalysis.complexity === 'high') { risks.push('Technical debt accumulation'); risks.push('Integration testing complexity'); } // Product-specific risks if (products.includes('configured-commerce')) { risks.push('Commerce platform version compatibility'); risks.push('Payment gateway integration issues'); } if (products.includes('experimentation')) { risks.push('Statistical significance requirements'); risks.push('Experiment interference with existing tests'); } // Multi-product risks if (products.length > 1) { risks.push('Cross-product data synchronization challenges'); risks.push('Version compatibility across multiple products'); } // Generic risks risks.push('Scope creep during implementation'); risks.push('Third-party dependency changes'); return risks.slice(0, 6); // Limit to top risks } /** * Identify dependencies */ identifyDependencies(products, requirements) { const dependencies = []; // Product-specific dependencies products.forEach(product => { switch (product) { case 'configured-commerce': dependencies.push('Commerce Manager access', 'Extension deployment pipeline'); break; case 'cms-paas': dependencies.push('CMS admin access', 'Content migration tools'); break; case 'experimentation': dependencies.push('Analytics platform setup', 'Goal tracking configuration'); break; } }); // Requirement-based dependencies const reqText = requirements.join(' ').toLowerCase(); if (reqText.includes('api')) { dependencies.push('API documentation', 'Rate limiting configuration'); } if (reqText.includes('database')) { dependencies.push('Database access', 'Backup and recovery procedures'); } if (reqText.includes('auth')) { dependencies.push('Identity provider configuration', 'Security audit'); } // Common dependencies dependencies.push('Development environment setup', 'CI/CD pipeline configuration'); return [...new Set(dependencies)]; } /** * Generate code templates */ async generateCodeTemplates(ticketAnalysis, products, plan) { const templates = []; // Generate templates based on primary product const primaryProduct = products[0] || 'platform'; switch (primaryProduct) { case 'configured-commerce': templates.push(...this.generateCommerceTemplates(ticketAnalysis, plan)); break; case 'cms-paas': templates.push(...this.generateCMSTemplates(ticketAnalysis, plan)); break; case 'experimentation': templates.push(...this.generateExperimentationTemplates(ticketAnalysis, plan)); break; default: templates.push(...this.generateGenericTemplates(ticketAnalysis, plan)); } return templates; } /** * Generate Commerce-specific templates */ generateCommerceTemplates(ticketAnalysis, plan) { return [ { filename: 'CustomExtension.cs', language: 'csharp', description: 'Main extension class with dependency injection', content: ` using EPiServer.Commerce.Extensions; using EPiServer.ServiceLocation; namespace ${this.sanitizeNamespace(ticketAnalysis.summary)} { [ServiceConfiguration(typeof(ICustomExtension))] public class CustomExtension : ICustomExtension { // TODO: Implement based on requirements public void Initialize() { // Initialization logic } } }`.trim() }, { filename: 'Models/CustomModel.cs', language: 'csharp', description: 'Data model for the feature', content: ` using System.ComponentModel.DataAnnotations; namespace ${this.sanitizeNamespace(ticketAnalysis.summary)}.Models { public class CustomModel { [Required] public string Name { get; set; } // TODO: Add properties based on requirements } }`.trim() } ]; } /** * Generate CMS-specific templates */ generateCMSTemplates(ticketAnalysis, plan) { return [ { filename: 'ContentTypes/CustomPageType.cs', language: 'csharp', description: 'Custom page type definition', content: ` using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAnnotations; namespace ${this.sanitizeNamespace(ticketAnalysis.summary)}.ContentTypes { [ContentType(DisplayName = "Custom Page", GUID = "GUID-HERE")] public class CustomPageType : PageData { [Display(Name = "Title", GroupName = SystemTabNames.Content)] public virtual string Title { get; set; } // TODO: Add properties based on requirements } }`.trim() } ]; } /** * Generate Experimentation templates */ generateExperimentationTemplates(ticketAnalysis, plan) { return [ { filename: 'experiment-config.js', language: 'javascript', description: 'Experiment configuration', content: ` // Optimizely Experiment Configuration const experimentConfig = { experimentId: 'experiment_${Date.now()}', variations: [ { id: 'control', name: 'Control' }, { id: 'variation_1', name: 'Variation 1' } ], audience: { // TODO: Define audience criteria }, goals: [ // TODO: Define conversion goals ] }; export default experimentConfig;`.trim() } ]; } /** * Generate generic templates */ generateGenericTemplates(ticketAnalysis, plan) { return [ { filename: 'implementation.ts', language: 'typescript', description: 'Main implementation file', content: ` /** * ${ticketAnalysis.summary} * Generated implementation template */ export class Implementation { constructor() { // Initialize based on requirements } // TODO: Implement methods based on requirements ${ticketAnalysis.requirements.map(req => `// Requirement: ${req}`).join('\n ')} }`.trim() } ]; } /** * Generate testing strategy */ generateTestingStrategy(ticketAnalysis, products) { const unitTests = [ 'Business logic validation tests', 'Data model validation tests', 'Service layer unit tests' ]; const integrationTests = [ 'API endpoint integration tests', 'Database integration tests', 'Third-party service integration tests' ]; const e2eTests = [ 'Complete user workflow tests', 'Cross-browser compatibility tests', 'Performance and load tests' ]; const userAcceptanceTests = ticketAnalysis.acceptanceCriteria.map(criteria => `UAT: ${criteria}`); return { unitTests, integrationTests, e2eTests, userAcceptanceTests }; } /** * Generate deployment considerations */ generateDeploymentConsiderations(products, plan) { const considerations = [ 'Backup existing system before deployment', 'Deploy during low-traffic periods', 'Monitor system performance post-deployment' ]; // Product-specific considerations if (products.includes('configured-commerce')) { considerations.push('Commerce database migration required'); considerations.push('Payment gateway testing in production'); } if (products.includes('cms-paas') || products.includes('cms-saas')) { considerations.push('Content migration and validation'); considerations.push('URL structure changes impact SEO'); } return considerations; } /** * Generate documentation plan */ generateDocumentationPlan(ticketAnalysis, products) { return { technicalSpecs: [ 'Architecture documentation', 'API specification', 'Database schema documentation', 'Security implementation guide' ], userGuides: [ 'End-user manual', 'Administrator guide', 'Troubleshooting guide' ], apiDocumentation: [ 'REST API endpoints', 'Authentication methods', 'Rate limiting information', 'Error codes and responses' ] }; } /** * Generate project milestones */ generateMilestones(plan, complexity) { const milestones = [ { name: 'Foundation Setup', deliverables: [ 'Development environment configured', 'Project structure established', 'Core dependencies installed' ], duration: '1 week' }, { name: 'Core Implementation', deliverables: plan.keyComponents.slice(0, 3), duration: complexity === 'enterprise' ? '4-6 weeks' : '2-3 weeks' }, { name: 'Integration & Testing', deliverables: [ 'Unit tests implemented', 'Integration tests passing', 'Performance testing completed' ], duration: '1-2 weeks' }, { name: 'Deployment & Documentation', deliverables: [ 'Production deployment', 'Documentation completed', 'User training conducted' ], duration: '1 week' } ]; return milestones; } /** * Sanitize text for use in code namespaces */ sanitizeNamespace(text) { return text .replace(/[^a-zA-Z0-9\s]/g, '') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join('') .substring(0, 50) || 'CustomImplementation'; } } //# sourceMappingURL=implementation-guide-tool.js.map