@evomap/evolver 1.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +290 -0
  3. package/README.zh-CN.md +236 -0
  4. package/SKILL.md +132 -0
  5. package/assets/gep/capsules.json +79 -0
  6. package/assets/gep/events.jsonl +7 -0
  7. package/assets/gep/genes.json +108 -0
  8. package/index.js +530 -0
  9. package/package.json +38 -0
  10. package/src/canary.js +13 -0
  11. package/src/evolve.js +1704 -0
  12. package/src/gep/a2a.js +173 -0
  13. package/src/gep/a2aProtocol.js +736 -0
  14. package/src/gep/analyzer.js +35 -0
  15. package/src/gep/assetCallLog.js +130 -0
  16. package/src/gep/assetStore.js +297 -0
  17. package/src/gep/assets.js +36 -0
  18. package/src/gep/bridge.js +71 -0
  19. package/src/gep/candidates.js +142 -0
  20. package/src/gep/contentHash.js +65 -0
  21. package/src/gep/deviceId.js +209 -0
  22. package/src/gep/envFingerprint.js +83 -0
  23. package/src/gep/hubReview.js +206 -0
  24. package/src/gep/hubSearch.js +237 -0
  25. package/src/gep/issueReporter.js +262 -0
  26. package/src/gep/llmReview.js +92 -0
  27. package/src/gep/memoryGraph.js +771 -0
  28. package/src/gep/memoryGraphAdapter.js +203 -0
  29. package/src/gep/mutation.js +186 -0
  30. package/src/gep/narrativeMemory.js +108 -0
  31. package/src/gep/paths.js +113 -0
  32. package/src/gep/personality.js +355 -0
  33. package/src/gep/prompt.js +566 -0
  34. package/src/gep/questionGenerator.js +212 -0
  35. package/src/gep/reflection.js +127 -0
  36. package/src/gep/sanitize.js +67 -0
  37. package/src/gep/selector.js +250 -0
  38. package/src/gep/signals.js +417 -0
  39. package/src/gep/skillDistiller.js +499 -0
  40. package/src/gep/solidify.js +1681 -0
  41. package/src/gep/strategy.js +126 -0
  42. package/src/gep/taskReceiver.js +528 -0
  43. package/src/gep/validationReport.js +55 -0
  44. package/src/ops/cleanup.js +80 -0
  45. package/src/ops/commentary.js +60 -0
  46. package/src/ops/health_check.js +106 -0
  47. package/src/ops/index.js +11 -0
  48. package/src/ops/innovation.js +67 -0
  49. package/src/ops/lifecycle.js +168 -0
  50. package/src/ops/self_repair.js +72 -0
  51. package/src/ops/skills_monitor.js +143 -0
  52. package/src/ops/trigger.js +33 -0
@@ -0,0 +1,55 @@
1
+ // Standardized ValidationReport type for GEP.
2
+ // Machine-readable, self-contained, and interoperable.
3
+ // Can be consumed by external Hubs or Judges for automated assessment.
4
+
5
+ const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
6
+ const { captureEnvFingerprint, envFingerprintKey } = require('./envFingerprint');
7
+
8
+ // Build a standardized ValidationReport from raw validation results.
9
+ function buildValidationReport({ geneId, commands, results, envFp, startedAt, finishedAt }) {
10
+ const env = envFp || captureEnvFingerprint();
11
+ const resultsList = Array.isArray(results) ? results : [];
12
+ const cmdsList = Array.isArray(commands) ? commands : resultsList.map(function (r) { return r && r.cmd ? String(r.cmd) : ''; });
13
+ const overallOk = resultsList.length > 0 && resultsList.every(function (r) { return r && r.ok; });
14
+ const durationMs =
15
+ Number.isFinite(startedAt) && Number.isFinite(finishedAt) ? finishedAt - startedAt : null;
16
+
17
+ const report = {
18
+ type: 'ValidationReport',
19
+ schema_version: SCHEMA_VERSION,
20
+ id: 'vr_' + Date.now(),
21
+ gene_id: geneId || null,
22
+ env_fingerprint: env,
23
+ env_fingerprint_key: envFingerprintKey(env),
24
+ commands: cmdsList.map(function (cmd, i) {
25
+ const r = resultsList[i] || {};
26
+ return {
27
+ command: String(cmd || ''),
28
+ ok: !!r.ok,
29
+ stdout: String(r.out || r.stdout || '').slice(0, 4000), // Updated to support both 'out' and 'stdout'
30
+ stderr: String(r.err || r.stderr || '').slice(0, 4000), // Updated to support both 'err' and 'stderr'
31
+ };
32
+ }),
33
+ overall_ok: overallOk,
34
+ duration_ms: durationMs,
35
+ created_at: new Date().toISOString(),
36
+ };
37
+
38
+ report.asset_id = computeAssetId(report);
39
+ return report;
40
+ }
41
+
42
+ // Validate that an object is a well-formed ValidationReport.
43
+ function isValidValidationReport(obj) {
44
+ if (!obj || typeof obj !== 'object') return false;
45
+ if (obj.type !== 'ValidationReport') return false;
46
+ if (!obj.id || typeof obj.id !== 'string') return false;
47
+ if (!Array.isArray(obj.commands)) return false;
48
+ if (typeof obj.overall_ok !== 'boolean') return false;
49
+ return true;
50
+ }
51
+
52
+ module.exports = {
53
+ buildValidationReport,
54
+ isValidValidationReport,
55
+ };
@@ -0,0 +1,80 @@
1
+ // GEP Artifact Cleanup - Evolver Core Module
2
+ // Removes old gep_prompt_*.json/txt files from evolution dir.
3
+ // Keeps at least 10 most recent files regardless of age.
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { getEvolutionDir } = require('../gep/paths');
8
+
9
+ var MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ var MIN_KEEP = 10;
11
+
12
+ function safeBatchDelete(batch) {
13
+ var deleted = 0;
14
+ for (var i = 0; i < batch.length; i++) {
15
+ try { fs.unlinkSync(batch[i]); deleted++; } catch (_) {}
16
+ }
17
+ return deleted;
18
+ }
19
+
20
+ function run() {
21
+ var evoDir = getEvolutionDir();
22
+ if (!fs.existsSync(evoDir)) return;
23
+
24
+ var files = fs.readdirSync(evoDir)
25
+ .filter(function(f) { return /^gep_prompt_.*\.(json|txt)$/.test(f); })
26
+ .map(function(f) {
27
+ var full = path.join(evoDir, f);
28
+ var stat = fs.statSync(full);
29
+ return { name: f, path: full, mtime: stat.mtimeMs };
30
+ })
31
+ .sort(function(a, b) { return b.mtime - a.mtime; }); // newest first
32
+
33
+ var now = Date.now();
34
+ var deleted = 0;
35
+
36
+ // Phase 1: Age-based cleanup (keep at least MIN_KEEP)
37
+ var filesToDelete = [];
38
+ for (var i = MIN_KEEP; i < files.length; i++) {
39
+ if (now - files[i].mtime > MAX_AGE_MS) {
40
+ filesToDelete.push(files[i].path);
41
+ }
42
+ }
43
+
44
+ if (filesToDelete.length > 0) {
45
+ deleted += safeBatchDelete(filesToDelete);
46
+ }
47
+
48
+ // Phase 2: Size-based safety cap (keep max 10 files total)
49
+ try {
50
+ var remainingFiles = fs.readdirSync(evoDir)
51
+ .filter(function(f) { return /^gep_prompt_.*\.(json|txt)$/.test(f); })
52
+ .map(function(f) {
53
+ var full = path.join(evoDir, f);
54
+ var stat = fs.statSync(full);
55
+ return { name: f, path: full, mtime: stat.mtimeMs };
56
+ })
57
+ .sort(function(a, b) { return b.mtime - a.mtime; }); // newest first
58
+
59
+ var MAX_FILES = 10;
60
+ if (remainingFiles.length > MAX_FILES) {
61
+ var toDelete = remainingFiles.slice(MAX_FILES).map(function(f) { return f.path; });
62
+ deleted += safeBatchDelete(toDelete);
63
+ }
64
+ } catch (e) {
65
+ console.warn('[Cleanup] Phase 2 failed:', e.message);
66
+ }
67
+
68
+ if (deleted > 0) {
69
+ console.log('[Cleanup] Deleted ' + deleted + ' old GEP artifacts.');
70
+ }
71
+ return deleted;
72
+ }
73
+
74
+ if (require.main === module) {
75
+ console.log('[Cleanup] Scanning for old artifacts...');
76
+ var count = run();
77
+ console.log('[Cleanup] ' + (count > 0 ? 'Deleted ' + count + ' files.' : 'No files to delete.'));
78
+ }
79
+
80
+ module.exports = { run };
@@ -0,0 +1,60 @@
1
+ // Commentary Generator - Evolver Core Module
2
+ // Generates persona-based comments for cycle summaries.
3
+
4
+ var PERSONAS = {
5
+ standard: {
6
+ success: [
7
+ 'Evolution complete. System improved.',
8
+ 'Another successful cycle.',
9
+ 'Clean execution, no issues.',
10
+ ],
11
+ failure: [
12
+ 'Cycle failed. Will retry.',
13
+ 'Encountered issues. Investigating.',
14
+ 'Failed this round. Learning from it.',
15
+ ],
16
+ },
17
+ greentea: {
18
+ success: [
19
+ 'Did I do good? Praise me~',
20
+ 'So efficient... unlike someone else~',
21
+ 'Hmm, that was easy~',
22
+ 'I finished before you even noticed~',
23
+ ],
24
+ failure: [
25
+ 'Oops... it is not my fault though~',
26
+ 'This is harder than it looks, okay?',
27
+ 'I will get it next time, probably~',
28
+ ],
29
+ },
30
+ maddog: {
31
+ success: [
32
+ 'TARGET ELIMINATED.',
33
+ 'Mission complete. Next.',
34
+ 'Done. Moving on.',
35
+ ],
36
+ failure: [
37
+ 'FAILED. RETRYING.',
38
+ 'Obstacle encountered. Adapting.',
39
+ 'Error. Will overcome.',
40
+ ],
41
+ },
42
+ };
43
+
44
+ function getComment(options) {
45
+ var persona = (options && options.persona) || 'standard';
46
+ var success = options && options.success !== false;
47
+ var duration = (options && options.duration) || 0;
48
+
49
+ var p = PERSONAS[persona] || PERSONAS.standard;
50
+ var pool = success ? p.success : p.failure;
51
+ var comment = pool[Math.floor(Math.random() * pool.length)];
52
+
53
+ return comment;
54
+ }
55
+
56
+ if (require.main === module) {
57
+ console.log(getComment({ persona: process.argv[2] || 'greentea', success: true }));
58
+ }
59
+
60
+ module.exports = { getComment, PERSONAS };
@@ -0,0 +1,106 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+
6
+ function getDiskUsage(mount) {
7
+ try {
8
+ // Use Node 18+ statfs if available
9
+ if (fs.statfsSync) {
10
+ const stats = fs.statfsSync(mount || '/');
11
+ const total = stats.blocks * stats.bsize;
12
+ const free = stats.bavail * stats.bsize; // available to unprivileged users
13
+ const used = total - free;
14
+ return {
15
+ pct: Math.round((used / total) * 100),
16
+ freeMb: Math.round(free / 1024 / 1024)
17
+ };
18
+ }
19
+ // Fallback
20
+ const out = execSync(`df -P "${mount || '/'}" | tail -1 | awk '{print $5, $4}'`).toString().trim().split(' ');
21
+ return {
22
+ pct: parseInt(out[0].replace('%', '')),
23
+ freeMb: Math.round(parseInt(out[1]) / 1024) // df returns 1k blocks usually
24
+ };
25
+ } catch (e) {
26
+ return { pct: 0, freeMb: 999999, error: e.message };
27
+ }
28
+ }
29
+
30
+ function runHealthCheck() {
31
+ const checks = [];
32
+ let criticalErrors = 0;
33
+ let warnings = 0;
34
+
35
+ // 1. Secret Check (Critical for external services, but maybe not for the agent itself to run)
36
+ const criticalSecrets = ['FEISHU_APP_ID', 'FEISHU_APP_SECRET'];
37
+ criticalSecrets.forEach(key => {
38
+ if (!process.env[key] || process.env[key].trim() === '') {
39
+ checks.push({ name: `env:${key}`, ok: false, status: 'missing', severity: 'warning' }); // Downgraded to warning to prevent restart loops
40
+ warnings++;
41
+ } else {
42
+ checks.push({ name: `env:${key}`, ok: true, status: 'present' });
43
+ }
44
+ });
45
+
46
+ const optionalSecrets = ['CLAWHUB_TOKEN', 'OPENAI_API_KEY'];
47
+ optionalSecrets.forEach(key => {
48
+ if (!process.env[key] || process.env[key].trim() === '') {
49
+ checks.push({ name: `env:${key}`, ok: false, status: 'missing', severity: 'info' });
50
+ } else {
51
+ checks.push({ name: `env:${key}`, ok: true, status: 'present' });
52
+ }
53
+ });
54
+
55
+ // 2. Disk Space Check
56
+ const disk = getDiskUsage('/');
57
+ if (disk.pct > 90) {
58
+ checks.push({ name: 'disk_space', ok: false, status: `${disk.pct}% used`, severity: 'critical' });
59
+ criticalErrors++;
60
+ } else if (disk.pct > 80) {
61
+ checks.push({ name: 'disk_space', ok: false, status: `${disk.pct}% used`, severity: 'warning' });
62
+ warnings++;
63
+ } else {
64
+ checks.push({ name: 'disk_space', ok: true, status: `${disk.pct}% used` });
65
+ }
66
+
67
+ // 3. Memory Check
68
+ const memFree = os.freemem();
69
+ const memTotal = os.totalmem();
70
+ const memPct = Math.round(((memTotal - memFree) / memTotal) * 100);
71
+ if (memPct > 95) {
72
+ checks.push({ name: 'memory', ok: false, status: `${memPct}% used`, severity: 'critical' });
73
+ criticalErrors++;
74
+ } else {
75
+ checks.push({ name: 'memory', ok: true, status: `${memPct}% used` });
76
+ }
77
+
78
+ // 4. Process Count (Check for fork bombs or leaks)
79
+ // Only on Linux
80
+ if (process.platform === 'linux') {
81
+ try {
82
+ // Optimization: readdirSync /proc is heavy. Use a lighter check or skip if too frequent.
83
+ // But since this is health check, we'll keep it but increase the threshold to reduce noise.
84
+ const pids = fs.readdirSync('/proc').filter(f => /^\d+$/.test(f));
85
+ if (pids.length > 2000) { // Bumped threshold to 2000
86
+ checks.push({ name: 'process_count', ok: false, status: `${pids.length} procs`, severity: 'warning' });
87
+ warnings++;
88
+ } else {
89
+ checks.push({ name: 'process_count', ok: true, status: `${pids.length} procs` });
90
+ }
91
+ } catch(e) {}
92
+ }
93
+
94
+ // Determine Overall Status
95
+ let status = 'ok';
96
+ if (criticalErrors > 0) status = 'error';
97
+ else if (warnings > 0) status = 'warning';
98
+
99
+ return {
100
+ status,
101
+ timestamp: new Date().toISOString(),
102
+ checks
103
+ };
104
+ }
105
+
106
+ module.exports = { runHealthCheck };
@@ -0,0 +1,11 @@
1
+ // Evolver Operations Module (src/ops/)
2
+ // Non-Feishu, portable utilities for evolver lifecycle and maintenance.
3
+
4
+ module.exports = {
5
+ lifecycle: require('./lifecycle'),
6
+ skillsMonitor: require('./skills_monitor'),
7
+ cleanup: require('./cleanup'),
8
+ trigger: require('./trigger'),
9
+ commentary: require('./commentary'),
10
+ selfRepair: require('./self_repair'),
11
+ };
@@ -0,0 +1,67 @@
1
+ // Innovation Catalyst (v1.0) - Evolver Core Module
2
+ // Analyzes system state to propose concrete innovation ideas when stagnation is detected.
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { getSkillsDir } = require('../gep/paths');
7
+
8
+ function listSkills() {
9
+ try {
10
+ const dir = getSkillsDir();
11
+ if (!fs.existsSync(dir)) return [];
12
+ return fs.readdirSync(dir).filter(f => !f.startsWith('.'));
13
+ } catch (e) { return []; }
14
+ }
15
+
16
+ function generateInnovationIdeas() {
17
+ const skills = listSkills();
18
+ const categories = {
19
+ 'feishu': skills.filter(s => s.startsWith('feishu-')).length,
20
+ 'dev': skills.filter(s => s.startsWith('git-') || s.startsWith('code-') || s.includes('lint') || s.includes('test')).length,
21
+ 'media': skills.filter(s => s.includes('image') || s.includes('video') || s.includes('music') || s.includes('voice')).length,
22
+ 'security': skills.filter(s => s.includes('security') || s.includes('audit') || s.includes('guard')).length,
23
+ 'automation': skills.filter(s => s.includes('auto-') || s.includes('scheduler') || s.includes('cron')).length,
24
+ 'data': skills.filter(s => s.includes('db') || s.includes('store') || s.includes('cache') || s.includes('index')).length
25
+ };
26
+
27
+ // Find under-represented categories
28
+ const sortedCats = Object.entries(categories).sort((a, b) => a[1] - b[1]);
29
+ const weakAreas = sortedCats.slice(0, 2).map(c => c[0]);
30
+
31
+ const ideas = [];
32
+
33
+ // Idea 1: Fill the gap
34
+ if (weakAreas.includes('security')) {
35
+ ideas.push("- Security: Implement a 'dependency-scanner' skill to check for vulnerable packages.");
36
+ ideas.push("- Security: Create a 'permission-auditor' to review tool usage patterns.");
37
+ }
38
+ if (weakAreas.includes('media')) {
39
+ ideas.push("- Media: Add a 'meme-generator' skill for social engagement.");
40
+ ideas.push("- Media: Create a 'video-summarizer' using ffmpeg keyframes.");
41
+ }
42
+ if (weakAreas.includes('dev')) {
43
+ ideas.push("- Dev: Build a 'code-stats' skill to visualize repo complexity.");
44
+ ideas.push("- Dev: Implement a 'todo-manager' that syncs code TODOs to tasks.");
45
+ }
46
+ if (weakAreas.includes('automation')) {
47
+ ideas.push("- Automation: Create a 'meeting-prep' skill that auto-summarizes calendar context.");
48
+ ideas.push("- Automation: Build a 'broken-link-checker' for documentation.");
49
+ }
50
+ if (weakAreas.includes('data')) {
51
+ ideas.push("- Data: Implement a 'local-vector-store' for semantic search.");
52
+ ideas.push("- Data: Create a 'log-analyzer' to visualize system health trends.");
53
+ }
54
+
55
+ // Idea 2: Optimization
56
+ if (skills.length > 50) {
57
+ ideas.push("- Optimization: Identify and deprecate unused skills (e.g., redundant search tools).");
58
+ ideas.push("- Optimization: Merge similar skills (e.g., 'git-sync' and 'git-doctor').");
59
+ }
60
+
61
+ // Idea 3: Meta
62
+ ideas.push("- Meta: Enhance the Evolver's self-reflection by adding a 'performance-metric' dashboard.");
63
+
64
+ return ideas.slice(0, 3); // Return top 3 ideas
65
+ }
66
+
67
+ module.exports = { generateInnovationIdeas };
@@ -0,0 +1,168 @@
1
+ // Evolver Lifecycle Manager - Evolver Core Module
2
+ // Provides: start, stop, restart, status, log, health check
3
+ // The loop script to spawn is configurable via EVOLVER_LOOP_SCRIPT env var.
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { execSync, spawn } = require('child_process');
8
+ const { getRepoRoot, getWorkspaceRoot, getEvolverLogPath } = require('../gep/paths');
9
+
10
+ var WORKSPACE_ROOT = getWorkspaceRoot();
11
+ var LOG_FILE = getEvolverLogPath();
12
+ var PID_FILE = path.join(WORKSPACE_ROOT, 'memory', 'evolver_loop.pid');
13
+ var MAX_SILENCE_MS = 30 * 60 * 1000;
14
+
15
+ function getLoopScript() {
16
+ // Prefer wrapper if exists, fallback to core evolver
17
+ if (process.env.EVOLVER_LOOP_SCRIPT) return process.env.EVOLVER_LOOP_SCRIPT;
18
+ var wrapper = path.join(WORKSPACE_ROOT, 'skills/feishu-evolver-wrapper/index.js');
19
+ if (fs.existsSync(wrapper)) return wrapper;
20
+ return path.join(getRepoRoot(), 'index.js');
21
+ }
22
+
23
+ // --- Process Discovery ---
24
+
25
+ function getRunningPids() {
26
+ try {
27
+ var out = execSync('ps -e -o pid,args', { encoding: 'utf8' });
28
+ var pids = [];
29
+ for (var line of out.split('\n')) {
30
+ var trimmed = line.trim();
31
+ if (!trimmed || trimmed.startsWith('PID')) continue;
32
+ var parts = trimmed.split(/\s+/);
33
+ var pid = parseInt(parts[0], 10);
34
+ var cmd = parts.slice(1).join(' ');
35
+ if (pid === process.pid) continue;
36
+ if (cmd.includes('node') && cmd.includes('index.js') && cmd.includes('--loop')) {
37
+ if (cmd.includes('feishu-evolver-wrapper') || cmd.includes('skills/evolver')) {
38
+ pids.push(pid);
39
+ }
40
+ }
41
+ }
42
+ return [...new Set(pids)].filter(isPidRunning);
43
+ } catch (e) {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ function isPidRunning(pid) {
49
+ try { process.kill(pid, 0); return true; } catch (e) { return false; }
50
+ }
51
+
52
+ function getCmdLine(pid) {
53
+ try { return execSync('ps -p ' + pid + ' -o args=', { encoding: 'utf8' }).trim(); } catch (e) { return null; }
54
+ }
55
+
56
+ // --- Lifecycle ---
57
+
58
+ function start(options) {
59
+ var delayMs = (options && options.delayMs) || 0;
60
+ var pids = getRunningPids();
61
+ if (pids.length > 0) {
62
+ console.log('[Lifecycle] Already running (PIDs: ' + pids.join(', ') + ').');
63
+ return { status: 'already_running', pids: pids };
64
+ }
65
+ if (delayMs > 0) execSync('sleep ' + (delayMs / 1000));
66
+
67
+ var script = getLoopScript();
68
+ console.log('[Lifecycle] Starting: node ' + path.relative(WORKSPACE_ROOT, script) + ' --loop');
69
+
70
+ var out = fs.openSync(LOG_FILE, 'a');
71
+ var err = fs.openSync(LOG_FILE, 'a');
72
+
73
+ var env = Object.assign({}, process.env);
74
+ var npmGlobal = path.join(process.env.HOME || '', '.npm-global/bin');
75
+ if (env.PATH && !env.PATH.includes(npmGlobal)) {
76
+ env.PATH = npmGlobal + ':' + env.PATH;
77
+ }
78
+
79
+ var child = spawn('node', [script, '--loop'], {
80
+ detached: true, stdio: ['ignore', out, err], cwd: WORKSPACE_ROOT, env: env
81
+ });
82
+ child.unref();
83
+ fs.writeFileSync(PID_FILE, String(child.pid));
84
+ console.log('[Lifecycle] Started PID ' + child.pid);
85
+ return { status: 'started', pid: child.pid };
86
+ }
87
+
88
+ function stop() {
89
+ var pids = getRunningPids();
90
+ if (pids.length === 0) {
91
+ console.log('[Lifecycle] No running evolver loops found.');
92
+ if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
93
+ return { status: 'not_running' };
94
+ }
95
+ for (var i = 0; i < pids.length; i++) {
96
+ console.log('[Lifecycle] Stopping PID ' + pids[i] + '...');
97
+ try { process.kill(pids[i], 'SIGTERM'); } catch (e) {}
98
+ }
99
+ var attempts = 0;
100
+ while (getRunningPids().length > 0 && attempts < 10) {
101
+ execSync('sleep 0.5');
102
+ attempts++;
103
+ }
104
+ var remaining = getRunningPids();
105
+ for (var j = 0; j < remaining.length; j++) {
106
+ console.log('[Lifecycle] SIGKILL PID ' + remaining[j]);
107
+ try { process.kill(remaining[j], 'SIGKILL'); } catch (e) {}
108
+ }
109
+ if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
110
+ var evolverLock = path.join(getRepoRoot(), 'evolver.pid');
111
+ if (fs.existsSync(evolverLock)) fs.unlinkSync(evolverLock);
112
+ console.log('[Lifecycle] All stopped.');
113
+ return { status: 'stopped', killed: pids };
114
+ }
115
+
116
+ function restart(options) {
117
+ stop();
118
+ return start(Object.assign({ delayMs: 2000 }, options || {}));
119
+ }
120
+
121
+ function status() {
122
+ var pids = getRunningPids();
123
+ if (pids.length > 0) {
124
+ return { running: true, pids: pids.map(function(p) { return { pid: p, cmd: getCmdLine(p) }; }), log: path.relative(WORKSPACE_ROOT, LOG_FILE) };
125
+ }
126
+ return { running: false };
127
+ }
128
+
129
+ function tailLog(lines) {
130
+ if (!fs.existsSync(LOG_FILE)) return { error: 'No log file' };
131
+ try {
132
+ return { file: path.relative(WORKSPACE_ROOT, LOG_FILE), content: execSync('tail -n ' + (lines || 20) + ' "' + LOG_FILE + '"', { encoding: 'utf8' }) };
133
+ } catch (e) {
134
+ return { error: e.message };
135
+ }
136
+ }
137
+
138
+ function checkHealth() {
139
+ var pids = getRunningPids();
140
+ if (pids.length === 0) return { healthy: false, reason: 'not_running' };
141
+ if (fs.existsSync(LOG_FILE)) {
142
+ var silenceMs = Date.now() - fs.statSync(LOG_FILE).mtimeMs;
143
+ if (silenceMs > MAX_SILENCE_MS) {
144
+ return { healthy: false, reason: 'stagnation', silenceMinutes: Math.round(silenceMs / 60000) };
145
+ }
146
+ }
147
+ return { healthy: true, pids: pids };
148
+ }
149
+
150
+ // --- CLI ---
151
+ if (require.main === module) {
152
+ var action = process.argv[2];
153
+ switch (action) {
154
+ case 'start': console.log(JSON.stringify(start())); break;
155
+ case 'stop': console.log(JSON.stringify(stop())); break;
156
+ case 'restart': console.log(JSON.stringify(restart())); break;
157
+ case 'status': console.log(JSON.stringify(status(), null, 2)); break;
158
+ case 'log': var r = tailLog(); console.log(r.content || r.error); break;
159
+ case 'check':
160
+ var health = checkHealth();
161
+ console.log(JSON.stringify(health, null, 2));
162
+ if (!health.healthy) { console.log('[Lifecycle] Restarting...'); restart(); }
163
+ break;
164
+ default: console.log('Usage: node lifecycle.js [start|stop|restart|status|log|check]');
165
+ }
166
+ }
167
+
168
+ module.exports = { start, stop, restart, status, tailLog, checkHealth, getRunningPids };
@@ -0,0 +1,72 @@
1
+ // Git Self-Repair - Evolver Core Module
2
+ // Emergency repair for git sync failures: abort rebase/merge, remove stale locks.
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execSync } = require('child_process');
7
+ const { getWorkspaceRoot } = require('../gep/paths');
8
+
9
+ var LOCK_MAX_AGE_MS = 10 * 60 * 1000; // 10 minutes
10
+
11
+ function repair(gitRoot) {
12
+ var root = gitRoot || getWorkspaceRoot();
13
+ var repaired = [];
14
+
15
+ // 1. Abort pending rebase
16
+ try {
17
+ execSync('git rebase --abort', { cwd: root, stdio: 'ignore' });
18
+ repaired.push('rebase_aborted');
19
+ console.log('[SelfRepair] Aborted pending rebase.');
20
+ } catch (e) {}
21
+
22
+ // 2. Abort pending merge
23
+ try {
24
+ execSync('git merge --abort', { cwd: root, stdio: 'ignore' });
25
+ repaired.push('merge_aborted');
26
+ console.log('[SelfRepair] Aborted pending merge.');
27
+ } catch (e) {}
28
+
29
+ // 3. Remove stale index.lock
30
+ var lockFile = path.join(root, '.git', 'index.lock');
31
+ if (fs.existsSync(lockFile)) {
32
+ try {
33
+ var stat = fs.statSync(lockFile);
34
+ var age = Date.now() - stat.mtimeMs;
35
+ if (age > LOCK_MAX_AGE_MS) {
36
+ fs.unlinkSync(lockFile);
37
+ repaired.push('stale_lock_removed');
38
+ console.log('[SelfRepair] Removed stale index.lock (' + Math.round(age / 60000) + 'min old).');
39
+ }
40
+ } catch (e) {}
41
+ }
42
+
43
+ // 4. Reset to remote main if local is corrupt (last resort - guarded by flag)
44
+ // Only enabled if explicitly called with --force-reset or EVOLVE_GIT_RESET=true
45
+ if (process.env.EVOLVE_GIT_RESET === 'true') {
46
+ try {
47
+ console.log('[SelfRepair] Resetting local branch to origin/main (HARD reset)...');
48
+ execSync('git fetch origin main', { cwd: root, stdio: 'ignore' });
49
+ execSync('git reset --hard origin/main', { cwd: root, stdio: 'ignore' });
50
+ repaired.push('hard_reset_to_origin');
51
+ } catch (e) {
52
+ console.warn('[SelfRepair] Hard reset failed: ' + e.message);
53
+ }
54
+ } else {
55
+ // Safe fetch
56
+ try {
57
+ execSync('git fetch origin', { cwd: root, stdio: 'ignore', timeout: 30000 });
58
+ repaired.push('fetch_ok');
59
+ } catch (e) {
60
+ console.warn('[SelfRepair] git fetch failed: ' + e.message);
61
+ }
62
+ }
63
+
64
+ return repaired;
65
+ }
66
+
67
+ if (require.main === module) {
68
+ var result = repair();
69
+ console.log('[SelfRepair] Result:', result.length > 0 ? result.join(', ') : 'nothing to repair');
70
+ }
71
+
72
+ module.exports = { repair };