superlocalmemory 2.8.6 → 3.0.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.
Files changed (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +62 -48
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.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)}")