Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

1685 lines
65 KiB
TypeScript

/**
* 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', '<invalid>'])).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);
});
});
});