/** * Daemon Core - Orchestrates all background watchers * * Combines: * 1. File Watcher (chokidar) - Monitors filesystem changes * 2. Guardian Rules - Validates files against rules on change * 3. Agent Bridge Listener - Receives tasks from Rigstate Cloud */ import chalk from 'chalk'; import * as fs from 'fs/promises'; import path from 'path'; import { EventEmitter } from 'events'; import { createFileWatcher } from './file-watcher.js'; import { createHeuristicEngine } from './heuristic-engine.js'; import { createInterventionProtocol } from './intervention-protocol.js'; import { createGuardianMonitor } from './guardian-monitor.js'; import { createBridgeListener } from './bridge-listener.js'; import { DaemonConfig, DaemonState } from './types.js'; import { trackSkillUsage } from './telemetry.js'; import { jitProvisionSkill } from '../utils/skills-provisioner.js'; import { syncProjectRules } from '../commands/sync-rules.js'; import { Logger } from '../utils/logger.js'; import { KnowledgeHarvester } from './harvester.js'; import { ViolationManager } from './violation-manager.js'; import { StatsReporter } from './stats-reporter.js'; import { PlanWatcher } from './plan-watcher.js'; export class GuardianDaemon extends EventEmitter { private config: DaemonConfig; private state: DaemonState; private fileWatcher: ReturnType | null = null; private guardianMonitor: ReturnType | null = null; private heuristicEngine: ReturnType | null = null; private interventionProtocol: ReturnType | null = null; private bridgeListener: ReturnType | null = null; private harvester: KnowledgeHarvester | null = null; private syncInterval: NodeJS.Timeout | null = null; private violationManager: ViolationManager; private statsReporter: StatsReporter; private planWatcher: PlanWatcher; constructor(config: DaemonConfig) { super(); this.config = config; this.state = { isRunning: false, startedAt: null, filesChecked: 0, violationsFound: 0, tasksProcessed: 0, lastActivity: null }; this.violationManager = new ViolationManager(this.config.watchPath, this); this.statsReporter = new StatsReporter({ projectId: this.config.projectId, apiUrl: this.config.apiUrl, apiKey: this.config.apiKey, watchPath: this.config.watchPath, verbose: this.config.verbose }); this.planWatcher = new PlanWatcher({ projectId: this.config.projectId, apiUrl: this.config.apiUrl, apiKey: this.config.apiKey, watchPath: this.config.watchPath }); } async start(): Promise { if (this.state.isRunning) return; this.printWelcome(); this.state.isRunning = true; this.state.startedAt = new Date().toISOString(); this.heuristicEngine = createHeuristicEngine(); this.interventionProtocol = createInterventionProtocol(); this.guardianMonitor = createGuardianMonitor(this.config.projectId, this.config.apiUrl, this.config.apiKey); this.harvester = new KnowledgeHarvester({ projectId: this.config.projectId, apiUrl: this.config.apiUrl, apiKey: this.config.apiKey, watchPath: this.config.watchPath }); await this.guardianMonitor.loadRules(); await this.runRuleSync(); await this.syncHeuristics(); await this.planWatcher.syncFromCloud(); if (this.config.checkOnChange) { this.setupFileWatcher(); await this.harvester.start(); this.syncInterval = setInterval(() => { this.runRuleSync().catch(e => Logger.error(`Auto-Sync failed: ${e.message}`)); }, 5 * 60 * 1000); } if (this.config.bridgeEnabled) await this.setupBridge().catch(() => { }); this.printActive(); this.emit('started', this.state); } private async runRuleSync() { try { await syncProjectRules(this.config.projectId, this.config.apiKey, this.config.apiUrl); } catch (error: any) { Logger.warn(`Rule sync hiccup: ${error.message}`); } } private printWelcome() { console.log(chalk.bold.blue('\nšŸ›”ļø Guardian Daemon Starting...')); console.log(chalk.dim(`Project: ${this.config.projectId} | Watch: ${this.config.watchPath}`)); console.log(chalk.dim('─'.repeat(50))); } private printActive() { console.log(chalk.dim('─'.repeat(50))); console.log(chalk.green.bold('āœ… Guardian Daemon is active. Watching for architectural drift.')); } private async syncHeuristics() { if (this.heuristicEngine) { await this.heuristicEngine.refreshRules(this.config.projectId, this.config.apiUrl, this.config.apiKey); } } private setupFileWatcher() { this.fileWatcher = createFileWatcher(this.config.watchPath); this.fileWatcher.on('change', (path) => this.handleFileChange(path)); this.fileWatcher.start(); Logger.info('Sovereign File Watcher Active'); } private async handleFileChange(filePath: string) { this.state.lastActivity = new Date().toISOString(); const lineCount = await this.getLineCount(filePath); await this.statsReporter.report(filePath, lineCount); await this.runPredictiveAnalysis(filePath, lineCount); if (path.basename(filePath).startsWith('IMPLEMENTATION_PLAN') && filePath.endsWith('.md')) { await this.planWatcher.checkForChanges(filePath); } await this.runIntegrityCheck(filePath); } private async getLineCount(filePath: string): Promise { try { const content = await fs.readFile(filePath, 'utf-8'); return content.split('\n').length; } catch { return 0; } } private async runPredictiveAnalysis(filePath: string, lineCount: number) { if (!this.heuristicEngine || !this.interventionProtocol || !this.guardianMonitor) return; const matches = await this.heuristicEngine.analyzeFile(filePath, { lineCount, rules: this.guardianMonitor.getRules() }); for (const match of matches) { Logger.info(`PREDICTIVE: ${match.skillId} (${match.reason})`); const decision = this.interventionProtocol.evaluateTrigger(match.skillId, match.confidence); this.interventionProtocol.enforce(decision); await jitProvisionSkill(match.skillId, this.config.apiUrl, this.config.apiKey, this.config.projectId, this.config.watchPath); await trackSkillUsage(this.config.apiUrl, this.config.apiKey, this.config.projectId, match.skillId); this.emit('skill:suggestion', match); } } private async runIntegrityCheck(filePath: string) { if (!this.guardianMonitor) return; if (this.interventionProtocol) this.interventionProtocol.clear(filePath); const result = await this.guardianMonitor.checkFile(filePath); this.state.filesChecked++; if (result.violations.length > 0) { this.violationManager.handleViolations(filePath, result.violations); this.state.violationsFound = this.violationManager.getViolationCount(); this.enforceIntervention(filePath, result.violations); } else { this.violationManager.clear(filePath); } } private enforceIntervention(filePath: string, violations: any[]) { if (!this.interventionProtocol) return; for (const v of violations) { const decision = this.interventionProtocol.evaluateViolation(v.message, v.severity as any); this.interventionProtocol.enforce(decision); this.interventionProtocol.registerViolation(filePath, decision); } } private async setupBridge() { this.bridgeListener = createBridgeListener(this.config.projectId, this.config.apiUrl, this.config.apiKey); this.bridgeListener.on('task', (task) => { this.state.lastActivity = new Date().toISOString(); this.state.tasksProcessed++; console.log(chalk.cyan(`\nšŸ“„ New task: ${task.id}`)); this.emit('task', task); }); await this.bridgeListener.connect(); console.log(chalk.green(' āœ“ Agent Bridge active')); } async stop(): Promise { if (!this.state.isRunning) return; if (this.fileWatcher) await this.fileWatcher.stop(); if (this.bridgeListener) await this.bridgeListener.disconnect(); if (this.harvester) await this.harvester.stop(); if (this.syncInterval) clearInterval(this.syncInterval); this.state.isRunning = false; this.emit('stopped', this.state); } getState(): DaemonState { return { ...this.state, violationsFound: this.violationManager.getViolationCount() }; } }