superlocalmemory 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/api_server.py CHANGED
@@ -671,6 +671,29 @@ async def get_tree():
671
671
  }
672
672
 
673
673
 
674
+ # ============================================================================
675
+ # v2.8 Routes (graceful — don't fail if engines unavailable)
676
+ # ============================================================================
677
+
678
+ try:
679
+ from routes.lifecycle import router as lifecycle_router
680
+ app.include_router(lifecycle_router)
681
+ except ImportError:
682
+ pass
683
+
684
+ try:
685
+ from routes.behavioral import router as behavioral_router
686
+ app.include_router(behavioral_router)
687
+ except ImportError:
688
+ pass
689
+
690
+ try:
691
+ from routes.compliance import router as compliance_router
692
+ app.include_router(compliance_router)
693
+ except ImportError:
694
+ pass
695
+
696
+
674
697
  # ============================================================================
675
698
  # Server Startup
676
699
  # ============================================================================
package/bin/slm CHANGED
@@ -55,6 +55,23 @@ case "$1" in
55
55
  "$SLM_DIR/bin/superlocalmemoryv2:profile" "$@"
56
56
  ;;
57
57
 
58
+ switch-profile)
59
+ shift
60
+ # Convenience alias: "slm switch-profile <name>" -> "slm profile switch <name>"
61
+ "$SLM_DIR/bin/superlocalmemoryv2:profile" switch "$@"
62
+ ;;
63
+
64
+ list-profiles)
65
+ # Convenience alias: "slm list-profiles" -> "slm profile list"
66
+ "$SLM_DIR/bin/superlocalmemoryv2:profile" list
67
+ ;;
68
+
69
+ create-profile)
70
+ shift
71
+ # Convenience alias: "slm create-profile <name>" -> "slm profile create <name>"
72
+ "$SLM_DIR/bin/superlocalmemoryv2:profile" create "$@"
73
+ ;;
74
+
58
75
  reset)
59
76
  shift
60
77
  # Calls existing command - no duplicate logic
@@ -310,6 +327,9 @@ PROFILE MANAGEMENT:
310
327
  slm profile switch <name> Switch to profile
311
328
  slm profile delete <name> Delete profile
312
329
  slm profile current Show current profile
330
+ slm switch-profile <name> Shortcut for profile switch
331
+ slm list-profiles Shortcut for profile list
332
+ slm create-profile <name> Shortcut for profile create
313
333
 
314
334
  KNOWLEDGE GRAPH:
315
335
  slm graph build Build/rebuild knowledge graph
package/install.sh CHANGED
@@ -338,6 +338,21 @@ print('Database ready')
338
338
  " && echo "✓ Database initialized (fallback)"
339
339
  fi
340
340
 
341
+ # Run v2.8 schema migration (adds lifecycle columns to existing databases)
342
+ echo ""
343
+ echo "Running schema migration..."
344
+ cd "$INSTALL_DIR"
345
+ python3 -c "
346
+ import sys
347
+ sys.path.insert(0, '.')
348
+ try:
349
+ from memory_store_v2 import MemoryStoreV2
350
+ MemoryStoreV2()
351
+ print(' Schema migration complete')
352
+ except Exception as e:
353
+ print(f' Migration note: {e}')
354
+ " 2>/dev/null || echo " Migration will run on first use"
355
+
341
356
  # Install core dependencies (required for graph & dashboard)
342
357
  echo ""
343
358
  echo "Installing core dependencies..."
package/mcp_server.py CHANGED
@@ -560,6 +560,36 @@ def _maybe_passive_decay() -> None:
560
560
  pass
561
561
 
562
562
 
563
+ # ============================================================================
564
+ # Eager initialization — ensure schema migration runs at startup (v2.8)
565
+ # ============================================================================
566
+
567
+ def _eager_init():
568
+ """Initialize all engines at startup. Ensures schema migration runs."""
569
+ try:
570
+ get_store() # Triggers MemoryStoreV2._init_db() which creates v2.8 columns
571
+ except Exception:
572
+ pass # Don't block server startup
573
+ try:
574
+ from lifecycle.lifecycle_engine import LifecycleEngine
575
+ LifecycleEngine() # Triggers _ensure_columns()
576
+ except Exception:
577
+ pass
578
+ try:
579
+ from behavioral.outcome_tracker import OutcomeTracker
580
+ OutcomeTracker(str(Path.home() / ".claude-memory" / "learning.db"))
581
+ except Exception:
582
+ pass
583
+ try:
584
+ from compliance.audit_db import AuditDB
585
+ AuditDB(str(Path.home() / ".claude-memory" / "audit.db"))
586
+ except Exception:
587
+ pass
588
+
589
+ # Run once at module load
590
+ _eager_init()
591
+
592
+
563
593
  # ============================================================================
564
594
  # MCP TOOLS (Functions callable by AI)
565
595
  # ============================================================================
@@ -585,7 +615,7 @@ async def remember(
585
615
  Args:
586
616
  content: The content to remember (required)
587
617
  tags: Comma-separated tags (optional, e.g. "python,api,backend")
588
- project: Project name (optional, groups related memories)
618
+ project: Project name to scope the memory
589
619
  importance: Importance score 1-10 (default 5)
590
620
 
591
621
  Returns:
@@ -1394,7 +1424,12 @@ try:
1394
1424
 
1395
1425
  @mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
1396
1426
  async def compact_memories(dry_run: bool = True, profile: str = None) -> dict:
1397
- """Evaluate and compact stale memories through lifecycle transitions. dry_run=True by default."""
1427
+ """Evaluate and compact stale memories through lifecycle transitions. dry_run=True by default.
1428
+
1429
+ Args:
1430
+ dry_run: If True (default), show what would happen without changes.
1431
+ profile: Profile name to filter.
1432
+ """
1398
1433
  return await _compact_memories(dry_run, profile)
1399
1434
 
1400
1435
  @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False))
@@ -1649,7 +1684,7 @@ if __name__ == "__main__":
1649
1684
  print(" - list_recent(limit)", file=sys.stderr)
1650
1685
  print(" - get_status()", file=sys.stderr)
1651
1686
  print(" - build_graph()", file=sys.stderr)
1652
- print(" - switch_profile(name)", file=sys.stderr)
1687
+ print(" - switch_profile(name) [Project/Profile switch]", file=sys.stderr)
1653
1688
  print(" - backup_status() [Auto-Backup]", file=sys.stderr)
1654
1689
  if LEARNING_AVAILABLE:
1655
1690
  print(" - memory_used(memory_id, query, usefulness) [v2.7 Learning]", file=sys.stderr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "Your AI Finally Remembers You - Local-first intelligent memory system for AI assistants. Works with Claude, Cursor, Windsurf, VS Code/Copilot, Codex, and 17+ AI tools. 100% local, zero cloud dependencies.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -336,7 +336,7 @@ date
336
336
  - `slm remember` - Save a new memory
337
337
  - `slm recall` - Search memories by relevance
338
338
  - `slm status` - Check memory count and stats
339
- - `slm switch-profile` - View different project's memories
339
+ - `slm switch-profile` - View different profile's memories
340
340
 
341
341
  ---
342
342
 
@@ -157,7 +157,7 @@ slm remember "Commit: $commit_msg (${commit_hash:0:7})" \
157
157
  - **Cross-tool sync:** All AI tools access same database (Cursor, ChatGPT, Claude, etc.)
158
158
  - **Unlimited:** No memory limits, no quotas
159
159
  - **Privacy:** Your data stays on your computer
160
- - **Profiles:** Use `slm switch-profile` for project isolation
160
+ - **Profiles:** Use `slm switch-profile` for profile isolation
161
161
 
162
162
  ## Related Commands
163
163
 
@@ -155,7 +155,7 @@ Corrupted Entries: 0 found
155
155
  - **Last Used:** Last access timestamp
156
156
 
157
157
  **Profiles allow:**
158
- - Project isolation
158
+ - Profile isolation
159
159
  - Context switching
160
160
  - Separate memory spaces
161
161
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: slm-switch-profile
3
- description: Switch between memory profiles for project isolation and context management. Use when the user wants to change project context, separate work/personal memories, or manage multiple independent memory spaces. Each profile has its own database, graph, and patterns.
3
+ description: Switch between memory profiles for context isolation and management. Use when the user wants to change profile context, separate work/personal memories, or manage multiple independent memory spaces. Each profile has its own database, graph, and patterns.
4
4
  version: "2.1.0"
5
5
  license: MIT
6
6
  compatibility: "Requires SuperLocalMemory V2 installed at ~/.claude-memory/"
@@ -26,8 +26,8 @@ slm switch-profile <name> [--create]
26
26
 
27
27
  Each profile has its own:
28
28
  - ✅ **Separate database** - Zero context bleeding
29
- - ✅ **Independent knowledge graph** - Project-specific relationships
30
- - ✅ **Unique patterns** - Different coding preferences per project
29
+ - ✅ **Independent knowledge graph** - Profile-specific relationships
30
+ - ✅ **Unique patterns** - Different coding preferences per profile
31
31
  - ✅ **Isolated history** - No cross-contamination
32
32
 
33
33
  **Think of profiles as workspaces:**
package/src/hnsw_index.py CHANGED
@@ -139,12 +139,18 @@ class HNSWIndex:
139
139
  with open(self.metadata_path, 'r') as f:
140
140
  metadata = json.load(f)
141
141
 
142
- # Validate metadata
142
+ # Validate metadata — auto-rebuild on dimension mismatch
143
143
  if metadata.get('dimension') != self.dimension:
144
- logger.warning(
145
- f"Index dimension mismatch: {metadata.get('dimension')} != {self.dimension}. "
146
- "Will rebuild index."
144
+ logger.info(
145
+ "Index dimension changed: %s -> %s. "
146
+ "Deleting old index files and rebuilding.",
147
+ metadata.get('dimension'), self.dimension,
147
148
  )
149
+ try:
150
+ self.index_path.unlink(missing_ok=True)
151
+ self.metadata_path.unlink(missing_ok=True)
152
+ except OSError as del_err:
153
+ logger.warning("Could not delete old index files: %s", del_err)
148
154
  return
149
155
 
150
156
  # Load HNSW index
@@ -38,6 +38,7 @@ class LifecycleEngine:
38
38
  self._db_path = str(db_path)
39
39
  self._config_path = config_path
40
40
  self._lock = threading.Lock()
41
+ self._ensure_columns()
41
42
 
42
43
  def _get_connection(self) -> sqlite3.Connection:
43
44
  """Get a SQLite connection to memory.db."""
@@ -45,6 +46,34 @@ class LifecycleEngine:
45
46
  conn.row_factory = sqlite3.Row
46
47
  return conn
47
48
 
49
+ def _ensure_columns(self) -> None:
50
+ """Ensure v2.8 lifecycle columns exist in memories table."""
51
+ try:
52
+ conn = self._get_connection()
53
+ try:
54
+ cursor = conn.cursor()
55
+ cursor.execute("PRAGMA table_info(memories)")
56
+ existing = {row[1] for row in cursor.fetchall()}
57
+ v28_cols = [
58
+ ("lifecycle_state", "TEXT DEFAULT 'active'"),
59
+ ("lifecycle_updated_at", "TIMESTAMP"),
60
+ ("lifecycle_history", "TEXT DEFAULT '[]'"),
61
+ ("access_level", "TEXT DEFAULT 'public'"),
62
+ ]
63
+ for col_name, col_type in v28_cols:
64
+ if col_name not in existing:
65
+ try:
66
+ cursor.execute(
67
+ f"ALTER TABLE memories ADD COLUMN {col_name} {col_type}"
68
+ )
69
+ except sqlite3.OperationalError:
70
+ pass
71
+ conn.commit()
72
+ finally:
73
+ conn.close()
74
+ except Exception:
75
+ pass # Graceful degradation — don't block engine init
76
+
48
77
  def is_valid_transition(self, from_state: str, to_state: str) -> bool:
49
78
  """Check if a state transition is valid per the state machine.
50
79
 
@@ -70,10 +99,22 @@ class LifecycleEngine:
70
99
  """
71
100
  conn = self._get_connection()
72
101
  try:
73
- row = conn.execute(
74
- "SELECT lifecycle_state FROM memories WHERE id = ?",
75
- (memory_id,),
76
- ).fetchone()
102
+ try:
103
+ row = conn.execute(
104
+ "SELECT lifecycle_state FROM memories WHERE id = ?",
105
+ (memory_id,),
106
+ ).fetchone()
107
+ except sqlite3.OperationalError as e:
108
+ if "no such column" in str(e):
109
+ conn.close()
110
+ self._ensure_columns()
111
+ conn = self._get_connection()
112
+ row = conn.execute(
113
+ "SELECT lifecycle_state FROM memories WHERE id = ?",
114
+ (memory_id,),
115
+ ).fetchone()
116
+ else:
117
+ raise
77
118
  if row is None:
78
119
  return None
79
120
  return row["lifecycle_state"] or "active"
@@ -278,10 +319,22 @@ class LifecycleEngine:
278
319
  conn = self._get_connection()
279
320
  try:
280
321
  dist = {state: 0 for state in self.STATES}
281
- rows = conn.execute(
282
- "SELECT lifecycle_state, COUNT(*) as cnt "
283
- "FROM memories GROUP BY lifecycle_state"
284
- ).fetchall()
322
+ try:
323
+ rows = conn.execute(
324
+ "SELECT lifecycle_state, COUNT(*) as cnt "
325
+ "FROM memories GROUP BY lifecycle_state"
326
+ ).fetchall()
327
+ except sqlite3.OperationalError as e:
328
+ if "no such column" in str(e):
329
+ conn.close()
330
+ self._ensure_columns()
331
+ conn = self._get_connection()
332
+ rows = conn.execute(
333
+ "SELECT lifecycle_state, COUNT(*) as cnt "
334
+ "FROM memories GROUP BY lifecycle_state"
335
+ ).fetchall()
336
+ else:
337
+ raise
285
338
  for row in rows:
286
339
  state = row["lifecycle_state"] if row["lifecycle_state"] else "active"
287
340
  if state in dist:
@@ -59,6 +59,15 @@ class LifecycleEvaluator:
59
59
  conn.row_factory = sqlite3.Row
60
60
  return conn
61
61
 
62
+ def _ensure_lifecycle_columns(self) -> None:
63
+ """Ensure v2.8 lifecycle columns exist via LifecycleEngine."""
64
+ try:
65
+ from lifecycle.lifecycle_engine import LifecycleEngine
66
+ engine = LifecycleEngine(db_path=self._db_path)
67
+ engine._ensure_columns()
68
+ except Exception:
69
+ pass # Best effort — don't block evaluation
70
+
62
71
  def evaluate_memories(
63
72
  self,
64
73
  profile: Optional[str] = None,
@@ -87,7 +96,17 @@ class LifecycleEvaluator:
87
96
  query += " AND profile = ?"
88
97
  params.append(profile)
89
98
 
90
- rows = conn.execute(query, params).fetchall()
99
+ try:
100
+ rows = conn.execute(query, params).fetchall()
101
+ except sqlite3.OperationalError as e:
102
+ if "no such column" in str(e):
103
+ conn.close()
104
+ self._ensure_lifecycle_columns()
105
+ conn = self._get_connection()
106
+ rows = conn.execute(query, params).fetchall()
107
+ else:
108
+ raise
109
+
91
110
  recommendations = []
92
111
  now = datetime.now()
93
112
 
@@ -123,11 +142,24 @@ class LifecycleEvaluator:
123
142
  config = self._load_config()
124
143
  conn = self._get_connection()
125
144
  try:
126
- row = conn.execute(
127
- "SELECT id, lifecycle_state, importance, last_accessed, created_at "
128
- "FROM memories WHERE id = ?",
129
- (memory_id,),
130
- ).fetchone()
145
+ try:
146
+ row = conn.execute(
147
+ "SELECT id, lifecycle_state, importance, last_accessed, created_at "
148
+ "FROM memories WHERE id = ?",
149
+ (memory_id,),
150
+ ).fetchone()
151
+ except sqlite3.OperationalError as e:
152
+ if "no such column" in str(e):
153
+ conn.close()
154
+ self._ensure_lifecycle_columns()
155
+ conn = self._get_connection()
156
+ row = conn.execute(
157
+ "SELECT id, lifecycle_state, importance, last_accessed, created_at "
158
+ "FROM memories WHERE id = ?",
159
+ (memory_id,),
160
+ ).fetchone()
161
+ else:
162
+ raise
131
163
  if row is None:
132
164
  return None
133
165
  return self._evaluate_row(row, config, datetime.now())
@@ -46,7 +46,7 @@ async def report_outcome(
46
46
  action_type: Category (code_written, decision_made, debug_resolved, etc.).
47
47
  context: Optional JSON string with additional context metadata.
48
48
  agent_id: Identifier for the reporting agent.
49
- project: Project name for scoping.
49
+ project: Project name for scoping outcomes.
50
50
 
51
51
  Returns:
52
52
  Dict with success status and outcome_id on success.
@@ -170,17 +170,18 @@ async def compact_memories(
170
170
 
171
171
  Args:
172
172
  dry_run: If True (default), show what would happen without changes.
173
- profile: Optional profile filter.
173
+ profile: Optional profile filter to scope compaction.
174
174
 
175
175
  Returns:
176
176
  Dict with recommendations (dry_run=True) or transition counts (dry_run=False).
177
177
  """
178
+ active_profile = profile
178
179
  try:
179
180
  from lifecycle.lifecycle_evaluator import LifecycleEvaluator
180
181
  from lifecycle.lifecycle_engine import LifecycleEngine
181
182
 
182
183
  evaluator = LifecycleEvaluator(DEFAULT_MEMORY_DB)
183
- recommendations = evaluator.evaluate_memories(profile=profile)
184
+ recommendations = evaluator.evaluate_memories(profile=active_profile)
184
185
 
185
186
  if dry_run:
186
187
  return {
@@ -496,6 +496,7 @@ class ProfileManager:
496
496
  return True
497
497
 
498
498
 
499
+
499
500
  def main():
500
501
  parser = argparse.ArgumentParser(
501
502
  description='SuperLocalMemory V2 - Profile Management (Column-Based)',
package/ui/index.html CHANGED
@@ -234,7 +234,7 @@
234
234
  vertical-align: middle;
235
235
  }
236
236
 
237
- /* Profile select in navbar */
237
+ /* Project select in navbar */
238
238
  .profile-select {
239
239
  background: rgba(255,255,255,0.15);
240
240
  border: 1px solid rgba(255,255,255,0.3);
@@ -316,7 +316,7 @@
316
316
  text-transform: uppercase;
317
317
  }
318
318
 
319
- /* Profile delete button */
319
+ /* Project delete button */
320
320
  .btn-delete-profile {
321
321
  padding: 2px 8px;
322
322
  font-size: 0.75rem;
@@ -546,7 +546,7 @@
546
546
  padding: 4px 8px;
547
547
  }
548
548
 
549
- /* Profile select - smaller */
549
+ /* Project select - smaller */
550
550
  .profile-select {
551
551
  min-width: 90px;
552
552
  font-size: 0.75rem;
@@ -641,7 +641,7 @@
641
641
  <div class="d-flex align-items-center gap-3">
642
642
  <div class="d-flex align-items-center gap-2">
643
643
  <i class="bi bi-person-circle text-white-50"></i>
644
- <select class="form-select form-select-sm profile-select" id="profile-select" title="Switch memory profile">
644
+ <select class="form-select form-select-sm profile-select" id="profile-select" title="Switch profile">
645
645
  <option value="default">default</option>
646
646
  </select>
647
647
  <button class="btn btn-sm theme-toggle" id="add-profile-btn" title="Create new profile" style="padding:2px 8px;font-size:1rem;line-height:1;">
@@ -738,6 +738,21 @@
738
738
  <i class="bi bi-robot"></i> Agents
739
739
  </button>
740
740
  </li>
741
+ <li class="nav-item" role="presentation">
742
+ <button class="nav-link" id="lifecycle-tab" data-bs-toggle="tab" data-bs-target="#lifecycle-pane" type="button" role="tab">
743
+ <i class="bi bi-hourglass-split"></i> Lifecycle <span class="badge bg-success ms-1">v2.8</span>
744
+ </button>
745
+ </li>
746
+ <li class="nav-item" role="presentation">
747
+ <button class="nav-link" id="behavioral-tab" data-bs-toggle="tab" data-bs-target="#behavioral-pane" type="button" role="tab">
748
+ <i class="bi bi-lightbulb"></i> Behavioral <span class="badge bg-success ms-1">v2.8</span>
749
+ </button>
750
+ </li>
751
+ <li class="nav-item" role="presentation">
752
+ <button class="nav-link" id="compliance-tab" data-bs-toggle="tab" data-bs-target="#compliance-pane" type="button" role="tab">
753
+ <i class="bi bi-shield-lock"></i> Compliance <span class="badge bg-success ms-1">v2.8</span>
754
+ </button>
755
+ </li>
741
756
  <li class="nav-item">
742
757
  <button class="nav-link" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-pane">
743
758
  <i class="bi bi-gear"></i> Settings
@@ -1193,6 +1208,136 @@
1193
1208
  </div>
1194
1209
  </div>
1195
1210
 
1211
+ <!-- Lifecycle (v2.8) -->
1212
+ <div class="tab-pane fade" id="lifecycle-pane" role="tabpanel">
1213
+ <div class="d-flex justify-content-between align-items-center mb-3">
1214
+ <h5 class="mb-0"><i class="bi bi-hourglass-split text-warning"></i> Memory Lifecycle</h5>
1215
+ <div>
1216
+ <span class="badge bg-secondary me-2" id="lifecycle-profile-badge">default</span>
1217
+ <button class="btn btn-sm btn-outline-info" onclick="compactDryRun()"><i class="bi bi-funnel"></i> Preview Compaction</button>
1218
+ <button class="btn btn-sm btn-outline-warning ms-1" onclick="compactExecute()"><i class="bi bi-lightning"></i> Compact Now</button>
1219
+ </div>
1220
+ </div>
1221
+ <!-- State Distribution -->
1222
+ <div class="row g-3 mb-4" id="lifecycle-states-row">
1223
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="text-success fw-bold fs-3" id="lc-active-count">-</div><small class="text-muted">Active</small></div></div>
1224
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="text-warning fw-bold fs-3" id="lc-warm-count">-</div><small class="text-muted">Warm</small></div></div>
1225
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="text-info fw-bold fs-3" id="lc-cold-count">-</div><small class="text-muted">Cold</small></div></div>
1226
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="text-secondary fw-bold fs-3" id="lc-archived-count">-</div><small class="text-muted">Archived</small></div></div>
1227
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="text-danger fw-bold fs-3" id="lc-tombstoned-count">-</div><small class="text-muted">Tombstoned</small></div></div>
1228
+ <div class="col-md-2"><div class="card p-3 text-center"><div class="fw-bold fs-3" id="lc-total-count">-</div><small class="text-muted">Total</small></div></div>
1229
+ </div>
1230
+ <!-- State Progress Bar -->
1231
+ <div class="card p-3 mb-4">
1232
+ <h6 class="mb-2">State Distribution</h6>
1233
+ <div class="progress" style="height: 24px;" id="lifecycle-progress-bar"></div>
1234
+ </div>
1235
+ <!-- Age Stats -->
1236
+ <div class="card p-3 mb-4" id="lifecycle-age-stats">
1237
+ <h6 class="mb-2">Average Memory Age by State</h6>
1238
+ <div id="lifecycle-age-content"><span class="text-muted">Loading...</span></div>
1239
+ </div>
1240
+ <!-- Recent Transitions -->
1241
+ <div class="card p-3">
1242
+ <h6 class="mb-2">Recent Transitions</h6>
1243
+ <div id="lifecycle-transitions-content"><span class="text-muted">Loading...</span></div>
1244
+ </div>
1245
+ <!-- Compaction Results Modal -->
1246
+ <div id="compaction-results" class="card p-3 mt-3 d-none">
1247
+ <h6 class="mb-2" id="compaction-results-title">Compaction Preview</h6>
1248
+ <div id="compaction-results-content"></div>
1249
+ </div>
1250
+ </div>
1251
+
1252
+ <!-- Behavioral Learning (v2.8) -->
1253
+ <div class="tab-pane fade" id="behavioral-pane" role="tabpanel">
1254
+ <div class="d-flex justify-content-between align-items-center mb-3">
1255
+ <h5 class="mb-0"><i class="bi bi-lightbulb text-info"></i> Behavioral Learning</h5>
1256
+ <span class="badge bg-secondary" id="behavioral-profile-badge">default</span>
1257
+ </div>
1258
+ <!-- Stats Row -->
1259
+ <div class="row g-3 mb-4">
1260
+ <div class="col-md-3"><div class="card p-3 text-center"><div class="fw-bold fs-3 text-success" id="bh-success-count">-</div><small class="text-muted">Successes</small></div></div>
1261
+ <div class="col-md-3"><div class="card p-3 text-center"><div class="fw-bold fs-3 text-danger" id="bh-failure-count">-</div><small class="text-muted">Failures</small></div></div>
1262
+ <div class="col-md-3"><div class="card p-3 text-center"><div class="fw-bold fs-3 text-warning" id="bh-partial-count">-</div><small class="text-muted">Partial</small></div></div>
1263
+ <div class="col-md-3"><div class="card p-3 text-center"><div class="fw-bold fs-3 text-info" id="bh-patterns-count">-</div><small class="text-muted">Patterns Learned</small></div></div>
1264
+ </div>
1265
+ <!-- Report Outcome Form -->
1266
+ <div class="card p-3 mb-4">
1267
+ <h6 class="mb-2"><i class="bi bi-plus-circle"></i> Report Outcome</h6>
1268
+ <div class="row g-2">
1269
+ <div class="col-md-3"><input type="text" class="form-control form-control-sm" id="bh-memory-ids" placeholder="Memory IDs (comma-separated)"></div>
1270
+ <div class="col-md-2"><select class="form-select form-select-sm" id="bh-outcome"><option value="success">Success</option><option value="failure">Failure</option><option value="partial">Partial</option></select></div>
1271
+ <div class="col-md-3"><select class="form-select form-select-sm" id="bh-action-type"><option value="code_written">Code Written</option><option value="decision_made">Decision Made</option><option value="debug_resolved">Debug Resolved</option><option value="architecture_chosen">Architecture Chosen</option><option value="other">Other</option></select></div>
1272
+ <div class="col-md-3"><input type="text" class="form-control form-control-sm" id="bh-context" placeholder="Context (optional)"></div>
1273
+ <div class="col-md-1"><button class="btn btn-sm btn-info w-100" onclick="reportOutcome()"><i class="bi bi-send"></i></button></div>
1274
+ </div>
1275
+ </div>
1276
+ <!-- Learned Patterns -->
1277
+ <div class="card p-3 mb-4">
1278
+ <h6 class="mb-2">Learned Patterns</h6>
1279
+ <div id="behavioral-patterns-content"><span class="text-muted">Loading...</span></div>
1280
+ </div>
1281
+ <!-- Cross-Project Transfers -->
1282
+ <div class="card p-3 mb-4">
1283
+ <h6 class="mb-2">Cross-Project Transfers</h6>
1284
+ <div id="behavioral-transfers-content"><span class="text-muted">Loading...</span></div>
1285
+ </div>
1286
+ <!-- Recent Outcomes -->
1287
+ <div class="card p-3">
1288
+ <h6 class="mb-2">Recent Outcomes</h6>
1289
+ <div id="behavioral-outcomes-content"><span class="text-muted">Loading...</span></div>
1290
+ </div>
1291
+ </div>
1292
+
1293
+ <!-- Compliance & Audit (v2.8) -->
1294
+ <div class="tab-pane fade" id="compliance-pane" role="tabpanel">
1295
+ <div class="d-flex justify-content-between align-items-center mb-3">
1296
+ <h5 class="mb-0"><i class="bi bi-shield-lock text-success"></i> Compliance &amp; Audit</h5>
1297
+ <span class="badge bg-secondary" id="compliance-profile-badge">default</span>
1298
+ </div>
1299
+ <!-- Stats Row -->
1300
+ <div class="row g-3 mb-4">
1301
+ <div class="col-md-4"><div class="card p-3 text-center"><div class="fw-bold fs-3" id="cp-audit-count">-</div><small class="text-muted">Audit Events</small></div></div>
1302
+ <div class="col-md-4"><div class="card p-3 text-center"><div class="fw-bold fs-3" id="cp-retention-count">-</div><small class="text-muted">Retention Policies</small></div></div>
1303
+ <div class="col-md-4"><div class="card p-3 text-center"><div class="fw-bold fs-3" id="cp-abac-count">-</div><small class="text-muted">Access Policies</small></div></div>
1304
+ </div>
1305
+ <!-- Set Retention Policy Form -->
1306
+ <div class="card p-3 mb-4">
1307
+ <h6 class="mb-2"><i class="bi bi-plus-circle"></i> Create Retention Policy</h6>
1308
+ <div class="row g-2">
1309
+ <div class="col-md-3"><input type="text" class="form-control form-control-sm" id="cp-policy-name" placeholder="Policy name"></div>
1310
+ <div class="col-md-2"><input type="number" class="form-control form-control-sm" id="cp-retention-days" placeholder="Days" value="90"></div>
1311
+ <div class="col-md-2"><input type="text" class="form-control form-control-sm" id="cp-category" placeholder="Category (optional)"></div>
1312
+ <div class="col-md-2"><select class="form-select form-select-sm" id="cp-action"><option value="archive">Archive</option><option value="tombstone">Tombstone</option><option value="notify">Notify Only</option></select></div>
1313
+ <div class="col-md-2"><button class="btn btn-sm btn-success w-100" onclick="createRetentionPolicy()"><i class="bi bi-shield-plus"></i> Create</button></div>
1314
+ </div>
1315
+ </div>
1316
+ <!-- Retention Policies List -->
1317
+ <div class="card p-3 mb-4">
1318
+ <h6 class="mb-2">Active Retention Policies</h6>
1319
+ <div id="compliance-policies-content"><span class="text-muted">Loading...</span></div>
1320
+ </div>
1321
+ <!-- Audit Trail -->
1322
+ <div class="card p-3">
1323
+ <div class="d-flex justify-content-between align-items-center mb-2">
1324
+ <h6 class="mb-0">Audit Trail</h6>
1325
+ <div class="d-flex gap-2">
1326
+ <select class="form-select form-select-sm" id="cp-audit-filter" style="width: auto;" onchange="loadCompliance()">
1327
+ <option value="">All Events</option>
1328
+ <option value="recall">Recall</option>
1329
+ <option value="remember">Remember</option>
1330
+ <option value="delete">Delete</option>
1331
+ <option value="lifecycle_transition">Lifecycle</option>
1332
+ <option value="access_denied">Access Denied</option>
1333
+ <option value="retention_enforced">Retention</option>
1334
+ </select>
1335
+ </div>
1336
+ </div>
1337
+ <div id="compliance-audit-content"><span class="text-muted">Loading...</span></div>
1338
+ </div>
1339
+ </div>
1340
+
1196
1341
  <!-- Settings & Backup -->
1197
1342
  <div class="tab-pane fade" id="settings-pane">
1198
1343
  <!-- Profile Management -->
@@ -1360,6 +1505,9 @@
1360
1505
  <script src="static/js/agents.js"></script>
1361
1506
  <script src="static/js/learning.js"></script>
1362
1507
  <script src="static/js/feedback.js"></script>
1508
+ <script src="static/js/lifecycle.js"></script>
1509
+ <script src="static/js/behavioral.js"></script>
1510
+ <script src="static/js/compliance.js"></script>
1363
1511
  <script src="static/js/init.js"></script>
1364
1512
 
1365
1513
  <footer>