mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
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>
222 lines
7.3 KiB
TypeScript
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);
|
|
});
|
|
});
|