@evomap/evolver 1.28.1
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/LICENSE +22 -0
- package/README.md +290 -0
- package/README.zh-CN.md +236 -0
- package/SKILL.md +132 -0
- package/assets/gep/capsules.json +79 -0
- package/assets/gep/events.jsonl +7 -0
- package/assets/gep/genes.json +108 -0
- package/index.js +530 -0
- package/package.json +38 -0
- package/src/canary.js +13 -0
- package/src/evolve.js +1704 -0
- package/src/gep/a2a.js +173 -0
- package/src/gep/a2aProtocol.js +736 -0
- package/src/gep/analyzer.js +35 -0
- package/src/gep/assetCallLog.js +130 -0
- package/src/gep/assetStore.js +297 -0
- package/src/gep/assets.js +36 -0
- package/src/gep/bridge.js +71 -0
- package/src/gep/candidates.js +142 -0
- package/src/gep/contentHash.js +65 -0
- package/src/gep/deviceId.js +209 -0
- package/src/gep/envFingerprint.js +83 -0
- package/src/gep/hubReview.js +206 -0
- package/src/gep/hubSearch.js +237 -0
- package/src/gep/issueReporter.js +262 -0
- package/src/gep/llmReview.js +92 -0
- package/src/gep/memoryGraph.js +771 -0
- package/src/gep/memoryGraphAdapter.js +203 -0
- package/src/gep/mutation.js +186 -0
- package/src/gep/narrativeMemory.js +108 -0
- package/src/gep/paths.js +113 -0
- package/src/gep/personality.js +355 -0
- package/src/gep/prompt.js +566 -0
- package/src/gep/questionGenerator.js +212 -0
- package/src/gep/reflection.js +127 -0
- package/src/gep/sanitize.js +67 -0
- package/src/gep/selector.js +250 -0
- package/src/gep/signals.js +417 -0
- package/src/gep/skillDistiller.js +499 -0
- package/src/gep/solidify.js +1681 -0
- package/src/gep/strategy.js +126 -0
- package/src/gep/taskReceiver.js +528 -0
- package/src/gep/validationReport.js +55 -0
- package/src/ops/cleanup.js +80 -0
- package/src/ops/commentary.js +60 -0
- package/src/ops/health_check.js +106 -0
- package/src/ops/index.js +11 -0
- package/src/ops/innovation.js +67 -0
- package/src/ops/lifecycle.js +168 -0
- package/src/ops/self_repair.js +72 -0
- package/src/ops/skills_monitor.js +143 -0
- package/src/ops/trigger.js +33 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
var paths = require('./paths');
|
|
7
|
+
|
|
8
|
+
var DISTILLER_MIN_CAPSULES = parseInt(process.env.DISTILLER_MIN_CAPSULES || '10', 10) || 10;
|
|
9
|
+
var DISTILLER_INTERVAL_HOURS = parseInt(process.env.DISTILLER_INTERVAL_HOURS || '24', 10) || 24;
|
|
10
|
+
var DISTILLER_MIN_SUCCESS_RATE = parseFloat(process.env.DISTILLER_MIN_SUCCESS_RATE || '0.7') || 0.7;
|
|
11
|
+
var DISTILLED_MAX_FILES = 12;
|
|
12
|
+
var DISTILLED_ID_PREFIX = 'gene_distilled_';
|
|
13
|
+
|
|
14
|
+
function ensureDir(dir) {
|
|
15
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJsonIfExists(filePath, fallback) {
|
|
19
|
+
try {
|
|
20
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
21
|
+
var raw = fs.readFileSync(filePath, 'utf8');
|
|
22
|
+
if (!raw.trim()) return fallback;
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readJsonlIfExists(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync(filePath)) return [];
|
|
32
|
+
var raw = fs.readFileSync(filePath, 'utf8');
|
|
33
|
+
return raw.split('\n').map(function (l) { return l.trim(); }).filter(Boolean).map(function (l) {
|
|
34
|
+
try { return JSON.parse(l); } catch (e) { return null; }
|
|
35
|
+
}).filter(Boolean);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function appendJsonl(filePath, obj) {
|
|
42
|
+
ensureDir(path.dirname(filePath));
|
|
43
|
+
fs.appendFileSync(filePath, JSON.stringify(obj) + '\n', 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function distillerLogPath() {
|
|
47
|
+
return path.join(paths.getMemoryDir(), 'distiller_log.jsonl');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function distillerStatePath() {
|
|
51
|
+
return path.join(paths.getMemoryDir(), 'distiller_state.json');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readDistillerState() {
|
|
55
|
+
return readJsonIfExists(distillerStatePath(), {});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeDistillerState(state) {
|
|
59
|
+
ensureDir(path.dirname(distillerStatePath()));
|
|
60
|
+
var tmp = distillerStatePath() + '.tmp';
|
|
61
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
62
|
+
fs.renameSync(tmp, distillerStatePath());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function computeDataHash(capsules) {
|
|
66
|
+
var ids = capsules.map(function (c) { return c.id || ''; }).sort();
|
|
67
|
+
return crypto.createHash('sha256').update(ids.join('|')).digest('hex').slice(0, 16);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Step 1: collectDistillationData
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
function collectDistillationData() {
|
|
74
|
+
var assetsDir = paths.getGepAssetsDir();
|
|
75
|
+
var evoDir = paths.getEvolutionDir();
|
|
76
|
+
|
|
77
|
+
var capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
|
|
78
|
+
var capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
|
|
79
|
+
var allCapsules = [].concat(capsulesJson.capsules || [], capsulesJsonl);
|
|
80
|
+
|
|
81
|
+
var unique = new Map();
|
|
82
|
+
allCapsules.forEach(function (c) { if (c && c.id) unique.set(String(c.id), c); });
|
|
83
|
+
allCapsules = Array.from(unique.values());
|
|
84
|
+
|
|
85
|
+
var successCapsules = allCapsules.filter(function (c) {
|
|
86
|
+
if (!c || !c.outcome) return false;
|
|
87
|
+
var status = typeof c.outcome === 'string' ? c.outcome : c.outcome.status;
|
|
88
|
+
if (status !== 'success') return false;
|
|
89
|
+
var score = c.outcome && Number.isFinite(Number(c.outcome.score)) ? Number(c.outcome.score) : 1;
|
|
90
|
+
return score >= DISTILLER_MIN_SUCCESS_RATE;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
var events = readJsonlIfExists(path.join(assetsDir, 'events.jsonl'));
|
|
94
|
+
|
|
95
|
+
var memGraphPath = process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
|
|
96
|
+
var graphEntries = readJsonlIfExists(memGraphPath);
|
|
97
|
+
|
|
98
|
+
var grouped = {};
|
|
99
|
+
successCapsules.forEach(function (c) {
|
|
100
|
+
var geneId = c.gene || c.gene_id || 'unknown';
|
|
101
|
+
if (!grouped[geneId]) {
|
|
102
|
+
grouped[geneId] = {
|
|
103
|
+
gene_id: geneId, capsules: [], total_count: 0,
|
|
104
|
+
total_score: 0, triggers: [], summaries: [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
var g = grouped[geneId];
|
|
108
|
+
g.capsules.push(c);
|
|
109
|
+
g.total_count += 1;
|
|
110
|
+
g.total_score += (c.outcome && Number.isFinite(Number(c.outcome.score))) ? Number(c.outcome.score) : 0.8;
|
|
111
|
+
if (Array.isArray(c.trigger)) g.triggers.push(c.trigger);
|
|
112
|
+
if (c.summary) g.summaries.push(String(c.summary));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
Object.keys(grouped).forEach(function (id) {
|
|
116
|
+
var g = grouped[id];
|
|
117
|
+
g.avg_score = g.total_count > 0 ? g.total_score / g.total_count : 0;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
successCapsules: successCapsules,
|
|
122
|
+
allCapsules: allCapsules,
|
|
123
|
+
events: events,
|
|
124
|
+
graphEntries: graphEntries,
|
|
125
|
+
grouped: grouped,
|
|
126
|
+
dataHash: computeDataHash(successCapsules),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Step 2: analyzePatterns
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
function analyzePatterns(data) {
|
|
134
|
+
var grouped = data.grouped;
|
|
135
|
+
var report = {
|
|
136
|
+
high_frequency: [],
|
|
137
|
+
strategy_drift: [],
|
|
138
|
+
coverage_gaps: [],
|
|
139
|
+
total_success: data.successCapsules.length,
|
|
140
|
+
total_capsules: data.allCapsules.length,
|
|
141
|
+
success_rate: data.allCapsules.length > 0 ? data.successCapsules.length / data.allCapsules.length : 0,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
Object.keys(grouped).forEach(function (geneId) {
|
|
145
|
+
var g = grouped[geneId];
|
|
146
|
+
if (g.total_count >= 5) {
|
|
147
|
+
var flat = [];
|
|
148
|
+
g.triggers.forEach(function (t) { if (Array.isArray(t)) flat = flat.concat(t); });
|
|
149
|
+
var freq = {};
|
|
150
|
+
flat.forEach(function (t) { var k = String(t).toLowerCase(); freq[k] = (freq[k] || 0) + 1; });
|
|
151
|
+
var top = Object.keys(freq).sort(function (a, b) { return freq[b] - freq[a]; }).slice(0, 5);
|
|
152
|
+
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
|
+
|
|
155
|
+
if (g.summaries.length >= 3) {
|
|
156
|
+
var first = g.summaries[0];
|
|
157
|
+
var last = g.summaries[g.summaries.length - 1];
|
|
158
|
+
if (first !== last) {
|
|
159
|
+
var fw = new Set(first.toLowerCase().split(/\s+/));
|
|
160
|
+
var lw = new Set(last.toLowerCase().split(/\s+/));
|
|
161
|
+
var inter = 0;
|
|
162
|
+
fw.forEach(function (w) { if (lw.has(w)) inter++; });
|
|
163
|
+
var union = fw.size + lw.size - inter;
|
|
164
|
+
var sim = union > 0 ? inter / union : 1;
|
|
165
|
+
if (sim < 0.6) {
|
|
166
|
+
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
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
var signalFreq = {};
|
|
173
|
+
(data.events || []).forEach(function (evt) {
|
|
174
|
+
if (evt && Array.isArray(evt.signals)) {
|
|
175
|
+
evt.signals.forEach(function (s) { var k = String(s).toLowerCase(); signalFreq[k] = (signalFreq[k] || 0) + 1; });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
var covered = new Set();
|
|
179
|
+
Object.keys(grouped).forEach(function (geneId) {
|
|
180
|
+
grouped[geneId].triggers.forEach(function (t) {
|
|
181
|
+
if (Array.isArray(t)) t.forEach(function (s) { covered.add(String(s).toLowerCase()); });
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
var gaps = Object.keys(signalFreq)
|
|
185
|
+
.filter(function (s) { return signalFreq[s] >= 3 && !covered.has(s); })
|
|
186
|
+
.sort(function (a, b) { return signalFreq[b] - signalFreq[a]; })
|
|
187
|
+
.slice(0, 10);
|
|
188
|
+
if (gaps.length > 0) {
|
|
189
|
+
report.coverage_gaps = gaps.map(function (s) { return { signal: s, frequency: signalFreq[s] }; });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return report;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Step 3: LLM response parsing
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
function extractJsonFromLlmResponse(text) {
|
|
199
|
+
var str = String(text || '');
|
|
200
|
+
var buffer = '';
|
|
201
|
+
var depth = 0;
|
|
202
|
+
for (var i = 0; i < str.length; i++) {
|
|
203
|
+
var ch = str[i];
|
|
204
|
+
if (ch === '{') { if (depth === 0) buffer = ''; depth++; buffer += ch; }
|
|
205
|
+
else if (ch === '}') {
|
|
206
|
+
depth--; buffer += ch;
|
|
207
|
+
if (depth === 0 && buffer.length > 2) {
|
|
208
|
+
try { var obj = JSON.parse(buffer); if (obj && typeof obj === 'object' && obj.type === 'Gene') return obj; } catch (e) {}
|
|
209
|
+
buffer = '';
|
|
210
|
+
}
|
|
211
|
+
if (depth < 0) depth = 0;
|
|
212
|
+
} else if (depth > 0) { buffer += ch; }
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
218
|
+
var genesRef = existingGenes.map(function (g) {
|
|
219
|
+
return { id: g.id, category: g.category || null, signals_match: g.signals_match || [] };
|
|
220
|
+
});
|
|
221
|
+
var samples = sampleCapsules.slice(0, 8).map(function (c) {
|
|
222
|
+
return { gene: c.gene || c.gene_id || null, trigger: c.trigger || [], summary: (c.summary || '').slice(0, 200), outcome: c.outcome || null };
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return [
|
|
226
|
+
'You are a Gene synthesis engine for the GEP (Genome Evolution Protocol).',
|
|
227
|
+
'',
|
|
228
|
+
'Analyze the following successful evolution capsules and extract a reusable Gene.',
|
|
229
|
+
'',
|
|
230
|
+
'RULES:',
|
|
231
|
+
'- Strategy steps MUST be actionable operations, NOT summaries',
|
|
232
|
+
'- Each step must be a concrete instruction an AI agent can execute',
|
|
233
|
+
'- Do NOT describe what happened; describe what TO DO next time',
|
|
234
|
+
'- The Gene MUST have a unique id starting with "' + DISTILLED_ID_PREFIX + '"',
|
|
235
|
+
'- constraints.max_files MUST be <= ' + DISTILLED_MAX_FILES,
|
|
236
|
+
'- constraints.forbidden_paths MUST include at least [".git", "node_modules"]',
|
|
237
|
+
'- Output valid Gene JSON only (no markdown, no explanation)',
|
|
238
|
+
'',
|
|
239
|
+
'SUCCESSFUL CAPSULES (grouped by pattern):',
|
|
240
|
+
JSON.stringify(samples, null, 2),
|
|
241
|
+
'',
|
|
242
|
+
'EXISTING GENES (avoid duplication):',
|
|
243
|
+
JSON.stringify(genesRef, null, 2),
|
|
244
|
+
'',
|
|
245
|
+
'ANALYSIS:',
|
|
246
|
+
JSON.stringify(analysis, null, 2),
|
|
247
|
+
'',
|
|
248
|
+
'Output a single Gene JSON object with these fields:',
|
|
249
|
+
'{ "type": "Gene", "id": "gene_distilled_...", "category": "...", "signals_match": [...], "preconditions": [...], "strategy": [...], "constraints": { "max_files": N, "forbidden_paths": [...] }, "validation": [...] }',
|
|
250
|
+
].join('\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function distillRequestPath() {
|
|
254
|
+
return path.join(paths.getMemoryDir(), 'distill_request.json');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Step 4: validateSynthesizedGene
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
function validateSynthesizedGene(gene, existingGenes) {
|
|
261
|
+
var errors = [];
|
|
262
|
+
if (!gene || typeof gene !== 'object') return { valid: false, errors: ['gene is not an object'] };
|
|
263
|
+
|
|
264
|
+
if (gene.type !== 'Gene') errors.push('missing or wrong type (must be "Gene")');
|
|
265
|
+
if (!gene.id || typeof gene.id !== 'string') errors.push('missing id');
|
|
266
|
+
if (!gene.category) errors.push('missing category');
|
|
267
|
+
if (!Array.isArray(gene.signals_match) || gene.signals_match.length === 0) errors.push('missing or empty signals_match');
|
|
268
|
+
if (!Array.isArray(gene.strategy) || gene.strategy.length === 0) errors.push('missing or empty strategy');
|
|
269
|
+
|
|
270
|
+
if (gene.id && !String(gene.id).startsWith(DISTILLED_ID_PREFIX)) {
|
|
271
|
+
gene.id = DISTILLED_ID_PREFIX + String(gene.id).replace(/^gene_/, '');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!gene.constraints || typeof gene.constraints !== 'object') gene.constraints = {};
|
|
275
|
+
if (!Array.isArray(gene.constraints.forbidden_paths) || gene.constraints.forbidden_paths.length === 0) {
|
|
276
|
+
gene.constraints.forbidden_paths = ['.git', 'node_modules'];
|
|
277
|
+
}
|
|
278
|
+
if (!gene.constraints.forbidden_paths.some(function (p) { return p === '.git' || p === 'node_modules'; })) {
|
|
279
|
+
errors.push('constraints.forbidden_paths must include .git or node_modules');
|
|
280
|
+
}
|
|
281
|
+
if (!gene.constraints.max_files || gene.constraints.max_files > DISTILLED_MAX_FILES) {
|
|
282
|
+
gene.constraints.max_files = DISTILLED_MAX_FILES;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
|
286
|
+
if (Array.isArray(gene.validation)) {
|
|
287
|
+
gene.validation = gene.validation.filter(function (cmd) {
|
|
288
|
+
var c = String(cmd || '').trim();
|
|
289
|
+
if (!c) return false;
|
|
290
|
+
if (!ALLOWED_PREFIXES.some(function (p) { return c.startsWith(p); })) return false;
|
|
291
|
+
if (/`|\$\(/.test(c)) return false;
|
|
292
|
+
var stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
|
|
293
|
+
return !/[;&|><]/.test(stripped);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
var existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
|
|
298
|
+
if (gene.id && existingIds.has(gene.id)) {
|
|
299
|
+
gene.id = gene.id + '_' + Date.now().toString(36);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (gene.signals_match && existingGenes && existingGenes.length > 0) {
|
|
303
|
+
var newSet = new Set(gene.signals_match.map(function (s) { return String(s).toLowerCase(); }));
|
|
304
|
+
for (var i = 0; i < existingGenes.length; i++) {
|
|
305
|
+
var eg = existingGenes[i];
|
|
306
|
+
var egSet = new Set((eg.signals_match || []).map(function (s) { return String(s).toLowerCase(); }));
|
|
307
|
+
if (newSet.size > 0 && egSet.size > 0) {
|
|
308
|
+
var overlap = 0;
|
|
309
|
+
newSet.forEach(function (s) { if (egSet.has(s)) overlap++; });
|
|
310
|
+
if (overlap === newSet.size && overlap === egSet.size) {
|
|
311
|
+
errors.push('signals_match fully overlaps with existing gene: ' + eg.id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { valid: errors.length === 0, errors: errors, gene: gene };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// shouldDistill: gate check
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
function shouldDistill() {
|
|
324
|
+
if (String(process.env.SKILL_DISTILLER || 'true').toLowerCase() === 'false') return false;
|
|
325
|
+
|
|
326
|
+
var state = readDistillerState();
|
|
327
|
+
if (state.last_distillation_at) {
|
|
328
|
+
var elapsed = Date.now() - new Date(state.last_distillation_at).getTime();
|
|
329
|
+
if (elapsed < DISTILLER_INTERVAL_HOURS * 3600000) return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
var assetsDir = paths.getGepAssetsDir();
|
|
333
|
+
var capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
|
|
334
|
+
var capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
|
|
335
|
+
var all = [].concat(capsulesJson.capsules || [], capsulesJsonl);
|
|
336
|
+
|
|
337
|
+
var recent = all.slice(-10);
|
|
338
|
+
var recentSuccess = recent.filter(function (c) {
|
|
339
|
+
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
340
|
+
}).length;
|
|
341
|
+
if (recentSuccess < 7) return false;
|
|
342
|
+
|
|
343
|
+
var totalSuccess = all.filter(function (c) {
|
|
344
|
+
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
345
|
+
}).length;
|
|
346
|
+
if (totalSuccess < DISTILLER_MIN_CAPSULES) return false;
|
|
347
|
+
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// Step 5a: prepareDistillation -- collect data, build prompt, write to file
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
function prepareDistillation() {
|
|
355
|
+
console.log('[Distiller] Preparing skill distillation...');
|
|
356
|
+
|
|
357
|
+
var data = collectDistillationData();
|
|
358
|
+
console.log('[Distiller] Collected ' + data.successCapsules.length + ' successful capsules across ' + Object.keys(data.grouped).length + ' gene groups.');
|
|
359
|
+
|
|
360
|
+
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
361
|
+
console.log('[Distiller] Not enough successful capsules (' + data.successCapsules.length + ' < ' + DISTILLER_MIN_CAPSULES + '). Skipping.');
|
|
362
|
+
return { ok: false, reason: 'insufficient_data' };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
var state = readDistillerState();
|
|
366
|
+
if (state.last_data_hash === data.dataHash) {
|
|
367
|
+
console.log('[Distiller] Data unchanged since last distillation (hash: ' + data.dataHash + '). Skipping.');
|
|
368
|
+
return { ok: false, reason: 'idempotent_skip' };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
var analysis = analyzePatterns(data);
|
|
372
|
+
console.log('[Distiller] Analysis: high_freq=' + analysis.high_frequency.length + ' drift=' + analysis.strategy_drift.length + ' gaps=' + analysis.coverage_gaps.length);
|
|
373
|
+
|
|
374
|
+
var assetsDir = paths.getGepAssetsDir();
|
|
375
|
+
var existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
376
|
+
var existingGenes = existingGenesJson.genes || [];
|
|
377
|
+
|
|
378
|
+
var prompt = buildDistillationPrompt(analysis, existingGenes, data.successCapsules);
|
|
379
|
+
|
|
380
|
+
var memDir = paths.getMemoryDir();
|
|
381
|
+
ensureDir(memDir);
|
|
382
|
+
var promptFileName = 'distill_prompt_' + Date.now() + '.txt';
|
|
383
|
+
var promptPath = path.join(memDir, promptFileName);
|
|
384
|
+
fs.writeFileSync(promptPath, prompt, 'utf8');
|
|
385
|
+
|
|
386
|
+
var reqPath = distillRequestPath();
|
|
387
|
+
var requestData = {
|
|
388
|
+
type: 'DistillationRequest',
|
|
389
|
+
created_at: new Date().toISOString(),
|
|
390
|
+
prompt_path: promptPath,
|
|
391
|
+
data_hash: data.dataHash,
|
|
392
|
+
input_capsule_count: data.successCapsules.length,
|
|
393
|
+
analysis_summary: {
|
|
394
|
+
high_frequency_count: analysis.high_frequency.length,
|
|
395
|
+
drift_count: analysis.strategy_drift.length,
|
|
396
|
+
gap_count: analysis.coverage_gaps.length,
|
|
397
|
+
success_rate: Math.round(analysis.success_rate * 100) / 100,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
fs.writeFileSync(reqPath, JSON.stringify(requestData, null, 2) + '\n', 'utf8');
|
|
401
|
+
|
|
402
|
+
console.log('[Distiller] Prompt written to: ' + promptPath);
|
|
403
|
+
return { ok: true, promptPath: promptPath, requestPath: reqPath, dataHash: data.dataHash };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Step 5b: completeDistillation -- validate LLM response and save gene
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
function completeDistillation(responseText) {
|
|
410
|
+
var reqPath = distillRequestPath();
|
|
411
|
+
var request = readJsonIfExists(reqPath, null);
|
|
412
|
+
|
|
413
|
+
if (!request) {
|
|
414
|
+
console.warn('[Distiller] No pending distillation request found.');
|
|
415
|
+
return { ok: false, reason: 'no_request' };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
var rawGene = extractJsonFromLlmResponse(responseText);
|
|
419
|
+
if (!rawGene) {
|
|
420
|
+
appendJsonl(distillerLogPath(), {
|
|
421
|
+
timestamp: new Date().toISOString(),
|
|
422
|
+
data_hash: request.data_hash,
|
|
423
|
+
status: 'error',
|
|
424
|
+
error: 'LLM response did not contain a valid Gene JSON',
|
|
425
|
+
});
|
|
426
|
+
console.error('[Distiller] LLM response did not contain a valid Gene JSON.');
|
|
427
|
+
return { ok: false, reason: 'no_gene_in_response' };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
var assetsDir = paths.getGepAssetsDir();
|
|
431
|
+
var existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
432
|
+
var existingGenes = existingGenesJson.genes || [];
|
|
433
|
+
|
|
434
|
+
var validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
435
|
+
|
|
436
|
+
var logEntry = {
|
|
437
|
+
timestamp: new Date().toISOString(),
|
|
438
|
+
data_hash: request.data_hash,
|
|
439
|
+
input_capsule_count: request.input_capsule_count,
|
|
440
|
+
analysis_summary: request.analysis_summary,
|
|
441
|
+
synthesized_gene_id: validation.gene ? validation.gene.id : null,
|
|
442
|
+
validation_passed: validation.valid,
|
|
443
|
+
validation_errors: validation.errors,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
if (!validation.valid) {
|
|
447
|
+
logEntry.status = 'validation_failed';
|
|
448
|
+
appendJsonl(distillerLogPath(), logEntry);
|
|
449
|
+
console.warn('[Distiller] Gene failed validation: ' + validation.errors.join(', '));
|
|
450
|
+
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
var gene = validation.gene;
|
|
454
|
+
gene._distilled_meta = {
|
|
455
|
+
distilled_at: new Date().toISOString(),
|
|
456
|
+
source_capsule_count: request.input_capsule_count,
|
|
457
|
+
data_hash: request.data_hash,
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
var assetStore = require('./assetStore');
|
|
461
|
+
assetStore.upsertGene(gene);
|
|
462
|
+
console.log('[Distiller] Gene "' + gene.id + '" written to genes.json.');
|
|
463
|
+
|
|
464
|
+
var state = readDistillerState();
|
|
465
|
+
state.last_distillation_at = new Date().toISOString();
|
|
466
|
+
state.last_data_hash = request.data_hash;
|
|
467
|
+
state.last_gene_id = gene.id;
|
|
468
|
+
state.distillation_count = (state.distillation_count || 0) + 1;
|
|
469
|
+
writeDistillerState(state);
|
|
470
|
+
|
|
471
|
+
logEntry.status = 'success';
|
|
472
|
+
logEntry.gene = gene;
|
|
473
|
+
appendJsonl(distillerLogPath(), logEntry);
|
|
474
|
+
|
|
475
|
+
try { fs.unlinkSync(reqPath); } catch (e) {}
|
|
476
|
+
try { if (request.prompt_path) fs.unlinkSync(request.prompt_path); } catch (e) {}
|
|
477
|
+
|
|
478
|
+
console.log('[Distiller] Distillation complete. New gene: ' + gene.id);
|
|
479
|
+
return { ok: true, gene: gene };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = {
|
|
483
|
+
collectDistillationData: collectDistillationData,
|
|
484
|
+
analyzePatterns: analyzePatterns,
|
|
485
|
+
prepareDistillation: prepareDistillation,
|
|
486
|
+
completeDistillation: completeDistillation,
|
|
487
|
+
validateSynthesizedGene: validateSynthesizedGene,
|
|
488
|
+
shouldDistill: shouldDistill,
|
|
489
|
+
buildDistillationPrompt: buildDistillationPrompt,
|
|
490
|
+
extractJsonFromLlmResponse: extractJsonFromLlmResponse,
|
|
491
|
+
computeDataHash: computeDataHash,
|
|
492
|
+
distillerLogPath: distillerLogPath,
|
|
493
|
+
distillerStatePath: distillerStatePath,
|
|
494
|
+
distillRequestPath: distillRequestPath,
|
|
495
|
+
readDistillerState: readDistillerState,
|
|
496
|
+
writeDistillerState: writeDistillerState,
|
|
497
|
+
DISTILLED_ID_PREFIX: DISTILLED_ID_PREFIX,
|
|
498
|
+
DISTILLED_MAX_FILES: DISTILLED_MAX_FILES,
|
|
499
|
+
};
|