UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

510 lines (420 loc) 17.1 kB
const fs = require('fs').promises; const path = require('path'); class GenericTemplateEngine { constructor(config, projectState) { this.config = config; this.projectState = projectState; this.templates = null; this.contextBuilder = null; } async initialize() { // Load templates from command-templates.json const templatesPath = path.join(__dirname, '../config/command-templates.json'); const templatesData = await fs.readFile(templatesPath, 'utf8'); this.templates = JSON.parse(templatesData).templates; // Initialize context builder const ContextBuilder = require('./context-builder'); this.contextBuilder = new ContextBuilder(this.projectState); } async generateCommand(taskSpecification) { if (!this.templates) { await this.initialize(); } // 1. Analyze task and select appropriate template const template = this.selectTemplate(taskSpecification); // 2. Build comprehensive context const context = await this.contextBuilder.buildContext(taskSpecification); // 3. Populate template with dynamic variables const command = this.populateTemplate(template, { ...taskSpecification, ...context, projectContext: this.getProjectContext() }); // 4. Add validation and success criteria return this.enhanceWithValidation(command, taskSpecification); } selectTemplate(taskSpec) { const taskType = this.classifyTask(taskSpec); // Return the appropriate template based on task type return this.templates[taskType] || this.templates.feature_development; } classifyTask(taskSpec) { // Simple classification based on description keywords const description = (taskSpec.description || '').toLowerCase(); if (description.includes('component') || description.includes('ui')) { return 'component_creation'; } else if (description.includes('api') || description.includes('service')) { return 'api_integration'; } else if (description.includes('database') || description.includes('schema')) { return 'database_changes'; } else if (description.includes('fix') || description.includes('bug')) { return 'bug_fix'; } else if (description.includes('test')) { return 'testing_implementation'; } return 'feature_development'; } getProjectContext() { return { currentFeature: this.projectState.current_session?.current_feature || 'none', recentChanges: this.getRecentChanges(), relatedComponents: this.findRelatedComponents(), databaseInfo: this.getCurrentDatabaseInfo(), testCoverage: this.projectState.code_metrics?.test_coverage || 0, codeStandards: this.config.code_standards, projectName: this.config.project?.name || 'Project', techStack: this.config.project?.tech_stack || {} }; } getRecentChanges() { // Return recent activities from project state return this.projectState.recent_activities || []; } findRelatedComponents() { // In a real implementation, this would analyze the codebase // For now, return empty array return []; } getCurrentDatabaseInfo() { // In a real implementation, this would read from the database schema // This is now generic and can be customized per project const dbConfig = this.config.project?.tech_stack?.backend?.database; return { type: dbConfig || 'unknown', tables: this.projectState.database_schema?.tables || [], lastMigration: this.projectState.dependencies?.last_updated || null }; } populateTemplate(template, variables) { let command = template.template; // Replace simple variables Object.keys(variables).forEach(key => { const regex = new RegExp(`{{${key}}}`, 'g'); const value = variables[key]; // Handle different value types if (typeof value === 'string' || typeof value === 'number') { command = command.replace(regex, value); } else if (typeof value === 'object') { command = command.replace(regex, JSON.stringify(value, null, 2)); } }); // Handle array iterations ({{#each requirements}}) command = this.handleArrayIterations(command, variables); return command; } handleArrayIterations(command, variables) { // Simple implementation of {{#each}} helper const eachRegex = /{{#each (\w+)}}([\s\S]*?){{\/each}}/g; return command.replace(eachRegex, (match, arrayName, template) => { const array = variables[arrayName]; if (!Array.isArray(array)) return ''; return array.map(item => { return template.replace(/{{this}}/g, item); }).join(''); }); } enhanceWithValidation(command, taskSpec) { const validationSteps = this.generateValidationSteps(taskSpec); const successCriteria = this.generateSuccessCriteria(taskSpec); return { command, validationSteps, successCriteria, estimatedTime: this.estimateTime(taskSpec), riskFactors: this.identifyRisks(taskSpec), fallbackStrategies: this.generateFallbacks(taskSpec) }; } generateValidationSteps(taskSpec) { const taskType = this.classifyTask(taskSpec); const techStack = this.config.project?.tech_stack || {}; // Generate validation steps based on the project's tech stack const commonSteps = []; // Type checking (if TypeScript is used) if (techStack.frontend?.language?.includes('TypeScript') || techStack.backend?.language?.includes('TypeScript')) { commonSteps.push('Type checking: npx tsc --noEmit'); } // Linting (if configured) if (this.config.code_standards?.linting) { commonSteps.push('Linting: npm run lint'); } // Build verification commonSteps.push('Build verification: npm run build'); const typeSpecificSteps = { component_creation: [ 'Component renders without errors', 'Props validation works correctly', 'Responsive design verified' ], api_integration: [ 'API endpoints respond correctly', 'Error handling works as expected', 'Rate limiting implemented' ], database_changes: [ 'Migration runs successfully', 'Database constraints verified', 'Schema types updated' ], testing_implementation: [ 'All new tests pass', 'Coverage meets target', 'No test regressions' ] }; return [...commonSteps, ...(typeSpecificSteps[taskType] || [])]; } generateSuccessCriteria(taskSpec) { const taskType = this.classifyTask(taskSpec); const projectName = this.config.project?.name || 'project'; const baseCriteria = [ 'No compilation errors', 'All tests passing', 'Build succeeds', `Code follows ${projectName} patterns` ]; const typeSpecificCriteria = { component_creation: [ 'Component is reusable and well-typed', 'Uses project UI patterns', 'Includes proper error handling' ], api_integration: [ 'API integration is secure', 'Proper error handling implemented', 'Integration follows project patterns' ], database_changes: [ 'Schema changes are backward compatible', 'Security policies properly configured', 'Performance considerations addressed' ] }; return [...baseCriteria, ...(typeSpecificCriteria[taskType] || [])]; } estimateTime(taskSpec) { const baseTimesByType = { 'component_creation': 45, 'bug_fix': 30, 'api_integration': 90, 'database_changes': 60, 'feature_development': 120, 'testing_implementation': 60 }; const taskType = this.classifyTask(taskSpec); const complexity = taskSpec.complexity || 2; const baseTime = baseTimesByType[taskType] || 60; // Adjust for complexity (1-5 scale) const complexityMultiplier = { 1: 0.7, 2: 1.0, 3: 1.4, 4: 2.0, 5: 3.0 }; return Math.round(baseTime * (complexityMultiplier[complexity] || 1.0)); } identifyRisks(taskSpec) { const risks = []; const taskType = this.classifyTask(taskSpec); // Common risks based on project state if (this.projectState.code_metrics?.compilation_errors > 0) { risks.push('Existing compilation errors may complicate development'); } // Type-specific risks const typeRisks = { database_changes: [ 'Schema changes may affect existing functionality', 'Data migration could impact performance' ], api_integration: [ 'External API rate limits', 'Authentication/CORS configuration issues' ], component_creation: [ 'Component may conflict with existing UI patterns' ] }; risks.push(...(typeRisks[taskType] || [])); return risks; } generateFallbacks(taskSpec) { const language = this.config.project?.tech_stack?.frontend?.language || this.config.project?.tech_stack?.backend?.language || 'code'; return [ `If ${language} compilation errors persist, focus on fixing type issues first`, 'If tests fail, check test data and fixtures', 'If build fails, verify all dependencies are installed', 'If stuck, return to meta-agent with specific error details' ]; } /** * Generate a specific command with all details filled in (no placeholders) * Used by ProjectPlanner for sequential development */ async generateSpecificCommand(taskSpecification) { if (!this.templates) { await this.initialize(); } // Get the base template const template = this.selectTemplate(taskSpecification); // Build enhanced context with specifics const context = await this.buildSpecificContext(taskSpecification); // Create a specific command with actual values const command = this.populateSpecificTemplate(template, { ...taskSpecification, ...context, projectContext: this.getProjectContext() }); // Add enhanced validation for specific tasks return this.enhanceWithSpecificValidation(command, taskSpecification); } /** * Build context with specific details from task specification */ async buildSpecificContext(taskSpec) { const context = await this.contextBuilder.buildContext(taskSpec); // Add specific details from the task specification if (taskSpec.specifics) { context.specific = taskSpec.specifics; // Extract component-specific details if (taskSpec.specifics.componentName) { context.componentName = taskSpec.specifics.componentName; context.fileName = taskSpec.specifics.filePath || `${taskSpec.specifics.componentName}.tsx`; context.componentProps = taskSpec.specifics.props || {}; } // Extract routing details if (taskSpec.specifics.routing) { context.routing = taskSpec.specifics.routing; context.routePath = taskSpec.specifics.routePath; context.routeFile = taskSpec.specifics.routeFile; } // Extract API details if (taskSpec.specifics.apiEndpoint) { context.apiEndpoint = taskSpec.specifics.apiEndpoint; context.apiMethod = taskSpec.specifics.apiMethod; context.apiParams = taskSpec.specifics.apiParams; } } // Add project-specific context if (taskSpec.context) { context.existingWork = taskSpec.context.existingWork || []; context.projectGoal = taskSpec.context.projectGoal; context.currentPhase = taskSpec.context.currentPhase; context.dependencies = taskSpec.context.dependencies || []; } return context; } /** * Populate template with specific values (no placeholders left behind) */ populateSpecificTemplate(template, variables) { let command = template.template; // Replace all variables with specific values Object.keys(variables).forEach(key => { const regex = new RegExp(`{{${key}}}`, 'g'); const value = variables[key]; if (typeof value === 'string' || typeof value === 'number') { command = command.replace(regex, value); } else if (typeof value === 'object' && value !== null) { command = command.replace(regex, JSON.stringify(value, null, 2)); } }); // Handle specific placeholders with actual values command = this.replaceSpecificPlaceholders(command, variables); // Handle array iterations with actual data command = this.handleArrayIterations(command, variables); // Remove any remaining placeholders and replace with defaults command = this.cleanupRemainingPlaceholders(command, variables); return command; } /** * Replace common placeholders with specific values */ replaceSpecificPlaceholders(command, variables) { const replacements = { // Component-specific '{{feature_name}}': variables.specific?.componentName || variables.componentName || 'NewFeature', '{{component_name}}': variables.specific?.componentName || variables.componentName || 'NewComponent', '{{file_path}}': variables.specific?.filePath || variables.fileName || 'src/components/NewComponent.tsx', // Project-specific '{{project_name}}': this.config.project?.name || 'MyProject', '{{framework}}': this.config.tech_stack?.frontend?.framework || 'React', '{{language}}': this.config.tech_stack?.frontend?.language || 'TypeScript', // Routing-specific '{{route_path}}': variables.specific?.routePath || '/new-page', '{{route_file}}': variables.specific?.routeFile || 'src/App.tsx', // API-specific '{{api_endpoint}}': variables.specific?.apiEndpoint || '/api/data', '{{api_method}}': variables.specific?.apiMethod || 'GET', // Database-specific '{{table_name}}': variables.specific?.tableName || 'new_table', '{{database_type}}': this.config.tech_stack?.backend?.database || 'PostgreSQL' }; Object.entries(replacements).forEach(([placeholder, value]) => { command = command.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value); }); return command; } /** * Clean up any remaining placeholders with sensible defaults */ cleanupRemainingPlaceholders(command, variables) { // Find any remaining {{placeholder}} patterns const remainingPlaceholders = command.match(/{{[^}]+}}/g) || []; remainingPlaceholders.forEach(placeholder => { const cleanPlaceholder = placeholder.replace(/[{}]/g, ''); // Provide context-aware defaults let defaultValue = cleanPlaceholder; if (cleanPlaceholder.includes('name')) { defaultValue = 'NewItem'; } else if (cleanPlaceholder.includes('path')) { defaultValue = 'src/'; } else if (cleanPlaceholder.includes('url') || cleanPlaceholder.includes('endpoint')) { defaultValue = '/api/endpoint'; } else if (cleanPlaceholder.includes('requirements')) { defaultValue = 'Basic functionality'; } command = command.replace(placeholder, defaultValue); }); return command; } /** * Enhanced validation for specific tasks */ enhanceWithSpecificValidation(command, taskSpec) { const baseValidation = this.enhanceWithValidation(command, taskSpec); // Add specific validation steps based on task details const specificValidation = []; if (taskSpec.specifics?.componentName) { specificValidation.push(`Verify ${taskSpec.specifics.componentName} component renders correctly`); specificValidation.push(`Check ${taskSpec.specifics.componentName} props are properly typed`); } if (taskSpec.specifics?.routePath) { specificValidation.push(`Navigate to ${taskSpec.specifics.routePath} and verify page loads`); specificValidation.push(`Check routing configuration is updated`); } if (taskSpec.specifics?.apiEndpoint) { specificValidation.push(`Test ${taskSpec.specifics.apiEndpoint} endpoint responds correctly`); specificValidation.push(`Verify API integration handles errors gracefully`); } // Enhanced success criteria const specificSuccess = []; if (taskSpec.context?.projectGoal) { specificSuccess.push(`Progress made toward: ${taskSpec.context.projectGoal}`); } if (taskSpec.specifics?.componentName) { specificSuccess.push(`${taskSpec.specifics.componentName} component is functional and properly integrated`); } return { ...baseValidation, validationSteps: [...baseValidation.validationSteps, ...specificValidation], successCriteria: [...baseValidation.successCriteria, ...specificSuccess], taskId: taskSpec.id || null, phase: taskSpec.context?.currentPhase || 'development' }; } } module.exports = GenericTemplateEngine;