superlocalmemory 2.8.5 → 3.0.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 (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -0,0 +1,399 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+ """SuperLocalMemory V3 - Memory Routes (MIT License).
5
+ Routes: /api/memories, /api/graph, /api/search, /api/clusters, /api/clusters/{id}
6
+ Uses V3 MemoryEngine for store/recall. Falls back to direct DB for list/graph.
7
+ """
8
+ import json
9
+ import logging
10
+ from typing import Optional
11
+
12
+ from fastapi import APIRouter, HTTPException, Query, Request
13
+
14
+ from .helpers import (
15
+ get_db_connection, dict_factory, get_active_profile,
16
+ SearchRequest, DB_PATH, MEMORY_DIR,
17
+ )
18
+
19
+ logger = logging.getLogger("superlocalmemory.routes.memories")
20
+ router = APIRouter()
21
+
22
+
23
+ def _get_engine(request: Request):
24
+ """Get V3 engine from app state, or None."""
25
+ return getattr(request.app.state, "engine", None)
26
+
27
+
28
+ def _preview(content: str | None) -> str:
29
+ """Truncate content for preview display."""
30
+ if not content:
31
+ return ""
32
+ return content[:100] + "..." if len(content) > 100 else content
33
+
34
+
35
+ def _has_table(cursor, name: str) -> bool:
36
+ """Check if a table exists in the database."""
37
+ try:
38
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (name,))
39
+ return cursor.fetchone() is not None
40
+ except Exception:
41
+ return False
42
+
43
+
44
+ def _fetch_graph_data(
45
+ cursor, profile: str, use_v3: bool, min_importance: int, max_nodes: int,
46
+ ) -> tuple[list, list, list]:
47
+ """Fetch graph nodes, links, clusters from V3 or V2 schema."""
48
+ if use_v3:
49
+ cursor.execute("""
50
+ SELECT f.fact_id as id, f.content, f.fact_type as category,
51
+ f.confidence as importance, f.session_id as project_name,
52
+ f.created_at
53
+ FROM atomic_facts f WHERE f.profile_id = ? AND f.confidence >= ?
54
+ ORDER BY f.confidence DESC, f.created_at DESC LIMIT ?
55
+ """, (profile, min_importance / 10.0, max_nodes))
56
+ nodes = cursor.fetchall()
57
+ for n in nodes:
58
+ n['entities'] = []
59
+ n['content_preview'] = _preview(n.get('content'))
60
+ ids = [n['id'] for n in nodes]
61
+ links = _fetch_edges_v3(cursor, profile, ids)
62
+ return nodes, links, []
63
+
64
+ # V2 fallback
65
+ try:
66
+ cursor.execute("""
67
+ SELECT m.id, m.content, m.summary, m.category, m.cluster_id,
68
+ m.importance, m.project_name, m.created_at, m.tags, gn.entities
69
+ FROM memories m LEFT JOIN graph_nodes gn ON m.id = gn.memory_id
70
+ WHERE m.importance >= ? AND m.profile = ?
71
+ ORDER BY m.importance DESC, m.updated_at DESC LIMIT ?
72
+ """, (min_importance, profile, max_nodes))
73
+ except Exception:
74
+ cursor.execute("""
75
+ SELECT id, content, summary, category, cluster_id, importance,
76
+ project_name, created_at, tags, NULL as entities
77
+ FROM memories WHERE importance >= ? AND profile = ?
78
+ ORDER BY importance DESC, updated_at DESC LIMIT ?
79
+ """, (min_importance, profile, max_nodes))
80
+ nodes = cursor.fetchall()
81
+ for n in nodes:
82
+ ent = n.get('entities')
83
+ n['entities'] = json.loads(ent) if ent else []
84
+ n['content_preview'] = _preview(n.get('content'))
85
+ ids = [n['id'] for n in nodes]
86
+ links = _fetch_edges_v2(cursor, ids)
87
+ try:
88
+ cursor.execute("""
89
+ SELECT cluster_id, COUNT(*) as size, AVG(importance) as avg_importance
90
+ FROM memories WHERE cluster_id IS NOT NULL AND profile = ?
91
+ GROUP BY cluster_id
92
+ """, (profile,))
93
+ clusters = cursor.fetchall()
94
+ except Exception:
95
+ clusters = []
96
+ return nodes, links, clusters
97
+
98
+
99
+ def _fetch_edges_v3(cursor, profile: str, fact_ids: list) -> list:
100
+ if not fact_ids:
101
+ return []
102
+ ph = ','.join('?' * len(fact_ids))
103
+ try:
104
+ cursor.execute(f"""
105
+ SELECT source_id as source, target_id as target,
106
+ weight, edge_type as relationship_type
107
+ FROM kg_edges WHERE profile_id = ?
108
+ AND source_id IN ({ph}) AND target_id IN ({ph})
109
+ ORDER BY weight DESC
110
+ """, [profile] + fact_ids + fact_ids)
111
+ return cursor.fetchall()
112
+ except Exception:
113
+ return []
114
+
115
+
116
+ def _fetch_edges_v2(cursor, memory_ids: list) -> list:
117
+ if not memory_ids:
118
+ return []
119
+ ph = ','.join('?' * len(memory_ids))
120
+ try:
121
+ cursor.execute(f"""
122
+ SELECT source_memory_id as source, target_memory_id as target,
123
+ weight, relationship_type, shared_entities
124
+ FROM graph_edges
125
+ WHERE source_memory_id IN ({ph}) AND target_memory_id IN ({ph})
126
+ ORDER BY weight DESC
127
+ """, memory_ids + memory_ids)
128
+ links = cursor.fetchall()
129
+ for lk in links:
130
+ se = lk.get('shared_entities')
131
+ if se:
132
+ try:
133
+ lk['shared_entities'] = json.loads(se)
134
+ except Exception:
135
+ lk['shared_entities'] = []
136
+ return links
137
+ except Exception:
138
+ return []
139
+
140
+
141
+ @router.get("/api/memories")
142
+ async def get_memories(
143
+ request: Request,
144
+ category: Optional[str] = None,
145
+ project_name: Optional[str] = None,
146
+ cluster_id: Optional[int] = None,
147
+ min_importance: Optional[int] = None,
148
+ tags: Optional[str] = None,
149
+ limit: int = Query(50, ge=1, le=200),
150
+ offset: int = Query(0, ge=0),
151
+ ):
152
+ """List memories with optional filtering and pagination."""
153
+ try:
154
+ conn = get_db_connection()
155
+ conn.row_factory = dict_factory
156
+ cursor = conn.cursor()
157
+ active_profile = get_active_profile()
158
+
159
+ use_v3 = _has_table(cursor, 'atomic_facts')
160
+
161
+ if use_v3:
162
+ query = """
163
+ SELECT fact_id as id, content, fact_type as category,
164
+ confidence as importance, access_count,
165
+ created_at, created_at as updated_at,
166
+ session_id as project_name
167
+ FROM atomic_facts WHERE profile_id = ?
168
+ """
169
+ params = [active_profile]
170
+ count_base = "SELECT COUNT(*) as total FROM atomic_facts WHERE profile_id = ?"
171
+ else:
172
+ query = """
173
+ SELECT id, content, summary, category, project_name, project_path,
174
+ importance, cluster_id, depth, access_count, parent_id,
175
+ created_at, updated_at, last_accessed, tags, memory_type
176
+ FROM memories WHERE profile = ?
177
+ """
178
+ params = [active_profile]
179
+ count_base = "SELECT COUNT(*) as total FROM memories WHERE profile = ?"
180
+
181
+ count_params = [active_profile]
182
+
183
+ if category:
184
+ if use_v3:
185
+ query += " AND fact_type = ?"
186
+ else:
187
+ query += " AND category = ?"
188
+ params.append(category)
189
+ count_base += " AND category = ?" if not use_v3 else " AND fact_type = ?"
190
+ count_params.append(category)
191
+ if project_name:
192
+ if use_v3:
193
+ query += " AND session_id = ?"
194
+ else:
195
+ query += " AND project_name = ?"
196
+ params.append(project_name)
197
+ count_base += " AND project_name = ?" if not use_v3 else " AND session_id = ?"
198
+ count_params.append(project_name)
199
+ if cluster_id is not None and not use_v3:
200
+ query += " AND cluster_id = ?"
201
+ params.append(cluster_id)
202
+ count_base += " AND cluster_id = ?"
203
+ count_params.append(cluster_id)
204
+ if min_importance:
205
+ if use_v3:
206
+ query += " AND confidence >= ?"
207
+ params.append(min_importance / 10.0)
208
+ else:
209
+ query += " AND importance >= ?"
210
+ params.append(min_importance)
211
+ if tags and not use_v3:
212
+ tag_list = [t.strip() for t in tags.split(',')]
213
+ for tag in tag_list:
214
+ query += " AND tags LIKE ?"
215
+ params.append(f'%{tag}%')
216
+
217
+ query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
218
+ params.extend([limit, offset])
219
+
220
+ cursor.execute(query, params)
221
+ memories = cursor.fetchall()
222
+
223
+ cursor.execute(count_base, count_params)
224
+ total = cursor.fetchone()['total']
225
+
226
+ conn.close()
227
+
228
+ return {
229
+ "memories": memories, "total": total,
230
+ "limit": limit, "offset": offset,
231
+ "has_more": (offset + limit) < total,
232
+ }
233
+
234
+ except Exception as e:
235
+ raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
236
+
237
+
238
+ @router.get("/api/graph")
239
+ async def get_graph(
240
+ request: Request,
241
+ max_nodes: int = Query(100, ge=10, le=500),
242
+ min_importance: int = Query(1, ge=1, le=10),
243
+ ):
244
+ """Get knowledge graph data for D3.js force-directed visualization."""
245
+ try:
246
+ conn = get_db_connection()
247
+ conn.row_factory = dict_factory
248
+ cursor = conn.cursor()
249
+ active_profile = get_active_profile()
250
+
251
+ use_v3 = _has_table(cursor, 'kg_edges')
252
+
253
+ nodes, links, clusters = _fetch_graph_data(
254
+ cursor, active_profile, use_v3, min_importance, max_nodes,
255
+ )
256
+
257
+ conn.close()
258
+
259
+ return {
260
+ "nodes": nodes, "links": links, "clusters": clusters,
261
+ "metadata": {
262
+ "node_count": len(nodes), "edge_count": len(links),
263
+ "cluster_count": len(clusters) if clusters else 0,
264
+ "filters_applied": {"max_nodes": max_nodes, "min_importance": min_importance},
265
+ },
266
+ }
267
+
268
+ except Exception as e:
269
+ raise HTTPException(status_code=500, detail=f"Graph error: {str(e)}")
270
+
271
+
272
+ @router.post("/api/search")
273
+ async def search_memories(request: Request, body: SearchRequest):
274
+ """Semantic search using V3 engine recall or fallback."""
275
+ try:
276
+ engine = _get_engine(request)
277
+
278
+ if engine:
279
+ response = engine.recall(body.query, limit=body.limit)
280
+ results = []
281
+ for r in response.results:
282
+ score = r.score
283
+ if score < body.min_score:
284
+ continue
285
+ if body.category and getattr(r.fact, 'fact_type', None) != body.category:
286
+ continue
287
+ results.append({
288
+ "id": r.fact.fact_id,
289
+ "content": r.fact.content,
290
+ "score": round(score, 4),
291
+ "confidence": round(r.confidence, 4),
292
+ "trust_score": round(r.trust_score, 4) if r.trust_score else None,
293
+ "channel_scores": r.channel_scores,
294
+ "fact_type": getattr(r.fact, 'fact_type', None),
295
+ "created_at": getattr(r.fact, 'created_at', None),
296
+ })
297
+ if len(results) >= body.limit:
298
+ break
299
+
300
+ return {
301
+ "query": body.query, "results": results, "total": len(results),
302
+ "query_type": response.query_type,
303
+ "retrieval_time_ms": response.retrieval_time_ms,
304
+ }
305
+
306
+ # Fallback: direct DB search (no V3 engine)
307
+ conn = get_db_connection()
308
+ conn.row_factory = dict_factory
309
+ cursor = conn.cursor()
310
+ active_profile = get_active_profile()
311
+ cursor.execute("""
312
+ SELECT fact_id as id, content, confidence as score,
313
+ fact_type as category, created_at
314
+ FROM atomic_facts
315
+ WHERE profile_id = ? AND content LIKE ?
316
+ ORDER BY confidence DESC LIMIT ?
317
+ """, (active_profile, f'%{body.query}%', body.limit))
318
+ results = cursor.fetchall()
319
+ conn.close()
320
+
321
+ return {
322
+ "query": body.query, "results": results, "total": len(results),
323
+ "query_type": "text_search", "retrieval_time_ms": 0,
324
+ }
325
+
326
+ except Exception as e:
327
+ raise HTTPException(status_code=500, detail=f"Search error: {str(e)}")
328
+
329
+
330
+ @router.get("/api/clusters")
331
+ async def get_clusters(request: Request):
332
+ """Get cluster information with member counts and statistics."""
333
+ try:
334
+ conn = get_db_connection()
335
+ conn.row_factory = dict_factory
336
+ cursor = conn.cursor()
337
+ profile = get_active_profile()
338
+ unclustered = 0
339
+
340
+ if _has_table(cursor, 'scene_facts'):
341
+ cursor.execute("""
342
+ SELECT s.scene_id as cluster_id, COUNT(sf.fact_id) as member_count,
343
+ s.summary, s.created_at as first_memory
344
+ FROM scenes s JOIN scene_facts sf ON s.scene_id = sf.scene_id
345
+ WHERE s.profile_id = ? GROUP BY s.scene_id ORDER BY member_count DESC
346
+ """, (profile,))
347
+ clusters = [dict(r, top_entities=[]) for r in cursor.fetchall()]
348
+ else:
349
+ try:
350
+ cursor.execute("""
351
+ SELECT cluster_id, COUNT(*) as member_count,
352
+ AVG(importance) as avg_importance,
353
+ GROUP_CONCAT(DISTINCT category) as categories
354
+ FROM memories WHERE cluster_id IS NOT NULL AND profile = ?
355
+ GROUP BY cluster_id ORDER BY member_count DESC
356
+ """, (profile,))
357
+ clusters = [dict(r, top_entities=[]) for r in cursor.fetchall()]
358
+ except Exception:
359
+ clusters = []
360
+ cursor.execute("SELECT COUNT(*) as c FROM memories WHERE cluster_id IS NULL AND profile = ?", (profile,))
361
+ unclustered = cursor.fetchone()['c']
362
+
363
+ conn.close()
364
+ return {"clusters": clusters, "total_clusters": len(clusters), "unclustered_count": unclustered}
365
+ except Exception as e:
366
+ raise HTTPException(status_code=500, detail=f"Cluster error: {str(e)}")
367
+
368
+
369
+ @router.get("/api/clusters/{cluster_id}")
370
+ async def get_cluster_detail(request: Request, cluster_id: int, limit: int = Query(50, ge=1, le=200)):
371
+ """Get detailed view of a specific cluster."""
372
+ try:
373
+ conn = get_db_connection()
374
+ conn.row_factory = dict_factory
375
+ cursor = conn.cursor()
376
+ profile = get_active_profile()
377
+
378
+ if _has_table(cursor, 'scene_facts'):
379
+ cursor.execute("""
380
+ SELECT f.fact_id as id, f.content, f.fact_type as category,
381
+ f.confidence as importance, f.created_at
382
+ FROM atomic_facts f JOIN scene_facts sf ON f.fact_id = sf.fact_id
383
+ WHERE sf.scene_id = ? AND f.profile_id = ? ORDER BY f.confidence DESC LIMIT ?
384
+ """, (str(cluster_id), profile, limit))
385
+ else:
386
+ cursor.execute("""
387
+ SELECT id, content, summary, category, project_name, importance, created_at, tags
388
+ FROM memories WHERE cluster_id = ? AND profile = ?
389
+ ORDER BY importance DESC, created_at DESC LIMIT ?
390
+ """, (cluster_id, profile, limit))
391
+ members = cursor.fetchall()
392
+ conn.close()
393
+ if not members:
394
+ raise HTTPException(status_code=404, detail="Cluster not found")
395
+ return {"cluster_info": {"cluster_id": cluster_id, "total_members": len(members)}, "members": members, "connections": []}
396
+ except HTTPException:
397
+ raise
398
+ except Exception as e:
399
+ raise HTTPException(status_code=500, detail=f"Cluster detail error: {str(e)}")
@@ -0,0 +1,219 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+ """SuperLocalMemory V3 - Profile Routes
5
+ - MIT License
6
+
7
+ Routes: /api/profiles, /api/profiles/{name}/switch,
8
+ /api/profiles/create, DELETE /api/profiles/{name}
9
+ """
10
+ import json
11
+ import logging
12
+ from datetime import datetime
13
+
14
+ from fastapi import APIRouter, HTTPException
15
+
16
+ from .helpers import (
17
+ get_db_connection, get_active_profile, validate_profile_name,
18
+ ProfileSwitch, MEMORY_DIR, DB_PATH,
19
+ )
20
+
21
+ logger = logging.getLogger("superlocalmemory.routes.profiles")
22
+ router = APIRouter()
23
+
24
+ # WebSocket manager reference (set by ui_server.py at startup)
25
+ ws_manager = None
26
+
27
+
28
+ def _load_profiles_config() -> dict:
29
+ """Load profiles.json config."""
30
+ config_file = MEMORY_DIR / "profiles.json"
31
+ if config_file.exists():
32
+ try:
33
+ with open(config_file, 'r') as f:
34
+ return json.load(f)
35
+ except (json.JSONDecodeError, IOError):
36
+ pass
37
+ return {
38
+ 'profiles': {'default': {'name': 'default', 'description': 'Default memory profile'}},
39
+ 'active_profile': 'default',
40
+ }
41
+
42
+
43
+ def _save_profiles_config(config: dict) -> None:
44
+ """Save profiles.json config."""
45
+ MEMORY_DIR.mkdir(parents=True, exist_ok=True)
46
+ config_file = MEMORY_DIR / "profiles.json"
47
+ with open(config_file, 'w') as f:
48
+ json.dump(config, f, indent=2)
49
+
50
+
51
+ def _get_memory_count(profile: str) -> int:
52
+ """Get memory count for a profile (V3 atomic_facts or V2 memories)."""
53
+ try:
54
+ conn = get_db_connection()
55
+ cursor = conn.cursor()
56
+ # Try V3 table first
57
+ try:
58
+ cursor.execute(
59
+ "SELECT COUNT(*) FROM atomic_facts WHERE profile_id = ?", (profile,),
60
+ )
61
+ count = cursor.fetchone()[0]
62
+ except Exception:
63
+ cursor.execute(
64
+ "SELECT COUNT(*) FROM memories WHERE profile = ?", (profile,),
65
+ )
66
+ count = cursor.fetchone()[0]
67
+ conn.close()
68
+ return count
69
+ except Exception:
70
+ return 0
71
+
72
+
73
+ @router.get("/api/profiles")
74
+ async def list_profiles():
75
+ """List available memory profiles."""
76
+ try:
77
+ config = _load_profiles_config()
78
+ active = config.get('active_profile', 'default')
79
+ profiles = []
80
+
81
+ for name, info in config.get('profiles', {}).items():
82
+ count = _get_memory_count(name)
83
+ profiles.append({
84
+ "name": name,
85
+ "description": info.get('description', ''),
86
+ "memory_count": count,
87
+ "created_at": info.get('created_at', ''),
88
+ "last_used": info.get('last_used', ''),
89
+ "is_active": name == active,
90
+ })
91
+
92
+ return {
93
+ "profiles": profiles,
94
+ "active_profile": active,
95
+ "total_profiles": len(profiles),
96
+ }
97
+
98
+ except Exception as e:
99
+ raise HTTPException(status_code=500, detail=f"Profile list error: {str(e)}")
100
+
101
+
102
+ @router.post("/api/profiles/{name}/switch")
103
+ async def switch_profile(name: str):
104
+ """Switch active memory profile."""
105
+ try:
106
+ if not validate_profile_name(name):
107
+ raise HTTPException(status_code=400, detail="Invalid profile name.")
108
+
109
+ config = _load_profiles_config()
110
+
111
+ if name not in config.get('profiles', {}):
112
+ available = ', '.join(config.get('profiles', {}).keys())
113
+ raise HTTPException(
114
+ status_code=404,
115
+ detail=f"Profile '{name}' not found. Available: {available}",
116
+ )
117
+
118
+ previous = config.get('active_profile', 'default')
119
+ config['active_profile'] = name
120
+ config['profiles'][name]['last_used'] = datetime.now().isoformat()
121
+ _save_profiles_config(config)
122
+
123
+ count = _get_memory_count(name)
124
+
125
+ if ws_manager:
126
+ await ws_manager.broadcast({
127
+ "type": "profile_switched", "profile": name,
128
+ "previous": previous, "memory_count": count,
129
+ "timestamp": datetime.now().isoformat(),
130
+ })
131
+
132
+ return {
133
+ "success": True, "active_profile": name,
134
+ "previous_profile": previous, "memory_count": count,
135
+ "message": f"Switched to profile '{name}' ({count} memories).",
136
+ }
137
+
138
+ except HTTPException:
139
+ raise
140
+ except Exception as e:
141
+ raise HTTPException(status_code=500, detail=f"Profile switch error: {str(e)}")
142
+
143
+
144
+ @router.post("/api/profiles/create")
145
+ async def create_profile(body: ProfileSwitch):
146
+ """Create a new memory profile."""
147
+ try:
148
+ name = body.profile_name
149
+ if not validate_profile_name(name):
150
+ raise HTTPException(status_code=400, detail="Invalid profile name")
151
+
152
+ config = _load_profiles_config()
153
+
154
+ if name in config.get('profiles', {}):
155
+ raise HTTPException(status_code=409, detail=f"Profile '{name}' already exists")
156
+
157
+ config['profiles'][name] = {
158
+ 'name': name, 'description': f'Memory profile: {name}',
159
+ 'created_at': datetime.now().isoformat(), 'last_used': None,
160
+ }
161
+ _save_profiles_config(config)
162
+
163
+ return {"success": True, "profile": name, "message": f"Profile '{name}' created"}
164
+
165
+ except HTTPException:
166
+ raise
167
+ except Exception as e:
168
+ raise HTTPException(status_code=500, detail=f"Profile create error: {str(e)}")
169
+
170
+
171
+ @router.delete("/api/profiles/{name}")
172
+ async def delete_profile(name: str):
173
+ """Delete a profile. Moves its memories to 'default'."""
174
+ try:
175
+ if name == 'default':
176
+ raise HTTPException(status_code=400, detail="Cannot delete 'default' profile")
177
+
178
+ config = _load_profiles_config()
179
+
180
+ if name not in config.get('profiles', {}):
181
+ raise HTTPException(status_code=404, detail=f"Profile '{name}' not found")
182
+ if config.get('active_profile') == name:
183
+ raise HTTPException(status_code=400, detail="Cannot delete active profile.")
184
+
185
+ conn = get_db_connection()
186
+ cursor = conn.cursor()
187
+ # Move memories to default (try V3 first, then V2)
188
+ moved = 0
189
+ try:
190
+ cursor.execute(
191
+ "UPDATE atomic_facts SET profile_id = 'default' WHERE profile_id = ?",
192
+ (name,),
193
+ )
194
+ moved = cursor.rowcount
195
+ except Exception:
196
+ pass
197
+ try:
198
+ cursor.execute(
199
+ "UPDATE memories SET profile = 'default' WHERE profile = ?",
200
+ (name,),
201
+ )
202
+ moved += cursor.rowcount
203
+ except Exception:
204
+ pass
205
+ conn.commit()
206
+ conn.close()
207
+
208
+ del config['profiles'][name]
209
+ _save_profiles_config(config)
210
+
211
+ return {
212
+ "success": True,
213
+ "message": f"Profile '{name}' deleted. {moved} memories moved to 'default'.",
214
+ }
215
+
216
+ except HTTPException:
217
+ raise
218
+ except Exception as e:
219
+ raise HTTPException(status_code=500, detail=f"Profile delete error: {str(e)}")