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

553 lines
18 KiB
TypeScript

/**
* Agentic QE v3 - Metrics Optimizer Service Unit Tests
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { MetricsOptimizerService, MetricsSnapshot } from '../../../../src/domains/learning-optimization/services/metrics-optimizer';
import { MemoryBackend, StoreOptions, VectorSearchResult } from '../../../../src/kernel/interfaces';
import {
Experience,
Strategy,
OptimizationObjective,
ABTestConfig,
PatternContext,
} from '../../../../src/domains/learning-optimization/interfaces';
import { DomainName, AgentId } from '../../../../src/shared/types';
// Mock MemoryBackend
function createMockMemoryBackend(): MemoryBackend {
const storage = new Map<string, unknown>();
return {
initialize: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
set: vi.fn().mockImplementation(async (key: string, value: unknown, _options?: StoreOptions) => {
storage.set(key, value);
}),
get: vi.fn().mockImplementation(async <T>(key: string): Promise<T | undefined> => {
return storage.get(key) as T | undefined;
}),
delete: vi.fn().mockImplementation(async (key: string) => {
return storage.delete(key);
}),
has: vi.fn().mockImplementation(async (key: string) => {
return storage.has(key);
}),
search: vi.fn().mockImplementation(async (pattern: string, _limit?: number) => {
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
return Array.from(storage.keys()).filter((key) => regex.test(key));
}),
vectorSearch: vi.fn().mockResolvedValue([] as VectorSearchResult[]),
storeVector: vi.fn().mockResolvedValue(undefined),
};
}
// Helper to create test experiences
function createTestExperience(overrides: Partial<Experience> = {}): Experience {
const agentId: AgentId = {
value: overrides.agentId?.value || 'test-agent-1',
domain: overrides.agentId?.domain || 'test-generation',
type: overrides.agentId?.type || 'generator',
};
return {
id: overrides.id || 'exp-1',
agentId,
domain: overrides.domain || 'test-generation',
action: overrides.action || 'generate-test',
state: overrides.state || {
context: { concurrency: 4, retryCount: 3 },
metrics: { coverage: 80, duration: 1000 },
},
result: overrides.result || {
success: true,
outcome: { coverage: 85, success_rate: 0.9 },
duration: 1000,
},
reward: overrides.reward ?? 0.8,
timestamp: overrides.timestamp || new Date(),
};
}
// Helper to create test strategy
function createTestStrategy(overrides: Partial<Strategy> = {}): Strategy {
return {
name: overrides.name || 'test-strategy',
parameters: overrides.parameters || { concurrency: 4, retryCount: 3, timeout: 30000 },
expectedOutcome: overrides.expectedOutcome || { success_rate: 0.85, coverage: 80 },
};
}
describe('MetricsOptimizerService', () => {
let service: MetricsOptimizerService;
let mockMemory: MemoryBackend;
beforeEach(() => {
mockMemory = createMockMemoryBackend();
service = new MetricsOptimizerService(mockMemory);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('optimizeStrategy', () => {
it('should optimize strategy with sufficient experiences', async () => {
const strategy = createTestStrategy();
const objective: OptimizationObjective = {
metric: 'coverage',
direction: 'maximize',
constraints: [],
};
const experiences = Array.from({ length: 25 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
result: {
success: true,
outcome: { coverage: 80 + Math.random() * 10 },
duration: 1000,
},
})
);
const result = await service.optimizeStrategy(strategy, objective, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.objective).toEqual(objective);
expect(result.value.currentStrategy).toEqual(strategy);
expect(result.value).toHaveProperty('improvement');
expect(result.value).toHaveProperty('confidence');
}
});
it('should reject optimization with insufficient experiences', async () => {
const strategy = createTestStrategy();
const objective: OptimizationObjective = {
metric: 'coverage',
direction: 'maximize',
constraints: [],
};
const experiences = Array.from({ length: 5 }, (_, i) =>
createTestExperience({ id: `exp-${i}` })
);
const result = await service.optimizeStrategy(strategy, objective, experiences);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.message).toContain('at least');
}
});
it('should store optimization result', async () => {
const strategy = createTestStrategy();
const objective: OptimizationObjective = {
metric: 'coverage',
direction: 'maximize',
constraints: [],
};
const experiences = Array.from({ length: 25 }, (_, i) =>
createTestExperience({ id: `exp-${i}` })
);
await service.optimizeStrategy(strategy, objective, experiences);
expect(mockMemory.set).toHaveBeenCalled();
const calls = (mockMemory.set as ReturnType<typeof vi.fn>).mock.calls;
const optimizedCall = calls.find((c) => (c[0] as string).includes('strategy:optimized'));
expect(optimizedCall).toBeDefined();
});
});
describe('runABTest', () => {
it('should run A/B test between two strategies', async () => {
const strategyA = createTestStrategy({ name: 'strategy-A' });
const strategyB = createTestStrategy({ name: 'strategy-B' });
const testConfig: ABTestConfig = {
duration: 3600000,
minSamples: 10,
confidenceLevel: 0.95,
metric: 'success_rate',
};
const result = await service.runABTest(strategyA, strategyB, testConfig);
expect(result.success).toBe(true);
if (result.success) {
expect(['A', 'B', 'inconclusive']).toContain(result.value.winner);
expect(result.value).toHaveProperty('strategyAMetrics');
expect(result.value).toHaveProperty('strategyBMetrics');
expect(result.value).toHaveProperty('pValue');
}
});
it('should calculate p-value correctly', async () => {
const strategyA = createTestStrategy({
name: 'strategy-A',
expectedOutcome: { success_rate: 0.9 },
});
const strategyB = createTestStrategy({
name: 'strategy-B',
expectedOutcome: { success_rate: 0.5 },
});
const testConfig: ABTestConfig = {
duration: 3600000,
minSamples: 20,
confidenceLevel: 0.95,
metric: 'success_rate',
};
const result = await service.runABTest(strategyA, strategyB, testConfig);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.pValue).toBeGreaterThanOrEqual(0);
expect(result.value.pValue).toBeLessThanOrEqual(1);
}
});
it('should store A/B test result', async () => {
const strategyA = createTestStrategy({ name: 'strategy-A' });
const strategyB = createTestStrategy({ name: 'strategy-B' });
const testConfig: ABTestConfig = {
duration: 3600000,
minSamples: 10,
confidenceLevel: 0.95,
metric: 'success_rate',
};
await service.runABTest(strategyA, strategyB, testConfig);
expect(mockMemory.set).toHaveBeenCalled();
const calls = (mockMemory.set as ReturnType<typeof vi.fn>).mock.calls;
const abTestCall = calls.find((c) => (c[0] as string).includes('abtest'));
expect(abTestCall).toBeDefined();
});
});
describe('recommendStrategy', () => {
it('should return default strategy when no optimized strategies exist', async () => {
const context: PatternContext = {
language: 'typescript',
framework: 'vitest',
tags: [],
};
const result = await service.recommendStrategy(context);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.name).toBe('default-strategy');
expect(result.value.parameters).toHaveProperty('timeout');
}
});
it('should include framework in default parameters when provided', async () => {
const context: PatternContext = {
language: 'typescript',
framework: 'vitest',
tags: [],
};
const result = await service.recommendStrategy(context);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.parameters['framework']).toBe('vitest');
}
});
it('should include language in default parameters when provided', async () => {
const context: PatternContext = {
language: 'python',
tags: [],
};
const result = await service.recommendStrategy(context);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.parameters['language']).toBe('python');
}
});
});
describe('evaluateStrategy', () => {
it('should evaluate strategy performance', async () => {
const strategy = createTestStrategy();
const experiences = Array.from({ length: 10 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
result: {
success: i < 8, // 80% success rate
outcome: { coverage: 80 },
duration: 3000,
},
reward: i < 8 ? 0.8 : 0.2,
})
);
const result = await service.evaluateStrategy(strategy, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.strategy).toEqual(strategy);
expect(result.value.metrics['success_rate']).toBe(0.8);
expect(result.value).toHaveProperty('strengths');
expect(result.value).toHaveProperty('weaknesses');
expect(result.value).toHaveProperty('improvementAreas');
}
});
it('should identify high success rate as strength', async () => {
const strategy = createTestStrategy();
const experiences = Array.from({ length: 10 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
result: { success: true, outcome: {}, duration: 1000 },
reward: 0.9,
})
);
const result = await service.evaluateStrategy(strategy, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.strengths.some((s) => s.includes('success rate'))).toBe(true);
}
});
it('should identify low success rate as weakness', async () => {
const strategy = createTestStrategy();
const experiences = Array.from({ length: 10 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
result: { success: i < 3, outcome: {}, duration: 1000 }, // 30% success
reward: 0.3,
})
);
const result = await service.evaluateStrategy(strategy, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.weaknesses.some((w) => w.includes('success rate'))).toBe(true);
}
});
it('should store evaluation result', async () => {
const strategy = createTestStrategy();
const experiences = [createTestExperience()];
await service.evaluateStrategy(strategy, experiences);
expect(mockMemory.set).toHaveBeenCalled();
const calls = (mockMemory.set as ReturnType<typeof vi.fn>).mock.calls;
const evalCall = calls.find((c) => (c[0] as string).includes('evaluation'));
expect(evalCall).toBeDefined();
});
});
describe('trackMetrics', () => {
it('should track metrics for a strategy', async () => {
const result = await service.trackMetrics('strategy-1', {
success_rate: 0.85,
avg_duration: 2000,
});
expect(result.success).toBe(true);
expect(mockMemory.set).toHaveBeenCalled();
});
it('should update aggregated metrics', async () => {
await service.trackMetrics('strategy-1', { success_rate: 0.85 });
await service.trackMetrics('strategy-1', { success_rate: 0.90 });
expect(mockMemory.set).toHaveBeenCalled();
const calls = (mockMemory.set as ReturnType<typeof vi.fn>).mock.calls;
const aggregatedCalls = calls.filter((c) => (c[0] as string).includes('aggregated'));
expect(aggregatedCalls.length).toBeGreaterThanOrEqual(2);
});
});
describe('getMetricsHistory', () => {
it('should retrieve metrics history for a strategy', async () => {
const snapshot: MetricsSnapshot = {
strategyId: 'strategy-1',
metrics: { success_rate: 0.85 },
timestamp: new Date(),
samples: 1,
};
await mockMemory.set('learning:metrics:history:strategy-1:123456', snapshot);
const result = await service.getMetricsHistory('strategy-1');
expect(result.success).toBe(true);
});
it('should respect limit parameter', async () => {
for (let i = 0; i < 10; i++) {
const snapshot: MetricsSnapshot = {
strategyId: 'strategy-1',
metrics: { success_rate: 0.8 + i * 0.01 },
timestamp: new Date(Date.now() + i * 1000),
samples: 1,
};
await mockMemory.set(`learning:metrics:history:strategy-1:${i}`, snapshot);
}
const result = await service.getMetricsHistory('strategy-1', 5);
expect(result.success).toBe(true);
});
it('should sort snapshots by timestamp', async () => {
const now = Date.now();
const snapshot1: MetricsSnapshot = {
strategyId: 'strategy-1',
metrics: { value: 1 },
timestamp: new Date(now - 2000),
samples: 1,
};
const snapshot2: MetricsSnapshot = {
strategyId: 'strategy-1',
metrics: { value: 2 },
timestamp: new Date(now),
samples: 1,
};
await mockMemory.set('learning:metrics:history:strategy-1:1', snapshot1);
await mockMemory.set('learning:metrics:history:strategy-1:2', snapshot2);
const result = await service.getMetricsHistory('strategy-1');
expect(result.success).toBe(true);
if (result.success && result.value.length >= 2) {
expect(result.value[0].timestamp.getTime()).toBeLessThanOrEqual(
result.value[1].timestamp.getTime()
);
}
});
});
describe('gridSearchOptimize', () => {
it('should find best parameters through grid search', async () => {
const parameterRanges = {
concurrency: [2, 4, 8],
retryCount: [1, 2, 3],
};
const objective: OptimizationObjective = {
metric: 'success_rate',
direction: 'maximize',
constraints: [],
};
const experiences = Array.from({ length: 20 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
state: {
context: { concurrency: 4, retryCount: 2 },
metrics: {},
},
result: {
success: true,
outcome: { success_rate: 0.85 },
duration: 1000,
},
})
);
const result = await service.gridSearchOptimize(parameterRanges, objective, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect([2, 4, 8]).toContain(result.value['concurrency']);
expect([1, 2, 3]).toContain(result.value['retryCount']);
}
});
it('should return empty object for empty parameter ranges', async () => {
const objective: OptimizationObjective = {
metric: 'success_rate',
direction: 'maximize',
constraints: [],
};
const experiences = [createTestExperience()];
const result = await service.gridSearchOptimize({}, objective, experiences);
expect(result.success).toBe(true);
if (result.success) {
expect(Object.keys(result.value).length).toBe(0);
}
});
it('should handle minimize direction', async () => {
const parameterRanges = {
timeout: [1000, 5000, 10000],
};
const objective: OptimizationObjective = {
metric: 'duration',
direction: 'minimize',
constraints: [],
};
const experiences = Array.from({ length: 20 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
state: { context: { timeout: 5000 }, metrics: {} },
result: { success: true, outcome: { duration: 2000 }, duration: 2000 },
})
);
const result = await service.gridSearchOptimize(parameterRanges, objective, experiences);
expect(result.success).toBe(true);
});
});
describe('edge cases', () => {
it('should handle empty experiences array in evaluateStrategy', async () => {
const strategy = createTestStrategy();
const result = await service.evaluateStrategy(strategy, []);
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.metrics['success_rate']).toBe(0);
}
});
it('should handle experiences with missing metric values', async () => {
const strategy = createTestStrategy();
const objective: OptimizationObjective = {
metric: 'non_existent_metric',
direction: 'maximize',
constraints: [],
};
const experiences = Array.from({ length: 25 }, (_, i) =>
createTestExperience({
id: `exp-${i}`,
result: { success: true, outcome: {}, duration: 1000 },
})
);
const result = await service.optimizeStrategy(strategy, objective, experiences);
expect(result.success).toBe(true);
});
});
});