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