Initial commit with translated description
This commit is contained in:
577
test/skillDistiller.test.js
Normal file
577
test/skillDistiller.test.js
Normal file
@@ -0,0 +1,577 @@
|
||||
const { describe, it, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const {
|
||||
collectDistillationData,
|
||||
analyzePatterns,
|
||||
validateSynthesizedGene,
|
||||
buildDistillationPrompt,
|
||||
extractJsonFromLlmResponse,
|
||||
computeDataHash,
|
||||
shouldDistill,
|
||||
prepareDistillation,
|
||||
completeDistillation,
|
||||
autoDistill,
|
||||
synthesizeGeneFromPatterns,
|
||||
distillRequestPath,
|
||||
readDistillerState,
|
||||
writeDistillerState,
|
||||
DISTILLED_ID_PREFIX,
|
||||
DISTILLED_MAX_FILES,
|
||||
} = require('../src/gep/skillDistiller');
|
||||
|
||||
// Create an isolated temp directory for each test to avoid polluting real assets.
|
||||
let tmpDir;
|
||||
let origGepAssetsDir;
|
||||
let origEvolutionDir;
|
||||
let origMemoryDir;
|
||||
let origSkillDistiller;
|
||||
|
||||
function setupTempEnv() {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'distiller-test-'));
|
||||
origGepAssetsDir = process.env.GEP_ASSETS_DIR;
|
||||
origEvolutionDir = process.env.EVOLUTION_DIR;
|
||||
origMemoryDir = process.env.MEMORY_DIR;
|
||||
origSkillDistiller = process.env.SKILL_DISTILLER;
|
||||
|
||||
process.env.GEP_ASSETS_DIR = path.join(tmpDir, 'assets');
|
||||
process.env.EVOLUTION_DIR = path.join(tmpDir, 'evolution');
|
||||
process.env.MEMORY_DIR = path.join(tmpDir, 'memory');
|
||||
process.env.MEMORY_GRAPH_PATH = path.join(tmpDir, 'evolution', 'memory_graph.jsonl');
|
||||
|
||||
fs.mkdirSync(process.env.GEP_ASSETS_DIR, { recursive: true });
|
||||
fs.mkdirSync(process.env.EVOLUTION_DIR, { recursive: true });
|
||||
fs.mkdirSync(process.env.MEMORY_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function teardownTempEnv() {
|
||||
if (origGepAssetsDir !== undefined) process.env.GEP_ASSETS_DIR = origGepAssetsDir;
|
||||
else delete process.env.GEP_ASSETS_DIR;
|
||||
if (origEvolutionDir !== undefined) process.env.EVOLUTION_DIR = origEvolutionDir;
|
||||
else delete process.env.EVOLUTION_DIR;
|
||||
if (origMemoryDir !== undefined) process.env.MEMORY_DIR = origMemoryDir;
|
||||
else delete process.env.MEMORY_DIR;
|
||||
if (origSkillDistiller !== undefined) process.env.SKILL_DISTILLER = origSkillDistiller;
|
||||
else delete process.env.SKILL_DISTILLER;
|
||||
delete process.env.MEMORY_GRAPH_PATH;
|
||||
|
||||
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) {}
|
||||
}
|
||||
|
||||
function makeCapsule(id, gene, status, score, trigger, summary) {
|
||||
return {
|
||||
type: 'Capsule', id: id, gene: gene,
|
||||
trigger: trigger || ['error', 'repair'],
|
||||
summary: summary || 'Fixed a bug in module X',
|
||||
outcome: { status: status, score: score },
|
||||
};
|
||||
}
|
||||
|
||||
function writeCapsules(capsules) {
|
||||
fs.writeFileSync(
|
||||
path.join(process.env.GEP_ASSETS_DIR, 'capsules.json'),
|
||||
JSON.stringify({ version: 1, capsules: capsules }, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
function writeEvents(events) {
|
||||
var lines = events.map(function (e) { return JSON.stringify(e); }).join('\n') + '\n';
|
||||
fs.writeFileSync(path.join(process.env.GEP_ASSETS_DIR, 'events.jsonl'), lines);
|
||||
}
|
||||
|
||||
function writeGenes(genes) {
|
||||
fs.writeFileSync(
|
||||
path.join(process.env.GEP_ASSETS_DIR, 'genes.json'),
|
||||
JSON.stringify({ version: 1, genes: genes }, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
function readJson(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
describe('computeDataHash', () => {
|
||||
it('returns stable hash for same capsule ids', () => {
|
||||
var c1 = [{ id: 'a' }, { id: 'b' }];
|
||||
var c2 = [{ id: 'b' }, { id: 'a' }];
|
||||
assert.equal(computeDataHash(c1), computeDataHash(c2));
|
||||
});
|
||||
|
||||
it('returns different hash for different capsule ids', () => {
|
||||
var c1 = [{ id: 'a' }];
|
||||
var c2 = [{ id: 'b' }];
|
||||
assert.notEqual(computeDataHash(c1), computeDataHash(c2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractJsonFromLlmResponse', () => {
|
||||
it('extracts Gene JSON from clean response', () => {
|
||||
var text = '{"type":"Gene","id":"gene_distilled_test","category":"repair","signals_match":["err"],"strategy":["fix it"]}';
|
||||
var gene = extractJsonFromLlmResponse(text);
|
||||
assert.ok(gene);
|
||||
assert.equal(gene.type, 'Gene');
|
||||
assert.equal(gene.id, 'gene_distilled_test');
|
||||
});
|
||||
|
||||
it('extracts Gene JSON wrapped in markdown', () => {
|
||||
var text = 'Here is the gene:\n```json\n{"type":"Gene","id":"gene_distilled_x","category":"opt","signals_match":["a"],"strategy":["b"]}\n```\n';
|
||||
var gene = extractJsonFromLlmResponse(text);
|
||||
assert.ok(gene);
|
||||
assert.equal(gene.id, 'gene_distilled_x');
|
||||
});
|
||||
|
||||
it('returns null when no Gene JSON present', () => {
|
||||
var text = 'No JSON here, just text.';
|
||||
assert.equal(extractJsonFromLlmResponse(text), null);
|
||||
});
|
||||
|
||||
it('skips non-Gene JSON objects', () => {
|
||||
var text = '{"type":"Capsule","id":"cap1"} then {"type":"Gene","id":"gene_distilled_y","category":"c","signals_match":["s"],"strategy":["do"]}';
|
||||
var gene = extractJsonFromLlmResponse(text);
|
||||
assert.ok(gene);
|
||||
assert.equal(gene.type, 'Gene');
|
||||
assert.equal(gene.id, 'gene_distilled_y');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateSynthesizedGene', () => {
|
||||
it('accepts a valid gene', () => {
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_test', category: 'repair',
|
||||
signals_match: ['error'], strategy: ['identify the root cause', 'apply the fix', 'verify the solution'],
|
||||
constraints: { max_files: 8, forbidden_paths: ['.git', 'node_modules'] },
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.ok(result.valid, 'Expected valid but got errors: ' + result.errors.join(', '));
|
||||
});
|
||||
|
||||
it('auto-prefixes id if missing distilled prefix', () => {
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_test_auto', category: 'opt',
|
||||
signals_match: ['optimize'], strategy: ['do stuff'],
|
||||
constraints: { forbidden_paths: ['.git'] },
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.ok(result.gene.id.startsWith(DISTILLED_ID_PREFIX));
|
||||
});
|
||||
|
||||
it('caps max_files to DISTILLED_MAX_FILES', () => {
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_big', category: 'opt',
|
||||
signals_match: ['x'], strategy: ['y'],
|
||||
constraints: { max_files: 50, forbidden_paths: ['.git', 'node_modules'] },
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.ok(result.gene.constraints.max_files <= DISTILLED_MAX_FILES);
|
||||
});
|
||||
|
||||
it('rejects gene without strategy', () => {
|
||||
var gene = { type: 'Gene', id: 'gene_distilled_empty', category: 'x', signals_match: ['a'] };
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.ok(!result.valid);
|
||||
assert.ok(result.errors.some(function (e) { return e.includes('strategy'); }));
|
||||
});
|
||||
|
||||
it('rejects gene without signals_match', () => {
|
||||
var gene = { type: 'Gene', id: 'gene_distilled_nosig', category: 'x', strategy: ['do'] };
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.ok(!result.valid);
|
||||
assert.ok(result.errors.some(function (e) { return e.includes('signals_match'); }));
|
||||
});
|
||||
|
||||
it('detects full overlap with existing gene', () => {
|
||||
var existing = [{ id: 'gene_existing', signals_match: ['error', 'repair'] }];
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_dup', category: 'repair',
|
||||
signals_match: ['error', 'repair'], strategy: ['fix'],
|
||||
constraints: { forbidden_paths: ['.git', 'node_modules'] },
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, existing);
|
||||
assert.ok(!result.valid);
|
||||
assert.ok(result.errors.some(function (e) { return e.includes('overlaps'); }));
|
||||
});
|
||||
|
||||
it('deduplicates id if conflict with existing gene', () => {
|
||||
var existing = [{ id: 'gene_distilled_conflict', signals_match: ['other'] }];
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_conflict', category: 'opt',
|
||||
signals_match: ['different'], strategy: ['do'],
|
||||
constraints: { forbidden_paths: ['.git', 'node_modules'] },
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, existing);
|
||||
assert.ok(result.gene.id !== 'gene_distilled_conflict');
|
||||
assert.ok(result.gene.id.startsWith('gene_distilled_conflict_'));
|
||||
});
|
||||
|
||||
it('strips unsafe validation commands', () => {
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_unsafe', category: 'opt',
|
||||
signals_match: ['x'], strategy: ['do'],
|
||||
constraints: { forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node test.js', 'rm -rf /', 'echo $(whoami)', 'npm test'],
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.deepEqual(result.gene.validation, ['node test.js', 'npm test']);
|
||||
});
|
||||
|
||||
it('strips node -e and node --eval commands (consistent with policyCheck)', () => {
|
||||
var gene = {
|
||||
type: 'Gene', id: 'gene_distilled_eval_block', category: 'opt',
|
||||
signals_match: ['test_signal'], strategy: ['step one', 'step two', 'step three'],
|
||||
constraints: { forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: [
|
||||
'node scripts/validate-modules.js ./src/evolve',
|
||||
'node -e "process.exit(0)"',
|
||||
'node --eval "require(\'fs\')"',
|
||||
'node -p "1+1"',
|
||||
'npm test',
|
||||
],
|
||||
};
|
||||
var result = validateSynthesizedGene(gene, []);
|
||||
assert.deepEqual(result.gene.validation, [
|
||||
'node scripts/validate-modules.js ./src/evolve',
|
||||
'npm test',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collectDistillationData', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('returns empty when no capsules exist', () => {
|
||||
var data = collectDistillationData();
|
||||
assert.equal(data.successCapsules.length, 0);
|
||||
assert.equal(data.allCapsules.length, 0);
|
||||
});
|
||||
|
||||
it('filters only successful capsules with score >= threshold', () => {
|
||||
var caps = [
|
||||
makeCapsule('c1', 'gene_a', 'success', 0.9),
|
||||
makeCapsule('c2', 'gene_a', 'failed', 0.2),
|
||||
makeCapsule('c3', 'gene_b', 'success', 0.5),
|
||||
];
|
||||
writeCapsules(caps);
|
||||
var data = collectDistillationData();
|
||||
assert.equal(data.allCapsules.length, 3);
|
||||
assert.equal(data.successCapsules.length, 1);
|
||||
assert.equal(data.successCapsules[0].id, 'c1');
|
||||
});
|
||||
|
||||
it('groups capsules by gene', () => {
|
||||
var caps = [
|
||||
makeCapsule('c1', 'gene_a', 'success', 0.9),
|
||||
makeCapsule('c2', 'gene_a', 'success', 0.8),
|
||||
makeCapsule('c3', 'gene_b', 'success', 0.95),
|
||||
];
|
||||
writeCapsules(caps);
|
||||
var data = collectDistillationData();
|
||||
assert.equal(Object.keys(data.grouped).length, 2);
|
||||
assert.equal(data.grouped['gene_a'].total_count, 2);
|
||||
assert.equal(data.grouped['gene_b'].total_count, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('analyzePatterns', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('identifies high-frequency groups (count >= 5)', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 6; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9, ['error', 'crash']));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
var data = collectDistillationData();
|
||||
var report = analyzePatterns(data);
|
||||
assert.equal(report.high_frequency.length, 1);
|
||||
assert.equal(report.high_frequency[0].gene_id, 'gene_a');
|
||||
assert.equal(report.high_frequency[0].count, 6);
|
||||
});
|
||||
|
||||
it('detects strategy drift when summaries diverge', () => {
|
||||
var caps = [
|
||||
makeCapsule('c1', 'gene_a', 'success', 0.9, ['err'], 'Fixed crash in module A by patching function foo'),
|
||||
makeCapsule('c2', 'gene_a', 'success', 0.9, ['err'], 'Fixed crash in module A by patching function foo'),
|
||||
makeCapsule('c3', 'gene_a', 'success', 0.9, ['err'], 'Completely redesigned the logging infrastructure to avoid all future problems with disk IO'),
|
||||
];
|
||||
writeCapsules(caps);
|
||||
var data = collectDistillationData();
|
||||
var report = analyzePatterns(data);
|
||||
assert.equal(report.strategy_drift.length, 1);
|
||||
assert.ok(report.strategy_drift[0].similarity < 0.6);
|
||||
});
|
||||
|
||||
it('identifies coverage gaps from events', () => {
|
||||
writeCapsules([makeCapsule('c1', 'gene_a', 'success', 0.9, ['error'])]);
|
||||
var events = [];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
events.push({ type: 'EvolutionEvent', signals: ['memory_leak', 'performance'] });
|
||||
}
|
||||
writeEvents(events);
|
||||
var data = collectDistillationData();
|
||||
var report = analyzePatterns(data);
|
||||
assert.ok(report.coverage_gaps.length > 0);
|
||||
assert.ok(report.coverage_gaps.some(function (g) { return g.signal === 'memory_leak'; }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildDistillationPrompt', () => {
|
||||
it('includes key instructions in prompt', () => {
|
||||
var analysis = { high_frequency: [], strategy_drift: [], coverage_gaps: [] };
|
||||
var genes = [{ id: 'gene_a', signals_match: ['err'] }];
|
||||
var caps = [makeCapsule('c1', 'gene_a', 'success', 0.9)];
|
||||
var prompt = buildDistillationPrompt(analysis, genes, caps);
|
||||
assert.ok(prompt.includes('actionable'));
|
||||
assert.ok(prompt.includes('gene_distilled_'));
|
||||
assert.ok(prompt.includes('Gene synthesis engine'));
|
||||
assert.ok(prompt.includes('forbidden_paths'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldDistill', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('returns false when SKILL_DISTILLER=false', () => {
|
||||
process.env.SKILL_DISTILLER = 'false';
|
||||
assert.equal(shouldDistill(), false);
|
||||
});
|
||||
|
||||
it('returns false when not enough successful capsules', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'failed', 0.3));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
assert.equal(shouldDistill(), false);
|
||||
});
|
||||
|
||||
it('returns false when interval not met', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeDistillerState({ last_distillation_at: new Date().toISOString() });
|
||||
assert.equal(shouldDistill(), false);
|
||||
});
|
||||
|
||||
it('returns true when all conditions met', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeDistillerState({});
|
||||
delete process.env.SKILL_DISTILLER;
|
||||
assert.equal(shouldDistill(), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('distiller state persistence', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('writes and reads state correctly', () => {
|
||||
var state = { last_distillation_at: '2025-01-01T00:00:00Z', last_data_hash: 'abc123', distillation_count: 3 };
|
||||
writeDistillerState(state);
|
||||
var loaded = readDistillerState();
|
||||
assert.equal(loaded.last_data_hash, 'abc123');
|
||||
assert.equal(loaded.distillation_count, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prepareDistillation', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('returns insufficient_data when not enough capsules', () => {
|
||||
writeCapsules([makeCapsule('c1', 'gene_a', 'success', 0.9)]);
|
||||
var result = prepareDistillation();
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.reason, 'insufficient_data');
|
||||
});
|
||||
|
||||
it('writes prompt and request files when conditions met', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeDistillerState({});
|
||||
writeGenes([]);
|
||||
|
||||
var result = prepareDistillation();
|
||||
assert.equal(result.ok, true);
|
||||
assert.ok(result.promptPath);
|
||||
assert.ok(result.requestPath);
|
||||
assert.ok(fs.existsSync(result.promptPath));
|
||||
assert.ok(fs.existsSync(result.requestPath));
|
||||
|
||||
var prompt = fs.readFileSync(result.promptPath, 'utf8');
|
||||
assert.ok(prompt.includes('Gene synthesis engine'));
|
||||
|
||||
var request = JSON.parse(fs.readFileSync(result.requestPath, 'utf8'));
|
||||
assert.equal(request.type, 'DistillationRequest');
|
||||
assert.equal(request.input_capsule_count, 12);
|
||||
});
|
||||
|
||||
it('returns idempotent_skip after completeDistillation with same data', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeGenes([]);
|
||||
writeDistillerState({});
|
||||
|
||||
var prep = prepareDistillation();
|
||||
assert.equal(prep.ok, true);
|
||||
|
||||
var llmResponse = JSON.stringify({
|
||||
type: 'Gene', id: 'gene_distilled_idem', category: 'repair',
|
||||
signals_match: ['error'], strategy: ['identify root cause', 'apply targeted fix', 'verify correction'],
|
||||
constraints: { max_files: 5, forbidden_paths: ['.git', 'node_modules'] },
|
||||
});
|
||||
var complete = completeDistillation(llmResponse);
|
||||
assert.equal(complete.ok, true);
|
||||
|
||||
var second = prepareDistillation();
|
||||
assert.equal(second.ok, false);
|
||||
assert.equal(second.reason, 'idempotent_skip');
|
||||
});
|
||||
});
|
||||
|
||||
describe('synthesizeGeneFromPatterns', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('builds a conservative distilled gene from repeated successful capsules', () => {
|
||||
writeCapsules([
|
||||
makeCapsule('c1', 'gene_perf', 'success', 0.95, ['perf_bottleneck', 'latency'], 'Reduced latency in hot path'),
|
||||
makeCapsule('c2', 'gene_perf', 'success', 0.92, ['perf_bottleneck', 'throughput'], 'Improved throughput under load'),
|
||||
makeCapsule('c3', 'gene_perf', 'success', 0.91, ['perf_bottleneck'], 'Cut slow-path overhead'),
|
||||
makeCapsule('c4', 'gene_perf', 'success', 0.93, ['perf_bottleneck'], 'Optimized repeated slow query'),
|
||||
makeCapsule('c5', 'gene_perf', 'success', 0.94, ['perf_bottleneck'], 'Reduced performance regressions'),
|
||||
makeCapsule('c6', 'gene_perf', 'success', 0.96, ['perf_bottleneck'], 'Stabilized latency under peak load'),
|
||||
makeCapsule('c7', 'gene_perf', 'success', 0.97, ['perf_bottleneck'], 'Optimized hot path validation'),
|
||||
makeCapsule('c8', 'gene_perf', 'success', 0.98, ['perf_bottleneck'], 'Minimized repeated bottleneck'),
|
||||
makeCapsule('c9', 'gene_perf', 'success', 0.99, ['perf_bottleneck'], 'Improved repeated performance pattern'),
|
||||
makeCapsule('c10', 'gene_perf', 'success', 0.91, ['perf_bottleneck'], 'Kept repeated success on perf fixes'),
|
||||
]);
|
||||
writeGenes([{
|
||||
type: 'Gene',
|
||||
id: 'gene_perf',
|
||||
category: 'optimize',
|
||||
signals_match: ['perf_bottleneck'],
|
||||
strategy: ['Profile the hot path', 'Apply the narrowest optimization', 'Run focused perf validation'],
|
||||
constraints: { max_files: 8, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node --test'],
|
||||
}]);
|
||||
|
||||
var data = collectDistillationData();
|
||||
var analysis = analyzePatterns(data);
|
||||
var gene = synthesizeGeneFromPatterns(data, analysis, [{ id: 'gene_perf', category: 'optimize', signals_match: ['perf_bottleneck'] }]);
|
||||
assert.ok(gene);
|
||||
assert.ok(gene.id.startsWith('gene_distilled_'));
|
||||
assert.equal(gene.category, 'optimize');
|
||||
assert.ok(gene.signals_match.includes('perf_bottleneck'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoDistill', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('writes a distilled gene automatically when enough successful capsules exist', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_perf', 'success', 0.95, ['perf_bottleneck'], 'Reduce repeated latency regressions'));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeGenes([{
|
||||
type: 'Gene',
|
||||
id: 'gene_perf',
|
||||
category: 'optimize',
|
||||
signals_match: ['perf_bottleneck'],
|
||||
strategy: ['Profile the slow path', 'Apply a targeted optimization', 'Run validation'],
|
||||
constraints: { max_files: 8, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node --test'],
|
||||
}]);
|
||||
|
||||
var result = autoDistill();
|
||||
assert.ok(result.ok, result.reason || 'autoDistill should succeed');
|
||||
assert.ok(result.gene.id.startsWith('gene_distilled_'));
|
||||
var genes = readJson(path.join(process.env.GEP_ASSETS_DIR, 'genes.json'));
|
||||
assert.ok(genes.genes.some(function (g) { return g.id === result.gene.id; }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('completeDistillation', () => {
|
||||
beforeEach(setupTempEnv);
|
||||
afterEach(teardownTempEnv);
|
||||
|
||||
it('returns no_request when no pending request', () => {
|
||||
var result = completeDistillation('{"type":"Gene"}');
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.reason, 'no_request');
|
||||
});
|
||||
|
||||
it('returns no_gene_in_response for invalid LLM output', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeDistillerState({});
|
||||
writeGenes([]);
|
||||
|
||||
var prep = prepareDistillation();
|
||||
assert.equal(prep.ok, true);
|
||||
|
||||
var result = completeDistillation('No valid JSON here');
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.reason, 'no_gene_in_response');
|
||||
});
|
||||
|
||||
it('validates and saves gene from valid LLM response', () => {
|
||||
var caps = [];
|
||||
for (var i = 0; i < 12; i++) {
|
||||
caps.push(makeCapsule('c' + i, 'gene_a', 'success', 0.9));
|
||||
}
|
||||
writeCapsules(caps);
|
||||
writeDistillerState({});
|
||||
writeGenes([]);
|
||||
|
||||
var prep = prepareDistillation();
|
||||
assert.equal(prep.ok, true);
|
||||
|
||||
var llmResponse = JSON.stringify({
|
||||
type: 'Gene',
|
||||
id: 'gene_distilled_test_complete',
|
||||
category: 'repair',
|
||||
signals_match: ['error', 'crash'],
|
||||
strategy: ['Identify the failing module', 'Apply targeted fix', 'Run validation'],
|
||||
constraints: { max_files: 5, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node test.js'],
|
||||
});
|
||||
|
||||
var result = completeDistillation(llmResponse);
|
||||
assert.equal(result.ok, true);
|
||||
assert.ok(result.gene);
|
||||
assert.equal(result.gene.type, 'Gene');
|
||||
assert.ok(result.gene.id.startsWith('gene_distilled_'));
|
||||
|
||||
var state = readDistillerState();
|
||||
assert.ok(state.last_distillation_at);
|
||||
assert.equal(state.distillation_count, 1);
|
||||
|
||||
assert.ok(!fs.existsSync(distillRequestPath()));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user