superlocalmemory 2.6.0 → 2.7.0

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 (46) hide show
  1. package/CHANGELOG.md +167 -1803
  2. package/README.md +212 -397
  3. package/bin/slm +179 -3
  4. package/bin/superlocalmemoryv2:learning +4 -0
  5. package/bin/superlocalmemoryv2:patterns +4 -0
  6. package/docs/ACCESSIBILITY.md +291 -0
  7. package/docs/ARCHITECTURE.md +12 -6
  8. package/docs/FRAMEWORK-INTEGRATIONS.md +300 -0
  9. package/docs/MCP-MANUAL-SETUP.md +14 -4
  10. package/install.sh +99 -3
  11. package/mcp_server.py +291 -1
  12. package/package.json +2 -1
  13. package/requirements-learning.txt +12 -0
  14. package/scripts/verify-v27.sh +233 -0
  15. package/skills/slm-show-patterns/SKILL.md +224 -0
  16. package/src/learning/__init__.py +201 -0
  17. package/src/learning/adaptive_ranker.py +826 -0
  18. package/src/learning/cross_project_aggregator.py +866 -0
  19. package/src/learning/engagement_tracker.py +638 -0
  20. package/src/learning/feature_extractor.py +461 -0
  21. package/src/learning/feedback_collector.py +690 -0
  22. package/src/learning/learning_db.py +842 -0
  23. package/src/learning/project_context_manager.py +582 -0
  24. package/src/learning/source_quality_scorer.py +685 -0
  25. package/src/learning/synthetic_bootstrap.py +1047 -0
  26. package/src/learning/tests/__init__.py +0 -0
  27. package/src/learning/tests/test_adaptive_ranker.py +328 -0
  28. package/src/learning/tests/test_aggregator.py +309 -0
  29. package/src/learning/tests/test_feedback_collector.py +295 -0
  30. package/src/learning/tests/test_learning_db.py +606 -0
  31. package/src/learning/tests/test_project_context.py +296 -0
  32. package/src/learning/tests/test_source_quality.py +355 -0
  33. package/src/learning/tests/test_synthetic_bootstrap.py +433 -0
  34. package/src/learning/tests/test_workflow_miner.py +322 -0
  35. package/src/learning/workflow_pattern_miner.py +665 -0
  36. package/ui/index.html +346 -13
  37. package/ui/js/clusters.js +90 -1
  38. package/ui/js/graph-core.js +445 -0
  39. package/ui/js/graph-cytoscape-monolithic-backup.js +1168 -0
  40. package/ui/js/graph-cytoscape.js +1168 -0
  41. package/ui/js/graph-d3-backup.js +32 -0
  42. package/ui/js/graph-filters.js +220 -0
  43. package/ui/js/graph-interactions.js +354 -0
  44. package/ui/js/graph-ui.js +214 -0
  45. package/ui/js/memories.js +52 -0
  46. package/ui/js/modal.js +104 -1
package/ui/index.html CHANGED
@@ -20,6 +20,11 @@
20
20
 
21
21
  body {
22
22
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
23
+ overflow-y: scroll; /* Always show scrollbar to prevent layout shift */
24
+ }
25
+
26
+ html {
27
+ scroll-behavior: smooth;
23
28
  }
24
29
 
25
30
  [data-bs-theme="light"] body,
@@ -70,12 +75,35 @@
70
75
  margin: 0;
71
76
  }
72
77
 
78
+ .tab-content {
79
+ min-height: 700px;
80
+ overflow: visible;
81
+ }
82
+
83
+ .tab-pane {
84
+ overflow: visible;
85
+ }
86
+
73
87
  #graph-container {
74
88
  background: var(--bs-body-bg);
75
89
  border-radius: 10px;
76
90
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
77
- min-height: 600px;
91
+ height: 600px !important;
92
+ max-height: 600px;
93
+ width: 100% !important;
78
94
  position: relative;
95
+ overflow: hidden;
96
+ /* Mobile touch support - prevent default gestures */
97
+ touch-action: none;
98
+ -webkit-user-select: none;
99
+ -moz-user-select: none;
100
+ -ms-user-select: none;
101
+ user-select: none;
102
+ }
103
+
104
+ #graph-container canvas {
105
+ max-width: 100% !important;
106
+ max-height: 100% !important;
79
107
  }
80
108
 
81
109
  .node {
@@ -363,6 +391,244 @@
363
391
  [data-bs-theme="dark"] footer a {
364
392
  color: #8fa4ff;
365
393
  }
394
+
395
+ /* ========================================
396
+ MOBILE & TABLET RESPONSIVE STYLES
397
+ ======================================== */
398
+
399
+ /* Tablet (768px and below) */
400
+ @media (max-width: 768px) {
401
+ .navbar-brand {
402
+ font-size: 1rem;
403
+ }
404
+
405
+ #navbar-subtitle {
406
+ display: none !important;
407
+ }
408
+
409
+ .stat-card {
410
+ margin-bottom: 12px;
411
+ }
412
+
413
+ .stat-value {
414
+ font-size: 1.4rem;
415
+ }
416
+
417
+ .stat-icon {
418
+ font-size: 1.5rem;
419
+ }
420
+
421
+ /* Graph container - smaller on tablet */
422
+ #graph-container {
423
+ height: 450px !important;
424
+ max-height: 450px;
425
+ }
426
+
427
+ /* Stack controls vertically on tablet */
428
+ .tab-pane .row > .col-md-6,
429
+ .tab-pane .row > .col-md-8,
430
+ .tab-pane .row > .col-md-4 {
431
+ margin-bottom: 10px;
432
+ }
433
+
434
+ /* Smaller buttons on mobile */
435
+ .btn-sm {
436
+ font-size: 0.8rem;
437
+ padding: 4px 8px;
438
+ }
439
+
440
+ /* Memory table - hide less important columns */
441
+ .memory-table th:nth-child(4),
442
+ .memory-table td:nth-child(4),
443
+ .memory-table th:nth-child(5),
444
+ .memory-table td:nth-child(5) {
445
+ display: none;
446
+ }
447
+
448
+ .memory-content {
449
+ max-width: 200px;
450
+ }
451
+ }
452
+
453
+ /* Mobile (480px and below) */
454
+ @media (max-width: 480px) {
455
+ body {
456
+ font-size: 0.9rem;
457
+ }
458
+
459
+ .navbar-brand {
460
+ font-size: 0.9rem;
461
+ }
462
+
463
+ .container-fluid {
464
+ padding-left: 10px;
465
+ padding-right: 10px;
466
+ }
467
+
468
+ /* Stat cards - 2 per row on mobile */
469
+ .col-6.mb-3 {
470
+ padding-left: 5px;
471
+ padding-right: 5px;
472
+ }
473
+
474
+ .stat-card {
475
+ padding: 12px 8px !important;
476
+ }
477
+
478
+ .stat-value {
479
+ font-size: 1.2rem;
480
+ }
481
+
482
+ .stat-icon {
483
+ font-size: 1.2rem;
484
+ }
485
+
486
+ .stat-card small {
487
+ font-size: 0.7rem;
488
+ }
489
+
490
+ /* Graph container - even smaller on mobile */
491
+ #graph-container {
492
+ height: 350px !important;
493
+ max-height: 350px;
494
+ border-radius: 6px;
495
+ }
496
+
497
+ /* Tabs - smaller text */
498
+ .nav-tabs .nav-link {
499
+ font-size: 0.8rem;
500
+ padding: 8px 10px;
501
+ }
502
+
503
+ .nav-tabs .nav-link i {
504
+ display: none;
505
+ }
506
+
507
+ /* Search controls - stack vertically */
508
+ #memories-pane .row > div {
509
+ margin-bottom: 8px;
510
+ }
511
+
512
+ /* Memory table - show only essentials */
513
+ .memory-table {
514
+ font-size: 0.8rem;
515
+ }
516
+
517
+ .memory-table th:nth-child(3),
518
+ .memory-table td:nth-child(3),
519
+ .memory-table th:nth-child(4),
520
+ .memory-table td:nth-child(4),
521
+ .memory-table th:nth-child(5),
522
+ .memory-table td:nth-child(5) {
523
+ display: none;
524
+ }
525
+
526
+ .memory-content {
527
+ max-width: 150px;
528
+ font-size: 0.75rem;
529
+ }
530
+
531
+ /* Modal - full screen on mobile */
532
+ .modal-dialog {
533
+ margin: 0;
534
+ max-width: 100%;
535
+ height: 100vh;
536
+ }
537
+
538
+ .modal-content {
539
+ height: 100%;
540
+ border-radius: 0;
541
+ }
542
+
543
+ /* Dropdown controls */
544
+ .form-select-sm {
545
+ font-size: 0.8rem;
546
+ padding: 4px 8px;
547
+ }
548
+
549
+ /* Profile select - smaller */
550
+ .profile-select {
551
+ min-width: 90px;
552
+ font-size: 0.75rem;
553
+ padding: 2px 24px 2px 8px;
554
+ }
555
+
556
+ /* Theme toggle - smaller */
557
+ .theme-toggle {
558
+ font-size: 0.8rem;
559
+ padding: 3px 10px;
560
+ }
561
+
562
+ /* Footer - smaller text */
563
+ footer {
564
+ font-size: 0.75rem;
565
+ padding: 15px;
566
+ margin-top: 20px;
567
+ }
568
+
569
+ /* Graph controls - stack vertically on mobile */
570
+ #graph-pane .card .row > div {
571
+ margin-bottom: 10px;
572
+ }
573
+
574
+ /* Hide "Show All Memories" button text on mobile, icon only */
575
+ #graph-status-filtered .btn span {
576
+ display: none;
577
+ }
578
+
579
+ #graph-status-filtered .btn i::after {
580
+ content: ' All';
581
+ }
582
+ }
583
+
584
+ /* Landscape mobile (small height) */
585
+ @media (max-height: 500px) and (orientation: landscape) {
586
+ #graph-container {
587
+ height: 250px !important;
588
+ max-height: 250px;
589
+ }
590
+
591
+ .stat-card {
592
+ padding: 8px !important;
593
+ }
594
+
595
+ .stat-value {
596
+ font-size: 1rem;
597
+ }
598
+
599
+ .navbar {
600
+ padding: 5px 0;
601
+ }
602
+ }
603
+
604
+ /* Touch feedback for interactive elements */
605
+ @media (hover: none) and (pointer: coarse) {
606
+ /* This targets touch devices only */
607
+ .btn:active,
608
+ .nav-link:active,
609
+ .sortable:active {
610
+ opacity: 0.7;
611
+ transform: scale(0.98);
612
+ }
613
+
614
+ /* Larger tap targets on touch devices */
615
+ .btn {
616
+ min-height: 44px;
617
+ min-width: 44px;
618
+ }
619
+
620
+ .nav-tabs .nav-link {
621
+ min-height: 44px;
622
+ }
623
+
624
+ /* Prevent accidental double-tap zoom on buttons */
625
+ .btn,
626
+ .nav-link,
627
+ .form-control,
628
+ .form-select {
629
+ touch-action: manipulation;
630
+ }
631
+ }
366
632
  </style>
367
633
  </head>
368
634
  <body>
@@ -383,8 +649,8 @@
383
649
  </button>
384
650
  </div>
385
651
  <span class="text-white-50 d-none d-md-inline" id="navbar-subtitle">Knowledge Graph Explorer</span>
386
- <button class="theme-toggle" id="theme-toggle" onclick="toggleDarkMode()" title="Toggle dark mode">
387
- <i class="bi bi-sun-fill" id="theme-icon"></i>
652
+ <button class="theme-toggle" id="theme-toggle" onclick="toggleDarkMode()" aria-label="Toggle dark mode">
653
+ <i class="bi bi-sun-fill" id="theme-icon" aria-hidden="true"></i>
388
654
  </button>
389
655
  </div>
390
656
  </div>
@@ -475,24 +741,77 @@
475
741
  <!-- Graph Visualization -->
476
742
  <div class="tab-pane fade show active" id="graph-pane">
477
743
  <div class="card p-3 mb-3">
478
- <div class="row align-items-center">
744
+ <div class="row align-items-center mb-2">
479
745
  <div class="col-md-8">
480
- <h5 class="mb-0">Force-Directed Knowledge Graph</h5>
481
- <small class="text-muted">Nodes = Memories | Links = Semantic Similarity</small>
746
+ <h5 class="mb-0">Interactive Knowledge Graph</h5>
747
+ <small class="text-muted">Zoom, pan, click nodes to explore Powered by Cytoscape.js</small>
748
+ <div id="graph-stats" class="mt-2"></div>
482
749
  </div>
483
750
  <div class="col-md-4 text-end">
484
- <button class="btn btn-sm btn-outline-primary" onclick="loadGraph()">
751
+ <button class="btn btn-sm btn-outline-primary" onclick="loadGraph()" aria-label="Refresh graph data">
485
752
  <i class="bi bi-arrow-clockwise"></i> Refresh
486
753
  </button>
487
- <select class="form-select form-select-sm d-inline-block w-auto" id="graph-max-nodes" onchange="loadGraph()">
488
- <option value="50">50 nodes</option>
489
- <option value="100" selected>100 nodes</option>
754
+ <select class="form-select form-select-sm d-inline-block w-auto" id="graph-max-nodes" onchange="loadGraph()" aria-label="Select maximum number of nodes to display">
755
+ <option value="50" selected>50 nodes</option>
756
+ <option value="100">100 nodes</option>
490
757
  <option value="200">200 nodes</option>
758
+ <option value="500">500 nodes</option>
491
759
  </select>
492
760
  </div>
493
761
  </div>
762
+ <div class="row align-items-center">
763
+ <div class="col-md-6">
764
+ <label class="form-label mb-1 small" for="graph-layout-selector">Layout Algorithm:</label>
765
+ <select class="form-select form-select-sm" id="graph-layout-selector" onchange="changeGraphLayout(this.value)" aria-label="Select graph layout algorithm">
766
+ <option value="fcose" selected>Force-Directed (Fast)</option>
767
+ <option value="cose">Force-Directed (Classic)</option>
768
+ <option value="circle">Circular</option>
769
+ <option value="grid">Grid</option>
770
+ <option value="breadthfirst">Hierarchical</option>
771
+ <option value="concentric">Concentric (by Importance)</option>
772
+ </select>
773
+ </div>
774
+ <div class="col-md-6">
775
+ <!-- Clear status indicator - shows what user is viewing -->
776
+ <div id="graph-status-container">
777
+ <!-- When viewing FULL graph (default state) -->
778
+ <div id="graph-status-full" style="display:block;" role="status" aria-live="polite">
779
+ <div class="d-flex align-items-center">
780
+ <span class="text-muted small">
781
+ <i class="bi bi-diagram-3"></i> <span id="graph-status-full-text">Showing all memories</span>
782
+ </span>
783
+ <button class="btn btn-sm btn-outline-secondary ms-2" onclick="loadGraph()" aria-label="Refresh current graph view">
784
+ <i class="bi bi-arrow-clockwise"></i> Refresh
785
+ </button>
786
+ </div>
787
+ </div>
788
+
789
+ <!-- When viewing FILTERED by cluster (show BIG clear button) -->
790
+ <div id="graph-status-filtered" style="display:none;" role="status" aria-live="polite">
791
+ <div class="alert alert-info py-2 px-3 mb-0 d-flex align-items-center justify-content-between">
792
+ <span>
793
+ <i class="bi bi-funnel-fill"></i>
794
+ <strong id="graph-filter-description">Viewing Cluster X</strong>
795
+ <span class="small text-muted ms-2" id="graph-filter-count">(X memories)</span>
796
+ </span>
797
+ <button class="btn btn-primary btn-sm ms-3" onclick="clearGraphFilters()" aria-label="Clear filter and show all memories">
798
+ <i class="bi bi-grid-3x3"></i> Show All Memories
799
+ </button>
800
+ </div>
801
+ </div>
802
+ </div>
803
+ </div>
804
+ </div>
805
+ </div>
806
+ <div id="graph-container"
807
+ role="application"
808
+ aria-label="Interactive knowledge graph - use Tab to navigate nodes, Enter to view details, Arrow keys to move between adjacent nodes, Escape to clear filters"
809
+ aria-describedby="graph-stats"
810
+ style="width:100%; height:600px; border:1px solid #dee2e6; border-radius:8px; background:#f8f9fa;">
494
811
  </div>
495
- <div id="graph-container"></div>
812
+
813
+ <!-- Skip link for keyboard users -->
814
+ <a href="#memories-pane" class="visually-hidden-focusable">Skip to Memories list</a>
496
815
  </div>
497
816
 
498
817
  <!-- Memories List -->
@@ -826,12 +1145,26 @@
826
1145
 
827
1146
  <!-- Bootstrap JS -->
828
1147
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
829
- <!-- D3.js -->
1148
+ <!-- D3.js (kept for backward compatibility) -->
830
1149
  <script src="https://d3js.org/d3.v7.min.js"></script>
831
1150
 
1151
+ <!-- Cytoscape.js + Extensions (v2.6.5 — interactive graph) -->
1152
+ <script src="https://unpkg.com/cytoscape@3.30.4/dist/cytoscape.min.js"></script>
1153
+ <script src="https://unpkg.com/layout-base@2.0.1/layout-base.js"></script>
1154
+ <script src="https://unpkg.com/cose-base@2.2.0/cose-base.js"></script>
1155
+ <script src="https://unpkg.com/cytoscape-fcose@2.2.0/cytoscape-fcose.js"></script>
1156
+ <link href="https://unpkg.com/cytoscape-navigator@1.3.3/cytoscape.js-navigator.css" rel="stylesheet" type="text/css" />
1157
+ <script src="https://unpkg.com/cytoscape-navigator@1.3.3/cytoscape-navigator.js"></script>
1158
+
832
1159
  <!-- Modular JS (v2.5 — split from monolith app.js) -->
833
1160
  <script src="static/js/core.js"></script>
834
- <script src="static/js/graph.js"></script>
1161
+
1162
+ <!-- Graph Visualization Scripts (v2.6.5 - Modular) -->
1163
+ <script src="static/js/graph-core.js"></script>
1164
+ <script src="static/js/graph-filters.js"></script>
1165
+ <script src="static/js/graph-ui.js"></script>
1166
+ <script src="static/js/graph-interactions.js"></script>
1167
+
835
1168
  <script src="static/js/memories.js"></script>
836
1169
  <script src="static/js/search.js"></script>
837
1170
  <script src="static/js/modal.js"></script>
package/ui/js/clusters.js CHANGED
@@ -30,7 +30,9 @@ function renderClusters(clusters) {
30
30
 
31
31
  var card = document.createElement('div');
32
32
  card.className = 'card cluster-card';
33
- card.style.borderColor = color;
33
+ card.style.cssText = 'border-color:' + color + '; cursor:pointer;';
34
+ card.setAttribute('data-cluster-id', cluster.cluster_id);
35
+ card.title = 'Click to filter graph to this cluster';
34
36
 
35
37
  var body = document.createElement('div');
36
38
  body.className = 'card-body';
@@ -76,5 +78,92 @@ function renderClusters(clusters) {
76
78
 
77
79
  card.appendChild(body);
78
80
  container.appendChild(card);
81
+
82
+ // v2.6.5: Click card → filter graph to this cluster
83
+ card.addEventListener('click', function(e) {
84
+ // Don't trigger if clicking on badge or entity
85
+ if (e.target.classList.contains('entity-badge') || e.target.classList.contains('badge')) {
86
+ return;
87
+ }
88
+
89
+ const clusterId = parseInt(card.getAttribute('data-cluster-id'));
90
+ filterGraphToCluster(clusterId);
91
+ });
92
+
93
+ // v2.6.5: Click entity badge → filter graph by entity
94
+ if (cluster.top_entities && cluster.top_entities.length > 0) {
95
+ const entityBadges = body.querySelectorAll('.entity-badge');
96
+ entityBadges.forEach(function(badge) {
97
+ badge.style.cursor = 'pointer';
98
+ badge.title = 'Click to show memories with this entity';
99
+ badge.addEventListener('click', function(e) {
100
+ e.stopPropagation(); // Don't trigger card click
101
+ const entityText = badge.textContent.split(' (')[0]; // Extract entity name
102
+ filterGraphByEntity(entityText);
103
+ });
104
+ });
105
+ }
106
+
107
+ // v2.6.5: Click "X memories" badge → show list in sidebar (future feature)
108
+ countBadge.style.cursor = 'pointer';
109
+ countBadge.title = 'Click to view memories in this cluster';
110
+ countBadge.addEventListener('click', function(e) {
111
+ e.stopPropagation(); // Don't trigger card click
112
+ showClusterMemories(cluster.cluster_id);
113
+ });
79
114
  });
80
115
  }
116
+
117
+ // v2.6.5: Filter graph to a specific cluster
118
+ function filterGraphToCluster(clusterId) {
119
+ // Switch to Graph tab
120
+ const graphTab = document.querySelector('a[href="#graph"]');
121
+ if (graphTab) {
122
+ graphTab.click();
123
+ }
124
+
125
+ // Apply filter after a delay (for tab to load)
126
+ setTimeout(function() {
127
+ if (typeof filterState !== 'undefined' && typeof filterByCluster === 'function' && typeof renderGraph === 'function') {
128
+ filterState.cluster_id = clusterId;
129
+ const filtered = filterByCluster(originalGraphData, clusterId);
130
+ renderGraph(filtered);
131
+
132
+ // Update URL
133
+ const url = new URL(window.location);
134
+ url.searchParams.set('cluster_id', clusterId);
135
+ window.history.replaceState({}, '', url);
136
+ }
137
+ }, 300);
138
+ }
139
+
140
+ // v2.6.5: Filter graph by entity
141
+ function filterGraphByEntity(entity) {
142
+ // Switch to Graph tab
143
+ const graphTab = document.querySelector('a[href="#graph"]');
144
+ if (graphTab) {
145
+ graphTab.click();
146
+ }
147
+
148
+ // Apply filter after a delay
149
+ setTimeout(function() {
150
+ if (typeof filterState !== 'undefined' && typeof filterByEntity === 'function' && typeof renderGraph === 'function') {
151
+ filterState.entity = entity;
152
+ const filtered = filterByEntity(originalGraphData, entity);
153
+ renderGraph(filtered);
154
+ }
155
+ }, 300);
156
+ }
157
+
158
+ // v2.6.5: Show memories in a cluster (future: sidebar list)
159
+ function showClusterMemories(clusterId) {
160
+ // For now, just filter Memories tab
161
+ const memoriesTab = document.querySelector('a[href="#memories"]');
162
+ if (memoriesTab) {
163
+ memoriesTab.click();
164
+ }
165
+
166
+ // TODO: Implement sidebar memory list view
167
+ console.log('Show memories for cluster', clusterId);
168
+ showToast('Filtering memories for cluster ' + clusterId);
169
+ }