UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

330 lines (289 loc) 9.57 kB
const fs = require('fs').promises; const path = require('path'); const { execSync } = require('child_process'); class ContextBuilder { constructor(projectState) { this.projectState = projectState; } async buildContext(taskSpec) { const context = { // Current project state currentBranch: await this.getCurrentBranch(), workingDirectory: await this.getWorkingDirectoryStatus(), // Code context relatedFiles: await this.findRelatedFiles(taskSpec), existingComponents: await this.findExistingComponents(taskSpec), databaseContext: await this.getDatabaseContext(taskSpec), // Recent development history recentCommits: await this.getRecentCommits(5), recentChanges: await this.getRecentFileChanges(), // Trip With Us specific context activeFeatures: this.getActiveFeatures(), userFlows: this.getRelevantUserFlows(taskSpec), architecturalPatterns: this.getArchitecturalPatterns(taskSpec), // Quality context currentTestCoverage: this.projectState.code_metrics?.test_coverage || 0, knownIssues: this.getKnownIssues(), performanceMetrics: this.getPerformanceMetrics() }; return this.enrichContext(context, taskSpec); } async getCurrentBranch() { try { return execSync('git branch --show-current', { encoding: 'utf8' }).trim(); } catch (error) { return 'unknown'; } } async getWorkingDirectoryStatus() { try { const status = execSync('git status --porcelain', { encoding: 'utf8' }); return { clean: status.trim() === '', modifiedFiles: status.split('\n').filter(line => line.trim()).length }; } catch (error) { return { clean: true, modifiedFiles: 0 }; } } async findRelatedFiles(taskSpec) { const files = []; const searchTerms = this.extractSearchTerms(taskSpec); // Search for related React components if (taskSpec.type?.includes('component') || taskSpec.description?.includes('component')) { files.push(...await this.findComponentFiles(searchTerms)); } // Search for related API services if (taskSpec.type?.includes('api') || taskSpec.description?.includes('api')) { files.push(...await this.findApiFiles(searchTerms)); } // Search for related database schemas if (taskSpec.type?.includes('database') || taskSpec.description?.includes('database')) { files.push(...await this.findDatabaseFiles(searchTerms)); } // Search for related tests files.push(...await this.findTestFiles(searchTerms)); return this.prioritizeFiles(files, taskSpec); } extractSearchTerms(taskSpec) { const description = taskSpec.description || ''; const words = description.toLowerCase().split(/\s+/); // Filter out common words const stopWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for']; return words.filter(word => word.length > 3 && !stopWords.includes(word) ); } async findComponentFiles(searchTerms) { const componentsDir = path.join(process.cwd(), 'src/components'); const files = []; try { // In a real implementation, this would recursively search for files // For now, return common component patterns return [ 'src/components/trips/TripCard.tsx', 'src/components/ui/card.tsx', 'src/components/Layout.tsx' ]; } catch (error) { return []; } } async findApiFiles(searchTerms) { // Return common API file patterns return [ 'src/lib/supabaseTripApi.ts', 'src/lib/supabaseClient.ts', 'src/hooks/useTrip.ts' ]; } async findDatabaseFiles(searchTerms) { // Return database-related files return [ 'supabase/migrations/', 'src/lib/database.types.ts', 'src/types/supabase.ts' ]; } async findTestFiles(searchTerms) { // Return test file patterns return [ 'src/__tests__/', 'e2e/', 'tests/' ]; } prioritizeFiles(files, taskSpec) { // Sort files by relevance to task // In a real implementation, this would use more sophisticated ranking return [...new Set(files)].slice(0, 10); } async findExistingComponents(taskSpec) { // Return list of existing components that might be related return { trips: [ 'TripCard', 'TripForm', 'TripList', 'TripDetails' ], ui: [ 'Button', 'Card', 'Dialog', 'Form' ], shared: [ 'Layout', 'Header', 'Navigation' ] }; } async getDatabaseContext(taskSpec) { return { tables: [ 'trips', 'trip_participants', 'trip_activities', 'trip_accommodations', 'trip_transportation', 'trip_meals', 'profiles' ], recentMigrations: [], rlsPolicies: { enabled: true, pattern: 'user-based access control' } }; } async getRecentCommits(count) { try { const commits = execSync(`git log --oneline -n ${count}`, { encoding: 'utf8' }); return commits.trim().split('\n'); } catch (error) { return []; } } async getRecentFileChanges() { try { const changes = execSync('git diff --name-only HEAD~1', { encoding: 'utf8' }); return changes.trim().split('\n').filter(file => file); } catch (error) { return []; } } getActiveFeatures() { return this.projectState.active_features || []; } getRelevantUserFlows(taskSpec) { const flows = { trip_creation: [ "User authentication", "Trip details form", "Invite collaborators", "Initial itinerary setup" ], trip_collaboration: [ "Real-time editing", "Conflict resolution", "Presence indicators", "Comment system" ], map_interaction: [ "Location search", "Point of interest marking", "Route planning", "Geocoding integration" ], ai_assistance: [ "Trip suggestions", "Itinerary optimization", "Activity recommendations", "Smart scheduling" ] }; // Determine which flows are relevant to current task const relevantFlows = []; const description = (taskSpec.description || '').toLowerCase(); Object.keys(flows).forEach(flowKey => { if (this.isFlowRelevant(flowKey, description)) { relevantFlows.push({ name: flowKey, steps: flows[flowKey] }); } }); return relevantFlows; } isFlowRelevant(flowKey, description) { const flowKeywords = { trip_creation: ['create', 'new', 'trip', 'form'], trip_collaboration: ['collaborate', 'real-time', 'edit', 'share'], map_interaction: ['map', 'location', 'route', 'geocoding'], ai_assistance: ['ai', 'suggest', 'optimize', 'smart'] }; const keywords = flowKeywords[flowKey] || []; return keywords.some(keyword => description.includes(keyword)); } getArchitecturalPatterns(taskSpec) { return { component_patterns: { functional_components: "Use React functional components with hooks", typescript_interfaces: "Define props with TypeScript interfaces", shadcn_ui: "Use shadcn/ui components for consistent design", error_boundaries: "Implement error boundaries for fault tolerance" }, state_patterns: { tanstack_query: "Use TanStack Query for server state management", react_context: "Use React Context for global client state", optimistic_updates: "Implement optimistic updates for better UX", form_handling: "Use React Hook Form with Zod validation" }, supabase_patterns: { rls_policies: "Implement Row Level Security for data access", realtime_subscriptions: "Use Supabase realtime for live updates", type_safety: "Generate and use TypeScript types from schema", error_handling: "Comprehensive error handling for database operations" }, testing_patterns: { unit_testing: "Test components and hooks in isolation", integration_testing: "Test feature workflows end-to-end", e2e_testing: "Use Playwright for critical user journeys", test_fixtures: "Use consistent test data fixtures" } }; } getKnownIssues() { return this.projectState.known_issues || []; } getPerformanceMetrics() { return { buildTime: this.projectState.code_metrics?.build_time || 'unknown', bundleSize: this.projectState.code_metrics?.bundle_size || 'unknown', lighthouseScore: this.projectState.code_metrics?.lighthouse_score || 'unknown' }; } enrichContext(context, taskSpec) { // Add task-specific enrichments if (taskSpec.type === 'component_creation') { context.componentGuidelines = { naming: 'PascalCase for components', structure: 'Functional components with TypeScript', styling: 'Tailwind CSS with shadcn/ui', testing: 'Unit tests with React Testing Library' }; } if (taskSpec.type === 'api_integration') { context.apiGuidelines = { pattern: 'Service layer pattern', stateManagement: 'TanStack Query', errorHandling: 'Try-catch with proper error types', security: 'Environment variables for secrets' }; } return context; } } module.exports = ContextBuilder;