@evomap/evolver 1.32.1 → 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/package.json +1 -1
- package/src/gep/a2aProtocol.js +20 -5
- package/src/gep/assetStore.js +28 -7
- package/src/gep/learningSignals.js +1 -0
- package/src/gep/skillDistiller.js +139 -140
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.2",
|
|
4
4
|
"description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/src/gep/a2aProtocol.js
CHANGED
|
@@ -305,7 +305,9 @@ function unwrapAssetFromMessage(input) {
|
|
|
305
305
|
function ensureDir(dir) {
|
|
306
306
|
try {
|
|
307
307
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
308
|
-
} catch (e) {
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.warn('[a2aProtocol] ensureDir failed:', dir, e && e.message || e);
|
|
310
|
+
}
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
function defaultA2ADir() {
|
|
@@ -337,7 +339,9 @@ function fileTransportReceive(opts) {
|
|
|
337
339
|
if (msg && msg.protocol === PROTOCOL_NAME) messages.push(msg);
|
|
338
340
|
} catch (e) {}
|
|
339
341
|
}
|
|
340
|
-
} catch (e) {
|
|
342
|
+
} catch (e) {
|
|
343
|
+
console.warn('[a2aProtocol] Failed to read inbox file:', files[fi], e && e.message || e);
|
|
344
|
+
}
|
|
341
345
|
}
|
|
342
346
|
return messages;
|
|
343
347
|
}
|
|
@@ -407,6 +411,8 @@ var _latestNoveltyHint = null;
|
|
|
407
411
|
var _latestCapabilityGaps = [];
|
|
408
412
|
var _pendingCommitmentUpdates = [];
|
|
409
413
|
var _cachedHubNodeSecret = null;
|
|
414
|
+
var _cachedHubNodeSecretAt = 0;
|
|
415
|
+
var _SECRET_CACHE_TTL_MS = 60000;
|
|
410
416
|
var _heartbeatIntervalMs = 0;
|
|
411
417
|
var _heartbeatRunning = false;
|
|
412
418
|
|
|
@@ -428,7 +434,9 @@ function _persistNodeSecret(secret) {
|
|
|
428
434
|
fs.mkdirSync(NODE_ID_DIR, { recursive: true, mode: 0o700 });
|
|
429
435
|
}
|
|
430
436
|
fs.writeFileSync(NODE_SECRET_FILE, secret, { encoding: 'utf8', mode: 0o600 });
|
|
431
|
-
} catch {
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.warn('[a2aProtocol] Failed to persist node secret:', e && e.message || e);
|
|
439
|
+
}
|
|
432
440
|
}
|
|
433
441
|
|
|
434
442
|
function getHubUrl() {
|
|
@@ -464,6 +472,7 @@ function sendHelloToHub() {
|
|
|
464
472
|
|| null;
|
|
465
473
|
if (secret && /^[a-f0-9]{64}$/i.test(secret)) {
|
|
466
474
|
_cachedHubNodeSecret = secret;
|
|
475
|
+
_cachedHubNodeSecretAt = Date.now();
|
|
467
476
|
_persistNodeSecret(secret);
|
|
468
477
|
}
|
|
469
478
|
return { ok: true, response: data };
|
|
@@ -473,10 +482,14 @@ function sendHelloToHub() {
|
|
|
473
482
|
|
|
474
483
|
function getHubNodeSecret() {
|
|
475
484
|
if (process.env.A2A_NODE_SECRET) return process.env.A2A_NODE_SECRET;
|
|
476
|
-
|
|
485
|
+
var now = Date.now();
|
|
486
|
+
if (_cachedHubNodeSecret && (now - _cachedHubNodeSecretAt) < _SECRET_CACHE_TTL_MS) {
|
|
487
|
+
return _cachedHubNodeSecret;
|
|
488
|
+
}
|
|
477
489
|
var persisted = _loadPersistedNodeSecret();
|
|
478
490
|
if (persisted) {
|
|
479
491
|
_cachedHubNodeSecret = persisted;
|
|
492
|
+
_cachedHubNodeSecretAt = now;
|
|
480
493
|
return persisted;
|
|
481
494
|
}
|
|
482
495
|
if (process.env.A2A_HUB_TOKEN) return process.env.A2A_HUB_TOKEN;
|
|
@@ -529,7 +542,9 @@ function sendHeartbeat() {
|
|
|
529
542
|
meta.env_fingerprint = fp;
|
|
530
543
|
_heartbeatFpSent = true;
|
|
531
544
|
}
|
|
532
|
-
} catch {
|
|
545
|
+
} catch (e) {
|
|
546
|
+
console.warn('[a2aProtocol] Failed to capture env fingerprint:', e && e.message || e);
|
|
547
|
+
}
|
|
533
548
|
}
|
|
534
549
|
|
|
535
550
|
if (Object.keys(meta).length > 0) {
|
package/src/gep/assetStore.js
CHANGED
|
@@ -100,7 +100,9 @@ function loadGenes() {
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
} catch(e) {
|
|
103
|
+
} catch(e) {
|
|
104
|
+
console.warn('[AssetStore] Failed to read genes.jsonl:', e && e.message || e);
|
|
105
|
+
}
|
|
104
106
|
|
|
105
107
|
// Combine and deduplicate by ID (JSONL takes precedence if newer, but here we just merge)
|
|
106
108
|
const combined = [...jsonGenes, ...jsonlGenes];
|
|
@@ -124,7 +126,9 @@ function loadCapsules() {
|
|
|
124
126
|
}
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
|
-
} catch(e) {
|
|
129
|
+
} catch(e) {
|
|
130
|
+
console.warn('[AssetStore] Failed to read capsules.jsonl:', e && e.message || e);
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
// Combine and deduplicate by ID
|
|
130
134
|
const combined = [...legacy, ...jsonlCapsules];
|
|
@@ -144,7 +148,10 @@ function getLastEventId() {
|
|
|
144
148
|
if (lines.length === 0) return null;
|
|
145
149
|
const last = JSON.parse(lines[lines.length - 1]);
|
|
146
150
|
return last && typeof last.id === 'string' ? last.id : null;
|
|
147
|
-
} catch {
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.warn('[AssetStore] Failed to read last event ID:', e && e.message || e);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
function readAllEvents() {
|
|
@@ -155,7 +162,10 @@ function readAllEvents() {
|
|
|
155
162
|
return raw.split('\n').map(l => l.trim()).filter(Boolean).map(l => {
|
|
156
163
|
try { return JSON.parse(l); } catch { return null; }
|
|
157
164
|
}).filter(Boolean);
|
|
158
|
-
} catch {
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.warn('[AssetStore] Failed to read events.jsonl:', e && e.message || e);
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
function appendEventJsonl(eventObj) {
|
|
@@ -198,7 +208,10 @@ function readRecentCandidates(limit = 20) {
|
|
|
198
208
|
} finally {
|
|
199
209
|
fs.closeSync(fd);
|
|
200
210
|
}
|
|
201
|
-
} catch {
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.warn('[AssetStore] Failed to read candidates.jsonl:', e && e.message || e);
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
function readRecentExternalCandidates(limit = 50) {
|
|
@@ -225,14 +238,21 @@ function readRecentExternalCandidates(limit = 50) {
|
|
|
225
238
|
} finally {
|
|
226
239
|
fs.closeSync(fd);
|
|
227
240
|
}
|
|
228
|
-
} catch {
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.warn('[AssetStore] Failed to read external_candidates.jsonl:', e && e.message || e);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
// Safety net: ensure schema_version and asset_id are present before writing.
|
|
232
248
|
function ensureSchemaFields(obj) {
|
|
233
249
|
if (!obj || typeof obj !== 'object') return obj;
|
|
234
250
|
if (!obj.schema_version) obj.schema_version = SCHEMA_VERSION;
|
|
235
|
-
if (!obj.asset_id) {
|
|
251
|
+
if (!obj.asset_id) {
|
|
252
|
+
try { obj.asset_id = computeAssetId(obj); } catch (e) {
|
|
253
|
+
console.warn('[AssetStore] Failed to compute asset ID:', e && e.message || e);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
236
256
|
return obj;
|
|
237
257
|
}
|
|
238
258
|
|
|
@@ -287,6 +307,7 @@ function readRecentFailedCapsules(limit) {
|
|
|
287
307
|
var list = Array.isArray(current.failed_capsules) ? current.failed_capsules : [];
|
|
288
308
|
return list.slice(Math.max(0, list.length - n));
|
|
289
309
|
} catch (e) {
|
|
310
|
+
console.warn('[AssetStore] Failed to read failed_capsules.json:', e && e.message || e);
|
|
290
311
|
return [];
|
|
291
312
|
}
|
|
292
313
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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_';
|
|
14
14
|
|
|
15
15
|
function ensureDir(dir) {
|
|
16
16
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
@@ -19,7 +19,7 @@ function ensureDir(dir) {
|
|
|
19
19
|
function readJsonIfExists(filePath, fallback) {
|
|
20
20
|
try {
|
|
21
21
|
if (!fs.existsSync(filePath)) return fallback;
|
|
22
|
-
|
|
22
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
23
23
|
if (!raw.trim()) return fallback;
|
|
24
24
|
return JSON.parse(raw);
|
|
25
25
|
} catch (e) {
|
|
@@ -30,7 +30,7 @@ function readJsonIfExists(filePath, fallback) {
|
|
|
30
30
|
function readJsonlIfExists(filePath) {
|
|
31
31
|
try {
|
|
32
32
|
if (!fs.existsSync(filePath)) return [];
|
|
33
|
-
|
|
33
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
34
34
|
return raw.split('\n').map(function (l) { return l.trim(); }).filter(Boolean).map(function (l) {
|
|
35
35
|
try { return JSON.parse(l); } catch (e) { return null; }
|
|
36
36
|
}).filter(Boolean);
|
|
@@ -58,13 +58,13 @@ function readDistillerState() {
|
|
|
58
58
|
|
|
59
59
|
function writeDistillerState(state) {
|
|
60
60
|
ensureDir(path.dirname(distillerStatePath()));
|
|
61
|
-
|
|
61
|
+
const tmp = distillerStatePath() + '.tmp';
|
|
62
62
|
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
63
63
|
fs.renameSync(tmp, distillerStatePath());
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function computeDataHash(capsules) {
|
|
67
|
-
|
|
67
|
+
const ids = capsules.map(function (c) { return c.id || ''; }).sort();
|
|
68
68
|
return crypto.createHash('sha256').update(ids.join('|')).digest('hex').slice(0, 16);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -72,40 +72,40 @@ function computeDataHash(capsules) {
|
|
|
72
72
|
// Step 1: collectDistillationData
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
74
|
function collectDistillationData() {
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
76
|
+
const evoDir = paths.getEvolutionDir();
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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);
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const unique = new Map();
|
|
83
83
|
allCapsules.forEach(function (c) { if (c && c.id) unique.set(String(c.id), c); });
|
|
84
84
|
allCapsules = Array.from(unique.values());
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
const successCapsules = allCapsules.filter(function (c) {
|
|
87
87
|
if (!c || !c.outcome) return false;
|
|
88
|
-
|
|
88
|
+
const status = typeof c.outcome === 'string' ? c.outcome : c.outcome.status;
|
|
89
89
|
if (status !== 'success') return false;
|
|
90
|
-
|
|
90
|
+
const score = c.outcome && Number.isFinite(Number(c.outcome.score)) ? Number(c.outcome.score) : 1;
|
|
91
91
|
return score >= DISTILLER_MIN_SUCCESS_RATE;
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
const events = readJsonlIfExists(path.join(assetsDir, 'events.jsonl'));
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const memGraphPath = process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
|
|
97
|
+
const graphEntries = readJsonlIfExists(memGraphPath);
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
const grouped = {};
|
|
100
100
|
successCapsules.forEach(function (c) {
|
|
101
|
-
|
|
101
|
+
const geneId = c.gene || c.gene_id || 'unknown';
|
|
102
102
|
if (!grouped[geneId]) {
|
|
103
103
|
grouped[geneId] = {
|
|
104
104
|
gene_id: geneId, capsules: [], total_count: 0,
|
|
105
105
|
total_score: 0, triggers: [], summaries: [],
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
const g = grouped[geneId];
|
|
109
109
|
g.capsules.push(c);
|
|
110
110
|
g.total_count += 1;
|
|
111
111
|
g.total_score += (c.outcome && Number.isFinite(Number(c.outcome.score))) ? Number(c.outcome.score) : 0.8;
|
|
@@ -114,7 +114,7 @@ function collectDistillationData() {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
Object.keys(grouped).forEach(function (id) {
|
|
117
|
-
|
|
117
|
+
const g = grouped[id];
|
|
118
118
|
g.avg_score = g.total_count > 0 ? g.total_score / g.total_count : 0;
|
|
119
119
|
});
|
|
120
120
|
|
|
@@ -132,8 +132,8 @@ function collectDistillationData() {
|
|
|
132
132
|
// Step 2: analyzePatterns
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
134
134
|
function analyzePatterns(data) {
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const grouped = data.grouped;
|
|
136
|
+
const report = {
|
|
137
137
|
high_frequency: [],
|
|
138
138
|
strategy_drift: [],
|
|
139
139
|
coverage_gaps: [],
|
|
@@ -143,26 +143,26 @@ function analyzePatterns(data) {
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
Object.keys(grouped).forEach(function (geneId) {
|
|
146
|
-
|
|
146
|
+
const g = grouped[geneId];
|
|
147
147
|
if (g.total_count >= 5) {
|
|
148
|
-
|
|
148
|
+
let flat = [];
|
|
149
149
|
g.triggers.forEach(function (t) { if (Array.isArray(t)) flat = flat.concat(t); });
|
|
150
|
-
|
|
151
|
-
flat.forEach(function (t) {
|
|
152
|
-
|
|
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);
|
|
153
153
|
report.high_frequency.push({ gene_id: geneId, count: g.total_count, avg_score: Math.round(g.avg_score * 100) / 100, top_triggers: top });
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
if (g.summaries.length >= 3) {
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const first = g.summaries[0];
|
|
158
|
+
const last = g.summaries[g.summaries.length - 1];
|
|
159
159
|
if (first !== last) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
const fw = new Set(first.toLowerCase().split(/\s+/));
|
|
161
|
+
const lw = new Set(last.toLowerCase().split(/\s+/));
|
|
162
|
+
let inter = 0;
|
|
163
163
|
fw.forEach(function (w) { if (lw.has(w)) inter++; });
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const union = fw.size + lw.size - inter;
|
|
165
|
+
const sim = union > 0 ? inter / union : 1;
|
|
166
166
|
if (sim < 0.6) {
|
|
167
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) });
|
|
168
168
|
}
|
|
@@ -170,19 +170,19 @@ function analyzePatterns(data) {
|
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
const signalFreq = {};
|
|
174
174
|
(data.events || []).forEach(function (evt) {
|
|
175
175
|
if (evt && Array.isArray(evt.signals)) {
|
|
176
|
-
evt.signals.forEach(function (s) {
|
|
176
|
+
evt.signals.forEach(function (s) { const k = String(s).toLowerCase(); signalFreq[k] = (signalFreq[k] || 0) + 1; });
|
|
177
177
|
}
|
|
178
178
|
});
|
|
179
|
-
|
|
179
|
+
const covered = new Set();
|
|
180
180
|
Object.keys(grouped).forEach(function (geneId) {
|
|
181
181
|
grouped[geneId].triggers.forEach(function (t) {
|
|
182
182
|
if (Array.isArray(t)) t.forEach(function (s) { covered.add(String(s).toLowerCase()); });
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
|
-
|
|
185
|
+
const gaps = Object.keys(signalFreq)
|
|
186
186
|
.filter(function (s) { return signalFreq[s] >= 3 && !covered.has(s); })
|
|
187
187
|
.sort(function (a, b) { return signalFreq[b] - signalFreq[a]; })
|
|
188
188
|
.slice(0, 10);
|
|
@@ -197,16 +197,16 @@ function analyzePatterns(data) {
|
|
|
197
197
|
// Step 3: LLM response parsing
|
|
198
198
|
// ---------------------------------------------------------------------------
|
|
199
199
|
function extractJsonFromLlmResponse(text) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
for (
|
|
204
|
-
|
|
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];
|
|
205
205
|
if (ch === '{') { if (depth === 0) buffer = ''; depth++; buffer += ch; }
|
|
206
206
|
else if (ch === '}') {
|
|
207
207
|
depth--; buffer += ch;
|
|
208
208
|
if (depth === 0 && buffer.length > 2) {
|
|
209
|
-
try {
|
|
209
|
+
try { const obj = JSON.parse(buffer); if (obj && typeof obj === 'object' && obj.type === 'Gene') return obj; } catch (e) {}
|
|
210
210
|
buffer = '';
|
|
211
211
|
}
|
|
212
212
|
if (depth < 0) depth = 0;
|
|
@@ -216,10 +216,10 @@ function extractJsonFromLlmResponse(text) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
function buildDistillationPrompt(analysis, existingGenes, sampleCapsules) {
|
|
219
|
-
|
|
219
|
+
const genesRef = existingGenes.map(function (g) {
|
|
220
220
|
return { id: g.id, category: g.category || null, signals_match: g.signals_match || [] };
|
|
221
221
|
});
|
|
222
|
-
|
|
222
|
+
const samples = sampleCapsules.slice(0, 8).map(function (c) {
|
|
223
223
|
return { gene: c.gene || c.gene_id || null, trigger: c.trigger || [], summary: (c.summary || '').slice(0, 200), outcome: c.outcome || null };
|
|
224
224
|
});
|
|
225
225
|
|
|
@@ -319,7 +319,7 @@ function distillRequestPath() {
|
|
|
319
319
|
// Derive a descriptive ID from gene content when the LLM gives a bad name
|
|
320
320
|
// ---------------------------------------------------------------------------
|
|
321
321
|
function deriveDescriptiveId(gene) {
|
|
322
|
-
|
|
322
|
+
let words = [];
|
|
323
323
|
if (Array.isArray(gene.signals_match)) {
|
|
324
324
|
gene.signals_match.slice(0, 3).forEach(function (s) {
|
|
325
325
|
String(s).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
@@ -328,7 +328,7 @@ function deriveDescriptiveId(gene) {
|
|
|
328
328
|
});
|
|
329
329
|
}
|
|
330
330
|
if (words.length < 3 && gene.summary) {
|
|
331
|
-
|
|
331
|
+
const STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had']);
|
|
332
332
|
String(gene.summary).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).forEach(function (w) {
|
|
333
333
|
if (w.length >= 3 && !STOP.has(w) && words.length < 6) words.push(w);
|
|
334
334
|
});
|
|
@@ -339,8 +339,8 @@ function deriveDescriptiveId(gene) {
|
|
|
339
339
|
});
|
|
340
340
|
}
|
|
341
341
|
if (words.length < 2) words = ['auto', 'distilled', 'strategy'];
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
const unique = [];
|
|
343
|
+
const seen = new Set();
|
|
344
344
|
words.forEach(function (w) { if (!seen.has(w)) { seen.add(w); unique.push(w); } });
|
|
345
345
|
return DISTILLED_ID_PREFIX + unique.slice(0, 5).join('-');
|
|
346
346
|
}
|
|
@@ -350,9 +350,9 @@ function deriveDescriptiveId(gene) {
|
|
|
350
350
|
// ---------------------------------------------------------------------------
|
|
351
351
|
function sanitizeSignalsMatch(signals) {
|
|
352
352
|
if (!Array.isArray(signals)) return [];
|
|
353
|
-
|
|
353
|
+
const cleaned = [];
|
|
354
354
|
signals.forEach(function (s) {
|
|
355
|
-
|
|
355
|
+
let sig = String(s || '').trim().toLowerCase();
|
|
356
356
|
if (!sig) return;
|
|
357
357
|
// Strip trailing timestamps (10+ digits) and random suffixes
|
|
358
358
|
sig = sig.replace(/[_-]\d{10,}$/g, '');
|
|
@@ -369,7 +369,7 @@ function sanitizeSignalsMatch(signals) {
|
|
|
369
369
|
cleaned.push(sig);
|
|
370
370
|
});
|
|
371
371
|
// Deduplicate
|
|
372
|
-
|
|
372
|
+
const seen = {};
|
|
373
373
|
return cleaned.filter(function (s) { if (seen[s]) return false; seen[s] = true; return true; });
|
|
374
374
|
}
|
|
375
375
|
|
|
@@ -377,7 +377,7 @@ function sanitizeSignalsMatch(signals) {
|
|
|
377
377
|
// Step 4: validateSynthesizedGene
|
|
378
378
|
// ---------------------------------------------------------------------------
|
|
379
379
|
function validateSynthesizedGene(gene, existingGenes) {
|
|
380
|
-
|
|
380
|
+
const errors = [];
|
|
381
381
|
if (!gene || typeof gene !== 'object') return { valid: false, errors: ['gene is not an object'] };
|
|
382
382
|
|
|
383
383
|
if (gene.type !== 'Gene') errors.push('missing or wrong type (must be "Gene")');
|
|
@@ -405,17 +405,16 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
if (gene.id) {
|
|
408
|
-
|
|
409
|
-
// Strip ALL embedded timestamps (10+ digit sequences) anywhere in the id
|
|
408
|
+
let suffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
410
409
|
suffix = suffix.replace(/[-_]?\d{10,}[-_]?/g, '-').replace(/[-_]+/g, '-').replace(/^[-_]+|[-_]+$/g, '');
|
|
411
|
-
|
|
410
|
+
const needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
|
|
412
411
|
|| /^(cursor|vscode|vim|emacs|windsurf|copilot|cline|codex)[-_]?\d*$/i.test(suffix);
|
|
413
412
|
if (needsRename) {
|
|
414
413
|
gene.id = deriveDescriptiveId(gene);
|
|
415
414
|
} else {
|
|
416
415
|
gene.id = DISTILLED_ID_PREFIX + suffix;
|
|
417
416
|
}
|
|
418
|
-
|
|
417
|
+
const cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
|
|
419
418
|
if (cleanSuffix.replace(/[-_]/g, '').length < 6) {
|
|
420
419
|
gene.id = deriveDescriptiveId(gene);
|
|
421
420
|
}
|
|
@@ -448,14 +447,14 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
448
447
|
}
|
|
449
448
|
|
|
450
449
|
// --- Validation command sanitization ---
|
|
451
|
-
|
|
450
|
+
const ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
|
452
451
|
if (Array.isArray(gene.validation)) {
|
|
453
452
|
gene.validation = gene.validation.filter(function (cmd) {
|
|
454
|
-
|
|
453
|
+
const c = String(cmd || '').trim();
|
|
455
454
|
if (!c) return false;
|
|
456
455
|
if (!ALLOWED_PREFIXES.some(function (p) { return c.startsWith(p); })) return false;
|
|
457
456
|
if (/`|\$\(/.test(c)) return false;
|
|
458
|
-
|
|
457
|
+
const stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
|
|
459
458
|
return !/[;&|><]/.test(stripped);
|
|
460
459
|
});
|
|
461
460
|
}
|
|
@@ -464,19 +463,19 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
464
463
|
if (!gene.schema_version) gene.schema_version = '1.6.0';
|
|
465
464
|
|
|
466
465
|
// --- Duplicate ID check ---
|
|
467
|
-
|
|
466
|
+
const existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
|
|
468
467
|
if (gene.id && existingIds.has(gene.id)) {
|
|
469
468
|
gene.id = gene.id + '_' + Date.now().toString(36);
|
|
470
469
|
}
|
|
471
470
|
|
|
472
471
|
// --- Signal overlap check ---
|
|
473
472
|
if (gene.signals_match && existingGenes && existingGenes.length > 0) {
|
|
474
|
-
|
|
475
|
-
for (
|
|
476
|
-
|
|
477
|
-
|
|
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(); }));
|
|
478
477
|
if (newSet.size > 0 && egSet.size > 0) {
|
|
479
|
-
|
|
478
|
+
let overlap = 0;
|
|
480
479
|
newSet.forEach(function (s) { if (egSet.has(s)) overlap++; });
|
|
481
480
|
if (overlap === newSet.size && overlap === egSet.size) {
|
|
482
481
|
errors.push('signals_match fully overlaps with existing gene: ' + eg.id);
|
|
@@ -494,24 +493,24 @@ function validateSynthesizedGene(gene, existingGenes) {
|
|
|
494
493
|
function shouldDistill() {
|
|
495
494
|
if (String(process.env.SKILL_DISTILLER || 'true').toLowerCase() === 'false') return false;
|
|
496
495
|
|
|
497
|
-
|
|
496
|
+
const state = readDistillerState();
|
|
498
497
|
if (state.last_distillation_at) {
|
|
499
|
-
|
|
498
|
+
const elapsed = Date.now() - new Date(state.last_distillation_at).getTime();
|
|
500
499
|
if (elapsed < DISTILLER_INTERVAL_HOURS * 3600000) return false;
|
|
501
500
|
}
|
|
502
501
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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);
|
|
507
506
|
|
|
508
|
-
|
|
509
|
-
|
|
507
|
+
const recent = all.slice(-10);
|
|
508
|
+
const recentSuccess = recent.filter(function (c) {
|
|
510
509
|
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
511
510
|
}).length;
|
|
512
511
|
if (recentSuccess < 7) return false;
|
|
513
512
|
|
|
514
|
-
|
|
513
|
+
const totalSuccess = all.filter(function (c) {
|
|
515
514
|
return c && c.outcome && (c.outcome.status === 'success' || c.outcome === 'success');
|
|
516
515
|
}).length;
|
|
517
516
|
if (totalSuccess < DISTILLER_MIN_CAPSULES) return false;
|
|
@@ -525,7 +524,7 @@ function shouldDistill() {
|
|
|
525
524
|
function prepareDistillation() {
|
|
526
525
|
console.log('[Distiller] Preparing skill distillation...');
|
|
527
526
|
|
|
528
|
-
|
|
527
|
+
const data = collectDistillationData();
|
|
529
528
|
console.log('[Distiller] Collected ' + data.successCapsules.length + ' successful capsules across ' + Object.keys(data.grouped).length + ' gene groups.');
|
|
530
529
|
|
|
531
530
|
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
@@ -533,29 +532,29 @@ function prepareDistillation() {
|
|
|
533
532
|
return { ok: false, reason: 'insufficient_data' };
|
|
534
533
|
}
|
|
535
534
|
|
|
536
|
-
|
|
535
|
+
const state = readDistillerState();
|
|
537
536
|
if (state.last_data_hash === data.dataHash) {
|
|
538
537
|
console.log('[Distiller] Data unchanged since last distillation (hash: ' + data.dataHash + '). Skipping.');
|
|
539
538
|
return { ok: false, reason: 'idempotent_skip' };
|
|
540
539
|
}
|
|
541
540
|
|
|
542
|
-
|
|
541
|
+
const analysis = analyzePatterns(data);
|
|
543
542
|
console.log('[Distiller] Analysis: high_freq=' + analysis.high_frequency.length + ' drift=' + analysis.strategy_drift.length + ' gaps=' + analysis.coverage_gaps.length);
|
|
544
543
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
544
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
545
|
+
const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
546
|
+
const existingGenes = existingGenesJson.genes || [];
|
|
548
547
|
|
|
549
|
-
|
|
548
|
+
const prompt = buildDistillationPrompt(analysis, existingGenes, data.successCapsules);
|
|
550
549
|
|
|
551
|
-
|
|
550
|
+
const memDir = paths.getMemoryDir();
|
|
552
551
|
ensureDir(memDir);
|
|
553
|
-
|
|
554
|
-
|
|
552
|
+
const promptFileName = 'distill_prompt_' + Date.now() + '.txt';
|
|
553
|
+
const promptPath = path.join(memDir, promptFileName);
|
|
555
554
|
fs.writeFileSync(promptPath, prompt, 'utf8');
|
|
556
555
|
|
|
557
|
-
|
|
558
|
-
|
|
556
|
+
const reqPath = distillRequestPath();
|
|
557
|
+
const requestData = {
|
|
559
558
|
type: 'DistillationRequest',
|
|
560
559
|
created_at: new Date().toISOString(),
|
|
561
560
|
prompt_path: promptPath,
|
|
@@ -575,7 +574,7 @@ function prepareDistillation() {
|
|
|
575
574
|
}
|
|
576
575
|
|
|
577
576
|
function inferCategoryFromSignals(signals) {
|
|
578
|
-
|
|
577
|
+
const list = Array.isArray(signals) ? signals.map(function (s) { return String(s).toLowerCase(); }) : [];
|
|
579
578
|
if (list.some(function (s) { return s.indexOf('error') !== -1 || s.indexOf('fail') !== -1 || s.indexOf('reliability') !== -1; })) {
|
|
580
579
|
return 'repair';
|
|
581
580
|
}
|
|
@@ -586,12 +585,12 @@ function inferCategoryFromSignals(signals) {
|
|
|
586
585
|
}
|
|
587
586
|
|
|
588
587
|
function chooseDistillationSource(data, analysis) {
|
|
589
|
-
|
|
590
|
-
|
|
588
|
+
const grouped = data && data.grouped ? data.grouped : {};
|
|
589
|
+
let best = null;
|
|
591
590
|
Object.keys(grouped).forEach(function (geneId) {
|
|
592
|
-
|
|
591
|
+
const g = grouped[geneId];
|
|
593
592
|
if (!g || g.total_count <= 0) return;
|
|
594
|
-
|
|
593
|
+
const score = (g.total_count * 2) + (g.avg_score || 0);
|
|
595
594
|
if (!best || score > best.score) {
|
|
596
595
|
best = { gene_id: geneId, group: g, score: score };
|
|
597
596
|
}
|
|
@@ -600,25 +599,25 @@ function chooseDistillationSource(data, analysis) {
|
|
|
600
599
|
}
|
|
601
600
|
|
|
602
601
|
function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
603
|
-
|
|
602
|
+
const source = chooseDistillationSource(data, analysis);
|
|
604
603
|
if (!source || !source.group) return null;
|
|
605
604
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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;
|
|
609
608
|
|
|
610
|
-
|
|
609
|
+
const triggerFreq = {};
|
|
611
610
|
(group.triggers || []).forEach(function (arr) {
|
|
612
611
|
(Array.isArray(arr) ? arr : []).forEach(function (s) {
|
|
613
|
-
|
|
612
|
+
const k = String(s).toLowerCase();
|
|
614
613
|
triggerFreq[k] = (triggerFreq[k] || 0) + 1;
|
|
615
614
|
});
|
|
616
615
|
});
|
|
617
|
-
|
|
616
|
+
let signalsMatch = Object.keys(triggerFreq)
|
|
618
617
|
.sort(function (a, b) { return triggerFreq[b] - triggerFreq[a]; })
|
|
619
618
|
.slice(0, 6);
|
|
620
|
-
|
|
621
|
-
|
|
619
|
+
const summaryText = (group.summaries || []).slice(0, 5).join(' ');
|
|
620
|
+
const derivedTags = learningSignals.expandSignals(signalsMatch, summaryText)
|
|
622
621
|
.filter(function (tag) { return tag.indexOf('problem:') === 0 || tag.indexOf('area:') === 0; })
|
|
623
622
|
.slice(0, 4);
|
|
624
623
|
signalsMatch = Array.from(new Set(signalsMatch.concat(derivedTags)));
|
|
@@ -626,8 +625,8 @@ function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
|
626
625
|
signalsMatch = sourceGene.signals_match.slice(0, 6);
|
|
627
626
|
}
|
|
628
627
|
|
|
629
|
-
|
|
630
|
-
|
|
628
|
+
const category = sourceGene && sourceGene.category ? sourceGene.category : inferCategoryFromSignals(signalsMatch);
|
|
629
|
+
const idSeed = {
|
|
631
630
|
type: 'Gene',
|
|
632
631
|
id: DISTILLED_ID_PREFIX + source.gene_id.replace(/^gene_/, '').replace(/^gene_distilled_/, ''),
|
|
633
632
|
category: category,
|
|
@@ -642,12 +641,12 @@ function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
|
642
641
|
],
|
|
643
642
|
};
|
|
644
643
|
|
|
645
|
-
|
|
644
|
+
let summaryBase = (group.summaries && group.summaries[0]) ? String(group.summaries[0]) : '';
|
|
646
645
|
if (!summaryBase) {
|
|
647
646
|
summaryBase = 'Reusable strategy for repeated successful pattern: ' + signalsMatch.slice(0, 3).join(', ');
|
|
648
647
|
}
|
|
649
648
|
|
|
650
|
-
|
|
649
|
+
const gene = {
|
|
651
650
|
type: 'Gene',
|
|
652
651
|
id: deriveDescriptiveId(idSeed),
|
|
653
652
|
summary: summaryBase.slice(0, 200),
|
|
@@ -674,7 +673,7 @@ function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
|
674
673
|
}
|
|
675
674
|
|
|
676
675
|
function finalizeDistilledGene(gene, requestLike, status) {
|
|
677
|
-
|
|
676
|
+
const state = readDistillerState();
|
|
678
677
|
state.last_distillation_at = new Date().toISOString();
|
|
679
678
|
state.last_data_hash = requestLike.data_hash;
|
|
680
679
|
state.last_gene_id = gene.id;
|
|
@@ -698,15 +697,15 @@ function finalizeDistilledGene(gene, requestLike, status) {
|
|
|
698
697
|
// Step 5b: completeDistillation -- validate LLM response and save gene
|
|
699
698
|
// ---------------------------------------------------------------------------
|
|
700
699
|
function completeDistillation(responseText) {
|
|
701
|
-
|
|
702
|
-
|
|
700
|
+
const reqPath = distillRequestPath();
|
|
701
|
+
const request = readJsonIfExists(reqPath, null);
|
|
703
702
|
|
|
704
703
|
if (!request) {
|
|
705
704
|
console.warn('[Distiller] No pending distillation request found.');
|
|
706
705
|
return { ok: false, reason: 'no_request' };
|
|
707
706
|
}
|
|
708
707
|
|
|
709
|
-
|
|
708
|
+
const rawGene = extractJsonFromLlmResponse(responseText);
|
|
710
709
|
if (!rawGene) {
|
|
711
710
|
appendJsonl(distillerLogPath(), {
|
|
712
711
|
timestamp: new Date().toISOString(),
|
|
@@ -718,13 +717,13 @@ function completeDistillation(responseText) {
|
|
|
718
717
|
return { ok: false, reason: 'no_gene_in_response' };
|
|
719
718
|
}
|
|
720
719
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
720
|
+
const assetsDir = paths.getGepAssetsDir();
|
|
721
|
+
const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
|
|
722
|
+
const existingGenes = existingGenesJson.genes || [];
|
|
724
723
|
|
|
725
|
-
|
|
724
|
+
const validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
726
725
|
|
|
727
|
-
|
|
726
|
+
const logEntry = {
|
|
728
727
|
timestamp: new Date().toISOString(),
|
|
729
728
|
data_hash: request.data_hash,
|
|
730
729
|
input_capsule_count: request.input_capsule_count,
|
|
@@ -741,18 +740,18 @@ function completeDistillation(responseText) {
|
|
|
741
740
|
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
742
741
|
}
|
|
743
742
|
|
|
744
|
-
|
|
743
|
+
const gene = validation.gene;
|
|
745
744
|
gene._distilled_meta = {
|
|
746
745
|
distilled_at: new Date().toISOString(),
|
|
747
746
|
source_capsule_count: request.input_capsule_count,
|
|
748
747
|
data_hash: request.data_hash,
|
|
749
748
|
};
|
|
750
749
|
|
|
751
|
-
|
|
750
|
+
const assetStore = require('./assetStore');
|
|
752
751
|
assetStore.upsertGene(gene);
|
|
753
752
|
console.log('[Distiller] Gene "' + gene.id + '" written to genes.json.');
|
|
754
753
|
|
|
755
|
-
|
|
754
|
+
const state = readDistillerState();
|
|
756
755
|
state.last_distillation_at = new Date().toISOString();
|
|
757
756
|
state.last_data_hash = request.data_hash;
|
|
758
757
|
state.last_gene_id = gene.id;
|
|
@@ -770,7 +769,7 @@ function completeDistillation(responseText) {
|
|
|
770
769
|
|
|
771
770
|
if (process.env.SKILL_AUTO_PUBLISH !== '0') {
|
|
772
771
|
try {
|
|
773
|
-
|
|
772
|
+
const skillPublisher = require('./skillPublisher');
|
|
774
773
|
skillPublisher.publishSkillToHub(gene).then(function (res) {
|
|
775
774
|
if (res.ok) {
|
|
776
775
|
console.log('[Distiller] Skill published to Hub: ' + (res.result?.skill_id || gene.id));
|
|
@@ -787,24 +786,24 @@ function completeDistillation(responseText) {
|
|
|
787
786
|
}
|
|
788
787
|
|
|
789
788
|
function autoDistill() {
|
|
790
|
-
|
|
789
|
+
const data = collectDistillationData();
|
|
791
790
|
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
792
791
|
return { ok: false, reason: 'insufficient_data' };
|
|
793
792
|
}
|
|
794
793
|
|
|
795
|
-
|
|
794
|
+
const state = readDistillerState();
|
|
796
795
|
if (state.last_data_hash === data.dataHash) {
|
|
797
796
|
return { ok: false, reason: 'idempotent_skip' };
|
|
798
797
|
}
|
|
799
798
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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);
|
|
805
804
|
if (!rawGene) return { ok: false, reason: 'no_candidate_gene' };
|
|
806
805
|
|
|
807
|
-
|
|
806
|
+
const validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
808
807
|
if (!validation.valid) {
|
|
809
808
|
appendJsonl(distillerLogPath(), {
|
|
810
809
|
timestamp: new Date().toISOString(),
|
|
@@ -816,7 +815,7 @@ function autoDistill() {
|
|
|
816
815
|
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
817
816
|
}
|
|
818
817
|
|
|
819
|
-
|
|
818
|
+
const gene = validation.gene;
|
|
820
819
|
gene._distilled_meta = {
|
|
821
820
|
distilled_at: new Date().toISOString(),
|
|
822
821
|
source_capsule_count: data.successCapsules.length,
|
|
@@ -824,7 +823,7 @@ function autoDistill() {
|
|
|
824
823
|
auto_distilled: true,
|
|
825
824
|
};
|
|
826
825
|
|
|
827
|
-
|
|
826
|
+
const assetStore = require('./assetStore');
|
|
828
827
|
assetStore.upsertGene(gene);
|
|
829
828
|
finalizeDistilledGene(gene, {
|
|
830
829
|
data_hash: data.dataHash,
|