Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

222 lines
7.3 KiB
TypeScript

/**
* @ruvector Wrapper Integration Tests
*
* Real integration tests for QE wrappers that delegate to @ruvector packages.
* These tests require native ARM64 binaries to be built and available.
*
* Prerequisites:
* - Rust toolchain installed
* - ARM64 binaries built from ruvector source
* - See: src/integrations/ruvector/TESTING_LIMITATIONS.md
*/
import { describe, it, expect, beforeAll } from 'vitest';
import {
QEGNNEmbeddingIndex,
initGNN,
toNumberArray as toNumberArrayGNN,
toIEmbedding,
batchDifferentiableSearch,
checkRuvectorPackagesAvailable,
} from '../../../src/integrations/ruvector/wrappers';
// Skip tests if packages aren't available
const canTest = checkRuvectorPackagesAvailable();
// Skip tests if packages not available - see TESTING_LIMITATIONS.md for build instructions
describe.runIf(canTest.gnn)('@ruvector/gnn Wrapper - Real Integration', () => {
describe('Package Availability', () => {
it('should verify GNN package is available', () => {
expect(canTest.gnn).toBe(true);
});
it('should initialize GNN package', () => {
const result = initGNN();
expect(result).toContain('initialized');
});
});
describe('QEGNNEmbeddingIndex - Real Operations', () => {
let index: QEGNNEmbeddingIndex;
beforeAll(() => {
index = new QEGNNEmbeddingIndex({
dimension: 384,
M: 16,
efConstruction: 200,
});
});
it('should initialize index for code namespace', () => {
index.initializeIndex('code');
expect(index.isInitialized('code')).toBe(true);
});
it('should add embeddings and get stats', () => {
index.initializeIndex('code');
const embedding = toIEmbedding(
[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
'code'
);
const id = index.addEmbedding(embedding);
expect(id).toBeGreaterThanOrEqual(0);
const stats = index.getIndexStats('code');
expect(stats).toBeDefined();
expect(stats?.size).toBe(1);
});
it('should search for similar embeddings', () => {
index.initializeIndex('code');
// Add multiple embeddings
for (let i = 0; i < 10; i++) {
const vec = Array.from({ length: 8 }, (_, j) => (i * 0.1) + (j * 0.01));
index.addEmbedding(toIEmbedding(vec, 'code'));
}
// Search
const query = toIEmbedding([0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57], 'code');
const results = index.search(query, { namespace: 'code', limit: 5 });
expect(results).toBeDefined();
expect(results.length).toBeGreaterThan(0);
expect(results[0]).toHaveProperty('id');
expect(results[0]).toHaveProperty('distance');
});
it('should perform differentiable search with soft weights', () => {
index.initializeIndex('code');
// Add candidates
const candidates = [
{ id: 0, embedding: toIEmbedding([0.1, 0.2, 0.3, 0.4], 'code') },
{ id: 1, embedding: toIEmbedding([0.5, 0.6, 0.7, 0.8], 'code') },
{ id: 2, embedding: toIEmbedding([0.9, 1.0, 1.1, 1.2], 'code') },
];
candidates.forEach((c) => {
index.addEmbedding(c.embedding);
});
// Differentiable search
const query = toIEmbedding([0.5, 0.6, 0.7, 0.8], 'code');
const result = index.differentiableSearchWithWeights(query, candidates, 3, 1.0);
expect(result).toBeDefined();
expect(result.indices).toHaveLength(3);
expect(result.weights).toHaveLength(3);
expect(result.weights.reduce((a, b) => a + b, 0)).toBeCloseTo(1.0, 1);
});
it('should compress and decompress embeddings', () => {
index.initializeIndex('code');
const original = toIEmbedding([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], 'code');
// Compress with high access frequency (no compression)
const compressedHot = index.compressEmbedding(original, 0.9);
expect(compressedHot.level).toBeDefined();
// Compress with low access frequency (high compression)
const compressedCold = index.compressEmbedding(original, 0.1);
expect(compressedCold.level).toBeDefined();
// Decompress
const decompressed = index.decompressEmbedding(compressedCold);
expect(decompressed).toBeDefined();
expect(decompressed.namespace).toBe('code');
});
it('should get configuration', () => {
const config = index.getConfig();
expect(config).toBeDefined();
expect(config.dimension).toBe(384);
expect(config.M).toBe(16);
expect(config.efConstruction).toBe(200);
});
it('should clear index', () => {
index.clearAll(); // Clear any previous state
index.initializeIndex('test-clear');
// Use test-clear namespace to match the initialized index
index.addEmbedding(toIEmbedding([0.1, 0.2], 'test-clear'));
expect(index.getSize('test-clear')).toBe(1);
index.clearIndex('test-clear');
expect(index.getSize('test-clear')).toBe(0);
});
});
describe('Utility Functions', () => {
it('should convert to number array', () => {
const embedding = toIEmbedding([0.1, 0.2, 0.3, 0.4], 'code');
// Convert manually since toNumberArray is a re-export
const arr = Array.from(embedding.vector);
expect(Array.isArray(arr)).toBe(true);
expect(arr).toEqual([0.1, 0.2, 0.3, 0.4]);
});
it('should create IEmbedding from vector', () => {
const emb = toIEmbedding([0.1, 0.2, 0.3], 'test');
expect(emb.namespace).toBe('test');
expect(emb.dimension).toBe(3);
expect(Array.from(emb.vector)).toEqual([0.1, 0.2, 0.3]);
});
it('should batch differentiable search', () => {
const queries = [[0.1, 0.2], [0.5, 0.6]];
const candidates = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]];
const results = batchDifferentiableSearch(queries, candidates, 2, 1.0);
expect(results).toHaveLength(2);
expect(results[0]).toHaveProperty('indices');
expect(results[0]).toHaveProperty('weights');
});
});
describe('GNN Layer Operations', () => {
let index: QEGNNEmbeddingIndex;
beforeAll(() => {
index = new QEGNNEmbeddingIndex({ dimension: 4 });
index.initializeIndex('gnn-layer');
});
it.skip('should perform hierarchical forward pass', () => {
// NOTE: Skipped due to native binding limitation
// The @ruvector/gnn hierarchicalForward expects native RuvectorLayer objects
// but JSON serialization/deserialization doesn't work properly.
// This is a limitation of the native binding, not the wrapper.
const query = [0.1, 0.2, 0.3, 0.4];
const layerEmbeddings = [
toIEmbedding([0.1, 0.2, 0.3, 0.4], 'code'),
toIEmbedding([0.2, 0.3, 0.4, 0.5], 'code'),
];
// RuvectorLayer requires: input_dim, hidden_dim, heads, dropout
const layerConfigs = [
{ inputDim: 4, hiddenDim: 4, heads: 1, dropout: 0.1 },
{ inputDim: 4, hiddenDim: 4, heads: 1, dropout: 0.1 },
];
const result = index.hierarchicalForward(query, layerEmbeddings, layerConfigs);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(4);
});
});
});
describe.runIf(canTest.all)('@ruvector Wrappers - Cross Package Integration', () => {
it('should verify all @ruvector packages are available', () => {
expect(checkRuvectorPackagesAvailable().all).toBe(true);
});
});