@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),
627 lines • 20.5 kB
JavaScript
/**
* VERSATIL SDLC Framework - Stack-Aware Orchestrator
* Optimized for: Cursor / Claude / Supabase / n8n / Vercel / OPERA
*/
import { EventEmitter } from 'events';
import { VERSATILLogger } from '../utils/logger.js';
import { AgenticRAGOrchestrator } from './agentic-rag-orchestrator.js';
import * as path from 'path';
import * as fs from 'fs/promises';
export class StackAwareOrchestrator extends EventEmitter {
constructor(paths) {
super();
// Stack-specific agents
this.stackAgents = new Map();
// MCP clients for each stack component
this.mcpClients = {
claude: null,
shadcn: null,
playwright: null,
chrome: null,
supabase: null,
n8n: null,
vercel: null
};
this.logger = new VERSATILLogger('StackAwareOrchestrator');
this.paths = paths;
this.ragOrchestrator = new AgenticRAGOrchestrator(paths);
// Default stack configuration
this.stackConfig = {
cursor: true,
claude: true,
supabase: true,
n8n: true,
vercel: true,
shadcn: true,
playwright: true,
chromeDevtools: true
};
}
async initialize() {
// Initialize RAG system
await this.ragOrchestrator.initialize();
// Load stack-specific agents
await this.loadStackAgents();
// Initialize MCP connections
await this.initializeMCPClients();
// Load agents.md if exists
await this.parseAgentsMD();
this.logger.info('Stack-aware orchestrator initialized', {
stack: this.stackConfig,
agents: this.stackAgents.size
});
}
/**
* Load stack-specific agent definitions
*/
async loadStackAgents() {
// Claude Coder Agent - integrates with claude-code-mcp
this.registerAgent({
id: 'claude-coder',
name: 'Claude Coder',
type: 'development',
mcp: 'claude-code-mcp',
capabilities: [
'code-generation',
'code-review',
'refactoring',
'bug-fixing',
'documentation'
],
stackIntegration: ['cursor', 'claude']
});
// Supabase Architect
this.registerAgent({
id: 'supabase-architect',
name: 'Supabase Database Architect',
type: 'database',
capabilities: [
'schema-design',
'rls-policies',
'edge-functions',
'vector-search',
'realtime-subscriptions'
],
stackIntegration: ['supabase']
});
// UI/UX Specialist with shadcn
this.registerAgent({
id: 'ui-specialist',
name: 'UI/UX Specialist',
type: 'design',
mcp: 'shadcn-mcp',
capabilities: [
'component-design',
'theme-creation',
'accessibility',
'responsive-design',
'animations'
],
stackIntegration: ['shadcn', 'cursor']
});
// Playwright Test Engineer
this.registerAgent({
id: 'playwright-tester',
name: 'Playwright Test Engineer',
type: 'testing',
mcp: 'playwright-mcp',
capabilities: [
'e2e-testing',
'visual-regression',
'cross-browser-testing',
'accessibility-testing',
'performance-testing'
],
stackIntegration: ['playwright']
});
// Chrome DevTools Analyst
this.registerAgent({
id: 'chrome-analyst',
name: 'Chrome DevTools Performance Analyst',
type: 'performance',
mcp: 'chrome-mcp',
capabilities: [
'performance-profiling',
'network-analysis',
'memory-profiling',
'lighthouse-audits',
'coverage-reports'
],
stackIntegration: ['chromeDevtools']
});
// n8n Workflow Automator
this.registerAgent({
id: 'n8n-automator',
name: 'n8n Workflow Automator',
type: 'automation',
capabilities: [
'workflow-creation',
'ci-cd-automation',
'webhook-integration',
'data-transformation',
'scheduled-tasks'
],
stackIntegration: ['n8n']
});
// Vercel Deployment Specialist
this.registerAgent({
id: 'vercel-deployer',
name: 'Vercel Deployment Specialist',
type: 'deployment',
capabilities: [
'preview-deployments',
'production-deployments',
'edge-functions',
'analytics-setup',
'domain-configuration'
],
stackIntegration: ['vercel']
});
// Introspective Meta-Agent
this.registerAgent({
id: 'introspective-agent',
name: 'Introspective Meta-Agent',
type: 'meta',
capabilities: [
'full-context-awareness',
'agent-monitoring',
'pattern-detection',
'self-improvement',
'optimization-suggestions'
],
stackIntegration: ['all']
});
}
/**
* Register an agent
*/
registerAgent(agent) {
this.stackAgents.set(agent.id, agent);
this.logger.debug('Registered stack agent', { agent: agent.name });
}
/**
* Initialize MCP clients for stack components
*/
async initializeMCPClients() {
// Claude Code MCP (steipete/claude-code-mcp)
if (this.stackConfig.claude) {
try {
const ClaudeCodeMCP = await import('claude-code-mcp');
this.mcpClients.claude = new ClaudeCodeMCP.Client({
workspace: this.paths.project.root,
contextProvider: async () => this.getFullContext()
});
this.logger.info('Connected to Claude Code MCP');
}
catch (error) {
this.logger.warn('Claude Code MCP not available', { error });
}
}
// Initialize other MCP clients similarly...
// Each would connect to their respective MCP servers
}
/**
* Parse agents.md file format
*/
async parseAgentsMD() {
const agentsPath = path.join(this.paths.project.root, 'agents.md');
try {
const agentsMD = await fs.readFile(agentsPath, 'utf-8');
const parsedAgents = this.parseAgentDefinitions(agentsMD);
// Register custom agents from agents.md
parsedAgents.forEach(agent => {
this.registerAgent({
id: agent.id,
name: agent.name,
type: agent.type || 'custom',
capabilities: agent.capabilities || [],
stackIntegration: agent.stack || []
});
});
this.logger.info('Loaded custom agents from agents.md', {
count: parsedAgents.length
});
}
catch (error) {
this.logger.debug('No agents.md file found');
}
}
/**
* Parse agent definitions from markdown
*/
parseAgentDefinitions(markdown) {
const agents = [];
const lines = markdown.split('\n');
let currentAgent = null;
for (const line of lines) {
// Parse agent headers (## AgentName)
if (line.startsWith('## ')) {
if (currentAgent)
agents.push(currentAgent);
currentAgent = {
name: line.substring(3).trim(),
id: line.substring(3).trim().toLowerCase().replace(/\s+/g, '-'),
capabilities: []
};
}
// Parse capabilities
else if (currentAgent && line.includes('Capabilities:')) {
// Parse following bullet points
}
// Parse other metadata
else if (currentAgent && line.includes('Stack:')) {
const stack = line.split(':')[1].trim().split(',').map(s => s.trim());
currentAgent.stack = stack;
}
}
if (currentAgent)
agents.push(currentAgent);
return agents;
}
/**
* Get full context for agents
*/
async getFullContext() {
return {
repository: await this.getRepositoryContext(),
stack: await this.getStackContext(),
development: await this.getDevelopmentContext(),
agents: await this.getAgentContext()
};
}
/**
* Get repository context
*/
async getRepositoryContext() {
const gitInfo = await this.getGitInfo();
const fileStructure = await this.getFileStructure();
const dependencies = await this.getDependencies();
return {
root: this.paths.project.root,
git: gitInfo,
structure: fileStructure,
dependencies: dependencies,
config: await this.getProjectConfig()
};
}
/**
* Get stack-specific context
*/
async getStackContext() {
const context = {};
// Supabase context
if (this.stackConfig.supabase) {
context.supabase = await this.getSupabaseContext();
}
// Vercel context
if (this.stackConfig.vercel) {
context.vercel = await this.getVercelContext();
}
// n8n context
if (this.stackConfig.n8n) {
context.n8n = await this.getN8NContext();
}
return context;
}
/**
* Get Supabase-specific context
*/
async getSupabaseContext() {
try {
// Read supabase config
const supabaseConfigPath = path.join(this.paths.project.root, 'supabase', 'config.toml');
const hasSupabase = await this.fileExists(supabaseConfigPath);
if (!hasSupabase)
return null;
return {
config: await fs.readFile(supabaseConfigPath, 'utf-8'),
migrations: await this.getSupabaseMigrations(),
functions: await this.getSupabaseEdgeFunctions(),
schema: await this.getSupabaseSchema()
};
}
catch (error) {
return null;
}
}
/**
* Get Vercel-specific context
*/
async getVercelContext() {
try {
const vercelJsonPath = path.join(this.paths.project.root, 'vercel.json');
const hasVercel = await this.fileExists(vercelJsonPath);
if (!hasVercel)
return null;
return {
config: await fs.readFile(vercelJsonPath, 'utf-8'),
env: process.env.VERCEL_ENV,
projectId: process.env.VERCEL_PROJECT_ID
};
}
catch (error) {
return null;
}
}
/**
* Get n8n workflow context
*/
async getN8NContext() {
try {
const n8nWorkflowsPath = path.join(this.paths.project.root, '.n8n', 'workflows');
const hasN8n = await this.fileExists(n8nWorkflowsPath);
if (!hasN8n)
return null;
return {
workflows: await this.getN8NWorkflows(),
credentials: 'configured' // Don't expose actual credentials
};
}
catch (error) {
return null;
}
}
/**
* Get development context
*/
async getDevelopmentContext() {
return {
plans: await this.getDevelopmentPlans(),
issues: await this.getGitHubIssues(),
prs: await this.getGitHubPRs(),
todos: await this.extractTODOs()
};
}
/**
* Get agent context
*/
async getAgentContext() {
const agents = Array.from(this.stackAgents.values());
return {
available: agents,
active: agents.filter(a => this.isAgentActive(a)),
memories: await this.ragOrchestrator.getAgentMemories()
};
}
/**
* Check if agent is active based on stack config
*/
isAgentActive(agent) {
if (agent.stackIntegration.includes('all'))
return true;
return agent.stackIntegration.some(stack => {
return this.stackConfig[stack];
});
}
/**
* Helper methods for context gathering
*/
async getGitInfo() {
try {
const { exec } = require('child_process').promises;
const [branch, status, lastCommit] = await Promise.all([
exec('git branch --show-current', { cwd: this.paths.project.root }),
exec('git status --porcelain', { cwd: this.paths.project.root }),
exec('git log -1 --oneline', { cwd: this.paths.project.root })
]);
return {
branch: branch.stdout.trim(),
hasChanges: status.stdout.trim().length > 0,
lastCommit: lastCommit.stdout.trim()
};
}
catch (error) {
return null;
}
}
async getFileStructure() {
// Get project file structure (simplified)
const structure = {};
const scanDir = async (dir, relativePath = '') => {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
// Skip node_modules, .git, etc.
if (this.shouldSkipDirectory(entry.name))
continue;
const fullPath = path.join(dir, entry.name);
const relPath = path.join(relativePath, entry.name);
if (entry.isDirectory()) {
structure[relPath] = 'directory';
await scanDir(fullPath, relPath);
}
else {
structure[relPath] = 'file';
}
}
};
await scanDir(this.paths.project.root);
return structure;
}
shouldSkipDirectory(name) {
const skipDirs = ['node_modules', '.git', '.next', 'dist', 'build', '.vercel'];
return skipDirs.includes(name);
}
async getDependencies() {
try {
const packageJsonPath = path.join(this.paths.project.root, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
return {
dependencies: Object.keys(packageJson.dependencies || {}),
devDependencies: Object.keys(packageJson.devDependencies || {}),
scripts: Object.keys(packageJson.scripts || {})
};
}
catch (error) {
return null;
}
}
async getProjectConfig() {
const configs = {};
// Check for various config files
const configFiles = [
'tsconfig.json',
'next.config.js',
'tailwind.config.js',
'postcss.config.js'
];
for (const configFile of configFiles) {
const configPath = path.join(this.paths.project.root, configFile);
if (await this.fileExists(configPath)) {
configs[configFile] = true;
}
}
return configs;
}
async fileExists(path) {
try {
await fs.access(path);
return true;
}
catch {
return false;
}
}
async getSupabaseMigrations() {
try {
const migrationsPath = path.join(this.paths.project.root, 'supabase', 'migrations');
const files = await fs.readdir(migrationsPath);
return files.filter(f => f.endsWith('.sql'));
}
catch {
return [];
}
}
async getSupabaseEdgeFunctions() {
try {
const functionsPath = path.join(this.paths.project.root, 'supabase', 'functions');
return await fs.readdir(functionsPath);
}
catch {
return [];
}
}
async getSupabaseSchema() {
// Would parse schema from migrations or connect to Supabase
return null;
}
async getN8NWorkflows() {
// Would load n8n workflow definitions
return [];
}
async getDevelopmentPlans() {
// Load from plan orchestrator
return [];
}
async getGitHubIssues() {
// Would use GitHub API
return [];
}
async getGitHubPRs() {
// Would use GitHub API
return [];
}
async extractTODOs() {
// Would scan codebase for TODO comments
return [];
}
/**
* Get project context for other orchestrators
*/
async getProjectContext() {
return await this.getRepositoryContext();
}
/**
* Get stack status
*/
async getStackStatus() {
return {
config: this.stackConfig,
connections: {
claude: this.mcpClients.claude !== null,
supabase: await this.testSupabaseConnection(),
vercel: await this.testVercelConnection(),
n8n: await this.testN8NConnection()
},
agents: {
total: this.stackAgents.size,
active: Array.from(this.stackAgents.values()).filter(a => this.isAgentActive(a)).length
}
};
}
async testSupabaseConnection() {
// Would test actual connection
return this.stackConfig.supabase;
}
async testVercelConnection() {
// Would test actual connection
return this.stackConfig.vercel;
}
async testN8NConnection() {
// Would test actual connection
return this.stackConfig.n8n;
}
/**
* Set stack configuration
*/
setStackConfig(config) {
this.stackConfig = { ...this.stackConfig, ...config };
this.logger.info('Updated stack configuration', { config: this.stackConfig });
}
/**
* Execute with specific agent
*/
async executeWithAgent(agentId, task) {
const agent = this.stackAgents.get(agentId);
if (!agent) {
throw new Error(`Agent not found: ${agentId}`);
}
// Get full context for agent
const context = await this.ragOrchestrator.getContextForAgent(agentId, task);
// Execute based on agent type
if (agent.mcp) {
return await this.executeViaMCP(agent, task, context);
}
else {
return await this.executeDirectly(agent, task, context);
}
}
/**
* Execute agent via MCP
*/
async executeViaMCP(agent, task, context) {
const mcpClient = this.mcpClients[agent.mcp?.split('-')[0]];
if (!mcpClient) {
throw new Error(`MCP client not available for ${agent.name}`);
}
return await mcpClient.execute(task, context);
}
/**
* Execute agent directly
*/
async executeDirectly(agent, task, context) {
// Direct execution logic
this.logger.info(`Executing task with ${agent.name}`, { task });
// Store execution in RAG
await this.ragOrchestrator.storeAgentExecution(agent.id, task, context, 'completed');
return {
agent: agent.name,
task: task,
status: 'completed',
timestamp: Date.now()
};
}
/**
* Cleanup
*/
async shutdown() {
// Disconnect MCP clients
for (const client of Object.values(this.mcpClients)) {
if (client && client.disconnect) {
await client.disconnect();
}
}
// Shutdown RAG
await this.ragOrchestrator.shutdown();
}
}
//# sourceMappingURL=stack-aware-orchestrator.js.map