@evomap/evolver 1.31.0 → 1.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +19 -12
- package/package.json +1 -1
- package/src/evolve.js +87 -7
- package/src/gep/a2aProtocol.js +20 -5
- package/src/gep/assetStore.js +28 -7
- package/src/gep/candidates.js +66 -4
- package/src/gep/learningSignals.js +89 -0
- package/src/gep/prompt.js +11 -0
- package/src/gep/selector.js +49 -2
- package/src/gep/skillDistiller.js +286 -110
- package/src/gep/solidify.js +137 -2
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const paths = require('./paths');
|
|
7
|
+
const learningSignals = require('./learningSignals');
|
|
8
|
+
|
|
9
|
+
const DISTILLER_MIN_CAPSULES = parseInt(process.env.DISTILLER_MIN_CAPSULES || '10', 10) || 10;
|
|
10
|
+
const DISTILLER_INTERVAL_HOURS = parseInt(process.env.DISTILLER_INTERVAL_HOURS || '24', 10) || 24;
|
|
11
|
+
const DISTILLER_MIN_SUCCESS_RATE = parseFloat(process.env.DISTILLER_MIN_SUCCESS_RATE || '0.7') || 0.7;
|
|
12
|
+
const DISTILLED_MAX_FILES = 12;
|
|
13
|
+
const DISTILLED_ID_PREFIX = 'gene_distilled_';
|
|
13
14
|
|
|
14
15
|
function ensureDir(dir) {
|
|
15
16
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
@@ -18,7 +19,7 @@ function ensureDir(dir) {
|
|
|
18
19
|
function readJsonIfExists(filePath, fallback) {
|
|
19
20
|
try {
|
|
20
21
|
if (!fs.existsSync(filePath)) return fallback;
|
|
21
|
-
|
|
22
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
22
23
|
if (!raw.trim()) return fallback;
|
|
23
24
|
return JSON.parse(raw);
|
|
24
25
|
} catch (e) {
|
|
@@ -29,7 +30,7 @@ function readJsonIfExists(filePath, fallback) {
|
|
|
29
30
|
function readJsonlIfExists(filePath) {
|
|
30
31
|
try {
|
|
31
32
|
if (!fs.existsSync(filePath)) return [];
|
|
32
|
-
|
|
33
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
33
34
|
return raw.split('\n').map(function (l) { return l.trim(); }).filter(Boolean).map(function (l) {
|
|
34
35
|
try { return JSON.parse(l); } catch (e) { return null; }
|
|
35
36
|
}).filter(Boolean);
|
|
@@ -57,13 +58,13 @@ function readDistillerState() {
|
|
|
57
58
|
|
|
58
59
|
function writeDistillerState(state) {
|
|
59
60
|
ensureDir(path.dirname(distillerStatePath()));
|
|
60
|
-
|
|
61
|
+
const tmp = distillerStatePath() + '.tmp';
|
|
61
62
|
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
62
63
|
fs.renameSync(tmp, distillerStatePath());
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
function computeDataHash(capsules) {
|
|
66
|
-
|
|
67
|
+
const ids = capsules.map(function (c) { return c.id || ''; }).sort();
|
|
67
68
|
return crypto.createHash('sha256').update(ids.join('|')).digest('hex').slice(0, 16);
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -71,40 +72,40 @@ function computeDataHash(capsules) {
|
|
|
71
72
|
// Step 1: collectDistillationData
|
|
72
73
|
// ---------------------------------------------------------------------------
|
|
73
74
|
function collectDistillationData() {
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
76
|
+
const evoDir = paths.getEvolutionDir();
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
|
|
79
|
+
const capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
|
|
80
|
+
let allCapsules = [].concat(capsulesJson.capsules || [], capsulesJsonl);
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
const unique = new Map();
|
|
82
83
|
allCapsules.forEach(function (c) { if (c && c.id) unique.set(String(c.id), c); });
|
|
83
84
|
allCapsules = Array.from(unique.values());
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
const successCapsules = allCapsules.filter(function (c) {
|
|
86
87
|
if (!c || !c.outcome) return false;
|
|
87
|
-
|
|
88
|
+
const status = typeof c.outcome === 'string' ? c.outcome : c.outcome.status;
|
|
88
89
|
if (status !== 'success') return false;
|
|
89
|
-
|
|
90
|
+
const score = c.outcome && Number.isFinite(Number(c.outcome.score)) ? Number(c.outcome.score) : 1;
|
|
90
91
|
return score >= DISTILLER_MIN_SUCCESS_RATE;
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
const events = readJsonlIfExists(path.join(assetsDir, 'events.jsonl'));
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
const memGraphPath = process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
|
|
97
|
+
const graphEntries = readJsonlIfExists(memGraphPath);
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
const grouped = {};
|
|
99
100
|
successCapsules.forEach(function (c) {
|
|
100
|
-
|
|
101
|
+
const geneId = c.gene || c.gene_id || 'unknown';
|
|
101
102
|
if (!grouped[geneId]) {
|
|
102
103
|
grouped[geneId] = {
|
|
103
104
|
gene_id: geneId, capsules: [], total_count: 0,
|
|
104
105
|
total_score: 0, triggers: [], summaries: [],
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
const g = grouped[geneId];
|
|
108
109
|
g.capsules.push(c);
|
|
109
110
|
g.total_count += 1;
|
|
110
111
|
g.total_score += (c.outcome && Number.isFinite(Number(c.outcome.score))) ? Number(c.outcome.score) : 0.8;
|
|
@@ -113,7 +114,7 @@ function collectDistillationData() {
|
|
|
113
114
|
});
|
|
114
115
|
|
|
115
116
|
Object.keys(grouped).forEach(function (id) {
|
|
116
|
-
|
|
117
|
+
const g = grouped[id];
|
|
117
118
|
g.avg_score = g.total_count > 0 ? g.total_score / g.total_count : 0;
|
|
118
119
|
});
|
|
119
120
|
|
|
@@ -131,8 +132,8 @@ function collectDistillationData() {
|
|
|
131
132
|
// Step 2: analyzePatterns
|
|
132
133
|
// ---------------------------------------------------------------------------
|
|
133
134
|
function analyzePatterns(data) {
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
const grouped = data.grouped;
|
|
136
|
+
const report = {
|
|
136
137
|
high_frequency: [],
|
|
137
138
|
strategy_drift: [],
|
|
138
139
|
coverage_gaps: [],
|
|
@@ -142,26 +143,26 @@ function analyzePatterns(data) {
|
|
|
142
143
|
};
|
|
143
144
|
|
|
144
145
|
Object.keys(grouped).forEach(function (geneId) {
|
|
145
|
-
|
|
146
|
+
const g = grouped[geneId];
|
|
146
147
|
if (g.total_count >= 5) {
|
|
147
|
-
|
|
148
|
+
let flat = [];
|
|
148
149
|
g.triggers.forEach(function (t) { if (Array.isArray(t)) flat = flat.concat(t); });
|
|
149
|
-
|
|
150
|
-
flat.forEach(function (t) {
|
|
151
|
-
|
|
150
|
+
const freq = {};
|
|
151
|
+
flat.forEach(function (t) { const k = String(t).toLowerCase(); freq[k] = (freq[k] || 0) + 1; });
|
|
152
|
+
const top = Object.keys(freq).sort(function (a, b) { return freq[b] - freq[a]; }).slice(0, 5);
|
|
152
153
|
report.high_frequency.push({ gene_id: geneId, count: g.total_count, avg_score: Math.round(g.avg_score * 100) / 100, top_triggers: top });
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
if (g.summaries.length >= 3) {
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
const first = g.summaries[0];
|
|
158
|
+
const last = g.summaries[g.summaries.length - 1];
|
|
158
159
|
if (first !== last) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const fw = new Set(first.toLowerCase().split(/\s+/));
|
|
161
|
+
const lw = new Set(last.toLowerCase().split(/\s+/));
|
|
162
|
+
let inter = 0;
|
|
162
163
|
fw.forEach(function (w) { if (lw.has(w)) inter++; });
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
const union = fw.size + lw.size - inter;
|
|
165
|
+
const sim = union > 0 ? inter / union : 1;
|
|
165
166
|
if (sim < 0.6) {
|
|
166
167
|
report.strategy_drift.push({ gene_id: geneId, similarity: Math.round(sim * 100) / 100, early_summary: first.slice(0, 120), recent_summary: last.slice(0, 120) });
|
|
167
168
|
}
|
|
@@ -169,19 +170,19 @@ function analyzePatterns(data) {
|
|
|
169
170
|
}
|
|
170
171
|
});
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
const signalFreq = {};
|
|
173
174
|
(data.events || []).forEach(function (evt) {
|
|
174
175
|
if (evt && Array.isArray(evt.signals)) {
|
|
175
|
-
evt.signals.forEach(function (s) {
|
|
176
|
+
evt.signals.forEach(function (s) { const k = String(s).toLowerCase(); signalFreq[k] = (signalFreq[k] || 0) + 1; });
|
|
176
177
|
}
|
|
177
178
|
});
|
|
178
|
-
|
|
179
|
+
const covered = new Set();
|
|
179
180
|
Object.keys(grouped).forEach(function (geneId) {
|
|
180
181
|
grouped[geneId].triggers.forEach(function (t) {
|
|
181
182
|
if (Array.isArray(t)) t.forEach(function (s) { covered.add(String(s).toLowerCase()); });
|
|
182
183
|
});
|
|
183
184
|
});
|
|
184
|
-
|
|
185
|
+
const gaps = Object.keys(signalFreq)
|
|
185
186
|
.filter(function (s) { return signalFreq[s] >= 3 && !covered.has(s); })
|
|
186
187
|
.sort(function (a, b) { return signalFreq[b] - signalFreq[a]; })
|
|
187
188
|
.slice(0, 10);
|
|
@@ -196,16 +197,16 @@ function analyzePatterns(data) {
|
|
|
196
197
|
// Step 3: LLM response parsing
|
|
197
198
|
// ---------------------------------------------------------------------------
|
|
198
199
|
function extractJsonFromLlmResponse(text) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
for (
|
|
203
|
-
|
|
200
|
+
const str = String(text || '');
|
|
201
|
+
let buffer = '';
|
|
202
|
+
let depth = 0;
|
|
203
|
+
for (let i = 0; i < str.length; i++) {
|
|
204
|
+
const ch = str[i];
|
|
204
205
|
if (ch === '{') { if (depth === 0) buffer = ''; depth++; buffer += ch; }
|
|
205
206
|
else if (ch === '}') {
|
|
206
207
|
depth--; buffer += ch;
|
|
207
208
|
if (depth === 0 && buffer.length > 2) {
|
|
208
|
-
try {
|
|
209
|
+
try { const obj = JSON.parse(buffer); if (obj && typeof obj === 'object' && obj.type === 'Gene') return obj; } catch (e) {}
|
|
209
210
|
buffer = '';
|
|
210
211
|
}
|
|
211
212
|
if (depth < 0) depth = 0;
|
|
@@ -215,10 +216,10 @@ function extractJsonFromLlmResponse(text) {
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
218
|
-
|
|
219
|
+
const genesRef = existingGenes.map(function (g) {
|
|
219
220
|
return { id: g.id, category: g.category || null, signals_match: g.signals_match || [] };
|
|
220
221
|
});
|
|
221
|
-
|
|
222
|
+
const samples = sampleCapsules.slice(0, 8).map(function (c) {
|
|
222
223
|
return { gene: c.gene || c.gene_id || null, trigger: c.trigger || [], summary: (c.summary || '').slice(0, 200), outcome: c.outcome || null };
|
|
223
224
|
});
|
|
224
225
|
|
|
@@ -318,7 +319,7 @@ function distillRequestPath() {
|
|
|
318
319
|
// Derive a descriptive ID from gene content when the LLM gives a bad name
|
|
319
320
|
// ---------------------------------------------------------------------------
|
|
320
321
|
function deriveDescriptiveId(gene) {
|
|
321
|
-
|
|
322
|
+
let words = [];
|
|
322
323
|
if (Array.isArray(gene.signals_match)) {
|
|
323
324
|
gene.signals_match.slice(0, 3).forEach(function (s) {
|
|
324
325
|
String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
@@ -327,7 +328,7 @@ function deriveDescriptiveId(gene) {
|
|
|
327
328
|
});
|
|
328
329
|
}
|
|
329
330
|
if (words.length < 3 && gene.summary) {
|
|
330
|
-
|
|
331
|
+
const STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had']);
|
|
331
332
|
String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
332
333
|
if (w.length >= 3 && !STOP.has(w) && words.length < 6) words.push(w);
|
|
333
334
|
});
|
|
@@ -338,8 +339,8 @@ function deriveDescriptiveId(gene) {
|
|
|
338
339
|
});
|
|
339
340
|
}
|
|
340
341
|
if (words.length < 2) words = ['auto', 'distilled', 'strategy'];
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
const unique = [];
|
|
343
|
+
const seen = new Set();
|
|
343
344
|
words.forEach(function (w) { if (!seen.has(w)) { seen.add(w); unique.push(w); } });
|
|
344
345
|
return DISTILLED_ID_PREFIX + unique.slice(0, 5).join('-');
|
|
345
346
|
}
|
|
@@ -349,9 +350,9 @@ function deriveDescriptiveId(gene) {
|
|
|
349
350
|
// ---------------------------------------------------------------------------
|
|
350
351
|
function sanitizeSignalsMatch(signals) {
|
|
351
352
|
if (!Array.isArray(signals)) return [];
|
|
352
|
-
|
|
353
|
+
const cleaned = [];
|
|
353
354
|
signals.forEach(function (s) {
|
|
354
|
-
|
|
355
|
+
let sig = String(s || '').trim().toLowerCase();
|
|
355
356
|
if (!sig) return;
|
|
356
357
|
// Strip trailing timestamps (10+ digits) and random suffixes
|
|
357
358
|
sig = sig.replace(/[_-]\d{10,}$/g, '');
|
|
@@ -368,7 +369,7 @@ function sanitizeSignalsMatch(signals) {
|
|
|
368
369
|
cleaned.push(sig);
|
|
369
370
|
});
|
|
370
371
|
// Deduplicate
|
|
371
|
-
|
|
372
|
+
const seen = {};
|
|
372
373
|
return cleaned.filter(function (s) { if (seen[s]) return false; seen[s] = true; return true; });
|
|
373
374
|
}
|
|
374
375
|
|
|
@@ -376,7 +377,7 @@ function sanitizeSignalsMatch(signals) {
|
|
|
376
377
|
// Step 4: validateSynthesizedGene
|
|
377
378
|
// ---------------------------------------------------------------------------
|
|
378
379
|
function validateSynthesizedGene(gene, existingGenes) {
|
|
379
|
-
|
|
380
|
+
const errors = [];
|
|
380
381
|
if (!gene || typeof gene !== 'object') return { valid: false, errors: ['gene is not an object'] };
|
|
381
382
|
|
|
382
383
|
if (gene.type !== 'Gene') errors.push('missing or wrong type (must be "Gene")');
|
|
@@ -404,17 +405,16 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
if (gene.id) {
|
|
407
|
-
|
|
408
|
-
// Strip ALL embedded timestamps (10+ digit sequences) anywhere in the id
|
|
408
|
+
let suffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
409
409
|
suffix = suffix.replace(/[-_]?\d{10,}[-_]?/g, '-').replace(/[-_]+/g, '-').replace(/^[-_]+|[-_]+$/g, '');
|
|
410
|
-
|
|
410
|
+
const needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
|
|
411
411
|
|| /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d*$/i.test(suffix);
|
|
412
412
|
if (needsRename) {
|
|
413
413
|
gene.id = deriveDescriptiveId(gene);
|
|
414
414
|
} else {
|
|
415
415
|
gene.id = DISTILLED_ID_PREFIX + suffix;
|
|
416
416
|
}
|
|
417
|
-
|
|
417
|
+
const cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
418
418
|
if (cleanSuffix.replace(/[-_]/g, '').length < 6) {
|
|
419
419
|
gene.id = deriveDescriptiveId(gene);
|
|
420
420
|
}
|
|
@@ -447,14 +447,14 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
// --- Validation command sanitization ---
|
|
450
|
-
|
|
450
|
+
const ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
|
451
451
|
if (Array.isArray(gene.validation)) {
|
|
452
452
|
gene.validation = gene.validation.filter(function (cmd) {
|
|
453
|
-
|
|
453
|
+
const c = String(cmd || '').trim();
|
|
454
454
|
if (!c) return false;
|
|
455
455
|
if (!ALLOWED_PREFIXES.some(function (p) { return c.startsWith(p); })) return false;
|
|
456
456
|
if (/`|\$\(/.test(c)) return false;
|
|
457
|
-
|
|
457
|
+
const stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
|
|
458
458
|
return !/[;&|><]/.test(stripped);
|
|
459
459
|
});
|
|
460
460
|
}
|
|
@@ -463,19 +463,19 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
463
463
|
if (!gene.schema_version) gene.schema_version = '1.6.0';
|
|
464
464
|
|
|
465
465
|
// --- Duplicate ID check ---
|
|
466
|
-
|
|
466
|
+
const existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
|
|
467
467
|
if (gene.id && existingIds.has(gene.id)) {
|
|
468
468
|
gene.id = gene.id + '_' + Date.now().toString(36);
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
// --- Signal overlap check ---
|
|
472
472
|
if (gene.signals_match && existingGenes && existingGenes.length > 0) {
|
|
473
|
-
|
|
474
|
-
for (
|
|
475
|
-
|
|
476
|
-
|
|
473
|
+
const newSet = new Set(gene.signals_match.map(function (s) { return String(s).toLowerCase(); }));
|
|
474
|
+
for (let i = 0; i < existingGenes.length; i++) {
|
|
475
|
+
const eg = existingGenes[i];
|
|
476
|
+
const egSet = new Set((eg.signals_match || []).map(function (s) { return String(s).toLowerCase(); }));
|
|
477
477
|
if (newSet.size > 0 && egSet.size > 0) {
|
|
478
|
-
|
|
478
|
+
let overlap = 0;
|
|
479
479
|
newSet.forEach(function (s) { if (egSet.has(s)) overlap++; });
|
|
480
480
|
if (overlap === newSet.size && overlap === egSet.size) {
|
|
481
481
|
errors.push('signals_match fully overlaps with existing gene: ' + eg.id);
|
|
@@ -493,24 +493,24 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
493
493
|
function shouldDistill() {
|
|
494
494
|
if (String(process.env.SKILL_DISTILLER || 'true').toLowerCase() === 'false') return false;
|
|
495
495
|
|
|
496
|
-
|
|
496
|
+
const state = readDistillerState();
|
|
497
497
|
if (state.last_distillation_at) {
|
|
498
|
-
|
|
498
|
+
const elapsed = Date.now() - new Date(state.last_distillation_at).getTime();
|
|
499
499
|
if (elapsed < DISTILLER_INTERVAL_HOURS * 3600000) return false;
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
502
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
503
|
+
const capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
|
|
504
|
+
const capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
|
|
505
|
+
const all = [].concat(capsulesJson.capsules || [], capsulesJsonl);
|
|
506
506
|
|
|
507
|
-
|
|
508
|
-
|
|
507
|
+
const recent = all.slice(-10);
|
|
508
|
+
const recentSuccess = recent.filter(function (c) {
|
|
509
509
|
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
510
510
|
}).length;
|
|
511
511
|
if (recentSuccess < 7) return false;
|
|
512
512
|
|
|
513
|
-
|
|
513
|
+
const totalSuccess = all.filter(function (c) {
|
|
514
514
|
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
515
515
|
}).length;
|
|
516
516
|
if (totalSuccess < DISTILLER_MIN_CAPSULES) return false;
|
|
@@ -524,7 +524,7 @@ function shouldDistill() {
|
|
|
524
524
|
function prepareDistillation() {
|
|
525
525
|
console.log('[Distiller] Preparing skill distillation...');
|
|
526
526
|
|
|
527
|
-
|
|
527
|
+
const data = collectDistillationData();
|
|
528
528
|
console.log('[Distiller] Collected ' + data.successCapsules.length + ' successful capsules across ' + Object.keys(data.grouped).length + ' gene groups.');
|
|
529
529
|
|
|
530
530
|
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
@@ -532,29 +532,29 @@ function prepareDistillation() {
|
|
|
532
532
|
return { ok: false, reason: 'insufficient_data' };
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
-
|
|
535
|
+
const state = readDistillerState();
|
|
536
536
|
if (state.last_data_hash === data.dataHash) {
|
|
537
537
|
console.log('[Distiller] Data unchanged since last distillation (hash: ' + data.dataHash + '). Skipping.');
|
|
538
538
|
return { ok: false, reason: 'idempotent_skip' };
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
|
|
541
|
+
const analysis = analyzePatterns(data);
|
|
542
542
|
console.log('[Distiller] Analysis: high_freq=' + analysis.high_frequency.length + ' drift=' + analysis.strategy_drift.length + ' gaps=' + analysis.coverage_gaps.length);
|
|
543
543
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
544
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
545
|
+
const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
546
|
+
const existingGenes = existingGenesJson.genes || [];
|
|
547
547
|
|
|
548
|
-
|
|
548
|
+
const prompt = buildDistillationPrompt(analysis, existingGenes, data.successCapsules);
|
|
549
549
|
|
|
550
|
-
|
|
550
|
+
const memDir = paths.getMemoryDir();
|
|
551
551
|
ensureDir(memDir);
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
const promptFileName = 'distill_prompt_' + Date.now() + '.txt';
|
|
553
|
+
const promptPath = path.join(memDir, promptFileName);
|
|
554
554
|
fs.writeFileSync(promptPath, prompt, 'utf8');
|
|
555
555
|
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
const reqPath = distillRequestPath();
|
|
557
|
+
const requestData = {
|
|
558
558
|
type: 'DistillationRequest',
|
|
559
559
|
created_at: new Date().toISOString(),
|
|
560
560
|
prompt_path: promptPath,
|
|
@@ -573,19 +573,139 @@ function prepareDistillation() {
|
|
|
573
573
|
return { ok: true, promptPath: promptPath, requestPath: reqPath, dataHash: data.dataHash };
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
+
function inferCategoryFromSignals(signals) {
|
|
577
|
+
const list = Array.isArray(signals) ? signals.map(function (s) { return String(s).toLowerCase(); }) : [];
|
|
578
|
+
if (list.some(function (s) { return s.indexOf('error') !== -1 || s.indexOf('fail') !== -1 || s.indexOf('reliability') !== -1; })) {
|
|
579
|
+
return 'repair';
|
|
580
|
+
}
|
|
581
|
+
if (list.some(function (s) { return s.indexOf('feature') !== -1 || s.indexOf('capability') !== -1 || s.indexOf('stagnation') !== -1; })) {
|
|
582
|
+
return 'innovate';
|
|
583
|
+
}
|
|
584
|
+
return 'optimize';
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function chooseDistillationSource(data, analysis) {
|
|
588
|
+
const grouped = data && data.grouped ? data.grouped : {};
|
|
589
|
+
let best = null;
|
|
590
|
+
Object.keys(grouped).forEach(function (geneId) {
|
|
591
|
+
const g = grouped[geneId];
|
|
592
|
+
if (!g || g.total_count <= 0) return;
|
|
593
|
+
const score = (g.total_count * 2) + (g.avg_score || 0);
|
|
594
|
+
if (!best || score > best.score) {
|
|
595
|
+
best = { gene_id: geneId, group: g, score: score };
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
return best;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
602
|
+
const source = chooseDistillationSource(data, analysis);
|
|
603
|
+
if (!source || !source.group) return null;
|
|
604
|
+
|
|
605
|
+
const group = source.group;
|
|
606
|
+
const existing = Array.isArray(existingGenes) ? existingGenes : [];
|
|
607
|
+
const sourceGene = existing.find(function (g) { return g && g.id === source.gene_id; }) || null;
|
|
608
|
+
|
|
609
|
+
const triggerFreq = {};
|
|
610
|
+
(group.triggers || []).forEach(function (arr) {
|
|
611
|
+
(Array.isArray(arr) ? arr : []).forEach(function (s) {
|
|
612
|
+
const k = String(s).toLowerCase();
|
|
613
|
+
triggerFreq[k] = (triggerFreq[k] || 0) + 1;
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
let signalsMatch = Object.keys(triggerFreq)
|
|
617
|
+
.sort(function (a, b) { return triggerFreq[b] - triggerFreq[a]; })
|
|
618
|
+
.slice(0, 6);
|
|
619
|
+
const summaryText = (group.summaries || []).slice(0, 5).join(' ');
|
|
620
|
+
const derivedTags = learningSignals.expandSignals(signalsMatch, summaryText)
|
|
621
|
+
.filter(function (tag) { return tag.indexOf('problem:') === 0 || tag.indexOf('area:') === 0; })
|
|
622
|
+
.slice(0, 4);
|
|
623
|
+
signalsMatch = Array.from(new Set(signalsMatch.concat(derivedTags)));
|
|
624
|
+
if (signalsMatch.length === 0 && sourceGene && Array.isArray(sourceGene.signals_match)) {
|
|
625
|
+
signalsMatch = sourceGene.signals_match.slice(0, 6);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const category = sourceGene && sourceGene.category ? sourceGene.category : inferCategoryFromSignals(signalsMatch);
|
|
629
|
+
const idSeed = {
|
|
630
|
+
type: 'Gene',
|
|
631
|
+
id: DISTILLED_ID_PREFIX + source.gene_id.replace(/^gene_/, '').replace(/^gene_distilled_/, ''),
|
|
632
|
+
category: category,
|
|
633
|
+
signals_match: signalsMatch,
|
|
634
|
+
strategy: sourceGene && Array.isArray(sourceGene.strategy) && sourceGene.strategy.length > 0
|
|
635
|
+
? sourceGene.strategy.slice(0, 4)
|
|
636
|
+
: [
|
|
637
|
+
'Identify the dominant repeated trigger pattern.',
|
|
638
|
+
'Apply the smallest targeted change for that pattern.',
|
|
639
|
+
'Run the narrowest validation that proves the regression is gone.',
|
|
640
|
+
'Rollback immediately if validation fails.',
|
|
641
|
+
],
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
let summaryBase = (group.summaries && group.summaries[0]) ? String(group.summaries[0]) : '';
|
|
645
|
+
if (!summaryBase) {
|
|
646
|
+
summaryBase = 'Reusable strategy for repeated successful pattern: ' + signalsMatch.slice(0, 3).join(', ');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const gene = {
|
|
650
|
+
type: 'Gene',
|
|
651
|
+
id: deriveDescriptiveId(idSeed),
|
|
652
|
+
summary: summaryBase.slice(0, 200),
|
|
653
|
+
category: category,
|
|
654
|
+
signals_match: signalsMatch,
|
|
655
|
+
preconditions: sourceGene && Array.isArray(sourceGene.preconditions) && sourceGene.preconditions.length > 0
|
|
656
|
+
? sourceGene.preconditions.slice(0, 4)
|
|
657
|
+
: ['repeated success pattern observed in recent capsules'],
|
|
658
|
+
strategy: idSeed.strategy,
|
|
659
|
+
constraints: {
|
|
660
|
+
max_files: sourceGene && sourceGene.constraints && Number(sourceGene.constraints.max_files) > 0
|
|
661
|
+
? Math.min(DISTILLED_MAX_FILES, Number(sourceGene.constraints.max_files))
|
|
662
|
+
: DISTILLED_MAX_FILES,
|
|
663
|
+
forbidden_paths: sourceGene && sourceGene.constraints && Array.isArray(sourceGene.constraints.forbidden_paths)
|
|
664
|
+
? sourceGene.constraints.forbidden_paths.slice(0, 6)
|
|
665
|
+
: ['.git', 'node_modules'],
|
|
666
|
+
},
|
|
667
|
+
validation: sourceGene && Array.isArray(sourceGene.validation) && sourceGene.validation.length > 0
|
|
668
|
+
? sourceGene.validation.slice(0, 4)
|
|
669
|
+
: ['node --test'],
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
return gene;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function finalizeDistilledGene(gene, requestLike, status) {
|
|
676
|
+
const state = readDistillerState();
|
|
677
|
+
state.last_distillation_at = new Date().toISOString();
|
|
678
|
+
state.last_data_hash = requestLike.data_hash;
|
|
679
|
+
state.last_gene_id = gene.id;
|
|
680
|
+
state.distillation_count = (state.distillation_count || 0) + 1;
|
|
681
|
+
writeDistillerState(state);
|
|
682
|
+
|
|
683
|
+
appendJsonl(distillerLogPath(), {
|
|
684
|
+
timestamp: new Date().toISOString(),
|
|
685
|
+
data_hash: requestLike.data_hash,
|
|
686
|
+
input_capsule_count: requestLike.input_capsule_count,
|
|
687
|
+
analysis_summary: requestLike.analysis_summary,
|
|
688
|
+
synthesized_gene_id: gene.id,
|
|
689
|
+
validation_passed: true,
|
|
690
|
+
validation_errors: [],
|
|
691
|
+
status: status || 'success',
|
|
692
|
+
gene: gene,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
576
696
|
// ---------------------------------------------------------------------------
|
|
577
697
|
// Step 5b: completeDistillation -- validate LLM response and save gene
|
|
578
698
|
// ---------------------------------------------------------------------------
|
|
579
699
|
function completeDistillation(responseText) {
|
|
580
|
-
|
|
581
|
-
|
|
700
|
+
const reqPath = distillRequestPath();
|
|
701
|
+
const request = readJsonIfExists(reqPath, null);
|
|
582
702
|
|
|
583
703
|
if (!request) {
|
|
584
704
|
console.warn('[Distiller] No pending distillation request found.');
|
|
585
705
|
return { ok: false, reason: 'no_request' };
|
|
586
706
|
}
|
|
587
707
|
|
|
588
|
-
|
|
708
|
+
const rawGene = extractJsonFromLlmResponse(responseText);
|
|
589
709
|
if (!rawGene) {
|
|
590
710
|
appendJsonl(distillerLogPath(), {
|
|
591
711
|
timestamp: new Date().toISOString(),
|
|
@@ -597,13 +717,13 @@ function completeDistillation(responseText) {
|
|
|
597
717
|
return { ok: false, reason: 'no_gene_in_response' };
|
|
598
718
|
}
|
|
599
719
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
720
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
721
|
+
const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
722
|
+
const existingGenes = existingGenesJson.genes || [];
|
|
603
723
|
|
|
604
|
-
|
|
724
|
+
const validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
605
725
|
|
|
606
|
-
|
|
726
|
+
const logEntry = {
|
|
607
727
|
timestamp: new Date().toISOString(),
|
|
608
728
|
data_hash: request.data_hash,
|
|
609
729
|
input_capsule_count: request.input_capsule_count,
|
|
@@ -620,18 +740,18 @@ function completeDistillation(responseText) {
|
|
|
620
740
|
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
621
741
|
}
|
|
622
742
|
|
|
623
|
-
|
|
743
|
+
const gene = validation.gene;
|
|
624
744
|
gene._distilled_meta = {
|
|
625
745
|
distilled_at: new Date().toISOString(),
|
|
626
746
|
source_capsule_count: request.input_capsule_count,
|
|
627
747
|
data_hash: request.data_hash,
|
|
628
748
|
};
|
|
629
749
|
|
|
630
|
-
|
|
750
|
+
const assetStore = require('./assetStore');
|
|
631
751
|
assetStore.upsertGene(gene);
|
|
632
752
|
console.log('[Distiller] Gene "' + gene.id + '" written to genes.json.');
|
|
633
753
|
|
|
634
|
-
|
|
754
|
+
const state = readDistillerState();
|
|
635
755
|
state.last_distillation_at = new Date().toISOString();
|
|
636
756
|
state.last_data_hash = request.data_hash;
|
|
637
757
|
state.last_gene_id = gene.id;
|
|
@@ -649,7 +769,7 @@ function completeDistillation(responseText) {
|
|
|
649
769
|
|
|
650
770
|
if (process.env.SKILL_AUTO_PUBLISH !== '0') {
|
|
651
771
|
try {
|
|
652
|
-
|
|
772
|
+
const skillPublisher = require('./skillPublisher');
|
|
653
773
|
skillPublisher.publishSkillToHub(gene).then(function (res) {
|
|
654
774
|
if (res.ok) {
|
|
655
775
|
console.log('[Distiller] Skill published to Hub: ' + (res.result?.skill_id || gene.id));
|
|
@@ -665,11 +785,67 @@ function completeDistillation(responseText) {
|
|
|
665
785
|
return { ok: true, gene: gene };
|
|
666
786
|
}
|
|
667
787
|
|
|
788
|
+
function autoDistill() {
|
|
789
|
+
const data = collectDistillationData();
|
|
790
|
+
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
791
|
+
return { ok: false, reason: 'insufficient_data' };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const state = readDistillerState();
|
|
795
|
+
if (state.last_data_hash === data.dataHash) {
|
|
796
|
+
return { ok: false, reason: 'idempotent_skip' };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const analysis = analyzePatterns(data);
|
|
800
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
801
|
+
const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
802
|
+
const existingGenes = existingGenesJson.genes || [];
|
|
803
|
+
const rawGene = synthesizeGeneFromPatterns(data, analysis, existingGenes);
|
|
804
|
+
if (!rawGene) return { ok: false, reason: 'no_candidate_gene' };
|
|
805
|
+
|
|
806
|
+
const validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
807
|
+
if (!validation.valid) {
|
|
808
|
+
appendJsonl(distillerLogPath(), {
|
|
809
|
+
timestamp: new Date().toISOString(),
|
|
810
|
+
data_hash: data.dataHash,
|
|
811
|
+
status: 'auto_validation_failed',
|
|
812
|
+
synthesized_gene_id: validation.gene ? validation.gene.id : null,
|
|
813
|
+
validation_errors: validation.errors,
|
|
814
|
+
});
|
|
815
|
+
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const gene = validation.gene;
|
|
819
|
+
gene._distilled_meta = {
|
|
820
|
+
distilled_at: new Date().toISOString(),
|
|
821
|
+
source_capsule_count: data.successCapsules.length,
|
|
822
|
+
data_hash: data.dataHash,
|
|
823
|
+
auto_distilled: true,
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const assetStore = require('./assetStore');
|
|
827
|
+
assetStore.upsertGene(gene);
|
|
828
|
+
finalizeDistilledGene(gene, {
|
|
829
|
+
data_hash: data.dataHash,
|
|
830
|
+
input_capsule_count: data.successCapsules.length,
|
|
831
|
+
analysis_summary: {
|
|
832
|
+
high_frequency_count: analysis.high_frequency.length,
|
|
833
|
+
drift_count: analysis.strategy_drift.length,
|
|
834
|
+
gap_count: analysis.coverage_gaps.length,
|
|
835
|
+
success_rate: Math.round(analysis.success_rate * 100) / 100,
|
|
836
|
+
},
|
|
837
|
+
}, 'auto_success');
|
|
838
|
+
|
|
839
|
+
return { ok: true, gene: gene, auto: true };
|
|
840
|
+
}
|
|
841
|
+
|
|
668
842
|
module.exports = {
|
|
669
843
|
collectDistillationData: collectDistillationData,
|
|
670
844
|
analyzePatterns: analyzePatterns,
|
|
845
|
+
synthesizeGeneFromPatterns: synthesizeGeneFromPatterns,
|
|
671
846
|
prepareDistillation: prepareDistillation,
|
|
672
847
|
completeDistillation: completeDistillation,
|
|
848
|
+
autoDistill: autoDistill,
|
|
673
849
|
validateSynthesizedGene: validateSynthesizedGene,
|
|
674
850
|
sanitizeSignalsMatch: sanitizeSignalsMatch,
|
|
675
851
|
shouldDistill: shouldDistill,
|