superlocalmemory 3.4.9 → 3.4.10

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 (31) hide show
  1. package/README.md +14 -0
  2. package/docs/cloud-backup.md +174 -0
  3. package/docs/skill-evolution.md +189 -0
  4. package/ide/hooks/tool-event-hook.sh +101 -11
  5. package/package.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/src/superlocalmemory/cli/commands.py +189 -0
  8. package/src/superlocalmemory/cli/ingest_cmd.py +81 -29
  9. package/src/superlocalmemory/cli/main.py +11 -0
  10. package/src/superlocalmemory/core/consolidation_engine.py +10 -0
  11. package/src/superlocalmemory/core/engine.py +7 -0
  12. package/src/superlocalmemory/core/maintenance_scheduler.py +24 -3
  13. package/src/superlocalmemory/encoding/entity_resolver.py +95 -28
  14. package/src/superlocalmemory/infra/backup.py +63 -20
  15. package/src/superlocalmemory/infra/cloud_backup.py +703 -0
  16. package/src/superlocalmemory/learning/skill_performance_miner.py +389 -0
  17. package/src/superlocalmemory/server/routes/backup.py +512 -8
  18. package/src/superlocalmemory/server/routes/behavioral.py +23 -5
  19. package/src/superlocalmemory/storage/schema_v3410.py +159 -0
  20. package/src/superlocalmemory/ui/index.html +55 -2
  21. package/src/superlocalmemory/ui/js/core.js +3 -0
  22. package/src/superlocalmemory/ui/js/ng-entities.js +27 -3
  23. package/src/superlocalmemory/ui/js/ng-shell.js +33 -0
  24. package/src/superlocalmemory/ui/js/ng-skills.js +227 -0
  25. package/src/superlocalmemory/ui/js/settings.js +311 -1
  26. package/src/superlocalmemory.egg-info/PKG-INFO +0 -594
  27. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -317
  28. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  29. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  30. package/src/superlocalmemory.egg-info/requires.txt +0 -55
  31. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3.4.10 "Fortress" — Schema Extensions.
6
+
7
+ New tables for cloud backup + entity quality:
8
+ - backup_destinations: Cloud backup targets (Google Drive, GitHub, local)
9
+ - entity_blacklist: Stop words and known garbage entity names
10
+
11
+ Design rules (inherited):
12
+ - CREATE IF NOT EXISTS for idempotency
13
+ - profile_id where applicable
14
+ - Never ALTER existing column types
15
+
16
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import sqlite3
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # DDL — Backup Destinations
28
+ # ---------------------------------------------------------------------------
29
+
30
+ _BACKUP_DESTINATIONS_DDL = """
31
+ CREATE TABLE IF NOT EXISTS backup_destinations (
32
+ id TEXT PRIMARY KEY,
33
+ profile_id TEXT DEFAULT 'default',
34
+ destination_type TEXT NOT NULL,
35
+ display_name TEXT DEFAULT '',
36
+ credentials_ref TEXT DEFAULT '',
37
+ config TEXT DEFAULT '{}',
38
+ last_sync_at TEXT,
39
+ last_sync_status TEXT DEFAULT 'never',
40
+ last_sync_error TEXT DEFAULT '',
41
+ enabled INTEGER DEFAULT 1,
42
+ created_at TEXT NOT NULL
43
+ );
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_backup_dest_type
46
+ ON backup_destinations(destination_type);
47
+ CREATE INDEX IF NOT EXISTS idx_backup_dest_profile
48
+ ON backup_destinations(profile_id);
49
+ """
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # DDL — Entity Blacklist
53
+ # ---------------------------------------------------------------------------
54
+
55
+ _ENTITY_BLACKLIST_DDL = """
56
+ CREATE TABLE IF NOT EXISTS entity_blacklist (
57
+ term TEXT PRIMARY KEY,
58
+ reason TEXT DEFAULT 'stop_word',
59
+ added_at TEXT NOT NULL
60
+ );
61
+ """
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Default blacklist entries (seeded on first migration)
65
+ # ---------------------------------------------------------------------------
66
+
67
+ _DEFAULT_BLACKLIST = [
68
+ # English stop words that frequently become garbage entities
69
+ "a", "an", "the", "all", "not", "no", "yes", "and", "or", "but",
70
+ "if", "is", "are", "was", "were", "be", "been", "being",
71
+ "have", "has", "had", "do", "does", "did", "will", "would",
72
+ "shall", "should", "can", "could", "just", "also", "only",
73
+ "very", "too", "so", "then", "than", "that", "this",
74
+ "each", "every", "both", "few", "more", "most", "other",
75
+ "some", "such", "any", "many", "much", "own", "same",
76
+ "new", "old", "first", "last", "next", "now",
77
+ # Months (biggest historical source of garbage)
78
+ "january", "february", "march", "april", "may", "june",
79
+ "july", "august", "september", "october", "november", "december",
80
+ # Technical terms commonly misclassified
81
+ "test", "fix", "build", "check", "run", "start", "stop",
82
+ "error", "status", "version", "query", "data", "file",
83
+ "ready", "done", "complete", "pending", "active", "failed",
84
+ "total", "count", "key", "value", "true", "false",
85
+ # Abstract nouns misclassified as people
86
+ "completeness", "correctness", "limitations", "requirements",
87
+ "performance", "security", "quality", "coverage", "progress",
88
+ "analysis", "research", "implementation", "verification",
89
+ ]
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Migration runner
94
+ # ---------------------------------------------------------------------------
95
+
96
+ def apply_v3410_schema(db_path: str | sqlite3.Connection) -> dict:
97
+ """Apply all v3.4.10 schema changes. Idempotent."""
98
+ result = {"applied": [], "errors": []}
99
+
100
+ if isinstance(db_path, sqlite3.Connection):
101
+ conn = db_path
102
+ own_connection = False
103
+ else:
104
+ conn = sqlite3.connect(str(db_path))
105
+ own_connection = True
106
+
107
+ try:
108
+ # Backup destinations table
109
+ try:
110
+ conn.executescript(_BACKUP_DESTINATIONS_DDL)
111
+ result["applied"].append("backup_destinations table + indexes")
112
+ except sqlite3.OperationalError as e:
113
+ result["errors"].append(f"backup_destinations: {e}")
114
+
115
+ # Entity blacklist table
116
+ try:
117
+ conn.executescript(_ENTITY_BLACKLIST_DDL)
118
+ result["applied"].append("entity_blacklist table")
119
+ except sqlite3.OperationalError as e:
120
+ result["errors"].append(f"entity_blacklist: {e}")
121
+
122
+ # Seed default blacklist (only inserts missing entries)
123
+ now = __import__("datetime").datetime.now().isoformat()
124
+ seeded = 0
125
+ for term in _DEFAULT_BLACKLIST:
126
+ try:
127
+ conn.execute(
128
+ "INSERT OR IGNORE INTO entity_blacklist (term, reason, added_at) "
129
+ "VALUES (?, 'stop_word', ?)",
130
+ (term, now),
131
+ )
132
+ seeded += 1
133
+ except sqlite3.OperationalError:
134
+ pass
135
+ if seeded:
136
+ result["applied"].append(f"entity_blacklist seeded ({len(_DEFAULT_BLACKLIST)} terms)")
137
+
138
+ # Mark version
139
+ try:
140
+ conn.execute(
141
+ "INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)",
142
+ ("3.4.10", now),
143
+ )
144
+ except sqlite3.OperationalError:
145
+ pass
146
+
147
+ conn.commit()
148
+
149
+ if result["applied"]:
150
+ logger.info("Schema v3.4.10 applied: %s", ", ".join(result["applied"]))
151
+
152
+ except Exception as e:
153
+ result["errors"].append(f"fatal: {e}")
154
+ logger.error("Schema v3.4.10 migration failed: %s", e)
155
+ finally:
156
+ if own_connection:
157
+ conn.close()
158
+
159
+ return result
@@ -43,6 +43,14 @@
43
43
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
44
44
  }
45
45
 
46
+ /* Cloud account widget — sidebar (v3.4.10) */
47
+ #ng-account-widget:hover { background: rgba(255,255,255,0.06) !important; }
48
+ .account-dest-item { padding: 6px 0; display: flex; align-items: center; gap: 8px; }
49
+ .account-dest-badge { font-size: 10px; padding: 1px 6px; border-radius: 4px; }
50
+ .account-dest-badge.synced { background: rgba(0,212,170,0.15); color: #00D4AA; }
51
+ .account-dest-badge.failed { background: rgba(255,71,87,0.15); color: #ff4757; }
52
+ .account-dest-badge.never { background: rgba(255,255,255,0.06); color: #888; }
53
+
46
54
  /* Stat cards with gradient backgrounds */
47
55
  .stat-card {
48
56
  border: none;
@@ -1717,6 +1725,25 @@
1717
1725
  <div id="entity-detail-panel" style="display:none;margin-top:24px"></div>
1718
1726
  </div>
1719
1727
 
1728
+ <!-- Skill Evolution (v3.4.10 — Arsenal Evolution) -->
1729
+ <div class="tab-pane fade" id="skills-pane">
1730
+ <div class="ng-content-header">
1731
+ <div>
1732
+ <div class="ng-content-title"><i class="bi bi-lightning-charge"></i> Skill Evolution</div>
1733
+ <div class="ng-content-subtitle">Track skill performance, evolution history, and health status</div>
1734
+ </div>
1735
+ <button class="ng-btn" onclick="loadSkillEvolution()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1736
+ </div>
1737
+ <div id="skills-overview-cards"></div>
1738
+ <div id="skills-list" style="margin-top:24px">
1739
+ <div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">
1740
+ <div class="spinner-border" style="color:var(--ng-accent)"></div>
1741
+ <div style="margin-top:8px">Loading skill performance data...</div>
1742
+ </div>
1743
+ </div>
1744
+ <div id="skill-detail-panel" style="display:none;margin-top:24px"></div>
1745
+ </div>
1746
+
1720
1747
  <!-- Mesh Peers (v3.4.3 — Neural Glass) -->
1721
1748
  <div class="tab-pane fade" id="mesh-pane">
1722
1749
  <div class="ng-content-header">
@@ -1974,6 +2001,31 @@
1974
2001
  </div>
1975
2002
  </div>
1976
2003
  </div>
2004
+ <!-- Cloud Backup (v3.4.10 "Fortress") -->
2005
+ <div class="card p-3 mb-3">
2006
+ <h5 class="mb-3"><i class="bi bi-cloud-arrow-up"></i> Cloud Backup</h5>
2007
+ <p class="text-muted small mb-2">
2008
+ Sync your memory backups to Google Drive or GitHub. Credentials are stored securely in your OS keychain.
2009
+ </p>
2010
+ <div id="cloud-destinations" class="mb-3">
2011
+ <div class="text-muted small">Loading cloud destinations...</div>
2012
+ </div>
2013
+ <div class="d-flex gap-2 flex-wrap">
2014
+ <button class="btn btn-outline-primary btn-sm" onclick="connectGoogleDrive()">
2015
+ <i class="bi bi-google"></i> Connect Google Drive
2016
+ </button>
2017
+ <button class="btn btn-outline-dark btn-sm" onclick="connectGitHub()">
2018
+ <i class="bi bi-github"></i> Connect GitHub
2019
+ </button>
2020
+ <button class="btn btn-outline-success btn-sm" onclick="syncCloudNow()">
2021
+ <i class="bi bi-cloud-upload"></i> Sync Now
2022
+ </button>
2023
+ <button class="btn btn-outline-info btn-sm" onclick="exportBackup()">
2024
+ <i class="bi bi-file-earmark-zip"></i> Export Backup
2025
+ </button>
2026
+ </div>
2027
+ </div>
2028
+
1977
2029
  <!-- Learning Data Management (v2.7.4) -->
1978
2030
  <div class="card p-3 mb-3">
1979
2031
  <h5 class="mb-3"><i class="bi bi-brain"></i> Learning Data</h5>
@@ -2097,9 +2149,10 @@
2097
2149
  <!-- Neural Glass v3.4.4 — Dashboard V2 -->
2098
2150
  <script src="static/js/ng-health.js?v=345"></script>
2099
2151
  <script src="static/js/ng-ingestion.js?v=345"></script>
2100
- <script src="static/js/ng-entities.js?v=345"></script>
2152
+ <script src="static/js/ng-entities.js?v=3410"></script>
2153
+ <script src="static/js/ng-skills.js?v=3410"></script>
2101
2154
  <script src="static/js/ng-mesh.js?v=345"></script>
2102
- <script src="static/js/ng-shell.js?v=345"></script>
2155
+ <script src="static/js/ng-shell.js?v=3410"></script>
2103
2156
 
2104
2157
  <footer style="text-align:center; padding:24px; border-top:1px solid var(--bs-border-color); font-size:0.8125rem;">
2105
2158
  <div style="display:flex; align-items:center; justify-content:center; gap:8px; margin-bottom:8px;">
@@ -243,4 +243,7 @@ window.addEventListener('DOMContentLoaded', function() {
243
243
  if (typeof initEventStream === 'function') initEventStream();
244
244
  if (typeof loadEventStats === 'function') loadEventStats();
245
245
  if (typeof loadAgents === 'function') loadAgents();
246
+
247
+ // v3.4.10 — Account widget (cloud backup status in header)
248
+ if (typeof loadCloudDestinations === 'function') loadCloudDestinations();
246
249
  });
@@ -85,14 +85,23 @@
85
85
  var typeColor = getTypeColor(entity.type);
86
86
  var summaryText = entity.summary_preview || 'No summary yet — click to view details';
87
87
  var hasTruth = entity.has_compiled_truth;
88
+ var typeIcon = getTypeIcon(entity.type);
89
+
90
+ // Skill entities get a distinctive accent border
91
+ var borderAccent = entity.type === 'skill'
92
+ ? 'border-left:3px solid ' + typeColor + ';'
93
+ : '';
88
94
 
89
95
  return '<div class="col-md-6 col-lg-4">' +
90
- '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s" ' +
96
+ '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s;' + borderAccent + '" ' +
91
97
  'onclick="showEntityDetail(\'' + escapeAttr(entity.name) + '\')" ' +
92
98
  'onmouseover="this.style.borderColor=\'var(--ng-border-prominent)\'" ' +
93
99
  'onmouseout="this.style.borderColor=\'var(--ng-border-subtle)\'">' +
94
100
  '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">' +
95
- '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' + escapeHtml(entity.name) + '</div>' +
101
+ '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' +
102
+ '<i class="bi ' + typeIcon + '" style="color:' + typeColor + ';margin-right:4px"></i>' +
103
+ escapeHtml(entity.name) +
104
+ '</div>' +
96
105
  '<span class="ng-badge ng-badge-accent">' + escapeHtml(entity.type) + '</span>' +
97
106
  '</div>' +
98
107
  '<div style="font-size:0.8125rem;color:var(--ng-text-secondary);margin-bottom:8px;' +
@@ -107,6 +116,18 @@
107
116
  '</div>';
108
117
  }
109
118
 
119
+ function getTypeIcon(type) {
120
+ var icons = {
121
+ person: 'bi-person',
122
+ concept: 'bi-lightbulb',
123
+ organization: 'bi-building',
124
+ place: 'bi-geo-alt',
125
+ event: 'bi-calendar-event',
126
+ skill: 'bi-lightning-charge'
127
+ };
128
+ return icons[type] || 'bi-circle';
129
+ }
130
+
110
131
  // Show entity detail panel
111
132
  window.showEntityDetail = function(entityName) {
112
133
  var panel = document.getElementById('entity-detail-panel');
@@ -253,7 +274,10 @@
253
274
  }
254
275
 
255
276
  function getTypeColor(type) {
256
- var colors = { person: '#3b82f6', concept: '#10b981', organization: '#f59e0b', location: '#ef4444' };
277
+ var colors = {
278
+ person: '#3b82f6', concept: '#10b981', organization: '#f59e0b',
279
+ location: '#ef4444', skill: '#8b5cf6', event: '#ec4899'
280
+ };
257
281
  return colors[type] || '#7C6AEF';
258
282
  }
259
283
 
@@ -44,6 +44,7 @@
44
44
  { id: 'health-pane', icon: 'bi-heart-pulse', text: 'Health Monitor', badge: 'NEW' },
45
45
  { id: 'ingestion-pane', icon: 'bi-cloud-download', text: 'Ingestion', badge: 'NEW' },
46
46
  { id: 'entities-pane', icon: 'bi-person-badge', text: 'Entity Explorer', badge: 'NEW' },
47
+ { id: 'skills-pane', icon: 'bi-lightning-charge', text: 'Skill Evolution', badge: 'NEW' },
47
48
  { id: 'mesh-pane', icon: 'bi-share', text: 'Mesh Peers', badge: 'NEW' }
48
49
  ]
49
50
  },
@@ -134,6 +135,35 @@
134
135
  var addProfileBtn = document.getElementById('add-profile-btn');
135
136
 
136
137
  footer.innerHTML =
138
+ // v3.4.10: Cloud Backup Account Widget
139
+ '<div id="ng-account-widget" style="margin-bottom:10px;padding:8px;border-radius:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);cursor:pointer;" onclick="document.querySelector(\'[data-target=settings]\')?.click()">' +
140
+ '<div style="display:flex;align-items:center;gap:8px;">' +
141
+ '<span id="ng-account-avatar" style="width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;overflow:hidden;">' +
142
+ '<i class="bi bi-cloud-slash" style="font-size:13px;opacity:0.4;"></i>' +
143
+ '</span>' +
144
+ '<div style="flex:1;min-width:0;">' +
145
+ '<div id="ng-account-name" style="font-size:12px;color:#e0e0e0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">Not connected</div>' +
146
+ '<div id="ng-account-status" style="font-size:10px;color:#666;">No cloud backup</div>' +
147
+ '</div>' +
148
+ '<span id="ng-account-dot" style="width:7px;height:7px;border-radius:50%;background:#444;flex-shrink:0;" title="No cloud backup"></span>' +
149
+ '</div>' +
150
+ '<div id="ng-account-actions" style="display:none;margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05);">' +
151
+ '<div style="display:flex;gap:4px;">' +
152
+ '<button class="ng-btn" onclick="event.stopPropagation();connectGoogleDrive()" title="Connect Google Drive" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
153
+ '<i class="bi bi-google" style="color:#4285f4;"></i>' +
154
+ '</button>' +
155
+ '<button class="ng-btn" onclick="event.stopPropagation();connectGitHub()" title="Connect GitHub" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
156
+ '<i class="bi bi-github"></i>' +
157
+ '</button>' +
158
+ '<button class="ng-btn" onclick="event.stopPropagation();syncCloudNow()" title="Sync Now" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
159
+ '<i class="bi bi-cloud-upload" style="color:#00D4AA;"></i>' +
160
+ '</button>' +
161
+ '<button class="ng-btn" onclick="event.stopPropagation();exportBackup()" title="Export Backup" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
162
+ '<i class="bi bi-download" style="color:#f39c12;"></i>' +
163
+ '</button>' +
164
+ '</div>' +
165
+ '</div>' +
166
+ '</div>' +
137
167
  '<div style="margin-bottom:8px">' +
138
168
  '<div class="ng-sidebar-section-label" style="padding:0 0 4px">Profile</div>' +
139
169
  '<div style="display:flex;gap:4px;align-items:center" id="ng-profile-container"></div>' +
@@ -359,6 +389,9 @@
359
389
  case 'entities-pane':
360
390
  if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
361
391
  break;
392
+ case 'skills-pane':
393
+ if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
394
+ break;
362
395
  case 'mesh-pane':
363
396
  if (typeof loadMeshPeers === 'function') loadMeshPeers();
364
397
  break;
@@ -0,0 +1,227 @@
1
+ // Neural Glass — Skill Evolution Tab
2
+ // Browse skill performance, evolution history, and health status (v3.4.10)
3
+ // API: /api/behavioral/assertions (category=skill_performance, skill_correlation)
4
+ // /api/behavioral/tool-events (tool_name=Skill)
5
+ // /api/entity/list (type filter for skill entities)
6
+
7
+ (function() {
8
+ 'use strict';
9
+
10
+ window.loadSkillEvolution = function() {
11
+ fetchSkillOverview();
12
+ fetchSkillPerformance();
13
+ };
14
+
15
+ function fetchSkillOverview() {
16
+ var el = document.getElementById('skills-overview-cards');
17
+ if (!el) return;
18
+
19
+ // Compatibility notice + ECC credit + docs links
20
+ var noticeHtml =
21
+ '<div class="card" style="padding:12px 16px;margin-bottom:16px;border-left:3px solid #8b5cf6">' +
22
+ '<div style="font-size:0.8125rem;color:#555">' +
23
+ '<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
24
+ '<strong>Skill Evolution</strong> currently tracks <strong>Claude Code</strong> skills. ' +
25
+ 'The <code>/api/v3/tool-event</code> endpoint accepts events from any IDE client. ' +
26
+ 'Enhanced observation support available with ' +
27
+ '<a href="https://github.com/affaan-m/everything-claude-code" target="_blank" style="color:#8b5cf6">Everything Claude Code (ECC)</a> ' +
28
+ 'via <code>slm ingest --source ecc</code>.' +
29
+ '</div>' +
30
+ '<div style="font-size:0.75rem;color:#888;margin-top:8px">' +
31
+ '<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
+ '<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
+ '</div>' +
34
+ '</div>';
35
+ el.innerHTML = noticeHtml + '<div id="skills-overview-inner"></div>';
36
+ el = document.getElementById('skills-overview-inner');
37
+
38
+ // Fetch tool events for Skill calls + assertions for skill_performance
39
+ Promise.all([
40
+ fetch('/api/behavioral/tool-events?tool_name=Skill&limit=500').then(function(r) { return r.json(); }),
41
+ fetch('/api/behavioral/assertions?category=skill_performance&limit=50').then(function(r) { return r.json(); }),
42
+ fetch('/api/behavioral/assertions?category=skill_correlation&limit=20').then(function(r) { return r.json(); }),
43
+ ]).then(function(results) {
44
+ var events = results[0].events || [];
45
+ var perfAssertions = results[1].assertions || [];
46
+ var corrAssertions = results[2].assertions || [];
47
+
48
+ // Count unique skills from events
49
+ var skillNames = {};
50
+ events.forEach(function(e) {
51
+ var name = extractSkillName(e);
52
+ if (name) skillNames[name] = (skillNames[name] || 0) + 1;
53
+ });
54
+
55
+ var html = '<div class="row g-3 mb-4">' +
56
+ overviewCard('Total Skill Events', events.length, 'bi-lightning-charge', 'var(--ng-accent)') +
57
+ overviewCard('Unique Skills', Object.keys(skillNames).length, 'bi-grid-3x3', '#8b5cf6') +
58
+ overviewCard('Performance Assertions', perfAssertions.length, 'bi-graph-up', '#10b981') +
59
+ overviewCard('Skill Correlations', corrAssertions.length, 'bi-link-45deg', '#f59e0b') +
60
+ '</div>';
61
+
62
+ el.innerHTML = html;
63
+ }).catch(function() {
64
+ el.innerHTML = '<div class="alert alert-warning">Could not load skill overview</div>';
65
+ });
66
+ }
67
+
68
+ function fetchSkillPerformance() {
69
+ var el = document.getElementById('skills-list');
70
+ if (!el) return;
71
+
72
+ Promise.all([
73
+ fetch('/api/behavioral/assertions?category=skill_performance&limit=50').then(function(r) { return r.json(); }),
74
+ fetch('/api/behavioral/assertions?category=skill_correlation&limit=20').then(function(r) { return r.json(); }),
75
+ fetch('/api/behavioral/tool-events?tool_name=Skill&limit=500').then(function(r) { return r.json(); }),
76
+ ]).then(function(results) {
77
+ var perfAssertions = results[0].assertions || [];
78
+ var corrAssertions = results[1].assertions || [];
79
+ var events = results[2].events || [];
80
+
81
+ var html = '';
82
+
83
+ // Section 1: Skill Performance
84
+ html += '<h5 style="margin-bottom:16px"><i class="bi bi-lightning-charge" style="color:#8b5cf6"></i> Skill Performance</h5>';
85
+
86
+ if (perfAssertions.length === 0 && events.length === 0) {
87
+ html += '<div class="card" style="padding:24px;text-align:center;color:#888">' +
88
+ '<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>' +
90
+ '<div style="font-size:0.8125rem">' +
91
+ 'Skill tracking starts automatically after the enriched hook captures data.<br>' +
92
+ 'Use skills in your sessions — performance assertions will appear after consolidation.' +
93
+ '</div>' +
94
+ '</div>';
95
+ } else if (perfAssertions.length > 0) {
96
+ html += '<div class="row g-3">';
97
+ perfAssertions.forEach(function(a) {
98
+ html += renderSkillCard(a);
99
+ });
100
+ html += '</div>';
101
+ } else {
102
+ // We have events but no assertions yet (need consolidation)
103
+ html += '<div class="card" style="padding:16px;margin-bottom:16px">' +
104
+ '<div style="font-size:0.875rem;color:#555">' +
105
+ '<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
106
+ events.length + ' skill events collected. Run consolidation to generate performance assertions.' +
107
+ '</div>' +
108
+ '</div>';
109
+
110
+ // Show raw event summary
111
+ var skillCounts = {};
112
+ events.forEach(function(e) {
113
+ var name = extractSkillName(e);
114
+ if (name) skillCounts[name] = (skillCounts[name] || 0) + 1;
115
+ });
116
+
117
+ html += '<div class="row g-3">';
118
+ Object.keys(skillCounts).sort(function(a, b) {
119
+ return skillCounts[b] - skillCounts[a];
120
+ }).forEach(function(name) {
121
+ html += '<div class="col-md-6 col-lg-4"><div class="card" style="padding:16px;border-left:3px solid #8b5cf6">' +
122
+ '<div style="display:flex;justify-content:space-between;align-items:center">' +
123
+ '<div style="font-weight:600;font-size:0.9375rem">' +
124
+ '<i class="bi bi-lightning-charge" style="color:#8b5cf6;margin-right:4px"></i>' +
125
+ escapeHtml(name) +
126
+ '</div>' +
127
+ '<span class="badge" style="background:#8b5cf620;color:#8b5cf6;font-size:0.75rem">' + skillCounts[name] + ' events</span>' +
128
+ '</div>' +
129
+ '</div></div>';
130
+ });
131
+ html += '</div>';
132
+ }
133
+
134
+ // Section 2: Skill Correlations
135
+ if (corrAssertions.length > 0) {
136
+ html += '<h5 style="margin-top:32px;margin-bottom:16px"><i class="bi bi-link-45deg" style="color:#f59e0b"></i> Skill Correlations</h5>';
137
+ html += '<div class="row g-3">';
138
+ corrAssertions.forEach(function(a) {
139
+ html += '<div class="col-md-6"><div class="card" style="padding:12px">' +
140
+ '<div style="font-size:0.875rem">' +
141
+ '<strong>' + escapeHtml(a.trigger_condition || '') + '</strong>' +
142
+ '</div>' +
143
+ '<div style="font-size:0.8125rem;color:#555;margin-top:4px">' +
144
+ escapeHtml(a.action || '') +
145
+ '</div>' +
146
+ '<div style="font-size:0.75rem;color:#888;margin-top:4px">' +
147
+ 'Confidence: ' + ((a.confidence || 0) * 100).toFixed(0) + '%' +
148
+ '</div>' +
149
+ '</div></div>';
150
+ });
151
+ html += '</div>';
152
+ }
153
+
154
+ el.innerHTML = html;
155
+ }).catch(function(err) {
156
+ el.innerHTML = '<div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">' +
157
+ 'Error loading skill data: ' + err.message + '</div>';
158
+ });
159
+ }
160
+
161
+ function renderSkillCard(assertion) {
162
+ var conf = assertion.confidence || 0;
163
+ var confPct = (conf * 100).toFixed(0);
164
+ var confColor = conf >= 0.7 ? '#10b981' : conf >= 0.5 ? '#f59e0b' : '#ef4444';
165
+
166
+ // Extract skill name from trigger_condition
167
+ var skillName = (assertion.trigger_condition || '').replace('when considering skill ', '');
168
+
169
+ return '<div class="col-md-6 col-lg-4">' +
170
+ '<div class="card" style="padding:16px;border-left:3px solid #8b5cf6;cursor:pointer">' +
171
+ '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">' +
172
+ '<div style="font-weight:600;font-size:0.9375rem">' +
173
+ '<i class="bi bi-lightning-charge" style="color:#8b5cf6;margin-right:4px"></i>' +
174
+ escapeHtml(skillName) +
175
+ '</div>' +
176
+ '<span class="badge" style="background:' + confColor + ';color:#fff;font-size:0.75rem">' +
177
+ confPct + '%' +
178
+ '</span>' +
179
+ '</div>' +
180
+ '<div style="font-size:0.8125rem;color:#555;margin-bottom:8px">' +
181
+ escapeHtml(assertion.action || 'No performance data yet') +
182
+ '</div>' +
183
+ '<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color:#888">' +
184
+ '<span>Evidence: ' + (assertion.evidence_count || 0) + ' invocations</span>' +
185
+ '<span>Reinforced: ' + (assertion.reinforcement_count || 0) + 'x</span>' +
186
+ '</div>' +
187
+ '</div>' +
188
+ '</div>';
189
+ }
190
+
191
+ function extractSkillName(event) {
192
+ var input = event.input_summary || '';
193
+ var output = event.output_summary || '';
194
+
195
+ // Try input_summary (enriched hook format)
196
+ if (input) {
197
+ try {
198
+ var inp = JSON.parse(input);
199
+ if (inp.skill) return inp.skill;
200
+ } catch(e) {}
201
+ }
202
+
203
+ // Try output_summary (ECC ingestion format)
204
+ if (output) {
205
+ try {
206
+ var out = JSON.parse(output);
207
+ if (out.commandName) return out.commandName;
208
+ } catch(e) {}
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ function overviewCard(label, value, icon, color) {
215
+ return '<div class="col-md-3 col-6"><div class="card" style="padding:12px;text-align:center">' +
216
+ '<i class="bi ' + icon + '" style="color:' + color + ';font-size:1.125rem;display:block;margin-bottom:4px"></i>' +
217
+ '<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>' +
219
+ '</div></div>';
220
+ }
221
+
222
+ function escapeHtml(s) {
223
+ var d = document.createElement('div');
224
+ d.textContent = s || '';
225
+ return d.innerHTML;
226
+ }
227
+ })();