/** * Unit Tests — evolution-memory.js */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import fs from 'fs'; import path from 'path'; const { EvolutionMemory, MEMORY_FILE, MAX_RECORDS } = require('../../src/core/evolution-memory'); describe('EvolutionMemory', () => { const TMP = '/tmp/test-evolution-memory'; const SKILL_DIR = path.join(TMP, 'test-skill'); beforeAll(() => fs.mkdirSync(SKILL_DIR, { recursive: true })); afterAll(() => { try { fs.rmSync(TMP, { recursive: true }); } catch {} }); beforeEach(() => { // 清理 memory 文件 const fp = path.join(SKILL_DIR, MEMORY_FILE); try { fs.unlinkSync(fp); } catch {} }); describe('getHistory', () => { it('should return empty history for new skill', () => { const memory = new EvolutionMemory(SKILL_DIR); const history = memory.getHistory(); expect(history.iterations).toEqual([]); expect(history.bestScore).toBeNull(); expect(history.totalAttempts).toBe(0); }); }); describe('record', () => { it('should record an iteration', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, strategy: { approach: 'full', steps: ['hunt', 'digest', 'skill_create'] }, score: 72, weakDimensions: ['accuracy', 'robustness'], lessonsLearned: ['需要更详细的 hunt'], }); const history = memory.getHistory(); expect(history.iterations).toHaveLength(1); expect(history.iterations[0].score).toBe(72); expect(history.totalAttempts).toBe(1); expect(history.bestScore).toBe(72); }); it('should track bestScore across multiple records', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, strategy: { approach: 'full' }, score: 60 }); memory.record({ iteration: 1, strategy: { approach: 'refine' }, score: 85 }); memory.record({ iteration: 2, strategy: { approach: 'refine' }, score: 78 }); const history = memory.getHistory(); expect(history.bestScore).toBe(85); expect(history.totalAttempts).toBe(3); }); it('should persist to disk and reload', () => { const memory1 = new EvolutionMemory(SKILL_DIR); memory1.record({ iteration: 0, strategy: { approach: 'full' }, score: 65 }); // 新实例应从磁盘加载 const memory2 = new EvolutionMemory(SKILL_DIR); const history = memory2.getHistory(); expect(history.iterations).toHaveLength(1); expect(history.iterations[0].score).toBe(65); }); }); describe('getLessons', () => { it('should extract unique lessons from history', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, score: 50, lessonsLearned: ['教训A', '教训B'] }); memory.record({ iteration: 1, score: 60, lessonsLearned: ['教训B', '教训C'] }); const lessons = memory.getLessons(); expect(lessons).toHaveLength(3); expect(lessons).toContain('教训A'); expect(lessons).toContain('教训B'); expect(lessons).toContain('教训C'); }); it('should return empty for no history', () => { const memory = new EvolutionMemory(SKILL_DIR); expect(memory.getLessons()).toEqual([]); }); }); describe('getBestStrategy', () => { it('should return strategy with highest score', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, strategy: { approach: 'full' }, score: 50 }); memory.record({ iteration: 1, strategy: { approach: 'refine' }, score: 88 }); memory.record({ iteration: 2, strategy: { approach: 'restructure' }, score: 75 }); const best = memory.getBestStrategy(); expect(best.approach).toBe('refine'); }); it('should return null for empty history', () => { const memory = new EvolutionMemory(SKILL_DIR); expect(memory.getBestStrategy()).toBeNull(); }); }); describe('getRecentWeakDimensions', () => { it('should return weak dimensions sorted by frequency', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, score: 50, weakDimensions: ['accuracy', 'completeness'] }); memory.record({ iteration: 1, score: 60, weakDimensions: ['accuracy', 'robustness'] }); memory.record({ iteration: 2, score: 65, weakDimensions: ['accuracy'] }); const weaks = memory.getRecentWeakDimensions(3); expect(weaks[0]).toBe('accuracy'); // 出现 3 次 }); }); describe('isStagnant', () => { it('should detect stagnation', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, score: 70 }); memory.record({ iteration: 1, score: 71 }); memory.record({ iteration: 2, score: 71 }); expect(memory.isStagnant(3, 2)).toBe(true); }); it('should not flag as stagnant when scores differ', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, score: 60 }); memory.record({ iteration: 1, score: 70 }); memory.record({ iteration: 2, score: 75 }); expect(memory.isStagnant(3, 2)).toBe(false); }); it('should not flag with insufficient data', () => { const memory = new EvolutionMemory(SKILL_DIR); memory.record({ iteration: 0, score: 60 }); expect(memory.isStagnant(3, 2)).toBe(false); }); }); });