mirror-magi-meta-agent
Version:
AI-powered development planning and execution system with Supabase integration
510 lines (420 loc) • 17.1 kB
JavaScript
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;