UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

497 lines (442 loc) 14.6 kB
import { ProcessDefinition, TriggerSuggestion, ProcessTrigger, TriggerPattern, TriggerTemplate, PersonaType, Activity, TriggerConflict } from './types.js'; import { ProcessStore } from './process-store.js'; export class TriggerSuggestionEngine { private store: ProcessStore; private patterns: Map<PersonaType, TriggerPattern[]> = new Map(); constructor(store: ProcessStore) { this.store = store; this.initializePatterns(); } async suggestTriggers(process: ProcessDefinition): Promise<TriggerSuggestion[]> { const suggestions: TriggerSuggestion[] = []; // Analyze activities to understand the process const activityAnalysis = this.analyzeActivities(process.activities); // Get persona-specific patterns const personaPatterns = process.persona ? this.patterns.get(process.persona) || [] : []; // Generate primary suggestions based on activity patterns const primarySuggestion = await this.generatePrimarySuggestion( process, activityAnalysis, personaPatterns ); if (primarySuggestion) { suggestions.push(primarySuggestion); } // Generate alternative suggestions const alternatives = await this.generateAlternatives( process, activityAnalysis, primarySuggestion ); suggestions.push(...alternatives); // Check for conflicts with existing processes for (const suggestion of suggestions) { suggestion.conflicts = await this.detectConflicts(suggestion.trigger); } return suggestions.sort((a, b) => b.confidence - a.confidence); } private analyzeActivities(activities: Activity[]): ActivityAnalysis { const analysis: ActivityAnalysis = { types: new Set(), hasHumanInteraction: false, hasExternalCalls: false, estimatedDuration: 0, isReportGeneration: false, isDataProcessing: false, isReview: false, isContinuous: false }; for (const activity of activities) { analysis.types.add(activity.type); if (activity.type === 'human') { analysis.hasHumanInteraction = true; } if (activity.type === 'external') { analysis.hasExternalCalls = true; } // Detect patterns from activity names const nameLower = activity.name.toLowerCase(); if (nameLower.includes('report') || nameLower.includes('summary')) { analysis.isReportGeneration = true; } if (nameLower.includes('process') || nameLower.includes('analyze') || nameLower.includes('extract') || nameLower.includes('transform') || nameLower.includes('load') || nameLower.includes('etl')) { analysis.isDataProcessing = true; } if (nameLower.includes('review') || nameLower.includes('check')) { analysis.isReview = true; } if (nameLower.includes('monitor') || nameLower.includes('watch')) { analysis.isContinuous = true; } } return analysis; } private async generatePrimarySuggestion( process: ProcessDefinition, analysis: ActivityAnalysis, personaPatterns: TriggerPattern[] ): Promise<TriggerSuggestion | null> { let trigger: ProcessTrigger | null = null; let confidence = 0; let reasoning = ''; // Check persona patterns first if (personaPatterns.length > 0 && personaPatterns[0].suggestedTriggers.length > 0) { const personaTrigger = personaPatterns[0].suggestedTriggers[0]; trigger = this.createTriggerFromTemplate(personaTrigger); confidence = 0.85; reasoning = personaTrigger.applicableWhen || 'Based on persona-specific patterns'; } // Check for continuous monitoring pattern else if (analysis.isContinuous) { trigger = this.createScheduleTrigger( 'Continuous Monitoring', '*/15 * * * *', // Every 15 minutes 'UTC' ); confidence = 0.9; reasoning = 'Continuous monitoring processes should run frequently to catch issues quickly'; } // Check for report generation pattern else if (analysis.isReportGeneration) { const schedule = this.determineReportSchedule(process.name); trigger = this.createScheduleTrigger( 'Report Generation Schedule', schedule.cron, schedule.timezone ); confidence = 0.85; reasoning = `Report generation typically follows a ${schedule.description} schedule`; } // Check for review/approval processes else if (analysis.isReview && analysis.hasHumanInteraction) { trigger = this.createScheduleTrigger( 'Review Cycle', '0 9 * * 1-5', // Weekdays at 9 AM 'UTC' ); confidence = 0.8; reasoning = 'Review processes work best during business hours when team members are available'; } // Check persona-specific patterns else if (personaPatterns.length > 0) { const bestPattern = this.findBestPattern(personaPatterns, analysis); if (bestPattern) { trigger = this.createTriggerFromTemplate(bestPattern.suggestedTriggers[0]); confidence = 0.75; reasoning = `Common pattern for ${process.persona} persona: ${bestPattern.suggestedTriggers[0].applicableWhen}`; } } if (!trigger) { // Default to manual trigger trigger = this.createManualTrigger(); confidence = 0.5; reasoning = 'No clear pattern detected, manual trigger provides maximum flexibility'; } return { trigger: { ...trigger, aiSuggested: true, reasoning }, confidence, reasoning, alternatives: [] }; } private async generateAlternatives( process: ProcessDefinition, analysis: ActivityAnalysis, primarySuggestion: TriggerSuggestion | null ): Promise<TriggerSuggestion[]> { const alternatives: TriggerSuggestion[] = []; // Add persona-specific alternatives if (process.persona) { const personaPatterns = this.patterns.get(process.persona) || []; for (const pattern of personaPatterns) { for (let i = 1; i < pattern.suggestedTriggers.length; i++) { const template = pattern.suggestedTriggers[i]; if (!primarySuggestion || template.type !== primarySuggestion.trigger.type) { const trigger = this.createTriggerFromTemplate(template); alternatives.push({ trigger: { ...trigger, aiSuggested: true }, confidence: 0.75, reasoning: template.applicableWhen || 'Based on persona-specific patterns', alternatives: [] }); } } } } // Event-based alternative for processes that might react to changes if (analysis.hasExternalCalls || analysis.isDataProcessing) { const eventTrigger = this.createEventTrigger( 'Data Change Event', 'data.updated', 'system' ); alternatives.push({ trigger: { ...eventTrigger, aiSuggested: true }, confidence: 0.7, reasoning: 'Trigger immediately when relevant data changes for real-time processing', alternatives: [] }); } // Scheduled batch alternative if (!primarySuggestion || primarySuggestion.trigger.type !== 'schedule') { const batchSchedule = this.determineBatchSchedule(analysis); const scheduleTrigger = this.createScheduleTrigger( 'Batch Processing', batchSchedule.cron, batchSchedule.timezone ); alternatives.push({ trigger: { ...scheduleTrigger, aiSuggested: true }, confidence: 0.65, reasoning: `Batch processing ${batchSchedule.description} for efficiency`, alternatives: [] }); } // Manual with reminder if (analysis.hasHumanInteraction) { const manualTrigger = this.createManualTrigger('Manual with Reminder'); alternatives.push({ trigger: { ...manualTrigger, aiSuggested: true }, confidence: 0.6, reasoning: 'Human-centric processes benefit from manual triggers with email reminders', alternatives: [] }); } return alternatives; } private async detectConflicts(trigger: ProcessTrigger): Promise<TriggerConflict[]> { const conflicts: TriggerConflict[] = []; if (trigger.type === 'schedule' && trigger.config.cron) { // Get all processes with schedule triggers const processes = await this.store.getAllProcesses(); for (const process of processes) { for (const existingTrigger of process.triggers) { if ( existingTrigger.type === 'schedule' && existingTrigger.config.cron && this.hasScheduleOverlap(trigger.config.cron, existingTrigger.config.cron) ) { conflicts.push({ processId: process.id, processName: process.name, triggerName: existingTrigger.name, conflictType: 'time', description: `Both processes scheduled at similar times`, suggestion: 'Consider offsetting schedules by 30 minutes' }); } } } } return conflicts; } private hasScheduleOverlap(cron1: string, cron2: string): boolean { // Simplified overlap detection // In a real implementation, use a cron parser return cron1 === cron2; } private determineReportSchedule(processName: string): ScheduleInfo { const nameLower = processName.toLowerCase(); if (nameLower.includes('daily')) { return { cron: '0 8 * * *', timezone: 'UTC', description: 'daily' }; } else if (nameLower.includes('weekly')) { return { cron: '0 8 * * 1', timezone: 'UTC', description: 'weekly (Monday mornings)' }; } else if (nameLower.includes('monthly')) { return { cron: '0 8 1 * *', timezone: 'UTC', description: 'monthly (first day of month)' }; } // Default to weekly return { cron: '0 8 * * 5', timezone: 'UTC', description: 'weekly (Friday mornings)' }; } private determineBatchSchedule(analysis: ActivityAnalysis): ScheduleInfo { if (analysis.estimatedDuration > 3600000) { // > 1 hour return { cron: '0 2 * * *', timezone: 'UTC', description: 'nightly at 2 AM when system load is low' }; } else if (analysis.hasExternalCalls) { return { cron: '0 */4 * * *', timezone: 'UTC', description: 'every 4 hours to balance freshness and API limits' }; } return { cron: '0 */2 * * *', timezone: 'UTC', description: 'every 2 hours for regular updates' }; } private findBestPattern( patterns: TriggerPattern[], analysis: ActivityAnalysis ): TriggerPattern | null { // Find pattern with most matching activity types let bestPattern: TriggerPattern | null = null; let bestScore = 0; for (const pattern of patterns) { let score = 0; for (const activityType of pattern.activityTypes) { if (analysis.types.has(activityType as any)) { score++; } } if (score > bestScore) { bestScore = score; bestPattern = pattern; } } return bestPattern; } private createScheduleTrigger( name: string, cron: string, timezone: string ): ProcessTrigger { return { id: this.generateId(), type: 'schedule', name, enabled: true, config: { cron, timezone } }; } private createEventTrigger( name: string, event: string, source: string ): ProcessTrigger { return { id: this.generateId(), type: 'event', name, enabled: true, config: { event, source } }; } private createManualTrigger(name: string = 'Manual Trigger'): ProcessTrigger { return { id: this.generateId(), type: 'manual', name, enabled: true, config: {} }; } private createTriggerFromTemplate(template: TriggerTemplate): ProcessTrigger { return { id: this.generateId(), type: template.type, name: template.name, enabled: true, config: template.defaultConfig as any }; } private generateId(): string { return `trigger-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; } private initializePatterns(): void { // Software Engineer patterns this.patterns.set('software-engineer', [ { persona: 'software-engineer', activityTypes: ['tool', 'agent'], suggestedTriggers: [ { type: 'schedule', name: 'Daily Development Routine', defaultConfig: { cron: '0 9 * * 1-5', timezone: 'local' }, applicableWhen: 'Daily development tasks like checking CI/CD, reviewing PRs' }, { type: 'event', name: 'On Code Push', defaultConfig: { event: 'git.push', source: 'repository' }, applicableWhen: 'Automated checks and workflows triggered by code changes' } ] } ]); // CTO patterns this.patterns.set('cto', [ { persona: 'cto', activityTypes: ['tool', 'human'], suggestedTriggers: [ { type: 'schedule', name: 'Weekly Technical Review', defaultConfig: { cron: '0 10 * * 1', timezone: 'local' }, applicableWhen: 'Weekly review of technical metrics and team performance' } ] } ]); // CEO patterns this.patterns.set('ceo', [ { persona: 'ceo', activityTypes: ['tool', 'human', 'external'], suggestedTriggers: [ { type: 'schedule', name: 'Monthly Business Review', defaultConfig: { cron: '0 9 1 * *', timezone: 'local' }, applicableWhen: 'Monthly business metrics and strategic planning' } ] } ]); // Add more persona patterns as needed } } interface ActivityAnalysis { types: Set<string>; hasHumanInteraction: boolean; hasExternalCalls: boolean; estimatedDuration: number; isReportGeneration: boolean; isDataProcessing: boolean; isReview: boolean; isContinuous: boolean; } interface ScheduleInfo { cron: string; timezone: string; description: string; }