@evomap/evolver 1.89.12 → 1.89.13

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 (93) hide show
  1. package/CONTRIBUTING.md +19 -0
  2. package/README.md +537 -90
  3. package/assets/cover.png +0 -0
  4. package/package.json +18 -6
  5. package/scripts/a2a_export.js +63 -0
  6. package/scripts/a2a_ingest.js +79 -0
  7. package/scripts/a2a_promote.js +118 -0
  8. package/scripts/analyze_by_skill.js +121 -0
  9. package/scripts/build_binaries.js +479 -0
  10. package/scripts/check-changelog.js +166 -0
  11. package/scripts/extract_log.js +85 -0
  12. package/scripts/generate_history.js +75 -0
  13. package/scripts/gep_append_event.js +96 -0
  14. package/scripts/gep_personality_report.js +234 -0
  15. package/scripts/human_report.js +147 -0
  16. package/scripts/recall-verify-report.js +234 -0
  17. package/scripts/recover_loop.js +61 -0
  18. package/scripts/refresh_stars_badge.js +168 -0
  19. package/scripts/seed-merchants.js +91 -0
  20. package/scripts/suggest_version.js +89 -0
  21. package/scripts/validate-modules.js +38 -0
  22. package/scripts/validate-suite.js +78 -0
  23. package/skills/index.json +14 -0
  24. package/src/evolve/guards.js +1 -721
  25. package/src/evolve/pipeline/collect.js +1 -1283
  26. package/src/evolve/pipeline/dispatch.js +1 -421
  27. package/src/evolve/pipeline/enrich.js +1 -440
  28. package/src/evolve/pipeline/hub.js +1 -319
  29. package/src/evolve/pipeline/select.js +1 -274
  30. package/src/evolve/pipeline/signals.js +1 -206
  31. package/src/evolve/utils.js +1 -264
  32. package/src/evolve.js +1 -350
  33. package/src/gep/a2aProtocol.js +1 -4463
  34. package/src/gep/antiAbuseTelemetry.js +1 -233
  35. package/src/gep/autoDistillConv.js +1 -205
  36. package/src/gep/autoDistillLlm.js +1 -315
  37. package/src/gep/candidateEval.js +1 -92
  38. package/src/gep/candidates.js +1 -198
  39. package/src/gep/contentHash.js +1 -30
  40. package/src/gep/conversationDistiller.js +1 -270
  41. package/src/gep/conversationSniffer.js +1 -266
  42. package/src/gep/crypto.js +1 -89
  43. package/src/gep/curriculum.js +1 -163
  44. package/src/gep/deviceId.js +1 -218
  45. package/src/gep/envFingerprint.js +1 -118
  46. package/src/gep/epigenetics.js +1 -31
  47. package/src/gep/execBridge.js +1 -712
  48. package/src/gep/explore.js +1 -289
  49. package/src/gep/hash.js +1 -15
  50. package/src/gep/hubFetch.js +1 -608
  51. package/src/gep/hubReview.js +1 -207
  52. package/src/gep/hubSearch.js +1 -526
  53. package/src/gep/hubVerify.js +1 -306
  54. package/src/gep/learningSignals.js +1 -89
  55. package/src/gep/memoryGraph.js +1 -1449
  56. package/src/gep/memoryGraphAdapter.js +1 -203
  57. package/src/gep/mutation.js +1 -203
  58. package/src/gep/narrativeMemory.js +1 -108
  59. package/src/gep/openPRRegistry.js +1 -205
  60. package/src/gep/personality.js +1 -423
  61. package/src/gep/policyCheck.js +1 -599
  62. package/src/gep/prompt.js +1 -836
  63. package/src/gep/recallInject.js +1 -409
  64. package/src/gep/recallVerifier.js +1 -318
  65. package/src/gep/reflection.js +1 -177
  66. package/src/gep/savingsCore.js +1 -112
  67. package/src/gep/selector.js +1 -602
  68. package/src/gep/skillDistiller.js +1 -1294
  69. package/src/gep/solidify.js +1 -1699
  70. package/src/gep/strategy.js +1 -136
  71. package/src/gep/tokenSavings.js +1 -95
  72. package/src/gep/workspaceKeychain.js +1 -174
  73. package/src/proxy/extensions/traceControl.js +1 -109
  74. package/src/proxy/inject.js +1 -52
  75. package/src/proxy/trace/extractor.js +1 -1403
  76. package/src/proxy/trace/usage.js +1 -105
  77. package/.cursor/BUGBOT.md +0 -182
  78. package/.env.example +0 -68
  79. package/.git-commit-guard-token +0 -1
  80. package/.github/CODEOWNERS +0 -63
  81. package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -23
  82. package/.github/pull_request_template.md +0 -45
  83. package/.github/workflows/test.yml +0 -75
  84. package/CHANGELOG.md +0 -1367
  85. package/README.public.md +0 -578
  86. package/SECURITY.md +0 -108
  87. package/assets/gep/events.jsonl +0 -3
  88. package/examples/atp-consumer-quickstart.md +0 -100
  89. package/examples/hello-world.md +0 -38
  90. package/proxy-package.json +0 -39
  91. package/public.manifest.json +0 -145
  92. /package/assets/gep/{genes.json → genes.seed.json} +0 -0
  93. /package/{bundled-skills → skills}/_meta/SKILL.md +0 -0
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.89.12",
3
+ "version": "1.89.13",
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": {
@@ -28,12 +28,10 @@
28
28
  "run": "node index.js run",
29
29
  "solidify": "node index.js solidify",
30
30
  "review": "node index.js review",
31
- "distill": "node index.js distill",
32
- "webui": "node index.js webui",
33
- "test": "node -e \"const fs=require('fs'),cp=require('child_process');const all=fs.readdirSync('test').filter(f=>f.endsWith('.test.js'));const iso=new Set(['solidifyIntegration.test.js']);const others=all.filter(f=>!iso.has(f)).map(f=>'test/'+f);const isoFiles=all.filter(f=>iso.has(f)).map(f=>'test/'+f);if(others.length)cp.execSync('node --test '+others.join(' '),{stdio:'inherit'});if(isoFiles.length)cp.execSync('node --test '+isoFiles.join(' '),{stdio:'inherit'})\"",
34
31
  "a2a:export": "node scripts/a2a_export.js",
35
32
  "a2a:ingest": "node scripts/a2a_ingest.js",
36
- "a2a:promote": "node scripts/a2a_promote.js"
33
+ "a2a:promote": "node scripts/a2a_promote.js",
34
+ "test": "node -e \"const fs=require('fs'),cp=require('child_process');const all=fs.readdirSync('test').filter(f=>f.endsWith('.test.js'));const iso=new Set(['solidifyIntegration.test.js']);const others=all.filter(f=>!iso.has(f)).map(f=>'test/'+f);const isoFiles=all.filter(f=>iso.has(f)).map(f=>'test/'+f);if(others.length)cp.execSync('node --test '+others.join(' '),{stdio:'inherit'});if(isoFiles.length)cp.execSync('node --test '+isoFiles.join(' '),{stdio:'inherit'})\""
37
35
  },
38
36
  "engines": {
39
37
  "node": ">=22.12"
@@ -50,5 +48,19 @@
50
48
  },
51
49
  "optionalDependencies": {
52
50
  "@napi-rs/keyring": "^1.1.6"
53
- }
51
+ },
52
+ "files": [
53
+ "assets/",
54
+ "index.js",
55
+ "src/",
56
+ "scripts/",
57
+ "skills/",
58
+ "conformance/",
59
+ "README.md",
60
+ "README.zh-CN.md",
61
+ "README.ja-JP.md",
62
+ "SKILL.md",
63
+ "CONTRIBUTING.md",
64
+ "LICENSE"
65
+ ]
54
66
  }
@@ -0,0 +1,63 @@
1
+ const { loadGenes, loadCapsules, readAllEvents } = require('../src/gep/assetStore');
2
+ const { exportEligibleCapsules, exportEligibleGenes, isAllowedA2AAsset } = require('../src/gep/a2a');
3
+ const { buildPublish, buildHello, getTransport } = require('../src/gep/a2aProtocol');
4
+ const { computeAssetId, SCHEMA_VERSION } = require('../src/gep/contentHash');
5
+
6
+ function main() {
7
+ var args = process.argv.slice(2);
8
+ var asJson = args.includes('--json');
9
+ var asProtocol = args.includes('--protocol');
10
+ var withHello = args.includes('--hello');
11
+ var persist = args.includes('--persist');
12
+ var includeEvents = args.includes('--include-events');
13
+
14
+ var capsules = loadCapsules();
15
+ var genes = loadGenes();
16
+ var events = readAllEvents();
17
+
18
+ // Build eligible list: Capsules (filtered) + Genes (filtered) + Events (opt-in)
19
+ var eligibleCapsules = exportEligibleCapsules({ capsules: capsules, events: events });
20
+ var eligibleGenes = exportEligibleGenes({ genes: genes });
21
+ var eligible = eligibleCapsules.concat(eligibleGenes);
22
+
23
+ if (includeEvents) {
24
+ var eligibleEvents = (Array.isArray(events) ? events : []).filter(function (e) {
25
+ return isAllowedA2AAsset(e) && e.type === 'EvolutionEvent';
26
+ });
27
+ for (var ei = 0; ei < eligibleEvents.length; ei++) {
28
+ var ev = eligibleEvents[ei];
29
+ if (!ev.schema_version) ev.schema_version = SCHEMA_VERSION;
30
+ if (!ev.asset_id) { try { ev.asset_id = computeAssetId(ev); } catch (e) {} }
31
+ }
32
+ eligible = eligible.concat(eligibleEvents);
33
+ }
34
+
35
+ if (withHello || asProtocol) {
36
+ var hello = buildHello({ geneCount: genes.length, capsuleCount: capsules.length });
37
+ process.stdout.write(JSON.stringify(hello) + '\n');
38
+ if (persist) { try { getTransport().send(hello); } catch (e) {} }
39
+ }
40
+
41
+ if (asProtocol) {
42
+ for (var i = 0; i < eligible.length; i++) {
43
+ var msg = buildPublish({ asset: eligible[i] });
44
+ process.stdout.write(JSON.stringify(msg) + '\n');
45
+ if (persist) { try { getTransport().send(msg); } catch (e) {} }
46
+ }
47
+ return;
48
+ }
49
+
50
+ if (asJson) {
51
+ process.stdout.write(JSON.stringify(eligible, null, 2) + '\n');
52
+ return;
53
+ }
54
+
55
+ for (var j = 0; j < eligible.length; j++) {
56
+ process.stdout.write(JSON.stringify(eligible[j]) + '\n');
57
+ }
58
+ }
59
+
60
+ try { main(); } catch (e) {
61
+ process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
62
+ process.exit(1);
63
+ }
@@ -0,0 +1,79 @@
1
+ var fs = require('fs');
2
+ var assetStore = require('../src/gep/assetStore');
3
+ var a2a = require('../src/gep/a2a');
4
+ var memGraph = require('../src/gep/memoryGraphAdapter');
5
+ var contentHash = require('../src/gep/contentHash');
6
+ var a2aProto = require('../src/gep/a2aProtocol');
7
+
8
+ function readStdin() {
9
+ try { return fs.readFileSync(0, 'utf8'); } catch (e) { return ''; }
10
+ }
11
+
12
+ function parseSignalsFromEnv() {
13
+ var raw = process.env.A2A_SIGNALS || '';
14
+ if (!raw) return [];
15
+ try {
16
+ var maybe = JSON.parse(raw);
17
+ if (Array.isArray(maybe)) return maybe.map(String).filter(Boolean);
18
+ } catch (e) {}
19
+ return String(raw).split(',').map(function (s) { return s.trim(); }).filter(Boolean);
20
+ }
21
+
22
+ function main() {
23
+ var args = process.argv.slice(2);
24
+ var inputPath = '';
25
+ for (var i = 0; i < args.length; i++) {
26
+ if (args[i] && !args[i].startsWith('--')) { inputPath = args[i]; break; }
27
+ }
28
+ var source = process.env.A2A_SOURCE || 'external';
29
+ var factor = Number.isFinite(Number(process.env.A2A_EXTERNAL_CONFIDENCE_FACTOR))
30
+ ? Number(process.env.A2A_EXTERNAL_CONFIDENCE_FACTOR) : 0.6;
31
+
32
+ var text = inputPath ? a2a.readTextIfExists(inputPath) : readStdin();
33
+ var parsed = a2a.parseA2AInput(text);
34
+ var signals = parseSignalsFromEnv();
35
+
36
+ var accepted = 0;
37
+ var rejected = 0;
38
+ var emitDecisions = process.env.A2A_EMIT_DECISIONS === 'true';
39
+
40
+ for (var j = 0; j < parsed.length; j++) {
41
+ var obj = parsed[j];
42
+ if (!a2a.isAllowedA2AAsset(obj)) continue;
43
+
44
+ if (obj.asset_id && typeof obj.asset_id === 'string') {
45
+ if (!contentHash.verifyAssetId(obj)) {
46
+ rejected += 1;
47
+ if (emitDecisions) {
48
+ try {
49
+ var dm = a2aProto.buildDecision({ assetId: obj.asset_id, localId: obj.id, decision: 'reject', reason: 'asset_id integrity check failed' });
50
+ a2aProto.getTransport().send(dm);
51
+ } catch (e) {}
52
+ }
53
+ continue;
54
+ }
55
+ }
56
+
57
+ var staged = a2a.lowerConfidence(obj, { source: source, factor: factor });
58
+ if (!staged) continue;
59
+
60
+ assetStore.appendExternalCandidateJsonl(staged);
61
+ try { memGraph.recordExternalCandidate({ asset: staged, source: source, signals: signals }); } catch (e) {}
62
+
63
+ if (emitDecisions) {
64
+ try {
65
+ var dm2 = a2aProto.buildDecision({ assetId: staged.asset_id, localId: staged.id, decision: 'quarantine', reason: 'staged as external candidate' });
66
+ a2aProto.getTransport().send(dm2);
67
+ } catch (e) {}
68
+ }
69
+
70
+ accepted += 1;
71
+ }
72
+
73
+ process.stdout.write('accepted=' + accepted + ' rejected=' + rejected + '\n');
74
+ }
75
+
76
+ try { main(); } catch (e) {
77
+ process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
78
+ process.exit(1);
79
+ }
@@ -0,0 +1,118 @@
1
+ var assetStore = require('../src/gep/assetStore');
2
+ var solidifyMod = require('../src/gep/solidify');
3
+ var contentHash = require('../src/gep/contentHash');
4
+ var a2aProto = require('../src/gep/a2aProtocol');
5
+
6
+ function parseArgs(argv) {
7
+ var out = { flags: new Set(), kv: new Map(), positionals: [] };
8
+ for (var i = 0; i < argv.length; i++) {
9
+ var a = argv[i];
10
+ if (!a) continue;
11
+ if (a.startsWith('--')) {
12
+ var eq = a.indexOf('=');
13
+ if (eq > -1) { out.kv.set(a.slice(2, eq), a.slice(eq + 1)); }
14
+ else {
15
+ var key = a.slice(2);
16
+ var next = argv[i + 1];
17
+ if (next && !String(next).startsWith('--')) { out.kv.set(key, next); i++; }
18
+ else { out.flags.add(key); }
19
+ }
20
+ } else { out.positionals.push(a); }
21
+ }
22
+ return out;
23
+ }
24
+
25
+ function main() {
26
+ var args = parseArgs(process.argv.slice(2));
27
+ var id = String(args.kv.get('id') || '').trim();
28
+ var typeRaw = String(args.kv.get('type') || '').trim().toLowerCase();
29
+ var validated = args.flags.has('validated') || String(args.kv.get('validated') || '') === 'true';
30
+ var limit = Number.isFinite(Number(args.kv.get('limit'))) ? Number(args.kv.get('limit')) : 500;
31
+
32
+ if (!id || !typeRaw) throw new Error('Usage: node scripts/a2a_promote.js --type capsule|gene|event --id <id> --validated');
33
+ if (!validated) throw new Error('Refusing to promote without --validated (local verification must be done first).');
34
+
35
+ var type = typeRaw === 'capsule' ? 'Capsule' : typeRaw === 'gene' ? 'Gene' : typeRaw === 'event' ? 'EvolutionEvent' : '';
36
+ if (!type) throw new Error('Invalid --type. Use capsule, gene, or event.');
37
+
38
+ var external = assetStore.readRecentExternalCandidates(limit);
39
+ var candidate = null;
40
+ for (var i = 0; i < external.length; i++) {
41
+ if (external[i] && external[i].type === type && String(external[i].id) === id) { candidate = external[i]; break; }
42
+ }
43
+ if (!candidate) throw new Error('Candidate not found in external zone: type=' + type + ' id=' + id);
44
+
45
+ if (type === 'Gene') {
46
+ var validation = Array.isArray(candidate.validation) ? candidate.validation : [];
47
+ for (var j = 0; j < validation.length; j++) {
48
+ var c = String(validation[j] || '').trim();
49
+ if (!c) continue;
50
+ if (!solidifyMod.isValidationCommandAllowed(c)) {
51
+ throw new Error('Refusing to promote Gene ' + id + ': validation command rejected by safety check: "' + c + '". Only node/npm/npx commands without shell operators are allowed.');
52
+ }
53
+ }
54
+ }
55
+
56
+ var promoted = JSON.parse(JSON.stringify(candidate));
57
+ if (!promoted.a2a || typeof promoted.a2a !== 'object') promoted.a2a = {};
58
+ promoted.a2a.status = 'promoted';
59
+ promoted.a2a.promoted_at = new Date().toISOString();
60
+ if (!promoted.schema_version) promoted.schema_version = contentHash.SCHEMA_VERSION;
61
+ promoted.asset_id = contentHash.computeAssetId(promoted);
62
+
63
+ var emitDecisions = process.env.A2A_EMIT_DECISIONS === 'true';
64
+
65
+ if (type === 'EvolutionEvent') {
66
+ assetStore.appendEventJsonl(promoted);
67
+ if (emitDecisions) {
68
+ try {
69
+ var dmEv = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'accept', reason: 'event promoted for provenance tracking' });
70
+ a2aProto.getTransport().send(dmEv);
71
+ } catch (e) {}
72
+ }
73
+ process.stdout.write('promoted_event=' + id + '\n');
74
+ return;
75
+ }
76
+
77
+ if (type === 'Capsule') {
78
+ assetStore.appendCapsule(promoted);
79
+ if (emitDecisions) {
80
+ try {
81
+ var dm = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'accept', reason: 'capsule promoted after validation' });
82
+ a2aProto.getTransport().send(dm);
83
+ } catch (e) {}
84
+ }
85
+ process.stdout.write('promoted_capsule=' + id + '\n');
86
+ return;
87
+ }
88
+
89
+ var localGenes = assetStore.loadGenes();
90
+ var exists = false;
91
+ for (var k = 0; k < localGenes.length; k++) {
92
+ if (localGenes[k] && localGenes[k].type === 'Gene' && String(localGenes[k].id) === id) { exists = true; break; }
93
+ }
94
+ if (exists) {
95
+ if (emitDecisions) {
96
+ try {
97
+ var dm2 = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'reject', reason: 'local gene with same ID already exists' });
98
+ a2aProto.getTransport().send(dm2);
99
+ } catch (e) {}
100
+ }
101
+ process.stdout.write('conflict_keep_local_gene=' + id + '\n');
102
+ return;
103
+ }
104
+
105
+ assetStore.upsertGene(promoted);
106
+ if (emitDecisions) {
107
+ try {
108
+ var dm3 = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'accept', reason: 'gene promoted after safety audit' });
109
+ a2aProto.getTransport().send(dm3);
110
+ } catch (e) {}
111
+ }
112
+ process.stdout.write('promoted_gene=' + id + '\n');
113
+ }
114
+
115
+ try { main(); } catch (e) {
116
+ process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
117
+ process.exit(1);
118
+ }
@@ -0,0 +1,121 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const REPO_ROOT = path.resolve(__dirname, '..');
5
+ const LOG_FILE = path.join(REPO_ROOT, 'evolution_history_full.md');
6
+ const OUT_FILE = path.join(REPO_ROOT, 'evolution_detailed_report.md');
7
+
8
+ function analyzeEvolution() {
9
+ if (!fs.existsSync(LOG_FILE)) {
10
+ console.error("Source file missing.");
11
+ return;
12
+ }
13
+
14
+ const content = fs.readFileSync(LOG_FILE, 'utf8');
15
+ // Split by divider
16
+ const entries = content.split('---').map(e => e.trim()).filter(e => e.length > 0);
17
+
18
+ const skillUpdates = {}; // Map<SkillName, Array<Changes>>
19
+ const generalUpdates = []; // Array<Changes>
20
+
21
+ // Regex to detect skills/paths
22
+ // e.g. `skills/feishu-card/send.js` or **Target**: `skills/git-sync`
23
+ const skillRegex = /skills\/([a-zA-Z0-9\-_]+)/;
24
+ const actionRegex = /Action:\s*([\s\S]*?)(?=\n\n|\n[A-Z]|$)/i; // Capture Action text
25
+ const statusRegex = /Status:\s*\[?([A-Z\s_]+)\]?/i;
26
+
27
+ entries.forEach(entry => {
28
+ // Extract basic info
29
+ const statusMatch = entry.match(statusRegex);
30
+ const status = statusMatch ? statusMatch[1].trim().toUpperCase() : 'UNKNOWN';
31
+
32
+ // Skip routine checks if we want a *detailed evolution* report (focus on changes)
33
+ // But user asked for "what happened", so routine scans might be boring unless they found something.
34
+ // Let's filter out "STABILITY" or "RUNNING" unless there is a clear "Mutated" or "Fixed" keyword.
35
+ const isInteresting =
36
+ entry.includes('Fixed') ||
37
+ entry.includes('Hardened') ||
38
+ entry.includes('Optimized') ||
39
+ entry.includes('Patched') ||
40
+ entry.includes('Created') ||
41
+ entry.includes('Added') ||
42
+ status === 'SUCCESS' ||
43
+ status === 'COMPLETED';
44
+
45
+ if (!isInteresting) return;
46
+
47
+ // Find associated skill
48
+ const skillMatch = entry.match(skillRegex);
49
+ let skillName = 'General / System';
50
+ if (skillMatch) {
51
+ skillName = skillMatch[1];
52
+ } else {
53
+ // Try heuristics
54
+ if (entry.toLowerCase().includes('feishu card')) skillName = 'feishu-card';
55
+ else if (entry.toLowerCase().includes('git sync')) skillName = 'git-sync';
56
+ else if (entry.toLowerCase().includes('logger')) skillName = 'interaction-logger';
57
+ else if (entry.toLowerCase().includes('evolve')) skillName = 'capability-evolver';
58
+ }
59
+
60
+ // Extract description
61
+ let description = "";
62
+ const actionMatch = entry.match(actionRegex);
63
+ if (actionMatch) {
64
+ description = actionMatch[1].trim();
65
+ } else {
66
+ // Fallback: take lines that look like bullet points or text after header
67
+ const lines = entry.split('\n');
68
+ description = lines.filter(l => l.match(/^[•\-\*]|\w/)).slice(1).join('\n').trim();
69
+ }
70
+
71
+ // Clean up description (remove duplicate "Action:" prefix if captured)
72
+ description = description.replace(/^Action:\s*/i, '');
73
+
74
+ if (!skillUpdates[skillName]) skillUpdates[skillName] = [];
75
+
76
+ // Dedup descriptions slightly (simple check)
77
+ const isDuplicate = skillUpdates[skillName].some(u => u.desc.includes(description.substring(0, 20)));
78
+ if (!isDuplicate) {
79
+ // Extract Date if possible
80
+ const dateMatch = entry.match(/\((\d{4}\/\d{1,2}\/\d{1,2}.*?)\)/);
81
+ const date = dateMatch ? dateMatch[1] : 'Unknown';
82
+
83
+ skillUpdates[skillName].push({
84
+ date,
85
+ status,
86
+ desc: description
87
+ });
88
+ }
89
+ });
90
+
91
+ // Generate Markdown
92
+ let md = "# Detailed Evolution Report (By Skill)\n\n> Comprehensive breakdown of system changes.\n\n";
93
+
94
+ // Sort skills alphabetically
95
+ const sortedSkills = Object.keys(skillUpdates).sort();
96
+
97
+ sortedSkills.forEach(skill => {
98
+ md += `## ${skill}\n`;
99
+ const updates = skillUpdates[skill];
100
+
101
+ updates.forEach(u => {
102
+ // Icon based on content
103
+ let icon = '*';
104
+ const lowerDesc = u.desc.toLowerCase();
105
+ if (lowerDesc.includes('optimiz')) icon = '[optimize]';
106
+ if (lowerDesc.includes('secur') || lowerDesc.includes('harden') || lowerDesc.includes('permission')) icon = '[security]';
107
+ if (lowerDesc.includes('fix') || lowerDesc.includes('patch')) icon = '[repair]';
108
+ if (lowerDesc.includes('creat') || lowerDesc.includes('add')) icon = '[add]';
109
+
110
+ md += `### ${icon} ${u.date}\n`;
111
+ md += `${u.desc}\n\n`;
112
+ });
113
+ md += `---\n`;
114
+ });
115
+
116
+ fs.writeFileSync(OUT_FILE, md);
117
+ console.log(`Generated report for ${sortedSkills.length} skills.`);
118
+ }
119
+
120
+ analyzeEvolution();
121
+