UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

833 lines (699 loc) 22.9 kB
/** * Ideation Engine * Story 11.1 - Enhanced Capabilities * * AI-powered analysis for codebase improvements. * Suggests optimizations for performance, security, code quality, and UX. */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // Import dependencies with fallbacks let GotchasMemory; try { GotchasMemory = require('../memory/gotchas-memory'); } catch { GotchasMemory = null; } class IdeationEngine { constructor(config = {}) { // Root path this.rootPath = config.rootPath || process.cwd(); // Analysis areas this.areas = config.areas || ['performance', 'security', 'codeQuality', 'ux', 'architecture']; // Dependencies this.gotchasMemory = config.gotchasMemory || (GotchasMemory ? new GotchasMemory() : null); // Output paths this.outputDir = config.outputDir || path.join(this.rootPath, '.aios', 'ideation'); // Analyzers this.analyzers = { performance: new PerformanceAnalyzer(this.rootPath), security: new SecurityAnalyzer(this.rootPath), codeQuality: new CodeQualityAnalyzer(this.rootPath), ux: new UXAnalyzer(this.rootPath), architecture: new ArchitectureAnalyzer(this.rootPath), }; } /** * Run ideation analysis * @param {Object} options - Analysis options * @returns {Promise<Object>} - Analysis results */ async ideate(options = {}) { const focus = options.focus || this.areas; const focusAreas = Array.isArray(focus) ? focus : [focus]; const suggestions = []; const startTime = Date.now(); for (const area of focusAreas) { const analyzer = this.analyzers[area]; if (!analyzer) continue; try { const findings = await analyzer.analyze(); suggestions.push( ...findings.map((f) => ({ ...f, area, priority: this.calculatePriority(f), })), ); } catch (error) { console.warn(`Analysis failed for ${area}:`, error.message); } } // Filter out known gotchas let filtered = suggestions; if (this.gotchasMemory) { try { const knownIssues = await this.gotchasMemory.getAll(); filtered = suggestions.filter((s) => !this.isKnownGotcha(s, knownIssues)); } catch { // Ignore } } // Sort by priority const sorted = filtered.sort((a, b) => b.priority - a.priority); // Categorize const result = { generatedAt: new Date().toISOString(), projectId: path.basename(this.rootPath), duration: Date.now() - startTime, summary: { totalSuggestions: sorted.length, quickWins: sorted.filter((s) => s.category === 'quick-win').length, highImpact: sorted.filter((s) => s.impact >= 0.8).length, byArea: this.countByArea(sorted), }, quickWins: sorted.filter((s) => s.effort === 'low' && s.impact >= 0.7), highImpact: sorted.filter((s) => s.impact >= 0.8), allSuggestions: sorted, }; // Save if requested if (options.save !== false) { await this.save(result); } return result; } /** * Calculate priority for a finding * @param {Object} finding - Finding * @returns {number} - Priority score */ calculatePriority(finding) { const impact = finding.impact || 0.5; const effortMultiplier = { low: 1.5, medium: 1.0, high: 0.6 }[finding.effort] || 1.0; // Quick wins get highest priority if (finding.effort === 'low' && impact >= 0.7) { finding.category = 'quick-win'; return impact * 1.5; } return impact * effortMultiplier; } /** * Check if suggestion matches known gotcha * @param {Object} suggestion - Suggestion * @param {Array} knownIssues - Known gotchas * @returns {boolean} - True if matches */ isKnownGotcha(suggestion, knownIssues) { if (!knownIssues) return false; const suggestionText = `${suggestion.title} ${suggestion.description}`.toLowerCase(); return knownIssues.some((gotcha) => { const gotchaText = `${gotcha.pattern || ''} ${gotcha.description || ''}`.toLowerCase(); // Check for significant overlap const words = gotchaText.split(/\s+/).filter((w) => w.length > 4); const matches = words.filter((w) => suggestionText.includes(w)); return matches.length >= 3; }); } /** * Count suggestions by area * @param {Array} suggestions - Suggestions * @returns {Object} - Counts by area */ countByArea(suggestions) { const counts = {}; for (const s of suggestions) { counts[s.area] = (counts[s.area] || 0) + 1; } return counts; } /** * Save results to files * @param {Object} result - Analysis result */ async save(result) { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } // Save JSON const jsonPath = path.join(this.outputDir, 'suggestions.json'); fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2)); // Save markdown const mdPath = path.join(this.outputDir, 'suggestions.md'); fs.writeFileSync(mdPath, this.formatMarkdown(result)); } /** * Format results as markdown * @param {Object} result - Analysis result * @returns {string} - Markdown content */ formatMarkdown(result) { let md = '# Ideation Report\n\n'; md += `> **Generated:** ${result.generatedAt}\n`; md += `> **Project:** ${result.projectId}\n`; md += `> **Duration:** ${result.duration}ms\n\n`; md += '## Summary\n\n'; md += '| Metric | Value |\n'; md += '|--------|-------|\n'; md += `| Total Suggestions | ${result.summary.totalSuggestions} |\n`; md += `| Quick Wins | ${result.summary.quickWins} |\n`; md += `| High Impact | ${result.summary.highImpact} |\n\n`; if (result.quickWins.length > 0) { md += '## 🎯 Quick Wins\n\n'; md += '*Low effort, high impact suggestions*\n\n'; for (const s of result.quickWins.slice(0, 5)) { md += this.formatSuggestion(s); } } if (result.highImpact.length > 0) { md += '## 🚀 High Impact\n\n'; md += '*Suggestions with significant potential impact*\n\n'; for (const s of result.highImpact.slice(0, 5)) { md += this.formatSuggestion(s); } } // By area for (const area of this.areas) { const areaSuggestions = result.allSuggestions.filter((s) => s.area === area); if (areaSuggestions.length === 0) continue; const areaTitle = area.charAt(0).toUpperCase() + area.slice(1); md += `## ${areaTitle}\n\n`; for (const s of areaSuggestions.slice(0, 3)) { md += this.formatSuggestion(s); } } md += '---\n*Generated by AIOS Ideation Engine*\n'; return md; } /** * Format a single suggestion * @param {Object} s - Suggestion * @returns {string} - Formatted markdown */ formatSuggestion(s) { let md = `### ${s.title}\n\n`; md += `- **Impact:** ${Math.round(s.impact * 100)}%\n`; md += `- **Effort:** ${s.effort}\n`; md += `- **Area:** ${s.area}\n`; if (s.location) { md += `- **Location:** \`${s.location.file}\``; if (s.location.lines) md += ` (lines ${s.location.lines})`; md += '\n'; } md += `\n${s.description}\n`; if (s.suggestedFix) { md += `\n**Suggested Fix:** ${s.suggestedFix}\n`; } md += '\n'; return md; } } /** * Performance Analyzer */ class PerformanceAnalyzer { constructor(rootPath) { this.rootPath = rootPath; } async analyze() { const findings = []; // Check for common performance issues findings.push(...this.checkSyncOperations()); findings.push(...this.checkLargeLoops()); findings.push(...this.checkUnoptimizedImports()); findings.push(...this.checkMissingCaching()); return findings; } checkSyncOperations() { const findings = []; try { // Search for sync file operations const result = execSync( `grep -rn "readFileSync\\|writeFileSync\\|existsSync" --include="*.js" --include="*.ts" ${this.rootPath}/src ${this.rootPath}/.aios-core 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 5 * 1024 * 1024, }, ); const lines = result.split('\n').filter((l) => l.trim() && !l.includes('node_modules')); if (lines.length > 10) { findings.push({ id: `perf-sync-${Date.now()}`, title: 'Excessive synchronous file operations', description: `Found ${lines.length} synchronous file operations that could block the event loop.`, impact: 0.7, effort: 'medium', location: { file: 'multiple files' }, suggestedFix: 'Convert to async/await using fs.promises', }); } } catch { // Ignore } return findings; } checkLargeLoops() { const findings = []; try { // Look for nested loops const result = execSync( `grep -rn "for.*for\\|forEach.*forEach" --include="*.js" --include="*.ts" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 5 * 1024 * 1024, }, ); const lines = result.split('\n').filter((l) => l.trim() && !l.includes('node_modules')); if (lines.length > 5) { findings.push({ id: `perf-loops-${Date.now()}`, title: 'Potential nested loop performance issue', description: `Found ${lines.length} nested loops that may impact performance.`, impact: 0.6, effort: 'medium', suggestedFix: 'Consider using Map/Set for lookups or optimizing algorithms', }); } } catch { // Ignore } return findings; } checkUnoptimizedImports() { const findings = []; try { // Check for large library imports without tree shaking const result = execSync( `grep -rn "import.*from 'lodash'" --include="*.js" --include="*.ts" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 5 * 1024 * 1024, }, ); const lines = result.split('\n').filter((l) => l.trim() && !l.includes('node_modules')); if (lines.length > 0) { findings.push({ id: `perf-imports-${Date.now()}`, title: 'Unoptimized lodash imports', description: 'Importing entire lodash library instead of specific functions.', impact: 0.5, effort: 'low', suggestedFix: "Use import { func } from 'lodash/func' for tree shaking", }); } } catch { // Ignore } return findings; } checkMissingCaching() { const findings = []; try { // Look for repeated file reads without caching const result = execSync( `grep -rn "JSON.parse.*readFile\\|readFile.*JSON.parse" --include="*.js" --include="*.ts" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 5 * 1024 * 1024, }, ); const lines = result .split('\n') .filter((l) => l.trim() && !l.includes('node_modules') && !l.includes('Cache')); if (lines.length > 5) { findings.push({ id: `perf-cache-${Date.now()}`, title: 'Consider adding caching for file reads', description: `Found ${lines.length} JSON file reads that could benefit from caching.`, impact: 0.6, effort: 'low', suggestedFix: 'Add TTL-based caching for frequently read config files', }); } } catch { // Ignore } return findings; } } /** * Security Analyzer */ class SecurityAnalyzer { constructor(rootPath) { this.rootPath = rootPath; } async analyze() { const findings = []; findings.push(...this.checkHardcodedSecrets()); findings.push(...this.checkInsecurePatterns()); findings.push(...this.checkDependencyVulnerabilities()); return findings; } checkHardcodedSecrets() { const findings = []; try { // Look for potential hardcoded secrets const result = execSync( `grep -rn "password.*=\\s*['\\"]\\|api_key.*=\\s*['\\"]\\|secret.*=\\s*['\\"]" --include="*.js" --include="*.ts" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 5 * 1024 * 1024, }, ); const lines = result .split('\n') .filter( (l) => l.trim() && !l.includes('node_modules') && !l.includes('.env') && !l.includes('test'), ); if (lines.length > 0) { findings.push({ id: `sec-secrets-${Date.now()}`, title: 'Potential hardcoded secrets', description: `Found ${lines.length} potential hardcoded secrets in source code.`, impact: 0.95, effort: 'low', suggestedFix: 'Move secrets to environment variables or secrets manager', }); } } catch { // Ignore } return findings; } checkInsecurePatterns() { const findings = []; try { // Check for eval usage const evalResult = execSync( `grep -rn "\\beval\\s*(" --include="*.js" --include="*.ts" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', }, ); const evalLines = evalResult .split('\n') .filter((l) => l.trim() && !l.includes('node_modules')); if (evalLines.length > 0) { findings.push({ id: `sec-eval-${Date.now()}`, title: 'Dangerous eval() usage', description: `Found ${evalLines.length} uses of eval() which can lead to code injection.`, impact: 0.9, effort: 'medium', suggestedFix: 'Replace eval with safer alternatives like JSON.parse or Function constructor', }); } } catch { // Ignore } return findings; } checkDependencyVulnerabilities() { const findings = []; try { const auditResult = execSync('npm audit --json 2>/dev/null || true', { cwd: this.rootPath, encoding: 'utf8', }); const audit = JSON.parse(auditResult); const vulns = audit.metadata?.vulnerabilities || {}; const critical = vulns.critical || 0; const high = vulns.high || 0; if (critical > 0 || high > 0) { findings.push({ id: `sec-deps-${Date.now()}`, title: 'Vulnerable dependencies detected', description: `Found ${critical} critical and ${high} high severity vulnerabilities in dependencies.`, impact: critical > 0 ? 0.95 : 0.8, effort: 'low', suggestedFix: 'Run npm audit fix or update vulnerable packages', }); } } catch { // Ignore audit failures } return findings; } } /** * Code Quality Analyzer */ class CodeQualityAnalyzer { constructor(rootPath) { this.rootPath = rootPath; } async analyze() { const findings = []; findings.push(...this.checkLongFunctions()); findings.push(...this.checkDuplication()); findings.push(...this.checkConsoleStatements()); return findings; } checkLongFunctions() { const findings = []; // This is a simplified check - in production would use AST analysis try { const result = execSync( `wc -l ${this.rootPath}/src/**/*.js ${this.rootPath}/.aios-core/**/*.js 2>/dev/null | sort -rn | head -10 || true`, { encoding: 'utf8', }, ); const lines = result.split('\n').filter((l) => l.trim() && !l.includes('total')); const longFiles = lines.filter((l) => { const count = parseInt(l.trim().split(/\s+/)[0]); return count > 500; }); if (longFiles.length > 0) { findings.push({ id: `quality-long-${Date.now()}`, title: 'Large source files detected', description: `Found ${longFiles.length} files with over 500 lines that may benefit from splitting.`, impact: 0.5, effort: 'high', suggestedFix: 'Consider splitting large files into smaller, focused modules', }); } } catch { // Ignore } return findings; } checkDuplication() { const findings = []; // Simplified duplication check try { const result = execSync( `grep -rh "function\\|const.*=.*=>" --include="*.js" ${this.rootPath}/src 2>/dev/null | sort | uniq -d | head -5 || true`, { encoding: 'utf8', }, ); if (result.trim().length > 0) { findings.push({ id: `quality-dup-${Date.now()}`, title: 'Potential code duplication', description: 'Some function patterns appear multiple times in the codebase.', impact: 0.4, effort: 'medium', suggestedFix: 'Extract common patterns into shared utility functions', }); } } catch { // Ignore } return findings; } checkConsoleStatements() { const findings = []; try { const result = execSync( `grep -rn "console\\.log\\|console\\.error" --include="*.js" --include="*.ts" ${this.rootPath}/src 2>/dev/null || true`, { encoding: 'utf8', }, ); const lines = result.split('\n').filter((l) => l.trim() && !l.includes('node_modules')); if (lines.length > 20) { findings.push({ id: `quality-console-${Date.now()}`, title: 'Excessive console statements', description: `Found ${lines.length} console statements that should use proper logging.`, impact: 0.3, effort: 'low', suggestedFix: 'Replace console.log with a proper logging library', }); } } catch { // Ignore } return findings; } } /** * UX Analyzer */ class UXAnalyzer { constructor(rootPath) { this.rootPath = rootPath; } async analyze() { const findings = []; findings.push(...this.checkAccessibility()); findings.push(...this.checkLoadingStates()); return findings; } checkAccessibility() { const findings = []; try { // Check for missing aria labels in React components const result = execSync( `grep -rn "<button\\|<a\\s" --include="*.tsx" --include="*.jsx" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', }, ); const lines = result .split('\n') .filter((l) => l.trim() && !l.includes('aria-') && !l.includes('node_modules')); if (lines.length > 10) { findings.push({ id: `ux-a11y-${Date.now()}`, title: 'Missing accessibility attributes', description: `Found ${lines.length} interactive elements potentially missing aria labels.`, impact: 0.6, effort: 'low', suggestedFix: 'Add aria-label or aria-labelledby to interactive elements', }); } } catch { // Ignore } return findings; } checkLoadingStates() { const findings = []; try { // Check for fetch/async without loading states const result = execSync( `grep -rn "fetch\\|axios\\|useQuery" --include="*.tsx" --include="*.jsx" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', }, ); const asyncCalls = result .split('\n') .filter((l) => l.trim() && !l.includes('node_modules')).length; const loadingResult = execSync( `grep -rn "isLoading\\|loading\\|Spinner\\|Skeleton" --include="*.tsx" --include="*.jsx" ${this.rootPath} 2>/dev/null || true`, { encoding: 'utf8', }, ); const loadingStates = loadingResult .split('\n') .filter((l) => l.trim() && !l.includes('node_modules')).length; if (asyncCalls > loadingStates * 2) { findings.push({ id: `ux-loading-${Date.now()}`, title: 'Consider adding loading states', description: 'Many async operations may be missing loading indicators.', impact: 0.5, effort: 'low', suggestedFix: 'Add loading spinners or skeletons for async operations', }); } } catch { // Ignore } return findings; } } /** * Architecture Analyzer */ class ArchitectureAnalyzer { constructor(rootPath) { this.rootPath = rootPath; } async analyze() { const findings = []; findings.push(...this.checkCircularDependencies()); findings.push(...this.checkLayerViolations()); return findings; } checkCircularDependencies() { const findings = []; try { // Use madge if available const result = execSync( `npx madge --circular --warning ${this.rootPath}/src 2>/dev/null || true`, { encoding: 'utf8', timeout: 30000, }, ); if (result.includes('Circular')) { const circularCount = (result.match(/→/g) || []).length; findings.push({ id: `arch-circular-${Date.now()}`, title: 'Circular dependencies detected', description: `Found ${circularCount} circular dependency chains that complicate the codebase.`, impact: 0.7, effort: 'high', suggestedFix: 'Refactor to break circular dependencies using dependency injection or interfaces', }); } } catch { // Ignore if madge not available } return findings; } checkLayerViolations() { const findings = []; try { // Check if UI imports from infrastructure const result = execSync( `grep -rn "from.*infrastructure\\|from.*database" --include="*.tsx" --include="*.jsx" ${this.rootPath}/src/components 2>/dev/null || true`, { encoding: 'utf8', }, ); const violations = result.split('\n').filter((l) => l.trim()); if (violations.length > 0) { findings.push({ id: `arch-layers-${Date.now()}`, title: 'Architecture layer violation', description: 'UI components are importing directly from infrastructure layer.', impact: 0.6, effort: 'medium', suggestedFix: 'Use services or hooks as intermediary layer', }); } } catch { // Ignore } return findings; } } module.exports = IdeationEngine; module.exports.IdeationEngine = IdeationEngine; module.exports.PerformanceAnalyzer = PerformanceAnalyzer; module.exports.SecurityAnalyzer = SecurityAnalyzer; module.exports.CodeQualityAnalyzer = CodeQualityAnalyzer; module.exports.UXAnalyzer = UXAnalyzer; module.exports.ArchitectureAnalyzer = ArchitectureAnalyzer;