UNPKG

mega-minds

Version:

Enhanced multi-agent workflow system for Claude Code projects with automated handoff management and Claude Code hooks integration

703 lines (626 loc) 22.3 kB
/** * Stack Profile Manager for Mega-Minds Variable-Driven Agent System * Manages predefined technology combinations and generates context-aware variables * Supports both popular stacks and custom configurations */ class StackProfileManager { constructor(config = {}) { this.config = { enableCustomProfiles: config.enableCustomProfiles !== false, profilesDirectory: config.profilesDirectory || null, ...config }; this.profiles = this.initializeProfiles(); this.customProfiles = new Map(); } /** * Initialize all predefined stack profiles * @returns {Object} Predefined stack profiles */ initializeProfiles() { return { // JavaScript/React Stacks 'react-nextjs-supabase': { name: 'React + Next.js + Supabase', description: 'Full-stack React application with Next.js and Supabase backend', category: 'javascript-fullstack', popularity: 'high', match: (techStack) => techStack.frameworks.frontend === 'React' && techStack.frameworks.metaFramework === 'Next.js', variables: { // Core tech stack '{{LANGUAGE_PRIMARY}}': 'TypeScript', '{{LANGUAGES_ALL}}': 'TypeScript, JavaScript', '{{FRONTEND_FRAMEWORK}}': 'React', '{{FRONTEND_VERSION}}': '18+', '{{META_FRAMEWORK}}': 'Next.js', '{{META_FRAMEWORK_VERSION}}': '14+', '{{BACKEND_FRAMEWORK}}': 'Next.js API Routes', '{{CSS_FRAMEWORK}}': 'Tailwind CSS', '{{STATE_MANAGEMENT}}': 'Zustand', '{{BUILD_TOOL}}': 'Webpack', // Testing '{{TESTING_FRAMEWORK}}': 'Jest', '{{E2E_FRAMEWORK}}': 'Playwright', '{{TESTING_LIBRARY}}': 'React Testing Library', // Database '{{DATABASE_TYPE}}': 'PostgreSQL', '{{DATABASE_SERVICE}}': 'Supabase', '{{ORM_TOOL}}': 'Supabase Client', // Project structure '{{PROJECT_TYPE}}': 'nextjs-app-router', '{{FRONTEND_DIR}}': 'app', '{{COMPONENTS_DIR}}': 'components', '{{UTILS_DIR}}': 'lib', '{{TESTS_DIR}}': '__tests__', '{{PUBLIC_DIR}}': 'public', '{{API_DIR}}': 'app/api', // File patterns '{{FILE_EXTENSION}}': '.tsx', '{{STYLE_EXTENSION}}': '.css', '{{CONFIG_EXTENSION}}': '.ts', // Infrastructure '{{DEPLOYMENT_TARGET}}': 'Vercel', '{{CI_CD_PLATFORM}}': 'GitHub Actions', '{{CONTAINER_PLATFORM}}': 'Docker', '{{CLOUD_PROVIDER}}': 'Vercel Edge Network', // Quality standards '{{TEST_COVERAGE_MIN}}': '85', '{{CRITICAL_COVERAGE}}': '95', '{{COMPLEXITY_THRESHOLD}}': '8', '{{PERFORMANCE_BUDGET}}': '2s FCP, 100KB JS bundle', '{{ACCESSIBILITY_LEVEL}}': 'WCAG 2.1 AA', // Agent workflow '{{PRIMARY_AGENTS}}': 'project-orchestrator-agent, frontend-development-agent, backend-development-agent', '{{HANDOFF_PATTERN}}': 'orchestrator → frontend/backend → testing → deployment' }, agentSpecializations: { 'frontend-development-agent': 'React 18+, Next.js App Router, TypeScript, Tailwind CSS', 'backend-development-agent': 'Next.js API Routes, Supabase integration, TypeScript', 'database-agent': 'Supabase PostgreSQL, Row Level Security, real-time subscriptions', 'testing-agent': 'Jest, React Testing Library, Playwright E2E testing' } }, 'react-vite-firebase': { name: 'React + Vite + Firebase', description: 'Single-page React application with Vite and Firebase backend', category: 'javascript-spa', popularity: 'medium', match: (techStack) => techStack.frameworks.frontend === 'React' && techStack.frameworks.buildTool === 'Vite', variables: { '{{LANGUAGE_PRIMARY}}': 'TypeScript', '{{FRONTEND_FRAMEWORK}}': 'React', '{{BUILD_TOOL}}': 'Vite', '{{TESTING_FRAMEWORK}}': 'Vitest', '{{DATABASE_SERVICE}}': 'Firebase', '{{DEPLOYMENT_TARGET}}': 'Firebase Hosting', '{{PROJECT_TYPE}}': 'react-spa', '{{FRONTEND_DIR}}': 'src', '{{FILE_EXTENSION}}': '.tsx' } }, // Vue.js Stacks 'vue-nuxt-prisma': { name: 'Vue + Nuxt + Prisma', description: 'Full-stack Vue application with Nuxt and Prisma ORM', category: 'vue-fullstack', popularity: 'medium', match: (techStack) => techStack.frameworks.frontend === 'Vue' && techStack.frameworks.metaFramework === 'Nuxt', variables: { '{{LANGUAGE_PRIMARY}}': 'TypeScript', '{{FRONTEND_FRAMEWORK}}': 'Vue', '{{FRONTEND_VERSION}}': '3+', '{{META_FRAMEWORK}}': 'Nuxt', '{{META_FRAMEWORK_VERSION}}': '3+', '{{CSS_FRAMEWORK}}': 'UnoCSS', '{{STATE_MANAGEMENT}}': 'Pinia', '{{BUILD_TOOL}}': 'Vite', '{{TESTING_FRAMEWORK}}': 'Vitest', '{{E2E_FRAMEWORK}}': 'Playwright', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{ORM_TOOL}}': 'Prisma', '{{PROJECT_TYPE}}': 'nuxt', '{{FILE_EXTENSION}}': '.vue', '{{DEPLOYMENT_TARGET}}': 'Vercel' } }, // Python Stacks 'python-django-postgres': { name: 'Python + Django + PostgreSQL', description: 'Full-stack Python web application with Django framework', category: 'python-fullstack', popularity: 'high', match: (techStack) => techStack.languages.includes('Python') && techStack.frameworks.backend === 'Django', variables: { '{{LANGUAGE_PRIMARY}}': 'Python', '{{LANGUAGES_ALL}}': 'Python, HTML, CSS, JavaScript', '{{BACKEND_FRAMEWORK}}': 'Django', '{{FRAMEWORK_VERSION}}': '4+', '{{FRONTEND_APPROACH}}': 'Django Templates + HTMX', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{ORM_TOOL}}': 'Django ORM', '{{TESTING_FRAMEWORK}}': 'pytest', '{{E2E_FRAMEWORK}}': 'Selenium', '{{PROJECT_TYPE}}': 'django', '{{BACKEND_DIR}}': '.', '{{TEMPLATES_DIR}}': 'templates', '{{STATIC_DIR}}': 'static', '{{TESTS_DIR}}': 'tests', '{{FILE_EXTENSION}}': '.py', '{{TEMPLATE_EXTENSION}}': '.html', '{{CONFIG_EXTENSION}}': '.py', '{{DEPLOYMENT_TARGET}}': 'AWS EC2', '{{CI_CD_PLATFORM}}': 'GitHub Actions', '{{CONTAINER_PLATFORM}}': 'Docker', '{{TEST_COVERAGE_MIN}}': '90', '{{CRITICAL_COVERAGE}}': '95' }, agentSpecializations: { 'backend-development-agent': 'Django 4+, Python best practices, Django ORM', 'database-agent': 'PostgreSQL, Django migrations, database optimization', 'testing-agent': 'pytest, Django TestCase, factory_boy', 'frontend-development-agent': 'Django templates, HTMX, CSS frameworks' } }, 'python-fastapi-async': { name: 'Python + FastAPI + Async', description: 'High-performance async API with FastAPI and modern Python', category: 'python-api', popularity: 'high', match: (techStack) => techStack.languages.includes('Python') && techStack.frameworks.backend === 'FastAPI', variables: { '{{LANGUAGE_PRIMARY}}': 'Python', '{{BACKEND_FRAMEWORK}}': 'FastAPI', '{{ASYNC_SUPPORT}}': 'Full async/await', '{{API_DOCS}}': 'Automatic OpenAPI/Swagger', '{{TESTING_FRAMEWORK}}': 'pytest', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{ORM_TOOL}}': 'SQLAlchemy + Alembic', '{{FILE_EXTENSION}}': '.py', '{{DEPLOYMENT_TARGET}}': 'AWS Lambda' } }, // Java Stacks 'java-spring-boot': { name: 'Java + Spring Boot', description: 'Enterprise Java application with Spring Boot framework', category: 'java-enterprise', popularity: 'high', match: (techStack) => techStack.languages.includes('Java') && techStack.frameworks.backend?.includes('Spring'), variables: { '{{LANGUAGE_PRIMARY}}': 'Java', '{{LANGUAGE_VERSION}}': '17+', '{{BACKEND_FRAMEWORK}}': 'Spring Boot', '{{FRAMEWORK_VERSION}}': '3+', '{{BUILD_TOOL}}': 'Maven', '{{TESTING_FRAMEWORK}}': 'JUnit', '{{TESTING_VERSION}}': '5+', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{ORM_TOOL}}': 'Spring Data JPA', '{{PROJECT_TYPE}}': 'maven-spring-boot', '{{BACKEND_DIR}}': 'src/main/java', '{{RESOURCES_DIR}}': 'src/main/resources', '{{TESTS_DIR}}': 'src/test/java', '{{FILE_EXTENSION}}': '.java', '{{CONFIG_EXTENSION}}': '.properties', '{{DEPLOYMENT_TARGET}}': 'AWS ECS', '{{CONTAINER_PLATFORM}}': 'Docker', '{{TEST_COVERAGE_MIN}}': '80', '{{CRITICAL_COVERAGE}}': '90' } }, // Node.js API Stacks 'node-express-mongo': { name: 'Node.js + Express + MongoDB', description: 'RESTful API with Express and MongoDB database', category: 'nodejs-api', popularity: 'medium', match: (techStack) => techStack.languages.includes('JavaScript') && techStack.frameworks.backend === 'Express', variables: { '{{LANGUAGE_PRIMARY}}': 'JavaScript', '{{BACKEND_FRAMEWORK}}': 'Express', '{{DATABASE_TYPE}}': 'MongoDB', '{{ORM_TOOL}}': 'Mongoose', '{{TESTING_FRAMEWORK}}': 'Jest', '{{API_STYLE}}': 'RESTful', '{{FILE_EXTENSION}}': '.js', '{{DEPLOYMENT_TARGET}}': 'Railway' } }, // Go Stacks 'go-gin-postgres': { name: 'Go + Gin + PostgreSQL', description: 'High-performance API with Go and Gin framework', category: 'go-api', popularity: 'medium', match: (techStack) => techStack.languages.includes('Go'), variables: { '{{LANGUAGE_PRIMARY}}': 'Go', '{{BACKEND_FRAMEWORK}}': 'Gin', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{TESTING_FRAMEWORK}}': 'Go testing', '{{FILE_EXTENSION}}': '.go', '{{DEPLOYMENT_TARGET}}': 'Google Cloud Run' } }, // Rust Stacks 'rust-axum-sqlx': { name: 'Rust + Axum + SQLx', description: 'Ultra-fast API with Rust and modern async frameworks', category: 'rust-api', popularity: 'low', match: (techStack) => techStack.languages.includes('Rust'), variables: { '{{LANGUAGE_PRIMARY}}': 'Rust', '{{BACKEND_FRAMEWORK}}': 'Axum', '{{DATABASE_TYPE}}': 'PostgreSQL', '{{ORM_TOOL}}': 'SQLx', '{{BUILD_TOOL}}': 'Cargo', '{{TESTING_FRAMEWORK}}': 'Rust test', '{{FILE_EXTENSION}}': '.rs', '{{DEPLOYMENT_TARGET}}': 'Fly.io' } } }; } /** * Find matching stack profile for given tech stack * @param {Object} techStack - Technology stack information * @returns {Object|null} Matched profile or null */ findMatchingProfile(techStack) { // Try custom profiles first for (const [key, profile] of this.customProfiles) { if (profile.match && profile.match(techStack)) { return { key, ...profile, source: 'custom' }; } } // Try predefined profiles for (const [key, profile] of Object.entries(this.profiles)) { try { if (profile.match(techStack)) { return { key, ...profile, source: 'predefined' }; } } catch (error) { console.warn(`Profile match error for ${key}:`, error.message); } } return null; } /** * Get a specific profile by key * @param {string} key - Profile key * @returns {Object|null} Profile or null if not found */ getProfile(key) { // Check predefined profiles first if (this.profiles[key]) { return { key, ...this.profiles[key], source: 'predefined' }; } // Check custom profiles if (this.customProfiles.has(key)) { return { key, ...this.customProfiles.get(key), source: 'custom' }; } return null; } /** * Get all available profiles * @param {Object} filters - Filter criteria * @returns {Array} Available profiles */ getAvailableProfiles(filters = {}) { const allProfiles = [ ...Object.entries(this.profiles).map(([key, profile]) => ({ key, ...profile, source: 'predefined' })), ...Array.from(this.customProfiles.entries()).map(([key, profile]) => ({ key, ...profile, source: 'custom' })) ]; // Apply filters let filtered = allProfiles; if (filters.category) { filtered = filtered.filter(profile => profile.category === filters.category); } if (filters.popularity) { filtered = filtered.filter(profile => profile.popularity === filters.popularity); } if (filters.language) { filtered = filtered.filter(profile => profile.variables['{{LANGUAGE_PRIMARY}}'] === filters.language ); } return filtered.sort((a, b) => { // Sort by popularity (high -> medium -> low), then name const popularityOrder = { high: 0, medium: 1, low: 2 }; const popularityDiff = (popularityOrder[a.popularity] || 3) - (popularityOrder[b.popularity] || 3); return popularityDiff !== 0 ? popularityDiff : a.name.localeCompare(b.name); }); } /** * Create a custom stack profile * @param {string} key - Profile identifier * @param {Object} profile - Profile configuration */ addCustomProfile(key, profile) { if (!this.config.enableCustomProfiles) { throw new Error('Custom profiles are disabled'); } // Validate profile structure this.validateProfileStructure(profile); this.customProfiles.set(key, { ...profile, source: 'custom', createdAt: new Date().toISOString() }); } /** * Update existing profile * @param {string} key - Profile key * @param {Object} updates - Profile updates */ updateProfile(key, updates) { if (this.customProfiles.has(key)) { const existing = this.customProfiles.get(key); this.customProfiles.set(key, { ...existing, ...updates, updatedAt: new Date().toISOString() }); } else if (this.profiles[key]) { // Create custom override of predefined profile this.addCustomProfile(`${key}-custom`, { ...this.profiles[key], ...updates }); } else { throw new Error(`Profile ${key} not found`); } } /** * Get profile recommendations for a tech stack * @param {Object} techStack - Technology stack * @returns {Array} Recommended profiles */ getRecommendations(techStack) { const recommendations = []; // Find exact matches const exactMatch = this.findMatchingProfile(techStack); if (exactMatch) { recommendations.push({ ...exactMatch, matchType: 'exact', confidence: 'high' }); } // Find partial matches const partialMatches = this.findPartialMatches(techStack); recommendations.push(...partialMatches); // Suggest popular alternatives if (recommendations.length === 0) { const popular = this.getAvailableProfiles({ popularity: 'high' }).slice(0, 3); recommendations.push(...popular.map(profile => ({ ...profile, matchType: 'suggested', confidence: 'low' }))); } return recommendations; } /** * Find partial matches for tech stack * @param {Object} techStack - Technology stack * @returns {Array} Partial matches */ findPartialMatches(techStack) { const matches = []; for (const [key, profile] of Object.entries(this.profiles)) { const score = this.calculateMatchScore(techStack, profile); if (score > 0.3 && score < 1.0) { // Partial match threshold matches.push({ key, ...profile, matchType: 'partial', confidence: score > 0.7 ? 'medium' : 'low', matchScore: score }); } } return matches.sort((a, b) => b.matchScore - a.matchScore); } /** * Calculate match score between tech stack and profile * @param {Object} techStack - Technology stack * @param {Object} profile - Stack profile * @returns {number} Match score (0-1) */ calculateMatchScore(techStack, profile) { let score = 0; let checks = 0; // Check language match const profileLanguage = profile.variables['{{LANGUAGE_PRIMARY}}']; if (profileLanguage && techStack.languages.includes(profileLanguage)) { score += 0.4; } checks++; // Check frontend framework match const profileFrontend = profile.variables['{{FRONTEND_FRAMEWORK}}']; if (profileFrontend && techStack.frameworks.frontend === profileFrontend) { score += 0.3; } checks++; // Check backend framework match const profileBackend = profile.variables['{{BACKEND_FRAMEWORK}}']; if (profileBackend && techStack.frameworks.backend === profileBackend) { score += 0.3; } checks++; return checks > 0 ? score / checks : 0; } /** * Generate variables for unknown/custom stack * @param {Object} techStack - Technology stack * @returns {Object} Generated variables */ generateCustomVariables(techStack) { const variables = { // Core tech stack '{{LANGUAGE_PRIMARY}}': techStack.languages[0] || 'JavaScript', '{{LANGUAGES_ALL}}': techStack.languages.join(', ') || 'JavaScript', '{{FRONTEND_FRAMEWORK}}': techStack.frameworks.frontend || 'React', '{{BACKEND_FRAMEWORK}}': techStack.frameworks.backend || 'Express', '{{DATABASE_TYPE}}': techStack.databases[0] || 'PostgreSQL', '{{TESTING_FRAMEWORK}}': techStack.frameworks.testing || 'Jest', // Defaults for unknown stack '{{PROJECT_TYPE}}': 'custom', '{{FRONTEND_DIR}}': 'src', '{{BACKEND_DIR}}': 'api', '{{FILE_EXTENSION}}': '.js', '{{DEPLOYMENT_TARGET}}': 'Custom', '{{CI_CD_PLATFORM}}': 'GitHub Actions', // Conservative quality standards '{{TEST_COVERAGE_MIN}}': '70', '{{CRITICAL_COVERAGE}}': '85', '{{COMPLEXITY_THRESHOLD}}': '12' }; return variables; } /** * Validate profile structure * @param {Object} profile - Profile to validate * @throws {Error} If profile is invalid */ validateProfileStructure(profile) { const required = ['name', 'description', 'variables']; for (const field of required) { if (!profile[field]) { throw new Error(`Profile missing required field: ${field}`); } } if (typeof profile.variables !== 'object') { throw new Error('Profile variables must be an object'); } if (profile.match && typeof profile.match !== 'function') { throw new Error('Profile match must be a function'); } } /** * Export profile for sharing * @param {string} key - Profile key * @returns {Object} Exportable profile */ exportProfile(key) { const profile = this.customProfiles.get(key) || this.profiles[key]; if (!profile) { throw new Error(`Profile ${key} not found`); } return { key, ...profile, exportedAt: new Date().toISOString() }; } /** * Import profile from external source * @param {Object} profileData - Profile data * @param {Object} options - Import options */ importProfile(profileData, options = {}) { const { key, ...profile } = profileData; if (!key) { throw new Error('Profile data missing key'); } this.validateProfileStructure(profile); if (options.allowOverwrite || !this.customProfiles.has(key)) { this.addCustomProfile(key, { ...profile, importedAt: new Date().toISOString() }); } else { throw new Error(`Profile ${key} already exists. Use allowOverwrite option.`); } } /** * Get profile statistics * @returns {Object} Usage statistics */ getStatistics() { const allProfiles = this.getAvailableProfiles(); return { totalProfiles: allProfiles.length, predefined: Object.keys(this.profiles).length, custom: this.customProfiles.size, categories: this.getCategoryStats(allProfiles), popularityDistribution: this.getPopularityStats(allProfiles), languageDistribution: this.getLanguageStats(allProfiles) }; } getCategoryStats(profiles) { const categories = {}; profiles.forEach(profile => { categories[profile.category] = (categories[profile.category] || 0) + 1; }); return categories; } getPopularityStats(profiles) { const popularity = {}; profiles.forEach(profile => { popularity[profile.popularity] = (popularity[profile.popularity] || 0) + 1; }); return popularity; } getLanguageStats(profiles) { const languages = {}; profiles.forEach(profile => { const lang = profile.variables['{{LANGUAGE_PRIMARY}}']; if (lang) { languages[lang] = (languages[lang] || 0) + 1; } }); return languages; } /** * Set the current active stack profile * @param {Object} profile - Stack profile object to set */ setProfile(profile) { if (profile && typeof profile === 'object') { this.currentProfile = profile; } } /** * Get the currently active stack profile * @returns {Object|null} Current stack profile */ getCurrentProfile() { return this.currentProfile || null; } } module.exports = { StackProfileManager };