@versatil/sdlc-framework
Version:
π AI-Native SDLC framework with 11-MCP ecosystem, RAG memory, OPERA orchestration, and 6 specialized agents achieving ZERO CONTEXT LOSS. Features complete CI/CD pipeline with 7 GitHub workflows (MCP testing, security scanning, performance benchmarking),
346 lines β’ 11.8 kB
JavaScript
/**
* Proactive Agent Orchestrator
*
* Automatically activates and coordinates OPERA agents based on file patterns,
* code context, and real-time development activity.
*
* @module ProactiveAgentOrchestrator
* @version 2.0.0
*/
import { EventEmitter } from 'events';
import { watch } from 'fs';
import { join, extname, basename } from 'path';
import { EnhancedMaria } from '../agents/enhanced-maria.js';
import { EnhancedJames } from '../agents/enhanced-james.js';
import { EnhancedMarcus } from '../agents/enhanced-marcus.js';
export class ProactiveAgentOrchestrator extends EventEmitter {
constructor(config) {
super();
this.config = {
enabled: true,
autoActivation: true,
backgroundMonitoring: true,
inlineSuggestions: true,
statuslineUpdates: true,
slashCommandsFallback: true,
...config
};
this.triggers = new Map();
this.activeAgents = new Map();
this.watchers = new Map();
this.agents = new Map();
this.initializeAgents();
this.initializeTriggers();
}
/**
* Initialize OPERA agent instances
*/
initializeAgents() {
this.agents.set('maria-qa', new EnhancedMaria());
this.agents.set('james-frontend', new EnhancedJames());
this.agents.set('marcus-backend', new EnhancedMarcus());
// Add other agents as needed
}
/**
* Initialize agent activation triggers from .cursor/settings.json
*/
initializeTriggers() {
// Maria-QA triggers
this.triggers.set('maria-qa', {
agentId: 'maria-qa',
filePatterns: ['*.test.*', '**/__tests__/**', '**/test/**', '*.spec.*'],
codePatterns: ['describe(', 'it(', 'test(', 'expect(', 'jest.', 'vitest.'],
keywords: ['test', 'spec', 'coverage', 'quality'],
autoRunOnSave: true,
backgroundAnalysis: true,
proactiveActions: [
'test_coverage_analysis',
'missing_test_detection',
'assertion_validation',
'quality_gate_enforcement'
]
});
// James-Frontend triggers
this.triggers.set('james-frontend', {
agentId: 'james-frontend',
filePatterns: ['*.tsx', '*.jsx', '*.vue', '*.svelte', '*.css', '*.scss'],
codePatterns: ['useState', 'useEffect', 'component', 'props', 'className'],
keywords: ['component', 'react', 'vue', 'ui', 'frontend'],
autoRunOnSave: true,
backgroundAnalysis: true,
proactiveActions: [
'accessibility_check_wcag',
'component_structure_validation',
'responsive_design_verification',
'performance_optimization_suggestions'
]
});
// Marcus-Backend triggers
this.triggers.set('marcus-backend', {
agentId: 'marcus-backend',
filePatterns: ['*.api.*', '**/routes/**', '**/controllers/**', '**/server/**'],
codePatterns: ['router.', 'app.', 'express.', 'fastify.', 'async function'],
keywords: ['api', 'server', 'database', 'auth', 'security'],
autoRunOnSave: true,
backgroundAnalysis: true,
proactiveActions: [
'security_pattern_validation_owasp',
'response_time_check_200ms',
'stress_test_generation',
'database_query_optimization'
]
});
}
/**
* Start watching file system for changes
*/
startMonitoring(projectPath) {
if (!this.config.backgroundMonitoring) {
console.log('Background monitoring disabled');
return;
}
console.log(`π€ VERSATIL: Starting proactive agent monitoring for ${projectPath}`);
const watcher = watch(projectPath, { recursive: true }, (eventType, filename) => {
if (!filename)
return;
// Ignore node_modules, dist, etc.
if (this.shouldIgnoreFile(filename))
return;
// Handle file change event
this.handleFileChange(eventType, join(projectPath, filename));
});
this.watchers.set(projectPath, watcher);
this.emit('monitoring-started', { projectPath });
}
/**
* Stop monitoring file system
*/
stopMonitoring(projectPath) {
if (projectPath) {
const watcher = this.watchers.get(projectPath);
if (watcher) {
watcher.close();
this.watchers.delete(projectPath);
}
}
else {
// Stop all watchers
this.watchers.forEach(watcher => watcher.close());
this.watchers.clear();
}
this.emit('monitoring-stopped', { projectPath });
}
/**
* Handle file change event and determine if agent activation is needed
*/
async handleFileChange(eventType, filePath) {
if (eventType !== 'change')
return;
console.log(`π File changed: ${filePath}`);
// Determine which agent(s) should be activated
const matchingAgents = this.findMatchingAgents(filePath);
if (matchingAgents.length === 0) {
console.log(`No matching agents for ${filePath}`);
return;
}
// Activate matching agents (parallel if Rule 1 enabled)
await this.activateAgents(matchingAgents, filePath);
}
/**
* Find agents that match the file pattern
*/
findMatchingAgents(filePath) {
const matchingAgents = [];
const fileName = basename(filePath);
const ext = extname(filePath);
for (const [agentId, trigger] of this.triggers.entries()) {
// Check file patterns
const fileMatch = trigger.filePatterns.some(pattern => {
// Simple pattern matching (can be enhanced with glob library)
return pattern.includes('*')
? this.matchGlobPattern(pattern, fileName)
: filePath.includes(pattern);
});
if (fileMatch) {
matchingAgents.push(agentId);
}
}
return matchingAgents;
}
/**
* Simple glob pattern matching (can be enhanced with micromatch library)
*/
matchGlobPattern(pattern, fileName) {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(fileName);
}
/**
* Activate one or more agents for a file
*/
async activateAgents(agentIds, filePath) {
console.log(`π€ Activating agents: ${agentIds.join(', ')} for ${filePath}`);
// Create activation contexts
const activationPromises = agentIds.map(agentId => {
return this.activateAgent(agentId, filePath);
});
// Execute in parallel (Rule 1: Parallel Task Execution)
try {
const results = await Promise.all(activationPromises);
// Emit results for statusline updates
this.emit('agents-completed', {
agentIds,
filePath,
results,
timestamp: Date.now()
});
console.log(`β
All agents completed for ${filePath}`);
}
catch (error) {
console.error(`β Agent activation failed:`, error);
this.emit('agents-failed', { agentIds, filePath, error });
}
}
/**
* Activate a single agent
*/
async activateAgent(agentId, filePath) {
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent not found: ${agentId}`);
}
// Read file content (in real implementation, use fs.readFile)
const content = ''; // Placeholder - implement actual file read
const context = {
filePath,
content,
language: this.detectLanguage(filePath),
framework: 'unknown', // Detect from content
userIntent: 'file_edit',
timestamp: Date.now(),
metadata: {
proactiveMode: true,
backgroundAnalysis: true
}
};
// Track active agent
const activeAgent = {
agentId,
agent,
context,
startTime: Date.now(),
progress: 0,
status: 'running'
};
this.activeAgents.set(agentId, activeAgent);
// Emit activation event for statusline
this.emit('agent-activated', {
agentId,
filePath,
timestamp: Date.now()
});
try {
// Execute agent analysis
const response = await agent.activate(context);
// Update status
activeAgent.status = 'completed';
activeAgent.progress = 100;
return response;
}
catch (error) {
activeAgent.status = 'failed';
throw error;
}
finally {
this.activeAgents.delete(agentId);
}
}
/**
* Get status of all active agents (for statusline)
*/
getActiveAgentsStatus() {
return new Map(this.activeAgents);
}
/**
* Manually activate an agent (fallback for slash commands)
*/
async manualActivation(agentId, filePath) {
console.log(`π§ Manual activation requested: ${agentId}`);
return this.activateAgent(agentId, filePath);
}
/**
* Disable proactive agents (fallback to manual mode)
*/
disableProactiveMode() {
this.config.autoActivation = false;
this.stopMonitoring();
console.log('βΈοΈ Proactive agents disabled. Use slash commands: /maria, /james, /marcus');
}
/**
* Enable proactive agents
*/
enableProactiveMode() {
this.config.autoActivation = true;
console.log('βΆοΈ Proactive agents enabled');
}
/**
* Check if file should be ignored
*/
shouldIgnoreFile(filePath) {
const ignorePatterns = [
'node_modules',
'dist',
'coverage',
'.git',
'.versatil/cache',
'.rebranding-backup'
];
return ignorePatterns.some(pattern => filePath.includes(pattern));
}
/**
* Detect language from file extension
*/
detectLanguage(filePath) {
const ext = extname(filePath);
const languageMap = {
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.py': 'python',
'.vue': 'vue',
'.svelte': 'svelte',
'.css': 'css',
'.scss': 'scss'
};
return languageMap[ext] || 'unknown';
}
/**
* Cleanup resources
*/
destroy() {
this.stopMonitoring();
this.removeAllListeners();
this.activeAgents.clear();
this.agents.clear();
this.triggers.clear();
}
}
// Singleton instance for global access
let orchestratorInstance = null;
export function getProactiveOrchestrator(config) {
if (!orchestratorInstance) {
orchestratorInstance = new ProactiveAgentOrchestrator(config);
}
return orchestratorInstance;
}
export function destroyProactiveOrchestrator() {
if (orchestratorInstance) {
orchestratorInstance.destroy();
orchestratorInstance = null;
}
}
//# sourceMappingURL=proactive-agent-orchestrator.js.map