superlocalmemory 3.4.10 → 3.4.12

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 (47) hide show
  1. package/README.md +17 -11
  2. package/docs/skill-evolution.md +77 -10
  3. package/ide/hooks/tool-event-hook.sh +4 -4
  4. package/package.json +1 -1
  5. package/pyproject.toml +3 -2
  6. package/src/superlocalmemory/cli/commands.py +170 -0
  7. package/src/superlocalmemory/cli/main.py +21 -0
  8. package/src/superlocalmemory/cli/setup_wizard.py +54 -11
  9. package/src/superlocalmemory/core/config.py +35 -0
  10. package/src/superlocalmemory/core/consolidation_engine.py +128 -0
  11. package/src/superlocalmemory/core/embedding_worker.py +1 -1
  12. package/src/superlocalmemory/core/engine.py +12 -0
  13. package/src/superlocalmemory/core/fact_consolidator.py +425 -0
  14. package/src/superlocalmemory/core/graph_pruner.py +290 -0
  15. package/src/superlocalmemory/core/maintenance_scheduler.py +20 -0
  16. package/src/superlocalmemory/core/recall_pipeline.py +9 -0
  17. package/src/superlocalmemory/core/tier_manager.py +325 -0
  18. package/src/superlocalmemory/encoding/entity_resolver.py +6 -5
  19. package/src/superlocalmemory/evolution/__init__.py +29 -0
  20. package/src/superlocalmemory/evolution/blind_verifier.py +115 -0
  21. package/src/superlocalmemory/evolution/evolution_store.py +302 -0
  22. package/src/superlocalmemory/evolution/mutation_generator.py +181 -0
  23. package/src/superlocalmemory/evolution/skill_evolver.py +555 -0
  24. package/src/superlocalmemory/evolution/triggers.py +367 -0
  25. package/src/superlocalmemory/evolution/types.py +92 -0
  26. package/src/superlocalmemory/hooks/hook_handlers.py +13 -0
  27. package/src/superlocalmemory/learning/skill_performance_miner.py +44 -11
  28. package/src/superlocalmemory/mcp/server.py +4 -0
  29. package/src/superlocalmemory/mcp/tools_evolution.py +338 -0
  30. package/src/superlocalmemory/retrieval/engine.py +98 -11
  31. package/src/superlocalmemory/retrieval/entity_channel.py +118 -0
  32. package/src/superlocalmemory/retrieval/forgetting_filter.py +22 -7
  33. package/src/superlocalmemory/retrieval/strategy.py +2 -2
  34. package/src/superlocalmemory/server/routes/behavioral.py +19 -15
  35. package/src/superlocalmemory/server/routes/evolution.py +213 -0
  36. package/src/superlocalmemory/server/routes/tiers.py +195 -0
  37. package/src/superlocalmemory/server/unified_daemon.py +39 -5
  38. package/src/superlocalmemory/storage/schema_v3411.py +149 -0
  39. package/src/superlocalmemory/ui/index.html +5 -2
  40. package/src/superlocalmemory/ui/js/lifecycle.js +83 -0
  41. package/src/superlocalmemory/ui/js/ng-skills.js +394 -10
  42. package/src/superlocalmemory.egg-info/PKG-INFO +614 -0
  43. package/src/superlocalmemory.egg-info/SOURCES.txt +335 -0
  44. package/src/superlocalmemory.egg-info/dependency_links.txt +1 -0
  45. package/src/superlocalmemory.egg-info/entry_points.txt +2 -0
  46. package/src/superlocalmemory.egg-info/requires.txt +55 -0
  47. package/src/superlocalmemory.egg-info/top_level.txt +1 -0
@@ -8,8 +8,101 @@
8
8
  'use strict';
9
9
 
10
10
  window.loadSkillEvolution = function() {
11
+ fetchEvolutionEngine();
11
12
  fetchSkillOverview();
12
13
  fetchSkillPerformance();
14
+ fetchSkillLineage();
15
+ };
16
+
17
+ function fetchEvolutionEngine() {
18
+ var container = document.getElementById('skills-overview-cards');
19
+ if (!container) return;
20
+
21
+ fetch('/api/evolution/status')
22
+ .then(function(r) { return r.json(); })
23
+ .then(function(data) {
24
+ var enabled = data.enabled || false;
25
+ var backend = data.backend || 'none';
26
+ var stats = data.stats || {};
27
+
28
+ var statusColor = enabled ? '#10b981' : '#888';
29
+ var statusText = enabled ? 'Enabled (' + backend + ')' : 'Disabled';
30
+ var statusIcon = enabled ? 'bi-lightning-charge-fill' : 'bi-lightning-charge';
31
+
32
+ var html = '<div class="card" style="padding:16px;margin-bottom:16px;border-left:3px solid ' + statusColor + '">' +
33
+ '<div style="display:flex;justify-content:space-between;align-items:center">' +
34
+ '<div>' +
35
+ '<div style="font-weight:600;font-size:1rem;margin-bottom:4px">' +
36
+ '<i class="bi ' + statusIcon + '" style="color:' + statusColor + ';margin-right:6px"></i>' +
37
+ 'Evolution Engine: ' + statusText +
38
+ '</div>' +
39
+ '<div style="font-size:0.8125rem;color:var(--bs-body-color)">' +
40
+ (enabled
41
+ ? 'Backend: <strong>' + escapeHtml(backend) + '</strong> | ' +
42
+ 'Evolved: ' + (stats.promoted || 0) + ' | ' +
43
+ 'Rejected: ' + (stats.rejected || 0) + ' | ' +
44
+ 'Budget: ' + (stats.cycle_budget_remaining || 3) + ' remaining this cycle'
45
+ : 'Enable via CLI: <code>slm config set evolution.enabled true</code> or via <code>slm setup</code>') +
46
+ '</div>' +
47
+ '</div>' +
48
+ '<div style="display:flex;gap:8px">' +
49
+ (enabled
50
+ ? '<button class="btn btn-sm btn-outline-success" onclick="triggerEvolution()"><i class="bi bi-play-fill"></i> Run Now</button>'
51
+ : '<button class="btn btn-sm btn-outline-primary" onclick="enableEvolution()"><i class="bi bi-power"></i> Enable</button>') +
52
+ '</div>' +
53
+ '</div>';
54
+
55
+ // Evolution history (if any)
56
+ if (data.recent && data.recent.length > 0) {
57
+ html += '<div style="margin-top:12px;border-top:1px solid var(--bs-border-color);padding-top:12px">' +
58
+ '<div style="font-size:0.8125rem;font-weight:600;margin-bottom:8px">Recent Evolution</div>';
59
+ data.recent.forEach(function(r) {
60
+ var sColor = r.status === 'promoted' ? '#10b981' : r.status === 'rejected' ? '#ef4444' : '#f59e0b';
61
+ html += '<div style="display:flex;justify-content:space-between;font-size:0.75rem;padding:4px 0;border-bottom:1px solid var(--bs-border-color)">' +
62
+ '<span><i class="bi bi-lightning-charge" style="color:#8b5cf6"></i> ' + escapeHtml(r.skill_name) + '</span>' +
63
+ '<span style="color:' + sColor + '">' + escapeHtml(r.status) + ' (' + escapeHtml(r.evolution_type) + ')</span>' +
64
+ '</div>';
65
+ });
66
+ html += '</div>';
67
+ }
68
+
69
+ html += '</div>';
70
+
71
+ // Insert before the overview cards
72
+ var overviewInner = document.getElementById('skills-overview-inner');
73
+ if (overviewInner) {
74
+ overviewInner.insertAdjacentHTML('beforebegin', html);
75
+ } else {
76
+ container.insertAdjacentHTML('afterbegin', html);
77
+ }
78
+ })
79
+ .catch(function() {
80
+ // API not available yet — skip silently
81
+ });
82
+ }
83
+
84
+ window.enableEvolution = function() {
85
+ fetch('/api/evolution/enable', { method: 'POST' })
86
+ .then(function(r) { return r.json(); })
87
+ .then(function(data) {
88
+ if (data.ok) {
89
+ loadSkillEvolution();
90
+ } else {
91
+ alert('Could not enable: ' + (data.error || 'unknown'));
92
+ }
93
+ })
94
+ .catch(function(err) { alert('Error: ' + err.message); });
95
+ };
96
+
97
+ window.triggerEvolution = function() {
98
+ fetch('/api/evolution/run', { method: 'POST' })
99
+ .then(function(r) { return r.json(); })
100
+ .then(function(data) {
101
+ alert('Evolution cycle: ' + (data.evolved || 0) + ' evolved, ' +
102
+ (data.rejected || 0) + ' rejected, ' + (data.candidates || 0) + ' candidates');
103
+ loadSkillEvolution();
104
+ })
105
+ .catch(function(err) { alert('Error: ' + err.message); });
13
106
  };
14
107
 
15
108
  function fetchSkillOverview() {
@@ -19,7 +112,7 @@
19
112
  // Compatibility notice + ECC credit + docs links
20
113
  var noticeHtml =
21
114
  '<div class="card" style="padding:12px 16px;margin-bottom:16px;border-left:3px solid #8b5cf6">' +
22
- '<div style="font-size:0.8125rem;color:#555">' +
115
+ '<div style="font-size:0.8125rem;color:var(--bs-body-color)">' +
23
116
  '<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
24
117
  '<strong>Skill Evolution</strong> currently tracks <strong>Claude Code</strong> skills. ' +
25
118
  'The <code>/api/v3/tool-event</code> endpoint accepts events from any IDE client. ' +
@@ -27,7 +120,7 @@
27
120
  '<a href="https://github.com/affaan-m/everything-claude-code" target="_blank" style="color:#8b5cf6">Everything Claude Code (ECC)</a> ' +
28
121
  'via <code>slm ingest --source ecc</code>.' +
29
122
  '</div>' +
30
- '<div style="font-size:0.75rem;color:#888;margin-top:8px">' +
123
+ '<div style="font-size:0.75rem;color:var(--bs-secondary-color);margin-top:8px">' +
31
124
  '<a href="https://superlocalmemory.com/skill-evolution" target="_blank" style="color:#8b5cf6;margin-right:12px"><i class="bi bi-globe"></i> Learn more</a>' +
32
125
  '<a href="https://github.com/qualixar/superlocalmemory/blob/main/docs/skill-evolution.md" target="_blank" style="color:#8b5cf6"><i class="bi bi-book"></i> Documentation</a>' +
33
126
  '</div>' +
@@ -84,9 +177,9 @@
84
177
  html += '<h5 style="margin-bottom:16px"><i class="bi bi-lightning-charge" style="color:#8b5cf6"></i> Skill Performance</h5>';
85
178
 
86
179
  if (perfAssertions.length === 0 && events.length === 0) {
87
- html += '<div class="card" style="padding:24px;text-align:center;color:#888">' +
180
+ html += '<div class="card" style="padding:24px;text-align:center;color:var(--bs-secondary-color)">' +
88
181
  '<i class="bi bi-lightning-charge" style="font-size:2.5rem;display:block;margin-bottom:12px;opacity:0.3"></i>' +
89
- '<div style="font-size:1rem;margin-bottom:4px;color:#444">No skill performance data yet</div>' +
182
+ '<div style="font-size:1rem;margin-bottom:4px;color:var(--bs-body-color)">No skill performance data yet</div>' +
90
183
  '<div style="font-size:0.8125rem">' +
91
184
  'Skill tracking starts automatically after the enriched hook captures data.<br>' +
92
185
  'Use skills in your sessions — performance assertions will appear after consolidation.' +
@@ -101,7 +194,7 @@
101
194
  } else {
102
195
  // We have events but no assertions yet (need consolidation)
103
196
  html += '<div class="card" style="padding:16px;margin-bottom:16px">' +
104
- '<div style="font-size:0.875rem;color:#555">' +
197
+ '<div style="font-size:0.875rem;color:var(--bs-body-color)">' +
105
198
  '<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
106
199
  events.length + ' skill events collected. Run consolidation to generate performance assertions.' +
107
200
  '</div>' +
@@ -140,10 +233,10 @@
140
233
  '<div style="font-size:0.875rem">' +
141
234
  '<strong>' + escapeHtml(a.trigger_condition || '') + '</strong>' +
142
235
  '</div>' +
143
- '<div style="font-size:0.8125rem;color:#555;margin-top:4px">' +
236
+ '<div style="font-size:0.8125rem;color:var(--bs-body-color);margin-top:4px">' +
144
237
  escapeHtml(a.action || '') +
145
238
  '</div>' +
146
- '<div style="font-size:0.75rem;color:#888;margin-top:4px">' +
239
+ '<div style="font-size:0.75rem;color:var(--bs-secondary-color);margin-top:4px">' +
147
240
  'Confidence: ' + ((a.confidence || 0) * 100).toFixed(0) + '%' +
148
241
  '</div>' +
149
242
  '</div></div>';
@@ -177,10 +270,10 @@
177
270
  confPct + '%' +
178
271
  '</span>' +
179
272
  '</div>' +
180
- '<div style="font-size:0.8125rem;color:#555;margin-bottom:8px">' +
273
+ '<div style="font-size:0.8125rem;color:var(--bs-body-color);margin-bottom:8px">' +
181
274
  escapeHtml(assertion.action || 'No performance data yet') +
182
275
  '</div>' +
183
- '<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color:#888">' +
276
+ '<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color:var(--bs-secondary-color)">' +
184
277
  '<span>Evidence: ' + (assertion.evidence_count || 0) + ' invocations</span>' +
185
278
  '<span>Reinforced: ' + (assertion.reinforcement_count || 0) + 'x</span>' +
186
279
  '</div>' +
@@ -215,7 +308,7 @@
215
308
  return '<div class="col-md-3 col-6"><div class="card" style="padding:12px;text-align:center">' +
216
309
  '<i class="bi ' + icon + '" style="color:' + color + ';font-size:1.125rem;display:block;margin-bottom:4px"></i>' +
217
310
  '<div style="font-size:1.25rem;font-weight:600">' + value + '</div>' +
218
- '<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color:#888">' + label + '</div>' +
311
+ '<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--bs-secondary-color)">' + label + '</div>' +
219
312
  '</div></div>';
220
313
  }
221
314
 
@@ -224,4 +317,295 @@
224
317
  d.textContent = s || '';
225
318
  return d.innerHTML;
226
319
  }
320
+
321
+ // ── Skill Lineage (Version DAG) ────────────────────────────
322
+
323
+ var LINEAGE_COLORS = {
324
+ promoted: '#22c55e',
325
+ rejected: '#ef4444',
326
+ pending: '#eab308',
327
+ original: '#6b7280'
328
+ };
329
+
330
+ function fetchSkillLineage() {
331
+ var container = document.getElementById('skill-lineage-container');
332
+ if (!container) return;
333
+
334
+ fetch('/api/evolution/lineage')
335
+ .then(function(r) { return r.json(); })
336
+ .then(function(data) {
337
+ var lineage = data.lineage || [];
338
+ if (lineage.length === 0) {
339
+ container.innerHTML =
340
+ '<div class="card" style="padding:24px;text-align:center;color:var(--bs-secondary-color)">' +
341
+ '<i class="bi bi-diagram-3" style="font-size:2.5rem;display:block;margin-bottom:12px;opacity:0.3"></i>' +
342
+ '<div style="font-size:1rem;margin-bottom:4px;color:var(--bs-body-color)">No skill lineage data yet</div>' +
343
+ '<div style="font-size:0.8125rem">' +
344
+ 'Lineage appears after skills evolve. Run an evolution cycle to generate skill versions.' +
345
+ '</div>' +
346
+ '</div>';
347
+ return;
348
+ }
349
+ var html = '<h5 style="margin-bottom:16px"><i class="bi bi-diagram-3" style="color:#8b5cf6"></i> Skill Lineage</h5>';
350
+ html += '<div class="card" style="padding:16px;margin-bottom:16px">';
351
+ html += '<div id="lineage-dag-wrapper" style="max-height:400px;overflow:auto;position:relative"></div>';
352
+ html += '</div>';
353
+ html += '<div id="lineage-table-wrapper"></div>';
354
+ container.innerHTML = html;
355
+ renderLineageDAG(lineage);
356
+ renderLineageTable(lineage);
357
+ })
358
+ .catch(function() {
359
+ container.innerHTML = '';
360
+ });
361
+ }
362
+
363
+ function renderLineageDAG(lineage) {
364
+ var wrapper = document.getElementById('lineage-dag-wrapper');
365
+ if (!wrapper) return;
366
+
367
+ // Build adjacency: id -> node, parent_skill_id -> children
368
+ var nodeMap = {};
369
+ var childrenMap = {};
370
+ var roots = [];
371
+
372
+ lineage.forEach(function(item) {
373
+ nodeMap[item.id] = item;
374
+ if (!childrenMap[item.id]) childrenMap[item.id] = [];
375
+ });
376
+
377
+ lineage.forEach(function(item) {
378
+ var pid = item.parent_skill_id;
379
+ if (pid && nodeMap[pid]) {
380
+ if (!childrenMap[pid]) childrenMap[pid] = [];
381
+ childrenMap[pid].push(item.id);
382
+ } else {
383
+ roots.push(item.id);
384
+ }
385
+ });
386
+
387
+ // If no explicit roots found, treat all nodes as roots
388
+ if (roots.length === 0) {
389
+ lineage.forEach(function(item) { roots.push(item.id); });
390
+ }
391
+
392
+ // Assign layers via BFS (Sugiyama layer assignment)
393
+ var layers = {};
394
+ var queue = [];
395
+ var visited = {};
396
+ roots.forEach(function(rid) {
397
+ layers[rid] = 0;
398
+ queue.push(rid);
399
+ visited[rid] = true;
400
+ });
401
+ var maxLayer = 0;
402
+ while (queue.length > 0) {
403
+ var nid = queue.shift();
404
+ var children = childrenMap[nid] || [];
405
+ children.forEach(function(cid) {
406
+ if (!visited[cid]) {
407
+ visited[cid] = true;
408
+ layers[cid] = (layers[nid] || 0) + 1;
409
+ if (layers[cid] > maxLayer) maxLayer = layers[cid];
410
+ queue.push(cid);
411
+ }
412
+ });
413
+ }
414
+
415
+ // Handle nodes not reached by BFS (disconnected)
416
+ lineage.forEach(function(item) {
417
+ if (layers[item.id] === undefined) {
418
+ layers[item.id] = 0;
419
+ }
420
+ });
421
+
422
+ // Group nodes by layer
423
+ var layerGroups = {};
424
+ Object.keys(layers).forEach(function(nid) {
425
+ var l = layers[nid];
426
+ if (!layerGroups[l]) layerGroups[l] = [];
427
+ layerGroups[l].push(nid);
428
+ });
429
+
430
+ // Layout constants
431
+ var nodeW = 140;
432
+ var nodeH = 44;
433
+ var layerGap = 80;
434
+ var nodeGap = 24;
435
+ var padX = 20;
436
+ var padY = 20;
437
+
438
+ // Compute max width needed
439
+ var maxNodesInLayer = 0;
440
+ for (var l = 0; l <= maxLayer; l++) {
441
+ var count = (layerGroups[l] || []).length;
442
+ if (count > maxNodesInLayer) maxNodesInLayer = count;
443
+ }
444
+
445
+ var svgW = Math.max(300, padX * 2 + maxNodesInLayer * (nodeW + nodeGap) - nodeGap);
446
+ var svgH = padY * 2 + (maxLayer + 1) * (nodeH + layerGap) - layerGap;
447
+
448
+ // Compute positions
449
+ var positions = {};
450
+ for (var ly = 0; ly <= maxLayer; ly++) {
451
+ var group = layerGroups[ly] || [];
452
+ var totalW = group.length * nodeW + (group.length - 1) * nodeGap;
453
+ var startX = (svgW - totalW) / 2;
454
+ var yPos = padY + ly * (nodeH + layerGap);
455
+ group.forEach(function(nid, idx) {
456
+ positions[nid] = {
457
+ x: startX + idx * (nodeW + nodeGap),
458
+ y: yPos
459
+ };
460
+ });
461
+ }
462
+
463
+ // Build SVG
464
+ var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + svgW + '" height="' + svgH + '" ' +
465
+ 'style="display:block;margin:0 auto;font-family:system-ui,-apple-system,sans-serif">';
466
+
467
+ // Arrowhead marker
468
+ svg += '<defs><marker id="lineage-arrow" viewBox="0 0 10 7" refX="10" refY="3.5" ' +
469
+ 'markerWidth="8" markerHeight="6" orient="auto-start-reverse">' +
470
+ '<path d="M 0 0 L 10 3.5 L 0 7 z" fill="#888"/></marker></defs>';
471
+
472
+ // Draw edges
473
+ lineage.forEach(function(item) {
474
+ var pid = item.parent_skill_id;
475
+ if (pid && positions[pid] && positions[item.id]) {
476
+ var from = positions[pid];
477
+ var to = positions[item.id];
478
+ var x1 = from.x + nodeW / 2;
479
+ var y1 = from.y + nodeH;
480
+ var x2 = to.x + nodeW / 2;
481
+ var y2 = to.y;
482
+ // Curved path
483
+ var midY = (y1 + y2) / 2;
484
+ svg += '<path d="M ' + x1 + ' ' + y1 + ' C ' + x1 + ' ' + midY + ', ' + x2 + ' ' + midY + ', ' + x2 + ' ' + y2 + '" ' +
485
+ 'fill="none" stroke="#888" stroke-width="1.5" marker-end="url(#lineage-arrow)"/>';
486
+ // Edge label
487
+ var etype = item.evolution_type || '';
488
+ if (etype) {
489
+ var lx = (x1 + x2) / 2;
490
+ var labelY = midY - 4;
491
+ svg += '<text x="' + lx + '" y="' + labelY + '" text-anchor="middle" ' +
492
+ 'fill="#888" font-size="10" font-weight="500">' + escapeHtml(etype) + '</text>';
493
+ }
494
+ }
495
+ });
496
+
497
+ // Draw nodes
498
+ lineage.forEach(function(item) {
499
+ var pos = positions[item.id];
500
+ if (!pos) return;
501
+ var status = (item.status || 'original').toLowerCase();
502
+ var fillColor = LINEAGE_COLORS[status] || LINEAGE_COLORS.original;
503
+ var label = (item.skill_name || 'unknown');
504
+ if (label.length > 16) label = label.substring(0, 14) + '..';
505
+
506
+ svg += '<g class="lineage-node" data-id="' + item.id + '" style="cursor:pointer">';
507
+ svg += '<rect x="' + pos.x + '" y="' + pos.y + '" width="' + nodeW + '" height="' + nodeH + '" ' +
508
+ 'rx="8" ry="8" fill="' + fillColor + '" fill-opacity="0.15" stroke="' + fillColor + '" stroke-width="2"/>';
509
+ // Node label (skill name)
510
+ svg += '<text x="' + (pos.x + nodeW / 2) + '" y="' + (pos.y + 18) + '" text-anchor="middle" ' +
511
+ 'fill="' + fillColor + '" font-size="12" font-weight="600">' + escapeHtml(label) + '</text>';
512
+ // Status sub-label
513
+ svg += '<text x="' + (pos.x + nodeW / 2) + '" y="' + (pos.y + 34) + '" text-anchor="middle" ' +
514
+ 'fill="' + fillColor + '" font-size="10" opacity="0.7">' + escapeHtml(status) + '</text>';
515
+ svg += '</g>';
516
+ });
517
+
518
+ svg += '</svg>';
519
+ wrapper.innerHTML = svg;
520
+
521
+ // Click handler: highlight row in table
522
+ wrapper.querySelectorAll('.lineage-node').forEach(function(g) {
523
+ g.addEventListener('click', function() {
524
+ var id = g.getAttribute('data-id');
525
+ highlightLineageRow(id);
526
+ });
527
+ });
528
+ }
529
+
530
+ function renderLineageTable(lineage) {
531
+ var wrapper = document.getElementById('lineage-table-wrapper');
532
+ if (!wrapper) return;
533
+
534
+ var html = '<div class="card" style="padding:16px">' +
535
+ '<div style="font-weight:600;font-size:0.9375rem;margin-bottom:12px">' +
536
+ '<i class="bi bi-table" style="color:#8b5cf6;margin-right:6px"></i>Lineage Details' +
537
+ '</div>' +
538
+ '<div class="table-responsive"><table class="table table-sm table-hover" style="font-size:0.8125rem;margin-bottom:0">' +
539
+ '<thead><tr>' +
540
+ '<th>Skill Name</th>' +
541
+ '<th>Type</th>' +
542
+ '<th>Parent</th>' +
543
+ '<th>Status</th>' +
544
+ '<th>Verified</th>' +
545
+ '<th>Created</th>' +
546
+ '</tr></thead><tbody>';
547
+
548
+ // Build a quick lookup for parent names
549
+ var nameMap = {};
550
+ lineage.forEach(function(item) {
551
+ nameMap[item.id] = item.skill_name || item.id;
552
+ });
553
+
554
+ lineage.forEach(function(item) {
555
+ var status = (item.status || 'original').toLowerCase();
556
+ var color = LINEAGE_COLORS[status] || LINEAGE_COLORS.original;
557
+ var parentName = item.parent_skill_id ? (nameMap[item.parent_skill_id] || item.parent_skill_id) : '-';
558
+ var verified = item.blind_verified ? '<i class="bi bi-check-circle-fill" style="color:#22c55e"></i>' : '<i class="bi bi-dash-circle" style="color:var(--bs-secondary-color)"></i>';
559
+ var created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '-';
560
+
561
+ html += '<tr class="lineage-table-row" data-id="' + item.id + '" style="cursor:pointer">' +
562
+ '<td style="font-weight:500">' + escapeHtml(item.skill_name || '') + '</td>' +
563
+ '<td><span class="badge" style="background:' + color + '20;color:' + color + ';font-size:0.6875rem">' +
564
+ escapeHtml(item.evolution_type || 'ORIGINAL') + '</span></td>' +
565
+ '<td>' + escapeHtml(parentName) + '</td>' +
566
+ '<td><span style="color:' + color + ';font-weight:500">' + escapeHtml(status) + '</span></td>' +
567
+ '<td>' + verified + '</td>' +
568
+ '<td style="color:var(--bs-secondary-color)">' + created + '</td>' +
569
+ '</tr>';
570
+ });
571
+
572
+ html += '</tbody></table></div></div>';
573
+ wrapper.innerHTML = html;
574
+
575
+ // Click rows to highlight in DAG
576
+ wrapper.querySelectorAll('.lineage-table-row').forEach(function(row) {
577
+ row.addEventListener('click', function() {
578
+ var id = row.getAttribute('data-id');
579
+ highlightLineageNode(id);
580
+ highlightLineageRow(id);
581
+ });
582
+ });
583
+ }
584
+
585
+ function highlightLineageRow(id) {
586
+ // Clear previous highlights
587
+ document.querySelectorAll('.lineage-table-row').forEach(function(row) {
588
+ row.style.background = '';
589
+ });
590
+ var target = document.querySelector('.lineage-table-row[data-id="' + id + '"]');
591
+ if (target) {
592
+ target.style.background = 'rgba(139, 92, 246, 0.12)';
593
+ target.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
594
+ }
595
+ }
596
+
597
+ function highlightLineageNode(id) {
598
+ // Reset all nodes
599
+ document.querySelectorAll('.lineage-node rect').forEach(function(rect) {
600
+ rect.setAttribute('stroke-width', '2');
601
+ });
602
+ // Highlight selected
603
+ var node = document.querySelector('.lineage-node[data-id="' + id + '"] rect');
604
+ if (node) {
605
+ node.setAttribute('stroke-width', '4');
606
+ node.closest('.lineage-node').parentElement.closest('svg')
607
+ .closest('#lineage-dag-wrapper')
608
+ .scrollTop = 0; // Scroll DAG to top if needed
609
+ }
610
+ }
227
611
  })();