@evomap/evolver 1.30.2 → 1.32.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/index.js +143 -18
- package/package.json +1 -1
- package/src/evolve.js +89 -10
- package/src/gep/a2aProtocol.js +1 -0
- package/src/gep/candidates.js +66 -4
- package/src/gep/learningSignals.js +88 -0
- package/src/gep/prompt.js +11 -0
- package/src/gep/selector.js +49 -2
- package/src/gep/skillDistiller.js +177 -0
- package/src/gep/solidify.js +141 -6
package/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
const evolve = require('./src/evolve');
|
|
3
3
|
const { solidify } = require('./src/gep/solidify');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
6
|
-
try { require('dotenv').config({ path: path.
|
|
5
|
+
const { getRepoRoot } = require('./src/gep/paths');
|
|
6
|
+
try { require('dotenv').config({ path: path.join(getRepoRoot(), '.env') }); } catch (e) { console.warn('[Evolver] Warning: dotenv not found or failed to load .env'); }
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const { spawn } = require('child_process');
|
|
9
9
|
|
|
@@ -24,6 +24,11 @@ function readJsonSafe(p) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Mark a pending evolution run as rejected (state-only, no git rollback).
|
|
29
|
+
* @param {string} statePath - Path to evolution_solidify_state.json
|
|
30
|
+
* @returns {boolean} true if a pending run was found and rejected
|
|
31
|
+
*/
|
|
27
32
|
function rejectPendingRun(statePath) {
|
|
28
33
|
try {
|
|
29
34
|
const state = readJsonSafe(statePath);
|
|
@@ -34,7 +39,9 @@ function rejectPendingRun(statePath) {
|
|
|
34
39
|
reason: 'loop_bridge_disabled_autoreject_no_rollback',
|
|
35
40
|
timestamp: new Date().toISOString(),
|
|
36
41
|
};
|
|
37
|
-
|
|
42
|
+
const tmp = `${statePath}.tmp`;
|
|
43
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
44
|
+
fs.renameSync(tmp, statePath);
|
|
38
45
|
return true;
|
|
39
46
|
}
|
|
40
47
|
} catch (e) {
|
|
@@ -120,8 +127,10 @@ async function main() {
|
|
|
120
127
|
process.on('SIGTERM', () => { releaseLock(); process.exit(); });
|
|
121
128
|
|
|
122
129
|
process.env.EVOLVE_LOOP = 'true';
|
|
123
|
-
process.env.EVOLVE_BRIDGE
|
|
124
|
-
|
|
130
|
+
if (!process.env.EVOLVE_BRIDGE) {
|
|
131
|
+
process.env.EVOLVE_BRIDGE = 'false';
|
|
132
|
+
}
|
|
133
|
+
console.log(`Loop mode enabled (internal daemon, bridge=${process.env.EVOLVE_BRIDGE}).`);
|
|
125
134
|
|
|
126
135
|
const { getEvolutionDir } = require('./src/gep/paths');
|
|
127
136
|
const solidifyStatePath = path.join(getEvolutionDir(), 'evolution_solidify_state.json');
|
|
@@ -273,7 +282,7 @@ async function main() {
|
|
|
273
282
|
|
|
274
283
|
if (res && res.ok && !dryRun) {
|
|
275
284
|
try {
|
|
276
|
-
const { shouldDistill, prepareDistillation } = require('./src/gep/skillDistiller');
|
|
285
|
+
const { shouldDistill, prepareDistillation, autoDistill } = require('./src/gep/skillDistiller');
|
|
277
286
|
const { readStateForSolidify } = require('./src/gep/solidify');
|
|
278
287
|
const solidifyState = readStateForSolidify();
|
|
279
288
|
const count = solidifyState.solidify_count || 0;
|
|
@@ -281,16 +290,21 @@ async function main() {
|
|
|
281
290
|
const autoTrigger = count > 0 && count % autoDistillInterval === 0;
|
|
282
291
|
|
|
283
292
|
if (autoTrigger || shouldDistill()) {
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
const auto = autoDistill();
|
|
294
|
+
if (auto && auto.ok && auto.gene) {
|
|
295
|
+
console.log('[Distiller] Auto-distilled gene: ' + auto.gene.id);
|
|
296
|
+
} else {
|
|
297
|
+
const dr = prepareDistillation();
|
|
298
|
+
if (dr && dr.ok && dr.promptPath) {
|
|
299
|
+
const trigger = autoTrigger ? `auto (every ${autoDistillInterval} solidifies, count=${count})` : 'threshold';
|
|
300
|
+
console.log('\n[DISTILL_REQUEST]');
|
|
301
|
+
console.log(`Distillation triggered: ${trigger}`);
|
|
302
|
+
console.log('Read the prompt file, process it with your LLM,');
|
|
303
|
+
console.log('save the LLM response to a file, then run:');
|
|
304
|
+
console.log(' node index.js distill --response-file=<path_to_llm_response>');
|
|
305
|
+
console.log('Prompt file: ' + dr.promptPath);
|
|
306
|
+
console.log('[/DISTILL_REQUEST]');
|
|
307
|
+
}
|
|
294
308
|
}
|
|
295
309
|
}
|
|
296
310
|
} catch (e) {
|
|
@@ -445,7 +459,9 @@ async function main() {
|
|
|
445
459
|
const s = readJsonSafe(sp);
|
|
446
460
|
if (s && s.last_run) {
|
|
447
461
|
s.last_solidify = { run_id: s.last_run.run_id, rejected: true, timestamp: new Date().toISOString() };
|
|
448
|
-
|
|
462
|
+
const tmpReject = `${sp}.tmp`;
|
|
463
|
+
fs.writeFileSync(tmpReject, JSON.stringify(s, null, 2) + '\n', 'utf8');
|
|
464
|
+
fs.renameSync(tmpReject, sp);
|
|
449
465
|
}
|
|
450
466
|
}
|
|
451
467
|
console.log('[Review] Changes rolled back.');
|
|
@@ -458,6 +474,112 @@ async function main() {
|
|
|
458
474
|
console.log('To reject and rollback: node index.js review --reject');
|
|
459
475
|
}
|
|
460
476
|
|
|
477
|
+
} else if (command === 'fetch') {
|
|
478
|
+
let skillId = null;
|
|
479
|
+
const eqFlag = args.find(a => typeof a === 'string' && (a.startsWith('--skill=') || a.startsWith('-s=')));
|
|
480
|
+
if (eqFlag) {
|
|
481
|
+
skillId = eqFlag.split('=').slice(1).join('=');
|
|
482
|
+
} else {
|
|
483
|
+
const sIdx = args.indexOf('-s');
|
|
484
|
+
const longIdx = args.indexOf('--skill');
|
|
485
|
+
const flagIdx = sIdx !== -1 ? sIdx : longIdx;
|
|
486
|
+
if (flagIdx !== -1 && args[flagIdx + 1] && !String(args[flagIdx + 1]).startsWith('-')) {
|
|
487
|
+
skillId = args[flagIdx + 1];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (!skillId) {
|
|
491
|
+
const positional = args[1];
|
|
492
|
+
if (positional && !String(positional).startsWith('-')) skillId = positional;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!skillId) {
|
|
496
|
+
console.error('Usage: evolver fetch --skill <skill_id>');
|
|
497
|
+
console.error(' evolver fetch -s <skill_id>');
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const { getHubUrl, getNodeId, buildHubHeaders, sendHelloToHub, getHubNodeSecret } = require('./src/gep/a2aProtocol');
|
|
502
|
+
|
|
503
|
+
const hubUrl = getHubUrl();
|
|
504
|
+
if (!hubUrl) {
|
|
505
|
+
console.error('[fetch] A2A_HUB_URL is not configured.');
|
|
506
|
+
console.error('Set it via environment variable or .env file:');
|
|
507
|
+
console.error(' export A2A_HUB_URL=https://evomap.ai');
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
if (!getHubNodeSecret()) {
|
|
513
|
+
console.log('[fetch] No node_secret found. Sending hello to Hub to register...');
|
|
514
|
+
const helloResult = await sendHelloToHub();
|
|
515
|
+
if (!helloResult || !helloResult.ok) {
|
|
516
|
+
console.error('[fetch] Failed to register with Hub:', helloResult && helloResult.error || 'unknown');
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
console.log('[fetch] Registered as ' + getNodeId());
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/skill/store/' + encodeURIComponent(skillId) + '/download';
|
|
523
|
+
const nodeId = getNodeId();
|
|
524
|
+
|
|
525
|
+
console.log('[fetch] Downloading skill: ' + skillId);
|
|
526
|
+
|
|
527
|
+
const resp = await fetch(endpoint, {
|
|
528
|
+
method: 'POST',
|
|
529
|
+
headers: buildHubHeaders(),
|
|
530
|
+
body: JSON.stringify({ sender_id: nodeId }),
|
|
531
|
+
signal: AbortSignal.timeout(30000),
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (!resp.ok) {
|
|
535
|
+
const body = await resp.text().catch(() => '');
|
|
536
|
+
let msg = 'HTTP ' + resp.status;
|
|
537
|
+
try { const j = JSON.parse(body); msg = j.error || j.message || msg; } catch (_) {}
|
|
538
|
+
console.error('[fetch] Download failed: ' + msg);
|
|
539
|
+
if (resp.status === 404) console.error(' Skill not found or not publicly available.');
|
|
540
|
+
if (resp.status === 401) console.error(' Authentication failed. Try deleting ~/.evomap/node_secret and retry.');
|
|
541
|
+
if (resp.status === 402) console.error(' Insufficient credits.');
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const data = await resp.json();
|
|
546
|
+
const outFlag = args.find(a => typeof a === 'string' && a.startsWith('--out='));
|
|
547
|
+
const safeId = String(data.skill_id || skillId).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
|
|
548
|
+
const outDir = outFlag
|
|
549
|
+
? outFlag.slice('--out='.length)
|
|
550
|
+
: path.join('.', 'skills', safeId);
|
|
551
|
+
|
|
552
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
553
|
+
|
|
554
|
+
if (data.content) {
|
|
555
|
+
fs.writeFileSync(path.join(outDir, 'SKILL.md'), data.content, 'utf8');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const bundled = Array.isArray(data.bundled_files) ? data.bundled_files : [];
|
|
559
|
+
for (const file of bundled) {
|
|
560
|
+
if (!file || !file.name || typeof file.content !== 'string') continue;
|
|
561
|
+
const safeName = path.basename(file.name);
|
|
562
|
+
fs.writeFileSync(path.join(outDir, safeName), file.content, 'utf8');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log('[fetch] Skill downloaded to: ' + outDir);
|
|
566
|
+
console.log(' Name: ' + (data.name || skillId));
|
|
567
|
+
console.log(' Version: ' + (data.version || '?'));
|
|
568
|
+
console.log(' Files: SKILL.md' + (bundled.length > 0 ? ', ' + bundled.map(f => f.name).join(', ') : ''));
|
|
569
|
+
if (data.already_purchased) {
|
|
570
|
+
console.log(' Cost: free (already purchased)');
|
|
571
|
+
} else {
|
|
572
|
+
console.log(' Cost: ' + (data.credit_cost || 0) + ' credits');
|
|
573
|
+
}
|
|
574
|
+
} catch (error) {
|
|
575
|
+
if (error && error.name === 'TimeoutError') {
|
|
576
|
+
console.error('[fetch] Request timed out. Check your network and A2A_HUB_URL.');
|
|
577
|
+
} else {
|
|
578
|
+
console.error('[fetch] Error:', error && error.message || error);
|
|
579
|
+
}
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
|
|
461
583
|
} else if (command === 'asset-log') {
|
|
462
584
|
const { summarizeCallLog, readCallLog, getLogPath } = require('./src/gep/assetCallLog');
|
|
463
585
|
|
|
@@ -502,7 +624,10 @@ async function main() {
|
|
|
502
624
|
}
|
|
503
625
|
|
|
504
626
|
} else {
|
|
505
|
-
console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|asset-log] [--loop]
|
|
627
|
+
console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|fetch|asset-log] [--loop]
|
|
628
|
+
- fetch flags:
|
|
629
|
+
- --skill=<id> | -s <id> (skill ID to download)
|
|
630
|
+
- --out=<dir> (output directory, default: ./skills/<skill_id>)
|
|
506
631
|
- solidify flags:
|
|
507
632
|
- --dry-run
|
|
508
633
|
- --no-rollback
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.1",
|
|
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/evolve.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
|
-
const { getRepoRoot, getMemoryDir, getSessionScope } = require('./gep/paths');
|
|
5
|
+
const { getRepoRoot, getWorkspaceRoot, getMemoryDir, getSessionScope } = require('./gep/paths');
|
|
6
6
|
const { extractSignals } = require('./gep/signals');
|
|
7
7
|
const {
|
|
8
8
|
loadGenes,
|
|
@@ -39,6 +39,8 @@ const { getEvolutionDir } = require('./gep/paths');
|
|
|
39
39
|
const { shouldReflect, buildReflectionContext, recordReflection } = require('./gep/reflection');
|
|
40
40
|
const { loadNarrativeSummary } = require('./gep/narrativeMemory');
|
|
41
41
|
const { maybeReportIssue } = require('./gep/issueReporter');
|
|
42
|
+
const { resolveStrategy } = require('./gep/strategy');
|
|
43
|
+
const { expandSignals } = require('./gep/learningSignals');
|
|
42
44
|
|
|
43
45
|
const REPO_ROOT = getRepoRoot();
|
|
44
46
|
|
|
@@ -352,6 +354,73 @@ function readRecentLog(filePath, size = 10000) {
|
|
|
352
354
|
}
|
|
353
355
|
}
|
|
354
356
|
|
|
357
|
+
function computeAdaptiveStrategyPolicy(opts) {
|
|
358
|
+
const recentEvents = Array.isArray(opts && opts.recentEvents) ? opts.recentEvents : [];
|
|
359
|
+
const selectedGene = opts && opts.selectedGene ? opts.selectedGene : null;
|
|
360
|
+
const signals = Array.isArray(opts && opts.signals) ? opts.signals : [];
|
|
361
|
+
const baseStrategy = resolveStrategy({ signals: signals });
|
|
362
|
+
|
|
363
|
+
const tail = recentEvents.slice(-8);
|
|
364
|
+
let repairStreak = 0;
|
|
365
|
+
for (let i = tail.length - 1; i >= 0; i--) {
|
|
366
|
+
if (tail[i] && tail[i].intent === 'repair') repairStreak++;
|
|
367
|
+
else break;
|
|
368
|
+
}
|
|
369
|
+
let failureStreak = 0;
|
|
370
|
+
for (let i = tail.length - 1; i >= 0; i--) {
|
|
371
|
+
if (tail[i] && tail[i].outcome && tail[i].outcome.status === 'failed') failureStreak++;
|
|
372
|
+
else break;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const antiPatterns = selectedGene && Array.isArray(selectedGene.anti_patterns) ? selectedGene.anti_patterns.slice(-5) : [];
|
|
376
|
+
const learningHistory = selectedGene && Array.isArray(selectedGene.learning_history) ? selectedGene.learning_history.slice(-6) : [];
|
|
377
|
+
const signalTags = new Set(expandSignals(signals, ''));
|
|
378
|
+
const overlappingAntiPatterns = antiPatterns.filter(function (ap) {
|
|
379
|
+
return ap && Array.isArray(ap.learning_signals) && ap.learning_signals.some(function (tag) {
|
|
380
|
+
return signalTags.has(String(tag));
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
const hardFailures = overlappingAntiPatterns.filter(function (ap) { return ap && ap.mode === 'hard'; }).length;
|
|
384
|
+
const softFailures = overlappingAntiPatterns.filter(function (ap) { return ap && ap.mode !== 'hard'; }).length;
|
|
385
|
+
const recentSuccesses = learningHistory.filter(function (x) { return x && x.outcome === 'success'; }).length;
|
|
386
|
+
|
|
387
|
+
const stagnation = signals.includes('stable_success_plateau') ||
|
|
388
|
+
signals.includes('evolution_saturation') ||
|
|
389
|
+
signals.includes('empty_cycle_loop_detected') ||
|
|
390
|
+
failureStreak >= 3 ||
|
|
391
|
+
repairStreak >= 3;
|
|
392
|
+
|
|
393
|
+
const forceInnovate = stagnation && !signals.includes('log_error');
|
|
394
|
+
const highRiskGene = hardFailures >= 1 || (softFailures >= 2 && recentSuccesses === 0);
|
|
395
|
+
const cautiousExecution = highRiskGene || failureStreak >= 2;
|
|
396
|
+
|
|
397
|
+
let blastRadiusMaxFiles = selectedGene && selectedGene.constraints && Number.isFinite(Number(selectedGene.constraints.max_files))
|
|
398
|
+
? Number(selectedGene.constraints.max_files)
|
|
399
|
+
: 12;
|
|
400
|
+
if (cautiousExecution) blastRadiusMaxFiles = Math.max(2, Math.min(blastRadiusMaxFiles, 6));
|
|
401
|
+
else if (forceInnovate) blastRadiusMaxFiles = Math.max(3, Math.min(blastRadiusMaxFiles, 10));
|
|
402
|
+
|
|
403
|
+
const directives = [];
|
|
404
|
+
directives.push('Base strategy: ' + baseStrategy.label + ' (' + baseStrategy.description + ')');
|
|
405
|
+
if (forceInnovate) directives.push('Force strategy shift: prefer innovate over repeating repair/optimize.');
|
|
406
|
+
if (highRiskGene) directives.push('Selected gene is high risk for current signals; keep blast radius narrow and prefer smallest viable change.');
|
|
407
|
+
if (failureStreak >= 2) directives.push('Recent failure streak detected; avoid repeating recent failed approach.');
|
|
408
|
+
directives.push('Target max files for this cycle: ' + blastRadiusMaxFiles + '.');
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
name: baseStrategy.name,
|
|
412
|
+
label: baseStrategy.label,
|
|
413
|
+
description: baseStrategy.description,
|
|
414
|
+
forceInnovate: forceInnovate,
|
|
415
|
+
cautiousExecution: cautiousExecution,
|
|
416
|
+
highRiskGene: highRiskGene,
|
|
417
|
+
repairStreak: repairStreak,
|
|
418
|
+
failureStreak: failureStreak,
|
|
419
|
+
blastRadiusMaxFiles: blastRadiusMaxFiles,
|
|
420
|
+
directives: directives,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
355
424
|
function checkSystemHealth() {
|
|
356
425
|
const report = [];
|
|
357
426
|
try {
|
|
@@ -492,7 +561,7 @@ function clearDormantHypothesis() {
|
|
|
492
561
|
}
|
|
493
562
|
// Read MEMORY.md and USER.md from the WORKSPACE root (not the evolver plugin dir).
|
|
494
563
|
// This avoids symlink breakage if the target file is temporarily deleted.
|
|
495
|
-
const WORKSPACE_ROOT =
|
|
564
|
+
const WORKSPACE_ROOT = getWorkspaceRoot();
|
|
496
565
|
const ROOT_MEMORY = path.join(WORKSPACE_ROOT, 'MEMORY.md');
|
|
497
566
|
const DIR_MEMORY = path.join(MEMORY_DIR, 'MEMORY.md');
|
|
498
567
|
const MEMORY_FILE = fs.existsSync(ROOT_MEMORY) ? ROOT_MEMORY : (fs.existsSync(DIR_MEMORY) ? DIR_MEMORY : ROOT_MEMORY);
|
|
@@ -1279,6 +1348,7 @@ async function run() {
|
|
|
1279
1348
|
const newCandidates = extractCapabilityCandidates({
|
|
1280
1349
|
recentSessionTranscript: recentMasterLog,
|
|
1281
1350
|
signals,
|
|
1351
|
+
recentFailedCapsules: readRecentFailedCapsules(50),
|
|
1282
1352
|
});
|
|
1283
1353
|
for (const c of newCandidates) {
|
|
1284
1354
|
try {
|
|
@@ -1433,6 +1503,11 @@ async function run() {
|
|
|
1433
1503
|
? capsuleCandidates.map(c => (c && c.id ? String(c.id) : null)).filter(Boolean)
|
|
1434
1504
|
: [];
|
|
1435
1505
|
const selectedCapsuleId = capsulesUsed.length ? capsulesUsed[0] : null;
|
|
1506
|
+
const strategyPolicy = computeAdaptiveStrategyPolicy({
|
|
1507
|
+
recentEvents,
|
|
1508
|
+
selectedGene,
|
|
1509
|
+
signals,
|
|
1510
|
+
});
|
|
1436
1511
|
|
|
1437
1512
|
// Personality selection (natural selection + small mutation when triggered).
|
|
1438
1513
|
// This state is persisted in MEMORY_DIR and is treated as an evolution control surface (not role-play).
|
|
@@ -1463,9 +1538,9 @@ async function run() {
|
|
|
1463
1538
|
tailAvgScore >= 0.7;
|
|
1464
1539
|
const forceInnovation =
|
|
1465
1540
|
String(process.env.FORCE_INNOVATION || process.env.EVOLVE_FORCE_INNOVATION || '').toLowerCase() === 'true';
|
|
1466
|
-
const mutationInnovateMode = !!IS_RANDOM_DRIFT || !!innovationPressure || !!forceInnovation;
|
|
1541
|
+
const mutationInnovateMode = !!IS_RANDOM_DRIFT || !!innovationPressure || !!forceInnovation || !!strategyPolicy.forceInnovate;
|
|
1467
1542
|
const mutationSignals = innovationPressure ? [...(Array.isArray(signals) ? signals : []), 'stable_success_plateau'] : signals;
|
|
1468
|
-
const mutationSignalsEffective = forceInnovation
|
|
1543
|
+
const mutationSignalsEffective = (forceInnovation || strategyPolicy.forceInnovate)
|
|
1469
1544
|
? [...(Array.isArray(mutationSignals) ? mutationSignals : []), 'force_innovation']
|
|
1470
1545
|
: mutationSignals;
|
|
1471
1546
|
|
|
@@ -1532,7 +1607,6 @@ async function run() {
|
|
|
1532
1607
|
try {
|
|
1533
1608
|
const runId = `run_${Date.now()}`;
|
|
1534
1609
|
const parentEventId = getLastEventId();
|
|
1535
|
-
const selectedBy = memoryAdvice && memoryAdvice.preferredGeneId ? 'memory_graph+selector' : 'selector';
|
|
1536
1610
|
|
|
1537
1611
|
// Baseline snapshot (before any edits).
|
|
1538
1612
|
let baselineUntracked = [];
|
|
@@ -1566,10 +1640,13 @@ async function run() {
|
|
|
1566
1640
|
console.warn('[SolidifyState] Failed to read git HEAD:', e && e.message || e);
|
|
1567
1641
|
}
|
|
1568
1642
|
|
|
1569
|
-
const maxFiles =
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1643
|
+
const maxFiles = strategyPolicy && Number.isFinite(Number(strategyPolicy.blastRadiusMaxFiles))
|
|
1644
|
+
? Number(strategyPolicy.blastRadiusMaxFiles)
|
|
1645
|
+
: (
|
|
1646
|
+
selectedGene && selectedGene.constraints && Number.isFinite(Number(selectedGene.constraints.max_files))
|
|
1647
|
+
? Number(selectedGene.constraints.max_files)
|
|
1648
|
+
: 12
|
|
1649
|
+
);
|
|
1573
1650
|
const blastRadiusEstimate = {
|
|
1574
1651
|
files: Number.isFinite(maxFiles) && maxFiles > 0 ? maxFiles : 0,
|
|
1575
1652
|
lines: Number.isFinite(maxFiles) && maxFiles > 0 ? Math.round(maxFiles * 80) : 0,
|
|
@@ -1603,6 +1680,7 @@ async function run() {
|
|
|
1603
1680
|
baseline_untracked: baselineUntracked,
|
|
1604
1681
|
baseline_git_head: baselineHead,
|
|
1605
1682
|
blast_radius_estimate: blastRadiusEstimate,
|
|
1683
|
+
strategy_policy: strategyPolicy,
|
|
1606
1684
|
active_task_id: activeTask ? (activeTask.id || activeTask.task_id || null) : null,
|
|
1607
1685
|
active_task_title: activeTask ? (activeTask.title || null) : null,
|
|
1608
1686
|
worker_assignment_id: activeTask ? (activeTask._worker_assignment_id || null) : null,
|
|
@@ -1740,6 +1818,7 @@ ${mutationDirective}
|
|
|
1740
1818
|
capabilityCandidatesPreview,
|
|
1741
1819
|
externalCandidatesPreview,
|
|
1742
1820
|
hubMatchedBlock,
|
|
1821
|
+
strategyPolicy,
|
|
1743
1822
|
failedCapsules: recentFailedCapsules,
|
|
1744
1823
|
hubLessons,
|
|
1745
1824
|
});
|
|
@@ -1828,5 +1907,5 @@ ${mutationDirective}
|
|
|
1828
1907
|
}
|
|
1829
1908
|
}
|
|
1830
1909
|
|
|
1831
|
-
module.exports = { run };
|
|
1910
|
+
module.exports = { run, computeAdaptiveStrategyPolicy };
|
|
1832
1911
|
|
package/src/gep/a2aProtocol.js
CHANGED
package/src/gep/candidates.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { expandSignals } = require('./learningSignals');
|
|
2
|
+
|
|
1
3
|
function stableHash(input) {
|
|
2
4
|
// Deterministic lightweight hash (not cryptographic).
|
|
3
5
|
const s = String(input || '');
|
|
@@ -60,13 +62,15 @@ function buildFiveQuestionsShape({ title, signals, evidence }) {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
|
|
65
|
+
function extractCapabilityCandidates({ recentSessionTranscript, signals, recentFailedCapsules }) {
|
|
64
66
|
const candidates = [];
|
|
67
|
+
const signalList = Array.isArray(signals) ? signals : [];
|
|
68
|
+
const expandedTags = expandSignals(signalList, recentSessionTranscript);
|
|
65
69
|
const toolCalls = extractToolCalls(recentSessionTranscript);
|
|
66
70
|
const freq = countFreq(toolCalls);
|
|
67
71
|
|
|
68
72
|
for (const [tool, count] of freq.entries()) {
|
|
69
|
-
if (count <
|
|
73
|
+
if (count < 3) continue;
|
|
70
74
|
const title = `Repeated tool usage: ${tool}`;
|
|
71
75
|
const evidence = `Observed ${count} occurrences of tool call marker for ${tool}.`;
|
|
72
76
|
const shape = buildFiveQuestionsShape({ title, signals, evidence });
|
|
@@ -76,13 +80,13 @@ function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
|
|
|
76
80
|
title,
|
|
77
81
|
source: 'transcript',
|
|
78
82
|
created_at: new Date().toISOString(),
|
|
79
|
-
signals:
|
|
83
|
+
signals: signalList,
|
|
84
|
+
tags: expandedTags,
|
|
80
85
|
shape,
|
|
81
86
|
});
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
// Signals-as-candidates: capture recurring pain points as reusable capability shapes.
|
|
85
|
-
const signalList = Array.isArray(signals) ? signals : [];
|
|
86
90
|
const signalCandidates = [
|
|
87
91
|
// Defensive signals
|
|
88
92
|
{ signal: 'log_error', title: 'Repair recurring runtime errors' },
|
|
@@ -109,10 +113,67 @@ function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
|
|
|
109
113
|
source: 'signals',
|
|
110
114
|
created_at: new Date().toISOString(),
|
|
111
115
|
signals: signalList,
|
|
116
|
+
tags: expandedTags,
|
|
112
117
|
shape,
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
|
|
121
|
+
var failedCapsules = Array.isArray(recentFailedCapsules) ? recentFailedCapsules : [];
|
|
122
|
+
var groups = {};
|
|
123
|
+
var problemPriority = [
|
|
124
|
+
'problem:performance',
|
|
125
|
+
'problem:protocol',
|
|
126
|
+
'problem:reliability',
|
|
127
|
+
'problem:stagnation',
|
|
128
|
+
'problem:capability',
|
|
129
|
+
];
|
|
130
|
+
for (var i = 0; i < failedCapsules.length; i++) {
|
|
131
|
+
var fc = failedCapsules[i];
|
|
132
|
+
if (!fc || fc.outcome && fc.outcome.status === 'success') continue;
|
|
133
|
+
var reason = String(fc.failure_reason || '').trim();
|
|
134
|
+
var failureTags = expandSignals((fc.trigger || []).concat(signalList), reason).filter(function (t) {
|
|
135
|
+
return t.indexOf('problem:') === 0 || t.indexOf('risk:') === 0 || t.indexOf('area:') === 0 || t.indexOf('action:') === 0;
|
|
136
|
+
});
|
|
137
|
+
if (failureTags.length === 0) continue;
|
|
138
|
+
var dominantProblem = null;
|
|
139
|
+
for (var p = 0; p < problemPriority.length; p++) {
|
|
140
|
+
if (failureTags.indexOf(problemPriority[p]) !== -1) {
|
|
141
|
+
dominantProblem = problemPriority[p];
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
var groupingTags = dominantProblem
|
|
146
|
+
? [dominantProblem]
|
|
147
|
+
: failureTags.filter(function (tag) { return tag.indexOf('area:') === 0 || tag.indexOf('risk:') === 0; }).slice(0, 1);
|
|
148
|
+
var key = groupingTags.join('|');
|
|
149
|
+
if (!groups[key]) groups[key] = { count: 0, tags: failureTags, reasons: [], gene: fc.gene || null };
|
|
150
|
+
groups[key].count += 1;
|
|
151
|
+
if (reason) groups[key].reasons.push(reason);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Object.keys(groups).forEach(function (key) {
|
|
155
|
+
var group = groups[key];
|
|
156
|
+
if (!group || group.count < 2) return;
|
|
157
|
+
var title = 'Learn from recurring failed evolution paths';
|
|
158
|
+
if (group.tags.indexOf('problem:performance') !== -1) title = 'Resolve recurring performance regressions';
|
|
159
|
+
else if (group.tags.indexOf('problem:protocol') !== -1) title = 'Prevent recurring protocol and validation regressions';
|
|
160
|
+
else if (group.tags.indexOf('problem:reliability') !== -1) title = 'Repair recurring reliability failures';
|
|
161
|
+
else if (group.tags.indexOf('problem:stagnation') !== -1) title = 'Break repeated stagnation loops with a new strategy';
|
|
162
|
+
else if (group.tags.indexOf('area:orchestration') !== -1) title = 'Stabilize task and orchestration behavior';
|
|
163
|
+
var evidence = 'Observed ' + group.count + ' recent failed evolutions with similar learning tags. ' +
|
|
164
|
+
(group.reasons[0] ? 'Latest reason: ' + clip(group.reasons[0], 180) : '');
|
|
165
|
+
candidates.push({
|
|
166
|
+
type: 'CapabilityCandidate',
|
|
167
|
+
id: 'cand_' + stableHash('failed:' + key),
|
|
168
|
+
title: title,
|
|
169
|
+
source: 'failed_capsules',
|
|
170
|
+
created_at: new Date().toISOString(),
|
|
171
|
+
signals: signalList,
|
|
172
|
+
tags: group.tags,
|
|
173
|
+
shape: buildFiveQuestionsShape({ title: title, signals: signalList, evidence: evidence }),
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
116
177
|
// Dedup by id
|
|
117
178
|
const seen = new Set();
|
|
118
179
|
return candidates.filter(c => {
|
|
@@ -142,5 +203,6 @@ function renderCandidatesPreview(candidates, maxChars = 1400) {
|
|
|
142
203
|
module.exports = {
|
|
143
204
|
extractCapabilityCandidates,
|
|
144
205
|
renderCandidatesPreview,
|
|
206
|
+
expandSignals,
|
|
145
207
|
};
|
|
146
208
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
function unique(items) {
|
|
2
|
+
return Array.from(new Set((Array.isArray(items) ? items : []).filter(Boolean).map(function (x) {
|
|
3
|
+
return String(x).trim();
|
|
4
|
+
}).filter(Boolean)));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function add(tags, value) {
|
|
8
|
+
if (!value) return;
|
|
9
|
+
tags.push(String(value).trim());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function expandSignals(signals, extraText) {
|
|
13
|
+
var raw = Array.isArray(signals) ? signals.map(function (s) { return String(s); }) : [];
|
|
14
|
+
var tags = [];
|
|
15
|
+
|
|
16
|
+
for (var i = 0; i < raw.length; i++) {
|
|
17
|
+
var signal = raw[i];
|
|
18
|
+
add(tags, signal);
|
|
19
|
+
var base = signal.split(':')[0];
|
|
20
|
+
if (base && base !== signal) add(tags, base);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var text = (raw.join(' ') + ' ' + String(extraText || '')).toLowerCase();
|
|
24
|
+
|
|
25
|
+
if (/(error|exception|failed|unstable|log_error|runtime|429)/.test(text)) {
|
|
26
|
+
add(tags, 'problem:reliability');
|
|
27
|
+
add(tags, 'action:repair');
|
|
28
|
+
}
|
|
29
|
+
if (/(protocol|prompt|audit|gep|schema|drift)/.test(text)) {
|
|
30
|
+
add(tags, 'problem:protocol');
|
|
31
|
+
add(tags, 'action:optimize');
|
|
32
|
+
add(tags, 'area:prompt');
|
|
33
|
+
}
|
|
34
|
+
if (/(perf|performance|bottleneck|latency|slow|throughput)/.test(text)) {
|
|
35
|
+
add(tags, 'problem:performance');
|
|
36
|
+
add(tags, 'action:optimize');
|
|
37
|
+
}
|
|
38
|
+
if (/(feature|capability_gap|user_feature_request|external_opportunity|stagnation recommendation)/.test(text)) {
|
|
39
|
+
add(tags, 'problem:capability');
|
|
40
|
+
add(tags, 'action:innovate');
|
|
41
|
+
}
|
|
42
|
+
if (/(stagnation|plateau|steady_state|saturation|empty_cycle_loop|loop_detected|recurring)/.test(text)) {
|
|
43
|
+
add(tags, 'problem:stagnation');
|
|
44
|
+
add(tags, 'action:innovate');
|
|
45
|
+
}
|
|
46
|
+
if (/(task|worker|heartbeat|hub|commitment|assignment|orchestration)/.test(text)) {
|
|
47
|
+
add(tags, 'area:orchestration');
|
|
48
|
+
}
|
|
49
|
+
if (/(memory|narrative|reflection)/.test(text)) {
|
|
50
|
+
add(tags, 'area:memory');
|
|
51
|
+
}
|
|
52
|
+
if (/(skill|dashboard)/.test(text)) {
|
|
53
|
+
add(tags, 'area:skills');
|
|
54
|
+
}
|
|
55
|
+
if (/(validation|canary|rollback|constraint|blast radius|destructive)/.test(text)) {
|
|
56
|
+
add(tags, 'risk:validation');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return unique(tags);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function geneTags(gene) {
|
|
63
|
+
if (!gene || typeof gene !== 'object') return [];
|
|
64
|
+
var inputs = [];
|
|
65
|
+
if (gene.category) inputs.push('action:' + String(gene.category).toLowerCase());
|
|
66
|
+
if (Array.isArray(gene.signals_match)) inputs = inputs.concat(gene.signals_match);
|
|
67
|
+
if (typeof gene.id === 'string') inputs.push(gene.id);
|
|
68
|
+
if (typeof gene.summary === 'string') inputs.push(gene.summary);
|
|
69
|
+
return expandSignals(inputs, '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function scoreTagOverlap(gene, signals) {
|
|
73
|
+
var signalTags = expandSignals(signals, '');
|
|
74
|
+
var geneTagList = geneTags(gene);
|
|
75
|
+
if (signalTags.length === 0 || geneTagList.length === 0) return 0;
|
|
76
|
+
var signalSet = new Set(signalTags);
|
|
77
|
+
var hits = 0;
|
|
78
|
+
for (var i = 0; i < geneTagList.length; i++) {
|
|
79
|
+
if (signalSet.has(geneTagList[i])) hits++;
|
|
80
|
+
}
|
|
81
|
+
return hits;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
expandSignals: expandSignals,
|
|
86
|
+
geneTags: geneTags,
|
|
87
|
+
scoreTagOverlap: scoreTagOverlap,
|
|
88
|
+
};
|
package/src/gep/prompt.js
CHANGED
|
@@ -266,6 +266,7 @@ function buildGepPrompt({
|
|
|
266
266
|
recentHistory,
|
|
267
267
|
failedCapsules,
|
|
268
268
|
hubLessons,
|
|
269
|
+
strategyPolicy,
|
|
269
270
|
}) {
|
|
270
271
|
const parentValue = parentEventId ? `"${parentEventId}"` : 'null';
|
|
271
272
|
const selectedGeneId = selectedGene && selectedGene.id ? selectedGene.id : 'gene_<name>';
|
|
@@ -289,6 +290,15 @@ ACTIVE STRATEGY (Generic):
|
|
|
289
290
|
3. Apply minimal, safe changes.
|
|
290
291
|
4. Validate changes strictly.
|
|
291
292
|
5. Solidify knowledge.
|
|
293
|
+
`.trim();
|
|
294
|
+
}
|
|
295
|
+
let strategyPolicyBlock = '';
|
|
296
|
+
if (strategyPolicy && Array.isArray(strategyPolicy.directives) && strategyPolicy.directives.length > 0) {
|
|
297
|
+
strategyPolicyBlock = `
|
|
298
|
+
ADAPTIVE STRATEGY POLICY:
|
|
299
|
+
${strategyPolicy.directives.map((s, i) => `${i + 1}. ${s}`).join('\n')}
|
|
300
|
+
${strategyPolicy.forceInnovate ? 'You MUST prefer INNOVATE unless a critical blocking error is present.' : ''}
|
|
301
|
+
${strategyPolicy.cautiousExecution ? 'You MUST reduce blast radius and avoid broad refactors in this cycle.' : ''}
|
|
292
302
|
`.trim();
|
|
293
303
|
}
|
|
294
304
|
|
|
@@ -384,6 +394,7 @@ II. Directives & Logic
|
|
|
384
394
|
|
|
385
395
|
2. Selection: Selected Gene "${selectedGeneId}".
|
|
386
396
|
${strategyBlock}
|
|
397
|
+
${strategyPolicyBlock ? '\n' + strategyPolicyBlock : ''}
|
|
387
398
|
|
|
388
399
|
3. Execution: Apply changes (tool calls). Repair/Optimize: small/reversible. Innovate: new skills in \`skills/<name>/\`.
|
|
389
400
|
4. Validation: Run gene's validation steps. Fail = ROLLBACK.
|
package/src/gep/selector.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const { scoreTagOverlap } = require('./learningSignals');
|
|
2
|
+
const { captureEnvFingerprint } = require('./envFingerprint');
|
|
3
|
+
|
|
1
4
|
function matchPatternToSignals(pattern, signals) {
|
|
2
5
|
if (!pattern || !signals || signals.length === 0) return false;
|
|
3
6
|
const p = String(pattern);
|
|
@@ -30,12 +33,54 @@ function matchPatternToSignals(pattern, signals) {
|
|
|
30
33
|
function scoreGene(gene, signals) {
|
|
31
34
|
if (!gene || gene.type !== 'Gene') return 0;
|
|
32
35
|
const patterns = Array.isArray(gene.signals_match) ? gene.signals_match : [];
|
|
33
|
-
|
|
36
|
+
var tagScore = scoreTagOverlap(gene, signals);
|
|
37
|
+
if (patterns.length === 0) return tagScore > 0 ? tagScore * 0.6 : 0;
|
|
34
38
|
let score = 0;
|
|
35
39
|
for (const pat of patterns) {
|
|
36
40
|
if (matchPatternToSignals(pat, signals)) score += 1;
|
|
37
41
|
}
|
|
38
|
-
return score;
|
|
42
|
+
return score + (tagScore * 0.6);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getEpigeneticBoostLocal(gene, envFingerprint) {
|
|
46
|
+
if (!gene || !Array.isArray(gene.epigenetic_marks)) return 0;
|
|
47
|
+
const platform = envFingerprint && envFingerprint.platform ? String(envFingerprint.platform) : '';
|
|
48
|
+
const arch = envFingerprint && envFingerprint.arch ? String(envFingerprint.arch) : '';
|
|
49
|
+
const nodeVersion = envFingerprint && envFingerprint.node_version ? String(envFingerprint.node_version) : '';
|
|
50
|
+
const envContext = [platform, arch, nodeVersion].filter(Boolean).join('/') || 'unknown';
|
|
51
|
+
const mark = gene.epigenetic_marks.find(function (m) { return m && m.context === envContext; });
|
|
52
|
+
return mark ? Number(mark.boost) || 0 : 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function scoreGeneLearning(gene, signals, envFingerprint) {
|
|
56
|
+
if (!gene || gene.type !== 'Gene') return 0;
|
|
57
|
+
var boost = 0;
|
|
58
|
+
|
|
59
|
+
var history = Array.isArray(gene.learning_history) ? gene.learning_history.slice(-8) : [];
|
|
60
|
+
for (var i = 0; i < history.length; i++) {
|
|
61
|
+
var entry = history[i];
|
|
62
|
+
if (!entry) continue;
|
|
63
|
+
if (entry.outcome === 'success') boost += 0.12;
|
|
64
|
+
else if (entry.mode === 'hard') boost -= 0.22;
|
|
65
|
+
else if (entry.mode === 'soft') boost -= 0.08;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
boost += getEpigeneticBoostLocal(gene, envFingerprint);
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(gene.anti_patterns) && gene.anti_patterns.length > 0) {
|
|
71
|
+
var overlapPenalty = 0;
|
|
72
|
+
var signalTags = new Set(require('./learningSignals').expandSignals(signals, ''));
|
|
73
|
+
var recentAntiPatterns = gene.anti_patterns.slice(-6);
|
|
74
|
+
for (var j = 0; j < recentAntiPatterns.length; j++) {
|
|
75
|
+
var anti = recentAntiPatterns[j];
|
|
76
|
+
if (!anti || !Array.isArray(anti.learning_signals)) continue;
|
|
77
|
+
var overlap = anti.learning_signals.some(function (tag) { return signalTags.has(String(tag)); });
|
|
78
|
+
if (overlap) overlapPenalty += anti.mode === 'hard' ? 0.4 : 0.18;
|
|
79
|
+
}
|
|
80
|
+
boost -= overlapPenalty;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Math.max(-1.5, Math.min(1.5, boost));
|
|
39
84
|
}
|
|
40
85
|
|
|
41
86
|
// Population-size-dependent drift intensity.
|
|
@@ -94,9 +139,11 @@ function selectGene(genes, signals, opts) {
|
|
|
94
139
|
var DISTILLED_PREFIX = 'gene_distilled_';
|
|
95
140
|
var DISTILLED_SCORE_FACTOR = 0.8;
|
|
96
141
|
|
|
142
|
+
const envFingerprint = captureEnvFingerprint();
|
|
97
143
|
const scored = genesList
|
|
98
144
|
.map(g => {
|
|
99
145
|
var s = scoreGene(g, signals);
|
|
146
|
+
s += scoreGeneLearning(g, signals, envFingerprint);
|
|
100
147
|
if (s > 0 && g.id && String(g.id).startsWith(DISTILLED_PREFIX)) s *= DISTILLED_SCORE_FACTOR;
|
|
101
148
|
return { gene: g, score: s };
|
|
102
149
|
})
|
|
@@ -4,6 +4,7 @@ var fs = require('fs');
|
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var crypto = require('crypto');
|
|
6
6
|
var paths = require('./paths');
|
|
7
|
+
var learningSignals = require('./learningSignals');
|
|
7
8
|
|
|
8
9
|
var DISTILLER_MIN_CAPSULES = parseInt(process.env.DISTILLER_MIN_CAPSULES || '10', 10) || 10;
|
|
9
10
|
var DISTILLER_INTERVAL_HOURS = parseInt(process.env.DISTILLER_INTERVAL_HOURS || '24', 10) || 24;
|
|
@@ -573,6 +574,126 @@ function prepareDistillation() {
|
|
|
573
574
|
return { ok: true, promptPath: promptPath, requestPath: reqPath, dataHash: data.dataHash };
|
|
574
575
|
}
|
|
575
576
|
|
|
577
|
+
function inferCategoryFromSignals(signals) {
|
|
578
|
+
var list = Array.isArray(signals) ? signals.map(function (s) { return String(s).toLowerCase(); }) : [];
|
|
579
|
+
if (list.some(function (s) { return s.indexOf('error') !== -1 || s.indexOf('fail') !== -1 || s.indexOf('reliability') !== -1; })) {
|
|
580
|
+
return 'repair';
|
|
581
|
+
}
|
|
582
|
+
if (list.some(function (s) { return s.indexOf('feature') !== -1 || s.indexOf('capability') !== -1 || s.indexOf('stagnation') !== -1; })) {
|
|
583
|
+
return 'innovate';
|
|
584
|
+
}
|
|
585
|
+
return 'optimize';
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function chooseDistillationSource(data, analysis) {
|
|
589
|
+
var grouped = data && data.grouped ? data.grouped : {};
|
|
590
|
+
var best = null;
|
|
591
|
+
Object.keys(grouped).forEach(function (geneId) {
|
|
592
|
+
var g = grouped[geneId];
|
|
593
|
+
if (!g || g.total_count <= 0) return;
|
|
594
|
+
var score = (g.total_count * 2) + (g.avg_score || 0);
|
|
595
|
+
if (!best || score > best.score) {
|
|
596
|
+
best = { gene_id: geneId, group: g, score: score };
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return best;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function synthesizeGeneFromPatterns(data, analysis, existingGenes) {
|
|
603
|
+
var source = chooseDistillationSource(data, analysis);
|
|
604
|
+
if (!source || !source.group) return null;
|
|
605
|
+
|
|
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;
|
|
609
|
+
|
|
610
|
+
var triggerFreq = {};
|
|
611
|
+
(group.triggers || []).forEach(function (arr) {
|
|
612
|
+
(Array.isArray(arr) ? arr : []).forEach(function (s) {
|
|
613
|
+
var k = String(s).toLowerCase();
|
|
614
|
+
triggerFreq[k] = (triggerFreq[k] || 0) + 1;
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
var signalsMatch = Object.keys(triggerFreq)
|
|
618
|
+
.sort(function (a, b) { return triggerFreq[b] - triggerFreq[a]; })
|
|
619
|
+
.slice(0, 6);
|
|
620
|
+
var summaryText = (group.summaries || []).slice(0, 5).join(' ');
|
|
621
|
+
var derivedTags = learningSignals.expandSignals(signalsMatch, summaryText)
|
|
622
|
+
.filter(function (tag) { return tag.indexOf('problem:') === 0 || tag.indexOf('area:') === 0; })
|
|
623
|
+
.slice(0, 4);
|
|
624
|
+
signalsMatch = Array.from(new Set(signalsMatch.concat(derivedTags)));
|
|
625
|
+
if (signalsMatch.length === 0 && sourceGene && Array.isArray(sourceGene.signals_match)) {
|
|
626
|
+
signalsMatch = sourceGene.signals_match.slice(0, 6);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
var category = sourceGene && sourceGene.category ? sourceGene.category : inferCategoryFromSignals(signalsMatch);
|
|
630
|
+
var idSeed = {
|
|
631
|
+
type: 'Gene',
|
|
632
|
+
id: DISTILLED_ID_PREFIX + source.gene_id.replace(/^gene_/, '').replace(/^gene_distilled_/, ''),
|
|
633
|
+
category: category,
|
|
634
|
+
signals_match: signalsMatch,
|
|
635
|
+
strategy: sourceGene && Array.isArray(sourceGene.strategy) && sourceGene.strategy.length > 0
|
|
636
|
+
? sourceGene.strategy.slice(0, 4)
|
|
637
|
+
: [
|
|
638
|
+
'Identify the dominant repeated trigger pattern.',
|
|
639
|
+
'Apply the smallest targeted change for that pattern.',
|
|
640
|
+
'Run the narrowest validation that proves the regression is gone.',
|
|
641
|
+
'Rollback immediately if validation fails.',
|
|
642
|
+
],
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
var summaryBase = (group.summaries && group.summaries[0]) ? String(group.summaries[0]) : '';
|
|
646
|
+
if (!summaryBase) {
|
|
647
|
+
summaryBase = 'Reusable strategy for repeated successful pattern: ' + signalsMatch.slice(0, 3).join(', ');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
var gene = {
|
|
651
|
+
type: 'Gene',
|
|
652
|
+
id: deriveDescriptiveId(idSeed),
|
|
653
|
+
summary: summaryBase.slice(0, 200),
|
|
654
|
+
category: category,
|
|
655
|
+
signals_match: signalsMatch,
|
|
656
|
+
preconditions: sourceGene && Array.isArray(sourceGene.preconditions) && sourceGene.preconditions.length > 0
|
|
657
|
+
? sourceGene.preconditions.slice(0, 4)
|
|
658
|
+
: ['repeated success pattern observed in recent capsules'],
|
|
659
|
+
strategy: idSeed.strategy,
|
|
660
|
+
constraints: {
|
|
661
|
+
max_files: sourceGene && sourceGene.constraints && Number(sourceGene.constraints.max_files) > 0
|
|
662
|
+
? Math.min(DISTILLED_MAX_FILES, Number(sourceGene.constraints.max_files))
|
|
663
|
+
: DISTILLED_MAX_FILES,
|
|
664
|
+
forbidden_paths: sourceGene && sourceGene.constraints && Array.isArray(sourceGene.constraints.forbidden_paths)
|
|
665
|
+
? sourceGene.constraints.forbidden_paths.slice(0, 6)
|
|
666
|
+
: ['.git', 'node_modules'],
|
|
667
|
+
},
|
|
668
|
+
validation: sourceGene && Array.isArray(sourceGene.validation) && sourceGene.validation.length > 0
|
|
669
|
+
? sourceGene.validation.slice(0, 4)
|
|
670
|
+
: ['node --test'],
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
return gene;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function finalizeDistilledGene(gene, requestLike, status) {
|
|
677
|
+
var state = readDistillerState();
|
|
678
|
+
state.last_distillation_at = new Date().toISOString();
|
|
679
|
+
state.last_data_hash = requestLike.data_hash;
|
|
680
|
+
state.last_gene_id = gene.id;
|
|
681
|
+
state.distillation_count = (state.distillation_count || 0) + 1;
|
|
682
|
+
writeDistillerState(state);
|
|
683
|
+
|
|
684
|
+
appendJsonl(distillerLogPath(), {
|
|
685
|
+
timestamp: new Date().toISOString(),
|
|
686
|
+
data_hash: requestLike.data_hash,
|
|
687
|
+
input_capsule_count: requestLike.input_capsule_count,
|
|
688
|
+
analysis_summary: requestLike.analysis_summary,
|
|
689
|
+
synthesized_gene_id: gene.id,
|
|
690
|
+
validation_passed: true,
|
|
691
|
+
validation_errors: [],
|
|
692
|
+
status: status || 'success',
|
|
693
|
+
gene: gene,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
576
697
|
// ---------------------------------------------------------------------------
|
|
577
698
|
// Step 5b: completeDistillation -- validate LLM response and save gene
|
|
578
699
|
// ---------------------------------------------------------------------------
|
|
@@ -665,11 +786,67 @@ function completeDistillation(responseText) {
|
|
|
665
786
|
return { ok: true, gene: gene };
|
|
666
787
|
}
|
|
667
788
|
|
|
789
|
+
function autoDistill() {
|
|
790
|
+
var data = collectDistillationData();
|
|
791
|
+
if (data.successCapsules.length < DISTILLER_MIN_CAPSULES) {
|
|
792
|
+
return { ok: false, reason: 'insufficient_data' };
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
var state = readDistillerState();
|
|
796
|
+
if (state.last_data_hash === data.dataHash) {
|
|
797
|
+
return { ok: false, reason: 'idempotent_skip' };
|
|
798
|
+
}
|
|
799
|
+
|
|
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);
|
|
805
|
+
if (!rawGene) return { ok: false, reason: 'no_candidate_gene' };
|
|
806
|
+
|
|
807
|
+
var validation = validateSynthesizedGene(rawGene, existingGenes);
|
|
808
|
+
if (!validation.valid) {
|
|
809
|
+
appendJsonl(distillerLogPath(), {
|
|
810
|
+
timestamp: new Date().toISOString(),
|
|
811
|
+
data_hash: data.dataHash,
|
|
812
|
+
status: 'auto_validation_failed',
|
|
813
|
+
synthesized_gene_id: validation.gene ? validation.gene.id : null,
|
|
814
|
+
validation_errors: validation.errors,
|
|
815
|
+
});
|
|
816
|
+
return { ok: false, reason: 'validation_failed', errors: validation.errors };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
var gene = validation.gene;
|
|
820
|
+
gene._distilled_meta = {
|
|
821
|
+
distilled_at: new Date().toISOString(),
|
|
822
|
+
source_capsule_count: data.successCapsules.length,
|
|
823
|
+
data_hash: data.dataHash,
|
|
824
|
+
auto_distilled: true,
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
var assetStore = require('./assetStore');
|
|
828
|
+
assetStore.upsertGene(gene);
|
|
829
|
+
finalizeDistilledGene(gene, {
|
|
830
|
+
data_hash: data.dataHash,
|
|
831
|
+
input_capsule_count: data.successCapsules.length,
|
|
832
|
+
analysis_summary: {
|
|
833
|
+
high_frequency_count: analysis.high_frequency.length,
|
|
834
|
+
drift_count: analysis.strategy_drift.length,
|
|
835
|
+
gap_count: analysis.coverage_gaps.length,
|
|
836
|
+
success_rate: Math.round(analysis.success_rate * 100) / 100,
|
|
837
|
+
},
|
|
838
|
+
}, 'auto_success');
|
|
839
|
+
|
|
840
|
+
return { ok: true, gene: gene, auto: true };
|
|
841
|
+
}
|
|
842
|
+
|
|
668
843
|
module.exports = {
|
|
669
844
|
collectDistillationData: collectDistillationData,
|
|
670
845
|
analyzePatterns: analyzePatterns,
|
|
846
|
+
synthesizeGeneFromPatterns: synthesizeGeneFromPatterns,
|
|
671
847
|
prepareDistillation: prepareDistillation,
|
|
672
848
|
completeDistillation: completeDistillation,
|
|
849
|
+
autoDistill: autoDistill,
|
|
673
850
|
validateSynthesizedGene: validateSynthesizedGene,
|
|
674
851
|
sanitizeSignalsMatch: sanitizeSignalsMatch,
|
|
675
852
|
shouldDistill: shouldDistill,
|
package/src/gep/solidify.js
CHANGED
|
@@ -383,12 +383,12 @@ function readStateForSolidify() {
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
function writeStateForSolidify(state) {
|
|
386
|
-
const
|
|
387
|
-
const statePath = path.join(
|
|
386
|
+
const evolutionDir = getEvolutionDir();
|
|
387
|
+
const statePath = path.join(evolutionDir, 'evolution_solidify_state.json');
|
|
388
388
|
try {
|
|
389
|
-
if (!fs.existsSync(
|
|
389
|
+
if (!fs.existsSync(evolutionDir)) fs.mkdirSync(evolutionDir, { recursive: true });
|
|
390
390
|
} catch (e) {
|
|
391
|
-
console.warn('[evolver] writeStateForSolidify mkdir failed:',
|
|
391
|
+
console.warn('[evolver] writeStateForSolidify mkdir failed:', evolutionDir, e && e.message || e);
|
|
392
392
|
}
|
|
393
393
|
const tmp = `${statePath}.tmp`;
|
|
394
394
|
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
@@ -671,6 +671,108 @@ function buildFailureReason(constraintCheck, validation, protocolViolations, can
|
|
|
671
671
|
return reasons.join('; ').slice(0, 2000) || 'unknown';
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
+
function buildSoftFailureLearningSignals(opts) {
|
|
675
|
+
const { expandSignals } = require('./learningSignals');
|
|
676
|
+
var signals = opts && Array.isArray(opts.signals) ? opts.signals : [];
|
|
677
|
+
var failureReason = opts && opts.failureReason ? String(opts.failureReason) : '';
|
|
678
|
+
var violations = opts && Array.isArray(opts.violations) ? opts.violations : [];
|
|
679
|
+
var validationResults = opts && Array.isArray(opts.validationResults) ? opts.validationResults : [];
|
|
680
|
+
var validationText = validationResults
|
|
681
|
+
.filter(function (r) { return r && r.ok === false; })
|
|
682
|
+
.map(function (r) { return [r.cmd, r.stderr, r.stdout].filter(Boolean).join(' '); })
|
|
683
|
+
.join(' ');
|
|
684
|
+
return expandSignals(signals.concat(violations), failureReason + ' ' + validationText)
|
|
685
|
+
.filter(function (tag) {
|
|
686
|
+
return tag.indexOf('problem:') === 0 || tag.indexOf('risk:') === 0 || tag.indexOf('area:') === 0 || tag.indexOf('action:') === 0;
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function classifyFailureMode(opts) {
|
|
691
|
+
var constraintViolations = opts && Array.isArray(opts.constraintViolations) ? opts.constraintViolations : [];
|
|
692
|
+
var protocolViolations = opts && Array.isArray(opts.protocolViolations) ? opts.protocolViolations : [];
|
|
693
|
+
var validation = opts && opts.validation ? opts.validation : null;
|
|
694
|
+
var canary = opts && opts.canary ? opts.canary : null;
|
|
695
|
+
|
|
696
|
+
if (constraintViolations.some(function (v) {
|
|
697
|
+
var s = String(v || '');
|
|
698
|
+
return /HARD CAP BREACH|CRITICAL_FILE_|critical_path_modified|forbidden_path touched|ethics:/i.test(s);
|
|
699
|
+
})) {
|
|
700
|
+
return { mode: 'hard', reasonClass: 'constraint_destructive', retryable: false };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (protocolViolations.length > 0) {
|
|
704
|
+
return { mode: 'hard', reasonClass: 'protocol', retryable: false };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (canary && !canary.ok && !canary.skipped) {
|
|
708
|
+
return { mode: 'hard', reasonClass: 'canary', retryable: false };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (constraintViolations.length > 0) {
|
|
712
|
+
return { mode: 'hard', reasonClass: 'constraint', retryable: false };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (validation && validation.ok === false) {
|
|
716
|
+
return { mode: 'soft', reasonClass: 'validation', retryable: true };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { mode: 'soft', reasonClass: 'unknown', retryable: true };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function adaptGeneFromLearning(opts) {
|
|
723
|
+
var gene = opts && opts.gene && opts.gene.type === 'Gene' ? opts.gene : null;
|
|
724
|
+
if (!gene) return gene;
|
|
725
|
+
|
|
726
|
+
var outcomeStatus = String(opts && opts.outcomeStatus || '').toLowerCase();
|
|
727
|
+
var learningSignals = Array.isArray(opts && opts.learningSignals) ? opts.learningSignals : [];
|
|
728
|
+
var failureMode = opts && opts.failureMode && typeof opts.failureMode === 'object'
|
|
729
|
+
? opts.failureMode
|
|
730
|
+
: { mode: 'soft', reasonClass: 'unknown', retryable: true };
|
|
731
|
+
|
|
732
|
+
if (!Array.isArray(gene.learning_history)) gene.learning_history = [];
|
|
733
|
+
if (!Array.isArray(gene.signals_match)) gene.signals_match = [];
|
|
734
|
+
|
|
735
|
+
var seenSignal = new Set(gene.signals_match.map(function (s) { return String(s); }));
|
|
736
|
+
if (outcomeStatus === 'success') {
|
|
737
|
+
for (var i = 0; i < learningSignals.length; i++) {
|
|
738
|
+
var sig = String(learningSignals[i] || '');
|
|
739
|
+
if (!sig || seenSignal.has(sig)) continue;
|
|
740
|
+
if (sig.indexOf('problem:') === 0 || sig.indexOf('area:') === 0) {
|
|
741
|
+
gene.signals_match.push(sig);
|
|
742
|
+
seenSignal.add(sig);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
gene.learning_history.push({
|
|
748
|
+
at: nowIso(),
|
|
749
|
+
outcome: outcomeStatus || 'unknown',
|
|
750
|
+
mode: failureMode.mode || 'soft',
|
|
751
|
+
reason_class: failureMode.reasonClass || 'unknown',
|
|
752
|
+
retryable: !!failureMode.retryable,
|
|
753
|
+
learning_signals: learningSignals.slice(0, 12),
|
|
754
|
+
});
|
|
755
|
+
if (gene.learning_history.length > 20) {
|
|
756
|
+
gene.learning_history = gene.learning_history.slice(gene.learning_history.length - 20);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (outcomeStatus === 'failed') {
|
|
760
|
+
if (!Array.isArray(gene.anti_patterns)) gene.anti_patterns = [];
|
|
761
|
+
var anti = {
|
|
762
|
+
at: nowIso(),
|
|
763
|
+
mode: failureMode.mode || 'soft',
|
|
764
|
+
reason_class: failureMode.reasonClass || 'unknown',
|
|
765
|
+
learning_signals: learningSignals.slice(0, 8),
|
|
766
|
+
};
|
|
767
|
+
gene.anti_patterns.push(anti);
|
|
768
|
+
if (gene.anti_patterns.length > 12) {
|
|
769
|
+
gene.anti_patterns = gene.anti_patterns.slice(gene.anti_patterns.length - 12);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return gene;
|
|
774
|
+
}
|
|
775
|
+
|
|
674
776
|
function rollbackTracked(repoRoot) {
|
|
675
777
|
const mode = String(process.env.EVOLVER_ROLLBACK_MODE || 'hard').toLowerCase();
|
|
676
778
|
|
|
@@ -1157,6 +1259,23 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
|
|
|
1157
1259
|
const ts = nowIso();
|
|
1158
1260
|
const outcomeStatus = success ? 'success' : 'failed';
|
|
1159
1261
|
const score = clamp01(success ? 0.85 : 0.2);
|
|
1262
|
+
const failureReason = !success ? buildFailureReason(constraintCheck, validation, protocolViolations, canary) : '';
|
|
1263
|
+
const failureMode = !success
|
|
1264
|
+
? classifyFailureMode({
|
|
1265
|
+
constraintViolations: constraintCheck.violations,
|
|
1266
|
+
protocolViolations: protocolViolations,
|
|
1267
|
+
validation: validation,
|
|
1268
|
+
canary: canary,
|
|
1269
|
+
})
|
|
1270
|
+
: { mode: 'none', reasonClass: null, retryable: false };
|
|
1271
|
+
const softFailureLearningSignals = !success
|
|
1272
|
+
? buildSoftFailureLearningSignals({
|
|
1273
|
+
signals,
|
|
1274
|
+
failureReason,
|
|
1275
|
+
violations: constraintCheck.violations,
|
|
1276
|
+
validationResults: validation.results,
|
|
1277
|
+
})
|
|
1278
|
+
: [];
|
|
1160
1279
|
|
|
1161
1280
|
const selectedCapsuleId =
|
|
1162
1281
|
lastRun && typeof lastRun.selected_capsule_id === 'string' && lastRun.selected_capsule_id.trim()
|
|
@@ -1224,6 +1343,12 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
|
|
|
1224
1343
|
protocol_ok: protocolViolations.length === 0,
|
|
1225
1344
|
protocol_violations: protocolViolations,
|
|
1226
1345
|
memory_graph: memoryGraphPath(),
|
|
1346
|
+
soft_failure: success ? null : {
|
|
1347
|
+
learning_signals: softFailureLearningSignals,
|
|
1348
|
+
retryable: !!failureMode.retryable,
|
|
1349
|
+
class: failureMode.reasonClass,
|
|
1350
|
+
mode: failureMode.mode,
|
|
1351
|
+
},
|
|
1227
1352
|
},
|
|
1228
1353
|
};
|
|
1229
1354
|
// Build desensitized execution trace for cross-agent experience sharing
|
|
@@ -1303,7 +1428,8 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
|
|
|
1303
1428
|
? 'Failed: ' + geneUsed.id + ' on signals [' + (signals.slice(0, 3).join(', ') || 'none') + ']'
|
|
1304
1429
|
: 'Failed evolution on signals [' + (signals.slice(0, 3).join(', ') || 'none') + ']',
|
|
1305
1430
|
diff_snapshot: diffSnapshot,
|
|
1306
|
-
failure_reason:
|
|
1431
|
+
failure_reason: failureReason,
|
|
1432
|
+
learning_signals: softFailureLearningSignals,
|
|
1307
1433
|
constraint_violations: constraintCheck.violations || [],
|
|
1308
1434
|
env_fingerprint: envFp,
|
|
1309
1435
|
blast_radius: { files: blast.files, lines: blast.lines },
|
|
@@ -1331,6 +1457,12 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
|
|
|
1331
1457
|
// Apply epigenetic marks to the gene based on outcome and environment
|
|
1332
1458
|
if (!dryRun && geneUsed && geneUsed.type === 'Gene') {
|
|
1333
1459
|
try {
|
|
1460
|
+
adaptGeneFromLearning({
|
|
1461
|
+
gene: geneUsed,
|
|
1462
|
+
outcomeStatus: outcomeStatus,
|
|
1463
|
+
learningSignals: success ? signals : softFailureLearningSignals,
|
|
1464
|
+
failureMode: failureMode,
|
|
1465
|
+
});
|
|
1334
1466
|
applyEpigeneticMarks(geneUsed, envFp, outcomeStatus);
|
|
1335
1467
|
upsertGene(geneUsed);
|
|
1336
1468
|
} catch (e) {
|
|
@@ -1562,7 +1694,7 @@ function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true }
|
|
|
1562
1694
|
// which we already do above. The Hub-side solicitLesson() handles the rest.
|
|
1563
1695
|
// For failures without a published event (no auto-publish), we still log locally.
|
|
1564
1696
|
if (!dryRun && !success && event && event.outcome) {
|
|
1565
|
-
var failureContent =
|
|
1697
|
+
var failureContent = failureReason;
|
|
1566
1698
|
event.failure_reason = failureContent;
|
|
1567
1699
|
event.summary = geneUsed
|
|
1568
1700
|
? 'Failed: ' + geneUsed.id + ' on signals [' + (signals.slice(0, 3).join(', ') || 'none') + '] - ' + failureContent.slice(0, 200)
|
|
@@ -1708,6 +1840,9 @@ module.exports = {
|
|
|
1708
1840
|
classifyBlastSeverity,
|
|
1709
1841
|
analyzeBlastRadiusBreakdown,
|
|
1710
1842
|
compareBlastEstimate,
|
|
1843
|
+
classifyFailureMode,
|
|
1844
|
+
adaptGeneFromLearning,
|
|
1845
|
+
buildSoftFailureLearningSignals,
|
|
1711
1846
|
runCanaryCheck,
|
|
1712
1847
|
applyEpigeneticMarks,
|
|
1713
1848
|
getEpigeneticBoost,
|