UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

612 lines (533 loc) • 16.7 kB
/** * QE Hive Bridge * * Anti-corruption layer for V3 Hive-Mind coordination integration. * Handles Queen coordinator registration, worker spawning, consensus, * and Byzantine fault-tolerant task execution. * * Integrates with V3 Hive-Mind services: * - Queen role registration for QE coordination * - Worker capability announcement * - Consensus for agent allocation * - Shared memory for coordination state * * Based on: * - ADR-030: Agentic-QE Plugin Integration * - ADR-007: Hive-Mind Consensus * * @module v3/plugins/agentic-qe/bridges/QEHiveBridge */ import type { IQEHiveBridge, HiveRole, QESwarmTask, QESwarmResult, AgentTaskResult, ConsensusResult, QELogger, } from '../interfaces.js'; // V3 Hive-Mind types (would be imported from @claude-flow/coordination in production) interface IHiveMindService { join(config: HiveJoinConfig): Promise<void>; leave(agentId: string): Promise<void>; consensus(proposal: ConsensusProposal): Promise<HiveConsensusResult>; broadcast(message: HiveBroadcastMessage): Promise<void>; memory(operation: HiveMemoryOperation): Promise<HiveMemoryResult>; getStatus(): Promise<HiveStatus>; getWorkers(): Promise<HiveWorker[]>; } interface HiveJoinConfig { agentId: string; role: HiveRole; capabilities: string[]; metadata?: Record<string, unknown>; } interface ConsensusProposal { action: 'propose' | 'vote' | 'status'; type?: string; value?: Record<string, unknown>; proposalId?: string; vote?: boolean; } interface HiveConsensusResult { accepted: boolean; reason?: string; votesFor: number; votesAgainst: number; totalVoters: number; proposalId?: string; } interface HiveBroadcastMessage { message: string; priority: 'low' | 'normal' | 'high' | 'critical'; fromId: string; targetRole?: string; } interface HiveMemoryOperation { action: 'get' | 'set' | 'delete' | 'list'; key?: string; value?: string; } interface HiveMemoryResult { success: boolean; value?: string; keys?: string[]; } interface HiveStatus { initialized: boolean; topology: string; queenId: string | null; workerCount: number; consensusAlgorithm: string; } interface HiveWorker { id: string; role: HiveRole; capabilities: string[]; status: 'active' | 'idle' | 'busy'; lastHeartbeat: number; } /** * QE-specific capabilities for hive workers */ const QE_CAPABILITIES = { testGeneration: [ 'unit-test-generation', 'integration-test-generation', 'e2e-test-generation', 'property-test-generation', ], testExecution: [ 'test-execution', 'parallel-execution', 'result-aggregation', ], coverageAnalysis: [ 'coverage-collection', 'gap-detection', 'priority-ranking', ], qualityAssessment: [ 'quality-gate-evaluation', 'risk-assessment', 'readiness-assessment', ], securityCompliance: [ 'sast-scanning', 'dast-scanning', 'compliance-checking', ], } as const; /** * QE Hive Bridge Implementation * * Bridges agentic-qe coordination needs to V3's Hive-Mind. * Manages Queen registration, worker spawning, and BFT execution. */ export class QEHiveBridge implements IQEHiveBridge { private hiveMind: IHiveMindService; private logger: QELogger; private queenId: string; private spawnedWorkers: Map<string, string> = new Map(); // workerId -> agentType private initialized: boolean = false; constructor( hiveMind: IHiveMindService, logger: QELogger ) { this.hiveMind = hiveMind; this.logger = logger; this.queenId = `aqe-queen-${Date.now()}`; } /** * Get the Queen identifier */ getQueenId(): string { return this.queenId; } /** * Register QE Queen with Hive Mind */ async registerQueen(): Promise<void> { if (this.initialized) { this.logger.debug('QE Queen already registered'); return; } try { this.logger.info(`Registering QE Queen: ${this.queenId}`); await this.hiveMind.join({ agentId: this.queenId, role: 'queen', capabilities: [ 'qe-coordination', 'test-orchestration', 'coverage-coordination', 'quality-gate-enforcement', 'security-scan-coordination', ], metadata: { source: 'agentic-qe', version: '3.2.3', contexts: [ 'test-generation', 'test-execution', 'coverage-analysis', 'quality-assessment', 'defect-intelligence', 'security-compliance', ], }, }); this.initialized = true; this.logger.info('QE Queen registered successfully'); } catch (error) { this.logger.error('Failed to register QE Queen', error); throw new QEHiveError('Failed to register Queen', error as Error); } } /** * Spawn a QE worker and join to hive */ async spawnQEWorker(agentType: string, context: string): Promise<string> { this.ensureInitialized(); try { const workerId = `aqe-${agentType}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; this.logger.info(`Spawning QE worker: ${workerId} (type: ${agentType}, context: ${context})`); // Get capabilities for this agent type const capabilities = this.getCapabilitiesForAgent(agentType, context); await this.hiveMind.join({ agentId: workerId, role: 'worker', capabilities, metadata: { parentQueen: this.queenId, agentType, qeContext: context, spawnedAt: Date.now(), }, }); this.spawnedWorkers.set(workerId, agentType); this.logger.debug(`QE worker spawned: ${workerId}`); return workerId; } catch (error) { this.logger.error(`Failed to spawn QE worker: ${agentType}`, error); throw new QEHiveError('Failed to spawn worker', error as Error); } } /** * Coordinate a QE swarm task */ async coordinateQESwarm(task: QESwarmTask): Promise<QESwarmResult> { this.ensureInitialized(); try { this.logger.info(`Coordinating QE swarm task: ${task.id} (agents: ${task.agents.join(', ')})`); // 1. Propose task allocation via consensus const consensusResult = await this.proposeTaskAllocation(task, task.agents); if (!consensusResult.accepted) { this.logger.warn(`Task allocation rejected: ${consensusResult.reason}`); return { taskId: task.id, agentResults: [], completedAgents: 0, totalAgents: task.agents.length, success: false, aggregatedOutput: { error: consensusResult.reason }, }; } // 2. Broadcast task to allocated workers await this.broadcastTask(task); // 3. Collect results from workers const results = await this.collectResults(task); // 4. Aggregate results const success = results.filter((r) => r.success).length >= Math.ceil(task.agents.length / 2); const swarmResult: QESwarmResult = { taskId: task.id, agentResults: results, completedAgents: results.length, totalAgents: task.agents.length, success, aggregatedOutput: this.aggregateResults(results), }; this.logger.info(`QE swarm task complete: ${task.id} (success: ${success})`); return swarmResult; } catch (error) { this.logger.error(`QE swarm task failed: ${task.id}`, error); throw new QEHiveError('Swarm coordination failed', error as Error); } } /** * Execute operation with Byzantine fault tolerance */ async executeWithBFT<T>( operation: () => Promise<T>, replicaCount: number = 3 ): Promise<T> { this.ensureInitialized(); this.logger.debug(`Executing operation with BFT (replicas: ${replicaCount})`); const results: T[] = []; const errors: Error[] = []; // Execute operation on multiple replicas const promises = Array(replicaCount).fill(null).map(async (_, i) => { try { const result = await operation(); return { success: true, result, index: i }; } catch (error) { return { success: false, error: error as Error, index: i }; } }); const outcomes = await Promise.all(promises); for (const outcome of outcomes) { if (outcome.success) { results.push(outcome.result as T); } else { errors.push(outcome.error as Error); } } // BFT requires 2f+1 agreeing results (f = max faults tolerated) // For 3 replicas: f=1, need 2 agreeing // For 5 replicas: f=2, need 3 agreeing const maxFaults = Math.floor((replicaCount - 1) / 3); const requiredAgreement = 2 * maxFaults + 1; if (results.length < requiredAgreement) { this.logger.error(`BFT consensus failed: ${results.length}/${replicaCount} replicas succeeded`); throw new QEHiveError( `BFT consensus failed: only ${results.length}/${replicaCount} replicas succeeded`, errors[0] ); } // Return first successful result (in production, would compare for consensus) this.logger.debug(`BFT consensus achieved: ${results.length}/${replicaCount} replicas agreed`); return results[0]; } /** * Propose task allocation via consensus */ async proposeTaskAllocation( task: QESwarmTask, requiredAgents: string[] ): Promise<ConsensusResult> { this.ensureInitialized(); try { this.logger.debug(`Proposing task allocation: ${task.id}`); const result = await this.hiveMind.consensus({ action: 'propose', type: 'qe-task-allocation', value: { taskId: task.id, taskType: task.type, requiredAgents, priority: task.priority, proposedBy: this.queenId, timestamp: Date.now(), }, }); return { accepted: result.accepted, reason: result.reason, votesFor: result.votesFor, votesAgainst: result.votesAgainst, totalVoters: result.totalVoters, }; } catch (error) { this.logger.error('Task allocation proposal failed', error); throw new QEHiveError('Consensus proposal failed', error as Error); } } /** * Broadcast result to hive */ async broadcastResult(taskId: string, result: QESwarmResult): Promise<void> { this.ensureInitialized(); try { this.logger.debug(`Broadcasting result for task: ${taskId}`); await this.hiveMind.broadcast({ message: JSON.stringify({ type: 'qe-result', taskId, result: { success: result.success, completedAgents: result.completedAgents, totalAgents: result.totalAgents, aggregatedOutput: result.aggregatedOutput, }, }), priority: result.success ? 'normal' : 'high', fromId: this.queenId, }); this.logger.debug(`Result broadcast complete for task: ${taskId}`); } catch (error) { this.logger.error(`Failed to broadcast result: ${taskId}`, error); throw new QEHiveError('Result broadcast failed', error as Error); } } /** * Store QE state in hive memory */ async storeQEState(key: string, value: unknown): Promise<void> { this.ensureInitialized(); try { const qeKey = `qe:${key}`; this.logger.debug(`Storing QE state: ${qeKey}`); await this.hiveMind.memory({ action: 'set', key: qeKey, value: JSON.stringify(value), }); } catch (error) { this.logger.error(`Failed to store QE state: ${key}`, error); throw new QEHiveError('Failed to store state', error as Error); } } /** * Retrieve QE state from hive memory */ async getQEState<T>(key: string): Promise<T | null> { this.ensureInitialized(); try { const qeKey = `qe:${key}`; const result = await this.hiveMind.memory({ action: 'get', key: qeKey, }); if (!result.value) return null; return JSON.parse(result.value) as T; } catch (error) { this.logger.error(`Failed to get QE state: ${key}`, error); return null; } } /** * Leave the hive (cleanup) */ async leave(): Promise<void> { if (!this.initialized) return; try { this.logger.info('QE Queen leaving hive...'); // Remove all spawned workers for (const [workerId] of this.spawnedWorkers) { try { await this.hiveMind.leave(workerId); this.logger.debug(`Worker removed: ${workerId}`); } catch { // Best effort cleanup } } this.spawnedWorkers.clear(); // Remove queen await this.hiveMind.leave(this.queenId); this.initialized = false; this.logger.info('QE Queen left hive successfully'); } catch (error) { this.logger.error('Error leaving hive', error); throw new QEHiveError('Failed to leave hive', error as Error); } } /** * Broadcast task to allocated workers */ private async broadcastTask(task: QESwarmTask): Promise<void> { await this.hiveMind.broadcast({ message: JSON.stringify({ type: 'qe-task', taskId: task.id, taskType: task.type, payload: task.payload, timeout: task.timeout, assignedAgents: task.agents, }), priority: task.priority === 'critical' ? 'critical' : 'normal', fromId: this.queenId, }); } /** * Collect results from workers */ private async collectResults(task: QESwarmTask): Promise<AgentTaskResult[]> { const results: AgentTaskResult[] = []; const timeout = task.timeout || 30000; const startTime = Date.now(); // Poll for results while (Date.now() - startTime < timeout) { const resultData = await this.getQEState<AgentTaskResult[]>(`task-results:${task.id}`); if (resultData && resultData.length >= task.agents.length) { return resultData; } // Wait before polling again await this.sleep(500); } // Return partial results on timeout this.logger.warn(`Task ${task.id} timed out, returning partial results`); const partialResults = await this.getQEState<AgentTaskResult[]>(`task-results:${task.id}`); return partialResults || []; } /** * Aggregate results from multiple agents */ private aggregateResults(results: AgentTaskResult[]): Record<string, unknown> { const successCount = results.filter((r) => r.success).length; const failureCount = results.filter((r) => !r.success).length; // Merge outputs const mergedOutput: Record<string, unknown> = {}; for (const result of results) { if (result.success && result.output) { Object.assign(mergedOutput, result.output); } } return { summary: { total: results.length, successful: successCount, failed: failureCount, successRate: results.length > 0 ? successCount / results.length : 0, }, errors: results.filter((r) => !r.success).map((r) => ({ agentId: r.agentId, error: r.error, })), mergedOutput, }; } /** * Get capabilities for an agent type */ private getCapabilitiesForAgent(agentType: string, context: string): string[] { const baseCapabilities = [`context:${context}`, agentType]; // Add context-specific capabilities if (context.includes('test-generation')) { baseCapabilities.push(...QE_CAPABILITIES.testGeneration); } else if (context.includes('test-execution')) { baseCapabilities.push(...QE_CAPABILITIES.testExecution); } else if (context.includes('coverage')) { baseCapabilities.push(...QE_CAPABILITIES.coverageAnalysis); } else if (context.includes('quality')) { baseCapabilities.push(...QE_CAPABILITIES.qualityAssessment); } else if (context.includes('security')) { baseCapabilities.push(...QE_CAPABILITIES.securityCompliance); } return baseCapabilities; } /** * Ensure bridge is initialized */ private ensureInitialized(): void { if (!this.initialized) { throw new QEHiveError('QEHiveBridge not initialized. Call registerQueen() first.'); } } /** * Sleep helper */ private sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } } /** * QE Hive Error class */ export class QEHiveError extends Error { public readonly cause?: Error; constructor(message: string, cause?: Error) { super(message); this.name = 'QEHiveError'; this.cause = cause; } }