@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.32.1",
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": {
@@ -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
- if (_cachedHubNodeSecret) return _cachedHubNodeSecret;
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) {
@@ -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 { return null; }
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 { return []; }
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 { return []; }
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 { return []; }
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) { try { obj.asset_id = computeAssetId(obj); } catch (e) {} }
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,3 +1,4 @@
1
+ // Structured learning signal expansion: raw signals -> categorized tags for gene selection and evolution feedback.
1
2
  function unique(items) {
2
3
  return Array.from(new Set((Array.isArray(items) ? items : []).filter(Boolean).map(function (x) {
3
4
  return String(x).trim();
@@ -1,16 +1,16 @@
1
1
  'use strict';
2
2
 
3
- var fs = require('fs');
4
- var path = require('path');
5
- var crypto = require('crypto');
6
- var paths = require('./paths');
7
- var learningSignals = require('./learningSignals');
8
-
9
- var DISTILLER_MIN_CAPSULES = parseInt(process.env.DISTILLER_MIN_CAPSULES || '10', 10) || 10;
10
- var DISTILLER_INTERVAL_HOURS = parseInt(process.env.DISTILLER_INTERVAL_HOURS || '24', 10) || 24;
11
- var DISTILLER_MIN_SUCCESS_RATE = parseFloat(process.env.DISTILLER_MIN_SUCCESS_RATE || '0.7') || 0.7;
12
- var DISTILLED_MAX_FILES = 12;
13
- var DISTILLED_ID_PREFIX = 'gene_distilled_';
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
- var raw = fs.readFileSync(filePath, 'utf8');
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
- var raw = fs.readFileSync(filePath, 'utf8');
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
- var tmp = distillerStatePath() + '.tmp';
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
- var ids = capsules.map(function (c) { return c.id || ''; }).sort();
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
- var assetsDir = paths.getGepAssetsDir();
76
- var evoDir = paths.getEvolutionDir();
75
+ const assetsDir = paths.getGepAssetsDir();
76
+ const evoDir = paths.getEvolutionDir();
77
77
 
78
- var capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
79
- var capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
80
- var allCapsules = [].concat(capsulesJson.capsules || [], capsulesJsonl);
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
- var unique = new Map();
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
- var successCapsules = allCapsules.filter(function (c) {
86
+ const successCapsules = allCapsules.filter(function (c) {
87
87
  if (!c || !c.outcome) return false;
88
- var status = typeof c.outcome === 'string' ? c.outcome : c.outcome.status;
88
+ const status = typeof c.outcome === 'string' ? c.outcome : c.outcome.status;
89
89
  if (status !== 'success') return false;
90
- var score = c.outcome && Number.isFinite(Number(c.outcome.score)) ? Number(c.outcome.score) : 1;
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
- var events = readJsonlIfExists(path.join(assetsDir, 'events.jsonl'));
94
+ const events = readJsonlIfExists(path.join(assetsDir, 'events.jsonl'));
95
95
 
96
- var memGraphPath = process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
97
- var graphEntries = readJsonlIfExists(memGraphPath);
96
+ const memGraphPath = process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
97
+ const graphEntries = readJsonlIfExists(memGraphPath);
98
98
 
99
- var grouped = {};
99
+ const grouped = {};
100
100
  successCapsules.forEach(function (c) {
101
- var geneId = c.gene || c.gene_id || 'unknown';
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
- var g = grouped[geneId];
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
- var g = grouped[id];
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
- var grouped = data.grouped;
136
- var report = {
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
- var g = grouped[geneId];
146
+ const g = grouped[geneId];
147
147
  if (g.total_count >= 5) {
148
- var flat = [];
148
+ let flat = [];
149
149
  g.triggers.forEach(function (t) { if (Array.isArray(t)) flat = flat.concat(t); });
150
- var freq = {};
151
- flat.forEach(function (t) { var k = String(t).toLowerCase(); freq[k] = (freq[k] || 0) + 1; });
152
- var top = Object.keys(freq).sort(function (a, b) { return freq[b] - freq[a]; }).slice(0, 5);
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
- var first = g.summaries[0];
158
- var last = g.summaries[g.summaries.length - 1];
157
+ const first = g.summaries[0];
158
+ const last = g.summaries[g.summaries.length - 1];
159
159
  if (first !== last) {
160
- var fw = new Set(first.toLowerCase().split(/\s+/));
161
- var lw = new Set(last.toLowerCase().split(/\s+/));
162
- var inter = 0;
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
- var union = fw.size + lw.size - inter;
165
- var sim = union > 0 ? inter / union : 1;
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
- var signalFreq = {};
173
+ const signalFreq = {};
174
174
  (data.events || []).forEach(function (evt) {
175
175
  if (evt && Array.isArray(evt.signals)) {
176
- evt.signals.forEach(function (s) { var k = String(s).toLowerCase(); signalFreq[k] = (signalFreq[k] || 0) + 1; });
176
+ evt.signals.forEach(function (s) { const k = String(s).toLowerCase(); signalFreq[k] = (signalFreq[k] || 0) + 1; });
177
177
  }
178
178
  });
179
- var covered = new Set();
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
- var gaps = Object.keys(signalFreq)
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
- var str = String(text || '');
201
- var buffer = '';
202
- var depth = 0;
203
- for (var i = 0; i < str.length; i++) {
204
- var ch = str[i];
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 { var obj = JSON.parse(buffer); if (obj && typeof obj === 'object' && obj.type === 'Gene') return obj; } catch (e) {}
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
- var genesRef = existingGenes.map(function (g) {
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
- var samples = sampleCapsules.slice(0, 8).map(function (c) {
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
- var words = [];
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
- var STOP = new Set(['the', 'and', 'for', 'with', 'from', 'that', 'this', 'into', 'when', 'are', 'was', 'has', 'had']);
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
- var unique = [];
343
- var seen = new Set();
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
- var cleaned = [];
353
+ const cleaned = [];
354
354
  signals.forEach(function (s) {
355
- var sig = String(s || '').trim().toLowerCase();
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
- var seen = {};
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
- var errors = [];
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
- var suffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
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
- var needsRename = /^\d+$/.test(suffix) || /^\d{10,}/.test(suffix)
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
- var cleanSuffix = String(gene.id).replace(DISTILLED_ID_PREFIX, '');
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
- var ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
450
+ const ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
452
451
  if (Array.isArray(gene.validation)) {
453
452
  gene.validation = gene.validation.filter(function (cmd) {
454
- var c = String(cmd || '').trim();
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
- var stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
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
- var existingIds = new Set((existingGenes || []).map(function (g) { return g.id; }));
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
- var newSet = new Set(gene.signals_match.map(function (s) { return String(s).toLowerCase(); }));
475
- for (var i = 0; i < existingGenes.length; i++) {
476
- var eg = existingGenes[i];
477
- var egSet = new Set((eg.signals_match || []).map(function (s) { return String(s).toLowerCase(); }));
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
- var overlap = 0;
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
- var state = readDistillerState();
496
+ const state = readDistillerState();
498
497
  if (state.last_distillation_at) {
499
- var elapsed = Date.now() - new Date(state.last_distillation_at).getTime();
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
- var assetsDir = paths.getGepAssetsDir();
504
- var capsulesJson = readJsonIfExists(path.join(assetsDir, 'capsules.json'), { capsules: [] });
505
- var capsulesJsonl = readJsonlIfExists(path.join(assetsDir, 'capsules.jsonl'));
506
- var all = [].concat(capsulesJson.capsules || [], capsulesJsonl);
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
- var recent = all.slice(-10);
509
- var recentSuccess = recent.filter(function (c) {
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
- var totalSuccess = all.filter(function (c) {
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
- var data = collectDistillationData();
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
- var state = readDistillerState();
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
- var analysis = analyzePatterns(data);
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
- var assetsDir = paths.getGepAssetsDir();
546
- var existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
547
- var existingGenes = existingGenesJson.genes || [];
544
+ const assetsDir = paths.getGepAssetsDir();
545
+ const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
546
+ const existingGenes = existingGenesJson.genes || [];
548
547
 
549
- var prompt = buildDistillationPrompt(analysis, existingGenes, data.successCapsules);
548
+ const prompt = buildDistillationPrompt(analysis, existingGenes, data.successCapsules);
550
549
 
551
- var memDir = paths.getMemoryDir();
550
+ const memDir = paths.getMemoryDir();
552
551
  ensureDir(memDir);
553
- var promptFileName = 'distill_prompt_' + Date.now() + '.txt';
554
- var promptPath = path.join(memDir, promptFileName);
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
- var reqPath = distillRequestPath();
558
- var requestData = {
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
- var list = Array.isArray(signals) ? signals.map(function (s) { return String(s).toLowerCase(); }) : [];
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
- var grouped = data && data.grouped ? data.grouped : {};
590
- var best = null;
588
+ const grouped = data && data.grouped ? data.grouped : {};
589
+ let best = null;
591
590
  Object.keys(grouped).forEach(function (geneId) {
592
- var g = grouped[geneId];
591
+ const g = grouped[geneId];
593
592
  if (!g || g.total_count <= 0) return;
594
- var score = (g.total_count * 2) + (g.avg_score || 0);
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
- var source = chooseDistillationSource(data, analysis);
602
+ const source = chooseDistillationSource(data, analysis);
604
603
  if (!source || !source.group) return null;
605
604
 
606
- var group = source.group;
607
- var existing = Array.isArray(existingGenes) ? existingGenes : [];
608
- var sourceGene = existing.find(function (g) { return g && g.id === source.gene_id; }) || null;
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
- var triggerFreq = {};
609
+ const triggerFreq = {};
611
610
  (group.triggers || []).forEach(function (arr) {
612
611
  (Array.isArray(arr) ? arr : []).forEach(function (s) {
613
- var k = String(s).toLowerCase();
612
+ const k = String(s).toLowerCase();
614
613
  triggerFreq[k] = (triggerFreq[k] || 0) + 1;
615
614
  });
616
615
  });
617
- var signalsMatch = Object.keys(triggerFreq)
616
+ let signalsMatch = Object.keys(triggerFreq)
618
617
  .sort(function (a, b) { return triggerFreq[b] - triggerFreq[a]; })
619
618
  .slice(0, 6);
620
- var summaryText = (group.summaries || []).slice(0, 5).join(' ');
621
- var derivedTags = learningSignals.expandSignals(signalsMatch, summaryText)
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
- var category = sourceGene && sourceGene.category ? sourceGene.category : inferCategoryFromSignals(signalsMatch);
630
- var idSeed = {
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
- var summaryBase = (group.summaries && group.summaries[0]) ? String(group.summaries[0]) : '';
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
- var gene = {
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
- var state = readDistillerState();
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
- var reqPath = distillRequestPath();
702
- var request = readJsonIfExists(reqPath, null);
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
- var rawGene = extractJsonFromLlmResponse(responseText);
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
- var assetsDir = paths.getGepAssetsDir();
722
- var existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
723
- var existingGenes = existingGenesJson.genes || [];
720
+ const assetsDir = paths.getGepAssetsDir();
721
+ const existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
722
+ const existingGenes = existingGenesJson.genes || [];
724
723
 
725
- var validation = validateSynthesizedGene(rawGene, existingGenes);
724
+ const validation = validateSynthesizedGene(rawGene, existingGenes);
726
725
 
727
- var logEntry = {
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
- var gene = validation.gene;
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
- var assetStore = require('./assetStore');
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
- var state = readDistillerState();
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
- var skillPublisher = require('./skillPublisher');
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
- var data = collectDistillationData();
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
- var state = readDistillerState();
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
- var analysis = analyzePatterns(data);
801
- var assetsDir = paths.getGepAssetsDir();
802
- var existingGenesJson = readJsonIfExists(path.join(assetsDir, 'genes.json'), { genes: [] });
803
- var existingGenes = existingGenesJson.genes || [];
804
- var rawGene = synthesizeGeneFromPatterns(data, analysis, existingGenes);
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
- var validation = validateSynthesizedGene(rawGene, existingGenes);
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
- var gene = validation.gene;
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
- var assetStore = require('./assetStore');
826
+ const assetStore = require('./assetStore');
828
827
  assetStore.upsertGene(gene);
829
828
  finalizeDistilledGene(gene, {
830
829
  data_hash: data.dataHash,