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>
407 lines
12 KiB
TypeScript
407 lines
12 KiB
TypeScript
/**
|
|
* Unit tests for Memory Coherence Auditor
|
|
* ADR-052 Phase 3 Action A3.2
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import {
|
|
MemoryCoherenceAuditor,
|
|
createMemoryAuditor,
|
|
DEFAULT_AUDITOR_CONFIG,
|
|
type MemoryAuditResult,
|
|
type PatternHotspot,
|
|
type AuditRecommendation,
|
|
} from '../../../src/learning/memory-auditor.js';
|
|
import type { CoherenceService, CoherenceResult } from '../../../src/integrations/coherence/index.js';
|
|
import type { QEPattern } from '../../../src/learning/qe-patterns.js';
|
|
import type { EventBus } from '../../../src/kernel/interfaces.js';
|
|
|
|
describe('MemoryCoherenceAuditor', () => {
|
|
let mockCoherenceService: CoherenceService;
|
|
let mockEventBus: EventBus;
|
|
let auditor: MemoryCoherenceAuditor;
|
|
|
|
// Sample test patterns
|
|
const createTestPattern = (
|
|
id: string,
|
|
domain: string,
|
|
qualityScore: number,
|
|
usageCount: number,
|
|
successRate: number
|
|
): QEPattern => ({
|
|
id,
|
|
patternType: 'test-template',
|
|
qeDomain: domain as any,
|
|
domain: domain as any,
|
|
name: `Pattern ${id}`,
|
|
description: `Test pattern ${id}`,
|
|
confidence: 0.8,
|
|
usageCount,
|
|
successRate,
|
|
qualityScore,
|
|
context: {
|
|
language: 'typescript',
|
|
framework: 'vitest',
|
|
testType: 'unit',
|
|
tags: ['test', 'pattern'],
|
|
},
|
|
template: {
|
|
type: 'code',
|
|
content: 'test code',
|
|
variables: [],
|
|
},
|
|
embedding: Array(64).fill(0).map(() => Math.random()),
|
|
tier: 'short-term',
|
|
createdAt: new Date(),
|
|
lastUsedAt: new Date(),
|
|
successfulUses: 5,
|
|
reusable: true,
|
|
reuseCount: 0,
|
|
averageTokenSavings: 0,
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// Mock coherence service
|
|
mockCoherenceService = {
|
|
checkCoherence: vi.fn().mockResolvedValue({
|
|
energy: 0.3,
|
|
isCoherent: true,
|
|
lane: 'reflex',
|
|
contradictions: [],
|
|
recommendations: [],
|
|
durationMs: 10,
|
|
usedFallback: false,
|
|
} as CoherenceResult),
|
|
isInitialized: vi.fn().mockReturnValue(true),
|
|
} as any;
|
|
|
|
// Mock event bus
|
|
mockEventBus = {
|
|
publish: vi.fn().mockResolvedValue(undefined),
|
|
} as any;
|
|
|
|
auditor = createMemoryAuditor(mockCoherenceService, mockEventBus);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('auditPatterns', () => {
|
|
it('should audit patterns and return results', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
createTestPattern('p2', 'test-generation', 0.7, 8, 0.85),
|
|
createTestPattern('p3', 'coverage-analysis', 0.9, 15, 0.95),
|
|
];
|
|
|
|
const result = await auditor.auditPatterns(patterns);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.totalPatterns).toBe(3);
|
|
expect(result.scannedPatterns).toBe(3);
|
|
expect(result.globalEnergy).toBeGreaterThanOrEqual(0);
|
|
expect(result.timestamp).toBeInstanceOf(Date);
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_started',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should detect high-energy hotspots', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
createTestPattern('p2', 'test-generation', 0.7, 8, 0.85),
|
|
];
|
|
|
|
// Mock high energy response
|
|
vi.mocked(mockCoherenceService.checkCoherence).mockResolvedValue({
|
|
energy: 0.8, // Above hotspot threshold
|
|
isCoherent: false,
|
|
lane: 'human',
|
|
contradictions: [
|
|
{
|
|
nodeIds: ['p1', 'p2'],
|
|
severity: 'high',
|
|
description: 'Test contradiction',
|
|
confidence: 0.9,
|
|
},
|
|
],
|
|
recommendations: ['Review patterns'],
|
|
durationMs: 10,
|
|
usedFallback: false,
|
|
});
|
|
|
|
const result = await auditor.auditPatterns(patterns);
|
|
|
|
expect(result.contradictionCount).toBeGreaterThan(0);
|
|
expect(result.globalEnergy).toBeGreaterThan(0.6);
|
|
expect(result.hotspots.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should emit audit events', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
await auditor.auditPatterns(patterns);
|
|
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_started',
|
|
source: 'learning-optimization',
|
|
})
|
|
);
|
|
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_completed',
|
|
source: 'learning-optimization',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle audit failures gracefully', async () => {
|
|
// Need multiple patterns in same domain to trigger coherence check
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
createTestPattern('p2', 'test-generation', 0.7, 8, 0.85),
|
|
];
|
|
|
|
vi.mocked(mockCoherenceService.checkCoherence).mockRejectedValue(
|
|
new Error('Coherence check failed')
|
|
);
|
|
|
|
await expect(auditor.auditPatterns(patterns)).rejects.toThrow('Coherence check failed');
|
|
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_failed',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('identifyHotspots', () => {
|
|
it('should identify domains with high energy', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
createTestPattern('p2', 'test-generation', 0.7, 8, 0.85),
|
|
];
|
|
|
|
vi.mocked(mockCoherenceService.checkCoherence).mockResolvedValue({
|
|
energy: 0.7, // Above hotspot threshold
|
|
isCoherent: false,
|
|
lane: 'heavy',
|
|
contradictions: [],
|
|
recommendations: [],
|
|
durationMs: 10,
|
|
usedFallback: false,
|
|
});
|
|
|
|
const hotspots = await auditor.identifyHotspots(patterns);
|
|
|
|
expect(hotspots).toHaveLength(1);
|
|
expect(hotspots[0].domain).toBe('test-generation');
|
|
expect(hotspots[0].energy).toBeGreaterThan(0.6);
|
|
expect(hotspots[0].patternIds).toContain('p1');
|
|
expect(hotspots[0].patternIds).toContain('p2');
|
|
});
|
|
|
|
it('should skip single-pattern domains', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
createTestPattern('p2', 'coverage-analysis', 0.7, 8, 0.85),
|
|
];
|
|
|
|
const hotspots = await auditor.identifyHotspots(patterns);
|
|
|
|
// No hotspots because each domain has only 1 pattern
|
|
expect(mockCoherenceService.checkCoherence).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('generateRecommendations', () => {
|
|
it('should recommend merging duplicate patterns', async () => {
|
|
// Create patterns with identical normalized names/descriptions
|
|
const p1 = createTestPattern('p1', 'test-generation', 0.8, 10, 0.9);
|
|
const p2 = createTestPattern('p2', 'test-generation', 0.7, 8, 0.85);
|
|
|
|
// Make p2 a duplicate of p1 by using same name/description
|
|
const patterns: QEPattern[] = [
|
|
p1,
|
|
{ ...p2, name: p1.name, description: p1.description },
|
|
];
|
|
|
|
const hotspots: PatternHotspot[] = [
|
|
{
|
|
domain: 'test-generation',
|
|
patternIds: ['p1', 'p2'],
|
|
energy: 0.7,
|
|
description: 'High energy in test-generation',
|
|
},
|
|
];
|
|
|
|
const recommendations = await auditor.generateRecommendations(hotspots, patterns);
|
|
|
|
// Should have recommendations
|
|
expect(recommendations.length).toBeGreaterThan(0);
|
|
|
|
// Should recommend merging duplicates
|
|
const mergeRec = recommendations.find(r => r.type === 'merge');
|
|
expect(mergeRec).toBeDefined();
|
|
});
|
|
|
|
it('should recommend removing outdated patterns', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.3, 2, 0.3), // Low usage, low success
|
|
createTestPattern('p2', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
const hotspots: PatternHotspot[] = [
|
|
{
|
|
domain: 'test-generation',
|
|
patternIds: ['p1', 'p2'],
|
|
energy: 0.5,
|
|
description: 'Moderate energy',
|
|
},
|
|
];
|
|
|
|
const recommendations = await auditor.generateRecommendations(hotspots, patterns);
|
|
|
|
const removeRec = recommendations.find(r => r.type === 'remove');
|
|
expect(removeRec).toBeDefined();
|
|
expect(removeRec?.patternIds).toContain('p1');
|
|
});
|
|
|
|
it('should recommend reviewing critical energy patterns', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
const hotspots: PatternHotspot[] = [
|
|
{
|
|
domain: 'test-generation',
|
|
patternIds: ['p1'],
|
|
energy: 0.8, // Critical energy
|
|
description: 'Critical energy',
|
|
},
|
|
];
|
|
|
|
const recommendations = await auditor.generateRecommendations(hotspots, patterns);
|
|
|
|
const reviewRec = recommendations.find(r => r.type === 'review');
|
|
expect(reviewRec).toBeDefined();
|
|
expect(reviewRec?.priority).toBe('high');
|
|
});
|
|
|
|
it('should limit recommendations to maxRecommendations', async () => {
|
|
const patterns: QEPattern[] = Array(20)
|
|
.fill(null)
|
|
.map((_, i) => createTestPattern(`p${i}`, 'test-generation', 0.5, 3, 0.4));
|
|
|
|
const hotspots: PatternHotspot[] = [
|
|
{
|
|
domain: 'test-generation',
|
|
patternIds: patterns.map(p => p.id),
|
|
energy: 0.8,
|
|
description: 'Many patterns',
|
|
},
|
|
];
|
|
|
|
const recommendations = await auditor.generateRecommendations(hotspots, patterns);
|
|
|
|
expect(recommendations.length).toBeLessThanOrEqual(DEFAULT_AUDITOR_CONFIG.maxRecommendations);
|
|
});
|
|
});
|
|
|
|
describe('runBackgroundAudit', () => {
|
|
it('should run background audit without blocking', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
const patternSource = vi.fn().mockResolvedValue(patterns);
|
|
|
|
await auditor.runBackgroundAudit(patternSource);
|
|
|
|
expect(patternSource).toHaveBeenCalled();
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_progress',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should skip if audit already in progress', async () => {
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
const slowSource = vi.fn().mockImplementation(
|
|
() => new Promise(resolve => setTimeout(() => resolve(patterns), 100))
|
|
);
|
|
|
|
// Start first audit (don't await)
|
|
const promise1 = auditor.runBackgroundAudit(slowSource);
|
|
|
|
// Try to start second audit immediately
|
|
await auditor.runBackgroundAudit(slowSource);
|
|
|
|
// Wait for first to complete
|
|
await promise1;
|
|
|
|
// Source should only be called once
|
|
expect(slowSource).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle errors in background audit', async () => {
|
|
const errorSource = vi.fn().mockRejectedValue(new Error('Source failed'));
|
|
|
|
await auditor.runBackgroundAudit(errorSource);
|
|
|
|
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'memory:audit_progress',
|
|
payload: expect.objectContaining({
|
|
status: 'failed',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('configuration', () => {
|
|
it('should use custom configuration', () => {
|
|
const customConfig = {
|
|
batchSize: 100,
|
|
energyThreshold: 0.5,
|
|
hotspotThreshold: 0.7,
|
|
maxRecommendations: 20,
|
|
};
|
|
|
|
const customAuditor = createMemoryAuditor(
|
|
mockCoherenceService,
|
|
mockEventBus,
|
|
customConfig
|
|
);
|
|
|
|
expect(customAuditor).toBeInstanceOf(MemoryCoherenceAuditor);
|
|
});
|
|
|
|
it('should work without event bus', async () => {
|
|
const auditorNoEvents = createMemoryAuditor(mockCoherenceService);
|
|
|
|
const patterns: QEPattern[] = [
|
|
createTestPattern('p1', 'test-generation', 0.8, 10, 0.9),
|
|
];
|
|
|
|
const result = await auditorNoEvents.auditPatterns(patterns);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.totalPatterns).toBe(1);
|
|
});
|
|
});
|
|
});
|