/** * Deep Tests for Memory and RuVector Subsystems * * Covers: * - Memory initializer: schema, quantization, batch operations, flash attention * - Intelligence module: SONA coordinator, ReasoningBank, persistence * - SONA optimizer: trajectory outcomes, routing suggestions, temporal decay * - EWC consolidation: Fisher matrix, consolidation, gradient recording * - AST analyzer: language detection, function/class extraction, complexity * - Diff classifier: classification, parsing, risk assessment, commit messages * - Coverage router: parsing formats, routing, gap analysis, trends * - Graph analyzer: circular deps, mincut, louvain, DOT export * - Q-learning router: route decisions, update, exploration, replay, cache * - Service layer: agentic-flow bridge, registry-api validation * - Infrastructure: in-memory repositories */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; // ============================================================================= // Memory Initializer // ============================================================================= describe('Memory Initializer', () => { describe('MEMORY_SCHEMA_V3', () => { it('should export a non-empty schema string', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toBeDefined(); expect(typeof MEMORY_SCHEMA_V3).toBe('string'); expect(MEMORY_SCHEMA_V3.length).toBeGreaterThan(100); }); it('should contain core memory_entries table', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS memory_entries'); }); it('should contain patterns table for learning', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS patterns'); }); it('should contain trajectories table for SONA', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS trajectories'); }); it('should contain migration_state table', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS migration_state'); }); it('should contain sessions table', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS sessions'); }); it('should contain vector_indexes table for HNSW', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS vector_indexes'); }); it('should contain metadata table', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('CREATE TABLE IF NOT EXISTS metadata'); }); it('should set WAL journal mode pragma', async () => { const { MEMORY_SCHEMA_V3 } = await import('../src/memory/memory-initializer.js'); expect(MEMORY_SCHEMA_V3).toContain('PRAGMA journal_mode = WAL'); }); }); describe('quantizeInt8 / dequantizeInt8', () => { it('should quantize a float embedding to int8', async () => { const { quantizeInt8 } = await import('../src/memory/memory-initializer.js'); const embedding = [0.5, -0.3, 0.8, -1.0, 0.0]; const result = quantizeInt8(embedding); expect(result.quantized).toBeInstanceOf(Int8Array); expect(result.quantized.length).toBe(5); expect(typeof result.scale).toBe('number'); expect(result.scale).toBeGreaterThan(0); }); it('should dequantize back to approximately original values', async () => { const { quantizeInt8, dequantizeInt8 } = await import('../src/memory/memory-initializer.js'); const original = [0.5, -0.3, 0.8, -1.0, 0.0]; const { quantized, scale, zeroPoint } = quantizeInt8(original); const recovered = dequantizeInt8(quantized, scale, zeroPoint); expect(recovered).toBeInstanceOf(Float32Array); for (let i = 0; i < original.length; i++) { expect(recovered[i]).toBeCloseTo(original[i], 1); } }); it('should handle Float32Array input', async () => { const { quantizeInt8 } = await import('../src/memory/memory-initializer.js'); const embedding = new Float32Array([0.1, 0.2, 0.3]); const result = quantizeInt8(embedding); expect(result.quantized.length).toBe(3); }); it('should handle all-zero embedding', async () => { const { quantizeInt8 } = await import('../src/memory/memory-initializer.js'); const embedding = [0, 0, 0, 0]; const result = quantizeInt8(embedding); expect(result.quantized.length).toBe(4); for (let i = 0; i < 4; i++) { expect(result.quantized[i]).toBe(0); } }); }); describe('quantizedCosineSim', () => { it('should compute similarity between identical quantized vectors', async () => { const { quantizeInt8, quantizedCosineSim } = await import('../src/memory/memory-initializer.js'); const a = quantizeInt8([0.5, 0.3, 0.8]); const b = quantizeInt8([0.5, 0.3, 0.8]); const sim = quantizedCosineSim(a.quantized, a.scale, b.quantized, b.scale); expect(sim).toBeCloseTo(1.0, 1); }); it('should return 0 for mismatched lengths', async () => { const { quantizedCosineSim } = await import('../src/memory/memory-initializer.js'); const a = new Int8Array([1, 2, 3]); const b = new Int8Array([1, 2]); expect(quantizedCosineSim(a, 1.0, b, 1.0)).toBe(0); }); }); describe('batchCosineSim', () => { it('should compute batch similarities', async () => { const { batchCosineSim } = await import('../src/memory/memory-initializer.js'); const query = new Float32Array([1, 0, 0]); const vectors = [ new Float32Array([1, 0, 0]), new Float32Array([0, 1, 0]), new Float32Array([-1, 0, 0]), ]; const scores = batchCosineSim(query, vectors); expect(scores).toBeInstanceOf(Float32Array); expect(scores.length).toBe(3); expect(scores[0]).toBeCloseTo(1.0, 5); expect(scores[1]).toBeCloseTo(0.0, 5); expect(scores[2]).toBeCloseTo(-1.0, 5); }); it('should handle empty vectors array', async () => { const { batchCosineSim } = await import('../src/memory/memory-initializer.js'); const scores = batchCosineSim(new Float32Array([1, 0]), []); expect(scores.length).toBe(0); }); it('should handle empty query', async () => { const { batchCosineSim } = await import('../src/memory/memory-initializer.js'); const scores = batchCosineSim(new Float32Array([]), [new Float32Array([1, 0])]); expect(scores[0]).toBe(0); }); }); describe('softmaxAttention', () => { it('should normalize scores to sum to 1', async () => { const { softmaxAttention } = await import('../src/memory/memory-initializer.js'); const scores = new Float32Array([1.0, 2.0, 3.0]); const weights = softmaxAttention(scores); let sum = 0; for (let i = 0; i < weights.length; i++) sum += weights[i]; expect(sum).toBeCloseTo(1.0, 5); }); it('should assign highest weight to highest score', async () => { const { softmaxAttention } = await import('../src/memory/memory-initializer.js'); const scores = new Float32Array([1.0, 5.0, 2.0]); const weights = softmaxAttention(scores); expect(weights[1]).toBeGreaterThan(weights[0]); expect(weights[1]).toBeGreaterThan(weights[2]); }); it('should handle empty array', async () => { const { softmaxAttention } = await import('../src/memory/memory-initializer.js'); const result = softmaxAttention(new Float32Array([])); expect(result.length).toBe(0); }); it('should respect temperature parameter', async () => { const { softmaxAttention } = await import('../src/memory/memory-initializer.js'); const scores = new Float32Array([1.0, 3.0]); const lowTemp = softmaxAttention(scores, 0.1); const highTemp = softmaxAttention(scores, 10.0); // Low temperature = more peaked distribution expect(lowTemp[1]).toBeGreaterThan(highTemp[1]); }); }); describe('topKIndices', () => { it('should return top-k indices sorted by score', async () => { const { topKIndices } = await import('../src/memory/memory-initializer.js'); const scores = new Float32Array([0.1, 0.9, 0.5, 0.3, 0.7]); const topK = topKIndices(scores, 3); expect(topK.length).toBe(3); expect(topK[0]).toBe(1); // 0.9 expect(topK[1]).toBe(4); // 0.7 expect(topK[2]).toBe(2); // 0.5 }); it('should return all indices if k >= n', async () => { const { topKIndices } = await import('../src/memory/memory-initializer.js'); const scores = new Float32Array([0.3, 0.1, 0.2]); const topK = topKIndices(scores, 10); expect(topK.length).toBe(3); expect(topK[0]).toBe(0); // 0.3 }); }); describe('flashAttentionSearch', () => { it('should combine batch sim, topk, and softmax', async () => { const { flashAttentionSearch } = await import('../src/memory/memory-initializer.js'); const query = new Float32Array([1, 0, 0]); const vectors = [ new Float32Array([1, 0, 0]), new Float32Array([0, 1, 0]), new Float32Array([0.9, 0.1, 0]), ]; const result = flashAttentionSearch(query, vectors, { k: 2, threshold: 0.1 }); expect(result.indices.length).toBeLessThanOrEqual(2); expect(result.scores.length).toBe(result.indices.length); expect(result.weights.length).toBe(result.indices.length); }); }); describe('getQuantizationStats', () => { it('should report compression ratio ~3.92x', async () => { const { getQuantizationStats } = await import('../src/memory/memory-initializer.js'); const embedding = new Array(384).fill(0.5); const stats = getQuantizationStats(embedding); expect(stats.originalBytes).toBe(384 * 4); expect(stats.quantizedBytes).toBe(384 + 8); expect(stats.compressionRatio).toBeGreaterThan(3.5); }); }); describe('getInitialMetadata', () => { it('should include schema version 3.0.0', async () => { const { getInitialMetadata } = await import('../src/memory/memory-initializer.js'); const sql = getInitialMetadata('sql.js'); expect(sql).toContain("'3.0.0'"); expect(sql).toContain("'sql.js'"); }); }); describe('HNSW status', () => { // #2356: `getHNSWStatus()` now separates "loaded in this process" // (`initialized`) from "@ruvector/core capability present" (`available`). // Previously `available` tracked the lazy in-process singleton, so a fresh // `neural status` process always reported "Not loaded" even when the // package was installed — a false negative. After clearing the index the // contract is: not initialized, 0 entries, 384-dim; `available` reflects // whether @ruvector/core is resolvable (env-dependent, so not hard-asserted). it('should report not-initialized after the index is cleared', async () => { const { getHNSWStatus, clearHNSWIndex } = await import('../src/memory/memory-initializer.js'); clearHNSWIndex(); const status = getHNSWStatus(); expect(status.initialized).toBe(false); expect(status.entryCount).toBe(0); expect(status.dimensions).toBe(384); expect(typeof status.available).toBe('boolean'); }); }); // AUDIT #3: generateEmbedding must expose a truthful `backend` field so an // operator can distinguish real ONNX semantics from the deterministic hash // fallback (inverted/meaningless semantics) even when `model` reports a // real-looking name. describe('generateEmbedding backend field', () => { const prevDisableBridge = process.env.CLAUDE_FLOW_DISABLE_BRIDGE; afterEach(() => { vi.resetModules(); vi.doUnmock('@huggingface/transformers'); vi.doUnmock('@xenova/transformers'); vi.doUnmock('ruvector'); vi.doUnmock('agentic-flow'); vi.doUnmock('agentic-flow/reasoningbank'); if (prevDisableBridge === undefined) delete process.env.CLAUDE_FLOW_DISABLE_BRIDGE; else process.env.CLAUDE_FLOW_DISABLE_BRIDGE = prevDisableBridge; }); it("reports backend='mock' when no real embedding model is available", async () => { // Force the raw fallback path (no AgentDB bridge) and make every real // embedding provider import fail so loadEmbeddingModel lands on the // hash fallback (model = null). process.env.CLAUDE_FLOW_DISABLE_BRIDGE = '1'; vi.resetModules(); const fail = () => { throw new Error('unavailable in test'); }; vi.doMock('@huggingface/transformers', fail); vi.doMock('@xenova/transformers', fail); vi.doMock('ruvector', fail); vi.doMock('agentic-flow', fail); vi.doMock('agentic-flow/reasoningbank', fail); const { generateEmbedding } = await import('../src/memory/memory-initializer.js'); const result = await generateEmbedding('audit-3 mock-backend assertion'); expect(result.backend).toBe('mock'); expect(result.model).toBe('hash-fallback'); expect(Array.isArray(result.embedding)).toBe(true); expect(result.embedding.length).toBe(result.dimensions); }); it("always sets backend, and 'mock' implies the hash-fallback model", async () => { // Invariant check that holds regardless of which providers are installed: // backend is one of the two known values, and mock <=> hash-fallback. process.env.CLAUDE_FLOW_DISABLE_BRIDGE = '1'; vi.resetModules(); const { generateEmbedding } = await import('../src/memory/memory-initializer.js'); const result = await generateEmbedding('audit-3 backend invariant'); expect(['onnx', 'mock']).toContain(result.backend); if (result.backend === 'mock') { expect(result.model).toBe('hash-fallback'); } }, 60000); // real ONNX model can be slow to cold-load when installed }); }); // ============================================================================= // Intelligence Module (SONA + ReasoningBank) // ============================================================================= describe('Intelligence Module', () => { beforeEach(async () => { const { clearIntelligence } = await import('../src/memory/intelligence.js'); clearIntelligence(); }); describe('initializeIntelligence', () => { it('should initialize successfully with defaults', async () => { const { initializeIntelligence } = await import('../src/memory/intelligence.js'); const result = await initializeIntelligence(); expect(result.success).toBe(true); expect(result.sonaEnabled).toBe(true); expect(result.reasoningBankEnabled).toBe(true); }); it('should return cached result on second call', async () => { const { initializeIntelligence } = await import('../src/memory/intelligence.js'); const first = await initializeIntelligence(); const second = await initializeIntelligence(); expect(first.success).toBe(true); expect(second.success).toBe(true); }); it('should accept custom config overrides', async () => { const { initializeIntelligence } = await import('../src/memory/intelligence.js'); const result = await initializeIntelligence({ maxSignals: 100, maxPatterns: 50, patternThreshold: 0.9, }); expect(result.success).toBe(true); }); }); describe('getIntelligenceStats', () => { it('should return valid stats after initialization', async () => { const { initializeIntelligence, getIntelligenceStats } = await import('../src/memory/intelligence.js'); await initializeIntelligence(); const stats = getIntelligenceStats(); expect(stats.sonaEnabled).toBe(true); // reasoningBankSize may be > 0 if patterns were loaded from persisted disk state expect(stats.reasoningBankSize).toBeGreaterThanOrEqual(0); expect(stats.patternsLearned).toBeGreaterThanOrEqual(0); expect(stats.trajectoriesRecorded).toBeGreaterThanOrEqual(0); }); it('should return disabled state when not initialized', async () => { const { getIntelligenceStats } = await import('../src/memory/intelligence.js'); const stats = getIntelligenceStats(); expect(stats.sonaEnabled).toBe(false); }); }); describe('clearIntelligence', () => { it('should reset all state', async () => { const { initializeIntelligence, clearIntelligence, getIntelligenceStats } = await import('../src/memory/intelligence.js'); await initializeIntelligence(); clearIntelligence(); const stats = getIntelligenceStats(); expect(stats.sonaEnabled).toBe(false); expect(stats.reasoningBankSize).toBe(0); }); }); describe('benchmarkAdaptation', () => { it('should achieve sub-0.05ms average adaptation time', async () => { const { initializeIntelligence, benchmarkAdaptation } = await import('../src/memory/intelligence.js'); await initializeIntelligence(); const result = benchmarkAdaptation(100); expect(result.avgMs).toBeLessThan(1); expect(result.totalMs).toBeGreaterThan(0); expect(typeof result.minMs).toBe('number'); expect(typeof result.maxMs).toBe('number'); }); }); describe('getNeuralDataDir', () => { it('should return a valid path string', async () => { const { getNeuralDataDir } = await import('../src/memory/intelligence.js'); const dir = getNeuralDataDir(); expect(typeof dir).toBe('string'); expect(dir.length).toBeGreaterThan(0); }); }); describe('getPersistenceStatus', () => { it('should report persistence enabled', async () => { const { getPersistenceStatus } = await import('../src/memory/intelligence.js'); const status = getPersistenceStatus(); expect(status.enabled).toBe(true); expect(typeof status.dataDir).toBe('string'); expect(typeof status.patternsFile).toBe('string'); expect(typeof status.statsFile).toBe('string'); }); }); }); // ============================================================================= // SONA Optimizer // ============================================================================= // SONAOptimizer's processTrajectoryOutcome / getRoutingSuggestion paths // pull in the optional native @ruvector/sona engine. Without the binary // (CI without postinstall scripts), 14 assertions fail because intent // detection returns empty results — even though the package resolves, // the WASM binary doesn't load. Skip in CI. const __SKIP_WASM_TESTS = process.env.CI === 'true'; describe.skipIf(__SKIP_WASM_TESTS)('SONA Optimizer', () => { let optimizer: any; beforeEach(async () => { const { SONAOptimizer } = await import('../src/memory/sona-optimizer.js'); optimizer = new SONAOptimizer({ persistencePath: '/tmp/sona-test-patterns.json' }); // Skip initialize to avoid disk I/O in tests }); describe('processTrajectoryOutcome', () => { it('should learn from a successful outcome', () => { const result = optimizer.processTrajectoryOutcome({ trajectoryId: 'test-1', task: 'implement authentication feature', agent: 'coder', success: true, }); expect(result.learned).toBe(true); expect(result.confidence).toBeGreaterThan(0); expect(result.keywordsExtracted.length).toBeGreaterThan(0); }); it('should learn from a failed outcome', () => { const result = optimizer.processTrajectoryOutcome({ trajectoryId: 'test-2', task: 'debug memory leak bug', agent: 'debugger', success: false, }); expect(result.learned).toBe(true); expect(result.confidence).toBeLessThan(0.5); }); it('should not learn from empty task', () => { const result = optimizer.processTrajectoryOutcome({ trajectoryId: 'test-3', task: '', agent: 'coder', success: true, }); expect(result.learned).toBe(false); }); it('should increase confidence on repeated success', () => { const task = 'write unit tests for auth module'; optimizer.processTrajectoryOutcome({ trajectoryId: 'a', task, agent: 'tester', success: true }); const r2 = optimizer.processTrajectoryOutcome({ trajectoryId: 'b', task, agent: 'tester', success: true }); expect(r2.confidence).toBeGreaterThan(0.5); }); }); describe('getRoutingSuggestion', () => { it('should suggest coder for implementation tasks', async () => { const suggestion = await optimizer.getRoutingSuggestion('implement a new feature for authentication'); expect(suggestion.agent).toBeDefined(); expect(suggestion.confidence).toBeGreaterThan(0); expect(suggestion.source).toBeDefined(); }); it('should suggest tester for test tasks', async () => { const suggestion = await optimizer.getRoutingSuggestion('write unit test coverage for the API'); expect(suggestion.agent).toBeDefined(); expect(['tester', 'coder', 'reviewer']).toContain(suggestion.agent); }); it('should fallback to default for unknown tasks', async () => { const suggestion = await optimizer.getRoutingSuggestion('xyzzy'); expect(suggestion.agent).toBe('coder'); expect(suggestion.source).toBe('default'); expect(suggestion.confidence).toBe(0.3); }); it('should include alternatives', async () => { const suggestion = await optimizer.getRoutingSuggestion('implement and test security audit'); expect(suggestion.alternatives).toBeDefined(); expect(Array.isArray(suggestion.alternatives)).toBe(true); }); }); describe('getStats', () => { it('should track trajectory processing', () => { optimizer.processTrajectoryOutcome({ trajectoryId: 't1', task: 'implement feature', agent: 'coder', success: true, }); const stats = optimizer.getStats(); expect(stats.totalPatterns).toBeGreaterThan(0); expect(stats.trajectoriesProcessed).toBe(1); expect(stats.successfulRoutings).toBe(1); }); }); describe('applyTemporalDecay', () => { it('should return number of decayed patterns', () => { optimizer.processTrajectoryOutcome({ trajectoryId: 't1', task: 'implement feature', agent: 'coder', success: true, }); const decayed = optimizer.applyTemporalDecay(); expect(typeof decayed).toBe('number'); }); }); describe('reset', () => { it('should clear all learned patterns', () => { optimizer.processTrajectoryOutcome({ trajectoryId: 't1', task: 'implement feature', agent: 'coder', success: true, }); optimizer.reset(); const stats = optimizer.getStats(); expect(stats.totalPatterns).toBe(0); expect(stats.trajectoriesProcessed).toBe(0); }); }); describe('exportPatterns / importPatterns', () => { it('should round-trip patterns through export/import', () => { optimizer.processTrajectoryOutcome({ trajectoryId: 't1', task: 'build new api endpoint', agent: 'coder', success: true, }); const exported = optimizer.exportPatterns(); expect(Object.keys(exported).length).toBeGreaterThan(0); const newOptimizer = new (optimizer.constructor)({ persistencePath: '/tmp/sona-test2.json' }); const imported = newOptimizer.importPatterns(exported); expect(imported).toBe(Object.keys(exported).length); }); it('should reject invalid patterns on import', () => { const imported = optimizer.importPatterns({ 'bad': { invalid: true } as any, }); expect(imported).toBe(0); }); }); }); // ============================================================================= // EWC Consolidation // ============================================================================= describe('EWC Consolidation', () => { let consolidator: any; beforeEach(async () => { const { EWCConsolidator } = await import('../src/memory/ewc-consolidation.js'); consolidator = new EWCConsolidator({ lambda: 0.4, dimensions: 8, maxPatterns: 100, storagePath: '/tmp/ewc-test-fisher.json', }); }); describe('computeFisherMatrix', () => { it('should compute diagonal Fisher from successful patterns', () => { const fisher = consolidator.computeFisherMatrix([ { id: 'p1', embedding: [0.5, 0.3, 0.2, 0.1, 0, 0, 0, 0], success: true }, { id: 'p2', embedding: [0.1, 0.4, 0.6, 0.2, 0, 0, 0, 0], success: true }, ]); expect(fisher.length).toBe(8); expect(fisher[0]).toBeGreaterThan(0); }); it('should ignore failed patterns', () => { const fisher = consolidator.computeFisherMatrix([ { id: 'p1', embedding: [1, 1, 1, 1, 0, 0, 0, 0], success: false }, ]); expect(fisher.every((v: number) => v === 0)).toBe(true); }); it('should handle empty patterns array', () => { const fisher = consolidator.computeFisherMatrix([]); expect(fisher.length).toBe(8); expect(fisher.every((v: number) => v === 0)).toBe(true); }); }); describe('getPenalty', () => { it('should compute EWC penalty between weight vectors', () => { const oldW = [0.5, 0.3, 0.2, 0.1, 0, 0, 0, 0]; const newW = [0.6, 0.4, 0.3, 0.2, 0, 0, 0, 0]; const fisher = [1, 1, 1, 1, 0, 0, 0, 0]; const penalty = consolidator.getPenalty(oldW, newW, fisher); expect(typeof penalty).toBe('number'); expect(penalty).toBeGreaterThan(0); }); it('should return 0 for identical weights', () => { const w = [0.5, 0.3, 0.2, 0.1, 0, 0, 0, 0]; const fisher = [1, 1, 1, 1, 0, 0, 0, 0]; const penalty = consolidator.getPenalty(w, w, fisher); expect(penalty).toBe(0); }); }); describe('consolidate', () => { it('should consolidate new patterns', () => { const result = consolidator.consolidate([ { id: 'new1', embedding: [0.5, 0.3, 0.2, 0.1, 0, 0, 0, 0], type: 'task-routing' }, { id: 'new2', embedding: [0.1, 0.4, 0.6, 0.2, 0, 0, 0, 0], type: 'learning' }, ]); expect(result.success).toBe(true); expect(result.patternsConsolidated).toBe(2); expect(result.modifiedPatterns.length).toBe(2); expect(result.duration).toBeGreaterThanOrEqual(0); }); it('should protect high-importance existing patterns', () => { // First consolidation consolidator.consolidate([ { id: 'important', embedding: [1, 0, 0, 0, 0, 0, 0, 0], type: 'core' }, ]); // Simulate many successes for (let i = 0; i < 10; i++) { consolidator.recordGradient('important', [1, 0, 0, 0, 0, 0, 0, 0], true); } // Try to overwrite const result = consolidator.consolidate([ { id: 'important', embedding: [0, 1, 0, 0, 0, 0, 0, 0], type: 'core' }, ]); expect(result.success).toBe(true); expect(result.modifiedPatterns).toContain('important'); }); it('should handle empty patterns input', () => { const result = consolidator.consolidate([]); expect(result.success).toBe(true); expect(result.patternsConsolidated).toBe(0); }); }); describe('recordGradient', () => { it('should update pattern success count', () => { consolidator.consolidate([ { id: 'p1', embedding: [0.5, 0.3, 0, 0, 0, 0, 0, 0], type: 'test' }, ]); consolidator.recordGradient('p1', [0.5, 0.3, 0, 0, 0, 0, 0, 0], true); const pattern = consolidator.getPatternWeights('p1'); expect(pattern.successCount).toBe(1); }); it('should update pattern failure count', () => { consolidator.consolidate([ { id: 'p2', embedding: [0.5, 0.3, 0, 0, 0, 0, 0, 0], type: 'test' }, ]); consolidator.recordGradient('p2', [0.5, 0.3, 0, 0, 0, 0, 0, 0], false); const pattern = consolidator.getPatternWeights('p2'); expect(pattern.failureCount).toBe(1); }); }); describe('getConsolidationStats', () => { it('should return valid statistics', () => { const stats = consolidator.getConsolidationStats(); expect(typeof stats.totalPatterns).toBe('number'); expect(typeof stats.avgFisherValue).toBe('number'); expect(typeof stats.consolidationCount).toBe('number'); }); }); describe('clear', () => { it('should reset all state', () => { consolidator.consolidate([ { id: 'p1', embedding: [0.5, 0.3, 0, 0, 0, 0, 0, 0], type: 'test' }, ]); consolidator.clear(); expect(consolidator.getAllPatterns().length).toBe(0); }); }); describe('setLambda / getLambda', () => { it('should update lambda parameter', () => { consolidator.setLambda(0.8); expect(consolidator.getLambda()).toBe(0.8); }); }); }); // ============================================================================= // AST Analyzer // ============================================================================= describe('AST Analyzer', () => { let analyzer: any; beforeEach(async () => { const { ASTAnalyzer } = await import('../src/ruvector/ast-analyzer.js'); analyzer = new ASTAnalyzer(); }); describe('analyze', () => { it('should detect TypeScript functions', () => { const code = ` export function greet(name: string): string { return \`Hello, \${name}\`; } function helper() { return true; } `; const result = analyzer.analyze(code, 'test.ts'); expect(result.language).toBe('typescript'); expect(result.functions.length).toBeGreaterThanOrEqual(2); expect(result.functions.some((f: any) => f.name === 'greet')).toBe(true); expect(result.functions.some((f: any) => f.name === 'helper')).toBe(true); }); it('should detect Python functions and classes', () => { const code = ` class MyClass(Base): def method(self): pass def standalone(): pass `; const result = analyzer.analyze(code, 'test.py'); expect(result.language).toBe('python'); expect(result.functions.length).toBeGreaterThanOrEqual(1); expect(result.classes.length).toBeGreaterThanOrEqual(1); }); it('should extract imports', () => { const code = ` import { foo } from './bar'; import path from 'path'; const fs = require('fs'); `; const result = analyzer.analyze(code, 'test.ts'); expect(result.imports.length).toBeGreaterThanOrEqual(2); }); it('should extract exports', () => { const code = ` export function foo() {} export const bar = 1; export class Baz {} `; const result = analyzer.analyze(code, 'test.ts'); expect(result.exports.length).toBeGreaterThanOrEqual(3); }); it('should calculate cyclomatic complexity', () => { const code = ` function complex(x) { if (x > 0) { if (x > 10) { return 'high'; } else { return 'low'; } } else { for (let i = 0; i < 10; i++) { while (x < 0) { x++; } } } return x ? 'yes' : 'no'; } `; const result = analyzer.analyze(code, 'test.js'); expect(result.complexity.cyclomatic).toBeGreaterThan(1); expect(result.complexity.cognitive).toBeGreaterThan(0); expect(result.complexity.loc).toBeGreaterThan(0); }); it('should compute comment density', () => { const code = ` // This is a comment /* Another comment */ function foo() { // Inline comment return 1; } `; const result = analyzer.analyze(code, 'test.js'); expect(result.complexity.commentDensity).toBeGreaterThan(0); }); it('should throw for files exceeding maxFileSize', () => { const bigCode = 'x'.repeat(1024 * 1024 + 1); expect(() => analyzer.analyze(bigCode, 'big.ts')).toThrow('File too large'); }); it('should cache analysis results', () => { const code = 'function test() {}'; const r1 = analyzer.analyze(code, 'test.ts'); const r2 = analyzer.analyze(code, 'test.ts'); expect(r1).toBe(r2); // Same reference from cache }); }); describe('getFunctionAtLine', () => { it('should find function containing a given line', () => { const code = ` function foo() { return 1; } `; const analysis = analyzer.analyze(code, 'test.js'); const func = analyzer.getFunctionAtLine(analysis, 3); expect(func).not.toBeNull(); expect(func?.name).toBe('foo'); }); it('should return null for lines not in any function', () => { const code = ` function foo() { return 1; } `; const analysis = analyzer.analyze(code, 'test.js'); const func = analyzer.getFunctionAtLine(analysis, 100); expect(func).toBeNull(); }); }); describe('getSymbols', () => { it('should list function and class names', () => { const code = ` class MyClass {} function myFunc() {} `; const analysis = analyzer.analyze(code, 'test.ts'); const symbols = analyzer.getSymbols(analysis); expect(symbols).toContain('myFunc'); expect(symbols).toContain('MyClass'); }); }); describe('getStats / clearCache', () => { it('should track and clear cache', () => { analyzer.analyze('function x() {}', 'a.ts'); expect(analyzer.getStats().cacheSize).toBe(1); analyzer.clearCache(); expect(analyzer.getStats().cacheSize).toBe(0); }); }); describe('createASTAnalyzer factory', () => { it('should create analyzer with custom config', async () => { const { createASTAnalyzer } = await import('../src/ruvector/ast-analyzer.js'); const a = createASTAnalyzer({ maxDepth: 5 }); expect(a).toBeDefined(); }); }); }); // ============================================================================= // Diff Classifier // ============================================================================= describe('Diff Classifier', () => { let classifier: any; beforeEach(async () => { const { DiffClassifier } = await import('../src/ruvector/diff-classifier.js'); classifier = new DiffClassifier(); }); describe('classifyCommitMessage', () => { it('should classify feat as feature', () => { expect(classifier.classifyCommitMessage('feat: add login page')).toBe('feature'); }); it('should classify fix as bugfix', () => { expect(classifier.classifyCommitMessage('fix: resolve null pointer')).toBe('bugfix'); }); it('should classify refactor as refactor', () => { expect(classifier.classifyCommitMessage('refactor: extract helper')).toBe('refactor'); }); it('should classify docs as docs', () => { expect(classifier.classifyCommitMessage('docs: update readme')).toBe('docs'); }); it('should classify test as test', () => { expect(classifier.classifyCommitMessage('test: add unit tests')).toBe('test'); }); it('should return unknown for unrecognized', () => { expect(classifier.classifyCommitMessage('misc: random stuff')).toBe('unknown'); }); }); describe('parseDiff', () => { it('should parse a git diff into file diffs', () => { const diff = `diff --git a/src/app.ts b/src/app.ts --- a/src/app.ts +++ b/src/app.ts @@ -1,3 +1,4 @@ import express from 'express'; +import cors from 'cors'; const app = express(); `; const files = classifier.parseDiff(diff); expect(files.length).toBe(1); expect(files[0].path).toBe('src/app.ts'); expect(files[0].additions).toBeGreaterThanOrEqual(1); }); it('should handle empty diff', () => { const files = classifier.parseDiff(''); expect(files.length).toBe(0); }); }); describe('classify', () => { it('should compute overall classification from file diffs', () => { const files = [ { path: 'src/auth.ts', hunks: [], additions: 50, deletions: 10, classification: { primary: 'feature' as const, secondary: [], confidence: 0.8, impactLevel: 'high' as const, suggestedReviewers: ['dev'], testingStrategy: ['unit-tests'], riskFactors: [] }, }, ]; const analysis = classifier.classify(files); expect(analysis.overall.primary).toBe('feature'); expect(analysis.stats.totalAdditions).toBe(50); expect(analysis.stats.totalDeletions).toBe(10); expect(analysis.stats.filesChanged).toBe(1); }); it('should handle empty files array', () => { const analysis = classifier.classify([]); expect(analysis.overall.primary).toBe('unknown'); expect(analysis.stats.filesChanged).toBe(0); }); }); describe('assessFileRisk', () => { it('should assess security files as high risk', async () => { const { assessFileRisk } = await import('../src/ruvector/diff-classifier.js'); const result = assessFileRisk({ path: 'src/security/auth.ts', status: 'modified', additions: 50, deletions: 10, hunks: 1, binary: false, }); expect(['high', 'critical']).toContain(result.risk); expect(result.reasons.some((r: string) => r.includes('Security'))).toBe(true); }); it('should assess test files as low risk', async () => { const { assessFileRisk } = await import('../src/ruvector/diff-classifier.js'); const result = assessFileRisk({ path: 'tests/helper.test.ts', status: 'modified', additions: 5, deletions: 2, hunks: 1, binary: false, }); expect(result.risk).toBe('low'); }); }); describe('assessOverallRisk', () => { it('should compute overall risk from file risks', async () => { const { assessOverallRisk } = await import('../src/ruvector/diff-classifier.js'); const files = [ { path: 'src/core.ts', status: 'modified' as const, additions: 20, deletions: 5, hunks: 1, binary: false }, ]; const fileRisks = [ { file: 'src/core.ts', risk: 'medium' as const, score: 30, reasons: ['Core module'] }, ]; const result = assessOverallRisk(files, fileRisks); expect(result.overall).toBeDefined(); expect(result.score).toBeGreaterThan(0); expect(result.breakdown).toBeDefined(); }); }); describe('validateGitRef', () => { it('should reject refs with shell metacharacters', async () => { const mod = await import('../src/ruvector/diff-classifier.js'); // getGitDiffNumstat calls validateGitRef internally which throws on unsafe chars expect(() => mod.getGitDiffNumstat('HEAD; rm -rf /')).toThrow('Invalid git ref'); }); }); }); // ============================================================================= // Coverage Router // ============================================================================= describe('Coverage Router', () => { let router: any; beforeEach(async () => { const { CoverageRouter } = await import('../src/ruvector/coverage-router.js'); router = new CoverageRouter({ minCoverage: 70, targetCoverage: 85 }); }); describe('parseCoverage - JSON', () => { it('should parse JSON coverage data', () => { const data = { 'src/app.ts': { lineCoverage: 80, branchCoverage: 60, functionCoverage: 90, statementCoverage: 85, uncoveredLines: [10, 20], totalLines: 100, coveredLines: 80 }, }; const report = router.parseCoverage(data, 'json'); expect(report.byFile.length).toBe(1); expect(report.byFile[0].path).toBe('src/app.ts'); expect(report.overall).toBeGreaterThan(0); }); }); describe('parseCoverage - LCOV', () => { it('should parse LCOV format', () => { const lcov = `SF:src/index.ts LF:50 LH:40 DA:10,0 DA:20,1 end_of_record SF:src/utils.ts LF:30 LH:25 end_of_record`; const report = router.parseCoverage(lcov, 'lcov'); expect(report.byFile.length).toBe(2); }); }); describe('route', () => { it('should determine add-tests action for low coverage', () => { const coverage = { overall: 50, byType: { line: 50, branch: 40, function: 60, statement: 50 }, byFile: [ { path: 'src/a.ts', lineCoverage: 30, branchCoverage: 20, functionCoverage: 50, statementCoverage: 30, uncoveredLines: [1,2,3,4,5,6,7,8,9,10,11], totalLines: 100, coveredLines: 30 }, { path: 'src/b.ts', lineCoverage: 40, branchCoverage: 30, functionCoverage: 50, statementCoverage: 40, uncoveredLines: [1,2,3,4,5,6,7,8,9,10,11], totalLines: 100, coveredLines: 40 }, { path: 'src/c.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3,4,5,6,7,8,9,10,11], totalLines: 100, coveredLines: 50 }, { path: 'src/d.ts', lineCoverage: 55, branchCoverage: 45, functionCoverage: 65, statementCoverage: 55, uncoveredLines: [1,2,3,4,5], totalLines: 100, coveredLines: 55 }, { path: 'src/e.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 60, uncoveredLines: [1,2], totalLines: 100, coveredLines: 60 }, { path: 'src/f.ts', lineCoverage: 65, branchCoverage: 55, functionCoverage: 75, statementCoverage: 65, uncoveredLines: [], totalLines: 100, coveredLines: 65 }, ], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now(), }; const result = router.route(coverage); expect(result.action).toBe('prioritize'); expect(result.gaps.length).toBeGreaterThan(0); expect(result.testTypes).toContain('unit'); }); it('should determine skip for good coverage', () => { const coverage = { overall: 95, byType: { line: 95, branch: 90, function: 95, statement: 95 }, byFile: [ { path: 'src/a.ts', lineCoverage: 95, branchCoverage: 90, functionCoverage: 95, statementCoverage: 95, uncoveredLines: [], totalLines: 100, coveredLines: 95 }, ], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now(), }; const result = router.route(coverage); expect(result.action).toBe('skip'); }); }); describe('getTrend', () => { it('should return stable with insufficient history', () => { const trend = router.getTrend(); expect(trend.direction).toBe('stable'); expect(trend.change).toBe(0); }); it('should detect upward trend', () => { const r1 = { overall: 70, byType: { line: 70, branch: 70, function: 70, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() }; const r2 = { overall: 80, byType: { line: 80, branch: 80, function: 80, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() }; router.addToHistory(r1); router.addToHistory(r2); const trend = router.getTrend(); expect(trend.direction).toBe('up'); expect(trend.change).toBe(10); }); }); describe('getStats', () => { it('should return config and runtime stats', () => { const stats = router.getStats(); expect(stats.minCoverage).toBe(70); expect(stats.targetCoverage).toBe(85); expect(stats.historySize).toBe(0); }); }); describe('clearCoverageCache', () => { it('should clear cache without error', async () => { const { clearCoverageCache, getCoverageCacheStats } = await import('../src/ruvector/coverage-router.js'); clearCoverageCache(); expect(getCoverageCacheStats().size).toBe(0); }); }); }); // ============================================================================= // Graph Analyzer // ============================================================================= describe('Graph Analyzer', () => { describe('detectCircularDependencies', () => { it('should detect simple cycles', async () => { const { detectCircularDependencies } = await import('../src/ruvector/graph-analyzer.js'); const graph = { nodes: new Map([ ['a.ts', { id: 'a.ts', path: 'a.ts', name: 'a', type: 'file' as const, imports: ['b.ts'], exports: [], size: 100 }], ['b.ts', { id: 'b.ts', path: 'b.ts', name: 'b', type: 'file' as const, imports: ['a.ts'], exports: [], size: 100 }], ]), edges: [ { source: 'a.ts', target: 'b.ts', type: 'import' as const, weight: 1 }, { source: 'b.ts', target: 'a.ts', type: 'import' as const, weight: 1 }, ], metadata: { rootDir: '/test', totalFiles: 2, totalEdges: 2, buildTime: 10 }, }; const cycles = detectCircularDependencies(graph); expect(cycles.length).toBeGreaterThan(0); expect(cycles[0].severity).toBeDefined(); expect(cycles[0].suggestion).toBeDefined(); }); it('should detect no cycles in acyclic graph', async () => { const { detectCircularDependencies } = await import('../src/ruvector/graph-analyzer.js'); const graph = { nodes: new Map([ ['a.ts', { id: 'a.ts', path: 'a.ts', name: 'a', type: 'file' as const, imports: ['b.ts'], exports: [], size: 100 }], ['b.ts', { id: 'b.ts', path: 'b.ts', name: 'b', type: 'file' as const, imports: [], exports: [], size: 100 }], ]), edges: [ { source: 'a.ts', target: 'b.ts', type: 'import' as const, weight: 1 }, ], metadata: { rootDir: '/test', totalFiles: 2, totalEdges: 1, buildTime: 5 }, }; const cycles = detectCircularDependencies(graph); expect(cycles.length).toBe(0); }); }); describe('fallbackMinCut', () => { it('should partition graph into two groups', async () => { const { fallbackMinCut } = await import('../src/ruvector/graph-analyzer.js'); const nodes = ['a', 'b', 'c', 'd']; const edges: Array<[string, string, number]> = [ ['a', 'b', 5], ['b', 'c', 1], ['c', 'd', 5], ]; const result = fallbackMinCut(nodes, edges); expect(result.partition1.length + result.partition2.length).toBe(4); expect(result.cutValue).toBeDefined(); }); it('should handle single node graph', async () => { const { fallbackMinCut } = await import('../src/ruvector/graph-analyzer.js'); const result = fallbackMinCut(['a'], []); expect(result.partition1.length).toBe(1); expect(result.partition2.length).toBe(0); expect(result.cutValue).toBe(0); }); }); describe('fallbackLouvain', () => { it('should detect communities in connected graph', async () => { const { fallbackLouvain } = await import('../src/ruvector/graph-analyzer.js'); const nodes = ['a', 'b', 'c', 'd']; const edges: Array<[string, string, number]> = [ ['a', 'b', 10], ['c', 'd', 10], ['b', 'c', 1], ]; const result = fallbackLouvain(nodes, edges); expect(result.communities.length).toBeGreaterThan(0); expect(typeof result.modularity).toBe('number'); }); it('should handle empty graph', async () => { const { fallbackLouvain } = await import('../src/ruvector/graph-analyzer.js'); const result = fallbackLouvain([], []); expect(result.communities.length).toBe(0); expect(result.modularity).toBe(0); }); it('should place isolated nodes in separate communities', async () => { const { fallbackLouvain } = await import('../src/ruvector/graph-analyzer.js'); const result = fallbackLouvain(['a', 'b', 'c'], []); expect(result.communities.length).toBe(3); }); }); describe('exportToDot', () => { it('should generate valid DOT format', async () => { const { exportToDot } = await import('../src/ruvector/graph-analyzer.js'); const result = { graph: { nodes: new Map([ ['a.ts', { id: 'a.ts', path: 'a.ts', name: 'a', type: 'file' as const, imports: [], exports: [], size: 100 }], ]), edges: [], metadata: { rootDir: '/test', totalFiles: 1, totalEdges: 0, buildTime: 5 }, }, circularDependencies: [], statistics: { nodeCount: 1, edgeCount: 0, avgDegree: 0, maxDegree: 0, density: 0, componentCount: 1 }, }; const dot = exportToDot(result); expect(dot).toContain('digraph'); expect(dot).toContain('"a.ts"'); }); }); describe('clearGraphCaches / getGraphCacheStats', () => { it('should clear and report cache', async () => { const { clearGraphCaches, getGraphCacheStats } = await import('../src/ruvector/graph-analyzer.js'); clearGraphCaches(); const stats = getGraphCacheStats(); expect(stats.graphCacheSize).toBe(0); expect(stats.analysisCacheSize).toBe(0); }); }); }); // ============================================================================= // Q-Learning Router // ============================================================================= describe('Q-Learning Router', () => { let router: any; beforeEach(async () => { const { QLearningRouter } = await import('../src/ruvector/q-learning-router.js'); router = new QLearningRouter({ learningRate: 0.1, gamma: 0.99, explorationInitial: 1.0, explorationFinal: 0.01, explorationDecay: 100, enableReplay: true, replayBufferSize: 50, replayBatchSize: 5, cacheSize: 10, cacheTTL: 60000, autoSaveInterval: 0, // Disable auto-save in tests modelPath: '/tmp/q-learning-test-model.json', }); }); describe('route', () => { it('should return a route decision', () => { const decision = router.route('implement a new user login feature', true); expect(decision.route).toBeDefined(); expect(typeof decision.confidence).toBe('number'); expect(Array.isArray(decision.qValues)).toBe(true); expect(Array.isArray(decision.alternatives)).toBe(true); }); it('should use cache for non-explore queries', () => { router.route('write unit tests', false); router.route('write unit tests', false); const stats = router.getStats(); expect(stats.cacheHits + stats.cacheMisses).toBeGreaterThan(0); }); }); describe('update', () => { it('should return TD error on update', () => { const tdError = router.update('implement feature', 'coder', 1.0); expect(typeof tdError).toBe('number'); }); it('should return 0 for unknown action', () => { const tdError = router.update('implement feature', 'nonexistent-agent', 1.0); expect(tdError).toBe(0); }); it('should decrease epsilon over updates', () => { const initialEpsilon = router.getStats().epsilon; for (let i = 0; i < 20; i++) { router.update(`task ${i}`, 'coder', 1.0); } const newEpsilon = router.getStats().epsilon; expect(newEpsilon).toBeLessThan(initialEpsilon); }); it('should populate replay buffer', () => { for (let i = 0; i < 10; i++) { router.update(`task ${i}`, 'coder', Math.random()); } const stats = router.getStats(); expect(stats.replayBufferSize).toBe(10); expect(stats.totalExperiences).toBe(10); }); }); describe('reset', () => { it('should clear Q-table and all buffers', () => { router.update('task', 'coder', 1.0); router.route('test', false); router.reset(); const stats = router.getStats(); expect(stats.qTableSize).toBe(0); expect(stats.replayBufferSize).toBe(0); expect(stats.cacheSize).toBe(0); expect(stats.totalExperiences).toBe(0); }); }); describe('invalidateCache', () => { it('should clear route cache', () => { router.route('task', false); router.invalidateCache(); expect(router.getStats().cacheSize).toBe(0); }); }); describe('export / import', () => { it('should export and import Q-table', async () => { router.update('implement feature', 'coder', 1.0); router.update('write tests', 'tester', 0.8); const exported = router.export(); expect(Object.keys(exported).length).toBeGreaterThan(0); const mod = await import('../src/ruvector/q-learning-router.js'); const newRouter = new mod.QLearningRouter(); newRouter.import(exported); expect(newRouter.getStats().qTableSize).toBe(Object.keys(exported).length); }); }); describe('getStats', () => { it('should return comprehensive statistics', () => { const stats = router.getStats(); expect(typeof stats.updateCount).toBe('number'); expect(typeof stats.qTableSize).toBe('number'); expect(typeof stats.epsilon).toBe('number'); expect(typeof stats.avgTDError).toBe('number'); expect(typeof stats.cacheHitRate).toBe('number'); expect(typeof stats.replayBufferSize).toBe('number'); }); }); describe('convergence behavior', () => { it('should learn to prefer rewarded routes', () => { // Train coder for "implement" tasks repeatedly for (let i = 0; i < 50; i++) { router.update('implement new feature module', 'coder', 1.0); } // Exploit mode should prefer coder const decision = router.route('implement new feature module', false); expect(decision.route).toBeDefined(); // The Q-value for coder should be highest for similar contexts const coderIdx = 0; // coder is index 0 in ROUTE_NAMES expect(decision.qValues[coderIdx]).toBeGreaterThan(0); }); }); describe('createQLearningRouter factory', () => { it('should create router with defaults', async () => { const { createQLearningRouter } = await import('../src/ruvector/q-learning-router.js'); const r = createQLearningRouter(); expect(r).toBeDefined(); expect(r.getStats().qTableSize).toBe(0); }); }); }); // ============================================================================= // Service Layer: Agentic Flow Bridge // ============================================================================= describe('Agentic Flow Bridge', () => { it('should export getReasoningBank function', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); expect(typeof bridge.getReasoningBank).toBe('function'); }); it('should export getRouter function', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); expect(typeof bridge.getRouter).toBe('function'); }); it('should export getOrchestration function', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); expect(typeof bridge.getOrchestration).toBe('function'); }); it('should handle missing agentic-flow gracefully in isAvailable', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); const available = await bridge.isAvailable(); expect(typeof available).toBe('boolean'); }); it('should return capabilities summary', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); const caps = await bridge.capabilities(); expect(typeof caps.available).toBe('boolean'); expect(typeof caps.reasoningBank).toBe('boolean'); expect(typeof caps.router).toBe('boolean'); expect(typeof caps.orchestration).toBe('boolean'); }); it('should return embedding or null for computeEmbedding', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); const result = await bridge.computeEmbedding('test'); // If agentic-flow is installed, returns an array-like of numbers (may be Float32Array); // otherwise null if (result !== null && result !== undefined) { // Accept both regular Array and TypedArray (Float32Array, etc.) expect(typeof result.length).toBe('number'); expect(result.length).toBeGreaterThan(0); expect(typeof result[0]).toBe('number'); } else { expect(result).toBeNull(); } }); it('should return array or handle error for retrieveMemories', async () => { const bridge = await import('../src/services/agentic-flow-bridge.js'); try { const result = await bridge.retrieveMemories('test'); // If it succeeds, it returns an array expect(Array.isArray(result)).toBe(true); } catch (err: any) { // If agentic-flow is installed but DB not initialized, it may throw expect(err.message).toMatch(/Database|not initialized|unavailable/i); } }); }); // ============================================================================= // Service Layer: Registry API // ============================================================================= describe('Registry API', () => { it('should export rateItem function', async () => { const api = await import('../src/services/registry-api.js'); expect(typeof api.rateItem).toBe('function'); }); it('should reject invalid item IDs', async () => { const api = await import('../src/services/registry-api.js'); await expect(api.rateItem('invalid<>id', 5)).rejects.toThrow('Invalid item ID'); }); it('should reject invalid ratings', async () => { const api = await import('../src/services/registry-api.js'); await expect(api.rateItem('@claude-flow/test', 0)).rejects.toThrow('Rating must be integer 1-5'); await expect(api.rateItem('@claude-flow/test', 6)).rejects.toThrow('Rating must be integer 1-5'); }); it('should reject non-integer ratings', async () => { const api = await import('../src/services/registry-api.js'); await expect(api.rateItem('@claude-flow/test', 3.5)).rejects.toThrow('Rating must be integer 1-5'); }); it('should accept valid item IDs with scopes', async () => { const api = await import('../src/services/registry-api.js'); // This should not throw on validation, but will fail on network // We just check it passes validation by catching the network error try { await api.getRating('@claude-flow/test-plugin', 'plugin'); } catch (e: any) { // Network error is expected - validation passed expect(e.message).not.toContain('Invalid item ID'); } }); it('should validate bulk rating IDs', async () => { const api = await import('../src/services/registry-api.js'); await expect(api.getBulkRatings(['valid-id', ''])).rejects.toThrow('Invalid item ID'); }); }); // ============================================================================= // RuVector Index Module // ============================================================================= describe('RuVector Index', () => { it('should export all major components', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(ruvector.ASTAnalyzer).toBeDefined(); expect(ruvector.DiffClassifier).toBeDefined(); expect(ruvector.CoverageRouter).toBeDefined(); expect(ruvector.QLearningRouter).toBeDefined(); expect(ruvector.createASTAnalyzer).toBeDefined(); expect(ruvector.createDiffClassifier).toBeDefined(); expect(ruvector.createCoverageRouter).toBeDefined(); expect(ruvector.createQLearningRouter).toBeDefined(); }); it('should export factory functions', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(typeof ruvector.createASTAnalyzer).toBe('function'); expect(typeof ruvector.createDiffClassifier).toBe('function'); expect(typeof ruvector.createCoverageRouter).toBe('function'); expect(typeof ruvector.createQLearningRouter).toBe('function'); }); it('should export isRuvectorAvailable', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(typeof ruvector.isRuvectorAvailable).toBe('function'); const available = await ruvector.isRuvectorAvailable(); expect(typeof available).toBe('boolean'); }); it('should export graph analyzer utilities', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(typeof ruvector.detectCircularDependencies).toBe('function'); expect(typeof ruvector.fallbackMinCut).toBe('function'); expect(typeof ruvector.fallbackLouvain).toBe('function'); expect(typeof ruvector.exportToDot).toBe('function'); }); it('should export diff analysis tools', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(typeof ruvector.assessFileRisk).toBe('function'); expect(typeof ruvector.assessOverallRisk).toBe('function'); expect(typeof ruvector.classifyDiff).toBe('function'); }); it('should export cache control functions', async () => { const ruvector = await import('../src/ruvector/index.js'); expect(typeof ruvector.clearDiffCache).toBe('function'); expect(typeof ruvector.clearAllDiffCaches).toBe('function'); expect(typeof ruvector.clearCoverageCache).toBe('function'); expect(typeof ruvector.clearGraphCaches).toBe('function'); }); }); // ============================================================================= // Additional Edge Cases // ============================================================================= describe('Edge Cases', () => { describe('AST Analyzer with arrow functions', () => { it('should detect arrow function assignments', async () => { const { ASTAnalyzer } = await import('../src/ruvector/ast-analyzer.js'); const analyzer = new ASTAnalyzer(); const code = `const handler = async (req, res) => {\n return res.json({});\n}\n`; const result = analyzer.analyze(code, 'test.ts'); expect(result.functions.some((f: any) => f.name === 'handler')).toBe(true); }); }); describe('Diff Classifier with refactoring detection', () => { it('should detect balanced adds/removes as refactoring', async () => { const { DiffClassifier } = await import('../src/ruvector/diff-classifier.js'); const dc = new DiffClassifier({ detectRefactoring: true }); const diff = `diff --git a/src/foo.ts b/src/foo.ts --- a/src/foo.ts +++ b/src/foo.ts @@ -1,10 +1,10 @@ -const old1 = 1; -const old2 = 2; -const old3 = 3; -const old4 = 4; -const old5 = 5; -const old6 = 6; +const new1 = 1; +const new2 = 2; +const new3 = 3; +const new4 = 4; +const new5 = 5; +const new6 = 6; `; const files = dc.parseDiff(diff); expect(files.length).toBe(1); // The balanced add/remove pattern is a refactor indicator }); }); describe('Coverage Router with changed files', () => { it('should prioritize changed files in routing', async () => { const { CoverageRouter } = await import('../src/ruvector/coverage-router.js'); const cr = new CoverageRouter(); const coverage = { overall: 75, byType: { line: 75, branch: 70, function: 80, statement: 75 }, byFile: [ { path: 'src/auth.ts', lineCoverage: 40, branchCoverage: 30, functionCoverage: 50, statementCoverage: 40, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 40 }, { path: 'src/utils.ts', lineCoverage: 90, branchCoverage: 85, functionCoverage: 95, statementCoverage: 90, uncoveredLines: [], totalLines: 50, coveredLines: 45 }, ], lowestCoverage: [{ path: 'src/auth.ts', lineCoverage: 40, branchCoverage: 30, functionCoverage: 50, statementCoverage: 40, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 40 }], highestCoverage: [{ path: 'src/utils.ts', lineCoverage: 90, branchCoverage: 85, functionCoverage: 95, statementCoverage: 90, uncoveredLines: [], totalLines: 50, coveredLines: 45 }], uncoveredCritical: ['src/auth.ts'], timestamp: Date.now(), }; const result = cr.route(coverage, ['src/auth.ts']); expect(result.targetFiles).toContain('src/auth.ts'); }); }); describe('Q-Learning exploration decay types', () => { it('should support linear decay', async () => { const { QLearningRouter } = await import('../src/ruvector/q-learning-router.js'); const r = new QLearningRouter({ explorationDecayType: 'linear', explorationInitial: 1.0, explorationFinal: 0.1, explorationDecay: 10, autoSaveInterval: 0, }); for (let i = 0; i < 10; i++) r.update('task', 'coder', 1.0); expect(r.getStats().epsilon).toBeLessThan(1.0); }); it('should support cosine decay', async () => { const { QLearningRouter } = await import('../src/ruvector/q-learning-router.js'); const r = new QLearningRouter({ explorationDecayType: 'cosine', explorationInitial: 1.0, explorationFinal: 0.1, explorationDecay: 10, autoSaveInterval: 0, }); for (let i = 0; i < 5; i++) r.update('task', 'coder', 1.0); expect(r.getStats().epsilon).toBeLessThan(1.0); expect(r.getStats().epsilon).toBeGreaterThan(0); }); }); describe.skipIf(__SKIP_WASM_TESTS)('SONA Optimizer keyword extraction', () => { it('should extract architecture keywords', async () => { const { SONAOptimizer } = await import('../src/memory/sona-optimizer.js'); const opt = new SONAOptimizer({ persistencePath: '/tmp/sona-kw-test.json' }); const result = opt.processTrajectoryOutcome({ trajectoryId: 'kw1', task: 'design the database schema architecture', agent: 'architect', success: true, }); expect(result.keywordsExtracted).toContain('design'); expect(result.keywordsExtracted).toContain('database'); expect(result.keywordsExtracted).toContain('schema'); expect(result.keywordsExtracted).toContain('architecture'); }); }); describe('EWC resetFisher', () => { it('should zero out Fisher matrix', async () => { const { EWCConsolidator } = await import('../src/memory/ewc-consolidation.js'); const c = new EWCConsolidator({ dimensions: 4, storagePath: '/tmp/ewc-rf-test.json' }); c.computeFisherMatrix([{ id: 'x', embedding: [1, 1, 1, 1], success: true }]); c.resetFisher(); const stats = c.getConsolidationStats(); expect(stats.avgFisherValue).toBe(0); }); }); });