@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),
339 lines (288 loc) • 9.22 kB
JavaScript
/**
* VERSATIL Framework - Background Monitoring Service
*
* Lightweight daemon that runs continuously in the background
* Collects framework metrics and writes to shared status file
* No UI - just data collection for dashboard consumption
*
* Usage:
* npm run dashboard:background # Start background service
* node scripts/background-monitor.cjs # Direct start
* node scripts/background-monitor.cjs --interval=5000 # Custom interval
*
* To stop:
* npm run dashboard:stop
* Kill the process (PID stored in /tmp/versatil-monitor.pid)
*/
const fs = require('fs');
const path = require('path');
// Configuration
const PROJECT_ROOT = process.cwd();
const SESSION_ID = process.env.CLAUDE_SESSION_ID || 'default';
const STATUS_FILE = `/tmp/versatil-sync-status-${SESSION_ID}.json`;
const PID_FILE = `/tmp/versatil-monitor-${SESSION_ID}.pid`;
const LOG_FILE = path.join(PROJECT_ROOT, '.versatil', 'logs', 'background-monitor.log');
// Parse command line arguments
const args = process.argv.slice(2);
const INTERVAL = parseInt(args.find(arg => arg.startsWith('--interval='))?.split('=')[1] || '2000');
const DAEMON_MODE = !args.includes('--foreground');
// Ensure log directory exists
const logDir = path.dirname(LOG_FILE);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// Logger
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
// Console output in foreground mode
if (!DAEMON_MODE) {
console.log(logMessage.trim());
}
// Always write to log file
try {
fs.appendFileSync(LOG_FILE, logMessage);
} catch (error) {
// Ignore file write errors
}
}
// Check if already running
if (fs.existsSync(PID_FILE)) {
const existingPid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
try {
// Check if process is still running
process.kill(existingPid, 0);
log(`Background monitor already running (PID: ${existingPid})`);
console.error(`Error: Background monitor already running (PID: ${existingPid})`);
console.error(`To stop it, run: npm run dashboard:stop`);
process.exit(1);
} catch (error) {
// Process not running, remove stale PID file
fs.unlinkSync(PID_FILE);
}
}
// Write PID file
fs.writeFileSync(PID_FILE, process.pid.toString());
log(`Background monitor started (PID: ${process.pid})`);
log(`Interval: ${INTERVAL}ms`);
log(`Status file: ${STATUS_FILE}`);
log(`Project: ${PROJECT_ROOT}`);
// ============================================================================
// DATA COLLECTION
// ============================================================================
/**
* Check orchestrator status
*/
function checkOrchestrators() {
const orchestrators = [
{ name: 'ProactiveOrchestrator', file: 'src/orchestration/proactive-agent-orchestrator.ts' },
{ name: 'AgenticRAGOrchestrator', file: 'src/orchestration/agentic-rag-orchestrator.ts' },
{ name: 'PlanFirstOpera', file: 'src/orchestration/plan-first-opera.ts' },
{ name: 'StackAware', file: 'src/orchestration/stack-aware-orchestrator.ts' },
{ name: 'GitHubSync', file: 'src/orchestration/github-sync-orchestrator.ts' },
{ name: 'ParallelTaskManager', file: 'src/orchestration/parallel-task-manager.ts' },
{ name: 'EfficiencyMonitor', file: 'src/monitoring/framework-efficiency-monitor.ts' },
{ name: 'IntrospectiveAgent', file: 'src/agents/introspective-agent.ts' }
];
let active = 0;
const statuses = {};
for (const orch of orchestrators) {
const fullPath = path.join(PROJECT_ROOT, orch.file);
const exists = fs.existsSync(fullPath);
if (exists) {
active++;
statuses[orch.name] = 'active';
} else {
statuses[orch.name] = 'inactive';
}
}
return {
total: orchestrators.length,
active,
inactive: orchestrators.length - active,
statuses
};
}
/**
* Calculate sync score
*/
function calculateSyncScore(orchestrators) {
const baseScore = (orchestrators.active / orchestrators.total) * 100;
// Additional factors could be added here
// - Event system health
// - Memory consistency
// - GitHub sync status
// For now, simplified to orchestrator availability
return Math.round(baseScore);
}
/**
* Get system metrics
*/
function getSystemMetrics() {
const used = process.memoryUsage();
return {
memory_mb: Math.round(used.heapUsed / 1024 / 1024),
memory_total_mb: Math.round(used.heapTotal / 1024 / 1024),
cpu_user: process.cpuUsage().user,
cpu_system: process.cpuUsage().system,
uptime_seconds: Math.round(process.uptime())
};
}
/**
* Check RAG system status
*/
function checkRAGStatus() {
const ragFiles = [
'src/orchestration/agentic-rag-orchestrator.ts',
'src/agents/rag-enabled-agent.ts'
];
let active = 0;
for (const file of ragFiles) {
if (fs.existsSync(path.join(PROJECT_ROOT, file))) {
active++;
}
}
return {
total: ragFiles.length,
active,
operational: active === ragFiles.length
};
}
/**
* Check Opera MCP status
*/
function checkOperaMCPStatus() {
const operaFiles = [
'src/orchestration/plan-first-opera.ts',
'src/opera/opera-orchestrator.ts',
'src/opera/opera-mcp-server.ts',
'init-opera-mcp.mjs'
];
let active = 0;
for (const file of operaFiles) {
if (fs.existsSync(path.join(PROJECT_ROOT, file))) {
active++;
}
}
return {
total: operaFiles.length,
active,
operational: active === operaFiles.length
};
}
/**
* Collect all framework status data
*/
function collectFrameworkStatus() {
try {
const orchestrators = checkOrchestrators();
const syncScore = calculateSyncScore(orchestrators);
const metrics = getSystemMetrics();
const ragStatus = checkRAGStatus();
const operaStatus = checkOperaMCPStatus();
const status = {
timestamp: Date.now(),
iso_timestamp: new Date().toISOString(),
synchronized: syncScore >= 90,
score: syncScore,
orchestrators_active: orchestrators.active,
orchestrators_total: orchestrators.total,
orchestrators_inactive: orchestrators.inactive,
orchestrator_statuses: orchestrators.statuses,
rag_system: {
active: ragStatus.active,
total: ragStatus.total,
operational: ragStatus.operational
},
opera_mcp: {
active: operaStatus.active,
total: operaStatus.total,
operational: operaStatus.operational
},
current_operation: process.env.VERSATIL_OPERATION || 'Framework monitoring',
system_metrics: metrics,
monitor_pid: process.pid,
session_id: SESSION_ID
};
return status;
} catch (error) {
log(`Error collecting status: ${error.message}`);
return null;
}
}
/**
* Write status to shared file
*/
function writeStatus(status) {
if (!status) {
return;
}
try {
fs.writeFileSync(STATUS_FILE, JSON.stringify(status, null, 2));
} catch (error) {
log(`Error writing status file: ${error.message}`);
}
}
// ============================================================================
// MONITORING LOOP
// ============================================================================
let updateCount = 0;
function monitoringLoop() {
updateCount++;
const status = collectFrameworkStatus();
writeStatus(status);
if (updateCount % 30 === 0) {
// Log every 30 updates (every minute with 2s interval)
log(`Health check #${updateCount}: Sync ${status.score}%, ${status.orchestrators_active}/${status.orchestrators_total} orchestrators active`);
}
}
// Initial status collection
log('Performing initial status collection...');
monitoringLoop();
// Start monitoring interval
const intervalId = setInterval(monitoringLoop, INTERVAL);
// ============================================================================
// SIGNAL HANDLERS
// ============================================================================
function cleanup() {
log('Shutting down background monitor...');
clearInterval(intervalId);
// Remove PID file
try {
if (fs.existsSync(PID_FILE)) {
fs.unlinkSync(PID_FILE);
}
} catch (error) {
// Ignore
}
log(`Background monitor stopped (${updateCount} updates performed)`);
process.exit(0);
}
process.on('SIGTERM', cleanup);
process.on('SIGINT', cleanup);
// Graceful error handling
process.on('uncaughtException', (error) => {
log(`Uncaught exception: ${error.message}`);
log(error.stack);
cleanup();
});
process.on('unhandledRejection', (reason, promise) => {
log(`Unhandled rejection: ${reason}`);
cleanup();
});
// ============================================================================
// DAEMON MODE
// ============================================================================
if (DAEMON_MODE) {
// Detach from parent process
process.stdin.pause();
log('Running in daemon mode');
log(`To view logs: tail -f ${LOG_FILE}`);
log(`To stop: npm run dashboard:stop or kill ${process.pid}`);
} else {
log('Running in foreground mode (--foreground)');
log('Press Ctrl+C to stop');
}
// Keep process alive
process.stdin.resume();